default requireMention to true

This commit is contained in:
jaydenfyi 2026-01-25 21:35:48 +08:00
parent ad828b9f0b
commit 11d00f663c
5 changed files with 82 additions and 29 deletions

View File

@ -38,7 +38,7 @@ Details: [Plugins](/plugin)
- If both are set, config takes precedence (env fallback is default-account only). - If both are set, config takes precedence (env fallback is default-account only).
4) Start the gateway. 4) Start the gateway.
**⚠️ Important:** Add `requireMention: true` and access control (`allowFrom` or `allowedRoles`) to prevent the bot from replying to all chat messages. **⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
Minimal config: Minimal config:
@ -51,7 +51,6 @@ Minimal config:
accessToken: "oauth:abc123...", // OAuth Access Token (or use CLAWDBOT_TWITCH_ACCESS_TOKEN env var) accessToken: "oauth:abc123...", // OAuth Access Token (or use CLAWDBOT_TWITCH_ACCESS_TOKEN env var)
clientId: "xyz789...", // Client ID from Token Generator clientId: "xyz789...", // Client ID from Token Generator
channel: "vevisk", // Which Twitch channel's chat to join channel: "vevisk", // Which Twitch channel's chat to join
requireMention: true, // (recommended) Only reply when mentioned
allowFrom: ["123456789"] // (recommended) Your Twitch user ID only allowFrom: ["123456789"] // (recommended) Your Twitch user ID only
} }
} }
@ -106,7 +105,6 @@ If both env and config are set, config takes precedence.
{ {
channels: { channels: {
twitch: { twitch: {
requireMention: true, // (recommended) Only respond when mentioned
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
allowedRoles: ["moderator"] // Or restrict to roles allowedRoles: ["moderator"] // Or restrict to roles
} }
@ -223,7 +221,9 @@ Users in `allowFrom` bypass role checks:
} }
``` ```
### Require @mention ### Disable @mention requirement
By default, `requireMention` is `true`. To disable and respond to all messages:
```json5 ```json5
{ {
@ -231,7 +231,7 @@ Users in `allowFrom` bypass role checks:
twitch: { twitch: {
accounts: { accounts: {
default: { default: {
requireMention: true requireMention: false
} }
} }
} }
@ -287,7 +287,7 @@ If you see "token refresh disabled (no refresh token)":
- `obtainmentTimestamp` - Token obtained timestamp - `obtainmentTimestamp` - Token obtained timestamp
- `allowFrom` - User ID allowlist - `allowFrom` - User ID allowlist
- `allowedRoles` - Role-based access control (`"moderator" | "owner" | "vip" | "subscriber" | "all"`) - `allowedRoles` - Role-based access control (`"moderator" | "owner" | "vip" | "subscriber" | "all"`)
- `requireMention` - Require @mention (default: `false`) - `requireMention` - Require @mention (default: `true`)
**Provider options:** **Provider options:**
- `channels.twitch.enabled` - Enable/disable channel startup - `channels.twitch.enabled` - Enable/disable channel startup
@ -310,7 +310,6 @@ Full example:
channel: "vevisk", channel: "vevisk",
clientSecret: "secret123...", clientSecret: "secret123...",
refreshToken: "refresh456...", refreshToken: "refresh456...",
requireMention: true,
allowFrom: ["123456789"], allowFrom: ["123456789"],
allowedRoles: ["moderator", "vip"], allowedRoles: ["moderator", "vip"],
accounts: { accounts: {
@ -325,8 +324,7 @@ Full example:
expiresIn: 14400, expiresIn: 14400,
obtainmentTimestamp: 1706092800000, obtainmentTimestamp: 1706092800000,
allowFrom: ["123456789", "987654321"], allowFrom: ["123456789", "987654321"],
allowedRoles: ["moderator"], allowedRoles: ["moderator"]
requireMention: true
} }
} }
} }

View File

@ -20,7 +20,7 @@ Onboarding: select Twitch and confirm the install prompt to fetch the plugin aut
Minimal config (simplified single-account): Minimal config (simplified single-account):
**⚠️ Important:** Strongly recommended to add `requireMention` and access control (`allowFrom` or `allowedRoles`) to prevent the bot from replying to all chat messages. **⚠️ Important:** `requireMention` defaults to `true`. Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot.
```json5 ```json5
{ {
@ -31,16 +31,15 @@ Minimal config (simplified single-account):
accessToken: "oauth:abc123...", // OAuth Access Token (add oauth: prefix) accessToken: "oauth:abc123...", // OAuth Access Token (add oauth: prefix)
clientId: "xyz789...", // Client ID from Token Generator clientId: "xyz789...", // Client ID from Token Generator
channel: "vevisk", // Channel to join channel: "vevisk", // Channel to join
requireMention: true, // (recommended) Only reply when mentioned
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only (Convert your twitch username to ID at https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/) allowFrom: ["123456789"], // (recommended) Your Twitch user ID only (Convert your twitch username to ID at https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/)
}, },
}, },
} }
``` ```
**Recommended access control options:** **Access control options:**
- `requireMention: true` - Only respond when the bot is mentioned with `@botname` - `requireMention: false` - Disable the default mention requirement to respond to all messages
- `allowFrom: ["your_user_id"]` - Restrict to your Twitch user ID only (find your ID at https://www.twitchangles.com/xqc or similar) - `allowFrom: ["your_user_id"]` - Restrict to your Twitch user ID only (find your ID at https://www.twitchangles.com/xqc or similar)
- `allowedRoles: ["moderator", "vip", "subscriber"]` - Restrict to specific roles - `allowedRoles: ["moderator", "vip", "subscriber"]` - Restrict to specific roles

View File

@ -16,9 +16,44 @@ describe("checkTwitchAccessControl", () => {
}; };
describe("when no restrictions are configured", () => { describe("when no restrictions are configured", () => {
it("allows all messages", () => { it("allows messages that mention the bot (default requireMention)", () => {
const message: TwitchChatMessage = {
...mockMessage,
message: "@testbot hello",
};
const result = checkTwitchAccessControl({ const result = checkTwitchAccessControl({
message: mockMessage, message,
account: mockAccount,
botUsername: "testbot",
});
expect(result.allowed).toBe(true);
});
});
describe("requireMention default", () => {
it("defaults to true when undefined", () => {
const message: TwitchChatMessage = {
...mockMessage,
message: "hello bot",
};
const result = checkTwitchAccessControl({
message,
account: mockAccount,
botUsername: "testbot",
});
expect(result.allowed).toBe(false);
expect(result.reason).toContain("does not mention the bot");
});
it("allows mention when requireMention is undefined", () => {
const message: TwitchChatMessage = {
...mockMessage,
message: "@testbot hello",
};
const result = checkTwitchAccessControl({
message,
account: mockAccount, account: mockAccount,
botUsername: "testbot", botUsername: "testbot",
}); });
@ -85,9 +120,13 @@ describe("checkTwitchAccessControl", () => {
...mockAccount, ...mockAccount,
allowFrom: ["123456", "789012"], allowFrom: ["123456", "789012"],
}; };
const message: TwitchChatMessage = {
...mockMessage,
message: "@testbot hello",
};
const result = checkTwitchAccessControl({ const result = checkTwitchAccessControl({
message: mockMessage, message,
account, account,
botUsername: "testbot", botUsername: "testbot",
}); });
@ -101,9 +140,13 @@ describe("checkTwitchAccessControl", () => {
...mockAccount, ...mockAccount,
allowFrom: ["789012"], allowFrom: ["789012"],
}; };
const message: TwitchChatMessage = {
...mockMessage,
message: "@testbot hello",
};
const result = checkTwitchAccessControl({ const result = checkTwitchAccessControl({
message: mockMessage, message,
account, account,
botUsername: "testbot", botUsername: "testbot",
}); });
@ -116,7 +159,11 @@ describe("checkTwitchAccessControl", () => {
...mockAccount, ...mockAccount,
allowFrom: ["123456"], allowFrom: ["123456"],
}; };
const message = { ...mockMessage, userId: undefined }; const message: TwitchChatMessage = {
...mockMessage,
message: "@testbot hello",
userId: undefined,
};
const result = checkTwitchAccessControl({ const result = checkTwitchAccessControl({
message, message,
@ -135,6 +182,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
isOwner: false, isOwner: false,
}; };
@ -154,6 +202,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
userId: "123456", userId: "123456",
isMod: true, isMod: true,
}; };
@ -175,6 +224,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
userId: "123456", userId: "123456",
isMod: false, isMod: false,
}; };
@ -197,6 +247,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
isMod: true, isMod: true,
}; };
@ -216,6 +267,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
isVip: true, isVip: true,
isMod: false, isMod: false,
isSub: false, isSub: false,
@ -236,6 +288,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
isMod: false, isMod: false,
}; };
@ -253,9 +306,13 @@ describe("checkTwitchAccessControl", () => {
...mockAccount, ...mockAccount,
allowedRoles: ["all"], allowedRoles: ["all"],
}; };
const message: TwitchChatMessage = {
...mockMessage,
message: "@testbot hello",
};
const result = checkTwitchAccessControl({ const result = checkTwitchAccessControl({
message: mockMessage, message,
account, account,
botUsername: "testbot", botUsername: "testbot",
}); });
@ -270,6 +327,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
isMod: true, isMod: true,
}; };
@ -288,6 +346,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
isSub: true, isSub: true,
}; };
@ -306,6 +365,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
isOwner: true, isOwner: true,
}; };
@ -324,6 +384,7 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
message: "@testbot hello",
isVip: true, isVip: true,
}; };
@ -365,7 +426,8 @@ describe("checkTwitchAccessControl", () => {
}; };
const message: TwitchChatMessage = { const message: TwitchChatMessage = {
...mockMessage, ...mockMessage,
isOwner: false, // Not owner, but in allowlist message: "@testbot hello",
isOwner: false,
}; };
const result = checkTwitchAccessControl({ const result = checkTwitchAccessControl({

View File

@ -38,7 +38,7 @@ export function checkTwitchAccessControl(params: {
}): TwitchAccessControlResult { }): TwitchAccessControlResult {
const { message, account, botUsername } = params; const { message, account, botUsername } = params;
if (account.requireMention) { if (account.requireMention ?? true) {
const mentions = extractMentions(message.message); const mentions = extractMentions(message.message);
if (!mentions.includes(botUsername.toLowerCase())) { if (!mentions.includes(botUsername.toLowerCase())) {
return { return {

View File

@ -269,10 +269,9 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
return "disabled"; return "disabled";
}, },
setPolicy: (cfg, policy) => { setPolicy: (cfg, policy) => {
// Map policy to Twitch roles
const allowedRoles: TwitchRole[] = const allowedRoles: TwitchRole[] =
policy === "open" ? ["all"] : policy === "allowlist" ? [] : ["moderator"]; policy === "open" ? ["all"] : policy === "allowlist" ? [] : ["moderator"];
return setTwitchAccessControl(cfg as ClawdbotConfig, allowedRoles, false); return setTwitchAccessControl(cfg as ClawdbotConfig, allowedRoles, true);
}, },
promptAllowFrom: async ({ cfg, prompter }) => { promptAllowFrom: async ({ cfg, prompter }) => {
const account = getAccountConfig(cfg, DEFAULT_ACCOUNT_ID); const account = getAccountConfig(cfg, DEFAULT_ACCOUNT_ID);
@ -378,12 +377,7 @@ export const twitchOnboardingAdapter: ChannelOnboardingAdapter = {
? ["moderator", "vip"] ? ["moderator", "vip"]
: []; : [];
const requireMention = accessConfig.policy === "open"; const cfgWithAccessControl = setTwitchAccessControl(cfgWithAllowFrom, allowedRoles, true);
const cfgWithAccessControl = setTwitchAccessControl(
cfgWithAllowFrom,
allowedRoles,
requireMention,
);
return { cfg: cfgWithAccessControl }; return { cfg: cfgWithAccessControl };
} }
} }