Commit 4052affc authored by nanahira's avatar nanahira

support consumer message

parent 0e892e04
......@@ -101,4 +101,11 @@ app.command('survey', '卡片调查').action(async ({ session }) => {
}
});
app.command('link').action(() => '<a href="https://koishi.chat">Koishi</a>');
app.command('wait <time:posint>').action(async (argv, time) => {
await new Promise((resolve) => setTimeout(resolve, time * 1000));
return `等待了 ${time} 秒`;
});
app.start();
This diff is collapsed.
......@@ -49,11 +49,11 @@
"form-data": "^4.0.0",
"koa-wechat-public": "^0.1.13",
"koa-xml-body": "^2.2.0",
"koishi-thirdeye": "^11.1.21",
"koishi-thirdeye": "^11.1.24",
"mime2ext": "^1.0.1"
},
"peerDependencies": {
"koishi": "^4.11.1"
"koishi": "^4.11.6"
},
"devDependencies": {
"@koishijs/plugin-help": "^2.0.2",
......
......@@ -34,6 +34,7 @@ import path from 'path';
import { Readable } from 'stream';
import { WechatAdapter } from './adapter';
import XmlParser from 'koa-xml-body';
import { WechatConsumerMessenger } from './message';
export * from './config';
declare module 'koishi' {
......@@ -54,7 +55,7 @@ export default class WechatBot extends Bot<Partial<WechatOfficialConfig>> {
internal: WechatApplication;
private menuMap = new Map<string, string>();
@Inject()
private http: Quester;
http: Quester;
@InjectLogger()
private logger: Logger;
......@@ -130,7 +131,7 @@ export default class WechatBot extends Bot<Partial<WechatOfficialConfig>> {
}
}
private async uploadMedia(element: Element) {
async uploadMedia(element: Element) {
try {
const { type } = element;
const uploadType = type === 'audio' ? 'voice' : type;
......@@ -213,7 +214,7 @@ export default class WechatBot extends Bot<Partial<WechatOfficialConfig>> {
session.subtype = 'private';
session.author = { userId: acc.fromUser };
}
const reply = await session.process();
const reply = await session.process(4500);
if (reply?.length) {
await this.setReply(acc.send, segment.normalize(reply));
}
......@@ -280,14 +281,16 @@ export default class WechatBot extends Bot<Partial<WechatOfficialConfig>> {
guildId?: string,
options?: SendOptions,
): Promise<string[]> {
return [];
return this.sendPrivateMessage(channelId, content, options);
}
async sendPrivateMessage(
userId: string,
content: segment.Fragment,
options?: SendOptions,
): Promise<string[]> {
return [];
return new WechatConsumerMessenger(this, userId, undefined, options).send(
content,
);
}
async getMessage(
channelId: string,
......
import { Logger, Messenger, Random, segment } from 'koishi';
import WechatBot from './index';
interface ConsumerMessagePayload {
msgtype: 'text' | 'image' | 'voice' | 'video';
touser?: string;
text?: {
content: string;
};
image?: {
media_id: string;
};
voice?: {
media_id: string;
};
video?: {
media_id: string;
thumb_media_id?: string;
title: string;
description: string;
};
}
export class WechatConsumerMessenger extends Messenger<WechatBot> {
logger = new Logger('wechat-consumer-messenger');
async sendConsumer(payload: ConsumerMessagePayload) {
const token = await this.bot.internal.getAccessToken();
let success = false;
try {
const result = await this.bot.http.post<{
errcode: number;
errmsg: string;
}>(
'https://api.weixin.qq.com/cgi-bin/message/custom/send',
{
touser: this.channelId,
...payload,
},
{
params: {
access_token: token,
},
},
);
if (result.errcode > 0) {
this.logger.warn(
`Send consumer message failed: ${result.errcode} ${result.errmsg}`,
);
} else {
success = true;
}
} catch (e) {
this.logger.error(`Send consumer message errored: ${e.message}`);
}
if (success) {
const session = this.bot.session();
session.messageId = Random.id();
session.app.emit(session, 'send', session);
this.results.push(session);
}
}
private buffer = '';
async flush() {
const content = this.buffer.trim();
if (content) {
await this.sendConsumer({
msgtype: 'text',
text: {
content,
},
});
}
}
text(text: string) {
this.buffer += text;
}
async visit(element: segment) {
const { type, attrs, children } = element;
switch (type) {
case 'text':
this.text(attrs.content);
break;
case 'p':
await this.render(children);
this.text('\n');
break;
case 'a':
this.text(element.toString());
break;
case 'at':
if (attrs.id) {
this.text(`@${attrs.id}`);
} else if (attrs.type === 'all') {
this.text('@全体成员');
} else if (attrs.type === 'here') {
this.text('@在线成员');
} else if (attrs.role) {
this.text(`@${attrs.role}`);
}
break;
case 'sharp':
this.text(` #${attrs.name} `);
break;
case 'message':
await this.flush();
await this.render(children);
await this.flush();
break;
case 'image':
case 'voice':
case 'video':
await this.flush();
const mediaId = await this.bot.uploadMedia(element);
if (mediaId) {
await this.sendConsumer({
msgtype: type,
[type]: {
media_id: mediaId,
title: attrs.title,
description: attrs.description,
},
});
}
break;
default:
await this.render(children);
}
}
}
import { Element } from 'koishi';
const patternTypes = ['message', 'p', 'a'];
const patternTypes = ['message', 'p'];
export function getFirstElement(elements: Element[]): Element {
if (!elements.length) return;
......@@ -18,12 +18,11 @@ export function getPlainText(elements: Element[]) {
for (const element of elements) {
if (element.type === 'text') {
buffer += element.attrs.content;
} else if (element.type === 'a') {
buffer += element.toString();
} else if (patternTypes.includes(element.type)) {
buffer += getPlainText(element.children);
buffer += '\n';
if (element.type === 'a' && element.attrs.href) {
buffer += ` (${element.attrs.href})`;
}
} else if (element.type === 'at') {
if (element.attrs.id) {
buffer += `@${element.attrs.id}`;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment