internetarchive / internetarchive/openlibrary
Slack bot: turn a Slack thread (text + images) into a GitHub issue (members-only, scoped to #openlibrary-g / #open-librarians-g)
- Dominant language
- Python
- Stars
- 6.7k
- Forks
- 2k
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 138
Description
## Motivation
A lot of Open Library's real bug reports and feature ideas start life as a Slack thread in `#openlibrary-g` or `#open-librarians-g` — often with screenshots attached. Today, turning one of those threads into a tracked GitHub issue is manual copy-paste: re-typing the discussion, re-attaching the images by hand, and losing the link back to the original conversation. Most of the time it simply doesn't happen, and the report is lost.
This issue proposes a small Slack bot that does that conversion in one action: pick a message in a thread, and the bot files a GitHub issue on `internetarchive/openlibrary` containing the thread's text (with author display names), any embedded images, the channel name, and a permalink back to the Slack thread.
## Scope
### Trigger UX
A **Slack message shortcut** (right-click / "more actions" on a message → *Create GitHub issue*), not a slash command. A shortcut payload carries the message and thread context implicitly (`channel.id`, `message.ts`, `message.thread_ts`, `user.id`), which is exactly what "operate on *this* thread" needs. A slash command has no implicit message context — the invoker would have to paste a permalink, which is most of the friction we're trying to remove.
### Membership check (hard requirement)
Call `users.info` for the invoking user and refuse unless **both** `is_restricted` and `is_ultra_restricted` are `false`. Either flag true means a guest (multi-channel or single-channel guest) and the bot must decline politely without creating an issue. Guests must not be able to file into our public repo through us.
### Channel restriction (hard requirement)
Functional only in `#openlibrary-g` and `#open-librarians-g`. Anywhere else — including for a full member — the bot responds with a polite ephemeral refusal and creates nothing. The allowed channels should be configuration, not hard-coded literals, but the deployed default is exactly those two.
### Content extraction
- Fetch the whole thread (`conversations.replies`), preserving order, with author display names resolved via `users.info` (cached per run).
- Collect image files attached to any message in the thread.
- Slack file URLs (`url_private`) require the bot token to fetch and are not publicly readable, so a raw Slack URL pasted into a GitHub issue renders as a broken image. Images therefore have to be **re-hosted** somewhere GitHub can embed by URL.
### Issue creation
Created via `gh`/the GitHub API as `@openlibrary-bot` (this is the bot's own automated action). Body includes: thread text, re-hosted image embeds, originating channel name, thread permalink (`chat.getPermalink`), and an explicit byline saying the issue was generated from a Slack thread and by whom.
## Open question 1 — privacy sign-off (needs @mekarpeles, separate from "does the code work")
Copying an internal Slack discussion — even one held in a Slack-public channel — onto a **fully public** GitHub repository is a policy decision, not just a technical one. Participants in a thread did not necessarily consent to their words and screenshots being republished publicly and permanently, and screenshots in particular can contain patron data, emails, or admin UI that nobody intended to publish.
The POC will ship with a conservative default (a confirmation step — either an ephemeral "this will post publicly, confirm?" dialog to the invoker, or a ✅ reaction requested in-thread before the issue is filed) so that no thread can be republished by accident.
**But the default is a POC default, not an approved policy.** Before any real/production use this needs an explicit decision on:
1. Is per-use confirmation from the invoker sufficient, or does republishing need consent from the *thread participants* (not just the person clicking the shortcut)?
2. Should images be re-hosted at all by default, given they are the highest-risk content (screenshots of patron data / admin screens)? A viable stricter default is: text only, with images listed as "N image(s) omitted — attach manually if appropriate."
3. Should there be a redaction/edit step (bot opens a pre-filled modal the invoker can edit) rather than filing verbatim?
Flagging, not deciding. This wants an answer before install, independent of whether the code is correct.
## Open question 2 — where to re-host images
Two real options; picking one is part of the implementation plan, not settled here.
- **A dedicated GitHub assets repo via the Contents API** (`PUT /repos/{owner}/{repo}/contents/{path}`), embedding the resulting `raw.githubusercontent.com` URL. Documented, supported, permanent, versioned, and needs no credential the bot doesn't already have. Downside: it's a new repo to create, and content is public and effectively permanent (deleting a file leaves it reachable in history).
- **An archive.org item**, matching this codebase's existing bot pattern (the `OLModSecurityBot` daily upload described in #13225). Downside: that pattern uses an **access-restricted** item, which by definition GitHub cannot embed — so this variant would require a *public* item, which is a meaningfully different privacy posture than the pattern it's borrowing from. It also needs the bot's IA S3 keypair on the host.
Current lean is the GitHub assets repo for robustness (documented API, no extra credentials, renders reliably in issue markdown). Explicitly **not** in scope: GitHub's browser-only attachment upload endpoint — it is not a public API and building on it would be fragile by design.
## Deliverables
1. **Working handler code** — Slack request-signature verification (`X-Slack-Signature` / `X-Slack-Request-Timestamp`, HMAC-SHA256 with the signing secret, timestamp replay window), membership check, channel check, thread fetch, image re-host, issue creation. Slack requires an ack within 3 seconds, so the slow work (fetching, uploading, filing) runs after the ack, not inline.
2. **Tests against recorded/mocked Slack payloads** — including the refusal paths: guest user, unlisted channel, bad signature, stale timestamp. No live workspace needed to run the suite.
3. **A step-by-step setup guide for @mekarpeles** covering the parts a bot cannot do for itself: creating the app at api.slack.com/apps, the required OAuth scopes (`channels:history`, `groups:history` if a private channel is ever in play, `users:read`, `files:read`, `chat:write`), enabling Interactivity & Shortcuts and registering the message shortcut, setting the Request URL (with ngrok-style tunnelling called out as the POC step, distinct from the eventual hosted deployment), installing to the workspace, and where the Bot Token + Signing Secret go in `olsystem` — following how other bot secrets are handled there. The real token must never be pasted into any chat/agent session; the guide points at the file location instead.
## Out of scope
- Creating the Slack app itself (workspace-admin action, requires Mek's own browser session).
- Two-way sync, issue updates from later thread replies, or Slack notifications when the issue changes.
- Any channel beyond the two named above.
## Notes
Related existing Slack integration in this repo runs the other direction (GitHub → Slack): `scripts/gh_scripts/issue_comment_bot.py` and `scripts/pr_slack_digest.py`, both cron-driven via GitHub Actions with `SLACK_TOKEN` from the environment. This is the first inbound Slack → GitHub path, and the first one needing a persistently reachable HTTPS endpoint rather than a cron job — so deployment shape is a genuine design decision, covered in the implementation plan.
Contributor guide
Assessment
This issue has not been assessed yet.