-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dingtalk): add adapter-dingtalk
- Loading branch information
1 parent
7bb4967
commit 2d796a1
Showing
8 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"name": "@satorijs/adapter-dingtalk", | ||
"description": "Dingtalk Adapter for Satorijs", | ||
"version": "1.0.1", | ||
"main": "lib/index.js", | ||
"typings": "lib/index.d.ts", | ||
"files": [ | ||
"lib" | ||
], | ||
"author": "LittleC <[email protected]>", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/satorijs/satori.git", | ||
"directory": "adapters/dingtalk" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/satorijs/satori/issues" | ||
}, | ||
"homepage": "https://koishi.chat/plugins/adapter/dingtalk.html", | ||
"keywords": [ | ||
"bot", | ||
"dingtalk", | ||
"adapter", | ||
"chatbot", | ||
"satori" | ||
], | ||
"peerDependencies": { | ||
"@satorijs/satori": "^2.5.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { Bot, Context, Logger, Quester, Schema, Universal } from '@satorijs/satori' | ||
import { HttpServer } from './http' | ||
import { DingtalkMessageEncoder } from './message' | ||
|
||
const logger = new Logger('dingtalk') | ||
|
||
export class DingtalkBot extends Bot<DingtalkBot.Config> { | ||
static MessageEncoder = DingtalkMessageEncoder | ||
public oldHttp: Quester | ||
public http: Quester | ||
constructor(ctx: Context, config: DingtalkBot.Config) { | ||
super(ctx, config) | ||
this.http = ctx.http.extend(config) | ||
ctx.plugin(HttpServer, this) | ||
} | ||
|
||
tokenExpiresAt: number; | ||
public token: string | ||
|
||
async refreshToken() { | ||
if (this.tokenExpiresAt && this.tokenExpiresAt > Date.now()) return | ||
const data = await this.http.post('/oauth2/accessToken', { | ||
appKey: this.config.appkey, | ||
appSecret: this.config.secret | ||
}) | ||
logger.debug('gettoken result: %o', data) | ||
this.tokenExpiresAt = Date.now() + data.expireIn * 1000 | ||
this.token = data.accessToken | ||
// https://open.dingtalk.com/document/orgapp/authorization-overview | ||
this.http = this.http.extend({ | ||
headers: { | ||
'x-acs-dingtalk-access-token': data.accessToken | ||
} | ||
}).extend(this.config) | ||
} | ||
} | ||
|
||
export namespace DingtalkBot { | ||
export interface Config extends Bot.Config, Quester.Config { | ||
secret: string | ||
protocol: string | ||
appkey: string | ||
} | ||
|
||
export const Config: Schema<Config> = Schema.intersect([ | ||
Schema.object({ | ||
protocol: Schema.string().required(), | ||
secret: Schema.string().required().description('机器人密钥。'), | ||
appkey: Schema.string().required() | ||
}), | ||
Quester.createConfig("https://api.dingtalk.com/v1.0/") | ||
Check failure on line 51 in adapters/dingtalk/src/bot.ts GitHub Actions / lint
|
||
]) | ||
} | ||
|
||
DingtalkBot.prototype.platform = 'dingtalk' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { Adapter, Context, Logger } from '@satorijs/satori' | ||
import { DingtalkBot } from './bot' | ||
import crypto from 'node:crypto' | ||
import internal from 'stream' | ||
import { TextMessage } from './types' | ||
|
||
export class HttpServer extends Adapter.Server<DingtalkBot> { | ||
logger = new Logger('dingtalk') | ||
constructor(ctx: Context, bot: DingtalkBot) { | ||
super() | ||
} | ||
|
||
async start(bot: DingtalkBot) { | ||
await bot.refreshToken() | ||
bot.selfId = bot.config.appkey | ||
bot.ctx.router.post('/dingtalk', async (ctx) => { | ||
const timestamp = ctx.get('timestamp'); | ||
const sign = ctx.get('sign'); | ||
|
||
if (!timestamp || !sign) return ctx.status = 403 | ||
const timeDiff = Math.abs(Date.now() - Number(timestamp)); | ||
if (timeDiff > 3600000) return ctx.status = 401 | ||
const signContent = timestamp + "\n" + bot.config.secret; | ||
const computedSign = crypto | ||
.createHmac('sha256', bot.config.secret) | ||
.update(signContent) | ||
.digest('base64'); | ||
|
||
if (computedSign !== sign) return ctx.status = 403 | ||
const body = ctx.request.body as TextMessage | ||
this.logger.debug(require('util').inspect(body, false, null, true)) | ||
const session = bot.session() | ||
session.type = "message" | ||
session.messageId = body.msgId | ||
session.isDirect = body.conversationType === "1" | ||
session.guildId = body.chatbotCorpId | ||
session.channelId = body.conversationId | ||
session.channelName = body.conversationTitle | ||
session.userId = body.senderStaffId | ||
session.author = { | ||
userId: body.senderStaffId, | ||
username: body.senderNick, | ||
roles: body.isAdmin ? ['admin'] : [], | ||
} | ||
session.timestamp = Number(body.createAt) | ||
if(body.msgtype === "text") { | ||
session.content = body.text.content | ||
} | ||
this.logger.debug(require('util').inspect(session, false, null, true)) | ||
bot.dispatch(session) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { DingtalkBot } from './bot' | ||
|
||
export * from './bot' | ||
export * from './utils' | ||
export * from './types' | ||
export * from './http' | ||
export * from './message' | ||
|
||
export default DingtalkBot |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { h, MessageEncoder } from '@satorijs/satori' | ||
import { DingtalkBot } from './bot' | ||
|
||
export class DingtalkMessageEncoder extends MessageEncoder<DingtalkBot> { | ||
buffer = '' | ||
|
||
async flush(): Promise<void> { | ||
console.log(await this.bot.http.post('/robot/groupMessages/send', { | ||
// https://open.dingtalk.com/document/orgapp/types-of-messages-sent-by-robots | ||
msgKey: 'sampleText', | ||
msgParam: JSON.stringify({ | ||
content: this.buffer | ||
}), | ||
robotCode: this.bot.config.appkey, | ||
openConversationId: this.channelId | ||
})) | ||
} | ||
|
||
|
||
async visit(element: h) { | ||
const { type, attrs, children } = element | ||
|
||
if (type === 'text') { | ||
this.buffer += attrs.content | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
export type AtUser = { | ||
dingtalkId: string; | ||
staffId?: string; // 企业内部群有的发送者在企业内的userid | ||
}; | ||
|
||
export type DingtalkRequestBase = { | ||
msgtype: string; // 消息类型 | ||
content: string; // 消息文本 | ||
msgId: string; // 加密的消息ID | ||
createAt: string; // 消息的时间戳,单位毫秒 | ||
conversationType: string; // 1:单聊 2:群聊 | ||
conversationId: string; // 会话ID | ||
conversationTitle?: string; // 群聊时才有的会话标题 | ||
senderId: string; // 加密的发送者ID | ||
senderNick: string; // 发送者昵称 | ||
senderCorpId?: string; // 企业内部群有的发送者当前群的企业corpId | ||
sessionWebhook: string; // 当前会话的Webhook地址 | ||
sessionWebhookExpiredTime: number; // 当前会话的Webhook地址过期时间 | ||
isAdmin?: boolean; // 是否为管理员 | ||
chatbotCorpId?: string; // 加密的机器人所在的企业corpId | ||
isInAtList?: boolean; // 是否在@列表中 | ||
senderStaffId?: string; // 企业内部群中@该机器人的成员userid | ||
chatbotUserId: string; // 加密的机器人ID | ||
atUsers?: AtUser[]; // 被@人的信息 | ||
robotCode: string | ||
}; | ||
|
||
export type Message = TextMessage | ||
|
||
export interface TextMessage extends DingtalkRequestBase { | ||
msgtype: "text" | ||
text: { | ||
content: string | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import { h, Session } from '@satorijs/satori' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"extends": "../../tsconfig.base", | ||
"compilerOptions": { | ||
"outDir": "lib", | ||
"rootDir": "src", | ||
}, | ||
"include": [ | ||
"src", | ||
], | ||
} |