feat:增加手性碳验证方法

This commit is contained in:
Jerry 2025-10-05 21:58:15 +08:00
parent 7b1fe3e402
commit ec0ad4101f
9 changed files with 396 additions and 67 deletions

95
apps/auth-set.js Normal file
View File

@ -0,0 +1,95 @@
import configControl from '../lib/config/configControl.js';
export class carbonAuthSetting extends plugin {
constructor() {
super({
name: 'carbonAuth-setting',
dsc: '手性碳验证设置',
event: 'message.group',
priority: -1000,
rule: [
{ reg: '^#开启验证$', fnc: 'enableAuth' },
{ reg: '^#关闭验证$', fnc: 'disableAuth' },
{ reg: '^#切换验证模式$', fnc: 'switchMode' },
{ reg: '^#设置验证(提示|困难)模式(开启|关闭)$', fnc: 'setCarbonMode' },
{ reg: '^#设置验证次数(\\d+)$', fnc: 'setFrequency' },
{ reg: '^#设置撤回(开启|关闭)$', fnc: 'setRecall' },
],
});
}
//获取奇妙的配置
async _getCfg(e) {
const cfg = (await configControl.get('auth')) || {};
const groupCfg = cfg.groups[e.group_id] || JSON.parse(JSON.stringify(cfg.default));
return { cfg, groupCfg };
}
//保存奇妙的配置
async _saveCfg(e, cfg, groupCfg) {
cfg.groups[e.group_id] = groupCfg;
await configControl.set('auth', cfg);
}
//在制定群开启验证
async enableAuth(e) {
if (!(e.sender.role === 'owner' || e.sender.role === 'admin' || e.isMaster))
return e.reply('只有群主或管理员可以设置验证..', true);
const { cfg, groupCfg } = await this._getCfg(e);
groupCfg.enable = true;
await this._saveCfg(e, cfg, groupCfg);
return e.reply('本群已开启入群验证,验证模式为数字验证..', true);
}
async disableAuth(e) {
if (!(e.sender.role === 'owner' || e.sender.role === 'admin' || e.isMaster))
return e.reply('只有群主或管理员可以设置验证..', true);
const { cfg, groupCfg } = await this._getCfg(e);
groupCfg.enable = false;
await this._saveCfg(e, cfg, groupCfg);
return e.reply('已关闭本群新人验证..', true);
}
async switchMode(e) {
if (!(e.sender.role === 'owner' || e.sender.role === 'admin' || e.isMaster))
return e.reply('只有群主或管理员可以设置验证..', true);
const { cfg, groupCfg } = await this._getCfg(e);
groupCfg.carbon.enable = !groupCfg.carbon.enable;
await this._saveCfg(e, cfg, groupCfg);
return e.reply(
groupCfg.carbon.enable ? '已切换为手性碳验证模式..' : '已切换为数字验证模式..',
true
);
}
async setCarbonMode(e) {
if (!(e.sender.role === 'owner' || e.sender.role === 'admin' || e.isMaster))
return e.reply('只有群主或管理员可以设置验证..', true);
const [, type, state] = e.msg.match(/^#设置验证(提示|困难)模式(开启|关闭)$/);
const { cfg, groupCfg } = await this._getCfg(e);
if (type === '提示') groupCfg.carbon.hint = state === '开启';
if (type === '困难') groupCfg.carbon['hard-mode'] = state === '开启';
await this._saveCfg(e, cfg, groupCfg);
return e.reply(`${state}手性碳${type}模式..`, true);
}
async setFrequency(e) {
if (!(e.sender.role === 'owner' || e.sender.role === 'admin' || e.isMaster))
return e.reply('只有群主或管理员可以设置验证..', true);
const [, num] = e.msg.match(/^#设置验证次数(\d+)$/);
const { cfg, groupCfg } = await this._getCfg(e);
groupCfg.frequency = parseInt(num);
await this._saveCfg(e, cfg, groupCfg);
return e.reply(`已将最大尝试次数设置为 ${num}..`, true);
}
async setRecall(e) {
if (!(e.sender.role === 'owner' || e.sender.role === 'admin' || e.isMaster))
return e.reply('只有群主或管理员可以设置验证..', true);
const [, state] = e.msg.match(/^#设置撤回(开启|关闭)$/);
const { cfg, groupCfg } = await this._getCfg(e);
groupCfg.recall = state === '开启';
await this._saveCfg(e, cfg, groupCfg);
return e.reply(`${state}错误回答自动撤回功能..`, true);
}
}

215
apps/auth.js Normal file
View File

@ -0,0 +1,215 @@
import configControl from '../lib/config/configControl.js';
import axios from 'axios';
import tools from '../components/tool.js';
import Group from '../lib/yunzai/group.js';
import Message from '../lib/yunzai/message.js';
export class CarbonAuth extends plugin {
constructor() {
super({
name: 'carbon-auth',
dsc: '手性碳验证',
event: 'message.group',
priority: -114514,
rule: [
{ reg: '^#绕过验证([\\s\\S]*)?$', fnc: 'cmdBypass' },
{ reg: '^#重新验证([\\s\\S]*)?$', fnc: 'cmdRevalidate' },
],
});
this.pending = new Map();
//答案监听
Bot.on?.('message.group', async (e) => {
const key = `${e.group_id}_${e.user_id}`;
//logger.info(key);
const session = this.pending.get(key);
if (!session) return;
session.tries++;
const { type, answer, tries, cfg } = session;
const pass = async () => {
this.pending.delete(key);
const redisKey = `Yz:pendingWelcome:${e.group_id}:${e.user_id}`;
const cached = await redis.get(redisKey);
if (cached) {
try {
const msgList = JSON.parse(cached);
await e.reply(msgList);
} finally {
await redis.del(redisKey);
}
} else {
return await e.reply('验证通过,欢迎加入本群~', true);
}
};
if (type === 'math') {
const msgStr = (e.message || [])
.filter((m) => m.type === 'text')
.map((m) => m.text)
.join('')
.trim();
const num = parseInt(msgStr, 10);
if (!isNaN(num) && num === answer) return pass();
if (tries >= cfg.frequency) {
this.pending.delete(key);
if (cfg.recall) await Message.deleteMsg(e, e.message_id);
e.reply([segment.at(e.user_id), '验证失败,你错太多次辣!'], true);
return await Group.groupKick(e, e.user_id, e.group_id, false);
}
if (cfg.recall) await Message.deleteMsg(e, e.message_id);
return e.reply(
[segment.at(e.user_id), `回答错了呢,你还有${cfg.frequency - tries}次机会,再试试看?`],
true
);
}
if (type === 'carbon') {
const msgStr = (e.message || [])
.filter((m) => m.type === 'text')
.map((m) => m.text)
.join('');
const msgRegions = msgStr
.toUpperCase()
.replace(//g, ',')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const rightRegions = answer.map((r) => r.toUpperCase());
let correct;
if (cfg.carbon['hard-mode']) {
correct = rightRegions.every((r) => msgRegions.includes(r));
} else {
correct = rightRegions.some((r) => msgRegions.includes(r));
}
if (correct) return pass();
if (tries >= cfg.frequency) {
if (cfg.recall) await Message.deleteMsg(e, e.message_id);
this.pending.delete(key);
e.reply([segment.at(e.user_id), '验证失败,你错太多次辣!'], true);
return await Group.groupKick(e, e.user_id, e.group_id, false);
}
if (cfg.recall) await Message.deleteMsg(e, e.message_id);
return e.reply(
[segment.at(e.user_id), `回答错了呢,你还有${cfg.frequency - tries}次机会,再试试看?`],
true
);
}
});
//主动退群
Bot.on?.('notice.group.decrease', async (e) => {
const key = `${e.group_id}_${e.user_id}`;
if (this.pending.has(key)) {
this.pending.delete(key);
logger.mark(`[crystelf-plugin] 用户 ${e.user_id} 主动退群,验证流程结束..`);
e.reply('害,怎么跑路了');
}
});
//加群事件
Bot.on?.('notice.group.increase', async (e) => {
if (e.isMaster) return true;
await this.auth(e, e.group_id, e.user_id);
});
}
/**
* 验证
* @param e 事件
* @param group_id 群号
* @param user_id 带验证用户id
* @returns {Promise<*>}
*/
async auth(e, group_id, user_id) {
const cfg = await configControl.get('auth');
if (!cfg) return;
const groupCfg = cfg.groups[group_id] || cfg.default;
if (!groupCfg.enable) return;
const key = `${group_id}_${user_id}`;
if (groupCfg.carbon.enable) {
try {
const res = await axios.post(`${cfg.url}/captcha/chiralCarbon/getChiralCarbonCaptcha`, {
answer: true,
hint: groupCfg.carbon.hint,
});
if (!res.data?.data?.data) return e.reply('获取验证图失败,请稍后重试..');
const { base64, regions } = res.data.data.data;
const regionCount = regions.length;
this.pending.set(key, { type: 'carbon', answer: regions, tries: 0, cfg: groupCfg });
e.reply([
segment.at(user_id),
segment.image(base64),
`上图中有一块或多块区域含有手性碳原子\n为了加入本群,你需要在${groupCfg.timeout}秒内正确找出${groupCfg.carbon['hard-mode'] ? '全部含有手性碳的区域' : '其中任意一块包含手性碳的区域'}\n回答的话,直接回复区域代号即可,多个区域用逗号隔开\n提示一下,本图共有${regionCount}块手性碳区域噢..`,
]);
} catch (err) {
logger.error('[crystelf-plugin] 请求手性碳验证API失败..', err);
}
} else {
await tools.sleep(500);
const a = Math.floor(Math.random() * 100);
const b = Math.floor(Math.random() * 100);
const op = Math.random() > 0.5 ? '+' : '-';
const ans = op === '+' ? a + b : a - b;
this.pending.set(key, { type: 'math', answer: ans, tries: 0, cfg: groupCfg });
e.reply([segment.at(user_id), `请在${groupCfg.timeout}秒内发送${a} ${op} ${b}的计算结果..`]);
}
if (groupCfg.timeout > 60) {
setTimeout(
async () => {
if (this.pending.has(key)) {
await e.reply([segment.at(user_id), `小朋友,你还有1分钟的时间完成验证噢~`]);
}
},
(groupCfg.timeout - 60) * 1000
);
}
setTimeout(async () => {
if (this.pending.has(key)) {
this.pending.delete(key);
await e.reply([segment.at(user_id), `小朋友,验证超时啦!请重新申请入群~`]);
await Group.groupKick(e, e.user_id, e.group_id, false);
}
}, groupCfg.timeout * 1000);
}
async cmdBypass(e) {
if (!(e.sender && (e.sender.role === 'owner' || e.sender.role === 'admin' || e.isMaster))) {
return e.reply('只有群主或管理员可以使用此命令..', true);
}
const atElem = (e.message || []).find((m) => m.type === 'at');
if (!atElem || !atElem.qq) return e.reply('你想绕过谁?', true);
const targetId = Number(atElem.qq);
const groupId = e.group_id;
const key = `${groupId}_${targetId}`;
if (this.pending.has(key)) this.pending.delete(key);
const redisKey = `Yz:pendingWelcome:${groupId}:${targetId}`;
const cached = await redis.get(redisKey);
if (cached) {
try {
const msgList = JSON.parse(cached);
await e.reply(msgList);
} finally {
await redis.del(redisKey);
}
} else {
return await e.reply([segment.at(targetId), '欢迎加入本群~'], true);
}
}
async cmdRevalidate(e) {
if (!(e.sender && (e.sender.role === 'owner' || e.sender.role === 'admin' || e.isMaster))) {
return e.reply('只有群主或管理员可以使用此命令..', true);
}
let atElem = (e.message || []).find((m) => m.type === 'at');
if (!atElem || !atElem.qq) return e.reply('你要验证谁?', true);
const targetId = Number(atElem.qq);
await this.auth(e, e.group_id, targetId);
}
}

View File

@ -3,6 +3,7 @@ import tool from '../components/tool.js';
import axios from 'axios'; import axios from 'axios';
import configControl from '../lib/config/configControl.js'; import configControl from '../lib/config/configControl.js';
import ConfigControl from '../lib/config/configControl.js'; import ConfigControl from '../lib/config/configControl.js';
import Group from '../lib/yunzai/group.js';
export default class ChuochuoPlugin extends plugin { export default class ChuochuoPlugin extends plugin {
constructor() { constructor() {
@ -51,13 +52,7 @@ async function pokeMaster(e) {
async function masterPoke(e) { async function masterPoke(e) {
logger.info(`跟主人一起戳!`); logger.info(`跟主人一起戳!`);
if (e.target_id !== e.uin) { if (e.target_id !== e.uin) await Group.groupPoke(e, e.target_id, e.group_id);
await e.bot.sendApi('group_poke', {
group_id: e.group_id,
user_id: e.target_id,
});
}
return true;
} }
async function handleBotPoke(e) { async function handleBotPoke(e) {
@ -75,10 +70,11 @@ async function handleBotPoke(e) {
await e.reply(res.data.data, false, 110); await e.reply(res.data.data, false, 110);
if (Math.random() < replyPoke) { if (Math.random() < replyPoke) {
await tool.sleep(1000); await tool.sleep(1000);
await e.bot.sendApi('group_poke', { group_id: e.group_id, user_id: e.operator_id }); return await Group.groupPoke(e, e.operator_id, e.group_id);
} }
return true;
} else { } else {
await e.reply( return await e.reply(
`戳一戳出错了!${configControl.get('profile')?.nickName}不知道该说啥好了..`, `戳一戳出错了!${configControl.get('profile')?.nickName}不知道该说啥好了..`,
false, false,
{ recallMsg: 60 } { recallMsg: 60 }
@ -86,7 +82,7 @@ async function handleBotPoke(e) {
} }
} catch (err) { } catch (err) {
logger.error('戳一戳请求失败', err); logger.error('戳一戳请求失败', err);
await e.reply( return await e.reply(
`戳一戳出错了!${configControl.get('profile')?.nickName}不知道该说啥好了..`, `戳一戳出错了!${configControl.get('profile')?.nickName}不知道该说啥好了..`,
false, false,
{ recallMsg: 60 } { recallMsg: 60 }

View File

@ -1,4 +1,5 @@
import configControl from '../lib/config/configControl.js'; import configControl from '../lib/config/configControl.js';
import tools from '../components/tool.js';
export class welcomeNewcomer extends plugin { export class welcomeNewcomer extends plugin {
constructor() { constructor() {
@ -10,25 +11,31 @@ export class welcomeNewcomer extends plugin {
}); });
} }
/**
* 新人入群欢迎
* @returns {Promise<void>}
*/
async accept(e) { async accept(e) {
try { try {
await tools.sleep(600);
if (e.user_id === e.self_id) return; if (e.user_id === e.self_id) return;
const groupId = e.group_id; const groupId = e.group_id;
const cdKey = `Yz:newcomers:${groupId}`; const cdKey = `Yz:newcomers:${groupId}`;
if (await redis.get(cdKey)) return; if (await redis.get(cdKey)) return;
await redis.set(cdKey, '1', { EX: 30 }); await redis.set(cdKey, '1', { EX: 30 });
const allCfg = configControl.get('newcomer') || {}; const newcomerCfg = (await configControl.get('newcomer')) || {};
const cfg = allCfg[groupId] || {}; const welcomeCfg = newcomerCfg[groupId] || {};
const authCfg = await configControl.get('auth');
const groupAuthCfg = authCfg?.groups?.[groupId] || authCfg?.default || {};
const msgList = [segment.at(e.user_id)]; const msgList = [segment.at(e.user_id)];
if (cfg.text) msgList.push(cfg.text); if (welcomeCfg.text) msgList.push(welcomeCfg.text);
if (cfg.image) msgList.push(segment.image(cfg.image)); if (welcomeCfg.image) msgList.push(segment.image(welcomeCfg.image));
if (!cfg.text && !cfg.image) msgList.push('欢迎新人~'); if (!welcomeCfg.text && !welcomeCfg.image) msgList.push('欢迎新人~');
if (groupAuthCfg?.enable) {
// 缓存欢迎消息
const redisKey = `Yz:pendingWelcome:${groupId}:${e.user_id}`;
await redis.set(redisKey, JSON.stringify(msgList), { EX: 300 });
return;
}
// 未开启验证
await e.reply(msgList); await e.reply(msgList);
} catch (e) { } catch (err) {
return e.reply('加群欢迎出现错误,请重新设置加群欢迎', true); return e.reply('加群欢迎出现错误,请重新设置加群欢迎', true);
} }
} }

16
config/auth.json Normal file
View File

@ -0,0 +1,16 @@
{
"url": "https://carbon.crystelf.top",
"default": {
"enable": false,
"carbon": {
"enable": false,
"hint": true,
"hard-mode": false
},
"timeout": 180,
"recall": true,
"frequency": 5
},
"groups": {
}
}

View File

@ -2,7 +2,6 @@
"debug": true, "debug": true,
"core": true, "core": true,
"maxFeed": 10, "maxFeed": 10,
"adapter": "lgr",
"poke": true, "poke": true,
"60s": true, "60s": true,
"fanqie": true, "fanqie": true,

View File

@ -1,16 +1,32 @@
import ConfigControl from '../config/configControl.js'; const Group = {
/**
* 群戳一戳
* @param e
* @param user_id 被戳的用户
* @param group_id 群号
* @returns {Promise<*>}
*/
async groupPoke(e, user_id, group_id) {
return await e.bot.sendApi('group_poke', {
group_id: group_id,
user_id: user_id,
});
},
class NapcatGroup {} /**
class LgrGroup {} * 群踢人
* @param e
async function getGroupAdapter() { * @param user_id 要踢的人
const adapter = (await ConfigControl.get('config'))?.adapter; * @param group_id 群号
if (!adapter || adapter === 'nc' || adapter === 'napcat') { * @param ban 是否允许再次加群
return new NapcatGroup(); * @returns {Promise<*>}
} else if (adapter === 'lgr' || adapter === 'lagrange') { */
return new LgrGroup(); async groupKick(e, user_id, group_id, ban) {
} return await e.bot.sendApi('set_group_kick', {
return new NapcatGroup(); user_id: user_id,
} group_id: group_id,
reject_add_request: ban,
export default await getGroupAdapter(); });
},
};
export default Group;

View File

@ -1,16 +1,14 @@
import ConfigControl from '../config/configControl.js'; const Message = {
/**
class NapcatMessage {} * 群撤回消息
class LgrMessage {} * @param e
* @param message_id 消息id
async function getMessageAdapter() { * @returns {Promise<*>}
const adapter = (await ConfigControl.get('config'))?.adapter; */
if (!adapter || adapter === 'nc' || adapter === 'napcat') { async deleteMsg(e, message_id) {
return new NapcatMessage(); return await e.bot.sendApi('delete_msg', {
} else if (adapter === 'lgr' || adapter === 'lagrange') { message_id: message_id,
return new LgrMessage(); });
} },
return new NapcatMessage(); };
} export default Message;
export default await getMessageAdapter();

View File

@ -1,16 +1,3 @@
import ConfigControl from '../config/configControl.js'; const Self = {};
class NapcatSelf {} export default Self;
class LgrSelf {}
async function getSelfAdapter() {
const adapter = (await ConfigControl.get('config'))?.adapter;
if (!adapter || adapter === 'nc' || adapter === 'napcat') {
return new NapcatSelf();
} else if (adapter === 'lgr' || adapter === 'lagrange') {
return new LgrSelf();
}
return new NapcatSelf();
}
export default await getSelfAdapter();