Skip to content

Commit 5fc0c18

Browse files
Implement fractional and automated VIP token rewards
1 parent cd5258c commit 5fc0c18

28 files changed

+1500
-57
lines changed

.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ ADMIN_TWITCH_USER_IDS=
1919
# Production should keep its own production bot username in deployed env/secrets.
2020
TWITCH_BOT_USERNAME=requestbot
2121
# Broadcaster OAuth scopes used by the main app login.
22-
# These should include the channel permissions needed for bot replies and chatter lookups.
23-
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot
22+
# These should include the channel permissions needed for bot replies, chatter lookups, gifted-sub automation, and cheer automation.
23+
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
## [0.1.1] - 2026-03-22
8+
79
### Added
810
- Shared bot reconnect controls for admins, including the ability to replace the connected bot account safely.
911
- 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.
12+
- Automatic VIP token rewards for Twitch-native support events, including gifted subs, gifted-sub recipients, and cheers with configurable conversion rules.
13+
- Public playlist messaging that shows viewers how they can earn VIP tokens when a channel has support-based VIP rewards enabled.
1014
- Public played-history search.
1115
- Charter blacklisting, including exact charter matching and clearer handling when only some song versions are blocked.
1216
- Richer sample catalog metadata for artists, charters, tunings, and future filtering work.
@@ -16,7 +20,9 @@ All notable changes to this project will be documented in this file.
1620
### Changed
1721
- Bot replies now use Twitch's bot-badge-compatible reply path, and the app prompts broadcasters to reconnect Twitch if required permissions are missing.
1822
- Broadcaster login now requests the Twitch permissions needed for chatter-aware moderation and bot-badged replies.
23+
- Broadcaster login now also requests the Twitch permissions needed for gifted-sub and cheer-based VIP token automation.
1924
- The app header and settings pages now surface Twitch reauthorization more clearly when a reconnect is required.
25+
- 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.
2026
- Local-development guidance now strongly separates production bot/broadcaster usage from local testing and explains the risks of cross-environment chat handling.
2127
- The moderation dashboard now supports faster Twitch username search with debouncing, in-chat prioritization, and clearer saved-state feedback for VIP tokens.
2228
- Search results now show newer song versions first, and public search includes a dedicated `!edit` copy command.
@@ -30,6 +36,7 @@ All notable changes to this project will be documented in this file.
3036

3137
### Fixed
3238
- Duplicate EventSub deliveries for `!addvip` no longer grant multiple VIP tokens or queue duplicate bot replies.
39+
- Duplicate EventSub deliveries for cheers and gifted-sub automation no longer double-grant VIP tokens.
3340
- Twitch reply handling now distinguishes between accepted API requests and messages that Twitch actually sent to chat.
3441
- Bot/account status screens now show the real connected bot identity instead of only the configured bot name.
3542
- Production deployment config regeneration now stays in sync after remote migrations.

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ npm run test:e2e
2929

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

32+
## Releases
33+
34+
- 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.
35+
- Use `0.x.x` for normal minor/patch releases while the project is still pre-`1.0`.
36+
- If the release is a major milestone or materially changes the product scope, bump the middle digit such as `0.2.0`.
37+
- Otherwise, use a patch release such as `0.1.1`.
38+
- Ask explicitly which release level is intended if it is not obvious from the scope of the work.
39+
3240
## Local development
3341

3442
Start here:

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Repository workflow docs:
2121
- Cloudflare worker configuration with D1, KV, queue producer, and an auxiliary backend worker
2222
- Drizzle schema and initial SQL migration for the required MVP entities
2323
- Server-side Twitch OAuth callback flow with session cookie + KV session storage
24-
- EventSub webhook intake for `channel.chat.message`, `stream.online`, and `stream.offline`
24+
- EventSub webhook intake for `channel.chat.message`, `channel.subscribe`, `channel.subscription.gift`, `channel.cheer`, `stream.online`, and `stream.offline`
2525
- Separate shared bot-account OAuth flow with bot replies sent from the bot identity
2626
- Per-channel opt-in bot presence with live-aware activation/deactivation
2727
- Always-on public playlist pages for each channel
@@ -82,7 +82,7 @@ TWITCH_CLIENT_SECRET=
8282
TWITCH_EVENTSUB_SECRET=local-dev-eventsub-secret
8383
SESSION_SECRET=local-dev-session-secret
8484
TWITCH_BOT_USERNAME=requestbot
85-
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot
85+
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read
8686
ADMIN_TWITCH_USER_IDS=
8787
VITE_ALLOWED_HOSTS=
8888
```
@@ -95,7 +95,7 @@ For basic local development, set:
9595
- `TWITCH_EVENTSUB_SECRET`
9696
- `SESSION_SECRET`
9797
- `TWITCH_BOT_USERNAME`
98-
- `TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot`
98+
- `TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read`
9999
- `VITE_ALLOWED_HOSTS=` if you need extra Vite hostnames
100100

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

107107
`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.
108108

109-
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.
109+
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.
110110

111111
Sentry stays off locally unless you explicitly set a DSN:
112112

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

503503
For the full deploy and GitHub workflow details, use [docs/deployment-workflow.md](/docs/deployment-workflow.md).
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
PRAGMA foreign_keys=OFF;
2+
3+
CREATE TABLE `__new_vip_tokens` (
4+
`channel_id` text NOT NULL,
5+
`normalized_login` text NOT NULL,
6+
`twitch_user_id` text,
7+
`login` text NOT NULL,
8+
`display_name` text,
9+
`available_count` real DEFAULT 0 NOT NULL,
10+
`granted_count` real DEFAULT 0 NOT NULL,
11+
`consumed_count` real DEFAULT 0 NOT NULL,
12+
`auto_subscriber_granted` integer DEFAULT false NOT NULL,
13+
`last_granted_at` integer,
14+
`last_consumed_at` integer,
15+
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
16+
`updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
17+
PRIMARY KEY(`channel_id`, `normalized_login`),
18+
FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE no action
19+
);
20+
21+
INSERT INTO `__new_vip_tokens` (
22+
`channel_id`,
23+
`normalized_login`,
24+
`twitch_user_id`,
25+
`login`,
26+
`display_name`,
27+
`available_count`,
28+
`granted_count`,
29+
`consumed_count`,
30+
`auto_subscriber_granted`,
31+
`last_granted_at`,
32+
`last_consumed_at`,
33+
`created_at`,
34+
`updated_at`
35+
)
36+
SELECT
37+
`channel_id`,
38+
`normalized_login`,
39+
`twitch_user_id`,
40+
`login`,
41+
`display_name`,
42+
`available_count`,
43+
`granted_count`,
44+
`consumed_count`,
45+
`auto_subscriber_granted`,
46+
`last_granted_at`,
47+
`last_consumed_at`,
48+
`created_at`,
49+
`updated_at`
50+
FROM `vip_tokens`;
51+
52+
DROP TABLE `vip_tokens`;
53+
54+
ALTER TABLE `__new_vip_tokens` RENAME TO `vip_tokens`;
55+
56+
CREATE INDEX `vip_tokens_channel_user_idx` ON `vip_tokens` (`channel_id`,`twitch_user_id`);
57+
58+
PRAGMA foreign_keys=ON;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
ALTER TABLE `channel_settings`
2+
ADD `auto_grant_vip_tokens_to_sub_gifters` integer DEFAULT false NOT NULL;
3+
4+
ALTER TABLE `channel_settings`
5+
ADD `auto_grant_vip_tokens_to_gift_recipients` integer DEFAULT false NOT NULL;
6+
7+
ALTER TABLE `channel_settings`
8+
ADD `auto_grant_vip_tokens_for_cheers` integer DEFAULT false NOT NULL;
9+
10+
ALTER TABLE `channel_settings`
11+
ADD `cheer_bits_per_vip_token` integer DEFAULT 200 NOT NULL;
12+
13+
ALTER TABLE `channel_settings`
14+
ADD `cheer_minimum_token_percent` integer DEFAULT 25 NOT NULL;
15+
16+
CREATE TABLE IF NOT EXISTS `eventsub_deliveries` (
17+
`channel_id` text NOT NULL,
18+
`message_id` text NOT NULL,
19+
`subscription_type` text NOT NULL,
20+
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
21+
PRIMARY KEY(`channel_id`, `message_id`),
22+
FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE no action
23+
);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "request-bot",
33
"private": true,
44
"type": "module",
5-
"version": "0.1.0",
5+
"version": "0.1.1",
66
"engines": {
77
"node": ">=22"
88
},
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const LATEST_MIGRATION_NAME = "0008_blacklisted_charters.sql";
1+
export const LATEST_MIGRATION_NAME = "0010_vip_token_automation.sql";

0 commit comments

Comments
 (0)