twikoo-vercel 1.7.15 → 1.7.17

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 (2) hide show
  1. package/api/index.js +121 -51
  2. package/package.json +2 -2
package/api/index.js CHANGED
@@ -75,10 +75,10 @@ const { RES_CODE, MAX_REQUEST_TIMES } = require('twikoo-func/utils/constants')
75
75
  // 全局变量 / variables
76
76
  let db = null
77
77
  let config
78
- let accessToken
79
78
  const requestTimes = {}
80
79
 
81
80
  module.exports = async (request, response) => {
81
+ let accessToken
82
82
  const event = request.body || {}
83
83
  logger.log('请求 IP:', getIp(request))
84
84
  logger.log('请求函数:', event.event)
@@ -99,28 +99,28 @@ module.exports = async (request, response) => {
99
99
  res = getFuncVersion({ VERSION })
100
100
  break
101
101
  case 'COMMENT_GET':
102
- res = await commentGet(event)
102
+ res = await commentGet(event, accessToken)
103
103
  break
104
104
  case 'COMMENT_GET_FOR_ADMIN':
105
- res = await commentGetForAdmin(event)
105
+ res = await commentGetForAdmin(event, accessToken)
106
106
  break
107
107
  case 'COMMENT_SET_FOR_ADMIN':
108
- res = await commentSetForAdmin(event)
108
+ res = await commentSetForAdmin(event, accessToken)
109
109
  break
110
110
  case 'COMMENT_DELETE_FOR_ADMIN':
111
- res = await commentDeleteForAdmin(event)
111
+ res = await commentDeleteForAdmin(event, accessToken)
112
112
  break
113
113
  case 'COMMENT_DELETE_FOR_USER':
114
- res = await commentDeleteForUser(event)
114
+ res = await commentDeleteForUser(event, accessToken)
115
115
  break
116
116
  case 'COMMENT_IMPORT_FOR_ADMIN':
117
- res = await commentImportForAdmin(event)
117
+ res = await commentImportForAdmin(event, accessToken)
118
118
  break
119
119
  case 'COMMENT_LIKE':
120
- res = await commentLike(event)
120
+ res = await commentLike(event, accessToken)
121
121
  break
122
122
  case 'COMMENT_SUBMIT':
123
- res = await commentSubmit(event, request)
123
+ res = await commentSubmit(event, request, accessToken)
124
124
  break
125
125
  case 'POST_SUBMIT':
126
126
  res = await postSubmit(event.comment, request)
@@ -132,16 +132,16 @@ module.exports = async (request, response) => {
132
132
  res = await getPasswordStatus(config, VERSION)
133
133
  break
134
134
  case 'SET_PASSWORD':
135
- res = await setPassword(event)
135
+ res = await setPassword(event, accessToken)
136
136
  break
137
137
  case 'GET_CONFIG':
138
- res = await getConfig({ config, VERSION, isAdmin: isAdmin() })
138
+ res = await getConfig({ config, VERSION, isAdmin: isAdmin(accessToken) })
139
139
  break
140
140
  case 'GET_CONFIG_FOR_ADMIN':
141
- res = await getConfigForAdmin({ config, isAdmin: isAdmin() })
141
+ res = await getConfigForAdmin({ config, isAdmin: isAdmin(accessToken) })
142
142
  break
143
143
  case 'SET_CONFIG':
144
- res = await setConfig(event)
144
+ res = await setConfig(event, accessToken)
145
145
  break
146
146
  case 'LOGIN':
147
147
  res = await login(event.password)
@@ -153,7 +153,7 @@ module.exports = async (request, response) => {
153
153
  res = await getRecentComments(event)
154
154
  break
155
155
  case 'EMAIL_TEST': // >= 1.4.6
156
- res = await emailTest(event, config, isAdmin())
156
+ res = await emailTest(event, config, isAdmin(accessToken))
157
157
  break
158
158
  case 'UPLOAD_IMAGE': // >= 1.5.0
159
159
  res = await uploadImage(event, config)
@@ -162,7 +162,7 @@ module.exports = async (request, response) => {
162
162
  res = await qqNickGet(event)
163
163
  break
164
164
  case 'COMMENT_EXPORT_FOR_ADMIN': // >= 1.6.13
165
- res = await commentExportForAdmin(event)
165
+ res = await commentExportForAdmin(event, accessToken)
166
166
  break
167
167
  case 'CAP_CHALLENGE':
168
168
  res = await capChallenge()
@@ -260,8 +260,8 @@ async function connectToDatabase (uri) {
260
260
  }
261
261
 
262
262
  // 写入管理密码
263
- async function setPassword (event) {
264
- const isAdminUser = isAdmin()
263
+ async function setPassword (event, accessToken) {
264
+ const isAdminUser = isAdmin(accessToken)
265
265
  // 如果数据库里没有密码,则写入密码
266
266
  // 如果数据库里有密码,则只有管理员可以写入密码
267
267
  if (config.ADMIN_PASS && !isAdminUser) {
@@ -290,13 +290,32 @@ async function login (password) {
290
290
  }
291
291
  }
292
292
 
293
+ function getSearchKeyword (event) {
294
+ if (event.keyword === undefined || event.keyword === null) return ''
295
+ if (typeof event.keyword !== 'string') throw new Error('搜索关键词必须是字符串')
296
+ const keyword = event.keyword.trim()
297
+ if (keyword.length > 100) throw new Error('搜索关键词不能超过 100 个字符')
298
+ return keyword
299
+ }
300
+
301
+ function commentMatchesKeyword (comment, keyword) {
302
+ return [comment.nick, comment.comment].some(value =>
303
+ typeof value === 'string' && value.toLowerCase().includes(keyword)
304
+ )
305
+ }
306
+
307
+ function escapeRegExp (value) {
308
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
309
+ }
310
+
293
311
  // 读取评论
294
- async function commentGet (event) {
312
+ async function commentGet (event, accessToken) {
295
313
  const res = {}
296
314
  try {
297
315
  validate(event, ['url'])
298
- const uid = getUid()
299
- const isAdminUser = isAdmin()
316
+ if (getSearchKeyword(event)) return commentSearch(event, accessToken)
317
+ const uid = accessToken
318
+ const isAdminUser = isAdmin(accessToken)
300
319
  const limit = parseInt(config.COMMENT_PAGE_SIZE) || 8
301
320
  const sort = event.sort || 'newest'
302
321
  let more = false
@@ -380,6 +399,62 @@ async function commentGet (event) {
380
399
  return res
381
400
  }
382
401
 
402
+ async function commentSearch (event, accessToken) {
403
+ const res = {}
404
+ try {
405
+ validate(event, ['url'])
406
+ const keyword = getSearchKeyword(event).toLowerCase()
407
+ const page = Math.max(parseInt(event.page) || 1, 1)
408
+ const uid = accessToken
409
+ const isAdminUser = isAdmin()
410
+ const limit = parseInt(config.COMMENT_PAGE_SIZE) || 8
411
+ const sort = event.sort || 'newest'
412
+ let more = false
413
+
414
+ const condition = { url: { $in: getUrlQuery(event.url) } }
415
+ const query = getCommentQuery({ condition, uid, isAdminUser })
416
+ const searchComments = await db.collection('comment').find(query).toArray()
417
+ const matchedRoots = new Set(searchComments
418
+ .filter(comment => commentMatchesKeyword(comment, keyword))
419
+ .map(comment => String(comment.rid || comment._id)))
420
+ let main = searchComments.filter(comment =>
421
+ (!comment.rid || comment.rid === '') && matchedRoots.has(String(comment._id))
422
+ )
423
+ const count = main.length
424
+
425
+ main.sort((a, b) => {
426
+ if (sort === 'oldest') return a.created - b.created
427
+ if (sort === 'popular') {
428
+ const ups = (b.ups || []).length - (a.ups || []).length
429
+ if (ups) return ups
430
+ }
431
+ return b.created - a.created
432
+ })
433
+ let top = []
434
+ if (!config.TOP_DISABLED) {
435
+ if (page === 1) top = main.filter(comment => comment.top === true)
436
+ main = main.filter(comment => comment.top !== true)
437
+ }
438
+ main = main.slice((page - 1) * limit, page * limit + 1)
439
+
440
+ if (main.length > limit) {
441
+ more = true
442
+ main = main.slice(0, limit)
443
+ }
444
+ main = [...top, ...main]
445
+
446
+ const mainIds = new Set(main.map(comment => comment._id.toString()))
447
+ const reply = searchComments.filter(comment => mainIds.has(String(comment.rid)))
448
+ res.data = parseComment([...main, ...reply], uid, config)
449
+ res.more = more
450
+ res.count = count
451
+ } catch (e) {
452
+ res.data = []
453
+ res.message = e.message
454
+ }
455
+ return res
456
+ }
457
+
383
458
  function getCommentQuery ({ condition, uid, isAdminUser }) {
384
459
  return {
385
460
  $or: [
@@ -390,9 +465,9 @@ function getCommentQuery ({ condition, uid, isAdminUser }) {
390
465
  }
391
466
 
392
467
  // 管理员读取评论
393
- async function commentGetForAdmin (event) {
468
+ async function commentGetForAdmin (event, accessToken) {
394
469
  const res = {}
395
- const isAdminUser = isAdmin()
470
+ const isAdminUser = isAdmin(accessToken)
396
471
  if (isAdminUser) {
397
472
  validate(event, ['per', 'page'])
398
473
  const collection = db
@@ -427,9 +502,10 @@ function getCommentSearchCondition (event) {
427
502
  break
428
503
  }
429
504
  }
430
- if (event.keyword) {
505
+ const keyword = getSearchKeyword(event)
506
+ if (keyword) {
431
507
  const regExp = {
432
- $regex: event.keyword,
508
+ $regex: escapeRegExp(keyword),
433
509
  $options: 'i'
434
510
  }
435
511
  condition = {
@@ -448,9 +524,9 @@ function getCommentSearchCondition (event) {
448
524
  }
449
525
 
450
526
  // 管理员修改评论
451
- async function commentSetForAdmin (event) {
527
+ async function commentSetForAdmin (event, accessToken) {
452
528
  const res = {}
453
- const isAdminUser = isAdmin()
529
+ const isAdminUser = isAdmin(accessToken)
454
530
  if (isAdminUser) {
455
531
  validate(event, ['id', 'set'])
456
532
  const data = await db
@@ -471,9 +547,9 @@ async function commentSetForAdmin (event) {
471
547
  }
472
548
 
473
549
  // 管理员删除评论
474
- async function commentDeleteForAdmin (event) {
550
+ async function commentDeleteForAdmin (event, accessToken) {
475
551
  const res = {}
476
- const isAdminUser = isAdmin()
552
+ const isAdminUser = isAdmin(accessToken)
477
553
  if (isAdminUser) {
478
554
  validate(event, ['id'])
479
555
  const data = await db
@@ -489,10 +565,10 @@ async function commentDeleteForAdmin (event) {
489
565
  }
490
566
 
491
567
  // 用户删除自己的评论
492
- async function commentDeleteForUser (event) {
568
+ async function commentDeleteForUser (event, accessToken) {
493
569
  const res = {}
494
570
  try {
495
- const uid = getUid()
571
+ const uid = accessToken
496
572
  await checkCommentOwnership(event.id, uid, async (id) => {
497
573
  return db.collection('comment').findOne({ _id: id })
498
574
  })
@@ -507,13 +583,13 @@ async function commentDeleteForUser (event) {
507
583
  }
508
584
 
509
585
  // 管理员导入评论
510
- async function commentImportForAdmin (event) {
586
+ async function commentImportForAdmin (event, accessToken) {
511
587
  const res = {}
512
588
  let logText = ''
513
589
  const log = (message) => {
514
590
  logText += `${new Date().toLocaleString()} ${message}\n`
515
591
  }
516
- const isAdminUser = isAdmin()
592
+ const isAdminUser = isAdmin(accessToken)
517
593
  if (isAdminUser) {
518
594
  try {
519
595
  validate(event, ['source', 'file'])
@@ -563,9 +639,9 @@ async function commentImportForAdmin (event) {
563
639
  return res
564
640
  }
565
641
 
566
- async function commentExportForAdmin (event) {
642
+ async function commentExportForAdmin (event, accessToken) {
567
643
  const res = {}
568
- const isAdminUser = isAdmin()
644
+ const isAdminUser = isAdmin(accessToken)
569
645
  if (isAdminUser) {
570
646
  const collection = event.collection || 'comment'
571
647
  const data = await db
@@ -608,11 +684,11 @@ async function bulkSaveComments (comments) {
608
684
  }
609
685
 
610
686
  // 点赞 / 反对 / 取消
611
- async function commentLike (event) {
687
+ async function commentLike (event, accessToken) {
612
688
  const res = {}
613
689
  validate(event, ['id'])
614
690
  const type = event.type || 'up'
615
- res.updated = await like(event.id, getUid(), type)
691
+ res.updated = await like(event.id, accessToken, type)
616
692
  return res
617
693
  }
618
694
 
@@ -667,7 +743,7 @@ async function like (id, uid, type) {
667
743
  * @param {String} event.pid 回复的 ID
668
744
  * @param {String} event.rid 评论楼 ID
669
745
  */
670
- async function commentSubmit (event, request) {
746
+ async function commentSubmit (event, request, accessToken) {
671
747
  const res = {}
672
748
  // 参数校验
673
749
  validate(event, ['url', 'ua', 'comment'])
@@ -676,7 +752,7 @@ async function commentSubmit (event, request) {
676
752
  // 验证码
677
753
  await checkCaptcha(event, request)
678
754
  // 预检测、转换
679
- const data = await parse(event, request)
755
+ const data = await parse(event, request, accessToken)
680
756
  // 保存
681
757
  const comment = await save(data)
682
758
  res.id = comment.id
@@ -727,16 +803,16 @@ async function postSubmit (comment, request) {
727
803
  }
728
804
 
729
805
  // 将评论转为数据库存储格式
730
- async function parse (comment, request) {
806
+ async function parse (comment, request, accessToken) {
731
807
  const timestamp = Date.now()
732
- const isAdminUser = isAdmin()
808
+ const isAdminUser = isAdmin(accessToken)
733
809
  const isBloggerMail = equalsMail(comment.mail, config.BLOGGER_EMAIL)
734
810
  if (isBloggerMail && !isAdminUser) throw new Error('请先登录管理面板,再使用博主身份发送评论')
735
811
  if (comment.mail && !isValidEmail(comment.mail)) throw new Error('邮箱格式不合法')
736
812
  const hashMethod = config.GRAVATAR_CDN === 'cravatar.cn' ? md5 : sha256
737
813
  const commentDo = {
738
814
  _id: uuidv4().replace(/-/g, ''),
739
- uid: getUid(),
815
+ uid: accessToken,
740
816
  nick: comment.nick ? comment.nick : '匿名',
741
817
  mail: comment.mail ? comment.mail : '',
742
818
  mailMd5: comment.mail ? hashMethod(normalizeMail(comment.mail)) : '',
@@ -996,8 +1072,8 @@ async function qqNickGet (event) {
996
1072
  }
997
1073
 
998
1074
  // 修改配置
999
- async function setConfig (event) {
1000
- const isAdminUser = isAdmin()
1075
+ async function setConfig (event, accessToken) {
1076
+ const isAdminUser = isAdmin(accessToken)
1001
1077
  if (isAdminUser) {
1002
1078
  writeConfig(event.config)
1003
1079
  return {
@@ -1064,15 +1140,9 @@ async function writeConfig (newConfig) {
1064
1140
  }
1065
1141
  }
1066
1142
 
1067
- // 获取用户 ID
1068
- function getUid () {
1069
- return accessToken
1070
- }
1071
-
1072
1143
  // 判断用户是否管理员
1073
- function isAdmin () {
1074
- const uid = getUid()
1075
- return config.ADMIN_PASS === md5(uid)
1144
+ function isAdmin (accessToken) {
1145
+ return config.ADMIN_PASS === md5(accessToken)
1076
1146
  }
1077
1147
 
1078
1148
  // 判断是否为递归调用(即云函数调用自身)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twikoo-vercel",
3
- "version": "1.7.15",
3
+ "version": "1.7.17",
4
4
  "description": "A simple comment system.",
5
5
  "author": "imaegoo <hello@imaegoo.com> (https://github.com/imaegoo)",
6
6
  "license": "MIT",
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "get-user-ip": "^1.0.1",
15
15
  "mongodb": "^6.3.0",
16
- "twikoo-func": "1.7.15",
16
+ "twikoo-func": "1.7.17",
17
17
  "uuid": "^8.3.2"
18
18
  }
19
19
  }