mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 14:40:43 +00:00
Twitch: align setup account routing
This commit is contained in:
@@ -28,9 +28,11 @@ import type { TwitchAccountConfig } from "./types.js";
|
||||
// Mock the helpers we're testing
|
||||
const mockPromptText = vi.fn();
|
||||
const mockPromptConfirm = vi.fn();
|
||||
const mockPromptNote = vi.fn();
|
||||
const mockPrompter: WizardPrompter = {
|
||||
text: mockPromptText,
|
||||
confirm: mockPromptConfirm,
|
||||
note: mockPromptNote,
|
||||
} as unknown as WizardPrompter;
|
||||
const originalEnvToken = process.env.OPENCLAW_TWITCH_ACCESS_TOKEN;
|
||||
|
||||
@@ -259,6 +261,35 @@ describe("setup surface helpers", () => {
|
||||
expect(lines).toEqual(["Twitch (secondary): configured"]);
|
||||
});
|
||||
|
||||
it("reports status for the requested account override", async () => {
|
||||
const lines = twitchSetupWizard.status?.resolveStatusLines?.({
|
||||
cfg: {
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
username: "default-bot",
|
||||
accessToken: "oauth:default",
|
||||
clientId: "default-client",
|
||||
channel: "#default",
|
||||
},
|
||||
secondary: {
|
||||
username: "secondary-bot",
|
||||
accessToken: "oauth:secondary",
|
||||
clientId: "secondary-client",
|
||||
channel: "#secondary",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountId: "secondary",
|
||||
configured: true,
|
||||
} as never);
|
||||
|
||||
expect(lines).toEqual(["Twitch (secondary): configured"]);
|
||||
});
|
||||
|
||||
it("reports env-token default account setup as configured", async () => {
|
||||
process.env.OPENCLAW_TWITCH_ACCESS_TOKEN = "oauth:fromenv";
|
||||
|
||||
@@ -283,6 +314,47 @@ describe("setup surface helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("setup wizard account routing", () => {
|
||||
it("writes to the requested account when defaultAccount is not created yet", async () => {
|
||||
mockPromptText
|
||||
.mockReset()
|
||||
.mockResolvedValueOnce("secondary-bot" as never)
|
||||
.mockResolvedValueOnce("oauth:secondary" as never)
|
||||
.mockResolvedValueOnce("secondary-client" as never)
|
||||
.mockResolvedValueOnce("#secondary" as never);
|
||||
mockPromptConfirm.mockReset().mockResolvedValue(false as never);
|
||||
|
||||
const result = await twitchSetupWizard.finalize?.({
|
||||
cfg: {
|
||||
channels: {
|
||||
twitch: {
|
||||
defaultAccount: "secondary",
|
||||
accounts: {
|
||||
default: {
|
||||
username: "default-bot",
|
||||
accessToken: "oauth:default",
|
||||
clientId: "default-client",
|
||||
channel: "#default",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Parameters<NonNullable<typeof twitchSetupWizard.finalize>>[0]["cfg"],
|
||||
accountId: "secondary",
|
||||
credentialValues: {},
|
||||
runtime: {} as Parameters<NonNullable<typeof twitchSetupWizard.finalize>>[0]["runtime"],
|
||||
prompter: mockPrompter,
|
||||
options: {},
|
||||
forceAllowFrom: false,
|
||||
});
|
||||
|
||||
const twitch = result?.cfg?.channels?.twitch;
|
||||
expect(twitch?.accounts?.secondary?.username).toBe("secondary-bot");
|
||||
expect(twitch?.accounts?.secondary?.accessToken).toBe("oauth:secondary");
|
||||
expect(twitch?.accounts?.default?.username).toBe("default-bot");
|
||||
});
|
||||
});
|
||||
|
||||
describe("setup-only plugin config", () => {
|
||||
it("lists all configured Twitch accounts", () => {
|
||||
const cfg = {
|
||||
|
||||
@@ -28,6 +28,10 @@ function resolveSetupAccountId(cfg: OpenClawConfig): string {
|
||||
return preferred || resolveDefaultTwitchAccountId(cfg);
|
||||
}
|
||||
|
||||
function resolveSetupTargetAccountId(cfg: OpenClawConfig, accountId?: string | null): string {
|
||||
return accountId?.trim() || resolveSetupAccountId(cfg);
|
||||
}
|
||||
|
||||
export function setTwitchAccount(
|
||||
cfg: OpenClawConfig,
|
||||
account: Partial<TwitchAccountConfig>,
|
||||
@@ -199,6 +203,7 @@ export async function configureWithEnvToken(
|
||||
envToken: string,
|
||||
forceAllowFrom: boolean,
|
||||
dmPolicy: ChannelSetupDmPolicy,
|
||||
accountId: string = resolveSetupAccountId(cfg),
|
||||
): Promise<{ cfg: OpenClawConfig } | null> {
|
||||
const useEnv = await prompter.confirm({
|
||||
message: "Twitch env var OPENCLAW_TWITCH_ACCESS_TOKEN detected. Use env token?",
|
||||
@@ -211,15 +216,19 @@ export async function configureWithEnvToken(
|
||||
const username = await promptUsername(prompter, account);
|
||||
const clientId = await promptClientId(prompter, account);
|
||||
|
||||
const cfgWithAccount = setTwitchAccount(cfg, {
|
||||
username,
|
||||
clientId,
|
||||
accessToken: "",
|
||||
enabled: true,
|
||||
});
|
||||
const cfgWithAccount = setTwitchAccount(
|
||||
cfg,
|
||||
{
|
||||
username,
|
||||
clientId,
|
||||
accessToken: "",
|
||||
enabled: true,
|
||||
},
|
||||
accountId,
|
||||
);
|
||||
|
||||
if (forceAllowFrom && dmPolicy.promptAllowFrom) {
|
||||
return { cfg: await dmPolicy.promptAllowFrom({ cfg: cfgWithAccount, prompter }) };
|
||||
return { cfg: await dmPolicy.promptAllowFrom({ cfg: cfgWithAccount, prompter, accountId }) };
|
||||
}
|
||||
|
||||
return { cfg: cfgWithAccount };
|
||||
@@ -229,9 +238,10 @@ function setTwitchAccessControl(
|
||||
cfg: OpenClawConfig,
|
||||
allowedRoles: TwitchRole[],
|
||||
requireMention: boolean,
|
||||
accountId?: string | null,
|
||||
): OpenClawConfig {
|
||||
const accountId = resolveSetupAccountId(cfg);
|
||||
const account = getAccountConfig(cfg, accountId);
|
||||
const resolvedAccountId = resolveSetupTargetAccountId(cfg, accountId);
|
||||
const account = getAccountConfig(cfg, resolvedAccountId);
|
||||
if (!account) {
|
||||
return cfg;
|
||||
}
|
||||
@@ -243,12 +253,15 @@ function setTwitchAccessControl(
|
||||
allowedRoles,
|
||||
requireMention,
|
||||
},
|
||||
accountId,
|
||||
resolvedAccountId,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveTwitchGroupPolicy(cfg: OpenClawConfig): "open" | "allowlist" | "disabled" {
|
||||
const account = getAccountConfig(cfg, resolveSetupAccountId(cfg));
|
||||
function resolveTwitchGroupPolicy(
|
||||
cfg: OpenClawConfig,
|
||||
accountId?: string | null,
|
||||
): "open" | "allowlist" | "disabled" {
|
||||
const account = getAccountConfig(cfg, resolveSetupTargetAccountId(cfg, accountId));
|
||||
if (account?.allowedRoles?.includes("all")) {
|
||||
return "open";
|
||||
}
|
||||
@@ -261,10 +274,11 @@ function resolveTwitchGroupPolicy(cfg: OpenClawConfig): "open" | "allowlist" | "
|
||||
function setTwitchGroupPolicy(
|
||||
cfg: OpenClawConfig,
|
||||
policy: "open" | "allowlist" | "disabled",
|
||||
accountId?: string | null,
|
||||
): OpenClawConfig {
|
||||
const allowedRoles: TwitchRole[] =
|
||||
policy === "open" ? ["all"] : policy === "allowlist" ? ["moderator", "vip"] : [];
|
||||
return setTwitchAccessControl(cfg, allowedRoles, true);
|
||||
return setTwitchAccessControl(cfg, allowedRoles, true, accountId);
|
||||
}
|
||||
|
||||
const twitchDmPolicy: ChannelSetupDmPolicy = {
|
||||
@@ -272,8 +286,8 @@ const twitchDmPolicy: ChannelSetupDmPolicy = {
|
||||
channel,
|
||||
policyKey: "channels.twitch.allowedRoles",
|
||||
allowFromKey: "channels.twitch.accounts.<default>.allowFrom",
|
||||
getCurrent: (cfg) => {
|
||||
const account = getAccountConfig(cfg, resolveSetupAccountId(cfg));
|
||||
getCurrent: (cfg, accountId) => {
|
||||
const account = getAccountConfig(cfg, resolveSetupTargetAccountId(cfg, accountId));
|
||||
if (account?.allowedRoles?.includes("all")) {
|
||||
return "open";
|
||||
}
|
||||
@@ -282,14 +296,14 @@ const twitchDmPolicy: ChannelSetupDmPolicy = {
|
||||
}
|
||||
return "disabled";
|
||||
},
|
||||
setPolicy: (cfg, policy) => {
|
||||
setPolicy: (cfg, policy, accountId) => {
|
||||
const allowedRoles: TwitchRole[] =
|
||||
policy === "open" ? ["all"] : policy === "allowlist" ? [] : ["moderator"];
|
||||
return setTwitchAccessControl(cfg, allowedRoles, true);
|
||||
return setTwitchAccessControl(cfg, allowedRoles, true, accountId);
|
||||
},
|
||||
promptAllowFrom: async ({ cfg, prompter }) => {
|
||||
const accountId = resolveSetupAccountId(cfg);
|
||||
const account = getAccountConfig(cfg, accountId);
|
||||
promptAllowFrom: async ({ cfg, prompter, accountId }) => {
|
||||
const resolvedAccountId = resolveSetupTargetAccountId(cfg, accountId);
|
||||
const account = getAccountConfig(cfg, resolvedAccountId);
|
||||
const existingAllowFrom = account?.allowFrom ?? [];
|
||||
|
||||
const entry = await prompter.text({
|
||||
@@ -309,7 +323,7 @@ const twitchDmPolicy: ChannelSetupDmPolicy = {
|
||||
...(account ?? undefined),
|
||||
allowFrom,
|
||||
},
|
||||
accountId,
|
||||
resolvedAccountId,
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -318,16 +332,16 @@ const twitchGroupAccess: NonNullable<ChannelSetupWizard["groupAccess"]> = {
|
||||
label: "Twitch chat",
|
||||
placeholder: "",
|
||||
skipAllowlistEntries: true,
|
||||
currentPolicy: ({ cfg }) => resolveTwitchGroupPolicy(cfg),
|
||||
currentEntries: ({ cfg }) => {
|
||||
const account = getAccountConfig(cfg, resolveSetupAccountId(cfg));
|
||||
currentPolicy: ({ cfg, accountId }) => resolveTwitchGroupPolicy(cfg, accountId),
|
||||
currentEntries: ({ cfg, accountId }) => {
|
||||
const account = getAccountConfig(cfg, resolveSetupTargetAccountId(cfg, accountId));
|
||||
return account?.allowFrom ?? [];
|
||||
},
|
||||
updatePrompt: ({ cfg }) => {
|
||||
const account = getAccountConfig(cfg, resolveSetupAccountId(cfg));
|
||||
updatePrompt: ({ cfg, accountId }) => {
|
||||
const account = getAccountConfig(cfg, resolveSetupTargetAccountId(cfg, accountId));
|
||||
return Boolean(account?.allowedRoles?.length || account?.allowFrom?.length);
|
||||
},
|
||||
setPolicy: ({ cfg, policy }) => setTwitchGroupPolicy(cfg, policy),
|
||||
setPolicy: ({ cfg, accountId, policy }) => setTwitchGroupPolicy(cfg, policy, accountId),
|
||||
resolveAllowlist: async () => [],
|
||||
applyAllowlist: ({ cfg }) => cfg,
|
||||
};
|
||||
@@ -346,27 +360,29 @@ export const twitchSetupAdapter: ChannelSetupAdapter = {
|
||||
|
||||
export const twitchSetupWizard: ChannelSetupWizard = {
|
||||
channel,
|
||||
resolveAccountIdForConfigure: ({ defaultAccountId }) => defaultAccountId,
|
||||
resolveAccountIdForConfigure: ({ cfg, accountOverride }) =>
|
||||
resolveSetupTargetAccountId(cfg, accountOverride),
|
||||
resolveShouldPromptAccountIds: () => false,
|
||||
status: {
|
||||
configuredLabel: "configured",
|
||||
unconfiguredLabel: "needs username, token, and clientId",
|
||||
configuredHint: "configured",
|
||||
unconfiguredHint: "needs setup",
|
||||
resolveConfigured: ({ cfg }) => {
|
||||
return resolveTwitchAccountContext(cfg, resolveSetupAccountId(cfg)).configured;
|
||||
resolveConfigured: ({ cfg, accountId }) => {
|
||||
return resolveTwitchAccountContext(cfg, resolveSetupTargetAccountId(cfg, accountId))
|
||||
.configured;
|
||||
},
|
||||
resolveStatusLines: ({ cfg }) => {
|
||||
const accountId = resolveSetupAccountId(cfg);
|
||||
const configured = resolveTwitchAccountContext(cfg, accountId).configured;
|
||||
resolveStatusLines: ({ cfg, accountId }) => {
|
||||
const resolvedAccountId = resolveSetupTargetAccountId(cfg, accountId);
|
||||
const configured = resolveTwitchAccountContext(cfg, resolvedAccountId).configured;
|
||||
return [
|
||||
`Twitch${accountId !== DEFAULT_ACCOUNT_ID ? ` (${accountId})` : ""}: ${configured ? "configured" : "needs username, token, and clientId"}`,
|
||||
`Twitch${resolvedAccountId !== DEFAULT_ACCOUNT_ID ? ` (${resolvedAccountId})` : ""}: ${configured ? "configured" : "needs username, token, and clientId"}`,
|
||||
];
|
||||
},
|
||||
},
|
||||
credentials: [],
|
||||
finalize: async ({ cfg, prompter, forceAllowFrom }) => {
|
||||
const accountId = resolveSetupAccountId(cfg);
|
||||
finalize: async ({ cfg, accountId: requestedAccountId, prompter, forceAllowFrom }) => {
|
||||
const accountId = resolveSetupTargetAccountId(cfg, requestedAccountId);
|
||||
const account = getAccountConfig(cfg, accountId);
|
||||
|
||||
if (!account || !isAccountConfigured(account)) {
|
||||
@@ -383,6 +399,7 @@ export const twitchSetupWizard: ChannelSetupWizard = {
|
||||
envToken,
|
||||
forceAllowFrom,
|
||||
twitchDmPolicy,
|
||||
accountId,
|
||||
);
|
||||
if (envResult) {
|
||||
return envResult;
|
||||
@@ -411,7 +428,7 @@ export const twitchSetupWizard: ChannelSetupWizard = {
|
||||
|
||||
const cfgWithAllowFrom =
|
||||
forceAllowFrom && twitchDmPolicy.promptAllowFrom
|
||||
? await twitchDmPolicy.promptAllowFrom({ cfg: cfgWithAccount, prompter })
|
||||
? await twitchDmPolicy.promptAllowFrom({ cfg: cfgWithAccount, prompter, accountId })
|
||||
: cfgWithAccount;
|
||||
|
||||
return { cfg: cfgWithAllowFrom };
|
||||
|
||||
Reference in New Issue
Block a user