update
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "docker",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"docker:build": "docker build -t docker.xiongxiao.me/code-flow:v0.0.2 .",
|
||||
"docker:push": "docker push docker.xiongxiao.me/code-flow:v0.0.2",
|
||||
"docker:run": "docker run -it --name code-flow -p 4000:4000 docker.xiongxiao.me/code-flow:v0.0.2"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
"license": "MIT",
|
||||
"type": "module"
|
||||
}
|
||||
47
pnpm-lock.yaml
generated
47
pnpm-lock.yaml
generated
@@ -152,6 +152,31 @@ importers:
|
||||
specifier: ^4.1.13
|
||||
version: 4.1.13
|
||||
|
||||
wxmsg:
|
||||
dependencies:
|
||||
'@kevisual/context':
|
||||
specifier: ^0.0.4
|
||||
version: 0.0.4
|
||||
'@kevisual/router':
|
||||
specifier: 0.0.33
|
||||
version: 0.0.33
|
||||
'@types/node':
|
||||
specifier: ^24.10.1
|
||||
version: 24.10.1
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
xml2js:
|
||||
specifier: ^0.6.2
|
||||
version: 0.6.2
|
||||
devDependencies:
|
||||
'@types/crypto-js':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
'@types/xml2js':
|
||||
specifier: ^0.4.14
|
||||
version: 0.4.14
|
||||
|
||||
packages:
|
||||
|
||||
'@ioredis/commands@1.4.0':
|
||||
@@ -309,6 +334,9 @@ packages:
|
||||
'@types/ws@8.18.1':
|
||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||
|
||||
'@types/xml2js@0.4.14':
|
||||
resolution: {integrity: sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==}
|
||||
|
||||
'@zxing/text-encoding@0.9.0':
|
||||
resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
|
||||
|
||||
@@ -1645,6 +1673,10 @@ packages:
|
||||
resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
xml2js@0.6.2:
|
||||
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
xmlbuilder@11.0.1:
|
||||
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
|
||||
engines: {node: '>=4.0'}
|
||||
@@ -1946,6 +1978,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 24.10.1
|
||||
|
||||
'@types/xml2js@0.4.14':
|
||||
dependencies:
|
||||
'@types/node': 24.10.1
|
||||
|
||||
'@zxing/text-encoding@0.9.0':
|
||||
optional: true
|
||||
|
||||
@@ -2183,6 +2219,10 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.3(supports-color@5.5.0):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -2987,7 +3027,7 @@ snapshots:
|
||||
|
||||
send@1.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
debug: 4.4.3
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
@@ -3299,6 +3339,11 @@ snapshots:
|
||||
sax: 1.4.1
|
||||
xmlbuilder: 11.0.1
|
||||
|
||||
xml2js@0.6.2:
|
||||
dependencies:
|
||||
sax: 1.4.1
|
||||
xmlbuilder: 11.0.1
|
||||
|
||||
xmlbuilder@11.0.1: {}
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
packages:
|
||||
- 'submodules/*'
|
||||
- 'wxmsg/*'
|
||||
|
||||
@@ -16,11 +16,15 @@ export { manager };
|
||||
// console.log('app', app, );
|
||||
// console.log('app2 context', global.context);
|
||||
// console.log('app equal', app === ManagerApp);
|
||||
|
||||
const delayRun = () => {
|
||||
if (isExist) {
|
||||
loadManager({ runtime: 'server', configFilename: 'assistant-apps-config.json' });
|
||||
} else {
|
||||
loadManager({ runtime: 'server' });
|
||||
}
|
||||
}
|
||||
setTimeout(delayRun, 100);
|
||||
|
||||
// middleware: ['auth-admin']
|
||||
/*
|
||||
|
||||
20
wxmsg/bun.config.mjs
Normal file
20
wxmsg/bun.config.mjs
Normal file
@@ -0,0 +1,20 @@
|
||||
import { resolvePath } from '@kevisual/use-config';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const entry = 'src/index.ts';
|
||||
const naming = 'app';
|
||||
const external = [];
|
||||
/**
|
||||
* @type {import('bun').BuildConfig}
|
||||
*/
|
||||
await Bun.build({
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||
naming: {
|
||||
entry: `${naming}.js`,
|
||||
},
|
||||
external,
|
||||
env: 'KEVISUAL_*',
|
||||
});
|
||||
40
wxmsg/package.json
Normal file
40
wxmsg/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@kevisual/wxmsg",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
"basename": "/root/wxmsg",
|
||||
"app": {
|
||||
"type": "system-app",
|
||||
"key": "root/wxmsg",
|
||||
"entry": "./app.js",
|
||||
"runtime": [
|
||||
"server"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "pnpm dev src/index.ts",
|
||||
"build": "bun run bun.config.mjs",
|
||||
"prepub": "rimraf dist && rimraf pack-dist && pnpm build",
|
||||
"pub": "envision pack -p -u -c"
|
||||
},
|
||||
"keywords": [],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/router": "0.0.33",
|
||||
"@types/node": "^24.10.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/xml2js": "^0.4.14"
|
||||
}
|
||||
}
|
||||
103
wxmsg/src/index.ts
Normal file
103
wxmsg/src/index.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { SimpleRouter } from '@kevisual/router/simple';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import xml2js from 'xml2js';
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import { Redis } from 'ioredis';
|
||||
import http from 'node:http';
|
||||
import { Wx, parseWxMessage } from './wx/index.ts';
|
||||
import { config } from './modules/config.ts';
|
||||
export const simpleRouter: SimpleRouter = await useContextKey('router');
|
||||
export const redis: Redis = await useContextKey('redis');
|
||||
|
||||
simpleRouter.get('/api/wxmsg', async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
console.log('微信检测服务是否可用');
|
||||
const query = simpleRouter.getSearch(req);
|
||||
const body = await simpleRouter.getBody(req);
|
||||
const {
|
||||
signature, // 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
|
||||
timestamp, // 时间戳
|
||||
nonce, // 随机数
|
||||
echostr, // 随机字符串
|
||||
} = query;
|
||||
const token = 'xiongabc123';
|
||||
let str = [token, timestamp, nonce].sort().join('');
|
||||
let strSha1 = CryptoJS.SHA1(str).toString();
|
||||
// 签名对比,相同则按照微信要求返回echostr参数值
|
||||
if (signature == strSha1) {
|
||||
res.end(echostr);
|
||||
} else {
|
||||
res.end('send fail');
|
||||
}
|
||||
});
|
||||
|
||||
export const getJsonFromXml = async (req: http.IncomingMessage): Promise<any> => {
|
||||
return await new Promise((resolve) => {
|
||||
// 读取请求数据
|
||||
let data = '';
|
||||
req.setEncoding('utf8');
|
||||
// 监听data事件,接收数据片段
|
||||
req.on('data', (chunk: string) => {
|
||||
data += chunk;
|
||||
});
|
||||
// 当请求结束时处理数据
|
||||
req.on('end', () => {
|
||||
try {
|
||||
// 使用xml2js解析XML
|
||||
xml2js.parseString(data, function (err, result) {
|
||||
if (err) {
|
||||
console.error('XML解析错误:', err);
|
||||
resolve(null);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('处理请求时出错:', error);
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
simpleRouter.post('/api/wxmsg', async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
try {
|
||||
const xml = await getJsonFromXml(req);
|
||||
const msgOrigin = xml?.xml;
|
||||
if (!msgOrigin) {
|
||||
console.error('No message received');
|
||||
res.end('');
|
||||
return
|
||||
}
|
||||
const msg = parseWxMessage(msgOrigin);
|
||||
const { fromusername, msgtype } = msg;
|
||||
res.end('')
|
||||
if (msgtype === 'event') {
|
||||
console.log('Received event message');
|
||||
return
|
||||
}
|
||||
if (fromusername) {
|
||||
// 代理分析返回
|
||||
const wx = new Wx({ appId: config.WX_MP_APP_ID, appSecret: config.WX_MP_APP_SECRET, redis });
|
||||
wx.analyzeUserMsg(msg);
|
||||
}
|
||||
return
|
||||
|
||||
|
||||
// 直接返回
|
||||
// const builder = new xml2js.Builder();
|
||||
// const result = builder.buildObject({
|
||||
// xml: {
|
||||
// ToUserName: fromusername,
|
||||
// FromUserName: tousername,
|
||||
// CreateTime: Date.now(),
|
||||
// MsgType: msgtype,
|
||||
// Content: 'Hello ' + content,
|
||||
// },
|
||||
// });
|
||||
// res.end(result);
|
||||
} catch (e) {
|
||||
console.error('Error processing message:', e);
|
||||
}
|
||||
});
|
||||
7
wxmsg/src/modules/config.ts
Normal file
7
wxmsg/src/modules/config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { useConfig } from "@kevisual/context";
|
||||
|
||||
type Config = {
|
||||
WX_MP_APP_ID: string;
|
||||
WX_MP_APP_SECRET: string;
|
||||
}
|
||||
export const config = useConfig<Config>();
|
||||
48
wxmsg/src/wx/access-token.ts
Normal file
48
wxmsg/src/wx/access-token.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
// const accessURL = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET'
|
||||
const accessURL = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential';
|
||||
|
||||
type AccessData = {
|
||||
"access_token": string;
|
||||
"expires_in": number; // 7200, 单位秒 2小时
|
||||
"accessToken": string;
|
||||
"expiredAt": number; // 到期时间戳,单位毫秒
|
||||
}
|
||||
|
||||
type ErrorData = {
|
||||
errcode: number;
|
||||
errmsg: string;
|
||||
}
|
||||
export const getAccessURL = (appId: string, appSecret: string): string => {
|
||||
return `${accessURL}&appid=${appId}&secret=${appSecret}`;
|
||||
};
|
||||
|
||||
export const getAccessToken = async (appId: string, appSecret: string): Promise<{
|
||||
code: number;
|
||||
data?: AccessData;
|
||||
message?: string;
|
||||
}> => {
|
||||
const url = getAccessURL(appId, appSecret);
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
if ((data as ErrorData).errcode) {
|
||||
return {
|
||||
code: 500,
|
||||
message: (data as ErrorData).errmsg,
|
||||
}
|
||||
}
|
||||
console.log('access token data', data);
|
||||
data.accessToken = data.access_token;
|
||||
data.expiredAt = Date.now() + data.expires_in * 1000;
|
||||
return {
|
||||
code: 200,
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
if(require.main === module) {
|
||||
// 测试代码
|
||||
const appId = 'wxff97d569b1db16b6';
|
||||
const appSecret = '012d84d0d2b914de95f4e9ca84923aed';
|
||||
const res = await getAccessToken(appId, appSecret);
|
||||
console.log('getAccessToken res', res);
|
||||
}
|
||||
78
wxmsg/src/wx/index.ts
Normal file
78
wxmsg/src/wx/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { getAccessToken } from './access-token';
|
||||
import { Redis } from 'ioredis';
|
||||
import { WxCustomServiceMsg, WxMsgText } from './type/custom-service.ts';
|
||||
|
||||
export * from './type/custom-service.ts';
|
||||
export * from './type/send.ts';
|
||||
|
||||
/**
|
||||
* 从
|
||||
* @param str
|
||||
* @returns
|
||||
*/
|
||||
export const changeToLowerCase = (str: string): string => {
|
||||
return str.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
export class Wx {
|
||||
private appId: string;
|
||||
private appSecret: string;
|
||||
public redis: Redis | null = null;
|
||||
constructor({ appId, appSecret, redis }: { appId: string; appSecret: string; redis?: Redis }) {
|
||||
this.appId = appId;
|
||||
this.appSecret = appSecret;
|
||||
this.redis = redis! || null;
|
||||
}
|
||||
|
||||
public async getAccessToken(): Promise<string> {
|
||||
const _accessToken = await this.redis?.get(`wx:access_token:${this.appId}`);
|
||||
if (_accessToken) {
|
||||
return _accessToken;
|
||||
}
|
||||
const res = await getAccessToken(this.appId, this.appSecret);
|
||||
if (res.code !== 200 || !res.data) {
|
||||
throw new Error(`Failed to get access token: ${res.message || 'unknown error'}`);
|
||||
}
|
||||
const { accessToken, expires_in } = res.data;
|
||||
await this.redis?.set(`wx:access_token:${this.appId}`, accessToken, 'EX', expires_in - 200);
|
||||
return accessToken;
|
||||
}
|
||||
public async analyzeUserMsg(msg: WxCustomServiceMsg) {
|
||||
const touser = msg.fromusername;
|
||||
const msgType = msg.msgtype;
|
||||
if (msgType !== 'text') {
|
||||
console.log('Only text messages are supported for auto-reply.');
|
||||
console.log('Analyzed message:', { touser, msgType });
|
||||
return;
|
||||
}
|
||||
const txtMsg = msg as WxMsgText;
|
||||
const content = txtMsg.content;
|
||||
|
||||
console.log('Analyzing user message:', { touser, msgType, content });
|
||||
const sendData = {
|
||||
touser,
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'Hello World',
|
||||
},
|
||||
}
|
||||
this.sendUserMessage(sendData);
|
||||
}
|
||||
public async sendUserMessage(data: any) {
|
||||
const accessToken = await this.getAccessToken();
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}`
|
||||
|
||||
const res = await this.post(url, data);
|
||||
if (res.errcode !== 0) {
|
||||
console.error('Failed to send user message:', res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
public async post(url: string, data: any) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => res.json());
|
||||
}
|
||||
}
|
||||
16
wxmsg/src/wx/send-user.ts
Normal file
16
wxmsg/src/wx/send-user.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export const sendUser = async (accessToken: string) => {
|
||||
const data = {
|
||||
touser: 'omcvy7AHC6bAA0QM4x9_bE0fGD1g',
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'Hello World',
|
||||
},
|
||||
};
|
||||
const url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN';
|
||||
const link = url.replace('ACCESS_TOKEN', accessToken);
|
||||
const res = await fetch(link, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => res.json());
|
||||
console.log('res', res);
|
||||
};
|
||||
94
wxmsg/src/wx/type/custom-service.ts
Normal file
94
wxmsg/src/wx/type/custom-service.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
export type WxMsgBase<T = any> = {
|
||||
tousername: string;
|
||||
fromusername: string;
|
||||
createtime: string;
|
||||
msgtype: T extends { msgtype: infer U } ? U : string;
|
||||
msgid: string;
|
||||
} & T;
|
||||
export type WxMsgText = WxMsgBase<{
|
||||
msgtype: 'text';
|
||||
content: string;
|
||||
}>;
|
||||
export type WxMsgEvent = WxMsgBase<{
|
||||
msgtype: 'event';
|
||||
event: 'subscribe' | 'unsubscribe' | 'click' | 'location' | 'scan';
|
||||
eventkey: string;
|
||||
/**
|
||||
*
|
||||
* 用户同意上报地理位置后,每次进入服务号会话时,都会在进入时上报地理位置,
|
||||
* 或在进入会话后每5秒上报一次地理位置,服务号可以在公众平台网站中修改以上设置。
|
||||
* 上报地理位置时,微信会将上报地理位置事件推送到开发者填写的URL。
|
||||
*/
|
||||
longitude?: string; // 经度
|
||||
latitude?: string; // 纬度
|
||||
precision?: string; // 精度
|
||||
|
||||
/**
|
||||
* 用户未关注时,进行关注后的扫描带参数二维码事件推送
|
||||
* eventkey为 qrscene_为前缀,后面为二维码的场景值ID
|
||||
*/
|
||||
ticket?: string;
|
||||
}>;
|
||||
export type WxMsgVoice = WxMsgBase<{
|
||||
mediaid: string;
|
||||
format: string; // 语音格式,如amr,speex等
|
||||
recognition?: string; // 语音识别结果,UTF8编码
|
||||
mediaid16k?: string; // 语音播放时间长度,单位为秒
|
||||
}>;
|
||||
|
||||
export type WxMsgImage = WxMsgBase<{
|
||||
msgtype: 'image';
|
||||
picurl: string;
|
||||
mediaid: string;
|
||||
}>;
|
||||
|
||||
export type WxMsgLocation = WxMsgBase<{
|
||||
msgtype: 'location';
|
||||
location_x: string; // 地理位置纬度
|
||||
location_y: string; // 地理位置经度
|
||||
scale: string; // 地图缩放大小
|
||||
label: string; // 地理位置信息
|
||||
}>;
|
||||
export type WxMsgVideo = WxMsgBase<{
|
||||
msgtype: 'video';
|
||||
mediaid: string;
|
||||
thumbmediaid: string;
|
||||
}>;
|
||||
|
||||
export type WxMsgLink = WxMsgBase<{
|
||||
msgtype: 'link';
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
}>;
|
||||
|
||||
export type WxMsgFile = WxMsgBase<{
|
||||
msgtype: 'file';
|
||||
title: string;
|
||||
description: string;
|
||||
filekey: string;
|
||||
filemd5: string;
|
||||
filetotallen: string;
|
||||
}>;
|
||||
|
||||
export type WxCustomServiceMsg = WxMsgText | WxMsgEvent | WxMsgVoice | WxMsgImage | WxMsgLocation | WxMsgVideo | WxMsgLink | WxMsgFile;
|
||||
|
||||
export const parseWxMessage = (msg: any): WxCustomServiceMsg => {
|
||||
const keys = Object.keys(msg);
|
||||
let values = keys.map((key) => {
|
||||
const value = msg[key];
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
return value[0];
|
||||
}
|
||||
return value;
|
||||
}).reduce((acc, curr, index) => {
|
||||
const newKey = keys[index].toLowerCase();
|
||||
if (newKey === 'event') {
|
||||
curr = curr.toLowerCase();
|
||||
}
|
||||
acc[newKey] = curr;
|
||||
return acc;
|
||||
}, {} as any);
|
||||
return values as WxCustomServiceMsg;
|
||||
|
||||
}
|
||||
91
wxmsg/src/wx/type/send.ts
Normal file
91
wxmsg/src/wx/type/send.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
export type Send<T = any> = {
|
||||
touser: string;
|
||||
msgtype: 'text' | 'image' | 'voice' | 'video' | 'music' | 'news' | 'mpnews' | 'mpnewsarticle' | 'msgmenu' | 'wxcard' | 'miniprogrampage' | 'customservice' | 'aimsgcontext';
|
||||
} & T;
|
||||
|
||||
type SendText = Send<{
|
||||
text: {
|
||||
content: string;
|
||||
}
|
||||
}>;
|
||||
|
||||
type SendImage = Send<{
|
||||
image: {
|
||||
media_id: string;
|
||||
}
|
||||
}>;
|
||||
type SendVoice = Send<{
|
||||
voice: {
|
||||
media_id: string;
|
||||
}
|
||||
}>
|
||||
type SendVideo = Send<{
|
||||
video: {
|
||||
media_id: string;
|
||||
thumb_media_id: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
}>
|
||||
type SendMusic = Send<{
|
||||
music: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
musicurl?: string;
|
||||
hqmusicurl?: string;
|
||||
thumb_media_id: string;
|
||||
}
|
||||
}>
|
||||
type SendNews = Send<{
|
||||
news: {
|
||||
articles: Array<{
|
||||
title: string;
|
||||
description?: string;
|
||||
url: string;
|
||||
picurl?: string;
|
||||
}>
|
||||
}
|
||||
}>
|
||||
type SendMpnews = Send<{
|
||||
mpnews: {
|
||||
media_id: string;
|
||||
}
|
||||
}>
|
||||
type SendMsgmenu = Send<{
|
||||
msgmenu: {
|
||||
head_content: string;
|
||||
tail_content: string;
|
||||
list: Array<{
|
||||
id: string;
|
||||
content: string;
|
||||
}>
|
||||
}
|
||||
}>
|
||||
type SendWxcard = Send<{
|
||||
wxcard: {
|
||||
card_id: string;
|
||||
}
|
||||
}>
|
||||
type SendMiniprogrampage = Send<{
|
||||
miniprogrampage: {
|
||||
title: string;
|
||||
appid: string;
|
||||
pagepath: string;
|
||||
thumb_media_id: string;
|
||||
}
|
||||
}>
|
||||
type SendCustomservice = Send<{
|
||||
customservice: {
|
||||
kf_account: string;
|
||||
}
|
||||
}>
|
||||
type SendAimsgcontext = Send<{
|
||||
aimsgcontext: {
|
||||
/**
|
||||
* 消息下方增加灰色 wording “内容由第三方AI生成” 0 不增加 1 增加
|
||||
*/
|
||||
is_ai_msg: number;
|
||||
}
|
||||
}>
|
||||
|
||||
export type WxSendMsgType = SendText | SendImage | SendVoice | SendVideo | SendMusic | SendNews | SendMpnews | SendMsgmenu | SendWxcard | SendMiniprogrampage | SendCustomservice | SendAimsgcontext;
|
||||
Reference in New Issue
Block a user