remove strip markdown config, enabled by default
This commit is contained in:
parent
be3a16f294
commit
ad828b9f0b
@ -289,9 +289,6 @@ If you see "token refresh disabled (no refresh token)":
|
|||||||
- `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: `false`)
|
||||||
|
|
||||||
**Plugin config:**
|
|
||||||
- `stripMarkdown` - Strip markdown from outbound (default: `true`)
|
|
||||||
|
|
||||||
**Provider options:**
|
**Provider options:**
|
||||||
- `channels.twitch.enabled` - Enable/disable channel startup
|
- `channels.twitch.enabled` - Enable/disable channel startup
|
||||||
- `channels.twitch.username` - Bot username (simplified single-account config)
|
- `channels.twitch.username` - Bot username (simplified single-account config)
|
||||||
@ -333,13 +330,6 @@ Full example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
entries: {
|
|
||||||
twitch: {
|
|
||||||
stripMarkdown: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@ -36,20 +36,22 @@ const TwitchAccountSchema = z.object({
|
|||||||
obtainmentTimestamp: z.number().optional(),
|
obtainmentTimestamp: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base configuration properties shared by both single and multi-account modes
|
||||||
|
*/
|
||||||
|
const TwitchConfigBaseSchema = z.object({
|
||||||
|
name: z.string().optional(),
|
||||||
|
enabled: z.boolean().optional(),
|
||||||
|
markdown: MarkdownConfigSchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simplified single-account configuration schema
|
* Simplified single-account configuration schema
|
||||||
*
|
*
|
||||||
* Use this for single-account setups. Properties are at the top level,
|
* Use this for single-account setups. Properties are at the top level,
|
||||||
* creating an implicit "default" account.
|
* creating an implicit "default" account.
|
||||||
*/
|
*/
|
||||||
const simplifiedSchema = z.intersection(
|
const simplifiedSchema = z.intersection(TwitchConfigBaseSchema, TwitchAccountSchema);
|
||||||
z.object({
|
|
||||||
name: z.string().optional(),
|
|
||||||
enabled: z.boolean().optional(),
|
|
||||||
markdown: MarkdownConfigSchema.optional(),
|
|
||||||
}),
|
|
||||||
TwitchAccountSchema,
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multi-account configuration schema
|
* Multi-account configuration schema
|
||||||
@ -57,11 +59,7 @@ const simplifiedSchema = z.intersection(
|
|||||||
* Use this for multi-account setups. Each key is an account ID (e.g., "default", "secondary").
|
* Use this for multi-account setups. Each key is an account ID (e.g., "default", "secondary").
|
||||||
*/
|
*/
|
||||||
const multiAccountSchema = z.intersection(
|
const multiAccountSchema = z.intersection(
|
||||||
z.object({
|
TwitchConfigBaseSchema,
|
||||||
name: z.string().optional(),
|
|
||||||
enabled: z.boolean().optional(),
|
|
||||||
markdown: MarkdownConfigSchema.optional(),
|
|
||||||
}),
|
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
/** Per-account configuration (for multi-account setups) */
|
/** Per-account configuration (for multi-account setups) */
|
||||||
|
|||||||
@ -1,33 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { getAccountConfig, parsePluginConfig } from "./config.js";
|
import { getAccountConfig } from "./config.js";
|
||||||
|
|
||||||
describe("parsePluginConfig", () => {
|
|
||||||
it("parses valid config with stripMarkdown true", () => {
|
|
||||||
const result = parsePluginConfig({ stripMarkdown: true });
|
|
||||||
expect(result.stripMarkdown).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("parses valid config with stripMarkdown false", () => {
|
|
||||||
const result = parsePluginConfig({ stripMarkdown: false });
|
|
||||||
expect(result.stripMarkdown).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("defaults to stripMarkdown true when not specified", () => {
|
|
||||||
const result = parsePluginConfig({});
|
|
||||||
expect(result.stripMarkdown).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles undefined config", () => {
|
|
||||||
const result = parsePluginConfig(undefined as unknown as Record<string, unknown>);
|
|
||||||
expect(result).toEqual({ stripMarkdown: true }); // Returns defaults when value is falsy
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles null config", () => {
|
|
||||||
const result = parsePluginConfig(null as unknown as Record<string, unknown>);
|
|
||||||
expect(result).toEqual({ stripMarkdown: true }); // Returns defaults when value is null
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getAccountConfig", () => {
|
describe("getAccountConfig", () => {
|
||||||
const mockMultiAccountConfig = {
|
const mockMultiAccountConfig = {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ClawdbotConfig } from "clawdbot/plugin-sdk";
|
import type { ClawdbotConfig } from "clawdbot/plugin-sdk";
|
||||||
import type { TwitchAccountConfig, TwitchPluginConfig } from "./types.js";
|
import type { TwitchAccountConfig } from "./types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default account ID for Twitch
|
* Default account ID for Twitch
|
||||||
@ -24,49 +24,52 @@ export function getAccountConfig(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cfg = coreConfig as Record<string, unknown>;
|
const cfg = coreConfig as ClawdbotConfig;
|
||||||
const channels = cfg.channels as Record<string, unknown> | undefined;
|
const twitch = cfg.channels?.twitch;
|
||||||
const twitch = channels?.twitch as Record<string, unknown> | undefined;
|
// Access accounts via unknown to handle union type (single-account vs multi-account)
|
||||||
const accounts = twitch?.accounts as Record<string, unknown> | undefined;
|
const twitchRaw = twitch as Record<string, unknown> | undefined;
|
||||||
|
const accounts = twitchRaw?.accounts as Record<string, TwitchAccountConfig> | undefined;
|
||||||
|
|
||||||
// For default account, check base-level config first
|
// For default account, check base-level config first
|
||||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
const accountFromAccounts = accounts?.[DEFAULT_ACCOUNT_ID] as
|
const accountFromAccounts = accounts?.[DEFAULT_ACCOUNT_ID];
|
||||||
| Record<string, unknown>
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
// Base-level properties that can form an implicit default account
|
// Base-level properties that can form an implicit default account
|
||||||
const baseLevel = {
|
const baseLevel = {
|
||||||
username: typeof twitch?.username === "string" ? twitch.username : undefined,
|
username: typeof twitchRaw?.username === "string" ? twitchRaw.username : undefined,
|
||||||
accessToken: typeof twitch?.accessToken === "string" ? twitch.accessToken : undefined,
|
accessToken: typeof twitchRaw?.accessToken === "string" ? twitchRaw.accessToken : undefined,
|
||||||
clientId: typeof twitch?.clientId === "string" ? twitch.clientId : undefined,
|
clientId: typeof twitchRaw?.clientId === "string" ? twitchRaw.clientId : undefined,
|
||||||
channel: typeof twitch?.channel === "string" ? twitch.channel : undefined,
|
channel: typeof twitchRaw?.channel === "string" ? twitchRaw.channel : undefined,
|
||||||
enabled: typeof twitch?.enabled === "boolean" ? twitch.enabled : undefined,
|
enabled: typeof twitchRaw?.enabled === "boolean" ? twitchRaw.enabled : undefined,
|
||||||
allowFrom: Array.isArray(twitch?.allowFrom) ? twitch.allowFrom : undefined,
|
allowFrom: Array.isArray(twitchRaw?.allowFrom) ? twitchRaw.allowFrom : undefined,
|
||||||
allowedRoles: Array.isArray(twitch?.allowedRoles) ? twitch.allowedRoles : undefined,
|
allowedRoles: Array.isArray(twitchRaw?.allowedRoles) ? twitchRaw.allowedRoles : undefined,
|
||||||
requireMention:
|
requireMention:
|
||||||
typeof twitch?.requireMention === "boolean" ? twitch.requireMention : undefined,
|
typeof twitchRaw?.requireMention === "boolean" ? twitchRaw.requireMention : undefined,
|
||||||
clientSecret: typeof twitch?.clientSecret === "string" ? twitch.clientSecret : undefined,
|
clientSecret:
|
||||||
refreshToken: typeof twitch?.refreshToken === "string" ? twitch.refreshToken : undefined,
|
typeof twitchRaw?.clientSecret === "string" ? twitchRaw.clientSecret : undefined,
|
||||||
expiresIn: typeof twitch?.expiresIn === "number" ? twitch.expiresIn : undefined,
|
refreshToken:
|
||||||
|
typeof twitchRaw?.refreshToken === "string" ? twitchRaw.refreshToken : undefined,
|
||||||
|
expiresIn: typeof twitchRaw?.expiresIn === "number" ? twitchRaw.expiresIn : undefined,
|
||||||
obtainmentTimestamp:
|
obtainmentTimestamp:
|
||||||
typeof twitch?.obtainmentTimestamp === "number" ? twitch.obtainmentTimestamp : undefined,
|
typeof twitchRaw?.obtainmentTimestamp === "number"
|
||||||
|
? twitchRaw.obtainmentTimestamp
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merge: base-level takes precedence over accounts.default
|
// Merge: base-level takes precedence over accounts.default
|
||||||
const merged = {
|
const merged: Partial<TwitchAccountConfig> = {
|
||||||
...accountFromAccounts,
|
...accountFromAccounts,
|
||||||
...baseLevel,
|
...baseLevel,
|
||||||
} as Record<string, unknown>;
|
} as Partial<TwitchAccountConfig>;
|
||||||
|
|
||||||
// Only return if we have at least username
|
// Only return if we have at least username
|
||||||
if (merged.username) {
|
if (merged.username) {
|
||||||
return merged as unknown as TwitchAccountConfig;
|
return merged as TwitchAccountConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall through to accounts.default if no base-level username
|
// Fall through to accounts.default if no base-level username
|
||||||
if (accountFromAccounts) {
|
if (accountFromAccounts) {
|
||||||
return accountFromAccounts as unknown as TwitchAccountConfig;
|
return accountFromAccounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -80,29 +83,16 @@ export function getAccountConfig(
|
|||||||
return accounts[accountId] as TwitchAccountConfig | null;
|
return accounts[accountId] as TwitchAccountConfig | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse plugin config
|
|
||||||
*/
|
|
||||||
export function parsePluginConfig(value: unknown): TwitchPluginConfig {
|
|
||||||
if (!value || typeof value !== "object") {
|
|
||||||
return { stripMarkdown: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const raw = value as Record<string, unknown>;
|
|
||||||
return {
|
|
||||||
stripMarkdown: typeof raw.stripMarkdown === "boolean" ? raw.stripMarkdown : true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all configured account IDs
|
* List all configured account IDs
|
||||||
*
|
*
|
||||||
* Includes both explicit accounts and implicit "default" from base-level config
|
* Includes both explicit accounts and implicit "default" from base-level config
|
||||||
*/
|
*/
|
||||||
export function listAccountIds(cfg: ClawdbotConfig): string[] {
|
export function listAccountIds(cfg: ClawdbotConfig): string[] {
|
||||||
const accounts = (cfg as Record<string, unknown>).channels as Record<string, unknown> | undefined;
|
const twitch = cfg.channels?.twitch;
|
||||||
const twitch = accounts?.twitch as Record<string, unknown> | undefined;
|
// Access accounts via unknown to handle union type (single-account vs multi-account)
|
||||||
const accountMap = twitch?.accounts as Record<string, unknown> | undefined;
|
const twitchRaw = twitch as Record<string, unknown> | undefined;
|
||||||
|
const accountMap = twitchRaw?.accounts as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
const ids: string[] = [];
|
const ids: string[] = [];
|
||||||
|
|
||||||
@ -113,10 +103,10 @@ export function listAccountIds(cfg: ClawdbotConfig): string[] {
|
|||||||
|
|
||||||
// Add implicit "default" if base-level config exists and "default" not already present
|
// Add implicit "default" if base-level config exists and "default" not already present
|
||||||
const hasBaseLevelConfig =
|
const hasBaseLevelConfig =
|
||||||
twitch &&
|
twitchRaw &&
|
||||||
(typeof twitch.username === "string" ||
|
(typeof twitchRaw.username === "string" ||
|
||||||
typeof twitch.accessToken === "string" ||
|
typeof twitchRaw.accessToken === "string" ||
|
||||||
typeof twitch.channel === "string");
|
typeof twitchRaw.channel === "string");
|
||||||
|
|
||||||
if (hasBaseLevelConfig && !ids.includes(DEFAULT_ACCOUNT_ID)) {
|
if (hasBaseLevelConfig && !ids.includes(DEFAULT_ACCOUNT_ID)) {
|
||||||
ids.push(DEFAULT_ACCOUNT_ID);
|
ids.push(DEFAULT_ACCOUNT_ID);
|
||||||
|
|||||||
@ -5,10 +5,9 @@
|
|||||||
* resolves agent routes, and handles replies.
|
* resolves agent routes, and handles replies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ReplyPayload } from "clawdbot/plugin-sdk";
|
import type { ReplyPayload, ClawdbotConfig } from "clawdbot/plugin-sdk";
|
||||||
import type { TwitchAccountConfig, TwitchChatMessage } from "./types.js";
|
import type { TwitchAccountConfig, TwitchChatMessage } from "./types.js";
|
||||||
import { checkTwitchAccessControl } from "./access-control.js";
|
import { checkTwitchAccessControl } from "./access-control.js";
|
||||||
import { parsePluginConfig } from "./config.js";
|
|
||||||
import { getTwitchRuntime } from "./runtime.js";
|
import { getTwitchRuntime } from "./runtime.js";
|
||||||
import { getOrCreateClientManager } from "./client-manager-registry.js";
|
import { getOrCreateClientManager } from "./client-manager-registry.js";
|
||||||
import { stripMarkdownForTwitch } from "./utils/markdown.js";
|
import { stripMarkdownForTwitch } from "./utils/markdown.js";
|
||||||
@ -46,9 +45,10 @@ async function processTwitchMessage(params: {
|
|||||||
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
|
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { message, account, accountId, config, runtime, core, statusSink } = params;
|
const { message, account, accountId, config, runtime, core, statusSink } = params;
|
||||||
|
const cfg = config as ClawdbotConfig;
|
||||||
|
|
||||||
const route = core.channel.routing.resolveAgentRoute({
|
const route = core.channel.routing.resolveAgentRoute({
|
||||||
cfg: config as Parameters<typeof core.channel.routing.resolveAgentRoute>[0]["cfg"],
|
cfg,
|
||||||
channel: "twitch",
|
channel: "twitch",
|
||||||
accountId,
|
accountId,
|
||||||
peer: {
|
peer: {
|
||||||
@ -62,9 +62,7 @@ async function processTwitchMessage(params: {
|
|||||||
channel: "Twitch",
|
channel: "Twitch",
|
||||||
from: message.displayName ?? message.username,
|
from: message.displayName ?? message.username,
|
||||||
timestamp: message.timestamp?.getTime(),
|
timestamp: message.timestamp?.getTime(),
|
||||||
envelope: core.channel.reply.resolveEnvelopeFormatOptions(
|
envelope: core.channel.reply.resolveEnvelopeFormatOptions(cfg),
|
||||||
config as Parameters<typeof core.channel.reply.resolveEnvelopeFormatOptions>[0],
|
|
||||||
),
|
|
||||||
body: rawBody,
|
body: rawBody,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,7 +87,7 @@ async function processTwitchMessage(params: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const storePath = core.channel.session.resolveStorePath(
|
const storePath = core.channel.session.resolveStorePath(
|
||||||
(config as Parameters<typeof core.channel.session.resolveStorePath>[0]["cfg"])?.session?.store,
|
config as Parameters<typeof core.channel.session.resolveStorePath>[0],
|
||||||
{ agentId: route.agentId },
|
{ agentId: route.agentId },
|
||||||
);
|
);
|
||||||
await core.channel.session.recordInboundSession({
|
await core.channel.session.recordInboundSession({
|
||||||
@ -102,16 +100,14 @@ async function processTwitchMessage(params: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
||||||
cfg: config as Parameters<typeof core.channel.text.resolveMarkdownTableMode>[0]["cfg"],
|
cfg,
|
||||||
channel: "twitch",
|
channel: "twitch",
|
||||||
accountId,
|
accountId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||||
ctx: ctxPayload,
|
ctx: ctxPayload,
|
||||||
cfg: config as Parameters<
|
cfg,
|
||||||
typeof core.channel.reply.dispatchReplyWithBufferedBlockDispatcher
|
|
||||||
>[0]["cfg"],
|
|
||||||
dispatcherOptions: {
|
dispatcherOptions: {
|
||||||
deliver: async (payload) => {
|
deliver: async (payload) => {
|
||||||
await deliverTwitchReply({
|
await deliverTwitchReply({
|
||||||
@ -168,9 +164,7 @@ async function deliverTwitchReply(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginCfg = parsePluginConfig((config as any).pluginConfig ?? {});
|
const textToSend = stripMarkdownForTwitch(payload.text);
|
||||||
const textToSend =
|
|
||||||
(pluginCfg.stripMarkdown ?? true) ? stripMarkdownForTwitch(payload.text) : payload.text;
|
|
||||||
|
|
||||||
await client.say(channel, textToSend);
|
await client.say(channel, textToSend);
|
||||||
statusSink?.({ lastOutboundAt: Date.now() });
|
statusSink?.({ lastOutboundAt: Date.now() });
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import type { ClawdbotConfig } from "clawdbot/plugin-sdk";
|
|||||||
vi.mock("./config.js", () => ({
|
vi.mock("./config.js", () => ({
|
||||||
DEFAULT_ACCOUNT_ID: "default",
|
DEFAULT_ACCOUNT_ID: "default",
|
||||||
getAccountConfig: vi.fn(),
|
getAccountConfig: vi.fn(),
|
||||||
parsePluginConfig: vi.fn(() => ({ stripMarkdown: true })),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("./send.js", () => ({
|
vi.mock("./send.js", () => ({
|
||||||
@ -294,35 +293,6 @@ describe("outbound", () => {
|
|||||||
}),
|
}),
|
||||||
).rejects.toThrow("Connection lost");
|
).rejects.toThrow("Connection lost");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should respect stripMarkdown config", async () => {
|
|
||||||
const { getAccountConfig } = await import("./config.js");
|
|
||||||
const { parsePluginConfig } = await import("./config.js");
|
|
||||||
const { sendMessageTwitchInternal } = await import("./send.js");
|
|
||||||
|
|
||||||
vi.mocked(getAccountConfig).mockReturnValue(mockAccount);
|
|
||||||
vi.mocked(parsePluginConfig).mockReturnValue({ stripMarkdown: false });
|
|
||||||
vi.mocked(sendMessageTwitchInternal).mockResolvedValue({
|
|
||||||
ok: true,
|
|
||||||
messageId: "msg-789",
|
|
||||||
});
|
|
||||||
|
|
||||||
await twitchOutbound.sendText({
|
|
||||||
cfg: mockConfig,
|
|
||||||
to: "#testchannel",
|
|
||||||
text: "**Bold** text",
|
|
||||||
accountId: "default",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(sendMessageTwitchInternal).toHaveBeenCalledWith(
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
expect.anything(),
|
|
||||||
false, // stripMarkdown disabled
|
|
||||||
expect.anything(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("sendMedia", () => {
|
describe("sendMedia", () => {
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
* Supports text and media (URL) sending with markdown stripping and chunking.
|
* Supports text and media (URL) sending with markdown stripping and chunking.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DEFAULT_ACCOUNT_ID, getAccountConfig, parsePluginConfig } from "./config.js";
|
import { DEFAULT_ACCOUNT_ID, getAccountConfig } from "./config.js";
|
||||||
import { sendMessageTwitchInternal } from "./send.js";
|
import { sendMessageTwitchInternal } from "./send.js";
|
||||||
import type {
|
import type {
|
||||||
ChannelOutboundAdapter,
|
ChannelOutboundAdapter,
|
||||||
@ -127,17 +127,12 @@ export const twitchOutbound: ChannelOutboundAdapter = {
|
|||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginCfg = parsePluginConfig(
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: pluginConfig is not part of CoreConfig
|
|
||||||
(cfg as any).pluginConfig ?? {},
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await sendMessageTwitchInternal(
|
const result = await sendMessageTwitchInternal(
|
||||||
normalizeTwitchChannel(channel),
|
normalizeTwitchChannel(channel),
|
||||||
text,
|
text,
|
||||||
cfg,
|
cfg,
|
||||||
resolvedAccountId,
|
resolvedAccountId,
|
||||||
pluginCfg.stripMarkdown ?? true,
|
true, // stripMarkdown
|
||||||
console,
|
console,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -35,14 +35,6 @@ import type { RuntimeEnv } from "../../../src/runtime.js";
|
|||||||
*/
|
*/
|
||||||
export type TwitchRole = "moderator" | "owner" | "vip" | "subscriber" | "all";
|
export type TwitchRole = "moderator" | "owner" | "vip" | "subscriber" | "all";
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin configuration passed from Clawdbot
|
|
||||||
*/
|
|
||||||
export interface TwitchPluginConfig {
|
|
||||||
/** Strip markdown from outbound messages before sending to Twitch (default true). */
|
|
||||||
stripMarkdown?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account configuration for a Twitch channel
|
* Account configuration for a Twitch channel
|
||||||
*/
|
*/
|
||||||
@ -140,5 +132,10 @@ export type {
|
|||||||
OutboundDeliveryResult,
|
OutboundDeliveryResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Import and re-export the schema type
|
||||||
|
import type { TwitchConfigSchema } from "./config-schema.js";
|
||||||
|
import type { z } from "zod";
|
||||||
|
export type TwitchConfig = z.infer<typeof TwitchConfigSchema>;
|
||||||
|
|
||||||
export type { ClawdbotConfig };
|
export type { ClawdbotConfig };
|
||||||
export type { RuntimeEnv };
|
export type { RuntimeEnv };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user