Planned features and improvements for the PBP Reminder Bot. Status: ✅ Done | 🔧 In Progress | 📋 Planned | 💡 Idea
Dates shown as [YYYY-MM-DD] next to status icons indicate when work was completed or the item was logged.
Players type /mystats in any PBP topic to see their own stats:
total posts, posting sessions, average gap, last post, current streak,
and weekly activity. No waiting for roster day.
Anyone can check combat status on demand: current round, whose phase it is, who has acted, who hasn't. Works outside the ping timer schedule.
The bot posts a random tip about one of its features once per day to a randomly chosen PBP chat topic. Helps players discover commands without needing to read GitHub or the issues topic. Tips rotate through all features so each one gets explained eventually.
Track consecutive days each player posts. Display in /mystats and roster.
Milestone celebrations at 7, 14, 30, 60, 90 days ("🔥 Alice is on a 30-day
streak!"). Could feed into POTW weighting or be a standalone shout-out.
Add a "longest active streak" section to the weekly leaderboard. Show top 5 across all campaigns. Encourage consistent engagement over burst posting.
A compact newsletter posted to the leaderboard topic once per week. One-line summary per campaign: name, post count, trend, top contributor, notable events (new player joined, combat started, anniversary). Designed to be scannable in 10 seconds.
Assign each campaign a simple health score (traffic light) based on weekly post volume. Show in digest. Helps the GM spot campaigns that need attention without reading every stat.
Every message in every PBP topic is logged to persistent markdown files
in the repo. Monthly files per campaign at data/pbp_logs/. Media is
tagged with type markers. Auto-generated README index. A complete
disaster-recovery backup of every campaign's story.
- Player-level drill-down (click a campaign to see individual stats)
- Summary cards, week filter, sortable columns
- Health indicator dots (green/yellow/orange/red)
- Trend arrows (week-over-week change)
- Mobile-responsive layout
/myhistory shows a player's posting pattern over time: weekly post
counts for the last 8 weeks as a text sparkline chart.
scripts/import_history.py imports past PBP messages from Telegram Desktop
JSON exports into data/pbp_logs/. Idempotent, supports --dry-run.
Optional gm_user_ids on individual topic_pairs replaces the global list
for that campaign. Allows different GMs per campaign (e.g. Theria).
Temporarily disable inactivity tracking for planned breaks, holidays,
or between arcs. Pause reason displayed in /status and /campaign.
Manually remove a player from tracking without waiting for the 4-week auto-removal.
Manually register a player who hasn't posted yet so they appear in the roster and get tracked.
Optional characters field per campaign maps user IDs to character names.
Names appear in rosters, /mystats, transcripts, and the new /party command.
Shows the in-fiction party composition: character names, players, activity.
Celebrates in the campaign's chat topic with escalating icons.
Celebrates across all campaigns in the leaderboard topic.
Shows what happened since you last posted: message counts by person, time since last post, and combat state. Essential for returning PBP players.
Summary cards, week filter, sortable columns, player drill-down,
health indicators, trend arrows, mobile-responsive. Powered by
player_breakdown data in the weekly archive.
Pace drop detection (>40% week-over-week) and conversation dying
warning (48h+ total silence). Both gated behind smart_alerts feature
flag (enabled by default). Use /pause to silence during planned breaks.
/scene <n> marks a narrative scene boundary in the campaign's
transcript file. Creates a styled divider with timestamp. Scene name
displayed in /status and /campaign output.
/note <text> adds persistent notes per campaign (max 20). View
with /notes, delete with /delnote <N>. Latest notes shown in
/campaign output. All players can view, only GMs can add/remove.
Every message records hour-of-day and day-of-week counters. Permanent lightweight data (24 + 7 buckets per user per campaign).
Campaign-level posting patterns: busiest days, peak time blocks, peak hour, and top posters. Available to everyone.
Cross-campaign player lookup. Shows every campaign a player is in with post counts, character names, last activity, and streaks. Matches by username, first name, or full name.
Players declare absences with duration or indefinitely. Away players are skipped in inactivity warnings and combat pings. Auto-clears on post or timer expiry.
Show the last N transcript entries from the campaign's archive. Quick catch-up for returning players using the persistent log files.
Roll dice inline with Pathfinder-standard notation. Supports modifiers, keep-highest/lowest, multiple dice groups, and labels. Uses character name when configured.
/quest, /quests, /done, /delquest. Track active objectives per
campaign so players never lose track of what they're doing.
Compact all-campaign overview: health icons, weekly posts, player counts, combat/pause/away/quest flags. One command to check everything.
PF2e DC lookup by level and difficulty. Proficiency DCs. Short aliases.
/pin, /pins, /delpin. Bookmark key story moments, clues, reveals.
/loot, /lootlist, /delloot. Track party treasure and equipment.
/npc, /npcs, /delnpc. Track named NPCs with descriptions.
/condition, /conditions, /endcondition, /clearconditions.
Track buffs, debuffs, and persistent effects during combat and RP.
/hp set, /hp d, /hp h, /hp remove, /hp clear, /hp view.
Visual HP bars with colour-coded status icons. Combat enemy management.
/clock, /tick, /untick, /delclock, /clocks.
Blades-in-the-Dark style progress clocks for investigations, rituals, countdowns.
HP tracker, conditions, and clocks now shown in /status and /summary.
Rebuilt for async PBP alongside Foundry VTT. /combat to start, /next to
advance phases, auto-notify GM when all players have acted, combat log with
/clog, enemy roster with /enemies, summary on /endcombat.
Refactor all Python files to a 200-line hard maximum. No code removal, no compression, no comment removal. Pure extraction, OOP, and SOLID principles. Incremental commits, each chunk tested and deployed.
- Create package directories (boons, combat, commands, transcript, scheduled, dispatch, parsing, players, helpers_pkg)
- Extract
boons/handler.py(94 lines)
- Extract
combat/display.py(111 lines): whosturn, combatlog, format_elapsed - Extract
combat/tracker.py(159 lines): combat message routing, all-acted check - Extract
combat/commands.py(131 lines): start, next, end, enemies - Extract
parsing/message.py(81 lines): Telegram message parser
- Extract
commands/status.py(194 lines): build_status, build_overview - Extract
commands/campaign.py(168 lines): build_campaign_report, roster_user_stats, roster_block - Extract
commands/player.py(130 lines): build_mystats, build_myhistory, sparkline - Moved
calc_streak+health_iconto helpers.py (shared utilities) - Remaining: gm_dashboard, summary, party, profile, catchup, activity, recap → chunk 4
- Extract
commands/trackers.py(124 lines): notes, quests, pins, loot, npcs, conditions - Extract
commands/mechanics.py(113 lines): vote, timer, hp_tracker, clocks - Extract
commands/summary.py(175 lines): summary, party - Extract
commands/dashboard.py(160 lines): gm_dashboard, activity - Extract
commands/profile.py(83 lines): cross-campaign profile - Extract
commands/catchup.py(167 lines): catchup + transcript post reader - Extract
commands/recap.py(136 lines): rich transcript recap - checker.py: 4744 → 3431 lines (−1313)
- Extract
transcript/formatting.py(94 lines): log entry + content formatting - Extract
transcript/logger.py(154 lines): append_to_transcript, write_scene_marker, cache - Extract
transcript/finalize.py(179 lines): month finalization + index generation - checker.py: 3431 → 3055 lines (−376)
- Extract
scheduled/tips_data.py(164 lines): _TIPS constant - Extract
scheduled/tips.py(49 lines): post_daily_tip - Extract
scheduled/alerts.py(146 lines): check_and_alert, check_player_activity - Extract
scheduled/reports.py(140 lines): post_roster_summary, post_pace_report - Extract
scheduled/potw.py(109 lines): _gather_potw_candidates, player_of_the_week - Extract
scheduled/milestones.py(157 lines): streak + anniversary milestones - Extract
scheduled/message_milestones.py(69 lines): check_message_milestones - Extract
scheduled/leaderboard_data.py(120 lines): _gather_leaderboard_stats - Extract
scheduled/leaderboard.py(135 lines): _format_leaderboard, post_campaign_leaderboard - Extract
scheduled/maintenance.py(180 lines): archive, cleanup, recruitment - Extract
scheduled/smart_alerts.py(136 lines): pace_drop, conversation_dying - Extract
scheduled/digest.py(87 lines): weekly digest - Extract
scheduled/combat_ping.py(102 lines): combat turns, expired timers - checker.py: 3055 → 1632 lines (−1423)
- Extract
dispatch/router.py(116 lines): process_updates main loop - Extract
dispatch/cmd_info.py(168 lines): 28 read-only info commands - Extract
dispatch/cmd_gm.py(81 lines): pause, resume, kick, addplayer, scene - Extract
dispatch/cmd_trackers.py(122 lines): note, quest CRUD - Extract
dispatch/cmd_trackers_items.py(144 lines): pin, loot, NPC CRUD - Extract
dispatch/cmd_conditions_hp.py(199 lines): condition + HP tracker writes - Extract
dispatch/cmd_clocks.py(127 lines): clock, tick, untick, delclock - Extract
dispatch/cmd_votes_timers.py(155 lines): vote, pick, endvote, timer - Extract
dispatch/cmd_player.py(112 lines): away, back, chooseboon, roll - Extract
dispatch/tracking.py(125 lines): post-message state tracking - Extract
dispatch/help_text.py(94 lines): _HELP_TEXT constant - Extract
players/management.py(109 lines): handle_kick, handle_addplayer - checker.py: 1632 → 277 lines (−1355)
- Extract
helpers_pkg/constants.py(60 lines): all config constants - Extract
helpers_pkg/config.py(180 lines): load, validate, settings, GM helpers - Extract
helpers_pkg/formatting.py(146 lines): display_name, rank_icon, fmt_*, html_escape, etc. - Extract
helpers_pkg/time_utils.py(128 lines): intervals, timestamps, gaps, away - Extract
helpers_pkg/topic_maps.py(92 lines): TopicMaps class, build_topic_maps - Extract
helpers_pkg/dice.py(101 lines): roll_dice - Extract
helpers_pkg/dc_lookup.py(127 lines): DC tables and lookup - Extract
helpers_pkg/mechanics.py(124 lines): timer, HP, clock, streak, health helpers.py→ thin 49-line re-export facade (zero external import changes needed)- Extract
helpers_pkg/character.py(character names, away tracking) - Extract
helpers_pkg/trackers.py(hp_bar, clock_display, timers)
- test_checker.py (5053 lines) uses backward-compat aliases in checker.py
- Splitting would require a test framework or shared runner — deferred to future session
- test_helpers.py (385 lines) and test_import_history.py (334 lines) are test-only, not production code
- Rewrote checker.py as clean orchestrator (201 lines: orchestration + backward-compat test aliases)
- Verified all production files ≤200 lines (43 modules)
- Remaining minor overages: post_changelog.py (207), import_history.py (312) — standalone utilities, not core bot
- Extracted
boons/resolution.py(101 lines) fromboons/handler.py(214 → 138): boon result formatting, campaign-name resolution, storage,_resolve_boon. - Extracted
scheduled/potw_links.py(57 lines) fromscheduled/potw.py(205 → 169): transcript post-link lookup (_find_player_post_links,_ENTRY_RE,_LOGS_DIR). - Both originals re-export the moved names, so
boons.handler.*/scheduled.potw.*imports,compataliases, and test patch targets resolve unchanged. The three_LOGS_DIR-redirect test patches were repointed toscheduled.potw_links. - ✅ v4.0.0 modularization complete: every production module is ≤200 lines.
Completed in v4.18.0. State is now stored in four partition files under
data/state/ and committed to the repo every hourly run. The gist is
retained as an emergency dual-write backup. See CHANGELOG for details.
Completed in v4.21.0–v4.25.0. C11 runs in a separate Telegram group
(-1003496373617). Full PBP tracking, weekly session poll (Mon–Sun,
multiple choice, Sunday start, pinned, daily link with @mentions), and
cross-campaign live vote notifications with C01. Both polls start Sunday
7am UTC. See CHANGELOG v4.21.0–v4.25.0 for full details.
C11 player roster: 8 players. Real Telegram IDs confirmed for
Sparkleslayer, Jack, Natasha, Craig. Remaining 4 (Malia, Safrah, Rino,
Elicia) auto-capture on next vote via promote_poll_voters.py.
IDs still pending for 4 C11 players (placeholders in config):
@molluggg (Molly Gibb), @Brookm126 (Brook),
@Luke_Skillen (Luke Skillen), @EliciaRoseT (Elicia Rose Taylor).
Patrick Coxx (@Thefununlce, placeholder 9100000006) also pending.
IDs auto-captured when they vote. Run promote_poll_voters.py --commit after.
Note: 9100000xxx range now detected — fixed 2026-04-08.
- Optional AI-generated "story so far" recap using Anthropic API
- Posts to chat topic on a configurable schedule
- Requires ANTHROPIC_API_KEY secret (see removed pbp_summary_feature.py for prior implementation)
- Allow per-campaign timezone config
- Schedule posts for reasonable local times instead of UTC cron
- Display "last post" times in local timezone
Dated log of discovered bugs — open and resolved.
| Date | Status | Description | Fix |
|---|---|---|---|
| 2026-03-27 | ✅ Fixed 2026-03-27 | Unknown voter UIDs displayed as raw numbers in poll pings | Added "Unknown (uid)" fallback; added DragonFox2000 to C01 config |
| 2026-03-27 | ✅ Fixed 2026-03-27 | None.items() crash in session_poll.py when poll_user_names is null |
Guard added |
| 2026-03-27 | ✅ Fixed 2026-03-27 | Missing build_hp_tracker import in cmd_conditions_hp.py (would NameError in production) |
Import added |
| 2026-04-06 | ✅ Fixed 2026-04-06 | C11 queue links broken — private group requires t.me/c/… not t.me/Path_Wars/… |
_build_link() added to queue_scan.py using per-campaign group_username |
| 2026-04-06 | ✅ Fixed 2026-04-06 | "Send failed ×7" — unpin_message logs 400 errors when Telegram already auto-unpinned expired polls |
suppress_errors param added to _post; unpin and delete silently ignore "message not found" |
| 2026-04-08 | ✅ Fixed 2026-04-08 | Poll week number wrong — Sunday W14 poll labelled "W14/52" instead of "W15/52" | Use Monday's ISO week number when posting on Sunday |
| 2026-04-08 | ✅ Fixed 2026-04-08 | Vote notification dates drift mid-week — Monday shown as Apr 14 instead of Apr 6 | Poll options stored in state at creation; all notifications use stored options |
| 2026-04-08 | ✅ Fixed 2026-04-08 | promote_poll_voters.py missed 9100000xxx placeholder range (Patrick Coxx) |
Expanded _is_placeholder to cover 9100000xxx |
| 2026-04-08 | ✅ Fixed 2026-04-08 | Telegram rate-limit warnings from per-topic queue bursts | 1s sleep between campaign posts in topic_queue_poster.py |
| 2026-04-08 | ✅ Fixed 2026-04-08 | Diagnostic showed Worker ID: {...} as rate-limit context — GitHub Actions infra lines not filtered correctly |
Fixed filter to run on timestamp-stripped line; expanded infra prefix list |
| 2026-04-09 | ✅ Fixed 2026-04-09 | Per-topic queue posted to wrong thread in multi-topic campaigns (C06 Kibwe combat entries posted to PBP thread) | Added thread_id to scan entries; poster groups by thread and posts to each separately |
| 2026-04-13 | ✅ Fixed 2026-04-13 | Diagnostic showing git credential file paths as rate-limit context | Added path-based filter for /home/runner and git-credentials- lines; preview bumped to 200 chars |
| 2026-04-15 | ✅ Closed 2026-04-15 | C09 Metal City CHAT topic 104202 — decision on whether to track |
Won't track. OOC/general discussion channel, not RP content. Queue, inactivity, and milestones don't apply. |
| 2026-06-15 | ✅ Fixed 2026-06-15 | Bot unpinned posts it didn't own — GM pins vanished when a thread's queue first posted | _post_thread_queue empty-slot branch called unpin_all_messages (Telegram unpinAllChatMessages, which ignores message_thread_id and clears pins group-wide). Removed the call; bot now only ever unpins a specific message id it pinned itself |
| 2026-04-15 | ✅ Closed 2026-04-15 | C11 unknown poll UIDs 7754924188, 6464119705, 6234551152 |
No action needed — promote_poll_voters.py auto-captures real UIDs on next Sunday vote. |
Ideas and feedback welcome in the Foundry & GitHub topic or via GitHub issues.
All code must maintain 100% test coverage. See docs/testing.md
for the full testing guide, including the critical requirement to test
falsy/absent inputs for every fallback path.
Key rule: 100% coverage does not mean 100% correct. For any function
with a fallback (entry.get("x", ""), if value: ... else: ...), tests
must include cases where the primary value is None, missing, or empty —
not just the happy path. Untested fallback paths are how bugs ship despite
full coverage.