├── README.md └── src └── plugins ├── Daily_epilepsy ├── README.MD ├── __init__.py ├── handle.py └── post.json ├── LianBao ├── README.MD ├── __init__.py └── resource │ ├── cnm傻逼吃屎滚.aac │ ├── 你寄吧谁啊(振声!).aac │ ├── 你寄吧谁啊我超.aac │ ├── 你怎么说脏话啊.aac │ ├── 你是不是有毛病.aac │ ├── 你正常一点.aac │ ├── 你爹都不认识了是吧.aac │ ├── 傻逼.aac │ ├── 南通啊.aac │ ├── 去死吧.aac │ ├── 变态啊.aac │ ├── 啊你好烦啊.aac │ ├── 我是你爹.aac │ ├── 我草泥马.aac │ ├── 操你妈.aac │ ├── 救命啊.aac │ ├── 草泥马(慢).aac │ └── 非礼啊.aac ├── ai ├── LICENSE ├── README.md ├── __init__.py ├── config.py ├── getnewbing.py ├── getopenai.py ├── keywordhandle.py ├── pyproject.toml ├── requirements.txt ├── resource │ ├── audio │ │ ├── cnm傻逼吃屎滚.aac │ │ ├── 你寄吧谁啊(振声!).aac │ │ ├── 你寄吧谁啊我超.aac │ │ ├── 你是不是有毛病.aac │ │ ├── 你正常一点.aac │ │ ├── 傻逼.aac │ │ ├── 南通啊.aac │ │ ├── 去死吧.aac │ │ ├── 变态啊.aac │ │ ├── 啊你好烦啊.aac │ │ ├── 我草泥马.aac │ │ ├── 操你妈.aac │ │ ├── 救命啊.aac │ │ ├── 草泥马(慢).aac │ │ └── 非礼啊.aac │ └── json │ │ └── data.json ├── txtToImg.py └── utils.py ├── bt ├── README.md ├── __init__.py └── utils.py ├── coser ├── README.MD └── __init__.py ├── homo_mathematician ├── README.MD ├── __init__.py ├── handle.py ├── show1.jpg ├── show2.jpg ├── show3.png └── utils.py ├── leetspeak ├── README.MD ├── __init__.py ├── bug.py ├── flip.py ├── handle.py └── hxw.py ├── nonebot_plugin_setu4 ├── LICENSE ├── README.md ├── __init__.py ├── config.py ├── get_data.py ├── handle.py ├── pyproject.toml ├── requirements.txt ├── resource │ └── lolicon.db ├── setu_message.py └── utils.py ├── offline_warning ├── __init__.py ├── config.py ├── image-1.png ├── image-2.png ├── image-3.png ├── image.png └── readme.md ├── pixiv_id ├── README.MD ├── __init__.py └── handle.py ├── prc_rank_broadcast ├── __init__.py ├── config.py ├── draw.py ├── fonts │ └── SIMYOU.TTF ├── header.py ├── image-1.png ├── image.png ├── main.py └── readme.md ├── random_essay ├── README.MD ├── __init__.py ├── data.json ├── handle.py └── txt2img.py ├── status ├── README.MD ├── __init__.py ├── avatar │ └── g.png ├── font │ └── 微软正黑体.ttf ├── img │ ├── 103342810_p0.jpg │ ├── 104731716_p0.jpg │ ├── 104861996_p0.jpg │ ├── 105119539_p0.jpg │ ├── 105533027_p0.jpg │ ├── 105948004_p0.jpg │ ├── 106158310_p0.jpg │ ├── 107028114_p0.jpg │ ├── 108069094_p0.jpg │ ├── 108467250_p0.jpg │ ├── 108524848_p0.jpg │ ├── 108871862_p0.jpg │ ├── 108931630_p0.jpg │ ├── 109071980_p0.jpg │ ├── 109133709_p0.jpg │ ├── 109281594_p0.jpg │ ├── 109343095_p0.jpg │ ├── 109487724_p0.jpg │ ├── 109558844_p0.jpg │ └── 96054312_p0.jpg ├── main.py ├── requirements.txt └── show.png ├── super_resolution ├── README.MD ├── RealESRGAN_x4plus_anime_6B.pth └── __init__.py ├── what_anime ├── README.MD └── __init__.py ├── word_cloud ├── __init__.py ├── data_sheet.py ├── fonts │ └── SIMYOU.TTF ├── readme.md ├── requirements.txt └── split_tense.py └── wordle_help ├── __init__.py ├── data.json ├── handle.py ├── preview01.jpg ├── preview02.jpg └── readme.md /README.md: -------------------------------------------------------------------------------- 1 | # 个人插件库 2 | 3 | 4 | 个人站点: https://www.tencent-sb.link/ 5 | -------------------------------------------------------------------------------- /src/plugins/Daily_epilepsy/README.MD: -------------------------------------------------------------------------------- 1 | # 输出发癫文章 2 | 3 | 4 | ## 命令头: {每日发癫 , 发癫} 后跟着参数或者艾特某人 -------------------------------------------------------------------------------- /src/plugins/Daily_epilepsy/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from .handle import epilepsy 3 | 4 | on_command("每日发癫", aliases={"发癫"}, priority=5, block=True, handlers=[epilepsy.main]) 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/plugins/Daily_epilepsy/handle.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | from pathlib import Path 4 | 5 | from nonebot.adapters.onebot.v11 import (Bot, GroupMessageEvent, Message, 6 | MessageEvent) 7 | from nonebot.matcher import Matcher 8 | from nonebot.params import CommandArg 9 | 10 | 11 | class Epilepsy: 12 | def __init__(self): 13 | module_path: Path = Path(__file__).parent 14 | self.post: list = ( 15 | json.load(open(module_path / "post.json", "r", encoding="utf-8")))['post'] 16 | 17 | async def get_at(self, bot: Bot, event: MessageEvent): 18 | if isinstance(event, GroupMessageEvent): 19 | msg = event.get_message() 20 | for msg_seg in msg: 21 | if msg_seg.type == "at": 22 | return (await self.get_user_card(bot, event.group_id, int(msg_seg.data["qq"]))) 23 | return None 24 | 25 | async def get_user_card(self, bot: Bot, group_id, qid) -> str: 26 | """返还用户nickname""" 27 | user_info: dict = await bot.get_group_member_info(group_id=group_id, user_id=qid) 28 | return user_info["card"] or user_info["nickname"] 29 | 30 | async def main( 31 | self, 32 | bot: Bot, 33 | event: MessageEvent, 34 | matcher: Matcher, 35 | arg: Message = CommandArg() 36 | ): 37 | lucky_user = await self.get_at(bot,event) 38 | if lucky_user is None: 39 | msg = arg.extract_plain_text().strip() 40 | if msg == "" or msg.isspace(): 41 | await matcher.finish("你要对谁发癫") 42 | random_post = random.choice(self.post).replace("阿咪", msg) 43 | else: 44 | if lucky_user == "" or lucky_user.isspace(): 45 | await matcher.finish("你要对谁发癫") 46 | random_post = random.choice(self.post).replace("阿咪", lucky_user) 47 | 48 | await matcher.finish(random_post) 49 | 50 | 51 | 52 | epilepsy = Epilepsy() -------------------------------------------------------------------------------- /src/plugins/LianBao/README.MD: -------------------------------------------------------------------------------- 1 | # 艾特bot发送"骂我" 输出冬雪莲语音 -------------------------------------------------------------------------------- /src/plugins/LianBao/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | from pathlib import Path 4 | 5 | from nonebot import on_command 6 | from nonebot.adapters.onebot.v11 import MessageSegment 7 | from nonebot.rule import to_me 8 | 9 | audio_path = Path(__file__).parent / "resource" 10 | audio_file_name = os.listdir(str(audio_path)) 11 | 12 | donxuelian = on_command("骂我",rule=to_me(), block=True) 13 | 14 | @donxuelian.handle() 15 | async def _(): 16 | audio_name = random.choice(audio_file_name) 17 | audio = audio_path / audio_name 18 | await donxuelian.send(MessageSegment.record(audio)) 19 | -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/cnm傻逼吃屎滚.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/cnm傻逼吃屎滚.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/你寄吧谁啊(振声!).aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/你寄吧谁啊(振声!).aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/你寄吧谁啊我超.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/你寄吧谁啊我超.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/你怎么说脏话啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/你怎么说脏话啊.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/你是不是有毛病.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/你是不是有毛病.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/你正常一点.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/你正常一点.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/你爹都不认识了是吧.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/你爹都不认识了是吧.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/傻逼.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/傻逼.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/南通啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/南通啊.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/去死吧.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/去死吧.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/变态啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/变态啊.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/啊你好烦啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/啊你好烦啊.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/我是你爹.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/我是你爹.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/我草泥马.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/我草泥马.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/操你妈.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/操你妈.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/救命啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/救命啊.aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/草泥马(慢).aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/草泥马(慢).aac -------------------------------------------------------------------------------- /src/plugins/LianBao/resource/非礼啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/LianBao/resource/非礼啊.aac -------------------------------------------------------------------------------- /src/plugins/ai/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Special-Week 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/plugins/ai/README.md: -------------------------------------------------------------------------------- 1 | # nonebot2智能(障)回复插件 2 | 3 | 问问提前请务必看完readme, 这是一个融合了openai, newbing, 词库的智障回复插件 4 | 5 | ### 提醒, new bing与openai国内服务器需要科学上网才可调用, 希望你能熟练使用v2ray或clash或其他代理软件 6 | 7 | 8 | 9 | ## 功能 10 | 11 | 艾特bot时回复一些基于词库的消息, 戳一戳回复特定的消息或者语音以及反戳 12 | 接入了new bing的接口, 详情见下文 13 | 接入了openai的接口, 详情见下文 14 | 15 | ## 词库添加关键词: 16 | 1、添加关键词 [text1] 答 [text2] 17 | 2、查看关键词 [text1] 18 | 3、查看所有关键词 19 | 4、删除关键词 [text1] 20 | 5、删除关键词 [text1] 删 [number] 21 | 注: 其中1,5相应器用的是on_regex, 其余全是on_command, 请注意是否需要带上.env响应头command_start 22 | 删除关键词的[number]可以用指令第二个查询查询 23 | 查看关键词,查看所有关键词采取的是输出图片的形式发送的, 如果这两个功能用的时候报错, 那么我猜测你的Linux没有simsun.ttc(宋体)这个字体 24 | 解决方案: 源码内txtToImg.py中函数txt_to_img第三个参数font_path的值, 换成你系统有的字体, 或者安装simsun.ttc这个字体 25 | 26 | ### 安装方式: 27 | 28 | nb plugin install nonebot-plugin-smart-reply 29 | pip install nonebot-plugin-smart-reply 30 | git clone https://github.com/Special-Week/nonebot_plugin_smart_reply.git 31 | Download Zip 32 | 33 | ### env配置项: 34 | 35 | |config |type |default |example |usage | 36 | |----------------|----------------|-----------|-----------------------------------------|----------------------------------------| 37 | | bot_nickname | string |我 |Bot_NICKNAME = "Hinata" | 你Bot的称呼 | 38 | | ai_reply_private | boolean |false |ai_reply_private = true | 私聊时是否启用AI聊天 | 39 | | openai_api_key | list |寄 |openai_api_key = ["aabb114514"] | openai的api_key, 详细请看下文 | 40 | | openai_max_tokens | int |1000 |openai_max_tokens = 1500 | openai的max_tokens, 详细请看下文 | 41 | | openai_cd_time | int |600 |openai_cd_time = 114 | openai创建会话的cd | 42 | | openai_max_conversation|int|10|openai_max_conversation = 10|openai的单个会话点最大交互数量| 43 | | newbing_cd_time | int |600 |newbing_cd_time = 114 | newbing创建会话的cd | 44 | |bing_or_openai_proxy|str |"" |bing_or_openai_proxy = "http://127.0.0.1:1081" | openai或者newbing的代理, 配置详细请看下文| 45 | |newbing_style |str |creative |newbing_style = "creative" |newbing的风格, "creative", "balanced", "precise", 三选一, 乱填报错我不管| 46 | 47 | .env完全不配置不影响插件运行, 但是部分功能会无法使用(openai, newbing) 48 | 49 | 50 | 51 | 对戳一戳的反应. 33%概率回复莲宝的藏话(发送语音需要配置好ffmpeg), 33%的概率回复poke__reply的内容, 剩下的概率戳回去, 如果想改概率的话, 找到@poke_.handle()下的函数, 根据注释改概率, 莲包的藏话放在了插件目录下的resource/audio, 想加可以任意 52 | 53 | 54 | 但由于优先级较低(数字越大越低), 可能被其他插件阻断, 如果没反应请查看控制台判断被哪个插件的on_message阻断了, 然后自行拉高或者降低相关响应器的优先级, 响应器见结尾 55 | 56 | 57 | ​ 58 | ## 关于openai: 59 | 60 | 1. openai_api_key请注册openai后在 https://beta.openai.com/account/api-keys 自己获取 61 | 2. openai_max_tokens貌似是ai返回的文本最大多少(根据我自己用的经验) 62 | 3. openai_api_key必须配置, openai_max_tokens随意, 有默认值(1000) 63 | 4. 需要配置代理, 否则无法使用, 代理配置详细请看下文 64 | 5. 这个模块貌似不是免费的, 注册的账号只有$18.00的免费额度(现在缩成了5刀??), 请注意使用 65 | 6. openai_api_key要求你填的是list, 创建会话的时候会随机从list选一个, 你可以填多个, 注意观察加载插件的时候, log会提示你加载了几个apikey 66 | 7. 尽量保证revChatGPT模块是最新(pip install revChatGPT --upgrade) 67 | 68 | 69 | 用法: 70 | 1. openai + 内容, 和openai发起会话, 如果没有会新建会话 71 | 2. 重置openai, 重置openai的会话 72 | 73 | 使用了与openai通讯的接口 [ChatGPT](https://github.com/acheong08/ChatGPT) 74 | 75 | 76 | 77 | 78 | ## 关于new bing的配置: 79 | 80 | 0. 也许需要科学上网, 代理配置详细请看下文 81 | 1. 使用功能必须配置cookie, 否则无法使用, 这个cookie内容过多不适合在.env, 所以这个cookie将会与json文件的形式进行配置 82 | 2. 首先你需要一个通过申请的账号, 使用edge浏览器安装"editthiscookie"浏览器插件, 或者使用相关的其他插件获取cookie. 进入"bing.com/chat"登录通过的账号 83 | 3. 右键界面选择"editthiscookie", 找到一个看上去像出门的样子的图标"导出cookie", cookie一般就能在你的剪贴板, 注意了, cookie导出来是一个list, 大概长这样[{},{},{}] 84 | 4. 新建cookiexxx.json文件(xxx为任意合法字符), 把你剪贴板的cookie的字符串粘贴进去, 再次强调json大概长[{},{},{}]这样 85 | 5. 打开你bot项目文件夹, 依次进入data/smart_reply, 没有就新建, 把json文件丢进去, 有几个账号可以放几个, 要求cookie开头, .json结尾, 载入插件时初始化会全部读取, 创建会话的时候会通过random来选择一个账号的cookie 86 | 6. 注意观察加载插件的时候, log会提示你加载了几个cookie 87 | 7. 调用时报错请检查cookie是否有效, 是否做到了科学上网 88 | 89 | 用法: 90 | 1. bing + 内容, 和bing发起会话, 如果没有会新建会话. 91 | 2. 重置bing, 重置bing的会话 92 | 93 | 使用了与Bing通讯的接口 [EdgeGPT](https://github.com/acheong08/EdgeGPT) 94 | tips: 根据这条[issue](https://github.com/acheong08/EdgeGPT/issues/584),中国大陆服务器你也许edgeGPT 0.10.16才能使用 95 | 96 | 97 | ## bing_or_openai_proxy的配置: 98 | 99 | 1. 你需要使用v2ray或者clash等代理工具开启本地监听端口 100 | 2. 根据http和socks5的不同, 配置不同, 101 | 3. 以v2rayN举例, 本地监听端口1080, 你应该配置成"socks5://127.0.0.1:1080"或者"http://127.0.0.1:1081" 102 | 4. 以clash for windows举例, 本地监听端口7890, 你应该配置成"socks5://127.0.0.1:7890"或者"http://127.0.0.1:7890" 103 | 104 | 105 | 106 | 107 | 108 | 响应器: 109 | ```python 110 | # 戳一戳响应器 优先级1, 不会向下阻断, 条件: 戳一戳bot触发 111 | on_notice(rule=to_me(), block=False, handlers=[key_word_module.poke_handle]) 112 | # 添加关键词响应器, 优先级11, 条件: 正则表达式 113 | on_regex(r"^添加关键词\s*(\S+.*?)\s*答\s*(\S+.*?)\s*$", flags=re.S, block=True, priority=11, permission=SUPERUSER, handlers=[key_word_module.add_new_keyword]) 114 | # 查看所有关键词响应器, 优先级11, 条件: 命令头 115 | on_command("查看所有关键词", aliases={"查询所有关键词"}, block=True, priority=11, permission=SUPERUSER, handlers=[key_word_module.check_all_keyword]) 116 | # 删除关键词响应器, 优先级11, 条件: 命令头 117 | on_regex(r"^删除关键词\s*(\S+.*?)\s*删\s*(\S+.*?)\s*$", flags=re.S, priority=10, permission=SUPERUSER, handlers=[key_word_module.del_akeyword_handle]) 118 | # 删除关键词的一个回复响应器, 优先级10, 条件: 正则表达式 119 | on_regex(r"^删除关键词\s*(\S+.*?)\s*删\s*(\S+.*?)\s*$", flags=re.S, priority=10, permission=SUPERUSER, handlers=[key_word_module.del_akeyword_handle]) 120 | # 普通回复响应器, 优先级999, 条件: 艾特bot就触发 121 | on_message(rule=to_me(), priority=999, block=False, handlers=[key_word_module.regular_reply]) 122 | # 查看关键词响应器 123 | on_command("查看关键词", aliases={"查询关键词"}, priority=11, block=True, permission=SUPERUSER, handlers=[key_word_module.check_keyword_handle]) 124 | # 使用bing的响应器 125 | on_command("bing", priority=55, block=True, handlers=[newbing.bing_handle]) 126 | on_command("重置bing", aliases={"重置会话", "bing重置", "会话重置"}, priority=10, block=True, handlers=[newbing.reserve_bing]) 127 | # 使用openai的响应器 128 | on_command("openai",aliases={"求助"},block=True, priority=55, handlers=[openai.openai_handle]) 129 | on_command("重置openai", aliases={"重置会话", "openai重置", "会话重置"}, priority=10, block=True, handlers=[openai.reserve_openai]) 130 | on_command("apikey_status", aliases={"apikey用量", "apikey状态"}, priority=10, block=True, handlers=[openai.apikey_status]) 131 | ``` 132 | -------------------------------------------------------------------------------- /src/plugins/ai/__init__.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import re 3 | 4 | from nonebot.permission import SUPERUSER 5 | from nonebot.plugin.on import on_command, on_message, on_notice, on_regex 6 | from nonebot.rule import to_me 7 | 8 | from .getnewbing import newbing 9 | from .getopenai import openai 10 | from .keywordhandle import key_word_module 11 | 12 | with contextlib.suppress(Exception): 13 | from nonebot.plugin import PluginMetadata 14 | 15 | __plugin_meta__ = PluginMetadata( 16 | name="smart_reply", 17 | description="nonebot2的融合了openai, newbing, 词库的智障回复插件", 18 | usage=""" 19 | openai [文本] # 使用openai的api进行交互 20 | bing [文本] # 使用new bing的api进行交互 21 | @bot [文本] # 使用词库进行交互 22 | 添加关键词 [关键词] 答 [回复] # 添加自带词库的关键词 23 | 删除关键词 [关键词] # 删除自带词库的关键词 24 | 删除关键词 [关键词] 删 [回复] # 删除自带词库的关键词的一个回复 25 | 查看所有关键词 # 查看自带词库的所有关键词 26 | 查看关键词 [关键词] # 查看自带词库的关键词的所有回复 27 | 重置bing # 重置bing的会话 28 | 重置openai # 重置openai的会话 29 | 重置会话 # 重置bing和openai的会话 30 | 群内戳一戳bot # 戳一戳bot触发 31 | """, 32 | type="application", 33 | homepage="https://github.com/Special-Week/nonebot_plugin_smart_reply", 34 | supported_adapters={"~onebot.v11"}, 35 | extra={ 36 | "author": "Special-Week", 37 | "link": "https://github.com/Special-Week/nonebot_plugin_smart_reply", 38 | "version": "0.05.114514", 39 | "priority": [1, 10, 11, 55, 999], 40 | }, 41 | ) 42 | 43 | 44 | # 戳一戳响应器 优先级1, 不会向下阻断, 条件: 戳一戳bot触发 45 | on_notice(rule=to_me(), block=False, handlers=[key_word_module.poke_handle]) 46 | 47 | # 添加关键词响应器, 优先级11, 条件: 正则表达式 48 | on_regex( 49 | r"^添加关键词\s*(\S+.*?)\s*答\s*(\S+.*?)\s*$", 50 | flags=re.S, 51 | block=True, 52 | priority=11, 53 | permission=SUPERUSER, 54 | handlers=[key_word_module.add_new_keyword], 55 | ) 56 | 57 | # 查看所有关键词响应器, 优先级11, 条件: 命令头 58 | on_command( 59 | "查看所有关键词", 60 | aliases={"查询所有关键词"}, 61 | block=True, 62 | priority=11, 63 | permission=SUPERUSER, 64 | handlers=[key_word_module.check_all_keyword], 65 | ) 66 | 67 | # 删除关键词响应器, 优先级11, 条件: 命令头 68 | on_command( 69 | "删除关键词", 70 | priority=11, 71 | block=True, 72 | permission=SUPERUSER, 73 | handlers=[key_word_module.del_keyword_handle], 74 | ) 75 | 76 | # 删除关键词的一个回复响应器, 优先级10, 条件: 正则表达式 77 | on_regex( 78 | r"^删除关键词\s*(\S+.*?)\s*删\s*(\S+.*?)\s*$", 79 | flags=re.S, 80 | priority=10, 81 | permission=SUPERUSER, 82 | handlers=[key_word_module.del_akeyword_handle], 83 | ) 84 | 85 | # 普通回复响应器, 优先级999, 条件: 艾特bot就触发 86 | on_message( 87 | rule=to_me(), priority=999, block=False, handlers=[key_word_module.regular_reply] 88 | ) 89 | 90 | # 查看关键词响应器 91 | on_command( 92 | "查看关键词", 93 | aliases={"查询关键词"}, 94 | priority=11, 95 | block=True, 96 | permission=SUPERUSER, 97 | handlers=[key_word_module.check_keyword_handle], 98 | ) 99 | 100 | # 使用bing的响应器 101 | on_command( 102 | "bing", aliases={"newbing"}, priority=55, block=True, handlers=[newbing.bing_handle] 103 | ) 104 | on_command( 105 | "重置bing", 106 | aliases={"重置会话", "bing重置", "会话重置"}, 107 | priority=10, 108 | block=True, 109 | handlers=[newbing.reserve_bing], 110 | ) 111 | 112 | # 使用openai的响应器 113 | on_command( 114 | "openai", 115 | aliases={"求助"}, 116 | block=True, 117 | priority=55, 118 | handlers=[openai.openai_handle], 119 | ) 120 | on_command( 121 | "重置openai", 122 | aliases={"重置会话", "openai重置", "会话重置"}, 123 | priority=10, 124 | block=True, 125 | handlers=[openai.reserve_openai], 126 | ) 127 | 128 | on_command( 129 | "apikey_status", 130 | aliases={"apikey用量", "apikey状态"}, 131 | priority=10, 132 | block=True, 133 | handlers=[openai.apikey_status], 134 | ) 135 | -------------------------------------------------------------------------------- /src/plugins/ai/config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Optional, Sequence 3 | 4 | from nonebot import get_driver 5 | from nonebot.log import logger 6 | from pydantic import BaseSettings, validator 7 | 8 | 9 | class Config(BaseSettings): 10 | bot_nickname: str = "我" 11 | smart_reply_path: Path = Path("data/smart_reply") 12 | ai_reply_private: bool = False 13 | openai_api_key: Optional[Sequence[str]] 14 | openai_max_tokens: int = 1000 15 | openai_cd_time: int = 600 16 | openai_max_conversation: int = 10 17 | newbing_cd_time: int = 600 18 | newbing_style: str = "creative" 19 | bing_or_openai_proxy: str = "" 20 | superusers: Sequence[str] = [] 21 | 22 | @validator("openai_api_key") 23 | def _check_openai_api_key(cls, v): 24 | if isinstance(v, str): 25 | logger.info("openai_api_key读取, 初始化成功, 共 1 个api_key") 26 | return [v] 27 | elif isinstance(v, Sequence) and v: 28 | logger.info(f"openai_api_key读取, 初始化成功, 共 {len(v)} 个api_key") 29 | return v 30 | else: 31 | logger.warning("未检测到 openai_api_key,已禁用相关功能") 32 | 33 | class Config: 34 | extra = "ignore" 35 | 36 | 37 | config: Config = Config.parse_obj(get_driver().config) 38 | 39 | 40 | if not config.smart_reply_path.exists() or not config.smart_reply_path.is_dir(): 41 | config.smart_reply_path.mkdir(0o755, parents=True, exist_ok=True) 42 | -------------------------------------------------------------------------------- /src/plugins/ai/getnewbing.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle 4 | from nonebot.adapters.onebot.v11 import ( 5 | Message, 6 | MessageEvent, 7 | MessageSegment, 8 | PrivateMessageEvent, 9 | ) 10 | from nonebot.matcher import Matcher 11 | from nonebot.params import CommandArg 12 | 13 | from .utils import utils 14 | 15 | 16 | class NewBing: 17 | def __init__(self) -> None: 18 | """初始化newbing, 标记cookie是否有效, 以及是否私聊启用""" 19 | self.cookie_allow = bool(utils.bing_cookies) 20 | self.reply_private: bool = utils.reply_private 21 | self.style: Dict[str, ConversationStyle] = { 22 | "creative": ConversationStyle.creative, 23 | "balanced": ConversationStyle.balanced, 24 | "precise": ConversationStyle.precise, 25 | } 26 | 27 | @staticmethod 28 | async def reserve_bing(matcher: Matcher, event: MessageEvent) -> None: 29 | await utils.newbing_new_chat(event=event, matcher=matcher) 30 | await matcher.send("newbing会话已重置", at_sender=True) 31 | 32 | async def pretreatment( 33 | self, event: MessageEvent, matcher: Matcher, msg: str 34 | ) -> None: 35 | """稍微预处理一下""" 36 | uid: str = event.get_user_id() # 获取用户id 37 | if not self.reply_private and isinstance(event, PrivateMessageEvent): 38 | await matcher.finish() # 配置私聊不启用后,私聊信息直接结束处理 39 | if msg.isspace() or not msg: # 如果消息为空或者全为空格, 则结束处理 40 | await matcher.finish() 41 | if not self.cookie_allow: 42 | await matcher.finish("cookie未设置, 无法访问") 43 | if msg in utils.nonsense: 44 | await matcher.finish( 45 | MessageSegment.text(await utils.rand_hello()), reply_message=True 46 | ) 47 | if uid not in utils.bing_chat_dict: 48 | await utils.newbing_new_chat(event=event, matcher=matcher) 49 | await matcher.send(MessageSegment.text("newbing新会话已创建"), reply_message=True) 50 | if utils.bing_chat_dict[uid]["isRunning"]: 51 | await matcher.finish( 52 | MessageSegment.text("当前会话正在运行中, 请稍后再发起请求"), reply_message=True 53 | ) 54 | utils.bing_chat_dict[uid]["isRunning"] = True 55 | 56 | async def bing_handle( 57 | self, matcher: Matcher, event: MessageEvent, args: Message = CommandArg() 58 | ) -> None: 59 | """newbing聊天的handle函数""" 60 | uid: str = event.get_user_id() # 获取用户id 61 | msg: str = args.extract_plain_text() # 获取消息 62 | 63 | await self.pretreatment(event=event, matcher=matcher, msg=msg) # 预处理 64 | 65 | bot: Chatbot = utils.bing_chat_dict[uid]["chatbot"] # 获取当前会话的Chatbot对象 66 | style: str = utils.bing_chat_dict[uid]["model"] # 获取当前会话的对话样式 67 | 68 | try: # 尝试获取bing的回复 69 | data: Dict[str, Any] = await bot.ask( 70 | prompt=msg, conversation_style=self.style[style], simplify_response=True 71 | ) 72 | except Exception as e: # 如果出现异常, 则返回异常信息, 并且将当前会话状态设置为未运行 73 | utils.bing_chat_dict[uid]["isRunning"] = False 74 | await matcher.finish( 75 | MessageSegment.text(f'askError: {repr(e)}多次askError请尝试"重置bing"'), 76 | reply_message=True, 77 | ) 78 | 79 | utils.bing_chat_dict[uid]["isRunning"] = False # 将当前会话状态设置为未运行 80 | utils.bing_chat_dict[uid]["sessions_number"] += 1 # 会话数+1 81 | if "text" not in data: 82 | await matcher.finish( 83 | MessageSegment.text("bing没有返回text, 请重试"), reply_message=True 84 | ) 85 | current_conversation: int = utils.bing_chat_dict[uid]["sessions_number"] 86 | max_conversation: int = data["messages_left"] + current_conversation 87 | 88 | rep_message: str = await utils.bing_string_handle(data["text"]) 89 | 90 | try: # 尝试发送回复 91 | await matcher.send( 92 | MessageSegment.text( 93 | f"{rep_message}\n\n当前{current_conversation} 共 {max_conversation}" 94 | ), 95 | reply_message=True, 96 | ) 97 | if max_conversation <= current_conversation: 98 | await matcher.send( 99 | MessageSegment.text("达到对话上限, 正帮你重置会话"), reply_message=True 100 | ) 101 | try: 102 | await utils.newbing_new_chat(event=event, matcher=matcher) 103 | except Exception: 104 | return 105 | except Exception as e: # 如果发送失败, 则尝试把文字写在图片上发送 106 | try: 107 | await matcher.send( 108 | MessageSegment.text(f"文本消息可能被风控了\n错误信息:{repr(e)}\n这里咱尝试把文字写在图片上发送了") 109 | + MessageSegment.image(await utils.text_to_img(rep_message)), 110 | reply_message=True, 111 | ) 112 | except Exception as eeee: # 如果还是失败, 我也没辙了, 只能返回异常信息了 113 | await matcher.send( 114 | MessageSegment.text(f"消息全被风控了, 这是捕获的异常: \n{repr(eeee)}"), 115 | reply_message=True, 116 | ) 117 | 118 | 119 | # 实例化一个NewBing对象 120 | newbing = NewBing() 121 | -------------------------------------------------------------------------------- /src/plugins/ai/getopenai.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from datetime import datetime, timedelta 3 | from typing import Dict 4 | 5 | from httpx import AsyncClient 6 | from nonebot.adapters.onebot.v11 import ( 7 | Message, 8 | MessageEvent, 9 | MessageSegment, 10 | PrivateMessageEvent, 11 | ) 12 | from nonebot.matcher import Matcher 13 | from nonebot.params import CommandArg 14 | from revChatGPT.V3 import Chatbot 15 | 16 | from .utils import utils 17 | 18 | 19 | class Openai: 20 | def __init__(self) -> None: 21 | """初始化, 获取openai的apikey是否存在, 获取是否私聊启用""" 22 | self.apikey_allow = bool(utils.openai_api_key) 23 | self.reply_private: bool = utils.reply_private 24 | self.max_sessions_number: int = utils.max_sessions_number 25 | # 查询openai的使用情况 26 | self.usage_url = "/v1/dashboard/billing/usage" 27 | self.subscription_url = "/v1/dashboard/billing/subscription" 28 | self.expire_time_url = "dashboard/billing/credit_grants" 29 | self.host = "https://api.openai.com" 30 | 31 | @staticmethod 32 | async def reserve_openai(matcher: Matcher, event: MessageEvent) -> None: 33 | """重置openai会话""" 34 | await utils.openai_new_chat(event=event, matcher=matcher) 35 | await matcher.send("openai会话已重置", at_sender=True) 36 | 37 | async def openai_handle( 38 | self, matcher: Matcher, event: MessageEvent, args: Message = CommandArg() 39 | ) -> None: 40 | """openai聊天的handle函数""" 41 | if not self.reply_private and isinstance(event, PrivateMessageEvent): 42 | return # 配置私聊不启用后,私聊信息直接结束处理 43 | uid: str = event.get_user_id() # 获取用户id 44 | msg: str = args.extract_plain_text() # 获取消息 45 | 46 | if not self.apikey_allow: 47 | await matcher.finish("openai_api_key未设置, 无法访问") 48 | 49 | if msg.isspace() or not msg: 50 | return # 如果消息为空, 则不处理 51 | if msg in utils.nonsense: 52 | # 如果消息为空或者是一些无意义的问候, 则返回一些问候语 53 | await matcher.finish( 54 | MessageSegment.text(await utils.rand_hello()), reply_message=True 55 | ) 56 | if uid not in utils.openai_chat_dict: # 如果用户id不在会话字典中, 则新建一个会话 57 | await utils.openai_new_chat(event=event, matcher=matcher) 58 | await matcher.send(MessageSegment.text("openai新会话已创建"), reply_message=True) 59 | if utils.openai_chat_dict[uid]["isRunning"]: # 如果当前会话正在运行, 则返回正在运行 60 | await matcher.finish( 61 | MessageSegment.text("当前会话正在运行中, 请稍后再发起请求"), reply_message=True 62 | ) 63 | if utils.openai_chat_dict[uid]["sessions_number"] >= self.max_sessions_number: 64 | # 如果会话数超过最大会话数, 则返回会话数已达上限 65 | await matcher.send( 66 | MessageSegment.text("会话数已达上限, 正在帮您请重置会话"), reply_message=True 67 | ) 68 | await self.reserve_openai(matcher=matcher, event=event) 69 | return 70 | utils.openai_chat_dict[uid]["isRunning"] = True # 将当前会话状态设置为运行中 71 | bot: Chatbot = utils.openai_chat_dict[uid]["chatbot"] # 获取当前会话的Chatbot对象 72 | try: 73 | loop = asyncio.get_event_loop() # 调用ask会阻塞asyncio 74 | data: str = await loop.run_in_executor(None, bot.ask, msg) 75 | utils.openai_chat_dict[uid]["sessions_number"] += 1 # 会话数+1 76 | except Exception as e: # 如果出现异常, 则返回异常信息, 并且将当前会话状态设置为未运行 77 | utils.openai_chat_dict[uid]["isRunning"] = False 78 | await matcher.finish( 79 | MessageSegment.text(f'askError: {repr(e)}多次askError请尝试发送"重置openai"'), 80 | reply_message=True, 81 | ) 82 | utils.openai_chat_dict[uid]["isRunning"] = False # 将当前会话状态设置为未运行 83 | sessions_number: int = utils.openai_chat_dict[uid][ 84 | "sessions_number" 85 | ] # 获取当前会话的会话数 86 | data += f'\n\n当前: {sessions_number} 共{self.max_sessions_number} \n字数异常请发送"重置openai"' 87 | try: 88 | await matcher.send(MessageSegment.text(data), reply_message=True) 89 | except Exception as e: 90 | try: 91 | await matcher.send( 92 | MessageSegment.text(f"文本消息被风控了,错误信息:{repr(e)}, 这里咱尝试把文字写在图片上发送了") 93 | + MessageSegment.image(await utils.text_to_img(data)), 94 | reply_message=True, 95 | ) 96 | except Exception as eeee: 97 | await matcher.send( 98 | MessageSegment.text(f"消息全被风控了, 这是捕获的异常: \n{repr(eeee)}"), 99 | reply_message=True, 100 | ) 101 | 102 | async def get_usage(self, params: dict, apikey: str) -> str: 103 | headers: Dict[str, str] = { 104 | "Content-Type": "application/json", 105 | "Authorization": f"Bearer {apikey}", 106 | } 107 | try: 108 | async with AsyncClient(proxies=utils.proxy) as client: 109 | usage_result = ( 110 | await client.get( 111 | self.host + self.usage_url, headers=headers, params=params 112 | ) 113 | ).json() 114 | subscription_result = ( 115 | await client.get(self.host + self.subscription_url, headers=headers) 116 | ).json() 117 | # 获取总额 118 | total_usd = subscription_result["hard_limit_usd"] 119 | # 获取过期时间 120 | expire_seconds = subscription_result["access_until"] 121 | date = datetime.fromtimestamp(int(expire_seconds)) 122 | # 获取使用量 123 | total_usage = usage_result["total_usage"] / 100 124 | return f"apikey:{f'{apikey[:7]}*******{apikey[-4:]}'}\n实际已使用金额:{round(total_usage, 3)}\n总共金额:{round(total_usd, 3)}\n过期日期:{date.isoformat()}\n\n" 125 | except Exception as e: 126 | return f"apikey:{f'{apikey[:7]}*******{apikey[-4:]}'}\n获取使用量失败, 错误信息:{repr(e)}\n\n" 127 | 128 | async def apikey_status( 129 | self, 130 | matcher: Matcher, 131 | ) -> None: 132 | """获取apikey状态""" 133 | if not self.apikey_allow: 134 | await matcher.finish("apikey未设置, 无法访问") 135 | else: 136 | params: Dict[str, str] = { 137 | "start_date": (datetime.now() - timedelta(days=90)).strftime( 138 | "%Y-%m-%d" 139 | ), 140 | "end_date": (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d"), 141 | } 142 | tasks = [ 143 | self.get_usage(params=params, apikey=apikey) 144 | for apikey in utils.openai_api_key 145 | ] 146 | result = await asyncio.gather(*tasks) 147 | await matcher.finish("".join(result)) 148 | 149 | 150 | # 创建实例 151 | openai = Openai() 152 | -------------------------------------------------------------------------------- /src/plugins/ai/keywordhandle.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import re 4 | from pathlib import Path 5 | from typing import Tuple, Union 6 | 7 | from nonebot.adapters.onebot.v11 import ( 8 | GroupMessageEvent, 9 | Message, 10 | MessageEvent, 11 | MessageSegment, 12 | PokeNotifyEvent, 13 | PrivateMessageEvent, 14 | ) 15 | from nonebot.matcher import Matcher 16 | from nonebot.params import CommandArg, RegexGroup 17 | from httpx import AsyncClient 18 | from .utils import utils 19 | 20 | 21 | class KeyWordModule: 22 | def __init__(self) -> None: 23 | self.reply_private: bool = utils.reply_private 24 | 25 | @staticmethod 26 | async def check_keyword_handle( 27 | matcher: Matcher, args: Message = CommandArg() 28 | ) -> None: 29 | """查看关键词""" 30 | key: str = args.extract_plain_text() 31 | if not key or key.isspace(): 32 | await matcher.finish("check失败, 你要输入关键词哦") 33 | mes: str = await utils.check_word(key) 34 | if mes == "寄": 35 | await matcher.finish("抱歉没有记过这个关键词捏,请输入[查询所有关键词]来获取全部关键词") 36 | else: 37 | output: bytes = await utils.text_to_img(text=mes) # 将文字转换为图片 38 | await matcher.finish(MessageSegment.image(output)) 39 | 40 | @staticmethod 41 | def have_url(s: str) -> bool: 42 | """判断传入的字符串中是否有url存在(我他娘的就不信这样还能输出广告?)""" 43 | index = s.find(".") # 找到.的下标 44 | if index == -1: # 如果没有.则返回False 45 | return False 46 | flag1 = ("\u0041" <= s[index - 1] <= "\u005a") or ( 47 | "\u0061" <= s[index - 1] <= "\u007a" 48 | ) # 判断.前面的字符是否为字母 49 | flag2 = ("\u0041" <= s[index + 1] <= "\u005a") or ( 50 | "\u0061" <= s[index + 1] <= "\u007a" 51 | ) # 判断.后面的字符是否为字母 52 | return flag1 and flag2 53 | 54 | async def get_qinyunkeapi(self, ques: str) -> str: 55 | async with AsyncClient() as client: 56 | try: 57 | resp = ( 58 | await client.get( 59 | f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={ques}" 60 | ) 61 | ).json() 62 | if self.have_url(resp["content"]): 63 | if bool(utils.bing_cookies) and bool(utils.openai_api_key): 64 | return f'这个问题{utils.bot_nickname}暂时不知道怎么回答你呢, 试试使用"openai"/"bing"命令头调用openai或new bing吧吧' 65 | if bool(utils.openai_api_key): 66 | return f'这个问题{utils.bot_nickname}暂时不知道怎么回答你呢, 试试使用"openai"命令头调用openai吧' 67 | if bool(utils.bing_cookies): 68 | return f'这个问题{utils.bot_nickname}暂时不知道怎么回答你呢, 试试使用"bing"命令头调用new bing吧' 69 | return f"这个问题{utils.bot_nickname}暂时不知道怎么回答你呢, 换个话题吧" 70 | content: str = (resp["content"]).replace("{br}", "\n") 71 | maybe_master = ["dn", "林欣", "贾彦娟", "周超辉", "鑫总", "张鑫", "1938877131"] 72 | maybe_nickname = ["菲菲", "小燕"] 73 | su = random.choice(list(utils.superuser)) 74 | for i in maybe_master: 75 | content = content.replace(i, su) 76 | for i in maybe_nickname: 77 | content = content.replace(i, utils.bot_nickname) 78 | return content 79 | except Exception as e: 80 | return f"调用api失败, 错误信息: {repr(e)}" 81 | 82 | @staticmethod 83 | async def del_akeyword_handle( 84 | matcher: Matcher, 85 | matched: Tuple[str, int] = RegexGroup(), 86 | ) -> None: 87 | """删除关键词, 通过序号删除或者通过关键词删除""" 88 | word1, word2 = matched 89 | if await utils.del_word(word1, word2) == "寄": 90 | await matcher.finish("找不到关键词或回复序号,请用查看命令核对") 91 | else: 92 | await matcher.finish("删除成功~") 93 | 94 | @staticmethod 95 | async def del_keyword_handle( 96 | matcher: Matcher, args: Message = CommandArg() 97 | ) -> None: 98 | """删除关键词, 通过关键词删除""" 99 | key: str = args.extract_plain_text() 100 | if not key or key.isspace(): 101 | await matcher.finish("没有关键词,del失败") 102 | else: 103 | try: 104 | del utils.anime_thesaurus[key] 105 | with open(utils.keyword_path, "w", encoding="utf8") as f: 106 | json.dump(utils.anime_thesaurus, f, ensure_ascii=False, indent=4) 107 | await matcher.send("已删除该关键词下所有回复~") 108 | except Exception: 109 | await matcher.finish("del失败, 貌似没有这个关键词呢") 110 | 111 | @staticmethod 112 | async def check_all_keyword(matcher: Matcher) -> None: 113 | """查看全部关键词""" 114 | mes: str = await utils.check_all() 115 | output: bytes = await utils.text_to_img(mes) 116 | await matcher.finish(MessageSegment.image(output)) 117 | 118 | @staticmethod 119 | async def add_new_keyword( 120 | matcher: Matcher, 121 | matched: Tuple[str, ...] = RegexGroup(), 122 | ) -> None: 123 | """添加新的关键词""" 124 | word1, word2 = matched 125 | if await utils.add_word(word1, word2) == "寄": 126 | await matcher.finish("这个关键词已经记住辣") 127 | else: 128 | await matcher.finish("我记住了\n关键词:" + word1 + "\n回复:" + word2) 129 | 130 | @staticmethod 131 | async def poke_handle(matcher: Matcher, event: PokeNotifyEvent) -> None: 132 | """戳一戳回复, 私聊会报错, 暂时摸不着头脑""" 133 | if event.is_tome(): 134 | probability: float = random.random() 135 | # 33%概率回复莲宝的藏话 136 | if probability < 0.33: 137 | # 发送语音需要配置ffmpeg, 这里try一下, 不行就随机回复poke__reply的内容 138 | try: 139 | await matcher.send( 140 | MessageSegment.record( 141 | Path(utils.audio_path) / random.choice(utils.audio_list) 142 | ) 143 | ) 144 | except Exception: 145 | await matcher.send(await utils.rand_poke()) 146 | elif probability > 0.66: 147 | # 33% 概率戳回去 148 | await matcher.send(Message(f"[CQ:poke,qq={event.user_id}]")) 149 | # probability在0.33和0.66之间的概率回复poke__reply的内容 150 | else: 151 | await matcher.send(await utils.rand_poke()) 152 | 153 | async def regular_reply(self, matcher: Matcher, event: MessageEvent) -> None: 154 | """普通回复""" 155 | # 配置私聊不启用后,私聊信息直接结束处理 156 | if not self.reply_private and isinstance(event, PrivateMessageEvent): 157 | return 158 | # 获取消息文本 159 | msg = event.get_message().extract_plain_text() 160 | # 如果是光艾特bot(没消息返回)或者打招呼的话,就回复以下内容 161 | if (not msg) or msg.isspace() or msg in utils.nonsense: 162 | await matcher.finish(MessageSegment.text(await utils.rand_hello())) 163 | # 获取用户nickname 164 | if isinstance(event, GroupMessageEvent): 165 | nickname: Union[str, None] = event.sender.card or event.sender.nickname 166 | else: 167 | nickname = event.sender.nickname 168 | # 从字典里获取结果 169 | if nickname is None: 170 | nickname = "你" 171 | result: Union[str, None] = await utils.get_chat_result(msg, nickname) 172 | # 如果词库没有结果,则调用api获取智能回复 173 | if result is None: 174 | result = await self.get_qinyunkeapi(msg) 175 | await matcher.finish(MessageSegment.text(result), reply_message=True) 176 | await matcher.finish(MessageSegment.text(result), reply_message=True) 177 | 178 | 179 | # 创建实例 180 | key_word_module = KeyWordModule() 181 | -------------------------------------------------------------------------------- /src/plugins/ai/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "nonebot_plugin_smart_reply" 3 | version = "0.02.114514" 4 | description = "nonebot2的融合了openai, newbing, 词库的智障回复插件" 5 | authors = [ 6 | {name = "Special-Week", email = "HuaMing27499@gmail.com"}, 7 | ] 8 | dependencies = [ 9 | "EdgeGPT==0.10.16", 10 | "revChatGPT>=3.3.5", 11 | "pillow>=9.1.1", 12 | "nonebot2>=2.0.0b5", 13 | "nonebot-adapter-onebot>=2.2.3", 14 | ] 15 | requires-python = ">=3.9" 16 | readme = "README.md" 17 | license = {text = "MIT"} 18 | -------------------------------------------------------------------------------- /src/plugins/ai/requirements.txt: -------------------------------------------------------------------------------- 1 | EdgeGPT==0.10.16 2 | revChatGPT>=3.3.5 3 | pillow>=9.1.1 4 | nonebot2>=2.0.0b5 5 | nonebot-adapter-onebot>=2.2.3 -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/cnm傻逼吃屎滚.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/cnm傻逼吃屎滚.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/你寄吧谁啊(振声!).aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/你寄吧谁啊(振声!).aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/你寄吧谁啊我超.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/你寄吧谁啊我超.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/你是不是有毛病.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/你是不是有毛病.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/你正常一点.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/你正常一点.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/傻逼.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/傻逼.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/南通啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/南通啊.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/去死吧.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/去死吧.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/变态啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/变态啊.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/啊你好烦啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/啊你好烦啊.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/我草泥马.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/我草泥马.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/操你妈.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/操你妈.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/救命啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/救命啊.aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/草泥马(慢).aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/草泥马(慢).aac -------------------------------------------------------------------------------- /src/plugins/ai/resource/audio/非礼啊.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/ai/resource/audio/非礼啊.aac -------------------------------------------------------------------------------- /src/plugins/ai/txtToImg.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | 3 | from PIL import Image, ImageDraw, ImageFont 4 | 5 | 6 | class TxtToImg: 7 | def __init__(self) -> None: 8 | self.LINE_CHAR_COUNT = 30 * 2 9 | self.CHAR_SIZE = 30 10 | self.TABLE_WIDTH = 4 11 | 12 | async def line_break(self, line: str) -> str: 13 | """将一行文本按照指定宽度进行换行""" 14 | ret: str = "" 15 | width = 0 16 | for c in line: 17 | if len(c.encode("utf8")) == 3: # 中文 18 | if self.LINE_CHAR_COUNT == width + 1: # 剩余位置不够一个汉字 19 | width = 2 20 | ret += "\n" + c 21 | else: # 中文宽度加2,注意换行边界 22 | width += 2 23 | ret += c 24 | elif c == "\n": 25 | width = 0 26 | ret += c 27 | elif c == "\t": 28 | space_c: int = ( 29 | self.TABLE_WIDTH - width % self.TABLE_WIDTH 30 | ) # 已有长度对TABLE_WIDTH取余 31 | ret += " " * space_c 32 | width += space_c 33 | else: 34 | width += 1 35 | ret += c 36 | if width >= self.LINE_CHAR_COUNT: 37 | ret += "\n" 38 | width = 0 39 | return ret if ret.endswith("\n") else ret + "\n" 40 | 41 | async def txt_to_img( 42 | self, text: str, font_size=30, font_path="simsun.ttc" 43 | ) -> bytes: 44 | """将文本转换为图片""" 45 | text = await self.line_break(text) 46 | d_font = ImageFont.truetype(font_path, font_size) 47 | lines: int = text.count("\n") 48 | image: Image.Image = Image.new( 49 | "L", 50 | (self.LINE_CHAR_COUNT * font_size // 2 + 50, font_size * lines + 50), 51 | "white", 52 | ) 53 | draw_table = ImageDraw.Draw(im=image) 54 | draw_table.text(xy=(25, 25), text=text, fill="#000000", font=d_font, spacing=4) 55 | new_img: Image.Image = image.convert("RGB") 56 | img_byte = BytesIO() 57 | new_img.save(img_byte, format="PNG") 58 | return img_byte.getvalue() 59 | 60 | 61 | # 创建一个实例 62 | txt_to_img = TxtToImg() 63 | -------------------------------------------------------------------------------- /src/plugins/ai/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import random 4 | import re 5 | from pathlib import Path 6 | from typing import Dict, List, Tuple, Union 7 | 8 | from EdgeGPT.EdgeGPT import Chatbot as bingChatbot 9 | from loguru import logger 10 | from nonebot.adapters.onebot.v11 import MessageEvent, MessageSegment 11 | from nonebot.matcher import Matcher 12 | from revChatGPT.V3 import Chatbot as openaiChatbot 13 | 14 | from .config import config 15 | from .txtToImg import txt_to_img 16 | 17 | 18 | class Utils: 19 | def __init__(self) -> None: 20 | """初始化""" 21 | self.reply_private: bool = config.ai_reply_private 22 | self.bot_nickname: str = config.bot_nickname 23 | self.poke__reply: Tuple = ( 24 | "lsp你再戳?", 25 | "连个可爱美少女都要戳的肥宅真恶心啊。", 26 | "你再戳!", 27 | "?再戳试试?", 28 | "别戳了别戳了再戳就坏了555", 29 | "我爪巴爪巴,球球别再戳了", 30 | "你戳你🐎呢?!", 31 | f"请不要戳{self.bot_nickname} >_<", 32 | "放手啦,不给戳QAQ", 33 | f"喂(#`O′) 戳{self.bot_nickname}干嘛!", 34 | "戳坏了,赔钱!", 35 | "戳坏了", 36 | "嗯……不可以……啦……不要乱戳", 37 | "那...那里...那里不能戳...绝对...", 38 | "(。´・ω・)ん?", 39 | "有事恁叫我,别天天一个劲戳戳戳!", 40 | "欸很烦欸!你戳🔨呢", 41 | "再戳一下试试?", 42 | "正在关闭对您的所有服务...关闭成功", 43 | "啊呜,太舒服刚刚竟然睡着了。什么事?", 44 | "正在定位您的真实地址...定位成功。轰炸机已起飞", 45 | ) 46 | self.hello_reply: Tuple = ( 47 | "你好!", 48 | "哦豁?!", 49 | "你好!Ov<", 50 | f"库库库,呼唤{config.bot_nickname}做什么呢", 51 | "我在呢!", 52 | "呼呼,叫俺干嘛", 53 | ) 54 | self.nonsense: Tuple = ( 55 | "你好啊", 56 | "你好", 57 | "在吗", 58 | "在不在", 59 | "您好", 60 | "您好啊", 61 | "你好", 62 | "在", 63 | ) 64 | self.superuser = config.superusers 65 | self.module_path: Path = Path(__file__).parent 66 | self.keyword_path: Path = self.module_path / "resource/json/data.json" 67 | self.anime_thesaurus: Dict = json.load( 68 | open(self.keyword_path, "r", encoding="utf-8") 69 | ) 70 | self.audio_path: Path = self.module_path / "resource/audio" 71 | self.audio_list: List[str] = os.listdir(self.audio_path) 72 | self.proxy = config.bing_or_openai_proxy 73 | # ==================================== bing工具属性 ==================================================== 74 | # 会话字典,用于存储会话 {"user_id": {"chatbot": bot, "last_time": time, "model": "balanced", isRunning: bool}} 75 | self.bing_chat_dict: Dict = {} 76 | bing_cookies_files: List[Path] = [ 77 | file 78 | for file in config.smart_reply_path.rglob("*.json") 79 | if file.stem.startswith("cookie") 80 | ] 81 | try: 82 | self.bing_cookies: List = [ 83 | json.load(open(file, "r", encoding="utf-8")) 84 | for file in bing_cookies_files 85 | ] 86 | logger.success(f"bing_cookies读取, 初始化成功, 共{len(self.bing_cookies)}个cookies") 87 | except Exception as e: 88 | logger.error(f"读取bing cookies失败 error信息: {repr(e)}") 89 | self.bing_cookies: List = [] 90 | # ==================================== openai工具属性 ==================================================== 91 | # 会话字典,用于存储会话 {"user_id": {"chatbot": bot, "last_time": time, "sessions_number": 0}} 92 | self.openai_chat_dict: dict = {} 93 | self.openai_api_key: List = config.openai_api_key # type: ignore 94 | self.openai_max_tokens: int = config.openai_max_tokens 95 | self.max_sessions_number: int = config.openai_max_conversation 96 | 97 | if self.proxy: 98 | logger.info(f"已设置代理, 值为:{self.proxy}") 99 | else: 100 | logger.warning("未检测到代理,国内用户可能无法使用bing或openai功能") 101 | 102 | # ================================================================================================ 103 | async def newbing_new_chat(self, event: MessageEvent, matcher: Matcher) -> None: 104 | """重置会话""" 105 | current_time: int = event.time 106 | user_id: str = str(event.user_id) 107 | if user_id in self.bing_chat_dict: 108 | last_time: int = self.bing_chat_dict[user_id]["last_time"] 109 | if (current_time - last_time < config.newbing_cd_time) and ( 110 | event.get_user_id() not in config.superusers 111 | ): # 如果当前时间减去上一次时间小于CD时间, 直接返回 # type: ignore 112 | await matcher.finish( 113 | MessageSegment.reply(event.message_id) 114 | + MessageSegment.text( 115 | f"非报错情况下每个会话需要{config.newbing_cd_time}秒才能新建哦, 当前还需要{config.newbing_cd_time - (current_time - last_time)}秒" 116 | ) 117 | ) 118 | bot: bingChatbot = await bingChatbot.create( 119 | cookies=random.choice(self.bing_cookies), proxy=self.proxy 120 | ) # 随机选择一个cookies创建一个Chatbot 121 | self.bing_chat_dict[user_id] = { 122 | "chatbot": bot, 123 | "last_time": current_time, 124 | "model": config.newbing_style, 125 | "sessions_number": 0, 126 | "isRunning": False, 127 | } 128 | 129 | @staticmethod 130 | async def bing_string_handle(input_string: str) -> str: 131 | """处理一下bing返回的字符串""" 132 | return re.sub(r'\[\^(\d+)\^]', r'[\1]', input_string) 133 | 134 | # ================================================================================================ 135 | 136 | # ================================================================================================ 137 | async def openai_new_chat(self, event: MessageEvent, matcher: Matcher) -> None: 138 | """重置会话""" 139 | current_time: int = event.time # 获取当前时间 140 | user_id: str = str(event.user_id) 141 | if user_id in self.openai_chat_dict: 142 | last_time: int = self.openai_chat_dict[user_id]["last_time"] 143 | if (current_time - last_time < config.openai_cd_time) and ( 144 | event.get_user_id() not in config.superusers 145 | ): # 如果当前时间减去上一次时间小于CD时间, 直接返回 # type: ignore 146 | await matcher.finish( 147 | MessageSegment.reply(event.message_id) 148 | + MessageSegment.text( 149 | f"非报错情况下每个会话需要{config.openai_cd_time}秒才能新建哦, 当前还需要{config.openai_cd_time - (current_time - last_time)}秒" 150 | ) 151 | ) 152 | bot = openaiChatbot( 153 | api_key=random.choice(self.openai_api_key), 154 | max_tokens=self.openai_max_tokens, 155 | proxy=self.proxy, 156 | ) # 随机选择一个api_key创建一个Chatbot 157 | self.openai_chat_dict[user_id] = { 158 | "chatbot": bot, 159 | "last_time": current_time, 160 | "sessions_number": 0, 161 | "isRunning": False, 162 | } 163 | 164 | # ================================================================================================ 165 | 166 | # ================================================================================================ 167 | async def rand_hello(self) -> str: 168 | """随机问候语""" 169 | return random.choice(self.hello_reply) 170 | 171 | async def rand_poke(self) -> str: 172 | """随机戳一戳""" 173 | return random.choice(self.poke__reply) 174 | 175 | async def get_chat_result(self, text: str, nickname: str) -> Union[str, None]: 176 | """从字典中返回结果""" 177 | if len(text) < 7: 178 | keys = self.anime_thesaurus.keys() 179 | for key in keys: 180 | if key in text: 181 | return random.choice(self.anime_thesaurus[key]).replace( 182 | "你", nickname 183 | ) 184 | 185 | async def add_word(self, word1: str, word2: str) -> Union[str, None]: 186 | """添加词条""" 187 | lis = [] 188 | for key in self.anime_thesaurus: 189 | if key == word1: 190 | lis = self.anime_thesaurus[key] 191 | for word in lis: 192 | if word == word2: 193 | return "寄" 194 | if lis == []: 195 | axis: Dict[str, List[str]] = {word1: [word2]} 196 | else: 197 | lis.append(word2) 198 | axis = {word1: lis} 199 | self.anime_thesaurus.update(axis) 200 | with open(self.keyword_path, "w", encoding="utf-8") as f: 201 | json.dump(self.anime_thesaurus, f, ensure_ascii=False, indent=4) 202 | 203 | async def check_word(self, target: str) -> str: 204 | """查询关键词下词条""" 205 | for item in self.anime_thesaurus: 206 | if target == item: 207 | mes: str = f"下面是关键词 {target} 的全部响应\n\n" 208 | # 获取关键词 209 | lis = self.anime_thesaurus[item] 210 | for n, word in enumerate(lis, start=1): 211 | mes = mes + str(n) + "、" + word + "\n" 212 | return mes 213 | return "寄" 214 | 215 | async def check_all(self) -> str: 216 | """查询全部关键词""" 217 | mes = "下面是全部关键词\n\n" 218 | for c in self.anime_thesaurus: 219 | mes: str = mes + c + "\n" 220 | return mes 221 | 222 | async def del_word(self, word1: str, word2: int) -> Union[str, None]: 223 | """删除关键词下具体回答""" 224 | axis = {} 225 | for key in self.anime_thesaurus: 226 | if key == word1: 227 | lis: list = self.anime_thesaurus[key] 228 | word2 = int(word2) - 1 229 | try: 230 | lis.pop(word2) 231 | axis = {word1: lis} 232 | except Exception: 233 | return "寄" 234 | if axis == {}: 235 | return "寄" 236 | self.anime_thesaurus.update(axis) 237 | with open(self.keyword_path, "w", encoding="utf8") as f: 238 | json.dump(self.anime_thesaurus, f, ensure_ascii=False, indent=4) 239 | 240 | # ================================================================================================ 241 | 242 | @staticmethod 243 | async def text_to_img(text: str) -> bytes: 244 | """将文字转换为图片""" 245 | return await txt_to_img.txt_to_img(text) 246 | 247 | 248 | # 创建一个工具实例 249 | utils = Utils() 250 | -------------------------------------------------------------------------------- /src/plugins/bt/README.md: -------------------------------------------------------------------------------- 1 | # nonebot2磁力搜索插件 2 | 3 | ## 整活, 不会真的有人找bot要磁力链接吧 4 | 5 | 功能: 磁力搜索, 通过机器人帮你寻找 电影 或者 学习资料 6 | 7 | 使用的磁力站: https://clm9.me 8 | 9 | 安装方式: 10 | 11 | pip install nonebot_plugin_BitTorrent 12 | nb plugin install nonebot_plugin_BitTorrent 13 | Download Zip 14 | git clone https://github.com/Special-Week/nonebot_plugin_BitTorrent.git 15 | 16 | env配置项: 17 | 18 | magnet_max_num 返回多少条结果, 类型int, 默认3, 最大12 例: magnet_max_num = 3 19 | 20 | 21 | 22 | 指令: 23 | 24 | 磁力搜索 xxx | bt xxx (xxx为关键词) 25 | 例如: 磁力搜索 真夏の夜の淫夢 26 | bt 真夏の夜の淫夢 27 | 注: 响应器是由on_command生成的, 需要带上env中配置的COMMAND_START前缀(默认["/"], 可设置空字符串[""]) 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/plugins/bt/__init__.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | from nonebot import on_command 4 | 5 | from .utils import bittorrent 6 | 7 | # 声明一个响应器, 优先级10, 向下阻断 8 | on_command("磁力搜索", aliases={"bt"}, priority=10, block=True, handlers=[bittorrent.main]) 9 | 10 | 11 | with contextlib.suppress(Exception): 12 | from nonebot.plugin import PluginMetadata 13 | 14 | __plugin_meta__ = PluginMetadata( 15 | name="bittorrent", 16 | description="磁力搜索插件", 17 | usage="磁力搜索 xxx", 18 | type="application", 19 | homepage="https://github.com/Special-Week/nonebot_plugin_BitTorrent", 20 | supported_adapters={"~onebot.v11"}, 21 | extra={ 22 | "author": "Special-Week", 23 | "version": "0.0.12", 24 | "priority": 10, 25 | }, 26 | ) 27 | -------------------------------------------------------------------------------- /src/plugins/bt/utils.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | from typing import Any, Coroutine, List 4 | 5 | import nonebot 6 | from bs4 import BeautifulSoup, NavigableString, ResultSet, Tag 7 | from httpx import AsyncClient, Response 8 | from loguru import logger 9 | from nonebot.adapters.onebot.v11 import ( 10 | Bot, 11 | GroupMessageEvent, 12 | Message, 13 | MessageEvent, 14 | PrivateMessageEvent, 15 | ) 16 | from nonebot.matcher import Matcher 17 | from nonebot.params import CommandArg 18 | 19 | 20 | class BitTorrent: 21 | def __init__(self) -> None: 22 | """初始化一些变量, 用env拿到magnet_max_num参数""" 23 | try: 24 | self.max_num: int = nonebot.get_driver().config.magnet_max_num 25 | except Exception: 26 | self.max_num: int = 3 27 | 28 | self.magnet_url = "https://cili.site" 29 | 30 | async def main( 31 | self, 32 | bot: Bot, 33 | matcher: Matcher, 34 | event: MessageEvent, 35 | msg: Message = CommandArg(), 36 | ) -> None: 37 | """主函数, 用于响应命令""" 38 | keyword: str = msg.extract_plain_text() 39 | if not keyword: 40 | await matcher.finish("虚空搜索?来点车牌gkd") 41 | try: 42 | data: List[str] = await self.get_items(keyword) 43 | except Exception as e: 44 | await matcher.finish("搜索失败, 下面是错误信息:\n" + repr(e)) 45 | # 如果搜索到了结果, 则尝试发送, 有些账号好像文本太长cqhttp会显示风控 46 | if not data: 47 | await matcher.finish("没有找到结果捏, 换个关键词试试吧") 48 | if isinstance(event, PrivateMessageEvent): 49 | await matcher.finish("\n".join(data)) 50 | if isinstance(event, GroupMessageEvent): 51 | messages: list = [ 52 | { 53 | "type": "node", 54 | "data": { 55 | "name": "bot", 56 | "uin": bot.self_id, 57 | "content": i, 58 | }, 59 | } 60 | for i in data 61 | ] 62 | await bot.call_api( 63 | "send_group_forward_msg", group_id=event.group_id, messages=messages 64 | ) 65 | 66 | async def get_items(self, keyword) -> List[str]: 67 | search_url: str = f"{self.magnet_url}/search?q={keyword}" 68 | async with AsyncClient() as client: 69 | try: 70 | resp: Response = await client.get(search_url) 71 | except Exception as e: 72 | print(repr(e)) 73 | return [f"获取{search_url}失败, 错误信息:{repr(e)}"] 74 | soup = BeautifulSoup(resp.text, "lxml") 75 | tr: ResultSet[Any] = soup.find_all("tr") 76 | if not tr: 77 | return [] 78 | a_list: list[Any] = [i.find_all("a") for i in tr] 79 | href_list: list[str] = [self.magnet_url + i[0].get("href") for i in a_list if i] 80 | maxnum: int = min(len(href_list), self.max_num) 81 | tasks: List[Coroutine] = [self.get_magnet(i) for i in href_list[:maxnum]] 82 | return await asyncio.gather(*tasks) 83 | 84 | async def get_magnet(self, search_url: str) -> str: 85 | try: 86 | async with AsyncClient() as client: 87 | resp: Response = await client.get(search_url) 88 | soup = BeautifulSoup(resp.text, "lxml") 89 | dl: Tag | NavigableString | None = soup.find( 90 | "dl", class_="dl-horizontal torrent-info col-sm-9" 91 | ) 92 | h2: Tag | NavigableString | None = soup.find("h2", class_="magnet-title") 93 | if isinstance(dl, Tag) and isinstance(h2, Tag): 94 | dt: ResultSet[Any] = dl.find_all("dt") 95 | dd: ResultSet[Any] = dl.find_all("dd") 96 | target: str = ( 97 | f"标题 :: {h2.text}\n磁力链接 :: magnet:?xt=urn:btih:{dd[0].text}\n" 98 | ) 99 | for i in range(1, min(len(dt), len(dd))): 100 | dt_temp: str = (dt[i].text).split("\n")[0] 101 | dd_temp: str = (dd[i].text).split("\n")[0] 102 | if not dd_temp: 103 | with contextlib.suppress(Exception): 104 | dd_temp = (dd[i].text).split("\n")[1] 105 | target += f"{dt_temp}: {dd_temp}\n" 106 | logger.info(f"{target}\n====================================") 107 | return target 108 | return f"获取{search_url}失败" 109 | except Exception as e: 110 | return f"获取{search_url}失败, 错误信息:{repr(e)}" 111 | 112 | 113 | # 实例化 114 | bittorrent = BitTorrent() 115 | -------------------------------------------------------------------------------- /src/plugins/coser/README.MD: -------------------------------------------------------------------------------- 1 | # 正则匹配 "^(cos|COS|coser|括丝)$" 获取一张三次元色图 -------------------------------------------------------------------------------- /src/plugins/coser/__init__.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | 3 | from nonebot import on_regex 4 | from nonebot.adapters.onebot.v11.message import MessageSegment 5 | 6 | coser = on_regex("^(cos|COS|coser|括丝)$", priority=5, block=True) 7 | url = ["https://imgapi.cn/cos.php"] 8 | 9 | @coser.handle() 10 | async def _(): 11 | try: 12 | await coser.send(MessageSegment.image(choice(url))) 13 | except Exception as e: 14 | await coser.send(f"出错了! 出错信息: {repr(e)}") 15 | -------------------------------------------------------------------------------- /src/plugins/homo_mathematician/README.MD: -------------------------------------------------------------------------------- 1 | # homo数学家 2 | 3 | 任何实数都用连续的114514通过加减乘除达成, 任给一组数据都能找出其内在规律(函数表达式) 4 | ![](show3.png) 5 | 6 | # 安装 7 | 8 | nb plugin install nonebot_plugin_homo_mathematician 9 | 10 | 11 | 12 | # 命令头: 13 | 14 | 命令头: {lag, 找规律} / {homonumber, 臭数字} eg: 找规律 1 2 3 4 5 6 7 114514 1919810 / homonumber 2749903559 15 | 16 | 17 | # 效果 18 | ![](show1.jpg) 19 | # 验算 20 | ![](show2.jpg) 21 | 22 | -------------------------------------------------------------------------------- /src/plugins/homo_mathematician/__init__.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | from nonebot import on_command 4 | 5 | from .handle import homo_number, lagrange_interpolation 6 | 7 | with contextlib.suppress(Exception): 8 | from nonebot.plugin import PluginMetadata 9 | 10 | __plugin_meta__ = PluginMetadata( 11 | name="homo_mathematician", 12 | description="任何实数都用连续的114514通过加减乘除达成, 任给一组数据都能找出其内在规律(函数表达式)", 13 | usage=r"命令头: {lag, 找规律} / {homonumber, 臭数字} eg: 找规律 1 2 3 4 5 6 7 114514 1919810 / homonumber 2749903559", 14 | type="application", 15 | homepage="https://github.com/Special-Week/Hinata-Bot/tree/main/src/plugins/homo_mathematician", 16 | supported_adapters=None, 17 | extra={ 18 | "author": "Special-Week", 19 | "version": "0.0.7", 20 | "priority": 10, 21 | }, 22 | ) 23 | 24 | 25 | on_command( 26 | cmd="homonumber", 27 | block=True, 28 | priority=10, 29 | aliases={"臭数字"}, 30 | handlers=[homo_number.main], 31 | ) 32 | 33 | on_command( 34 | cmd="lag", 35 | block=True, 36 | priority=10, 37 | aliases={"找规律"}, 38 | handlers=[lagrange_interpolation.main], 39 | ) 40 | -------------------------------------------------------------------------------- /src/plugins/homo_mathematician/handle.py: -------------------------------------------------------------------------------- 1 | import fractions 2 | import math 3 | import re 4 | from typing import Any, List, Union 5 | 6 | from nonebot.adapters import Message 7 | from nonebot.matcher import Matcher 8 | from nonebot.params import CommandArg 9 | from scipy.interpolate import lagrange # type: ignore 10 | 11 | from .utils import Nums 12 | 13 | 14 | class HomoNumber: 15 | # 参考并移植自 https://github.com/itorr/homo 16 | # 参考并移植自 https://github.com/HiDolen/nonebot_plugin_homonumber 17 | async def main(self, matcher: Matcher, arg: Message = CommandArg()) -> None: 18 | number: str = arg.extract_plain_text() 19 | message: str = self.demolish(number) 20 | if not message: 21 | message = "需要一个数字,这事数字吗(恼)" 22 | await matcher.send(message) 23 | 24 | def demolish(self, num_str: str) -> str: 25 | if not re.sub("[-.]", "", num_str).isdigit(): # 如果输入的不是数字 26 | return "" 27 | num: Union[float, int] = float(num_str) if "." in num_str else int(num_str) 28 | if not math.isfinite(num): # 若输入的不是 无穷 或 不是数字 29 | return f"这么臭的{num}有必要论证吗" 30 | 31 | if num < 0: 32 | return f"(11-4-5+1-4)*({self.demolish(str(num * -1))})" 33 | if not isinstance(num, int): # 如果不是整数 34 | temp = str(num) 35 | start_on: int = temp.find(".") + 1 36 | length: int = len(temp[start_on:]) 37 | _next: str = self.demolish(str(int(num * (10**length)))) 38 | return f"({_next})/({self.demolish(str(10**length))})" 39 | 40 | if num in Nums: 41 | return Nums[num] 42 | div: int = next( 43 | (one for one in Nums if num >= one), 1 44 | ) # 获取刚好比 num 大的那个数 45 | first_number: str = self.demolish(str(int(num / div))) 46 | second_number: str = self.demolish(str(int(num % div))) 47 | return f"({Nums[div]})*({first_number})+({second_number})" 48 | 49 | 50 | class LagrangeInterpolation: 51 | async def main(self, matcher: Matcher, arg: Message = CommandArg()) -> None: 52 | msg: str = arg.extract_plain_text().strip() 53 | if not msg: 54 | return 55 | msgs: List[str] = msg.split(" ") 56 | items: List[str] = [item.strip() for item in msgs if item.strip()] 57 | if len(items) < 2 or not self.check_if_number(items): 58 | return 59 | y: List[Union[float, int]] = self.convert_to_number(items) # y轴坐标 60 | x = list(range(1, len(y) + 1)) # x轴坐标 61 | coeffs: list = self.lagrange_fraction(x, y) 62 | func: str = "" 63 | count: int = len(coeffs) 64 | for i in coeffs: 65 | count -= 1 66 | if str(i) == "0": 67 | continue 68 | if count == 0: 69 | func += f"({str(i)})" if int(i) < 0 else str(i) 70 | elif count == 1: 71 | func += "x+" if str(i) == "1" else f"({str(i)})x+" 72 | else: 73 | func += f"x^{count}+" if str(i) == "1" else f"({str(i)})x^{count}+" 74 | func = func[:-1] if func[-1] == "+" else func 75 | await matcher.send(f"f(x) = {func}") 76 | 77 | @staticmethod 78 | def lagrange_fraction(x, y) -> list: 79 | """拉格朗日插值法,返回分数列表""" 80 | p: Any = lagrange(x, y) 81 | c: Any = p.c 82 | d: Any = p.order 83 | return [fractions.Fraction(c[i]).limit_denominator() for i in range(d + 1)] 84 | 85 | @staticmethod 86 | def check_if_number(strings: List[str]) -> bool: 87 | """用于判断列表的str是否为数字, 匹配正负整数和正负小数""" 88 | pattern = r"^[-+]?\d*\.?\d+$" 89 | flag = True 90 | # 但凡出现一个不匹配的就返回False 91 | return next( 92 | (False for string in strings if not re.match(pattern, string)), flag 93 | ) 94 | 95 | @staticmethod 96 | def convert_to_number(strings: List[str]) -> List[Union[int, float]]: 97 | """将字符串列表转换为数字列表""" 98 | numbers: list = [] 99 | for string in strings: 100 | if string.isdigit(): # 正整数直接插入 101 | numbers.append(int(string)) 102 | elif string.startswith("-") and string[1:].isdigit(): # 判断是否为负整数 103 | numbers.append(int(string)) 104 | else: # 只剩下小数的可能了 105 | numbers.append(float(string)) 106 | return numbers 107 | 108 | 109 | homo_number = HomoNumber() 110 | lagrange_interpolation = LagrangeInterpolation() 111 | -------------------------------------------------------------------------------- /src/plugins/homo_mathematician/show1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/homo_mathematician/show1.jpg -------------------------------------------------------------------------------- /src/plugins/homo_mathematician/show2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/homo_mathematician/show2.jpg -------------------------------------------------------------------------------- /src/plugins/homo_mathematician/show3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/homo_mathematician/show3.png -------------------------------------------------------------------------------- /src/plugins/homo_mathematician/utils.py: -------------------------------------------------------------------------------- 1 | Nums = { 2 | 114514: "114514", 3 | 58596: "114*514", 4 | 49654: "11*4514", 5 | 45804: "11451*4", 6 | 23256: "114*51*4", 7 | 22616: "11*4*514", 8 | 19844: "11*451*4", 9 | 16030: "1145*14", 10 | 14515: "1+14514", 11 | 14514: "1*14514", 12 | 14513: "-1+14514", 13 | 11455: "11451+4", 14 | 11447: "11451-4", 15 | 9028: "(1+1)*4514", 16 | 8976: "11*4*51*4", 17 | 7980: "114*5*14", 18 | 7710: "(1+14)*514", 19 | 7197: "1+14*514", 20 | 7196: "1*14*514", 21 | 7195: "-1+14*514", 22 | 6930: "11*45*14", 23 | 6682: "(1-14)*-514", 24 | 6270: "114*(51+4)", 25 | 5818: "114*51+4", 26 | 5810: "114*51-4", 27 | 5808: "(1+1451)*4", 28 | 5805: "1+1451*4", 29 | 5804: "1*1451*4", 30 | 5803: "-1+1451*4", 31 | 5800: "(1-1451)*-4", 32 | 5725: "1145*(1+4)", 33 | 5698: "11*(4+514)", 34 | 5610: "-11*(4-514)", 35 | 5358: "114*(51-4)", 36 | 5005: "11*(451+4)", 37 | 4965: "11*451+4", 38 | 4957: "11*451-4", 39 | 4917: "11*(451-4)", 40 | 4584: "(1145+1)*4", 41 | 4580: "1145*1*4", 42 | 4576: "(1145-1)*4", 43 | 4525: "11+4514", 44 | 4516: "1+1+4514", 45 | 4515: "1+1*4514", 46 | 4514: "1-1+4514", 47 | 4513: "-1*1+4514", 48 | 4512: "-1-1+4514", 49 | 4503: "-11+4514", 50 | 4112: "(1+1)*4*514", 51 | 3608: "(1+1)*451*4", 52 | 3598: "(11-4)*514", 53 | 3435: "-1145*(1-4)", 54 | 3080: "11*4*5*14", 55 | 3060: "(11+4)*51*4", 56 | 2857: "1+14*51*4", 57 | 2856: "1*14*51*4", 58 | 2855: "-1+14*51*4", 59 | 2850: "114*5*(1+4)", 60 | 2736: "114*(5+1)*4", 61 | 2652: "(1-14)*51*-4", 62 | 2570: "1*(1+4)*514", 63 | 2475: "11*45*(1+4)", 64 | 2420: "11*4*(51+4)", 65 | 2280: "114*5*1*4", 66 | 2248: "11*4*51+4", 67 | 2240: "11*4*51-4", 68 | 2166: "114*(5+14)", 69 | 2068: "11*4*(51-4)", 70 | 2067: "11+4*514", 71 | 2058: "1+1+4*514", 72 | 2057: "1/1+4*514", 73 | 2056: "1/1*4*514", 74 | 2055: "-1/1+4*514", 75 | 2054: "-1-1+4*514", 76 | 2045: "-11+4*514", 77 | 2044: "(1+145)*14", 78 | 2031: "1+145*14", 79 | 2030: "1*145*14", 80 | 2029: "-1+145*14", 81 | 2024: "11*(45+1)*4", 82 | 2016: "-(1-145)*14", 83 | 1980: "11*45*1*4", 84 | 1936: "11*(45-1)*4", 85 | 1848: "(11+451)*4", 86 | 1824: "114*(5-1)*4", 87 | 1815: "11+451*4", 88 | 1808: "1*(1+451)*4", 89 | 1806: "1+1+451*4", 90 | 1805: "1+1*451*4", 91 | 1804: "1-1+451*4", 92 | 1803: "1*-1+451*4", 93 | 1802: "-1-1+451*4", 94 | 1800: "1*-(1-451)*4", 95 | 1793: "-11+451*4", 96 | 1760: "-(11-451)*4", 97 | 1710: "114*-5*(1-4)", 98 | 1666: "(114+5)*14", 99 | 1632: "(1+1)*4*51*4", 100 | 1542: "1*-(1-4)*514", 101 | 1526: "(114-5)*14", 102 | 1485: "11*-45*(1-4)", 103 | 1456: "1+1451+4", 104 | 1455: "1*1451+4", 105 | 1454: "-1+1451+4", 106 | 1448: "1+1451-4", 107 | 1447: "1*1451-4", 108 | 1446: "-1+1451-4", 109 | 1428: "(11-4)*51*4", 110 | 1386: "11*(4+5)*14", 111 | 1260: "(1+1)*45*14", 112 | 1159: "1145+14", 113 | 1150: "1145+1+4", 114 | 1149: "1145+1*4", 115 | 1148: "1145-1+4", 116 | 1142: "1145+1-4", 117 | 1141: "1145-1*4", 118 | 1140: "(1145-1)-4", 119 | 1131: "1145-14", 120 | 1100: "11*4*5*(1+4)", 121 | 1056: "11*4*(5+1)*4", 122 | 1050: "(11+4)*5*14", 123 | 1036: "(1+1)*(4+514)", 124 | 1026: "114*-(5-14)", 125 | 1020: "1*(1+4)*51*4", 126 | 981: "1+14*5*14", 127 | 980: "1*14*5*14", 128 | 979: "-1+14*5*14", 129 | 910: "-(1-14)*5*14", 130 | 906: "(1+1)*451+4", 131 | 898: "(1+1)*451-4", 132 | 894: "(1+1)*(451-4)", 133 | 880: "11*4*5*1*4", 134 | 836: "11*4*(5+14)", 135 | 827: "11+4*51*4", 136 | 825: "(11+4)*(51+4)", 137 | 818: "1+1+4*51*4", 138 | 817: "1*1+4*51*4", 139 | 816: "1*1*4*51*4", 140 | 815: "-1+1*4*51*4", 141 | 814: "-1-1+4*51*4", 142 | 805: "-11+4*51*4", 143 | 784: "(11+45)*14", 144 | 771: "1+14*(51+4)", 145 | 770: "1*14*(51+4)", 146 | 769: "(11+4)*51+4", 147 | 761: "(1+14)*51-4", 148 | 730: "(1+145)*(1+4)", 149 | 726: "1+145*(1+4)", 150 | 725: "1*145*(1+4)", 151 | 724: "-1-145*-(1+4)", 152 | 720: "(1-145)*-(1+4)", 153 | 719: "1+14*51+4", 154 | 718: "1*14*51+4", 155 | 717: "-1-14*-51+4", 156 | 715: "(1-14)*-(51+4)", 157 | 711: "1+14*51-4", 158 | 710: "1*14*51-4", 159 | 709: "-1+14*51-4", 160 | 705: "(1+14)*(51-4)", 161 | 704: "11*4*(5-1)*4", 162 | 688: "114*(5+1)+4", 163 | 680: "114*(5+1)-4", 164 | 667: "-(1-14)*51+4", 165 | 660: "(114+51)*4", 166 | 659: "1+14*(51-4)", 167 | 658: "1*14*(51-4)", 168 | 657: "-1+14*(51-4)", 169 | 649: "11*(45+14)", 170 | 644: "1*(1+45)*14", 171 | 641: "11+45*14", 172 | 632: "1+1+45*14", 173 | 631: "1*1+45*14", 174 | 630: "1*1*45*14", 175 | 629: "1*-1+45*14", 176 | 628: "114+514", 177 | 619: "-11+45*14", 178 | 616: "1*-(1-45)*14", 179 | 612: "-1*(1-4)*51*4", 180 | 611: "(1-14)*-(51-4)", 181 | 609: "11*(4+51)+4", 182 | 601: "11*(4+51)-4", 183 | 595: "(114+5)*(1+4)", 184 | 584: "114*5+14", 185 | 581: "1+145*1*4", 186 | 580: "1*145/1*4", 187 | 579: "-1+145*1*4", 188 | 576: "1*(145-1)*4", 189 | 575: "114*5+1+4", 190 | 574: "114*5/1+4", 191 | 573: "114*5-1+4", 192 | 567: "114*5+1-4", 193 | 566: "114*5*1-4", 194 | 565: "114*5-1-4", 195 | 561: "11/4*51*4", 196 | 560: "(1+1)*4*5*14", 197 | 558: "11*4+514", 198 | 556: "114*5-14", 199 | 545: "(114-5)*(1+4)", 200 | 529: "1+14+514", 201 | 528: "1*14+514", 202 | 527: "-1+14+514", 203 | 522: "(1+1)*4+514", 204 | 521: "11-4+514", 205 | 520: "1+1+4+514", 206 | 519: "1+1*4+514", 207 | 518: "1-1+4+514", 208 | 517: "-1+1*4+514", 209 | 516: "-1-1+4+514", 210 | 514: "(1-1)/4+514", 211 | 513: "-11*(4-51)-4", 212 | 512: "1+1-4+514", 213 | 511: "1*1-4+514", 214 | 510: "1-1-4+514", 215 | 509: "11*45+14", 216 | 508: "-1-1-4+514", 217 | 507: "-11+4+514", 218 | 506: "-(1+1)*4+514", 219 | 502: "11*(45+1)-4", 220 | 501: "1-14+514", 221 | 500: "11*45+1+4", 222 | 499: "11*45*1+4", 223 | 498: "11*45-1+4", 224 | 495: "11*(4+5)*(1+4)", 225 | 492: "11*45+1-4", 226 | 491: "11*45-1*4", 227 | 490: "11*45-1-4", 228 | 488: "11*(45-1)+4", 229 | 481: "11*45-14", 230 | 480: "11*(45-1)-4", 231 | 476: "(114+5)/1*4", 232 | 470: "-11*4+514", 233 | 466: "11+451+4", 234 | 460: "114*(5-1)+4", 235 | 458: "11+451-4", 236 | 457: "1+1+451+4", 237 | 456: "1*1+451+4", 238 | 455: "1-1+451+4", 239 | 454: "-1+1*451+4", 240 | 453: "-1-1+451+4", 241 | 452: "114*(5-1)-4", 242 | 450: "(1+1)*45*(1+4)", 243 | 449: "1+1+451-4", 244 | 448: "1+1*451-4", 245 | 447: "1/1*451-4", 246 | 446: "1*-1+451-4", 247 | 445: "-1-1+451-4", 248 | 444: "-11+451+4", 249 | 440: "(1+1)*4*(51+4)", 250 | 438: "(1+145)*-(1-4)", 251 | 436: "-11+451-4", 252 | 435: "-1*145*(1-4)", 253 | 434: "-1-145*(1-4)", 254 | 432: "(1-145)*(1-4)", 255 | 412: "(1+1)*4*51+4", 256 | 404: "(1+1)*4*51-4", 257 | 400: "-114+514", 258 | 396: "11*4*-(5-14)", 259 | 385: "(11-4)*(51+4)", 260 | 376: "(1+1)*4*(51-4)", 261 | 375: "(1+14)*5*(1+4)", 262 | 368: "(1+1)*(45+1)*4", 263 | 363: "(1+1451)/4", 264 | 361: "(11-4)*51+4", 265 | 360: "(1+1)*45*1*4", 266 | 357: "(114+5)*-(1-4)", 267 | 353: "(11-4)*51-4", 268 | 352: "(1+1)*(45-1)*4", 269 | 351: "1+14*-5*-(1+4)", 270 | 350: "1*(1+4)*5*14", 271 | 349: "-1+14*5*(1+4)", 272 | 341: "11*(45-14)", 273 | 337: "1-14*-(5+1)*4", 274 | 336: "1*14*(5+1)*4", 275 | 335: "-1+14*(5+1)*4", 276 | 329: "(11-4)*(51-4)", 277 | 327: "-(114-5)*(1-4)", 278 | 325: "-(1-14)*5*(1+4)", 279 | 318: "114+51*4", 280 | 312: "(1-14)*-(5+1)*4", 281 | 300: "(11+4)*5/1*4", 282 | 297: "-11*(4+5)*(1-4)", 283 | 291: "11+4*5*14", 284 | 286: "(1145-1)/4", 285 | 285: "(11+4)*(5+14)", 286 | 282: "1+1+4*5*14", 287 | 281: "1+14*5/1*4", 288 | 280: "1-1+4*5*14", 289 | 279: "1*-1+4*5*14", 290 | 278: "-1-1+4*5*14", 291 | 275: "1*(1+4)*(51+4)", 292 | 270: "(1+1)*45*-(1-4)", 293 | 269: "-11+4*5*14", 294 | 268: "11*4*(5+1)+4", 295 | 267: "1+14*(5+14)", 296 | 266: "1*14*(5+14)", 297 | 265: "-1+14*(5+14)", 298 | 260: "1*(14+51)*4", 299 | 259: "1*(1+4)*51+4", 300 | 257: "(1+1)/4*514", 301 | 252: "(114-51)*4", 302 | 251: "1*-(1+4)*-51-4", 303 | 248: "11*4+51*4", 304 | 247: "-(1-14)*(5+14)", 305 | 240: "(11+4)*(5-1)*4", 306 | 236: "11+45*(1+4)", 307 | 235: "1*(1+4)*(51-4)", 308 | 234: "11*4*5+14", 309 | 231: "11+4*(51+4)", 310 | 230: "1*(1+45)*(1+4)", 311 | 229: "1145/(1+4)", 312 | 227: "1+1+45*(1+4)", 313 | 226: "1*1+45*(1+4)", 314 | 225: "11*4*5+1+4", 315 | 224: "11*4*5/1+4", 316 | 223: "11*4*5-1+4", 317 | 222: "1+1+4*(51+4)", 318 | 221: "1/1+4*(51+4)", 319 | 220: "1*1*(4+51)*4", 320 | 219: "1+14+51*4", 321 | 218: "1*14+51*4", 322 | 217: "11*4*5+1-4", 323 | 216: "11*4*5-1*4", 324 | 215: "11*4*5-1-4", 325 | 214: "-11+45*(1+4)", 326 | 212: "(1+1)*4+51*4", 327 | 211: "11-4+51*4", 328 | 210: "1+1+4+51*4", 329 | 209: "1+1*4*51+4", 330 | 208: "1*1*4+51*4", 331 | 207: "-1+1*4*51+4", 332 | 206: "11*4*5-14", 333 | 204: "(1-1)/4+51*4", 334 | 202: "1+1-4+51*4", 335 | 201: "1/1-4+51*4", 336 | 200: "1/1*4*51-4", 337 | 199: "1*-1+4*51-4", 338 | 198: "-1-1+4*51-4", 339 | 197: "-11+4+51*4", 340 | 196: "-(1+1)*4+51*4", 341 | 195: "(1-14)*5*(1-4)", 342 | 192: "(1+1)*4*(5+1)*4", 343 | 191: "1-14+51*4", 344 | 190: "1*-14+51*4", 345 | 189: "-11-4+51*4", 346 | 188: "1-1-(4-51)*4", 347 | 187: "1/-1+4*(51-4)", 348 | 186: "1+1+(45+1)*4", 349 | 185: "1-1*-(45+1)*4", 350 | 184: "114+5*14", 351 | 183: "-1+1*(45+1)*4", 352 | 182: "1+1+45/1*4", 353 | 181: "1+1*45*1*4", 354 | 180: "1*1*45*1*4", 355 | 179: "-1/1+45*1*4", 356 | 178: "-1-1+45*1*4", 357 | 177: "1+1*(45-1)*4", 358 | 176: "1*1*(45-1)*4", 359 | 175: "-1+1*(45-1)*4", 360 | 174: "-1-1+(45-1)*4", 361 | 172: "11*4*(5-1)-4", 362 | 171: "114*(5+1)/4", 363 | 170: "(11-45)*-(1+4)", 364 | 169: "114+51+4", 365 | 168: "(11+45)*-(1-4)", 366 | 165: "11*-45/(1-4)", 367 | 161: "114+51-4", 368 | 160: "1+145+14", 369 | 159: "1*145+14", 370 | 158: "-1+145+14", 371 | 157: "1*(1-4)*-51+4", 372 | 154: "11*(4-5)*-14", 373 | 152: "(1+1)*4*(5+14)", 374 | 151: "1+145+1+4", 375 | 150: "1+145*1+4", 376 | 149: "1*145*1+4", 377 | 148: "1*145-1+4", 378 | 147: "-1+145-1+4", 379 | 146: "11+45*-(1-4)", 380 | 143: "1+145+1-4", 381 | 142: "1+145*1-4", 382 | 141: "1+145-1-4", 383 | 140: "1*145-1-4", 384 | 139: "-1+145-1-4", 385 | 138: "-1*(1+45)*(1-4)", 386 | 137: "1+1-45*(1-4)", 387 | 136: "1*1-45*(1-4)", 388 | 135: "-1/1*45*(1-4)", 389 | 134: "114+5/1*4", 390 | 133: "114+5+14", 391 | 132: "1+145-14", 392 | 131: "1*145-14", 393 | 130: "-1+145-14", 394 | 129: "114+5*-(1-4)", 395 | 128: "1+1+(4+5)*14", 396 | 127: "1-14*(5-14)", 397 | 126: "1*(14-5)*14", 398 | 125: "-1-14*(5-14)", 399 | 124: "114+5+1+4", 400 | 123: "114-5+14", 401 | 122: "114+5-1+4", 402 | 121: "11*(45-1)/4", 403 | 120: "-(1+1)*4*5*(1-4)", 404 | 118: "(1+1)*(45+14)", 405 | 117: "(1-14)*(5-14)", 406 | 116: "114+5+1-4", 407 | 115: "114+5*1-4", 408 | 114: "11*4+5*14", 409 | 113: "114-5/1+4", 410 | 112: "114-5-1+4", 411 | 111: "11+4*5*(1+4)", 412 | 110: "-(11-451)/4", 413 | 107: "11-4*-(5+1)*4", 414 | 106: "114-5+1-4", 415 | 105: "114+5-14", 416 | 104: "114-5-1-4", 417 | 103: "11*(4+5)+1*4", 418 | 102: "11*(4+5)-1+4", 419 | 101: "1+1*4*5*(1+4)", 420 | 100: "1*(1+4)*5*1*4", 421 | 99: "11*4+51+4", 422 | 98: "1+1+4*(5+1)*4", 423 | 97: "1+1*4*(5+1)*4", 424 | 96: "11*(4+5)+1-4", 425 | 95: "114-5-14", 426 | 94: "114-5/1*4", 427 | 93: "(1+1)*45-1+4", 428 | 92: "(1+1)*(45-1)+4", 429 | 91: "11*4+51-4", 430 | 90: "-114+51*4", 431 | 89: "(1+14)*5+14", 432 | 88: "1*14*(5+1)+4", 433 | 87: "11+4*(5+14)", 434 | 86: "(1+1)*45*1-4", 435 | 85: "1+14+5*14", 436 | 84: "1*14+5*14", 437 | 83: "-1+14+5*14", 438 | 82: "1+1+4*5/1*4", 439 | 81: "1/1+4*5*1*4", 440 | 80: "1-1+4*5*1*4", 441 | 79: "1*-1+4*5/1*4", 442 | 78: "(1+1)*4+5*14", 443 | 77: "11-4+5*14", 444 | 76: "1+1+4+5*14", 445 | 75: "1+14*5*1+4", 446 | 74: "1/1*4+5*14", 447 | 73: "1*14*5-1+4", 448 | 72: "-1-1+4+5*14", 449 | 71: "(1+14)*5-1*4", 450 | 70: "11+45+14", 451 | 69: "1*14+51+4", 452 | 68: "1+1-4+5*14", 453 | 67: "1-1*4+5*14", 454 | 66: "1*14*5-1*4", 455 | 65: "1*14*5-1-4", 456 | 64: "11*4+5*1*4", 457 | 63: "11*4+5+14", 458 | 62: "1+14+51-4", 459 | 61: "1+1+45+14", 460 | 60: "11+45*1+4", 461 | 59: "114-51-4", 462 | 58: "-1+1*45+14", 463 | 57: "1+14*5-14", 464 | 56: "1*14*5-14", 465 | 55: "-1+14*5-14", 466 | 54: "11-4+51-4", 467 | 53: "11+45+1-4", 468 | 52: "11+45/1-4", 469 | 51: "11+45-1-4", 470 | 50: "1+1*45/1+4", 471 | 49: "1*1*45/1+4", 472 | 48: "-11+45+14", 473 | 47: "1/-1+45-1+4", 474 | 46: "11*4+5+1-4", 475 | 45: "11+4*5+14", 476 | 44: "114-5*14", 477 | 43: "1+1*45+1-4", 478 | 42: "11+45-14", 479 | 41: "1/1*45*1-4", 480 | 40: "-11+4*51/4", 481 | 39: "-11+45+1+4", 482 | 38: "-11+45*1+4", 483 | 37: "-11+45-1+4", 484 | 36: "11+4*5+1+4", 485 | 35: "11*4+5-14", 486 | 34: "1-14+51-4", 487 | 33: "1+1+45-14", 488 | 32: "1*1+45-14", 489 | 31: "1/1*45-14", 490 | 30: "1*-1+45-14", 491 | 29: "-11+45-1-4", 492 | 28: "11+4*5+1-4", 493 | 27: "11+4*5/1-4", 494 | 26: "11-4+5+14", 495 | 25: "11*4-5-14", 496 | 24: "1+14-5+14", 497 | 23: "1*14-5+14", 498 | 22: "1*14+5-1+4", 499 | 21: "-1-1+4+5+14", 500 | 20: "-11+45-14", 501 | 19: "1+1+4*5+1-4", 502 | 18: "1+1+4*5*1-4", 503 | 17: "11+4*5-14", 504 | 16: "11-4-5+14", 505 | 15: "1+14-5+1+4", 506 | 14: "11+4-5/1+4", 507 | 13: "1*14-5/1+4", 508 | 12: "-11+4+5+14", 509 | 11: "11*-4+51+4", 510 | 10: "-11/4+51/4", 511 | 9: "11-4+5+1-4", 512 | 8: "11-4+5/1-4", 513 | 7: "11-4+5-1-4", 514 | 6: "1-14+5+14", 515 | 5: "11-4*5+14", 516 | 4: "-11-4+5+14", 517 | 3: "11*-4+51-4", 518 | 2: "-11+4-5+14", 519 | 1: "11/(45-1)*4", 520 | 0: "(1-1)*4514", 521 | } 522 | 523 | 524 | 525 | 526 | -------------------------------------------------------------------------------- /src/plugins/leetspeak/README.MD: -------------------------------------------------------------------------------- 1 | # 字符串转换 2 | 3 | "火星文" 4 | "蚂蚁文" 5 | "翻转文字" 6 | "故障文字" -------------------------------------------------------------------------------- /src/plugins/leetspeak/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | 3 | from .handle import leetspeak 4 | 5 | on_command("hxw", aliases={"火星文"}, priority=5, block=False, handlers=[leetspeak.hxw_text]) 6 | on_command("ant", aliases={"蚂蚁文"}, priority=5, block=False, handlers=[leetspeak.ant_text]) 7 | on_command("flip", aliases={"翻转文字"}, priority=5, block=False, handlers=[leetspeak.flip_text]) 8 | on_command("bug", aliases={"故障文字"}, priority=5, block=False, handlers=[leetspeak.bug_text]) 9 | -------------------------------------------------------------------------------- /src/plugins/leetspeak/bug.py: -------------------------------------------------------------------------------- 1 | bug_code = { 2 | "up": [ 3 | "̀", 4 | "́", 5 | "̂", 6 | "̃", 7 | "̄", 8 | "̅", 9 | "̆", 10 | "̇", 11 | "̈", 12 | "̉", 13 | "̊", 14 | "̋", 15 | "̌", 16 | "̍", 17 | "̎", 18 | "̏", 19 | "̐", 20 | "̑", 21 | "̒", 22 | "̓", 23 | "̔", 24 | "̚", 25 | "̽", 26 | "̾", 27 | "̿", 28 | "̀", 29 | "́", 30 | "͂", 31 | "̓", 32 | "̈́", 33 | "͆", 34 | "͊", 35 | "͋", 36 | "͌", 37 | "͐", 38 | "͑", 39 | "͒", 40 | "͗", 41 | "͛", 42 | ], 43 | "mid": ["̴", "̵", "̶", "̷", "̸", "҈", "҉"], 44 | "down": [ 45 | "̖", 46 | "̗", 47 | "̘", 48 | "̙", 49 | "̜", 50 | "̝", 51 | "̞", 52 | "̟", 53 | "̠", 54 | "̣", 55 | "̤", 56 | "̥", 57 | "̦", 58 | "̩", 59 | "̪", 60 | "̫", 61 | "̬", 62 | "̭", 63 | "̮", 64 | "̯", 65 | "̰", 66 | "̱", 67 | "̲", 68 | "̳", 69 | "ͅ", 70 | "͇", 71 | "͈", 72 | "͉", 73 | "͍", 74 | "͎", 75 | "͓", 76 | "͔", 77 | "͕", 78 | "͖", 79 | "͙", 80 | "͚", 81 | ], 82 | "under": ["̡", "̢", "̧", "̨", "͜", "͢"], 83 | "above": ["̕", "̛", "͝", "͞", "͠", "͡", "҇"], 84 | "other": [ 85 | "͏", 86 | "͘", 87 | "ͣ", 88 | "ͤ", 89 | "ͥ", 90 | "ͦ", 91 | "ͧ", 92 | "ͨ", 93 | "ͩ", 94 | "ͪ", 95 | "ͫ", 96 | "ͬ", 97 | "ͭ", 98 | "ͮ", 99 | "ͯ", 100 | ], 101 | "": ["̹", "̺", "̻", "̼", "͟"], 102 | } 103 | 104 | bug_level = [ 105 | {"mid": 0, "above": 0, "under": 0, "up": 0, "down": 0}, 106 | {"mid": 0, "above": 1, "under": 0, "up": [2, 5], "down": 0}, 107 | {"mid": 0, "above": 1, "under": 0, "up": [4, 12], "down": 0}, 108 | {"mid": 1, "above": 1, "under": 0, "up": [8, 16], "down": 0}, 109 | {"mid": 0, "above": 0, "under": 1, "up": 0, "down": [2, 5]}, 110 | {"mid": 0, "above": 0, "under": 1, "up": 0, "down": [4, 12]}, 111 | {"mid": 1, "above": 0, "under": 1, "up": 0, "down": [8, 16]}, 112 | {"mid": 1, "above": 0, "under": 0, "up": 0, "down": 0}, 113 | {"mid": [1, 2], "above": 0, "under": 0, "up": 0, "down": 0}, 114 | {"mid": 1, "above": 1, "under": 1, "up": 0, "down": 0}, 115 | {"mid": 1, "above": 1, "under": 1, "up": [1, 3], "down": [1, 3]}, 116 | {"mid": 1, "above": 1, "under": 1, "up": [2, 5], "down": [2, 5]}, 117 | {"mid": 1, "above": 1, "under": 1, "up": [2, 8], "down": [2, 8]}, 118 | {"mid": 0, "above": 0, "under": 0, "up": [4, 12], "down": [4, 12]}, 119 | {"mid": 1, "above": 0, "under": 0, "up": [4, 12], "down": [4, 12]}, 120 | {"mid": 0, "above": 0, "under": 0, "up": [4, 16], "down": [4, 16]}, 121 | {"mid": 1, "above": 0, "under": 0, "up": [4, 16], "down": [4, 16]}, 122 | {"mid": 0, "above": 0, "under": 0, "up": [8, 16], "down": [8, 16]}, 123 | {"mid": 1, "above": 0, "under": 0, "up": [8, 16], "down": [8, 16]}, 124 | {"mid": 0, "above": 0, "under": 0, "up": [12, 24], "down": [12, 24]}, 125 | {"mid": 1, "above": 0, "under": 0, "up": [12, 24], "down": [12, 24]}, 126 | ] -------------------------------------------------------------------------------- /src/plugins/leetspeak/flip.py: -------------------------------------------------------------------------------- 1 | flip_table = { 2 | "a": "\u0250", 3 | "b": "q", 4 | "c": "\u0254", 5 | "d": "p", 6 | "e": "\u01DD", 7 | "f": "\u025F", 8 | "g": "\u0183", 9 | "h": "\u0265", 10 | "i": "\u0131", 11 | "j": "\u027E", 12 | "k": "\u029E", 13 | # 'l': '\u0283', 14 | "m": "\u026F", 15 | "n": "u", 16 | "r": "\u0279", 17 | "t": "\u0287", 18 | "v": "\u028C", 19 | "w": "\u028D", 20 | "y": "\u028E", 21 | ".": "\u02D9", 22 | "[": "]", 23 | "(": ")", 24 | "{": "}", 25 | "?": "\u00BF", 26 | "!": "\u00A1", 27 | "'": ",", 28 | "<": ">", 29 | "_": "\u203E", 30 | ";": "\u061B", 31 | "\u203F": "\u2040", 32 | "\u2045": "\u2046", 33 | "\u2234": "\u2235", 34 | "\r": "\n", 35 | } 36 | 37 | 38 | flip_table_new = flip_table.copy() 39 | for i in flip_table: 40 | flip_table_new[flip_table[i]] = i 41 | flip_table = flip_table_new -------------------------------------------------------------------------------- /src/plugins/leetspeak/handle.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | 4 | from nonebot.adapters.onebot.v11 import Message 5 | from nonebot.matcher import Matcher 6 | from nonebot.params import CommandArg 7 | 8 | from .bug import bug_code, bug_level 9 | from .flip import flip_table 10 | from .hxw import encharhxw, enchars, ftw, hxw, jtw 11 | 12 | 13 | class LeetSpeak: 14 | def __init__(self) -> None: 15 | ... 16 | 17 | async def hxw_text( 18 | self, 19 | matcher: Matcher, 20 | arg: Message = CommandArg() 21 | ): 22 | msg = arg.extract_plain_text().strip() 23 | if msg == "" or msg.isspace(): 24 | return 25 | await matcher.send(await self.get_hxw_text(msg)) 26 | 27 | async def ant_text( 28 | self, 29 | matcher: Matcher, 30 | arg: Message = CommandArg() 31 | ): 32 | msg = arg.extract_plain_text().strip() 33 | if msg == "" or msg.isspace(): 34 | return 35 | await matcher.send(await self.get_ant_text(msg)) 36 | 37 | async def flip_text( 38 | self, 39 | matcher: Matcher, 40 | arg: Message = CommandArg() 41 | ): 42 | msg = arg.extract_plain_text().strip() 43 | if msg == "" or msg.isspace(): 44 | return 45 | await matcher.send(await self.get_flip_text(msg)) 46 | 47 | 48 | async def bug_text( 49 | self, 50 | matcher: Matcher, 51 | arg: Message = CommandArg() 52 | ): 53 | msg = arg.extract_plain_text().strip() 54 | if msg == "" or msg.isspace(): 55 | return 56 | await matcher.send(await self.get_bug_text(msg)) 57 | 58 | 59 | async def get_hxw_text(self, text: str) -> str: 60 | result = "" 61 | for s in text: 62 | c = s 63 | if s in enchars: 64 | c = encharhxw[enchars.index(s)] 65 | elif s in jtw: 66 | c = hxw[jtw.index(s)] 67 | elif s in ftw: 68 | c = hxw[ftw.index(s)] 69 | result += c 70 | return result 71 | 72 | 73 | async def get_ant_text(self, text: str): 74 | return "".join(s + chr(1161) for s in text) 75 | 76 | 77 | 78 | async def get_flip_text(self, text: str): 79 | text = text.lower() 80 | return "".join(flip_table[s] if s in flip_table else s for s in text[::-1]) 81 | 82 | 83 | async def get_bug_text(self, text: str): 84 | def bug(p, n): 85 | result = "" 86 | if isinstance(n, list): 87 | n = math.floor(random.random() * (n[1] - n[0] + 1)) + n[0] 88 | for _ in range(n): 89 | result += bug_code[p][int(random.random() * len(bug_code[p]))] 90 | return result 91 | 92 | level = 12 93 | u = bug_level[level] 94 | result = "" 95 | for s in text: 96 | result += s 97 | if s != " ": 98 | result += ( 99 | bug("mid", u["mid"]) 100 | + bug("above", u["above"]) 101 | + bug("under", u["under"]) 102 | + bug("up", u["up"]) 103 | + bug("down", u["down"]) 104 | ) 105 | return result 106 | 107 | 108 | 109 | 110 | 111 | 112 | leetspeak = LeetSpeak() -------------------------------------------------------------------------------- /src/plugins/leetspeak/hxw.py: -------------------------------------------------------------------------------- 1 | jtw = "啊阿埃挨哎唉哀皑癌蔼矮艾碍爱隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翱袄傲奥懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙坝霸罢爸白柏百摆佰败拜稗斑班搬扳般颁板版扮拌伴瓣半办绊邦帮梆榜膀绑棒磅蚌镑傍谤苞胞包褒剥薄雹保堡饱宝抱报暴豹鲍爆杯碑悲卑北辈背贝钡倍狈备惫焙被奔苯本笨崩绷甭泵蹦迸逼鼻比鄙笔彼碧蓖蔽毕毙毖币庇痹闭敝弊必辟壁臂避陛鞭边编贬扁便变卞辨辩辫遍标彪膘表鳖憋别瘪彬斌濒滨宾摈兵冰柄丙秉饼炳病并玻菠播拨钵波博勃搏铂箔伯帛舶脖膊渤泊驳捕卜哺补埠不布步簿部怖擦猜裁材才财睬踩采彩菜蔡餐参蚕残惭惨灿苍舱仓沧藏操糙槽曹草厕策侧册测层蹭插叉茬茶查碴搽察岔差诧拆柴豺搀掺蝉馋谗缠铲产阐颤昌猖场尝常长偿肠厂敞畅唱倡超抄钞朝嘲潮巢吵炒车扯撤掣彻澈郴臣辰尘晨忱沉陈趁衬撑称城橙成呈乘程惩澄诚承逞骋秤吃痴持匙池迟弛驰耻齿侈尺赤翅斥炽充冲虫崇宠抽酬畴踌稠愁筹仇绸瞅丑臭初出橱厨躇锄雏滁除楚础储矗搐触处揣川穿椽传船喘串疮窗幢床闯创吹炊捶锤垂春椿醇唇淳纯蠢戳绰疵茨磁雌辞慈瓷词此刺赐次聪葱囱匆从丛凑粗醋簇促蹿篡窜摧崔催脆瘁粹淬翠村存寸磋撮搓措挫错搭达答瘩打大呆歹傣戴带殆代贷袋待逮怠耽担丹单郸掸胆旦氮但惮淡诞弹蛋当挡党荡档刀捣蹈倒岛祷导到稻悼道盗德得的蹬灯登等瞪凳邓堤低滴迪敌笛狄涤翟嫡抵底地蒂第帝弟递缔颠掂滇碘点典靛垫电佃甸店惦奠淀殿碉叼雕凋刁掉吊钓调跌爹碟蝶迭谍叠丁盯叮钉顶鼎锭定订丢东冬董懂动栋侗恫冻洞兜抖斗陡豆逗痘都督毒犊独读堵睹赌杜镀肚度渡妒端短锻段断缎堆兑队对墩吨蹲敦顿囤钝盾遁掇哆多夺垛躲朵跺舵剁惰堕蛾峨鹅俄额讹娥恶厄扼遏鄂饿恩而儿耳尔饵洱二贰发罚筏伐乏阀法珐藩帆番翻樊矾钒繁凡烦反返范贩犯饭泛坊芳方肪房防妨仿访纺放菲非啡飞肥匪诽吠肺废沸费芬酚吩氛分纷坟焚汾粉奋份忿愤粪丰封枫蜂峰锋风疯烽逢冯缝讽奉凤佛否夫敷肤孵扶拂辐幅氟符伏俘服浮涪福袱弗甫抚辅俯釜斧脯腑府腐赴副覆赋复傅付阜父腹负富讣附妇缚咐噶嘎该改概钙盖溉干甘杆柑竿肝赶感秆敢赣冈刚钢缸肛纲岗港杠篙皋高膏羔糕搞镐稿告哥歌搁戈鸽胳疙割革葛格蛤阁隔铬个各给根跟耕更庚羹埂耿梗工攻功恭龚供躬公宫弓巩汞拱贡共钩勾沟苟狗垢构购够辜菇咕箍估沽孤姑鼓古蛊骨谷股故顾固雇刮瓜剐寡挂褂乖拐怪棺关官冠观管馆罐惯灌贯光广逛瑰规圭硅归龟闺轨鬼诡癸桂柜跪贵刽辊滚棍锅郭国果裹过哈骸孩海氦亥害骇酣憨邯韩含涵寒函喊罕翰撼捍旱憾悍焊汗汉夯杭航壕嚎豪毫郝好号浩呵喝荷菏核禾和何合盒貉阂河涸赫褐鹤贺嘿黑痕狠很恨哼亨横衡恒轰哄烘虹鸿洪宏弘红喉侯猴吼厚候后呼乎忽瑚壶葫胡蝴狐糊湖弧虎唬护互沪户花哗华猾滑画划化话槐徊怀淮坏欢环桓还缓换患唤痪豢焕涣宦幻荒慌黄磺蝗簧皇凰惶煌晃幌恍谎灰挥辉徽恢蛔回毁悔慧卉惠晦贿秽会烩汇讳诲绘荤昏婚魂浑混豁活伙火获或惑霍货祸击圾基机畸稽积箕肌饥迹激讥鸡姬绩缉吉极棘辑籍集及急疾汲即嫉级挤几脊己蓟技冀季伎祭剂悸济寄寂计记既忌际妓继纪嘉枷夹佳家加荚颊贾甲钾假稼价架驾嫁歼监坚尖笺间煎兼肩艰奸缄茧检柬碱拣捡简俭剪减荐槛鉴践贱见键箭件健舰剑饯渐溅涧建僵姜将浆江疆蒋桨奖讲匠酱降蕉椒礁焦胶交郊浇骄娇嚼搅铰矫侥脚狡角饺缴绞剿教酵轿较叫窖揭接皆秸街阶截劫节桔杰捷睫竭洁结解姐戒藉芥界借介疥诫届巾筋斤金今津襟紧锦仅谨进靳晋禁近烬浸尽劲荆兢茎睛晶鲸京惊精粳经井警景颈静境敬镜径痉靖竟竞净炯窘揪究纠玖韭久灸九酒厩救旧臼舅咎就疚鞠拘狙疽居驹菊局咀矩举沮聚拒据巨具距踞锯俱句惧炬剧捐鹃娟倦眷卷绢撅攫抉掘倔爵觉决诀绝均菌钧军君峻俊竣浚郡骏喀咖卡咯开揩楷凯慨刊堪勘坎砍看康慷糠扛抗亢炕考拷烤靠坷苛柯棵磕颗科壳咳可渴克刻客课肯啃垦恳坑吭空恐孔控抠口扣寇枯哭窟苦酷库裤夸垮挎跨胯块筷侩快宽款匡筐狂框矿眶旷况亏盔岿窥葵奎魁傀馈愧溃坤昆捆困括扩廓阔垃拉喇蜡腊辣啦莱来赖蓝婪栏拦篮阑兰澜谰揽览懒缆烂滥琅榔狼廊郎朗浪捞劳牢老佬姥酪烙涝勒乐雷镭蕾磊累儡垒擂肋类泪棱楞冷厘梨犁黎篱狸离漓理李里鲤礼莉荔吏栗丽厉励砾历利僳例俐痢立粒沥隶力璃哩俩联莲连镰廉怜涟帘敛脸链恋炼练粮凉梁粱良两辆量晾亮谅撩聊僚疗燎寥辽潦了撂镣廖料列裂烈劣猎琳林磷霖临邻鳞淋凛赁吝拎玲菱零龄铃伶羚凌灵陵岭领另令溜琉榴硫馏留刘瘤流柳六龙聋咙笼窿隆垄拢陇楼娄搂篓漏陋芦卢颅庐炉掳卤虏鲁麓碌露路赂鹿潞禄录陆戮驴吕铝侣旅履屡缕虑氯律率滤绿峦挛孪滦卵乱掠略抡轮伦仑沦纶论萝螺罗逻锣箩骡裸落洛骆络妈麻玛码蚂马骂嘛吗埋买麦卖迈脉瞒馒蛮满蔓曼慢漫谩芒茫盲氓忙莽猫茅锚毛矛铆卯茂冒帽貌贸么玫枚梅酶霉煤没眉媒镁每美昧寐妹媚门闷们萌蒙檬盟锰猛梦孟眯醚靡糜迷谜弥米秘觅泌蜜密幂棉眠绵冕免勉娩缅面苗描瞄藐秒渺庙妙蔑灭民抿皿敏悯闽明螟鸣铭名命谬摸摹蘑模膜磨摩魔抹末莫墨默沫漠寞陌谋牟某拇牡亩姆母墓暮幕募慕木目睦牧穆拿哪呐钠那娜纳氖乃奶耐奈南男难囊挠脑恼闹淖呢馁内嫩能妮霓倪泥尼拟你匿腻逆溺蔫拈年碾撵捻念娘酿鸟尿捏聂孽啮镊镍涅您柠狞凝宁拧泞牛扭钮纽脓浓农弄奴努怒女暖虐疟挪懦糯诺哦欧鸥殴藕呕偶沤啪趴爬帕怕琶拍排牌徘湃派攀潘盘磐盼畔判叛乓庞旁耪胖抛咆刨炮袍跑泡呸胚培裴赔陪配佩沛喷盆砰抨烹澎彭蓬棚硼篷膨朋鹏捧碰坯砒霹批披劈琵毗啤脾疲皮匹痞僻屁譬篇偏片骗飘漂瓢票撇瞥拼频贫品聘乒坪苹萍平凭瓶评屏坡泼颇婆破魄迫粕剖扑铺仆莆葡菩蒲埔朴圃普浦谱曝瀑期欺栖戚妻七凄漆柒沏其棋奇歧畦崎脐齐旗祈祁骑起岂乞企启契砌器气迄弃汽泣讫掐洽牵扦钎铅千迁签仟谦乾黔钱钳前潜遣浅谴堑嵌欠歉枪呛腔羌墙蔷强抢橇锹敲悄桥瞧乔侨巧鞘撬翘峭俏窍切茄且怯窃钦侵亲秦琴勤芹擒禽寝沁青轻氢倾卿清擎晴氰情顷请庆琼穷秋丘邱球求囚酋泅趋区蛆曲躯屈驱渠取娶龋趣去圈颧权醛泉全痊拳犬券劝缺炔瘸却鹊榷确雀裙群然燃冉染瓤壤攘嚷让饶扰绕惹热壬仁人忍韧任认刃妊纫扔仍日戎茸蓉荣融熔溶容绒冗揉柔肉茹蠕儒孺如辱乳汝入褥软阮蕊瑞锐闰润若弱撒洒萨腮鳃塞赛三叁伞散桑嗓丧搔骚扫嫂瑟色涩森僧莎砂杀刹沙纱傻啥煞筛晒珊苫杉山删煽衫闪陕擅赡膳善汕扇缮墒伤商赏晌上尚裳梢捎稍烧芍勺韶少哨邵绍奢赊蛇舌舍赦摄射慑涉社设砷申呻伸身深娠绅神沈审婶甚肾慎渗声生甥牲升绳省盛剩胜圣师失狮施湿诗尸虱十石拾时什食蚀实识史矢使屎驶始式示士世柿事拭誓逝势是嗜噬适仕侍释饰氏市恃室视试收手首守寿授售受瘦兽蔬枢梳殊抒输叔舒淑疏书赎孰熟薯暑曙署蜀黍鼠属术述树束戍竖墅庶数漱恕刷耍摔衰甩帅栓拴霜双爽谁水睡税吮瞬顺舜说硕朔烁斯撕嘶思私司丝死肆寺嗣四伺似饲巳松耸怂颂送宋讼诵搜艘擞嗽苏酥俗素速粟塑溯宿诉肃酸蒜算虽隋随绥髓碎岁穗遂隧祟孙损笋蓑梭唆缩琐索锁所塌他它她塔獭挞蹋踏胎苔抬台泰酞太态汰坍摊贪瘫滩坛檀痰潭谭谈坦毯袒碳探叹炭汤塘搪堂棠膛唐糖倘躺淌趟烫掏涛滔绦萄桃逃淘陶讨套特藤腾疼誊梯剔踢锑提题蹄啼体替嚏惕涕剃屉天添填田甜恬舔腆挑条迢眺跳贴铁帖厅听烃汀廷停亭庭挺艇通桐酮瞳同铜彤童桶捅筒统痛偷投头透凸秃突图徒途涂屠土吐兔湍团推颓腿蜕褪退吞屯臀拖托脱鸵陀驮驼椭妥拓唾挖哇蛙洼娃瓦袜歪外豌弯湾玩顽丸烷完碗挽晚皖惋宛婉万腕汪王亡枉网往旺望忘妄威巍微危韦违桅围唯惟为潍维苇萎委伟伪尾纬未蔚味畏胃喂魏位渭谓尉慰卫瘟温蚊文闻纹吻稳紊问嗡翁瓮挝蜗涡窝我斡卧握沃巫呜钨乌污诬屋无芜梧吾吴毋武五捂午舞伍侮坞戊雾晤物勿务悟误昔熙析西硒矽晰嘻吸锡牺稀息希悉膝夕惜熄烯溪汐犀檄袭席习媳喜铣洗系隙戏细瞎虾匣霞辖暇峡侠狭下厦夏吓掀锨先仙鲜纤咸贤衔舷闲涎弦嫌显险现献县腺馅羡宪陷限线相厢镶香箱襄湘乡翔祥详想响享项巷橡像向象萧硝霄削哮嚣销消宵淆晓小孝校肖啸笑效楔些歇蝎鞋协挟携邪斜胁谐写械卸蟹懈泄泻谢屑薪芯锌欣辛新忻心信衅星腥猩惺兴刑型形邢行醒幸杏性姓兄凶胸匈汹雄熊休修羞朽嗅锈秀袖绣墟戌需虚嘘须徐许蓄酗叙旭序畜恤絮婿绪续轩喧宣悬旋玄选癣眩绚靴薛学穴雪血勋熏循旬询寻驯巡殉汛训讯逊迅压押鸦鸭呀丫芽牙蚜崖衙涯雅哑亚讶焉咽阉烟淹盐严研蜒岩延言颜阎炎沿奄掩眼衍演艳堰燕厌砚雁唁彦焰宴谚验殃央鸯秧杨扬佯疡羊洋阳氧仰痒养样漾邀腰妖瑶摇尧遥窑谣姚咬舀药要耀椰噎耶爷野冶也页掖业叶曳腋夜液一壹医揖铱依伊衣颐夷遗移仪胰疑沂宜姨彝椅蚁倚已乙矣以艺抑易邑屹亿役臆逸肄疫亦裔意毅忆义益溢诣议谊译异翼翌绎茵荫因殷音阴姻吟银淫寅饮尹引隐印英樱婴鹰应缨莹萤营荧蝇迎赢盈影颖硬映哟拥佣臃痈庸雍踊蛹咏泳涌永恿勇用幽优悠忧尤由邮铀犹油游酉有友右佑釉诱又幼迂淤于盂榆虞愚舆余俞逾鱼愉渝渔隅予娱雨与屿禹宇语羽玉域芋郁吁遇喻峪御愈欲狱育誉浴寓裕预豫驭鸳渊冤元垣袁原援辕园员圆猿源缘远苑愿怨院曰约越跃钥岳粤月悦阅耘云郧匀陨允运蕴酝晕韵孕匝砸杂栽哉灾宰载再在咱攒暂赞赃脏葬遭糟凿藻枣早澡蚤躁噪造皂灶燥责择则泽贼怎增憎曾赠扎喳渣札轧铡闸眨栅榨咋乍炸诈摘斋宅窄债寨瞻毡詹粘沾盏斩辗崭展蘸栈占战站湛绽樟章彰漳张掌涨杖丈帐账仗胀瘴障招昭找沼赵照罩兆肇召遮折哲蛰辙者锗蔗这浙珍斟真甄砧臻贞针侦枕疹诊震振镇阵蒸挣睁征狰争怔整拯正政帧症郑证芝枝支吱蜘知肢脂汁之织职直植殖执值侄址指止趾只旨纸志挚掷至致置帜峙制智秩稚质炙痔滞治窒中盅忠钟衷终种肿重仲众舟周州洲诌粥轴肘帚咒皱宙昼骤珠株蛛朱猪诸诛逐竹烛煮拄瞩嘱主著柱助蛀贮铸筑住注祝驻抓爪拽专砖转撰赚篆桩庄装妆撞壮状椎锥追赘坠缀谆准捉拙卓桌琢茁酌啄着灼浊兹咨资姿滋淄孜紫仔籽滓子自渍字鬃棕踪宗综总纵邹走奏揍租足卒族祖诅阻组钻纂嘴醉最罪尊遵昨左佐柞做作坐座婵ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789" 2 | hxw = "錒阿埃挨砹唉哀皚癌藹矮艾碍噯隘鞍氨侒唵按黯堓胺案肮昻盎垇敖熬翱襖傲奧懊澳芭仈扒叭紦笆ハ疤巴拔跋靶夿耙垻灞罷爸苩柏粨擺佰贁拜稗斑班搬扳般頒板版扮拌姅瓣怑办絆邦幫梆榜膀綁棒磅蚌鎊徬謗苞胞苞褒剝薄雹葆堡飽寶菢報儤豹鮑爆柸碑蕜萆苝輩偝貝鋇倍狽俻憊焙被逩苯夲笨崩繃甭泵蹦逬逼濞笓鄙毞彼碧蓖蔽畢斃毖幣庇痹閉敝弊苾辟壁臂避陛鞭笾編貶扁緶変卞辨辯辮遍標滮膘裱鱉憋莂癟彬贇瀕濱賓擯兵栤柄丙秉餅炳寎幷箥菠播撥鉢啵愽葧搏鉑箔伯帛舶脖膊渤泊駁捕ト哺補埠卟咘埗簿蔀怖擦猜裁材ォ財睬踩綵彩菜蔡餐參蚕殘慚慘燦仺艙倉滄藏操糙槽曹愺廁憡側冊測層蹭插扠茬嗏查碴搽嚓岔鎈詫拆柴豺攙摻蟬饞谗纏鏟産闡顫誯猖場嘗鏛苌償腸廠敞暢唱倡趠抄鈔朝嘲謿巢吵炒車扯撤掣徹澈郴宧宸塵曟忱沉蔯趁襯撐稱城橙晟呈乗程惩澄誠承逞騁秤吃癡持匙池咫弛馳恥齒侈尺赤翅斥炽充沖蟲漴寵菗酬疇躊稠矁籌仇綢瞅醜溴初詘櫥廚躇鋤雏滁滁濋礎儲矗搐觸処揣巛穿椽傳船喘賗瘡窻幢床闖創吹炊捶錘垂舂椿醇滣淳蒓蠢戳綽疵茨磁雌辭慈瓷詞茈刺賜佽聰蔥囪匆苁丛湊粗醋簇促躥篡竄摧崔催脆瘁粹淬翠村洊団磋撮搓措挫諎搭達答瘩咑槑歹傣戴帶殆笩貸袋待逮怠耽擔丹單鄲撣膽旦氮但憚淡誕彈蛋噹擋黨蕩檔刀搗蹈倒島禱導菿稻悼檤盜德嘚の蹬燈登等瞪櫈鄧隄低嘀廸敵笛狄涤翟嫡牴厎哋蒂第偙弚递締顛掂滇碘嚸典靛墊電佃甸店惦奠澱殿碉叼雕凋刁鋽吊釣調跌爹碟蝶迭諜疊丁盯叮釘頂鼎錠啶訂銩倲咚董懂動棟侗恫凍洞兜抖乧陡豆逗痘嘟督毐犢獨讀堵睹賭杜鍍肚喥渡妒端短鍛段斷緞堆兌隊対墩吨蹲敦頓囤鈍盾遁掇哆誃奪垛躲朶跥舵刴惰墮蛾峨鵝俄額訛娥噁厄扼遏鄂餓恩侕ル珥爾餌洱ニ貳髮罰筏伐乏閥法琺藩帆番翻樊矾釩繁凡煩仮返範販犯飯泛坊芳方肪房防妨仿訪紡放菲非啡飛肥匪誹吠肺廢沸費芬酚吩氛衯紛墳燓汾蒶奮份忿憤糞丯葑楓蜂峯鋒颩瘋烽逢馮縫諷奉鳳佛俖夫敷肤孵扶拂輻幅氟符伏俘菔浮涪冨袱丳甫撫輔俯釜斧脯腑椨腐赴副覆賦複傅怤阜父腹負冨訃附婦縛咐噶嘎該攺概鈣蓋溉幹咁桿柑竿肝趕感稈澉贛岡剛鋼罁肛綱崗港杠篙臯髙膏羔糕搞鎬稿吿滒歌擱戈鴿胳疙割革葛格蛤閣隔鉻嗰各給根哏耕莄庚羹埂耿梗エ攻功恭龔供躬厷宮弜巩汞拱貢珙鈎勾溝苟狗垢構購夠辜菇咕箍估沽菰姑鼓古蠱嗗谷股故顧固雇刮苽剮寡掛褂乖箉怪棺関悹冠觀菅館罐慣灌貫茪廣逛瑰規圭硅歸龜閨軌廆詭癸溎櫃跪匮劊輥滾棍鍋郭啯淉裹濄哈骸孩海氦亥嗐駭酣憨邯韓浛菡寒函喊罕翰撼捍旱憾悍焊漢漢夯杭航壕嚎譹毫郝ぬ呺澔哬喝荷菏核秝啝何合盒貉閡菏涸赫褐鶴賀嘿潶痕狠佷詪哼亨橫衡恆轟哄烘渱鴻洪浤弘葒喉侯瘊吼厚堠逅泘苸惚瑚壺葫鬍鰗狐糊煳弧箎唬護彑滬戶埖嘩譁猾滑婳劃囮話槐徊懷淮壞歡環桓還緩換患喚瘓豢煥渙宦幻荒慌簧磺蝗簧隍凰惶瑝晃幌恍謊洃揮輝徽恢蛔徊毀誨懳泋蕙晦賄穢哙燴匯諱誨繪葷昬婚魂渾緄豁萿夥吙獲彧惑霍貨禍擊圾簊機畸稽積箕肌飢跡激讥鳮姬績緝吉極棘輯籍雧岌ゑ疾汲即嫉級擠凣脊巳薊技冀悸伎漈劑悸濟寄漃計誋溉誋際妓繼紀嘉枷夾佳傢伽莢頰賈曱鉀徦稼價架駕嫁殲監堅尖箋間煎兼肩艱奸緘茧檢柬碱揀撿簡儉剪減洊檻鑒踐濺笕鍵箭件旔艦劍餞漸濺澗建僵葁將漿茳彊蔣槳獎講匠醬跭蕉椒礁潐賋鲛郊澆驕嬌嚼攪鉸矯僥腳狡捔餃繳絞剿教酵轎較訆窖揭椄喈稭街階截劫兯桔烋捷睫竭潔結繲姊诫藉芥堺徣妎疥誡屆巾筋斤釒訡堻襟緊錦僅謹進靳晉僸近燼浸烬勁荊兢莖聙橸鯨亰驚精粳俓丼警景頸瀞境敬鏡徑痙靖竟競淨炯窘揪究糾玖韭玖灸艽酒廄救舊臼舅咎僦疚鞠拘狙疽居駒菊侷咀矩舉沮聚拒據巨具距踞鋸俱佝懼炬劇捐鵑鹃倦眷眷絹撅攫抉掘倔爵覺決訣絕均菌鈞軍珺峻俊竣浚郡駿喀咖咔咯幵揩楷凱慨刊堪勘坎砍看慷慷糠扛抗伉炕栲拷烤靠坷苛牁棵磕顆萪殼咳岢渴剋刻愙課肯啃墾懇坑吭悾恐孔控摳ロ扣寇喖哭窟苫酷庫褲誇垮挎跨胯塊筷儈筷寬款匡筐誑框礦眶曠況虧盔巋窺葵喹魁傀饋愧潰坤崑捆悃括擴廓闊垃菈喇蜡臘辣菈萊莱籟蘫婪欄攔籃闌蘭瀾讕攬覽懶纜爛濫琅榔誏廊鎯朗蒗撈勞哰佬佬姥酪烙澇勒楽雷鐳蕾磊蔂儡壘擂肋類涙棱楞泠厘藜犁黎籬狸離漓理李裡鯉禮峲荔吏栗郦厲勵礫歷悧僳例俐痢竝粒瀝隸ㄌ璃哩倆聯蓮連鐮濂憐漣簾斂臉鏈纞練練糧涼梁粱峎兩輛糧晾湸諒撩窷僚療燎寥遼潦ㄋ撂鐐廖料列裂煭劣獵諃林磷霖臨鄰鱗淋凜賃悋拎紷菱蕶齡鈴伶羚淩棂陵嶺領另囹溜琉榴硫餾留瀏瘤蓅柳⑥尨聋嚨籠窿隆壟攏隴樓婁摟簍漏陋蘆盧顱廬爐擄鹵虜魯麓碌虂璐賂簏潞祿錄陸戮驴呂鋁侶旅履屢縷慮氯嵂率濾綠巒攣孿滦卵薍掠略掄輪倫侖淪綸論蘿螺羅邏鑼籮騾裸落洛駱絡媽痲瑪碼螞馬罵嘛嗎埋買麥賣邁脈瞞饅蠻懑蔓謾謾漫謾芒鋩吂氓忙莽貓茅錨芼矛鉚卯茂瑁帽貌貿庅玫枚梅酶黣煤莈眉媒鎂每媄昧寐妹媚冂悶扪萌濛檬盟錳猛儚孟眯醚靡糜蒾謎彌冞秘覓泌藌嘧冪嬵眠綿冕浼勉娩緬緬苗描瞄藐秒渺廟妙蔑滅囻抿皿慜憫閩明螟鳴銘佲掵謬摸摹蘑模膜磨嚤魇抹ま嗼嚜默沫漠寞陌謀牟謀拇牡畝姆毋墓暮幕募慕朩目睦牧穆嗱哪呐鈉那娜納氖艿奶耐萘湳侽難囊撓脳悩鬧淖呢餒內嫩褦妮霓倪泥苨擬伱匿膩縌溺蔫拈哖碾攆撚淰娘釀袅尿捏聶孽啮鑷鎳涅您檸獰凝苧擰濘犇扭鈕紐膿濃侬挵奴努怒ㄝ暖虐瘧挪懦糯諾哦歐鷗毆蕅嘔偶漚啪趴爬帕怕琶拍排簰徘湃蒎攀瀋盤磐盻畔判頖乓龐臱耪胖拋咆刨炮袍跑萢呸肧培裵賠婄蓜姵沛噴盆砰抨烹澎嘭蓬棚硼篷膨倗鵬捧碰坯砒霹批披劈琵毗啤簲疲皮匹痞僻屁譬篇偏魸騙飄薸瓢票撇瞥拚頻貧榀聘乒坪蘋萍泙凴頩評幈坡潑頗嘙破魄迫粕剖扑鋪僕莆葡菩蒲埔樸圃鐠浦譜曝瀑剘欺棲鏚萋⑦淒漆柒沏萁棋奇歧畦崎臍齊旂祈祁騎起豈乞峜啓葜砌噐芞迄棄汽泣訖掐洽縴扦釺鉛仟遷簽仟謙乾黔銭鉗偂潛遣淺譴塹嵌芡歉槍嗆腔羌牆薔強搶橇鍬敲悄橋瞧喬僑巧鞘撬翹陗佾竅苆茄且怯窃欽侵瀙溱琴懃芹擒禽寢沁圊輕氫傾卿淸擎晴氰情頃請慶瓊窮萩丘邱銶俅囚酋泅趋岖蛆浀軀屈驅渠冣娶齲趣呿圏顴權醛湶佺痊拳猋券勸蒛炔瘸卻鵲榷確雀裙群嘫燃苒蒅瓤壤禳嚷讓饒擾繞惹熱壬仁亽忍韌恁認刃妊紉扔芿ㄖ戎茸蓉榮融熔溶傛絨冗揉柔肉茹蠕儒孺侞嗕乳汝兦褥軟阮蕊瑞銳閏潤婼鰯撒灑萨腮鰓塞賽③弎傘潵鎟嗓喪搔騷掃嫂瑟铯澀森僧莎砂殺刹唦紗儍啥煞篩曬珊苫杉屾刪煽衫閃陝擅贍膳善汕煽繕墒傷商賞晌丄尙裳梢捎稍燒芍勺韶尐哨邵紹奢賒蛇舌舎赦攝射懾涉社設砷申呻伸裑罙娠紳榊訦审嬸葚腎慎滲聲甡甥牲昇繩偗盛剰勝聖師矢獅施濕詩ㄕ虱⑩鉐拾埘什喰蝕實識史矢使屎駛始鉽呩壵丗柹亊拭誓逝勢媞嗜噬適仕侍釋飾氏铈恃室視試収手首垨壽授售綬瘦嘼蔬樞梳殊抒輸惄舒淑疎お贖孰熟薯暑曙署蜀黍癙属ポ蒁樹涑戍豎墅庻數漱恕刷耍摔縗甩帥栓拴孀雙爽誰氺娷稅吮瞬順舜說碩溯爍斯凘嘶偲俬司絲死肆寺嗣④伺似飼巳倯聳慫頌送宋訟誦搜艘擻嗽囌酥俗傃速粟塑溯宿訴肅酸蒜匴雖隋隨綏髓誶歲穗遂隧祟荪損筍蓑梭唆縮瑣索鎖所塌咃咜祂塔獺撻蹋踏胎苔擡囼泰酞冭態汰坍攤貪癱灘壇檀痰潭譚談鉭毯襢碳探歎炭湯塘搪嘡棠膛傏醣倘躺淌趟燙掏涛滔縧萄洮逃淘陶討套特藤騰疼謄梯剔踢銻提題蹄啼躰鐟嚏惕涕剃屜迗添填畾憇恬舔腆挑條迢眺跳貼鐵帖廳厛烴汀廷諪亭庭挺艇嗵桐酮瞳茼銅浵瞳桶捅筒統痛偸投頭透凸禿突图徒途塗屠汢吐兎湍团推頽腿蛻褪煺呑芚臋拖託脫鴕陀馱駝橢妥拓唾挖哇蛙窪鲑咓襪歪外豌彎灣魭頑芄烷綄碗挽晚皖惋宛婉萬腕汪仼亡枉網往旺望莣妄葳巍嶶危玮违桅圍蓶惟ゐ濰維葦萎諉偉偽尾緯耒蔚菋葨胄餵魏莅渭謂尉慰衛瘟薀蚊呅聞紋吻穩紊問嗡翁瓮撾蝸渦窩涐斡臥渥沃莁嗚鎢烏汚誣箼兂蕪梧忢吳毋倵⑤捂仵儛伍侮塢戊霧晤粅伆務悟誤昔熈析迺硒矽晰嘻吸錫犧稀蒠俙悉膝タ惜熄烯溪汐犀檄襲席習媳囍銑洗係隙戲細瞎蝦匣霞轄暇峽俠狹丅廈嗄嚇掀鍁筅苮鮮纖咸賢銜舷閑涎誸嫌显險現獻縣腺餡羨宪陥限綫葙廂鑲稥葙襄湘鄉翔祥詳想響享項巷橡像姠潒蕭硝霄削哮囂銷銷宵淆哓尐哮校綃嘯笶傚楔些歇蝎鞵協挾携峫斜脇諧冩械卸蠏懈洩瀉謝屑薪蕊鋅俽莘噺忻吢信衅煋鯹忻忻興刑型形邢哘醒啈莕性姓兄兇洶匈洶雄熊咻修羞朽嗅銹琇袖綉墟戌繻噓噓須蒣許蓄酗敘旭垿畜恤絮婿緒續軒喧宣懸漩玄選癬眩絢鞾薛學穴膤洫勛燻循旬詢尋馴巡殉汛訓訊遜迅壓押鴉鴨吖ㄚ芽伢蚜崖衙涯蕥啞娅訝焉咽閹煙淹盐严研蜒岩筵誩顏閻焱沿奄掩眼衍縯滟堰嚥厭硯鴈唁彥焰宴諺驗殃央鴦秧楊揚佯瘍佯烊陽氧仰癢養樣漾邀腰妖瑤搖堯遙窰謠姚咬舀葯崾耀椰噎耶爺嘢冶乜頁掖業葉曳腋夜液①壹醫揖銥铱咿衣頤夷遺移儀胰疑沂宐姨彝椅蚁倚已乙矣苡藝抑易邑屹億伇臆逸肄疫亦裔噫毅憶図益溢詣議誼譯异瀷翌繹茵蔭洇殷喑陰姻荶銀婬寅飲伊蚓隱茚渶櫻嬰鷹應纓瑩螢營荧蠅迎贏盈影穎硬映哟擁傭臃痈庸雍踴蛹詠泳湧詠慂勇甪幽優滺憂鱿甴郵鈾猶油遊酉冇叐祐祐釉誘ㄡ呦迂淤亍盂榆虞愚輿悇俞踰魚愉渝漁隅予娯雨與嶼禹穻語羽钰域芋喐訏遇喻峪禦癒慾獄逳誉浴寓裕預豫馭鴛渊冤え垣媴傆援轅園員圓猿源緣逺苑願惌院曰約樾跃鈅捳粵仴悅閲耘雲鄖匀隕允運蘊酝暈韵孕匝砸雜栽哉災縡載侢恠詯攢暫贊賍脏髒遭糟凿藻枣早澡蚤躁噪慥皂灶燥責萚則澤賊怎增憎噌贈紮喳渣札軋鍘閘眨柵榨咋乍炸詐摘斋宅窄債寨瞻毡詹粘惉盏斬輾嶄展蘸棧占戰站湛綻樟璋彰漳張掌漲杖丈帳賬仗脹瘴障招詔找沼趙照罩兆肇召遮悊哲蟄轍者鍺蔗這浙紾斟眞甄砧臻貞針偵枕疹診震振鎮陣蒸掙睜征猙爭怔整拯㊣政幀症鄭證芝枝支吱蜘倁肢脂汁徔织职直植殖執値侄址指芷趾呮恉紙綕摯擲臸致置帜峙製潪秩稚質炙痔滯治窒ф盅忠鍾衷終種腫喠仲众舟周喌洲诌粥軸肘帚咒皱宙昼驟珠株蛛茱潴諸誅逐竹燭煮拄瞩嘱註著柱助蛀贮铸筑住紸祝駐抓爪拽專磚啭撰賺篆桩莊娤妝撞壯狀椎錐縋贅墜綴諄准捉拙卓桌琢茁酌啄着灼濁茲咨澬姿滋淄孜紫仔籽滓孒洎漬牸鬃棕蹤宗綜總緃邹赱奏揍租哫卒鏃祖詛阻組钻纂嘴酔蕞罪尊遵葃佐佐柞做莋侳蓙嬋AвсDЁ℉GHIJκLMЙOPQRST∪∨W×YZābcdéfɡhījklmńōpqr$τūvωxyz①②③④⑤⑥⑦⑧⑨" 3 | ftw = "啊阿埃挨哎唉哀皚癌藹矮艾礙愛隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翺襖傲奧懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙壩霸罷爸白柏百擺佰敗拜稗斑班搬扳般頒板版扮拌伴瓣半辦絆邦幫梆榜膀綁棒磅蚌鎊傍謗苞胞包褒剝薄雹保堡飽寶抱報暴豹鮑爆杯碑悲卑北輩背貝鋇倍狽備憊焙被奔苯本笨崩繃甭泵蹦迸逼鼻比鄙筆彼碧蓖蔽畢斃毖幣庇痹閉敝弊必辟壁臂避陛鞭邊編貶扁便變卞辨辯辮遍標彪膘表鼈憋別癟彬斌瀕濱賓擯兵冰柄丙秉餅炳病並玻菠播撥缽波博勃搏鉑箔伯帛舶脖膊渤泊駁捕蔔哺補埠不布步簿部怖擦猜裁材才財睬踩采彩菜蔡餐參蠶殘慚慘燦蒼艙倉滄藏操糙槽曹草廁策側冊測層蹭插叉茬茶查碴搽察岔差詫拆柴豺攙摻蟬饞讒纏鏟産闡顫昌猖場嘗常長償腸廠敞暢唱倡超抄鈔朝嘲潮巢吵炒車扯撤掣徹澈郴臣辰塵晨忱沈陳趁襯撐稱城橙成呈乘程懲澄誠承逞騁秤吃癡持匙池遲弛馳恥齒侈尺赤翅斥熾充沖蟲崇寵抽酬疇躊稠愁籌仇綢瞅醜臭初出櫥廚躇鋤雛滁除楚礎儲矗搐觸處揣川穿椽傳船喘串瘡窗幢床闖創吹炊捶錘垂春椿醇唇淳純蠢戳綽疵茨磁雌辭慈瓷詞此刺賜次聰蔥囪匆從叢湊粗醋簇促躥篡竄摧崔催脆瘁粹淬翠村存寸磋撮搓措挫錯搭達答瘩打大呆歹傣戴帶殆代貸袋待逮怠耽擔丹單鄲撣膽旦氮但憚淡誕彈蛋當擋黨蕩檔刀搗蹈倒島禱導到稻悼道盜德得的蹬燈登等瞪凳鄧堤低滴迪敵笛狄滌翟嫡抵底地蒂第帝弟遞締顛掂滇碘點典靛墊電佃甸店惦奠澱殿碉叼雕凋刁掉吊釣調跌爹碟蝶叠諜疊丁盯叮釘頂鼎錠定訂丟東冬董懂動棟侗恫凍洞兜抖鬥陡豆逗痘都督毒犢獨讀堵睹賭杜鍍肚度渡妒端短鍛段斷緞堆兌隊對墩噸蹲敦頓囤鈍盾遁掇哆多奪垛躲朵跺舵剁惰墮蛾峨鵝俄額訛娥惡厄扼遏鄂餓恩而兒耳爾餌洱二貳發罰筏伐乏閥法琺藩帆番翻樊礬釩繁凡煩反返範販犯飯泛坊芳方肪房防妨仿訪紡放菲非啡飛肥匪誹吠肺廢沸費芬酚吩氛分紛墳焚汾粉奮份忿憤糞豐封楓蜂峰鋒風瘋烽逢馮縫諷奉鳳佛否夫敷膚孵扶拂輻幅氟符伏俘服浮涪福袱弗甫撫輔俯釜斧脯腑府腐赴副覆賦複傅付阜父腹負富訃附婦縛咐噶嘎該改概鈣蓋溉幹甘杆柑竿肝趕感稈敢贛岡剛鋼缸肛綱崗港杠篙臯高膏羔糕搞鎬稿告哥歌擱戈鴿胳疙割革葛格蛤閣隔鉻個各給根跟耕更庚羹埂耿梗工攻功恭龔供躬公宮弓鞏汞拱貢共鈎勾溝苟狗垢構購夠辜菇咕箍估沽孤姑鼓古蠱骨谷股故顧固雇刮瓜剮寡挂褂乖拐怪棺關官冠觀管館罐慣灌貫光廣逛瑰規圭矽歸龜閨軌鬼詭癸桂櫃跪貴劊輥滾棍鍋郭國果裹過哈骸孩海氦亥害駭酣憨邯韓含涵寒函喊罕翰撼捍旱憾悍焊汗漢夯杭航壕嚎豪毫郝好號浩呵喝荷菏核禾和何合盒貉閡河涸赫褐鶴賀嘿黑痕很很恨哼亨橫衡恒轟哄烘虹鴻洪宏弘紅喉侯猴吼厚候後呼乎忽瑚壺葫胡蝴狐糊湖弧虎唬護互滬戶花嘩華猾滑畫劃化話槐徊懷淮壞歡環桓還緩換患喚瘓豢煥渙宦幻荒慌黃磺蝗簧皇凰惶煌晃幌恍謊灰揮輝徽恢蛔回毀悔慧卉惠晦賄穢會燴彙諱誨繪葷昏婚魂渾混豁活夥火獲或惑霍貨禍擊圾基機畸稽積箕肌饑迹激譏雞姬績緝吉極棘輯籍集及急疾汲即嫉級擠幾脊己薊技冀季伎祭劑悸濟寄寂計記既忌際妓繼紀嘉枷夾佳家加莢頰賈甲鉀假稼價架駕嫁殲監堅尖箋間煎兼肩艱奸緘繭檢柬鹼揀撿簡儉剪減薦檻鑒踐賤見鍵箭件健艦劍餞漸濺澗建僵姜將漿江疆蔣槳獎講匠醬降蕉椒礁焦膠交郊澆驕嬌嚼攪鉸矯僥腳狡角餃繳絞剿教酵轎較叫窖揭接皆稭街階截劫節桔傑捷睫竭潔結解姐戒藉芥界借介疥誡屆巾筋斤金今津襟緊錦僅謹進靳晉禁近燼浸盡勁荊兢莖睛晶鯨京驚精粳經井警景頸靜境敬鏡徑痙靖竟競淨炯窘揪究糾玖韭久灸九酒廄救舊臼舅咎就疚鞠拘狙疽居駒菊局咀矩舉沮聚拒據巨具距踞鋸俱句懼炬劇捐鵑娟倦眷卷絹撅攫抉掘倔爵覺決訣絕均菌鈞軍君峻俊竣浚郡駿喀咖卡咯開揩楷凱慨刊堪勘坎砍看康慷糠扛抗亢炕考拷烤靠坷苛柯棵磕顆科殼咳可渴克刻客課肯啃墾懇坑吭空恐孔控摳口扣寇枯哭窟苦酷庫褲誇垮挎跨胯塊筷儈快寬款匡筐狂框礦眶曠況虧盔巋窺葵奎魁傀饋愧潰坤昆捆困括擴廓闊垃拉喇蠟臘辣啦萊來賴藍婪欄攔籃闌蘭瀾讕攬覽懶纜爛濫琅榔狼廊郎朗浪撈勞牢老佬姥酪烙澇勒樂雷鐳蕾磊累儡壘擂肋類淚棱楞冷厘梨犁黎籬狸離漓理李裏鯉禮莉荔吏栗麗厲勵礫曆利傈例俐痢立粒瀝隸力璃哩倆聯蓮連鐮廉憐漣簾斂臉鏈戀煉練糧涼梁粱良兩輛量晾亮諒撩聊僚療燎寥遼潦了撂鐐廖料列裂烈劣獵琳林磷霖臨鄰鱗淋凜賃吝拎玲菱零齡鈴伶羚淩靈陵嶺領另令溜琉榴硫餾留劉瘤流柳六龍聾嚨籠窿隆壟攏隴樓婁摟簍漏陋蘆盧顱廬爐擄鹵虜魯麓碌露路賂鹿潞祿錄陸戮驢呂鋁侶旅履屢縷慮氯律率濾綠巒攣孿灤卵亂掠略掄輪倫侖淪綸論蘿螺羅邏鑼籮騾裸落洛駱絡媽麻瑪碼螞馬罵嘛嗎埋買麥賣邁脈瞞饅蠻滿蔓曼慢漫謾芒茫盲氓忙莽貓茅錨毛矛鉚卯茂冒帽貌貿麽玫枚梅酶黴煤沒眉媒鎂每美昧寐妹媚門悶們萌蒙檬盟錳猛夢孟眯醚靡糜迷謎彌米秘覓泌蜜密冪棉眠綿冕免勉娩緬面苗描瞄藐秒渺廟妙蔑滅民抿皿敏憫閩明螟鳴銘名命謬摸摹蘑模膜磨摩魔抹末莫墨默沫漠寞陌謀牟某拇牡畝姆母墓暮幕募慕木目睦牧穆拿哪呐鈉那娜納氖乃奶耐奈南男難囊撓腦惱鬧淖呢餒內嫩能妮霓倪泥尼擬你匿膩逆溺蔫拈年碾攆撚念娘釀鳥尿捏聶孽齧鑷鎳涅您檸獰凝甯擰濘牛扭鈕紐膿濃農弄奴努怒女暖虐瘧挪懦糯諾哦歐鷗毆藕嘔偶漚啪趴爬帕怕琶拍排牌徘湃派攀潘盤磐盼畔判叛乓龐旁耪胖抛咆刨炮袍跑泡呸胚培裴賠陪配佩沛噴盆砰抨烹澎彭蓬棚硼篷膨朋鵬捧碰坯砒霹批披劈琵毗啤脾疲皮匹痞僻屁譬篇偏片騙飄漂瓢票撇瞥拼頻貧品聘乒坪蘋萍平憑瓶評屏坡潑頗婆破魄迫粕剖撲鋪仆莆葡菩蒲埔樸圃普浦譜曝瀑期欺棲戚妻七淒漆柒沏其棋奇歧畦崎臍齊旗祈祁騎起豈乞企啓契砌器氣迄棄汽泣訖掐洽牽扡釺鉛千遷簽仟謙乾黔錢鉗前潛遣淺譴塹嵌欠歉槍嗆腔羌牆薔強搶橇鍬敲悄橋瞧喬僑巧鞘撬翹峭俏竅切茄且怯竊欽侵親秦琴勤芹擒禽寢沁青輕氫傾卿清擎晴氰情頃請慶瓊窮秋丘邱球求囚酋泅趨區蛆曲軀屈驅渠取娶齲趣去圈顴權醛泉全痊拳犬券勸缺炔瘸卻鵲榷確雀裙群然燃冉染瓤壤攘嚷讓饒擾繞惹熱壬仁人忍韌任認刃妊紉扔仍日戎茸蓉榮融熔溶容絨冗揉柔肉茹蠕儒孺如辱乳汝入褥軟阮蕊瑞銳閏潤若弱撒灑薩腮鰓塞賽三三傘散桑嗓喪搔騷掃嫂瑟色澀森僧莎砂殺刹沙紗傻啥煞篩曬珊苫杉山刪煽衫閃陝擅贍膳善汕扇繕墒傷商賞晌上尚裳梢捎稍燒芍勺韶少哨邵紹奢賒蛇舌舍赦攝射懾涉社設砷申呻伸身深娠紳神沈審嬸甚腎慎滲聲生甥牲升繩省盛剩勝聖師失獅施濕詩屍虱十石拾時什食蝕實識史矢使屎駛始式示士世柿事拭誓逝勢是嗜噬適仕侍釋飾氏市恃室視試收手首守壽授售受瘦獸蔬樞梳殊抒輸叔舒淑疏書贖孰熟薯暑曙署蜀黍鼠屬術述樹束戍豎墅庶數漱恕刷耍摔衰甩帥栓拴霜雙爽誰水睡稅吮瞬順舜說碩朔爍斯撕嘶思私司絲死肆寺嗣四伺似飼巳松聳慫頌送宋訟誦搜艘擻嗽蘇酥俗素速僳塑溯宿訴肅酸蒜算雖隋隨綏髓碎歲穗遂隧祟孫損筍蓑梭唆縮瑣索鎖所塌他它她塔獺撻蹋踏胎苔擡台泰酞太態汰坍攤貪癱灘壇檀痰潭譚談坦毯袒碳探歎炭湯塘搪堂棠膛唐糖倘躺淌趟燙掏濤滔縧萄桃逃淘陶討套特藤騰疼謄梯剔踢銻提題蹄啼體替嚏惕涕剃屜天添填田甜恬舔腆挑條迢眺跳貼鐵帖廳聽烴汀廷停亭庭挺艇通桐酮瞳同銅彤童桶捅筒統痛偷投頭透凸禿突圖徒途塗屠土吐兔湍團推頹腿蛻褪退吞屯臀拖托脫鴕陀馱駝橢妥拓唾挖哇蛙窪娃瓦襪歪外豌彎灣玩頑丸烷完碗挽晚皖惋宛婉萬腕汪王亡枉網往旺望忘妄威巍微危韋違桅圍唯惟爲濰維葦萎委偉僞尾緯未蔚味畏胃餵魏位渭謂尉慰衛瘟溫蚊文聞紋吻穩紊問嗡翁甕撾蝸渦窩我斡臥握沃巫嗚鎢烏汙誣屋無蕪梧吾吳毋武五捂午舞伍侮塢戊霧晤物勿務悟誤昔熙析西硒矽晰嘻吸錫犧稀息希悉膝夕惜熄烯溪汐犀檄襲席習媳喜銑洗系隙戲細瞎蝦匣霞轄暇峽俠狹下廈夏嚇掀鍁先仙鮮纖鹹賢銜舷閑涎弦嫌顯險現獻縣腺餡羨憲陷限線相廂鑲香箱襄湘鄉翔祥詳想響享項巷橡像向象蕭硝霄削哮囂銷消宵淆曉小孝校肖嘯笑效楔些歇蠍鞋協挾攜邪斜脅諧寫械卸蟹懈泄瀉謝屑薪芯鋅欣辛新忻心信釁星腥猩惺興刑型形邢行醒幸杏性姓兄凶胸匈洶雄熊休修羞朽嗅鏽秀袖繡墟戌需虛噓須徐許蓄酗敘旭序畜恤絮婿緒續軒喧宣懸旋玄選癬眩絢靴薛學穴雪血勳熏循旬詢尋馴巡殉汛訓訊遜迅壓押鴉鴨呀丫芽牙蚜崖衙涯雅啞亞訝焉咽閹煙淹鹽嚴研蜒岩延言顔閻炎沿奄掩眼衍演豔堰燕厭硯雁唁彥焰宴諺驗殃央鴦秧楊揚佯瘍羊洋陽氧仰癢養樣漾邀腰妖瑤搖堯遙窯謠姚咬舀藥要耀椰噎耶爺野冶也頁掖業葉曳腋夜液一壹醫揖銥依伊衣頤夷遺移儀胰疑沂宜姨彜椅蟻倚已乙矣以藝抑易邑屹億役臆逸肄疫亦裔意毅憶義益溢詣議誼譯異翼翌繹茵蔭因殷音陰姻吟銀淫寅飲尹引隱印英櫻嬰鷹應纓瑩螢營熒蠅迎贏盈影穎硬映喲擁傭臃癰庸雍踴蛹詠泳湧永恿勇用幽優悠憂尤由郵鈾猶油遊酉有友右佑釉誘又幼迂淤于盂榆虞愚輿余俞逾魚愉渝漁隅予娛雨與嶼禹宇語羽玉域芋郁籲遇喻峪禦愈欲獄育譽浴寓裕預豫馭鴛淵冤元垣袁原援轅園員圓猿源緣遠苑願怨院曰約越躍鑰嶽粵月悅閱耘雲鄖勻隕允運蘊醞暈韻孕匝砸雜栽哉災宰載再在咱攢暫贊贓髒葬遭糟鑿藻棗早澡蚤躁噪造皂竈燥責擇則澤賊怎增憎曾贈紮喳渣劄軋鍘閘眨柵榨咋乍炸詐摘齋宅窄債寨瞻氈詹粘沾盞斬輾嶄展蘸棧占戰站湛綻樟章彰漳張掌漲杖丈帳賬仗脹瘴障招昭找沼趙照罩兆肇召遮折哲蟄轍者鍺蔗這浙珍斟真甄砧臻貞針偵枕疹診震振鎮陣蒸掙睜征猙爭怔整拯正政幀症鄭證芝枝支吱蜘知肢脂汁之織職直植殖執值侄址指止趾只旨紙志摯擲至致置幟峙制智秩稚質炙痔滯治窒中盅忠鍾衷終種腫重仲衆舟周州洲謅粥軸肘帚咒皺宙晝驟珠株蛛朱豬諸誅逐竹燭煮拄矚囑主著柱助蛀貯鑄築住注祝駐抓爪拽專磚轉撰賺篆樁莊裝妝撞壯狀椎錐追贅墜綴諄准捉拙卓桌琢茁酌啄著灼濁茲咨資姿滋淄孜紫仔籽滓子自漬字鬃棕蹤宗綜總縱鄒走奏揍租足卒族祖詛阻組鑽纂嘴醉最罪尊遵昨左佐柞做作坐座嬋ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';" 4 | enchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 5 | encharhxw = "AвсDЁ℉GHIJκLMЙOPQRST∪∨W×YZābcdéfɡhījklmńōpqr$τūvωxyz" -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Special-Week 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/README.md: -------------------------------------------------------------------------------- 1 | # youth-version-of-setu4 2 | 3 | 内置数据库的setu插件, 另外尝试降低因为风控发不出图的概率(随机修改左上角一颗像素点) (tx好像改了算法, 作用不明显了) 4 | 5 | 6 | ### 目前数据库去除unavailable, 共81358条记录 7 | 8 | ghs比较纯粹, 只有一般的权限控制, 相比完整版功能简单 9 | 10 | 安装方式: 11 | 12 | pip install youth-version-of-setu4 13 | 14 | 记得nonebot.load_plugin("youth-version-of-setu4") 15 | 16 | 有能力尽量从本仓库clone, 因为pypi不一定最新 17 | 18 | ## env 配置项 19 | 20 | >以下配置项均可不填,插件会按照默认值读取 21 | 22 | |config |type |default|example |usage | 23 | |-------------------|----------------|-------|---------------------------------|----------------------| 24 | |setu_cd |int |20 |setu_cd = 30 |setu的cd | 25 | |setu_withdraw_time |int |100 |setu_withdraw_time = 30 |setu撤回时间 | 26 | |setu_max_num |int |10 |setu_max_num = 20 |setu一次性最大数量 | 27 | |setu_save |str |None |setu_save = './data/setu4/img' |setu时候保存到本地的路径 可用绝对路径| 28 | 29 | setu_save保存后下一次调用碰到这个setu就不需要再下载 30 | 31 | 32 | 一般无需科学上网, 确认一下图片代理是否可用: 33 | 34 | 一些也许可用的pixiv代理, 用来填入env的setu_proxy变量: "i.pixiv.re" , "sex.nyan.xyz" , "px2.rainchan.win" , "pximg.moonchan.xyz" , "piv.deception.world" , "px3.rainchan.win" , "px.s.rainchan.win" , "pixiv.yuki.sh" , "pixiv.kagarise.workers.dev" , "pixiv.kagarise.workers.dev" 35 | 36 | Example: 37 | 38 | 数据库给的url为: https://i.pixiv.re/img-original/img/2022/07/09/18/51/03/99606781_p0.jpg 39 | 40 | 有些代理可能会暂时不可用, 可以用来换成可用的代理, 比如px2.rainchan.win 41 | 42 | 即: https://px2.rainchan.win/img-original/img/2022/07/09/18/51/03/99606781_p0.jpg 43 | 44 | 能正常访问即可用, 详情请看下文superuser指令 45 | 46 | 47 | ​ 48 | 49 | ## 插件指令 50 | 51 | setu命令: 52 | 53 | 命令头: setu|色图|涩图|想色色|来份色色|来份色图|想涩涩|多来点|来点色图|来张setu|来张色图|来点色色|色色|涩涩 (任意一个) 54 | 55 | 张数: 1 2 3 4 ... 张|个|份 (可不填, 默认1) 56 | 57 | r18: 填了就是r18, 不填则不是 (私聊生效, 群聊除非add_r18, 不然视为false) 58 | 59 | 关键词: 任意 (可不填) 60 | 61 | 参考: 62 | 63 | setu 10张 r18 白丝 64 | 65 | setu 10张 白丝 66 | 67 | setu r18 白丝 68 | 69 | setu 白丝 70 | 71 | setu 72 | 73 | (空格可去掉, 多tag用空格分开 eg:setu 白丝 loli) 74 | 75 | 76 | 77 | superuser指令: 78 | 79 | r18名单: 查看r18有哪些群聊或者账号 80 | 81 | add_r18 xxx: 添加r18用户/群聊 82 | 83 | del_r18 xxx: 移除r18用户 84 | 85 | disactivate | 解除禁用 xxx: 恢复该群的setu功能 86 | 87 | ban_setu xxx: 禁用xxx群聊的色图权限 88 | 89 | setu_proxy 更换setu代理(当默pixiv.re不可用时), 先发送setu_proxy, 然后他会给你一个魔方阵, 自己选择用哪个, 也可以用自己的 90 | 91 | 92 | 93 | 群主/管理员指令: 94 | 95 | ban_setu: 禁用当前群聊功能, 解除需要找superuser 96 | 97 | 98 | 99 | 其他指令: 100 | 101 | setu_help 102 | 103 | -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/__init__.py: -------------------------------------------------------------------------------- 1 | from re import I 2 | 3 | from nonebot import on_command, on_regex 4 | from nonebot.adapters.onebot.v11 import GROUP, PRIVATE_FRIEND 5 | from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER 6 | from nonebot.permission import SUPERUSER 7 | 8 | from .handle import setu_handle 9 | 10 | # 主功能响应器 11 | on_regex( 12 | r"^(setu|色图|涩图|想色色|来份色色|来份色图|想涩涩|多来点|来点色图|来张setu|来张色图|来点色色|色色|涩涩)\s?([x|✖️|×|X|*]?\d+[张|个|份]?)?\s?(r18)?\s?(.*)?", 13 | flags=I, 14 | permission=PRIVATE_FRIEND | GROUP, 15 | priority=10, 16 | block=True, 17 | handlers=[setu_handle.main], 18 | ) 19 | 20 | # r18列表添加用的,权限SUPERSUSER 21 | on_command( 22 | "add_r18", 23 | permission=SUPERUSER, 24 | block=True, priority=10, 25 | handlers=[setu_handle.add_r18list] 26 | ) 27 | 28 | 29 | # r18列表删除用的,权限SUPERSUSER 30 | on_command( 31 | "del_r18", 32 | permission=SUPERUSER, 33 | block=True, 34 | priority=10, 35 | handlers=[setu_handle.del_r18list] 36 | ) 37 | 38 | # 查看r18名单 39 | on_command( 40 | "r18名单", 41 | permission=SUPERUSER, 42 | block=True, 43 | priority=10, 44 | handlers=[setu_handle.get_r18list] 45 | ) 46 | 47 | # 色图功能帮助 48 | on_command( 49 | "setu_help", 50 | block=True, 51 | priority=9, 52 | handlers=[setu_handle.setu_help] 53 | ) 54 | 55 | # 禁用色图功能 56 | on_command( 57 | "ban_setu", 58 | aliases={"setu_ban", "禁用色图"}, 59 | permission=GROUP_OWNER | GROUP_ADMIN, 60 | priority=9, 61 | block=True, 62 | handlers=[setu_handle.admin_ban_setu] 63 | ) 64 | 65 | on_command( 66 | "ban_setu", 67 | aliases={"setu_ban", "禁用色图"}, 68 | permission=SUPERUSER, 69 | priority=8, 70 | block=True, 71 | handlers=[setu_handle.su_ban_setu] 72 | ) 73 | 74 | 75 | 76 | # 解除禁用色图功能 77 | on_command( 78 | "disactivate", 79 | aliases={"解除禁用"}, 80 | permission=SUPERUSER, 81 | priority=9, 82 | block=True, 83 | handlers=[setu_handle.disactivate] 84 | ) 85 | 86 | # 更换代理 87 | on_command( 88 | "更换代理", 89 | aliases={"替换代理", "setu_proxy"}, 90 | permission=SUPERUSER, 91 | block=True, 92 | priority=9, 93 | handlers=[setu_handle.replace_proxy, setu_handle.replace_proxy_got] 94 | ) 95 | -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/config.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import Union 4 | 5 | from nonebot import get_driver 6 | from pydantic import BaseSettings 7 | 8 | 9 | class Config(BaseSettings): 10 | setu_cd: int = 30 11 | setu_withdraw_time: int = 100 12 | setu_max_num: int = 10 13 | config_path: Path = Path("data/youth-version-of-setu4") 14 | config_file: Path = config_path / "config.json" 15 | setu_save: Union[bool, str] = False 16 | 17 | class Config: 18 | extra = "ignore" 19 | 20 | 21 | config = Config.parse_obj(get_driver().config) 22 | if not config.config_path.exists(): 23 | config.config_path.mkdir(parents=True, exist_ok=True) 24 | 25 | 26 | if not config.config_file.exists(): 27 | config_json = { 28 | "r18list": [], 29 | "banlist": [], 30 | "setu_proxy": "i.pixiv.re" 31 | } 32 | with open(config.config_file, "w", encoding="utf-8") as f: 33 | json.dump(config_json, f, indent=4, ensure_ascii=False) 34 | -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/get_data.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import random 4 | import sqlite3 5 | from io import BytesIO 6 | from pathlib import Path 7 | 8 | from httpx import AsyncClient 9 | from nonebot.log import logger 10 | from PIL import Image 11 | 12 | from .config import config 13 | from .utils import utils 14 | 15 | 16 | class GetSetu: 17 | def __init__(self) -> None: 18 | self.error = "Error:" 19 | # 数据库相关 20 | database_path = Path(__file__).parent / "resource" 21 | self.conn = sqlite3.connect(database_path / "lolicon.db") 22 | self.cur = self.conn.cursor() 23 | # 色图路径以及本地图片文件 24 | self.setu_save = config.setu_save 25 | self.all_file_name = os.listdir(self.setu_save) if config.setu_save else [] 26 | 27 | # 返回列表,内容为setu消息(列表套娃) 28 | async def get_setu( 29 | self, 30 | num: int = 1, 31 | quality: int = 75, 32 | r18: bool = False, 33 | keywords: list = None 34 | ) -> list: 35 | if keywords is None: 36 | keywords = [] 37 | data = [] 38 | # sql操作,根据keyword和r18进行查询拿到数据, sql操作要避免选中status为unavailable的 39 | if not keywords: 40 | sql = f"SELECT pid,title,author,r18,tags,urls from main where r18='{r18}' and status!='unavailable' order by random() limit {num}" 41 | elif len(keywords) == 1: 42 | sql = f"SELECT pid,title,author,r18,tags,urls from main where (tags like '%{keywords[0]}%' or title like '%{keywords[0]}%' or author like '%{keywords[0]}%') and r18='{r18}' and status!='unavailable' order by random() limit {num}" 43 | else: # 多tag的情况下的sql语句 44 | tag_sql = "".join( 45 | f"tags like '%{i}%'" 46 | if i == keywords[-1] 47 | else f"tags like '%{i}%' and " 48 | for i in keywords 49 | ) 50 | sql = f"SELECT pid,title,author,r18,tags,urls from main where (({tag_sql}) and r18='{r18}' and status!='unavailable') order by random() limit {num}" 51 | cursor = self.cur.execute(sql) 52 | db_data = cursor.fetchall() 53 | 54 | # 如果没有返回结果 55 | if db_data == []: 56 | data.append([self.error, f"图库中没有搜到关于{keywords}的图。", False]) 57 | return data 58 | 59 | async with AsyncClient() as client: 60 | tasks = [self.pic(setu, quality, client) for setu in db_data] 61 | data = await asyncio.gather(*tasks) 62 | return data 63 | 64 | 65 | 66 | async def pic( 67 | self, 68 | setu: list, 69 | quality: int, 70 | client: AsyncClient 71 | ) -> list: 72 | """返回setu消息列表,内容 [图片, 信息, True/False, url]""" 73 | setu_proxy = utils.read_proxy() # 读取代理 74 | setu_pid = setu[0] # pid 75 | setu_title = setu[1] # 标题 76 | setu_author = setu[2] # 作者 77 | setu_r18 = setu[3] # r18 78 | setu_tags = setu[4] # 标签 79 | setu_url = setu[5].replace('i.pixiv.re', setu_proxy) # 图片url 80 | 81 | data = ( 82 | "标题:" 83 | + setu_title 84 | + "\npid:" 85 | + str(setu_pid) 86 | + "\n画师:" 87 | + setu_author 88 | ) 89 | 90 | logger.info("\n"+data+"\ntags:" + 91 | setu_tags+"\nR18:"+setu_r18) 92 | file_name = setu_url.split("/")[-1] 93 | 94 | # 判断文件是否本地存在 95 | is_in_all_file_name = file_name in self.all_file_name 96 | if is_in_all_file_name: 97 | logger.info("图片本地存在") 98 | image = Image.open(f"{self.setu_save}/{file_name}") 99 | else: 100 | logger.info(f"图片本地不存在,正在去{setu_proxy}下载") 101 | content = await self.down_pic(setu_url, client) 102 | if type(content) == int: 103 | return [self.error, f"图片下载失败, 状态码{content}", False, setu_url] 104 | # 尝试打开图片, 如果失败就返回错误信息 105 | try: 106 | image = Image.open(BytesIO(content)) 107 | except Exception as e: 108 | return [self.error, f"图片下载失败, 错误信息:{e}", False, setu_url] 109 | # 保存图片, 如果save_path不为空, 以及图片不在all_file_name中, 那么就保存图片 110 | if self.setu_save: 111 | try: 112 | with open(f"{self.setu_save}/{file_name}", "wb") as f: 113 | f.write(content) 114 | self.all_file_name.append(file_name) 115 | except Exception as e: 116 | logger.error(f'图片存储失败: {e}') 117 | try: 118 | pic = await self.change_pixel(image, quality) 119 | except Exception as e: 120 | logger.error("图片处理失败") 121 | return [self.error, f"图片处理失败: {e}", False, setu_url] 122 | return [pic, data, True, setu_url] 123 | 124 | 125 | 126 | async def change_pixel( 127 | self, 128 | image: Image, 129 | quality: int 130 | ) -> bytes: 131 | """图片左右镜像,并且随机修改第一个像素点""" 132 | image = image.transpose(Image.FLIP_LEFT_RIGHT) 133 | image = image.convert("RGB") 134 | image.load()[0, 0] = (random.randint(0, 255), 135 | random.randint(0, 255), random.randint(0, 255)) 136 | byte_data = BytesIO() 137 | image.save(byte_data, format="JPEG", quality=quality) 138 | return byte_data.getvalue() 139 | 140 | async def down_pic( 141 | self, 142 | url: str, 143 | client: AsyncClient 144 | ): 145 | """下载图片并且返回content(bytes),或者status_code(int)""" 146 | try: 147 | re = await client.get(url=url, timeout=120) 148 | if re.status_code != 200: 149 | if re.status_code == 404: 150 | await self.update_status_unavailable(url.replace(utils.read_proxy(), 'i.pixiv.re')) 151 | return re.status_code 152 | logger.success("成功获取图片") 153 | return re.content 154 | except Exception: 155 | return 408 156 | 157 | 158 | async def update_status_unavailable(self, urls: str) -> None: 159 | """更新数据库中的图片状态为unavailable""" 160 | # 手搓sql语句 161 | sql = f"UPDATE main set status='unavailable' where urls='{urls}'" 162 | self.cur.execute(sql) # 执行 163 | self.conn.commit() # 提交事务 164 | 165 | 166 | get_setu = GetSetu() -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/handle.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | import os 4 | import platform 5 | import random 6 | from re import sub 7 | from typing import Tuple 8 | 9 | import nonebot 10 | from nonebot.adapters.onebot.v11 import (Bot, GroupMessageEvent, Message, 11 | MessageEvent, MessageSegment, 12 | PrivateMessageEvent) 13 | from nonebot.log import logger 14 | from nonebot.matcher import Matcher 15 | from nonebot.params import CommandArg, RegexGroup 16 | 17 | from .config import config 18 | from .get_data import get_setu 19 | from .setu_message import setu_sendcd, setu_sendmessage 20 | from .utils import setu_help, utils 21 | 22 | 23 | class SetuHandle: 24 | def __init__(self) -> None: 25 | self.max_num = config.setu_max_num # 最大数量 26 | self.cd_dir = {} # cd字典 27 | self.cd_time = config.setu_cd # cd时间 28 | self.withdraw_time = config.setu_withdraw_time # 撤回时间 29 | 30 | 31 | async def number_check( 32 | self, 33 | num: int, 34 | matcher: Matcher, 35 | event: MessageEvent, 36 | ): 37 | qid = event.get_user_id() 38 | # 色图图片质量, 如果num为3-6质量为70,如果num为7-max质量为50,其余为95(图片质量太高发起来太费时间了) 39 | # 注:quality值95为原图 40 | if num >= 3 and num <= 6: 41 | quality = 70 42 | elif num >= 7: 43 | quality = 50 44 | else: 45 | quality = 95 46 | if num >= 3: 47 | await matcher.send(f"由于数量过多请等待\n当前图片质量为{quality}\n3-6:quality = 70\n7-{self.max_num}:quality = 50") 48 | # 记录cd 49 | self.cd_dir.update({qid: event.time}) 50 | return quality 51 | 52 | async def cd_allow( 53 | self, 54 | key, 55 | r18, 56 | bot: Bot, 57 | num: int, 58 | flag_log: str, 59 | matcher: Matcher, 60 | event: MessageEvent, 61 | ): 62 | quality = await self.number_check(num, matcher, event) 63 | # data是数组套娃, 数组中的每个元素内容为: [图片, 信息, True/False, url] 64 | try: 65 | data = await get_setu.get_setu(num, quality, r18, key) 66 | except Exception as e: 67 | await matcher.finish(f"Error: {str(e)}") 68 | 69 | 70 | # 发送的消息列表 71 | message_list = [] 72 | for pic in data: 73 | # 如果状态为True,说明图片拿到了 74 | if pic[2]: 75 | message = f"{random.choice(setu_sendmessage)}{flag_log}" + \ 76 | Message(pic[1]) + MessageSegment.image(pic[0]) 77 | flag_log = "" 78 | else: 79 | message = pic[0] + pic[1] 80 | message_list.append(message) 81 | # 为后面撤回消息做准备 82 | setu_msg_id = [] 83 | # 尝试发送 84 | try: 85 | if isinstance(event, PrivateMessageEvent): 86 | # 私聊直接发送 87 | for msg in message_list: 88 | setu_msg_id.append((await matcher.send(msg))['message_id']) 89 | elif isinstance(event, GroupMessageEvent): 90 | # 群聊以转发消息的方式发送 91 | msgs = [utils.to_json(msg, bot.self_id, "setu-bot") 92 | for msg in message_list] 93 | setu_msg_id.append((await bot.call_api('send_group_forward_msg', group_id=event.group_id, messages=msgs))['message_id']) 94 | 95 | # 发送失败 96 | except Exception as e: 97 | # logger以及移除cd 98 | logger.warning(e) 99 | self.cd_dir.pop(event.get_user_id()) 100 | await matcher.finish( 101 | message=Message(f"消息被风控了捏,图发不出来,请尽量减少发送的图片数量尝试捕获错误信息: {str(e)}"), 102 | at_sender=True, 103 | ) 104 | 105 | # 自动撤回涩图 106 | if self.withdraw_time != 0: 107 | with contextlib.suppress(Exception): 108 | await asyncio.sleep(self.withdraw_time) 109 | for msg_id in setu_msg_id: 110 | await bot.delete_msg(message_id=msg_id) 111 | 112 | 113 | async def cd_notallow( 114 | self, 115 | cd, 116 | matcher: Matcher, 117 | ): 118 | time_last = self.cd_time - cd 119 | hours, minutes, seconds = 0, 0, 0 120 | if time_last >= 60: 121 | minutes, seconds = divmod(time_last, 60) 122 | hours, minutes = divmod(minutes, 60) 123 | else: 124 | seconds = time_last 125 | cd_msg = f"{f'{str(hours)}小时' if hours else ''}{f'{str(minutes)}分钟' if minutes else ''}{f'{str(seconds)}秒' if seconds else ''}" 126 | 127 | await matcher.send(f"{random.choice(setu_sendcd)} 你的CD还有{cd_msg}", at_sender=True) 128 | 129 | 130 | 131 | async def r18_ban_check( 132 | self, 133 | num, 134 | r18flag, 135 | matcher: Matcher, 136 | event: MessageEvent, 137 | ): 138 | # 私聊的话暂且让他按照输入的来 139 | r18 = bool((isinstance(event, PrivateMessageEvent) and r18flag)) 140 | # 获取会话id, 有群号有qq号 141 | sid = event.get_session_id() 142 | # 遍历banlist 143 | for session_id in utils.banlist: 144 | if session_id in sid: 145 | await matcher.finish("涩图功能已在此会话中禁用!") 146 | if num > self.max_num or num < 1: 147 | await matcher.finish(f"数量需要在1-{self.max_num}之间") 148 | # 如果r18是false的话在进行r18list判断 149 | if not r18: 150 | for groubnumber in utils.r18list: 151 | if groubnumber in sid: 152 | r18 = bool(r18flag) 153 | return r18 154 | 155 | 156 | 157 | async def main( 158 | self, 159 | bot: Bot, 160 | matcher: Matcher, 161 | event: MessageEvent, 162 | args: Tuple = RegexGroup() 163 | ): 164 | r18flag = args[2] 165 | key = args[3] # 获取关键词参数 166 | key = sub('[\'\"]', '', key) # 去掉引号防止sql注入 167 | num = args[1] # 获取数量参数 168 | num = int(sub(r"[张|个|份|x|✖️|×|X|*]", "", num)) if num else 1 169 | 170 | qid = event.get_user_id() 171 | try: 172 | cd = event.time - self.cd_dir[qid] 173 | except KeyError: 174 | cd = self.cd_time + 1 175 | r18 = await self.r18_ban_check(num, r18flag, matcher, event) 176 | 177 | # key按照空格切割为数组, 用于多关键词搜索, 并且把数组中的空元素去掉 178 | key = key.split(" ") 179 | key = [word.strip() for word in key if word.strip()] 180 | 181 | flag_log = ( 182 | f"\nR18 == {str(r18)}\nkeyword == {key}\nnum == {num}\n" 183 | if key 184 | else f"\nR18 == {str(r18)}\nkeyword == NULL\nnum == {num}\n" 185 | ) 186 | logger.info(f"key = {key}\tr18 = {r18}\tnum = {num}") # 控制台输出 187 | # cd判断,superusers无视cd 188 | if ( 189 | cd > self.cd_time 190 | or event.get_user_id() in nonebot.get_driver().config.superusers 191 | ): 192 | await self.cd_allow(key, r18, bot, num, flag_log, matcher, event) 193 | # cd还没过的情况 194 | else: 195 | await self.cd_notallow(cd, matcher) 196 | 197 | 198 | 199 | 200 | 201 | async def add_r18list( 202 | self, 203 | matcher: Matcher, 204 | arg: Message = CommandArg() 205 | ): 206 | # 获取消息文本 207 | msg = arg.extract_plain_text().strip().split()[0] 208 | # 如果不是数字就返回 209 | if not msg.isdigit(): 210 | await matcher.finish(f"ID:{msg}不是数字") 211 | # 如果已经存在就返回 212 | if msg in utils.r18list: 213 | await matcher.finish(f"ID:{msg}已存在") 214 | utils.r18list.append(msg) 215 | # 写入文件 216 | utils.config.update({"r18list": utils.r18list}) 217 | utils.write_configjson() 218 | await matcher.finish(f"ID:{msg}添加成功") 219 | 220 | 221 | 222 | async def del_r18list( 223 | self, 224 | matcher: Matcher, 225 | arg: Message = CommandArg() 226 | ): 227 | # 获取消息文本 228 | msg = arg.extract_plain_text().strip() 229 | try: 230 | utils.r18list.remove(msg) 231 | except ValueError: # 如果不存在就返回 232 | await matcher.finish(f"ID:{msg}不存在") 233 | # 写入文件 234 | utils.config.update({"r18list": utils.r18list}) # 更新dict 235 | utils.write_configjson() # 写入文件 236 | await matcher.finish(f"ID:{msg}删除成功") 237 | 238 | 239 | async def get_r18list( 240 | self, 241 | matcher: Matcher, 242 | ): 243 | await matcher.finish("R18名单:\n" + str(utils.r18list)) 244 | 245 | 246 | async def setu_help( 247 | self, 248 | matcher: Matcher, 249 | ): 250 | await matcher.finish(setu_help) # 发送 251 | 252 | 253 | 254 | async def admin_ban_setu( 255 | self, 256 | matcher: Matcher, 257 | event: GroupMessageEvent 258 | ): 259 | gid: str = str(event.group_id) 260 | # 如果存在 261 | if gid in utils.banlist: 262 | await matcher.finish(f"ID:{gid}已存在") 263 | utils.banlist.append(gid) 264 | utils.config.update({"banlist": utils.banlist}) # 更新dict 265 | utils.write_configjson() # 写入文件 266 | await matcher.finish(f"ID:{gid}禁用成功, 恢复需要找superuser") 267 | 268 | 269 | async def su_ban_setu( 270 | self, 271 | matcher: Matcher, 272 | arg: Message = CommandArg() 273 | ): 274 | # 获取消息文本 275 | msg = arg.extract_plain_text().strip() 276 | if not msg.isdigit(): 277 | await matcher.finish(f"ID:{msg}不是数字") 278 | # 如果已经存在就返回 279 | if msg in utils.banlist: 280 | await matcher.finish(f"ID:{msg}已存在") 281 | utils.banlist.append(msg) # 添加到list 282 | utils.config.update({"banlist": utils.banlist}) # 更新dict 283 | utils.write_configjson() # 写入文件 284 | await matcher.finish(f"ID:{msg}禁用成功") 285 | 286 | 287 | 288 | async def disactivate( 289 | self, 290 | matcher: Matcher, 291 | arg: Message = CommandArg() 292 | ): 293 | # 获取消息文本 294 | msg = arg.extract_plain_text().strip() 295 | try: 296 | utils.banlist.remove(msg) # 如果不存在就直接finish 297 | except ValueError: 298 | await matcher.finish(f"ID:{msg}不存在") 299 | utils.config.update({"banlist": utils.banlist}) # 更新dict 300 | utils.write_configjson() # 写入文件 301 | await matcher.finish(f"ID:{msg}解除成功") 302 | 303 | 304 | 305 | async def set_proxy(self, proxy): 306 | utils.config.update({"setu_proxy": proxy}) 307 | utils.write_configjson() 308 | plat = platform.system().lower() # 获取系统 309 | if plat == 'windows': 310 | result = os.popen(f"ping {proxy}").read() # windows下的ping 311 | elif plat == 'linux': 312 | result = os.popen(f"ping -c 4 {proxy}").read() # linux下的ping 313 | return result 314 | 315 | 316 | async def replace_proxy_got( 317 | self, 318 | matcher: Matcher, 319 | event: MessageEvent 320 | ): 321 | msg: str = str(event.get_message()) # 获取消息文本 322 | if not msg or msg.isspace(): 323 | await matcher.finish("需要输入proxy") 324 | await matcher.send(f"{msg}已经替换, 正在尝试ping操作验证连通性") # 发送消息 325 | result = await self.set_proxy(msg.strip()) 326 | await matcher.send(f"{result}\n如果丢失的数据比较多, 请考虑重新更换代理") # 发送消息 327 | 328 | async def replace_proxy( 329 | self, 330 | matcher: Matcher, 331 | arg: Message = CommandArg() 332 | ): 333 | msg = arg.extract_plain_text().strip() # 获取消息文本 334 | if not msg or msg.isspace(): 335 | await matcher.pause(f"请输入你要替换的proxy, 当前proxy为:{utils.read_proxy()}\ntips: 一些也许可用的proxy\ni.pixiv.re\nsex.nyan.xyz\npx2.rainchan.win\npximg.moonchan.xyz\npiv.deception.world\npx3.rainchan.win\npx.s.rainchan.win\npixiv.yuki.sh\npixiv.kagarise.workers.dev\npixiv.a-f.workers.dev\n等等....\n\neg:px2.rainchan.win\n警告:不要尝试命令行注入其他花里胡哨的东西, 可能会损伤你的电脑") 336 | else: 337 | await matcher.send(f"{msg}已经替换, 正在尝试ping操作验证连通性") # 发送消息 338 | result = await self.set_proxy(msg) 339 | await matcher.finish(f"{result}\n如果丢失的数据比较多, 请考虑重新更换代理") # 发送消息 340 | 341 | 342 | 343 | setu_handle = SetuHandle() -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "nonebot_plugin_setu4" 3 | version = "0.00.32" 4 | description = "内置数据库的setu插件, 尝试降低因为风控发不出图的概率" 5 | authors = [ 6 | {name = "Special-Week", email = "HuaMing27499@gmail.com"}, 7 | ] 8 | dependencies = [ 9 | "pillow>=9.1.1", 10 | "nonebot2>=2.0.0b5", 11 | "nonebot-adapter-onebot>=2.2.3", 12 | ] 13 | requires-python = ">=3.8.0" 14 | readme = "README.md" 15 | license = {text = "MIT"} -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/requirements.txt: -------------------------------------------------------------------------------- 1 | pillow>=9.1.1 2 | nonebot2>=2.0.0b5 3 | nonebot-adapter-onebot>=2.2.3 -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/resource/lolicon.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/nonebot_plugin_setu4/resource/lolicon.db -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/setu_message.py: -------------------------------------------------------------------------------- 1 | setu_sendmessage = [ 2 | "这是你的🐍图", 3 | "你是大色批", 4 | "看!要色图的色批出现了!", 5 | "喏,图", 6 | "给给给个🐍图", 7 | "色图有我好冲吗?", 8 | "呐呐呐,欧尼酱别看色图了呐", 9 | "有什么好色图有给发出来让大伙看看!", 10 | "没有,有也不给(骗你的~)", 11 | "天天色图色图的,今天就把你变成色图!", 12 | "咱没有色图(骗你的~)", 13 | "哈?你的脑子一天都在想些什么呢,咱才没有这种东西啦。", 14 | "呀!不要啊!等一...下~", 15 | "呜...不要啦!太色了咱~", 16 | "不要这样子啦(*/ω\*)", 17 | "Hen....Hentai!。", 18 | "讨....讨厌了(脸红)", 19 | "你想...想做什么///", 20 | "啊.....你...你要干什么?!走开.....走开啦大hentai!一巴掌拍飞!(╯‵□′)╯︵┻━┻", 21 | "变态baka死宅?", 22 | "已经可以了,现在很多死宅也都没你这么恶心了", 23 | "噫…你这个死变态想干嘛!居然想叫咱做这种事,死宅真恶心!快离我远点,我怕你污染到周围空气了(嫌弃脸)", 24 | "这么喜欢色图呢?不如来点岛风色图?", 25 | "hso!", 26 | "这么喜欢看色图哦?变态?", 27 | "eee,死肥宅不要啦!恶心心!", 28 | "不管到了哪里,变态可疑分子果然还是变态可疑分子呢!", 29 | "变态可疑分子先生,你要的色图", 30 | "不许导,积回去!", 31 | "∫1/(1+x^4)dx", 32 | "做涩涩的事情是对的!", 33 | "有色鬼,我不说是谁!", 34 | "看完色图能不能关注一下嘉然小姐 https://b23.tv/0jC0EDl", 35 | "涩涩了一整天,好累哦", 36 | "呃...好像冲了好多次...感觉不太好呢...", 37 | "注意身体,色图看太多对身体不好", 38 | ] 39 | setu_sendcd = [ 40 | "憋冲了!你已经冲不出来了!", 41 | "憋住,不准冲!", 42 | "你的色图不出来了!", 43 | "注意身体,色图看太多对身体不好", 44 | "憋再冲了!", 45 | "呃...好像冲了好多次...感觉不太好呢...", 46 | "???", 47 | "你急啥呢?", 48 | "你这么喜欢色图,还不快点冲!", 49 | ] 50 | -------------------------------------------------------------------------------- /src/plugins/nonebot_plugin_setu4/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | from .config import config 3 | 4 | class Utils: 5 | def __init__(self) -> None: 6 | self.config = json.load(open(config.config_file, 'r', encoding="utf-8")) 7 | """ 8 | json结构: 9 | { 10 | "r18list": [ 11 | "123456789", 12 | "987654321" 13 | ], 14 | "banlist": [ 15 | "123456789", 16 | "987654321" 17 | ], 18 | "setu_proxy":"i.pixiv.re" 19 | } 20 | """ 21 | self.r18list = self.config["r18list"] 22 | self.banlist = self.config["banlist"] 23 | 24 | 25 | def write_configjson(self) -> None: 26 | """写入json""" 27 | with open(config.config_file, 'w', encoding="utf-8") as fp: 28 | json.dump(self.config, fp, ensure_ascii=False) 29 | 30 | 31 | def read_proxy(self) -> str: 32 | """读取代理""" 33 | return self.config["setu_proxy"] 34 | 35 | 36 | def write_proxy(self, proxy: str) -> None: 37 | """写入代理""" 38 | self.config["setu_proxy"] = proxy 39 | self.write_configjson() 40 | 41 | 42 | def to_json( 43 | self, 44 | msg, 45 | uin: str, 46 | name: str 47 | ) -> dict: 48 | """转换为dict, 转发消息用""" 49 | return { 50 | 'type': 'node', 51 | 'data': { 52 | 'name': name, 53 | 'uin': uin, 54 | 'content': msg 55 | } 56 | } 57 | 58 | 59 | utils = Utils() 60 | 61 | 62 | 63 | 64 | setu_help = """命令头: setu|色图|涩图|想色色|来份色色|来份色图|想涩涩|多来点|来点色图|来张setu|来张色图|来点色色|色色|涩涩 (任意一个) 65 | 参数可接r18, 数量, 关键词 66 | eg: 67 | setu 10张 r18 白丝 68 | setu 10张 白丝 69 | setu r18 白丝 70 | setu 白丝 71 | setu 72 | (空格可去掉, 多tag用空格分开 eg:setu 白丝 loli) 73 | 74 | superuser指令: 75 | r18名单: 查看r18有哪些群聊或者账号 76 | add_r18 xxx: 添加r18用户/群聊 77 | del_r18 xxx: 移除r18用户 78 | disactivate | 解除禁用 xxx: 恢复该群的setu功能 79 | ban_setu xxx: 禁用xxx群聊的色图权限 80 | setu_proxy: 更换setu代理(会提示一些允许可用的代理) 81 | 82 | 群主/管理员: 83 | ban_setu: 禁用当前群聊功能, 解除需要找superuser""" -------------------------------------------------------------------------------- /src/plugins/offline_warning/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | import smtplib 4 | from email.mime.multipart import MIMEMultipart 5 | from email.mime.text import MIMEText 6 | 7 | from loguru import logger 8 | from nonebot import get_driver 9 | from nonebot.adapters.onebot.v11 import Bot 10 | 11 | from .config import Config 12 | 13 | config = Config.parse_obj(get_driver().config) 14 | 15 | if ( 16 | isinstance(config.sender_email, str) 17 | and isinstance(config.sender_password, str) 18 | and isinstance(config.receiver_email, str) 19 | ): 20 | is_configured = True 21 | else: 22 | is_configured = False 23 | logger.error("sender_email, sender_password, receiver_email 其中有至少一个未配置,邮件功能将无法使用") 24 | 25 | 26 | driver = get_driver() 27 | 28 | 29 | def send_email(bot_id) -> None: 30 | message = MIMEMultipart() 31 | message["From"] = config.sender_email 32 | message["To"] = config.receiver_email 33 | message["Subject"] = "你的bot掉线了,快去看看吧" 34 | body = f"你的bot,账号:{bot_id}掉线了,快去看看吧" 35 | message.attach(MIMEText(body, "plain")) 36 | 37 | server = smtplib.SMTP("smtp.office365.com", 587) 38 | server.starttls() 39 | for _ in range(config.retry_count): 40 | try: 41 | server.login(config.sender_email, config.sender_password) # type: ignore 42 | server.sendmail(config.sender_email, config.receiver_email, message.as_string()) # type: ignore 43 | server.quit() 44 | break 45 | except Exception as e: 46 | logger.error("Error sending email:", e) 47 | 48 | 49 | @driver.on_bot_disconnect 50 | async def _(bot: Bot) -> None: 51 | if not is_configured: 52 | return 53 | loop = asyncio.get_event_loop() 54 | await loop.run_in_executor(None, send_email, bot.self_id) 55 | 56 | 57 | with contextlib.suppress(Exception): 58 | from nonebot.plugin import PluginMetadata 59 | 60 | __plugin_meta__ = PluginMetadata( 61 | name="offline_warning", 62 | description="nonebot2机器人掉线警告", 63 | usage="", 64 | type="application", 65 | homepage="https://github.com/Special-Week/", 66 | supported_adapters={"~onebot.v11"}, 67 | extra={ 68 | "author": "Special-Week", 69 | "link": "https://github.com/Special-Week/n", 70 | "version": "0.0.114514", 71 | }, 72 | ) 73 | -------------------------------------------------------------------------------- /src/plugins/offline_warning/config.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseSettings 4 | 5 | 6 | class Config(BaseSettings): 7 | # 配置发件人邮箱 8 | sender_email: Optional[str] = None 9 | sender_password: Optional[str] = None 10 | 11 | # 配置收件人邮箱 12 | receiver_email: Optional[str] = None 13 | # 重发次数 14 | retry_count: int = 3 15 | 16 | class Config: 17 | extra = "ignore" 18 | -------------------------------------------------------------------------------- /src/plugins/offline_warning/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/offline_warning/image-1.png -------------------------------------------------------------------------------- /src/plugins/offline_warning/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/offline_warning/image-2.png -------------------------------------------------------------------------------- /src/plugins/offline_warning/image-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/offline_warning/image-3.png -------------------------------------------------------------------------------- /src/plugins/offline_warning/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/offline_warning/image.png -------------------------------------------------------------------------------- /src/plugins/offline_warning/readme.md: -------------------------------------------------------------------------------- 1 | # nonebot2 bot掉线邮件提醒 2 | 3 | ## 目前暂时只能用作outlook邮箱 4 | 5 | ### 使用方法 6 | env配置项: 7 | ```env 8 | sender_email = "发送者邮箱" 9 | sender_password = "发送者邮箱密码" 10 | receiver_email = "接收者邮箱" 11 | retry_count = 3 # 重试次数 12 | ``` 13 | 其中 retry_count 可选,其余不填无法正常使用 14 | 15 | ### warning 16 | 密码并非你的邮箱密码,而是授权码 17 | 举例: 18 | - 网页登录你的outlook邮箱设置如图
19 | ![](./image.png)
20 | - 进入我的Microsoft账户,点击上方“安全”选项 21 | - 打开双重验证
22 | ![](./image-1.png)
23 | - 创建新应用密码!
24 | ![](./image-2.png)
25 | ![](./image-3.png)
26 | 这个密码才是作为你邮箱登录的密码 27 | -------------------------------------------------------------------------------- /src/plugins/pixiv_id/README.MD: -------------------------------------------------------------------------------- 1 | # 输入命令头pixiv_id, 后跟着图片id, 自动下载图片发送 2 | 3 | 单图模式参数直接id 4 | 多图模式参数id+序号 -------------------------------------------------------------------------------- /src/plugins/pixiv_id/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | 3 | from .handle import pixiv_id 4 | 5 | on_command("pixiv_id", block=True, handlers=[pixiv_id.main]) 6 | -------------------------------------------------------------------------------- /src/plugins/pixiv_id/handle.py: -------------------------------------------------------------------------------- 1 | import random 2 | from io import BytesIO 3 | 4 | from httpx import AsyncClient 5 | from loguru import logger 6 | from nonebot.adapters.onebot.v11 import Message, MessageSegment 7 | from nonebot.matcher import Matcher 8 | from nonebot.params import CommandArg 9 | from PIL import Image 10 | 11 | 12 | class PixivID: 13 | def __init__(self) -> None: 14 | self.url = "https://pixiv.re/" 15 | 16 | 17 | async def main( 18 | self, 19 | matcher: Matcher, 20 | msg: Message = CommandArg() 21 | ): 22 | _id = msg.extract_plain_text().strip() 23 | url = f"{self.url}{_id}.jpg" 24 | # 下载图片 25 | try: 26 | content = await self.down_pic(url) 27 | except Exception: 28 | await matcher.finish("图片下载失败") 29 | # 返回值为404则没有这个id或者id为多图模式 30 | if type(content) == int: 31 | await matcher.finish("没有找到这个id,多张作品请用xxxxxx-x格式") 32 | # 打开图像,随机修改左上角第一颗像素点,并且转为base64编码 33 | image = Image.open(BytesIO(content)) 34 | img_format = image.format # 获取图片的格式 35 | image.load()[0, 0] = (random.randint(0, 255), 36 | random.randint(0, 255), random.randint(0, 255)) 37 | byte_data = BytesIO() 38 | image.save(byte_data, format=img_format, quality=95) 39 | pic = byte_data.getvalue() 40 | 41 | # 发送图片 42 | try: 43 | await matcher.send(MessageSegment.image(pic)) 44 | except Exception: 45 | await matcher.send(f"消息被风控,图片发送失败,这是连接{url}") 46 | 47 | 48 | 49 | # 下载图片并且返回content,或者status_code 50 | async def down_pic(self, url: str): 51 | async with AsyncClient() as client: 52 | try: 53 | re = await client.get(url=url, timeout=120) 54 | if re.status_code == 200: 55 | logger.success("插件pixiv_id成功获取图片") 56 | return re.content 57 | else: 58 | logger.error(f"插件pixiv_id获取图片失败: {re.status_code}") 59 | return re.status_code 60 | except Exception: 61 | logger.error("http访问超时") 62 | return 408 63 | 64 | 65 | pixiv_id = PixivID() 66 | -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/__init__.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from typing import Dict, List, Union 3 | 4 | import nonebot 5 | from nonebot import get_driver, on_command, require 6 | from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageSegment 7 | from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER 8 | from nonebot.params import CommandArg 9 | from nonebot.permission import SUPERUSER 10 | 11 | from .config import config 12 | from .draw import drawtable 13 | from .main import pcr_rank 14 | 15 | require("nonebot_plugin_apscheduler") 16 | from nonebot_plugin_apscheduler import scheduler 17 | 18 | rank = on_command("公会排名", priority=5, block=False) 19 | is_open = on_command( 20 | "定时", 21 | aliases={"播报"}, 22 | rule=pcr_rank.rule, 23 | priority=5, 24 | block=False, 25 | permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER, 26 | ) 27 | add_group = on_command( 28 | "添加公会", 29 | aliases={"添加"}, 30 | priority=5, 31 | block=False, 32 | permission=SUPERUSER, 33 | ) 34 | del_group = on_command( 35 | "删除公会", 36 | aliases={"删除"}, 37 | priority=5, 38 | block=False, 39 | permission=SUPERUSER, 40 | ) 41 | 42 | clean_data = on_command( 43 | "清除排名数据", 44 | aliases={"清除排名"}, 45 | priority=5, 46 | block=False, 47 | permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER, 48 | ) 49 | 50 | @clean_data.handle() 51 | async def _(event: GroupMessageEvent) -> None: 52 | gid = event.group_id 53 | if gid in pcr_rank.group.keys(): 54 | name = pcr_rank.group[gid] 55 | pcr_rank.history_data[name] = [] 56 | await pcr_rank.save_json() 57 | await clean_data.finish("清除成功") 58 | else: 59 | await clean_data.finish("未找到对应公会") 60 | 61 | 62 | @add_group.handle() 63 | async def _(event: GroupMessageEvent, args: Message = CommandArg()) -> None: 64 | gid = event.group_id 65 | target = args.extract_plain_text() 66 | if not target: 67 | await add_group.finish("请在命令后接上公会名") 68 | resp = await pcr_rank.get_guild_rank(target) 69 | if isinstance(resp, str): 70 | await add_group.finish("获取公会排名失败, 添加不成功") 71 | else: 72 | pcr_rank.group[gid] = target 73 | await pcr_rank.save_config() 74 | await add_group.finish(f"添加成功, 获取到的信息为{resp}") 75 | 76 | 77 | @del_group.handle() 78 | async def _(event: GroupMessageEvent, args: Message = CommandArg()) -> None: 79 | gid = event.group_id 80 | target = args.extract_plain_text() 81 | if not target: 82 | await del_group.finish("请在命令后接上公会名") 83 | if pcr_rank.group.get(gid) == target: 84 | del pcr_rank.group[gid] 85 | await pcr_rank.save_config() 86 | await del_group.finish("删除成功") 87 | else: 88 | await del_group.finish("删除失败, 未找到对应公会") 89 | 90 | 91 | @is_open.handle() 92 | async def _(event: GroupMessageEvent) -> None: 93 | original_message = event.raw_message 94 | if "关闭" in original_message or "取消" in original_message: 95 | config.guild_default_switch = False 96 | await is_open.finish("已关闭") 97 | elif "开启" in original_message or "打开" in original_message: 98 | config.guild_default_switch = True 99 | await is_open.send("已开启") 100 | await sched() 101 | 102 | 103 | @rank.handle() 104 | async def _(event: GroupMessageEvent) -> None: 105 | if event.group_id not in pcr_rank.group.keys(): 106 | await rank.finish("config中未找到对应公会") 107 | 108 | group_name: str = pcr_rank.group[event.group_id] 109 | data: Union[str, List[Dict]] = await pcr_rank.get_guild_rank(group_name) 110 | if isinstance(data, str): 111 | await rank.finish("获取排名失败") 112 | pic = await drawtable.draw(data) 113 | message = MessageSegment.text("当前公会的排名") + MessageSegment.image(pic) 114 | gear_score_line: Union[str, List[Dict]] = await pcr_rank.get_gear_score_line() 115 | if isinstance(gear_score_line, list): 116 | target = await drawtable.draw(gear_score_line) 117 | message += MessageSegment.text("档线公会的排名") + MessageSegment.image(target) 118 | await rank.finish(message) 119 | 120 | 121 | async def sched() -> None: 122 | if not config.guild_default_switch: 123 | return 124 | bot = nonebot.get_bot() 125 | data: Dict[int, Union[bytes, None]] = {} 126 | for _ in range(3): 127 | with contextlib.suppress(Exception): 128 | data = await pcr_rank.timed_crawling() 129 | break 130 | 131 | if data != {}: 132 | target: Union[bytes, None] = data.get(800000000) 133 | del data[800000000] 134 | for i, value in data.items(): 135 | if value is not None: 136 | send_msg = MessageSegment.text("当前公会的排名"), MessageSegment.image(value) 137 | if target is not None: 138 | send_msg += MessageSegment.text("档线公会的排名") 139 | send_msg += MessageSegment.image(target) 140 | with contextlib.suppress(Exception): 141 | name: str = pcr_rank.group[i] 142 | image_bytes: bytes = await pcr_rank.draw_line_chart( 143 | pcr_rank.history_data[name], name 144 | ) 145 | send_msg += MessageSegment.text("排名趋势图") 146 | send_msg += MessageSegment.image(image_bytes) 147 | with contextlib.suppress(Exception): 148 | await bot.send_group_msg(group_id=i, message=send_msg) 149 | else: 150 | for key in pcr_rank.group.keys(): 151 | await bot.send_group_msg(group_id=key, message="获取公会排名失败") 152 | 153 | 154 | scheduler.add_job(sched, "interval", hours=config.interval, id="job_1") 155 | 156 | driver = get_driver() 157 | 158 | 159 | @driver.on_bot_connect 160 | async def _() -> None: 161 | await sched() 162 | -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/config.py: -------------------------------------------------------------------------------- 1 | from nonebot import get_driver 2 | from pydantic import BaseSettings 3 | 4 | 5 | class Config(BaseSettings): 6 | guild_default_switch: bool = True 7 | interval: int = 5 8 | 9 | class Config: 10 | extra = "ignore" 11 | 12 | 13 | config: Config = Config.parse_obj(get_driver().config) 14 | -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/draw.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from pathlib import Path 3 | from typing import Any, Dict, List, Tuple 4 | 5 | from PIL import Image, ImageDraw, ImageFont 6 | 7 | 8 | class DrawTable: 9 | def __init__(self) -> None: 10 | self.module_path: Path = Path(__file__).parent 11 | self.font = str(self.module_path / "fonts" / "SIMYOU.TTF") 12 | self.slit: list[int] = [0, 89, 285, 450, 540, 740, 944] 13 | 14 | @staticmethod 15 | def convert_dict(data: List[Dict[str, Any]]) -> Dict[str, List]: 16 | table: Dict[str, List] = { 17 | "排名": [], 18 | "公会名": [], 19 | "分数": [], 20 | "上期排名": [], 21 | "会长": [], 22 | "会长ID": [], 23 | } 24 | for i in data: 25 | table["排名"].append(i["rank"]) 26 | table["公会名"].append(i["clanName"]) 27 | table["分数"].append(i["damage"]) 28 | table["上期排名"].append(i["gradeRank"]) 29 | table["会长"].append(i["userName"]) 30 | table["会长ID"].append(i["leaderViewerId"]) 31 | return table 32 | 33 | async def draw(self, table: List[Dict[str, Any]]) -> bytes: 34 | data: Dict[str, List] = self.convert_dict(table) 35 | length = len(data[list(data.keys())[0]]) 36 | image = Image.new("RGBA", (944, 60 * (length + 1)), (255, 255, 255, 255)) 37 | draw = ImageDraw.Draw(image) 38 | 39 | # ------------------------ 每行画一条浅浅的灰色的线 ------------------------ 40 | 41 | for i in range(1, length): 42 | draw.line((0, 60 * i, 944, 60 * i), fill=(240, 240, 240), width=1) 43 | 44 | for i in range(1, len(data.keys()) + 1): 45 | draw.line( 46 | (self.slit[i - 1], 10, self.slit[i - 1], 50), 47 | fill=(240, 240, 240), 48 | width=1, 49 | ) 50 | 51 | # ------------------------ 遍历每个数据画表格 ------------------------ 52 | def get_centre(key: str, size: int) -> Tuple[float, float]: 53 | font_box = ImageFont.truetype(self.font, size).getbbox(key) 54 | font_width = font_box[2] - font_box[0] 55 | font_height = font_box[3] - font_box[1] 56 | return (font_width / 2, font_height / 2) 57 | 58 | for count, (key, value) in enumerate(data.items()): 59 | font_center = get_centre(key, 20) 60 | font_left_top = ( 61 | self.slit[count] 62 | + (self.slit[count + 1] - self.slit[count]) / 2 63 | - font_center[0], 64 | 30 - font_center[1], 65 | ) 66 | draw.text( 67 | font_left_top, key, fill="black", font=ImageFont.truetype(self.font, 20) 68 | ) 69 | for i in range(len(value)): 70 | font_center = get_centre(str(value[i]), 20) 71 | font_left_top = ( 72 | self.slit[count] 73 | + (self.slit[count + 1] - self.slit[count]) / 2 74 | - font_center[0], 75 | 60 * (i + 1) + 30 - font_center[1], 76 | ) 77 | draw.text( 78 | font_left_top, 79 | str(value[i]), 80 | fill="black", 81 | font=ImageFont.truetype(self.font, 20), 82 | ) 83 | image_bytes = BytesIO() 84 | image.save(image_bytes, format="png") 85 | return image_bytes.getvalue() 86 | 87 | 88 | drawtable = DrawTable() 89 | -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/fonts/SIMYOU.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/prc_rank_broadcast/fonts/SIMYOU.TTF -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/header.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import random 3 | import time 4 | from typing import Dict, List, Tuple 5 | 6 | a: Dict[str, str] = { 7 | "m": "q", 8 | "3": "/", 9 | "5": ")", 10 | "y": "e", 11 | "t": "h", 12 | "i": "u", 13 | "a": "}", 14 | "8": "$", 15 | "c": "\u007f", 16 | "1": "-", 17 | "6": "*", 18 | "4": "(", 19 | "2": ".", 20 | "z": "f", 21 | "f": "z", 22 | "h": "t", 23 | "r": "n", 24 | "0": ",", 25 | "7": "+", 26 | "g": "{", 27 | "j": "v", 28 | "w": "k", 29 | "x": "d", 30 | "e": "y", 31 | "l": "p", 32 | "o": "s", 33 | "s": "o", 34 | "v": "j", 35 | "9": "%", 36 | "k": "w", 37 | "q": "m", 38 | "n": "r", 39 | "d": "x", 40 | "u": "i", 41 | "b": "~", 42 | "p": "l", 43 | } 44 | b: Dict[str, str] = { 45 | "A": "!", 46 | "B": '"', 47 | "C": "#", 48 | "D": "$", 49 | "E": "%", 50 | "F": "&", 51 | "G": "'", 52 | "H": "(", 53 | "I": ")", 54 | "J": "*", 55 | "K": "+", 56 | "L": ",", 57 | "M": "-", 58 | "N": ".", 59 | "O": "/", 60 | "P": "0", 61 | "Q": "1", 62 | "R": "2", 63 | "S": "3", 64 | "T": "4", 65 | "U": "5", 66 | "V": "6", 67 | "W": "7", 68 | "X": "8", 69 | "Y": "9", 70 | "Z": ":", 71 | "a": ";", 72 | "b": "<", 73 | "c": "=", 74 | "d": ">", 75 | "e": "?", 76 | "f": "@", 77 | "g": "A", 78 | "h": "B", 79 | "i": "C", 80 | "j": "D", 81 | "k": "E", 82 | "l": "F", 83 | "m": "G", 84 | "n": "H", 85 | "o": "I", 86 | "p": "J", 87 | "q": "K", 88 | "r": "L", 89 | "s": "M", 90 | "t": "N", 91 | "u": "O", 92 | "v": "P", 93 | "w": "Q", 94 | "x": "R", 95 | "y": "S", 96 | "z": "T", 97 | "0": "U", 98 | "1": "V", 99 | "2": "W", 100 | "3": "X", 101 | "4": "Y", 102 | "5": "Z", 103 | "6": "[", 104 | "7": "\\", 105 | "8": "]", 106 | "9": "^", 107 | "+": "_", 108 | "/": "`", 109 | "=": "~", 110 | } 111 | 112 | 113 | def get_headers() -> Dict[str, str]: 114 | def get_sign() -> Tuple[str, str, str]: 115 | c, r, t, s = ( 116 | "", 117 | "", 118 | str(int(time.time())), 119 | "".join( 120 | random.sample("".join([chr(l).lower() for l in range(65, 91)]), 11) 121 | ), 122 | ) 123 | for i in s + t: 124 | c += a[i] 125 | for i in base64.b64encode(c.encode()).decode(): 126 | r += b[i] 127 | return s, t, f"SIGv1.0|{r}" 128 | 129 | s, t, sign = get_sign() 130 | 131 | return { 132 | "authority": "pcr-api.himaribot.com", 133 | "accept": "application/json, text/plain, */*", 134 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", 135 | "content-type": "application/json", 136 | "cookie": "token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjM4NDQsImV4cCI6MTY5MDk5MzYwNywiaWF0IjoxNjkwMzg4ODA3fQ.1VBd6aoIPbM2XNlK66l_VWvZyjg_5eu79z30Sa8Rr4w", 137 | "origin": "https://kyouka.kengxxiao.com", 138 | "referer": "https://kyouka.kengxxiao.com/", 139 | "sec-ch-ua": '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"', 140 | "sec-ch-ua-mobile": "?0", 141 | "sec-ch-ua-platform": '"Windows"', 142 | "sec-fetch-dest": "empty", 143 | "sec-fetch-mode": "cors", 144 | "sec-fetch-site": "cross-site", 145 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183", 146 | "x-nonce": s, 147 | "x-sign": sign, 148 | "x-timestamp": t, 149 | } 150 | -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/prc_rank_broadcast/image-1.png -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/prc_rank_broadcast/image.png -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | from io import BytesIO 3 | from pathlib import Path 4 | from typing import Dict, List, Union 5 | 6 | from httpx import AsyncClient 7 | from loguru import logger 8 | from nonebot.adapters.onebot.v11 import GroupMessageEvent 9 | from PIL import Image, ImageDraw, ImageFilter, ImageFont 10 | 11 | from .config import config 12 | from .draw import drawtable 13 | from .header import get_headers 14 | 15 | 16 | class PcrRank: 17 | def __init__(self) -> None: 18 | self.module_path: Path = Path(__file__).parent 19 | self.data_file = "data.json" 20 | config_file = "config.json" 21 | if not (self.module_path / config_file).exists(): 22 | with open(self.module_path / config_file, "w", encoding="utf-8") as f: 23 | temp_config: Dict[str, str] = {} 24 | json.dump(temp_config, f, ensure_ascii=False, indent=4) 25 | with open(self.module_path / config_file, "r", encoding="utf-8") as f: 26 | group: Dict[str, str] = json.load(f) 27 | self.group: Dict[int, str] = {int(k): v for k, v in group.items()} 28 | logger.info(f"监听的公会有{self.group}") 29 | if not self.group: 30 | config.guild_default_switch = False 31 | 32 | if not (self.module_path / self.data_file).exists(): 33 | with open(self.module_path / self.data_file, "w", encoding="utf-8") as f: 34 | temp_data: Dict[str, List] = {} 35 | json.dump(temp_data, f, ensure_ascii=False, indent=4) 36 | with open(self.module_path / self.data_file, "r", encoding="utf-8") as f: 37 | self.history_data = json.load(f) 38 | self.font = str(self.module_path / "fonts" / "SIMYOU.TTF") 39 | self.api_url = "https://pcr-api.himaribot.com/clan/rankSearch" 40 | 41 | async def get_gear_score_line(self) -> Union[str, List[Dict]]: 42 | data = '{"name":"","leaderName":"","minRank":1,"maxRank":99999,"score":0,"page":0,"period":0,"maxPerPage":10,"fav":false,"onlyRank":true}' 43 | async with AsyncClient() as client: 44 | response = (await client.post(self.api_url, data=data, headers=get_headers())).json() # type: ignore 45 | if response["statusCode"] != 200: 46 | return "挡位线获取失败" 47 | rank_data: List[Dict] = response["data"]["clans"] 48 | return rank_data 49 | 50 | async def get_guild_rank(self, guild_name: str) -> Union[str, List[Dict]]: 51 | data = '{"name":"guild_name","leaderName":"","minRank":1,"maxRank":99999,"score":0,"page":0,"period":0,"maxPerPage":10,"fav":false,"onlyRank":false}' 52 | async with AsyncClient() as client: 53 | response = (await client.post(self.api_url, headers=get_headers(), data=data.replace("guild_name", guild_name))).json() # type: ignore 54 | if response["statusCode"] != 200: 55 | return "公会名获取失败" 56 | return "公会名获取失败" if response["data"] is None else response["data"]["clans"] 57 | 58 | async def timed_crawling(self) -> Dict[int, Union[None, bytes]]: 59 | pic_data: Dict[int, Union[None, bytes]] = {} 60 | for group_id, names in self.group.items(): 61 | data: Union[str, List] = await self.get_guild_rank(names) 62 | if isinstance(data, str): 63 | pic_data[group_id] = None 64 | continue 65 | logger.info(f"获取{names}的排名成功") 66 | if names not in self.history_data: 67 | self.history_data[names] = [] 68 | self.history_data[names].append(data[0]["rank"]) 69 | pic_data[group_id] = await drawtable.draw(data) 70 | await self.save_json() 71 | gear_score: Union[str, List] = await self.get_gear_score_line() 72 | if isinstance(gear_score, str): 73 | pic_data[800000000] = None 74 | else: 75 | logger.info("获取档线公会的排名成功") 76 | pic_data[800000000] = await drawtable.draw(gear_score) 77 | return pic_data 78 | 79 | async def save_json(self) -> None: 80 | with open(self.module_path / self.data_file, "w", encoding="utf-8") as f: 81 | json.dump(self.history_data, f, ensure_ascii=False, indent=4) 82 | 83 | async def save_config(self) -> None: 84 | with open(self.module_path / "config.json", "w", encoding="utf-8") as f: 85 | json.dump(self.group, f, ensure_ascii=False, indent=4) 86 | 87 | async def rule(self, event: GroupMessageEvent) -> bool: 88 | return event.group_id in self.group 89 | 90 | async def draw_line_chart(self, data: List[str], title: str) -> bytes: 91 | # 画折线图 92 | values = [int(i) for i in data] 93 | keys = title 94 | image = Image.new("RGBA", (1920, 1080), (255, 255, 255, 255)) 95 | draw = ImageDraw.Draw(image) 96 | 97 | # ------------------------ 画一个框框 ------------------------ 98 | 99 | image_new = Image.new("RGBA", (1920, 1080), (255, 255, 255, 0)) 100 | draw_new = ImageDraw.Draw(image_new) 101 | draw_new.rectangle((420, 25, 1860, 1060), fill=(255, 255, 255, 220)) 102 | image_new = image_new.filter(ImageFilter.GaussianBlur(radius=0.1)) 103 | image.paste(image_new, (0, 0), image_new) 104 | 105 | # ------------------------ 画一个坐标轴 ------------------------ 106 | 107 | draw.line((490, 1000, 1800, 1000), fill="black", width=2) 108 | draw.line((500, 50, 500, 1030), fill="black", width=2) 109 | maxnum_scale = max(values) / 9 110 | 111 | # ------------------------ 画一些虚线 ------------------------ 112 | 113 | def draw_dotted_line(y): 114 | x_start, x_end = 500, 1800 115 | dash_length = 10 116 | gap_length = 5 117 | x = x_start 118 | while x < x_end: 119 | x_dash_end = min(x + dash_length, x_end) 120 | draw.line((x, y, x_dash_end, y), fill="black", width=1) 121 | x += dash_length + gap_length 122 | 123 | for i in range(10): 124 | draw_dotted_line(1000 - 950 * i / 10) 125 | 126 | maxnum_scale = (int(maxnum_scale / 20) + 1) * 20 127 | if maxnum_scale == 0: 128 | maxnum_scale = 20 129 | 130 | for i in range(10): 131 | draw.text( 132 | (450, 1000 - 950 * i / 10 - 10), 133 | str(maxnum_scale * i), 134 | fill="black", 135 | font=ImageFont.truetype(self.font, 20), 136 | ) 137 | 138 | # ------------------------ 画折线图 ------------------------ 139 | 140 | def draw_line_chart(): 141 | x_start = 540 142 | x_gap = (1800 - x_start) / (len(values) - 1) 143 | x = x_start 144 | y = 1000 - 95 * (values[0] / maxnum_scale) 145 | draw.ellipse((x - 5, y - 5, x + 5, y + 5), fill="black", width=2) 146 | draw.text( 147 | (x - 20, y - 30), 148 | str(values[0]), 149 | fill="black", 150 | font=ImageFont.truetype(self.font, 32), 151 | ) 152 | for i in range(1, len(values)): 153 | x_new = x_start + x_gap * i 154 | y_new = 1000 - 95 * (values[i] / maxnum_scale) 155 | draw.line((x, y, x_new, y_new), fill="black", width=2) 156 | x, y = x_new, y_new 157 | # 给每个点画个圆圈 158 | draw.ellipse((x - 5, y - 5, x + 5, y + 5), fill="black", width=2) 159 | # 在每个点上写上值 160 | draw.text( 161 | (x - 20, y - 30), 162 | str(values[i]), 163 | fill="black", 164 | font=ImageFont.truetype(self.font, 32), 165 | ) 166 | 167 | draw_line_chart() 168 | 169 | # ------------------------ 画标题 ------------------------ 170 | 171 | draw.text( 172 | (200, 600), 173 | keys, 174 | fill="black", 175 | font=ImageFont.truetype(self.font, 50), 176 | anchor="mm", 177 | ) 178 | 179 | bytes_io = BytesIO() 180 | image.save(bytes_io, format="PNG") 181 | return bytes_io.getvalue() 182 | 183 | 184 | pcr_rank = PcrRank() 185 | -------------------------------------------------------------------------------- /src/plugins/prc_rank_broadcast/readme.md: -------------------------------------------------------------------------------- 1 | # pcr公会排名播报 2 | 3 | ## Env配置项 4 | guild_default_switch 5 | interval 6 | 7 | 其中: 8 | 9 | guild_default_switch是播报开关, 默认为True, 10 | interval是播报间隔,单位为hour, 默认为5 11 | 12 | ## Tips 13 | - 默认加载进去是什么都不播报的,首先使用添加公会指令绑定你的工会在群聊, eg: 添加公会 桜之秋猫猫乐园 14 | - 然后bot会回复你“添加成功, 获取到的信息为xxx”, 如果名字不对的话,“获取公会排名失败, 添加不成功” 15 | - 删除公会 指令同理 16 | - 使用 公会排名 指令会播报当前排名 17 | - 使用 “定时” 或者 “播报” 后面带有 “取消”、“关闭” 或 “开启” 、“打开”指令会开启定时播报, 并且会立即执行一次 18 | - 使用 "清除排名数据" 指令会清除该群组用来画折线图的排名数据, 但是不会清除公会绑定数据 19 | 20 | ## 效果图 21 | ![](./image.png) 22 | ![](./image-1.png) -------------------------------------------------------------------------------- /src/plugins/random_essay/README.MD: -------------------------------------------------------------------------------- 1 | # 随机小作文 -------------------------------------------------------------------------------- /src/plugins/random_essay/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from .handle import essay 3 | 4 | 5 | 6 | on_command("随机小作文",aliases={"发病小作文"}, block=True, handlers=[essay.main]) 7 | 8 | -------------------------------------------------------------------------------- /src/plugins/random_essay/handle.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | from pathlib import Path 4 | 5 | from nonebot.adapters.onebot.v11 import MessageSegment 6 | from nonebot.matcher import Matcher 7 | 8 | from .txt2img import txt_to_img 9 | 10 | 11 | 12 | class Essay: 13 | def __init__(self) -> None: 14 | self.essay_list = json.load(open(Path(__file__).parent / "data.json", "r", encoding="utf-8")) 15 | 16 | async def main(self, matcher: Matcher): 17 | data = random.choice(self.essay_list) 18 | essay_title = data["title"] 19 | await matcher.send(MessageSegment.text(f"标题: {essay_title}") + MessageSegment.image(await txt_to_img.txt_to_img(data["content"]))) 20 | 21 | essay = Essay() 22 | -------------------------------------------------------------------------------- /src/plugins/random_essay/txt2img.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | 3 | from PIL import Image, ImageDraw, ImageFont 4 | 5 | 6 | class TxtToImg: 7 | def __init__(self) -> None: 8 | self.LINE_CHAR_COUNT = 30 * 2 9 | self.CHAR_SIZE = 30 10 | self.TABLE_WIDTH = 4 11 | 12 | 13 | async def line_break(self, line: str) -> str: 14 | """将一行文本按照指定宽度进行换行""" 15 | ret = "" 16 | width = 0 17 | for c in line: 18 | if len(c.encode("utf8")) == 3: # 中文 19 | if self.LINE_CHAR_COUNT == width + 1: # 剩余位置不够一个汉字 20 | width = 2 21 | ret += "\n" + c 22 | else: # 中文宽度加2,注意换行边界 23 | width += 2 24 | ret += c 25 | elif c == "\n": 26 | width = 0 27 | ret += c 28 | elif c == "\t": 29 | space_c = self.TABLE_WIDTH - width % self.TABLE_WIDTH # 已有长度对TABLE_WIDTH取余 30 | ret += " " * space_c 31 | width += space_c 32 | else: 33 | width += 1 34 | ret += c 35 | if width >= self.LINE_CHAR_COUNT: 36 | ret += "\n" 37 | width = 0 38 | return ret if ret.endswith("\n") else ret + "\n" 39 | 40 | 41 | async def txt_to_img(self, text: str, font_size=30, font_path="simsun.ttc") -> bytes: 42 | """将文本转换为图片""" 43 | text = await self.line_break(text) 44 | d_font = ImageFont.truetype(font_path, font_size) 45 | lines = text.count("\n") 46 | image = Image.new( 47 | "L", (self.LINE_CHAR_COUNT * font_size // 2 + 50, font_size * lines + 50), "white" 48 | ) 49 | draw_table = ImageDraw.Draw(im=image) 50 | draw_table.text( 51 | xy=(25, 25), text=text, fill="#000000", font=d_font, spacing=4 52 | ) 53 | new_img = image.convert("RGB") 54 | img_byte = BytesIO() 55 | new_img.save(img_byte, format="PNG") 56 | return img_byte.getvalue() 57 | 58 | # 创建一个实例 59 | txt_to_img = TxtToImg() -------------------------------------------------------------------------------- /src/plugins/status/README.MD: -------------------------------------------------------------------------------- 1 | # 以图片输出服务器状态 2 | 3 | ## 命令头: 4 | {"状态", "status", "服务器状态"} 5 | 6 | 来自对[nonebot-plugin-picstatus](https://github.com/lgc-NB2Dev/nonebot-plugin-picstatus)的模仿 7 | 8 | ## 效果 9 | ![](./show.png) 10 | 11 | ## tips 12 | 可以在插件目录下的img放图片以增加背景图, 但是要注意长宽比例, 否则会变形 -------------------------------------------------------------------------------- /src/plugins/status/__init__.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from typing import Any, Dict 3 | 4 | from nonebot import get_driver, on_command 5 | from nonebot.adapters import Bot 6 | from nonebot.adapters.onebot.v11 import Bot as Onev11Bot 7 | from nonebot.adapters.onebot.v11 import MessageEvent, MessageSegment 8 | from nonebot.message import event_preprocessor 9 | 10 | from .main import status 11 | 12 | driver = get_driver() 13 | 14 | # 优先级10, 不向下阻断 15 | server_status = on_command( 16 | "服务器状态", 17 | aliases={"状态", "status"}, 18 | priority=10, 19 | block=False, 20 | ) 21 | 22 | 23 | @event_preprocessor 24 | async def _(event: MessageEvent) -> None: 25 | """依赖注入消息事件,如果是消息事件则将消息计数器加一""" 26 | bot_id = event.self_id 27 | status.receive_msg[bot_id] += 1 28 | 29 | 30 | @driver.on_bot_connect 31 | async def _(bot: Onev11Bot) -> None: 32 | """依赖注入 Bot 对象,给连接的机器人创建计数器""" 33 | bot_id = int(bot.self_id) 34 | if bot_id not in status.send_msg: 35 | status.send_msg[bot_id] = 0 36 | status.receive_msg[bot_id] = 0 37 | 38 | 39 | @Bot.on_calling_api 40 | async def _(bot: Bot, api: str, data: Dict[str, Any]) -> None: 41 | """钩子函数,如果调用的api是send则将消息计数器加一""" 42 | with contextlib.suppress(Exception): 43 | if "send" in api: 44 | bot_id = int(bot.self_id) 45 | status.send_msg[bot_id] += 1 46 | 47 | 48 | @server_status.handle() 49 | async def _(bot: Onev11Bot) -> None: 50 | """服务器状态命令处理函数""" 51 | login_info: Dict[str, Any] = await bot.get_login_info() 52 | await server_status.send( 53 | MessageSegment.image( 54 | await status.draw_img(login_info["user_id"], login_info["nickname"]) 55 | ) 56 | ) 57 | 58 | 59 | with contextlib.suppress(Exception): 60 | from nonebot.plugin import PluginMetadata 61 | 62 | __plugin_meta__ = PluginMetadata( 63 | name="status", 64 | description="获取服务器状态", 65 | usage=r'命令头:状态 | status | 服务器状态', 66 | type="application", 67 | homepage="https://github.com/Special-Week/Hinata-Bot/tree/main/src/plugins/status", 68 | supported_adapters={"~onebot.v11"}, 69 | extra={ 70 | "author": "Special-Week", 71 | "version": "0.0.1", 72 | "priority": 10, 73 | "block": False, 74 | }, 75 | ) 76 | -------------------------------------------------------------------------------- /src/plugins/status/avatar/g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/avatar/g.png -------------------------------------------------------------------------------- /src/plugins/status/font/微软正黑体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/font/微软正黑体.ttf -------------------------------------------------------------------------------- /src/plugins/status/img/103342810_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/103342810_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/104731716_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/104731716_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/104861996_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/104861996_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/105119539_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/105119539_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/105533027_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/105533027_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/105948004_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/105948004_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/106158310_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/106158310_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/107028114_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/107028114_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/108069094_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/108069094_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/108467250_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/108467250_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/108524848_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/108524848_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/108871862_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/108871862_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/108931630_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/108931630_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/109071980_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/109071980_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/109133709_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/109133709_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/109281594_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/109281594_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/109343095_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/109343095_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/109487724_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/109487724_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/109558844_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/109558844_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/img/96054312_p0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/img/96054312_p0.jpg -------------------------------------------------------------------------------- /src/plugins/status/requirements.txt: -------------------------------------------------------------------------------- 1 | nonebot2>=2.0.0b5 2 | nonebot-adapter-onebot>=2.2.3 3 | pillow>=9.1.1 4 | py-cpuinfo 5 | psutil 6 | httpx -------------------------------------------------------------------------------- /src/plugins/status/show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/status/show.png -------------------------------------------------------------------------------- /src/plugins/super_resolution/README.MD: -------------------------------------------------------------------------------- 1 | # super_resolution 2 | ## 利用本地算力的超分辨率工具(二次元图片) 3 | 实测2c2g4m(腾讯云首单那款)的轻量级服务器能够正常运行 4 | 5 | > 部分~~大量~~代码借鉴~~抄~~于 https://github.com/zhenxun-org/nonebot_plugins_zhenxun_bot/tree/master/super_resolution 6 | 7 | 8 | 重要的依赖: 9 | 10 | torch pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu116 11 | (如果想要能调用显卡的cuda, 请好好安装pytorch, 官网https://pytorch.org/, 验证方法见下) 12 | realesrgan pip install realesrgan 13 | basicsr pip install basicsr 14 | 15 | 验证cuda能否调用, 返回False | True 16 | ```python 17 | import torch 18 | print(torch.cuda.is_available()) 19 | ``` 20 | 21 | 其他依赖项 22 | 23 | imageio pip install imageio 24 | numpy pip install numpy 25 | loguru pip install loguru 26 | PIL pip install pillow 27 | httpx pip install httpx 28 | nonebot2 pip install nonebot2 29 | nonebot.adapters.onebot pip install nonebot-adapter-onebot 30 | 31 | 32 | 33 | 34 | 35 | 导包部分: 36 | ```python 37 | import asyncio 38 | import io 39 | import json 40 | import time 41 | from io import BytesIO 42 | from pathlib import Path 43 | from typing import List, Union 44 | 45 | import imageio 46 | import numpy as np 47 | from basicsr.archs.rrdbnet_arch import RRDBNet 48 | from httpx import AsyncClient 49 | from loguru import logger 50 | from nonebot import on_command 51 | from nonebot.adapters.onebot.v11 import Message, MessageEvent, MessageSegment 52 | from nonebot.params import Arg, Depends 53 | from nonebot.typing import T_State 54 | from PIL import Image as IMG 55 | from PIL import ImageSequence 56 | from realesrgan import RealESRGANer 57 | ``` 58 | 59 | 60 | 61 | 62 | 63 | > 注释已补全, 详细运行逻辑请看注释 64 | 65 | > 还有一件事, 因为GIF是一帧一帧处理的大一点的gif可能要处理很久, 所以我把gif部分注释了, 想用可以手动解除那一部分的注释, 并且把紧跟着的finish注释掉 66 | -------------------------------------------------------------------------------- /src/plugins/super_resolution/RealESRGAN_x4plus_anime_6B.pth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/super_resolution/RealESRGAN_x4plus_anime_6B.pth -------------------------------------------------------------------------------- /src/plugins/super_resolution/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import io 3 | import json 4 | import time 5 | from io import BytesIO 6 | from pathlib import Path 7 | from typing import List, Union 8 | 9 | import imageio 10 | import numpy as np 11 | from basicsr.archs.rrdbnet_arch import RRDBNet 12 | from httpx import AsyncClient 13 | from loguru import logger 14 | from nonebot import on_command 15 | from nonebot.adapters.onebot.v11 import Message, MessageEvent, MessageSegment 16 | from nonebot.params import Arg, Depends 17 | from nonebot.typing import T_State 18 | from PIL import Image as IMG 19 | from PIL import ImageSequence 20 | from realesrgan import RealESRGANer 21 | 22 | # 超分辨率的参数 23 | upsampler = RealESRGANer( 24 | scale=4, 25 | model_path=str(Path(__file__).parent.joinpath("RealESRGAN_x4plus_anime_6B.pth")), 26 | model=RRDBNet( 27 | num_in_ch=3, 28 | num_out_ch=3, 29 | num_feat=64, 30 | num_block=6, 31 | num_grow_ch=32, 32 | scale=4, 33 | ), 34 | tile=100, 35 | tile_pad=10, 36 | pre_pad=0, 37 | half=False, 38 | ) 39 | 40 | 41 | max_size = 3686400 # 分辨率太大内存或者显存吃不消 42 | isRunning = False # 正在运行时不允许再次运行 43 | 44 | # 响应器部分 45 | superResolution = on_command("超分", priority=5, block=True) # 超分辨率响应器 46 | 47 | 48 | def parse_image(key: str): 49 | async def _key_parser(state: T_State, img: Message = Arg(key)): 50 | if not get_message_img(img): 51 | await superResolution.finish("格式错误,超分已取消...") 52 | state[key] = img 53 | 54 | return _key_parser 55 | 56 | 57 | @superResolution.handle() 58 | async def _(event: MessageEvent, state: T_State): 59 | if event.reply: 60 | state["img"] = event.reply.message 61 | if get_message_img(event.json()): 62 | state["img"] = event.message 63 | 64 | 65 | 66 | @superResolution.got( 67 | "img", prompt="请发送需要处理的图片...", parameterless=[Depends(parse_image("img"))] 68 | ) 69 | async def _(img: Message = Arg("img")): 70 | global isRunning 71 | if isRunning: # 正在运行时不允许再次运行 72 | await superResolution.finish("当前有任务正在进行,请稍后再试...") # 结束 73 | isRunning = True # 开始运行 74 | img_url = get_message_img(img)[0] # 获取图片url 75 | await superResolution.send("开始处理图片...") # 发送消息 76 | async with AsyncClient() as client: 77 | try: 78 | re = await client.get(img_url) # 尝试下载图片 79 | except Exception as e: 80 | isRunning = False # 结束 81 | await superResolution.finish(f"下载图片失败...错误信息:{str(e)}") 82 | if re.status_code == 200: # 下载成功 83 | try: 84 | image = IMG.open(BytesIO(re.content)) # 打开图片 85 | except Exception as e: 86 | isRunning = False # 重置isRunning 87 | await superResolution.finish(f"图片打开失败...错误信息{str(e)}") 88 | else: # 下载失败 89 | isRunning = False # 重置isRunning 90 | await superResolution.finish("图片下载失败...") # 结束 91 | is_gif = getattr(image, "is_animated", False) # 判断是否为gif 92 | start = time.time() # 计时开始 93 | image_size = image.size[0] * image.size[1] # 计算图片大小 94 | if image_size > max_size: # 图片太大 95 | isRunning = False # 重置isRunning并且结束响应器 96 | await superResolution.finish( 97 | f"图片尺寸过大!请发送1440p以内即像素数小于 2560*1440=3686400的照片!\n此图片尺寸为:{image.size[0]}×{image.size[1]}={image_size}!" 98 | ) 99 | result = io.BytesIO() # 创建一个BytesIO对象 100 | loop = asyncio.get_event_loop() # 获取事件循环 101 | if is_gif: # 如果是gif 102 | # outputs = [] # 创建一个空列表, 给gif用 103 | # for i in ImageSequence.Iterator(image): 104 | # logger.info(f"一共有{image.n_frames}帧, 正在超第{image.tell()}帧") 105 | # image_array=np.array(i) 106 | # output, _ = await loop.run_in_executor(None, upsampler.enhance, image_array, 2) 107 | # outputs.append(output) 108 | # imageio.mimsave(result, outputs[1:], format='gif', duration=image.info["duration"] / 1000) 109 | isRunning = False # 重置isRunning并且结束响应器 110 | await superResolution.finish("崽种,不准超GIF,给你block了!") 111 | # 因为gif每一帧都要超分, 而且超分的时候会占用大量内存, 所以我把gif的超分功能给注释了, 你们可以自己打开 112 | else: # 如果不是gif 113 | image_array = np.array(image) # 转换为numpy数组 114 | try: 115 | output, _ = await loop.run_in_executor( 116 | None, upsampler.enhance, image_array, 2 117 | ) # 尝试超分 118 | except Exception as e: # 超分失败 119 | isRunning = False # 重置isRunning并且结束响应器 120 | await superResolution.finish(f"超分失败...这是抛出的异常:{str(e)}") 121 | img = IMG.fromarray(output) # 转换为PIL图片 122 | img.save(result, format="PNG") # format: PNG / JPEG 123 | end = time.time() # 计时结束 124 | use_time = round(end - start, 2) # 计算用时 125 | isRunning = False # 重置isRunning 126 | await superResolution.finish( 127 | Message(f"超分完成!处理用时:{use_time}s") + MessageSegment.image(result.getvalue()) 128 | ) # 发送消息 129 | 130 | 131 | 132 | 133 | 134 | def get_message_img(data: Union[str, Message]) -> List[str]: 135 | img_list = [] 136 | if isinstance(data, str): 137 | data = json.loads(data) 138 | img_list.extend( 139 | msg["data"]["url"] for msg in data["message"] if msg["type"] == "image" 140 | ) 141 | else: 142 | img_list.extend(seg.data["url"] for seg in data["image"]) 143 | return img_list 144 | -------------------------------------------------------------------------------- /src/plugins/what_anime/README.MD: -------------------------------------------------------------------------------- 1 | # what_anime 2 | 命令头: {识番}, 后跟图片 3 | 识别图片中的番剧(图一乐, 别认真) -------------------------------------------------------------------------------- /src/plugins/what_anime/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | from typing import List, Union 4 | 5 | from httpx import AsyncClient 6 | from nonebot import on_command 7 | from nonebot.adapters.onebot.v11 import Message, MessageEvent 8 | from nonebot.params import Arg 9 | from nonebot.typing import T_State 10 | 11 | what_anime = on_command("识番", priority=5, block=True) 12 | 13 | 14 | def get_message_img(data: Union[str, Message]) -> List[str]: 15 | """ 16 | 说明: 17 | 获取消息中所有的 图片 的链接 18 | 参数: 19 | :param data: event.json() 20 | """ 21 | img_list = [] 22 | if isinstance(data, str): 23 | data = json.loads(data) 24 | img_list.extend( 25 | msg["data"]["url"] for msg in data["message"] if msg["type"] == "image" 26 | ) 27 | else: 28 | img_list.extend(seg.data["url"] for seg in data["image"]) 29 | return img_list 30 | 31 | 32 | @what_anime.handle() 33 | async def _(event: MessageEvent, state: T_State): 34 | if img_url := get_message_img(event.json()): 35 | state["img_url"] = img_url[0] 36 | 37 | 38 | @what_anime.got("img_url", prompt="虚空识番?来图来图GKD") 39 | async def _(img_url: Message = Arg("img_url")): 40 | img_url = get_message_img(img_url) 41 | if not img_url: 42 | await what_anime.reject_arg("img_url", "发送的必须是图片!") 43 | img_url = img_url[0] 44 | await what_anime.send("开始识别.....") 45 | anime_data_report = await get_anime(img_url) 46 | if anime_data_report: 47 | await what_anime.send(anime_data_report, at_sender=True) 48 | else: 49 | await what_anime.send("没有寻找到该番剧,果咩..", at_sender=True) 50 | 51 | 52 | async def get_anime(anime: str) -> str: 53 | s_time = time.time() 54 | url = f"https://api.trace.moe/search?anilistInfo&url={anime}" 55 | try: 56 | async with AsyncClient() as client: 57 | anime_json = (await client.get(url)).json() 58 | if anime_json["error"]: 59 | return f'访问错误 error:{anime_json["error"]}' 60 | if anime_json == "Error reading imagenull": 61 | return "图像源错误,注意必须是静态图片哦" 62 | repass = "" 63 | # 拿到动漫 中文名 64 | for anime in anime_json["result"][:5]: 65 | synonyms = anime["anilist"]["synonyms"] 66 | for x in synonyms: 67 | _count_ch = sum("\u4e00" <= word <= "\u9fff" for word in x) 68 | if _count_ch > 3: 69 | anime_name = x 70 | break 71 | else: 72 | anime_name = anime["anilist"]["title"]["native"] 73 | episode = anime["episode"] 74 | m, s = divmod(int(anime["from"]), 60) 75 | similarity = anime["similarity"] 76 | putline = "[ {} ][{}][{}:{}] 相似度:{:.2%}".format( 77 | anime_name, episode or "?", m, s, similarity 78 | ) 79 | repass += putline + "\n" 80 | return f"耗时 {int(time.time() - s_time)} 秒\n{repass[:-1]}" 81 | except Exception: 82 | return "发生了奇怪的错误,那就没办法了,再试一次?" 83 | -------------------------------------------------------------------------------- /src/plugins/word_cloud/__init__.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from pathlib import Path 3 | from typing import Dict 4 | 5 | import numpy as np 6 | from nonebot import on_command, on_message 7 | from nonebot.adapters.onebot.v11 import ( 8 | GroupMessageEvent, 9 | Message, 10 | MessageEvent, 11 | MessageSegment, 12 | ) 13 | from nonebot.params import CommandArg 14 | from PIL import Image 15 | from wordcloud import WordCloud 16 | 17 | from .data_sheet import worddata 18 | from .split_tense import split_tool 19 | 20 | wordcloud_handle = on_command("词云", aliases={"wordcloud"}, priority=20, block=False) 21 | 22 | 23 | @wordcloud_handle.handle() 24 | async def wordcloud_handler(event: GroupMessageEvent, args: Message = CommandArg()): 25 | group_id = event.group_id 26 | args = args.extract_plain_text() 27 | if "周" in args: 28 | data = worddata.get_week_data(group_id) 29 | elif "历史" in args: 30 | data = worddata.get_history_data(group_id) 31 | elif "月" in args: 32 | data = worddata.get_month_data(group_id) 33 | else: 34 | data = worddata.get_today_data(group_id) 35 | if not data: 36 | await wordcloud_handle.finish("暂时没有数据呢") 37 | data = dict(sorted(data.items(), key=lambda x: x[1], reverse=True)) 38 | if len(data) > 80: 39 | data = dict(list(data.items())[:80]) 40 | img = await get_wordcloud_img(data) 41 | await wordcloud_handle.finish(MessageSegment.image(img)) 42 | 43 | 44 | async def rule(event: MessageEvent) -> bool: 45 | return ( 46 | isinstance(event, GroupMessageEvent) 47 | and event.message.extract_plain_text() != "" 48 | ) 49 | 50 | 51 | split_word = on_message( 52 | block=False, 53 | rule=rule, 54 | ) 55 | 56 | 57 | @split_word.handle() 58 | async def split_word_handler(event: GroupMessageEvent): 59 | text = event.get_plaintext() 60 | data = await split_tool.split_tense(text) 61 | if data: 62 | worddata.insert_data(event.group_id, data) 63 | 64 | 65 | async def get_wordcloud_img(data: Dict[str, int]) -> bytes: 66 | wordcloud = WordCloud( 67 | width=1200, 68 | height=1200, 69 | background_color="white", 70 | font_path=str(Path(__file__).parent / "fonts" / "SIMYOU.TTF"), 71 | ).generate_from_frequencies(data) 72 | image_array = np.array(wordcloud) 73 | pillow_image = Image.fromarray(image_array) 74 | pillow_image = pillow_image.convert("RGB") 75 | bytes_io = BytesIO() 76 | pillow_image.save(bytes_io, format="PNG") 77 | return bytes_io.getvalue() 78 | -------------------------------------------------------------------------------- /src/plugins/word_cloud/data_sheet.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | from typing import Dict, List, Tuple 4 | 5 | from sqlalchemy import ( 6 | Column, 7 | Engine, 8 | Integer, 9 | MetaData, 10 | String, 11 | Table, 12 | create_engine, 13 | inspect, 14 | text, 15 | ) 16 | from sqlalchemy.orm import sessionmaker 17 | 18 | 19 | class WordData: 20 | """sqlalchemy操作""" 21 | 22 | def __init__(self): 23 | DATA_PATH = "data/wordcloud" 24 | if not os.path.exists("data"): 25 | os.mkdir("data") 26 | if not os.path.exists(DATA_PATH): 27 | os.mkdir(DATA_PATH) 28 | self.engine: Engine = create_engine( 29 | f"sqlite:///{DATA_PATH}/words.db" 30 | ) # 创建数据库引擎 31 | self.session = sessionmaker(self.engine) # 创建会话 32 | self.metadata = MetaData() # 创建元数据 33 | self.all_table_name: List[str] = inspect( 34 | self.engine 35 | ).get_table_names() # 获取所有表名 36 | 37 | self.switch_table() # 切换表 38 | 39 | @staticmethod 40 | def get_today() -> Tuple[str, int]: 41 | """获取当前年月日格式: [2023-02, 30]""" 42 | return ( 43 | time.strftime("%Y-%m", time.localtime(time.time())), 44 | int(time.strftime("%d", time.localtime(time.time()))), 45 | ) 46 | 47 | def switch_table(self) -> None: 48 | """切换表""" 49 | current_month: str = self.get_today()[0] 50 | self.custom_table: Table = Table( 51 | current_month, 52 | self.metadata, 53 | Column("id", Integer, primary_key=True), 54 | Column("date", Integer, nullable=False, index=True), 55 | Column("group_id", Integer, nullable=False, index=True), 56 | Column("word", String, nullable=False), 57 | Column("appeal", Integer, nullable=False), 58 | extend_existing=True, 59 | ) 60 | if current_month not in self.all_table_name: # 如果当前月份不在表名中 61 | self.custom_table.create(self.engine) 62 | self.all_table_name.append(self.custom_table.name) 63 | 64 | def insert_data(self, group_id: int, data: Dict[str, int]): 65 | """插入数据""" 66 | month, day = self.get_today() # 获取当前月份和日期 67 | if month not in self.all_table_name: 68 | self.switch_table() 69 | with self.session() as s: 70 | # 如果这一天群id没有数据, 则直接插入数据 71 | if ( 72 | not s.query(self.custom_table) 73 | .filter( 74 | self.custom_table.c.date == day, 75 | self.custom_table.c.group_id == group_id, 76 | ) 77 | .all() 78 | ): 79 | for k, v in data.items(): 80 | s.execute( 81 | self.custom_table.insert(), 82 | [{"date": day, "group_id": group_id, "word": k, "appeal": v}], 83 | ) 84 | # 如果这一天群id有数据, 则分情况插入数据 85 | else: 86 | for k, v in data.items(): 87 | # 如果这一天群id有数据, 但是没有这个词, 则直接插入数据 88 | if ( 89 | not s.query(self.custom_table) 90 | .filter( 91 | self.custom_table.c.date == day, 92 | self.custom_table.c.group_id == group_id, 93 | self.custom_table.c.word == k, 94 | ) 95 | .all() 96 | ): 97 | s.execute( 98 | self.custom_table.insert(), 99 | [ 100 | { 101 | "date": day, 102 | "group_id": group_id, 103 | "word": k, 104 | "appeal": v, 105 | } 106 | ], 107 | ) 108 | # 如果这一天群id有数据, 也有这个词, 则更新数据 109 | else: 110 | s.execute( 111 | self.custom_table.update() 112 | .where( 113 | self.custom_table.c.date == day, 114 | self.custom_table.c.group_id == group_id, 115 | self.custom_table.c.word == k, 116 | ) 117 | .values(appeal=self.custom_table.c.appeal + v) 118 | ) 119 | s.commit() 120 | 121 | def get_today_data(self, group_id: int) -> Dict[str, int]: 122 | """获取今日数据""" 123 | day = self.get_today()[1] 124 | with self.session() as s: 125 | data = ( 126 | s.query(self.custom_table) 127 | .filter( 128 | self.custom_table.c.date == day, 129 | self.custom_table.c.group_id == group_id, 130 | ) 131 | .all() 132 | ) 133 | return {d.word: d.appeal for d in data} 134 | 135 | def get_history_data(self, group_id: int) -> Dict[str, int]: 136 | """获取历史数据""" 137 | target = {} 138 | with self.session() as s: 139 | # 遍历所有表 140 | for table in self.all_table_name: 141 | data = s.execute( 142 | text(f"select word,appeal from '{table}' where group_id={group_id}") 143 | ) 144 | # 遍历每个表的数据, target中没有就创造一个值为零, 然后加上这个词的appeal 145 | for d in data: 146 | if d[0] not in target: 147 | target[d[0]] = 0 148 | target[d[0]] += d[1] 149 | return target 150 | 151 | def get_week_data(self, group_id: int) -> Dict[str, int]: 152 | """获取一周数据""" 153 | day = self.get_today()[1] 154 | since = day - 7 if day > 8 else day 155 | target = {} 156 | with self.session() as s: 157 | data = ( 158 | s.query(self.custom_table) 159 | .filter( 160 | self.custom_table.c.date >= since, 161 | self.custom_table.c.group_id == group_id, 162 | ) 163 | .all() 164 | ) 165 | for d in data: 166 | if d.word not in target: 167 | target[d.word] = 0 168 | target[d.word] += d.appeal 169 | return target 170 | 171 | def get_month_data(self, group_id: int) -> Dict[str, int]: 172 | """获取一月数据""" 173 | target = {} 174 | with self.session() as s: 175 | data = ( 176 | s.query(self.custom_table) 177 | .filter( 178 | self.custom_table.c.group_id == group_id, 179 | ) 180 | .all() 181 | ) 182 | for d in data: 183 | if d.word not in target: 184 | target[d.word] = 0 185 | target[d.word] += d.appeal 186 | return target 187 | 188 | 189 | worddata = WordData() 190 | -------------------------------------------------------------------------------- /src/plugins/word_cloud/fonts/SIMYOU.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/word_cloud/fonts/SIMYOU.TTF -------------------------------------------------------------------------------- /src/plugins/word_cloud/readme.md: -------------------------------------------------------------------------------- 1 | # 词云插件 2 | 3 | 4 | ```python 5 | on_command("词云", aliases={"wordcloud"}, priority=20, block=False) 6 | ``` 7 | 8 | 后面可以接: "本周" 代表获取这一周 9 | 接: "本月" 代表获取本月词云 10 | 接: "历史" 代表获取历史词云 11 | 12 | 13 | ## 安装依赖 14 | ```bash 15 | pip install -r requirements.txt 16 | spacy download en_core_web_sm 17 | ``` -------------------------------------------------------------------------------- /src/plugins/word_cloud/requirements.txt: -------------------------------------------------------------------------------- 1 | pillow>=9.5.0 2 | sqlalchemy 3 | spacy 4 | jieba 5 | wordcloud 6 | numpy 7 | nonebot2 8 | nonebot-adapter-onebot 9 | -------------------------------------------------------------------------------- /src/plugins/word_cloud/split_tense.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Dict 3 | 4 | import jieba.posseg as pseg 5 | import spacy 6 | 7 | 8 | class SplitTense: 9 | def __init__(self) -> None: 10 | self.nlp = spacy.load("en_core_web_sm") 11 | self.chinese_pattern = re.compile(r"[\u4e00-\u9fa5]") 12 | 13 | async def split_tense(self, text: str) -> Dict[str, int]: 14 | """分析text的词性""" 15 | target = [] 16 | words = pseg.cut(text) 17 | target += [ 18 | word.word 19 | for word in words 20 | if (word.flag.startswith("n") or word.flag.startswith("v")) 21 | and len(word.word) > 1 22 | ] 23 | cleaned_text = await self.clean_text(text) 24 | doc = self.nlp(cleaned_text) 25 | target += [token.text for token in doc if token.pos_ in ["NOUN", "VERB"] and len(token.text) > 1] 26 | data = {} 27 | for i in target: 28 | if i not in data: 29 | data[i] = 0 30 | data[i] += 1 31 | return data 32 | 33 | async def clean_text(self, text: str) -> str: 34 | """去除text的汉字""" 35 | return re.sub(self.chinese_pattern, "", text) 36 | 37 | split_tool = SplitTense() -------------------------------------------------------------------------------- /src/plugins/wordle_help/__init__.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | from nonebot import on_regex 4 | 5 | from .handle import wordle_help 6 | 7 | # 注册正则表达式, 优先级为10, 阻断式, 处理函数为wordle_help.main 8 | on_regex( 9 | r'^(?=.*[a-zA-Z])(?=.*_)[a-zA-Z_]+(#(?=[a-zA-Z]+$)[a-zA-Z]*)?$', 10 | priority=10, 11 | block=True, 12 | handlers=[wordle_help.main] 13 | ) 14 | 15 | with contextlib.suppress(Exception): 16 | from nonebot.plugin import PluginMetadata 17 | __plugin_meta__ = PluginMetadata( 18 | name="wordle_help", 19 | description="wordle游戏小助手", 20 | usage=r'^(?=.*[a-zA-Z])(?=.*_)[a-zA-Z_]+(#(?=[a-zA-Z]+$)[a-zA-Z]*)?$', 21 | type="application", 22 | homepage="https://github.com/Special-Week/Hinata-Bot/tree/main/src/plugins/wordle_help", 23 | supported_adapters={"~onebot.v11"}, 24 | extra={ 25 | 'author': 'Special-Week', 26 | 'version': '0.0.1', 27 | 'priority': 10, 28 | } 29 | ) 30 | -------------------------------------------------------------------------------- /src/plugins/wordle_help/handle.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import List 4 | 5 | from nonebot.adapters.onebot.v11 import MessageEvent 6 | from nonebot.matcher import Matcher 7 | 8 | 9 | class WordleHelp: 10 | def __init__(self) -> None: 11 | """初始化, 读取单词列表""" 12 | plugin_path = Path(__file__).parent 13 | with open(plugin_path / "data.json",'r', encoding='utf-8') as f: 14 | self.words: List[str] = json.load(f) 15 | 16 | async def get_matching_words(self, target: str) -> List[str]: 17 | """获取匹配的单词""" 18 | return [word for word in self.words if len(word) == len(target) and all(target[i] == "_" or word[i] == target[i] for i in range(len(word)))] 19 | 20 | async def main( 21 | self, 22 | matcher: Matcher, 23 | event: MessageEvent 24 | ) -> None: 25 | """处理消息""" 26 | target = event.get_message().__str__().strip().split("#") # 按照#分割成列表, 理论上最多两个元素, 前者为目标单词, 后者排除的字母 27 | matching_words = await self.get_matching_words(target[0]) # 先匹配单词 28 | if len(target) == 2: # 如果有排除的字母, 则再次筛选 29 | matching_words = [word for word in matching_words if not set(word) & set(target[1])] 30 | 31 | if len(matching_words) == 0: # 如果没有匹配到单词, 则返回 32 | await matcher.send("没有匹配到单词捏") 33 | elif len(matching_words) > 50: # 如果匹配到的单词太多, 则返回 34 | await matcher.send("匹配到的单词太多了(>50), 建议您缩小一下范围") 35 | else: # 返回匹配到的单词 36 | await matcher.send( 37 | "以下是匹配到的单词:\n"+ 38 | "\n".join(matching_words) 39 | ) 40 | 41 | wordle_help = WordleHelp() 42 | -------------------------------------------------------------------------------- /src/plugins/wordle_help/preview01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/wordle_help/preview01.jpg -------------------------------------------------------------------------------- /src/plugins/wordle_help/preview02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Special-Week/Hinata-Bot/27cf1effe2a44791a8466700ffbbd7bb6605df14/src/plugins/wordle_help/preview02.jpg -------------------------------------------------------------------------------- /src/plugins/wordle_help/readme.md: -------------------------------------------------------------------------------- 1 | # wordle小游戏的帮助工具 2 | 搭配插件 https://github.com/noneplugin/nonebot-plugin-wordle 一起食用效果最佳 3 | 4 | 5 | ### 安装方式: 6 | 7 | nb plugin install nonebot-plugin-wordle-help 8 | pip install nonebot-plugin-wordle-help 9 | 10 | ## 功能预览 11 | ![预览](./preview02.jpg) 12 | ##### ------------------------------------------分割线----------------------------------- 13 | ![预览](./preview01.jpg) 14 | 15 | ## 命令 16 | r'^(?=.*[a-zA-Z])(?=.*_)[a-zA-Z_]+(#(?=[a-zA-Z]+$)[a-zA-Z]*)?$' 17 | 此插件通过以上的正则表达式进行响应器匹配. 18 | 19 | ## 命令说明 20 | 21 | 匹配由下划线和英文字母的消息, 并且需要至少存在一个英文字母与下划线. 22 | 23 | 后面有一个可选的井号, 在有井号时前面一半匹配规则如上, 后面则是一串英文字母. 24 | 25 | 前面由下划线和英文字母组成的部分, 下划线代表未知字母, 英文字母代表已知字母. 26 | 27 | 井号后的内容为你想找的单词中不存在的字母, 用于筛选单词. 28 | --------------------------------------------------------------------------------