From 11ea599bbda47a161176f54f0927f409cf7618ae Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Tue, 12 May 2026 20:05:48 -0400 Subject: [PATCH] Fix Slack API calls that require form-urlencoded encoding (#7) conversations.members, conversations.open, and users.info reject JSON body with 'missing required field'. Use form-urlencoded for these methods while keeping JSON for chat.postMessage and files.upload which require it for structured blocks/payloads. --- src/shared/shared/slack.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/shared/shared/slack.py b/src/shared/shared/slack.py index 546380d..1f22778 100644 --- a/src/shared/shared/slack.py +++ b/src/shared/shared/slack.py @@ -1,7 +1,8 @@ import json import os -import urllib.request import urllib.error +import urllib.parse +import urllib.request from shared.secrets import get_secret, get_parameter _token = None @@ -18,14 +19,19 @@ def _get_channel() -> str: return get_parameter(os.environ["SLACK_CHANNEL_PARAM"]) -def _api_call(method: str, payload: dict) -> dict: - data = json.dumps(payload).encode() +def _api_call(method: str, payload: dict, use_json: bool = True) -> dict: + if use_json: + data = json.dumps(payload).encode() + content_type = "application/json; charset=utf-8" + else: + data = urllib.parse.urlencode(payload).encode() + content_type = "application/x-www-form-urlencoded" req = urllib.request.Request( f"https://slack.com/api/{method}", data=data, headers={ "Authorization": f"Bearer {_get_token()}", - "Content-Type": "application/json", + "Content-Type": content_type, }, ) with urllib.request.urlopen(req) as resp: @@ -40,7 +46,7 @@ def post_channel_message(text: str, blocks: list[dict] | None = None): def send_dm(user_id: str, text: str, blocks: list[dict] | None = None): - open_resp = _api_call("conversations.open", {"users": user_id}) + open_resp = _api_call("conversations.open", {"users": user_id}, use_json=False) if not open_resp.get("ok"): return open_resp channel = open_resp["channel"]["id"] @@ -69,7 +75,7 @@ def get_channel_members(channel_id: str) -> list[str]: payload = {"channel": channel_id, "limit": 200} if cursor: payload["cursor"] = cursor - resp = _api_call("conversations.members", payload) + resp = _api_call("conversations.members", payload, use_json=False) if not resp.get("ok"): break members.extend(resp.get("members", [])) @@ -80,7 +86,7 @@ def get_channel_members(channel_id: str) -> list[str]: def get_user_info(user_id: str) -> dict | None: - resp = _api_call("users.info", {"user": user_id}) + resp = _api_call("users.info", {"user": user_id}, use_json=False) if resp.get("ok"): return resp["user"] return None