优化
This commit is contained in:
1
client/packages/tailchat-client-sdk/.gitignore
vendored
Normal file
1
client/packages/tailchat-client-sdk/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
lib
|
||||
3
client/packages/tailchat-client-sdk/README.md
Normal file
3
client/packages/tailchat-client-sdk/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Document
|
||||
|
||||
visit website to learn more: [https://tailchat.msgbyte.com/docs/advanced-usage/openapp/about](https://tailchat.msgbyte.com/docs/advanced-usage/openapp/about)
|
||||
5
client/packages/tailchat-client-sdk/jest.config.js
Normal file
5
client/packages/tailchat-client-sdk/jest.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
27
client/packages/tailchat-client-sdk/package.json
Normal file
27
client/packages/tailchat-client-sdk/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "tailchat-client-sdk",
|
||||
"version": "1.0.9",
|
||||
"description": "",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"prepare": "tsc",
|
||||
"release": "npm publish --registry https://registry.npmjs.com/",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "moonrailgun <moonrailgun@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/node": "^18.16.1",
|
||||
"jest": "27.5.1",
|
||||
"ts-jest": "27.1.4",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.3.2",
|
||||
"tailchat-types": "workspace:*",
|
||||
"socket.io-client": "^4.7.1",
|
||||
"socket.io-msgpack-parser": "^3.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { stripMentionTag } from '../utils';
|
||||
|
||||
describe('stripMentionTag', () => {
|
||||
test('simple', () => {
|
||||
expect(
|
||||
stripMentionTag('[at=6448e822834c12425646f473]Robot[/at] Hello')
|
||||
).toBe('Hello');
|
||||
});
|
||||
|
||||
test('not remove other message', () => {
|
||||
expect(
|
||||
stripMentionTag(
|
||||
'[at=6448e822834c12425646f473]Robot[/at] Hello [at=6448e822834c12425646f4732]Robot[/at]'
|
||||
)
|
||||
).toBe('Hello [at=6448e822834c12425646f4732]Robot[/at]');
|
||||
});
|
||||
|
||||
test('also can remove mention ', () => {
|
||||
expect(stripMentionTag('@Robot Hello')).toBe('Hello');
|
||||
});
|
||||
});
|
||||
3
client/packages/tailchat-client-sdk/src/index.ts
Normal file
3
client/packages/tailchat-client-sdk/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './openapi';
|
||||
export * from './plugins/simplenotify';
|
||||
export * from './utils';
|
||||
162
client/packages/tailchat-client-sdk/src/openapi/client/base.ts
Normal file
162
client/packages/tailchat-client-sdk/src/openapi/client/base.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import crypto from 'crypto';
|
||||
|
||||
export class TailchatBaseClient {
|
||||
request: AxiosInstance;
|
||||
jwt: string | null = null;
|
||||
userId: string | null = null;
|
||||
loginP: Promise<void>;
|
||||
|
||||
constructor(
|
||||
public url: string,
|
||||
public appId: string,
|
||||
public appSecret: string
|
||||
) {
|
||||
if (!url || !appId || !appSecret) {
|
||||
throw new Error(
|
||||
'Require params: apiUrl, appId, appSecret. You can set it with env'
|
||||
);
|
||||
}
|
||||
|
||||
this.request = axios.create({
|
||||
baseURL: url,
|
||||
});
|
||||
this.request.interceptors.request.use(async (val) => {
|
||||
if (
|
||||
this.jwt &&
|
||||
['post', 'get'].includes(String(val.method).toLowerCase()) &&
|
||||
!val.headers['X-Token']
|
||||
) {
|
||||
// 任何请求都尝试增加token
|
||||
val.headers['X-Token'] = this.jwt;
|
||||
}
|
||||
|
||||
return val;
|
||||
});
|
||||
this.loginP = this.login();
|
||||
}
|
||||
|
||||
async login() {
|
||||
try {
|
||||
console.log('Login...');
|
||||
const { data } = await this.request.post('/api/openapi/bot/login', {
|
||||
appId: this.appId,
|
||||
token: this.getBotToken(),
|
||||
});
|
||||
|
||||
// NOTICE: 注意,有30天过期时间,需要定期重新登录以换取新的token
|
||||
// 这里先不换
|
||||
this.jwt = data.data?.jwt;
|
||||
this.userId = data.data?.userId;
|
||||
|
||||
console.log('tailchat openapp login success!');
|
||||
|
||||
// 尝试调用函数
|
||||
// this.whoami().then(console.log);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error(
|
||||
`Login failed, please check application credentials or network(Error: ${String(
|
||||
err
|
||||
)})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async waitingForLogin(): Promise<void> {
|
||||
await Promise.resolve(this.loginP);
|
||||
}
|
||||
|
||||
async call(action: string, params = {}) {
|
||||
try {
|
||||
await this.waitingForLogin();
|
||||
console.log('Calling:', action);
|
||||
const { data } = await this.request.post(
|
||||
'/api/' + action.replace(/\./g, '/'),
|
||||
params
|
||||
);
|
||||
|
||||
return data.data;
|
||||
} catch (err: any) {
|
||||
console.error('Service Call Failed:', err);
|
||||
const data: string = err?.response?.data;
|
||||
if (data) {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
action,
|
||||
data,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async whoami(): Promise<{
|
||||
userAgent: string;
|
||||
language: string;
|
||||
user: {
|
||||
_id: string;
|
||||
nickname: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
};
|
||||
token: string;
|
||||
userId: string;
|
||||
}> {
|
||||
return this.call('user.whoami');
|
||||
}
|
||||
|
||||
getBotToken() {
|
||||
return crypto
|
||||
.createHash('md5')
|
||||
.update(this.appId + this.appSecret)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send normal message to tailchat
|
||||
*/
|
||||
async sendMessage(payload: {
|
||||
converseId: string;
|
||||
groupId?: string;
|
||||
content: string;
|
||||
plain?: string;
|
||||
meta?: object;
|
||||
}) {
|
||||
return this.call('chat.message.sendMessage', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply message
|
||||
*/
|
||||
async replyMessage(
|
||||
replyInfo: {
|
||||
messageId: string;
|
||||
author: string;
|
||||
content: string;
|
||||
},
|
||||
payload: {
|
||||
converseId: string;
|
||||
groupId?: string;
|
||||
content: string;
|
||||
plain?: string;
|
||||
meta?: object;
|
||||
}
|
||||
) {
|
||||
return this.sendMessage({
|
||||
...payload,
|
||||
meta: {
|
||||
...payload.meta,
|
||||
mentions: [replyInfo.author],
|
||||
reply: {
|
||||
_id: replyInfo.messageId,
|
||||
author: replyInfo.author,
|
||||
content: replyInfo.content,
|
||||
},
|
||||
},
|
||||
content: `[at=${replyInfo.author}][/at] ${payload.content}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { TailchatBaseClient } from './base';
|
||||
|
||||
export class TailchatHTTPClient extends TailchatBaseClient {}
|
||||
@@ -0,0 +1,8 @@
|
||||
export {
|
||||
/**
|
||||
* @deprecated please rename to TailchatHTTPClient
|
||||
*/
|
||||
TailchatHTTPClient as TailchatClient,
|
||||
TailchatHTTPClient,
|
||||
} from './http';
|
||||
export { TailchatWsClient } from './ws';
|
||||
111
client/packages/tailchat-client-sdk/src/openapi/client/ws.ts
Normal file
111
client/packages/tailchat-client-sdk/src/openapi/client/ws.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { TailchatBaseClient } from './base';
|
||||
import io, { Socket } from 'socket.io-client';
|
||||
import * as msgpackParser from 'socket.io-msgpack-parser';
|
||||
import type { ChatMessage } from 'tailchat-types';
|
||||
|
||||
export class TailchatWsClient extends TailchatBaseClient {
|
||||
public socket: Socket | null = null;
|
||||
|
||||
constructor(
|
||||
public url: string,
|
||||
public appId: string,
|
||||
public appSecret: string,
|
||||
public disableMsgpack: boolean = false
|
||||
) {
|
||||
super(url, appId, appSecret);
|
||||
}
|
||||
|
||||
connect(): Promise<Socket> {
|
||||
return new Promise<Socket>(async (resolve, reject) => {
|
||||
await this.waitingForLogin();
|
||||
|
||||
const token = this.jwt;
|
||||
const socket = (this.socket = io(this.url, {
|
||||
transports: ['websocket'],
|
||||
auth: {
|
||||
token,
|
||||
},
|
||||
forceNew: true,
|
||||
parser: this.disableMsgpack ? undefined : msgpackParser,
|
||||
}));
|
||||
|
||||
socket.once('connect', () => {
|
||||
// 连接成功
|
||||
this.emit('chat.converse.findAndJoinRoom')
|
||||
.then((res) => {
|
||||
console.log('Joined rooms', res.data);
|
||||
resolve(socket);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
socket.once('error', () => {
|
||||
reject();
|
||||
});
|
||||
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.log(`disconnect due to ${reason}`);
|
||||
this.socket = null;
|
||||
});
|
||||
|
||||
socket.onAny((ev) => {
|
||||
console.log('onAny', ev);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (!this.socket) {
|
||||
console.warn('You should call it after connect');
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.disconnect();
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
emit(eventName: string, eventData: any = {}) {
|
||||
if (!this.socket) {
|
||||
console.warn('You should call it after connect');
|
||||
throw new Error('You should call it after connect');
|
||||
}
|
||||
|
||||
return this.socket.emitWithAck(eventName, eventData);
|
||||
}
|
||||
|
||||
on(eventName: string, callback: (payload: any) => void) {
|
||||
if (!this.socket) {
|
||||
console.warn('You should call it after connect');
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.on(eventName, callback);
|
||||
}
|
||||
|
||||
once(eventName: string, callback: (payload: any) => void) {
|
||||
if (!this.socket) {
|
||||
console.warn('You should call it after connect');
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.once(eventName, callback);
|
||||
}
|
||||
|
||||
off(eventName: string, callback: (payload: any) => void) {
|
||||
if (!this.socket) {
|
||||
console.warn('You should call it after connect');
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.off(eventName, callback);
|
||||
}
|
||||
|
||||
onMessage(callback: (messagePayload: ChatMessage) => void) {
|
||||
this.on('notify:chat.message.add', callback);
|
||||
}
|
||||
|
||||
onMessageUpdate(callback: (messagePayload: ChatMessage) => void) {
|
||||
this.on('notify:chat.message.update', callback);
|
||||
}
|
||||
}
|
||||
1
client/packages/tailchat-client-sdk/src/openapi/index.ts
Normal file
1
client/packages/tailchat-client-sdk/src/openapi/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './client';
|
||||
@@ -0,0 +1,24 @@
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* 基于简易推送插件的消息通知服务
|
||||
*
|
||||
* @param hostUrl 实例地址url
|
||||
* @param subscribeId 订阅id
|
||||
* @param text 发送的文本,默认支持bbcode
|
||||
*/
|
||||
export async function sendSimpleNotify(
|
||||
hostUrl: string,
|
||||
subscribeId: string,
|
||||
text: string
|
||||
) {
|
||||
await axios({
|
||||
method: 'post',
|
||||
baseURL: hostUrl,
|
||||
url: '/api/plugin:com.msgbyte.simplenotify/webhook/callback',
|
||||
data: {
|
||||
subscribeId,
|
||||
text,
|
||||
},
|
||||
});
|
||||
}
|
||||
10
client/packages/tailchat-client-sdk/src/utils.ts
Normal file
10
client/packages/tailchat-client-sdk/src/utils.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* remove first [at=xxx]xxx[/at] in message first
|
||||
*/
|
||||
export function stripMentionTag(message: string): string {
|
||||
return message
|
||||
.trim()
|
||||
.replace(/^\[at=.*?\[\/at\]/, '')
|
||||
.replace(/^@\S*\s?/, '')
|
||||
.trimStart();
|
||||
}
|
||||
20
client/packages/tailchat-client-sdk/test/index.ts
Normal file
20
client/packages/tailchat-client-sdk/test/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { TailchatWsClient } from '../src';
|
||||
|
||||
const HOST = process.env.HOST;
|
||||
const APPID = process.env.APPID;
|
||||
const APPSECRET = process.env.APPSECRET;
|
||||
|
||||
if (!HOST || !APPID || !APPSECRET) {
|
||||
console.log('require env: HOST, APPID, APPSECRET');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = new TailchatWsClient(HOST, APPID, APPSECRET);
|
||||
|
||||
client.connect().then(async () => {
|
||||
console.log('Login Success!');
|
||||
|
||||
client.onMessage((message) => {
|
||||
console.log('Receive message', message);
|
||||
});
|
||||
});
|
||||
18
client/packages/tailchat-client-sdk/tsconfig.json
Normal file
18
client/packages/tailchat-client-sdk/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": ["ESNext"],
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"typeRoots": ["./node_modules/@types"]
|
||||
},
|
||||
"include": ["./src/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user