twikoo-func 1.6.16 → 1.6.18

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/index.js CHANGED
@@ -40,6 +40,7 @@ const {
40
40
  const { postCheckSpam } = require('./utils/spam')
41
41
  const { sendNotice, emailTest } = require('./utils/notify')
42
42
  const { uploadImage } = require('./utils/image')
43
+ const logger = require('./utils/logger')
43
44
 
44
45
  // 云函数 SDK / tencent cloudbase sdk
45
46
  const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV })
@@ -60,9 +61,9 @@ const requestTimes = {}
60
61
 
61
62
  // 云函数入口点 / entry point
62
63
  exports.main = async (event, context) => {
63
- console.log('请求 IP:', auth.getClientIP())
64
- console.log('请求函数:', event.event)
65
- console.log('请求参数:', event)
64
+ logger.log('请求 IP:', auth.getClientIP())
65
+ logger.log('请求函数:', event.event)
66
+ logger.log('请求参数:', event)
66
67
  let res = {}
67
68
  try {
68
69
  protect()
@@ -141,13 +142,13 @@ exports.main = async (event, context) => {
141
142
  }
142
143
  }
143
144
  } catch (e) {
144
- console.error('Twikoo 遇到错误,请参考以下错误信息。如有疑问,请反馈至 https://github.com/imaegoo/twikoo/issues')
145
- console.error('请求参数:', event)
146
- console.error('错误信息:', e)
145
+ logger.error('Twikoo 遇到错误,请参考以下错误信息。如有疑问,请反馈至 https://github.com/imaegoo/twikoo/issues')
146
+ logger.error('请求参数:', event)
147
+ logger.error('错误信息:', e)
147
148
  res.code = RES_CODE.FAIL
148
149
  res.message = e.message
149
150
  }
150
- console.log('请求返回:', res)
151
+ logger.log('请求返回:', res)
151
152
  return res
152
153
  }
153
154
 
@@ -182,7 +183,7 @@ async function checkAndSaveCredentials (credentials) {
182
183
  await writeConfig({ CREDENTIALS: credentials })
183
184
  return true
184
185
  } catch (e) {
185
- console.error('私钥文件异常:', e)
186
+ logger.error('私钥文件异常:', e)
186
187
  return false
187
188
  }
188
189
  }
@@ -452,7 +453,7 @@ async function commentImportForAdmin (event) {
452
453
  }
453
454
  res.code = RES_CODE.SUCCESS
454
455
  res.log = logText
455
- console.log(logText)
456
+ logger.info(logText)
456
457
  } else {
457
458
  res.code = RES_CODE.NEED_LOGIN
458
459
  res.message = '请先登录'
@@ -572,7 +573,7 @@ async function commentSubmit (event, context) {
572
573
  data: { event: 'POST_SUBMIT', comment }
573
574
  }, { timeout: 300 }) // 设置较短的 timeout 来实现异步
574
575
  } catch (e) {
575
- console.log('开始异步垃圾检测、发送评论通知')
576
+ logger.log('开始异步垃圾检测、发送评论通知')
576
577
  }
577
578
  return res
578
579
  }
@@ -836,10 +837,10 @@ function protect () {
836
837
  const ip = auth.getClientIP()
837
838
  requestTimes[ip] = (requestTimes[ip] || 0) + 1
838
839
  if (requestTimes[ip] > MAX_REQUEST_TIMES) {
839
- console.log(`${ip} 当前请求次数为 ${requestTimes[ip]},已超过最大请求次数`)
840
+ logger.warn(`${ip} 当前请求次数为 ${requestTimes[ip]},已超过最大请求次数`)
840
841
  throw new Error('Too Many Requests')
841
842
  } else {
842
- console.log(`${ip} 当前请求次数为 ${requestTimes[ip]}`)
843
+ logger.log(`${ip} 当前请求次数为 ${requestTimes[ip]}`)
843
844
  }
844
845
  }
845
846
 
@@ -853,7 +854,7 @@ async function readConfig () {
853
854
  config = res.data[0] || {}
854
855
  return config
855
856
  } catch (e) {
856
- console.error('读取配置失败:', e)
857
+ logger.error('读取配置失败:', e)
857
858
  await createCollections()
858
859
  config = {}
859
860
  return config
@@ -863,7 +864,7 @@ async function readConfig () {
863
864
  // 写入配置
864
865
  async function writeConfig (newConfig) {
865
866
  if (!Object.keys(newConfig).length) return 0
866
- console.log('写入配置:', newConfig)
867
+ logger.info('写入配置:', newConfig)
867
868
  try {
868
869
  let updated
869
870
  let res = await db
@@ -882,7 +883,7 @@ async function writeConfig (newConfig) {
882
883
  if (updated > 0) config = null
883
884
  return updated
884
885
  } catch (e) {
885
- console.error('写入配置失败:', e)
886
+ logger.error('写入配置失败:', e)
886
887
  return null
887
888
  }
888
889
  }
@@ -913,7 +914,7 @@ async function createCollections () {
913
914
  try {
914
915
  res[collection] = await db.createCollection(collection)
915
916
  } catch (e) {
916
- console.error('建立数据库失败:', e)
917
+ logger.error('建立数据库失败:', e)
917
918
  }
918
919
  }
919
920
  return res
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twikoo-func",
3
- "version": "1.6.16",
3
+ "version": "1.6.18",
4
4
  "description": "A simple comment system.",
5
5
  "author": "imaegoo <hello@imaegoo.com> (https://github.com/imaegoo)",
6
6
  "license": "MIT",
@@ -27,6 +27,6 @@
27
27
  "nodemailer": "^6.4.17",
28
28
  "pushoo": "latest",
29
29
  "tencentcloud-sdk-nodejs": "^4.0.65",
30
- "xml2js": "^0.4.23"
30
+ "xml2js": "^0.6.0"
31
31
  }
32
32
  }
package/utils/image.js CHANGED
@@ -4,6 +4,7 @@ const path = require('path')
4
4
  const { isUrl } = require('.')
5
5
  const { RES_CODE } = require('./constants')
6
6
  const { axios, FormData } = require('./lib')
7
+ const logger = require('./logger')
7
8
 
8
9
  const fn = {
9
10
  async uploadImage (event, config) {
@@ -22,7 +23,7 @@ const fn = {
22
23
  await fn.uploadImageToLskyPro({ photo, fileName, config, res, imageCdn: config.IMAGE_CDN })
23
24
  }
24
25
  } catch (e) {
25
- console.error(e)
26
+ logger.error(e)
26
27
  res.code = RES_CODE.UPLOAD_FAILED
27
28
  res.err = e.message
28
29
  }
package/utils/index.js CHANGED
@@ -2,6 +2,7 @@ const { URL } = require('url')
2
2
  const { axios, bowser, ipToRegion, md5 } = require('./lib')
3
3
  const { RES_CODE } = require('./constants')
4
4
  const ipRegionSearcher = ipToRegion.create() // 初始化 IP 属地
5
+ const logger = require('./logger')
5
6
 
6
7
  const fn = {
7
8
  // 获取 Twikoo 云函数版本
@@ -48,7 +49,7 @@ const fn = {
48
49
  displayOs = [os.name, os.versionName ? os.versionName : os.version].join(' ')
49
50
  displayBrowser = [ua.getBrowserName(), ua.getBrowserVersion()].join(' ')
50
51
  } catch (e) {
51
- console.log('bowser 错误:', e)
52
+ logger.warn('bowser 错误:', e)
52
53
  }
53
54
  }
54
55
  const showRegion = !!config.SHOW_REGION && config.SHOW_REGION !== 'false'
@@ -90,17 +91,19 @@ const fn = {
90
91
  try {
91
92
  // 将 IPv6 格式的 IPv4 地址转换为 IPv4 格式
92
93
  ip = ip.replace(/^::ffff:/, '')
94
+ // Zeabur 返回的地址带端口号,去掉端口号。TODO: 不知道该怎么去掉 IPv6 地址后面的端口号
95
+ ip = ip.replace(/:[0-9]*$/, '')
93
96
  const { region } = ipRegionSearcher.binarySearchSync(ip)
94
97
  const [country,, province, city, isp] = region.split('|')
95
98
  // 有省显示省,没有省显示国家
96
- const area = province.trim() ? province : country
99
+ const area = province.trim() && province !== '0' ? province : country
97
100
  if (detail) {
98
101
  return area === city ? [city, isp].join(' ') : [area, city, isp].join(' ')
99
102
  } else {
100
103
  return area.replace(/(省|市)$/, '')
101
104
  }
102
105
  } catch (e) {
103
- console.error('IP 属地查询失败:', e.message)
106
+ logger.warn('IP 属地查询失败:', e.message, ip)
104
107
  return ''
105
108
  }
106
109
  },
@@ -151,15 +154,13 @@ const fn = {
151
154
  async getQQAvatar (qq) {
152
155
  try {
153
156
  const qqNum = qq.replace(/@qq.com/ig, '')
154
- const result = await axios.get(`https://ptlogin2.qq.com/getface?imgtype=4&uin=${qqNum}`)
155
- if (result && result.data) {
156
- const start = result.data.indexOf('http')
157
- const end = result.data.indexOf('"', start)
158
- if (start === -1 || end === -1) return null
159
- return result.data.substring(start, end)
160
- }
157
+ const result = await axios.get(`https://s.p.qq.com/pub/get_face?img_type=4&uin=${qqNum}`, {
158
+ maxRedirects: 0,
159
+ validateStatus: status => [301, 302, 307, 308].includes(status)
160
+ })
161
+ return result?.headers?.location || null
161
162
  } catch (e) {
162
- console.error('获取 QQ 头像失败:', e)
163
+ logger.warn('获取 QQ 头像失败:', e)
163
164
  }
164
165
  },
165
166
  // 判断是否存在管理员密码
@@ -181,13 +182,13 @@ const fn = {
181
182
  }
182
183
  if (config.AKISMET_KEY === 'MANUAL_REVIEW') {
183
184
  // 人工审核
184
- console.log('已使用人工审核模式,评论审核后才会发表~')
185
+ logger.info('已使用人工审核模式,评论审核后才会发表~')
185
186
  return true
186
187
  } else if (config.FORBIDDEN_WORDS) {
187
188
  // 违禁词检测
188
189
  for (const forbiddenWord of config.FORBIDDEN_WORDS.split(',')) {
189
190
  if (comment.indexOf(forbiddenWord.trim()) !== -1 || nick.indexOf(forbiddenWord.trim()) !== -1) {
190
- console.log('包含违禁词,直接标记为垃圾评论~')
191
+ logger.warn('包含违禁词,直接标记为垃圾评论~')
191
192
  return true
192
193
  }
193
194
  }
@@ -211,6 +212,7 @@ const fn = {
211
212
  SHOW_EMOTION: config.SHOW_EMOTION || 'true',
212
213
  EMOTION_CDN: config.EMOTION_CDN,
213
214
  COMMENT_PLACEHOLDER: config.COMMENT_PLACEHOLDER,
215
+ DISPLAYED_FIELDS: config.DISPLAYED_FIELDS,
214
216
  REQUIRED_FIELDS: config.REQUIRED_FIELDS,
215
217
  HIDE_ADMIN_CRYPT: config.HIDE_ADMIN_CRYPT,
216
218
  HIGHLIGHT: config.HIGHLIGHT || 'true',
@@ -0,0 +1,22 @@
1
+ let envLogLevel = process.env.TWIKOO_LOG_LEVEL || 'info'
2
+ envLogLevel = envLogLevel.toLowerCase()
3
+ const logLevel = { verbose: 1, info: 2, warn: 3, error: 4 }[envLogLevel] || 2
4
+
5
+ const logger = {
6
+ log: (...messages) => {
7
+ if (logLevel <= 1) console.log(logPrefix(), ...messages)
8
+ },
9
+ info: (...messages) => {
10
+ if (logLevel <= 2) console.info(logPrefix(), ...messages)
11
+ },
12
+ warn: (...messages) => {
13
+ if (logLevel <= 3) console.warn(logPrefix(), ...messages)
14
+ },
15
+ error: (...messages) => {
16
+ if (logLevel <= 4) console.error(logPrefix(), ...messages)
17
+ }
18
+ }
19
+
20
+ const logPrefix = () => `${new Date().toLocaleString()} Twikoo:`
21
+
22
+ module.exports = logger
package/utils/notify.js CHANGED
@@ -5,6 +5,7 @@ const {
5
5
  pushoo
6
6
  } = require('./lib')
7
7
  const { RES_CODE } = require('./constants')
8
+ const logger = require('./logger')
8
9
 
9
10
  let transporter
10
11
 
@@ -17,7 +18,7 @@ const fn = {
17
18
  fn.noticeReply(comment, config, getParentComment),
18
19
  fn.noticePushoo(comment, config)
19
20
  ]).catch(err => {
20
- console.error('通知异常:', err)
21
+ logger.error('通知异常:', err)
21
22
  })
22
23
  },
23
24
  // 初始化邮件插件
@@ -44,31 +45,35 @@ const fn = {
44
45
  transporter = nodemailer.createTransport(transportConfig)
45
46
  try {
46
47
  const success = await transporter.verify()
47
- if (success) console.log('SMTP 邮箱配置正常')
48
+ if (success) logger.info('SMTP 邮箱配置正常')
48
49
  } catch (error) {
49
50
  throw new Error('SMTP 邮箱配置异常:', error)
50
51
  }
51
52
  return true
52
53
  } catch (e) {
53
- console.error('邮件初始化异常:', e.message)
54
- if (throwErr) throw e
54
+ if (throwErr) {
55
+ logger.error('邮件初始化异常:', e.message)
56
+ throw e
57
+ } else {
58
+ logger.warn('邮件初始化异常:', e.message)
59
+ }
55
60
  return false
56
61
  }
57
62
  },
58
63
  // 博主通知
59
64
  async noticeMaster (comment, config) {
60
65
  if (!transporter && !await fn.initMailer({ config })) {
61
- console.log('未配置邮箱或邮箱配置有误,不通知')
66
+ logger.info('未配置邮箱或邮箱配置有误,不通知')
62
67
  return
63
68
  }
64
69
  if (config.BLOGGER_EMAIL && config.BLOGGER_EMAIL === comment.mail) {
65
- console.log('博主本人评论,不发送通知给博主')
70
+ logger.info('博主本人评论,不发送通知给博主')
66
71
  return
67
72
  }
68
73
  // 判断是否存在即时消息推送配置
69
74
  const hasIMPushConfig = config.PUSHOO_CHANNEL && config.PUSHOO_TOKEN
70
75
  if (hasIMPushConfig && config.SC_MAIL_NOTIFY !== 'true') {
71
- console.log('存在即时消息推送配置,默认不发送邮件给博主,您可以在管理面板修改此行为')
76
+ logger.info('存在即时消息推送配置,默认不发送邮件给博主,您可以在管理面板修改此行为')
72
77
  return
73
78
  }
74
79
  const SITE_NAME = config.SITE_NAME
@@ -113,17 +118,17 @@ const fn = {
113
118
  } catch (e) {
114
119
  sendResult = e
115
120
  }
116
- console.log('博主通知结果:', sendResult)
121
+ logger.log('博主通知结果:', sendResult)
117
122
  return sendResult
118
123
  },
119
124
  // 即时消息通知
120
125
  async noticePushoo (comment, config) {
121
126
  if (!config.PUSHOO_CHANNEL || !config.PUSHOO_TOKEN) {
122
- console.log('没有配置 pushoo,放弃即时消息通知')
127
+ logger.info('没有配置 pushoo,放弃即时消息通知')
123
128
  return
124
129
  }
125
130
  if (config.BLOGGER_EMAIL && config.BLOGGER_EMAIL === comment.mail) {
126
- console.log('博主本人评论,不发送通知给博主')
131
+ logger.info('博主本人评论,不发送通知给博主')
127
132
  return
128
133
  }
129
134
  const pushContent = fn.getIMPushContent(comment, config)
@@ -137,7 +142,7 @@ const fn = {
137
142
  }
138
143
  }
139
144
  })
140
- console.log('即时消息通知结果:', sendResult)
145
+ logger.info('即时消息通知结果:', sendResult)
141
146
  },
142
147
  // 即时消息推送内容获取
143
148
  getIMPushContent (comment, config) {
@@ -165,20 +170,20 @@ const fn = {
165
170
  // 回复通知
166
171
  async noticeReply (currentComment, config, getParentComment) {
167
172
  if (!currentComment.pid) {
168
- console.log('无父级评论,不通知')
173
+ logger.info('无父级评论,不通知')
169
174
  return
170
175
  }
171
176
  if (!transporter && !await fn.initMailer({ config })) {
172
- console.log('未配置邮箱或邮箱配置有误,不通知')
177
+ logger.info('未配置邮箱或邮箱配置有误,不通知')
173
178
  return
174
179
  }
175
180
  const parentComment = await getParentComment(currentComment)
176
181
  if (config.BLOGGER_EMAIL === parentComment.mail) {
177
- console.log('回复给博主,因为会发博主通知邮件,所以不再重复通知')
182
+ logger.info('回复给博主,因为会发博主通知邮件,所以不再重复通知')
178
183
  return
179
184
  }
180
185
  if (currentComment.mail === parentComment.mail) {
181
- console.log('回复自己的评论,不邮件通知')
186
+ logger.info('回复自己的评论,不邮件通知')
182
187
  return
183
188
  }
184
189
  const PARENT_NICK = parentComment.nick
@@ -232,7 +237,7 @@ const fn = {
232
237
  } catch (e) {
233
238
  sendResult = e
234
239
  }
235
- console.log('回复通知结果:', sendResult)
240
+ logger.log('回复通知结果:', sendResult)
236
241
  return sendResult
237
242
  },
238
243
  appendHashToUrl (url, hash) {
package/utils/spam.js CHANGED
@@ -3,6 +3,7 @@ const {
3
3
  CryptoJS,
4
4
  tencentcloud
5
5
  } = require('./lib')
6
+ const logger = require('./logger')
6
7
 
7
8
  const fn = {
8
9
  // 后垃圾评论检测
@@ -24,7 +25,7 @@ const fn = {
24
25
  Device: { IP: comment.ip },
25
26
  User: { Nickname: comment.nick }
26
27
  })
27
- console.log('腾讯云返回结果:', checkResult)
28
+ logger.log('腾讯云返回结果:', checkResult)
28
29
  isSpam = checkResult.EvilFlag !== 0
29
30
  } else if (config.AKISMET_KEY) {
30
31
  // Akismet
@@ -34,7 +35,7 @@ const fn = {
34
35
  })
35
36
  const isValid = await akismetClient.verifyKey()
36
37
  if (!isValid) {
37
- console.log('Akismet key 不可用:', config.AKISMET_KEY)
38
+ logger.warn('Akismet key 不可用:', config.AKISMET_KEY)
38
39
  return
39
40
  }
40
41
  isSpam = await akismetClient.checkSpam({
@@ -48,10 +49,10 @@ const fn = {
48
49
  comment_content: comment.comment
49
50
  })
50
51
  }
51
- console.log('垃圾评论检测结果:', isSpam)
52
+ logger.log('垃圾评论检测结果:', isSpam)
52
53
  return isSpam
53
54
  } catch (err) {
54
- console.error('垃圾评论检测异常:', err)
55
+ logger.error('垃圾评论检测异常:', err)
55
56
  }
56
57
  }
57
58
  }