---
title: Extend Discord intake to DMs sent directly to the Trashman Jr bot
type: task
status: draft
description: >-
  A DM sent to the Trashman Jr bot account is invisible to intake (the bot gateway has no
  direct-messages intent and reads are operator-token-only). Wire it only when base discord dm first
  lists a bot DM channel.
base_uri: user:task/league/extend-discord-intake-to-dms-sent-directly-to-the-bot.md
created_at: '2026-10-18T00:27:09.002Z'
entity_id: 9fcdccc9-669a-4d44-af52-134236f5dd5d
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-10-18T00:27:09.002Z'
visibility_analyzed_at: '2026-11-06T00:17:51.242Z'
---

# Extend Discord intake to DMs sent directly to the Trashman Jr bot

## Context

A contributor who DMs the Trashman Jr bot account gets silence. The bot gateway subscribes no `INTENT_DIRECT_MESSAGES` and `handle_bot_message` drops anything without `guild_id`; the read path is operator-token-only. Verified 2026-10-18: the bot REST-accessible `/users/@me/channels` (identity `bot`) returns 0 DM channels, so no contributor has DM'd the bot yet. `base discord dm` (extension/discord/command.mjs, eefed737ad) now lists the bot's DM lanes for the watch. The watch-for in `██████████████████████████████████████████████████` triggers on the first bot DM.

## Outcome when done

A contributor DM sent to the Trashman Jr bot account is handled by intake exactly like the operator-DM lane: the message triggers a scan, the read resolves the channel, and Junior can reply in that DM. The `dm-<slug>` source maps to whichever token owns the channel.

## Plan

Status: Draft. Gate — this stays parked until `base discord dm` lists a non-zero channel count. Do not act on this until evidence appears.

- [ ] `extension/discord/worker/gateway-listener.mjs` — add `INTENT_DIRECT_MESSAGES` to the bot connection's intents and let `handle_bot_message` dispatch DMs (remove the `guild_id` filter or branch it).
- [ ] `detect-junior-trigger.mjs` — `resolve_dm_scope` must resolve a bot-DM channel's counterpart; the channel lookup runs through `discord_request` default user identity, which 403s on a channel the operator does not own. Retry the counterpart lookup under the bot identity, or thread the owning connection through the handler.
- [ ] `read-discord-intake.mjs` — `find_dm_channel_id` lists the USER's DMs only; extend to try the bot's `/users/@me/channels` (identity `bot`) so `dm-<slug>` resolves the bot↔contributor channel when the operator has no such DM.
- [ ] Reply path: in the bot's own DM, Junior can post as the bot directly (no `Junior:` prefix needed) — decide whether replies use `send_message_as_bot` or keep the operator-account prefix.
- [ ] Verify with `base discord dm` against a real DM (open one with the bot from the operator's account), then `base contribution ██████████████ read --source dm-<slug>` and a scan.

## Key locations

- `extension/discord/worker/gateway-listener.mjs` — the two-connection gateway, CONNECTIONS block.
- `extension/contribution/lib/read-discord-intake.mjs` — source resolution, `find_dm_channel_id`, watermark.
- `extension/contribution/lib/detect-junior-trigger.mjs` — `resolve_dm_scope`.
- `extension/contribution/provide/discord-message-handler.mjs` — the handler that enqueues scans.
- `extension/discord/lib/client.mjs` — `discord_request({ identity })`; bot identity is `Bot <token>`.

## Source

- Review verdict 2026-10-18 (session █████████████████████████████████████████████████).
- Watch-for: `██████████████████████████████████████████████████`.
