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.
- package/client/vue/index.vue +60 -634
- package/client/web/env.d.ts +56 -0
- package/client/web/index.html +21 -0
- package/client/web/package-lock.json +3419 -0
- package/client/web/package.json +62 -0
- package/client/web/public/bcui/css/color-dark.css +35 -0
- package/client/web/public/bcui/css/color-light.css +35 -0
- package/client/web/public/bcui/css/style.css +468 -0
- package/client/web/public/bcui/js/main.js +193 -0
- package/client/web/public/css/append-dark.css +13 -0
- package/client/web/public/css/append-light.css +13 -0
- package/client/web/public/css/append-napcat-dark.css +14 -0
- package/client/web/public/css/append-napcat-light.css +14 -0
- package/client/web/public/favicon.ico +0 -0
- package/client/web/public/img/icons/icon-for-fuck-apple.png +0 -0
- package/client/web/public/img/icons/icon-maskable.png +0 -0
- package/client/web/public/img/icons/icon.png +0 -0
- package/client/web/public/img/icons/icon.svg +10 -0
- package/client/web/public/robots.txt +2 -0
- package/client/web/public/sw.js +58 -0
- package/client/web/scripts/copy-bcui.mjs +17 -0
- package/client/web/src/App.vue +1127 -0
- package/client/web/src/assets/css/append/append_darwin.css +68 -0
- package/client/web/src/assets/css/append/append_full_vibrancy.css +290 -0
- package/client/web/src/assets/css/append/append_linux.css +125 -0
- package/client/web/src/assets/css/append/append_linux_vibrancy.css +22 -0
- package/client/web/src/assets/css/append/append_napcat.css +35 -0
- package/client/web/src/assets/css/append/append_new.css +843 -0
- package/client/web/src/assets/css/append/append_vibrancy.css +113 -0
- package/client/web/src/assets/css/append/append_win32.css +109 -0
- package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +349 -0
- package/client/web/src/assets/css/append/mobile/append_mobile_vertical.css +96 -0
- package/client/web/src/assets/css/chat.css +2104 -0
- package/client/web/src/assets/css/msg.css +941 -0
- package/client/web/src/assets/css/options.css +811 -0
- package/client/web/src/assets/css/sys_notice.css +62 -0
- package/client/web/src/assets/css/view.css +2105 -0
- package/client/web/src/assets/font//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223.ttf +0 -0
- package/client/web/src/assets/img/Vue.png +0 -0
- package/client/web/src/assets/img/cakes.svg +189 -0
- package/client/web/src/assets/img/icons/AppIcon.png +0 -0
- package/client/web/src/assets/img/icons/AppIconDark.png +0 -0
- package/client/web/src/assets/img/icons/CapacitorAppIcon.png +0 -0
- package/client/web/src/assets/img/icons/CapacitorAppIconDark.png +0 -0
- package/client/web/src/assets/img/icons/NapcatAppIcon.png +0 -0
- package/client/web/src/assets/img/icons/NapcatAppIconDark.png +0 -0
- package/client/web/src/assets/img/icons/exit.png +0 -0
- package/client/web/src/assets/img/icons/pen.png +0 -0
- package/client/web/src/assets/img/stars.svg +89 -0
- package/client/web/src/assets/img//346/234/237/345/276/205.png +0 -0
- package/client/web/src/assets/l10n/_About I10n.md +35 -0
- package/client/web/src/assets/l10n/_l10nconfig.json +7 -0
- package/client/web/src/assets/l10n/zh-CN.po +1941 -0
- package/client/web/src/assets/logo.png +0 -0
- package/client/web/src/assets/pathMap/LLOneBot.yaml +2 -0
- package/client/web/src/assets/pathMap/Lagrange.OneBot.yaml +180 -0
- package/client/web/src/assets/pathMap/NapCat.Onebot.yaml +224 -0
- package/client/web/src/assets/pathMap/SnowLuma.yaml +80 -0
- package/client/web/src/assets/qed.txt +1 -0
- package/client/web/src/components/AboutPan.vue +197 -0
- package/client/web/src/components/BulletinBody.vue +129 -0
- package/client/web/src/components/DepPan.vue +310 -0
- package/client/web/src/components/EmojiFace.vue +32 -0
- package/client/web/src/components/FacePan.vue +461 -0
- package/client/web/src/components/FileBody.vue +129 -0
- package/client/web/src/components/FileManager.vue +532 -0
- package/client/web/src/components/FriendBody.vue +54 -0
- package/client/web/src/components/GlobalSessionSearchBar.vue +301 -0
- package/client/web/src/components/History.vue +49 -0
- package/client/web/src/components/LazyLottie.vue +82 -0
- package/client/web/src/components/MergePan.vue +127 -0
- package/client/web/src/components/MsgBody.vue +1478 -0
- package/client/web/src/components/MusicPlayer.vue +549 -0
- package/client/web/src/components/NoticeBody.vue +168 -0
- package/client/web/src/components/QzonePermissionPan.vue +150 -0
- package/client/web/src/components/RawMsgRenderPreviewPan.vue +190 -0
- package/client/web/src/components/ThemeColorPickerPan.vue +420 -0
- package/client/web/src/components/TinySessionBody.vue +51 -0
- package/client/web/src/components/UmamiInfoPan.vue +1549 -0
- package/client/web/src/components/UpdatePan.vue +263 -0
- package/client/web/src/components/ViewerCom.vue +1360 -0
- package/client/web/src/components/VoiceMsg.vue +626 -0
- package/client/web/src/components/WelPan.vue +894 -0
- package/client/web/src/components/msg-body/.gitkeep +0 -0
- package/client/web/src/components/msg-component/JsonSegComp.vue +138 -0
- package/client/web/src/components/msg-component/XmlSegComp.vue +132 -0
- package/client/web/src/components/msg-component/jsonComp/AutoReply.vue +97 -0
- package/client/web/src/components/msg-component/jsonComp/Contact.lua.vue +129 -0
- package/client/web/src/components/msg-component/jsonComp/Feed.lua.vue +94 -0
- package/client/web/src/components/msg-component/jsonComp/Forum.vue +84 -0
- package/client/web/src/components/msg-component/jsonComp/Mannounce.vue +111 -0
- package/client/web/src/components/msg-component/jsonComp/Map.vue +52 -0
- package/client/web/src/components/msg-component/jsonComp/Miniapp.lua.vue +53 -0
- package/client/web/src/components/msg-component/jsonComp/Miniapp.vue +69 -0
- package/client/web/src/components/msg-component/jsonComp/Music.lua.vue +135 -0
- package/client/web/src/components/msg-component/jsonComp/Tuwen.lua.vue +56 -0
- package/client/web/src/components/notice-component/.gitkeep +0 -0
- package/client/web/src/components/notice-component/MealHungryPan.vue +119 -0
- package/client/web/src/components/tooltip/CustomFaceTooltip.vue +22 -0
- package/client/web/src/components/tooltip/Tooltip.vue +50 -0
- package/client/web/src/components/tooltip/Tooltips.vue +14 -0
- package/client/web/src/components/tooltip/UserInfoTooltip.vue +69 -0
- package/client/web/src/function/base.ts +238 -0
- package/client/web/src/function/connect.ts +2042 -0
- package/client/web/src/function/debug.ts +10 -0
- package/client/web/src/function/elements/information.ts +141 -0
- package/client/web/src/function/elements/system.ts +101 -0
- package/client/web/src/function/elements/vueComp.ts +62 -0
- package/client/web/src/function/favicon.ts +76 -0
- package/client/web/src/function/input.ts +69 -0
- package/client/web/src/function/model/emoji.ts +127 -0
- package/client/web/src/function/model/img.ts +150 -0
- package/client/web/src/function/model/msg-body.ts +51 -0
- package/client/web/src/function/msg.ts +2674 -0
- package/client/web/src/function/notify.ts +225 -0
- package/client/web/src/function/option.ts +749 -0
- package/client/web/src/function/satori-model.ts +818 -0
- package/client/web/src/function/satori.ts +325 -0
- package/client/web/src/function/sender.ts +178 -0
- package/client/web/src/function/tooltip.ts +49 -0
- package/client/web/src/function/utils/appUtil.ts +2288 -0
- package/client/web/src/function/utils/avatarUtil.ts +31 -0
- package/client/web/src/function/utils/backgroundUtil.ts +110 -0
- package/client/web/src/function/utils/linkViewUtil.ts +101 -0
- package/client/web/src/function/utils/localHistoryUtil.ts +517 -0
- package/client/web/src/function/utils/msgUtil.ts +1242 -0
- package/client/web/src/function/utils/pinyin.ts +141 -0
- package/client/web/src/function/utils/recordUtil.ts +187 -0
- package/client/web/src/function/utils/sessionUtil.ts +114 -0
- package/client/web/src/function/utils/systemUtil.ts +664 -0
- package/client/web/src/main.ts +91 -0
- package/client/web/src/pages/Chat.vue +3453 -0
- package/client/web/src/pages/Friends.vue +636 -0
- package/client/web/src/pages/Info.vue +717 -0
- package/client/web/src/pages/Messages.vue +875 -0
- package/client/web/src/pages/Options.vue +82 -0
- package/client/web/src/pages/Qzone.vue +1178 -0
- package/client/web/src/pages/chat-view/ChatDanmaku.vue +759 -0
- package/client/web/src/pages/chat-view/ChatGlagame.vue +642 -0
- package/client/web/src/pages/chat-view/ChatTerminal.vue +1044 -0
- package/client/web/src/pages/chat-view/SystemNotice.vue +171 -0
- package/client/web/src/pages/options/OptAccount.vue +107 -0
- package/client/web/src/pages/options/OptAddon.vue +185 -0
- package/client/web/src/pages/options/OptDev.vue +704 -0
- package/client/web/src/pages/options/OptFunction.vue +403 -0
- package/client/web/src/pages/options/OptInfo.vue +165 -0
- package/client/web/src/pages/options/OptView.vue +846 -0
- package/client/web/src/runtime/backend.ts +310 -0
- package/client/web/src/state/auth.ts +14 -0
- package/client/web/src/state/chat.ts +36 -0
- package/client/web/src/state/connection.ts +20 -0
- package/client/web/src/state/contact.ts +37 -0
- package/client/web/src/state/qzone.ts +18 -0
- package/client/web/src/state/sessionHistory.ts +25 -0
- package/client/web/src/state/settings.ts +18 -0
- package/client/web/src/state/sticker.ts +10 -0
- package/client/web/src/state/ui.ts +56 -0
- package/client/web/tsconfig.json +27 -0
- package/client/web/tsconfig.tsbuildinfo +1 -0
- package/client/web/vite.config.ts +33 -0
- package/dist/index.js +1 -12
- package/dist/style.css +1 -1
- package/lib/index.js +2070 -2077
- package/package.json +15 -2
- package/readme.md +0 -22
- package/src/bootstrap.ts +71 -0
- package/src/cache.ts +319 -0
- package/src/config.ts +23 -35
- package/src/database.ts +650 -0
- package/src/index.ts +27 -59
- package/src/media.ts +302 -0
- package/src/recorder.ts +175 -0
- package/src/self-message.ts +456 -0
- package/src/server.d.ts +26 -0
- package/src/types.ts +108 -48
- package/src/web.ts +712 -0
- package/client/vue/chat-logic.ts +0 -683
- package/client/vue/composables/useChatActions.ts +0 -61
- package/client/vue/composables/useChatData.ts +0 -185
- package/client/vue/composables/useImageCache.ts +0 -281
- package/client/vue/composables/useVideoCache.ts +0 -125
- package/client/vue/types.ts +0 -72
- package/lib/api-handlers.d.ts +0 -25
- package/lib/config.d.ts +0 -14
- package/lib/file-manager.d.ts +0 -124
- package/lib/index.d.ts +0 -17
- package/lib/logger.d.ts +0 -9
- package/lib/message-handler.d.ts +0 -35
- package/lib/types.d.ts +0 -57
- package/lib/utils.d.ts +0 -18
- package/src/api-handlers.ts +0 -646
- package/src/file-manager.ts +0 -1436
- package/src/message-handler.ts +0 -507
- 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(/&/g, '&')
|
|
79
|
+
s = s.replace(/</g, '<')
|
|
80
|
+
s = s.replace(/>/g, '>')
|
|
81
|
+
s = s.replace(/ /g, ' ')
|
|
82
|
+
s = s.replace(/'/g, '\'')
|
|
83
|
+
s = s.replace(/"/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
|
+
|