diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..31ecd93
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,34 @@
+name: test
+
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ test:
+ name: test
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: 'false'
+ fetch-depth: 0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '20.3.1'
+
+ - name: Test
+ run: |
+ yarn
+ yarn build
+ yarn test
+ shell: bash
+
+ - name: Upload Coverage Reports to Codecov
+ uses: codecov/codecov-action@v3
diff --git a/package.json b/package.json
index fdf63ed..35932bd 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"jest": "^29.6.3",
"prettier": "^3.0.2",
"ts-jest": "^29.1.1",
+ "ts-node": "^10.9.1",
"typescript": "~5.1.0"
}
}
diff --git a/packages/engine-chronocat-event/tests/mocks/index.ts b/packages/engine-chronocat-event/tests/mocks/index.ts
new file mode 100644
index 0000000..8a92692
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/mocks/index.ts
@@ -0,0 +1,24 @@
+import type { ChronocatContext } from '@chronocat/shell'
+import h from '@satorijs/element'
+
+export const ctx: ChronocatContext = {
+ //@ts-expect-error aaa
+ chronocat: {
+ l: {
+ info: jest.fn(),
+ warn: jest.fn(),
+ error: jest.fn(),
+ debug: jest.fn(),
+ } as unknown as ChronocatContext['chronocat']['l'],
+ h,
+ },
+}
+
+export const satoriConfig = {
+ type: 'satori',
+ listen: '0.0.0.0',
+ port: 5500,
+ self_url: 'https://chronocat.vercel.app',
+ token: 'DEFINE_CHRONO_TOKEN',
+ enable: true,
+} as const
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000..54e5052
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/__snapshots__/index.test.ts.snap
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Red 解析器应当正确解析 群聊 纯文本消息 1`] = `
+{
+ "channel": {
+ "id": "100000000",
+ "name": "001",
+ "type": 0,
+ },
+ "guild": {
+ "avatar": "https://p.qlogo.cn/gh/100000000/100000000/640",
+ "id": "100000000",
+ "name": "001",
+ },
+ "id": undefined,
+ "member": {
+ "nick": "000",
+ },
+ "message": {
+ "content": "xxx",
+ "id": "1000000000000000000",
+ },
+ "platform": undefined,
+ "self_id": undefined,
+ "timestamp": 1000000000000,
+ "type": "message-created",
+ "user": {
+ "avatar": "http://thirdqq.qlogo.cn/headimg_dl?dst_uin=10000&spec=640",
+ "id": "10000",
+ "name": undefined,
+ },
+}
+`;
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/data.json b/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/data.json
new file mode 100644
index 0000000..535a91f
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/data.json
@@ -0,0 +1,109 @@
+{
+ "msgId": "1000000000000000000",
+ "msgRandom": "100000000",
+ "msgSeq": "100000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 2,
+ "subMsgType": 1,
+ "sendType": 0,
+ "senderUid": "u_0000000000000000000000",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "000",
+ "sendNickName": "",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 1,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": {
+ "content": "xxx",
+ "atType": 0,
+ "atUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "atTinyId": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "atNtUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "subElementType": 0,
+ "atChannelId": "0",
+ "atRoleId": "0",
+ "atRoleColor": 0,
+ "atRoleName": "",
+ "needNotify": 0,
+ "atNtUin": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED"
+ },
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": null,
+ "grayTipElement": null,
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "001",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": true,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "10000"
+}
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/index.test.ts b/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/index.test.ts
new file mode 100644
index 0000000..e6aed63
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-2-2-1-0-0/index.test.ts
@@ -0,0 +1,19 @@
+import type { RedMessage } from '@chronocat/red'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { buildParser } from '../../../../src/parser'
+import { ctx, satoriConfig } from '../../../mocks'
+
+test('Red 解析器应当正确解析 群聊 纯文本消息', async () => {
+ const message = JSON.parse(
+ (await readFile(join(__dirname, 'data.json'))).toString('utf-8'),
+ ) as RedMessage
+
+ const events = await buildParser(ctx, satoriConfig)(message)
+
+ expect(events).toBeTruthy() // 解析到的消息应该存在
+ expect(events).toHaveLength(1) // 应当解析到 1 条消息
+
+ const [event] = events!
+ expect(event).toMatchSnapshot()
+})
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000..7159684
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/__snapshots__/index.test.ts.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Red 解析器应当正确解析 群聊 文件消息 1`] = `
+{
+ "channel": {
+ "id": "100000000",
+ "name": "001",
+ "type": 0,
+ },
+ "guild": {
+ "avatar": "https://p.qlogo.cn/gh/100000000/100000000/640",
+ "id": "100000000",
+ "name": "001",
+ },
+ "id": undefined,
+ "member": {},
+ "message": {
+ "content": "",
+ "id": "1000000000000000000",
+ },
+ "platform": undefined,
+ "self_id": undefined,
+ "timestamp": 1000000000000,
+ "type": "message-created",
+ "user": {
+ "avatar": "http://thirdqq.qlogo.cn/headimg_dl?dst_uin=10000&spec=640",
+ "id": "10000",
+ "name": "000",
+ },
+}
+`;
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/data.json b/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/data.json
new file mode 100644
index 0000000..51976ef
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/data.json
@@ -0,0 +1,120 @@
+{
+ "msgId": "1000000000000000000",
+ "msgRandom": "1000000000",
+ "msgSeq": "100000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 3,
+ "subMsgType": 512,
+ "sendType": 0,
+ "senderUid": "u_0000000000000000000000",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "",
+ "sendNickName": "000",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 3,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": null,
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": null,
+ "grayTipElement": null,
+ "arkElement": null,
+ "fileElement": {
+ "fileMd5": "00000000000000000000000000000000",
+ "fileName": "file.zip",
+ "filePath": "",
+ "fileSize": "100000",
+ "picHeight": 0,
+ "picWidth": 0,
+ "picThumbPath": {},
+ "expireTime": "0",
+ "file10MMd5": "",
+ "fileSha": "",
+ "fileSha3": "",
+ "videoDuration": 0,
+ "transferStatus": 1,
+ "progress": 0,
+ "invalidState": 0,
+ "fileUuid": "/00000000-0000-0000-0000-000000000000",
+ "fileSubId": "",
+ "thumbFileSize": 0,
+ "fileBizId": null,
+ "thumbMd5": null,
+ "folderId": null,
+ "fileGroupIndex": 0,
+ "fileTransType": null
+ },
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "001",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": true,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "10000"
+}
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/index.test.ts b/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/index.test.ts
new file mode 100644
index 0000000..1be8144
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-3-512-0/index.test.ts
@@ -0,0 +1,19 @@
+import type { RedMessage } from '@chronocat/red'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { buildParser } from '../../../../src/parser'
+import { ctx, satoriConfig } from '../../../mocks'
+
+test('Red 解析器应当正确解析 群聊 文件消息', async () => {
+ const message = JSON.parse(
+ (await readFile(join(__dirname, 'data.json'))).toString('utf-8'),
+ ) as RedMessage
+
+ const events = await buildParser(ctx, satoriConfig)(message)
+
+ expect(events).toBeTruthy() // 解析到的消息应该存在
+ expect(events).toHaveLength(1) // 应当解析到 1 条消息
+
+ const [event] = events!
+ expect(event).toMatchSnapshot()
+})
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000..03fa3ca
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/__snapshots__/index.test.ts.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Red 解析器应当正确解析 群聊 旧版群成员邀请新人入群消息 1`] = `
+{
+ "channel": {
+ "id": "100000000",
+ "name": "",
+ "type": 0,
+ },
+ "guild": {
+ "avatar": "https://p.qlogo.cn/gh/100000000/100000000/640",
+ "id": "100000000",
+ "name": "",
+ },
+ "id": undefined,
+ "member": {},
+ "message": {
+ "content": "",
+ "id": "1000000000000000000",
+ },
+ "operator": {
+ "id": "10001",
+ "name": undefined,
+ },
+ "platform": undefined,
+ "self_id": undefined,
+ "timestamp": 1000000000000,
+ "type": "guild-member-added",
+ "user": {
+ "avatar": "http://thirdqq.qlogo.cn/headimg_dl?dst_uin=10000&spec=640",
+ "id": "10000",
+ "name": undefined,
+ },
+}
+`;
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/data.json b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/data.json
new file mode 100644
index 0000000..1d07405
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/data.json
@@ -0,0 +1,125 @@
+{
+ "msgId": "1000000000000000000",
+ "msgRandom": "1000000000000000000",
+ "msgSeq": "100000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 5,
+ "subMsgType": 12,
+ "sendType": 3,
+ "senderUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "",
+ "sendNickName": "",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 8,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": null,
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": null,
+ "grayTipElement": {
+ "subElementType": 12,
+ "revokeElement": null,
+ "proclamationElement": null,
+ "emojiReplyElement": null,
+ "groupElement": null,
+ "buddyElement": null,
+ "feedMsgElement": null,
+ "essenceElement": null,
+ "groupNotifyElement": null,
+ "buddyNotifyElement": null,
+ "xmlElement": {
+ "busiType": "1",
+ "busiId": "10145",
+ "c2cType": 0,
+ "serviceType": 0,
+ "ctrlFlag": 7,
+ "content": " ",
+ "templId": "10179",
+ "seqId": "1000000000",
+ "templParam": {},
+ "pbReserv": "0x",
+ "members": {}
+ },
+ "fileReceiptElement": null,
+ "localGrayTipElement": null,
+ "blockGrayTipElement": null,
+ "aioOpGrayTipElement": null,
+ "jsonGrayTipElement": null
+ },
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": false,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED"
+}
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/index.test.ts b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/index.test.ts
new file mode 100644
index 0000000..db0f98d
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-12-3-8-12-1-10145/index.test.ts
@@ -0,0 +1,19 @@
+import type { RedMessage } from '@chronocat/red'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { buildParser } from '../../../../src/parser'
+import { ctx, satoriConfig } from '../../../mocks'
+
+test('Red 解析器应当正确解析 群聊 旧版群成员邀请新人入群消息', async () => {
+ const message = JSON.parse(
+ (await readFile(join(__dirname, 'data.json'))).toString('utf-8'),
+ ) as RedMessage
+
+ const events = await buildParser(ctx, satoriConfig)(message)
+
+ expect(events).toBeTruthy() // 解析到的消息应该存在
+ expect(events).toHaveLength(1) // 应当解析到 1 条消息
+
+ const [event] = events!
+ expect(event).toMatchSnapshot()
+})
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000..3ad4e28
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/__snapshots__/index.test.ts.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Red 解析器应当正确解析 群聊 新人自行入群消息 1`] = `
+{
+ "channel": {
+ "id": "100000000",
+ "name": "",
+ "type": 0,
+ },
+ "guild": {
+ "avatar": "https://p.qlogo.cn/gh/100000000/100000000/640",
+ "id": "100000000",
+ "name": "",
+ },
+ "id": undefined,
+ "member": {},
+ "message": {
+ "content": "",
+ "id": "1000000000000000000",
+ },
+ "operator": {
+ "id": "10001",
+ "name": undefined,
+ },
+ "platform": undefined,
+ "self_id": undefined,
+ "timestamp": 1000000000000,
+ "type": "guild-member-added",
+ "user": {
+ "avatar": "http://thirdqq.qlogo.cn/headimg_dl?dst_uin=10000&spec=640",
+ "id": "10000",
+ "name": "000",
+ },
+}
+`;
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/data.json b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/data.json
new file mode 100644
index 0000000..074a91a
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/data.json
@@ -0,0 +1,141 @@
+{
+ "msgId": "1000000000000000000",
+ "msgRandom": "1000000000000000000",
+ "msgSeq": "100000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 5,
+ "subMsgType": 8,
+ "sendType": 3,
+ "senderUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "",
+ "sendNickName": "",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 8,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": null,
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": null,
+ "grayTipElement": {
+ "subElementType": 4,
+ "revokeElement": null,
+ "proclamationElement": null,
+ "emojiReplyElement": null,
+ "groupElement": {
+ "type": 1,
+ "role": 0,
+ "groupName": "",
+ "memberUid": "u_0000000000000000000000",
+ "memberNick": "000",
+ "memberRemark": "",
+ "adminUid": "u_0000000000000000000001",
+ "adminNick": "001",
+ "adminRemark": "001",
+ "createGroup": null,
+ "memberAdd": {
+ "showType": 0,
+ "otherAdd": {
+ "uid": "u_0000000000000000000000",
+ "name": "000",
+ "uin": "10000"
+ },
+ "otherAddByOtherQRCode": null,
+ "otherAddByYourQRCode": null,
+ "youAddByOtherQRCode": null,
+ "otherInviteOther": null,
+ "otherInviteYou": null,
+ "youInviteOther": null
+ },
+ "shutUp": null,
+ "memberUin": "10000",
+ "adminUin": "10001"
+ },
+ "buddyElement": null,
+ "feedMsgElement": null,
+ "essenceElement": null,
+ "groupNotifyElement": null,
+ "buddyNotifyElement": null,
+ "xmlElement": null,
+ "fileReceiptElement": null,
+ "localGrayTipElement": null,
+ "blockGrayTipElement": null,
+ "aioOpGrayTipElement": null,
+ "jsonGrayTipElement": null
+ },
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": false,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED"
+}
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/index.test.ts b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/index.test.ts
new file mode 100644
index 0000000..1f68a09
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-1/index.test.ts
@@ -0,0 +1,19 @@
+import type { RedMessage } from '@chronocat/red'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { buildParser } from '../../../../src/parser'
+import { ctx, satoriConfig } from '../../../mocks'
+
+test('Red 解析器应当正确解析 群聊 新人自行入群消息', async () => {
+ const message = JSON.parse(
+ (await readFile(join(__dirname, 'data.json'))).toString('utf-8'),
+ ) as RedMessage
+
+ const events = await buildParser(ctx, satoriConfig)(message)
+
+ expect(events).toBeTruthy() // 解析到的消息应该存在
+ expect(events).toHaveLength(1) // 应当解析到 1 条消息
+
+ const [event] = events!
+ expect(event).toMatchSnapshot()
+})
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000..f2301c2
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/__snapshots__/index.test.ts.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Red 解析器应当正确解析 群聊 群成员被禁言消息 1`] = `
+{
+ "channel": {
+ "id": "100000000",
+ "name": "",
+ "type": 0,
+ },
+ "guild": {
+ "avatar": "https://p.qlogo.cn/gh/100000000/100000000/640",
+ "id": "100000000",
+ "name": "",
+ },
+ "id": undefined,
+ "message": {
+ "content": "",
+ "id": "1000000000000000000",
+ },
+ "operator": {
+ "id": "10001",
+ "name": undefined,
+ },
+ "platform": undefined,
+ "self_id": undefined,
+ "timestamp": 1000000000000,
+ "type": "unsafe-guild-mute",
+ "user": {
+ "avatar": "http://thirdqq.qlogo.cn/headimg_dl?dst_uin=10000&spec=640",
+ "id": "10000",
+ "name": "000",
+ },
+}
+`;
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/data.json b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/data.json
new file mode 100644
index 0000000..b878246
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/data.json
@@ -0,0 +1,145 @@
+{
+ "msgId": "1000000000000000000",
+ "msgRandom": "1000000000000000000",
+ "msgSeq": "100000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 5,
+ "subMsgType": 8,
+ "sendType": 3,
+ "senderUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "",
+ "sendNickName": "",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 8,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": null,
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": null,
+ "grayTipElement": {
+ "subElementType": 4,
+ "revokeElement": null,
+ "proclamationElement": null,
+ "emojiReplyElement": null,
+ "groupElement": {
+ "type": 8,
+ "role": 0,
+ "groupName": "",
+ "memberUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "memberNick": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "memberRemark": "",
+ "adminUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "adminNick": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "adminRemark": "",
+ "createGroup": null,
+ "memberAdd": null,
+ "shutUp": {
+ "curTime": "1000000000",
+ "duration": "1296000",
+ "admin": {
+ "uid": "u_0000000000000000000001",
+ "card": "",
+ "name": "",
+ "role": 3,
+ "uin": "10001"
+ },
+ "member": {
+ "uid": "u_0000000000000000000000",
+ "card": "",
+ "name": "000",
+ "role": 0,
+ "uin": "10000"
+ }
+ },
+ "memberUin": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "adminUin": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED"
+ },
+ "buddyElement": null,
+ "feedMsgElement": null,
+ "essenceElement": null,
+ "groupNotifyElement": null,
+ "buddyNotifyElement": null,
+ "xmlElement": null,
+ "fileReceiptElement": null,
+ "localGrayTipElement": null,
+ "blockGrayTipElement": null,
+ "aioOpGrayTipElement": null,
+ "jsonGrayTipElement": null
+ },
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": false,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED"
+}
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/index.test.ts b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/index.test.ts
new file mode 100644
index 0000000..7dcdb08
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-5-8-3-8-4-8/index.test.ts
@@ -0,0 +1,19 @@
+import type { RedMessage } from '@chronocat/red'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { buildParser } from '../../../../src/parser'
+import { ctx, satoriConfig } from '../../../mocks'
+
+test('Red 解析器应当正确解析 群聊 群成员被禁言消息', async () => {
+ const message = JSON.parse(
+ (await readFile(join(__dirname, 'data.json'))).toString('utf-8'),
+ ) as RedMessage
+
+ const events = await buildParser(ctx, satoriConfig)(message)
+
+ expect(events).toBeTruthy() // 解析到的消息应该存在
+ expect(events).toHaveLength(1) // 应当解析到 1 条消息
+
+ const [event] = events!
+ expect(event).toMatchSnapshot()
+})
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000..2a25357
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/__snapshots__/index.test.ts.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Red 解析器应当正确解析 群聊 语音消息 1`] = `
+{
+ "channel": {
+ "id": "100000000",
+ "name": "001",
+ "type": 0,
+ },
+ "guild": {
+ "avatar": "https://p.qlogo.cn/gh/100000000/100000000/640",
+ "id": "100000000",
+ "name": "001",
+ },
+ "id": undefined,
+ "member": {},
+ "message": {
+ "content": "",
+ "id": "1000000000000000000",
+ },
+ "platform": undefined,
+ "self_id": undefined,
+ "timestamp": 1000000000000,
+ "type": "message-created",
+ "user": {
+ "avatar": "http://thirdqq.qlogo.cn/headimg_dl?dst_uin=10000&spec=640",
+ "id": "10000",
+ "name": "000",
+ },
+}
+`;
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/data.json b/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/data.json
new file mode 100644
index 0000000..1c7b6b1
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/data.json
@@ -0,0 +1,120 @@
+{
+ "msgId": "1000000000000000000",
+ "msgRandom": "1000000000",
+ "msgSeq": "10000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 6,
+ "subMsgType": 0,
+ "sendType": 0,
+ "senderUid": "u_0000000000000000000000",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "",
+ "sendNickName": "000",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 4,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": null,
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": {
+ "fileName": "00000000000000000000000000000000.amr",
+ "filePath": "C:\\00000000000000000000000000000000.amr",
+ "md5HexStr": "00000000000000000000000000000000",
+ "fileSize": "10000",
+ "duration": 15,
+ "formatType": 1,
+ "voiceType": 0,
+ "voiceChangeType": 0,
+ "canConvert2Text": false,
+ "fileId": 0,
+ "fileUuid": "000000000000000000000000000000000000",
+ "text": "",
+ "translateStatus": 0,
+ "transferStatus": 0,
+ "progress": 0,
+ "playState": 0,
+ "waveAmplitudes": [
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99
+ ],
+ "invalidState": 0,
+ "fileSubId": "1000000000",
+ "fileBizId": null
+ },
+ "videoElement": null,
+ "grayTipElement": null,
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "001",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": true,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "10000"
+}
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/index.test.ts b/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/index.test.ts
new file mode 100644
index 0000000..3aadcc4
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-6-0-0/index.test.ts
@@ -0,0 +1,19 @@
+import type { RedMessage } from '@chronocat/red'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { buildParser } from '../../../../src/parser'
+import { ctx, satoriConfig } from '../../../mocks'
+
+test('Red 解析器应当正确解析 群聊 语音消息', async () => {
+ const message = JSON.parse(
+ (await readFile(join(__dirname, 'data.json'))).toString('utf-8'),
+ ) as RedMessage
+
+ const events = await buildParser(ctx, satoriConfig)(message)
+
+ expect(events).toBeTruthy() // 解析到的消息应该存在
+ expect(events).toHaveLength(1) // 应当解析到 1 条消息
+
+ const [event] = events!
+ expect(event).toMatchSnapshot()
+})
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000..9176160
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/__snapshots__/index.test.ts.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Red 解析器应当正确解析 群聊 视频消息 1`] = `
+{
+ "channel": {
+ "id": "100000000",
+ "name": "001",
+ "type": 0,
+ },
+ "guild": {
+ "avatar": "https://p.qlogo.cn/gh/100000000/100000000/640",
+ "id": "100000000",
+ "name": "001",
+ },
+ "id": undefined,
+ "member": {},
+ "message": {
+ "content": "",
+ "id": "1000000000000000000",
+ },
+ "platform": undefined,
+ "self_id": undefined,
+ "timestamp": 1000000000000,
+ "type": "message-created",
+ "user": {
+ "avatar": "http://thirdqq.qlogo.cn/headimg_dl?dst_uin=10000&spec=640",
+ "id": "10000",
+ "name": "000",
+ },
+}
+`;
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/data.json b/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/data.json
new file mode 100644
index 0000000..75ff91e
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/data.json
@@ -0,0 +1,116 @@
+{
+ "msgId": "1000000000000000000",
+ "msgRandom": "1000000000",
+ "msgSeq": "100000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 7,
+ "subMsgType": 0,
+ "sendType": 0,
+ "senderUid": "u_0000000000000000000000",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "",
+ "sendNickName": "000",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 5,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": null,
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": {
+ "filePath": "C:\\00000000000000000000000000000000.mp4",
+ "fileName": "00000000000000000000000000000000.mp4",
+ "videoMd5": "00000000000000000000000000000000",
+ "thumbMd5": "00000000000000000000000000000001",
+ "fileTime": 90,
+ "thumbSize": 10000,
+ "fileFormat": 2,
+ "fileSize": "20000000",
+ "thumbWidth": 640,
+ "thumbHeight": 480,
+ "busiType": 0,
+ "subBusiType": 0,
+ "thumbPath": {},
+ "transferStatus": 0,
+ "progress": 0,
+ "invalidState": 0,
+ "fileUuid": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "fileSubId": "",
+ "fileBizId": null
+ },
+ "grayTipElement": null,
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "001",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": true,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "10000"
+}
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/index.test.ts b/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/index.test.ts
new file mode 100644
index 0000000..7dfbb85
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-7-0-0/index.test.ts
@@ -0,0 +1,19 @@
+import type { RedMessage } from '@chronocat/red'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { buildParser } from '../../../../src/parser'
+import { ctx, satoriConfig } from '../../../mocks'
+
+test('Red 解析器应当正确解析 群聊 视频消息', async () => {
+ const message = JSON.parse(
+ (await readFile(join(__dirname, 'data.json'))).toString('utf-8'),
+ ) as RedMessage
+
+ const events = await buildParser(ctx, satoriConfig)(message)
+
+ expect(events).toBeTruthy() // 解析到的消息应该存在
+ expect(events).toHaveLength(1) // 应当解析到 1 条消息
+
+ const [event] = events!
+ expect(event).toMatchSnapshot()
+})
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/__snapshots__/index.test.ts.snap
new file mode 100644
index 0000000..44b1775
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/__snapshots__/index.test.ts.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Red 解析器应当正确解析 群聊 引用消息 1`] = `
+{
+ "channel": {
+ "id": "100000000",
+ "name": "002",
+ "type": 0,
+ },
+ "guild": {
+ "avatar": "https://p.qlogo.cn/gh/100000000/100000000/640",
+ "id": "100000000",
+ "name": "002",
+ },
+ "id": undefined,
+ "member": {},
+ "message": {
+ "content": "yyy
xxx",
+ "id": "1000000000000000000",
+ },
+ "platform": undefined,
+ "self_id": undefined,
+ "timestamp": 1000000000000,
+ "type": "message-created",
+ "user": {
+ "avatar": "http://thirdqq.qlogo.cn/headimg_dl?dst_uin=10000&spec=640",
+ "id": "10000",
+ "name": "000",
+ },
+}
+`;
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/data.json b/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/data.json
new file mode 100644
index 0000000..4613bda
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/data.json
@@ -0,0 +1,270 @@
+{
+ "msgId": "1000000000000000000",
+ "msgRandom": "1000000000",
+ "msgSeq": "100000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 9,
+ "subMsgType": 33,
+ "sendType": 0,
+ "senderUid": "u_0000000000000000000000",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "",
+ "sendNickName": "000",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 7,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": null,
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": {
+ "replayMsgId": "0",
+ "replayMsgSeq": "100000",
+ "replayMsgRootSeq": "0",
+ "replayMsgRootMsgId": "0",
+ "replayMsgRootCommentCnt": "0",
+ "sourceMsgIdInRecords": "1000000000000000001",
+ "sourceMsgText": "",
+ "sourceMsgTextElems": [
+ {
+ "replyAbsElemType": 1,
+ "textElemContent": "yyy",
+ "faceElem": null
+ }
+ ],
+ "senderUid": "10001",
+ "senderUidStr": "u_0000000000000000000001",
+ "replyMsgClientSeq": "0",
+ "replyMsgTime": "1000000000",
+ "replyMsgRevokeType": 0,
+ "sourceMsgIsIncPic": false,
+ "sourceMsgExpired": false,
+ "anonymousNickName": null,
+ "originalMsgState": null,
+ "senderUin": "u_0000000000000000000001"
+ },
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": null,
+ "grayTipElement": null,
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ },
+ {
+ "elementType": 1,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": {
+ "content": "xxx",
+ "atType": 0,
+ "atUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "atTinyId": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "atNtUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "subElementType": 0,
+ "atChannelId": "0",
+ "atRoleId": "0",
+ "atRoleColor": 0,
+ "atRoleName": "",
+ "needNotify": 0,
+ "atNtUin": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED"
+ },
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": null,
+ "grayTipElement": null,
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [
+ {
+ "msgId": "1000000000000000001",
+ "msgRandom": "1000000000",
+ "msgSeq": "100000",
+ "cntSeq": "0",
+ "chatType": 2,
+ "msgType": 2,
+ "subMsgType": 1,
+ "sendType": 0,
+ "senderUid": "u_0000000000000000000001",
+ "peerUid": "100000000",
+ "channelId": "",
+ "guildId": "",
+ "guildCode": "0",
+ "fromUid": "0",
+ "fromAppid": "0",
+ "msgTime": "1000000000",
+ "msgMeta": "0x",
+ "sendStatus": 2,
+ "sendMemberName": "",
+ "sendNickName": "001",
+ "guildName": "",
+ "channelName": "",
+ "elements": [
+ {
+ "elementType": 1,
+ "elementId": "1000000000000000000",
+ "extBufForUI": "0x",
+ "textElement": {
+ "content": "yyy",
+ "atType": 0,
+ "atUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "atTinyId": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "atNtUid": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED",
+ "subElementType": 0,
+ "atChannelId": "0",
+ "atRoleId": "0",
+ "atRoleColor": 0,
+ "atRoleName": "",
+ "needNotify": 0,
+ "atNtUin": "DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED"
+ },
+ "faceElement": null,
+ "marketFaceElement": null,
+ "replyElement": null,
+ "picElement": null,
+ "pttElement": null,
+ "videoElement": null,
+ "grayTipElement": null,
+ "arkElement": null,
+ "fileElement": null,
+ "liveGiftElement": null,
+ "markdownElement": null,
+ "structLongMsgElement": null,
+ "multiForwardMsgElement": null,
+ "giphyElement": null,
+ "walletElement": null,
+ "inlineKeyboardElement": null,
+ "textGiftElement": null,
+ "calendarElement": null,
+ "yoloGameResultElement": null,
+ "avRecordElement": null
+ }
+ ],
+ "records": [],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "002",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": false,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "10001"
+ }
+ ],
+ "emojiLikesList": [],
+ "commentCnt": "0",
+ "directMsgFlag": 0,
+ "directMsgMembers": [],
+ "peerName": "002",
+ "freqLimitInfo": null,
+ "editable": false,
+ "avatarMeta": "",
+ "avatarPendant": "",
+ "feedId": "",
+ "roleId": "0",
+ "timeStamp": "0",
+ "clientIdentityInfo": null,
+ "isImportMsg": false,
+ "atType": 0,
+ "roleType": 0,
+ "fromChannelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "fromGuildRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "levelRoleInfo": {
+ "roleId": "0",
+ "name": "",
+ "color": 0
+ },
+ "recallTime": "0",
+ "isOnlineMsg": true,
+ "generalFlags": "0x",
+ "clientSeq": "0",
+ "fileGroupSize": null,
+ "foldingInfo": null,
+ "nameType": 0,
+ "avatarFlag": 0,
+ "anonymousExtInfo": null,
+ "personalMedal": null,
+ "roleManagementTag": null,
+ "senderUin": "10000"
+}
diff --git a/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/index.test.ts b/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/index.test.ts
new file mode 100644
index 0000000..ece939d
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/parser/fixtures/f-9-33-0/index.test.ts
@@ -0,0 +1,19 @@
+import type { RedMessage } from '@chronocat/red'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { buildParser } from '../../../../src/parser'
+import { ctx, satoriConfig } from '../../../mocks'
+
+test('Red 解析器应当正确解析 群聊 引用消息', async () => {
+ const message = JSON.parse(
+ (await readFile(join(__dirname, 'data.json'))).toString('utf-8'),
+ ) as RedMessage
+
+ const events = await buildParser(ctx, satoriConfig)(message)
+
+ expect(events).toBeTruthy() // 解析到的消息应该存在
+ expect(events).toHaveLength(1) // 应当解析到 1 条消息
+
+ const [event] = events!
+ expect(event).toMatchSnapshot()
+})
diff --git a/packages/engine-chronocat-event/tests/tsconfig.json b/packages/engine-chronocat-event/tests/tsconfig.json
new file mode 100644
index 0000000..dd8328f
--- /dev/null
+++ b/packages/engine-chronocat-event/tests/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../../tsconfig.base",
+ "compilerOptions": {
+ "rootDir": ".."
+ },
+ "include": [
+ ".",
+ "../src"
+ ]
+}
diff --git a/tsconfig.json b/tsconfig.json
index 8c74413..7a8039c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,6 +2,7 @@
"extends": "./tsconfig.base",
"include": [
"jest.config.ts",
+ "external",
"packages",
"scripts",
],
diff --git a/yarn.lock b/yarn.lock
index b5e03ad..c603352 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6407,6 +6407,7 @@ __metadata:
jest: "npm:^29.6.3"
prettier: "npm:^3.0.2"
ts-jest: "npm:^29.1.1"
+ ts-node: "npm:^10.9.1"
typescript: "npm:~5.1.0"
languageName: unknown
linkType: soft