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.
- package/api/index.js +121 -51
- 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
|
-
|
|
299
|
-
const
|
|
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
|
-
|
|
505
|
+
const keyword = getSearchKeyword(event)
|
|
506
|
+
if (keyword) {
|
|
431
507
|
const regExp = {
|
|
432
|
-
$regex:
|
|
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 =
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
16
|
+
"twikoo-func": "1.7.17",
|
|
17
17
|
"uuid": "^8.3.2"
|
|
18
18
|
}
|
|
19
19
|
}
|