koishi-plugin-chat-patch 3.0.2 → 5.0.1

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 (237) hide show
  1. package/client/vue/index.vue +60 -634
  2. package/client/web/dist/assets/AppIcon-DIku6-h0.png +0 -0
  3. package/client/web/dist/assets/AppIconDark-BYymN71t.png +0 -0
  4. package/client/web/dist/assets/CapacitorAppIcon-CSZwNhvi.png +0 -0
  5. package/client/web/dist/assets/CapacitorAppIconDark-BMvMOzsg.png +0 -0
  6. package/client/web/dist/assets/Chat-CTdyeaX2.css +1 -0
  7. package/client/web/dist/assets/Chat-DkZvD7d8.js +28 -0
  8. package/client/web/dist/assets/MealHungryPan-Beos3lRI.js +1 -0
  9. package/client/web/dist/assets/MsgBody-aZ4_aija.js +1 -0
  10. package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-CPZjRLp6.js +67 -0
  11. package/client/web/dist/assets/NapcatAppIcon-C7VOAkW9.png +0 -0
  12. package/client/web/dist/assets/Vue-lEhHouxB.png +0 -0
  13. package/client/web/dist/assets/append_darwin-CqBNU62J.css +1 -0
  14. package/client/web/dist/assets/append_full_vibrancy-j0D2LM9m.css +1 -0
  15. package/client/web/dist/assets/append_linux-uhxeBqeC.css +1 -0
  16. package/client/web/dist/assets/append_linux_vibrancy-COiZq8MS.css +1 -0
  17. package/client/web/dist/assets/append_napcat-DV8CAyAd.css +1 -0
  18. package/client/web/dist/assets/append_new-407Lo5_V.css +1 -0
  19. package/client/web/dist/assets/append_vibrancy-6FHctkOY.css +1 -0
  20. package/client/web/dist/assets/append_win32-srmD-78y.css +1 -0
  21. package/client/web/dist/assets/cakes-CxTVKdnK.svg +189 -0
  22. package/client/web/dist/assets/core-DhEqZVGG.js +1 -0
  23. package/client/web/dist/assets/event-Cadp6m6J.js +1 -0
  24. package/client/web/dist/assets/index-CPTXiemn.js +4045 -0
  25. package/client/web/dist/assets/index-Cg_esKB0.js +1 -0
  26. package/client/web/dist/assets/index-Cln012sP.css +1 -0
  27. package/client/web/dist/assets//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223-CJKIKBn3.ttf +0 -0
  28. package/client/web/dist/assets//346/234/237/345/276/205-C90_OY60.png +0 -0
  29. package/client/web/dist/bcui/css/color-dark.css +35 -0
  30. package/client/web/dist/bcui/css/color-light.css +35 -0
  31. package/client/web/dist/bcui/css/style.css +468 -0
  32. package/client/web/dist/bcui/js/main.js +193 -0
  33. package/client/web/dist/css/append-dark.css +13 -0
  34. package/client/web/dist/css/append-light.css +13 -0
  35. package/client/web/dist/css/append-napcat-dark.css +14 -0
  36. package/client/web/dist/css/append-napcat-light.css +14 -0
  37. package/client/web/dist/favicon.ico +0 -0
  38. package/client/web/dist/img/icons/icon-for-fuck-apple.png +0 -0
  39. package/client/web/dist/img/icons/icon-maskable.png +0 -0
  40. package/client/web/dist/img/icons/icon.png +0 -0
  41. package/client/web/dist/img/icons/icon.svg +10 -0
  42. package/client/web/dist/index.html +22 -0
  43. package/client/web/dist/robots.txt +2 -0
  44. package/client/web/dist/sw.js +58 -0
  45. package/client/web/env.d.ts +56 -0
  46. package/client/web/index.html +21 -0
  47. package/client/web/package-lock.json +3419 -0
  48. package/client/web/package.json +62 -0
  49. package/client/web/public/bcui/css/color-dark.css +35 -0
  50. package/client/web/public/bcui/css/color-light.css +35 -0
  51. package/client/web/public/bcui/css/style.css +468 -0
  52. package/client/web/public/bcui/js/main.js +193 -0
  53. package/client/web/public/css/append-dark.css +13 -0
  54. package/client/web/public/css/append-light.css +13 -0
  55. package/client/web/public/css/append-napcat-dark.css +14 -0
  56. package/client/web/public/css/append-napcat-light.css +14 -0
  57. package/client/web/public/favicon.ico +0 -0
  58. package/client/web/public/img/icons/icon-for-fuck-apple.png +0 -0
  59. package/client/web/public/img/icons/icon-maskable.png +0 -0
  60. package/client/web/public/img/icons/icon.png +0 -0
  61. package/client/web/public/img/icons/icon.svg +10 -0
  62. package/client/web/public/robots.txt +2 -0
  63. package/client/web/public/sw.js +58 -0
  64. package/client/web/scripts/copy-bcui.mjs +17 -0
  65. package/client/web/src/App.vue +1127 -0
  66. package/client/web/src/assets/css/append/append_darwin.css +68 -0
  67. package/client/web/src/assets/css/append/append_full_vibrancy.css +290 -0
  68. package/client/web/src/assets/css/append/append_linux.css +125 -0
  69. package/client/web/src/assets/css/append/append_linux_vibrancy.css +22 -0
  70. package/client/web/src/assets/css/append/append_napcat.css +35 -0
  71. package/client/web/src/assets/css/append/append_new.css +843 -0
  72. package/client/web/src/assets/css/append/append_vibrancy.css +113 -0
  73. package/client/web/src/assets/css/append/append_win32.css +109 -0
  74. package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +349 -0
  75. package/client/web/src/assets/css/append/mobile/append_mobile_vertical.css +96 -0
  76. package/client/web/src/assets/css/chat.css +2104 -0
  77. package/client/web/src/assets/css/msg.css +941 -0
  78. package/client/web/src/assets/css/options.css +811 -0
  79. package/client/web/src/assets/css/sys_notice.css +62 -0
  80. package/client/web/src/assets/css/view.css +2105 -0
  81. package/client/web/src/assets/font//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223.ttf +0 -0
  82. package/client/web/src/assets/img/Vue.png +0 -0
  83. package/client/web/src/assets/img/cakes.svg +189 -0
  84. package/client/web/src/assets/img/icons/AppIcon.png +0 -0
  85. package/client/web/src/assets/img/icons/AppIconDark.png +0 -0
  86. package/client/web/src/assets/img/icons/CapacitorAppIcon.png +0 -0
  87. package/client/web/src/assets/img/icons/CapacitorAppIconDark.png +0 -0
  88. package/client/web/src/assets/img/icons/NapcatAppIcon.png +0 -0
  89. package/client/web/src/assets/img/icons/NapcatAppIconDark.png +0 -0
  90. package/client/web/src/assets/img/icons/exit.png +0 -0
  91. package/client/web/src/assets/img/icons/pen.png +0 -0
  92. package/client/web/src/assets/img/stars.svg +89 -0
  93. package/client/web/src/assets/img//346/234/237/345/276/205.png +0 -0
  94. package/client/web/src/assets/l10n/_About I10n.md +35 -0
  95. package/client/web/src/assets/l10n/_l10nconfig.json +7 -0
  96. package/client/web/src/assets/l10n/zh-CN.po +1941 -0
  97. package/client/web/src/assets/logo.png +0 -0
  98. package/client/web/src/assets/pathMap/LLOneBot.yaml +2 -0
  99. package/client/web/src/assets/pathMap/Lagrange.OneBot.yaml +180 -0
  100. package/client/web/src/assets/pathMap/NapCat.Onebot.yaml +224 -0
  101. package/client/web/src/assets/pathMap/SnowLuma.yaml +80 -0
  102. package/client/web/src/assets/qed.txt +1 -0
  103. package/client/web/src/components/AboutPan.vue +197 -0
  104. package/client/web/src/components/BulletinBody.vue +129 -0
  105. package/client/web/src/components/DepPan.vue +310 -0
  106. package/client/web/src/components/EmojiFace.vue +32 -0
  107. package/client/web/src/components/FacePan.vue +461 -0
  108. package/client/web/src/components/FileBody.vue +129 -0
  109. package/client/web/src/components/FileManager.vue +532 -0
  110. package/client/web/src/components/FriendBody.vue +54 -0
  111. package/client/web/src/components/GlobalSessionSearchBar.vue +301 -0
  112. package/client/web/src/components/History.vue +49 -0
  113. package/client/web/src/components/LazyLottie.vue +82 -0
  114. package/client/web/src/components/MergePan.vue +127 -0
  115. package/client/web/src/components/MsgBody.vue +1478 -0
  116. package/client/web/src/components/MusicPlayer.vue +549 -0
  117. package/client/web/src/components/NoticeBody.vue +168 -0
  118. package/client/web/src/components/QzonePermissionPan.vue +150 -0
  119. package/client/web/src/components/RawMsgRenderPreviewPan.vue +190 -0
  120. package/client/web/src/components/ThemeColorPickerPan.vue +420 -0
  121. package/client/web/src/components/TinySessionBody.vue +51 -0
  122. package/client/web/src/components/UmamiInfoPan.vue +1549 -0
  123. package/client/web/src/components/UpdatePan.vue +263 -0
  124. package/client/web/src/components/ViewerCom.vue +1360 -0
  125. package/client/web/src/components/VoiceMsg.vue +626 -0
  126. package/client/web/src/components/WelPan.vue +894 -0
  127. package/client/web/src/components/msg-body/.gitkeep +0 -0
  128. package/client/web/src/components/msg-component/JsonSegComp.vue +138 -0
  129. package/client/web/src/components/msg-component/XmlSegComp.vue +132 -0
  130. package/client/web/src/components/msg-component/jsonComp/AutoReply.vue +97 -0
  131. package/client/web/src/components/msg-component/jsonComp/Contact.lua.vue +129 -0
  132. package/client/web/src/components/msg-component/jsonComp/Feed.lua.vue +94 -0
  133. package/client/web/src/components/msg-component/jsonComp/Forum.vue +84 -0
  134. package/client/web/src/components/msg-component/jsonComp/Mannounce.vue +111 -0
  135. package/client/web/src/components/msg-component/jsonComp/Map.vue +52 -0
  136. package/client/web/src/components/msg-component/jsonComp/Miniapp.lua.vue +53 -0
  137. package/client/web/src/components/msg-component/jsonComp/Miniapp.vue +69 -0
  138. package/client/web/src/components/msg-component/jsonComp/Music.lua.vue +135 -0
  139. package/client/web/src/components/msg-component/jsonComp/Tuwen.lua.vue +56 -0
  140. package/client/web/src/components/notice-component/.gitkeep +0 -0
  141. package/client/web/src/components/notice-component/MealHungryPan.vue +119 -0
  142. package/client/web/src/components/tooltip/CustomFaceTooltip.vue +22 -0
  143. package/client/web/src/components/tooltip/Tooltip.vue +50 -0
  144. package/client/web/src/components/tooltip/Tooltips.vue +14 -0
  145. package/client/web/src/components/tooltip/UserInfoTooltip.vue +69 -0
  146. package/client/web/src/function/base.ts +238 -0
  147. package/client/web/src/function/connect.ts +2042 -0
  148. package/client/web/src/function/debug.ts +10 -0
  149. package/client/web/src/function/elements/information.ts +141 -0
  150. package/client/web/src/function/elements/system.ts +101 -0
  151. package/client/web/src/function/elements/vueComp.ts +62 -0
  152. package/client/web/src/function/favicon.ts +76 -0
  153. package/client/web/src/function/input.ts +69 -0
  154. package/client/web/src/function/model/emoji.ts +127 -0
  155. package/client/web/src/function/model/img.ts +150 -0
  156. package/client/web/src/function/model/msg-body.ts +51 -0
  157. package/client/web/src/function/msg.ts +2674 -0
  158. package/client/web/src/function/notify.ts +225 -0
  159. package/client/web/src/function/option.ts +749 -0
  160. package/client/web/src/function/satori-model.ts +818 -0
  161. package/client/web/src/function/satori.ts +325 -0
  162. package/client/web/src/function/sender.ts +178 -0
  163. package/client/web/src/function/tooltip.ts +49 -0
  164. package/client/web/src/function/utils/appUtil.ts +2288 -0
  165. package/client/web/src/function/utils/avatarUtil.ts +31 -0
  166. package/client/web/src/function/utils/backgroundUtil.ts +110 -0
  167. package/client/web/src/function/utils/linkViewUtil.ts +101 -0
  168. package/client/web/src/function/utils/localHistoryUtil.ts +517 -0
  169. package/client/web/src/function/utils/msgUtil.ts +1242 -0
  170. package/client/web/src/function/utils/pinyin.ts +141 -0
  171. package/client/web/src/function/utils/recordUtil.ts +187 -0
  172. package/client/web/src/function/utils/sessionUtil.ts +114 -0
  173. package/client/web/src/function/utils/systemUtil.ts +664 -0
  174. package/client/web/src/main.ts +91 -0
  175. package/client/web/src/pages/Chat.vue +3453 -0
  176. package/client/web/src/pages/Friends.vue +636 -0
  177. package/client/web/src/pages/Info.vue +717 -0
  178. package/client/web/src/pages/Messages.vue +875 -0
  179. package/client/web/src/pages/Options.vue +82 -0
  180. package/client/web/src/pages/Qzone.vue +1178 -0
  181. package/client/web/src/pages/chat-view/ChatDanmaku.vue +759 -0
  182. package/client/web/src/pages/chat-view/ChatGlagame.vue +642 -0
  183. package/client/web/src/pages/chat-view/ChatTerminal.vue +1044 -0
  184. package/client/web/src/pages/chat-view/SystemNotice.vue +171 -0
  185. package/client/web/src/pages/options/OptAccount.vue +107 -0
  186. package/client/web/src/pages/options/OptAddon.vue +185 -0
  187. package/client/web/src/pages/options/OptDev.vue +704 -0
  188. package/client/web/src/pages/options/OptFunction.vue +403 -0
  189. package/client/web/src/pages/options/OptInfo.vue +165 -0
  190. package/client/web/src/pages/options/OptView.vue +846 -0
  191. package/client/web/src/runtime/backend.ts +310 -0
  192. package/client/web/src/state/auth.ts +14 -0
  193. package/client/web/src/state/chat.ts +36 -0
  194. package/client/web/src/state/connection.ts +20 -0
  195. package/client/web/src/state/contact.ts +37 -0
  196. package/client/web/src/state/qzone.ts +18 -0
  197. package/client/web/src/state/sessionHistory.ts +25 -0
  198. package/client/web/src/state/settings.ts +18 -0
  199. package/client/web/src/state/sticker.ts +10 -0
  200. package/client/web/src/state/ui.ts +56 -0
  201. package/client/web/tsconfig.json +27 -0
  202. package/client/web/tsconfig.tsbuildinfo +1 -0
  203. package/client/web/vite.config.ts +33 -0
  204. package/dist/index.js +1 -12
  205. package/dist/style.css +1 -1
  206. package/lib/index.js +2070 -2077
  207. package/package.json +15 -2
  208. package/readme.md +0 -22
  209. package/src/bootstrap.ts +71 -0
  210. package/src/cache.ts +319 -0
  211. package/src/config.ts +23 -35
  212. package/src/database.ts +650 -0
  213. package/src/index.ts +27 -59
  214. package/src/media.ts +302 -0
  215. package/src/recorder.ts +175 -0
  216. package/src/self-message.ts +456 -0
  217. package/src/server.d.ts +26 -0
  218. package/src/types.ts +108 -48
  219. package/src/web.ts +712 -0
  220. package/client/vue/chat-logic.ts +0 -683
  221. package/client/vue/composables/useChatActions.ts +0 -61
  222. package/client/vue/composables/useChatData.ts +0 -185
  223. package/client/vue/composables/useImageCache.ts +0 -281
  224. package/client/vue/composables/useVideoCache.ts +0 -125
  225. package/client/vue/types.ts +0 -72
  226. package/lib/api-handlers.d.ts +0 -25
  227. package/lib/config.d.ts +0 -14
  228. package/lib/file-manager.d.ts +0 -124
  229. package/lib/index.d.ts +0 -17
  230. package/lib/logger.d.ts +0 -9
  231. package/lib/message-handler.d.ts +0 -35
  232. package/lib/types.d.ts +0 -57
  233. package/lib/utils.d.ts +0 -18
  234. package/src/api-handlers.ts +0 -646
  235. package/src/file-manager.ts +0 -1436
  236. package/src/message-handler.ts +0 -507
  237. package/src/utils.ts +0 -189
@@ -0,0 +1,664 @@
1
+ import { i18n } from '../../main'
2
+
3
+ import l10nConfig from '../../assets/l10n/_l10nconfig.json'
4
+ import PO from 'pofile'
5
+ import { Logger, PopInfo, PopType } from '../base';
6
+ import { backend } from '../../runtime/backend';
7
+
8
+ /**
9
+ * 异步延迟
10
+ * @param ms 延迟时间
11
+ * @returns Promise<void>
12
+ */
13
+ export function delay(ms: number): Promise<void> {
14
+ return new Promise(resolve => setTimeout(resolve, ms));
15
+ }
16
+
17
+ /**
18
+ * 区分安卓、iOS、MacOS 和其他
19
+ */
20
+ export function getDeviceType() {
21
+ const userAgent = navigator.userAgent
22
+ if (userAgent.indexOf('Android') > -1 || userAgent.indexOf('Adr') > -1) {
23
+ return 'Android'
24
+ // eslint-disable-next-line
25
+ } else if (!!userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {
26
+ return 'iOS'
27
+ } else if (userAgent.indexOf('Mac OS X') > -1) {
28
+ return 'MacOS'
29
+ } else {
30
+ return 'Other'
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 获取当前启用的语言的地区代码
36
+ * @returns 符合规范的地区代码
37
+ */
38
+ export function getTrueLang(): string {
39
+ let back = 'zh-CN'
40
+ const locale = i18n.global.locale
41
+ const currentLocale = typeof locale === 'string' ? locale : locale.value
42
+ l10nConfig.forEach((item) => {
43
+ if (item.value === currentLocale) {
44
+ back = item.lang
45
+ }
46
+ })
47
+ return back
48
+ }
49
+
50
+ /**
51
+ * 读取 PO 语言文件,转换为 JSON 格式
52
+ * @param name 文件名
53
+ */
54
+ export function getPortableFileLang(name: string) {
55
+ const files = import.meta.glob('../../assets/l10n/*.po',
56
+ { eager: true, query: '?raw', import: 'default' })
57
+ const filePath = Object.keys(files).find(
58
+ (item) => item.includes(name))
59
+ const final = {} as { [key: string]: string }
60
+ if(filePath) {
61
+ const file = files[filePath] as string
62
+ const items = PO.parse(file).items
63
+ for(const item of items) {
64
+ final[item.msgid] = item.msgstr[0] == '' ? item.msgid : item.msgstr[0]
65
+ }
66
+ }
67
+ return final
68
+ }
69
+
70
+ /**
71
+ * 将被 HTML 编码的符号转回来
72
+ * @param str 待处理的字符串
73
+ * @returns 处理完成的字符串
74
+ */
75
+ export function escape2Html(str: string): string {
76
+ let s = ''
77
+ if (str.length === 0) return ''
78
+ s = str.replace(/&amp;/g, '&')
79
+ s = s.replace(/&lt;/g, '<')
80
+ s = s.replace(/&gt;/g, '>')
81
+ s = s.replace(/&nbsp;/g, ' ')
82
+ s = s.replace(/&#39;/g, '\'')
83
+ s = s.replace(/&quot;/g, '"')
84
+ return s
85
+ }
86
+
87
+ /**
88
+ * 将 gitmoji 字符串转为 emoji 符号
89
+ * @param name 名称
90
+ * @returns emoji 符号
91
+ */
92
+ export function gitmojiToEmoji(name: string) {
93
+ return {
94
+ ':art:': '🎨',
95
+ ':zap:': '⚡️',
96
+ ':fire:': '🔥',
97
+ ':bug:': '🐛',
98
+ ':ambulance:': '🚑️',
99
+ ':sparkles:': '✨',
100
+ ':memo:': '📝',
101
+ ':rocket:': '🚀',
102
+ ':lipstick:': '💄',
103
+ ':tada:': '🎉',
104
+ ':white_check_mark:': '✅',
105
+ ':lock:': '🔒️',
106
+ ':closed_lock_with_key:': '🔐',
107
+ ':bookmark:': '🔖',
108
+ ':rotating_light:': '🚨',
109
+ ':construction:': '🚧',
110
+ ':green_heart:': '💚',
111
+ ':arrow_down:': '⬇️',
112
+ ':arrow_up:': '⬆️',
113
+ ':pushpin:': '📌',
114
+ ':construction_worker:': '👷',
115
+ ':chart_with_upwards_trend:': '📈',
116
+ ':recycle:': '♻️',
117
+ ':heavy_plus_sign:': '➕',
118
+ ':heavy_minus_sign:': '➖',
119
+ ':wrench:': '🔧',
120
+ ':hammer:': '🔨',
121
+ ':globe_with_meridians:': '🌐',
122
+ ':pencil2:': '✏️',
123
+ ':poop:': '💩',
124
+ ':rewind:': '⏪️',
125
+ ':twisted_rightwards_arrows:': '🔀',
126
+ ':package:': '📦️',
127
+ ':alien:': '👽️',
128
+ ':truck:': '🚚',
129
+ ':page_facing_up:': '📄',
130
+ ':boom:': '💥',
131
+ ':bento:': '🍱',
132
+ ':wheelchair:': '♿️',
133
+ ':bulb:': '💡',
134
+ ':beers:': '🍻',
135
+ ':speech_balloon:': '💬',
136
+ ':card_file_box:': '🗃️',
137
+ ':loud_sound:': '🔊',
138
+ ':mute:': '🔇',
139
+ ':busts_in_silhouette:': '👥',
140
+ ':children_crossing:': '🚸',
141
+ ':building_construction:': '🏗️',
142
+ ':iphone:': '📱',
143
+ ':clown_face:': '🤡',
144
+ ':egg:': '🥚',
145
+ ':see_no_evil:': '🙈',
146
+ ':camera_flash:': '📸',
147
+ ':alembic:': '⚗️',
148
+ ':mag:': '🔍️',
149
+ ':label:': '🏷️',
150
+ ':seedling:': '🌱',
151
+ ':triangular_flag_on_post:': '🚩',
152
+ ':goal_net:': '🥅',
153
+ ':dizzy:': '💫',
154
+ ':wastebasket:': '🗑️',
155
+ ':passport_control:': '🛂',
156
+ ':adhesive_bandage:': '🩹',
157
+ ':monocle_face:': '🧐',
158
+ ':coffin:': '⚰️',
159
+ ':test_tube:': '🧪',
160
+ ':necktie:': '👔',
161
+ ':stethoscope:': '🩺',
162
+ ':bricks:': '🧱',
163
+ ':technologist:': '🧑‍💻',
164
+ ':money_with_wings:': '💸',
165
+ ':thread:': '🧵',
166
+ ':safety_vest:': '🦺',
167
+ }[name]
168
+ }
169
+
170
+ /**
171
+ * RGB 颜色值转换为 HSL.
172
+ * 转换公式参考自 http://en.wikipedia.org/wiki/HSL_color_space.
173
+ * r, g, 和 b 需要在 [0, 255] 范围内
174
+ * 返回的 h, s, 和 l 在 [0, 1] 之间
175
+ *
176
+ * @param r 红色色值
177
+ * @param g 绿色色值
178
+ * @param b 蓝色色值
179
+ * @return HSL各值数组
180
+ */
181
+ export function rgbToHsl(r: number, g: number, b: number) {
182
+ (r /= 255), (g /= 255), (b /= 255)
183
+ const max = Math.max(r, g, b),
184
+ min = Math.min(r, g, b)
185
+ let h = 0,
186
+ s
187
+ const l = (max + min) / 2
188
+
189
+ if (max == min) {
190
+ h = s = 0
191
+ } else {
192
+ const d = max - min
193
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
194
+ switch (max) {
195
+ case r:
196
+ h = (g - b) / d + (g < b ? 6 : 0)
197
+ break
198
+ case g:
199
+ h = (b - r) / d + 2
200
+ break
201
+ case b:
202
+ h = (r - g) / d + 4
203
+ break
204
+ }
205
+ h /= 6
206
+ }
207
+
208
+ return [h, s, l]
209
+ }
210
+
211
+ /**
212
+ * HSL颜色值转换为RGB.
213
+ * 换算公式改编自 http://en.wikipedia.org/wiki/HSL_color_space.
214
+ * h, s, 和 l 设定在 [0, 1] 之间
215
+ * 返回的 r, g, 和 b 在 [0, 255]之间
216
+ *
217
+ * @param h 色相
218
+ * @param s 饱和度
219
+ * @param l 亮度
220
+ * @return RGB色值数值
221
+ */
222
+ export function hslToRgb(h: number, s: number, l: number) {
223
+ let r, g, b
224
+
225
+ if (s == 0) {
226
+ r = g = b = l
227
+ } else {
228
+ const hue2rgb = function hue2rgb(p: number, q: number, t: number) {
229
+ if (t < 0) t += 1
230
+ if (t > 1) t -= 1
231
+ if (t < 1 / 6) return p + (q - p) * 6 * t
232
+ if (t < 1 / 2) return q
233
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
234
+ return p
235
+ }
236
+
237
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s
238
+ const p = 2 * l - q
239
+ r = hue2rgb(p, q, h + 1 / 3)
240
+ g = hue2rgb(p, q, h)
241
+ b = hue2rgb(p, q, h - 1 / 3)
242
+ }
243
+
244
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]
245
+ }
246
+
247
+ export type ForegroundTone = 'dark' | 'light'
248
+
249
+ /**
250
+ * 根据图片 URL 计算前景色建议(深色或浅色)
251
+ * 说明:返回 dark 表示背景偏亮,前景建议用深色;返回 light 表示背景偏暗,前景建议用浅色
252
+ * @param url 图片地址
253
+ * @param threshold 亮度阈值,范围 [0, 1],默认 0.55
254
+ */
255
+ export async function getForegroundToneFromImageUrl(
256
+ url: string,
257
+ threshold = 0.55,
258
+ ): Promise<ForegroundTone> {
259
+ const img = await loadImageElement(url)
260
+ const size = 32
261
+ const canvas = document.createElement('canvas')
262
+ canvas.width = size
263
+ canvas.height = size
264
+ const ctx = canvas.getContext('2d', { willReadFrequently: true })
265
+ if (!ctx) {
266
+ return 'light'
267
+ }
268
+
269
+ try {
270
+ ctx.drawImage(img, 0, 0, size, size)
271
+ const { data } = ctx.getImageData(0, 0, size, size)
272
+ let luminanceSum = 0
273
+ let pixelCount = 0
274
+
275
+ for (let i = 0; i < data.length; i += 4) {
276
+ const alpha = data[i + 3] / 255
277
+ if (alpha < 0.1) continue
278
+
279
+ const r = srgbToLinear(data[i] / 255)
280
+ const g = srgbToLinear(data[i + 1] / 255)
281
+ const b = srgbToLinear(data[i + 2] / 255)
282
+
283
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
284
+ luminanceSum += luminance
285
+ pixelCount += 1
286
+ }
287
+
288
+ if (pixelCount === 0) {
289
+ return 'light'
290
+ }
291
+
292
+ const avgLuminance = luminanceSum / pixelCount
293
+ return avgLuminance >= normalizeThreshold(threshold) ? 'dark' : 'light'
294
+ } catch (error) {
295
+ new Logger().error(error as Error, '读取图片像素失败,使用默认浅色前景')
296
+ return 'light'
297
+ }
298
+ }
299
+
300
+ /**
301
+ * 将图片划分为 3x3 并返回每一块的前景色建议
302
+ * @param url 图片地址
303
+ * @param threshold 亮度阈值,范围 [0, 1],默认 0.55
304
+ */
305
+ export async function getForegroundToneGridFromImageUrl(
306
+ url: string,
307
+ threshold = 0.55,
308
+ ): Promise<ForegroundTone[][]> {
309
+ const img = await loadImageElement(url)
310
+ const size = 90
311
+ const grid = 3
312
+ const cellSize = size / grid
313
+ const safeThreshold = normalizeThreshold(threshold)
314
+ const canvas = document.createElement('canvas')
315
+ canvas.width = size
316
+ canvas.height = size
317
+ const ctx = canvas.getContext('2d', { willReadFrequently: true })
318
+ if (!ctx) {
319
+ return buildFallbackGrid(grid)
320
+ }
321
+
322
+ try {
323
+ ctx.drawImage(img, 0, 0, size, size)
324
+ const { data } = ctx.getImageData(0, 0, size, size)
325
+ const result: ForegroundTone[][] = []
326
+
327
+ for (let row = 0; row < grid; row++) {
328
+ result.push([])
329
+ for (let col = 0; col < grid; col++) {
330
+ const startX = Math.floor(col * cellSize)
331
+ const endX = Math.floor((col + 1) * cellSize)
332
+ const startY = Math.floor(row * cellSize)
333
+ const endY = Math.floor((row + 1) * cellSize)
334
+
335
+ let luminanceSum = 0
336
+ let pixelCount = 0
337
+
338
+ for (let y = startY; y < endY; y++) {
339
+ for (let x = startX; x < endX; x++) {
340
+ const index = (y * size + x) * 4
341
+ const alpha = data[index + 3] / 255
342
+ if (alpha < 0.1) continue
343
+
344
+ const r = srgbToLinear(data[index] / 255)
345
+ const g = srgbToLinear(data[index + 1] / 255)
346
+ const b = srgbToLinear(data[index + 2] / 255)
347
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
348
+
349
+ luminanceSum += luminance
350
+ pixelCount += 1
351
+ }
352
+ }
353
+
354
+ const avgLuminance = pixelCount > 0 ? luminanceSum / pixelCount : 0
355
+ result[row].push(avgLuminance >= safeThreshold ? 'dark' : 'light')
356
+ }
357
+ }
358
+
359
+ return result
360
+ } catch (error) {
361
+ new Logger().error(error as Error, '读取图片分块像素失败,使用默认浅色前景')
362
+ return buildFallbackGrid(grid)
363
+ }
364
+ }
365
+
366
+ function loadImageElement(url: string): Promise<HTMLImageElement> {
367
+ return new Promise((resolve, reject) => {
368
+ const image = new Image()
369
+ image.crossOrigin = 'anonymous'
370
+ image.decoding = 'async'
371
+ image.onload = () => resolve(image)
372
+ image.onerror = () => reject(new Error(`图片加载失败: ${url}`))
373
+ image.src = url
374
+ })
375
+ }
376
+
377
+ function srgbToLinear(value: number): number {
378
+ if (value <= 0.04045) {
379
+ return value / 12.92
380
+ }
381
+ return Math.pow((value + 0.055) / 1.055, 2.4)
382
+ }
383
+
384
+ function normalizeThreshold(value: number): number {
385
+ if (Number.isNaN(value)) return 0.55
386
+ if (value < 0) return 0
387
+ if (value > 1) return 1
388
+ return value
389
+ }
390
+
391
+ function buildFallbackGrid(grid: number): ForegroundTone[][] {
392
+ const result: ForegroundTone[][] = []
393
+ for (let row = 0; row < grid; row++) {
394
+ result.push([])
395
+ for (let col = 0; col < grid; col++) {
396
+ result[row].push('light')
397
+ }
398
+ }
399
+ return result
400
+ }
401
+
402
+ /**
403
+ * 将字节大小转为可读的文件大小
404
+ * @param size 字节大小
405
+ * @returns
406
+ */
407
+ export function getSizeFromBytes(size: number): string {
408
+ if (!size) {
409
+ return ''
410
+ }
411
+
412
+ const num = 1024.0
413
+
414
+ if (size < num) {
415
+ return size + 'B'
416
+ }
417
+ if (size < Math.pow(num, 2)) {
418
+ return (size / num).toFixed(2) + 'K'
419
+ }
420
+ if (size < Math.pow(num, 3)) {
421
+ return (size / Math.pow(num, 2)).toFixed(2) + 'M'
422
+ }
423
+ if (size < Math.pow(num, 4)) {
424
+ return (size / Math.pow(num, 3)).toFixed(2) + 'G'
425
+ }
426
+ return (size / Math.pow(num, 4)).toFixed(2) + 'T'
427
+ }
428
+
429
+ /**
430
+ * 根据区间和位数生成指定长度的随机数
431
+ * @param num 是否包含数字
432
+ * @param maxA 是否包含大写字母
433
+ * @param minlA 是否包含小写字母
434
+ * @param fqy 生成的随机数的位数
435
+ * @returns 生成的随机数字符串
436
+ */
437
+ export function getRandom(
438
+ num: boolean,
439
+ maxA: boolean,
440
+ minlA: boolean,
441
+ fqy: number,
442
+ ): string {
443
+ const arr = [] as number[]
444
+ const arr1 = [] as number[]
445
+ const arr2 = [] as number[]
446
+ if (num) {
447
+ for (let m = 0; m <= 9; m++) {
448
+ arr.push(m)
449
+ }
450
+ }
451
+ if (maxA) {
452
+ for (let m = 65; m <= 90; m++) {
453
+ arr1.push(m)
454
+ }
455
+ }
456
+ if (minlA) {
457
+ for (let m = 97; m <= 122; m++) {
458
+ arr2.push(m)
459
+ }
460
+ }
461
+ const mergeArr = arr.concat(arr1)
462
+ const mergeArr1 = mergeArr.concat(arr2)
463
+ const _length = mergeArr1.length
464
+ let text = ''
465
+ for (let m = 0; m < fqy; m++) {
466
+ let text1 = ''
467
+ let max = 0
468
+ let min = _length
469
+ if (_length > 0) {
470
+ max = _length
471
+ min = 0
472
+ }
473
+ const random = parseInt((Math.random() * (max - min)).toString()) + min
474
+ if (mergeArr1[random] <= 9) {
475
+ text1 = mergeArr1[random].toString()
476
+ } else if (mergeArr1[random] > 9) {
477
+ text1 = String.fromCharCode(mergeArr1[random])
478
+ }
479
+ text += text1
480
+ }
481
+ return text
482
+ }
483
+
484
+ /**
485
+ * 根据区间生成一个随机数
486
+ * @param minNum 最小值
487
+ * @param maxNum 最大值
488
+ * @returns 随机数
489
+ */
490
+ export function randomNum(minNum: number, maxNum: number) {
491
+ switch (arguments.length) {
492
+ case 1:
493
+ return parseInt((Math.random() * minNum + 1).toString(), 10)
494
+ case 2:
495
+ return parseInt(
496
+ (Math.random() * (maxNum - minNum + 1) + minNum).toString(),
497
+ 10,
498
+ )
499
+ default:
500
+ return 0
501
+ }
502
+ }
503
+
504
+ /**
505
+ * 从参数列表中随机选择一个元素
506
+ * @param args 参数列表
507
+ * @returns 随机选择的元素
508
+ */
509
+ export function randomChoice<T>(...args: T[]): T{
510
+ const id = randomNum(0, args.length - 1)
511
+ return args[id]
512
+ }
513
+
514
+ /**
515
+ * 将秒级或毫秒级 Unix 时间戳统一为毫秒。
516
+ * 已是毫秒的输入会原样返回,因此允许在显示边界重复规范化。
517
+ * @param time 秒级或毫秒级 Unix 时间戳
518
+ * @returns 毫秒级 Unix 时间戳
519
+ */
520
+ export function getViewTime(time: number) {
521
+ if (time.toString().length === 10) {
522
+ return time * 1000
523
+ } else {
524
+ return time
525
+ }
526
+ }
527
+
528
+ /**
529
+ * 格式化会话列表中的最新消息时间。输入可为秒或毫秒,内部仅规范化一次;
530
+ * getViewTime 对已经由消息处理流程保存的毫秒值保持不变。
531
+ * 今天显示时间,本周显示星期和时间,更早的消息显示日期。
532
+ */
533
+ export function formatSessionTime(time: number | undefined, now = new Date()) {
534
+ if (time === undefined) return ''
535
+
536
+ const viewTime = getViewTime(Number(time))
537
+ const date = new Date(viewTime)
538
+ if (!Number.isFinite(viewTime) || Number.isNaN(date.getTime())) return ''
539
+
540
+ const startOfToday = new Date(now)
541
+ startOfToday.setHours(0, 0, 0, 0)
542
+ const startOfTomorrow = new Date(startOfToday)
543
+ startOfTomorrow.setDate(startOfTomorrow.getDate() + 1)
544
+
545
+ const startOfWeek = new Date(startOfToday)
546
+ const daysSinceMonday = (startOfWeek.getDay() + 6) % 7
547
+ startOfWeek.setDate(startOfWeek.getDate() - daysSinceMonday)
548
+ const startOfNextWeek = new Date(startOfWeek)
549
+ startOfNextWeek.setDate(startOfNextWeek.getDate() + 7)
550
+
551
+ let config: Intl.DateTimeFormatOptions
552
+ if (date >= startOfToday && date < startOfTomorrow) {
553
+ config = {
554
+ hour: 'numeric',
555
+ minute: 'numeric',
556
+ }
557
+ } else if (date >= startOfWeek && date < startOfNextWeek) {
558
+ config = {
559
+ weekday: 'short',
560
+ hour: 'numeric',
561
+ minute: 'numeric',
562
+ }
563
+ } else {
564
+ config = {
565
+ month: 'short',
566
+ day: 'numeric',
567
+ }
568
+ if (date.getFullYear() !== now.getFullYear()) {
569
+ config.year = 'numeric'
570
+ }
571
+ }
572
+
573
+ return Intl.DateTimeFormat(getTrueLang(), config).format(date)
574
+ }
575
+
576
+ /**
577
+ * 获取时间的配置
578
+ * @param date
579
+ * @returns
580
+ */
581
+ export function getTimeConfig(date: Date) {
582
+ const base = {
583
+ hour: 'numeric',
584
+ minute: 'numeric',
585
+ second: 'numeric',
586
+ } as Intl.DateTimeFormatOptions
587
+ const nowDate = new Date()
588
+ const todayDate = new Date().setHours(0, 0, 0, 0)
589
+ const paramsDate = date.setHours(0, 0, 0, 0)
590
+ if (todayDate != paramsDate) {
591
+ if (
592
+ nowDate.getFullYear() == date.getFullYear() &&
593
+ nowDate.getMonth() == date.getMonth()
594
+ ) {
595
+ base.weekday = 'short'
596
+ } else if (nowDate.getFullYear() == date.getFullYear()) {
597
+ base.day = 'numeric'
598
+ base.month = 'short'
599
+ } else {
600
+ base.day = 'numeric'
601
+ base.month = 'short'
602
+ base.year = 'numeric'
603
+ }
604
+ }
605
+ return base
606
+ }
607
+
608
+ /**
609
+ * 获得一英尺的像素点数
610
+ */
611
+ export function getInch(): number {
612
+ const div = document.createElement('div')
613
+ div.style.width = '1in'
614
+ div.style.visibility = 'hidden'
615
+ document.body.appendChild(div)
616
+ const dpi = div.offsetWidth
617
+ document.body.removeChild(div)
618
+ return dpi
619
+ }
620
+
621
+ /**
622
+ * 请求 API,暂时未支持 method: string, data: any
623
+ * @param url 请求的地址
624
+ */
625
+ export async function getApi(url: string) {
626
+ // 先尝试在前端请求
627
+ try {
628
+ const response = await fetch(url)
629
+ if(response.ok) {
630
+ const data = await response.json()
631
+ return data
632
+ }
633
+ } catch (error) {
634
+ new Logger().error(error as Error, '前端请求 API 失败,尝试后端请求……')
635
+ if(!backend.isWeb()) {
636
+ return await backend.call('Onebot', 'sys:getApi', true, url)
637
+ } else {
638
+ return null
639
+ }
640
+ }
641
+ }
642
+
643
+ /**
644
+ * 复制内容到剪贴板
645
+ * @param text
646
+ */
647
+ export async function copyToClipboard(text: string)
648
+ /**
649
+ * 复制内容到剪贴板
650
+ * @param content
651
+ */
652
+ export async function copyToClipboard(content: ClipboardItem[])
653
+ export async function copyToClipboard(content: ClipboardItem[] | string) {
654
+ if (window.navigator.clipboard === undefined) {
655
+ new PopInfo().add(PopType.ERR, i18n.global.t('当前环境不支持剪贴板操作'))
656
+ throw new Error('当前环境不支持剪贴板操作')
657
+ }
658
+ if (typeof content === 'string')
659
+ await window.navigator.clipboard.writeText(content)
660
+ else
661
+ await window.navigator.clipboard.write(content)
662
+ }
663
+
664
+