update docs and remove bot channel fallback
This commit is contained in:
parent
d9093572ac
commit
0412ee0aba
7
.claude/settings.local.json
Normal file
7
.claude/settings.local.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(pnpm vitest:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,10 +6,50 @@ read_when:
|
|||||||
---
|
---
|
||||||
# Twitch (plugin)
|
# Twitch (plugin)
|
||||||
|
|
||||||
Twitch chat support via IRC connection. Clawdbot connects as a Twitch user (bot account) to receive and send messages in channels. Requires a Twitch application with OAuth token.
|
Twitch chat support via IRC connection. Clawdbot connects as a Twitch user (bot account) to receive and send messages in channels.
|
||||||
|
|
||||||
Status: ready for Twitch chat via IRC connection with @twurple.
|
Status: ready for Twitch chat via IRC connection with @twurple.
|
||||||
|
|
||||||
|
## Quick setup (beginner)
|
||||||
|
1) Install the Twitch plugin and create a Twitch application.
|
||||||
|
2) Generate your OAuth token (recommended: use [Twitch Token Generator](https://twitchtokengenerator.com/)).
|
||||||
|
3) Set the token for Clawdbot:
|
||||||
|
- Env: `CLAWDBOT_TWITCH_ACCESS_TOKEN=...` (default account only)
|
||||||
|
- Or config: `channels.twitch.accounts.default.token`
|
||||||
|
- If both are set, config takes precedence (env fallback is default-account only).
|
||||||
|
4) Start the gateway.
|
||||||
|
5) The bot joins your channel and responds to messages.
|
||||||
|
|
||||||
|
Minimal config:
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
twitch: {
|
||||||
|
enabled: true,
|
||||||
|
accounts: {
|
||||||
|
default: {
|
||||||
|
username: "clawdbot", // Bot's Twitch account
|
||||||
|
token: "oauth:abc123...", // Or omit to use CLAWDBOT_TWITCH_ACCESS_TOKEN env var
|
||||||
|
clientId: "your_client_id_here",
|
||||||
|
channel: "vevisk" // Which Twitch channel's chat to join
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** `username` is the bot's account, `channel` is which chat to join.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
1. Create a Twitch application and bot account (or use an existing account).
|
||||||
|
2. Configure Clawdbot with `channels.twitch.accounts.default.token` (or `CLAWDBOT_TWITCH_ACCESS_TOKEN` as a fallback).
|
||||||
|
3. Run the gateway; it auto-starts the Twitch channel when a token is available (config first, env fallback) and `channels.twitch.enabled` is not `false`.
|
||||||
|
4. The bot joins the specified `channel` to send/receive messages.
|
||||||
|
5. Direct chats collapse into the agent's main session (default `agent:main:main`); each account maps to an isolated session key `agent:<agentId>:twitch:<accountName>`.
|
||||||
|
|
||||||
|
**Key distinction:** `username` is who the bot authenticates as (the bot's account), `channel` is which chat room it joins.
|
||||||
|
|
||||||
## Plugin required
|
## Plugin required
|
||||||
|
|
||||||
Twitch ships as a plugin and is not bundled with the core install.
|
Twitch ships as a plugin and is not bundled with the core install.
|
||||||
@ -30,31 +70,25 @@ Details: [Plugins](/plugin)
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1) Install the Twitch plugin:
|
### 1) Create a Twitch application
|
||||||
- From npm: `clawdbot plugins install @clawdbot/twitch`
|
- Go to [Twitch Developer Console](https://dev.twitch.tv/console)
|
||||||
- From a local checkout: `clawdbot plugins install ./extensions/twitch`
|
- Click "Register Your Application"
|
||||||
|
- Set Application Type to "Chat Bot"
|
||||||
|
- Copy the **Client ID**
|
||||||
|
|
||||||
2) Create a Twitch application:
|
### 2) Generate your OAuth token (recommended: Twitch Token Generator)
|
||||||
- Go to [Twitch Developer Console](https://dev.twitch.tv/console)
|
- Use [Twitch Token Generator](https://twitchtokengenerator.com/)
|
||||||
- Click "Register Your Application"
|
- Select scopes: `chat:read` and `chat:write`
|
||||||
- Set Application Type to "Chat Bot"
|
- Copy the token (starts with `oauth:`)
|
||||||
- Copy the Client ID
|
|
||||||
|
|
||||||
3) Generate your OAuth token:
|
### 3) Configure credentials
|
||||||
- Use [Twitch Token Generator](https://twitchtokengenerator.com/) or [TwitchApps TMI](https://twitchapps.com/tmi/)
|
Env (default account only):
|
||||||
- Select scopes: `chat:read` and `chat:write`
|
|
||||||
- Copy the token (starts with `oauth:`)
|
|
||||||
|
|
||||||
4) Configure credentials:
|
```bash
|
||||||
- Env: `CLAWDBOT_TWITCH_ACCESS_TOKEN=...` (default account only)
|
export CLAWDBOT_TWITCH_ACCESS_TOKEN=oauth:your_token_here
|
||||||
- Or config: `channels.twitch.accounts.default.token`
|
```
|
||||||
- If both are set, config takes precedence (env fallback is default-account only)
|
|
||||||
|
|
||||||
5) Start the gateway. Twitch starts when a token is resolved (config first, env fallback).
|
Or config:
|
||||||
|
|
||||||
6) Invite the bot to your channel (it will auto-join the channel specified in config).
|
|
||||||
|
|
||||||
Minimal config (default account):
|
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
@ -63,9 +97,10 @@ Minimal config (default account):
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
username: "mybot",
|
username: "clawdbot", // Bot's Twitch account
|
||||||
token: "oauth:your_token_here",
|
token: "oauth:abc123...", // Or omit to use CLAWDBOT_TWITCH_ACCESS_TOKEN env var
|
||||||
clientId: "your_client_id_here"
|
clientId: "your_client_id_here",
|
||||||
|
channel: "vevisk" // Which Twitch channel's chat to join
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,46 +108,22 @@ Minimal config (default account):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Env-only setup:
|
**Note:** `username` is the bot's account, `channel` is which chat to join.
|
||||||
|
|
||||||
```bash
|
With env, you still need `clientId` and `channel` in config (or use the minimal config above without `token`).
|
||||||
export CLAWDBOT_TWITCH_ACCESS_TOKEN=oauth:your_token_here
|
|
||||||
```
|
|
||||||
|
|
||||||
With env, you still need `clientId` in config (or use the minimal config above without `token`).
|
### 4) Start the gateway
|
||||||
|
Twitch starts when a token is resolved (config first, env fallback).
|
||||||
|
|
||||||
## Token setup
|
### 5) Join a channel
|
||||||
|
The bot joins the channel specified in `channel`.
|
||||||
|
|
||||||
### Option 1: Static token (simple)
|
## Token refresh (optional, recommended for long-running bots)
|
||||||
|
|
||||||
Use [Twitch Token Generator](https://twitchtokengenerator.com/):
|
For long-running bots, configure automatic token refresh to avoid expired tokens:
|
||||||
|
|
||||||
1. Select scopes: `chat:read` and `chat:write`
|
|
||||||
2. Copy the token (starts with `oauth:`)
|
|
||||||
3. Add to config:
|
|
||||||
|
|
||||||
```json5
|
|
||||||
{
|
|
||||||
channels: {
|
|
||||||
twitch: {
|
|
||||||
accounts: {
|
|
||||||
default: {
|
|
||||||
username: "mybot",
|
|
||||||
token: "oauth:abc123...",
|
|
||||||
clientId: "your_client_id"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Automatic token refresh (recommended)
|
|
||||||
|
|
||||||
For long-running bots, configure RefreshingAuthProvider:
|
|
||||||
|
|
||||||
1. Use [Twitch Token Generator](https://twitchtokengenerator.com/) with **"Include Refresh Token"** checked
|
1. Use [Twitch Token Generator](https://twitchtokengenerator.com/) with **"Include Refresh Token"** checked
|
||||||
2. Get your Client Secret from [Twitch Developer Console](https://dev.twitch.tv/console)
|
2. Get your **Client Secret** from [Twitch Developer Console](https://dev.twitch.tv/console)
|
||||||
3. Add to config:
|
3. Add to config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@ -121,7 +132,7 @@ For long-running bots, configure RefreshingAuthProvider:
|
|||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
username: "mybot",
|
username: "clawdbot",
|
||||||
token: "oauth:abc123...",
|
token: "oauth:abc123...",
|
||||||
clientId: "your_client_id",
|
clientId: "your_client_id",
|
||||||
clientSecret: "your_client_secret",
|
clientSecret: "your_client_secret",
|
||||||
@ -135,24 +146,34 @@ For long-running bots, configure RefreshingAuthProvider:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The bot will automatically refresh tokens before they expire and log refresh events.
|
The bot automatically refreshes tokens before they expire and logs refresh events.
|
||||||
|
|
||||||
## Access control
|
## Routing model
|
||||||
|
- Replies always go back to Twitch.
|
||||||
|
- Each account maps to `agent:<agentId>:twitch:<accountName>`.
|
||||||
|
|
||||||
### Allowlist by User ID (recommended)
|
## Multi-account support
|
||||||
|
|
||||||
Most secure: only allow specific Twitch user IDs:
|
Use `channels.twitch.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||||
|
|
||||||
|
Example (one bot account in two different channels):
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
ninjaChannel: {
|
||||||
username: "mybot",
|
username: "clawdbot",
|
||||||
token: "oauth:...",
|
token: "oauth:...",
|
||||||
clientId: "...",
|
clientId: "...",
|
||||||
allowFrom: ["123456789", "987654321"]
|
channel: "vevisk"
|
||||||
|
},
|
||||||
|
shroudChannel: {
|
||||||
|
username: "clawdbot",
|
||||||
|
token: "oauth:...",
|
||||||
|
clientId: "...",
|
||||||
|
channel: "secondchannel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,11 +181,9 @@ Most secure: only allow specific Twitch user IDs:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Why user IDs instead of usernames?** Twitch usernames can change, which could allow someone to hijack another user's access. User IDs are permanent.
|
## Access control
|
||||||
|
|
||||||
Find your Twitch user ID at: https://www.streamweasels.com/tools/convert-your-twitch-username-to-user-id/
|
### Role-based restrictions (recommended)
|
||||||
|
|
||||||
### Role-based restrictions
|
|
||||||
|
|
||||||
Restrict access to specific roles:
|
Restrict access to specific roles:
|
||||||
|
|
||||||
@ -192,9 +211,37 @@ Restrict access to specific roles:
|
|||||||
- `"subscriber"` - Subscribers
|
- `"subscriber"` - Subscribers
|
||||||
- `"all"` - Anyone in chat
|
- `"all"` - Anyone in chat
|
||||||
|
|
||||||
|
### Allowlist by User ID
|
||||||
|
|
||||||
|
Only allow specific Twitch user IDs (most secure):
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
twitch: {
|
||||||
|
accounts: {
|
||||||
|
default: {
|
||||||
|
username: "mybot",
|
||||||
|
token: "oauth:...",
|
||||||
|
clientId: "...",
|
||||||
|
allowFrom: ["123456789", "987654321"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why user IDs instead of usernames?** Twitch usernames can change, which could allow someone to hijack another user's access. User IDs are permanent.
|
||||||
|
|
||||||
|
Find your Twitch user ID at: https://www.streamweasels.com/tools/convert-your-twitch-username-to-user-id/
|
||||||
|
|
||||||
### Combined allowlist + roles
|
### Combined allowlist + roles
|
||||||
|
|
||||||
Users in `allowFrom` bypass role checks:
|
Users in `allowFrom` bypass role checks. In this example:
|
||||||
|
- User `123456789` can always message (bypasses role check)
|
||||||
|
- All moderators can message
|
||||||
|
- Everyone else is blocked
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
@ -214,11 +261,6 @@ Users in `allowFrom` bypass role checks:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example:
|
|
||||||
- User `123456789` can always message (bypasses role check)
|
|
||||||
- All moderators can message
|
|
||||||
- Everyone else is blocked
|
|
||||||
|
|
||||||
### Require @mention
|
### Require @mention
|
||||||
|
|
||||||
Only respond when the bot is mentioned:
|
Only respond when the bot is mentioned:
|
||||||
@ -240,34 +282,6 @@ Only respond when the bot is mentioned:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Multiple accounts
|
|
||||||
|
|
||||||
Configure multiple Twitch channels:
|
|
||||||
|
|
||||||
```json5
|
|
||||||
{
|
|
||||||
channels: {
|
|
||||||
twitch: {
|
|
||||||
accounts: {
|
|
||||||
main: {
|
|
||||||
username: "mybot",
|
|
||||||
token: "oauth:...",
|
|
||||||
clientId: "...",
|
|
||||||
channel: "streamer1"
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
username: "mybot",
|
|
||||||
token: "oauth:...",
|
|
||||||
clientId: "...",
|
|
||||||
channel: "streamer2",
|
|
||||||
allowedRoles: ["moderator"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
For the default account, you can use environment variables instead of config:
|
For the default account, you can use environment variables instead of config:
|
||||||
@ -282,7 +296,7 @@ Example:
|
|||||||
export CLAWDBOT_TWITCH_ACCESS_TOKEN=oauth:abc123def456...
|
export CLAWDBOT_TWITCH_ACCESS_TOKEN=oauth:abc123def456...
|
||||||
```
|
```
|
||||||
|
|
||||||
Config:
|
Config with env fallback:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
@ -323,6 +337,25 @@ Control markdown stripping behavior:
|
|||||||
|
|
||||||
Twitch doesn't support markdown, so this is enabled by default. Disable if you want to send markdown as-is (it will appear as plain text with markdown symbols).
|
Twitch doesn't support markdown, so this is enabled by default. Disable if you want to send markdown as-is (it will appear as plain text with markdown symbols).
|
||||||
|
|
||||||
|
## Capabilities & limits
|
||||||
|
|
||||||
|
**Supported:**
|
||||||
|
- ✅ Channel messages (group chat)
|
||||||
|
- ✅ Whispers/DMs (received but replies not supported - Twitch doesn't allow bots to send whispers)
|
||||||
|
- ✅ Markdown stripping (automatically applied)
|
||||||
|
- ✅ Message chunking (500 char limit)
|
||||||
|
- ✅ Access control (user ID allowlist, role-based)
|
||||||
|
- ✅ @mention requirement
|
||||||
|
- ✅ Automatic token refresh (with RefreshingAuthProvider)
|
||||||
|
- ✅ Multi-account support
|
||||||
|
|
||||||
|
**Not supported:**
|
||||||
|
- ❌ Native reactions
|
||||||
|
- ❌ Threaded replies
|
||||||
|
- ❌ Message editing
|
||||||
|
- ❌ Message deletion
|
||||||
|
- ❌ Rich embeds/media uploads (sends media URLs as text)
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
First, run diagnostic commands:
|
First, run diagnostic commands:
|
||||||
@ -374,35 +407,18 @@ If you see "token refresh disabled (no refresh token)":
|
|||||||
- Ensure `clientSecret` is provided
|
- Ensure `clientSecret` is provided
|
||||||
- Ensure `refreshToken` is provided (from Twitch Token Generator with "Include Refresh Token" checked)
|
- Ensure `refreshToken` is provided (from Twitch Token Generator with "Include Refresh Token" checked)
|
||||||
|
|
||||||
## Capabilities & limits
|
## Configuration reference (Twitch)
|
||||||
|
|
||||||
**Supported:**
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
- ✅ Channel messages (group chat)
|
|
||||||
- ✅ Whispers/DMs (received but replies not supported - Twitch doesn't allow bots to send whispers)
|
|
||||||
- ✅ Markdown stripping (automatically applied)
|
|
||||||
- ✅ Message chunking (500 char limit)
|
|
||||||
- ✅ Access control (user ID allowlist, role-based)
|
|
||||||
- ✅ @mention requirement
|
|
||||||
- ✅ Automatic token refresh (with RefreshingAuthProvider)
|
|
||||||
- ✅ Multi-account support
|
|
||||||
|
|
||||||
**Not supported:**
|
|
||||||
- ❌ Native reactions
|
|
||||||
- ❌ Threaded replies
|
|
||||||
- ❌ Message editing
|
|
||||||
- ❌ Message deletion
|
|
||||||
- ❌ Rich embeds/media uploads (sends media URLs as text)
|
|
||||||
|
|
||||||
## Config reference
|
|
||||||
|
|
||||||
### Account config
|
### Account config
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
{
|
{
|
||||||
username: string, // Bot username (required)
|
username: string, // Bot username
|
||||||
token: string, // OAuth token with chat:read and chat:write (required)
|
token: string, // OAuth token with chat:read and chat:write
|
||||||
clientId: string, // Twitch Client ID (required)
|
clientId: string, // Twitch Client ID
|
||||||
channel?: string, // Channel to join (default: username)
|
channel: string, // Channel to join
|
||||||
enabled?: boolean, // Enable this account (default: true)
|
enabled?: boolean, // Enable this account (default: true)
|
||||||
clientSecret?: string, // For RefreshingAuthProvider
|
clientSecret?: string, // For RefreshingAuthProvider
|
||||||
refreshToken?: string, // For RefreshingAuthProvider
|
refreshToken?: string, // For RefreshingAuthProvider
|
||||||
@ -424,6 +440,21 @@ If you see "token refresh disabled (no refresh token)":
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Provider options:
|
||||||
|
- `channels.twitch.enabled`: enable/disable channel startup.
|
||||||
|
- `channels.twitch.accounts.<accountName>.username`: bot username.
|
||||||
|
- `channels.twitch.accounts.<accountName>.token`: OAuth token.
|
||||||
|
- `channels.twitch.accounts.<accountName>.clientId`: Twitch Client ID.
|
||||||
|
- `channels.twitch.accounts.<accountName>.channel`: channel to join.
|
||||||
|
- `channels.twitch.accounts.<accountName>.enabled`: enable/disable account (default: true).
|
||||||
|
- `channels.twitch.accounts.<accountName>.clientSecret`: for RefreshingAuthProvider.
|
||||||
|
- `channels.twitch.accounts.<accountName>.refreshToken`: for RefreshingAuthProvider.
|
||||||
|
- `channels.twitch.accounts.<accountName>.expiresIn`: token expiry in seconds.
|
||||||
|
- `channels.twitch.accounts.<accountName>.obtainmentTimestamp`: token obtained timestamp.
|
||||||
|
- `channels.twitch.accounts.<accountName>.allowFrom`: user ID allowlist.
|
||||||
|
- `channels.twitch.accounts.<accountName>.allowedRoles`: role-based access control.
|
||||||
|
- `channels.twitch.accounts.<accountName>.requireMention`: require @mention (default: false).
|
||||||
|
|
||||||
## Tool actions
|
## Tool actions
|
||||||
|
|
||||||
The agent can call `twitch` with action:
|
The agent can call `twitch` with action:
|
||||||
|
|||||||
@ -138,9 +138,8 @@ export const twitchMessageActions: ChannelMessageActionAdapter = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the channel from account config if not specified
|
// Use the channel from account config (or override with `to` parameter)
|
||||||
const targetChannel = to || account.channel || account.username;
|
const targetChannel = to || account.channel;
|
||||||
|
|
||||||
if (!targetChannel) {
|
if (!targetChannel) {
|
||||||
return errorResponse("No channel specified and no default channel in account config");
|
return errorResponse("No channel specified and no default channel in account config");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -219,7 +219,7 @@ describe("outbound", () => {
|
|||||||
it("should throw when no channel specified", async () => {
|
it("should throw when no channel specified", async () => {
|
||||||
const { getAccountConfig } = await import("./config.js");
|
const { getAccountConfig } = await import("./config.js");
|
||||||
|
|
||||||
const accountWithoutChannel = { ...mockAccount, channel: undefined, username: undefined };
|
const accountWithoutChannel = { ...mockAccount, channel: undefined as unknown as string };
|
||||||
vi.mocked(getAccountConfig).mockReturnValue(accountWithoutChannel);
|
vi.mocked(getAccountConfig).mockReturnValue(accountWithoutChannel);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@ -124,8 +124,8 @@ export const twitchOutbound: ChannelOutboundAdapter = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get channel (support target parameter)
|
// Get channel (support target parameter override)
|
||||||
const channel = to || account.channel || account.username;
|
const channel = to || account.channel;
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
throw new Error("No channel specified and no default channel in account config");
|
throw new Error("No channel specified and no default channel in account config");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,6 +58,7 @@ describe("probeTwitch", () => {
|
|||||||
const mockAccount: TwitchAccountConfig = {
|
const mockAccount: TwitchAccountConfig = {
|
||||||
username: "testbot",
|
username: "testbot",
|
||||||
token: "oauth:test123456789",
|
token: "oauth:test123456789",
|
||||||
|
channel: "testchannel",
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -101,7 +102,7 @@ describe("probeTwitch", () => {
|
|||||||
expect(result.ok).toBe(true);
|
expect(result.ok).toBe(true);
|
||||||
expect(result.connected).toBe(true);
|
expect(result.connected).toBe(true);
|
||||||
expect(result.username).toBe("testbot");
|
expect(result.username).toBe("testbot");
|
||||||
expect(result.channel).toBe("testbot"); // defaults to username
|
expect(result.channel).toBe("testchannel"); // uses account's configured channel
|
||||||
expect(result.elapsedMs).toBeGreaterThan(0);
|
expect(result.elapsedMs).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -102,7 +102,7 @@ export async function probeTwitch(
|
|||||||
ok: true,
|
ok: true,
|
||||||
connected: true,
|
connected: true,
|
||||||
username: account.username,
|
username: account.username,
|
||||||
channel: account.channel ?? account.username,
|
channel: account.channel,
|
||||||
elapsedMs: Date.now() - started,
|
elapsedMs: Date.now() - started,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -110,7 +110,7 @@ export async function probeTwitch(
|
|||||||
ok: false,
|
ok: false,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
username: account.username,
|
username: account.username,
|
||||||
channel: account.channel ?? account.username,
|
channel: account.channel,
|
||||||
elapsedMs: Date.now() - started,
|
elapsedMs: Date.now() - started,
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -163,13 +163,12 @@ describe("send", () => {
|
|||||||
const { getAccountConfig } = await import("./config.js");
|
const { getAccountConfig } = await import("./config.js");
|
||||||
const { isAccountConfigured } = await import("./utils/twitch.js");
|
const { isAccountConfigured } = await import("./utils/twitch.js");
|
||||||
|
|
||||||
// Set both channel and username to undefined to trigger the error
|
// Set channel to undefined to trigger the error (bypassing type check)
|
||||||
const accountWithoutChannelOrUsername = {
|
const accountWithoutChannel = {
|
||||||
...mockAccount,
|
...mockAccount,
|
||||||
channel: undefined,
|
channel: undefined as unknown as string,
|
||||||
username: undefined,
|
|
||||||
};
|
};
|
||||||
vi.mocked(getAccountConfig).mockReturnValue(accountWithoutChannelOrUsername);
|
vi.mocked(getAccountConfig).mockReturnValue(accountWithoutChannel);
|
||||||
vi.mocked(isAccountConfigured).mockReturnValue(true);
|
vi.mocked(isAccountConfigured).mockReturnValue(true);
|
||||||
|
|
||||||
const result = await sendMessageTwitchInternal(
|
const result = await sendMessageTwitchInternal(
|
||||||
|
|||||||
@ -78,8 +78,8 @@ export async function sendMessageTwitchInternal(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize channel
|
// Normalize channel (parameter override, then account channel)
|
||||||
const normalizedChannel = channel || account.channel || account.username;
|
const normalizedChannel = channel || account.channel;
|
||||||
if (!normalizedChannel) {
|
if (!normalizedChannel) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|||||||
@ -125,12 +125,10 @@ export class TwitchClientManager {
|
|||||||
// Create auth provider
|
// Create auth provider
|
||||||
const authProvider = await this.createAuthProvider(account, normalizedToken);
|
const authProvider = await this.createAuthProvider(account, normalizedToken);
|
||||||
|
|
||||||
const channel = account.channel ?? account.username;
|
|
||||||
|
|
||||||
// Create chat client
|
// Create chat client
|
||||||
const client = new ChatClient({
|
const client = new ChatClient({
|
||||||
authProvider,
|
authProvider,
|
||||||
channels: [channel],
|
channels: [account.channel],
|
||||||
rejoinChannelsOnReconnect: true,
|
rejoinChannelsOnReconnect: true,
|
||||||
requestMembershipEvents: true,
|
requestMembershipEvents: true,
|
||||||
logger: {
|
logger: {
|
||||||
@ -301,7 +299,7 @@ export class TwitchClientManager {
|
|||||||
* Generate a unique key for an account
|
* Generate a unique key for an account
|
||||||
*/
|
*/
|
||||||
public getAccountKey(account: TwitchAccountConfig): string {
|
public getAccountKey(account: TwitchAccountConfig): string {
|
||||||
return `${account.username}:${account.channel ?? account.username}`;
|
return `${account.username}:${account.channel}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -53,8 +53,8 @@ export interface TwitchAccountConfig {
|
|||||||
token: string;
|
token: string;
|
||||||
/** Twitch client ID (from Twitch Developer Portal or twitchtokengenerator.com) */
|
/** Twitch client ID (from Twitch Developer Portal or twitchtokengenerator.com) */
|
||||||
clientId?: string;
|
clientId?: string;
|
||||||
/** Channel name to join (defaults to username) */
|
/** Channel name to join (required) */
|
||||||
channel?: string;
|
channel: string;
|
||||||
/** Enable this account */
|
/** Enable this account */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
/** Allowlist of Twitch user IDs who can interact with the bot (use IDs for safety, not usernames) */
|
/** Allowlist of Twitch user IDs who can interact with the bot (use IDs for safety, not usernames) */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user