9b4a25799f
Replace fixed ackText with a rule-based AckProvider that picks response templates by message type and intent (translate, summary, rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync, zero I/O, auto-falls back to config.ackText on any error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
131 lines
3.5 KiB
JavaScript
Executable File
131 lines
3.5 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { loadH5Environment } from './load-env.mjs';
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
loadH5Environment(__dirname);
|
||
|
||
const DEFAULT_TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/stable_token';
|
||
const DEFAULT_MENU_CREATE_URL = 'https://api.weixin.qq.com/cgi-bin/menu/create';
|
||
const DEFAULT_MENU_GET_URL = 'https://api.weixin.qq.com/cgi-bin/menu/get';
|
||
|
||
const MENU = {
|
||
button: [
|
||
{
|
||
type: 'view',
|
||
name: 'MeMind',
|
||
url: 'https://m.tkmind.cn',
|
||
},
|
||
{
|
||
type: 'view',
|
||
name: 'M空间',
|
||
url: 'https://m.tkmind.cn/space',
|
||
},
|
||
{
|
||
type: 'view',
|
||
name: 'M广场',
|
||
url: 'https://plaza.tkmind.cn',
|
||
},
|
||
],
|
||
};
|
||
|
||
function endpointFromTokenUrl(pathname, fallback) {
|
||
const tokenUrl = process.env.H5_WECHAT_MP_TOKEN_URL?.trim() || DEFAULT_TOKEN_URL;
|
||
try {
|
||
const url = new URL(tokenUrl);
|
||
url.pathname = pathname;
|
||
url.search = '';
|
||
return url.toString();
|
||
} catch {
|
||
return fallback;
|
||
}
|
||
}
|
||
|
||
async function readJson(response) {
|
||
const text = await response.text();
|
||
if (!response.ok) {
|
||
throw new Error(text || `HTTP ${response.status}`);
|
||
}
|
||
return text ? JSON.parse(text) : null;
|
||
}
|
||
|
||
async function getAccessToken() {
|
||
const appid =
|
||
process.env.H5_WECHAT_MP_APP_ID?.trim() ?? process.env.H5_WECHAT_APP_ID?.trim() ?? '';
|
||
const secret =
|
||
process.env.H5_WECHAT_MP_APP_SECRET?.trim() ?? process.env.H5_WECHAT_APP_SECRET?.trim() ?? '';
|
||
if (!appid || !secret) {
|
||
throw new Error(
|
||
'缺少 H5_WECHAT_MP_APP_ID / H5_WECHAT_MP_APP_SECRET(或 H5_WECHAT_APP_ID / H5_WECHAT_APP_SECRET)',
|
||
);
|
||
}
|
||
|
||
const tokenUrl = process.env.H5_WECHAT_MP_TOKEN_URL?.trim() || DEFAULT_TOKEN_URL;
|
||
const payload = await readJson(
|
||
await fetch(tokenUrl, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
grant_type: 'client_credential',
|
||
appid,
|
||
secret,
|
||
force_refresh: false,
|
||
}),
|
||
}),
|
||
);
|
||
if (!payload?.access_token) {
|
||
throw new Error(payload?.errmsg || `获取 access_token 失败 (${payload?.errcode ?? 'unknown'})`);
|
||
}
|
||
return payload.access_token;
|
||
}
|
||
|
||
async function createMenu(accessToken) {
|
||
const baseUrl =
|
||
process.env.H5_WECHAT_MP_MENU_CREATE_URL?.trim() ||
|
||
endpointFromTokenUrl('/cgi-bin/menu/create', DEFAULT_MENU_CREATE_URL);
|
||
const url = `${baseUrl}?access_token=${encodeURIComponent(accessToken)}`;
|
||
const payload = await readJson(
|
||
await fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(MENU),
|
||
}),
|
||
);
|
||
if (Number(payload?.errcode ?? 0) !== 0) {
|
||
throw new Error(payload?.errmsg || `创建菜单失败 (${payload?.errcode})`);
|
||
}
|
||
return payload;
|
||
}
|
||
|
||
async function getMenu(accessToken) {
|
||
const baseUrl =
|
||
process.env.H5_WECHAT_MP_MENU_GET_URL?.trim() ||
|
||
endpointFromTokenUrl('/cgi-bin/menu/get', DEFAULT_MENU_GET_URL);
|
||
const url = `${baseUrl}?access_token=${encodeURIComponent(accessToken)}`;
|
||
return readJson(await fetch(url, { method: 'GET' }));
|
||
}
|
||
|
||
const dryRun = process.argv.includes('--dry-run');
|
||
if (dryRun) {
|
||
console.log(JSON.stringify(MENU, null, 2));
|
||
process.exit(0);
|
||
}
|
||
|
||
const accessToken = await getAccessToken();
|
||
const created = await createMenu(accessToken);
|
||
const current = await getMenu(accessToken).catch((err) => ({ warning: err.message }));
|
||
|
||
console.log(
|
||
JSON.stringify(
|
||
{
|
||
ok: true,
|
||
created,
|
||
menu: MENU,
|
||
current,
|
||
},
|
||
null,
|
||
2,
|
||
),
|
||
);
|