Title: How to capture root message of a thread in a Space? · Issue #265 · WebexCommunity/WebexPythonSDK · GitHub
Open Graph Title: How to capture root message of a thread in a Space? · Issue #265 · WebexCommunity/WebexPythonSDK
X Title: How to capture root message of a thread in a Space? · Issue #265 · WebexCommunity/WebexPythonSDK
Description: By root message I mean the first original message from which a thread starts. In an attempt to capture the root message of a thread (both 1:1 and Space) from which the webhook was called, starter = api.messages.get( parent_id ) works for...
Open Graph Description: By root message I mean the first original message from which a thread starts. In an attempt to capture the root message of a thread (both 1:1 and Space) from which the webhook was called, starter =...
X Description: By root message I mean the first original message from which a thread starts. In an attempt to capture the root message of a thread (both 1:1 and Space) from which the webhook was called, starter =...
Opengraph URL: https://github.com/WebexCommunity/WebexPythonSDK/issues/265
X: @github
Domain: patch-diff.githubusercontent.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"How to capture root message of a thread in a Space?","articleBody":"By root message I mean the first original message from which a thread starts.\n\n\u003cimg width=\"955\" height=\"177\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/90abf083-b2c1-4226-bedc-ff7e49391c20\" /\u003e\n\nIn an attempt to capture the root message of a thread (both 1:1 and Space) from which the webhook was called,\n\n`starter = api.messages.get( parent_id )` works for 1:1 not in a Space, throws error:\n```\nTraceback (most recent call last):\nFile \"/app/webex-bot/app.py\", line 174, in collect_thread_text_and_attachments\nstarter = api.messages.get(parent_id)\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/api/messages.py\", line 339, in get\njson_data = self._session.get(API_ENDPOINT + \"/\" + messageId)\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/restsession.py\", line 428, in get\nresponse = self.request(\"GET\", url, erc, params=params, **kwargs)\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/restsession.py\", line 393, in request\ncheck_response_code(response, erc)\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/utils.py\", line 207, in check_response_code\nraise ApiError(response)\nwebexpythonsdk.exceptions.ApiError: [404] Not Found - Unable to get message. [Tracking ID: ROUTERGW_add856f9-8590-471d-a355-3b4b2405fdb9] \n```\n\nAlso, `starter_candidates = list( api.messages.list( roomId = room_id, max = 5, beforeMessage = parent_id ) )` works 1:1 but not in Space, throws error:\n```\nTraceback (most recent call last):\nFile \"/app/webex-bot/app.py\", line 179, in collect_thread_text_and_attachments\nstarter_candidates = list( api.messages.list( roomId = room_id, max = 5, beforeMessage = parent_id ) )\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/api/messages.py\", line 138, in list\nfor item in items:\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/restsession.py\", line 502, in get_items\nfor json_page in pages:\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/restsession.py\", line 455, in get_pages\nresponse = self.request(\"GET\", url, erc, params=params, **kwargs)\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/restsession.py\", line 393, in request\ncheck_response_code(response, erc)\nFile \"/app/webex-bot/pyvenv/lib/python3.10/site-packages/webexpythonsdk/utils.py\", line 207, in check_response_code\nraise ApiError(response)\nwebexpythonsdk.exceptions.ApiError: [403] Forbidden - Failed to get activity. [Tracking ID: ROUTERGW_48f845fd-b077-4dab-8ec8-57291972479f] \n```\n\nThis piece works fine to capture the root message of a 1:1 thread but fails to capture the root message of a thread in a Space.\n```\ndef collect_thread_text_and_attachments(msg) -\u003e tuple[str, list[str]]:\n \"\"\"\n Robustly collect thread text + attachments. Works in 1:1 but not in spaces.\n Strategy:\n 1) Try api.messages.get(parent_id)\n 2) If that fails, scan recent messages in the room up to MAX_SCAN to find the parent\n 3) If still not found, try beforeMessage(parent_id) as a fallback\n 4) Always include replies (list parentId=...) ordered oldest-\u003enewest\n 5) Ensure the incoming message 'msg' is present\n 6) If starter can't be found, add a placeholder notice\n Returns (thread_text, [attachment_text]) where attachment_text is list with single big string\n \"\"\"\n author_cache = {}\n thread_text_lines = []\n attachment_blocks = []\n\n def process_single_message(m):\n author = get_display_name(getattr(m, \"personId\", \"unknown\"), author_cache)\n mtext = (getattr(m, \"text\", \"\") or \"\").strip()\n if mtext:\n thread_text_lines.append(f\"[{author}]: {mtext}\")\n\n if getattr(m, \"files\", None):\n for f_url in m.files:\n try:\n content, fname, ctype = download_webex_file(f_url)\n extracted = extract_text_from_file(content, fname, ctype)\n attachment_blocks.append(f\"[Attachment {fname}]:\\n{extracted}\")\n except Exception as e:\n # keep going; record the error in attachments so user sees it\n attachment_blocks.append(f\"[Attachment error for {fname}]: {e}\")\n\n parent_id = getattr(msg, \"parentId\", None)\n room_id = getattr(msg, \"roomId\", None)\n\n messages_to_process = []\n found_starter = None\n starter_unavailable = False\n\n if parent_id and room_id:\n # 1) Try to fetch starter directly (works in many cases)\n try:\n starter = api.messages.get(parent_id)\n found_starter = starter\n except Exception as ex_get:\n # failed to get the parent message (common in spaces)\n # 2) Try scanning recent messages in the room to find that id (paginated)\n MAX_SCAN = 500 # \u003c= number of messages to scan; adjust as needed\n scanned = 0\n try:\n for m in api.messages.list(roomId=room_id, max=100):\n scanned += 1\n if getattr(m, \"id\", None) == parent_id:\n found_starter = m\n break\n if scanned \u003e= MAX_SCAN:\n break\n except Exception:\n # scanning may also fail due to permissions; ignore and fallback\n pass\n\n # 3) fallback: try beforeMessage (sometimes works)\n if not found_starter:\n try:\n candidates = list(api.messages.list(roomId=room_id, max=1, beforeMessage=parent_id))\n if candidates:\n found_starter = candidates[0]\n except Exception:\n pass\n\n if not found_starter:\n starter_unavailable = True # note that we couldn't retrieve the starter\n\n # If we found a starter, add it first\n if found_starter:\n messages_to_process.append(found_starter)\n\n # Collect replies (newest-first), reverse to oldest-\u003enewest\n try:\n replies = list(api.messages.list(roomId=room_id, parentId=parent_id, max=100))\n replies.reverse() \n messages_to_process.extend(replies)\n messages_to_process.pop()\n except Exception:\n # if replies cannot be fetched, continue - we'll at least include incoming message\n pass\n\n # Ensure incoming 'msg' is present (sometimes it's not in replies list)\n if not any(getattr(m, \"id\", None) == getattr(msg, \"id\", None) for m in messages_to_process):\n messages_to_process.append(msg)\n\n # If we couldn't find the starter, insert a placeholder at the top (so LLM sees lack of context)\n if starter_unavailable:\n thread_text_lines.append(\"[Starter message unavailable — bot may have joined after the thread started or lacks permission to read the original message.]\")\n\n else:\n # Not a thread or missing metadata: just process the single message\n messages_to_process = [msg]\n\n # Now process messages in order, avoid duplicates\n seen_ids = set()\n for m in messages_to_process:\n mid = getattr(m, \"id\", None)\n if mid and mid in seen_ids:\n continue\n if mid:\n seen_ids.add(mid)\n process_single_message(m)\n\n # Combine, guardrail sizes\n thread_text = \"\\n\".join(thread_text_lines)\n MAX_CHARS = 60_000\n if len(thread_text) \u003e MAX_CHARS:\n thread_text = thread_text[:MAX_CHARS] + \"\\n...[truncated]\"\n\n att_text = \"\\n\\n\".join(attachment_blocks)\n if len(att_text) \u003e MAX_CHARS:\n att_text = att_text[:MAX_CHARS] + \"\\n...[attachments truncated]\"\n\n return thread_text, [att_text] if att_text else [] \n```","author":{"url":"https://github.com/ddebta","@type":"Person","name":"ddebta"},"datePublished":"2025-09-02T21:47:38.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/265/WebexPythonSDK/issues/265"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:8a8d7b48-1571-8ad9-9dab-5efcc6ba3d91 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9CC2:358866:3725D5:4C195E:696E55BE |
| html-safe-nonce | 9a71c9fe250a6fc45857e7f1e70bff22034117654b944c870c0e94c0c973c515 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5Q0MyOjM1ODg2NjozNzI1RDU6NEMxOTVFOjY5NkU1NUJFIiwidmlzaXRvcl9pZCI6IjgxMjA5NTY2NzM2NTMxMDIwMTQiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | f09950eb950eb0936808e1f1a68756a4841930e1f84eab5e3c38933ca3aa79cf |
| hovercard-subject-tag | issue:3377580788 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/WebexCommunity/WebexPythonSDK/265/issue_layout |
| twitter:image | https://opengraph.githubassets.com/944e5bf3d32048b104056d326714d090bd16a58d902f3018a19a4b4488cbe755/WebexCommunity/WebexPythonSDK/issues/265 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/944e5bf3d32048b104056d326714d090bd16a58d902f3018a19a4b4488cbe755/WebexCommunity/WebexPythonSDK/issues/265 |
| og:image:alt | By root message I mean the first original message from which a thread starts. In an attempt to capture the root message of a thread (both 1:1 and Space) from which the webhook was called, starter =... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | ddebta |
| hostname | github.com |
| expected-hostname | github.com |
| None | f68b42d371252b0f236260d6234f4304a806fe5ac43d59faa21fb59d80df103b |
| turbo-cache-control | no-preview |
| go-import | github.com/WebexCommunity/WebexPythonSDK git https://github.com/WebexCommunity/WebexPythonSDK.git |
| octolytics-dimension-user_id | 112501140 |
| octolytics-dimension-user_login | WebexCommunity |
| octolytics-dimension-repository_id | 63186293 |
| octolytics-dimension-repository_nwo | WebexCommunity/WebexPythonSDK |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 63186293 |
| octolytics-dimension-repository_network_root_nwo | WebexCommunity/WebexPythonSDK |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 6b74bc8dbcd10b5d69fd9ee9d2cfdc8b35e18a4c |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width