twikoo-vercel 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/api/index.js +97 -17
- package/package.json +1 -1
package/api/index.js
CHANGED
|
@@ -46,6 +46,7 @@ const RES_CODE = {
|
|
|
46
46
|
AKISMET_ERROR: 1030,
|
|
47
47
|
UPLOAD_FAILED: 1040
|
|
48
48
|
}
|
|
49
|
+
const MAX_REQUEST_TIMES = parseInt(process.env.TWIKOO_THROTTLE) || 250
|
|
49
50
|
|
|
50
51
|
// 全局变量 / variables
|
|
51
52
|
let db = null
|
|
@@ -54,15 +55,18 @@ let transporter
|
|
|
54
55
|
let request
|
|
55
56
|
let response
|
|
56
57
|
let accessToken
|
|
58
|
+
const requestTimes = {}
|
|
57
59
|
|
|
58
60
|
module.exports = async (requestArg, responseArg) => {
|
|
59
61
|
request = requestArg
|
|
60
62
|
response = responseArg
|
|
61
63
|
const event = request.body || {}
|
|
64
|
+
console.log('请求IP:', request.headers['x-real-ip'])
|
|
62
65
|
console.log('请求方法:', event.event)
|
|
63
66
|
console.log('请求参数:', event)
|
|
64
67
|
let res = {}
|
|
65
68
|
try {
|
|
69
|
+
protect()
|
|
66
70
|
anonymousSignIn()
|
|
67
71
|
await connectToDatabase(process.env.MONGODB_URI)
|
|
68
72
|
await readConfig()
|
|
@@ -1174,6 +1178,12 @@ async function limitFilter () {
|
|
|
1174
1178
|
|
|
1175
1179
|
// 预垃圾评论检测
|
|
1176
1180
|
function preCheckSpam (comment) {
|
|
1181
|
+
// 长度限制
|
|
1182
|
+
let limitLength = parseInt(config.LIMIT_LENGTH)
|
|
1183
|
+
if (Number.isNaN(limitLength)) limitLength = 500
|
|
1184
|
+
if (limitLength && comment.length > limitLength) {
|
|
1185
|
+
throw new Error('评论内容过长')
|
|
1186
|
+
}
|
|
1177
1187
|
if (config.AKISMET_KEY === 'MANUAL_REVIEW') {
|
|
1178
1188
|
// 人工审核
|
|
1179
1189
|
console.log('已使用人工审核模式,评论审核后才会发表~')
|
|
@@ -1393,9 +1403,9 @@ async function emailTest (event) {
|
|
|
1393
1403
|
const isAdminUser = await isAdmin()
|
|
1394
1404
|
if (isAdminUser) {
|
|
1395
1405
|
try {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1406
|
+
// 邮件测试前清除 transporter,保证读取的是最新的配置
|
|
1407
|
+
transporter = null
|
|
1408
|
+
await initMailer({ throwErr: true })
|
|
1399
1409
|
const sendResult = await transporter.sendMail({
|
|
1400
1410
|
from: config.SENDER_EMAIL,
|
|
1401
1411
|
to: event.mail || config.BLOGGER_EMAIL || config.SENDER_EMAIL,
|
|
@@ -1417,21 +1427,16 @@ async function uploadImage (event) {
|
|
|
1417
1427
|
const { photo, fileName } = event
|
|
1418
1428
|
const res = {}
|
|
1419
1429
|
try {
|
|
1420
|
-
if (!config.IMAGE_CDN_TOKEN) {
|
|
1430
|
+
if (!config.IMAGE_CDN || !config.IMAGE_CDN_TOKEN) {
|
|
1421
1431
|
throw new Error('未配置图片上传服务')
|
|
1422
1432
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
}
|
|
1430
|
-
})
|
|
1431
|
-
if (uploadResult.data.code === 200) {
|
|
1432
|
-
res.data = uploadResult.data.data
|
|
1433
|
-
} else {
|
|
1434
|
-
throw new Error(uploadResult.data.msg)
|
|
1433
|
+
// tip: qcloud 图床走前端上传,其他图床走后端上传
|
|
1434
|
+
if (config.IMAGE_CDN === '7bu') {
|
|
1435
|
+
await uploadImageTo7Bu({ photo, fileName, config, res })
|
|
1436
|
+
} else if (config.IMAGE_CDN === 'smms') {
|
|
1437
|
+
await uploadImageToSmms({ photo, fileName, config, res })
|
|
1438
|
+
} else if (isUrl(config.IMAGE_CDN)) {
|
|
1439
|
+
await uploadImageToLskyPro({ photo, fileName, config, res })
|
|
1435
1440
|
}
|
|
1436
1441
|
} catch (e) {
|
|
1437
1442
|
console.error(e)
|
|
@@ -1441,6 +1446,64 @@ async function uploadImage (event) {
|
|
|
1441
1446
|
return res
|
|
1442
1447
|
}
|
|
1443
1448
|
|
|
1449
|
+
async function uploadImageTo7Bu ({ photo, fileName, config, res }) {
|
|
1450
|
+
// 去不图床旧版本 https://7bu.top
|
|
1451
|
+
// TODO: 2022 年 4 月 30 日后去不图床将会升级新版本,此处逻辑要同步更新
|
|
1452
|
+
const formData = new FormData()
|
|
1453
|
+
formData.append('image', base64UrlToReadStream(photo, fileName))
|
|
1454
|
+
const uploadResult = await axios.post('https://7bu.top/api/upload', formData, {
|
|
1455
|
+
headers: {
|
|
1456
|
+
...formData.getHeaders(),
|
|
1457
|
+
token: config.IMAGE_CDN_TOKEN
|
|
1458
|
+
}
|
|
1459
|
+
})
|
|
1460
|
+
if (uploadResult.data.code === 200) {
|
|
1461
|
+
res.data = uploadResult.data.data
|
|
1462
|
+
} else {
|
|
1463
|
+
throw new Error(uploadResult.data.msg)
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
async function uploadImageToSmms ({ photo, fileName, config, res }) {
|
|
1468
|
+
// SM.MS 图床 https://sm.ms
|
|
1469
|
+
const formData = new FormData()
|
|
1470
|
+
formData.append('smfile', base64UrlToReadStream(photo, fileName))
|
|
1471
|
+
const uploadResult = await axios.post('https://sm.ms/api/v2/upload', formData, {
|
|
1472
|
+
headers: {
|
|
1473
|
+
...formData.getHeaders(),
|
|
1474
|
+
Authorization: config.IMAGE_CDN_TOKEN
|
|
1475
|
+
}
|
|
1476
|
+
})
|
|
1477
|
+
if (uploadResult.data.success) {
|
|
1478
|
+
res.data = uploadResult.data.data
|
|
1479
|
+
} else {
|
|
1480
|
+
throw new Error(uploadResult.data.message)
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
async function uploadImageToLskyPro ({ photo, fileName, config, res }) {
|
|
1485
|
+
// 自定义兰空图床(v2)URL
|
|
1486
|
+
const formData = new FormData()
|
|
1487
|
+
formData.append('file', base64UrlToReadStream(photo, fileName))
|
|
1488
|
+
const url = `${config.IMAGE_CDN}/api/v1/upload`
|
|
1489
|
+
let token = config.IMAGE_CDN_TOKEN
|
|
1490
|
+
if (!token.startsWith('Bearer')) {
|
|
1491
|
+
token = `Bearer ${token}`
|
|
1492
|
+
}
|
|
1493
|
+
const uploadResult = await axios.post(url, formData, {
|
|
1494
|
+
headers: {
|
|
1495
|
+
...formData.getHeaders(),
|
|
1496
|
+
Authorization: token
|
|
1497
|
+
}
|
|
1498
|
+
})
|
|
1499
|
+
if (uploadResult.data.status) {
|
|
1500
|
+
res.data = uploadResult.data.data
|
|
1501
|
+
res.data.url = res.data.links.url
|
|
1502
|
+
} else {
|
|
1503
|
+
throw new Error(uploadResult.data.message)
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1444
1507
|
function base64UrlToReadStream (base64Url, fileName) {
|
|
1445
1508
|
const base64 = base64Url.split(';base64,').pop()
|
|
1446
1509
|
const path = `/tmp/${fileName}`
|
|
@@ -1448,6 +1511,10 @@ function base64UrlToReadStream (base64Url, fileName) {
|
|
|
1448
1511
|
return fs.createReadStream(path)
|
|
1449
1512
|
}
|
|
1450
1513
|
|
|
1514
|
+
function isUrl (s) {
|
|
1515
|
+
return /^http(s)?:\/\//.test(s)
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1451
1518
|
function getAvatar (comment) {
|
|
1452
1519
|
if (comment.avatar) {
|
|
1453
1520
|
return comment.avatar
|
|
@@ -1504,7 +1571,8 @@ async function getConfig () {
|
|
|
1504
1571
|
REQUIRED_FIELDS: config.REQUIRED_FIELDS,
|
|
1505
1572
|
HIDE_ADMIN_CRYPT: config.HIDE_ADMIN_CRYPT,
|
|
1506
1573
|
HIGHLIGHT: config.HIGHLIGHT || 'true',
|
|
1507
|
-
HIGHLIGHT_THEME: config.HIGHLIGHT_THEME
|
|
1574
|
+
HIGHLIGHT_THEME: config.HIGHLIGHT_THEME,
|
|
1575
|
+
LIMIT_LENGTH: config.LIMIT_LENGTH
|
|
1508
1576
|
}
|
|
1509
1577
|
}
|
|
1510
1578
|
}
|
|
@@ -1541,6 +1609,18 @@ async function setConfig (event) {
|
|
|
1541
1609
|
}
|
|
1542
1610
|
}
|
|
1543
1611
|
|
|
1612
|
+
function protect () {
|
|
1613
|
+
// 防御
|
|
1614
|
+
const ip = request.headers['x-real-ip']
|
|
1615
|
+
requestTimes[ip] = (requestTimes[ip] || 0) + 1
|
|
1616
|
+
if (requestTimes[ip] > MAX_REQUEST_TIMES) {
|
|
1617
|
+
console.log(`${ip} 当前请求次数为 ${requestTimes[ip]},已超过最大请求次数`)
|
|
1618
|
+
throw new Error('Too Many Requests')
|
|
1619
|
+
} else {
|
|
1620
|
+
console.log(`${ip} 当前请求次数为 ${requestTimes[ip]}`)
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1544
1624
|
// 读取配置
|
|
1545
1625
|
async function readConfig () {
|
|
1546
1626
|
try {
|
package/package.json
CHANGED