Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ ADMIN_TWITCH_USER_IDS=
# Production should keep its own production bot username in deployed env/secrets.
TWITCH_BOT_USERNAME=requestbot
# Broadcaster OAuth scopes used by the main app login.
# These should include the channel permissions needed for bot replies and chatter lookups.
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot
# These should include the channel permissions needed for bot replies, chatter lookups, gifted-sub automation, and cheer automation.
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.1.1] - 2026-03-22

### Added
- Shared bot reconnect controls for admins, including the ability to replace the connected bot account safely.
- VIP token management from both chat commands and the dashboard, with Twitch user lookup, chatter-aware search, and an editable token table in the app.
- Automatic VIP token rewards for Twitch-native support events, including gifted subs, gifted-sub recipients, and cheers with configurable conversion rules.
- Public playlist messaging that shows viewers how they can earn VIP tokens when a channel has support-based VIP rewards enabled.
- Public played-history search.
- Charter blacklisting, including exact charter matching and clearer handling when only some song versions are blocked.
- Richer sample catalog metadata for artists, charters, tunings, and future filtering work.
Expand All @@ -16,7 +20,9 @@ All notable changes to this project will be documented in this file.
### Changed
- Bot replies now use Twitch's bot-badge-compatible reply path, and the app prompts broadcasters to reconnect Twitch if required permissions are missing.
- Broadcaster login now requests the Twitch permissions needed for chatter-aware moderation and bot-badged replies.
- Broadcaster login now also requests the Twitch permissions needed for gifted-sub and cheer-based VIP token automation.
- The app header and settings pages now surface Twitch reauthorization more clearly when a reconnect is required.
- VIP token balances now support fractional values, including partial token grants and clearer balance handling when a viewer has less than one full VIP token remaining.
- Local-development guidance now strongly separates production bot/broadcaster usage from local testing and explains the risks of cross-environment chat handling.
- The moderation dashboard now supports faster Twitch username search with debouncing, in-chat prioritization, and clearer saved-state feedback for VIP tokens.
- Search results now show newer song versions first, and public search includes a dedicated `!edit` copy command.
Expand All @@ -30,6 +36,7 @@ All notable changes to this project will be documented in this file.

### Fixed
- Duplicate EventSub deliveries for `!addvip` no longer grant multiple VIP tokens or queue duplicate bot replies.
- Duplicate EventSub deliveries for cheers and gifted-sub automation no longer double-grant VIP tokens.
- Twitch reply handling now distinguishes between accepted API requests and messages that Twitch actually sent to chat.
- Bot/account status screens now show the real connected bot identity instead of only the configured bot name.
- Production deployment config regeneration now stays in sync after remote migrations.
Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ npm run test:e2e

Merges to `main` are intended to trigger a production deploy.

## Releases

- Keep [CHANGELOG.md](/C:/Users/james/Documents/Projects/request-bot/CHANGELOG.md) and [package.json](/C:/Users/james/Documents/Projects/request-bot/package.json) in sync when preparing a release PR.
- Use `0.x.x` for normal minor/patch releases while the project is still pre-`1.0`.
- If the release is a major milestone or materially changes the product scope, bump the middle digit such as `0.2.0`.
- Otherwise, use a patch release such as `0.1.1`.
- Ask explicitly which release level is intended if it is not obvious from the scope of the work.

## Local development

Start here:
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Repository workflow docs:
- Cloudflare worker configuration with D1, KV, queue producer, and an auxiliary backend worker
- Drizzle schema and initial SQL migration for the required MVP entities
- Server-side Twitch OAuth callback flow with session cookie + KV session storage
- EventSub webhook intake for `channel.chat.message`, `stream.online`, and `stream.offline`
- EventSub webhook intake for `channel.chat.message`, `channel.subscribe`, `channel.subscription.gift`, `channel.cheer`, `stream.online`, and `stream.offline`
- Separate shared bot-account OAuth flow with bot replies sent from the bot identity
- Per-channel opt-in bot presence with live-aware activation/deactivation
- Always-on public playlist pages for each channel
Expand Down Expand Up @@ -82,7 +82,7 @@ TWITCH_CLIENT_SECRET=
TWITCH_EVENTSUB_SECRET=local-dev-eventsub-secret
SESSION_SECRET=local-dev-session-secret
TWITCH_BOT_USERNAME=requestbot
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read
ADMIN_TWITCH_USER_IDS=
VITE_ALLOWED_HOSTS=
```
Expand All @@ -95,7 +95,7 @@ For basic local development, set:
- `TWITCH_EVENTSUB_SECRET`
- `SESSION_SECRET`
- `TWITCH_BOT_USERNAME`
- `TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot`
- `TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read`
- `VITE_ALLOWED_HOSTS=` if you need extra Vite hostnames

To test Twitch sign-in, bot behavior, and EventSub locally, also set:
Expand All @@ -106,7 +106,7 @@ To test Twitch sign-in, bot behavior, and EventSub locally, also set:

`ADMIN_TWITCH_USER_IDS` should contain the Twitch user ID for the admin account that is allowed to connect the shared bot account and access admin pages.

If a broadcaster connected before `channel:bot` was added to your configured scopes, they need to reconnect Twitch from the app before bot replies can use Twitch's bot badge path.
If a broadcaster connected before `channel:bot`, `channel:read:subscriptions`, or `bits:read` were added to your configured scopes, they need to reconnect Twitch from the app before bot replies and VIP token automation can use the updated Twitch permissions.

Sentry stays off locally unless you explicitly set a DSN:

Expand Down Expand Up @@ -497,7 +497,7 @@ gh secret set CLOUDFLARE_D1_DATABASE_ID
gh secret set CLOUDFLARE_SESSION_KV_ID
gh secret set APP_URL --body "https://your-production-url.example"
gh variable set TWITCH_BOT_USERNAME --body "your_bot_username"
gh variable set TWITCH_SCOPES --body "openid user:read:moderated_channels moderator:read:chatters channel:bot"
gh variable set TWITCH_SCOPES --body "openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read"
```

For the full deploy and GitHub workflow details, use [docs/deployment-workflow.md](/docs/deployment-workflow.md).
Expand Down
58 changes: 58 additions & 0 deletions drizzle/0009_fractional_vip_tokens.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
PRAGMA foreign_keys=OFF;

CREATE TABLE `__new_vip_tokens` (
`channel_id` text NOT NULL,
`normalized_login` text NOT NULL,
`twitch_user_id` text,
`login` text NOT NULL,
`display_name` text,
`available_count` real DEFAULT 0 NOT NULL,
`granted_count` real DEFAULT 0 NOT NULL,
`consumed_count` real DEFAULT 0 NOT NULL,
`auto_subscriber_granted` integer DEFAULT false NOT NULL,
`last_granted_at` integer,
`last_consumed_at` integer,
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
`updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
PRIMARY KEY(`channel_id`, `normalized_login`),
FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE no action
);

INSERT INTO `__new_vip_tokens` (
`channel_id`,
`normalized_login`,
`twitch_user_id`,
`login`,
`display_name`,
`available_count`,
`granted_count`,
`consumed_count`,
`auto_subscriber_granted`,
`last_granted_at`,
`last_consumed_at`,
`created_at`,
`updated_at`
)
SELECT
`channel_id`,
`normalized_login`,
`twitch_user_id`,
`login`,
`display_name`,
`available_count`,
`granted_count`,
`consumed_count`,
`auto_subscriber_granted`,
`last_granted_at`,
`last_consumed_at`,
`created_at`,
`updated_at`
FROM `vip_tokens`;

DROP TABLE `vip_tokens`;

ALTER TABLE `__new_vip_tokens` RENAME TO `vip_tokens`;

CREATE INDEX `vip_tokens_channel_user_idx` ON `vip_tokens` (`channel_id`,`twitch_user_id`);

PRAGMA foreign_keys=ON;
23 changes: 23 additions & 0 deletions drizzle/0010_vip_token_automation.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
ALTER TABLE `channel_settings`
ADD `auto_grant_vip_tokens_to_sub_gifters` integer DEFAULT false NOT NULL;

ALTER TABLE `channel_settings`
ADD `auto_grant_vip_tokens_to_gift_recipients` integer DEFAULT false NOT NULL;

ALTER TABLE `channel_settings`
ADD `auto_grant_vip_tokens_for_cheers` integer DEFAULT false NOT NULL;

ALTER TABLE `channel_settings`
ADD `cheer_bits_per_vip_token` integer DEFAULT 200 NOT NULL;

ALTER TABLE `channel_settings`
ADD `cheer_minimum_token_percent` integer DEFAULT 25 NOT NULL;

CREATE TABLE IF NOT EXISTS `eventsub_deliveries` (
`channel_id` text NOT NULL,
`message_id` text NOT NULL,
`subscription_type` text NOT NULL,
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
PRIMARY KEY(`channel_id`, `message_id`),
FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE no action
);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "request-bot",
"private": true,
"type": "module",
"version": "0.1.0",
"version": "0.1.1",
"engines": {
"node": ">=22"
},
Expand Down
2 changes: 1 addition & 1 deletion src/lib/db/latest-migration.generated.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const LATEST_MIGRATION_NAME = "0008_blacklisted_charters.sql";
export const LATEST_MIGRATION_NAME = "0010_vip_token_automation.sql";
Loading
Loading