feat: 支持可选配置图床服务器地址

This commit is contained in:
sliverp
2026-01-30 15:12:28 +08:00
parent a13aacbc3b
commit a9b0c3ff10
5 changed files with 111 additions and 17 deletions

View File

@@ -63,6 +63,7 @@ export const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount> = {
clientSecret, clientSecret,
clientSecretFile: input.tokenFile, clientSecretFile: input.tokenFile,
name: input.name, name: input.name,
imageServerBaseUrl: input.imageServerBaseUrl,
}); });
}, },
}, },

View File

@@ -63,6 +63,7 @@ export function resolveQQBotAccount(
dmPolicy: qqbot?.dmPolicy, dmPolicy: qqbot?.dmPolicy,
allowFrom: qqbot?.allowFrom, allowFrom: qqbot?.allowFrom,
systemPrompt: qqbot?.systemPrompt, systemPrompt: qqbot?.systemPrompt,
imageServerBaseUrl: qqbot?.imageServerBaseUrl,
}; };
appId = qqbot?.appId ?? ""; appId = qqbot?.appId ?? "";
} else { } else {
@@ -97,6 +98,7 @@ export function resolveQQBotAccount(
clientSecret, clientSecret,
secretSource, secretSource,
systemPrompt: accountConfig.systemPrompt, systemPrompt: accountConfig.systemPrompt,
imageServerBaseUrl: accountConfig.imageServerBaseUrl || process.env.QQBOT_IMAGE_SERVER_BASE_URL,
config: accountConfig, config: accountConfig,
}; };
} }
@@ -107,7 +109,7 @@ export function resolveQQBotAccount(
export function applyQQBotAccountConfig( export function applyQQBotAccountConfig(
cfg: MoltbotConfig, cfg: MoltbotConfig,
accountId: string, accountId: string,
input: { appId?: string; clientSecret?: string; clientSecretFile?: string; name?: string } input: { appId?: string; clientSecret?: string; clientSecretFile?: string; name?: string; imageServerBaseUrl?: string }
): MoltbotConfig { ): MoltbotConfig {
const next = { ...cfg }; const next = { ...cfg };
@@ -124,6 +126,7 @@ export function applyQQBotAccountConfig(
? { clientSecretFile: input.clientSecretFile } ? { clientSecretFile: input.clientSecretFile }
: {}), : {}),
...(input.name ? { name: input.name } : {}), ...(input.name ? { name: input.name } : {}),
...(input.imageServerBaseUrl ? { imageServerBaseUrl: input.imageServerBaseUrl } : {}),
}, },
}; };
} else { } else {
@@ -144,6 +147,7 @@ export function applyQQBotAccountConfig(
? { clientSecretFile: input.clientSecretFile } ? { clientSecretFile: input.clientSecretFile }
: {}), : {}),
...(input.name ? { name: input.name } : {}), ...(input.name ? { name: input.name } : {}),
...(input.imageServerBaseUrl ? { imageServerBaseUrl: input.imageServerBaseUrl } : {}),
}, },
}, },
}, },

View File

@@ -96,8 +96,15 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
throw new Error("QQBot not configured (missing appId or clientSecret)"); throw new Error("QQBot not configured (missing appId or clientSecret)");
} }
// 尝试启动图床服务器 // 如果配置了公网 URL启动图床服务器
const imageServerBaseUrl = await ensureImageServer(log); let imageServerBaseUrl: string | null = null;
if (account.imageServerBaseUrl) {
await ensureImageServer(log);
imageServerBaseUrl = account.imageServerBaseUrl;
log?.info(`[qqbot:${account.accountId}] Image server enabled with URL: ${imageServerBaseUrl}`);
} else {
log?.info(`[qqbot:${account.accountId}] Image server disabled (no imageServerBaseUrl configured)`);
}
let reconnectAttempts = 0; let reconnectAttempts = 0;
let isAborted = false; let isAborted = false;
@@ -232,15 +239,17 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
const envelopeOptions = pluginRuntime.channel.reply.resolveEnvelopeFormatOptions(cfg); const envelopeOptions = pluginRuntime.channel.reply.resolveEnvelopeFormatOptions(cfg);
// 组装消息体,添加系统提示词 // 组装消息体,添加系统提示词
const builtinPrompt = `由于平台限制你的回复中不可以包含任何URL。 let builtinPrompt = "由于平台限制你的回复中不可以包含任何URL。";
// 只有配置了图床公网地址,才告诉 AI 可以发送图片
if (imageServerBaseUrl) {
builtinPrompt += `
【发送图片】 【发送图片】
如果需要发送本地图片文件给用户,请在回复中使用以下格式: 你可以发送本地图片文件给用户。只需在回复中直接引用图片的绝对路径即可,系统会自动处理。
MEDIA:/绝对路径/图片文件.png 支持 png、jpg、gif、webp 格式。`;
}
例如MEDIA:/home/ubuntu/clawd/downloads/image.png
系统会自动将该路径的图片发送给用户。支持 png、jpg、gif、webp 格式。`;
const systemPrompts = [builtinPrompt]; const systemPrompts = [builtinPrompt];
if (account.systemPrompt) { if (account.systemPrompt) {
systemPrompts.push(account.systemPrompt); systemPrompts.push(account.systemPrompt);
@@ -486,13 +495,16 @@ MEDIA:/绝对路径/图片文件.png
textWithoutImages = textWithoutImages.replace(match[0], "").trim(); textWithoutImages = textWithoutImages.replace(match[0], "").trim();
} }
// 处理剩余文本中的 URL 点号 // 处理剩余文本中的 URL 点号(只有在没有图片的情况下才替换,避免误伤)
const originalText = textWithoutImages; const hasImages = imageUrls.length > 0;
textWithoutImages = textWithoutImages.replace(/([a-zA-Z0-9])\.([a-zA-Z0-9])/g, "$1_$2"); let hasReplacement = false;
if (!hasImages) {
const hasReplacement = textWithoutImages !== originalText; const originalText = textWithoutImages;
if (hasReplacement && textWithoutImages.trim()) { textWithoutImages = textWithoutImages.replace(/([a-zA-Z0-9])\.([a-zA-Z0-9])/g, "$1_$2");
textWithoutImages += "\n\n由于平台限制回复中的部分符号已被替换"; hasReplacement = textWithoutImages !== originalText;
if (hasReplacement && textWithoutImages.trim()) {
textWithoutImages += "\n\n由于平台限制回复中的部分符号已被替换";
}
} }
try { try {

View File

@@ -29,12 +29,14 @@ interface QQBotChannelConfig {
clientSecret?: string; clientSecret?: string;
clientSecretFile?: string; clientSecretFile?: string;
name?: string; name?: string;
imageServerBaseUrl?: string;
accounts?: Record<string, { accounts?: Record<string, {
enabled?: boolean; enabled?: boolean;
appId?: string; appId?: string;
clientSecret?: string; clientSecret?: string;
clientSecretFile?: string; clientSecretFile?: string;
name?: string; name?: string;
imageServerBaseUrl?: string;
}>; }>;
} }
@@ -195,6 +197,44 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
).trim(); ).trim();
} }
// 询问是否配置图片发送功能
let imageServerBaseUrl: string | null = null;
const existingImageUrl = (next.channels?.qqbot as QQBotChannelConfig)?.imageServerBaseUrl
|| process.env.QQBOT_IMAGE_SERVER_BASE_URL;
const wantImageSupport = await prompter.confirm({
message: "是否启用图片发送功能?(需要服务器有公网 IP",
initialValue: Boolean(existingImageUrl),
});
if (wantImageSupport) {
imageServerBaseUrl = String(
await prompter.text({
message: "请输入服务器公网地址(格式: http://公网IP:18765",
placeholder: "例如: http://123.45.67.89:18765",
initialValue: existingImageUrl || undefined,
validate: (value) => {
if (!value?.trim()) return "公网地址不能为空";
if (!value.startsWith("http://") && !value.startsWith("https://")) {
return "地址必须以 http:// 或 https:// 开头";
}
return undefined;
},
}),
).trim();
await prompter.note(
[
"图片发送功能已启用。请确保:",
"1. 服务器防火墙已开放 18765 端口",
"2. 云服务器安全组已放行 18765 端口(入站)",
"",
"如果图片发送失败,请检查端口是否可从公网访问。",
].join("\n"),
"图片功能配置",
);
}
// 应用配置 // 应用配置
if (appId && clientSecret) { if (appId && clientSecret) {
if (accountId === DEFAULT_ACCOUNT_ID) { if (accountId === DEFAULT_ACCOUNT_ID) {
@@ -207,6 +247,7 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
enabled: true, enabled: true,
appId, appId,
clientSecret, clientSecret,
...(imageServerBaseUrl ? { imageServerBaseUrl } : {}),
}, },
}, },
}; };
@@ -225,6 +266,38 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
enabled: true, enabled: true,
appId, appId,
clientSecret, clientSecret,
...(imageServerBaseUrl ? { imageServerBaseUrl } : {}),
},
},
},
},
};
}
} else if (imageServerBaseUrl) {
// 只更新 imageServerBaseUrl
if (accountId === DEFAULT_ACCOUNT_ID) {
next = {
...next,
channels: {
...next.channels,
qqbot: {
...next.channels?.qqbot,
imageServerBaseUrl,
},
},
};
} else {
next = {
...next,
channels: {
...next.channels,
qqbot: {
...next.channels?.qqbot,
accounts: {
...(next.channels?.qqbot as QQBotChannelConfig)?.accounts,
[accountId]: {
...(next.channels?.qqbot as QQBotChannelConfig)?.accounts?.[accountId],
imageServerBaseUrl,
}, },
}, },
}, },

View File

@@ -19,6 +19,8 @@ export interface ResolvedQQBotAccount {
secretSource: "config" | "file" | "env" | "none"; secretSource: "config" | "file" | "env" | "none";
/** 系统提示词 */ /** 系统提示词 */
systemPrompt?: string; systemPrompt?: string;
/** 图床服务器公网地址 */
imageServerBaseUrl?: string;
config: QQBotAccountConfig; config: QQBotAccountConfig;
} }
@@ -35,6 +37,8 @@ export interface QQBotAccountConfig {
allowFrom?: string[]; allowFrom?: string[];
/** 系统提示词,会添加在用户消息前面 */ /** 系统提示词,会添加在用户消息前面 */
systemPrompt?: string; systemPrompt?: string;
/** 图床服务器公网地址,用于发送图片,例如 http://your-ip:18765 */
imageServerBaseUrl?: string;
} }
/** /**