wechaty-puppet-matrix 0.0.54 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/engine-schema.d.ts +1 -0
- package/dist/cjs/src/engine-schema.d.ts.map +1 -1
- package/dist/cjs/src/matrix/messages/message-contact-card.d.ts +16 -0
- package/dist/cjs/src/matrix/messages/message-contact-card.d.ts.map +1 -0
- package/dist/cjs/src/matrix/messages/message-contact-card.js +25 -0
- package/dist/cjs/src/matrix/messages/message-emotion.d.ts +7 -0
- package/dist/cjs/src/matrix/messages/message-emotion.d.ts.map +1 -1
- package/dist/cjs/src/matrix/messages/message-emotion.js +20 -0
- package/dist/cjs/src/matrix/messages/message-image.d.ts +8 -0
- package/dist/cjs/src/matrix/messages/message-image.d.ts.map +1 -1
- package/dist/cjs/src/matrix/messages/message-image.js +38 -2
- package/dist/cjs/src/matrix/messages/message-image.spec.d.ts +3 -0
- package/dist/cjs/src/matrix/messages/message-image.spec.d.ts.map +1 -0
- package/dist/cjs/src/matrix/messages/message-image.spec.js +66 -0
- package/dist/cjs/src/matrix/messages/message-video.d.ts +6 -0
- package/dist/cjs/src/matrix/messages/message-video.d.ts.map +1 -1
- package/dist/cjs/src/matrix/messages/message-video.js +20 -0
- package/dist/cjs/src/matrix/messages/message-video.spec.d.ts +3 -0
- package/dist/cjs/src/matrix/messages/message-video.spec.d.ts.map +1 -0
- package/dist/cjs/src/matrix/messages/message-video.spec.js +27 -0
- package/dist/cjs/src/matrix/service/request.d.ts +21 -0
- package/dist/cjs/src/matrix/service/request.d.ts.map +1 -1
- package/dist/cjs/src/matrix/service/request.js +194 -32
- package/dist/cjs/src/matrix/utils/file-type.d.ts +4 -0
- package/dist/cjs/src/matrix/utils/file-type.d.ts.map +1 -0
- package/dist/cjs/src/matrix/utils/file-type.js +55 -0
- package/dist/cjs/src/matrix/utils/file-type.spec.d.ts +3 -0
- package/dist/cjs/src/matrix/utils/file-type.spec.d.ts.map +1 -0
- package/dist/cjs/src/matrix/utils/file-type.spec.js +25 -0
- package/dist/cjs/src/puppet-matrix.d.ts +3 -0
- package/dist/cjs/src/puppet-matrix.d.ts.map +1 -1
- package/dist/cjs/src/puppet-matrix.js +121 -33
- package/dist/esm/src/engine-schema.d.ts +1 -0
- package/dist/esm/src/engine-schema.d.ts.map +1 -1
- package/dist/esm/src/matrix/messages/message-contact-card.d.ts +16 -0
- package/dist/esm/src/matrix/messages/message-contact-card.d.ts.map +1 -0
- package/dist/esm/src/matrix/messages/message-contact-card.js +22 -0
- package/dist/esm/src/matrix/messages/message-emotion.d.ts +7 -0
- package/dist/esm/src/matrix/messages/message-emotion.d.ts.map +1 -1
- package/dist/esm/src/matrix/messages/message-emotion.js +19 -0
- package/dist/esm/src/matrix/messages/message-image.d.ts +8 -0
- package/dist/esm/src/matrix/messages/message-image.d.ts.map +1 -1
- package/dist/esm/src/matrix/messages/message-image.js +37 -2
- package/dist/esm/src/matrix/messages/message-image.spec.d.ts +3 -0
- package/dist/esm/src/matrix/messages/message-image.spec.d.ts.map +1 -0
- package/dist/esm/src/matrix/messages/message-image.spec.js +64 -0
- package/dist/esm/src/matrix/messages/message-video.d.ts +6 -0
- package/dist/esm/src/matrix/messages/message-video.d.ts.map +1 -1
- package/dist/esm/src/matrix/messages/message-video.js +19 -0
- package/dist/esm/src/matrix/messages/message-video.spec.d.ts +3 -0
- package/dist/esm/src/matrix/messages/message-video.spec.d.ts.map +1 -0
- package/dist/esm/src/matrix/messages/message-video.spec.js +25 -0
- package/dist/esm/src/matrix/service/request.d.ts +21 -0
- package/dist/esm/src/matrix/service/request.d.ts.map +1 -1
- package/dist/esm/src/matrix/service/request.js +193 -32
- package/dist/esm/src/matrix/utils/file-type.d.ts +4 -0
- package/dist/esm/src/matrix/utils/file-type.d.ts.map +1 -0
- package/dist/esm/src/matrix/utils/file-type.js +47 -0
- package/dist/esm/src/matrix/utils/file-type.spec.d.ts +3 -0
- package/dist/esm/src/matrix/utils/file-type.spec.d.ts.map +1 -0
- package/dist/esm/src/matrix/utils/file-type.spec.js +23 -0
- package/dist/esm/src/puppet-matrix.d.ts +3 -0
- package/dist/esm/src/puppet-matrix.d.ts.map +1 -1
- package/dist/esm/src/puppet-matrix.js +121 -33
- package/package.json +1 -1
- package/src/engine-schema.ts +1 -0
- package/src/matrix/messages/message-contact-card.ts +48 -0
- package/src/matrix/messages/message-emotion.ts +36 -0
- package/src/matrix/messages/message-image.spec.ts +107 -0
- package/src/matrix/messages/message-image.ts +63 -2
- package/src/matrix/messages/message-video.spec.ts +37 -0
- package/src/matrix/messages/message-video.ts +28 -1
- package/src/matrix/service/request.ts +274 -32
- package/src/matrix/utils/file-type.spec.ts +29 -0
- package/src/matrix/utils/file-type.ts +59 -0
- package/src/puppet-matrix.ts +150 -35
package/src/puppet-matrix.ts
CHANGED
|
@@ -12,6 +12,7 @@ import Client from './matrix/service/request.js'
|
|
|
12
12
|
import { getUnixTime } from 'date-fns'
|
|
13
13
|
|
|
14
14
|
import { mergeWithFallback } from './matrix/utils/index.js'
|
|
15
|
+
import { detectImageExt, fetchRemoteHead, replaceExt } from './matrix/utils/file-type.js'
|
|
15
16
|
import path from 'path'
|
|
16
17
|
// 参考
|
|
17
18
|
import { CacheManager, RoomMemberMap } from './matrix/cache-manager.js'
|
|
@@ -19,11 +20,12 @@ import { isIMContactId, isRoomId } from './matrix/utils/is-type.js'
|
|
|
19
20
|
import { AppMessagePayload, AppMessageType, parseAppmsgMessagePayload } from './matrix/messages/message-appmsg.js'
|
|
20
21
|
import { parseRecordItem, RecordItem, RecordItemType } from './matrix/messages/message-chat-history.js'
|
|
21
22
|
import { parseMiniProgramMessagePayload } from './matrix/messages/message-miniprogram.js'
|
|
22
|
-
import { parseEmotionMessagePayload } from './matrix/messages/message-emotion.js'
|
|
23
|
+
import { EmojiMessagePayload, parseEmotionMessagePayload } from './matrix/messages/message-emotion.js'
|
|
23
24
|
import { ImageMessagePayload, parseImageMessagePayload } from './matrix/messages/message-image.js'
|
|
24
25
|
import { parseAudioMessagePayload, AudioMessagePayload } from './matrix/messages/message-audio.js'
|
|
25
26
|
import { parseVideoMessagePayload, VideoMessagePayload } from './matrix/messages/message-video.js'
|
|
26
27
|
import { parseLocationMessagePayload } from './matrix/messages/message-location.js'
|
|
28
|
+
import { ContactCardPayload, parseContactCardMessagePayload } from './matrix/messages/message-contact-card.js'
|
|
27
29
|
import { CachedPromiseFunc } from './matrix/utils/cached-promise.js'
|
|
28
30
|
import { engineMessageToWechaty } from './matrix/schema-mapper/message.js'
|
|
29
31
|
import { engineContactToWechaty } from './matrix/schema-mapper/contact.js'
|
|
@@ -35,7 +37,6 @@ import {
|
|
|
35
37
|
import { parseEvent, EventType } from './matrix/events/mod.js'
|
|
36
38
|
import { addRunningPuppet, removeRunningPuppet } from './cleanup.js'
|
|
37
39
|
import { packageJson } from './package-json.js'
|
|
38
|
-
import { JsonToXml } from './matrix/utils/xml-to-json.js'
|
|
39
40
|
|
|
40
41
|
const VERSION = packageJson.version || '0.0.0'
|
|
41
42
|
const PRE = '[PuppetMatrix]'
|
|
@@ -655,7 +656,8 @@ class PuppetMatrix extends PUPPET.Puppet {
|
|
|
655
656
|
hello = (option as any).hello
|
|
656
657
|
}
|
|
657
658
|
}
|
|
658
|
-
|
|
659
|
+
// 搜索联系人的 id 带有 SEARCH_CONTACT_PREFIX, 真实用户名在缓存的 wxid 里
|
|
660
|
+
await this._client?.addFriendByWxid({ content: hello, ticket, scene: addContactScene, wxid: cachedContactSearch?.wxid || contactId })
|
|
659
661
|
}
|
|
660
662
|
|
|
661
663
|
/**
|
|
@@ -724,7 +726,70 @@ class PuppetMatrix extends PUPPET.Puppet {
|
|
|
724
726
|
// 名片
|
|
725
727
|
override async messageContact (messageId: string): Promise<string> {
|
|
726
728
|
log.verbose('PuppetWeChat', 'messageContact(%s)', messageId)
|
|
727
|
-
|
|
729
|
+
const rawPayload = await this.messageRawPayload(messageId)
|
|
730
|
+
if (rawPayload.msgType !== WechatMessageType.ShareCard) {
|
|
731
|
+
throw new Error(`message ${messageId} is not a contact card`)
|
|
732
|
+
}
|
|
733
|
+
const card = await parseContactCardMessagePayload(rawPayload.msg)
|
|
734
|
+
if (!card) {
|
|
735
|
+
throw new Error(`can not parse contact card from message ${messageId}`)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// 好友名片: username 就是 wxid
|
|
739
|
+
if (!card.username.includes(STRANGER_SUFFIX)) {
|
|
740
|
+
return card.username
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// 微信发出的名片即便是好友也只带 v3 加密名, 尝试在本地联系人中匹配出 wxid
|
|
744
|
+
const friendId = await this._findFriendByCard(card)
|
|
745
|
+
if (friendId) {
|
|
746
|
+
log.verbose(PRE, 'messageContact(%s) matched friend %s', messageId, friendId)
|
|
747
|
+
return friendId
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// 非好友名片: 缓存为搜索联系人, friendshipAdd 可用 scene 17 + v4 票据添加
|
|
751
|
+
const searchId = `${SEARCH_CONTACT_PREFIX}${card.username}`
|
|
752
|
+
await this._cacheMgr!.setContactSearch(searchId, {
|
|
753
|
+
alias: '',
|
|
754
|
+
avatar: card.avatar,
|
|
755
|
+
city: card.city,
|
|
756
|
+
country: '',
|
|
757
|
+
isFriend: 0,
|
|
758
|
+
name: card.nickname,
|
|
759
|
+
province: card.province,
|
|
760
|
+
scene: card.scene,
|
|
761
|
+
sex: card.sex,
|
|
762
|
+
sign: card.sign,
|
|
763
|
+
ticket: card.antispamticket,
|
|
764
|
+
wxNum: card.alias,
|
|
765
|
+
wxid: card.username,
|
|
766
|
+
})
|
|
767
|
+
return searchId
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* 名片(v3 加密名) -> 本地好友 wxid
|
|
772
|
+
* 先按头像路径匹配(忽略 /0 /132 尺寸后缀), 再按唯一昵称匹配
|
|
773
|
+
*/
|
|
774
|
+
private async _findFriendByCard (card: ContactCardPayload): Promise<string | undefined> {
|
|
775
|
+
const avatarKey = (url?: string) => (url || '').replace(/^https?:\/\//, '').replace(/\/\d+$/, '')
|
|
776
|
+
const cardAvatar = avatarKey(card.avatar)
|
|
777
|
+
const contacts = (await this._cacheMgr!.getAllContacts())
|
|
778
|
+
.filter(c => c.wxid && !isRoomId(c.wxid) && !c.wxid.includes(STRANGER_SUFFIX) && c.isFriend !== 0)
|
|
779
|
+
|
|
780
|
+
if (cardAvatar) {
|
|
781
|
+
const byAvatar = contacts.find(c => avatarKey(c.avatar) === cardAvatar)
|
|
782
|
+
if (byAvatar) {
|
|
783
|
+
return byAvatar.wxid
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (card.nickname) {
|
|
787
|
+
const byName = contacts.filter(c => c.name === card.nickname)
|
|
788
|
+
if (byName.length === 1) {
|
|
789
|
+
return byName[0]!.wxid
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return undefined
|
|
728
793
|
}
|
|
729
794
|
|
|
730
795
|
override async messageChannel (
|
|
@@ -774,12 +839,22 @@ class PuppetMatrix extends PUPPET.Puppet {
|
|
|
774
839
|
return this._getMessageFileFileBox(messageId, messagePayload)
|
|
775
840
|
case PUPPET.types.Message.Emoticon: {
|
|
776
841
|
const emotionPayload = await parseEmotionMessagePayload(messagePayload)
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
842
|
+
// downloadWWMedia 返回的是下载地址字符串
|
|
843
|
+
const url: string = emotionPayload.is_ww
|
|
844
|
+
? (await this._client?.downloadWWMedia({
|
|
845
|
+
aes_key: emotionPayload.aeskey || '',
|
|
846
|
+
auth_key: emotionPayload.authkey || '',
|
|
847
|
+
file_id: emotionPayload.cdnurl || '',
|
|
848
|
+
file_name: `message-${messageId}-emoticon.gif`,
|
|
849
|
+
}, 3)) || ''
|
|
850
|
+
: emotionPayload.cdnurl
|
|
851
|
+
if (!url) {
|
|
852
|
+
throw new Error(`Can not get emoticon url for message: ${messageId}`)
|
|
853
|
+
}
|
|
854
|
+
// 按文件头确定扩展名(静态多为 png,动图为 gif),识别失败时按表情类型猜测
|
|
855
|
+
const head = await fetchRemoteHead(url)
|
|
856
|
+
const ext = (head && detectImageExt(head)) || (emotionPayload.type === 2 && !emotionPayload.game ? 'gif' : 'png')
|
|
857
|
+
const emoticonBox = FileBox.fromUrl(url, { name: `message-${messageId}-emoticon.${ext}` })
|
|
783
858
|
|
|
784
859
|
emoticonBox.metadata = {
|
|
785
860
|
payload: emotionPayload,
|
|
@@ -953,28 +1028,14 @@ class PuppetMatrix extends PUPPET.Puppet {
|
|
|
953
1028
|
***************************************************************************/
|
|
954
1029
|
// 发送名片
|
|
955
1030
|
override async messageSendContact (toUserId: string, contactId: string): Promise<void | string> {
|
|
956
|
-
log.verbose('PuppetWeChat', '
|
|
1031
|
+
log.verbose('PuppetWeChat', 'messageSendContact("%s", %s)', toUserId, contactId)
|
|
957
1032
|
const contactPayload = await this.contactRawPayload(contactId)
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
city: contactPayload?.city,
|
|
965
|
-
fullpy: contactPayload?.name,
|
|
966
|
-
imagestatus: '3',
|
|
967
|
-
nickname: contactPayload?.name,
|
|
968
|
-
province: contactPayload?.province,
|
|
969
|
-
regionCode: 'CN_Shanghai',
|
|
970
|
-
scene: '17',
|
|
971
|
-
sex: contactPayload?.sex,
|
|
972
|
-
smallheadimgurl: contactPayload?.avatar,
|
|
973
|
-
username: contactPayload?.ticket,
|
|
974
|
-
},
|
|
975
|
-
}
|
|
976
|
-
const xml = JsonToXml(xmlObj)
|
|
977
|
-
const res = await this._client?.sendContactCard(toUserId, xml, this._self?.wxid)
|
|
1033
|
+
const res = await this._client?.sendContact({
|
|
1034
|
+
shareHeadImg: contactPayload?.avatar,
|
|
1035
|
+
shareId: contactPayload?.wxid || contactId,
|
|
1036
|
+
shareNickname: contactPayload?.name,
|
|
1037
|
+
toId: toUserId,
|
|
1038
|
+
}, this._self?.wxid)
|
|
978
1039
|
if (res) {
|
|
979
1040
|
await this._cacheMgr?.setMessage(res.id, res)
|
|
980
1041
|
return res.id
|
|
@@ -993,7 +1054,17 @@ class PuppetMatrix extends PUPPET.Puppet {
|
|
|
993
1054
|
// @ts-ignore
|
|
994
1055
|
const fileUrl = fileBox.remoteUrl || ''
|
|
995
1056
|
if (metadata.type === 'emoticon') {
|
|
996
|
-
|
|
1057
|
+
// 优先 md5 发送(收到的表情原样转发、骰子/猜拳均可),失败再按 url 发送
|
|
1058
|
+
const emoji = metadata.payload as EmojiMessagePayload | undefined
|
|
1059
|
+
const byMd5 = emoji?.md5 && emoji.len
|
|
1060
|
+
? await this._client?.sendEmojiMd5(conversationId, emoji.md5, emoji.len, this._self?.wxid)
|
|
1061
|
+
: undefined
|
|
1062
|
+
const msgInfo = byMd5 || (fileUrl ? await this._client?.sendEmojiUrl(conversationId, fileUrl, this._self?.wxid) : undefined)
|
|
1063
|
+
if (!msgInfo) {
|
|
1064
|
+
throw new Error(`send emoticon failed: ${fileBox.name}`)
|
|
1065
|
+
}
|
|
1066
|
+
await this._cacheMgr?.setMessage(msgInfo.id, msgInfo)
|
|
1067
|
+
return msgInfo.id
|
|
997
1068
|
} else if (fileBox.mediaType.startsWith('image/')) {
|
|
998
1069
|
if (fileUrl) {
|
|
999
1070
|
const msgInfo = await this._client?.sendUrlImg(conversationId, fileUrl, this._self?.wxid)
|
|
@@ -1114,11 +1185,37 @@ class PuppetMatrix extends PUPPET.Puppet {
|
|
|
1114
1185
|
}
|
|
1115
1186
|
|
|
1116
1187
|
/**
|
|
1117
|
-
*
|
|
1188
|
+
* 拍一拍(Wechaty 无标准接口,通过 bot.puppet 调用)
|
|
1189
|
+
* @param conversationId 会话 id:私聊为好友 wxid,群聊为群 id
|
|
1190
|
+
* @param contactId 被拍的人 wxid,私聊时默认拍对方
|
|
1191
|
+
*/
|
|
1192
|
+
public async sendPat (conversationId: string, contactId?: string): Promise<boolean> {
|
|
1193
|
+
log.verbose(PRE, 'sendPat(%s, %s)', conversationId, contactId)
|
|
1194
|
+
const patted = contactId || conversationId
|
|
1195
|
+
if (isRoomId(patted)) {
|
|
1196
|
+
throw new Error('sendPat: contactId is required in room')
|
|
1197
|
+
}
|
|
1198
|
+
return (await this._client?.sendPat(conversationId, patted)) || false
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* 消息撤回(只能撤回自己发送的消息)
|
|
1118
1203
|
* @param messageId
|
|
1119
1204
|
*/
|
|
1120
1205
|
override async messageRecall (messageId: string): Promise<boolean> {
|
|
1121
|
-
|
|
1206
|
+
log.verbose(PRE, 'messageRecall(%s)', messageId)
|
|
1207
|
+
const payload = await this._cacheMgr?.getMessage(messageId)
|
|
1208
|
+
if (!payload) {
|
|
1209
|
+
log.warn(PRE, 'messageRecall(%s): message not found in cache', messageId)
|
|
1210
|
+
return false
|
|
1211
|
+
}
|
|
1212
|
+
// 自己发送的消息 listenerId 为会话 id(好友 wxid 或群 id)
|
|
1213
|
+
const toId = payload.listenerId || payload.fromWxid || ''
|
|
1214
|
+
if (!toId) {
|
|
1215
|
+
log.warn(PRE, 'messageRecall(%s): can not find conversation id', messageId)
|
|
1216
|
+
return false
|
|
1217
|
+
}
|
|
1218
|
+
return (await this._client?.revokeMsg(toId, messageId, payload.clientMsgId)) || false
|
|
1122
1219
|
}
|
|
1123
1220
|
|
|
1124
1221
|
/**
|
|
@@ -1894,7 +1991,25 @@ class PuppetMatrix extends PUPPET.Puppet {
|
|
|
1894
1991
|
log.error(`Can not get file for message: ${messageId}`)
|
|
1895
1992
|
}
|
|
1896
1993
|
const url = imageInfo.is_ww ? await this._client?.downloadWWMedia(imageInfo, 1) : await this._client?.downloadImage(imageInfo)
|
|
1897
|
-
return this._fileBoxFromUrl(url, messageId)
|
|
1994
|
+
return this._fileBoxFromUrl(url, messageId, url ? await this._imageNameByHead(url) : undefined)
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
/**
|
|
1998
|
+
* 下载接口固定返回 .png 文件名,实际多为 JPEG。读取文件头修正扩展名,识别失败时保持原名
|
|
1999
|
+
*/
|
|
2000
|
+
private async _imageNameByHead (url: string): Promise<string | undefined> {
|
|
2001
|
+
const head = await fetchRemoteHead(url)
|
|
2002
|
+
const ext = head && detectImageExt(head)
|
|
2003
|
+
if (!ext) {
|
|
2004
|
+
return undefined
|
|
2005
|
+
}
|
|
2006
|
+
let rawName = 'image'
|
|
2007
|
+
try {
|
|
2008
|
+
rawName = decodeURIComponent(new URL(url).pathname.split('/').pop() || rawName)
|
|
2009
|
+
} catch {
|
|
2010
|
+
// url 解析失败时用默认名
|
|
2011
|
+
}
|
|
2012
|
+
return replaceExt(rawName, ext)
|
|
1898
2013
|
}
|
|
1899
2014
|
|
|
1900
2015
|
private async _getMessageAudioFileBox (messageId: string, messagePayload: MessagePayload): Promise<FileBoxInterface> {
|