Discord bot that watches pubg.report for clipped moments involving configured PUBG players (kills and deaths) and posts a 30-second inline-playable video to a chosen Discord channel.
- Polls
api.pubg.report/v1/players/{accountId}/streamson an interval for every tracked player. - Filters events to ones where the configured user is the killer ("X just killed Y") or the victim ("X just died to Y").
- Groups knock + kill events for the same encounter into a single moment so metadata (weapon, distance) merges correctly across them.
- Resolves the source VOD's HLS playlist via
api.pubg.report/v2/vod/{id}(no Twitch authentication needed — same path the site's "save" button uses) and downloads only the relevant 30 seconds via bundled ffmpeg. - Posts the clip in a Discord Components V2 container so the inline-playable mp4 sits inside the embed-style border alongside the metadata and link buttons.
npm install
cp .env.example .env
# fill in BOT_TOKEN, CLIENT_ID, GUILD_ID
npm run deploy # register the /config slash command
npm run dev # start polling (or `npm run build && npm start` for prod)The bot needs only the Guilds gateway intent. When inviting it to a server
grant Send Messages, Embed Links, and Attach Files in the target
channel.
/config users add username:<PUBG name> channel:<#channel>
/config users remove username:<PUBG name>
/config users list
/config users test username:<PUBG name>
add resolves the PUBG name via pubg.report's search endpoint and stores the
resulting account.<id>. The existing backlog of clips is marked seen on add
so you don't get bombarded with weeks of history. test posts the most
recent clip on demand without affecting the seen-id state. All require the
Manage Server permission.
| Variable | Default | Notes |
|---|---|---|
BOT_TOKEN |
— | Discord bot token (required) |
CLIENT_ID |
— | Discord application ID (required) |
GUILD_ID |
— | Optional. If set, slash commands deploy instantly to this guild instead of globally (which can take an hour to propagate). |
POLL_INTERVAL_SECONDS |
120 |
Per-cycle poll cadence; clamped to a 15s minimum. |
DATA_DIR |
./data |
Where config.json (tracked users + seen IDs) lives. |
CLIP_PRE_SECONDS |
0 |
Seconds of lead-in before the pubg.report timestamp. |
CLIP_POST_SECONDS |
30 |
Seconds of trail-out after the latest event in the encounter. |
CLIP_MAX_HEIGHT |
480 |
Max HLS variant height; bumps to 720 only if 480 isn't published. |
CLIP_UPLOAD_CAP_BYTES |
25165824 |
Hard cap on the produced mp4 to stay under Discord's default 25 MB attachment limit. |
cp .env.example .env
# fill in .env
docker compose up -d --build
docker compose logs -f dickhead-botThe data/ directory is volume-mounted so tracked users + seen IDs persist
across rebuilds. The @ffmpeg-installer/ffmpeg npm package pulls down the
linux-x64 ffmpeg binary at build time, so the runtime image needs nothing
extra installed.
pubg.report /v1/players/.../streams → raw kill/death events
↓
group into clip moments (knock + kill collapse, kill vs death)
↓
pubg.report /v2/vod/{id} → CloudFront HLS playlist URL
↓
ffmpeg dual-seek + transcode → ~5–10 MB mp4 segment
↓
Discord Components V2 container → inline player + metadata + buttons
source/
index.ts entrypoint, dynamic command/event loaders
config.ts env validation
deploy-commands.ts REST registrar for /config
commands/config.ts /config users add|remove|list|test
events/ clientReady, interactionCreate
services/
pubgReport.ts search + streams API client
vodPlaylist.ts VOD metadata → HLS URL (with LRU cache)
clipExtractor.ts bundled-ffmpeg HLS → mp4 with dual-seek
tracker.ts poll loop, moment grouping, message builder
storage/store.ts JSON-file backed per-guild user store
utils/ twitch URL helpers + logger
types/ shared interfaces
The naive choices don't work for inline-playable Discord clips:
- Twitch VOD URLs (
twitch.tv/videos/...) don't auto-embed as Discord players anymore — onlyclips.twitch.tv/<slug>URLs do. - Twitch's
createClipAPI requires a user OAuth token and only works on live streams, not VODs (itsvideoIDvariant requires the user account to be clip-eligible, which gates new accounts and can returnUSER_RESTRICTED). - Discord won't render iframe embeds, only video files or whitelisted provider URLs.
The path that actually works without manual auth: take pubg.report's own
trick. Their "save" button reads VOD metadata from
api.pubg.report/v2/vod/{id}, parses the CloudFront path out of
thumbnail_url, and pulls HLS segments directly from Twitch's public CDN.
We do the same, then ffmpeg cuts a 30-second window around the kill,
transcodes to 480p mp4, and Discord Components V2 lets the resulting
attachment sit inside an embed-style container.