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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twikoo-func",
3
- "version": "1.6.40",
3
+ "version": "1.6.42",
4
4
  "description": "A simple comment system.",
5
5
  "author": "imaegoo <hello@imaegoo.com> (https://github.com/imaegoo)",
6
6
  "license": "MIT",
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('https://smms.app/api/v2/upload', formData, {
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.getOS())
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 (os) {
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('包含违禁词,直接标记为垃圾评论~')