twikoo-func 1.5.1 → 1.5.4
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 +101 -21
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -50,20 +50,24 @@ const RES_CODE = {
|
|
|
50
50
|
UPLOAD_FAILED: 1040
|
|
51
51
|
}
|
|
52
52
|
const ADMIN_USER_ID = 'admin'
|
|
53
|
+
const MAX_REQUEST_TIMES = parseInt(process.env.TWIKOO_THROTTLE) || 250
|
|
53
54
|
|
|
54
55
|
// 全局变量 / variables
|
|
55
56
|
// 警告:全局定义的变量,会被云函数缓存,请慎重定义全局变量
|
|
56
57
|
// 参考 https://docs.cloudbase.net/cloud-function/deep-principle.html 中的 “实例复用”
|
|
57
58
|
let config
|
|
58
59
|
let transporter
|
|
60
|
+
const requestTimes = {}
|
|
59
61
|
|
|
60
62
|
// 云函数入口点 / entry point
|
|
61
63
|
exports.main = async (event, context) => {
|
|
64
|
+
console.log('请求IP:', auth.getClientIP())
|
|
62
65
|
console.log('请求方法:', event.event)
|
|
63
66
|
console.log('请求参数:', event)
|
|
64
67
|
let res = {}
|
|
65
|
-
await readConfig()
|
|
66
68
|
try {
|
|
69
|
+
protect()
|
|
70
|
+
await readConfig()
|
|
67
71
|
switch (event.event) {
|
|
68
72
|
case 'GET_FUNC_VERSION':
|
|
69
73
|
res = getFuncVersion()
|
|
@@ -87,7 +91,7 @@ exports.main = async (event, context) => {
|
|
|
87
91
|
res = await commentLike(event)
|
|
88
92
|
break
|
|
89
93
|
case 'COMMENT_SUBMIT':
|
|
90
|
-
res = await commentSubmit(event)
|
|
94
|
+
res = await commentSubmit(event, context)
|
|
91
95
|
break
|
|
92
96
|
case 'POST_SUBMIT':
|
|
93
97
|
res = await postSubmit(event.comment, context)
|
|
@@ -834,7 +838,7 @@ async function like (id, uid) {
|
|
|
834
838
|
* @param {String} event.pid 回复的 ID
|
|
835
839
|
* @param {String} event.rid 评论楼 ID
|
|
836
840
|
*/
|
|
837
|
-
async function commentSubmit (event) {
|
|
841
|
+
async function commentSubmit (event, context) {
|
|
838
842
|
const res = {}
|
|
839
843
|
// 参数校验
|
|
840
844
|
validate(event, ['url', 'ua', 'comment'])
|
|
@@ -848,7 +852,7 @@ async function commentSubmit (event) {
|
|
|
848
852
|
// 异步垃圾检测、发送评论通知
|
|
849
853
|
try {
|
|
850
854
|
await app.callFunction({
|
|
851
|
-
name:
|
|
855
|
+
name: context.function_name,
|
|
852
856
|
data: { event: 'POST_SUBMIT', comment }
|
|
853
857
|
}, { timeout: 300 }) // 设置较短的 timeout 来实现异步
|
|
854
858
|
} catch (e) {
|
|
@@ -1168,6 +1172,12 @@ async function limitFilter () {
|
|
|
1168
1172
|
|
|
1169
1173
|
// 预垃圾评论检测
|
|
1170
1174
|
function preCheckSpam (comment) {
|
|
1175
|
+
// 长度限制
|
|
1176
|
+
let limitLength = parseInt(config.LIMIT_LENGTH)
|
|
1177
|
+
if (Number.isNaN(limitLength)) limitLength = 500
|
|
1178
|
+
if (limitLength && comment.length > limitLength) {
|
|
1179
|
+
throw new Error('评论内容过长')
|
|
1180
|
+
}
|
|
1171
1181
|
if (config.AKISMET_KEY === 'MANUAL_REVIEW') {
|
|
1172
1182
|
// 人工审核
|
|
1173
1183
|
console.log('已使用人工审核模式,评论审核后才会发表~')
|
|
@@ -1386,9 +1396,9 @@ async function emailTest (event) {
|
|
|
1386
1396
|
const isAdminUser = await isAdmin()
|
|
1387
1397
|
if (isAdminUser) {
|
|
1388
1398
|
try {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
}
|
|
1399
|
+
// 邮件测试前清除 transporter,保证读取的是最新的配置
|
|
1400
|
+
transporter = null
|
|
1401
|
+
await initMailer({ throwErr: true })
|
|
1392
1402
|
const sendResult = await transporter.sendMail({
|
|
1393
1403
|
from: config.SENDER_EMAIL,
|
|
1394
1404
|
to: event.mail || config.BLOGGER_EMAIL || config.SENDER_EMAIL,
|
|
@@ -1410,21 +1420,16 @@ async function uploadImage (event) {
|
|
|
1410
1420
|
const { photo, fileName } = event
|
|
1411
1421
|
const res = {}
|
|
1412
1422
|
try {
|
|
1413
|
-
if (!config.IMAGE_CDN_TOKEN) {
|
|
1423
|
+
if (!config.IMAGE_CDN || !config.IMAGE_CDN_TOKEN) {
|
|
1414
1424
|
throw new Error('未配置图片上传服务')
|
|
1415
1425
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
}
|
|
1423
|
-
})
|
|
1424
|
-
if (uploadResult.data.code === 200) {
|
|
1425
|
-
res.data = uploadResult.data.data
|
|
1426
|
-
} else {
|
|
1427
|
-
throw new Error(uploadResult.data.msg)
|
|
1426
|
+
// tip: qcloud 图床走前端上传,其他图床走后端上传
|
|
1427
|
+
if (config.IMAGE_CDN === '7bu') {
|
|
1428
|
+
await uploadImageTo7Bu({ photo, fileName, config, res })
|
|
1429
|
+
} else if (config.IMAGE_CDN === 'smms') {
|
|
1430
|
+
await uploadImageToSmms({ photo, fileName, config, res })
|
|
1431
|
+
} else if (isUrl(config.IMAGE_CDN)) {
|
|
1432
|
+
await uploadImageToLskyPro({ photo, fileName, config, res })
|
|
1428
1433
|
}
|
|
1429
1434
|
} catch (e) {
|
|
1430
1435
|
console.error(e)
|
|
@@ -1434,6 +1439,64 @@ async function uploadImage (event) {
|
|
|
1434
1439
|
return res
|
|
1435
1440
|
}
|
|
1436
1441
|
|
|
1442
|
+
async function uploadImageTo7Bu ({ photo, fileName, config, res }) {
|
|
1443
|
+
// 去不图床旧版本 https://7bu.top
|
|
1444
|
+
// TODO: 2022 年 4 月 30 日后去不图床将会升级新版本,此处逻辑要同步更新
|
|
1445
|
+
const formData = new FormData()
|
|
1446
|
+
formData.append('image', base64UrlToReadStream(photo, fileName))
|
|
1447
|
+
const uploadResult = await axios.post('https://7bu.top/api/upload', formData, {
|
|
1448
|
+
headers: {
|
|
1449
|
+
...formData.getHeaders(),
|
|
1450
|
+
token: config.IMAGE_CDN_TOKEN
|
|
1451
|
+
}
|
|
1452
|
+
})
|
|
1453
|
+
if (uploadResult.data.code === 200) {
|
|
1454
|
+
res.data = uploadResult.data.data
|
|
1455
|
+
} else {
|
|
1456
|
+
throw new Error(uploadResult.data.msg)
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
async function uploadImageToSmms ({ photo, fileName, config, res }) {
|
|
1461
|
+
// SM.MS 图床 https://sm.ms
|
|
1462
|
+
const formData = new FormData()
|
|
1463
|
+
formData.append('smfile', base64UrlToReadStream(photo, fileName))
|
|
1464
|
+
const uploadResult = await axios.post('https://sm.ms/api/v2/upload', formData, {
|
|
1465
|
+
headers: {
|
|
1466
|
+
...formData.getHeaders(),
|
|
1467
|
+
Authorization: config.IMAGE_CDN_TOKEN
|
|
1468
|
+
}
|
|
1469
|
+
})
|
|
1470
|
+
if (uploadResult.data.success) {
|
|
1471
|
+
res.data = uploadResult.data.data
|
|
1472
|
+
} else {
|
|
1473
|
+
throw new Error(uploadResult.data.message)
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
async function uploadImageToLskyPro ({ photo, fileName, config, res }) {
|
|
1478
|
+
// 自定义兰空图床(v2)URL
|
|
1479
|
+
const formData = new FormData()
|
|
1480
|
+
formData.append('file', base64UrlToReadStream(photo, fileName))
|
|
1481
|
+
const url = `${config.IMAGE_CDN}/api/v1/upload`
|
|
1482
|
+
let token = config.IMAGE_CDN_TOKEN
|
|
1483
|
+
if (!token.startsWith('Bearer')) {
|
|
1484
|
+
token = `Bearer ${token}`
|
|
1485
|
+
}
|
|
1486
|
+
const uploadResult = await axios.post(url, formData, {
|
|
1487
|
+
headers: {
|
|
1488
|
+
...formData.getHeaders(),
|
|
1489
|
+
Authorization: token
|
|
1490
|
+
}
|
|
1491
|
+
})
|
|
1492
|
+
if (uploadResult.data.status) {
|
|
1493
|
+
res.data = uploadResult.data.data
|
|
1494
|
+
res.data.url = res.data.links.url
|
|
1495
|
+
} else {
|
|
1496
|
+
throw new Error(uploadResult.data.message)
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1437
1500
|
function base64UrlToReadStream (base64Url, fileName) {
|
|
1438
1501
|
const base64 = base64Url.split(';base64,').pop()
|
|
1439
1502
|
const path = `/tmp/${fileName}`
|
|
@@ -1441,6 +1504,10 @@ function base64UrlToReadStream (base64Url, fileName) {
|
|
|
1441
1504
|
return fs.createReadStream(path)
|
|
1442
1505
|
}
|
|
1443
1506
|
|
|
1507
|
+
function isUrl (s) {
|
|
1508
|
+
return /^http(s)?:\/\//.test(s)
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1444
1511
|
function getAvatar (comment) {
|
|
1445
1512
|
if (comment.avatar) {
|
|
1446
1513
|
return comment.avatar
|
|
@@ -1497,7 +1564,8 @@ function getConfig () {
|
|
|
1497
1564
|
REQUIRED_FIELDS: config.REQUIRED_FIELDS,
|
|
1498
1565
|
HIDE_ADMIN_CRYPT: config.HIDE_ADMIN_CRYPT,
|
|
1499
1566
|
HIGHLIGHT: config.HIGHLIGHT || 'true',
|
|
1500
|
-
HIGHLIGHT_THEME: config.HIGHLIGHT_THEME
|
|
1567
|
+
HIGHLIGHT_THEME: config.HIGHLIGHT_THEME,
|
|
1568
|
+
LIMIT_LENGTH: config.LIMIT_LENGTH
|
|
1501
1569
|
}
|
|
1502
1570
|
}
|
|
1503
1571
|
}
|
|
@@ -1534,6 +1602,18 @@ async function setConfig (event) {
|
|
|
1534
1602
|
}
|
|
1535
1603
|
}
|
|
1536
1604
|
|
|
1605
|
+
function protect () {
|
|
1606
|
+
// 防御
|
|
1607
|
+
const ip = auth.getClientIP()
|
|
1608
|
+
requestTimes[ip] = (requestTimes[ip] || 0) + 1
|
|
1609
|
+
if (requestTimes[ip] > MAX_REQUEST_TIMES) {
|
|
1610
|
+
console.log(`${ip} 当前请求次数为 ${requestTimes[ip]},已超过最大请求次数`)
|
|
1611
|
+
throw new Error('Too Many Requests')
|
|
1612
|
+
} else {
|
|
1613
|
+
console.log(`${ip} 当前请求次数为 ${requestTimes[ip]}`)
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1537
1617
|
// 读取配置
|
|
1538
1618
|
async function readConfig () {
|
|
1539
1619
|
try {
|
package/package.json
CHANGED