Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
K
koishi-plugin-adapter-wecom
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
1
Issues
1
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
3rdeye
koishi-plugin-adapter-wecom
Commits
4657b3d3
Commit
4657b3d3
authored
Mar 19, 2022
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add menu support
parent
0a55fbb8
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
266 additions
and
5 deletions
+266
-5
README.md
README.md
+25
-0
dev/index.ts
dev/index.ts
+40
-2
src/bot.ts
src/bot.ts
+63
-2
src/def/index.ts
src/def/index.ts
+1
-0
src/def/menu.ts
src/def/menu.ts
+121
-0
src/index.ts
src/index.ts
+1
-1
src/utils.ts
src/utils.ts
+15
-0
No files found.
README.md
View file @
4657b3d3
...
...
@@ -21,6 +21,28 @@ npm install koishi-plugin-adapter-wecom
corpId
:
'
corpId
'
,
// 企业 ID,在企业信息 https://work.weixin.qq.com/wework_admin/frame#profile 中查看
agentId
:
'
agentId
'
,
// 应用 ID,在应用管理中查看
secret
:
'
secret
'
,
// 应用密钥,在应用管理中查看
menus
:
[
{
type
:
'
view
'
,
// 打开网页
name
:
'
MyCard
'
,
// 按钮名称
url
:
'
https://mycard.moe
'
,
// 网页地址
},
{
type
:
'
click
'
,
// 运行指令
name
:
'
看图
'
,
// 按钮名称
command
:
'
media -c 10000000
'
,
// 指令
},
{
type
:
'
parent
'
,
// 子指令
name
:
'
更多
'
,
// 按钮名称
children
:
[
{
type
:
'
click
'
,
name
:
'
点击事件
'
,
command
:
'
markdown
'
},
{
type
:
'
scancode_push
'
,
name
:
'
扫一扫
'
},
// 扫一扫并打开网页。
{
type
:
'
scancode_waitmsg
'
,
name
:
'
扫一扫2
'
,
command
:
'
scan
'
},
// 扫一扫并运行指令。
{
type
:
'
location_select
'
,
name
:
'
地理位置
'
,
command
:
'
location
'
},
// 选择地理位置并运行指令。
],
},
],
},
],
path
:
'
/wecom
'
,
// 回调 API 路径
...
...
@@ -29,3 +51,6 @@ npm install koishi-plugin-adapter-wecom
}
```
## 事件
*
`wecom/{Event}`
:企业微信事件,以
`wecom/`
开头,参考
[
企业微信文档
](
https://developer.work.weixin.qq.com/document/path/90240
)
。
dev/index.ts
View file @
4657b3d3
...
...
@@ -19,6 +19,28 @@ app.plugin(adapterPlugin, {
corpId
:
'
corpId
'
,
// 企业 ID,在企业信息 https://work.weixin.qq.com/wework_admin/frame#profile 中查看
agentId
:
'
agentId
'
,
// 应用 ID,在应用管理中查看
secret
:
'
secret
'
,
// 应用密钥,在应用管理中查看
menus
:
[
{
type
:
'
view
'
,
// 打开网页
name
:
'
MyCard
'
,
// 按钮名称
url
:
'
https://mycard.moe
'
,
// 网页地址
},
{
type
:
'
click
'
,
// 运行指令
name
:
'
看图
'
,
// 按钮名称
command
:
'
media -c 10000000
'
,
// 指令
},
{
type
:
'
parent
'
,
// 子指令
name
:
'
更多
'
,
// 按钮名称
children
:
[
{
type
:
'
click
'
,
name
:
'
点击事件
'
,
command
:
'
markdown
'
},
{
type
:
'
scancode_push
'
,
name
:
'
扫一扫
'
},
// 扫一扫并打开网页。
{
type
:
'
scancode_waitmsg
'
,
name
:
'
扫一扫2
'
,
command
:
'
scan
'
},
// 扫一扫并运行指令。
{
type
:
'
location_select
'
,
name
:
'
地理位置
'
,
command
:
'
location
'
},
// 选择地理位置并运行指令。
],
},
],
},
],
path
:
'
/wecom
'
,
// 回调 API 路径
...
...
@@ -32,6 +54,10 @@ app.on('wecom/LOCATION', (session) => {
);
});
app
.
on
(
'
wecom/click
'
,
(
session
)
=>
{
console
.
log
(
`
${
session
.
userId
}
clicked
${
session
.
wecom
.
EventKey
}
`
);
});
app
.
on
(
'
message
'
,
(
session
)
=>
{
console
.
log
(
`Got message
${
session
.
content
}
`
);
});
...
...
@@ -46,12 +72,24 @@ app.command('markdown').action(async () => {
return
`
${
segment
(
'
markdown
'
)}
# 女装\n> 今天梦梦女装了吗?`
;
});
app
.
command
(
'
scan
'
)
.
action
(
(
argv
)
=>
`扫出来了:
${
argv
.
session
.
wecom
[
'
ScanCodeInfo
'
][
'
ScanResult
'
]}
`
,
);
app
.
command
(
'
location
'
).
action
(
async
(
argv
)
=>
{
const
{
session
}
=
argv
;
return
`你的位置是
${
session
.
wecom
[
'
SendLocationInfo
'
][
'
Label
'
]}
,经纬度是
${
session
.
wecom
[
'
SendLocationInfo
'
][
'
Location_X
'
]}
${
session
.
wecom
[
'
SendLocationInfo
'
][
'
Location_Y
'
]}
`
;
});
app
.
command
(
'
media
'
)
.
option
(
'
type
'
,
'
-t, <type:string>
'
,
{
fallback
:
'
image
'
})
.
option
(
'
type
'
,
'
-t <type:string>
'
,
{
fallback
:
'
image
'
})
.
option
(
'
cardcode
'
,
'
-c <cardcode:string>
'
,
{
fallback
:
'
10000
'
})
.
action
(
async
(
argv
)
=>
segment
(
argv
.
options
.
type
,
{
url
:
'
https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/10000.jpg
'
,
url
:
`https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/
${
argv
.
options
.
cardcode
}
.jpg`
,
}),
);
...
...
src/bot.ts
View file @
4657b3d3
import
{
Adapter
,
Bot
,
Dict
,
omit
,
Quester
,
Schema
,
segment
}
from
'
koishi
'
;
import
{
Adapter
,
Bot
,
Dict
,
omit
,
Quester
,
Schema
,
segment
,
Session
,
}
from
'
koishi
'
;
import
moment
,
{
Moment
}
from
'
moment
'
;
import
{
CommonOutMessage
,
...
...
@@ -11,6 +20,8 @@ import {
WecomSendMessageResponse
,
WeComUser
,
WeComAgentInfo
,
WecomMenuDef
,
adaptMenu
,
}
from
'
./def
'
;
import
{
AdapterConfig
,
adaptUser
}
from
'
./utils
'
;
import
FormData
from
'
form-data
'
;
...
...
@@ -22,6 +33,7 @@ export interface BotConfig extends Bot.BaseConfig, Quester.Config {
corpId
?:
string
;
agentId
?:
string
;
secret
?:
string
;
menus
?:
WecomMenuDef
[];
}
export
const
BotConfig
:
Schema
<
BotConfig
>
=
Schema
.
object
({
...
...
@@ -31,6 +43,7 @@ export const BotConfig: Schema<BotConfig> = Schema.object({
.
role
(
'
secret
'
)
.
required
()
.
description
(
'
企业应用密钥。
'
),
menus
:
Schema
.
array
(
WecomMenuDef
).
description
(
'
企业微信菜单项。
'
),
...
omit
(
Quester
.
Config
.
dict
,
[
'
endpoint
'
]),
});
...
...
@@ -40,6 +53,7 @@ export class WeComBot extends Bot<BotConfig> {
http
:
Quester
;
private
accessToken
:
string
;
private
accessTokenUntil
:
Moment
;
private
buttonKeyMap
=
new
Map
<
string
,
string
>
();
constructor
(
adapter
:
Adapter
,
config
:
BotConfig
)
{
super
(
adapter
,
config
);
...
...
@@ -47,13 +61,60 @@ export class WeComBot extends Bot<BotConfig> {
this
.
selfId
=
config
.
agentId
;
}
private
async
initializeMenu
()
{
if
(
!
this
.
config
.
menus
?.
length
)
return
true
;
const
button
=
this
.
config
.
menus
.
map
((
menu
)
=>
adaptMenu
(
menu
,
this
.
buttonKeyMap
),
);
const
buttonData
=
{
button
};
const
data
=
await
this
.
http
.
post
<
WecomSendMessageResponse
>
(
'
https://qyapi.weixin.qq.com/cgi-bin/menu/create
'
,
buttonData
,
{
params
:
{
access_token
:
await
this
.
getToken
(),
agentid
:
this
.
config
.
agentId
,
},
},
);
if
(
data
.
errcode
)
{
this
.
logger
.
error
(
`Failed to initialize menu
${
JSON
.
stringify
(
buttonData
)}
:
${
data
.
errmsg
}
`
,
);
return
false
;
}
return
true
;
}
async
handleMenuEvent
(
event
:
string
,
session
:
Session
)
{
if
(
!
this
.
app
.
isActive
)
return
;
const
eventKey
=
session
.
wecom
[
'
EventKey
'
]
as
string
;
const
command
=
this
.
buttonKeyMap
.
get
(
eventKey
);
console
.
log
(
eventKey
,
command
);
if
(
command
)
{
const
result
=
await
session
.
execute
(
command
);
if
(
result
)
{
await
this
.
sendPrivateMessage
(
session
.
userId
,
result
);
}
}
}
async
initialize
()
{
try
{
const
self
=
await
this
.
getSelf
();
const
[
self
,
menuResult
]
=
await
Promise
.
all
([
this
.
getSelf
(),
this
.
initializeMenu
(),
]);
if
(
!
self
)
{
this
.
reject
(
new
Error
(
'
Invalid credentials.
'
));
return
;
}
if
(
!
menuResult
)
{
this
.
reject
(
new
Error
(
'
Failed to initialize menu.
'
));
return
;
}
Object
.
assign
(
this
,
self
);
this
.
resolve
();
}
catch
(
e
)
{
...
...
src/def/index.ts
View file @
4657b3d3
...
...
@@ -4,3 +4,4 @@ export * from './specific/WeComAgentInfo';
export
*
from
'
./specific/ApprovalInfo
'
;
export
*
from
'
./specific/ChangeContact
'
;
export
*
from
'
./events
'
;
export
*
from
'
./menu
'
;
src/def/menu.ts
0 → 100644
View file @
4657b3d3
import
{
createHash
}
from
'
crypto
'
;
import
{
Schema
}
from
'
koishi
'
;
export
type
WecomButtonTypes
=
|
'
click
'
|
'
view
'
|
'
scancode_push
'
|
'
scancode_waitmsg
'
|
'
pic_sysphoto
'
|
'
pic_photo_or_album
'
|
'
pic_weixin
'
|
'
location_select
'
|
'
view_miniprogram
'
;
export
interface
WecomButton
{
type
:
WecomButtonTypes
;
name
:
string
;
key
?:
string
;
url
?:
string
;
pagepath
?:
string
;
appid
?:
string
;
}
export
interface
WecomParentButton
{
name
:
string
;
sub_button
:
WecomButton
[];
}
export
interface
WecomMenuDef
{
type
:
WecomButtonTypes
|
'
parent
'
;
name
:
string
;
command
?:
string
;
children
?:
(
Omit
<
WecomMenuDef
,
'
children
'
>
&
{
type
:
WecomButtonTypes
})[];
url
?:
string
;
pagePath
?:
string
;
mediaId
?:
string
;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export
const
WecomMenuDef
:
Schema
<
WecomMenuDef
>
=
Schema
.
object
({
type
:
Schema
.
union
([
'
parent
'
,
'
click
'
,
'
view
'
,
'
scancode_push
'
,
'
scancode_waitmsg
'
,
'
pic_sysphoto
'
,
'
pic_photo_or_album
'
,
'
pic_weixin
'
,
'
location_select
'
,
'
view_miniprogram
'
,
])
.
required
()
.
description
(
'
菜单的响应动作类型。
'
),
name
:
Schema
.
string
().
description
(
'
菜单标题。
'
).
required
(),
command
:
Schema
.
string
().
description
(
'
菜单的响应动作指令。
'
),
children
:
Schema
.
array
(
Schema
.
object
({
type
:
Schema
.
union
([
'
click
'
,
'
view
'
,
'
scancode_push
'
,
'
scancode_waitmsg
'
,
'
pic_sysphoto
'
,
'
pic_photo_or_album
'
,
'
pic_weixin
'
,
'
location_select
'
,
'
view_miniprogram
'
,
])
.
required
()
.
description
(
'
菜单的响应动作类型。
'
),
name
:
Schema
.
string
().
description
(
'
菜单标题。
'
).
required
(),
command
:
Schema
.
string
().
description
(
'
菜单的响应动作指令。
'
),
url
:
Schema
.
string
().
description
(
'
网页链接,成员点击菜单可打开链接,不超过1024字节。
'
,
),
pagePath
:
Schema
.
string
().
description
(
'
小程序的页面路径。
'
),
appId
:
Schema
.
string
().
description
(
'
小程序的appid。
'
),
}),
).
description
(
'
子菜单。
'
),
url
:
Schema
.
string
().
description
(
'
网页链接,成员点击菜单可打开链接,不超过1024字节。
'
,
),
pagePath
:
Schema
.
string
().
description
(
'
小程序的页面路径。
'
),
appId
:
Schema
.
string
().
description
(
'
小程序的appid。
'
),
});
export
const
adaptMenu
=
(
menu
:
WecomMenuDef
,
registerMap
?:
Map
<
string
,
string
>
,
):
WecomButton
|
WecomParentButton
=>
{
if
(
!
menu
.
type
&&
!
menu
.
children
)
{
throw
new
Error
(
'
Menu type or children must be set
'
);
}
if
(
menu
.
type
!==
'
parent
'
)
{
const
key
=
createHash
(
'
sha512
'
)
.
update
(
`
${
menu
.
type
}${
menu
.
name
}${
menu
.
command
||
''
}
`
)
.
digest
(
'
hex
'
);
if
(
menu
.
command
)
{
registerMap
?.
set
(
key
,
menu
.
command
);
}
return
{
type
:
menu
.
type
as
WecomButtonTypes
,
name
:
menu
.
name
,
key
,
url
:
menu
.
url
,
pagepath
:
menu
.
pagePath
,
appid
:
menu
.
mediaId
,
};
}
else
{
return
{
name
:
menu
.
name
,
sub_button
:
menu
.
children
.
map
((
c
)
=>
adaptMenu
(
c
,
registerMap
)),
}
as
WecomParentButton
;
}
};
export
interface
WecomMenu
{
button
:
WecomButton
[];
}
src/index.ts
View file @
4657b3d3
...
...
@@ -38,7 +38,7 @@ declare module 'koishi' {
}
>
;
'
wecom/click
'
:
WecomEventFunction
<
EventKeyBody
>
;
'
wecom/view
'
:
WecomEventFunction
<
EventKeyBody
>
;
'
wecom/scan
'
:
WecomEventFunction
<
ScanCodeEventBody
>
;
'
wecom/scan
code_push
'
:
WecomEventFunction
<
ScanCodeEventBody
>
;
'
wecom/scancode_waitmsg
'
:
WecomEventFunction
<
ScanCodeEventBody
>
;
'
wecom/pic_sysphoto
'
:
WecomEventFunction
<
PhotoEventBody
>
;
'
wecom/pic_photo_or_album
'
:
WecomEventFunction
<
PhotoEventBody
>
;
...
...
src/utils.ts
View file @
4657b3d3
...
...
@@ -111,4 +111,19 @@ export function dispatchSession(bot: WeComBot, message: WecomEventResponse) {
const
session
=
new
Session
(
bot
,
payload
);
session
.
wecom
=
message
.
body
;
bot
.
adapter
.
dispatch
(
session
);
if
(
message
.
body
.
Event
&&
[
'
click
'
,
'
view
'
,
'
scan
'
,
'
scancode_waitmsg
'
,
'
pic_sysphoto
'
,
'
pic_photo_or_album
'
,
'
pic_weixin
'
,
'
location_select
'
,
].
includes
(
message
.
body
.
Event
)
)
{
bot
.
handleMenuEvent
(
message
.
body
.
Event
,
session
).
then
();
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment