├── temporary ├── data.json └── index.ts ├── eventsHandle ├── otherPlay │ ├── runtime │ │ ├── child.ts │ │ ├── index.ts │ │ └── runtime.ts │ ├── search │ │ ├── index.ts │ │ ├── interface.ts │ │ └── search.ts │ ├── interface.ts │ ├── index.ts │ ├── landlords.ts │ ├── goodMorning.ts │ ├── goodNight.ts │ ├── getCopyWriting.ts │ ├── getSuggestions.ts │ ├── xiaoqiuChat.ts │ ├── makeSuggestion.ts │ └── listenMusic.ts ├── groupchatAdmin │ ├── setCard │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── setCard.ts │ │ └── tools.ts │ ├── checkDeadPerson │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── tools.ts │ │ ├── relieveCheckDeadPerson.ts │ │ └── checkDeadPerson.ts │ ├── notAllowedSpeak │ │ ├── index.ts │ │ ├── tools.ts │ │ ├── relieveNotAllowedSpeak.ts │ │ └── notAllowedSpeak.ts │ ├── index.ts │ ├── setGroupKick.ts │ ├── alert.ts │ ├── delMemberMessage.ts │ ├── banCommon.ts │ └── setGroupWholeBan.ts ├── scorePlayMethods │ ├── draw │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── draw.ts │ │ ├── tools.ts │ │ └── drawCount.ts │ ├── packsack │ │ ├── index.ts │ │ ├── tools.ts │ │ ├── addScore.ts │ │ └── getPacksack.ts │ ├── propCard │ │ ├── index.ts │ │ ├── banAndDelCard.ts │ │ └── seeCard.ts │ ├── index.ts │ ├── getScoreWay.ts │ ├── getDrawDiscount.ts │ └── changeDrawDiscount.ts ├── groupchat │ ├── index.ts │ ├── askQuestions.ts │ ├── signIn.ts │ └── repeater.ts ├── proxy │ ├── index.ts │ ├── interface.ts │ ├── verifyAllowedSpeak.ts │ └── verifyAnswer.ts ├── xiaoqiu │ ├── index.ts │ ├── tools.ts │ ├── versions.ts │ ├── menu.ts │ ├── isXiaoQiuOnline.ts │ └── switchCommand.ts ├── arguments │ ├── menuHelp.ts │ └── info.ts ├── fns.ts └── index.ts ├── .gitignore ├── assets ├── images │ ├── emoji │ │ ├── 比心2.jpg │ │ ├── 万用表情 │ │ │ └── default.jpg │ │ ├── 代码相关 │ │ │ └── default.jpg │ │ └── 群聊相关 │ │ │ └── default.jpg │ ├── seeCard │ │ └── default.jpg │ ├── groupchat │ │ ├── 第一届线上前端交流会.jpg │ │ ├── dark-menu-jfwf.png │ │ ├── dark-menu-qglcz.png │ │ ├── dark-menu-qlxg.png │ │ ├── dark-menu-qtwf.png │ │ ├── dark-menu-xqxg.png │ │ ├── dark-version-1.png │ │ ├── lights-menu-jfwf.png │ │ ├── lights-menu-qglcz.png │ │ ├── lights-menu-qlxg.png │ │ ├── lights-menu-qtwf.png │ │ ├── lights-menu-xqxg.png │ │ └── lights-version-1.png │ └── xiaoqiu │ │ ├── 浅色模式 │ │ ├── 其它玩法 │ │ │ └── default.jpg │ │ ├── 小秋相关 │ │ │ └── default.jpg │ │ ├── 积分玩法 │ │ │ └── default.jpg │ │ ├── 群聊相关 │ │ │ └── default.jpg │ │ └── 群管理操作 │ │ │ └── default.jpg │ │ └── 深色模式 │ │ ├── 其它玩法 │ │ └── default.jpg │ │ ├── 小秋相关 │ │ └── default.jpg │ │ ├── 积分玩法 │ │ └── default.jpg │ │ ├── 群聊相关 │ │ └── default.jpg │ │ └── 群管理操作 │ │ └── default.jpg ├── readme │ ├── darks │ │ └── default.jpg │ └── lights │ │ └── default.jpg ├── videos │ ├── listen │ │ └── default.jpg │ └── other │ │ └── default.jpg ├── package.json └── xiaoqiu_alpha.sql ├── lib ├── groupchat │ ├── interface.ts │ └── index.ts ├── time │ ├── interface.ts │ ├── index.ts │ └── timeUnitTransform.ts ├── methods │ ├── types.ts │ ├── createImg.ts │ ├── index.ts │ ├── pubsub.ts │ └── tools.ts ├── interface │ ├── methods.ts │ ├── account.ts │ ├── toolsType.ts │ ├── index.ts │ └── command.ts ├── user-copy │ ├── interface.ts │ ├── index.ts │ ├── account.ts │ └── account-alpha.ts ├── oicq │ ├── index.ts │ ├── menuKeywords.ts │ ├── tools.ts │ ├── oicqOperations.ts │ ├── interface.ts │ └── registerEvents.ts └── Temp │ ├── temp.ts │ └── temp-comment.ts ├── .idea ├── misc.xml ├── modules.xml ├── bot-master.iml └── workspace.xml ├── babel.config.js ├── database ├── forms │ ├── index.ts │ ├── user_packsack.ts │ ├── switch_commands.ts │ ├── suggestions.ts │ ├── groups_config.ts │ └── interface.ts ├── interface.ts ├── index.ts └── tools.ts ├── app.ts ├── timing ├── index.ts └── clearCurMaxScore.ts ├── package.json └── README.md /temporary/data.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/runtime/child.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript 3 | */ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略所有node_modules文件夹 2 | node_modules/ 3 | # 仅忽略项目根目录下的xiaoqiu文件夹 4 | /xiaoqiu 5 | /lib/user -------------------------------------------------------------------------------- /assets/images/emoji/比心2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/emoji/比心2.jpg -------------------------------------------------------------------------------- /eventsHandle/otherPlay/runtime/index.ts: -------------------------------------------------------------------------------- 1 | import { runtime } from './runtime' 2 | 3 | export { runtime } 4 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/setCard/index.ts: -------------------------------------------------------------------------------- 1 | import { setCard } from './setCard' 2 | 3 | export { setCard } 4 | -------------------------------------------------------------------------------- /assets/images/seeCard/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/seeCard/default.jpg -------------------------------------------------------------------------------- /assets/readme/darks/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/readme/darks/default.jpg -------------------------------------------------------------------------------- /assets/readme/lights/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/readme/lights/default.jpg -------------------------------------------------------------------------------- /assets/videos/listen/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/videos/listen/default.jpg -------------------------------------------------------------------------------- /assets/videos/other/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/videos/other/default.jpg -------------------------------------------------------------------------------- /eventsHandle/otherPlay/search/index.ts: -------------------------------------------------------------------------------- 1 | import { searchJueJin } from './search' 2 | 3 | export { searchJueJin } 4 | -------------------------------------------------------------------------------- /temporary/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 机器人开机时的临时任务,大多用于开发环境中,例如统计数据等 3 | */ 4 | 5 | const t = () => {} 6 | 7 | export { t } 8 | -------------------------------------------------------------------------------- /assets/images/emoji/万用表情/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/emoji/万用表情/default.jpg -------------------------------------------------------------------------------- /assets/images/emoji/代码相关/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/emoji/代码相关/default.jpg -------------------------------------------------------------------------------- /assets/images/emoji/群聊相关/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/emoji/群聊相关/default.jpg -------------------------------------------------------------------------------- /assets/images/groupchat/第一届线上前端交流会.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/第一届线上前端交流会.jpg -------------------------------------------------------------------------------- /lib/groupchat/interface.ts: -------------------------------------------------------------------------------- 1 | type MemberName = { 2 | nickname: string 3 | card: string 4 | } 5 | 6 | export { MemberName } 7 | -------------------------------------------------------------------------------- /assets/images/groupchat/dark-menu-jfwf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/dark-menu-jfwf.png -------------------------------------------------------------------------------- /assets/images/groupchat/dark-menu-qglcz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/dark-menu-qglcz.png -------------------------------------------------------------------------------- /assets/images/groupchat/dark-menu-qlxg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/dark-menu-qlxg.png -------------------------------------------------------------------------------- /assets/images/groupchat/dark-menu-qtwf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/dark-menu-qtwf.png -------------------------------------------------------------------------------- /assets/images/groupchat/dark-menu-xqxg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/dark-menu-xqxg.png -------------------------------------------------------------------------------- /assets/images/groupchat/dark-version-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/dark-version-1.png -------------------------------------------------------------------------------- /assets/images/xiaoqiu/浅色模式/其它玩法/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/浅色模式/其它玩法/default.jpg -------------------------------------------------------------------------------- /assets/images/xiaoqiu/浅色模式/小秋相关/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/浅色模式/小秋相关/default.jpg -------------------------------------------------------------------------------- /assets/images/xiaoqiu/浅色模式/积分玩法/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/浅色模式/积分玩法/default.jpg -------------------------------------------------------------------------------- /assets/images/xiaoqiu/浅色模式/群聊相关/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/浅色模式/群聊相关/default.jpg -------------------------------------------------------------------------------- /assets/images/xiaoqiu/深色模式/其它玩法/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/深色模式/其它玩法/default.jpg -------------------------------------------------------------------------------- /assets/images/xiaoqiu/深色模式/小秋相关/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/深色模式/小秋相关/default.jpg -------------------------------------------------------------------------------- /assets/images/xiaoqiu/深色模式/积分玩法/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/深色模式/积分玩法/default.jpg -------------------------------------------------------------------------------- /assets/images/xiaoqiu/深色模式/群聊相关/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/深色模式/群聊相关/default.jpg -------------------------------------------------------------------------------- /assets/images/groupchat/lights-menu-jfwf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/lights-menu-jfwf.png -------------------------------------------------------------------------------- /assets/images/groupchat/lights-menu-qglcz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/lights-menu-qglcz.png -------------------------------------------------------------------------------- /assets/images/groupchat/lights-menu-qlxg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/lights-menu-qlxg.png -------------------------------------------------------------------------------- /assets/images/groupchat/lights-menu-qtwf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/lights-menu-qtwf.png -------------------------------------------------------------------------------- /assets/images/groupchat/lights-menu-xqxg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/lights-menu-xqxg.png -------------------------------------------------------------------------------- /assets/images/groupchat/lights-version-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/groupchat/lights-version-1.png -------------------------------------------------------------------------------- /assets/images/xiaoqiu/浅色模式/群管理操作/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/浅色模式/群管理操作/default.jpg -------------------------------------------------------------------------------- /assets/images/xiaoqiu/深色模式/群管理操作/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/777miss/xiao-qiu/HEAD/assets/images/xiaoqiu/深色模式/群管理操作/default.jpg -------------------------------------------------------------------------------- /lib/time/interface.ts: -------------------------------------------------------------------------------- 1 | type UnitEng = 'mins' | 'hours' | 'hours' 2 | type UnitChi = '分钟' | '小时' | '天' 3 | 4 | export { 5 | UnitEng, 6 | UnitChi 7 | } 8 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/draw/index.ts: -------------------------------------------------------------------------------- 1 | import { draw } from './draw' 2 | import { drawCount } from './drawCount' 3 | 4 | export { 5 | draw, 6 | drawCount 7 | } 8 | -------------------------------------------------------------------------------- /eventsHandle/groupchat/index.ts: -------------------------------------------------------------------------------- 1 | import { askQuestions } from './askQuestions' 2 | import { signIn } from './signIn' 3 | 4 | export { 5 | askQuestions, 6 | signIn 7 | } 8 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/packsack/index.ts: -------------------------------------------------------------------------------- 1 | import { getPacksack } from './getPacksack' 2 | import { addScore } from './addScore' 3 | 4 | export { 5 | getPacksack, 6 | addScore 7 | } 8 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/propCard/index.ts: -------------------------------------------------------------------------------- 1 | import { banAndDelCard } from './banAndDelCard' 2 | import { seeCard } from './seeCard' 3 | 4 | export { 5 | banAndDelCard, 6 | seeCard 7 | } 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /eventsHandle/proxy/index.ts: -------------------------------------------------------------------------------- 1 | import { verifyAnswer } from './verifyAnswer' 2 | import { verifyAllowedSpeak } from './verifyAllowedSpeak' 3 | 4 | export { 5 | verifyAnswer, 6 | verifyAllowedSpeak 7 | } 8 | -------------------------------------------------------------------------------- /lib/methods/types.ts: -------------------------------------------------------------------------------- 1 | type BasisType = string | number | boolean 2 | interface TupleType { 3 | (...args: T): T 4 | } 5 | const tuple: TupleType = (...args) => args 6 | 7 | export { tuple } 8 | -------------------------------------------------------------------------------- /lib/interface/methods.ts: -------------------------------------------------------------------------------- 1 | import { self } from '../user' 2 | 3 | // 转发聊天记录 4 | type ForwardRecord = { 5 | user_id: typeof self.uin 6 | nickname: string 7 | message: string 8 | } 9 | 10 | export { ForwardRecord } 11 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/checkDeadPerson/index.ts: -------------------------------------------------------------------------------- 1 | import { checkDeadPerson } from './checkDeadPerson' 2 | import { relieveCheckDeadPerson } from './relieveCheckDeadPerson' 3 | 4 | export { 5 | checkDeadPerson, 6 | relieveCheckDeadPerson 7 | } 8 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/notAllowedSpeak/index.ts: -------------------------------------------------------------------------------- 1 | import { notAllowedSpeak } from './notAllowedSpeak' 2 | import { relieveNotAllowedSpeak } from './relieveNotAllowedSpeak' 3 | 4 | export { 5 | notAllowedSpeak, 6 | relieveNotAllowedSpeak 7 | } 8 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/typescript'], 3 | plugins: [ 4 | '@babel/plugin-transform-modules-commonjs', 5 | '@babel/proposal-class-properties', 6 | '@babel/proposal-object-rest-spread' 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /lib/user-copy/interface.ts: -------------------------------------------------------------------------------- 1 | type GroupchatConfigArrs = [T] extends V ? T : never 2 | 3 | type InAuthId = T extends { [propsName: string]: infer V } ? V[] : never 4 | type AuthIdInType = InAuthId 5 | 6 | export { 7 | GroupchatConfigArrs, 8 | AuthIdInType 9 | } 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /eventsHandle/xiaoqiu/index.ts: -------------------------------------------------------------------------------- 1 | import { isXiaoQiuOnline } from './isXiaoQiuOnline' 2 | import { menu } from './menu' 3 | import { switchCommand } from './switchCommand' 4 | import { versions } from './versions' 5 | 6 | export { 7 | isXiaoQiuOnline, 8 | menu, 9 | switchCommand, 10 | versions 11 | } 12 | -------------------------------------------------------------------------------- /lib/oicq/index.ts: -------------------------------------------------------------------------------- 1 | import { menu, versions } from './menuKeywords' 2 | import { bot, segment, operations } from './oicqOperations' 3 | import { registerEvents } from './registerEvents' 4 | 5 | export { 6 | menu, 7 | versions, 8 | bot, 9 | segment, 10 | operations, 11 | registerEvents 12 | } 13 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/checkDeadPerson/interface.ts: -------------------------------------------------------------------------------- 1 | type TopicInfo = { 2 | topic: string 3 | answer: string[] 4 | times: number 5 | isFailed: boolean 6 | } 7 | type CheckDeadPerson = { 8 | token: NodeJS.Timeout 9 | } & TopicInfo 10 | 11 | export { 12 | TopicInfo, 13 | CheckDeadPerson 14 | } 15 | -------------------------------------------------------------------------------- /database/forms/index.ts: -------------------------------------------------------------------------------- 1 | import { groups_config } from './groups_config' 2 | import { suggestions } from './suggestions' 3 | import { switch_commands } from './switch_commands' 4 | import { user_packsack } from './user_packsack' 5 | 6 | export { 7 | groups_config, 8 | suggestions, 9 | switch_commands, 10 | user_packsack 11 | } 12 | -------------------------------------------------------------------------------- /app.ts: -------------------------------------------------------------------------------- 1 | import { timing } from './timing' 2 | import { registerEvents } from './lib/oicq' 3 | import { t } from './temporary' 4 | import { groupchatsId } from './lib/user' 5 | import { toInitFormData } from './database/tools' 6 | 7 | // 临时任务 8 | t() 9 | // 定时任务 10 | timing() 11 | // 初始化数据 12 | groupchatsId.forEach(id => toInitFormData(id)) 13 | // 注册事件 14 | registerEvents() 15 | -------------------------------------------------------------------------------- /lib/user-copy/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | self, 3 | groupchatsId, 4 | autoGroupchatsName, 5 | connectionDbConfig, 6 | AuthId, 7 | AuthIdType, 8 | GroupsId 9 | } from './account-alpha' 10 | 11 | export { 12 | self, 13 | groupchatsId, 14 | autoGroupchatsName, 15 | connectionDbConfig, 16 | AuthId, 17 | AuthIdType, 18 | GroupsId 19 | } 20 | -------------------------------------------------------------------------------- /lib/methods/createImg.ts: -------------------------------------------------------------------------------- 1 | import images from 'images' 2 | 3 | type CreateImg = (urls: { init: string; save: string }) => void 4 | // 以指定图片为样本,生成一张它的副本 5 | const createImg: CreateImg = urls => { 6 | const { init, save } = urls 7 | const width = images(init).width() 8 | const height = images(init).height() 9 | images(width, height).draw(images(init), 0, 0).save(save) 10 | } 11 | 12 | export { createImg } 13 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/notAllowedSpeak/tools.ts: -------------------------------------------------------------------------------- 1 | import type { GroupchatsId } from '../../../lib/interface' 2 | 3 | // 存储当前正处于禁止发言期间的成员 4 | const persons = new Map([]) 5 | // 检测某位成员是否已经处于禁止发言期间 6 | const judgeMemberNotSpeak = (gId: GroupchatsId, uId: number) => { 7 | const id = String(gId) + String(uId) 8 | return !!persons.get(id) 9 | } 10 | 11 | export { 12 | persons, 13 | judgeMemberNotSpeak 14 | } 15 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/draw/interface.ts: -------------------------------------------------------------------------------- 1 | type CardResult = { 2 | ban: number 3 | delMsg: number 4 | immune: number 5 | see: number 6 | } 7 | type AllResult = { 8 | score: number 9 | card: CardResult 10 | } 11 | type LucklyResult = { 12 | all: AllResult 13 | advance: { 14 | explain: string 15 | } 16 | } 17 | 18 | export { 19 | CardResult, 20 | AllResult, 21 | LucklyResult 22 | } 23 | -------------------------------------------------------------------------------- /eventsHandle/proxy/interface.ts: -------------------------------------------------------------------------------- 1 | import type { GroupchatsId } from '../../lib/interface' 2 | import type { OicqOperations } from '../../lib/oicq/interface' 3 | 4 | // 禁止发言 5 | type ProxyArgs = { 6 | bot: any 7 | gId: GroupchatsId 8 | uId: number 9 | operations: OicqOperations 10 | raw_message: string 11 | } 12 | type VerifyProxy = (info: ProxyArgs) => boolean 13 | 14 | export { 15 | ProxyArgs, 16 | VerifyProxy 17 | } 18 | -------------------------------------------------------------------------------- /lib/groupchat/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | sexArr, 3 | sexBelong, 4 | areaAll, 5 | areaAbbr, 6 | keywordsReg, 7 | randomProvince, 8 | nicknameFormCard, 9 | formSpecificProvinceToShort 10 | } from './groupNickname' 11 | 12 | export { 13 | sexArr, 14 | sexBelong, 15 | areaAll, 16 | areaAbbr, 17 | keywordsReg, 18 | randomProvince, 19 | nicknameFormCard, 20 | formSpecificProvinceToShort 21 | } 22 | -------------------------------------------------------------------------------- /lib/Temp/temp.ts: -------------------------------------------------------------------------------- 1 | import type { SendContent, CommandFn } from '../interface' 2 | 3 | const sendContent: SendContent = { 4 | name: '小秋你好', 5 | reg: /reg/gi, 6 | role: 'member', 7 | member: () => [], 8 | admin: () => [], 9 | owner: () => [], 10 | deverDefined: () => [[], []], 11 | equal: () => [], 12 | level: () => [] 13 | } 14 | const fn: CommandFn = originData => { } 15 | const commandFileName = { fn, sendContent } 16 | 17 | export { commandFileName } 18 | -------------------------------------------------------------------------------- /eventsHandle/proxy/verifyAllowedSpeak.ts: -------------------------------------------------------------------------------- 1 | import { judgeMemberNotSpeak } from '../groupchatAdmin/notAllowedSpeak/tools' 2 | 3 | import type { VerifyProxy } from './interface' 4 | 5 | const verifyAllowedSpeak: VerifyProxy = info => { 6 | const { gId, uId, operations: { delMsg } } = info 7 | const isHave = judgeMemberNotSpeak(gId, uId) 8 | if (!isHave) return false 9 | // 处于禁止发言期间,直接撤回消息 10 | delMsg(gId, uId, 1) 11 | return true 12 | } 13 | 14 | export { verifyAllowedSpeak } 15 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/checkDeadPerson/tools.ts: -------------------------------------------------------------------------------- 1 | import type { GroupchatsId } from '../../../lib/interface' 2 | import type { CheckDeadPerson } from './interface' 3 | 4 | // 存储当前人机验证的成员(包含其所对应的题目及答案) 5 | const persons = new Map([]) 6 | // 判断某位成员是否已经处于人机验证环节中 7 | const inCheckDeadOf = (gId: GroupchatsId, oId: number) => { 8 | const id = String(gId) + String(oId) 9 | return !!persons.get(id) 10 | } 11 | 12 | export { 13 | persons, 14 | inCheckDeadOf 15 | } 16 | -------------------------------------------------------------------------------- /.idea/bot-master.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/interface/account.ts: -------------------------------------------------------------------------------- 1 | import { AuthId, GroupsId } from '../user' 2 | 3 | // 小秋的账号与密码 4 | type BotAccount = { 5 | readonly uin: number 6 | readonly pwd: string 7 | } 8 | // 要监听群聊的ID、要修改群昵称的群聊ID 9 | type GroupchatsId = GroupsId['groupchatsId'] 10 | type AutoGroupchatsName = GroupsId['autoGroupchatsName'] 11 | // 小秋的群昵称格式 12 | type VersionsRule = `小秋 | Release v${number}.${number}.${number}` 13 | 14 | export { 15 | AuthId, 16 | BotAccount, 17 | GroupchatsId, 18 | AutoGroupchatsName, 19 | VersionsRule 20 | } 21 | -------------------------------------------------------------------------------- /lib/interface/toolsType.ts: -------------------------------------------------------------------------------- 1 | type GetObject = { 2 | [k: string]: T 3 | } 4 | type ChoiceProps = { 5 | [k in keyof T]?: T[k] 6 | } 7 | type TransformStringFormat = T extends `${infer P}_${infer N}` 8 | ? `${P}${Capitalize}` 9 | : never 10 | type UnderlineTransformHumpKeys = { 11 | [k in keyof object as k extends string ? TransformStringFormat : never]: T[k] 12 | } 13 | 14 | export { 15 | GetObject, 16 | ChoiceProps, 17 | UnderlineTransformHumpKeys 18 | } 19 | -------------------------------------------------------------------------------- /timing/index.ts: -------------------------------------------------------------------------------- 1 | import { bot, versions } from '../lib/oicq' 2 | import { clearCurMaxScore } from './clearCurMaxScore' 3 | import { self, groupchatsId, autoGroupchatsName } from '../lib/user' 4 | 5 | const { uin } = self 6 | // 定时任务 7 | const timing = () => { 8 | // 每次启动时,5分钟后自动将群昵称修改为当前版本的版本号 9 | setTimeout( 10 | () => autoGroupchatsName.forEach(v => bot.setGroupCard(v, uin, versions)), 11 | 1000 * 60 * 5 12 | ) 13 | // 凌晨12点自动清空每日积分获取上限 14 | clearCurMaxScore(groupchatsId) 15 | } 16 | 17 | export { timing } 18 | -------------------------------------------------------------------------------- /eventsHandle/xiaoqiu/tools.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | const groupchatUrl = './assets/images/groupchat' 4 | // 获取对应模式下的菜单 5 | // true代表浅色,false代表深色 6 | const getModeImg = (classify: string[][], type: string, mode: boolean) => { 7 | // arr为一维数组,例如[init, init] 8 | // init代表每个模块所对应的路径 9 | const arr: string[][] = [] 10 | const curMode = mode ? 'lights' : 'dark' 11 | classify.forEach(v => arr.push([path.resolve(`${groupchatUrl}/${curMode}-${type}-${v[0]}.png`), v[1]])) 12 | return arr 13 | } 14 | 15 | export { getModeImg } 16 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/index.ts: -------------------------------------------------------------------------------- 1 | import { changeDrawDiscount } from './changeDrawDiscount' 2 | import { draw, drawCount } from './draw' 3 | import { getDrawDiscount } from './getDrawDiscount' 4 | import { getScoreWay } from './getScoreWay' 5 | import { getPacksack, addScore } from './packsack' 6 | import { banAndDelCard, seeCard } from './propCard' 7 | 8 | export { 9 | changeDrawDiscount, 10 | draw, 11 | drawCount, 12 | getDrawDiscount, 13 | getPacksack, 14 | addScore, 15 | banAndDelCard, 16 | seeCard, 17 | getScoreWay 18 | } 19 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/interface.ts: -------------------------------------------------------------------------------- 1 | // 文案指令 2 | type YiYanApi = { 3 | from: string 4 | from_who: string 5 | hitokoto: string 6 | } 7 | // 听歌指令 8 | type MusicResCode = { 9 | code: number 10 | } 11 | type MusicId = MusicResCode & { 12 | result: { 13 | songs: [ 14 | { 15 | id: string 16 | } 17 | ] 18 | } 19 | } 20 | type MusicUrl = MusicResCode & { 21 | data: [ 22 | { 23 | url: string 24 | } 25 | ] 26 | } 27 | 28 | export { 29 | YiYanApi, 30 | MusicId, 31 | MusicUrl 32 | } 33 | -------------------------------------------------------------------------------- /lib/time/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | belong, 3 | secToFormat, 4 | getDate, 5 | getWholeDate, 6 | getCurTime, 7 | getCurTimeAlias, 8 | getAssignTimestamp, 9 | transformTimeNameAlias, 10 | transformTimeNameEn, 11 | getSpecificTimeMS, 12 | getCurDarkOrLights 13 | } from './timeUnitTransform' 14 | 15 | export { 16 | belong, 17 | secToFormat, 18 | getDate, 19 | getWholeDate, 20 | getCurTime, 21 | getCurTimeAlias, 22 | getAssignTimestamp, 23 | transformTimeNameAlias, 24 | transformTimeNameEn, 25 | getSpecificTimeMS, 26 | getCurDarkOrLights 27 | } 28 | -------------------------------------------------------------------------------- /lib/methods/index.ts: -------------------------------------------------------------------------------- 1 | import { createImg } from './createImg' 2 | import { pubsub } from './pubsub' 3 | import { 4 | getRandomScope, 5 | getRandom, 6 | returnOneOfContent, 7 | otherIdFromCq, 8 | disruptOrder, 9 | getRandomDigitNumberStr, 10 | isDecimals, 11 | formatNumCount 12 | } from './tools' 13 | import { tuple } from './types' 14 | 15 | export { 16 | createImg, 17 | pubsub, 18 | getRandomScope, 19 | getRandom, 20 | returnOneOfContent, 21 | otherIdFromCq, 22 | disruptOrder, 23 | getRandomDigitNumberStr, 24 | isDecimals, 25 | formatNumCount, 26 | tuple 27 | } 28 | -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xiaoqiu", 3 | "version": "2.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node main.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "combine-async-error": "^0.2.0", 15 | "images": "^3.2.4", 16 | "mysql": "^2.18.1", 17 | "oicq": "^1.13.1", 18 | "protobufjs": "^7.0.0", 19 | "request": "^2.88.2", 20 | "superagent": "^8.0.0" 21 | }, 22 | "devDependencies": {} 23 | } 24 | -------------------------------------------------------------------------------- /lib/oicq/menuKeywords.ts: -------------------------------------------------------------------------------- 1 | import { commandsConfig } from '../../eventsHandle/fns' 2 | 3 | import type { GroupsSwitchCommands } from '../../database/forms/interface' 4 | import type { VersionsRule } from '../interface' 5 | import type { CommandsConfig } from '../../eventsHandle/fns' 6 | 7 | type Commands = (keyof CommandsConfig)[] 8 | 9 | const versions: VersionsRule = '小秋 | Release v2.0.0' 10 | const menu = {} as GroupsSwitchCommands 11 | const commandsFnNames = Object.keys(commandsConfig) as Commands 12 | commandsFnNames.forEach(fnName => { 13 | const key = commandsConfig[fnName].sendContent.name 14 | menu[key] = true 15 | }) 16 | 17 | export { 18 | menu, 19 | versions 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xiaoqiu", 3 | "version": "2.0.0", 4 | "description": "", 5 | "main": "babel.config.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@types/node": "^18.7.3" 16 | }, 17 | "dependencies": { 18 | "@babel/plugin-transform-modules-commonjs": "^7.18.6", 19 | "@types/images": "^3.2.1", 20 | "@types/mysql": "^2.15.21", 21 | "@types/request": "^2.48.8", 22 | "@types/superagent": "^4.1.15" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/getScoreWay.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [获取积分方式]指令: 3 | * 查询获取积分的途径有哪些 4 | */ 5 | import type { SendContent, CommandFn } from '../../lib/interface' 6 | 7 | const way = `①通过每日签到\n[详见:菜单-群聊相关]\n\n②为小秋提出有效建议(15积分)\n[详见:菜单-群聊相关-提建议]\n\n③为小秋找出有效bug(30积分)\n[详见:菜单-群聊相关-提建议]` 8 | const sendContent: SendContent = { 9 | name: '获取积分方式', 10 | reg: [ 11 | /^获取积分$/, 12 | /^获取积分方式$/ 13 | ], 14 | role: 'member', 15 | deverDefined: () => [ 16 | [ 17 | `现有的积分获取方式为:\n${way}`, 18 | `小秋查询到的积分获取方式是:\n${way}` 19 | ] 20 | ] 21 | } 22 | const fn: CommandFn = () => ({ items: 1 }) 23 | const getScoreWay = { fn, sendContent } 24 | 25 | export { getScoreWay } 26 | -------------------------------------------------------------------------------- /lib/oicq/tools.ts: -------------------------------------------------------------------------------- 1 | // 使用数组结构替代数据库中存储的群聊消息 2 | import { groupchatsId } from '../user' 3 | 4 | import type { PreGroupchatMessage } from '../../database/forms/interface' 5 | import type { GroupchatsId } from '../interface' 6 | 7 | type CacheGroupMsg = { 8 | [k in GroupchatsId]: PreGroupchatMessage[] 9 | } 10 | 11 | const cacheGroupMsg = {} as CacheGroupMsg 12 | groupchatsId.forEach(id => (cacheGroupMsg[id] = [])) 13 | // 只缓存最近100条消息 14 | const addCacheMsgRows = (groupId: GroupchatsId, content: PreGroupchatMessage) => { 15 | if (cacheGroupMsg[groupId].length >= 100) cacheGroupMsg[groupId].shift() 16 | cacheGroupMsg[groupId].push(content) 17 | } 18 | 19 | export { 20 | cacheGroupMsg, 21 | addCacheMsgRows 22 | } 23 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/index.ts: -------------------------------------------------------------------------------- 1 | import { alert } from './alert' 2 | import { banCommon } from './banCommon' 3 | import { checkDeadPerson, relieveCheckDeadPerson } from './checkDeadPerson' 4 | import { notAllowedSpeak, relieveNotAllowedSpeak } from './notAllowedSpeak' 5 | import { delMemberMessage } from './delMemberMessage' 6 | import { setCard } from './setCard' 7 | import { setGroupKick } from './setGroupKick' 8 | import { setGroupWholeBan } from './setGroupWholeBan' 9 | 10 | export { 11 | alert, 12 | banCommon, 13 | checkDeadPerson, 14 | relieveCheckDeadPerson, 15 | notAllowedSpeak, 16 | relieveNotAllowedSpeak, 17 | delMemberMessage, 18 | setCard, 19 | setGroupKick, 20 | setGroupWholeBan 21 | } 22 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/index.ts: -------------------------------------------------------------------------------- 1 | import { runtime } from './runtime' 2 | import { getCopyWriting } from './getCopyWriting' 3 | import { goodMorning } from './goodMorning' 4 | import { goodNight } from './goodNight' 5 | import { getSuggestions } from './getSuggestions' 6 | import { landlords } from './landlords' 7 | import { listenMusic } from './listenMusic' 8 | import { makeSuggestion } from './makeSuggestion' 9 | import { xiaoqiuChat } from './xiaoqiuChat' 10 | import { searchJueJin } from './search' 11 | 12 | export { 13 | runtime, 14 | getCopyWriting, 15 | goodNight, 16 | getSuggestions, 17 | landlords, 18 | goodMorning, 19 | listenMusic, 20 | makeSuggestion, 21 | xiaoqiuChat, 22 | searchJueJin 23 | } 24 | -------------------------------------------------------------------------------- /eventsHandle/arguments/menuHelp.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import { getCurDarkOrLights } from '../../lib/time' 4 | import { returnOneOfContent } from '../../lib/methods' 5 | 6 | import type { OicqOperations } from '../../lib/oicq/interface' 7 | 8 | const lightsCommandUrl = './assets/images/xiaoqiu/浅色模式/小秋相关/0.jpg' 9 | const darkCommandUrl = './assets/images/xiaoqiu/深色模式/小秋相关/0.jpg' 10 | 11 | const menuUseCommandImgUrl = getCurDarkOrLights() ? lightsCommandUrl : darkCommandUrl 12 | const getMenuHelp = (promiseImage: OicqOperations['promiseImage']) => 13 | returnOneOfContent([ 14 | `哎呀 不要来烦小秋哦~,有事看[菜单]`, 15 | `烦死了 别艾特我 有事看[菜单]哦~`, 16 | `小秋给您准备了[菜单]指令哦~\n${promiseImage(path.resolve(menuUseCommandImgUrl))}`, 17 | `艾特回去` 18 | ]) 19 | 20 | export { getMenuHelp } 21 | -------------------------------------------------------------------------------- /lib/interface/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthId, 3 | BotAccount, 4 | GroupchatsId, 5 | AutoGroupchatsName, 6 | VersionsRule 7 | } from './account' 8 | import { 9 | Role, 10 | LowestRole, 11 | GroupAt, 12 | MemberBasisInfo, 13 | OriginMemberBasisInfo, 14 | SendContent, 15 | GroupBasisInfo, 16 | InfoTemp, 17 | OriginData, 18 | CommandFn 19 | } from './command' 20 | import { ForwardRecord } from './methods' 21 | 22 | export { 23 | AuthId, 24 | BotAccount, 25 | GroupchatsId, 26 | AutoGroupchatsName, 27 | VersionsRule, 28 | 29 | Role, 30 | LowestRole, 31 | GroupAt, 32 | MemberBasisInfo, 33 | OriginMemberBasisInfo, 34 | SendContent, 35 | GroupBasisInfo, 36 | InfoTemp, 37 | OriginData, 38 | CommandFn, 39 | ForwardRecord 40 | } 41 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/search/interface.ts: -------------------------------------------------------------------------------- 1 | type Articles = { 2 | flag: boolean 3 | article: string[] 4 | } 5 | type Model = { 6 | result_model: { 7 | article_info: { 8 | title: string 9 | article_id: string 10 | brief_content: string 11 | view_count: number 12 | digg_count: number 13 | comment_count: number 14 | } 15 | author_user_info: { 16 | user_name: string 17 | } 18 | } 19 | } 20 | type JueJinSearch = { 21 | data: Model[] 22 | err_no: number 23 | } 24 | type ArticleTemp = ( 25 | id: string, 26 | title: string, 27 | abstract: string, 28 | author: string, 29 | viewCounts: number, 30 | likeCounts: number, 31 | commentCounts: number 32 | ) => string 33 | 34 | export { 35 | Articles, 36 | Model, 37 | JueJinSearch, 38 | ArticleTemp 39 | } 40 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/landlords.ts: -------------------------------------------------------------------------------- 1 | /**、 2 | * [斗地主]指令: 3 | * 发送指定关键词后,小秋会随机发送一条有关斗地主的语句,仅作为一种娱乐玩法而存在 4 | */ 5 | import type { SendContent, CommandFn } from '../../lib/interface' 6 | 7 | const arr = [ 8 | '十七张牌,你能秒我?', 9 | '得得得得得得得得得得', 10 | '搞快点 搞快点', 11 | '这四百万是我最后的倔强', 12 | '心态崩了呀', 13 | '就这?', 14 | '这谁顶得住啊', 15 | '顶不住也要顶', 16 | '我能怎么办,我也很绝望啊', 17 | '快点啊,等到我花儿都谢了', 18 | '你的牌打的也太好了', 19 | '你是MM还是GG', 20 | '大家好,很高兴见到各位', 21 | '不要走,决战到天亮', 22 | '不好意思,我要离开一会', 23 | '嘿嘿 这局小秋一定会赢~' 24 | ] 25 | 26 | const sendContent: SendContent = { 27 | name: '斗地主', 28 | reg: /^((王炸)|(炸弹)|(单牌)|(对牌)|(三张牌)|(三带一)|(三带二)|(三带一对)|(单顺)|(双顺)|(三顺)|(飞机带翅膀)|(飞机))$/, 29 | role: 'member', 30 | member: () => arr, 31 | admin: () => arr, 32 | owner: () => arr 33 | } 34 | const fn: CommandFn = () => { } 35 | const landlords = { fn, sendContent } 36 | 37 | export { landlords } 38 | -------------------------------------------------------------------------------- /lib/user-copy/account.ts: -------------------------------------------------------------------------------- 1 | // 正式版配置 2 | import { tuple } from '../methods/types' 3 | 4 | import type { AuthIdInType } from './interface' 5 | import type { BotAccount } from '../interface' 6 | 7 | // 账号与密码 8 | const self: BotAccount = { 9 | uin: 111111, 10 | pwd: '111111' 11 | } 12 | const g1 = 222 13 | // 要监听的群聊ID 14 | const groupchatsId = tuple(g1) 15 | // 要修改群昵称的群聊ID 16 | const autoGroupchatsName = tuple(g1) 17 | // 数据库配置 18 | const connectionDbConfig = { 19 | host: '', 20 | user: '', 21 | password: '', 22 | database: '' 23 | } 24 | // 绝对权限(特定用户) 25 | const AuthId = { 26 | FuncJin: 333 27 | } as const 28 | type AuthIdType = AuthIdInType 29 | interface GroupsId { 30 | groupchatsId: typeof g1 31 | autoGroupchatsName: typeof g1 32 | } 33 | 34 | export { 35 | self, 36 | groupchatsId, 37 | autoGroupchatsName, 38 | connectionDbConfig, 39 | AuthId, 40 | AuthIdType, 41 | GroupsId 42 | } 43 | -------------------------------------------------------------------------------- /lib/user-copy/account-alpha.ts: -------------------------------------------------------------------------------- 1 | // 测试版配置 2 | import { tuple } from '../methods/types' 3 | 4 | import type { AuthIdInType } from './interface' 5 | import type { BotAccount } from '../interface' 6 | 7 | // 账号与密码 8 | const self: BotAccount = { 9 | uin: 111111, 10 | pwd: '111111' 11 | } 12 | const g1 = 222 13 | // 要监听的群聊ID 14 | const groupchatsId = tuple(g1) 15 | // 要修改群昵称的群聊ID 16 | const autoGroupchatsName = tuple(g1) 17 | // 数据库配置 18 | const connectionDbConfig = { 19 | host: '', 20 | user: '', 21 | password: '', 22 | database: '' 23 | } 24 | // 绝对权限(特定用户) 25 | const AuthId = { 26 | FuncJin: 333 27 | } as const 28 | type AuthIdType = AuthIdInType 29 | interface GroupsId { 30 | groupchatsId: typeof g1 31 | autoGroupchatsName: typeof g1 32 | } 33 | 34 | export { 35 | self, 36 | groupchatsId, 37 | autoGroupchatsName, 38 | connectionDbConfig, 39 | AuthId, 40 | AuthIdType, 41 | GroupsId 42 | } 43 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/setCard/interface.ts: -------------------------------------------------------------------------------- 1 | import type { GroupchatsId, OriginData } from '../../../lib/interface' 2 | 3 | type Province = { 4 | province: '京' 5 | sex: string 6 | nickname: string 7 | } 8 | type Sex = 'male' | 'female' | 'unknown' 9 | type BeAtInfoGroupSex = { 10 | data: { 11 | sex: Sex 12 | card: string 13 | } 14 | } 15 | type BeAtInfoGroupCard = { 16 | data: { 17 | area: string 18 | sex: Sex 19 | card: string 20 | nickname: string 21 | } 22 | } 23 | 24 | type ChangeCard = ( 25 | bot: OriginData['bot'], 26 | groupId: GroupchatsId, 27 | userId: number 28 | ) => Promise 29 | type IsCardRule = ( 30 | bot: OriginData['bot'], 31 | groupId: GroupchatsId, 32 | userId: number 33 | ) => Promise<{ flag: boolean, reason: string }> 34 | 35 | export { 36 | Province, 37 | BeAtInfoGroupSex, 38 | BeAtInfoGroupCard, 39 | ChangeCard, 40 | IsCardRule 41 | } -------------------------------------------------------------------------------- /lib/methods/pubsub.ts: -------------------------------------------------------------------------------- 1 | class PubSub { 2 | // map: [[eventName, fn]] 3 | container = new Map([]) 4 | // 发布 5 | publish(name: string, data: unknown) { 6 | /** 7 | * 发布事件有两种情况: 8 | * 1. 当前事件已被订阅,则触发其事件 9 | * 2. 当前事件未被订阅,则抛出错误 10 | */ 11 | const fn = this.container.get(name) 12 | if (!fn) throw new TypeError(`${name}事件未被订阅`) 13 | // 每个订阅的函数都会收到其发布的数据,及当前事件名 14 | const args = [data, name] 15 | fn(...args) 16 | } 17 | // 订阅 18 | subscribe(name: string, fn: Function) { 19 | this.container.set(name, fn) 20 | } 21 | // 取消订阅 22 | unsubscribe(name: string) { 23 | /** 24 | * 取消订阅有两种情况: 25 | * 1. 当前事件已被订阅过,则在container中删除该事件 26 | * 2. 当前事件未被订阅,则抛出错误 27 | */ 28 | const have = this.container.get(name) 29 | if (!have) throw new TypeError(`${name}事件属于无效事件`) 30 | this.container.delete(name) 31 | } 32 | } 33 | const pubsub = new PubSub() 34 | 35 | export { pubsub } 36 | -------------------------------------------------------------------------------- /database/interface.ts: -------------------------------------------------------------------------------- 1 | import { getDataBaseData } from './tools' 2 | 3 | // 每个指令至少接收到的操作数据库的方法 4 | type OperationsBasisSet = { 5 | recordRow: 'recordRow' 6 | updateData: 'updateData' 7 | retrieveData: 'retrieveData' 8 | } 9 | type ProcessOperationsSet = T & OperationsBasisSet 10 | 11 | type GroupsConfigOperationsSet = ProcessOperationsSet<{ 12 | name: 'groups_config' 13 | }> 14 | type SuggestionsOperationsSet = ProcessOperationsSet<{ 15 | name: 'suggestions' 16 | rowSuggestion: 'rowSuggestion' 17 | }> 18 | type SwitchCommandsOperationsSet = ProcessOperationsSet<{ 19 | name: 'switch_commands' 20 | }> 21 | type UserPacksackOperationsSet = ProcessOperationsSet<{ 22 | name: 'user_packsack' 23 | }> 24 | 25 | type DataBase = { 26 | getDataBaseData: typeof getDataBaseData 27 | formSet: { 28 | groups_config: GroupsConfigOperationsSet 29 | suggestions: SuggestionsOperationsSet 30 | switch_commands: SwitchCommandsOperationsSet 31 | user_packsack: UserPacksackOperationsSet 32 | } 33 | } 34 | 35 | export { DataBase } 36 | -------------------------------------------------------------------------------- /timing/clearCurMaxScore.ts: -------------------------------------------------------------------------------- 1 | import { getDate, getWholeDate } from '../lib/time' 2 | import { getDataBaseData, formSet } from '../database' 3 | import { GroupchatsId } from '../lib/interface' 4 | 5 | const { user_packsack } = formSet 6 | 7 | // 凌晨12点清除积分上限 8 | const clearCurMaxScore = (groupchatsId: GroupchatsId[]) => { 9 | const time = new Date() 10 | const dur = 1000 * 60 * 60 * 24 11 | const curDateTemp = +new Date(getWholeDate(time)) 12 | const nextDateTemp = +new Date(getDate(time)) + dur 13 | const clear = async () => { 14 | for (let i = 0, len = groupchatsId.length; i < len; i++) { 15 | const curGroupPacksack = await getDataBaseData(user_packsack.name, user_packsack.retrieveData)(groupchatsId[i]) 16 | Object.keys(curGroupPacksack).forEach(me => (curGroupPacksack[me].curInc = 0)) 17 | await getDataBaseData(user_packsack.name, user_packsack.updateData)(groupchatsId[i], curGroupPacksack) 18 | } 19 | setTimeout(clear, dur) 20 | } 21 | setTimeout(clear, nextDateTemp - curDateTemp) 22 | } 23 | 24 | export { clearCurMaxScore } 25 | -------------------------------------------------------------------------------- /database/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | databaseSet, 3 | getDataBaseData, 4 | operationSql, 5 | toInitFormData 6 | } from './tools' 7 | 8 | import type { DataBase } from './interface' 9 | 10 | const basis = { 11 | recordRow: 'recordRow', 12 | updateData: 'updateData', 13 | retrieveData: 'retrieveData' 14 | } as const 15 | const basisGroupsConfig = { 16 | name: 'groups_config', 17 | ...basis 18 | } as const 19 | const basisSuggestions = { 20 | name: 'suggestions', 21 | rowSuggestion: 'rowSuggestion', 22 | ...basis 23 | } as const 24 | const basisSwitchCommands = { 25 | name: 'switch_commands', 26 | ...basis 27 | } as const 28 | const basisUserPacksack = { 29 | name: 'user_packsack', 30 | ...basis 31 | } as const 32 | 33 | // 用于在读取数据时获取对应的表名及操作 34 | const formSet: DataBase['formSet'] = { 35 | groups_config: basisGroupsConfig, 36 | suggestions: basisSuggestions, 37 | switch_commands: basisSwitchCommands, 38 | user_packsack: basisUserPacksack 39 | } 40 | 41 | export { 42 | databaseSet, 43 | getDataBaseData, 44 | operationSql, 45 | toInitFormData, 46 | formSet 47 | } 48 | -------------------------------------------------------------------------------- /database/forms/user_packsack.ts: -------------------------------------------------------------------------------- 1 | import { operationSql } from '../tools' 2 | 3 | import type { GroupchatsId } from '../../lib/interface' 4 | import type { DataBaseDataType, GroupsUserPacksack } from './interface' 5 | 6 | type UserPacksackReocrd = Promise 7 | type UserPacksackUpdate = Promise 8 | type UserPacksackRetrieve = Promise 9 | 10 | const create = { 11 | recordRow: async (id: GroupchatsId): UserPacksackReocrd => 12 | await operationSql(`insert into user_packsack(group_id,whole_user_id) values('${id}','{}')`) 13 | } 14 | const update = { 15 | updateData: async (id: GroupchatsId, wholeUserId: GroupsUserPacksack): UserPacksackUpdate => 16 | await operationSql(`update user_packsack set whole_user_id='${JSON.stringify(wholeUserId)}' where group_id='${id}'`) 17 | } 18 | const retrieve = { 19 | retrieveData: async (id: GroupchatsId): UserPacksackRetrieve => 20 | await operationSql(`select whole_user_id from user_packsack where group_id=${id}`) 21 | } 22 | const user_packsack = { ...create, ...update, ...retrieve } 23 | 24 | export { user_packsack } 25 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/goodMorning.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [早]指令: 3 | * 问候 4 | */ 5 | import path from 'path' 6 | 7 | import type { SendContent, CommandFn } from '../../lib/interface' 8 | 9 | const sendContent: SendContent = { 10 | name: '早', 11 | reg: [ 12 | /^早$/, 13 | /^早小秋$/, 14 | /^小秋早$/, 15 | /^小秋早安$/, 16 | /^早,小秋$/ 17 | ], 18 | role: 'member', 19 | deverDefined: ({ user: { name }, defined: { img }, operations: { at } }) => [ 20 | [ 21 | `${at('user')} 早 小秋才刚睡醒哦~`, 22 | `早 ${name}`, 23 | `${name},早安` 24 | ], 25 | [ 26 | `${at('user')} 不早了,干活吧${img}`, 27 | `${name},这还早啥???赶紧上号${img}`, 28 | `${at('user')} 不早了哦,一起来了解下React18新特性吧~${img}` 29 | ] 30 | ] 31 | } 32 | const fn: CommandFn = originData => { 33 | const { operations: { promiseImage } } = originData 34 | const hours = new Date().getHours() 35 | if (hours >= 6 && hours <= 10) return { items: 1 } 36 | const url = path.resolve('./assets/images/emoji/代码相关/goVScode.jpg') 37 | const img = promiseImage(url) 38 | return { items: 2, args: { img } } 39 | } 40 | const goodMorning = { fn, sendContent } 41 | 42 | export { goodMorning } 43 | -------------------------------------------------------------------------------- /database/forms/switch_commands.ts: -------------------------------------------------------------------------------- 1 | import { menu } from '../../lib/oicq' 2 | import { operationSql } from '../tools' 3 | 4 | import type { GroupchatsId } from '../../lib/interface' 5 | import type { DataBaseDataType, GroupsSwitchCommands } from './interface' 6 | 7 | type SwitchCommandsRecord = Promise 8 | type SwitchCommandsUpdate = Promise 9 | type SwitchCommandsRetrieve = Promise 10 | 11 | const create = { 12 | recordRow: async (id: GroupchatsId): SwitchCommandsRecord => 13 | await operationSql(`insert into switch_commands(group_id,commands) values('${id}','${JSON.stringify(menu)}')`) 14 | } 15 | const update = { 16 | updateData: async (id: GroupchatsId, commands: GroupsSwitchCommands): SwitchCommandsUpdate => 17 | await operationSql(`update switch_commands set commands='${JSON.stringify(commands)}' where group_id='${id}'`) 18 | } 19 | const retrieve = { 20 | retrieveData: async (id: GroupchatsId): SwitchCommandsRetrieve => 21 | await operationSql(`select commands from switch_commands where group_id=${id}`) 22 | } 23 | const switch_commands = { ...create, ...update, ...retrieve } 24 | 25 | export { switch_commands } 26 | -------------------------------------------------------------------------------- /database/forms/suggestions.ts: -------------------------------------------------------------------------------- 1 | import { operationSql } from '../tools' 2 | 3 | import type { DataBaseDataType, SuggestionFormat } from './interface' 4 | 5 | type GroupsSuggestion = SuggestionFormat[] 6 | type SuggestionsRecord = Promise 7 | type SuggestionsUpdate = Promise 8 | type SuggestionsRowData = Promise 9 | type SuggestionsRetrieve = Promise 10 | 11 | const create = { 12 | recordRow: async (id: number, sug: GroupsSuggestion): SuggestionsRecord => 13 | await operationSql(`insert into suggestions(user_id,suggestion) values('${id}','${JSON.stringify(sug)}')`) 14 | } 15 | const update = { 16 | updateData: async (id: number, sug: GroupsSuggestion): SuggestionsUpdate => 17 | await operationSql(`update suggestions set suggestion='${JSON.stringify(sug)}' where user_id='${id}'`) 18 | } 19 | const retrieve = { 20 | rowSuggestion: async (id: number): SuggestionsRowData => 21 | await operationSql(`select suggestion from suggestions where user_id=${id}`), 22 | retrieveData: async (): SuggestionsRetrieve => 23 | await operationSql(`select user_id,suggestion from suggestions`, true) 24 | } 25 | const suggestions = { ...create, ...update, ...retrieve } 26 | 27 | export { suggestions } 28 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/goodNight.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [晚安]指令: 3 | * 问候 4 | */ 5 | import request from 'request' 6 | 7 | import { getCurTime } from '../../lib/time' 8 | 9 | import type { SendContent, CommandFn } from '../../lib/interface' 10 | 11 | const sendContent: SendContent = { 12 | name: '晚', 13 | reg: [ 14 | /^晚安小秋$/, 15 | /^小秋晚安$/, 16 | /^晚安$/, 17 | /^晚安兄弟们$/ 18 | ], 19 | role: 'member', 20 | deverDefined: ({ user: { name, sex }, defined: { hours, mins, text }, operations: { at } }) => [ 21 | [ 22 | `${at('user')} 这??都${hours}:${mins}了,这个年纪你能睡得着吗?`, 23 | `${at('user')} ${hours}:${mins},这个年纪你睡得下吗?`, 24 | `${at('user')} ${sex ? '哥哥' : '姐姐'}先睡吧!${hours}:${mins}分,小秋这时候还在上班哦` 25 | ], 26 | [ 27 | `${at('user')} ${text}`, 28 | `${name},${text}` 29 | ] 30 | ] 31 | } 32 | const fn: CommandFn = () => 33 | new Promise(resolve => { 34 | const [hours, mins] = getCurTime().split(':') 35 | const nHours = Number(hours) 36 | if (nHours > 6 && nHours < 21) return resolve({ items: 1, args: { hours, mins } }) 37 | request.get('https://api.lovelive.tools/api/SweetNothings', (err: unknown, req: unknown, text: string) => { 38 | resolve({ items: 2, args: { text } }) 39 | }) 40 | }) 41 | const goodNight = { fn, sendContent } 42 | 43 | export { goodNight } 44 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/getDrawDiscount.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [查积分抽奖折扣]指令: 3 | * 查询当前群聊中积分抽奖池的折扣数及折扣时间 4 | */ 5 | import { getWholeDate } from '../../lib/time' 6 | import { isDiscount } from './draw/tools' 7 | 8 | import type { SendContent, CommandFn } from '../../lib/interface' 9 | 10 | const sendContent: SendContent = { 11 | name: '查询限时折扣', 12 | reg: [ 13 | /^查询积分抽奖折扣$/, 14 | /^查询积分抽奖池折扣$/, 15 | /^积分抽奖池折扣$/, 16 | /^积分抽奖折扣$/, 17 | /^抽奖折扣$/, 18 | /^积分折扣$/ 19 | ], 20 | role: 'member', 21 | deverDefined: ({ operations: { at }, defined: { discount, lastTime } }) => [ 22 | [ 23 | `${at('user')} 小秋查询到当前尚不处于折扣期间哦~`, 24 | `${at('user')} 积分抽奖池当前未进行限时折扣`, 25 | `${at('user')} 小秋发现当前不属于折扣期间` 26 | ], 27 | [ 28 | `${at('user')} 哇 现在正是奖池的折扣时间呀!${lastTime}前都是${discount}折哦~`, 29 | `${at('user')} 小秋听说${lastTime}前都是${discount}折呦`, 30 | `${at('user')} 这不巧了嘛 ${lastTime}前,积分抽奖池都是${discount}折的折扣哦~` 31 | ] 32 | ] 33 | } 34 | const fn: CommandFn = async originData => { 35 | const { group } = originData 36 | const { flag, discount, timestamp } = await isDiscount(group.id) 37 | if (!flag) return { items: 1 } 38 | const lastTime = getWholeDate(new Date(timestamp!)) 39 | return { items: 2, args: { discount, lastTime } } 40 | } 41 | const getDrawDiscount = { fn, sendContent } 42 | 43 | export { getDrawDiscount } 44 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/setGroupKick.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [踢]指令: 3 | * 将指定成员从群聊中移除 4 | */ 5 | import type { SendContent, CommandFn } from '../../lib/interface' 6 | 7 | const sendContent: SendContent = { 8 | name: '踢', 9 | reg: /^踢\[CQ:at,qq=(?\d*),text=.*\]\s*$/, 10 | role: 'admin', 11 | member: ({ user: { sex }, operations: { at } }) => [ 12 | `${at('user')} ${sex ? '哥哥' : '姐姐'},等你当管理or成为群主就行了`, 13 | `${at('user')} 抱歉 您权限不足`, 14 | `${at('user')} 小秋检测到您暂无此权限` 15 | ], 16 | deverDefined: ({ other: { name }, operations: { at } }) => [ 17 | [ 18 | `${at('user')} 已撤回1小时内${name}发送的所有消息并将其踢出了群聊`, 19 | `${at('user')} 小秋已撤回1小时内${name}发送的所有消息并将其踢出了群聊`, 20 | `${at('user')} ${name}已被您撤回1小时内所有消息并被踢出了群聊` 21 | ] 22 | ], 23 | equal: ({ user: { name }, operations: { at } }) => [ 24 | `${at('user')} 对不起 小秋无法帮您进行踢出`, 25 | `${at('other')} ${name}居然要把你踢出去`, 26 | `${at('user')} 小秋暂时无法帮您使用[踢]指令` 27 | ], 28 | level: ({ operations: { at } }) => [ 29 | `${at('user')} 抱歉 小秋权限不足 暂时不能帮您执行踢出操作哦~`, 30 | `${at('user')} 呜呜 小秋权限不足 暂时不能帮您执行踢出操作哦~` 31 | ] 32 | } 33 | const fn: CommandFn = originData => { 34 | const { bot, group, other, operations } = originData 35 | operations.delMsg(group.id, other.id, 0) 36 | bot.setGroupKick(group.id, other.id) 37 | return { items: 1 } 38 | } 39 | const setGroupKick = { fn, sendContent } 40 | 41 | export { setGroupKick } 42 | -------------------------------------------------------------------------------- /eventsHandle/groupchat/askQuestions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [问题格式]指令: 3 | * 当某群成员的问题格式不是理想格式时,可以使用此指令来提醒群成员 4 | */ 5 | import type { SendContent, CommandFn } from '../../lib/interface' 6 | 7 | const stop = '①遇到的问题是什么\n②出错的截图+代码\n③想实现的效果大概是?' 8 | 9 | const sendContent: SendContent = { 10 | name: '问题格式', 11 | reg: [ 12 | /^问题格式$/, 13 | /^问问题的格式$/, 14 | /^问题方式$/, 15 | /^怎样请教$/, 16 | /^问题形式$/, 17 | /^问问题都不会吗$/ 18 | ], 19 | role: 'member', 20 | member: ({ user: { name } }) => [ 21 | `${name}温馨提示,向群友请教问题的基本步骤是:\n${stop}`, 22 | `${name}与小秋一致认为,向群友请教问题的基本步骤是:\n${stop}`, 23 | `哎呀 同志,问问题的时候应该要有三个基本步骤哦~\n${stop}`, 24 | `小秋认为请教问题的时候至少要符合以下三个基本步骤\n${stop}`, 25 | `你看这个步骤,它又完整又详细\n${stop}\n\n[听说在请教问题时,遵循以上步骤会有意想不到的收获]`, 26 | `请教问题的正确姿势~\n${stop}` 27 | ], 28 | admin: ({ user: { sex, name } }) => [ 29 | `${name}以管理员的身份提醒群友,请教问题时至少要有以下格式:\n${stop}`, 30 | `${name}管理${sex ? '哥哥' : '姐姐'}觉得向群友请教问题的基本步骤必须要:\n${stop}`, 31 | `哎呀 宝贝,问问题的时候应该要有三个基本步骤哦~\n${stop}`, 32 | `${stop}\n\n[${name}与小秋打听到在请教问题时,遵循以上步骤会有意想不到的收获哦~]`, 33 | `请教问题的正确姿势(非ban必选)~\n${stop}` 34 | ], 35 | owner: ({ user: { sex, name } }) => [ 36 | `群主${name}带着正确的请教步骤来了!\n${stop}`, 37 | `${name}群主${sex ? '哥哥' : '姐姐'}觉得向群友请教问题的基本步骤是:\n${stop}`, 38 | `群主为大家打听到了问问题的时候至少要有的三个基本步骤\n${stop}`, 39 | `家人们 群主发话了,问问题时必须要:\n${stop}` 40 | ] 41 | } 42 | const fn: CommandFn = () => { } 43 | const askQuestions = { fn, sendContent } 44 | 45 | export { askQuestions } 46 | -------------------------------------------------------------------------------- /lib/Temp/temp-comment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 每个指令都对应着sendContent配置对象与fn处理函数,其中 3 | * - sendContent标识该指令的触发方式、触发角色、返回的内容等 4 | * - fn则表示该指令会触发的处理函数(fn会收到固定参数originData,originData中包含了用到的所有基本信息) 5 | */ 6 | // 限制sendContent与fn函数的形参、及其返回值 7 | import type { SendContent, CommandFn } from '../interface' 8 | 9 | // sendContent为一个配置对象 10 | const sendContent: SendContent = { 11 | // 当前指令的名称,用于开启/关闭当前指令 12 | name: '小秋你好', 13 | // reg代表触发该指令的正则。其值可以是一个正则表达式,也可以是一个由正则表达式组成的数组 14 | reg: /reg/gi, 15 | // role代表发起者应拥有怎样的角色才可以触发该指令。其值必须是一个最低角色权限(如果role的值为数字数组,则代表只有特定用户可以触发该指令) 16 | // 例如role: [123789456],代表只有账号为123789456的用户可以触发该指令 17 | role: 'member', 18 | /** 19 | * member函数、admin函数、owner函数、deverDefined函数、equal函数、level函数: 20 | * 以上函数的形参全部遵循infoTemp类型 21 | * 仅deverDefined函数的infoTemp中多了一项defined 22 | */ 23 | // 当发起者是member时,会自动调用该函数。member必须返回一个字符串数组,当发送消息时,会从当前数组中随机抽取一条进行发送 24 | member: () => [], 25 | // 当发起者是admin时,会自动调用该函数。admin必须返回一个字符串数组,当发送消息时,会从当前数组中随机抽取一条进行发送 26 | admin: () => [], 27 | // 当发起者是owner时,会自动调用该函数。owner必须返回一个字符串数组,当发送消息时,会从当前数组中随机抽取一条进行发送 28 | owner: () => [], 29 | // 不管发起者是谁,只要处理函数fn中返回了{ items },则自动调用该函数。 30 | // 且deverDefined必须返回一个二维字符串数组,当发送消息时,会从当前数组(二维数组)中的一个随机数组中随机抽取一条进行发送 31 | deverDefined: () => [[], []], 32 | // 当机器人小秋与被执行人权限相同时,会自动触发该函数 33 | equal: () => [], 34 | // 表示当前指令对小秋的最低权限要求 35 | level: () => [] 36 | } 37 | // 当前指令的事件处理函数 38 | const fn: CommandFn = originData => { } 39 | // 对其进行组合(指令的函数名称应当与所在文件的名称一致) 40 | const commandFileName = { fn, sendContent } 41 | 42 | export { commandFileName } 43 | -------------------------------------------------------------------------------- /database/forms/groups_config.ts: -------------------------------------------------------------------------------- 1 | import { operationSql } from '../tools' 2 | 3 | import type { GroupsConfig, DataBaseDataType } from './interface' 4 | import type { GroupchatsId } from '../../lib/interface' 5 | 6 | type GroupsConfigRecord = Promise 7 | type GroupsConfigUpdate = Promise 8 | type GroupsConfigRetrieve = Promise 9 | 10 | const groupsConfigTemp: GroupsConfig = { 11 | draw: { 12 | discount: 5, 13 | timestamp: 0 14 | }, 15 | setCard: { 16 | isAuto: true, 17 | content: '又有大佬来了,群地位-1' 18 | }, 19 | events: { 20 | message: true, 21 | increase: true, 22 | ban: true, 23 | decrease: true, 24 | poke: true 25 | }, 26 | score: { 27 | dailyLimit: 4 28 | } 29 | } 30 | 31 | const create = { 32 | recordRow: async (id: GroupchatsId): GroupsConfigRecord => 33 | await operationSql(`insert into groups_config(group_id,config) values('${id}','${JSON.stringify(groupsConfigTemp)}')`) 34 | } 35 | const update = { 36 | updateData: async (id: GroupchatsId, config: GroupsConfig): GroupsConfigUpdate => 37 | await operationSql(`update groups_config set config='${JSON.stringify(config)}' where group_id='${id}'`) 38 | } 39 | const retrieve = { 40 | retrieveData: async (id: GroupchatsId): GroupsConfigRetrieve => 41 | await operationSql(`select config from groups_config where group_id=${id}`) 42 | } 43 | const groups_config = { ...create, ...update, ...retrieve } 44 | 45 | export { groups_config } 46 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/packsack/tools.ts: -------------------------------------------------------------------------------- 1 | import { getDataBaseData, formSet } from '../../../database' 2 | 3 | import type { CardResult } from '../draw/interface' 4 | import type { GroupchatsId } from '../../../lib/interface' 5 | import type { ChoiceProps } from '../../../lib/interface/toolsType' 6 | import type { UserPacksack } from '../../../database/forms/interface' 7 | 8 | type AllType = { 9 | score?: number 10 | card?: ChoiceProps 11 | curInc?: number 12 | } 13 | 14 | const { user_packsack } = formSet 15 | 16 | const temp: UserPacksack = { score: 0, card: { ban: 0, immune: 0, delMsg: 0, see: 0 }, curInc: 0 } 17 | const setPacksack = async (groupId: GroupchatsId, userId: number, all: AllType) => { 18 | const wholePacksack = await getDataBaseData(user_packsack.name, user_packsack.retrieveData)(groupId) 19 | const initUserPasksack = async () => { 20 | wholePacksack[userId] = { ...temp } 21 | await getDataBaseData(user_packsack.name, user_packsack.updateData)(groupId, wholePacksack) 22 | } 23 | // 判断当前用户是否开启了背包 24 | if (!wholePacksack[userId]) initUserPasksack() 25 | const changePacksack = (newPacksack: any, prePacksack: any) => { 26 | Object.keys(newPacksack).forEach(name => { 27 | if (newPacksack[name] instanceof Object) changePacksack(newPacksack[name], prePacksack[name]) 28 | else prePacksack[name] = prePacksack[name] + newPacksack[name] 29 | }) 30 | } 31 | // 修改背包数据 32 | changePacksack(all, wholePacksack[userId]) 33 | await getDataBaseData(user_packsack.name, user_packsack.updateData)(groupId, wholePacksack) 34 | } 35 | 36 | export { setPacksack } 37 | -------------------------------------------------------------------------------- /eventsHandle/xiaoqiu/versions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [小秋版本介绍]指令: 3 | * 获取小秋的版本介绍。 4 | * (只会发送当前所处的主版本号的版本介绍,例如当前处于2.x.x,则只会发送2.0.0-3.0.0之间所经历的版本变化,不会发送1.x.x的版本变化) 5 | */ 6 | import { getCurDarkOrLights } from '../../lib/time' 7 | import { getModeImg } from './tools' 8 | import { self } from '../../lib/user' 9 | 10 | import type { SendContent, CommandFn } from '../../lib/interface' 11 | 12 | const sendContent: SendContent = { 13 | name: '小秋版本介绍', 14 | reg: [ 15 | /^小秋版本介绍$/, 16 | /^小秋多大了$/, 17 | /^小秋几岁了$/, 18 | /^小秋版本号$/, 19 | /^秋秋的版本介绍$/ 20 | ], 21 | role: 'member', 22 | deverDefined: ({ operations: { at } }) => [ 23 | [ 24 | `这就是小秋的过往啦~`, 25 | `秋秋总共经历过以下版本哦`, 26 | `${at('user')} 想要再了解一下小秋吗`, 27 | `${at('user')} 这就是v2.x的全部内容啦`, 28 | `${at('user')} 遇到不懂的地方,记得向小秋发送[菜单]指令`, 29 | `${at('user')} 悄悄告诉你,小秋也希望当前版本没有bug哦·` 30 | ] 31 | ] 32 | } 33 | const classify = [['1', '1']] 34 | // 版本介绍使用旧图变新图的方式 35 | const fn: CommandFn = async originData => { 36 | const { bot, group, segment } = originData 37 | // 获取当前是白天还是黑天 38 | const flag = getCurDarkOrLights() 39 | const arr = getModeImg(classify, 'version', flag) 40 | // 拿到路径后,因菜单较长,所以使用转发聊天记录的形式发出 41 | const result = arr.map(([url]) => ({ 42 | user_id: self.uin, 43 | nickname: '这就是小秋所经过的版本介绍了~', 44 | message: segment.image(url) 45 | })) 46 | const forward = await bot.makeForwardMsg([...result]) 47 | bot.sendGroupMsg(group.id, segment.xml(forward.data.data.data)) 48 | return { items: 1 } 49 | } 50 | const versions = { fn, sendContent } 51 | 52 | export { versions } 53 | -------------------------------------------------------------------------------- /database/tools.ts: -------------------------------------------------------------------------------- 1 | import mysql from 'mysql' 2 | 3 | import { connectionDbConfig } from '../lib/user' 4 | import { suggestions, switch_commands, user_packsack, groups_config } from './forms' 5 | 6 | import type { GroupchatsId } from '../lib/interface' 7 | 8 | const databaseSet = { 9 | suggestions, 10 | switch_commands, 11 | user_packsack, 12 | groups_config 13 | } 14 | // 连接数据库 15 | const connection = mysql.createPool(connectionDbConfig) 16 | // 对数据库进行操作 17 | const operationSql = (sql: string, isWhole?: boolean): Promise => 18 | new Promise((res, rej) => 19 | connection.query(sql, (err: null | Error, data) => { 20 | // 抛出了错误 21 | if (err) { 22 | console.log('operationSql数据库操作异常:', err) 23 | return rej(err) 24 | } 25 | console.log('operationSql读取数据库数据', data) 26 | // 读取单条记录或全部记录(采用的sql语句大都为读取单条记录,所以此处对单条数据进行格式化) 27 | const obj = data[0] 28 | if (!obj) return res(obj) // 返回空数据 29 | const result = isWhole ? data : JSON.parse(obj[Object.keys(obj)[0]]) 30 | res(result) 31 | }) 32 | ) 33 | // 根据数据表读取数据 34 | const getDataBaseData = < 35 | T extends keyof typeof databaseSet, 36 | K extends keyof typeof databaseSet[T] 37 | >(form: T, operation: K) => { 38 | const sqlFn = databaseSet[form][operation] 39 | return sqlFn 40 | } 41 | // 对需要进行初始化的数据表进行初始化 42 | const initForm = [groups_config, switch_commands, user_packsack] 43 | const toInitFormData = (id: GroupchatsId) => 44 | initForm.forEach(async form => { 45 | const data = await form.retrieveData(id) 46 | if (data) return 47 | form.recordRow(id) 48 | }) 49 | 50 | export { databaseSet, getDataBaseData, operationSql, toInitFormData } 51 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/packsack/addScore.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [发放积分]指令: 3 | * 为指定成员发放积分 4 | */ 5 | import { setPacksack } from './tools' 6 | 7 | import { AuthId } from '../../../lib/interface' 8 | import type { SendContent, CommandFn } from '../../../lib/interface' 9 | 10 | const sendContent: SendContent = { 11 | name: '发放积分', 12 | reg: [ 13 | /^发放(?\d*)积分\[CQ:at,qq=(?\d*),text=.*\]\s*$/, 14 | /^发放积分(?\d*)\[CQ:at,qq=(?\d*),text=.*\]\s*$/ 15 | ], 16 | role: [AuthId.FuncJin], 17 | member: ({ user: { name }, operations: { at } }) => [ 18 | `${at('user')} 此操作仅供特定用户使用`, 19 | `${at('user')} 您暂无此权限`, 20 | `${name},反了你了` 21 | ], 22 | admin: ({ operations: { at } }) => [ 23 | `${at('user')} 就算你是管理也不行 此操作仅供特定用户使用`, 24 | `${at('user')} 抱歉 您无此权限~` 25 | ], 26 | owner: ({ operations: { at } }) => [ 27 | `${at('user')} 听说就连群主也无法触发此指令哦!`, 28 | `${at('user')} 糟糕 居然是红灯[尊贵的群主大人,您暂时无法使用此指令]` 29 | ], 30 | deverDefined: ({ user: { name }, operations: { at }, defined: { score } }) => [ 31 | [ 32 | `${at('user')} 小秋提示您,积分发放范围必须在1~1000万之间`, 33 | `${at('user')} 积分发放范围必须在1~1000万之间` 34 | ], 35 | [ 36 | `${at('other')} Hi ${name}为您发放${score}积分`, 37 | `${at('other')} 人品大爆发 ${name}为您发放${score}积分` 38 | ] 39 | ] 40 | } 41 | const fn: CommandFn = originData => { 42 | const { group, other, raw_message, reg } = originData 43 | const num = reg.exec(raw_message)?.groups?.num 44 | const score = Number(num) 45 | if (score <= 0 || score > 10000000) return { items: 1, args: {} } 46 | setPacksack(group.id, other.id, { score }) 47 | return { items: 2, args: { score } } 48 | } 49 | const addScore = { fn, sendContent } 50 | 51 | export { addScore } 52 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/draw/draw.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [抽奖]指令: 3 | * 用于在积分抽奖池中进行一次抽奖 4 | */ 5 | import { judge, luckly } from './tools' 6 | import { setPacksack } from '../packsack/tools' 7 | import { formatNumCount, isDecimals } from '../../../lib/methods' 8 | 9 | import type { SendContent, CommandFn } from '../../../lib/interface' 10 | 11 | // 每位用户只能在上一次抽奖完成后,再进行下一次抽奖 12 | const drawPlaceholder = new Map() 13 | 14 | const sendContent: SendContent = { 15 | name: '抽奖', 16 | reg: /^抽奖$/, 17 | role: 'member', 18 | deverDefined: ({ user: { name }, defined: { reason, explain, score }, operations: { at } }) => [ 19 | [ 20 | `${at('user')} ${reason}` 21 | ], 22 | [ 23 | `${at('user')} 小秋恭喜您抽到了(${explain})\n[本次抽奖积分变化:${score}积分]`, 24 | `${at('user')} 小秋帮您抽奖完成啦,您抽到的是(${explain})\n[本次抽奖积分变化:${score}积分]`, 25 | `呀 ${name}人品大爆发,抽到了(${explain})\n[本次抽奖积分变化:${score}积分]` 26 | ] 27 | ] 28 | } 29 | const fn: CommandFn = async originData => { 30 | const { group, user, database } = originData 31 | const isOk = await judge(group.id, user.id, 1, database, drawPlaceholder) 32 | const { flag, reason } = isOk 33 | if (!flag) return { items: 1, args: { reason } } 34 | // 记录本次抽奖是否完成 35 | const drawFlag = `${group.id}${user.id}` 36 | const count = 1 37 | drawPlaceholder.set(drawFlag, count) 38 | const { all, advance } = await luckly(group.id) 39 | await setPacksack(group.id, user.id, all) 40 | // 得到一次抽奖所消耗的积分 41 | // 确认更改数据后,清除原先所记录的抽奖标识 42 | drawPlaceholder.delete(drawFlag) 43 | const r = all.score 44 | return { 45 | items: 2, 46 | args: { 47 | explain: advance.explain, 48 | score: formatNumCount(isDecimals(r) ? Number(r.toFixed(1)) : r) 49 | } 50 | } 51 | } 52 | const draw = { fn, sendContent } 53 | 54 | export { draw } 55 | -------------------------------------------------------------------------------- /eventsHandle/xiaoqiu/menu.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [菜单]指令: 3 | * 查看小秋菜单 4 | */ 5 | import { getCurDarkOrLights } from '../../lib/time' 6 | import { getModeImg } from './tools' 7 | import { self } from '../../lib/user' 8 | 9 | import type { SendContent, CommandFn } from '../../lib/interface' 10 | 11 | const sendContent: SendContent = { 12 | name: '菜单', 13 | reg: [ 14 | /^小秋菜单$/, 15 | /^菜单$/ 16 | ], 17 | role: 'member', 18 | deverDefined: ({ user: { name, sex }, operations: { at } }) => [ 19 | [ 20 | `小秋菜单如下`, 21 | `${at('user')} 小${sex ? '哥哥' : '姐姐'},这就是小秋的全部功能啦`, 22 | `${name},悄悄告诉你,这就是小秋的全部魔法哦`, 23 | `${at('user')} 还想让小秋学会什么?赶快通过[提建议]指令告诉我吧`, 24 | `${at('user')} 听说有的指令会触发彩蛋哦~` 25 | ] 26 | ] 27 | } 28 | // 菜单指令分为浅色和深色模式,每个模式下各包含5张图片,分别对应着菜单中的5个模块: 29 | // 群聊相关、群管理操作、积分玩法、其它玩法、小秋相关 30 | const classify = [ 31 | ['qlxg', '群聊相关'], 32 | ['qglcz', '群管理操作'], 33 | ['jfwf', '积分玩法'], 34 | ['qtwf', '其它玩法'], 35 | ['xqxg', '小秋相关'] 36 | ] 37 | const fn: CommandFn = async originData => { 38 | const { bot, group, segment } = originData 39 | // 获取当前是白天还是黑天 40 | const flag = getCurDarkOrLights() 41 | const arr = getModeImg(classify, 'menu', flag) 42 | // 拿到路径后,因菜单较长,所以使用转发聊天记录的形式发出 43 | const info = { 44 | user_id: self.uin, 45 | nickname: '小秋菜单现有五个分类哦~' 46 | } 47 | const result = arr.map(([url]) => ({ 48 | ...info, 49 | message: segment.image(url) 50 | })) 51 | const menuIntroduce = { 52 | ...info, 53 | message: '截止当前,小秋菜单共包含4个分类,每个分类下皆拥有若干指令\n(若某个指令序号后面带有符号☆,则代表该指令只能由特定用户进行触发)' 54 | } 55 | result.push(menuIntroduce) 56 | const forward = await bot.makeForwardMsg([...result]) 57 | bot.sendGroupMsg(group.id, segment.xml(forward.data.data.data)) 58 | return { items: 1 } 59 | } 60 | const menu = { fn, sendContent } 61 | 62 | export { menu } 63 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/notAllowedSpeak/relieveNotAllowedSpeak.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [解除禁止发言]指令: 3 | * 为某位成员解除禁止发言 4 | */ 5 | import { persons, judgeMemberNotSpeak } from './tools' 6 | 7 | import type { SendContent, CommandFn } from '../../../lib/interface' 8 | 9 | const sendContent: SendContent = { 10 | name: '解除禁止发言', 11 | reg: /^解除禁止发言\[CQ:at,qq=\d*,text=.*\]\s*$/, 12 | role: 'admin', 13 | member: ({ other: { name }, operations: { at, face } }) => [ 14 | `${at('user')} 听我说谢谢你 但是你还是不能使用该指令`, 15 | `${at('user')} 想啥呢 小秋没法帮您解除哦${face(34)}`, 16 | `${at('user')} ${name}不需要解除哦~` 17 | ], 18 | deverDefined: ({ user: { name: username }, other: { name: othername }, operations: { at } }) => [ 19 | [ 20 | `${at('user')} 不带这么玩的 ${othername}尚不处于禁止发言期间`, 21 | `${at('user')} 别闹,小秋没发现${othername}处于禁止发言期间`, 22 | `${at('user')} 等${othername}进入了禁止发言期间再来使用该指令吧!` 23 | ], 24 | [ 25 | `${at('other')} 呜呼啦呼 你已被解除禁止发言`, 26 | `${at('other')} 人品大爆发 您被${username}解除了禁止发言`, 27 | `${at('other')} ${username}已解除您的禁止发言` 28 | ] 29 | ], 30 | equal: ({ other: { name }, operations: { at, face } }) => [ 31 | `${at('user')} 本是同根生 相煎何太急`, 32 | `${at('user')} 小心${name}干你嗷${face(178)}`, 33 | `${at('user')} 都是同行 别这样` 34 | ], 35 | level: ({ operations: { at } }) => [ 36 | `${at('user')} 抱歉 小秋暂时无法帮您完成此操作`, 37 | `${at('user')} 哎呀 小秋还没有[解除禁止发言]指令的权限呢` 38 | ] 39 | } 40 | const fn: CommandFn = originData => { 41 | const { group, other } = originData 42 | const isHave = judgeMemberNotSpeak(group.id, other.id) 43 | // 如果已经处于禁止发言期间了 44 | if (!isHave) return { items: 1 } 45 | const id = String(group.id) + String(other.id) 46 | persons.delete(id) 47 | return { items: 2 } 48 | } 49 | const relieveNotAllowedSpeak = { fn, sendContent } 50 | 51 | export { relieveNotAllowedSpeak } 52 | -------------------------------------------------------------------------------- /lib/methods/tools.ts: -------------------------------------------------------------------------------- 1 | // 记录上次的随机数,避免重复 2 | const recordRandom = { 3 | randomScope: 0, 4 | random: 0 5 | } 6 | // 返回 0-x 之间的随机数 7 | const getRandomScope = (len: number): number => { 8 | if (len === 1) return 0 9 | const num = Math.floor(Math.random() * len) 10 | if (num === recordRandom.randomScope) return getRandomScope(len) 11 | recordRandom.randomScope = num 12 | return num 13 | } 14 | // 获取x-y之间的随机数,包含x和y 15 | const getRandom = (x: number, y: number): number => { 16 | const num = Math.round(Math.random() * (y - x) + x) 17 | if (num === recordRandom.random) return getRandom(x, y) 18 | recordRandom.random = num 19 | return num 20 | } 21 | // 打乱数组中的顺序(会影响原数组) 22 | const disruptOrder = (arr: []) => { 23 | const newArr: [] = [] 24 | while (arr.length) { 25 | const [el] = arr.splice(getRandomScope(arr.length), 1) 26 | newArr.push(el) 27 | } 28 | return newArr 29 | } 30 | // 返回数组中的一个随机成员 31 | const returnOneOfContent = (contents: T[]): T => contents[getRandomScope(contents.length)] 32 | // 提取CQ码中的qq,并以字符串的形式返回 33 | const otherIdFromCq = (along: string): string | null => { 34 | const groups = /\[CQ:at,qq=(?\d*),text=.*\]/.exec(along)?.groups 35 | return groups?.qq! 36 | } 37 | // 获取一个由指定位数组成的随机数字字符串 38 | const getRandomDigitNumberStr = (digit: number) => { 39 | let str = '' 40 | for (let i = 1; i <= digit; i++) str += Math.floor(Math.random() * 10) 41 | return str 42 | } 43 | // 判断一个数有没有小数点 44 | const isDecimals = (num: number) => { 45 | const integer = Math.floor(num) 46 | return integer < num 47 | } 48 | // 判断一个数是正数还是负数 49 | // 例如3则返回'+3',-3则返回'-3' 50 | const formatNumCount = (num: number): string => (num >= 0 ? `+${num}` : `${num}`) 51 | 52 | export { 53 | getRandomScope, 54 | getRandom, 55 | returnOneOfContent, 56 | otherIdFromCq, 57 | disruptOrder, 58 | getRandomDigitNumberStr, 59 | isDecimals, 60 | formatNumCount 61 | } 62 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/notAllowedSpeak/notAllowedSpeak.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [禁止发言]指令: 3 | * 当某成员被禁止发言后,其所发出的每一条消息都会被小秋撤回(但并不会将该成员禁言) 4 | */ 5 | import { persons, judgeMemberNotSpeak } from './tools' 6 | 7 | import type { SendContent, CommandFn } from '../../../lib/interface' 8 | 9 | const sendContent: SendContent = { 10 | name: '禁止发言', 11 | reg: /^禁止发言\[CQ:at,qq=\d*,text=.*\]\s*$/, 12 | role: 'admin', 13 | member: ({ other: { name }, operations: { at, face } }) => [ 14 | `${at('user')} 要造反了吗???`, 15 | `${at('user')} 你居然要禁止${name}发言${face(34)}`, 16 | `${at('user')} 等你成为管理or群主就可以禁止别人发言了~` 17 | ], 18 | deverDefined: ({ user: { name: username }, other: { name: othername }, operations: { at, face } }) => [ 19 | [ 20 | `${at('user')} 不带这么玩的 ${othername}已经处于禁止发言期间了`, 21 | `${at('user')} 别闹 小秋看到${othername}已经被禁止发言了`, 22 | `${at('user')} 不可以对${othername}同时使用两次[禁止发言]指令哦` 23 | ], 24 | [ 25 | `${at('other')} 您现在已进入禁止发言期间`, 26 | `${username}已将${othername}列入禁止发言名单${face(178)}`, 27 | `${at('other')} 你已进入禁止发言期间,在此期间 您发送的所有消息都会被一一撤回` 28 | ] 29 | ], 30 | equal: ({ other: { name }, operations: { at, face } }) => [ 31 | `${at('user')} 本是同根生 相煎何太急`, 32 | `${at('user')} 小心${name}干你嗷${face(178)}`, 33 | `${at('user')} 都是同行 别这样` 34 | ], 35 | level: ({ operations: { at } }) => [ 36 | `${at('user')} 抱歉 小秋暂时无法帮您完成此操作`, 37 | `${at('user')} 哎呀 小秋还没有[禁止发言]指令的权限呢` 38 | ] 39 | } 40 | const fn: CommandFn = originData => { 41 | const { group, other } = originData 42 | const isHave = judgeMemberNotSpeak(group.id, other.id) 43 | // 如果已经处于禁止发言期间了 44 | if (isHave) return { items: 1 } 45 | const id = String(group.id) + String(other.id) 46 | persons.set(id, true) 47 | return { items: 2 } 48 | } 49 | const notAllowedSpeak = { fn, sendContent } 50 | 51 | export { notAllowedSpeak } 52 | -------------------------------------------------------------------------------- /eventsHandle/fns.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 注意,所有指令的正则之间可能会发生冲突,例如[禁言卡@成员]与[禁言1分钟] 3 | * 解决冲突的办法就是在export中尽可能让复杂的正则排在前面,或者修改正则触发方式 4 | */ 5 | import { askQuestions, signIn } from './groupchat' 6 | import { 7 | runtime, 8 | getCopyWriting, 9 | goodNight, 10 | getSuggestions, 11 | landlords, 12 | goodMorning, 13 | listenMusic, 14 | makeSuggestion, 15 | xiaoqiuChat, 16 | searchJueJin 17 | } from './otherPlay' 18 | import { 19 | changeDrawDiscount, 20 | draw, 21 | drawCount, 22 | getDrawDiscount, 23 | getPacksack, 24 | addScore, 25 | banAndDelCard, 26 | seeCard, 27 | getScoreWay 28 | } from './scorePlayMethods' 29 | import { 30 | alert, 31 | banCommon, 32 | checkDeadPerson, 33 | relieveCheckDeadPerson, 34 | notAllowedSpeak, 35 | relieveNotAllowedSpeak, 36 | delMemberMessage, 37 | setCard, 38 | setGroupKick, 39 | setGroupWholeBan 40 | } from './groupchatAdmin' 41 | import { isXiaoQiuOnline, menu, switchCommand, versions } from './xiaoqiu' 42 | 43 | const commandsConfig = { 44 | askQuestions, 45 | signIn, 46 | runtime, 47 | getCopyWriting, 48 | goodNight, 49 | getSuggestions, 50 | landlords, 51 | goodMorning, 52 | listenMusic, 53 | makeSuggestion, 54 | xiaoqiuChat, 55 | searchJueJin, 56 | changeDrawDiscount, 57 | draw, 58 | drawCount, 59 | getDrawDiscount, 60 | getPacksack, 61 | addScore, 62 | banAndDelCard, 63 | seeCard, 64 | getScoreWay, 65 | alert, 66 | banCommon, 67 | checkDeadPerson, 68 | relieveCheckDeadPerson, 69 | notAllowedSpeak, 70 | relieveNotAllowedSpeak, 71 | delMemberMessage, 72 | setCard, 73 | setGroupKick, 74 | setGroupWholeBan, 75 | isXiaoQiuOnline, 76 | menu, 77 | switchCommand, 78 | versions 79 | } as const 80 | 81 | type CommandsConfig = typeof commandsConfig 82 | 83 | export { 84 | commandsConfig, 85 | CommandsConfig 86 | } 87 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/alert.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [警告]指令: 3 | * 撤回指定成员的全部消息,并禁言15分钟 4 | */ 5 | import path from 'path' 6 | 7 | import type { SendContent, CommandFn } from '../../lib/interface' 8 | 9 | const sendContent: SendContent = { 10 | name: '警告', 11 | reg: /^警告\[CQ:at,qq=(?\d*),text=.*\]\s*$/, 12 | role: 'admin', 13 | member: ({ user: { name }, other: { role }, operations: { at, promiseImage } }) => [ 14 | `${at('user')} 等你成为管理就可以警告别人了`, 15 | `${at('user')} 这指令只有管理or群主才可以触发哦~`, 16 | `${at('user')} 想弄${role === 'admin' ? '管理' : '群主'}搞事情?`, 17 | `${at('other')} ${name}想要警告你${promiseImage(path.resolve('./assets/images/emoji/麻了1.jpg'))}` 18 | ], 19 | admin: ({ user: { name }, operations: { at } }) => [ 20 | `${at('other')} 小秋发现管理员${name}对您发出了[警告]指令,因此小秋已撤回您1小时内所有消息,并禁言以作警告`, 21 | `${at('other')} 呜呜呜 小秋被臭管理逮到了,${name}对您发出了[警告]指令,因此小秋已撤回您1小时内所有消息,并禁言以作警告`, 22 | `${at('other')} 请务必文明发言。[本次已撤回您1小时内所有消息,并禁言以作警告]` 23 | ], 24 | owner: ({ user: { name }, operations: { at } }) => [ 25 | `${at('other')} 小秋发现群主大人${name}对您发出了[警告]指令,因此小秋已撤回您1小时内所有消息,并禁言以作警告`, 26 | `${at('other')} 呜呜呜 小秋被臭群主逮到了,${name}对您发出了[警告]指令,因此小秋已撤回您1小时内所有消息,并禁言以作警告`, 27 | `${at('other')} 请务必文明发言。[本次已撤回您1小时内所有消息,并禁言以作警告]` 28 | ], 29 | equal: ({ user: { name: username }, other: { name: othername }, operations: { at, face } }) => [ 30 | `${at('user')} 嗯??大家都是同道中人,干嘛要禁言${othername}呢`, 31 | `${at('user')} 糟了 小秋无法帮您进行警告`, 32 | `${at('other')} ${username}要警告你!${face(278)}` 33 | ], 34 | level: ({ operations: { at } }) => [ 35 | `${at('user')} 抱歉 小秋权限不足 暂时不能帮您执行警告操作哦~`, 36 | `${at('user')} 小秋暂无此权限` 37 | ] 38 | } 39 | const fn: CommandFn = originData => { 40 | const { group, other, operations } = originData 41 | operations.delMsg(group.id, other.id, 0) 42 | operations.banMember(group.id, other.id, '分钟', 15) 43 | } 44 | const alert = { fn, sendContent } 45 | 46 | export { alert } 47 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/packsack/getPacksack.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [我的背包]指令: 3 | * 发送当前用户背包中的积分、道具卡数量 4 | */ 5 | import { isDecimals } from '../../../lib/methods' 6 | 7 | import type { SendContent, CommandFn } from '../../../lib/interface' 8 | 9 | const sendContent: SendContent = { 10 | name: '我的背包', 11 | reg: [ 12 | /^我的背包$/, 13 | /^我的积分$/, 14 | /^查询背包$/, 15 | /^背包查询$/, 16 | /^查询积分$/, 17 | /^积分查询$/, 18 | /^查看背包$/, 19 | /^查看积分$/ 20 | ], 21 | role: 'member', 22 | deverDefined: ({ defined: { score, ban, delMsg, see, immune }, operations: { at } }) => [ 23 | [ 24 | `${at('user')} 小秋查询到您的背包如下~\n积分:${score}分\n禁言卡:${ban}张,免疫卡:${immune}张\n撤回卡:${delMsg}张,康康卡:${see}张`, 25 | `${at('user')}\n积分${score}\n禁言卡${ban}张\n免疫卡${immune}张\n撤回卡${delMsg}张\n康康卡${see}张`, 26 | `${at('user')} 您的背包情况为:${score}积分,${ban}张禁言卡、${immune}张免疫卡,${delMsg}张撤回卡、康康卡${see}张!` 27 | ], 28 | [ 29 | `${at('user')} 小秋查询到您的背包如下哦~\n积分:${score}分\n禁言卡:${ban}张\n免疫卡:${immune}张\n撤回卡:${delMsg}张\n康康卡:${see}张`, 30 | `${at('user')} 哇 太多了叭 羡慕啊\n积分:${score}分\n禁言卡:${ban}张\n免疫卡:${immune}张\n撤回卡:${delMsg}张\n康康卡:${see}张` 31 | ] 32 | ] 33 | } 34 | const fn: CommandFn = async originData => { 35 | const { group, user: { id }, database } = originData 36 | const { getDataBaseData, formSet: { user_packsack } } = database 37 | const value = await getDataBaseData(user_packsack.name, user_packsack.retrieveData)(group.id) 38 | const all = value[id] || { score: 0, card: { ban: 0, immune: 0, delMsg: 0, see: 0 } } 39 | const { score: myScore, card } = all 40 | const { ban, immune, delMsg, see } = card 41 | const score = isDecimals(myScore) ? myScore.toFixed(1) : myScore 42 | if ([ban, immune, delMsg, see].find(v => v >= 100)) 43 | return { items: 2, args: { score, ban, immune, delMsg, see } } 44 | return { items: 1, args: { score, ban, immune, delMsg, see } } 45 | } 46 | const getPacksack = { fn, sendContent } 47 | 48 | export { getPacksack } 49 | -------------------------------------------------------------------------------- /eventsHandle/proxy/verifyAnswer.ts: -------------------------------------------------------------------------------- 1 | import { persons } from '../groupchatAdmin/checkDeadPerson/tools' 2 | 3 | import type { VerifyProxy } from './interface' 4 | 5 | // 存储正在进行人机验证的用户 6 | const answerTip = '答案中不应该带有修饰性词语,否则判断为错误' 7 | // 检测当前消息是否为人机验证的作答消息 8 | const verifyAnswer: VerifyProxy = info => { 9 | const { bot, gId, uId, operations, raw_message } = info 10 | // 如果没有添加人机验证,则直接返回false 11 | if (!persons) return false 12 | const id = String(gId) + String(uId) 13 | // 当前用户是否为正在进行人机验证的成员 14 | const member = persons.get(id) 15 | if (!member) return false 16 | const { answer, times, isFailed, token } = member 17 | const { doAt: at, delMsg } = operations 18 | // 如果当前用户3分钟内未作答,或3分钟内的3次作答机会全部错误,此时会在15秒后踢出本成员 19 | // 但这15秒中,用户还有可能发送消息,所以只要检测到这位成员发送的消息,就立即撤回 20 | if (isFailed) { 21 | delMsg(gId, uId, 1) 22 | bot.sendGroupMsg(gId, `${at(uId)},请耐心等待15秒计时结束~`) 23 | return true 24 | } 25 | // 如果属于正在进行人机验证的成员,则对该条消息进行验证 26 | const isTrue = answer.find(r => r === raw_message.trim()) 27 | // 如果作答成功 28 | if (isTrue) { 29 | // 关闭之前开启的定时器 30 | clearTimeout(token) 31 | // 删除id 32 | persons.delete(id) 33 | bot.sendGroupMsg(gId, `${at(uId)},小秋检测到您已成功完成人机验证,恭喜~`) 34 | return true 35 | } 36 | const nextTimes = times + 1 37 | persons.set(id, { ...member, times: nextTimes }) 38 | // 如果次数已用尽 39 | if (nextTimes >= 3) { 40 | persons.set(id, { ...member, times: nextTimes, isFailed: true }) 41 | setTimeout(() => { 42 | const have = persons.get(id) 43 | if (!have) return 44 | // 关闭定时器 45 | clearTimeout(have.token) 46 | // 清除该id 47 | persons.delete(id) 48 | bot.setGroupKick(gId, uId) 49 | }, 1000 * 15) 50 | bot.sendGroupMsg(gId, `${at(uId)},检测到您所提交的三个答案全部错误,因此15秒后您将被踢出此群聊\n\n[如误判,请添加QQ二群191515766进行说明]`) 51 | return true 52 | } 53 | bot.sendGroupMsg(gId, `${at(uId)},本次回答错误,您还有${3 - nextTimes}次机会\n[温馨提示:${answerTip}]`) 54 | return true 55 | } 56 | 57 | export { verifyAnswer } 58 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/getCopyWriting.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [文案]指令: 3 | * 使用一言Api随机获取指定类型的文案 4 | */ 5 | import request from 'request' 6 | 7 | import type { YiYanApi } from './interface' 8 | import type { SendContent, CommandFn } from '../../lib/interface' 9 | 10 | type WritingType = keyof typeof reqContent 11 | 12 | const reqContent = { 13 | 动画: 'a', 14 | 漫画: 'b', 15 | 游戏: 'c', 16 | 文学: 'd', 17 | 原创: 'e', 18 | 网络: 'f', 19 | 其它: 'g', 20 | 影视: 'h', 21 | 诗词: 'i', 22 | 哲学: 'k' 23 | } 24 | 25 | const sendContent: SendContent = { 26 | name: '文案', 27 | reg: /^文案:(?.*)\s*$/, 28 | role: 'member', 29 | deverDefined: ({ user: { name }, defined: { message }, operations: { at } }) => [ 30 | [ 31 | `${at('user')} 小秋发现您还没有输入文案类型`, 32 | `${name},必须输入一个文案类型才可以哦~`, 33 | `${at('user')} 哎呀 没有文案类型的话,小秋无法帮您寻找哦~` 34 | ], 35 | [ 36 | `${at('user')} 小秋发现您的文案类型有误,您可以尝试更换类型后重新进行搜索`, 37 | `${name},哎呀 该类型走丢了,请换个类型重新尝试吧~`, 38 | `${at('user')} 暂时没有找到该文案类型哦` 39 | ], 40 | [`${name},${message}`, `${message}`] 41 | ] 42 | } 43 | 44 | const fn: CommandFn = originData => { 45 | const { raw_message, reg } = originData 46 | return new Promise(resolve => { 47 | const type = reg.exec(raw_message)?.groups?.c as WritingType 48 | if (!type) return resolve({ items: 1, args: {} }) 49 | const flag = Object.keys(reqContent).includes(type) 50 | if (!flag) return resolve({ items: 2, args: {} }) 51 | const c = reqContent[type] 52 | request(`https://v1.hitokoto.cn`, { qs: { c, encode: 'json' } }, (err: unknown, rep: unknown, data: string) => { 53 | if (!!data) { 54 | const { from, from_who, hitokoto }: YiYanApi = JSON.parse(data) 55 | const writer = from_who ? from_who : '' 56 | const message = `${hitokoto}\n——${writer}《${from}》` 57 | resolve({ items: 3, args: { message } }) 58 | } 59 | } 60 | ) 61 | }) 62 | } 63 | const getCopyWriting = { fn, sendContent } 64 | 65 | export { getCopyWriting } 66 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/getSuggestions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [查建议]指令: 3 | * 获取所有用户给小秋提出的建议,每条建议均配备“进度”标识,代表当前所提出的建议进度 4 | */ 5 | import { getWholeDate } from '../../lib/time' 6 | import { self } from '../../lib/user' 7 | 8 | import type { SuggestionFormat } from '../../database/forms/interface' 9 | import type { SendContent, CommandFn, ForwardRecord } from '../../lib/interface' 10 | 11 | const sendContent: SendContent = { 12 | name: '查建议', 13 | reg: [ 14 | /^查建议$/, 15 | /^查看建议$/, 16 | /^看建议$/, 17 | /^查询建议$/ 18 | ], 19 | role: 'member', 20 | deverDefined: ({ defined: { tip } }) => [ 21 | [ 22 | `小秋暂时没有发现更多建议`, `暂时没有更多建议,快来提出第一条建议吧` 23 | ], 24 | [ 25 | `${tip}` 26 | ] 27 | ] 28 | } 29 | // 处理建议 30 | const centerLine = (i: number) => (i > 1 ? '\n- - -\n' : '') 31 | const process = (json: string) => { 32 | const arrs: SuggestionFormat[] = JSON.parse(json) 33 | let text = '' 34 | const handleResult = (result: string | undefined) => result ? `\n处理结果:${result}` : '' 35 | arrs.forEach(sug => { 36 | const { content, timestamp, plan, result } = sug 37 | text += `建议:『${content}』\n提出时间:${getWholeDate(new Date(timestamp))}\n当前进度:${plan}${handleResult(result)}${centerLine(arrs.length)}` 38 | }) 39 | return text 40 | } 41 | const fn: CommandFn = async originData => { 42 | const { bot, segment, group, database } = originData 43 | const { getDataBaseData, formSet: { suggestions } } = database 44 | const data = await getDataBaseData(suggestions.name, suggestions.retrieveData)() 45 | if (!data) return { items: 1 } 46 | const result: ForwardRecord[] = [] 47 | data.forEach(row => result.push({ 48 | user_id: self.uin, 49 | nickname: '来自小秋用户的建议', 50 | message: process(row.suggestion) 51 | })) 52 | const forward = await bot.makeForwardMsg([...result]) 53 | bot.sendGroupMsg(group.id, segment.xml(forward.data.data.data)) 54 | const tip = '只要是小秋的用户,便可看到所有关于小秋的建议,以此才能更好地帮助小秋成长' 55 | return { items: 2, args: { tip } } 56 | } 57 | const getSuggestions = { fn, sendContent } 58 | 59 | export { getSuggestions } 60 | -------------------------------------------------------------------------------- /assets/xiaoqiu_alpha.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : database 5 | Source Server Type : MySQL 6 | Source Server Version : 80026 7 | Source Host : localhost:3306 8 | Source Schema : xiaoqiu_alpha 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 80026 12 | File Encoding : 65001 13 | 14 | Date: 24/10/2022 00:00:00 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for groups_config 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `groups_config`; 24 | CREATE TABLE `groups_config` ( 25 | `group_id` int(0) NOT NULL, 26 | `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 27 | PRIMARY KEY (`group_id`) USING BTREE 28 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; 29 | 30 | -- ---------------------------- 31 | -- Table structure for suggestions 32 | -- ---------------------------- 33 | DROP TABLE IF EXISTS `suggestions`; 34 | CREATE TABLE `suggestions` ( 35 | `user_id` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, 36 | `suggestion` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL 37 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 38 | 39 | -- ---------------------------- 40 | -- Table structure for switch_commands 41 | -- ---------------------------- 42 | DROP TABLE IF EXISTS `switch_commands`; 43 | CREATE TABLE `switch_commands` ( 44 | `group_id` int(0) NOT NULL DEFAULT 0, 45 | `commands` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, 46 | PRIMARY KEY (`group_id`) USING BTREE 47 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 48 | 49 | -- ---------------------------- 50 | -- Table structure for user_packsack 51 | -- ---------------------------- 52 | DROP TABLE IF EXISTS `user_packsack`; 53 | CREATE TABLE `user_packsack` ( 54 | `group_id` int(0) NOT NULL, 55 | `whole_user_id` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, 56 | PRIMARY KEY (`group_id`) USING BTREE 57 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 58 | 59 | SET FOREIGN_KEY_CHECKS = 1; 60 | -------------------------------------------------------------------------------- /eventsHandle/groupchat/signIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [签到]指令: 3 | * 签到被触发时,会根据当前群成员的签到天数自动发放积分 4 | * (每日仅可签到一次,凌晨12:00重置次数) 5 | */ 6 | import path from 'path' 7 | 8 | import { setPacksack } from '../scorePlayMethods/packsack/tools' 9 | 10 | import type { SendContent, CommandFn } from '../../lib/interface' 11 | 12 | const sendContent: SendContent = { 13 | name: '签到', 14 | reg: [ 15 | /^签到$/, 16 | /^冒泡$/, 17 | /^打卡$/, 18 | /^活跃$/, 19 | /^摸鱼$/, 20 | /^疯狂星期四$/, 21 | /^肯德基疯狂星期四$/, 22 | /^vme50$/, 23 | /^v我50$/, 24 | /^v50$/ 25 | ], 26 | role: 'member', 27 | deverDefined: ({ user: { name }, defined: { score }, operations: { at, face, promiseImage } }) => [ 28 | [ 29 | `${at('user')} 您今日已经签过到了哦~`, 30 | `${at('user')} 不允许重复签到,请明日再来吧`, 31 | `${at('user')} 今日已签到过了~\n[每日凌晨12点重置签到次数]` 32 | ], 33 | [ 34 | `${at('user')} 努力做一位前端追梦人吧!\n${score}`, 35 | `${at('user')} 加油 前端人!${score}${face(183)}`, 36 | `${name},不积跬步无以至千里 今天也要多多敲代码哦~\n${score}`, 37 | `${at('user')} 听说vue3与vite搭配 风味更佳哦~\n${score}`, 38 | `${name},我们都在努力奔跑,我们都是追梦人\n${score}`, 39 | `${at('user')} 欲速则不达 今天你准备怎么提升自己呢?${score}${promiseImage( 40 | path.resolve('./assets/images/emoji/加倍焦虑1.jpg') 41 | )}` 42 | ] 43 | ] 44 | } 45 | const fn: CommandFn = async originData => { 46 | const { group, user, database } = originData 47 | const { getDataBaseData, formSet: { user_packsack, groups_config } } = database 48 | const value = await getDataBaseData(user_packsack.name, user_packsack.retrieveData)(group.id) 49 | const curUser = value[user.id] 50 | // 获取当前群聊的每日积分上限 51 | const groupConfig = await getDataBaseData(groups_config.name, groups_config.retrieveData)(group.id) 52 | const { score: { dailyLimit } } = groupConfig 53 | const isMaxCurScore = curUser ? curUser.curInc >= dailyLimit : false 54 | const update = () => { 55 | setPacksack(group.id, user.id, { score: dailyLimit, curInc: dailyLimit }) 56 | return `积分+${dailyLimit}` 57 | } 58 | if (isMaxCurScore) return { items: 1 } 59 | const score = `[${update()}]` 60 | return { items: 2, args: { score } } 61 | } 62 | 63 | const signIn = { fn, sendContent } 64 | 65 | export { signIn } 66 | -------------------------------------------------------------------------------- /lib/oicq/oicqOperations.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { createClient, segment, cqcode } from 'oicq' 3 | 4 | import { getDataBaseData } from '../../database/tools' 5 | import { belong } from '../time' 6 | import { self } from '../user' 7 | import { cacheGroupMsg } from './tools' 8 | 9 | import type { OicqOperations } from './interface' 10 | import type { UnitChi } from '../time/interface' 11 | 12 | const { uin, pwd } = self 13 | 14 | // 创建客户端(传递机器人(小秋)的账号) 15 | const bot = createClient(uin) 16 | // 登录 17 | bot.login(pwd) 18 | // 艾特 19 | const doAt: OicqOperations['doAt'] = id => segment.toCqcode(segment.at(id)) 20 | // 发送表情 21 | const face: OicqOperations['face'] = order => segment.toCqcode(segment.face(order)) 22 | // 查询某个群聊的历史消息,如果未指定查询的条数,则默认为当前群聊中的全部历史消息 23 | // 从后面往前查historyMsg: [1, 2, 3, 4, 5, 6],如果是3,则返回4 5 6 条消息 24 | const getHistoryGroupMsg: OicqOperations['getHistoryGroupMsg'] = (gId, counts?) => { 25 | const arrs = cacheGroupMsg[gId] 26 | if (!counts) return arrs 27 | // 返回指定的条数 28 | const len = counts >= arrs.length ? 0 : arrs.length - counts 29 | const msg = arrs.slice(len) 30 | return msg 31 | } 32 | // 撤回 33 | const delMsg: OicqOperations['delMsg'] = async (groupId, otherId, num) => { 34 | // num为0则代表删除全部消息(全部消息 === 最近30条消息) 35 | const preMessage = cacheGroupMsg[groupId] 36 | const row = num === 0 ? 30 : num 37 | let i = preMessage.length - 1 38 | let beCountMsg = 0 39 | while (i >= 0) { 40 | if (beCountMsg === row) break 41 | if (preMessage[i].userId === otherId) { 42 | const [single] = preMessage.splice(i, 1) 43 | bot.deleteMsg(single.msgId) 44 | beCountMsg++ 45 | } 46 | i-- 47 | } 48 | // 返回已撤回的消息条数 49 | return beCountMsg 50 | } 51 | // 禁言,传入单位(分钟、小时、天数) 以及 持续时间(1、2、...) 52 | const banMember: OicqOperations['banMember'] = (groupId, otherId, unit, dur) => 53 | bot.setGroupBan(groupId, otherId, belong[unit as UnitChi].sec(dur)) 54 | 55 | // 获取指定文件中的图片,以备发送(返回图片的CQ码,可以单独发送,也可以内嵌在字符串中发送) 56 | // 在聊天记录中发送图片,需要使用segment.image,而不是cqcode.image 57 | const promiseImage: OicqOperations['promiseImage'] = imgPath => cqcode.image(imgPath) 58 | 59 | const operations = { 60 | getDataBaseData, 61 | doAt, 62 | face, 63 | getHistoryGroupMsg, 64 | delMsg, 65 | banMember, 66 | promiseImage 67 | } 68 | 69 | export { 70 | bot, 71 | segment, 72 | operations 73 | } 74 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/xiaoqiuChat.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [:]指令: 3 | * 使用在线聊天Api,与另一位机器人对话,其表现形式类似于语音助手。使用该指令会自动消耗当前用户1积分 4 | */ 5 | import request from 'request' 6 | 7 | import { setPacksack } from '../scorePlayMethods/packsack/tools' 8 | 9 | import type { GroupAt } from '../../lib/interface' 10 | import type { SendContent, CommandFn } from '../../lib/interface' 11 | 12 | const successTip = '\n[您消耗了1积分]' 13 | const msg = (at: GroupAt, names: string) => [ 14 | `${at('user')} 您的积分不足以完成此次对话`, 15 | `${names},您的积分太少啦,不能够继续发起本次会话哦~` 16 | ] 17 | const sendContent: SendContent = { 18 | name: '在线聊天', 19 | reg: /^:/, 20 | role: 'member', 21 | member: ({ user: { name }, operations: { at } }) => msg(at, name), 22 | admin: ({ user: { name }, operations: { at } }) => msg(at, name), 23 | owner: ({ user: { name }, operations: { at } }) => msg(at, name), 24 | deverDefined: ({ user: { name }, operations: { at }, defined }) => [ 25 | [ 26 | `${at('user')} 呜呜 必须输入有效内容才可以进行对话哦~\n[此次未消耗积分]`, 27 | `${at('user')} 咦?暂时没有发现您所输入的聊天内容哦~\n[此次未消耗积分]`, 28 | `${name} 主动一点呀,您可以输入更多的聊天内容来重新尝试发起此会话~\n[此次未消耗积分]` 29 | ], 30 | [ 31 | `${at('user')} ${defined.content}${successTip}`, 32 | `${name},${defined.content}${successTip}` 33 | ] 34 | ] 35 | } 36 | const fn: CommandFn = originData => 37 | new Promise(async res => { 38 | const { group, user, raw_message, database } = originData 39 | const { getDataBaseData, formSet: { user_packsack } } = database 40 | const value = await getDataBaseData(user_packsack.name, user_packsack.retrieveData)(group.id) 41 | const curUser = value[user.id] 42 | if (!curUser) return res(undefined) 43 | const { score } = curUser 44 | if (Number(score) < 1) return res(undefined) 45 | const content = raw_message.replace(/:|\s/g, '') 46 | if (!content) return res({ items: 1, args: {} }) 47 | const url = 'https://api.ownthink.com/bot?appid=xiaosi&userid=user&spoken=' + encodeURIComponent(content) 48 | request(url, (_: unknown, __: unknown, data: string) => { 49 | setPacksack(group.id, user.id, { score: -1 }) 50 | const content = JSON.parse(data).data.info.text 51 | res({ items: 2, args: { content } }) 52 | }) 53 | }) 54 | const xiaoqiuChat = { fn, sendContent } 55 | 56 | export { xiaoqiuChat } 57 | -------------------------------------------------------------------------------- /lib/oicq/interface.ts: -------------------------------------------------------------------------------- 1 | import type { DataBase } from '../../database/interface' 2 | import type { PreGroupchatMessage } from '../../database/forms/interface' 3 | import type { OriginData, MemberBasisInfo, OriginMemberBasisInfo, GroupchatsId } from '../interface' 4 | 5 | type DoAt = (id: number) => string 6 | type ToCqCode = (id: number) => string 7 | type GetHistoryGroupMsg = (gId: GroupchatsId, counts?: number) => PreGroupchatMessage[] 8 | type DelMsg = (groupId: GroupchatsId, otherId: number, num: number) => Promise 9 | type BanMember = (groupId: GroupchatsId, otherId: number, unit: string, dur: number) => string 10 | type PromiseImage = (imgPath: string) => string 11 | 12 | type OicqOperations = { 13 | doAt: DoAt 14 | face: ToCqCode 15 | getHistoryGroupMsg: GetHistoryGroupMsg 16 | delMsg: DelMsg 17 | banMember: BanMember 18 | promiseImage: PromiseImage 19 | } 20 | // 事件处理函数 21 | type GroupData = { 22 | group_id: GroupchatsId 23 | sender: MemberBasisInfo 24 | message_id: string 25 | raw_message: string 26 | group_name: string 27 | self_id: number 28 | atme: boolean 29 | } 30 | type OriginGroupData = { 31 | group_id: GroupchatsId 32 | sender: OriginMemberBasisInfo 33 | message_id: string 34 | raw_message: string 35 | group_name: string 36 | self_id: number 37 | atme: boolean 38 | } 39 | type IncreaseData = { 40 | group_id: GroupchatsId 41 | user_id: number 42 | nickname: string 43 | } 44 | type BanData = { 45 | group_id: number 46 | user_id: number 47 | operator_id: number 48 | duration: number 49 | } 50 | type DecreaseData = { 51 | group_id: number 52 | operator_id: number 53 | member: { 54 | user_id: number 55 | card: string 56 | role: string 57 | nickname: string 58 | } 59 | } 60 | type NoticeGroupPoke = { 61 | group_id: number 62 | self_id: number 63 | user_id: number 64 | target_id: number 65 | operator_id: number 66 | action: string 67 | suffix: string 68 | } 69 | // 用于存储群聊信息/数据,然后传递给被正则触发的函数 70 | type HandleMessage = { 71 | data: OriginGroupData 72 | bot: OriginData['bot'] 73 | operations: OriginData['operations'] 74 | segment: OriginData['segment'] 75 | database: DataBase 76 | } 77 | 78 | export { 79 | OicqOperations, 80 | GroupData, 81 | OriginGroupData, 82 | IncreaseData, 83 | BanData, 84 | DecreaseData, 85 | NoticeGroupPoke, 86 | HandleMessage 87 | } 88 | -------------------------------------------------------------------------------- /eventsHandle/arguments/info.ts: -------------------------------------------------------------------------------- 1 | import { nicknameFormCard } from '../../lib/groupchat' 2 | import { getDataBaseData, formSet } from '../../database' 3 | 4 | import type { HandleMessage } from '../../lib/oicq/interface' 5 | import type { GroupAt, GroupBasisInfo, InfoTemp, MemberBasisInfo } from '../../lib/interface' 6 | 7 | // 格式化一些指令所需信息 8 | const getUserInfo = (info: HandleMessage) => { 9 | const { sender } = info.data 10 | const user: MemberBasisInfo = { 11 | id: sender.user_id, 12 | sex: sender.sex != 'female', 13 | role: sender.role, 14 | card: sender.card, 15 | nickname: sender.nickname, 16 | name: nicknameFormCard({ 17 | nickname: sender.nickname, 18 | card: sender.card 19 | }) 20 | } 21 | return user 22 | } 23 | const member = 'member' as const 24 | const getOtherInfo = ( 25 | id = 10000, 26 | sex = true, 27 | card = '', 28 | role = member, 29 | nickname = '', 30 | name = '' 31 | ) => { 32 | const other: MemberBasisInfo = { id, sex, card, role, nickname, name } 33 | return other 34 | } 35 | const getGroupInfo = (info: HandleMessage) => { 36 | const { group_id, group_name } = info.data 37 | const group: GroupBasisInfo = { 38 | id: group_id, 39 | name: group_name 40 | } 41 | return group 42 | } 43 | const getWantOriginData = ( 44 | info: HandleMessage, 45 | user: MemberBasisInfo, 46 | other: MemberBasisInfo, 47 | group: GroupBasisInfo, 48 | reg: RegExp 49 | ) => { 50 | const { bot, data, operations, segment } = info 51 | const { raw_message } = data 52 | const wantOriginData = { 53 | bot, 54 | segment, 55 | user, 56 | other, 57 | group, 58 | raw_message, 59 | reg, 60 | operations, 61 | database: { 62 | getDataBaseData, 63 | formSet 64 | } 65 | } 66 | return wantOriginData 67 | } 68 | const getInfoTemp = (info: HandleMessage, user: MemberBasisInfo, at: GroupAt) => { 69 | const { operations } = info 70 | const infoTemp: InfoTemp = { 71 | user, 72 | other: { 73 | id: 10000, 74 | card: '', 75 | role: 'member', 76 | sex: true, 77 | nickname: '', 78 | name: '' 79 | }, 80 | defined: {}, 81 | operations: { 82 | at: at, 83 | face: operations.face, 84 | promiseImage: operations.promiseImage 85 | } 86 | } 87 | return infoTemp 88 | } 89 | 90 | export { 91 | getUserInfo, 92 | getOtherInfo, 93 | getGroupInfo, 94 | getWantOriginData, 95 | getInfoTemp 96 | } 97 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/checkDeadPerson/relieveCheckDeadPerson.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [解除人机验证]指令: 3 | * 用于为某位成员手动解除人机验证,解除后,当前成员不受“人机验证”的任何限制 4 | */ 5 | import path from 'path' 6 | 7 | import { persons, inCheckDeadOf } from './tools' 8 | 9 | import type { SendContent, CommandFn } from '../../../lib/interface' 10 | 11 | const sendContent: SendContent = { 12 | name: '解除人机验证', 13 | reg: /^\s*解除人机验证\[CQ:at,qq=(?\d*),text=.*\]\s*$/, 14 | role: 'admin', 15 | member: ({ user: { sex, name }, operations: { at, face, promiseImage } }) => [ 16 | `${at('user')} 您的权限不足以进行解除人机验证的操作${promiseImage(path.resolve('./assets/images/emoji/反了你了1.jpg'))}`, 17 | `${name},抱歉 小秋检测到您暂未拥有[解除人机验证]指令的权限`, 18 | `${at('other')}${sex ? '哥哥' : '姐姐'},<${at('user')}>要对你进行人机验证${face(178)}`, 19 | `${at('user')} 要造反了???这[解除人机验证]可不能说用就用啊`, 20 | `${name},迎难而上??小心被反验证哦~`, 21 | `${at('user')} 抱歉 小秋不能帮您执行该指令` 22 | ], 23 | deverDefined: ({ user: { name: username, sex }, other: { name: othername }, operations: { at, face, promiseImage } }) => [ 24 | [ 25 | `${at('user')} 哎呀 ${othername}当前未在进行人机验证哦,因此无需解除`, 26 | `${at('user')} 小秋检测到${othername}未处于人机验证环节,可以不用解除哦${face(176)}`, 27 | `${at('user')} 不可以对未进行人机验证的成员进行解除哦~`, 28 | `${at('other')} 快谢谢${username},虽然你没有在进行人机验证,但${sex ? '他' : '她'}还想帮你解除哦~${promiseImage(path.resolve('./assets/images/emoji/比心3.jpg'))}` 29 | ], 30 | [ 31 | `${at('other')} ${username}已关闭您目前所处于的人机验证阶段${face(180)}`, 32 | `${at('other')} 您当前所进行的人机验证已关闭\n[来自${username}]`, 33 | `${at('other')} 人品大爆发 ${username}帮您解除了人机验证${promiseImage(path.resolve('./assets/images/emoji/朋友醒醒1.jpg'))}` 34 | ] 35 | ], 36 | equal: ({ other: { name }, operations: { at, face } }) => [ 37 | `${at('user')} 大家都一样 不用给${name}解除${face(178)}`, 38 | `${at('user')} 哎呀 ${name}不需要小秋进行解除人机验证哦`, 39 | `${at('user')} 放心好啦 ${name}能够免疫人机验证` 40 | ], 41 | level: ({ operations: { at, promiseImage } }) => [ 42 | `${at('user')} 抱歉 小秋暂时无法使用[解除人机验证]指令`, 43 | `${at('user')} 很抱歉 小秋权限不足${promiseImage(path.resolve('./assets/images/emoji/我一路向北。离开有你的季节1.jpg'))}`, 44 | `${at('user')} 抱歉 小秋暂时无法为其解除人机验证环节` 45 | ] 46 | } 47 | const fn: CommandFn = originData => { 48 | const { group, other } = originData 49 | // 判断是否已经处于人机验证当中 50 | const haved = inCheckDeadOf(group.id, other.id) 51 | if (!haved) return { items: 1 } 52 | // 否则进行关闭 53 | const id = String(group.id) + String(other.id) 54 | const { token } = persons.get(id)! 55 | clearTimeout(token) 56 | persons.delete(id) 57 | return { items: 2 } 58 | } 59 | const relieveCheckDeadPerson = { fn, sendContent } 60 | 61 | export { relieveCheckDeadPerson } 62 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/delMemberMessage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [撤回]指令: 3 | * 撤回指定成员的消息,撤回的条数可选,默认为全部 4 | */ 5 | import path from 'path' 6 | 7 | import type { SendContent, CommandFn } from '../../lib/interface' 8 | 9 | const sendContent: SendContent = { 10 | name: '撤回', 11 | reg: /^撤回(?\d*)\[CQ:at,qq=(?\d*),text=.*\]\s*$/, 12 | role: 'admin', 13 | member: ({ operations: { at, promiseImage } }) => [ 14 | `${at('user')} 想啥呢 撤回指令得管理/群主才可以触发哦~`, 15 | `${at('user')} 抱歉 您暂无撤回权限${promiseImage(path.resolve('./assets/images/emoji/轻轻敲打沉睡的心灵1.jpg'))}`, 16 | `${at('user')} 小秋检测到您暂无撤回权限` 17 | ], 18 | deverDefined: ({ user: { sex }, other: { name }, defined: { count }, operations: { at, face, promiseImage } }) => [ 19 | [ 20 | `${at('user')} 请输入有效条数`, 21 | `${at('user')} ${sex ? '老弟' : '老妹'},这撤回条数不对啊${promiseImage(path.resolve('./assets/images/emoji/您可蒜了吧1.jpg'))}`, 22 | `${at('user')} 小秋提示您请输入一个合法的撤回条数` 23 | ], 24 | [ 25 | `${at('user')} 检测到${name}并没有发送${count}条消息,建议减少要撤回的消息条数`, 26 | `${at('user')} 超载了!${name}并没有发送${count}条消息\n[建议减少要撤回的消息条数]`, 27 | `${at('user')} 撤回条数的太多啦,您可以减少要撤回的消息条数 然后重新进行尝试${promiseImage(path.resolve('./assets/images/emoji/我突然烦你了拜拜1.jpg'))}` 28 | ], 29 | [ 30 | `${at('other')} 已撤回您${count}条消息,请注意言辞 文明且积极发言,下次不再警告${face(179)}`, 31 | `${at('other')} 小秋已撤回您${count}条消息,请不要再次发送类似的言论`, 32 | `${at('other')} 您被小秋撤回了${count}条消息,小秋提醒您一定要遵守群规哦~` 33 | ] 34 | ], 35 | equal: ({ user: { name }, operations: { at, face } }) => [ 36 | `${at('user')} 对不起 小秋无法帮您撤回消息`, 37 | `${at('other')},${name}要撤回你消息哎${face(174)}` 38 | ], 39 | level: ({ operations: { at } }) => [ 40 | `${at('user')} 抱歉 小秋权限不足 暂时不能帮您撤回消息~`, 41 | `${at('user')} 呜呜 小秋权限不足 暂时不能帮您撤回消息~` 42 | ] 43 | } 44 | const fn: CommandFn = async originData => { 45 | const { group, raw_message, reg, operations: { delMsg } } = originData 46 | // 被艾特的人、以及要撤回几条 47 | const { qq, num } = reg.exec(raw_message)?.groups! 48 | const otherId = Number(qq) 49 | // 默认撤回全部 50 | if (num === '') { 51 | const rows = await delMsg(group.id, otherId, 0) 52 | return rows === 0 ? { items: 2, args: { count: 1 } } : undefined 53 | } 54 | // 撤回的条数小于等于0 55 | const row = Number(num) 56 | if (row <= 0) return { items: 1 } 57 | // 正确条数(不考虑当前群聊是否有消息产生,因为在使用撤回指令时,已经产生了消息) 58 | const count = await delMsg(group.id, otherId, row) 59 | // 撤回失败,因为对方没有发送消息 60 | if (count === 0) return { items: 2, args: { count: row } } 61 | // 撤回成功 62 | return { items: 3, args: { count } } 63 | } 64 | const delMemberMessage = { fn, sendContent } 65 | 66 | export { delMemberMessage } 67 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/makeSuggestion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [提建议]指令: 3 | * 提建议指令作为小秋开发者与小秋用户之间一种沟通的桥梁而存在,小秋会将用户提出的建议记录至数据库中,并由小秋开发者来标识当前建议的进度如何 4 | */ 5 | import type { SuggestionFormat } from '../../database/forms/interface' 6 | import type { SendContent, CommandFn } from '../../lib/interface' 7 | 8 | const sendContent: SendContent = { 9 | name: '提建议', 10 | reg: /^提建议:/, 11 | role: 'member', 12 | member: ({ user: { name }, operations: { at } }) => [ 13 | `${at('user')} 提交失败啦,每条建议都必须要在五个字以上`, 14 | `${name},请尝试重新描述您遇到的问题\n[建议必须要在五个字以上]`, 15 | `${name},本次提交未通过 尝试多输入点内容吧` 16 | ], 17 | admin: ({ operations: { at } }) => [ 18 | `${at('user')} 管理大哥 本次提交未通过\n[原因:应至少五个字]`, 19 | `${at('user')} 好管理 建议应至少包含五个字符`, 20 | `${at('user')} 提交失败啦,最少要有五个字哦` 21 | ], 22 | owner: ({ operations: { at } }) => [ 23 | `${at('user')} 尊贵的群主大人 您本次提交未通过,原因:应至少五个字`, 24 | `群主大人,建议至少要有五个字哦~ 否则小秋不能帮您提交`, 25 | `${at('user')} 好群主 建议应至少包含五个字符` 26 | ], 27 | deverDefined: ({ user: { name }, operations: { at }, defined: { len, content } }) => [ 28 | [ 29 | `${at('user')} [提建议]指令的字数最多只能30字哦~`, 30 | `${at('user')} 超过30个字了,小秋记不住呀`, 31 | `${name},抱歉 请尝试缩短建议后重新提交 ` 32 | ], 33 | [ 34 | `${at('user')} 抱歉 小秋暂不支持此类型的建议`, 35 | `${at('user')} 此建议暂时不能被收纳 请尝试重新整理后再次发送`, 36 | `${at('user')} 小秋暂不能收纳该建议哦` 37 | ], 38 | [ 39 | `${at('user')} 您的第${len}条建议『${content}』已被收纳,感谢反馈。`, 40 | `${at('user')} 您的第${len}条建议『${content}』已被收纳,感谢你为小秋的付出~`, 41 | `${at('user')} 灰常感谢,您的第${len}条建议『${content}』已被收纳,开发者看到后会在第一时间进行处理!` 42 | ] 43 | ] 44 | } 45 | // 提建议不分群聊 46 | const fn: CommandFn = async originData => { 47 | const { user: { id }, raw_message, database } = originData 48 | const { getDataBaseData, formSet: { suggestions } } = database 49 | const content = raw_message.slice(4) 50 | if (content.length < 5) return 51 | if (content.length > 30) return { items: 1 } 52 | // 建议中不能包含图片、语音、音视频等(判断标准为CQ码) 53 | const cqReg = /\[CQ:(.*)\]/gi 54 | if (cqReg.test(content)) return { items: 2 } 55 | const sugs = await getDataBaseData(suggestions.name, suggestions.rowSuggestion)(id) 56 | const sug: SuggestionFormat = { 57 | content, 58 | timestamp: +new Date(), 59 | plan: '已反馈给开发同学' 60 | } 61 | const newSugList = sugs ? [...sugs, sug] : [sug] 62 | if (sugs) await getDataBaseData(suggestions.name, suggestions.updateData)(id, newSugList) 63 | else await getDataBaseData(suggestions.name, suggestions.recordRow)(id, newSugList) 64 | return { items: 3, args: { len: newSugList.length, content } } 65 | } 66 | const makeSuggestion = { fn, sendContent } 67 | 68 | export { makeSuggestion } 69 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/banCommon.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [禁言]指令: 3 | * 禁言指定成员,禁言的时间由时间单位决定 4 | */ 5 | import path from 'path' 6 | 7 | import { belong } from '../../lib/time' 8 | 9 | import type { SendContent, CommandFn } from '../../lib/interface' 10 | 11 | const sendContent: SendContent = { 12 | name: '禁言', 13 | reg: /^禁言(?\d*)(?.*)\[CQ:at,qq=\d*,text=.*\]\s*$/, 14 | role: 'admin', 15 | member: ({ user: { name }, operations: { at } }) => [ 16 | `${at('user')} 想啥呢 禁言指令得管理/群主才可以触发哦~`, 17 | `${at('other')} ${name}要禁言你`, 18 | `${at('user')} 抱歉 您没有禁言权限哦` 19 | ], 20 | deverDefined: ({ user: { name, sex }, defined: { dur, max, unit }, operations: { at, face, promiseImage } }) => [ 21 | [ 22 | `${at('user')} 最低禁言的时间单位为1`, 23 | `${at('user')} ${sex ? '老弟' : '老妹'},你这单位不太对劲啊${promiseImage(path.resolve('./assets/images/emoji/给你一小巴掌1.jpg'))}`, 24 | `${name},小秋提示您 最低可禁言的时间单位为1` 25 | ], 26 | [ 27 | `${at('user')} 怎么还有按年为单位禁言的啊!`, 28 | `${at('user')} 禁言单位不能是年`, 29 | `${at('user')} 禁言${dur}年??${face(291)}`, 30 | `${name},事不能做的太绝 哪有按年禁言的呀` 31 | ], 32 | [ 33 | `${at('user')} 单位错误`, 34 | `${name} 单位错了!单位错了!`, 35 | `${at('user')} 小秋检测到您输入的禁言时间单位错误${face(283)}`, 36 | `${at('user')} 小秋暂时还不认识当前时间单位哦~` 37 | ], 38 | [ 39 | `${at('user')} 不行~最长禁言时间为${max}${unit}`, 40 | `${name},超过最长禁言时间${max}${unit}了`, 41 | `${at('user')} 小秋提示您最长禁言时间为${max}${unit}${promiseImage(path.resolve('./assets/images/emoji/我突然烦你了拜拜1.jpg'))}` 42 | ] 43 | ], 44 | equal: ({ user: { name: username }, other: { name: othername }, operations: { at, face } }) => [ 45 | `${at('user')} 对不起,小秋无法帮您进行禁言${face(187)}`, 46 | `${at('other')} ${username}要禁言你`, 47 | `${at('user')} 别搞事 我可不敢禁言${othername}` 48 | ], 49 | level: ({ operations: { at } }) => [ 50 | `${at('user')} 抱歉 小秋权限不足 暂时不能帮您执行禁言操作哦~`, 51 | `${at('user')} 小秋暂时无法为您执行禁言操作` 52 | ] 53 | } 54 | const fn: CommandFn = originData => { 55 | const { group, other, raw_message, reg, operations: { banMember } } = originData 56 | // 禁言时间的单位只能是分钟、小时、秒 57 | const { durStr, unit } = reg.exec(raw_message)?.groups! 58 | const dur = Number.parseFloat(durStr) 59 | if (dur < 1) return { items: 1 } 60 | if (unit === '年') return { items: 2, args: { dur } } 61 | if (unit !== '分钟' && unit !== '小时' && unit !== '天') return { items: 3 } 62 | // 最多只能禁言30天的时间(30天,720小时,43200分钟) 63 | if (dur > belong[unit].max) return { items: 4, args: { max: belong[unit].max, unit } } 64 | banMember(group.id, other.id, unit, dur) 65 | return { noMsg: true } 66 | } 67 | const banCommon = { fn, sendContent } 68 | 69 | export { banCommon } 70 | -------------------------------------------------------------------------------- /lib/interface/command.ts: -------------------------------------------------------------------------------- 1 | // 有关Oicq的类型,本文件使用any来替代(例如bot、segment、...),具体api请参考其官方文档 2 | //// 3 | 4 | import type { DataBase } from '../../database/interface' 5 | import type { AuthIdType } from '../user' 6 | import type { OicqOperations } from '../oicq/interface' 7 | import type { CommandsName } from '../../database/forms/interface' 8 | import type { GroupchatsId } from './account' 9 | import type { GetObject } from './toolsType' 10 | 11 | // 每个指令的配置项 12 | type Role = 'member' | 'admin' | 'owner' 13 | type LowestRole = 'member' | 'admin' | 'owner' 14 | type GroupAt = (who: 'user' | 'other') => string 15 | type PersonInfo = { 16 | card: string 17 | nickname: string 18 | role: Role 19 | } 20 | 21 | type MemberBasisInfo = { 22 | id: number 23 | sex: boolean 24 | // card、nickname有可能为空,所以使用name兜底 25 | name: string 26 | } & PersonInfo 27 | type OriginMemberBasisInfo = { 28 | user_id: number 29 | sex: string 30 | } & PersonInfo 31 | type GroupBasisInfo = { 32 | id: GroupchatsId 33 | name: string 34 | } 35 | 36 | type InfoTemp = { 37 | user: MemberBasisInfo 38 | other: MemberBasisInfo 39 | defined: GetObject 40 | operations: { 41 | at: GroupAt 42 | face: OicqOperations['face'] 43 | promiseImage: OicqOperations['promiseImage'] 44 | } 45 | } 46 | 47 | type ReturnsMsg = (infoTemp: InfoTemp) => string[] 48 | type SendContent = { 49 | // 当前指令的名称 50 | name: CommandsName 51 | // 触发该指令的条件 52 | reg: RegExp | RegExp[] 53 | // 能够触发该指令的角色 54 | role: Role | AuthIdType 55 | // 触发者是普通成员 56 | member?: ReturnsMsg 57 | // 触发者是管理员 58 | admin?: ReturnsMsg 59 | // 触发者是群主 60 | owner?: ReturnsMsg 61 | // 自定义参数 62 | deverDefined?: (infoTemp: InfoTemp) => string[][] 63 | // 当存在被执行人时,是否希望检查小秋与被执行人的权限 64 | // 指定了该值,则代表小秋执行当前指令时会校验小秋与被执行人的权限是否相同;若未指定,则代表无需进行相同权限判断 65 | equal?: ReturnsMsg, 66 | // 表示执行该指令时小秋所需的最低身份 67 | // 指定了该值,则代表小秋最低身份必须为admin;若未指定该值,则代表小秋最低身份只需是member即可 68 | level?: ReturnsMsg 69 | } 70 | // 每个指令所对应的处理函数的形参、返回值 71 | type OriginData = { 72 | bot: any 73 | segment: any 74 | user: MemberBasisInfo 75 | other: MemberBasisInfo 76 | group: GroupBasisInfo 77 | raw_message: string 78 | reg: RegExp 79 | operations: OicqOperations 80 | database: DataBase 81 | } 82 | type ReturnConfig = { 83 | items?: number 84 | args?: GetObject 85 | noMsg?: boolean 86 | } 87 | type CommandFn = ( 88 | originData: OriginData 89 | ) => undefined | void | ReturnConfig | Promise 90 | 91 | export { 92 | Role, 93 | LowestRole, 94 | GroupAt, 95 | MemberBasisInfo, 96 | OriginMemberBasisInfo, 97 | SendContent, 98 | GroupBasisInfo, 99 | InfoTemp, 100 | OriginData, 101 | CommandFn 102 | } 103 | -------------------------------------------------------------------------------- /eventsHandle/xiaoqiu/isXiaoQiuOnline.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [小秋小秋]指令: 3 | * 查看小秋是否处于在线状态 4 | */ 5 | import { getCurTime, getCurTimeAlias } from '../../lib/time' 6 | 7 | import type { GroupAt } from '../../lib/interface' 8 | import type { SendContent, CommandFn } from '../../lib/interface' 9 | 10 | const male = (at: GroupAt, name: string) => [ 11 | `小秋一直在`, 12 | `${at('user')} ${getCurTimeAlias()}`, 13 | `披荆斩棘的${name},你好呀`, 14 | `${at('user')} Hi,你想做我的小迷弟吗?`, 15 | `${at('user')} 兄弟,${getCurTime()}了,你想干啥`, 16 | `${name}哥哥好`, 17 | `小秋暂时不在哦,${getCurTime()}了,眯一会~` 18 | ] 19 | const female = (at: GroupAt, name: string) => [ 20 | `${at('user')} 来了来了,请问小秋可以做你的闺蜜吗`, 21 | `${at('user')} 姐姐是想小秋了嘛~`, 22 | `小秋一直在的哦`, 23 | `乘风破浪的${name},你好`, 24 | `${at('user')} 如果小秋会魔法的话,那你会做小秋的迷妹嘛`, 25 | `小秋来啦` 26 | ] 27 | const reg = [ 28 | /^小秋小秋$/, 29 | /^在吗小秋$/, 30 | /^你好小秋$/, 31 | /^小秋你好$/, 32 | /^嘿小秋$/, 33 | /^小秋在吗$/, 34 | /^嘿,小秋$/, 35 | /^小秋同学$/, 36 | /^秋总$/, 37 | /^小秋$/, 38 | /^小小秋$/, 39 | /^秋秋$/ 40 | ] 41 | const admin = (at: GroupAt, name: string, sex: boolean) => { 42 | const maleFn = () => { 43 | const arrs = male(at, name) 44 | const text = [ 45 | `${at('user')} 管理哥哥,小秋一直在这里哦`, 46 | `${at('user')} 管理哥哥你好,我叫小秋~`, 47 | `${at('user')} 小秋好想拥有一个身为管理员的小迷弟哦~` 48 | ] 49 | return [...arrs, ...text] 50 | } 51 | const femaleFn = () => { 52 | const arrs = female(at, name) 53 | const text = [ 54 | `${at('user')} 管理姐姐,小秋会一直陪伴你哦`, 55 | `${at('user')} 管理姐姐你好,我叫小秋,你可以叫我秋秋哦`, 56 | `${at('user')} 小秋也想拥有一个身为管理员的小迷妹哦~` 57 | ] 58 | return [...arrs, ...text] 59 | } 60 | const result = sex ? maleFn() : femaleFn() 61 | return result 62 | } 63 | const owner = (at: GroupAt, name: string, sex: boolean) => { 64 | const maleFn = () => { 65 | const arrs = male(at, name) 66 | const text = [ 67 | `${at('user')} 群主大人,小秋一直在哦`, 68 | `${at('user')} 如果群主是小秋的小迷弟就好咯~` 69 | ] 70 | return [...arrs, ...text] 71 | } 72 | const femaleFn = () => { 73 | const arrs = female(at, name) 74 | const text = [ 75 | `${at('user')} 群主姐姐,小秋会一直陪伴你哦`, 76 | `${at('user')} 如果群主是小秋的小迷妹就太棒啦~` 77 | ] 78 | return [...arrs, ...text] 79 | } 80 | const result = sex ? maleFn() : femaleFn() 81 | return result 82 | } 83 | const sendContent: SendContent = { 84 | name: '小秋你好', 85 | reg, 86 | role: 'member', 87 | member: ({ user: { name, sex }, operations: { at } }) => sex ? male(at, name) : female(at, name), 88 | admin: ({ user: { name, sex }, operations: { at } }) => admin(at, name, sex), 89 | owner: ({ user: { name, sex }, operations: { at } }) => owner(at, name, sex) 90 | } 91 | const fn: CommandFn = () => { } 92 | const isXiaoQiuOnline = { fn, sendContent } 93 | 94 | export { isXiaoQiuOnline } 95 | -------------------------------------------------------------------------------- /eventsHandle/otherPlay/listenMusic.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [听歌]指令: 3 | * 使用网易云Api,以QQ语音的格式(song-id.amr)发送用户所指定的歌曲 4 | * (音频转换时可能会出现音质破损) 5 | */ 6 | import fs from 'fs' 7 | import request from 'request' 8 | 9 | import type { MusicId, MusicUrl } from './interface' 10 | import type { SendContent, CommandFn } from '../../lib/interface' 11 | 12 | const sendContent: SendContent = { 13 | name: '听歌', 14 | reg: /^\s*听(?.*)\s*$/, 15 | role: 'member', 16 | deverDefined: ({ defined: { songName }, operations: { at } }) => [ 17 | [ 18 | `${at('user')} 哎呀 讨厌!歌名当然不允许为空啦`, 19 | `${at('user')} 哼 你没有输入歌曲名,那小秋就不会唱给你听喽~`, 20 | `${at('user')} 请输入一个歌曲名称` 21 | ], 22 | [ 23 | `${at('user')} 稍等稍等 小秋唱的有点口渴啦~`, 24 | `${at('user')} 让小秋喝完这口水再唱吧`, 25 | `${at('user')} 小秋好累呀 可以不唱了嘛~` 26 | ], 27 | [ 28 | `${at('user')} 您点的《${songName}》来喽~`, 29 | `${at('user')} 小秋录制了一首您点的《${songName}》,快来听听吧~`, 30 | `${at('user')} 不得不说《${songName}》唱的好棒呀~` 31 | ], 32 | [ 33 | `${at('user')} 哎呀 网络好像开小差了,没有点到《${songName}》`, 34 | `${at('user')} 小秋发现网络好像芭比Q了,《${songName}》没有成功播放,不过您可以再试一次哦~` 35 | ] 36 | ] 37 | } 38 | 39 | const lh = 'http://localhost:3000' 40 | const writeFileFromRequest = (url: string, localUrl: string) => 41 | new Promise(r => { 42 | const ws = fs.createWriteStream(localUrl) 43 | request(url).pipe(ws) 44 | ws.on('close', r) 45 | }) 46 | const getMusicId = (url: string): Promise => 47 | new Promise(r => request(`${lh}${url}`, (_: unknown, __: unknown, data: string) => r(JSON.parse(data)))) 48 | const getMusicUrl = (url: string): Promise => 49 | new Promise(r => request(`${lh}${url}`, (_: unknown, __: unknown, data: string) => r(JSON.parse(data)))) 50 | 51 | const fn: CommandFn = originData => 52 | new Promise(async res => { 53 | const { bot, group, raw_message, segment, reg } = originData 54 | const songName = reg.exec(raw_message)?.groups?.name! 55 | if (songName === '') return res({ items: 1 }) 56 | const dataId = await getMusicId(`/search?keywords=${encodeURI(songName)}`) 57 | if (dataId.code !== 200) return res({ items: 2 }) 58 | const id = dataId.result.songs[0].id 59 | const dataUrl = await getMusicUrl(`/song/url?id=${id}`) 60 | if (dataUrl.code !== 200) return res({ items: 2 }) 61 | const url = dataUrl.data[0].url 62 | const filename = `./assets/videos/listen/${id}.mp3` 63 | await writeFileFromRequest(url, filename) 64 | fs.readFile(filename, (_: NodeJS.ErrnoException | null, data: Buffer) => { 65 | try { 66 | const video = segment.record(Buffer.from(data)) 67 | bot.sendGroupMsg(group.id, video) 68 | res({ items: 3, args: { songName } }) 69 | } catch (e) { 70 | res({ items: 4, args: { songName } }) 71 | } 72 | }) 73 | }) 74 | const listenMusic = { fn, sendContent } 75 | 76 | export { listenMusic } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 小秋(xiaoqiu) 2 | 3 | ## 项目介绍 4 | 5 | 机器人基于库OICQ进行开发,其菜单可分为五个部分,即群管理操作、群聊相关、小秋相关、积分玩法、其它玩法。每个部分均由若干指令构成,每个指令又有独自的配置项,例如指令名称、返回内容、触发其所需的最低权限等,且每个指令的触发方式有多种(作为隐藏彩蛋),成员可通过\[菜单\]指令获取更加具体的玩法 6 | 7 | ## 生成项目 8 | 9 | 克隆至本地 10 | 11 | ```javascript 12 | git clone https://github.com/777miss/xiao-qiu.git 13 | ``` 14 | 15 | 在根目录`/xiao-qiu`中安装其声明文件 16 | 17 | ```javascript 18 | npm i -D 19 | ``` 20 | 21 | 由于账号所对应的数据问题,需要将`xiao-qiu/lib`下的`user-copy`文件夹名称修改为`user`(`user`文件中所需的数据可稍后进行配置),随后在`xiao-qiu`中编译TS文件 22 | 23 | ```javascript 24 | tsc 25 | ``` 26 | 27 | 使用`tsc`命令编译后会生成`xiaoqiu`新文件夹,部署时只需部署该文件夹即可 28 | 29 | ## 准备工作 30 | 31 | 按照上述步骤生成项目后,目录结构为 32 | 33 | ``` 34 | ├─xiao-qiu // 开发环境 35 | | ├─assets 36 | | ├─database 37 | | ├─eventHandle 38 | | ├─lib 39 | | ├─temporary 40 | | ├─timing 41 | | ├─xiaoqiu // 使用tsc命令生成的js文件所在处 42 | | ├─...... 43 | | ├─app.ts 44 | ``` 45 | 46 | 其`xiao-qiu/assets`文件夹结构如下 47 | ``` 48 | ├─assets 49 | | ├─images 50 | | ├─readme 51 | | ├─videos 52 | | ├─package.json 53 | | ├─xiaoqiu_alpha.sql 54 | ``` 55 | 56 | 将`xiao-qiu/assets`文件夹整体移至`xiao-qiu/xiaoqiu`文件夹下,并将`xiao-qiu/xiaoqiu/assets`中的`pakcage.json`文件移至`xiao-qiu/xiaoqiu`目录下,新的`xiao-qiu/xiaoqiu`目录结构为 57 | 58 | ``` 59 | ├─xiaoqiu 60 | | ├─assets 61 | | | └images 62 | | | └readme 63 | | | └videos 64 | | | └xiaoqiu_alpha.sql 65 | | ├─...... 66 | | ├─app.js 67 | | ├─package.json 68 | ``` 69 | 70 | 随后在`xiao-qiu/xiaoqiu`下安装所需依赖 71 | 72 | ```javascript 73 | npm i 74 | ``` 75 | 76 | ## 账号相关 77 | 78 | `xiao-qiu/lib/user`中存在`account.ts`与`account-alpha.ts`两个文件,前者为正式版配置,后者为测试版配置,可以在`xiao-qiu/lib/user/index.ts`中根据不同的需要进行切换 79 | 80 | 以`account-alpha.ts`为例,用户必须指定机器人的登录账号与密码、所监听的群聊账号、要修改群昵称的群聊账号、绝对权限、以及数据库配置 81 | 82 | 绝对权限是相对于指令的叫法,对于用户来说,绝对权限即特定用户,是独立于`member(普通成员)`、`admin(管理员)`、`owner(群主)`之外的用户,若某个指令指定了其所需的最低触发权限为特定用户,则只有该特定用户可触发,判断规则是账号层面的判断,而不是权限层面的判断 83 | 84 | 数据库配置方面,您可以选择新建一个数据库,并运行`xiao-qiu/xiaoqiu/assets/xiaoqiu_alpha.sql`文件,创建其所需数据结构(项目启动时会自动初始化表数据,所以只需关心它们的结构,而非数据),然后在`connectionDbConfig`中指定刚才创建好的数据库相关数据,例如 85 | 86 | ```javascript 87 | const connectionDbConfig = { 88 | host: '主机名', 89 | user: '数据库账号', 90 | password: '数据库密码', 91 | database: '数据库名称' 92 | } 93 | ``` 94 | 95 | > 在修改`account-alpha.ts`文件之后,应当重新执行`tsc`命令 96 | 97 | ## 启动项目 98 | 99 | 完成以上步骤,便可在`xiao-qiu/xiaoqiu`下启动该项目 100 | 101 | ```javascript 102 | node app 103 | ``` 104 | 105 | 初次登陆时,您可能会收到一条滑动验证,此时需要在该验证地址中得到`token`,然后将其输入在终端窗口中即可成功登陆(在`network`中找到对应请求地址) 106 | 107 | > 登录后,`oicq`会在您的`xiao-qiu/xiaoqiu`下自动生成`data`文件夹,用于存放账号相关数据 108 | 109 | ## 开发新指令 110 | 111 | > 只要您修改了源代码,则必须通过`tsc`命令生成新的JS文件,并`node app`运行它们 112 | 113 | 由于每个指令配置项众多,所以您可以参考`xiao-qiu/lib/Temp/temp-comment.ts`进行开发,或者您可以选择复制一份`xiao-qiu/lib/Temp/temp.ts`文件至您的新指令所在处 114 | 115 | 开发新指令时您应当注意: 116 | 117 | 1. 在`xiao-qiu/database/forms/interface.ts`中新增一个指令名称 118 | 2. 在`xiao-qiu/eventHandle/fn.ts`中引入新指令 119 | 3. 确保数据库中`switch_commands`中该指令为开启状态,否则不会被触发 120 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/setGroupWholeBan.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [全员禁言]指令: 3 | * 开启/关闭全员禁言,禁言的单位同[禁言]指令相同 4 | */ 5 | import { belong, secToFormat, getWholeDate } from '../../lib/time' 6 | 7 | import type { SendContent, CommandFn } from '../../lib/interface' 8 | 9 | const sendContent: SendContent = { 10 | name: '全员禁言', 11 | reg: /^(开启|关闭)全员禁言(?\d*)(?.*)$/, 12 | role: 'admin', 13 | member: ({ operations: { at } }) => [ 14 | `${at('user')} 造反啊?小小成员 可笑可笑`, 15 | `${at('user')} 这全员禁言 说开就开?`, 16 | `${at('user')} 您无此权限~` 17 | ], 18 | deverDefined: ({ user: { sex }, defined, operations: { at } }) => [ 19 | [ 20 | `${at('user')} 全员禁言已被关闭`, 21 | `${at('user')} 小秋已关闭全员禁言~`, 22 | `${at('user')} ,${sex ? '哥哥' : '姐姐'} 小秋已经把全员禁言关闭了` 23 | ], 24 | [ 25 | `${at('user')} 哎呀 最低全员禁言时间单位为1哦~`, 26 | `${at('user')} 小秋检测到了非法的时间单位,请重新尝试`, 27 | `${at('user')} 呜呜呜 小秋还不认识当前的时间单位哦~` 28 | ], 29 | [ 30 | `${at('user')} 全员禁言${defined.dur}年,还让不让群友活了啊`, 31 | `${at('user')} 小秋不能帮您进行全员禁言${defined.dur}年`, 32 | `${at('user')} 全员禁言${defined.dur}年,这不符合美少女小秋的风格哦~` 33 | ], 34 | [ 35 | `${at('user')} 请输入正确的时间单位`, 36 | `${at('user')} 小秋提示您 请输入正确的时间单位`, 37 | `${at('user')} 哎呀 小秋还不认识这个时间单位呢~` 38 | ], 39 | [ 40 | `${at('user')} 最长可全员禁言时间为${defined.bMax}${defined.unit}`, 41 | `${at('user')} 小秋提醒您,最长可全员禁言时间为${defined.bMax}${defined.unit}`, 42 | `${at('user')} 呜呜 小秋最多只能帮您全员禁言${defined.bMax}${defined.unit}哦~` 43 | ], 44 | [ 45 | `${at('user')} 芜湖 您已开启全员禁言${defined.time}\n${defined.nextTime}`, 46 | `小秋已开启全员禁言${defined.time}\n${defined.nextTime}` 47 | ] 48 | ], 49 | level: ({ operations: { at } }) => [ 50 | `${at('user')} 抱歉 小秋权限不足 暂时不能帮您执行全员禁言操作哦~`, 51 | `${at('user')} 呜呜 小秋权限不足 暂时不能帮您执行全员禁言操作哦~` 52 | ] 53 | } 54 | const fn: CommandFn = originData => { 55 | const { bot, group, raw_message, reg } = originData 56 | // oicq1.x中无法得知当前是否处于全员禁言状态 57 | if (/^关闭全员禁言/gi.test(raw_message)) { 58 | bot.setGroupWholeBan(group.id, false) 59 | return { items: 1 } 60 | } 61 | const { dur, unit } = reg.exec(raw_message)?.groups! 62 | const durNum = Number.parseFloat(dur) 63 | if (durNum < 1) return { items: 2 } 64 | if (unit === '年') return { items: 3, args: { dur } } 65 | if (unit !== '分钟' && unit !== '小时' && unit !== '天') return { items: 4 } 66 | // 最多只能禁言30天的时间(30天,720小时,43200分钟) 67 | const bMax = belong[unit].max 68 | const bSec = belong[unit].sec(durNum) 69 | const bMs = bSec * 1000 70 | if (durNum > bMax) return { items: 5, args: { bMax, unit } } 71 | bot.setGroupWholeBan(group.id, true) 72 | const nextTime = getWholeDate(new Date(+new Date() + bMs)) 73 | setTimeout(() => { 74 | bot.setGroupWholeBan(group.id, false) 75 | }, bMs) 76 | return { items: 6, args: { time: secToFormat(bSec), nextTime: `[${nextTime}]自动关闭` } } 77 | } 78 | const setGroupWholeBan = { fn, sendContent } 79 | 80 | export { setGroupWholeBan } 81 | -------------------------------------------------------------------------------- /database/forms/interface.ts: -------------------------------------------------------------------------------- 1 | import type { CommandsConfig } from '../../eventsHandle/fns' 2 | 3 | // groups_config表中的群配置 4 | type GroupEvents = 'message' | 'increase' | 'ban' | 'decrease' | 'poke' 5 | type GroupsConfig = { 6 | draw: { 7 | discount: 5 | 6 | 7 | 8 | 9 8 | timestamp: number 9 | } 10 | setCard: { 11 | isAuto: boolean 12 | content: string 13 | } 14 | events: { 15 | [K in GroupEvents]: boolean 16 | } 17 | score: { 18 | dailyLimit: number 19 | } 20 | } 21 | // 存储单条群聊消息的格式(在群聊中每产出一条消息,均按照此固定格式存储) 22 | type PreGroupchatMessage = { 23 | userId: number 24 | msgId: string 25 | timestamp: number 26 | content: string 27 | } 28 | // suggestions表中所存储的每条建议的格式 29 | type SuggestionFormat = { 30 | content: string 31 | timestamp: number 32 | plan: string, 33 | result?: string 34 | } 35 | type SuggestionsWhole = { 36 | user_id: number 37 | suggestion: string // SuggestionFormat[] 38 | } 39 | type SuggestionsWholeFormat = SuggestionsWhole[] 40 | // switch_commands表中所存储的指令名称 41 | /* type CommandsName = CommandsConfig[keyof CommandsConfig] extends { 42 | fn: any, 43 | sendContent: { 44 | name: `${infer U}`, 45 | [k: string]: any 46 | } 47 | } ? `${U}` : never */ 48 | type CommandsName = 49 | | '问题格式' 50 | | '签到' 51 | | '人机验证' 52 | | '解除人机验证' 53 | | '禁止发言' 54 | | '解除禁止发言' 55 | | '改群昵称' 56 | | '警告' 57 | | '禁言' 58 | | '撤回' 59 | | '踢' 60 | | '全员禁言' 61 | | 'js' 62 | | '搜掘金' 63 | | '文案' 64 | | '查建议' 65 | | '早' 66 | | '晚' 67 | | '斗地主' 68 | | '听歌' 69 | | '提建议' 70 | | '在线聊天' 71 | | '抽奖' 72 | | '发放积分' 73 | | '我的背包' 74 | | '道具卡' 75 | | '抽奖限时折扣' 76 | | '查询限时折扣' 77 | | '获取积分方式' 78 | | '小秋你好' 79 | | '菜单' 80 | | '切换指令' 81 | | '小秋版本介绍' 82 | type GroupsSwitchCommands = { 83 | [k in CommandsName]: boolean 84 | } 85 | // user_packsack表中所存储的用户背包格式 86 | type UserPacksack = { 87 | score: number 88 | curInc: number 89 | card: { 90 | ban: number 91 | delMsg: number 92 | immune: number 93 | see: number 94 | } 95 | } 96 | type GroupsUserPacksack = { 97 | [k: string]: UserPacksack 98 | } 99 | 100 | type DataBaseBasisField = { 101 | recordRow: never 102 | updateData: never 103 | } 104 | type ProcessField = T & DataBaseBasisField 105 | 106 | type Groups_config = ProcessField<{ 107 | retrieveData: GroupsConfig 108 | }> 109 | type Suggestions = ProcessField<{ 110 | rowSuggestion: [SuggestionFormat] | undefined 111 | retrieveData: SuggestionsWholeFormat 112 | }> 113 | type Switch_commands = ProcessField<{ 114 | retrieveData: GroupsSwitchCommands 115 | }> 116 | type User_packsack = ProcessField<{ 117 | retrieveData: GroupsUserPacksack 118 | }> 119 | 120 | type DataBaseDataType = { 121 | groups_config: Groups_config 122 | suggestions: Suggestions 123 | switch_commands: Switch_commands 124 | user_packsack: User_packsack 125 | } 126 | 127 | export { 128 | GroupEvents, 129 | CommandsName, 130 | UserPacksack, 131 | GroupsUserPacksack, 132 | GroupsSwitchCommands, 133 | SuggestionFormat, 134 | PreGroupchatMessage, 135 | GroupsConfig, 136 | 137 | DataBaseDataType 138 | } 139 | -------------------------------------------------------------------------------- /eventsHandle/xiaoqiu/switchCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [开启/关闭指令]指令: 3 | * 用于切换某个指令的状态,当某个指令被关闭后,再次尝试触发时,小秋不会作出任何回应 4 | */ 5 | import { AuthId, GroupAt } from '../../lib/interface' 6 | import type { CommandsName } from '../../database/forms/interface' 7 | import type { SendContent, CommandFn } from '../../lib/interface' 8 | 9 | const member = (sex: boolean, name: string, at: GroupAt) => { 10 | const male = [ 11 | `${at('user')} 小秋收到的指示是,当前指令只能由长的最帅的人触发哦~`, 12 | `${name}哥哥,您不能使用当前指令`, 13 | `${at('user')} 听说颜值在30以下的哥哥是不能触发该指令的哦` 14 | ] 15 | const female = [ 16 | `很抱歉,乘风破浪的${name}姐姐,小秋不能帮您使用该指令`, 17 | `${name},啊这 我的小迷妹也喜欢用这个指令吗`, 18 | `${at('user')} 当前指令只能由颜值99.999以下的小姐姐触发哦~` 19 | ] 20 | return sex ? male : female 21 | } 22 | const admin = (sex: boolean, at: GroupAt) => { 23 | const male = [ 24 | `${at('user')} 想要触发当前指令,光是管理员可不够,还不能做个小小的男同哦~`, 25 | `${at('user')} 兄弟 这个指令就算是管理也不能用哦~`, 26 | `${at('user')} 等你成为群主了再来试着触发当前指令吧~` 27 | ] 28 | const female = [ 29 | `${at('user')} 呜呜 好姐姐,这个指令太丑了,您不能使用它`, 30 | `${at('user')} 小秋认为您如果使用这个指令,会降低你的管理员姐姐身份哦~`, 31 | `${at('user')} 尊贵的管理姐姐,您不能使用当前指令` 32 | ] 33 | return sex ? male : female 34 | } 35 | const sendContent: SendContent = { 36 | name: '切换指令', 37 | reg: [ 38 | /^(开启|关闭)指令[(?.*)]$/, 39 | /^(开启|关闭)[(?.*)]指令$/, 40 | /^(开启|关闭)(?.*)指令$/ 41 | ], 42 | role: [AuthId.FuncJin], 43 | member: ({ user: { name, sex }, operations: { at } }) => member(sex, name, at), 44 | admin: ({ user: { sex }, operations: { at } }) => admin(sex, at), 45 | owner: ({ user: { name, sex }, operations: { at } }) => member(sex, name, at), 46 | deverDefined: ({ operations: { at }, defined: { text, state } }) => [ 47 | [ 48 | `${at('user')} 小秋没有查询到您所输入的指令`, 49 | `${at('user')} 您输入的指令无效,请重新输入后再次尝试`, 50 | `${at('user')} 小秋发现没有当前指令,所以无法对其进行操作哦` 51 | ], 52 | [ 53 | `${at('user')} 哎呀 小秋没有糊涂哦,当前指令已经是关闭状态啦`, 54 | `${at('user')} 小秋认为已经关闭的指令就不需要再次关闭了`, 55 | `${at('user')} 咦 是在考验小秋吗?当前指令已经是关闭状态了哦` 56 | ], 57 | [ 58 | `${at('user')} 哎呀 小秋没有糊涂哦,当前指令已经是开启状态啦`, 59 | `${at('user')} 小秋认为已经开启的指令就不需要再次开启了,嘻嘻`, 60 | `${at('user')} 咦 是在考验小秋吗?当前指令已经是开启状态了哦` 61 | ], 62 | [ 63 | `${at('user')} 小秋帮您把${text}变为了${state}状态哦`, 64 | `${at('user')} nice!小秋已经把${text}改为了${state}状态`, 65 | `${at('user')} 小秋将${text}改为了${state}状态,还不快夸夸人家` 66 | ] 67 | ] 68 | } 69 | const fn: CommandFn = async originData => { 70 | const { group, raw_message, reg, database } = originData 71 | const { getDataBaseData, formSet: { switch_commands } } = database 72 | const text = reg.exec(raw_message)?.groups?.who! as CommandsName 73 | const state = /开启/g.test(raw_message) 74 | const whole = await getDataBaseData(switch_commands.name, switch_commands.retrieveData)(group.id) 75 | const cur = whole[text] 76 | if (cur === undefined) return { items: 1 } 77 | if (state === false && cur === false) return { items: 2 } 78 | if (state === true && cur === true) return { items: 3 } 79 | whole[text] = !cur 80 | await getDataBaseData(switch_commands.name, switch_commands.updateData)(group.id, whole) 81 | return { items: 4, args: { text: `[${text}]`, state: state ? '开启' : '关闭' } } 82 | } 83 | const switchCommand = { fn, sendContent } 84 | 85 | export { switchCommand } 86 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/setCard/setCard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [改群昵称]指令: 3 | * 修改指定成员的群昵称。由于群昵称可遵循的规则较多,但小秋采用的是 地区简称-性别-昵称 形式,其修改的规则或稍有不同 4 | */ 5 | import path from 'path' 6 | 7 | import { 8 | isCardRule, 9 | getChangeAllCard, 10 | getChangeProvinceCard, 11 | getChangeSexCard, 12 | getChangeNicknameCard 13 | } from './tools' 14 | 15 | import type { SendContent, CommandFn } from '../../../lib/interface' 16 | 17 | const sendContent: SendContent = { 18 | name: '改群昵称', 19 | reg: [ 20 | /^改群昵称\[CQ:at,qq=\w*,text=@(?.*)\]\s*$/, 21 | /^修改群昵称\[CQ:at,qq=\w*,text=@(?.*)\]\s*$/, 22 | /^换群昵称\[CQ:at,qq=\w*,text=@(?.*)\]\s*$/ 23 | ], 24 | role: 'admin', 25 | member: ({ other: { name }, operations: { at, promiseImage } }) => [ 26 | `${at('user')} 对不起 您的权限不足以修改${name}的群昵称`, 27 | `${at('user')} 很抱歉 小秋检测到您没有修改${name}群昵称的权限${promiseImage(path.resolve('./assets/images/emoji/万用表情/1.jpg'))}`, 28 | `${at('user')} 当前指令只能由管理/群主去使用哦~` 29 | ], 30 | deverDefined: ({ defined: { card, result }, operations: { at, face } }) => [ 31 | [ 32 | `${at('user')} 检测到<${card}>符合我群已制定的群昵称规则,因此无需更改`, 33 | `${at('user')} 抱歉 小秋暂时无法对已经符合群昵称规则的昵称进行更改`, 34 | `${at('user')} 小秋检测到群昵称<${card}>符合规则,因此无需更改${face(174)}` 35 | ], 36 | [ 37 | `${at('other')} 小秋检测到群昵称<${card}>不符合我群已制定的规则,现已将其更改为<${result}>`, 38 | `${at('other')} 因为<${card}>不符合本群所制定的昵称规则,因此我已将其更改为<${result}>`, 39 | `${at('other')} 小秋已将您的原群昵称<${card}>更改为<${result}>` 40 | ], 41 | [ 42 | `${at('other')} 小秋检测到群昵称<${card}>中的省份不符合规则,现已将其修改为<${result}>`, 43 | `${at('other')} 因为<${card}>的省份不符合本群所制定的昵称规则,因此我已将其修改为<${result}>`, 44 | `${at('other')} 小秋已将<${card}>中的省份给您修改为<${result}>` 45 | ], 46 | [ 47 | `${at('other')} 小秋检测到群昵称<${card}>中的性别不符合规则,现已将其修改为<${result}>`, 48 | `${at('other')} 因为<${card}>的性别不符合本群所制定的昵称规则,因此我已将其修改为<${result}>`, 49 | `${at('other')} 小秋已将<${card}>中的性别给您修改为<${result}>` 50 | ], 51 | [ 52 | `${at('other')} 小秋检测到群昵称<${card}>中的名称不符合规则,现已将其修改为<${result}>`, 53 | `${at('other')} 因为<${card}>的名称不符合本群所制定的昵称规则,因此我已将其修改为<${result}>`, 54 | `${at('other')} 小秋已将<${card}>中的名称给您更改为<${result}>` 55 | ] 56 | ], 57 | equal: ({ other: { name }, operations: { at } }) => [ 58 | `${at('user')} 抱歉 小秋无法修改对方群昵称`, 59 | `小秋暂时无法修改${name}的群昵称`, 60 | `${at('user')} 别对自己人修改群昵称呀` 61 | ], 62 | level: ({ other: { name } }) => [ 63 | `小秋权限不够哦,无法修改${name}的群昵称`, 64 | `很抱歉 小秋暂无此权限`, 65 | `抱歉 小秋暂时无法使用该指令去修改${name}的群昵称` 66 | ] 67 | } 68 | const bar = [ 69 | { 70 | type: 'none', 71 | getCard: getChangeAllCard, 72 | items: 2 73 | }, 74 | { 75 | type: 'province', 76 | getCard: getChangeProvinceCard, 77 | items: 3 78 | }, 79 | { 80 | type: 'sex', 81 | getCard: getChangeSexCard, 82 | items: 4 83 | }, 84 | { 85 | type: 'nickname', 86 | getCard: getChangeNicknameCard, 87 | items: 5 88 | } 89 | ] 90 | // 修改用户群昵称 91 | const fn: CommandFn = async originData => { 92 | const { bot, group, other: { id, card, nickname } } = originData 93 | const { flag, reason } = await isCardRule(bot, group.id, id) 94 | // 群昵称符合规则 95 | if (flag) return { items: 1, args: { card } } 96 | const { getCard, items } = bar.find(({ type }) => type === reason)! 97 | const result = await getCard(bot, group.id, id) 98 | bot.setGroupCard(group.id, id, result) 99 | return { items, args: { card: card ? card : nickname, result } } 100 | } 101 | const setCard = { fn, sendContent } 102 | 103 | export { setCard } 104 | -------------------------------------------------------------------------------- /lib/time/timeUnitTransform.ts: -------------------------------------------------------------------------------- 1 | import { isDecimals } from '../methods' 2 | import { UnitEng } from './interface' 3 | 4 | // 转为秒 5 | const belong = { 6 | 分钟: { 7 | sec: (dur: number): number => dur * 60, 8 | max: 43200 9 | }, 10 | 小时: { 11 | sec: (dur: number): number => dur * 60 * 60, 12 | max: 720 13 | }, 14 | 天: { 15 | sec: (dur: number): number => dur * 60 * 60 * 24, 16 | max: 30 17 | } 18 | } 19 | const transformTimeNameAlias = { mins: '分钟', hours: '小时', days: '天' } as const 20 | const transformTimeNameEn = { 分钟: 'mins', 小时: 'hours', 天: 'days' } 21 | // 得到具体的分钟(例如,传入121分钟,返回1分钟) 22 | const getMins = (mins: number): number => { 23 | const result = mins - 60 24 | return result < 60 ? result : getMins(result) 25 | } 26 | // 得到具体的小时(例如,传入25小时,返回1小时) 27 | const getHours = (Hours: number): number => { 28 | const result = Hours - 24 29 | return result < 24 ? result : getHours(result) 30 | } 31 | // 秒转为对应的分钟、小时、天数 32 | const getIntegerTime = (num: number) => { 33 | if (isDecimals(num)) return num.toFixed(2) 34 | return num 35 | } 36 | const secToFormat = (dur: number): string => { 37 | const mins = dur / 60 38 | if (mins < 60) return `${getIntegerTime(mins)}分钟` 39 | const hours = mins / 60 40 | if (hours < 24) return `${getIntegerTime(hours)}小时${getIntegerTime(getMins(mins))}分钟` 41 | const days = hours / 24 42 | return `${getIntegerTime(days)}天${getIntegerTime(getHours(hours))}小时${getIntegerTime(getMins(mins))}分钟` 43 | } 44 | // 得到对应的日期(2022/06/28) 45 | const process = (time: number): string => (time < 10 ? `0${time}` : `${time}`) 46 | const getDate = (date: Date): string => { 47 | const year = date.getFullYear() 48 | const month = process(date.getMonth() + 1) 49 | const day = process(date.getDate()) 50 | return `${year}/${month}/${day}` 51 | } 52 | // 得到对应的日期(2022/06/28 15:21) 53 | const getWholeDate = (date: Date): string => { 54 | const hour = process(date.getHours()) 55 | const mins = process(date.getMinutes()) 56 | return `${getDate(date)} ${hour}:${mins}` 57 | } 58 | // 得到当前的时间,例如 13:01 59 | const getCurTime = (): string => { 60 | const time = new Date() 61 | const hours = process(time.getHours()) 62 | const mins = process(time.getMinutes()) 63 | return `${hours}:${mins}` 64 | } 65 | // 返回当前时间段的别名,例如 08:00 为 早上好 66 | const getCurTimeAlias = (): string => { 67 | const frame = [ 68 | [6, '夜深了,快睡吧'], 69 | [9, '早上好'], 70 | [12, '中午好'], 71 | [18, '下午好'], 72 | [23, '晚上好'] 73 | ] 74 | const time = new Date() 75 | const hours = time.getHours() 76 | const result = frame.find(v => hours <= v[0])! 77 | return `${result[1]}` 78 | } 79 | // 传入指定的时间段,返回当前时间段所对应的时间戳 80 | // 例如当前时间为:2022/08/16 14:17 81 | // 获取三天后的时间戳,即返回2022/08/19 14:17 所对应的时间戳 82 | type timeInfo = { 83 | mins: number 84 | hours: number 85 | days: number 86 | } 87 | const getAssignTimestamp = (timeInfo: timeInfo) => +new Date() + getSpecificTimeMS(timeInfo) 88 | // 得到分钟、小时、天数,所对应的毫秒数 89 | const getSpecificTimeMS = (timeInfo: timeInfo) => { 90 | const keys = Object.keys(timeInfo) as UnitEng[] 91 | let ms = 0 92 | keys.forEach(t => { 93 | const timestamp = belong[transformTimeNameAlias[t]].sec(timeInfo[t]) * 1000 94 | ms += timestamp 95 | }) 96 | return ms 97 | } 98 | // 得到当前是白天还是黑天 99 | // true代表白天,false代表黑天 100 | const getCurDarkOrLights = () => { 101 | const hours = new Date().getHours() 102 | return hours >= 6 && hours < 19 103 | } 104 | 105 | export { 106 | belong, 107 | secToFormat, 108 | getDate, 109 | getWholeDate, 110 | getCurTime, 111 | getCurTimeAlias, 112 | getAssignTimestamp, 113 | transformTimeNameAlias, 114 | transformTimeNameEn, 115 | getSpecificTimeMS, 116 | getCurDarkOrLights 117 | } 118 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/setCard/tools.ts: -------------------------------------------------------------------------------- 1 | import { 2 | areaAbbr, 3 | sexArr, 4 | sexBelong, 5 | keywordsReg, 6 | areaAll, 7 | randomProvince, 8 | formSpecificProvinceToShort 9 | } from '../../../lib/groupchat' 10 | 11 | import type { 12 | Province, 13 | BeAtInfoGroupSex, 14 | BeAtInfoGroupCard, 15 | ChangeCard, 16 | IsCardRule 17 | } from './interface' 18 | 19 | // 拆分用户的群昵称 20 | const getSpecificCard = (card: string) => { 21 | const result = card.split('-') 22 | return result.slice(0, 3) 23 | } 24 | // 判断群昵称是否符合规则,如果不符合规则,则返回false;如果符合规则,则返回true 25 | const isCardRule: IsCardRule = async (bot, gId, uId) => { 26 | const beAtInfoGroup = await bot.getGroupMemberInfo(gId, uId) 27 | const { card } = beAtInfoGroup.data 28 | const reg = /^(?.*)-(?.*)-(?.*)\s*$/ 29 | // 根本不符合 30 | if (!reg.test(card)) return { flag: false, reason: 'none' } 31 | const name = reg.exec(card)?.groups! 32 | const { province, sex, nickname } = name as Province 33 | // 省份不符合 34 | if (!areaAbbr[province]) return { flag: false, reason: 'province' } 35 | // 性别不符合 36 | if (!sexArr.includes(sex)) return { flag: false, reason: 'sex' } 37 | const isNoKeys = keywordsReg.test(nickname) 38 | // 昵称中包含敏感词汇,不符合 39 | if (isNoKeys) return { flag: false, reason: 'nickname' } 40 | return { flag: true, reason: '符合' } 41 | } 42 | // 避免用户的网名为(_ - 空格) 43 | const nicknameSymbols = ['_', '-', ' ', '', '.'] 44 | // 获取修改后的某位用户的整体群昵称(省份、性别、昵称,全部修改) 45 | const getChangeAllCard: ChangeCard = async (bot, groupId, userId) => { 46 | const beAtInfoGroup: BeAtInfoGroupCard = await bot.getGroupMemberInfo(groupId, userId) 47 | const { area, sex: curSex, nickname: curNickname } = beAtInfoGroup.data 48 | const r_area_all_keys = Object.keys(areaAll) 49 | const curProvince = r_area_all_keys.find(p => new RegExp(area, 'ig').test(p)) 50 | // 如果用户的area在r_area_all中不存在,则使用随机省份 51 | const province = curProvince ? formSpecificProvinceToShort(curProvince) : randomProvince() 52 | // 如果用户的sex不存在,则默认为男 53 | const sex = curSex && curSex !== 'unknown' ? sexBelong[curSex] : '男' 54 | const nickname = nicknameSymbols.find(s => s === curNickname.trim()) ? '入门' : curNickname 55 | return `${province}-${sex}-${nickname}` 56 | } 57 | // 获取修改后的用户的群昵称中的省份 58 | const getChangeProvinceCard: ChangeCard = async (bot, gId, userId) => { 59 | const beAtInfoGroup = await bot.getGroupMemberInfo(gId, userId) 60 | const { area, card } = beAtInfoGroup.data 61 | const r_area_all_keys = Object.keys(areaAll) 62 | const curProvince = r_area_all_keys.find(p => new RegExp(area, 'ig').test(p)) 63 | // 如果用户的area在r_area_all中不存在,则使用随机省份 64 | const province = curProvince ? formSpecificProvinceToShort(curProvince) : randomProvince() 65 | const result = getSpecificCard(card) 66 | result[0] = province 67 | return result.join('-') 68 | } 69 | // 获取修改后的某位用户群昵称中的性别 70 | const getChangeSexCard: ChangeCard = async (bot, gId, userId) => { 71 | const beAtInfoGroup: BeAtInfoGroupSex = await bot.getGroupMemberInfo(gId, userId) 72 | const { sex, card } = beAtInfoGroup.data 73 | const bar = sex && sex !== 'unknown' ? sexBelong[sex] : '男' 74 | const result = getSpecificCard(card) 75 | result[1] = bar 76 | return result.join('-') 77 | } 78 | // 获取修改后的用户的群昵称中的名字 79 | const getChangeNicknameCard: ChangeCard = async (bot, gId, userId) => { 80 | const beAtInfoGroup = await bot.getGroupMemberInfo(gId, userId) 81 | const { nickname, card } = beAtInfoGroup.data 82 | const isNoKeys = keywordsReg.test(nickname) || nicknameSymbols.find(s => s === nickname.trim()) 83 | const bar = isNoKeys ? '小萌新' : nickname 84 | const result = getSpecificCard(card) 85 | result[2] = bar 86 | return result.join('-') 87 | } 88 | 89 | export { 90 | isCardRule, 91 | getChangeAllCard, 92 | getChangeProvinceCard, 93 | getChangeSexCard, 94 | getChangeNicknameCard 95 | } 96 | -------------------------------------------------------------------------------- /eventsHandle/groupchat/repeater.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [检测复读]指令: 3 | * 当群聊中发生了复读事件后,小秋会根据复读的人数及消息数作出相应回应。 4 | * (该指令会自动进行检测,因此无法主动开启/关闭。在主动进行复读时,小秋会避开复读已开发的指令文字, 5 | * 理论上来讲不会造成这种情况,因为每条指令始终都对应着一条回复) 6 | */ 7 | import path from 'path' 8 | 9 | import { menu } from '../../lib/oicq/menuKeywords' 10 | import { operations } from '../../lib/oicq/oicqOperations' 11 | import { returnOneOfContent } from '../../lib/methods' 12 | 13 | import type { GroupchatsId, OriginData } from '../../lib/interface' 14 | import type { PreGroupchatMessage, CommandsName } from '../../database/forms/interface' 15 | 16 | type Repeater = ( 17 | bot: OriginData['bot'], 18 | id: GroupchatsId, 19 | card: string, 20 | operations: OriginData['operations'] 21 | ) => Promise 22 | 23 | const { promiseImage, face } = operations 24 | 25 | // 用于分析群聊中复读的情况 26 | const getSecondRepeatItems = (arr: PreGroupchatMessage[]) => { 27 | // 得到每个重复的消息及它们被重复的次数 28 | const container = new Map([]) 29 | // 统计每个重复的消息都是由哪些用户发出的 30 | const user = new Set() 31 | const result = { 32 | // 被复读的消息 33 | msg: '', 34 | // 被复读消息的最大复读次数 35 | times: 0, 36 | // 被复读消息总共有多少人进行复读 37 | userCounts: 0 38 | } 39 | arr.forEach(({ userId, content }) => { 40 | const counts = container.get(content) 41 | // 如果该消息没有出现过,则默认出现过一次 42 | if (!counts) return container.set(content, 1) 43 | const num = counts + 1 44 | // 如果未超过当前最高的复读次数,则只更新该消息自身所对应的复读次数 45 | if (num < result.times) return container.set(content, num) 46 | // 如果超过了当前最高的复读次数,则更新result对象(因为此时统计到了最新数据) 47 | result.msg = content 48 | result.times = num 49 | container.set(content, num) 50 | // 用于统计都是哪些人复读的消息 51 | user.add(userId) 52 | }) 53 | result.userCounts = user.size 54 | return result 55 | } 56 | const reason1 = [ 57 | `你有孤独和烈酒,所以在这复读走一走?`, 58 | `小秋发现你很无聊哎`, 59 | `别复读了,整一局?${promiseImage(path.resolve('./assets/images/emoji/送你花花1.jpg'))}`, 60 | `别复读了,整一局?${face(98)}`, 61 | `别复读了,整一局?${promiseImage(path.resolve('./assets/images/emoji/比心1.jpg'))}`, 62 | `别复读了,整一局?${face(179)}` 63 | ] 64 | const reason2 = [ 65 | `${path.resolve('./assets/images/emoji/复读1.jpg')}`, 66 | `${path.resolve('./assets/images/emoji/不要这样先生1.jpg')}`, 67 | `${path.resolve('./assets/images/emoji/复读2.jpg')}`, 68 | `${path.resolve('./assets/images/emoji/两狗对视1.jpg')}`, 69 | `${path.resolve('./assets/images/emoji/以德服人1.jpg')}`, 70 | `${path.resolve('./assets/images/emoji/帅者的肯定1.jpg')}` 71 | ] 72 | 73 | // 每次复读均有100分钟的冷却时间 74 | let isRepeat = true 75 | const repeater: Repeater = async (bot, id, card, operations) => { 76 | // 是否在冷却时间内 77 | if (!isRepeat) return 78 | // 复读功能有80%的几率触发 79 | if (Math.random() >= 0.2) return 80 | const { getHistoryGroupMsg, promiseImage } = operations 81 | // 以本群最近的10条消息为样本进行分析 82 | const latest = getHistoryGroupMsg(id, 10) 83 | const { msg, times, userCounts } = getSecondRepeatItems(latest) 84 | // 只有复读次数大于3才认定此行为的本质是复读 85 | if (times < 3) return 86 | // 如果复读的次数大于3,且复读的用户等于1,则提醒该用户不要复读(造成这种情况的原因是某位成员在独自进行复读) 87 | // 如果复读的次数大于3,且复读的用户等于2,则小秋发送一张图片,主动打断复读(造成这种情况的原因是只有两位用户在复读) 88 | // 如果复读的次数大于3,且复读的用户大于等于3,则小秋也加入复读(造成这种情况的原因是有多位用户在复读) 89 | isRepeat = false 90 | // 定时清除冷却时间(小秋检测复读的冷却时间为100分钟) 91 | setTimeout(() => { 92 | isRepeat = true 93 | }, 1000 * 60 * 60 * 100) 94 | const repeatGo = [ 95 | () => bot.sendGroupMsg(id, `${card},${returnOneOfContent(reason1)}`), 96 | () => bot.sendGroupMsg(id, `${promiseImage(returnOneOfContent(reason2))}`) 97 | ] 98 | const who = repeatGo[userCounts - 1] 99 | if (who) return who() 100 | // 当全部判断通过后,此时已经可以让小秋加入复读,但若被复读消息的内容是某个已开发指令的名称,则不进行复读 101 | // 无论是否与指令名称冲突,均进入冷却时间内 102 | const isConflict = Object.keys(menu) as CommandsName[] 103 | const ok = isConflict.find(name => name === msg) 104 | if (ok) return 105 | bot.sendGroupMsg(id, msg) 106 | } 107 | 108 | export { repeater } 109 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/changeDrawDiscount.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [修改积分抽奖折扣]指令: 3 | * 用于修改积分抽奖的折扣数(5-9折) 4 | */ 5 | import { 6 | getAssignTimestamp, 7 | transformTimeNameEn, 8 | getWholeDate, 9 | getSpecificTimeMS 10 | } from '../../lib/time' 11 | 12 | import { AuthId, GroupAt } from '../../lib/interface' 13 | import type { SendContent, CommandFn } from '../../lib/interface' 14 | import type { GroupsConfig } from '../../database/forms/interface' 15 | import type { UnitChi } from '../../lib/time/interface' 16 | import type { OicqOperations } from '../../lib/oicq/interface' 17 | 18 | const member = (at: GroupAt, sex: boolean, face: OicqOperations['face']) => { 19 | const male = [ 20 | `${at('user')} 好哥哥 你干嘛要开启折扣呀${face(104)}`, 21 | `${at('user')} 小秋不能帮您开启哦~${face(104)}`, 22 | `${at('user')} 呜呜呜 因为不能帮哥哥开启折扣,所以小秋的心好痛${face(104)}` 23 | ] 24 | const female = [ 25 | `${at('user')} 小秋只会帮我的好闺蜜开启哦~${face(104)}`, 26 | `${at('user')} 呜呜 答应做小秋的闺蜜,小秋就帮您开启哦${face(104)}`, 27 | `${at('user')} 呜呜呜 因为不能帮给小姐姐使用折扣了,所以小秋的心好痛痛${face(104)}` 28 | ] 29 | return sex ? male : female 30 | } 31 | const sendContent: SendContent = { 32 | name: '抽奖限时折扣', 33 | reg: /^积分抽奖池开启(?\d+)折,为期(?\d*)(?.*)$/, 34 | role: [AuthId.FuncJin], 35 | member: ({ user: { sex }, operations: { at, face } }) => member(at, sex, face), 36 | admin: ({ user: { sex }, operations: { at, face } }) => member(at, sex, face), 37 | owner: ({ user: { sex }, operations: { at, face } }) => member(at, sex, face), 38 | deverDefined: ({ operations: { at }, defined: { unit, dur, discount, lastTime } }) => [ 39 | [ 40 | `${at('user')} ${discount}折不行哦,小秋能够帮您开启的折扣数必须要处于5-9期间`, 41 | `${at('user')} 哎呀 小秋暂时只能开启5-9折,还不支持${discount}折哦~`, 42 | `${at('user')} ${discount}折不行啊,折扣数必须位于5-9之间哦~` 43 | ], 44 | [ 45 | `${at('user')} 给个时间单位哦~`, 46 | `${at('user')} 听说没有准确的时间,是不能够开启积分抽奖池限时折扣的`, 47 | `${at('user')} 咦 没有具体的期限是不能开启限时折扣哦~` 48 | ], 49 | [ 50 | `${at('user')} 小秋认为${unit}不是合格的时间单位哦~`, 51 | `${at('user')} 小秋暂时还不认识${unit}哎~`, 52 | `${at('user')} 不能够是${unit}哦,换个时间单位再试试吧` 53 | ], 54 | [ 55 | `${at('user')} ${unit}不合法哦,换个时间再试试吧`, 56 | `${at('user')} ${unit}太小了呀,尝试换个大一点的时间`, 57 | `${at('user')} 小秋觉得${unit}不符合折扣期限,换个时间段再试试吧~` 58 | ], 59 | [ 60 | `积分奖池已开启为期${dur}${unit}的限时折扣(${discount}折),快来抽奖吧~${lastTime}`, 61 | `${at('user')} 小秋已将奖池折扣改为${discount}折,并且会持续${dur}${unit}哦${lastTime}`, 62 | `哇 积分抽奖池${discount}折来袭,持续${dur}${unit},赶紧来抽奖吧!${lastTime}`, 63 | `${at('user')} 小秋已开启积分抽奖池的限时折扣,听说连续水群的抽中概率更高哦~${lastTime}` 64 | ] 65 | ] 66 | } 67 | const fn: CommandFn = async originData => { 68 | const { bot, group, raw_message, reg, database } = originData 69 | const { getDataBaseData, formSet: { groups_config } } = database 70 | const { dur, unit, discount: drawDiscount } = reg.exec(raw_message)?.groups! 71 | const discount = Number(drawDiscount) as GroupsConfig['draw']['discount'] 72 | if (discount < 5 || discount > 9) return { items: 1, args: { discount } } 73 | const timeKey = transformTimeNameEn[unit as UnitChi] 74 | if (!dur) return { items: 2 } 75 | if (!timeKey) return { items: 3, args: { unit } } 76 | if (Number(dur) <= 0) return { items: 4, args: { dur } } 77 | const initTime = { mins: 0, hours: 0, days: 0 } 78 | const nextTime = { ...initTime, [timeKey]: dur } 79 | const timestamp = getAssignTimestamp(nextTime) 80 | const config = await getDataBaseData(groups_config.name, groups_config.retrieveData)(group.id) 81 | const draw = { ...config.draw, discount, timestamp } 82 | await getDataBaseData(groups_config.name, groups_config.updateData)(group.id, { ...config, draw }) 83 | const lastTime = `\n[${getWholeDate(new Date(timestamp))}分会自动关闭]` 84 | setTimeout(() => { 85 | bot.sendGroupMsg(group.id, '小秋已关闭积分抽奖池限时折扣') 86 | }, getSpecificTimeMS(nextTime)) 87 | return { items: 5, args: { unit, dur, lastTime, discount } } 88 | } 89 | const changeDrawDiscount = { fn, sendContent } 90 | 91 | export { changeDrawDiscount } 92 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/propCard/banAndDelCard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [禁言卡]、[撤回卡]指令: 3 | * 禁言卡会自动禁言指定成员5分钟,撤回卡会自动撤回指定成员的1条消息 4 | */ 5 | import { setPacksack } from '../packsack/tools' 6 | 7 | import type { SendContent, CommandFn } from '../../../lib/interface' 8 | 9 | const sendContent: SendContent = { 10 | name: '道具卡', 11 | reg: /^(?(禁言)|(撤回))卡\[CQ:at,qq=(?\d*),text=.*\]\s*$/, 12 | role: 'member', 13 | member: ({ user: { name }, operations: { at } }) => [ 14 | `小小${name} 可笑可笑,您的背包为空,所以小秋不能帮您使用此道具哦~`, 15 | `${name},您背包目前为空,暂时无法使用,赶快去获取吧~`, 16 | `${at('user')} 您的背包中尚不存在此道具哦,因此无法使用` 17 | ], 18 | admin: ({ user: { name }, operations: { at } }) => [ 19 | `小小管理 ${name},可笑可笑,背包为空,所以小秋不能帮您使用此道具哦~`, 20 | `${name},您还没有此道具卡,赶快去获取吧~`, 21 | `${at('user')} 管理大哥 你背包里还没有道具卡呢,因此小秋无法使用` 22 | ], 23 | owner: ({ user: { name }, operations: { at } }) => [ 24 | `小小群主 ${name},可笑可笑,背包为空,所以不能使用!!`, 25 | `${name},您还没有此道具卡哎,赶快去获取吧~`, 26 | `${at('user')} 我说群主啊 你背包里还没有道具卡呢,小秋不能帮您使用` 27 | ], 28 | deverDefined: ({ user: { name: username }, other: { name: othername }, operations: { at } }) => [ 29 | [ 30 | `${at('user')} 小秋发现您的背包中不存在禁言卡,所以无法使用`, 31 | `${at('user')} 不要妄图欺骗小秋哦 因为您背包中不存在禁言卡,所以无法使用`, 32 | `${username},你还没有禁言卡呢,赶快去获取吧~` 33 | ], 34 | [ 35 | `${at('user')} 很不幸,对方<${at('other')}>自动消耗一张免疫卡,免除了此次禁言`, 36 | `${at('user')} 唉 真不凑巧 <${at('other')}>自动消耗一张免疫卡,竟然免除了此次禁言`, 37 | `${at('user')} 芭比Q啦,<${at('other')}>自动消耗一张免疫卡,免除了此次禁言` 38 | ], 39 | [ 40 | `${at('user')} 小秋发现您的背包中还没有撤回卡哦,所以无法使用~`, 41 | `${at('user')} 小秋看到您的撤回卡数量为0,所以无法使用~`, 42 | `${username},你还没有撤回卡呢,赶快去获取吧~` 43 | ], 44 | [ 45 | `${at('user')} 灰常抱歉,<${at('other')}>自动消耗一张免疫卡,免除了此次撤回`, 46 | `${at('user')} <${at('other')}>自动消耗一张免疫卡,免除了此次撤回`, 47 | `${at('user')} 芭比Q啦,<${at('other')}>自动消耗一张免疫卡,免除了此次撤回` 48 | ], 49 | [ 50 | `${at('user')} 小秋没有看到${othername}发言哦~\n[撤回卡已自动消耗]`, 51 | `${at('user')} 咦 是我眼花了吗?好像${othername}没有发言哦~\n[撤回卡已自动消耗]`, 52 | `${at('user')} 小秋检测到${othername}没有发言啊\n[撤回卡已自动消耗]` 53 | ] 54 | ], 55 | equal: ({ other: { name, role }, operations: { at } }) => [ 56 | `${at('user')} 先不扣你的卡了 下次不准向${name}使用了`, 57 | `${at('user')} 飘了是吧 还敢对${name}用道具卡??`, 58 | `${at('user')} 再向${role === 'admin' ? '管理' : '群主'}使用道具卡 小心我干你` 59 | ], 60 | level: () => [ 61 | `对不起,小秋权限不足,您暂时无法发挥此卡的最大价值\n[暂未扣除此道具卡数量]`, 62 | `呜呜呜 就连小秋都好难过,使用道具卡失败,因为小秋无此权限~\n[暂未扣除此道具卡数量]`, 63 | `哭了 小秋竟然没有权限去帮您使用此卡\n[暂未扣除此道具卡数量]` 64 | ] 65 | } 66 | const fn: CommandFn = async originData => { 67 | const { group, user, other, raw_message, operations, reg, database } = originData 68 | const { getDataBaseData, formSet: { user_packsack } } = database 69 | const { delMsg, banMember } = operations 70 | const value = await getDataBaseData(user_packsack.name, user_packsack.retrieveData)(group.id) 71 | const curUser = value[user.id] 72 | if (!curUser) return 73 | const { card } = curUser 74 | const type = reg.exec(raw_message)?.groups?.type 75 | const allOtherCard = value[other.id]?.card 76 | if (type === '禁言') { 77 | if (!card.ban) return { items: 1 } 78 | setPacksack(group.id, user.id, { card: { ban: -1 } }) 79 | if (allOtherCard?.immune) { 80 | setPacksack(group.id, other.id, { card: { immune: -1 } }) 81 | setPacksack(group.id, user.id, { card: { ban: -1 } }) 82 | return { items: 2 } 83 | } 84 | // 对方无免疫卡,且自己拥有禁言卡时,视为使用成功 85 | banMember(group.id, other.id, '分钟', 5) 86 | return { noMsg: true } 87 | } 88 | if (!card.delMsg) return { items: 3 } 89 | setPacksack(group.id, user.id, { card: { delMsg: -1 } }) 90 | if (allOtherCard?.immune) { 91 | setPacksack(group.id, other.id, { card: { immune: -1 } }) 92 | setPacksack(group.id, user.id, { card: { delMsg: -1 } }) 93 | return { items: 4 } 94 | } 95 | // 对方无免疫卡,且自己拥有撤回卡时,视为使用成功 96 | const count = await delMsg(group.id, other.id, 1) 97 | if (!count) return { items: 5 } 98 | return { noMsg: true } 99 | } 100 | const banAndDelCard = { fn, sendContent } 101 | 102 | export { banAndDelCard } 103 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/draw/tools.ts: -------------------------------------------------------------------------------- 1 | import { getDataBaseData, formSet } from '../../../database' 2 | import { returnOneOfContent } from '../../../lib/methods' 3 | 4 | import type { CardResult, AllResult } from './interface' 5 | import type { DataBase } from '../../../database/interface' 6 | import type { GroupchatsId } from '../../../lib/interface' 7 | 8 | type Judge = ( 9 | group_id: GroupchatsId, 10 | user_id: number, 11 | count: number, 12 | database: DataBase, 13 | drawPlaceholder: Map 14 | ) => Promise<{ 15 | flag: boolean 16 | reason: string 17 | }> 18 | 19 | const { groups_config } = formSet 20 | 21 | // 判断是否处于限时折扣期间 22 | const isDiscount = async (gId: GroupchatsId) => { 23 | const { draw: { discount, timestamp } } = await getDataBaseData(groups_config.name, groups_config.retrieveData)(gId) 24 | const curTime = +new Date() 25 | if (curTime < timestamp) return { flag: true, discount, timestamp } 26 | return { flag: false } 27 | } 28 | // 得到在某个群聊中进行一次抽奖所消耗的积分 29 | const getSinglePrice = async (gId: GroupchatsId) => { 30 | const { flag, discount } = await isDiscount(gId) 31 | const price = flag ? (discount! / 10) * 6 : 6 32 | return price 33 | } 34 | const reason1 = (count: number) => { 35 | const reason = [ 36 | `你刚才的${count}次抽奖还没抽完呢,再等会`, 37 | `哎呀 小秋发现你刚才的${count}次抽奖还没完成哦`, 38 | `小秋还在努力的将您刚才的${count}次抽奖抽完,请稍等一会哦~` 39 | ] 40 | return returnOneOfContent(reason) 41 | } 42 | const reason2 = () => { 43 | const reason = [ 44 | `小秋发现您的背包为空,暂时无法进行抽奖`, 45 | `呜呜 您的背包中没有积分,因此小秋不能帮您进行抽奖`, 46 | `小秋的心好痛,因为您背包中没有积分可以进行抽奖` 47 | ] 48 | return returnOneOfContent(reason) 49 | } 50 | const reason3 = (text: string | number) => { 51 | const reason = [ 52 | `小秋发现您的积分不足以完成${text}次抽奖哦~`, 53 | `小秋检测到您的积分不足以完成${text}次抽奖`, 54 | `您不足以完成${text}次抽奖,因为小秋发现您背包中的积分不足` 55 | ] 56 | return returnOneOfContent(reason) 57 | } 58 | const judge: Judge = async (group_id, user_id, count, database, drawPlaceholder) => { 59 | const { getDataBaseData, formSet: { user_packsack } } = database 60 | // 先判断当前用户的上一次抽奖是否完成,如果未完成,则返回true 61 | const drawFlag = `${group_id}${user_id}` 62 | if (drawPlaceholder.has(drawFlag)) 63 | return { flag: false, reason: reason1(drawPlaceholder.get(drawFlag)!) } 64 | const value = await getDataBaseData(user_packsack.name, user_packsack.retrieveData)(group_id) 65 | const curUser = value[user_id] 66 | if (!curUser) return { flag: false, reason: reason2() } 67 | const text = count === 1 ? '此' : count 68 | const price = await getSinglePrice(group_id) 69 | if (Number(curUser.score) < count * price) return { flag: false, reason: reason3(text) } 70 | return { flag: true, reason: `可以抽奖` } 71 | } 72 | const luckly = async (gId: GroupchatsId) => { 73 | const property = [ 74 | { name: '禁言卡', code: 'ban' }, 75 | { name: '未抽中', code: 'no' }, 76 | { name: '免疫卡', code: 'immune' }, 77 | { name: '4积分', code: 'score' }, 78 | { name: '撤回卡', code: 'delMsg' }, 79 | { name: '康康卡', code: 'see' } 80 | ] 81 | const Prab = [0.1, 1, 0.2, 0.8, 0.2, 0.7] 82 | //const Prab = [0.8, 1, 0.8, 1, 0.8, 0.6] 83 | const Alias = [2, 2, 4, 4, 2, 4] 84 | const advance = { 85 | prop: { 86 | name: '', 87 | code: 'no' 88 | }, 89 | explain: '' 90 | } 91 | const randomFirst = Math.round(Math.random() * 5) 92 | const randomSecode = Math.random() 93 | advance.prop = randomSecode < Prab[randomFirst] ? property[randomFirst] : property[Alias[randomFirst] - 1] 94 | advance.explain = advance.prop.code === 'no' ? '未抽中~' : `${advance.prop.name}` 95 | const all: AllResult = { 96 | score: 0, 97 | card: { 98 | ban: 0, 99 | immune: 0, 100 | delMsg: 0, 101 | see: 0 102 | } 103 | } 104 | // 单次抽奖所需要的积分 105 | const price = await getSinglePrice(gId) 106 | const score = Number(price.toFixed(1)) * -1 107 | if (advance.prop.code === 'no') { 108 | all.score = score 109 | } else if (advance.prop.code === 'score') { 110 | all.score = score + 4 111 | } else { 112 | all.score = score 113 | all.card = { [advance.prop.code]: 1 } as CardResult 114 | } 115 | return { all, advance } 116 | } 117 | 118 | export { 119 | judge, 120 | luckly, 121 | isDiscount 122 | } 123 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/draw/drawCount.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [抽奖xx]指令: 3 | * 用于在积分抽奖池中进行多次抽奖 4 | */ 5 | import { judge, luckly } from './tools' 6 | import { self } from '../../../lib/user' 7 | import { setPacksack } from '../packsack/tools' 8 | import { returnOneOfContent, formatNumCount, isDecimals } from '../../../lib/methods' 9 | 10 | import type { CardResult, LucklyResult } from './interface' 11 | import type { SendContent, CommandFn, ForwardRecord } from '../../../lib/interface' 12 | 13 | type DrawResult = { 14 | content: string 15 | arr: LucklyResult[] 16 | } 17 | 18 | // 每位用户只能在上一次抽奖完成后,再进行下一次抽奖 19 | const drawPlaceholder = new Map() 20 | 21 | const sendContent: SendContent = { 22 | name: '抽奖', 23 | reg: [/^抽奖x(?\d+)$/, /^抽奖\*(?\d+)$/, /^抽奖(?\d+)次$/], 24 | role: 'member', 25 | member: ({ user: { name }, operations: { at } }) => [ 26 | `${at('user')} 你身为一个群成员,你不知道抽奖次数最低为1吗?`, 27 | `小秋好伤心啊 因为${name}不知道最低抽奖次数是1次`, 28 | `${at('user')} 难道你要成为群主才知道抽奖次数最低是1次吗?` 29 | ], 30 | admin: ({ user: { name }, operations: { at } }) => [ 31 | `${name}!你这管理别要了,最低抽奖次数只能是1次`, 32 | `${at('user')} 呜呜 这位管理,小秋好难过,因为您最少要进行的抽奖次数为1哦`, 33 | `管理员${name}真抠啊,连小秋都知道最低抽奖次数是1次` 34 | ], 35 | owner: ({ user: { name }, operations: { at } }) => [ 36 | `群主别搞事,最低抽奖次数是1次`, 37 | `${name} 你咋回事,最低抽奖次数是1次!小秋可不怕你这个坏群主哦`, 38 | `${at('user')} 别搞事啊群主 最低的抽奖次数可是1次呀` 39 | ], 40 | deverDefined: ({ user: { name }, operations: { at }, defined: { reason, explain } }) => [ 41 | [ 42 | `${at('user')} ${reason}` 43 | ], 44 | [ 45 | `${at('user')} 抽到这么多东西,小秋好羡慕啊。${explain}\n`, 46 | `吆西 ${name}人品大爆发,${explain}\n`, 47 | `${at('user')} 运气不错,羡慕你哦\n${explain}\n` 48 | ] 49 | ] 50 | } 51 | const fn: CommandFn = async originData => { 52 | const { bot, group, user, raw_message, segment, reg, database } = originData 53 | const count = Number(reg.exec(raw_message)?.groups?.count) 54 | if (count <= 0) return 55 | const isOk = await judge(group.id, user.id, count, database, drawPlaceholder) 56 | const { flag, reason } = isOk 57 | if (!flag) return { items: 1, args: { reason } } 58 | // 记录本次抽奖是否完成 59 | const drawFlag = `${group.id}${user.id}` 60 | drawPlaceholder.set(drawFlag, count) 61 | const result: DrawResult = { content: '', arr: [] } 62 | const process = async () => await luckly(group.id) 63 | for (let i = 0; i < count; i++) result.arr.push(await process()) 64 | const end = { score: 0, card: { ban: 0, delMsg: 0, immune: 0, see: 0 } } 65 | result.arr.forEach((v, i) => { 66 | const { all, advance } = v 67 | end.score += all.score 68 | const arr = Object.keys(all.card) as (keyof CardResult)[] 69 | arr.forEach(v => (end.card[v] += all.card[v])) 70 | result.content += `${i + 1}:${advance.explain}\n` 71 | }) 72 | end.score = Number(end.score.toFixed(1)) 73 | await setPacksack(group.id, user.id, end) 74 | // 确认更改数据后,清除原先所记录的抽奖标识 75 | drawPlaceholder.delete(drawFlag) 76 | const r = end.score 77 | const text = (content: string) => 78 | `抽奖记录为:\n${content}[本次抽奖积分变化:${formatNumCount(isDecimals(r) ? Number(r.toFixed(1)) : r)}积分]` 79 | // 抽奖次数超过10时,以转发聊天记录的形式发出 80 | if (count <= 10) return { items: 2, args: { explain: text(result.content) } } 81 | const arr = [ 82 | `大家快看 ${user.name}居然抽到了这么多东西`, 83 | `羡慕啊 小机灵鬼${user.name} 这么多次抽奖居然没亏哎`, 84 | `hhh ${user.name},快看看你都抽到了啥吧~` 85 | ] 86 | // 由于qq对每条消息的字数存在限制,所以此处以2000个字符为例,当超出此限制,则在聊天记录中对抽奖结果进行分割 87 | const rowMaxLength = 2000 88 | const drawCountResult: ForwardRecord[] = [] 89 | const getFormat = (message: string) => ({ 90 | user_id: self.uin, 91 | nickname: '抽奖结果', 92 | message 93 | }) 94 | if (result.content.length <= rowMaxLength) { 95 | drawCountResult.push(getFormat(`@${user.card},${text(result.content)}`)) 96 | } else { 97 | const rule = Math.floor(result.content.length / rowMaxLength) 98 | Array.from({ length: rule }).forEach((v, i) => { 99 | const start = i * rowMaxLength 100 | const end = (i + 1) * rowMaxLength 101 | const boundary = i === rule ? result.content.length : end 102 | const text = result.content.slice(start, boundary) 103 | drawCountResult.push(getFormat(`【第${i + 1}条消息记录】\n\n${text}`)) 104 | }) 105 | } 106 | const forward = await bot.makeForwardMsg(drawCountResult) 107 | bot.sendGroupMsg(group.id, returnOneOfContent(arr)) 108 | bot.sendGroupMsg(group.id, segment.xml(forward.data.data.data)) 109 | return { noMsg: true } 110 | } 111 | const drawCount = { fn, sendContent } 112 | 113 | export { drawCount } 114 | -------------------------------------------------------------------------------- /eventsHandle/scorePlayMethods/propCard/seeCard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [康康卡]指令: 3 | * 使用该道具卡会自动消耗当前成员2积分,表现形式为随机发送一张图片 4 | * (api:风景图、头像图、二次元) 5 | */ 6 | import fs from 'fs' 7 | import path from 'path' 8 | import request_r from 'request' 9 | 10 | import { getRandom } from '../../../lib/methods' 11 | import { setPacksack } from '../packsack/tools' 12 | 13 | import type { SendContent, CommandFn } from '../../../lib/interface' 14 | import type { NoParamCallback } from 'fs' 15 | 16 | type SeeCard = { 17 | title: string 18 | msg: string 19 | } 20 | 21 | const request = request_r.defaults({ encoding: null }) 22 | 23 | const writeFileFromRequest = (https: string, localUrl: string, fn: NoParamCallback) => 24 | request(https, (_: unknown, __: unknown, data: string) => fs.writeFile(`${localUrl}`, data, fn)) 25 | 26 | const sendContent: SendContent = { 27 | name: '道具卡', 28 | reg: /^康康卡$/, 29 | role: 'member', 30 | member: ({ user: { name }, operations: { at } }) => [ 31 | `小小${name} 可笑可笑,您的背包为空,所以小秋不能帮您使用此道具哦~`, 32 | `${name},您背包目前为空,暂时无法使用,赶快去获取吧~`, 33 | `${at('user')} 您的背包中尚不存在此道具哦,因此无法使用` 34 | ], 35 | admin: ({ user: { name }, operations: { at } }) => [ 36 | `小小管理 ${name},可笑可笑,背包为空,所以小秋不能帮您使用此道具哦~`, 37 | `${name},您还没有此道具卡,赶快去获取吧~`, 38 | `${at('user')} 管理大哥 你背包里还没有道具卡呢,小秋不能帮您使用` 39 | ], 40 | owner: ({ user: { name }, operations: { at } }) => [ 41 | `小小群主 ${name},可笑可笑,背包为空,所以不能使用!!`, 42 | `${name},您还没有此道具卡哎,赶快去获取吧~`, 43 | `${at('user')} 我说群主啊 你背包里还没有道具卡呢,小秋不能帮您使用` 44 | ], 45 | deverDefined: ({ operations: { at }, defined }) => [ 46 | [ 47 | `${at('user')} 嗯?就这么想康好康的吗,但是你没有康康卡!!!`, 48 | `${at('user')} 小秋发现您还没有康康卡哦~`, 49 | `${at('user')} 没有康康卡的选手,是不可以康好看的哦~` 50 | ], 51 | [ 52 | `${at('user')} 人品大爆发!您使用一张[康康卡]发现了一张${defined.title}${defined.content}`, 53 | `${at('user')} 哇 小秋为您找到了一张${defined.title}${defined.content}`, 54 | `${at('user')} 芜湖 您居然康到了一张绝美的${defined.title}${defined.content}` 55 | ] 56 | ] 57 | } 58 | const fn: CommandFn = async originData => { 59 | const { group, user, operations, database } = originData 60 | const { getDataBaseData, formSet: { user_packsack } } = database 61 | const value = await getDataBaseData(user_packsack.name, user_packsack.retrieveData)(group.id) 62 | const curUser = value[user.id] 63 | if (!curUser) return 64 | const { card } = curUser 65 | if (card.see <= 0) return { items: 1, args: {} } 66 | const randomType = getRandom(0, 2) 67 | const type = [ 68 | ['https://img.xjh.me/random_img.php', '二刺猿图'], 69 | ['https://api.ixiaowai.cn/gqapi/gqapi.php', '风景图'], 70 | ['https://api.sunweihu.com/api/sjtx/api.php?lx=', '头像图'] 71 | ] 72 | const backErCiYuan = (curTypeRandom: string[]): Promise => 73 | new Promise(r => { 74 | const [https, title] = curTypeRandom 75 | request(https, (_: unknown, __: unknown, data: string) => { 76 | const name = /img.xjh.me\/img\/(?.*)\.jpg" src/.exec(data)?.groups?.name 77 | const seeCard_img_local_url = path.resolve(`./assets/images/seeCard/${name}.jpg`) 78 | const https = `https://img.xjh.me/img/${name}.jpg` 79 | writeFileFromRequest(https, seeCard_img_local_url, () => 80 | r({ title, msg: seeCard_img_local_url }) 81 | ) 82 | }) 83 | }) 84 | const backScenery = (curTypeRandom: string[]): Promise => 85 | new Promise(r => { 86 | const [https, title] = curTypeRandom 87 | const random = getRandom(1, 999) 88 | const seeCard_img_local_url = path.resolve(`./assets/images/seeCard/scenery${random}.jpg`) 89 | writeFileFromRequest(https, seeCard_img_local_url, () => 90 | r({ title, msg: seeCard_img_local_url }) 91 | ) 92 | }) 93 | const backAvator = (curTypeRandom: string[]): Promise => 94 | new Promise(r => { 95 | // [参数:lx] [ 值:男头a1, 女头b1, 动漫c1, 动漫女头c2, 动漫男头c3 ] 96 | const [https, title] = curTypeRandom 97 | const avatorType = [ 98 | ['a1', '男头'], 99 | ['b1', '女头'], 100 | ['c1', '动漫'], 101 | ['c2', '动漫女头'], 102 | ['c3', '动漫男头'] 103 | ] 104 | const [types, text] = avatorType[getRandom(0, 4)] 105 | const random = getRandom(1, 999) 106 | const seeCard_img_local_url = path.resolve(`./assets/images/seeCard/avator${random}.jpg`) 107 | writeFileFromRequest(`${https}${types}`, seeCard_img_local_url, () => 108 | r({ title: `${title}-${text}`, msg: seeCard_img_local_url }) 109 | ) 110 | }) 111 | const get = [backErCiYuan, backScenery, backAvator] 112 | const data = await get[randomType](type[randomType]) 113 | const { title, msg } = data 114 | setPacksack(group.id, user.id, { card: { see: -1 } }) 115 | return { items: 2, args: { title, content: operations.promiseImage(msg) } } 116 | } 117 | const seeCard = { fn, sendContent } 118 | 119 | export { seeCard } 120 | -------------------------------------------------------------------------------- /eventsHandle/groupchatAdmin/checkDeadPerson/checkDeadPerson.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * [人机验证]指令: 3 | * 使用该指令可以对某一成员进行“人机验证”,形式为向该成员发送一道随机题目; 4 | * 若成员在规定时间内回答正确,则视为人机验证通过,未通过人机验证将被踢出群聊 5 | */ 6 | import path from 'path' 7 | 8 | import { returnOneOfContent } from '../../../lib/methods' 9 | import { persons, inCheckDeadOf } from './tools' 10 | 11 | import type { TopicInfo } from './interface' 12 | import type { SendContent, CommandFn, GroupchatsId } from '../../../lib/interface' 13 | 14 | type TempReturn = [string, TopicInfo] 15 | 16 | const verifyTip = '若三分钟内未作答,或三分钟内累计答错3次,将被踢出群聊' 17 | 18 | const sendContent: SendContent = { 19 | name: '人机验证', 20 | reg: [ 21 | /^\s*人机验证\[CQ:at,qq=(?\d*),text=.*\]\s*$/, 22 | /^\s*开启人机验证\[CQ:at,qq=(?\d*),text=.*\]\s*$/, 23 | /^\s*使用人机验证\[CQ:at,qq=(?\d*),text=.*\]\s*$/ 24 | ], 25 | role: 'admin', 26 | member: ({ user: { name: username }, other: { name: othername }, operations: { at, face, promiseImage } }) => [ 27 | `${at('user')} 这???您的权限不足以开启[人机验证]指令`, 28 | `${username},抱歉 小秋检测到您暂未拥有[人机验证]指令的权限${face(98)}`, 29 | `${at('user')} 要造反了吗?这[人机验证]可不是说用就用的啊${promiseImage(path.resolve('./assets/images/emoji/反了你了1.jpg'))}`, 30 | `${username},小秋不能帮您开启人机验证`, 31 | `${at('user')} 这...小心${othername}干你啊${face(104)}` 32 | ], 33 | deverDefined: ({ user: { name: username }, other: { name: othername }, defined: { topic }, operations: { at, face, promiseImage } }) => [ 34 | [ 35 | `${username},${othername}当前正处于人机验证当中,请不要再次发起哦~`, 36 | `${at('user')} 小秋检测到${othername}正处于人机验证环节当中,不允许再次发起哦~`, 37 | `${at('user')} 不可以为${othername}同时进行两次人机验证哦~`, 38 | `${at('user')} 不要试图为${othername}开启两次人机验证哦${face(27)}`, 39 | `${username},请耐心等待当前人机验证完成后再开启下一次验证`, 40 | `${at('other')} ${username}居然要为你开启两次人机验证,快干他${promiseImage(path.resolve('./assets/images/emoji/以德服人1.jpg'))}` 41 | ], 42 | [ 43 | `${at('other')} ${username}现对您进行人机验证。题目为:\n\n${topic}\n\n[${verifyTip}]`, 44 | `${at('other')} ${username}通知小秋对您进行人机验证。题目为:\n\n${topic}\n\n[${verifyTip}]`, 45 | `${at('other')} 小秋现在对您进行人机验证。题目为:\n\n${topic}\n\n[${verifyTip}]`, 46 | `${at('other')} 快来完成这个人机验证吧,避免被误踢哦~\n\n${topic}\n\n[${verifyTip}]`, 47 | `${at('other')} 小秋对您发起了人机验证\n\n${topic}\n\n[${verifyTip}]`, 48 | `${at('other')} 听说小秋这次准备的人机验证题目很有难度?快来完成吧~\n\n${topic}\n\n[${verifyTip}]` 49 | ] 50 | ], 51 | equal: ({ other: { name }, operations: { at } }) => [ 52 | `${at('user')} 怎么个意思?验证我同行???`, 53 | `${at('user')} 哎呀 小秋还没迷糊呢 不会对${name}进行人机验证哦`, 54 | `${at('user')} 小秋不能够对${name}进行人机验证哦`, 55 | `${at('user')} 相煎何太急!!!不可以对${name}进行人机验证`, 56 | `${at('user')} 听${name}说,这人机验证可真谢谢你~`, 57 | `${at('user')} 请不要让小秋对${name}进行人机验证` 58 | ], 59 | level: ({ other: { name }, operations: { at } }) => [ 60 | `${at('user')} 抱歉,小秋暂时无法使用[人机验证]指令`, 61 | `${at('user')} 哎呀 小秋好像暂时不能使用[人机验证]指令哦~`, 62 | `${at('user')} 很不幸,小秋无法为${name}开启人机验证` 63 | ] 64 | } 65 | // 人机验证的题目 66 | const verifyTopic = [ 67 | ['《三国演义》的作者是谁?', ['罗贯中']], 68 | ['在JavaScript中,通过什么关键字来声明一个常量?', ['const', 'const关键字', '关键字const']], 69 | ['万有引力是谁提出来的?', ['牛顿', '英国人牛顿', '英国牛顿', '物理学家牛顿', '物理家牛顿', '英国物理学家牛顿', '英国物理家牛顿', '数学家牛顿']], 70 | ['农夫山泉矿泉水的建议零售价为几元?', ['2元', '2', '两元']], 71 | ['驾驶机动车在高速公路或者城市快速路上行驶时,驾驶人未按规定系安全带的,请问一次扣几分', ['2分', '2', '两分']], 72 | ['在markdown中,使用什么符号来定义一级标题?', ['#', '#号']], 73 | ['香港回归是在哪一年?', ['1997', '1997年', '1997年7月1日', '1997-07-01', '1997.07.01']], 74 | ['除了唱跳rap,还应该掌握哪些必备技能?', ['篮球']], 75 | ['列举一种常见的机械键盘轴体', ['红轴', '黑轴', '青轴', '茶轴', '白轴']], 76 | ['诗句"风萧萧兮易水寒"的下一句是什么?', ['壮士一去兮不复还', '壮士一去兮不复还!']] 77 | ] 78 | // 一个[人机验证]的记录形式 79 | const temp = (gId: GroupchatsId, oId: number): TempReturn => { 80 | // map: '群ID+用户ID', { topic, answer } 81 | const id = String(gId) + String(oId) 82 | // 随机抽取一道题目 83 | const [topic, answer]: any = returnOneOfContent(verifyTopic) 84 | return [id, { topic, answer, times: 0, isFailed: false }] 85 | } 86 | const fn: CommandFn = originData => { 87 | const { bot, group, other } = originData 88 | // 判断是否已经处于人机验证当中 89 | const haved = inCheckDeadOf(group.id, other.id) 90 | if (haved) return { items: 1 } 91 | const [id, oppositeQuestion] = temp(group.id, other.id) 92 | const token = setTimeout(() => { 93 | const have = persons.get(id) 94 | // 如果当前成员没有在人机验证序列中被删除,则意味着验证失败,所以将会在15秒之后被踢出本群聊 95 | // 反之,如果被删除了,则代表验证成功,所以不作出任何操作 96 | if (!have) return 97 | bot.sendGroupMsg(group.id, `由于<${other.card}>在三分钟内未能完成本次人机验证,因此将在15秒后被踢出此群聊`) 98 | setTimeout(() => { 99 | const id = String(group.id) + String(other.id) 100 | const have = persons.get(id) 101 | if (!have) return 102 | persons.delete(id) 103 | bot.setGroupKick(group.id, other.id) 104 | }, 1000 * 15) 105 | }, 1000 * 60 * 1) 106 | // 写入persons中进行记录 107 | persons.set(id, { ...oppositeQuestion, token }) 108 | return { items: 2, args: { topic: oppositeQuestion.topic } } 109 | } 110 | const checkDeadPerson = { fn, sendContent } 111 | 112 | export { checkDeadPerson } 113 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |