Skip to content

Commit

Permalink
feat: anti spam by captcha & blocked words (#636)
Browse files Browse the repository at this point in the history
  • Loading branch information
imaegoo authored Dec 28, 2023
1 parent ddde134 commit d965eb4
Show file tree
Hide file tree
Showing 19 changed files with 171 additions and 19 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,5 @@ docs/.vitepress/cache
# database
db.json
db.json.*

pnpm-lock.yaml
2 changes: 1 addition & 1 deletion demo/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<div class="field">
<label class="label">语言 | Language: <code>lang</code></label>
<div class="control">
<input class="input" id="lang" type="text" placeholder="e.g en-US" value="en-US">
<input class="input" id="lang" type="text" placeholder="e.g en-US" value="zh-CN">
</div>
</div>
<div class="field">
Expand Down
2 changes: 1 addition & 1 deletion docs/.vitepress/theme/Twikoo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@ onMounted(() => {

<!-- Twikoo -->
<div id="twikoo"></div>
<component :is="'script'" src="https://cdn.jsdelivr.net/npm/[email protected].27/dist/twikoo.min.js" ref="twikooJs"></component>
<component :is="'script'" src="https://cdn.jsdelivr.net/npm/[email protected].28/dist/twikoo.min.js" ref="twikooJs"></component>
</div>
</template>
2 changes: 1 addition & 1 deletion docs/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ exports.main = require('twikoo-func').main
8. 创建完成后,点击“twikoo"进入云函数详情页,进入“函数代码”标签,点击“文件 - 新建文件”,输入 `package.json`,回车
9. 复制以下代码、粘贴到代码框中,点击“保存并安装依赖”
``` json
{ "dependencies": { "twikoo-func": "1.6.27" } }
{ "dependencies": { "twikoo-func": "1.6.28" } }
```

## 腾讯云命令行部署
Expand Down
10 changes: 5 additions & 5 deletions docs/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ twikoo:

``` html
<div id="tcomment"></div>
<script src="https://cdn.staticfile.org/twikoo/1.6.27/twikoo.all.min.js"></script>
<script src="https://cdn.staticfile.org/twikoo/1.6.28/twikoo.all.min.js"></script>
<script>
twikoo.init({
envId: '您的环境id', // 腾讯云环境填 envId;Vercel 环境填地址(https://xxx.vercel.app)
Expand All @@ -103,10 +103,10 @@ twikoo.init({

如果遇到默认 CDN 加载速度缓慢,可更换其他 CDN 镜像。以下为可供选择的公共 CDN,其中一些 CDN 可能需要数天时间同步最新版本:

* `https://cdn.staticfile.org/twikoo/1.6.27/twikoo.all.min.js`
* `https://lib.baomitu.com/twikoo/1.6.27/twikoo.all.min.js`
* `https://cdn.bootcdn.net/ajax/libs/twikoo/1.6.27/twikoo.all.min.js`
* `https://cdn.jsdelivr.net/npm/[email protected].27/dist/twikoo.all.min.js`
* `https://cdn.staticfile.org/twikoo/1.6.28/twikoo.all.min.js`
* `https://lib.baomitu.com/twikoo/1.6.28/twikoo.all.min.js`
* `https://cdn.bootcdn.net/ajax/libs/twikoo/1.6.28/twikoo.all.min.js`
* `https://cdn.jsdelivr.net/npm/[email protected].28/dist/twikoo.all.min.js`

## 开启管理面板(腾讯云环境)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twikoo",
"version": "1.6.27",
"version": "1.6.28",
"description": "A simple comment system.",
"keywords": [
"twikoojs",
Expand Down
24 changes: 24 additions & 0 deletions src/client/utils/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,14 @@ export default {
'Тақиқланган сўзларни созланг. Тақиқланган сўзларни ўз ичига олган шарҳлар автоматик равишда спамга юборилади. Вергул билан ажратинг.',
'禁止語設定、禁止語を含むコンテンツは直ちにスパムコメントとしてマークされます。コンマで区切ってください。'
],
[S.ACI + '_BLOCKED_WORDS']: [
'屏蔽词配置,包含屏蔽词的内容会直接评论失败。英文逗号分隔。',
'屏蔽词配置,包含屏蔽词的内容会直接评论失败。英文逗号分隔。',
'屏蔽词配置,包含屏蔽词的内容会直接评论失败。英文逗号分隔。',
'Configure blocked words. Comments containing blocked words will fail to send. Separate by comma.',
'Configure blocked words. Comments containing blocked words will fail to send. Separate by comma.',
'Configure blocked words. Comments containing blocked words will fail to send. Separate by comma.'
],
[S.ACI + '_GRAVATAR_CDN']: [
'自定义头像 CDN 地址。如:cn.gravatar.com, cravatar.cn, sdn.geekzu.org, gravatar.loli.net,默认:cravatar.cn',
'自定義頭像 CDN 地址。如:cn.gravatar.com, cravatar.cn, sdn.geekzu.org, gravatar.loli.net,預設:cravatar.cn',
Expand Down Expand Up @@ -535,6 +543,22 @@ export default {
'Спам шарҳлар учун билдиришномалар. Стандарт: рост.',
'スパムコメントの通知を送信するかどうか、デフォルト:true'
],
[S.ACI + '_TURNSTILE_SITE_KEY']: [
'Turnstile 验证码的站点密钥。申请地址: https://dash.cloudflare.com/?to=/:account/turnstile',
'Turnstile 验证码的站点密钥。申请地址: https://dash.cloudflare.com/?to=/:account/turnstile',
'Turnstile 验证码的站点密钥。申请地址: https://dash.cloudflare.com/?to=/:account/turnstile',
'Turnstile CAPTCHA Site Key. Get from: https://dash.cloudflare.com/?to=/:account/turnstile',
'Turnstile CAPTCHA Site Key. Get from: https://dash.cloudflare.com/?to=/:account/turnstile',
'Turnstile CAPTCHA Site Key. Get from: https://dash.cloudflare.com/?to=/:account/turnstile'
],
[S.ACI + '_TURNSTILE_SECRET_KEY']: [
'Turnstile 验证码的密钥',
'Turnstile 验证码的密钥',
'Turnstile 验证码的密钥',
'Turnstile CAPTCHA Secret Key',
'Turnstile CAPTCHA Secret Key',
'Turnstile CAPTCHA Secret Key'
],
[S.ACI + '_QCLOUD_SECRET_ID']: [
'腾讯云 secret id,用于垃圾评论检测。同时设置腾讯云和 Akismet 时,只有腾讯云会生效。注册:https://twikoo.js.org/cms.html',
'騰訊雲 secret id,用於垃圾評論檢測。同時設定騰訊雲和 Akismet 時,只有騰訊雲會生效。註冊:https://twikoo.js.org/cms.html',
Expand Down
2 changes: 1 addition & 1 deletion src/client/version.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const version = '1.6.27'
const version = '1.6.28'

export { version }
5 changes: 4 additions & 1 deletion src/client/view/components/TkAdminConfig.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ export default {
{ key: 'LIMIT_PER_MINUTE_ALL', desc: t('ADMIN_CONFIG_ITEM_LIMIT_PER_MINUTE_ALL'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}5`, value: '' },
{ key: 'LIMIT_LENGTH', desc: t('ADMIN_CONFIG_ITEM_LIMIT_LENGTH'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}100`, value: '' },
{ key: 'FORBIDDEN_WORDS', desc: t('ADMIN_CONFIG_ITEM_FORBIDDEN_WORDS'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}快递,空包`, value: '' },
{ key: 'NOTIFY_SPAM', desc: t('ADMIN_CONFIG_ITEM_NOTIFY_SPAM'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}false`, value: '' }
{ key: 'BLOCKED_WORDS', desc: t('ADMIN_CONFIG_ITEM_BLOCKED_WORDS'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}快递,空包`, value: '' },
{ key: 'NOTIFY_SPAM', desc: t('ADMIN_CONFIG_ITEM_NOTIFY_SPAM'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}false`, value: '' },
{ key: 'TURNSTILE_SITE_KEY', desc: t('ADMIN_CONFIG_ITEM_TURNSTILE_SITE_KEY'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}0x4AAAAAAAPLTtpBr_T12345`, value: '' },
{ key: 'TURNSTILE_SECRET_KEY', desc: t('ADMIN_CONFIG_ITEM_TURNSTILE_SECRET_KEY'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}0x4AAAAAAAPLTmBm6gHmOnOqC1iwmU12345`, value: '', secret: true }
]
},
{
Expand Down
45 changes: 45 additions & 0 deletions src/client/view/components/TkSubmit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
size="small"
:disabled="!canSend"
@click="send">{{ isSending ? t('SUBMIT_SENDING') : t('SUBMIT_SEND') }}</el-button>
<div class="tk-turnstile-container" ref="turnstile-container">
<div class="tk-turnstile" id="tk-turnstile"></div>
</div>
</div>
<div class="tk-preview-container" v-if="isPreviewing" v-html="commentHtml" ref="comment-preview"></div>
</div>
Expand Down Expand Up @@ -94,6 +97,7 @@ export default {
nick: '',
mail: '',
link: '',
turnstileLoad: null,
iconMarkdown,
iconEmotion,
iconImage
Expand Down Expand Up @@ -144,6 +148,32 @@ export default {
marked.setOptions({ odata: initMarkedOwo(odata) })
}
},
initTurnstile () {
if (!this.config.TURNSTILE_SITE_KEY) return
this.turnstileLoad = new Promise((resolve, reject) => {
const scriptEl = document.createElement('script')
scriptEl.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'
scriptEl.onload = resolve
scriptEl.onerror = reject
this.$refs['turnstile-container'].appendChild(scriptEl)
})
},
getTurnstileToken () {
return new Promise((resolve, reject) => {
this.turnstileLoad.then(() => {
const widgetId = window.turnstile.render('#tk-turnstile', {
sitekey: this.config.TURNSTILE_SITE_KEY,
callback: (token) => {
resolve(token)
setTimeout(() => {
window.turnstile.remove(widgetId)
}, 5000)
},
'error-callback': reject
})
})
})
},
onMetaUpdate (updates) {
this.nick = updates.meta.nick
this.mail = updates.meta.mail
Expand Down Expand Up @@ -190,6 +220,9 @@ export default {
pid: this.pid ? this.pid : this.replyId,
rid: this.replyId
}
if (this.config.TURNSTILE_SITE_KEY) {
comment.turnstileToken = await this.getTurnstileToken()
}
const sendResult = await call(this.$tcb, 'COMMENT_SUBMIT', comment)
if (sendResult && sendResult.result && sendResult.result.id) {
this.comment = ''
Expand Down Expand Up @@ -346,6 +379,9 @@ export default {
},
'config.COMMENT_BG_IMG': function () {
this.onBgImgChange()
},
'config.TURNSTILE_SITE_KEY': function () {
this.initTurnstile()
}
}
}
Expand Down Expand Up @@ -412,6 +448,15 @@ export default {
background-position: right bottom;
background-repeat: no-repeat;
}
.tk-turnstile-container {
position: absolute;
right: 0;
bottom: -75px;
}
.tk-turnstile {
display: flex;
flex-direction: column;
}
.tk-preview-container {
margin-left: 3rem;
margin-bottom: 1rem;
Expand Down
13 changes: 13 additions & 0 deletions src/server/function/twikoo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const {
getQQAvatar,
getPasswordStatus,
preCheckSpam,
checkTurnstileCaptcha,
getConfig,
getConfigForAdmin,
validate
Expand Down Expand Up @@ -563,6 +564,8 @@ async function commentSubmit (event, context) {
validate(event, ['url', 'ua', 'comment'])
// 限流
await limitFilter()
// 验证码
await checkCaptcha(event)
// 预检测、转换
const data = await parse(event)
// 保存
Expand Down Expand Up @@ -675,6 +678,16 @@ async function limitFilter () {
}
}

async function checkCaptcha (comment) {
if (config.TURNSTILE_SITE_KEY && config.TURNSTILE_SECRET_KEY) {
await checkTurnstileCaptcha({
ip: auth.getClientIP(),
turnstileToken: comment.turnstileToken,
turnstileTokenSecretKey: config.TURNSTILE_SECRET_KEY
})
}
}

async function saveSpamCheckResult (comment, isSpam) {
comment.isSpam = isSpam
if (isSpam) {
Expand Down
2 changes: 1 addition & 1 deletion src/server/function/twikoo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twikoo-func",
"version": "1.6.27",
"version": "1.6.28",
"description": "A simple comment system.",
"author": "imaegoo <[email protected]> (https://github.com/imaegoo)",
"license": "MIT",
Expand Down
30 changes: 28 additions & 2 deletions src/server/function/twikoo/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { URL } = require('url')
const { axios, bowser, ipToRegion, md5 } = require('./lib')
const { axios, FormData, bowser, ipToRegion, md5 } = require('./lib')
const { RES_CODE } = require('./constants')
const ipRegionSearcher = ipToRegion.create() // 初始化 IP 属地
const logger = require('./logger')
Expand Down Expand Up @@ -210,6 +210,16 @@ const fn = {
if (limitLength && comment.length > limitLength) {
throw new Error('评论内容过长')
}
if (config.BLOCKED_WORDS) {
const commentLowerCase = comment.toLowerCase()
const nickLowerCase = nick.toLowerCase()
for (const blockedWord of config.BLOCKED_WORDS.split(',')) {
const blockedWordLowerCase = blockedWord.trim().toLowerCase()
if (commentLowerCase.indexOf(blockedWordLowerCase) !== -1 || nickLowerCase.indexOf(blockedWordLowerCase) !== -1) {
throw new Error('包含屏蔽词')
}
}
}
if (config.AKISMET_KEY === 'MANUAL_REVIEW') {
// 人工审核
logger.info('已使用人工审核模式,评论审核后才会发表~')
Expand All @@ -228,6 +238,21 @@ const fn = {
}
return false
},
async checkTurnstileCaptcha ({ ip, turnstileToken, turnstileTokenSecretKey }) {
try {
const formData = new FormData()
formData.append('secret', turnstileTokenSecretKey)
formData.append('response', turnstileToken)
formData.append('remoteip', ip)
const { data } = await axios.post('https://challenges.cloudflare.com/turnstile/v0/siteverify', formData, {
headers: formData.getHeaders()
})
logger.log('验证码检测结果', data)
if (!data.success) throw new Error('验证码错误')
} catch (e) {
throw new Error('验证码检测失败: ' + e.message)
}
},
async getConfig ({ config, VERSION, isAdmin }) {
return {
code: RES_CODE.SUCCESS,
Expand All @@ -250,7 +275,8 @@ const fn = {
HIDE_ADMIN_CRYPT: config.HIDE_ADMIN_CRYPT,
HIGHLIGHT: config.HIGHLIGHT || 'true',
HIGHLIGHT_THEME: config.HIGHLIGHT_THEME,
LIMIT_LENGTH: config.LIMIT_LENGTH
LIMIT_LENGTH: config.LIMIT_LENGTH,
TURNSTILE_SITE_KEY: config.TURNSTILE_SITE_KEY
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion src/server/netlify/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twikoo-netlify",
"version": "1.6.27",
"version": "1.6.28",
"description": "A simple comment system.",
"author": "imaegoo <[email protected]> (https://github.com/imaegoo)",
"license": "MIT",
Expand Down
13 changes: 13 additions & 0 deletions src/server/self-hosted/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const {
getQQAvatar,
getPasswordStatus,
preCheckSpam,
checkTurnstileCaptcha,
getConfig,
getConfigForAdmin,
validate
Expand Down Expand Up @@ -603,6 +604,8 @@ async function commentSubmit (event, request) {
validate(event, ['url', 'ua', 'comment'])
// 限流
await limitFilter(request)
// 验证码
await checkCaptcha(event, request)
// 预检测、转换
const data = await parse(event, request)
// 保存
Expand Down Expand Up @@ -709,6 +712,16 @@ async function limitFilter (request) {
}
}

async function checkCaptcha (comment, request) {
if (config.TURNSTILE_SITE_KEY && config.TURNSTILE_SECRET_KEY) {
await checkTurnstileCaptcha({
ip: getIp(request),
turnstileToken: comment.turnstileToken,
turnstileTokenSecretKey: config.TURNSTILE_SECRET_KEY
})
}
}

async function saveSpamCheckResult (comment, isSpam) {
comment.isSpam = isSpam
if (isSpam) {
Expand Down
13 changes: 13 additions & 0 deletions src/server/self-hosted/mongo.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const {
getQQAvatar,
getPasswordStatus,
preCheckSpam,
checkTurnstileCaptcha,
getConfig,
getConfigForAdmin,
validate
Expand Down Expand Up @@ -585,6 +586,8 @@ async function commentSubmit (event, request) {
validate(event, ['url', 'ua', 'comment'])
// 限流
await limitFilter(request)
// 验证码
await checkCaptcha(event, request)
// 预检测、转换
const data = await parse(event, request)
// 保存
Expand Down Expand Up @@ -691,6 +694,16 @@ async function limitFilter (request) {
}
}

async function checkCaptcha (comment, request) {
if (config.TURNSTILE_SITE_KEY && config.TURNSTILE_SECRET_KEY) {
await checkTurnstileCaptcha({
ip: getIp(request),
turnstileToken: comment.turnstileToken,
turnstileTokenSecretKey: config.TURNSTILE_SECRET_KEY
})
}
}

async function saveSpamCheckResult (comment, isSpam) {
comment.isSpam = isSpam
if (isSpam) {
Expand Down
4 changes: 2 additions & 2 deletions src/server/self-hosted/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tkserver",
"version": "1.6.27",
"version": "1.6.28",
"description": "A simple comment system.",
"keywords": [
"twikoo",
Expand Down Expand Up @@ -31,7 +31,7 @@
"get-user-ip": "^1.0.1",
"lokijs": "^1.5.12",
"mongodb": "^3.6.3",
"twikoo-func": "1.6.27",
"twikoo-func": "1.6.28",
"uuid": "^8.3.2"
}
}
Loading

0 comments on commit d965eb4

Please sign in to comment.