twikoo-func 1.6.40 → 1.6.42
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/package.json +1 -1
- package/utils/image.js +79 -3
- package/utils/index.js +20 -4
package/package.json
CHANGED
package/utils/image.js
CHANGED
|
@@ -20,9 +20,17 @@ const fn = {
|
|
|
20
20
|
if (config.IMAGE_CDN === '7bu') {
|
|
21
21
|
await fn.uploadImageToLskyPro({ photo, fileName, config, res, imageCdn: 'https://7bu.top' })
|
|
22
22
|
} else if (config.IMAGE_CDN === 'smms') {
|
|
23
|
-
await fn.uploadImageToSmms({ photo, fileName, config, res })
|
|
23
|
+
await fn.uploadImageToSmms({ photo, fileName, config, res, imageCdn: 'https://smms.app/api/v2/upload' })
|
|
24
24
|
} else if (isUrl(config.IMAGE_CDN)) {
|
|
25
25
|
await fn.uploadImageToLskyPro({ photo, fileName, config, res, imageCdn: config.IMAGE_CDN })
|
|
26
|
+
} else if (config.IMAGE_CDN === 'lskypro') {
|
|
27
|
+
await fn.uploadImageToLskyPro({ photo, fileName, config, res, imageCdn: config.IMAGE_CDN_URL })
|
|
28
|
+
} else if (config.IMAGE_CDN === 'piclist') {
|
|
29
|
+
await fn.uploadImageToPicList({ photo, fileName, config, res, imageCdn: config.IMAGE_CDN_URL })
|
|
30
|
+
} else if (config.IMAGE_CDN === 'easyimage') {
|
|
31
|
+
await fn.uploadImageToEasyImage({ photo, fileName, config, res })
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error('不支持的图片上传服务')
|
|
26
34
|
}
|
|
27
35
|
} catch (e) {
|
|
28
36
|
logger.error(e)
|
|
@@ -31,11 +39,11 @@ const fn = {
|
|
|
31
39
|
}
|
|
32
40
|
return res
|
|
33
41
|
},
|
|
34
|
-
async uploadImageToSmms ({ photo, fileName, config, res }) {
|
|
42
|
+
async uploadImageToSmms ({ photo, fileName, config, res, imageCdn }) {
|
|
35
43
|
// SM.MS 图床 https://sm.ms
|
|
36
44
|
const formData = new FormData()
|
|
37
45
|
formData.append('smfile', fn.base64UrlToReadStream(photo, fileName))
|
|
38
|
-
const uploadResult = await axios.post(
|
|
46
|
+
const uploadResult = await axios.post(imageCdn, formData, {
|
|
39
47
|
headers: {
|
|
40
48
|
...formData.getHeaders(),
|
|
41
49
|
Authorization: config.IMAGE_CDN_TOKEN
|
|
@@ -72,6 +80,74 @@ const fn = {
|
|
|
72
80
|
throw new Error(uploadResult.data.message)
|
|
73
81
|
}
|
|
74
82
|
},
|
|
83
|
+
async uploadImageToPicList ({ photo, fileName, config, res, imageCdn }) {
|
|
84
|
+
// PicList https://piclist.cn/ 高效的云存储和图床平台管理工具
|
|
85
|
+
// 鉴权使用 query 参数 key
|
|
86
|
+
const formData = new FormData()
|
|
87
|
+
formData.append('file', fn.base64UrlToReadStream(photo, fileName))
|
|
88
|
+
let url = `${imageCdn}/upload`
|
|
89
|
+
// 如果填写了 key 则拼接 url
|
|
90
|
+
if (config.IMAGE_CDN_TOKEN) {
|
|
91
|
+
url += `?key=${config.IMAGE_CDN_TOKEN}`
|
|
92
|
+
}
|
|
93
|
+
const uploadResult = await axios.post(url, formData)
|
|
94
|
+
if (uploadResult.data.success) {
|
|
95
|
+
res.data = uploadResult.data
|
|
96
|
+
res.data.url = uploadResult.data.result[0]
|
|
97
|
+
} else {
|
|
98
|
+
throw new Error(uploadResult.data.message)
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
async uploadImageToEasyImage ({ photo, fileName, config, res }) {
|
|
102
|
+
// EasyImage2.0 https://github.com/icret/EasyImages2.0 简单图床 - 一款功能强大无数据库的图床 2.0版
|
|
103
|
+
try {
|
|
104
|
+
// 参数校验
|
|
105
|
+
if (!config.IMAGE_CDN_URL) {
|
|
106
|
+
throw new Error('未配置 EasyImage2.0 的 API 地址 (IMAGE_CDN_URL)')
|
|
107
|
+
}
|
|
108
|
+
if (!config.IMAGE_CDN_TOKEN) {
|
|
109
|
+
throw new Error('未配置 EasyImage2.0 的 Token (IMAGE_CDN_TOKEN)')
|
|
110
|
+
}
|
|
111
|
+
// 构建固定格式的 FormData
|
|
112
|
+
const formData = new FormData()
|
|
113
|
+
// 添加 token 参数到 Body
|
|
114
|
+
formData.append('token', config.IMAGE_CDN_TOKEN)
|
|
115
|
+
// 添加图片文件(固定参数名 image)
|
|
116
|
+
formData.append('image', fn.base64UrlToReadStream(photo, fileName), {
|
|
117
|
+
filename: fileName
|
|
118
|
+
})
|
|
119
|
+
// 发送请求
|
|
120
|
+
const uploadResult = await axios.post(config.IMAGE_CDN_URL, formData, {
|
|
121
|
+
headers: {
|
|
122
|
+
...formData.getHeaders(),
|
|
123
|
+
'User-Agent': 'Twikoo'
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
// 解析响应
|
|
127
|
+
const response = uploadResult.data
|
|
128
|
+
// 检查业务状态码
|
|
129
|
+
if (response.code !== 200 || response.result !== 'success') {
|
|
130
|
+
throw new Error(`API 返回错误 (CODE: ${response.code})`)
|
|
131
|
+
}
|
|
132
|
+
// 提取图片 URL(固定 JSON 路径 url)
|
|
133
|
+
if (!response.url) {
|
|
134
|
+
throw new Error('未找到有效图片 URL')
|
|
135
|
+
}
|
|
136
|
+
// 返回标准化结构
|
|
137
|
+
res.data = {
|
|
138
|
+
url: response.url,
|
|
139
|
+
thumb: response.thumb, // 可选返回缩略图
|
|
140
|
+
del: response.del // 可选返回删除链接
|
|
141
|
+
}
|
|
142
|
+
} catch (e) {
|
|
143
|
+
let errorMsg = `EasyImage2.0 上传失败: ${e.message}`
|
|
144
|
+
// 追加 API 返回的错误详情
|
|
145
|
+
if (e.response?.data) {
|
|
146
|
+
errorMsg += ` | 错误类型: ${e.response.data.message || '未知'}`
|
|
147
|
+
}
|
|
148
|
+
throw new Error(errorMsg)
|
|
149
|
+
}
|
|
150
|
+
},
|
|
75
151
|
base64UrlToReadStream (base64Url, fileName) {
|
|
76
152
|
const base64 = base64Url.split(';base64,').pop()
|
|
77
153
|
const writePath = path.resolve(os.tmpdir(), fileName)
|
package/utils/index.js
CHANGED
|
@@ -67,7 +67,7 @@ const fn = {
|
|
|
67
67
|
if (config.SHOW_UA !== 'false') {
|
|
68
68
|
try {
|
|
69
69
|
const ua = bowser.getParser(comment.ua)
|
|
70
|
-
const os = fn.fixOS(ua
|
|
70
|
+
const os = fn.fixOS(ua)
|
|
71
71
|
displayOs = [os.name, os.versionName ? os.versionName : os.version].join(' ')
|
|
72
72
|
displayBrowser = [ua.getBrowserName(), ua.getBrowserVersion()].join(' ')
|
|
73
73
|
} catch (e) {
|
|
@@ -98,7 +98,8 @@ const fn = {
|
|
|
98
98
|
updated: comment.updated
|
|
99
99
|
}
|
|
100
100
|
},
|
|
101
|
-
fixOS (
|
|
101
|
+
fixOS (ua) {
|
|
102
|
+
const os = ua.getOS()
|
|
102
103
|
if (!os.versionName) {
|
|
103
104
|
// fix version name of Win 11 & macOS ^11 & Android ^10
|
|
104
105
|
if (os.name === 'Windows' && os.version === 'NT 11.0') {
|
|
@@ -120,12 +121,27 @@ const fn = {
|
|
|
120
121
|
12: 'Snow Cone',
|
|
121
122
|
13: 'Tiramisu',
|
|
122
123
|
14: 'Upside Down Cake',
|
|
123
|
-
15: 'Vanilla Ice Cream'
|
|
124
|
+
15: 'Vanilla Ice Cream',
|
|
125
|
+
16: 'Baklava'
|
|
124
126
|
}[majorPlatformVersion]
|
|
127
|
+
} else if (ua.test(/harmony/i)) {
|
|
128
|
+
os.name = 'Harmony'
|
|
129
|
+
os.version = fn.getFirstMatch(/harmony[\s/-](\d+(\.\d+)*)/i, ua.getUA())
|
|
130
|
+
os.versionName = ''
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
return os
|
|
128
134
|
},
|
|
135
|
+
/**
|
|
136
|
+
* Get first matched item for a string
|
|
137
|
+
* @param {RegExp} regexp
|
|
138
|
+
* @param {String} ua
|
|
139
|
+
* @return {Array|{index: number, input: string}|*|boolean|string}
|
|
140
|
+
*/
|
|
141
|
+
getFirstMatch (regexp, ua) {
|
|
142
|
+
const match = ua.match(regexp)
|
|
143
|
+
return (match && match.length > 0 && match[1]) || ''
|
|
144
|
+
},
|
|
129
145
|
// 获取回复人昵称 / Get replied user nick name
|
|
130
146
|
ruser (pid, comments = []) {
|
|
131
147
|
const comment = comments.find((item) => item._id === pid)
|
|
@@ -262,7 +278,7 @@ const fn = {
|
|
|
262
278
|
// 违禁词检测
|
|
263
279
|
const commentLowerCase = comment.toLowerCase()
|
|
264
280
|
const nickLowerCase = nick.toLowerCase()
|
|
265
|
-
for (const forbiddenWord of config.FORBIDDEN_WORDS.split(',')) {
|
|
281
|
+
for (const forbiddenWord of config.FORBIDDEN_WORDS.replace(/,+$/, '').split(',')) {
|
|
266
282
|
const forbiddenWordLowerCase = forbiddenWord.trim().toLowerCase()
|
|
267
283
|
if (commentLowerCase.indexOf(forbiddenWordLowerCase) !== -1 || nickLowerCase.indexOf(forbiddenWordLowerCase) !== -1) {
|
|
268
284
|
logger.warn('包含违禁词,直接标记为垃圾评论~')
|