>實務上的業務情境,經常會需要在不同平台之間反覆切換;像是在 Jira 查找專案進度,去資料庫查找訂單狀態、去 Azure 查看線上系統的錯誤報告、專案的緊急變動可能又要隨時留意 Slack…… > >這些手動的查詢和資訊同步過程,雖然每個環節看似不難,卻不斷地打斷你的專注力,降低了團隊的整體效率。尤其是當資訊分散在不同地方,要即時掌握全貌,更是難上加難。 > >透過 Slack 機器人,我們可以將來自不同系統的數據與通知,無縫整合到團隊的日常溝通中。不再需要手動查詢,也無需離開 Slack 視窗,就能即時獲取所有關鍵資訊。 > >本文將示範如何從零開始,在 Slack 的工作空間建立一個機器人應用程式,並根據公司需求設計業務邏輯,整合公司的服務與資訊,提升團隊工作效率。 # 一、前置作業 Slack Bot 的核心是透過 Slack API 接收事件(如訊息、指令)並回應。你會需要: - Django 作為 Web Server(處理 Slack 的 webhook) - Slack App(在 Slack Developer Portal 註冊) - Slack API Token(用於呼叫 Slack API) - ngrok 或雲端部署(讓 Slack 能夠存取你的 webhook) 1. 在 [Slack API](https://api.slack.com/apps) 建立一個新的 APP ![image](https://hackmd.io/_uploads/B1zMOvltgx.png) 這邊有兩個建立方式可以選擇: - `manifest`:YMAL 或 JSON 的設定檔,明確定義權限與功能,且如果需要建立新環境,在重復使用上很方便。 ```ymal display_information: name: My Slack Bot features: bot_user: display_name: MyBot always_online: true oauth_config: scopes: bot: - chat:write - app_mentions:read settings: event_subscriptions: request_url: https://yourdomain.com/slack/events bot_events: - app_mention ``` 熟悉 YAML/JSON 的開發者,或是有想要自動化部署或 CI/CD,以及有多個 App 或 workspace 要管理的需求,推薦使用 manifest。 - `scratch`:Slack 提供的 UI 步驟,適合初學者,可以邊設定邊測試 webhook、OAuth 等功能。 適合第一次開發 Slack App,想快速測試功能,或是還不熟悉 YAML/JSON 或沒有版本化設定需求的用戶。 2. 這邊我們選擇用 scratch,輸入 App 名稱和要建置的工作環境後,就會看到 App 的設定頁面 ![8x3nyobr](https://hackmd.io/_uploads/r1VtyvF5xg.png) 在 ==Event Subscription== 的選項,完成以下動作: - 打開 "Enable Events",並輸入你的 Webhook 路徑 ( Slack 會立刻發送請求去驗證這個 URL 是否有效 )。 - 在 "Subscribe to bot events" 加入三個事件: - `app_mention`(被提及) - `message.channels`(公開頻道訊息) - `message.im`(私訊) ![mnygykzn](https://hackmd.io/_uploads/HJks1DK5le.png) 3. 在 ==OAuth & Permission== 選項,點選 "Install to <工作空間>" 的按鈕取得 OAuth Token。 ![mslgp84e](https://hackmd.io/_uploads/SJkegDY5gl.png) 然後在 "Bot Token Scopes" 加入一個 Token: * `chat:write`:允許你的 bot 發送訊息到頻道、私訊、thread 等 ![image](https://hackmd.io/_uploads/ryTxuueKeg.png) 4. Slack 會自動幫你在「應用程式 (Apps)」建立一個私訊對話 (Direct Message),通稱 App DM。 ![ccx3po3f](https://hackmd.io/_uploads/BkzoxPY5xx.png) 傳送訊息被關閉是正常的。這個頁面不是頻道,也不是真正的 DM 對話,除非你有在 Slack Api 設定 Event subscriptions(例如 message.im),以及 Interactivity 或 slash commands,否則你無法在這裡輸入訊息給 bot。 :::info Slack 的 DM 有幾種形式: - **人與人之間的私訊:** 你可以直接點選某個人的名字,開啟一對一對話 - **多人私訊:** 你可以跟兩個以上的人開啟群組私訊(但不是頻道) - **App DM(Bot DM):** 這是 Slack 自動幫你的 bot 建立的私訊頁面,通常在左側 sidebar 的「Apps」區塊裡 ::: # 二、Django 開發 首先加上環境變數,將機器人的 OAuth Token 加進系統: ```python SLACK_BOT_TOKEN = "xoxb-...TXd20f" ``` 然後新增一個 app,這裡我們將它命名為 'slack_bot' ```python # bot_view.py import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt import requests import os SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN") @csrf_exempt def slack_webhook(request): payload = json.loads(request.body) # Slack URL verification if payload.get("type") == "url_verification": return JsonResponse({"challenge": payload.get("challenge")}) # 處理訊息事件 if payload.get("type") == "event_callback": event = payload.get("event") if event.get("type") == "app_mention": user = event.get("user") channel = event.get("channel") text = event.get("text") reply = f"Hi <@{user}>! 你說的是:{text}" requests.post("https://slack.com/api/chat.postMessage", headers={ "Authorization": f"Bearer {SLACK_BOT_TOKEN}", "Content-Type": "application/json" }, json={ "channel": channel, "text": reply }) return JsonResponse({"status": "ok"}) # urls.py from django.urls import path from app.slack_bot.views.bot_view import slack_webhook urlpatterns = [ path("webhook/", slack_webhook), ] # <django-project> urls.py urlpatterns = [ ... path("api/slack/", include("app.slack_bot.urls")), ... ] ``` ## 測試連線:在頻道中提及 Bot 在 Slack 我們可以先試著透過觸發標註事件 (app_mention) 去檢查 Webhook 是否有順利連線且順利運作我們設計的邏輯。 打開想讓 Bot 回應的頻道 (這邊我們新建一個頻道,叫 "bot_test"),在訊息框輸入 `/invite @<機器人名稱>` ![4ziql1c3](https://hackmd.io/_uploads/B1G4ZPY5xe.png) 機器人進入群組後就可以使用了。只要你標註機器人,訊息就會觸發 Webhook。 ![q8ak2tls](https://hackmd.io/_uploads/Hyn9-vF5xg.png) ## 讓 Slack Bot 處理圖片 要接收圖片,我們要在 "Subscribe to bot events" 加入一個事件: - `files:read`:讀取使用者上傳的檔案(圖片) 並在 Event Subscription 確認有加上這三個事件: - `message.channels`(公開頻道) - `message.groups`(私密頻道) - `message.im`(私訊) Slack 只會幫可以處理這三種事件的帳號處理圖片,否則 Slack 不會發送圖片訊息給你的 webhook。 加完後記得重新「Install to Workspace」更新 token 權限。 然後我們修改一下 Webhook 的邏輯,在事件處理上做出區分: ```python def download_slack_image(image_url): headers = { "Authorization": f"Bearer {SLACK_BOT_TOKEN}" } response = requests.get(image_url, headers=headers) if response.status_code == 200: filename = "downloaded_image.png" with open(filename, "wb") as f: f.write(response.content) return filename return None ... # 處理圖片訊息(message + files) elif event.get("type") == "message" and "files" in event: print("收到 message 和 files 了!") for file in event["files"]: if file.get("mimetype", "").startswith("image/"): image_url = file.get("url_private") filename = download_slack_image(image_url) if filename: reply = f"Hi <@{user}>! 我已經收到你的圖片 `{filename}`,準備處理囉!" send_slack_message(channel, reply) ``` 測試時不用標註機器人,直接上傳圖片即可。 ![zofwbswo](https://hackmd.io/_uploads/H1ifzvK5ee.png) # 三、業務情境實作 - 串接 API 如果公司有內部的系統服務 API(如資料查詢、身分驗證等),也可以將它們串接到 Slack 機器人上,其實就是把剛才處理圖片的訊息內容改成 API 的回傳值就可以了。 這邊以串接公司票據辨識的 API 作為範例,得到的回傳結果如下: ![gyg6awmr](https://hackmd.io/_uploads/Byy0GPF5lg.png) ![螢幕擷取畫面 2025-09-04 153402](https://hackmd.io/_uploads/ryixI-d5el.png) ![螢幕擷取畫面 2025-09-04 153613](https://hackmd.io/_uploads/HJseLWd5gg.png) (車票與發票皆為網路搜尋的範本,敏感資訊已有額外處理。) ## 補充:訊息事件的去重處理 要注意的是,通常在 Slack 串接 API 可能會需要去重 (去除重複呼叫) 的設計。因為如果伺服器回應太慢,Slack 就會重複發送請求。 在 View 裡面我們要做兩個處理:一個是在 Webhook 開頭直接先確認標頭,看是不是 Slack 重送的訊息,是的話就直接忽略。 ```python @csrf_exempt def slack_webhook(request): # Slack retry 重送直接忽略 if request.headers.get("X-Slack-Retry-Num"): return JsonResponse({"status": "ignored"}) ... ``` 另一個是要在處理圖片事件 (message + file_share) 時,檢查是否有重複發送的事件。 可以用 set 去接住事件 id,但 **Slack 不是每種情境都有 client_msg_id** (像手機傳圖、桌機拖圖、或直接用第三方應用傳圖就會回傳 None)。 所以去重的 key 可以再加上 event_ts (事件時間戳),在 Slack 這絕對是每個訊息的唯一值。 ```python msg_id = event.get("client_msg_id") or event.get("event_ts") if msg_id in processed_messages: print(f"跳過重複訊息 {msg_id}") return processed_messages.add(msg_id) ``` 或是也可以用 Redis 暫存已經處理過的圖片 URL,如果在處理前的檢查發現這是重複請求的話,就會擋下來。存入、取得和刪除 URL 的方法設計如下: ```python class RedisUtils: ... @staticmethod def store_image_url(email: str, image_url: str, ttl=600): """將 image_url 存入 Redis,預設有效 10 分鐘""" redis_conn = get_redis_connection("default") redis_conn.set(f"image_url:{email}", image_url, ex=ttl) print(f"將 image_url 存入 Redis:{image_url}") @staticmethod def get_stored_image_url(email: str): redis_conn = get_redis_connection("default") return redis_conn.get(f"image_url:{email}") ``` 然後在處理圖片訊息前,我們也檢查一下這張圖是否已經重複檢驗過。Redis 的時間抓 30 秒就好,因為重送的訊息通常都很快。 最後整段檢驗圖片(檔案)訊息是否重複的驗證流程如下: ```python ... # 處理圖片訊息(message + files) elif event.get("type") == "message" and "files" in event: # 取用 client_msg_id 或 event_ts 當唯一 key msg_id = event.get("client_msg_id") or event.get("event_ts") if event.get("type") == "message" and "files" in event: # 在 Redis 檢查這張圖是否已經處理過 if RedisUtils.get_stored_image_url(msg_id): print(f"跳過重複訊息 {msg_id}") return JsonResponse({"status": "duplicate"}) RedisUtils.store_image_url(msg_id, ttl=30) ... ``` # 四、儲存 Slack 用戶資訊 如果有需要紀錄用戶資料去做統計、或是身分驗證等用途,Slack 有提供相關 API 可以存取留言過的用戶資訊: 1. 在 Django 安裝 slack app 的套件:`uv add django-slack-app` ```python # settings.py INSTALLED_APPS = ['slack_app', ...] AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', 'slack_app.auth_backends.SlackAuthenticationBackend', ] SLACK_CLIENT_ID = '你的 Slack App Client ID' SLACK_CLIENT_SECRET = '你的 Slack App Secret' SLACK_SIGNING_SECRET = '你的 Signing Secret' ``` 2. 去 Slack api 的 "Bot Token Scope" 加上 `users:read` 的權限。(2017 年以後,如果要查詢 email,需再加上 `users:read:email` 的權限) 3. 在 Django 建立 Slack 的 Model。收到訊息並完成 OAuth 驗證後,可以得到的欄位有這些: ![image](https://hackmd.io/_uploads/ry0V8emtge.png) ![image](https://hackmd.io/_uploads/HkjB8gQtge.png) 4. 串接 Slack 身分驗證的 API,就可以取得想要的用戶資訊了。 ```python # 取得用戶資訊 (串接 Slack 提供的用戶驗證 API) def get_slack_user_info(user_id): url = 'https://slack.com/api/users.info' headers = { 'Authorization': f'Bearer {SLACK_BOT_TOKEN}' } params = { 'user': user_id } response = requests.get(url, headers=headers, params=params) data = response.json() return data.get('user', {}) ... # 處理事件 if payload.get("type") == "event_callback": event = payload.get("event") team_id = payload.get('team_id') channel = event.get("channel") user = event.get("user") # 呼叫 Slack API 取得使用者資訊 user_info = get_slack_user_info(user).get('profile', {}) # 儲存或更新 Model CustomSlackUser.objects.update_or_create( slack_user_id=user, defaults={ 'team_id': team_id, 'real_name': user_info.get('real_name'), 'display_name': user_info.get('display_name'), 'email': user_info.get('email'), 'updated_time': timezone.now() } ) ... ``` 本來應該這樣就完成了。 但是 django-slack-app 本身有個**很大的問題**:他在 PyPI 上的最新版本是 1.0.40(2020 年 3 月 27 日)——**也就是說,它已經 5 年多沒有更新,還停留在 Django 2.x / Postgres JSONField 的時代,沒有針對 SQLite 或新版 Django 做過調整。** 所以我們會遇到這個錯誤: ```powershell ModuleNotFoundError: No module named 'psycopg2' ``` 這是因為在 slack-app 的 Model 有用到舊版的 JSONField:在 Django 3.1 以前,JSON 的模型欄位是引用 `django.contrib.postgres.fields.JSONField` 提供的方法,當時這還是 PostgreSQL 專用的欄位,所以要安裝相關套件,也就是 'psycopg2'。 但在 Django 3.1 以後,JSONField 已經變成跨資料庫的核心欄位,可以直接從 `django.db.models` 匯入,SQLite、PostgreSQL、MySQL 都能用。 一個方法是,我們可以直接修改裡面的 import 就好 (記得要連遷移檔都一起改),但這樣當我們更新或重新安裝 django-slack-app 時,就有可能被改回來。 所以另一種做法是:**fork 一份 django-slack-app 到自己的 repo,把 model 和 migration 都改好,之後直接用你自己的版本安裝。** 1. 先解除安裝舊套件:`uv remove django-slack-app` 2. 到 [django-slack-app 的 GitHub](https://github.com/webscopeio/django-slack-app),點選右上角的 fork,按下 create fork 後,就會在你的 repository 建立一個相同的 django-slack-app。 ![2jsfr4sx](https://hackmd.io/_uploads/ByHq7DKqel.png) 3. 在專案外的資料夾將 fork 的套件 clone 到本地端。(安全起見) ```powershell cd 你的專案資料夾外面 # 先到一個安全的位置 git clone https://github.com/你的GitHub帳號/django-slack-app.git cd django-slack-app ``` 4. 打開檔案,修改裡面所有用到 `django.contrib.postgres.fields.JSONField` 的地方 (包含遷移檔),改成 `django.db.models`。 :::success GitHub 的版本似乎有更新過,因此只要修改遷移檔的 import,以及 29 行 JSONField 的引用路徑拿掉,就可以使用了。 ::: 5. 到 setup.py 修改版本號,方便後續確認使用的是自己修改的版本。 ```python setup( version="1.0.0+sqlitefix", # 版本號格式可能會影響 import,請自行注意 install_requires=["slackclient", "celery"] ) ``` 6. commit 並 push 回 GitHub 7. 回到原本的專案,將我們修改過的套件透過 git 網址安裝:`uv add git+https://github.com/StevenShih-0402/django-slack-app.git@master`,在安裝的套件中有看到你版本號的 django-slack-app 就表示安裝成功了。 ```powershell ... + django-slack-app==1.0.0+sqlitefix (from git+https://github.com/StevenShih-0402/django-slack-app.git@6b3530cbe0a121fa1c64574803f73c44484e3f25) ... ``` :::warning **後續維護** 官方更新時,你可以在 Fork 拉取更新後再 push 到 GitHub,然後用 `uv sync` 更新。 ::: 這樣我們在傳訊息時,就可以將用戶資訊記錄下來了。 ![geex6hak](https://hackmd.io/_uploads/S1tZEwF5el.png) # 五、公開發布你的機器人 當我們的機器人 (應用程式) 開發完成後,當然是希望別人可以下載來用了。在 Slack 要公開發布你的機器人,有兩種方式: 1. **上架到 App Directory (Slack Market)** >提交應用程式給 Slack 審核團隊,通過後會正式列在 Slack 官方的 Marketplace 上。 像是 Google 系列工具、Notion 筆記工具、Zapier 流程自動化工具等應用程式,都有公開發布在 Slack Market 讓各公司的用戶下載。如果你希望廣泛推廣、獲得更多曝光、並建立品牌信任度,這是最合適的管道。 2. **公開發佈 (Public Distribution)** >即使不給 Slack 官方審核,也可以在應用程式儀表板中啟用 OAuth 流程,並獲得一個可分享的「新增至 Slack」按鈕或 URL。 像是如果你的應用程式只給特定公司使用,~~或是你的應用程式可能過不了 Slack 官方審核~~,就很適合這個方法。 如果有興趣或真的想開發大型商用的應用程式,可以再去研究如何上架到 Slack Market。但無論如何,公開發布都是必須的,因此這邊先示範如何發布你的機器人: 1. 請先確認程式部署的伺服器網域 (測試時可用 zrok 或 ngrok 等臨時網域,如:`https://4x47wqlbrawp.share.zrok.io`,但這需要每天更新。),以及 OAuth Permissions 有順利建立。 2. 在 Slack API 左側選擇 "Manage Distribute" 選項,確認它要求的所有規範都沒問題,就可以按下 Activate Public Distribution: ![image](https://hackmd.io/_uploads/ry0cOPfJWx.png) ![image](https://hackmd.io/_uploads/S1u2uwGyZg.png) 3. 之後就可以透過上述三種方法 (按鈕 UI、分享網址、嵌入連結)選擇你要安裝的工作空間。 ![image](https://hackmd.io/_uploads/Hk5A5wGJbl.png) # 六、總結 Slack 可以根據需求去設計流程自動化的應用程式 (機器人),若運用得當,員工甚至可以在 Slack 就完成所有的公司行政流程與業務,減少重複的工作量與時間,這正是流程自動化帶來的長遠效益。 若對 Slack 機器人有興趣,可以參考[官方文件](https://docs.slack.dev/apis/)深入研究。 **此文章同時公開發表於「韜睿軟體有限公司」官網,對於影像辨識、自然語言處理,以及自動化流程等技術應用有興趣的話,這裡分享了許多相關主題的文章,歡迎前來瀏覽~** {%preview https://www.ignsw.com/slack-%e6%a9%9f%e5%99%a8%e4%ba%ba%e5%a6%82%e4%bd%95%e5%8d%94%e5%8a%a9%e6%95%b4%e5%90%88-ocr-%e7%a5%a8%e6%93%9a%e8%88%87%e8%b7%a8%e5%b9%b3%e5%8f%b0%e8%b3%87%e8%a8%8a%e6%8f%90%e5%8d%87%e5%b7%a5%e6%95%88/ %}