Changelog - Version 5.8 (2026-10-13)
Version Information
- Updated version from 5.7.29 to 5.8
- Major architecture improvements and custom bot support
Improvements
!permitsong- Mods and the broadcaster can run
!permitsong @usernameto give that viewer one request past the same-song gap. - The permit is kept until their next request that would have been refused as played too soon. A normal request does not use it up. A bot restart clears unused permits.
- Mods and the broadcaster can run
- Same song request gap
- The same song cannot be requested again until 720 minutes have passed. The requester is told “This song has been played too soon.”
- 0 turns the check off. A shorter gap tells the requester the song was requested less than that many minutes ago.
- Bot creator welcome
- The first time the bot creator chats in a channel while the stream is online, and they are not already in that channel’s
seen_userstable, the bot posts a one-time hello that names them as its creator. - That hello still goes out when the channel has welcome messages turned off. Later visits use the channel’s normal welcome. Chatting while the stream is offline does not use up the hello.
- The first time the bot creator chats in a channel while the stream is online, and they are not already in that channel’s
- Rerun notice in chat
- While a scheduled VOD rerun is playing on the channel, the bot @ mentions each chatter once to let them know it is a rerun of a past stream and the streamer may be away from keyboard.
- Each viewer is told only once per rerun; the streamer’s own messages never trigger it, and nothing is sent outside a rerun.
- Who has been told is saved in the new per-user
rerun_noticestable, so a bot restart mid-rerun does not repeat the notice.
- Viewer birthdays
- Mods and the broadcaster save a viewer’s birthday with
!addbirthday @user DD/MM. Viewers use!mybirthday(shows their saved date, or saves one if none is set) and!updatebirthday DD/MM. - On a viewer’s birthday (checked in the channel’s timezone), their first chat of the stream gets a
Happy Birthday, @user! 🎂message from the bot. 29 February birthdays are celebrated on 28 February in non-leap years. - The wish is its own chat message, sent after the welcome message. A long welcome can never push it past the 500-character limit, and it is still sent when welcome messages are turned off.
!addbirthdayuses a per-user cooldown so one mod adding birthdays does not lock the other mods out.- Birthdays are stored in the per-user
birthdaystable (keyed by Twitch user ID) and listed on the new Birthdays dashboard page (Community menu).
- Mods and the broadcaster save a viewer’s birthday with
- Per-artist song request cap
- The media player global artist limit still applies to everyone (or to the include/exclude list).
- Each artist on the list can now store their own number. Blank keeps the global cap. A number replaces it for that artist, including 0 for no cap on them.
- Spotify and YouTube still share one counter. Chat enforcement starts after the bot is restarted.
- Ad-end chat notice survives a bot reboot
- When a mid-roll starts, the bot now writes
started_at,duration_seconds, andeta_endto the per-userad_break_statetable. - After a restart while live, it reads that row: if
eta_endis still ahead, the welcome-back line is scheduled for that time; if it already passed, the line is sent immediately. The row is markedend_notice_sentso a second restart cannot double-post. - Stream-offline and leftover rows from a previous stream are marked sent without chatting.
- When a mid-roll starts, the bot now writes
- Editable success chat for flavour builtins
- Builtin Commands edit modal now shows chat templates for success lines that are the streamer’s voice: deaths, lurk/unlurk/lurking, hug/kiss/highfive, game, uptime, followage, watchtime, points, mybits, subscription, ping, bot, roadmap, cheerleader, and pet feed/play/sad/sleep.
- Seeded with the current default using
(var)tokens. Lists, error trees, and system/ops commands stay hardcoded. - Per-user
command_optionsrows are seeded fromdashboard/includes/usr_database.php(same pattern astwitch_chat_alerts). Dashboard field labels/vars come fromapi/builtin_commands.json. The bot fills(var)tokens from those rows.
- Custom
!deathadd/!deathremovechat messages- Builtin Commands edit modal can now change the chat line those commands send.
- The field is seeded with the current default text using custom-command variables:
(game),(deaths),(deaths.total),(deaths.stream),(arg),(user). - Stored in per-user
command_optionsJSON (message). Empty or missing falls back to the built-in default.
- Express TTS
- TTS Settings now has a Normal / Expressive style. Normal keeps the existing voice and language. Expressive shows a separate voice list and follows the viewer’s language automatically.
- Expressive reads ALL CAPS as shouting and
lol/hahaas laughter. If expressive generation fails, that line falls back to the saved Normal voice. - Per-user
tts_settingsgainsstyleandexpressive_voiceviausr_database.php.
!lurkingnot-lurking message- When a viewer checks
!lurkingbut is not in lurk mode, the reply now tells them how to start: you’re not currently lurking. To lurk, use the !lurk command.
- When a viewer checks
Bug Fixes
- API key kept out of overlay notice web addresses
- The bot sends the channel’s API key to the websocket server in the
X-API-KEYheader instead of the/notifyweb address, so it no longer lands in server access logs.
- The bot sends the channel’s API key to the websocket server in the
- Song requester survives a bot restart
- The Spotify track ID and an in-queue flag are now stored on
song_request_analytics, so!skipstill knows who requested the current song after the bot restarts.
- The Spotify track ID and an in-queue flag are now stored on
!scheduleno longer treats a same-day replacement as a cancellation- Cancelling one occurrence of a recurring Twitch schedule slot and adding a one-off later the same day produced two chat lines: the cancelled occurrence, then the real next stream.
- The command now skips cancelled segments when picking the next stream, and only announces a cancellation when that occurrence is not replaced by another stream on the same local calendar day.
- Chat spelling is cancelled. Helix fetch raised from 3 to 25 segments so cancelled slots cannot hide the real next stream.
!sono longer double-queues a user who is already waiting- The shoutout queue did not track who was already waiting, so
!soon a user already queued (for example after a welcome(shoutout)) added a second entry. When that user reached the front, chat posted the same shoutout twice. - Outgoing shoutouts now record the target user ID while they are queued or in-flight. A second
!sois rejected and the mod is told@mod, @user is already in the shoutout queue.Auto sources (raid, welcome, variables) also skip instead of stacking. - Incoming “they shouted us out” notices are not treated as the same queue slot, so they still post.
- The shoutout queue did not track who was already waiting, so
- Stream Bounty bits now fan out as
TWITCH_CHEER- Extra cards and extra votes paid with bits already posted chat and wrote
stream_credits/bits_data, but they never emitted the bits websocket event. - Twitch
channel.bits.usedoes not include extension transactions, so Discord logs, Specter Alerts, and the dashboard ticker never saw those spends. - Both
EXTRA_CARD_WITH_BITSandEXTRA_VOTE_WITH_BITSnow also sendTWITCH_CHEERwith the same payload as a normal cheer. Credits and chat are unchanged (not re-run throughprocess_cheer_event).
- Extra cards and extra votes paid with bits already posted chat and wrote
- Custom command cooldown bucket
- Custom Commands page only exposed cooldown seconds; streamers could not choose global vs per-user vs mod buckets (builtin commands already could).
- Added
cooldown_bucketoncustom_commands(auto-migrated viausr_database.php), add/edit UI + table column ondashboard/custom_commands.php, and the bot now honorsdefault/user/modviaresolve_cooldown_bucket_key.
New Features & Major Changes
- Working & Study Task List
- Viewers manage their own tasks on the Working & Study overlay from chat.
!task <title>sets the task you are working on now, and any task you already had moves to the front of your backlog. - Backlog commands:
!laterqueues at the end,!soonqueues at the front,!now <title|n|skip>switches the active task,!backloglists the queue, and!mytasksshows the active task plus as many backlog titles as fit in one message. Use;to add several tasks at once. !donefinishes the active task,!done <n>finishes backlog item #n,!done 2; 4; 5finishes several, and!done nextfinishes the active task and promotes backlog #1.!rename,!remove, and!taskclear(clears the done list) round out the set.!taskhelpposts a quick guide.- Per-viewer projects:
!project <name>switches project (created on first use),!project move,!project rename,!project delete, and!project clear.!projectslists projects with their open task counts. - Overlay badges use each viewer’s backlog position, not the global task ID. Task commands always have no cooldown.
- Viewers manage their own tasks on the Working & Study overlay from chat.
- Personal Timers
!timer <minutes> <title>is a general countdown that pings you in chat.!timer <minutes> "title" focusruns a focus block and adds it to the task list.!timer <work>/<break>/<cycles> [label]runs a multi-cycle focus/break session.!checktimershows the time left,!timer stopcancels, and!timerhelpexplains every mode. Aliases:!personaltimer,!ptimer,!mytimer,!focus.- The bot stores each timer’s phase end time in the per-user
user_pomostable, so timers resume (and catch up on missed phases) after a bot restart. The websocket server only relays overlay updates. !tasktimer(mods and the broadcaster, aliases!ttimer/!stimer) controls the streamer’s own overlay timer: start, stop, pause, resume, auto-pilot, and focus/break/cycle lengths.
- Point Store
- Viewers spend bot points (not Twitch channel points) on items the streamer sets up: sound alerts, video alerts, TTS, or chat messages.
!storelists items and!store <name|id>buys one. Viewers can also shop atmembers.botofthespecter.com/{channel}/store. Items and store settings (enabled, paused, live-only, cooldowns) are managed on the new Point Store dashboard page.- Points are debited in one atomic update before anything is played, and checkouts are handled one at a time per viewer so two quick purchases cannot both slip past a limit.
- Purchases emit a new
STOREwebsocket event alongside the matching sound, video, or TTS event, so existing overlays play the item straight away.
- Interactive Channel Pet
- A corner companion overlay with happiness, hunger, energy, levels, and XP. Streamers upload sprite sheets or GIFs, or pick one of the free starter packs (Specter, Specter Bot, cat, dog, and more).
- Chat keywords, commands, Twitch events, gift subs, and channel-point rewards can trigger reactions. Viewers use
!pet(status),!feed,!play,!sad, and!sleep(restores energy). - The bot announces XP gains, can post scheduled pet sayings, and sends low-stat chat alerts when energy, hunger, or happiness drops to a set threshold.
- The pet logic lives in a shared
bot/modules/pet.py. Configured from the new Pet dashboard page.
- Game Tracker
- On the new Tracked Games dashboard page, streamers keep a per-channel list of games marked playing, on hold, finished, dropped, or 100%. The bot auto-adds the live Twitch category (minus an editable denylist) and counts session time.
!gamenow shows play time and status for tracked games. Mods can use!game done,!game hold,!game drop,!game 100, and!game time 12h30m, and!game listlists the tracked games.
- Channel Warnings
- Mods and the broadcaster can issue
!warn @user <reason>(alias!warning). Warnings are stored in the per-userwarningstable and reviewed or cleared on the new Channel Moderation dashboard page (Analytics).
- Mods and the broadcaster can issue
- Makers & Crafting
- New Makers & Crafting overlay and dashboard page.
!craftmanages it from chat: set the current project, add notes, links and images, mark projects finished, queue upcoming ideas, and switch the display mode. The overlay updates live.
- New Makers & Crafting overlay and dashboard page.
- Custom Command Aliases
- Custom commands can have comma-separated aliases, set on the Custom Commands add/edit form. An alias runs the main command and shares its cooldown.
- Builtin Command Cooldowns
- Every builtin command reads its rate, time, and bucket from
builtin_commands, so the dashboard settings always apply. Remaining wait time is always shown as at least 1 second. - Social and self-targeted commands (hug, kiss, highfive, lurk, points, games, raffle entry, pet, birthdays, and similar) use a per-user cooldown so one viewer cannot lock a command for everyone.
!clip,!joke, and!pingstay on the shared cooldown. Existing channels are moved over when the bot starts.
- Every builtin command reads its rate, time, and bucket from
- Media Player Song Requests (YouTube fallback)
- When Spotify is not linked,
!songrequestresolves YouTube links or searches and adds them to the dashboard Media Player queue, with the same guardrails as Spotify requests. Mods can remove queued songs with!removesong <position>. - Each artist on the artist list can have their own request cap, and streamers choose whether the cap applies to everyone or only to listed artists.
- When Spotify is not linked,
- Known Bots Registry
- The bot loads an admin-managed list of known bots from
website.known_bots(with a built-in fallback list). Known bots are counted but never welcomed or auto-shouted-out.
- The bot loads an admin-managed list of known bots from
- Ad Breaks: Raid Auto-Snooze and Specter Alerts Card
- When a raid arrives, the bot can snooze the next ad automatically (configured in the ad notice settings). The generic “ad snoozed” chat line is skipped for these bot-initiated snoozes.
- The bot emits
TWITCH_AD_BREAK/TWITCH_AD_BREAK_ENDso Specter Alerts can show an ad-break card with a live countdown. This works whether or not the chat ad notices are turned on.
- Scheduled Timed Messages
- A new scheduled trigger type posts a timed message at a set time of day in the streamer’s timezone, once per day, only while the stream is live.
- Shoutout History
- Every shoutout that is sent is recorded in a new
shoutout_historytable, which the API and dashboard use for shoutout counts.
- Every shoutout that is sent is recorded in a new
- Twitch Charity and Gift-Sub Events
- Charity donations (with correct decimal handling) and gift subs are sent to the websocket as
TWITCH_CHARITYandTWITCH_GIFT_SUB, for overlays and Discord.
- Charity donations (with correct decimal handling) and gift subs are sent to the websocket as
- More Custom Command Variables
(count.<name>)named counters and(clearcount.<name>)to reset one.(count),(count_name), and(usercount)always add exactly 1 per use, so viewers cannot change the increment.(storeredeem)records a channel-point redemption in the newstored_redeemstable, readable throughGET /channel/twitch/redeems/store.
!puzzledone- Mods can add one to the completed Tanggle puzzle count by hand when the automatic completion notice is missed.
- Custom Bot Mode Support
- Added support for running the bot with custom bot accounts via database credentials
- New
-botusernamecommand line argument for specifying custom bot username - Automatic credential fetching for the custom bot account
- Comprehensive validation checks before bot initialization
- Custom bots now show the Chat Bot badge in chat and appear under “Chat Bots” in the viewers list
- BotOfTheSpecter maintains a separate IRC presence in the channel when running in custom mode, so it always appears in the viewers list alongside the custom bot
- Chat sending in custom mode uses the shared app access token so the Chat Bot badge stays. The custom bot’s own user token is only used to keep IRC presence (so it appears in the viewers list).
- Robust IRC presence loop: Rewrote
twitch_irc_presence()with proper auth-failure backoff, capability-handshake handling, and notice-id classification -msg_banned/msg_channel_blocked/msg_suspendedetc. trigger a 10-minute back-off, account-action notices (msg_requires_verified_phone_number, etc.) fail loudly with a manual-action message, and room-mode notices are silently consumed. Watch-streak IRC NOTICEs are explicitly ignored - the bot uses thechannel.chat.notificationEventSub event as the source of truth. Self, custom, and default modes each use the matching bot credentials; custom-bot tokens retry on 401. The function now acceptsoverride_nick/override_tokenso a secondary BotOfTheSpecter presence can run alongside a custom bot from the same loop.
- Per-Viewer Song Request Limits & Pruning
- Integrated
evaluate_guardrailsvalidation frommedia_helpers.pyfor Spotify song requests insongrequest_command. - Spotify requests now validate against the unified
media_request_settingsconfiguration (checking maximum duration, queue length, per-viewer limit, and keyword/track ban lists). - Implemented real-time request tracking list pruning in
songrequest_commandby querying the active Spotify player queue, ensuring that viewer request limits are updated immediately as songs play or get skipped. - Fixed a bug where currently playing tracks were prematurely pruned from the
song_requestscache (since they exit the future queue array returned by Spotify). Whitelisted the activecurrently_playingtrack to ensure that the requester can successfully use the!skipcommand on their own song. - Added case-insensitive username matching (
.lower() == .lower()) and safety checks for empty item structures when querying active playback inskipsong_command.
- Integrated
- Per-Channel Module System
- New
custom_channel_modules/package allows packaging channel-specific behavior (commands, event handlers, websocket interceptors) as Python modules without touching the core bot. - On startup, each module’s
claims_channel(CHANNEL_NAME)is consulted; modules that claim the active channel are instantiated and have their tables ensured viaensure_tables(). - Modules expose optional hooks:
handle_<event>(...)coroutines (dispatched viadispatch_module_event),is_module_command/handle_module_command, andis_bureau_command/handle_bureau_command(dispatched viadispatch_module_commandfor every chat line). websocket_notice(..., interceptable=True)lets a module preempt or augment internal websocket emits before they fan out.- Currently bundled modules:
botofthespecterandgfaundead.
- New
- EventSub WebSocket Robustness Improvements
session_reconnecthandling: The bot now correctly handles server-initiated reconnects. When Twitch sends asession_reconnectmessage it includes a new WebSocket URL; the bot connects to that URL immediately (without resubscribing - existing subscriptions transfer automatically) instead of falling back to the hardcoded default URI. A newEventSubReconnectexception propagates the URL fromtwitch_receive_messages()totwitch_eventsub(), which sets the next connection target and loops without delay.revocationhandling: Subscription revocations (e.g.chat_user_banned,user_removed,authorization_revoked) are now detected and logged at ERROR level with the subscription type, status, and ID. When achannel.chat.*subscription is revoked because the bot was banned or removed from chat, an additional error log is emitted so the issue is immediately visible in logs.- Session-id persistence: Each new EventSub session id (initial connect or post-reconnect) is upserted into the
eventsub_sessionstable withsession_name = "{SYSTEM} Bot"andlast_updated = CURRENT_TIMESTAMP, giving the dashboard a live view of the active EventSub session. - Fixed chat EventSub subscriptions failing with HTTP 400:
channel.chat.message,channel.chat.notification, andchannel.chat.user_message_holdwere being subscribed using an App Access Token, which Twitch rejects for WebSocket transport (App tokens are only valid for webhook transport). All EventSub subscriptions now always use the broadcaster’s User Access Token, resolving the “invalid transport and auth combination” error.
- Fixed Duplicate Gift Sub Chat Messages
- When a streamer gifted multiple subs to the community, the bot was sending one chat message and stream marker per recipient (e.g. 10 gifted subs → 10 identical messages). This was caused by the
sub_giftEventSub notification firing once per recipient in addition to the singlecommunity_sub_giftevent. - Individual
sub_giftevents that belong to a community gift batch (identified by acommunity_gift_idin the payload) are now silently skipped. Thecommunity_sub_giftevent continues to handle the single aggregate “X gifted N subs” message and stream marker as before. - Individually targeted gift subs (no
community_gift_id) are unaffected and still produce their own alert. - The recipient-tracking dict (
gift_sub_recipients) used to suppress duplicate sub alerts within the 30-second window is now pruned by acleanup_gift_sub_tracking()background task that runs every 60 seconds, so the dict can’t grow unbounded during a long stream.
- When a streamer gifted multiple subs to the community, the bot was sending one chat message and stream marker per recipient (e.g. 10 gifted subs → 10 identical messages). This was caused by the
- AI-Powered Ad Break Notifications
- Uses the bot’s OpenAI chat model (
OPENAI_MODEL) to generate contextual ad break messages - AI analyzes recent chat history to create personalized ad break announcements
analyze_chat_vibe()runs a fast keyword scan over recent chat (coding/game/music/hype topics, exclamation/emoji density, caps usage) to produce a tone label (hype/focused/gamey/neutral) that is appended to the AI prompt - so the AI references topics that actually came up rather than inventing unrelated activities.- Tracks ad break count per stream session for smarter messaging
- Premium tier 2+ feature with automatic fallback to standard messages
- Resets ad-break chat history on stream-session resets and clears it when the first AI ad-break response is produced, preventing stale context from accumulating across streams
- Filters out timed messages from AI context while preserving command responses
- Includes stream title and game context for better awareness
- Restricted ad schedule checking and notifications to be triggered exclusively through the main
handle_upcoming_adslooped task, removing the redundant post-ad background polling tasks to prevent duplicate notification triggers. - Corrected looped task lifecycle: the
handle_upcoming_adstask is now started immediately when the stream goes online and cancelled/ended immediately when the stream goes offline. - Increased ad-check responsiveness by reducing the main loop check interval to 10 seconds, improving reliability of upcoming ad notifications and reducing missed or late alerts.
- Improved error handling and logging for ad polling: non-200 responses and JSON parse failures are logged and retried; duplicate notifications are now deduplicated by the actual
next_ad_atvalue to prevent repeated warnings for the same scheduled ad (including startup/restart edge cases). - Fixed delayed ad-break end notice: The end-of-ad countdown timer is now started immediately when the ad-break event is received (before any AI processing begins). Previously the timer only started after the AI start-message had been generated and sent, causing the “ad has ended” notice to fire several seconds late.
- Uses the bot’s OpenAI chat model (
- Enhanced Ad Break Configuration
- Added individual toggle controls for each ad message type (upcoming, start, end, snoozed)
- New database fields:
enable_upcoming_ad_message,enable_start_ad_message,enable_end_ad_message,enable_snoozed_ad_message,enable_ai_ad_breaks - Improved ad break messaging logic with granular control
- Better logging for ad notification debugging
- Unified Points Management System
- New
manage_user_points()function for centralized point operations - Supports get, credit, and debit actions with consistent return structure
- Automatic user creation in bot_points table if not exists
- Comprehensive error handling with detailed response objects
- New
- Improved Shoutout System
- New
get_shoutout_message()function with game information integration - Different behavior for raid vs command-triggered shoutouts
- Fetches latest stream game automatically for richer shoutout messages
- Graceful fallback when game information unavailable
(shoutout.<login>)placeholder: Custom commands, channel-point rewards, and timed messages can now silently queue a shoutout for a named user via the dynamic-variable engine. The placeholder is stripped from chat output and the standard automated-shoutout cooldown applies.cleanup_expired_shoutouts()background task runs every 5 minutes, deleting both in-memory andautomated_shoutout_trackingrows older than the configured cooldown window, so stale per-user shoutout tracking can’t accumulate across streams.- Fixed duplicate automated shoutout chat messages during cooldown windows
- Automated shoutout queue requests now return whether a shoutout was actually queued.
- Chat shoutout text is only sent when queueing succeeds, preventing duplicate shoutout lines when users are still on the 60-minute automated/Twitch cooldown (for example: raid auto-shoutout followed by a walk-on
(shoutout)trigger). - Added explicit log entries when a shoutout message is suppressed due to automated cooldown, including source context (welcome_message, raid, cheer, subscription, subscription_message, follow).
- Fixed shoutout queue blocking bug: Reordered cooldown checks to prevent queue blocking when users are on per-user cooldown
- Per-user cooldown (60 min) now checked before global cooldown (2 min) wait
- Users on cooldown are skipped immediately without blocking other users in queue
- Allows multiple different users to be shouted out every 2 minutes while respecting per-user limits
- Fixed
channel.shoutout.createEventSub notification handling- Fixed bug where
shoutout_user.lower()was being called on a dictionary - Now properly detects command-triggered vs manual/UI-triggered shoutouts
- Command-triggered shoutouts skip EventSub message to prevent duplicates
- Manual/UI-triggered shoutouts now properly send chat messages via EventSub
- Uses
get_shoutout_message()function with “eventsub” action for consistent messaging - Fixed typo: “inforamtion” → “information” in error message
- Fixed bug where
- New
- Stream Watch Streak Notifications
- Added handling for Twitch’s
watch_streaknotice type in thechannel.chat.notificationEventSub event, matching the existing display in the YourChat system. - When a viewer achieves a watch streak milestone (N consecutive streams watched), the bot now announces it in chat.
- Message is configurable via the
twitch_chat_alertstable usingalert_type = 'watch_streak'; supported placeholders:(user),(value)(current consecutive count), and(total)(lifetime total streams watched). - Default message:
Congrats (user) on watching (value) consecutive streams!(uses the longer form including(total)once the user has prior history). - Streak losses are detected: when the new streak count is lower than the stored value the bot sends a sympathetic “We’re sorry you lost your N stream streak…” message (using the previous streak length, current count, and new lifetime total).
- Database: the
analytic_stream_watch_streaktable is auto-migrated to addhighest_streakandtotal_streams_watchedcolumns on first run. - Events are logged to the event log for auditing.
- Added handling for Twitch’s
- Pronouns Placeholder Support
- Added
(pronouns),(pronouns.they), and(pronouns.them)placeholders, resolved from thepronouns.alejo.iov1 API. - Available in raid alerts, cheer alerts, subscription alerts, gift-sub alerts, and any custom chat alert that runs through the dynamic-variable pipeline.
- Per-user pronouns are cached for 1 hour (
USER_PRONOUNS_CACHE_TTL = 3600); the master pronoun map is cached for 24 hours, keeping API traffic light. - Graceful fallback: anonymous gifters and users with no pronouns set resolve to
they/them(and split parts tothey/them).
- Added
- Channel-point VIP grants
- Added support for channel-point reward placeholders
(vip)and(vip.today)in rewardcustom_message.(vip)- immediately grants the redeemer a Twitch VIP (uses Helix POST/channels/vips).(vip.today)- grants VIP and records the redeemer in a per-uservip_todaytable; the bot will automatically remove these VIPs when the stream goes offline (Helix DELETE/channels/vips).
- Note:
(vip)and(vip.today)only resolve inside a channel-point rewardcustom_message(the bot uses the redeemer’s user id from the redemption payload as the VIP target). They are no-ops in other contexts (custom commands, timed messages, alerts). - Database: per-channel
vip_todaytable - the bot writes(user_id, username)(the dashboard schema also includes auditing columns likeadded_atthat the bot does not read). - Safety: bot logs failures, notifies chat on error, and prunes
vip_todayafter attempting removals to avoid retries. - Rate-limits: removals are rate-aware (small delay between deletes) and failures are logged for manual follow-up.
- Use case: add
(vip.today)to a channel-point reward message to grant a time-limited VIP until stream end.
- Added support for channel-point reward placeholders
- Channel-point
(redeem.*)placeholders- New placeholders for channel-point reward
custom_message:(redeem.title),(redeem.input),(redeem.cost),(redeem.prompt),(redeem.id),(redeem.status),(redeem.redeemed_at). They expose the full redemption payload (title, the user’s typed input, cost, prompt text, redemption id/status, timestamp) to reward custom messages.
- New placeholders for channel-point reward
- Raffle System
- Added a new raffle system enabling streamers to run raffles via both chat and the dashboard.
- Entry method: free chat command (
!joinraffle); raffles may be started with an optionalweightedflag so subscribers/VIPs receive improved odds. - Commands:
!createraffle <name> <prize> <number_of_winners> [weighted]to create a scheduled raffle,!startraffle [raffle_id]to start it,!joinraffleto enter,!leaveraffleto remove your entry,!stopraffle(mod-only) to end a running raffle without drawing, and!drawraffle [raffle_id]for mods to draw winners. - Entry-rule schema (stored on the raffle row, configurable via the dashboard):
- Per-tier weights for weighted raffles - Tier 1 ×2.00, Tier 2 ×3.00, Tier 3 ×4.00, VIP ×1.50 by default (
weight_sub_t1/weight_sub_t2/weight_sub_t3/weight_vip). - Optional
exclude_mods,subscribers_only,followers_onlyflags enforced at!joinraffle. - Optional minimum-follow-duration rule (
followers_min_enabled/followers_min_value/followers_min_unitwhere unit isdays/weeks/months/years) - uses the newhas_followed_minimum_duration()helper to gate entries.
- Per-tier weights for weighted raffles - Tier 1 ×2.00, Tier 2 ×3.00, Tier 3 ×4.00, VIP ×1.50 by default (
- Dashboard: New
dashboard/raffles.phppage to create, view, and draw raffles from the UI (draw triggers a websocket notify). - API/WebSocket: Added
/websocket/raffle_winnerAPI endpoint andRAFFLE_WINNERwebsocket event; drawer updates database, bot announces winner in chat and notifies overlays. - Database: Added per-channel tables
rafflesandraffle_entriesto store raffle metadata and entries. - Notes/Future: CSV export of entries, scheduled auto-draw and channel-points/bot-points entry costs are planned as optional enhancements.
- Fortune & Lotto Reward Messaging Fixes
- Fortune reward chat format: Fixed channel-point
(fortune)responses so chat output is formatted asusername, fortune message, making it clear who the fortune is for. - Weighted lotto user tickets: Updated user lotto-number generation to be weighted using the current stream winning numbers from
stream_lotto_winning_numbers, guaranteeing at least one points-eligible result per play (minimum Division 6 threshold).
- Fortune reward chat format: Fixed channel-point
- Raid Analytics & Outgoing Raid Tracking
- Raid notices used for analytics are sourced from Twitch EventSub
channel.raidnotifications (bothto_broadcaster_user_idfor received raids andfrom_broadcaster_user_idfor outgoing raids). - Incoming raids are tracked in
raid_dataandanalytic_raids(source: ‘received’): raid counts and viewer totals are aggregated per raider, and stream credits are recorded. - Outgoing (sent) raids are detected when the broadcaster initiates a raid; the bot captures the viewer count, holds the raid data until the stream goes offline to ensure accurate viewer persistence, and then inserts a record into
analytic_raidswith source ‘sent’. - Outgoing raids are canceled if an ‘unraid’ is received before persistence; a background task (
wait_and_persist_outgoing_raid) waits for stream offline to persist the raid. - Points, raid alerts, stream markers, and optional shoutouts continue to be handled for raid events as before.
- Raid notices used for analytics are sourced from Twitch EventSub
- Known Users Timestamps
- Added
first_seenandlast_seenDATETIME columns to theseen_userstable (nullable). - Bot behavior:
first_seenis set to the current timestamp the first time a user is observed;last_seenis updated on every message. - Dashboard:
Known Userspage now displays First Seen/Last Seen columns and shows “Unknown” when timestamps are missing. - Migration: existing
last_seenvalues are used to populatefirst_seenwhen appropriate.
- Added
- URL Blocking System Overhaul
- Blacklist Always Active: Blacklisted URLs are now blocked regardless of URL blocking setting
- Blacklist check happens first before any other URL validation
- Sends “Code Red” alert to mods when blacklisted URL is detected
- Ensures harmful/unwanted URLs are always blocked
- Regex Support for Whitelist: Whitelist now supports full regex pattern matching
- Allows flexible URL pattern matching (e.g.,
.*\.example\.comfor subdomains) - Backwards compatible - can still use simple domain names
- Invalid regex patterns are logged and skipped gracefully
- Allows flexible URL pattern matching (e.g.,
- Improved Privilege Handling: Mods and streamers bypass URL blocking when enabled
- Clearer logging for privileged user URL allowances
- Permit command functionality preserved
- Enhanced URL Blocking Logic:
- Enabled: Removes all links except whitelisted regex patterns and Twitch links
- Disabled: Allows all URLs except blacklisted ones
- Twitch.tv and clips.twitch.tv links always allowed when URL blocking enabled
- Better Logging: More descriptive log messages for URL blocking decisions
- Logs when blacklisted URLs are detected
- Logs when URLs are allowed due to whitelist match
- Logs when URLs are allowed due to mod/streamer privilege
- Logs when non-whitelisted URLs are blocked
- Blacklist Always Active: Blacklisted URLs are now blocked regardless of URL blocking setting
- Text Term Blocking System
- New Feature: Block specific words from chat
- Messages containing blocked terms are automatically deleted
- Case-insensitive matching (e.g., “badword”, “BADWORD”, “BadWord” all match)
- Independent enable/disable toggle separate from URL blocking
- Single word per entry validation (no spaces allowed)
- Database Tables:
- Added
term_blockingcolumn toprotectiontable (VARCHAR(500), default: ‘False’) - New
blocked_termstable to store blocked terms list
- Added
- Bot Logic:
- Scans all chat messages for blocked terms when enabled
- Deletes messages containing blocked terms instantly
- Sends notification to user explaining removal
- Independent from URL blocking - can be enabled separately
- Bug fix: Fixed intermittent “Cursor closed” errors by using a dedicated DB connection+cursor for blocked-terms checks (no longer reuses cursors that may have been released).
- Word Replacer Module
- New syllable-swap fun module: the bot occasionally re-posts a viewer’s chat line with random syllables swapped for a streamer-chosen word (for example
How has your day been?→How has your fun been?). The original message stays in chat; the bot adds a mangled copy underneath. This is an engagement toy, not moderation - it is fully independent from URL/term blocking. - Configurable per channel from the dashboard (Modules → Chat Protection → Word Replacer): enable/disable, replacement word (default
fun), trigger frequency (1-in-N messages, default 30), per-syllable rate (1-in-N, default 10), and an echo cooldown (default 30 s). Off by default. - Default exclusions: the random echo never targets the streamer, the bot’s own messages (timed messages, alerts, command replies), or opted-out viewers.
- Viewer opt-out: anyone can type
!wordreplaceoffto stop their messages being randomly echoed and!wordreplaceonto opt back in. These commands are always available (not gated by the module toggle and not disableable from the Builtin Commands page). Streamers/mods can also manage opted-out users and an ignored-words list from the dashboard. - Syllable splitting uses the
pyphenhyphenation dictionary with a vowel-cluster fallback for words it doesn’t recognise. ALL-CAPS words echo in ALL-CAPS, mixed-case casing is mirrored, and trailings/zplurals are preserved. - New per-channel tables
word_replace_ignored_usersandword_replace_ignored_words, plusword_replace_*columns on theprotectiontable (auto-migrated).
- New syllable-swap fun module: the bot occasionally re-posts a viewer’s chat line with random syllables swapped for a streamer-chosen word (for example
- First-Message Command Protection Enhancements
- Added support for two first-message command blocking modes:
- All commands: blocks all bot-managed commands until the user has sent a normal chat message.
- Selected commands only: blocks only commands selected in the dashboard.
- Selection supports both built-in and custom commands.
- Unknown/non-bot commands are ignored by this protection and no warning message is sent for them.
- Database updates in
protectiontable:block_first_message_command_modeblock_first_message_selected_commands
- Added support for two first-message command blocking modes:
- Welcome Message Fix (Custom Mode)
- Fixed issue where
botofthespectercould still receive welcome messages when running with a custom bot account. - Welcome flow now ignores both the default bot account and the currently active bot username in custom mode (
IGNORED_WELCOME_USERNAMES = {"botofthespecter", BOT_USERNAME.lower()}). - Applied to both normal welcome detection and first-command welcome flow.
- Fixed issue where
- Timed Message Trigger Mode Selection
- Streamers can now choose whether each timed message fires on a fixed timer (every N minutes), after a set number of chat lines, or both simultaneously.
-
A new trigger_typecolumn (timerchat_linesboth) is stored in thetimed_messagestable; existing rows default totimervia automatic migration. The ENUM is also expanded automatically on existing installs. - The bot’s
start_timed_message()function activates the interval task and/or the chat-line counter independently based ontrigger_type, allowing both to fire in parallel whenbothis selected.
- Timed Message Variable Expansion & Command Chaining
- Added support for
(command.<name>)inside timed messages and custom commands - inlines another command’s resolved response into the current message. (call.<name>)placeholder (separate from(command.<name>)): defers an actual!commandinvocation until after the main response is sent. The placeholder is stripped from output and the named built-in/custom command runs as if a moderator had typed it, so it can have its own permission checks and side effects. Optional dot-separated arguments are forwarded to the called command - e.g.(call.todolist.3)runs!todolist 3, and(call.todolist.1,2,3)runs!todolist 1,2,3.- Referenced command responses are processed through the same variable engine, so nested placeholders like
(game),(count),(random.*),(customapi.*), and(json.*)resolve before sending. - Existing timed-message placeholders remain supported, including simple cases like
Today we're playing (game).
- Added support for
(if.<condition>|true_text|false_text)Conditional Placeholder- Lets a custom command / timed message branch on resolved values. Supports
=,!=,<,>,<=,>=,contains,startswith,endswith(case-insensitive). Example:(if.(json.username) = (user)|You're authorised|You're not authorised). - Evaluated last, after all other variables have been substituted, so any other placeholder (
(json.*),(user),(count), etc.) can appear inside the condition.
- Lets a custom command / timed message branch on resolved values. Supports
(todo.add.<category>.[description])Silent Task Add- Custom commands, channel-point reward
custom_messages, and timed messages can silently add an item to the to-do list using(todo.add.1.[Buy more snacks])syntax. Category is parsed as an integer (defaults to id 1), description is the bracketed text. The placeholder is removed from the chat output.
- Custom commands, channel-point reward
Code Improvements
- IRC and EventSub Chat Deduplication
- When EventSub and IRC both report the same chat message, the bot now processes it only once. Seen message IDs are tracked for a short time, and EventSub messages are converted into the same message objects as IRC so both go through one handler.
- OpenAI Responses API
- AI chat replies now use the OpenAI Responses API instead of Chat Completions. Forbidden-phrase rules moved into the AI instructions.
- API V2 Calls
- Weather, kill, and fortune lookups use the
/v2/*API endpoints with theX-API-KEYheader instead of?api_key=query parameters, ready for the V1 API shutdown.
- Weather, kill, and fortune lookups use the
- Opt-in Custom Channel Modules
- A channel module in
custom_channel_modules/is only loaded when the module file exists,users.use_custom_moduleis enabled for that user, and the bot is started with-load-custom-module(set through the bots API and dashboard).
- A channel module in
- Smaller Fixes
- First-chat welcome messages still send if the pet trigger fails.
- Automated chat shoutouts (raid, welcome, and so on) still post when the per-user shoutout cooldown is active. Only the Twitch shoutout API call is skipped.
- Timer Command Refactored to Use TwitchIO Routine
!timerno longer holds a database connection open for the entire timer duration viaawait sleep().- The end-of-timer notification is now scheduled using
@routines.routine(seconds=..., iterations=1, wait_first=True), the same pattern used for ad-break end notices and poll half-time messages. - After inserting the timer record, the DB connection is released immediately; the routine fires after the requested duration, opens a fresh connection, confirms the timer is still active (guards against a concurrent
!stoptimer), sends the end message, and cleans up. !stoptimernow cancels the running routine (viaactive_timer_routinesdict) so the end message is never sent after a manual stop.
- Direct Per-Call Database Connections
- Replaced the previous shared/pooled MySQL handler with
DirectConnection: eachmysql_connection()call opens a freshaiomysqlconnection and closes it on release (via async context manager), eliminating stale-connection issues across long-lived tasks. _MySQLCompatshim preserves the oldmysql_handler.get_connection(db_name)interface so module code keeps working unchanged.- Added a
cleanup_idle_db_pools()background task placeholder that runs on a 30-minute loop (no-op; reserved for future pool reintroduction). - Multi-database support retained: callers pass
db_name(user DB,website,spam_pattern).
- Replaced the previous shared/pooled MySQL handler with
- Chat Message Enhancements
- Increased maximum message length from 255 to 500 characters
- Updated
send_chat_message()to support three bot modes:- Self Mode (
-self): Uses broadcaster’s own account as the bot - Custom Mode (
-custom): Uses custom bot account credentials (with App Access Token for chat sending - see Custom Bot Mode Support) - Standard Mode: Uses main “botofthespecter” bot account
- Self Mode (
- Dynamic sender_id and access_token selection based on active mode
- Automatic BOT_USERNAME assignment (channel name for self mode, custom name for custom mode)
- Improved error logging for failed message delivery
- Reliability fixes:
send_chat_message()now uses a shared aiohttpClientSession(lazy-created) to reduce transient connection churn and is closed on shutdown- Added retries and small backoff for transient network/server errors (502, 503, 504) and for transient aiohttp connection errors
- Parses response body inside the request context (avoids accessing closed response objects) and uses parsed JSON/text in logs for better diagnostics
send_long_chat_message(): New helper splits any response longer than 500 characters into multiple chat sends, breaking on the last whitespace before the limit (or hard-cutting at the limit if no space exists) with a 0.5-second gap between chunks. The dynamic-variable engine uses this for command output, so long commands no longer truncate at 500 characters.
- New Internal WebSocket Event Handlers
CUSTOM_COMMAND- accepts{command, response}from the website/API and posts the response to chat, running it through the dynamic-variable engine first so placeholders like(game),(count),(json.*)resolve. Lets the dashboard/API fire any custom command remotely.TASK_REWARD_TRIGGER- when a website “task” reward fires, the bot credits the configured points viamanage_user_points, looks up the channel’spoint_name, announces the reward in chat, and emits aTASK_REWARD_CONFIRMack with the new total.
- TwitchIO IRC Auto-Recovery
- Added an
irc_health_check()background task that pollsBOTS_TWITCH_BOT.connected_channelsevery 60 seconds. If the TwitchIO IRC connection has silently dropped (no joined channels), the task logs a warning and callsbot.connect()to re-establish IRC so chat commands continue to work without a manual restart.
- Added an
safe_create_task()for Fire-and-Forget Background Work- Added a
safe_create_task(coro)helper that wrapsasyncio.create_task()and stores the resulting task in a module-level_background_tasksset withadd_done_callbackcleanup. This prevents the asyncio garbage collector from collecting in-flight tasks (which previously surfaced as “Task was destroyed but it is pending!” warnings during shutdown and under load). Most fire-and-forget call sites - websocket notices, alert sends, shoutout removals - now use it instead of barecreate_task.
- Added a
- Event Processing
- New
process_chat_message_event()helper function for consistent event handling - Orchestrates message counting and welcome messages; point awarding is deferred to the inner
message_counting_and_welcome_messagesfinallyblock to avoid double-credit. - Simplified event processing from EventSub notifications
- Provides centralized logging for all chat message events
- New
- Command Management Reliability
- Fixed
!enablecommandand!disablecommandfailures caused by an uninitializedcooldown_bucketwhen command configuration rows were missing. - Added safe cooldown defaults and kept DB lookups/updates inside an active cursor scope to prevent runtime command execution errors.
- Improved target command parsing for these commands by normalizing input (trim, remove leading
!, lowercase). - Enhanced
(customapi.*)variable handling so placeholders inside custom API URLs (for example(user)) are resolved before the API request is made. - Improved custom API placeholder parsing to correctly handle nested parentheses in URLs, preventing truncated requests.
- Added JSON context decoding flow for custom API placeholders:
(customapi.json.<url>)now performs a silent fetch only (never posts raw JSON or fetch errors to chat).- The fetched JSON is stored in temporary in-message context and the
(customapi.json.<url>)placeholder is removed from output. (json.<path>)placeholders resolve values from that stored JSON context, including nested paths (for example(json.last_sign_in.today)).- If JSON fetch fails or a path is missing, placeholders resolve to empty output instead of leaking payload/error text to chat.
- Fixed
- Ping Command Internet Test Update
- Updated
!pingto runspeedtest --json --secureon the bot server via async subprocess. !pingnow reports Speedtest latency (ping) plus transfer totals frombytes_sentandbytes_received.- Byte counters are converted to human-readable units before being posted to chat.
- Added timeout and JSON/error handling for failed or invalid Speedtest responses.
- Updated
- Dynamic Variable Processor Consolidation
- Renamed
process_custom_command_variables()toprocess_dynamic_variables()to reflect use beyond chat custom commands. - Centralized placeholder switch detection into a shared tuple (
DYNAMIC_VARIABLE_SWITCHES) with ahas_dynamic_variables()helper, removing duplicate timed-message vs command switch lists. - Timed messages and custom-command execution now use the same detection/processing path for consistent behavior across systems.
- Renamed
- Logging Improvements
- Changed multiple
warninglevel logs toerrorfor better issue visibility - Enhanced startup logging with detailed bot configuration information
- Added comprehensive initialization logs showing channel, username, system, and version
- Better error context in ad break notification failures
- New
systemandintegrationslog streams: Added two dedicated logger categories — system (startup, shutdown, credential refresh) and integrations (Tanggle, Stream Bingo, StreamElements, StreamLabs, Fourthwall, Ko-fi, Patreon). Keepsbot.logandevent_log.logsmaller and more focused. - Sequential integration startup:
connect_to_integrations()now starts Stream Bingo, Tanggle, and tipping services with 2-second gaps between each, so the startup log shows each integration’s “connected” or “no credentials” line in a clean order instead of interleaved. - Stream Bingo & Tanggle: deduplicated “no credentials” log spam: When no Stream Bingo API key or Tanggle credentials are configured, the bot previously logged a “skipping connection” message on every 5-minute retry loop. Both integrations now log the missing-credentials message only once per absence. The flag resets as soon as valid credentials are found and a connection is established, so if credentials are later removed the message will appear once again.
- Changed multiple
- MySQL Query Updates
- Updated
INSERT ... ON DUPLICATE KEY UPDATEsyntax to MySQL 8.0+ compatible format for thewatch_timequery (AS new_watch_timerow alias) and theeveryonegreeting query (AS newrow alias). - Improved watch_time tracking query for better compatibility.
- Note:
tanggle_puzzle_statsandvip_todayON DUPLICATE KEY UPDATEclauses still use the deprecatedVALUES(col)form and remain a follow-up cleanup target for full MySQL 8.0.20+ compatibility. - Fixed
Data truncated for column 'winner_timer_seconds'warning:winner.get('timer')is now explicitly cast tointbefore the Tanggle room-completion insert, handling float values returned by the API. - Fixed
Task was destroyed but it is pending!warnings at shutdown: Thefinallyblock inmessage_counting_and_welcome_messagesnow catchesCancelledErrorand re-raises it instead of attempting async DB work while the task is being cancelled, preventing dangling pending tasks during bot shutdown.
- Updated
- Stream Metadata Accuracy Improvements
- Improved stream-online announcement reliability when category data is temporarily missing from Twitch
helix/streams. - If stream data confirms the channel is live but
game_nameis missing/unknown, the bot now performs a fallback metadata lookup to Twitchhelix/channelsand updates globalcurrent_game/stream_titlebefore posting the online message. - Fixed fallback metadata lookup not executing: The initial streams call and the fallback channels call are now performed inside a single shared
aiohttpsession. Previously the session was closed after the first call, causing the fallback request to silently fail and the online message to always read “Streaming Unknown”. - If Twitch indicates the stream is offline, the bot intentionally keeps the category as
Unknown(no fallback overwrite) to avoid stale game data. - Added a live metadata verification loop (
periodic_stream_metadata_refresh) that refreshescurrent_gameandstream_titleevery 60 seconds whilestream_onlineis true. - Metadata refresh calls are skipped while offline to reduce unnecessary API traffic and prevent incorrect global updates.
stream_session_started_atis now sourced from the real Twitchstarted_attimestamp returned byhelix/streams(fall back to the bot’s local “now” only if the API field is missing). Uptime, duration milestones, and ad-break-chat session windowing all use this anchor.
- Improved stream-online announcement reliability when category data is temporarily missing from Twitch
- SSH Manager Initialization
- Changed SSH manager logger from
bot_loggertosystem_loggerfor better categorization
- Changed SSH manager logger from
- AI Conversation History
- Bot replies sent in response to a user
!commandor@mentionare now appended to a short per-viewer AI history (capped at 200 messages). Future AI mentions from the same user can reference prior bot turns for more coherent multi-turn conversations.
- Bot replies sent in response to a user
- Private Todo Tasks
- The
!todo addaction now accepts an optionalprivatekeyword - e.g.!todo add "My task" privateor!todo add "My task" 2 private. The bot stores the task as private and confirms with a[Private]note in chat. - Public/private separation is enforced by
!todolist, which only returns tasks whereprivate = 0.
- The
- New
!todolistCommand- Added a new
!todolistcommand that displays the top 5 public (non-private) pending tasks from the to-do list directly in chat. The SQL filters out completed tasks (completed != 'Yes'), so only the pending bullet (○) is shown in practice. - Each task shows its completion status marker, ID, and objective.
- Optional category filtering:
!todolist 3shows only category ID 3, and!todolist 1,2,3shows tasks from multiple categories. The chat output is prefixed with the matching category name(s), e.g.To-Do List (Work): .... Unknown category IDs return a clear error instead of falling back to all tasks. - The same category syntax works via
(call.todolist.<id>)/(call.todolist.<id>,<id>)in custom commands, timed messages, and channel-point reward messages.
- Added a new
Thank you for your feedback and support!
As always, your feedback is appreciated. Please report any issues, bugs, or suggestions in the #ticket-info channel on our Discord server. You can also submit feedback and bug reports directly on our website: https://botofthespecter.com/feedback.php