Skip to content

Commit

Permalink
refactor: 重构 WechatApp/WechatRobot 到新版接口
Browse files Browse the repository at this point in the history
  • Loading branch information
CaoMeiYouRen committed Nov 8, 2024
1 parent 3ae9c5b commit 8d4d7a5
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 91 deletions.
8 changes: 4 additions & 4 deletions src/push/server-chan-turbo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface ServerChanTurboConfig {
* Server酱 Turbo 的 SCTKEY
* 请前往 https://sct.ftqq.com/sendkey 领取
*/
SERVER_CHAN_TURBO_SCTKEY: string
SERVER_CHAN_TURBO_SENDKEY: string
}

/**
Expand Down Expand Up @@ -82,11 +82,11 @@ export class ServerChanTurbo implements Send {
* @param config 请前往 https://sct.ftqq.com/sendkey 领取
*/
constructor(config: ServerChanTurboConfig) {
const { SERVER_CHAN_TURBO_SCTKEY } = config
this.SCTKEY = SERVER_CHAN_TURBO_SCTKEY
const { SERVER_CHAN_TURBO_SENDKEY } = config
this.SCTKEY = SERVER_CHAN_TURBO_SENDKEY
Debugger('set SCTKEY: "%s"', this.SCTKEY)
if (!this.SCTKEY) {
throw new Error('SERVER_CHAN_TURBO_SCTKEY 是必须的!')
throw new Error('SERVER_CHAN_TURBO_SENDKEY 是必须的!')
}
}
/**
Expand Down
117 changes: 76 additions & 41 deletions src/push/wechat-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,64 @@ import { ajax } from '@/utils/ajax'
import { SendResponse } from '@/interfaces/response'

const Debugger = debug('push:wechat-app')
export type WechatAppMsgType = 'text' | 'markdown'
export type WechatAppOption = {
export type WechatAppMsgType = 'text' | 'markdown' | 'voice' | 'file' | 'image' | 'voice' | 'video' | 'textcard' | 'news' | 'mpnews' | 'miniprogram_notice' | 'template_card'
export interface WechatAppConfig {
/**
* 企业ID,获取方式参考:[术语说明-corpid](https://work.weixin.qq.com/api/doc/90000/90135/91039#14953/corpid)
*
*/
WX_APP_CORPID: string
WECHAT_APP_CORPID: string
/**
* 应用的凭证密钥,获取方式参考:[术语说明-secret](https://work.weixin.qq.com/api/doc/90000/90135/91039#14953/secret)
*
*/
WX_APP_SECRET: string
WECHAT_APP_SECRET: string
/**
* 企业应用的id。企业内部开发,可在应用的设置页面查看
*
*/
WX_APP_AGENTID: number
/**
* 指定接收消息的成员。若不指定则默认为 ”@all”。
* 成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。
* 特殊情况:指定为”@all”,则向该企业应用的全部成员发送。
*
*/
WX_APP_USERID?: string
WECHAT_APP_AGENTID: number
}

export type WechatAppOption = {
// 消息类型
msgtype: WechatAppMsgType
// 表示是否是保密消息,0表示可对外分享,1表示不能
safe?: 0 | 1
// 表示是否开启id转译,0表示否,1表示是,默认0。
enable_id_trans?: 0 | 1
// 表示是否开启重复消息检查,0表示否,1表示是,默认
enable_duplicate_check?: 0 | 1
// 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
duplicate_check_interval?: number
[key: string]: any
} & ({
// 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。
// 特殊情况:指定为"@all",则向该企业应用的全部成员发送
touser?: string
} | {
// 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。
// 当touser为"@all"时忽略本参数
toparty?: string
} | {
// 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。
// 当touser为"@all"时忽略本参数
totag?: string
})

export interface WechatAppResponse {
// 企业微信返回的错误码,为0表示成功,非0表示调用失败
errcode: number
errmsg: string
invaliduser?: string
invalidparty?: string
invalidtag?: string
unlicenseduser?: string
msgid: string
response_code?: string
}
/**
* 企业微信应用推送,文档:https://work.weixin.qq.com/api/doc/90000/90135/90664
* 企业微信应用推送,文档:https://developer.work.weixin.qq.com/document/path/90664
*
* @author CaoMeiYouRen
* @date 2021-02-28
Expand All @@ -41,46 +71,42 @@ export type WechatAppOption = {
*/
export class WechatApp implements Send {

private WX_APP_CORPID: string
private WX_APP_SECRET: string
private WX_APP_AGENTID: number
private WX_APP_USERID?: string
private ACCESS_TOKEN: string

private WECHAT_APP_CORPID: string
private WECHAT_APP_SECRET: string
private WECHAT_APP_AGENTID: number

/**
* ACCESS_TOKEN 的过期时间(时间戳)
*
* @private
*/
private expiresTime: number

constructor(option: WechatAppOption) {
Debugger('option: %O', option)
Object.assign(this, option)
if (!this.WX_APP_CORPID) {
throw new Error('WX_APP_CORPID 是必须的!')
constructor(config: WechatAppConfig) {
Debugger('config: %O', config)
Object.assign(this, config)
if (!this.WECHAT_APP_CORPID) {
throw new Error('WECHAT_APP_CORPID 是必须的!')
}
if (!this.WX_APP_SECRET) {
throw new Error('WX_APP_SECRET 是必须的!')
if (!this.WECHAT_APP_SECRET) {
throw new Error('WECHAT_APP_SECRET 是必须的!')
}
if (!this.WX_APP_AGENTID) {
throw new Error('WX_APP_AGENTID 是必须的!')
}
if (!this.WX_APP_USERID) {
warn('未提供 WX_APP_USERID!将使用 "@all" 向全体成员推送')
this.WX_APP_USERID = '@all'
if (!this.WECHAT_APP_AGENTID) {
throw new Error('WECHAT_APP_AGENTID 是必须的!')
}
}

private async getAccessToken(): Promise<string> {
const { data } = await ajax({
url: 'https://qyapi.weixin.qq.com/cgi-bin/gettoken',
query: {
corpid: this.WX_APP_CORPID,
corpsecret: this.WX_APP_SECRET,
corpid: this.WECHAT_APP_CORPID,
corpsecret: this.WECHAT_APP_SECRET,
},
})
if (data?.errcode !== 0) { // 出错返回码,为0表示成功,非0表示调用失败
error(data?.errmsg)
throw new Error(data?.errmsg || '获取 access_token 失败!')
}
const { access_token, expires_in = 7200 } = data
Expand All @@ -101,18 +127,26 @@ export class WechatApp implements Send {
}

/**
*
* 发送消息
*
* @author CaoMeiYouRen
* @date 2022-08-01
* @param content 消息内容,最长不超过2048个字节,超过将截断(支持id转译)
* @param [msgtype='text'] 消息类型,text | markdown
* @date 2024-11-08
* @param title 消息标题
* @param [desp] 消息内容,最长不超过2048个字节,超过将截断(支持id转译)
* @param [option] 额外推送选项
*/
async send(content: string, msgtype: WechatAppMsgType = 'text'): Promise<SendResponse> {
Debugger('content: %s, msgtype: "%s"', content, msgtype)
async send(title: string, desp?: string, option?: WechatAppOption): Promise<SendResponse<WechatAppResponse>> {
Debugger('title: "%s", desp: "%s", option: %O', title, desp, option)
if (!this.ACCESS_TOKEN || Date.now() >= this.expiresTime) {
this.ACCESS_TOKEN = await this.getAccessToken()
}
const { msgtype = 'text', touser: _touser, ...args } = option || {}
if (!_touser) {
warn('未指定 touser,将使用 "@all" 向全体成员推送')
}
const sep = msgtype === 'markdown' ? '\n\n' : '\n'
const content = `${title}${desp ? `${sep}${desp}` : ''}`
const touser = _touser || '@all' // 如果没有指定 touser,则使用全体成员
return ajax({
url: 'https://qyapi.weixin.qq.com/cgi-bin/message/send',
method: 'POST',
Expand All @@ -123,12 +157,13 @@ export class WechatApp implements Send {
access_token: this.ACCESS_TOKEN,
},
data: {
touser: this.WX_APP_USERID,
touser,
msgtype,
agentid: this.WX_APP_AGENTID,
agentid: this.WECHAT_APP_AGENTID,
[msgtype]: {
content,
},
...args,
},
})
}
Expand Down
91 changes: 45 additions & 46 deletions src/push/wechat-robot.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { AxiosResponse } from 'axios'
import debug from 'debug'
import { Send } from '../interfaces/send'
import { MarkdownMsg } from './wechat/MarkdownMsg'
import { TextMsg } from './wechat/TextMsg'
import { ajax } from '@/utils/ajax'
import { SendResponse } from '@/interfaces/response'

const Debugger = debug('push:wechat-robot')

export type MsgType = 'text' | 'markdown'
type Msg = TextMsg | MarkdownMsg
export type WechatRobotMsgType = 'text' | 'markdown' | 'image' | 'news' | 'file' | 'voice' | 'template_card'

export interface WechatRobotConfig {
// 企业微信机器人的key
WECHAT_ROBOT_KEY: string
}

export interface WechatRobotOption {
msgtype?: WechatRobotMsgType
[key: string]: any
}

export interface WechatRobotResponse {
// 企业微信机器人返回的错误码,为0表示成功,非0表示调用失败
errcode: number
errmsg: string
}

/**
* 企业微信群机器人。文档: [如何使用群机器人](https://work.weixin.qq.com/help?person_id=1&doc_id=13376)
* 企业微信群机器人。文档: [如何使用群机器人](https://developer.work.weixin.qq.com/document/path/91770)
*
* @author CaoMeiYouRen
* @date 2021-02-28
Expand All @@ -21,56 +33,43 @@ type Msg = TextMsg | MarkdownMsg
*/
export class WechatRobot implements Send {

private WX_ROBOT_KEY: string
private WECHAT_ROBOT_KEY: string

constructor(WX_ROBOT_KEY: string) {
this.WX_ROBOT_KEY = WX_ROBOT_KEY
Debugger('set WX_ROBOT_KEY: "%s"', WX_ROBOT_KEY)
if (!this.WX_ROBOT_KEY) {
throw new Error('WX_ROBOT_KEY 是必须的!')
constructor(config: WechatRobotConfig) {
const { WECHAT_ROBOT_KEY } = config
this.WECHAT_ROBOT_KEY = WECHAT_ROBOT_KEY
Debugger('set WECHAT_ROBOT_KEY: "%s"', WECHAT_ROBOT_KEY)
if (!this.WECHAT_ROBOT_KEY) {
throw new Error('WECHAT_ROBOT_KEY 是必须的!')
}
}

private async push(message: Msg): Promise<AxiosResponse> {
/**
*
*
* @author CaoMeiYouRen
* @date 2024-11-08
* @param title 消息标题
* @param [desp] 消息内容。text内容,最长不超过2048个字节;markdown内容,最长不超过4096个字节;必须是utf8编码
* @param [option] 额外推送选项
*/
async send(title: string, desp?: string, option?: WechatRobotOption): Promise<SendResponse<WechatRobotResponse>> {
Debugger('title: "%s", desp: "%s", option: %O', title, desp, option)
const { msgtype = 'text', ...args } = option || {}
const sep = msgtype === 'markdown' ? '\n\n' : '\n'
const content = `${title}${desp ? `${sep}${desp}` : ''}`
return ajax({
url: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send',
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
query: { key: this.WX_ROBOT_KEY },
data: { ...message },
query: { key: this.WECHAT_ROBOT_KEY },
data: {
msgtype,
[msgtype]: { content },
...args,
},
})
}
/**
*
*
* @author CaoMeiYouRen
* @date 2021-02-28
* @param content 要发送的内容。text内容,最长不超过2048个字节;markdown内容,最长不超过4096个字节;必须是utf8编码
* @param [msgtype='text'] 消息类型
* @returns
*/
async send(content: string, msgtype: MsgType = 'text'): Promise<SendResponse> {
Debugger('content: "%s", msgtype: "%s"', content, msgtype)
switch (msgtype) {
case 'text':
return this.push(new TextMsg({
msgtype: 'text',
text: {
content,
},
}))
case 'markdown':
return this.push(new MarkdownMsg({
msgtype: 'markdown',
markdown: {
content,
},
}))
default:
break
}
}

}

0 comments on commit 8d4d7a5

Please sign in to comment.