koishi-plugin-chat-patch 3.0.2 → 5.0.0

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.
Files changed (194) hide show
  1. package/client/vue/index.vue +60 -634
  2. package/client/web/env.d.ts +56 -0
  3. package/client/web/index.html +21 -0
  4. package/client/web/package-lock.json +3419 -0
  5. package/client/web/package.json +62 -0
  6. package/client/web/public/bcui/css/color-dark.css +35 -0
  7. package/client/web/public/bcui/css/color-light.css +35 -0
  8. package/client/web/public/bcui/css/style.css +468 -0
  9. package/client/web/public/bcui/js/main.js +193 -0
  10. package/client/web/public/css/append-dark.css +13 -0
  11. package/client/web/public/css/append-light.css +13 -0
  12. package/client/web/public/css/append-napcat-dark.css +14 -0
  13. package/client/web/public/css/append-napcat-light.css +14 -0
  14. package/client/web/public/favicon.ico +0 -0
  15. package/client/web/public/img/icons/icon-for-fuck-apple.png +0 -0
  16. package/client/web/public/img/icons/icon-maskable.png +0 -0
  17. package/client/web/public/img/icons/icon.png +0 -0
  18. package/client/web/public/img/icons/icon.svg +10 -0
  19. package/client/web/public/robots.txt +2 -0
  20. package/client/web/public/sw.js +58 -0
  21. package/client/web/scripts/copy-bcui.mjs +17 -0
  22. package/client/web/src/App.vue +1127 -0
  23. package/client/web/src/assets/css/append/append_darwin.css +68 -0
  24. package/client/web/src/assets/css/append/append_full_vibrancy.css +290 -0
  25. package/client/web/src/assets/css/append/append_linux.css +125 -0
  26. package/client/web/src/assets/css/append/append_linux_vibrancy.css +22 -0
  27. package/client/web/src/assets/css/append/append_napcat.css +35 -0
  28. package/client/web/src/assets/css/append/append_new.css +843 -0
  29. package/client/web/src/assets/css/append/append_vibrancy.css +113 -0
  30. package/client/web/src/assets/css/append/append_win32.css +109 -0
  31. package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +349 -0
  32. package/client/web/src/assets/css/append/mobile/append_mobile_vertical.css +96 -0
  33. package/client/web/src/assets/css/chat.css +2104 -0
  34. package/client/web/src/assets/css/msg.css +941 -0
  35. package/client/web/src/assets/css/options.css +811 -0
  36. package/client/web/src/assets/css/sys_notice.css +62 -0
  37. package/client/web/src/assets/css/view.css +2105 -0
  38. package/client/web/src/assets/font//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223.ttf +0 -0
  39. package/client/web/src/assets/img/Vue.png +0 -0
  40. package/client/web/src/assets/img/cakes.svg +189 -0
  41. package/client/web/src/assets/img/icons/AppIcon.png +0 -0
  42. package/client/web/src/assets/img/icons/AppIconDark.png +0 -0
  43. package/client/web/src/assets/img/icons/CapacitorAppIcon.png +0 -0
  44. package/client/web/src/assets/img/icons/CapacitorAppIconDark.png +0 -0
  45. package/client/web/src/assets/img/icons/NapcatAppIcon.png +0 -0
  46. package/client/web/src/assets/img/icons/NapcatAppIconDark.png +0 -0
  47. package/client/web/src/assets/img/icons/exit.png +0 -0
  48. package/client/web/src/assets/img/icons/pen.png +0 -0
  49. package/client/web/src/assets/img/stars.svg +89 -0
  50. package/client/web/src/assets/img//346/234/237/345/276/205.png +0 -0
  51. package/client/web/src/assets/l10n/_About I10n.md +35 -0
  52. package/client/web/src/assets/l10n/_l10nconfig.json +7 -0
  53. package/client/web/src/assets/l10n/zh-CN.po +1941 -0
  54. package/client/web/src/assets/logo.png +0 -0
  55. package/client/web/src/assets/pathMap/LLOneBot.yaml +2 -0
  56. package/client/web/src/assets/pathMap/Lagrange.OneBot.yaml +180 -0
  57. package/client/web/src/assets/pathMap/NapCat.Onebot.yaml +224 -0
  58. package/client/web/src/assets/pathMap/SnowLuma.yaml +80 -0
  59. package/client/web/src/assets/qed.txt +1 -0
  60. package/client/web/src/components/AboutPan.vue +197 -0
  61. package/client/web/src/components/BulletinBody.vue +129 -0
  62. package/client/web/src/components/DepPan.vue +310 -0
  63. package/client/web/src/components/EmojiFace.vue +32 -0
  64. package/client/web/src/components/FacePan.vue +461 -0
  65. package/client/web/src/components/FileBody.vue +129 -0
  66. package/client/web/src/components/FileManager.vue +532 -0
  67. package/client/web/src/components/FriendBody.vue +54 -0
  68. package/client/web/src/components/GlobalSessionSearchBar.vue +301 -0
  69. package/client/web/src/components/History.vue +49 -0
  70. package/client/web/src/components/LazyLottie.vue +82 -0
  71. package/client/web/src/components/MergePan.vue +127 -0
  72. package/client/web/src/components/MsgBody.vue +1478 -0
  73. package/client/web/src/components/MusicPlayer.vue +549 -0
  74. package/client/web/src/components/NoticeBody.vue +168 -0
  75. package/client/web/src/components/QzonePermissionPan.vue +150 -0
  76. package/client/web/src/components/RawMsgRenderPreviewPan.vue +190 -0
  77. package/client/web/src/components/ThemeColorPickerPan.vue +420 -0
  78. package/client/web/src/components/TinySessionBody.vue +51 -0
  79. package/client/web/src/components/UmamiInfoPan.vue +1549 -0
  80. package/client/web/src/components/UpdatePan.vue +263 -0
  81. package/client/web/src/components/ViewerCom.vue +1360 -0
  82. package/client/web/src/components/VoiceMsg.vue +626 -0
  83. package/client/web/src/components/WelPan.vue +894 -0
  84. package/client/web/src/components/msg-body/.gitkeep +0 -0
  85. package/client/web/src/components/msg-component/JsonSegComp.vue +138 -0
  86. package/client/web/src/components/msg-component/XmlSegComp.vue +132 -0
  87. package/client/web/src/components/msg-component/jsonComp/AutoReply.vue +97 -0
  88. package/client/web/src/components/msg-component/jsonComp/Contact.lua.vue +129 -0
  89. package/client/web/src/components/msg-component/jsonComp/Feed.lua.vue +94 -0
  90. package/client/web/src/components/msg-component/jsonComp/Forum.vue +84 -0
  91. package/client/web/src/components/msg-component/jsonComp/Mannounce.vue +111 -0
  92. package/client/web/src/components/msg-component/jsonComp/Map.vue +52 -0
  93. package/client/web/src/components/msg-component/jsonComp/Miniapp.lua.vue +53 -0
  94. package/client/web/src/components/msg-component/jsonComp/Miniapp.vue +69 -0
  95. package/client/web/src/components/msg-component/jsonComp/Music.lua.vue +135 -0
  96. package/client/web/src/components/msg-component/jsonComp/Tuwen.lua.vue +56 -0
  97. package/client/web/src/components/notice-component/.gitkeep +0 -0
  98. package/client/web/src/components/notice-component/MealHungryPan.vue +119 -0
  99. package/client/web/src/components/tooltip/CustomFaceTooltip.vue +22 -0
  100. package/client/web/src/components/tooltip/Tooltip.vue +50 -0
  101. package/client/web/src/components/tooltip/Tooltips.vue +14 -0
  102. package/client/web/src/components/tooltip/UserInfoTooltip.vue +69 -0
  103. package/client/web/src/function/base.ts +238 -0
  104. package/client/web/src/function/connect.ts +2042 -0
  105. package/client/web/src/function/debug.ts +10 -0
  106. package/client/web/src/function/elements/information.ts +141 -0
  107. package/client/web/src/function/elements/system.ts +101 -0
  108. package/client/web/src/function/elements/vueComp.ts +62 -0
  109. package/client/web/src/function/favicon.ts +76 -0
  110. package/client/web/src/function/input.ts +69 -0
  111. package/client/web/src/function/model/emoji.ts +127 -0
  112. package/client/web/src/function/model/img.ts +150 -0
  113. package/client/web/src/function/model/msg-body.ts +51 -0
  114. package/client/web/src/function/msg.ts +2674 -0
  115. package/client/web/src/function/notify.ts +225 -0
  116. package/client/web/src/function/option.ts +749 -0
  117. package/client/web/src/function/satori-model.ts +818 -0
  118. package/client/web/src/function/satori.ts +325 -0
  119. package/client/web/src/function/sender.ts +178 -0
  120. package/client/web/src/function/tooltip.ts +49 -0
  121. package/client/web/src/function/utils/appUtil.ts +2288 -0
  122. package/client/web/src/function/utils/avatarUtil.ts +31 -0
  123. package/client/web/src/function/utils/backgroundUtil.ts +110 -0
  124. package/client/web/src/function/utils/linkViewUtil.ts +101 -0
  125. package/client/web/src/function/utils/localHistoryUtil.ts +517 -0
  126. package/client/web/src/function/utils/msgUtil.ts +1242 -0
  127. package/client/web/src/function/utils/pinyin.ts +141 -0
  128. package/client/web/src/function/utils/recordUtil.ts +187 -0
  129. package/client/web/src/function/utils/sessionUtil.ts +114 -0
  130. package/client/web/src/function/utils/systemUtil.ts +664 -0
  131. package/client/web/src/main.ts +91 -0
  132. package/client/web/src/pages/Chat.vue +3453 -0
  133. package/client/web/src/pages/Friends.vue +636 -0
  134. package/client/web/src/pages/Info.vue +717 -0
  135. package/client/web/src/pages/Messages.vue +875 -0
  136. package/client/web/src/pages/Options.vue +82 -0
  137. package/client/web/src/pages/Qzone.vue +1178 -0
  138. package/client/web/src/pages/chat-view/ChatDanmaku.vue +759 -0
  139. package/client/web/src/pages/chat-view/ChatGlagame.vue +642 -0
  140. package/client/web/src/pages/chat-view/ChatTerminal.vue +1044 -0
  141. package/client/web/src/pages/chat-view/SystemNotice.vue +171 -0
  142. package/client/web/src/pages/options/OptAccount.vue +107 -0
  143. package/client/web/src/pages/options/OptAddon.vue +185 -0
  144. package/client/web/src/pages/options/OptDev.vue +704 -0
  145. package/client/web/src/pages/options/OptFunction.vue +403 -0
  146. package/client/web/src/pages/options/OptInfo.vue +165 -0
  147. package/client/web/src/pages/options/OptView.vue +846 -0
  148. package/client/web/src/runtime/backend.ts +310 -0
  149. package/client/web/src/state/auth.ts +14 -0
  150. package/client/web/src/state/chat.ts +36 -0
  151. package/client/web/src/state/connection.ts +20 -0
  152. package/client/web/src/state/contact.ts +37 -0
  153. package/client/web/src/state/qzone.ts +18 -0
  154. package/client/web/src/state/sessionHistory.ts +25 -0
  155. package/client/web/src/state/settings.ts +18 -0
  156. package/client/web/src/state/sticker.ts +10 -0
  157. package/client/web/src/state/ui.ts +56 -0
  158. package/client/web/tsconfig.json +27 -0
  159. package/client/web/tsconfig.tsbuildinfo +1 -0
  160. package/client/web/vite.config.ts +33 -0
  161. package/dist/index.js +1 -12
  162. package/dist/style.css +1 -1
  163. package/lib/index.js +2070 -2077
  164. package/package.json +15 -2
  165. package/readme.md +0 -22
  166. package/src/bootstrap.ts +71 -0
  167. package/src/cache.ts +319 -0
  168. package/src/config.ts +23 -35
  169. package/src/database.ts +650 -0
  170. package/src/index.ts +27 -59
  171. package/src/media.ts +302 -0
  172. package/src/recorder.ts +175 -0
  173. package/src/self-message.ts +456 -0
  174. package/src/server.d.ts +26 -0
  175. package/src/types.ts +108 -48
  176. package/src/web.ts +712 -0
  177. package/client/vue/chat-logic.ts +0 -683
  178. package/client/vue/composables/useChatActions.ts +0 -61
  179. package/client/vue/composables/useChatData.ts +0 -185
  180. package/client/vue/composables/useImageCache.ts +0 -281
  181. package/client/vue/composables/useVideoCache.ts +0 -125
  182. package/client/vue/types.ts +0 -72
  183. package/lib/api-handlers.d.ts +0 -25
  184. package/lib/config.d.ts +0 -14
  185. package/lib/file-manager.d.ts +0 -124
  186. package/lib/index.d.ts +0 -17
  187. package/lib/logger.d.ts +0 -9
  188. package/lib/message-handler.d.ts +0 -35
  189. package/lib/types.d.ts +0 -57
  190. package/lib/utils.d.ts +0 -18
  191. package/src/api-handlers.ts +0 -646
  192. package/src/file-manager.ts +0 -1436
  193. package/src/message-handler.ts +0 -507
  194. package/src/utils.ts +0 -189
File without changes
@@ -0,0 +1,138 @@
1
+ <!--
2
+ * @FileDescription: 卡片消息消息组件
3
+ * @Author: Stapxs
4
+ * @Date: 2023/05/23
5
+ * 2026/02/13
6
+ * @Version: 1.0 - 初始版本
7
+ * 2.0 - 重构为单独组件
8
+ -->
9
+
10
+ <template>
11
+ <template v-if="comp">
12
+ <component :is="comp" :id="id" :data="data" />
13
+ </template>
14
+ <div v-else-if="genericCard" class="msg-json generic-card" @click="openGenericCard(genericCard.url)">
15
+ <img v-if="genericCard.img" :src="genericCard.img" alt="">
16
+ <div>
17
+ <p>{{ genericCard.title }}</p>
18
+ <span v-if="genericCard.desc">{{ genericCard.desc }}</span>
19
+ <small v-if="genericCard.tag">{{ genericCard.tag }}</small>
20
+ </div>
21
+ </div>
22
+ <span v-else class="msg-unknown">{{
23
+ '( ' + $t('不支持的卡片类型') + ': ' + id + ' )'
24
+ }}</span>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import { computed } from 'vue'
29
+ import { openLink } from '../../function/utils/appUtil'
30
+
31
+ const comps = import.meta.glob('./jsonComp/*.vue', {
32
+ eager: true,
33
+ import: 'default',
34
+ })
35
+
36
+ const cardComponentMap = {
37
+ 'com.tencent.tuwen.lua': comps['./jsonComp/Tuwen.lua.vue'],
38
+ 'com.tencent.mannounce': comps['./jsonComp/Mannounce.vue'],
39
+ 'com.tencent.miniapp.lua': comps['./jsonComp/Miniapp.lua.vue'],
40
+ 'com.tencent.miniapp_01': comps['./jsonComp/Miniapp.vue'],
41
+ 'com.tencent.music.lua': comps['./jsonComp/Music.lua.vue'],
42
+ 'com.tencent.contact.lua': comps['./jsonComp/Contact.lua.vue'],
43
+ 'com.tencent.map': comps['./jsonComp/Map.vue'],
44
+ 'com.tencent.forum': comps['./jsonComp/Forum.vue'],
45
+ 'com.tencent.autoreply': comps['./jsonComp/AutoReply.vue'],
46
+ 'com.tencent.feed.lua': comps['./jsonComp/Feed.lua.vue'],
47
+ }
48
+
49
+ const { data } = defineProps<{
50
+ data: string,
51
+ }>()
52
+
53
+ let json: unknown
54
+ let id = ''
55
+
56
+ try {
57
+ json = JSON.parse(data)
58
+ if (json && typeof (json as any).app === 'string') {
59
+ id = (json as any).app
60
+ }
61
+ } catch {
62
+ json = null
63
+ id = ''
64
+ }
65
+
66
+ const comp = cardComponentMap[id]
67
+
68
+ function getString(value: unknown): string {
69
+ return typeof value === 'string' ? value : ''
70
+ }
71
+
72
+ const genericCard = computed(() => {
73
+ if (comp || !json || typeof json !== 'object' || Array.isArray(json)) return undefined
74
+ const root = json as Record<string, unknown>
75
+ const meta = root.meta
76
+ if (typeof meta !== 'object' || meta === null) return undefined
77
+ const metaList = Object.values(meta as Record<string, unknown>)
78
+ for (const value of metaList) {
79
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) continue
80
+ const card = value as Record<string, unknown>
81
+ const title = getString(card.title) || getString(card.name) || getString(card.nickname)
82
+ const desc = getString(card.desc)
83
+ || getString(card.summary)
84
+ || getString(card.content)
85
+ || getString(card.address)
86
+ const img = getString(card.preview)
87
+ || getString(card.avatar)
88
+ || getString(card.cover)
89
+ || getString(card.img)
90
+ if (!title && !desc && !img) continue
91
+ return {
92
+ title,
93
+ desc,
94
+ img,
95
+ tag: getString(card.tag) || getString(card.source) || getString(root.desc),
96
+ url: getString(card.jumpUrl) || getString(card.url) || getString(card.qqdocurl),
97
+ }
98
+ }
99
+ return undefined
100
+ })
101
+
102
+ function openGenericCard(url: string) {
103
+ if (url) openLink(url)
104
+ }
105
+ </script>
106
+
107
+ <style scoped>
108
+ .generic-card {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 12px;
112
+ cursor: pointer;
113
+ }
114
+ .generic-card img {
115
+ width: 48px;
116
+ height: 48px;
117
+ border-radius: 7px;
118
+ object-fit: cover;
119
+ flex-shrink: 0;
120
+ }
121
+ .generic-card > div {
122
+ min-width: 0;
123
+ }
124
+ .generic-card p {
125
+ margin: 0;
126
+ font-weight: bold;
127
+ }
128
+ .generic-card span,
129
+ .generic-card small {
130
+ display: block;
131
+ margin-top: 4px;
132
+ font-size: 0.8rem;
133
+ opacity: 0.7;
134
+ white-space: nowrap;
135
+ overflow: hidden;
136
+ text-overflow: ellipsis;
137
+ }
138
+ </style>
@@ -0,0 +1,132 @@
1
+ <!--
2
+ * @FileDescription: 卡片消息消息组件
3
+ * @Author: Mr.Lee
4
+ * @Date: 2026/02/13
5
+ * @Version: 1.0 - 从CardMsgComp拆分出来
6
+ -->
7
+
8
+ <template>
9
+ <div
10
+ ref="main"
11
+ @click="cardClick()"
12
+ v-html="buildXML(item, id, id)" />
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { Logger } from '../../function/base';
17
+ import { openLink } from '../../function/utils/appUtil'
18
+ import app from '../../main'
19
+ import { useTemplateRef } from 'vue'
20
+
21
+ const { item, id } = defineProps<{
22
+ item: string,
23
+ id: string,
24
+ }>()
25
+
26
+ const mainDom = useTemplateRef('main')
27
+
28
+ let link: string | undefined = undefined
29
+
30
+ /**
31
+ * 尝试解析渲染 XML 消息
32
+ * @param xml 原始的 XML 消息内容
33
+ * @param id XML 消息 ID(暂时不知道有什么用)
34
+ * @param msgid 消息 ID
35
+ * @returns 处理完成的 HTML 代码
36
+ */
37
+ function buildXML(xml: string, id: string, msgid: string) {
38
+ const $t = app.config.globalProperties.$t
39
+ try {
40
+ // <msg> 标签内的为本体
41
+ let item = xml.substring(xml.indexOf('<item'), xml.indexOf('</msg>'))
42
+ // 尝试转换标签为 html
43
+ // item = item.replaceAll('/>', '>')
44
+ item = item.replaceAll('item', 'div') // item
45
+ item = item.replaceAll('<div', '<div class="msg-xml"')
46
+ item = item.replaceAll('title', 'p') // title
47
+ item = item.replaceAll('summary', 'a') // summary
48
+ item = item.replaceAll('<a', '<a class="msg-xml-summary"')
49
+ item = item.replaceAll('<picture', '<img class="msg-xml-img"') // picture
50
+ // 将不正确的参数改为 dataset
51
+ item = item.replaceAll('size=', 'data-size=')
52
+ item = item.replaceAll('linespace=', 'data-linespace=')
53
+ item = item.replaceAll('cover=', 'src=')
54
+ // 处理出处标签
55
+ item = item.replace('source name=', 'source data-name=')
56
+ // 处理错误的 style 位置
57
+ const div = document.createElement('div')
58
+ div.id = 'xml-' + msgid
59
+ div.dataset.id = id
60
+ div.innerHTML = item
61
+ for (const element of div.children[0].children) {
62
+ if (element.nodeName === 'P') {
63
+ const pBody = element as HTMLParagraphElement
64
+ pBody.style.fontSize =
65
+ (Number(pBody.dataset.size) / 30).toString() + 'rem'
66
+ pBody.style.marginBottom = Number(pBody.dataset.size) / 5 + 'px'
67
+ break
68
+ }
69
+ }
70
+ // 解析 msg 消息体
71
+ let msgHeader =
72
+ xml.substring(xml.indexOf('<msg'), xml.indexOf('<item')) + '</msg>'
73
+ msgHeader = msgHeader.replace('msg', 'div')
74
+ msgHeader = msgHeader.replace('m_resid=', 'data-resid=')
75
+ msgHeader = msgHeader.replace('url=', 'data-url=')
76
+ const header = document.createElement('div')
77
+ header.innerHTML = msgHeader
78
+ // 处理特殊的出处
79
+ let sourceBody = undefined as HTMLElement | undefined
80
+ for (const element of div.children) {
81
+ if (element.nodeName === 'SOURCE') {
82
+ sourceBody = element as HTMLElement
83
+ }
84
+ }
85
+ if (sourceBody !== undefined) {
86
+ const source = sourceBody.dataset.name
87
+ if (source === '群投票')
88
+ return (
89
+ '<a class="msg-unknow">(' +
90
+ $t('chat_xml_unsupport') +
91
+ ':' +
92
+ source +
93
+ ')</a>'
94
+ )
95
+ }
96
+ // 附带链接的 xml 消息处理
97
+ if ((header.children[0] as HTMLElement).dataset.url !== undefined) {
98
+ link = (header.children[0] as HTMLElement).dataset.url
99
+ div.style.cursor = 'pointer'
100
+ }
101
+ return div.outerHTML
102
+ } catch (ex) {
103
+ new Logger().error(ex as Error, 'xml 消息解析错误')
104
+ return (
105
+ '<span v-else class="msg-unknown">( ' +
106
+ $t('解析消息错误') +
107
+ ': xml )</span>'
108
+ )
109
+ }
110
+ }
111
+
112
+ /**
113
+ * xml 消息的点击事件
114
+ */
115
+ function cardClick() {
116
+ const sender = mainDom.value
117
+
118
+ if (!sender) return
119
+
120
+ // 如果存在 url 项,优先打开 url
121
+ if (link !== undefined && link !== 'undefined' && link !== '') {
122
+ const openType =
123
+ sender.dataset.urlOpenType || sender.dataset.urlopentype
124
+ if (openType == '_self') {
125
+ window.open(link, '_self')
126
+ } else {
127
+ // 默认都以 _blank 打开
128
+ openLink(link)
129
+ }
130
+ }
131
+ }
132
+ </script>
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <div v-if="success" class="msg-json self-help">
3
+ <p>{{ parsedContent.title }}</p>
4
+ <span class="reply-buttons">
5
+ <span
6
+ v-for="(button, index) in parsedContent.button"
7
+ :key="index"
8
+ class="reply-button"
9
+ @click="sendNotify(button.action_data)">
10
+ {{ button.name }}
11
+ </span>
12
+ </span>
13
+ <div class="bottom-bar">
14
+ <font-awesome-icon icon="square-poll-horizontal" />
15
+ <span>{{ $t('自助问答') }}</span>
16
+ </div>
17
+ </div>
18
+ <span v-else class="msg-unknown">{{
19
+ '( ' + $t('加载失败') + ': ' + id + ' )'
20
+ }}</span>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
25
+ import { Logger } from '../../../function/base';
26
+ import { sendMsgRaw } from '../../../function/utils/msgUtil'
27
+ import { useChatStore } from '../../../state/chat'
28
+ import * as z from 'zod'
29
+
30
+ const { data: jsonData, id } = defineProps<{
31
+ data: string,
32
+ id: string,
33
+ }>()
34
+
35
+ const autoReply = z
36
+ .object({
37
+ app: z.literal('com.tencent.autoreply'),
38
+ meta: z.object({
39
+ metadata: z.object({
40
+ buttons: z.array(
41
+ z.object({
42
+ action: z.literal('notify'),
43
+ action_data: z.string(),
44
+ name: z.string(),
45
+ slot: z.number(),
46
+ }),
47
+ ),
48
+ title: z.string(),
49
+ type: z.string(),
50
+ }),
51
+ }),
52
+ })
53
+ .transform((o) => ({
54
+ title: o.meta.metadata.title,
55
+ button: o.meta.metadata.buttons.map((b) => ({
56
+ name: b.name,
57
+ action_data: b.action_data,
58
+ })),
59
+ }))
60
+ const json = JSON.parse(jsonData)
61
+ const parsedData = autoReply.safeParse(json)
62
+ const success = parsedData.success
63
+ const parsedContent = parsedData.data!
64
+ const chatStore = useChatStore()
65
+ if (!success) {
66
+ new Logger().error(parsedData.error, 'Card Parse Error')
67
+ }
68
+
69
+ function sendNotify(data: string) {
70
+ sendMsgRaw(
71
+ String(chatStore.chatInfo.show.id),
72
+ chatStore.chatInfo.show.type,
73
+ [
74
+ {
75
+ type: 'at',
76
+ qq: '2854196310'
77
+ },
78
+ {
79
+ type: 'text',
80
+ text: data,
81
+ }
82
+ ]
83
+ )
84
+ }
85
+ </script>
86
+
87
+ <style lang="css" scoped>
88
+ .reply-buttons {
89
+ display: flex;
90
+ flex-direction: column;
91
+ margin: 1ch 2ch;
92
+ }
93
+ .reply-button {
94
+ text-decoration: underline;
95
+ cursor: pointer;
96
+ }
97
+ </style>
@@ -0,0 +1,129 @@
1
+ <template>
2
+ <div v-if="success" class="msg-json">
3
+ <div class="contact-data">
4
+ <img :src="parsedContent.img" alt="">
5
+ <div>
6
+ <p>{{ parsedContent.title }}</p>
7
+ <span v-if="parsedContent.type === 'group'">{{ parsedContent.desc }}</span>
8
+ </div>
9
+ </div>
10
+ <div class="bottom-bar">
11
+ <span>{{ parsedContent.name }}</span>
12
+ </div>
13
+ </div>
14
+ <span v-else class="msg-unknown">{{
15
+ '( ' + $t('加载失败') + ': ' + id + ' )'
16
+ }}</span>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { Logger } from '../../../function/base';
21
+ import * as z from 'zod'
22
+
23
+ const { data: jsonData, id } = defineProps<{
24
+ data: string,
25
+ id: string,
26
+ }>()
27
+
28
+ const friend = z
29
+ .object({
30
+ app: z.literal('com.tencent.contact.lua'),
31
+ meta: z.object({
32
+ contact: z.object({
33
+ avatar: z.string(),
34
+ nickname: z.string(),
35
+ contact: z.string(),
36
+ jumpUrl: z.string(),
37
+ tag: z.literal('推荐好友'),
38
+ }),
39
+ }),
40
+ })
41
+ .transform((o) => ({
42
+ type: 'user' as const,
43
+ img: o.meta.contact.avatar,
44
+ title: o.meta.contact.nickname,
45
+ jumpUrl: o.meta.contact.jumpUrl,
46
+ name: o.meta.contact.tag,
47
+ }))
48
+
49
+ const group = z
50
+ .object({
51
+ app: z.literal('com.tencent.contact.lua'),
52
+ meta: z.object({
53
+ contact: z.object({
54
+ avatar: z.string().transform((s) => s.replace('/100', '/')),
55
+ nickname: z.string(),
56
+ contact: z.string().transform((s) => `${s}…`),
57
+ jumpUrl: z.string(),
58
+ tag: z.literal('群名片'),
59
+ }),
60
+ }),
61
+ })
62
+ .transform((o) => ({
63
+ type: 'group' as const,
64
+ img: o.meta.contact.avatar,
65
+ title: o.meta.contact.nickname,
66
+ desc: o.meta.contact.contact,
67
+ jumpUrl: o.meta.contact.jumpUrl,
68
+ name: o.meta.contact.tag,
69
+ }))
70
+
71
+ const bot = z
72
+ .object({
73
+ app: z.literal('com.tencent.contact.lua'),
74
+ meta: z.object({
75
+ contact: z.object({
76
+ avatar: z.string(),
77
+ nickname: z.string(),
78
+ contact: z.string(),
79
+ jumpUrl: z.string(),
80
+ tag: z.literal('机器人名片'),
81
+ }),
82
+ }),
83
+ })
84
+ .transform((o) => ({
85
+ type: 'bot' as const,
86
+ img: o.meta.contact.avatar,
87
+ title: o.meta.contact.nickname,
88
+ jumpUrl: o.meta.contact.jumpUrl,
89
+ name: o.meta.contact.tag,
90
+ }))
91
+
92
+ const contact = z.union([friend, group, bot])
93
+
94
+ const json = JSON.parse(jsonData)
95
+ const parsedData = contact.safeParse(json)
96
+ const success = parsedData.success
97
+ const parsedContent = parsedData.data!
98
+ if (!success) {
99
+ new Logger().error(parsedData.error, 'Card Parse Error')
100
+ }
101
+ </script>
102
+
103
+ <style scoped>
104
+ .contact-data {
105
+ display: flex;
106
+ align-items: center;
107
+ }
108
+ .contact-data img {
109
+ width: 45px;
110
+ height: 45px;
111
+ border-radius: 100%;
112
+ margin-right: 15px;
113
+ }
114
+ .contact-data > div {
115
+ display: flex;
116
+ flex-direction: column;
117
+ }
118
+ .contact-data p {
119
+ text-wrap: nowrap;
120
+ margin: 0;
121
+ font-weight: bold;
122
+ }
123
+ .contact-data span {
124
+ text-wrap: nowrap;
125
+ font-size: 0.8rem;
126
+ opacity: 0.7;
127
+ margin-top: 5px;
128
+ }
129
+ </style>
@@ -0,0 +1,94 @@
1
+ <template>
2
+ <div v-if="success" class="msg-json" @click="openLink(parsedContent.jumpUrl)">
3
+ <p>{{ parsedContent.title }}</p>
4
+ <span>{{ parsedContent.desc }}</span>
5
+ <img :src="parsedContent.img" alt="">
6
+ <div class="bottom-bar">
7
+ <img :src="parsedContent.icon" alt="">
8
+ <span>{{ parsedContent.name }}</span>
9
+ </div>
10
+ </div>
11
+ <span v-else class="msg-unknown">{{
12
+ '( ' + $t('加载失败') + ': ' + id + ' )'
13
+ }}</span>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { Logger } from '../../../function/base';
18
+ import { openLink } from '../../../function/utils/appUtil'
19
+ import * as z from 'zod'
20
+
21
+ const { data: jsonData, id } = defineProps<{
22
+ data: string,
23
+ id: string,
24
+ }>()
25
+
26
+ /**
27
+ * 解包 Qzone 风格的嵌套 URL 链接
28
+ * @param url 原始的完整 URL 字符串
29
+ * @returns 解码后的内部链接,如果解析失败则返回 null
30
+ */
31
+ function unpackNestedUrl(url: string): string | null {
32
+ const logger = new Logger()
33
+ try {
34
+ const urlObj = new URL(url)
35
+
36
+ // 1. 处理路径部分 (pathname)
37
+ // 示例路径: /groupphoto/inqq/detail/https%3A%2F%2F...
38
+ const pathname = urlObj.pathname
39
+
40
+ // 寻找 "detail/" 之后的部分,这是常见的 Qzone 嵌套模式
41
+ const detailMarker = 'detail/'
42
+ const markerIndex = pathname.indexOf(detailMarker)
43
+
44
+ if (markerIndex !== -1) {
45
+ // 截取 detail/ 之后的所有内容
46
+ const encodedPart = pathname.substring(
47
+ markerIndex + detailMarker.length,
48
+ )
49
+
50
+ // 进行 URL 解码
51
+ // 注意:decodeURIComponent 会处理 %3A, %2F, %26 等
52
+ return decodeURIComponent(encodedPart)
53
+ }
54
+
55
+ return null
56
+ } catch (error) {
57
+ logger.error(error as Error, 'URL 解包失败')
58
+ return null
59
+ }
60
+ }
61
+
62
+ const feed = z
63
+ .object({
64
+ app: z.literal('com.tencent.feed.lua'),
65
+ meta: z.object({
66
+ feed: z.object({
67
+ cover: z.string(),
68
+ title: z.string(),
69
+ tagIcon: z.string(),
70
+ tagName: z.string(),
71
+ forwardMessage: z.string(),
72
+ pcJumpUrl: z.string(),
73
+ }),
74
+ }),
75
+ prompt: z.string(),
76
+ })
77
+ .transform((o) => ({
78
+ title: o.meta.feed.title,
79
+ jumpUrl:
80
+ unpackNestedUrl(o.meta.feed.pcJumpUrl) ?? o.meta.feed.pcJumpUrl,
81
+ img: o.meta.feed.cover,
82
+ icon: o.meta.feed.tagIcon,
83
+ name: o.meta.feed.tagName,
84
+ desc: o.meta.feed.forwardMessage,
85
+ }))
86
+
87
+ const json = JSON.parse(jsonData)
88
+ const parsedData = feed.safeParse(json)
89
+ const success = parsedData.success
90
+ const parsedContent = parsedData.data!
91
+ if (!success) {
92
+ new Logger().error(parsedData.error, 'Card Parse Error')
93
+ }
94
+ </script>
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <div v-if="success" class="msg-json" @click="openLink(parsedContent.jumpUrl)">
3
+ <p>{{ parsedContent.title }}</p>
4
+ <span>
5
+ <font-awesome-icon icon="eye" /> {{ parsedContent.view }}
6
+ <font-awesome-icon icon="message" /> {{ parsedContent.comment }}
7
+ <font-awesome-icon icon="thumbs-up" /> {{ parsedContent.prefer }}
8
+ </span>
9
+ <img v-if="parsedContent.img" :src="parsedContent.img" alt=""
10
+ class="forum-img">
11
+ <div class="bottom-bar">
12
+ <img :src="parsedContent.icon" alt="">
13
+ <span>{{ parsedContent.name }} ({{ $t('频道') }})</span>
14
+ </div>
15
+ </div>
16
+ <span v-else class="msg-unknown">{{
17
+ '( ' + $t('加载失败') + ': ' + id + ' )'
18
+ }}</span>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { Logger } from '../../../function/base'
23
+ import { openLink } from '../../../function/utils/appUtil'
24
+ import * as z from 'zod'
25
+
26
+ const { data: jsonData, id } = defineProps<{
27
+ data: string,
28
+ id: string,
29
+ }>()
30
+
31
+ const forum = z
32
+ .object({
33
+ app: z.literal('com.tencent.forum'),
34
+ meta: z.object({
35
+ detail: z.object({
36
+ channel_info: z.object({
37
+ guild_icon: z.string(),
38
+ guild_name: z.string(),
39
+ }),
40
+ feed: z.object({
41
+ comment_count: z.number().optional(),
42
+ images: z
43
+ .array(
44
+ z.object({
45
+ pic_url: z.string(),
46
+ height: z.number(),
47
+ width: z.number(),
48
+ }),
49
+ )
50
+ .optional(),
51
+ prefer_count: z.number().optional(),
52
+ view_count: z.number().optional(),
53
+ }),
54
+ jump_url: z.string(),
55
+ }),
56
+ }),
57
+ prompt: z.string(),
58
+ })
59
+ .transform((o) => ({
60
+ title: o.prompt.replace('[频道帖子]', ''),
61
+ view: o.meta.detail.feed.view_count ?? 0,
62
+ comment: o.meta.detail.feed.comment_count ?? 0,
63
+ prefer: o.meta.detail.feed.prefer_count ?? 0,
64
+ jumpUrl: o.meta.detail.jump_url,
65
+ img: o.meta.detail.feed.images?.[0]?.pic_url,
66
+ icon: o.meta.detail.channel_info.guild_icon,
67
+ name: o.meta.detail.channel_info.guild_name,
68
+ }))
69
+ const json = JSON.parse(jsonData)
70
+ const parsedData = forum.safeParse(json)
71
+ const success = parsedData.success
72
+ const parsedContent = parsedData.data!
73
+ if (!success) {
74
+ new Logger().error(parsedData.error, 'Card Parse Error')
75
+ }
76
+ </script>
77
+
78
+ <style lang="css" scoped>
79
+ .forum-img {
80
+ max-width: 30vw;
81
+ min-width: 240px;
82
+ object-fit: cover;
83
+ }
84
+ </style>