├── .gitignore ├── LICENSE ├── README.md ├── bot.py ├── format.sh ├── plugins ├── bot_5000choyen.py ├── bot_ApologizeToGirlfriend.py ├── bot_TaoShow.py ├── bot_WhatDoYouWantToEat.py ├── bot_amusing_language.py ├── bot_autoRepeat.py ├── bot_autoRevoke.py ├── bot_baidu_ocr.py ├── bot_bili_subscriber │ ├── __init__.py │ ├── api.py │ ├── db.py │ └── utils.py ├── bot_bnhhsh.py ├── bot_cleanGroupZombie.py ├── bot_cockroach.py ├── bot_corona_virus.py ├── bot_custom_image │ ├── __init__.py │ ├── config.py │ ├── core.py │ └── images │ │ ├── 02 │ │ ├── 02.jpg │ │ └── config.ini │ │ ├── akai │ │ ├── akai.jpg │ │ └── config.ini │ │ ├── alice │ │ ├── alice.jpg │ │ └── config.ini │ │ ├── anzhongguancha │ │ ├── anzhongguancha.jpg │ │ └── config.ini │ │ ├── aqua │ │ ├── aqua.jpg │ │ └── config.ini │ │ ├── chouyou │ │ ├── chouyou.jpg │ │ └── config.ini │ │ ├── fbk1 │ │ ├── config.ini │ │ └── fbk1.jpg │ │ ├── fbk2 │ │ ├── config.ini │ │ └── fbk2.jpg │ │ ├── gbf │ │ ├── config.ini │ │ └── gbf.jpg │ │ ├── haoxue │ │ ├── config.ini │ │ └── haoxue.jpg │ │ ├── heishou │ │ ├── config.ini │ │ └── heishou.jpg │ │ ├── heishou_four │ │ ├── config.ini │ │ └── heishou_four.jpg │ │ ├── initial │ │ ├── config.ini │ │ └── initial.jpg │ │ ├── jojo │ │ ├── config.ini │ │ └── jojo.jpg │ │ ├── jumozhanjiang │ │ ├── config.ini │ │ └── jumozhanjiang.jpg │ │ ├── jupai │ │ ├── config.ini │ │ └── jupai.jpg │ │ ├── kaguranana │ │ ├── config.ini │ │ └── kaguranana.jpg │ │ ├── kaguranana4 │ │ ├── config.ini │ │ └── kaguranana4.jpg │ │ ├── kaguranana5 │ │ ├── config.ini │ │ └── kaguranana5.jpg │ │ ├── kaguranana_1 │ │ ├── config.ini │ │ └── kaguranana_1.jpg │ │ ├── kaguranana_2 │ │ ├── config.ini │ │ └── kaguranana_2.jpg │ │ ├── kasumi │ │ ├── config.ini │ │ └── kasumi.jpg │ │ ├── katsuna │ │ ├── config.ini │ │ └── katsuna.jpg │ │ ├── keduoli │ │ ├── config.ini │ │ └── keduoli.jpg │ │ ├── kiyohime │ │ ├── config.ini │ │ └── kiyohime.jpg │ │ ├── kmmm │ │ ├── config.ini │ │ └── kmmm.jpg │ │ ├── ksm │ │ ├── config.ini │ │ └── ksm.jpg │ │ ├── lxy1 │ │ ├── config.ini │ │ └── lxy1.jpg │ │ ├── lxy2 │ │ ├── config.ini │ │ └── lxy2.jpg │ │ ├── mao │ │ ├── config.ini │ │ └── mao.jpg │ │ ├── maotouying │ │ ├── config.ini │ │ └── maotouying.jpg │ │ ├── matsuri │ │ ├── config.ini │ │ └── matsuri.jpg │ │ ├── matsuri_1 │ │ ├── config.ini │ │ └── matsuri_1.jpg │ │ ├── mayi │ │ ├── config.ini │ │ └── mayi.jpg │ │ ├── mea │ │ ├── config.ini │ │ └── mea.jpg │ │ ├── mengheqianli │ │ ├── config.ini │ │ └── mengheqianli.jpg │ │ ├── mingzhongguancha │ │ ├── config.ini │ │ └── mingzhongguancha.jpg │ │ ├── moe │ │ ├── config.ini │ │ └── moe.jpg │ │ ├── names.json │ │ ├── panda_shuai │ │ ├── config.ini │ │ └── panda_shuai.jpg │ │ ├── panda_tutou │ │ ├── config.ini │ │ └── panda_tutou.jpg │ │ ├── peko │ │ ├── config.ini │ │ └── peko.jpg │ │ ├── peko2 │ │ ├── config.ini │ │ └── peko2.jpg │ │ ├── qichuang │ │ ├── config.ini │ │ └── qichuang.jpg │ │ ├── qqdata │ │ └── .gitignore │ │ ├── serena │ │ ├── config.ini │ │ └── serena.jpg │ │ ├── xianbei │ │ ├── config.ini │ │ └── xianbei.jpg │ │ ├── xingjie │ │ ├── config.ini │ │ └── xingjie.jpg │ │ └── xuexiaoban │ │ ├── config.ini │ │ └── xuexiaoban.jpg ├── bot_dl_douyin.py ├── bot_generateQrcode.py ├── bot_genshin_calendar │ ├── __init__.py │ ├── draw.py │ ├── event.py │ └── generate.py ├── bot_github_thumbnail.py ├── bot_jikipedia.py ├── bot_joinGroupAudit.py ├── bot_juejuezi.py ├── bot_kiss_gif │ ├── __init__.py │ ├── frames.zip │ └── module.py ├── bot_meiriyiwen.py ├── bot_moechat.py ├── bot_moegirl.py ├── bot_niubi.py ├── bot_peep.py ├── bot_phlogo.py ├── bot_replay.py ├── bot_rip_avatar.py ├── bot_rua │ ├── __init__.py │ └── frames │ │ ├── hand-1.png │ │ ├── hand-2.png │ │ ├── hand-3.png │ │ ├── hand-4.png │ │ └── hand-5.png ├── bot_russian_turntable.py ├── bot_sbqb.py ├── bot_sese.py ├── bot_steam.py ├── bot_stopRevoke.py ├── bot_sysinfo.py ├── bot_toPinYin.py ├── bot_versailles.py ├── bot_weather.py ├── bot_weirdfonts │ ├── __init__.py │ └── internal.py ├── bot_whatis.py ├── bot_yesno.py └── readme.md └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | __pycache__ 3 | 4 | REMOVED_PLUGINS 5 | botoy.json 6 | botoy-cache 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OPQ Open Source Community 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # botoy-plugins 2 | 3 | # 配置 4 | 5 | 新建`botoy.json`文件进行配置 6 | 7 | ```json 8 | { 9 | "qq": "机器人qq号", 10 | "master": "你的QQ号" 11 | } 12 | ``` 13 | 14 | # 运行 15 | 16 | 无特殊说明,说明插件可以直接运行 17 | 18 | ```shell 19 | pip install -r requirements.txt 20 | botoy run 21 | ``` 22 | 23 | # LICENSE 24 | 25 | MIT 26 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from botoy import AsyncBotoy, S, jconfig 4 | from botoy.decorators import equal_content, ignore_botself 5 | 6 | qq = jconfig.qq 7 | bot = AsyncBotoy(qq=qq, use_plugins=True) 8 | 9 | 10 | @bot.group_context_use 11 | def group_mid(ctx): 12 | ctx.master = jconfig.master 13 | return ctx 14 | 15 | 16 | @bot.on_group_msg 17 | @ignore_botself 18 | @equal_content("帮助") 19 | def help(_): 20 | S.text(bot.plugMgr.help) 21 | 22 | 23 | if __name__ == "__main__": 24 | asyncio.run(bot.run()) 25 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | black . && isort . -m3 2 | -------------------------------------------------------------------------------- /plugins/bot_5000choyen.py: -------------------------------------------------------------------------------- 1 | """5000兆元字体风格图片生成 格式:5000 {上部文字} {下部文字} {下部文字向右的额外偏移量(可选)}""" 2 | import base64 3 | 4 | import httpx 5 | from botoy import GroupMsg, jconfig 6 | from botoy.collection import MsgTypes 7 | from botoy.decorators import ignore_botself, startswith, these_msgtypes 8 | from botoy.sugar import Picture 9 | 10 | API = jconfig.get_jconfig("5000choyen_api") or "http://127.0.0.1:4000/api/v1/gen" 11 | 12 | 13 | def gen5000(top="", bottom="", offset=""): 14 | try: 15 | resp = httpx.get( 16 | API, params=dict(top=top, bottom=bottom, offset=offset), timeout=20 17 | ) 18 | resp.raise_for_status() 19 | except Exception: 20 | pass 21 | else: 22 | if "image" in resp.headers["Content-Type"]: 23 | return base64.b64encode(resp.content).decode() 24 | return None 25 | 26 | 27 | @ignore_botself 28 | @these_msgtypes(MsgTypes.TextMsg) 29 | @startswith("5000 ") 30 | def receive_group_msg(ctx: GroupMsg): 31 | # top bottom offset 32 | args = ctx.Content[4:].strip().split(" ") 33 | if not args: 34 | return 35 | if len(args) == 1: 36 | args.extend(["", ""]) 37 | elif len(args) == 2: 38 | args.append("") 39 | elif len(args) > 3: 40 | args = args[:3] 41 | 42 | pic_base64 = gen5000(*args) 43 | 44 | if pic_base64 is not None: 45 | Picture(pic_base64=pic_base64) 46 | -------------------------------------------------------------------------------- /plugins/bot_ApologizeToGirlfriend.py: -------------------------------------------------------------------------------- 1 | """生成给女朋友道歉信. 格式:给女朋友道歉{名}|{事情} 或 直接发送 给女朋友道歉 (接受私聊)""" 2 | import random 3 | 4 | from botoy.collection import MsgTypes 5 | from botoy.decorators import ignore_botself, startswith, these_msgtypes 6 | from botoy.session import SessionHandler, ctx, session 7 | 8 | nickname = [ 9 | "宝贝name,", 10 | "亲爱的name,", 11 | "可爱的name,", 12 | "我最爱的name,", 13 | "name小可爱,", 14 | "name小仙女,", 15 | "name小甜心,", 16 | ] 17 | 18 | apology = [ 19 | "event的事是我错了。", 20 | "我已经为event深刻反省了。", 21 | "还在为event的事生气吗?", 22 | "event的事的确是我不对。", 23 | "我千不该万不该,最不该event。", 24 | "我保证下次一定不会event。", 25 | "我不该event的。", 26 | "我为event的事诚挚的道歉。", 27 | ] 28 | 29 | apology_text = [ 30 | "我知道你很生气。而且你每次生气我都好害怕。理解我,好么?原谅我,好么?", 31 | "气球太饱会爆,固然你的皮厚,可是也不能撑过久啊,放点气吧,哪怕是从上面。", 32 | "宝贝抱歉,刚刚我太冲动了,无意中伤到了你,请你原谅!这样的我你还爱我吗?", 33 | "你是我的,谁都抢不走,我就是这么霸道,我是你的,谁都领不走,我就是这么死心。", 34 | "我只想说:我不是有意要气你的,只因为我爱你,我不想多说只求你能原谅我,我爱你!", 35 | "男人如果得不到女人的谅解。就算膝下有黄金又能怎样。我跪下来表示百分之百的赔罪!", 36 | "原以为彼此相爱平淡,谁知转眼风云突变;方知昨日难求,放出爱的信鸽,我很想念你!", 37 | "我只想说:我不是有意要气你的,只因为我爱你,我不想多说只求你能原谅我!", 38 | "霓虹在夜晚将天空撕裂,悔恨的泪水在眼前模糊,也许这是注定的错!亲爱的,我想你了。", 39 | "想编个幽默的故事,告诉你那个开了四年的玩笑,却又怕弄湿了干干净净的秋季,请原谅我。", 40 | "我已经不再生你的气了,像我这样胸襟开阔、德高望重的人肯定会原谅,你还在生我的气吗!", 41 | "你是知道我一直很用心的爱你,我不想欠你有太多,如果有一天我真的走,你一定要原谅我。", 42 | "真的好想你呀,想你甜甜的笑,柔柔的话,和你美丽的脸!喂?喂?别再生气了吧!我爱你!", 43 | "对不起,我为我的粗鲁深表歉意。因为真的太想见你,心里从来没有那么在乎。原谅我好吗?", 44 | "会用行动来说明一切,不会再让你感到伤心与失望,在这我真诚的请你原谅,永远爱你的人!", 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 | NICKNAME_LEN = len(nickname) 72 | APOLOGY = len(apology) 73 | APOLOGY_TEXT = len(apology_text) 74 | 75 | 76 | def generate(name, event): 77 | bullshit = str() 78 | bullshit += nickname[random.randint(0, NICKNAME_LEN - 1)] 79 | bullshit += apology[random.randint(0, APOLOGY - 1)] 80 | while len(bullshit) <= 1000: 81 | randNum = random.randint(0, 15) 82 | if randNum % 4 == 0: 83 | bullshit += nickname[random.randint(0, NICKNAME_LEN - 1)] 84 | bullshit += apology[random.randint(0, APOLOGY - 1)] 85 | else: 86 | bullshit += apology_text[random.randint(0, APOLOGY_TEXT - 1)] 87 | bullshit = bullshit.replace("name", name) 88 | bullshit = bullshit.replace("event", event) 89 | 90 | bullshit = "\n".join([bullshit[i : i + 50] for i in range(0, len(bullshit), 50)]) 91 | return f"关于{event}致最亲爱的{name}的道歉信\n\n{bullshit}" 92 | 93 | 94 | honey = ( 95 | SessionHandler( 96 | ignore_botself, 97 | these_msgtypes(MsgTypes.TextMsg), 98 | startswith("给女朋友道歉"), 99 | ) 100 | .receive_group_msg() 101 | .receive_friend_msg() 102 | ) 103 | 104 | 105 | @honey.handle 106 | def _(): 107 | options = ctx.Content[6:].split("|") 108 | if len(options) == 2: 109 | honey.finish(generate(*options)) 110 | 111 | name = session.want("name", "你想给你女朋友的称呼是啥?", timeout=60) 112 | if name is None: 113 | honey.finish() 114 | 115 | event = session.want("event", "为了啥事道歉呢?", timeout=60) 116 | if event is None: 117 | honey.finish() 118 | 119 | honey.finish(generate(name, event)) 120 | -------------------------------------------------------------------------------- /plugins/bot_TaoShow.py: -------------------------------------------------------------------------------- 1 | """买家秀:发送 来点买家秀或买家秀即可""" 2 | import httpx 3 | from botoy import GroupMsg, S 4 | from botoy.decorators import ignore_botself 5 | 6 | 7 | @ignore_botself 8 | def receive_group_msg(ctx: GroupMsg): 9 | if ctx.Content in ("来点买家秀", "买家秀"): 10 | try: 11 | data = httpx.get( 12 | "https://api.vvhan.com/api/tao?type=json", timeout=11 13 | ).json() 14 | title, pic = data["title"], data["pic"] 15 | except Exception: 16 | pass 17 | else: 18 | S.image(pic, title) 19 | -------------------------------------------------------------------------------- /plugins/bot_WhatDoYouWantToEat.py: -------------------------------------------------------------------------------- 1 | """吃啥?我帮你选! 格式:今天吃啥?早上吃啥?晚餐吃什么?...""" 2 | import os 3 | import random 4 | import re 5 | 6 | from botoy import GroupMsg 7 | from botoy.collection import MsgTypes 8 | from botoy.contrib import get_cache_dir 9 | from botoy.decorators import ignore_botself, on_regexp, these_msgtypes 10 | from botoy.sugar import Picture 11 | 12 | recipe_dir = get_cache_dir("what do you want to eat") 13 | 14 | 15 | @ignore_botself 16 | @these_msgtypes(MsgTypes.TextMsg) 17 | @on_regexp(r"(今天|[早中午晚][上饭餐]|夜宵)吃(什么|啥|点啥)") 18 | def receive_group_msg(ctx: GroupMsg): 19 | time = ctx._match.group(1).strip() 20 | recipes = os.listdir(recipe_dir) 21 | if recipes: 22 | choose = recipe_dir / random.choice(recipes) 23 | Picture( 24 | pic_path=choose, 25 | text=f"建议你{time}吃: {choose.name[:-4]}", 26 | ) 27 | 28 | 29 | if __name__ == "__main__": 30 | import asyncio 31 | 32 | import aiofiles 33 | import httpx 34 | 35 | async def fetch_one(client, title, cover): 36 | print("downloading...", title, cover) 37 | path = recipe_dir / f"{title}.jpg" 38 | if path.exists(): 39 | print(title, "already exists") 40 | return 41 | resp = await client.get(cover) 42 | async with aiofiles.open(path, "wb") as f: 43 | await f.write(resp.content) 44 | print("download", title, "successfully") 45 | 46 | async def main(): 47 | page = 0 48 | async with httpx.AsyncClient( 49 | headers={ 50 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.56" 51 | }, 52 | ) as client: 53 | while True: 54 | resp = await client.get( 55 | f"https://home.meishichina.com/ajax/ajax.php?ac=recipe&op=getMoreDiffStateRecipeList&classid=59&orderby=tag&page={page}", 56 | ) 57 | page += 1 58 | if resp.json()["error"] == 0: 59 | tasks = [ 60 | fetch_one(client, recipe["title"], recipe["cover"]) 61 | for recipe in resp.json()["data"] 62 | ] 63 | await asyncio.gather(*tasks) 64 | else: 65 | break 66 | 67 | asyncio.run(main()) 68 | -------------------------------------------------------------------------------- /plugins/bot_amusing_language.py: -------------------------------------------------------------------------------- 1 | """生成和解码瞎叫语言 2 | 生成: 瞎叫+{文字内容} 3 | 解码: 瞎叫啥+{瞎叫字符串} 4 | """ 5 | import random 6 | from base64 import b64decode, b64encode 7 | 8 | from botoy import GroupMsg, jconfig 9 | from botoy.decorators import ignore_botself 10 | from botoy.sugar import Text 11 | 12 | b64_chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/=" 13 | sep, zero_chars = ( 14 | "\u200b", 15 | "\u200c\u200d\u0300\u0301\u0302\u0303\u0304\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311", 16 | ) 17 | 18 | matrices = jconfig.amusing_language_matrices or ["唱", "跳", "rap", "篮球", "鸡你太美"] 19 | 20 | zeros = [] 21 | loop_num = 0 22 | while len(zeros) < len(b64_chars): 23 | prefix = zero_chars[:loop_num] 24 | for zero_char in zero_chars[loop_num:]: 25 | if len(zeros) == len(b64_chars): 26 | break 27 | zeros.append(f"{prefix}{zero_char}") 28 | loop_num += 1 29 | 30 | table = dict(zip(b64_chars, zeros)) 31 | reversd_table = {v: k for k, v in table.items()} 32 | 33 | 34 | def encode(string: str) -> str: 35 | # 随机插入语言字母表 36 | # WARNING: 随机插入其实有点迷惑行为??? 37 | s = list(map(lambda char: table[char], b64encode(string.encode()).decode())) 38 | for _ in range(random.randint(2, len(matrices))): 39 | s.insert(random.randint(0, len(zeros) - 1), random.choice(matrices)) 40 | return sep.join(s) 41 | 42 | 43 | def decode(amusing_string: str) -> str: 44 | try: 45 | return b64decode( 46 | "".join( 47 | list( 48 | map( 49 | lambda x: reversd_table[x], 50 | [s for s in amusing_string.split(sep) if s not in matrices], 51 | ) 52 | ) 53 | ) 54 | ).decode() 55 | except Exception: 56 | return "" 57 | 58 | 59 | @ignore_botself 60 | def receive_group_msg(ctx: GroupMsg): 61 | if ctx.Content.startswith("瞎叫啥"): 62 | msg = decode(ctx.Content[3:]) 63 | if msg: 64 | Text(msg) 65 | elif ctx.Content.startswith("瞎叫"): 66 | Text(encode(ctx.Content[2:])) 67 | -------------------------------------------------------------------------------- /plugins/bot_autoRepeat.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from queue import deque # type: ignore 3 | from threading import Lock 4 | 5 | from botoy import GroupMsg 6 | from botoy import decorators as deco 7 | from botoy.collection import MsgTypes 8 | from botoy.parser import group as gp 9 | from botoy.sugar import Picture, Text 10 | 11 | # 自动消息加一功能, 支持文字消息和图片消息 12 | 13 | # 文本消息存文本,图片消息存MD5 14 | class RepeatDeque(deque): 15 | def __init__(self, *args, **kwargs): 16 | super().__init__(maxlen=3, *args, **kwargs) 17 | self.last = None 18 | self.lock = Lock() 19 | with self.lock: 20 | self.refresh() 21 | 22 | def refresh(self): 23 | for i in range(self.maxlen): 24 | self.append(i) 25 | 26 | def should_repeat(self, item): 27 | ret = False 28 | with self.lock: 29 | if item != self.last: 30 | self.last = None 31 | 32 | self.append(item) 33 | if len(set(self)) == 1: 34 | self.refresh() 35 | if item != self.last: 36 | ret = True 37 | self.last = item 38 | return ret 39 | 40 | 41 | text_deque_dict = defaultdict(RepeatDeque) 42 | pic_deque_dict = defaultdict(RepeatDeque) 43 | 44 | 45 | @deco.ignore_botself 46 | @deco.these_msgtypes(MsgTypes.TextMsg, MsgTypes.PicMsg) 47 | def receive_group_msg(ctx: GroupMsg): 48 | if ctx.MsgType == MsgTypes.TextMsg: 49 | text = ctx.Content 50 | if text_deque_dict[ctx.FromGroupId].should_repeat(text): 51 | Text(text) 52 | elif ctx.MsgType == MsgTypes.PicMsg: 53 | pic_data = gp.pic(ctx) 54 | if pic_data: 55 | pic_md5 = pic_data.GroupPic[0].FileMd5 56 | if pic_deque_dict[ctx.FromGroupId].should_repeat(pic_md5): 57 | Picture(pic_md5=pic_md5) 58 | -------------------------------------------------------------------------------- /plugins/bot_autoRevoke.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import re 4 | import time 5 | 6 | from botoy import Action, GroupMsg, jconfig 7 | from botoy.decorators import from_botself, in_content 8 | 9 | _KEYWORD = jconfig.autorevoke_keyword or "revoke" 10 | 11 | # 包含指定关键字时撤回,假设关键字为 revoke 12 | # revoke 随机0-90s撤回 13 | # revoke[10] 10s后撤回 14 | 15 | 16 | @from_botself 17 | @in_content(_KEYWORD) 18 | def receive_group_msg(ctx: GroupMsg): 19 | delay = re.findall(_KEYWORD + r"\[(\d+)\]", ctx.Content) 20 | if delay: 21 | delay = min(int(delay[0]), 90) 22 | else: 23 | random.seed(os.urandom(30)) 24 | delay = random.randint(30, 80) 25 | time.sleep(delay) 26 | Action(ctx.CurrentQQ).revokeGroupMsg( 27 | group=ctx.FromGroupId, 28 | msgSeq=ctx.MsgSeq, 29 | msgRandom=ctx.MsgRandom, 30 | ) 31 | -------------------------------------------------------------------------------- /plugins/bot_baidu_ocr.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | import time 3 | 4 | import requests 5 | 6 | try: 7 | from aip import AipOcr 8 | except ImportError: 9 | print("请安装库:pip install baidu-aip") 10 | raise 11 | 12 | from botoy import GroupMsg, Text, jconfig 13 | from botoy.collection import MsgTypes 14 | from botoy.decorators import ignore_botself, these_msgtypes 15 | from botoy.refine import refine_pic_group_msg 16 | 17 | __name__ = "百度OCR" 18 | __doc__ = "图片提取文字 格式:ocr加图片" 19 | 20 | APP_ID = jconfig.baidu_ocr_app_id 21 | API_KEY = jconfig.baidu_ocr_api_key 22 | SECRET_KEY = jconfig.baidu_ocr_secret_key 23 | 24 | assert all([APP_ID, API_KEY, SECRET_KEY]), "这全都是必要配置哦" 25 | 26 | client = AipOcr(APP_ID, API_KEY, SECRET_KEY) 27 | 28 | 29 | def ocr(image_url: str) -> str: 30 | try: 31 | image = requests.get(image_url, timeout=10).content 32 | except Exception: 33 | return "识别出错" 34 | try: 35 | resp = client.basicAccurate(image) 36 | except Exception: 37 | return "识别出错" 38 | if "error_code" not in resp: 39 | words = [word["words"] for word in resp["words_result"]] 40 | msg = "\n".join(words) 41 | return msg 42 | return "识别出错" 43 | 44 | 45 | @ignore_botself 46 | @these_msgtypes(MsgTypes.PicMsg) 47 | def receive_group_msg(ctx: GroupMsg): 48 | pic_ctx = refine_pic_group_msg(ctx) 49 | if "ocr" in pic_ctx.Content: 50 | for pic in pic_ctx.GroupPic: 51 | Text(ocr(pic.Url)) 52 | time.sleep(0.5) 53 | -------------------------------------------------------------------------------- /plugins/bot_bili_subscriber/__init__.py: -------------------------------------------------------------------------------- 1 | """ B站视频或番剧订阅 2 | 3 | 哔哩视频订阅+{UID:123} 4 | 哔哩视频订阅+{UP名字} 5 | 哔哩视频退订+{UID} 6 | 哔哩视频列表 7 | 8 | 哔哩番剧订阅+{番剧名} 9 | 哔哩番剧退订+{番剧id} 10 | 哔哩番剧列表 11 | """ 12 | # pylint: disable=R0915 13 | import re 14 | 15 | from botoy import Action, GroupMsg 16 | from botoy.schedule import scheduler 17 | from botoy.session import Prompt, SessionHandler, ctx, session 18 | 19 | from .api import API 20 | from .db import DB 21 | from .utils import clean_html 22 | 23 | # 订阅逻辑 24 | bilibili_handler = SessionHandler() 25 | 26 | 27 | @bilibili_handler.handle 28 | def _(): 29 | if ctx.Content.startswith("哔哩视频订阅"): 30 | # 确定订阅UP的mid, 如果无法确定则随时退出 31 | # 通过格式1 -> UID:数字 32 | try: 33 | mid = re.findall(r"UID:(\d+)", ctx.Content)[0] 34 | except Exception: 35 | mid = None 36 | 37 | # 格式1不行,通过格式2,关键词搜索 38 | if mid is None: 39 | keyword = ctx.Content[6:] 40 | ups = API.search_up_by_keyword(keyword) 41 | if not ups: 42 | bilibili_handler.finish("未找到相关UP,请重试或修改指令内容") 43 | if len(ups) == 1: 44 | mid = ups[0].mid 45 | else: 46 | choose_msgs = [] 47 | for idx, up in enumerate(ups[:10]): 48 | choose_msgs.append(f"{idx} 【{up.name}】") 49 | choose = session.want( 50 | "choose", "发送对应序号选择UP主:\n" + "\n".join(choose_msgs), timeout=60 51 | ) 52 | if isinstance(choose, str) and choose.isdigit(): 53 | try: 54 | mid = ups[int(choose)].mid 55 | except IndexError: 56 | bilibili_handler.finish("序号错误,已退出当前会话!") 57 | else: 58 | bilibili_handler.finish("序号错误,已退出当前会话!") 59 | 60 | db = DB() 61 | 62 | if db.subscribe_up(ctx.FromGroupId, mid): 63 | upinfo = API.get_up_info_by_mid(mid) 64 | if upinfo is None: 65 | bilibili_handler.finish(f"成功订阅UP主:{mid}") 66 | else: 67 | bilibili_handler.finish( 68 | Prompt.group_picture( 69 | url=upinfo.face, 70 | text=f"成功订阅UP主:{upinfo.name}", 71 | ) 72 | ) 73 | else: 74 | bilibili_handler.finish("本群已订阅该UP主") 75 | 76 | elif ctx.Content.startswith("哔哩番剧订阅"): 77 | keyword = ctx.Content[6:] 78 | bangumis = API.search_bangumi_by_keyword(keyword) 79 | if not bangumis: 80 | bilibili_handler.finish("未找到相关番剧,请重试或修改指令内容") 81 | if len(bangumis) == 1: 82 | choose_bangumi = bangumis[0] 83 | else: 84 | choose_msgs = [] 85 | for idx, bangumi in enumerate(bangumis[:10]): 86 | choose_msgs.append( 87 | f"{idx} 【{clean_html(bangumi.title)}】\n{bangumi.styles}\n{bangumi.desc}" 88 | ) 89 | choose = session.want( 90 | "choose", "发送对应序号选择番剧:\n" + "\n".join(choose_msgs), timeout=60 91 | ) 92 | if isinstance(choose, str) and choose.isdigit(): 93 | try: 94 | choose_bangumi = bangumis[int(choose)] 95 | except IndexError: 96 | bilibili_handler.finish("序号错误,已退出当前会话!") 97 | else: 98 | bilibili_handler.finish("序号错误,已退出当前会话!") 99 | 100 | db = DB() 101 | 102 | if db.subscribe_bangumi(ctx.FromGroupId, choose_bangumi.media_id): 103 | bilibili_handler.finish( 104 | Prompt.group_picture( 105 | url=choose_bangumi.cover, 106 | text=f"成功订阅番剧: {clean_html(choose_bangumi.title)}", 107 | ) 108 | ) 109 | else: 110 | bilibili_handler.finish("本群已订阅过该番剧") 111 | 112 | # ----------------------- 113 | bilibili_handler.finish() 114 | 115 | 116 | # ============== 117 | # 用于定时任务 118 | action = None 119 | # ============== 120 | 121 | # 订阅使用session, 其他操作使用普通指令 122 | def receive_group_msg(ctx: GroupMsg): 123 | global action # pylint: disable=W0603 124 | if action is None: 125 | # pylint: disable=W0212 126 | action = Action(ctx.CurrentQQ, host=ctx._host, port=ctx._port) 127 | 128 | if ctx.FromUserId == ctx.CurrentQQ: 129 | return 130 | 131 | # 退订UP 132 | if ctx.Content.startswith("哔哩视频退订"): 133 | try: 134 | mid = re.findall(r"(\d+)", ctx.Content)[0] 135 | except Exception: 136 | msg = "UID应为数字" 137 | else: 138 | db = DB() 139 | if db.unsubscribe_up(ctx.FromGroupId, mid): 140 | upinfo = API.get_up_info_by_mid(mid) 141 | if upinfo is not None: 142 | msg = "成功退订UP主:{}".format(upinfo.name) 143 | else: 144 | msg = "成功退订UP主:{}".format(mid) 145 | else: 146 | msg = "本群未订阅该UP主" 147 | action.sendGroupText(ctx.FromGroupId, msg) 148 | # 查看订阅UP列表 149 | elif ctx.Content == "哔哩视频列表": 150 | db = DB() 151 | mids = db.get_ups_by_gid(ctx.FromGroupId) 152 | if mids: 153 | ups = [] 154 | for mid in mids: 155 | upinfo = API.get_up_info_by_mid(mid) 156 | if upinfo is not None: 157 | ups.append("{}({})".format(upinfo.mid, upinfo.name)) 158 | else: 159 | ups.append(str(mid)) 160 | msg = "本群已订阅UP主:\n" + "\n".join(ups) 161 | else: 162 | msg = "本群还没有订阅过一个UP主" 163 | action.sendGroupText(ctx.FromGroupId, msg) 164 | 165 | # 退订番剧 166 | elif ctx.Content.startswith("哔哩番剧退订"): 167 | try: 168 | mid = re.findall(r"(\d+)", ctx.Content)[0] 169 | except Exception: 170 | msg = "番剧ID应为数字" 171 | else: 172 | db = DB() 173 | if db.unsubscribe_bangumi(ctx.FromGroupId, mid): 174 | # 通过最新集数中的api获取番剧基本信息勉勉强强满足需求 175 | bangumi = API.get_latest_ep_by_media_id(mid) 176 | if bangumi is not None: 177 | msg = "成功退订番剧:{}".format(bangumi.long_title) 178 | else: 179 | msg = "成功退订番剧:{}".format(mid) 180 | else: 181 | msg = "本群未订阅该UP主" 182 | action.sendGroupText(ctx.FromGroupId, msg) 183 | # 查看订阅番剧列表 184 | elif ctx.Content == "哔哩番剧列表": 185 | db = DB() 186 | mids = db.get_bangumi_by_gid(ctx.FromGroupId) 187 | if mids: 188 | msgs = [] 189 | for mid in mids: 190 | bangumi = API.get_latest_ep_by_media_id(mid) 191 | if bangumi is not None: 192 | msgs.append("{}({})".format(mid, bangumi.long_title)) 193 | else: 194 | msgs.append(str(mid)) 195 | msg = "本群已订阅番剧:\n" + "\n".join(msgs) 196 | else: 197 | msg = "本群还没有订阅过一部番剧" 198 | action.sendGroupText(ctx.FromGroupId, msg) 199 | 200 | # 其他操作逻辑转到session操作 201 | else: 202 | bilibili_handler.message_receiver(ctx) 203 | 204 | 205 | # schedule task 206 | 207 | 208 | def check_up_video(): 209 | db = DB() 210 | for mid in db.get_subscribed_ups(): 211 | video = API.get_latest_video_by_mid(mid) 212 | if video is not None: 213 | if db.judge_up_updated(mid, video.created): 214 | info = "UP主<{}>发布了新视频!\n{}\n{}\n{}".format( 215 | video.author, 216 | video.title, 217 | video.description, 218 | video.bvid, 219 | ) 220 | if action is not None: 221 | for group in db.get_gids_by_up_mid(mid): 222 | action.sendGroupPic( 223 | group, 224 | content=info, 225 | picUrl=video.pic, 226 | ) 227 | 228 | 229 | def check_bangumi(): 230 | db = DB() 231 | for mid in db.get_subscribed_bangumis(): 232 | ep = API.get_latest_ep_by_media_id(mid) 233 | if ep is not None: 234 | if db.judge_bangumi_updated(mid, ep.id): 235 | info = "番剧《{}》更新了!\n{}".format( 236 | ep.long_title, 237 | ep.url, 238 | ) 239 | if action is not None: 240 | for group in db.get_gids_by_bangumi_mid(mid): 241 | action.sendGroupPic( 242 | group, 243 | content=info, 244 | picUrl=ep.cover, 245 | ) 246 | 247 | 248 | scheduler.add_job(check_up_video, "interval", minutes=5) 249 | scheduler.add_job(check_bangumi, "interval", minutes=10) 250 | -------------------------------------------------------------------------------- /plugins/bot_bili_subscriber/api.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | import httpx 4 | from pydantic import BaseModel # pylint: disable=E0611 5 | 6 | 7 | # 番剧 8 | class EP(BaseModel): 9 | id: int 10 | cover: str 11 | long_title: str 12 | url: str 13 | 14 | 15 | class Bangumi(BaseModel): 16 | media_id: int 17 | title: str 18 | cover: str 19 | styles: str 20 | desc: str 21 | eps: List[EP] 22 | 23 | 24 | # 视频 25 | class Video(BaseModel): 26 | author: str 27 | title: str 28 | description: str 29 | aid: int 30 | bvid: str 31 | pic: str 32 | created: int 33 | 34 | 35 | # 用户 36 | class UPInfo(BaseModel): 37 | mid: str 38 | name: str 39 | face: str 40 | sign: str 41 | 42 | 43 | class API: 44 | @classmethod 45 | def get_latest_video_by_mid(cls, mid: int) -> Optional[Video]: 46 | try: 47 | resp = httpx.get( 48 | f"https://api.bilibili.com/x/space/arc/search?mid={mid}&ps=1", 49 | timeout=20, 50 | ) 51 | resp.raise_for_status() 52 | result = resp.json() 53 | if result["code"] == 0: 54 | vlist = result["data"]["list"]["vlist"] 55 | return Video(**vlist[0]) 56 | except Exception as e: 57 | print(e) 58 | return None 59 | 60 | @classmethod 61 | def get_up_info_by_mid(cls, mid: int) -> Optional[UPInfo]: 62 | try: 63 | resp = httpx.get( 64 | f"https://api.bilibili.com/x/web-interface/card?mid={mid}", timeout=30 65 | ) 66 | resp.raise_for_status() 67 | result = resp.json() 68 | if result["code"] == 0: 69 | return UPInfo(**result["data"]["card"]) 70 | except Exception as e: 71 | print(e) 72 | return None 73 | 74 | @classmethod 75 | def search_up_by_keyword(cls, keyword: str) -> Optional[List[UPInfo]]: 76 | try: 77 | resp = httpx.get( 78 | "https://api.bilibili.com/x/web-interface/search/type", 79 | params={"search_type": "bili_user", "keyword": keyword}, 80 | timeout=30, 81 | ) 82 | resp.raise_for_status() 83 | result = resp.json() 84 | if result["code"] == 0: 85 | return [ 86 | UPInfo( 87 | mid=up["mid"], 88 | name=up["uname"], 89 | face=up["upic"], 90 | sign=up["usign"], 91 | ) 92 | for up in result["data"]["result"] 93 | ] 94 | except Exception as e: 95 | print(e) 96 | return None 97 | 98 | @classmethod 99 | def search_bangumi_by_keyword(cls, keyword: str) -> Optional[List[Bangumi]]: 100 | try: 101 | resp = httpx.get( 102 | "https://api.bilibili.com/x/web-interface/search/type", 103 | params={"search_type": "media_bangumi", "keyword": keyword}, 104 | timeout=30, 105 | ) 106 | resp.raise_for_status() 107 | result = resp.json() 108 | if result["code"] == 0: 109 | return [Bangumi(**media) for media in result["data"]["result"]] 110 | except Exception as e: 111 | print(e) 112 | return None 113 | 114 | @classmethod 115 | def get_latest_ep_by_media_id(cls, media_id: int) -> Optional[EP]: 116 | try: 117 | resp = httpx.get( 118 | f"https://api.bilibili.com/pgc/review/user?media_id={media_id}", 119 | timeout=30, 120 | ) 121 | resp.raise_for_status() 122 | result = resp.json() 123 | if result["code"] == 0: 124 | media = result["result"]["media"] 125 | return EP( 126 | id=media["new_ep"]["id"], 127 | cover=media["cover"], 128 | long_title=media["title"], 129 | url=media["share_url"], 130 | ) 131 | except Exception as e: 132 | print(e) 133 | return None 134 | 135 | 136 | if __name__ == "__main__": 137 | print( 138 | API.get_latest_ep_by_media_id( 139 | API.search_bangumi_by_keyword("辉夜大小姐")[0].media_id 140 | ) 141 | ) 142 | -------------------------------------------------------------------------------- /plugins/bot_bili_subscriber/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from typing import List 3 | 4 | from botoy.contrib import get_cache_dir 5 | 6 | DB_PATH = get_cache_dir("bili_subscriber") / "db.sqlite3" 7 | 8 | 9 | class DB: 10 | def __init__(self): 11 | self.con = sqlite3.connect(DB_PATH) 12 | self.cur = self.con.cursor() 13 | # up's video 14 | # up_video_data: mid, create 15 | self.cur.execute( 16 | "CREATE TABLE IF NOT EXISTS up_video_data(mid interger primary key, created integer);" 17 | ) 18 | # up_subscribed: id, gid, mid 19 | self.cur.execute( 20 | "CREATE TABLE IF NOT EXISTS up_subscribed(id integer primary key autoincrement, gid integer, mid interger);" 21 | ) 22 | # bangumi 23 | # bangumi_data: mid, ep_id 24 | self.cur.execute( 25 | "CREATE TABLE IF NOT EXISTS bangumi_data(mid interger primary key, ep_id integer);" 26 | ) 27 | # bangumi_subscribed: id, gid, mid 28 | self.cur.execute( 29 | "CREATE TABLE IF NOT EXISTS bangumi_subscribed(id integer primary key autoincrement, gid integer, mid interger);" 30 | ) 31 | self.con.commit() 32 | 33 | # up video 34 | def get_ups_by_gid(self, gid: int) -> List[int]: 35 | """list of up's mid""" 36 | self.cur.execute(f"SELECT * FROM up_subscribed WHERE gid={gid}") 37 | return [ret[2] for ret in self.cur.fetchall()] 38 | 39 | def get_gids_by_up_mid(self, mid: int) -> List[int]: 40 | """list of subscribed gid""" 41 | self.cur.execute(f"SELECT * FROM up_subscribed WHERE mid={mid}") 42 | return [ret[1] for ret in self.cur.fetchall()] 43 | 44 | def subscribe_up(self, gid: int, mid: int) -> bool: 45 | """返回False表示已经订阅过了""" 46 | self.cur.execute(f"SELECT * FROM up_subscribed WHERE gid={gid} AND mid={mid}") 47 | if self.cur.fetchall(): 48 | return False 49 | self.cur.execute(f"INSERT INTO up_subscribed (gid, mid) VALUES ({gid}, {mid})") 50 | self.con.commit() 51 | return True 52 | 53 | def unsubscribe_up(self, gid: int, mid: int) -> bool: 54 | """返回False表示未订阅""" 55 | self.cur.execute(f"SELECT * FROM up_subscribed WHERE gid={gid} AND mid={mid}") 56 | if not self.cur.fetchall(): 57 | return False 58 | self.cur.execute(f"DELETE FROM up_subscribed WHERE gid={gid} AND mid={mid}") 59 | self.con.commit() 60 | return True 61 | 62 | def get_subscribed_ups(self) -> List[int]: 63 | """list of up's mid""" 64 | self.cur.execute("SELECT * FROM up_subscribed") 65 | return [ret[2] for ret in self.cur.fetchall()] 66 | 67 | def judge_up_updated(self, mid: int, created: int) -> bool: 68 | """如果更新了则返回True,并更新数据""" 69 | self.cur.execute(f"SELECT * FROM up_video_data WHERE mid={mid}") 70 | found = self.cur.fetchone() 71 | if found: 72 | if created > found[1]: 73 | self.cur.execute( 74 | f"UPDATE up_video_data SET created={created} WHERE mid={mid}" 75 | ) 76 | self.con.commit() 77 | return True 78 | else: 79 | # 没找到说明首次更新,就不提示了 80 | self.cur.execute( 81 | f"INSERT INTO up_video_data (mid, created) VALUES ({mid}, {created})" 82 | ) 83 | self.con.commit() 84 | return False 85 | return False 86 | 87 | # bangumi 88 | def get_bangumi_by_gid(self, gid: int) -> List[int]: 89 | """list of bangumi's mid""" 90 | self.cur.execute(f"SELECT * FROM bangumi_subscribed WHERE gid={gid}") 91 | return [ret[2] for ret in self.cur.fetchall()] 92 | 93 | def get_gids_by_bangumi_mid(self, mid: int) -> List[int]: 94 | """list of gid that has subscribed""" 95 | self.cur.execute(f"SELECT * FROM bangumi_subscribed WHERE mid={mid}") 96 | return [ret[1] for ret in self.cur.fetchall()] 97 | 98 | def subscribe_bangumi(self, gid: int, mid: int) -> bool: 99 | """返回False表示已经订阅过了""" 100 | self.cur.execute( 101 | f"SELECT * FROM bangumi_subscribed WHERE gid={gid} AND mid={mid}" 102 | ) 103 | if self.cur.fetchall(): 104 | return False 105 | self.cur.execute( 106 | f"INSERT INTO bangumi_subscribed (gid, mid) VALUES ({gid}, {mid})" 107 | ) 108 | self.con.commit() 109 | return True 110 | 111 | def unsubscribe_bangumi(self, gid: int, mid: int) -> bool: 112 | """返回False表示未订阅""" 113 | self.cur.execute( 114 | f"SELECT * FROM bangumi_subscribed WHERE gid={gid} AND mid={mid}" 115 | ) 116 | if not self.cur.fetchall(): 117 | return False 118 | self.cur.execute( 119 | f"DELETE FROM bangumi_subscribed WHERE gid={gid} AND mid={mid}" 120 | ) 121 | self.con.commit() 122 | return True 123 | 124 | def get_subscribed_bangumis(self) -> List[int]: 125 | """list of up's mid""" 126 | self.cur.execute("SELECT * FROM bangumi_subscribed") 127 | return [ret[2] for ret in self.cur.fetchall()] 128 | 129 | def judge_bangumi_updated(self, mid: int, ep_id: int) -> bool: 130 | """如果更新了则返回True,并更新数据""" 131 | self.cur.execute(f"SELECT * FROM bangumi_data WHERE mid={mid}") 132 | found = self.cur.fetchone() 133 | if found: 134 | if ep_id > found[1]: 135 | self.cur.execute( 136 | f"UPDATE bangumi_data SET ep_id={ep_id} WHERE mid={mid}" 137 | ) 138 | self.con.commit() 139 | return True 140 | else: 141 | # 没找到说明首次更新,就不提示了 142 | self.cur.execute( 143 | f"INSERT INTO bangumi_data (mid, ep_id) VALUES ({mid}, {ep_id})" 144 | ) 145 | self.con.commit() 146 | return False 147 | return False 148 | -------------------------------------------------------------------------------- /plugins/bot_bili_subscriber/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def clean_html(string): 5 | return re.sub(r"", "", string) 6 | -------------------------------------------------------------------------------- /plugins/bot_bnhhsh.py: -------------------------------------------------------------------------------- 1 | """不能好好说话 2 | 3 | 发送 说个屁+{缩写} 如:说个屁ynmm 4 | """ 5 | import pickle 6 | 7 | from botoy import GroupMsg, S 8 | from botoy import decorators as deco 9 | from botoy.contrib import download, get_cache_dir 10 | 11 | data = get_cache_dir("bnhhsh") / "data.pkl" 12 | if not data.is_file(): 13 | download( 14 | "https://github.com/opq-osc/botoy-plugins/releases/download/bnhhsh%E6%95%B0%E6%8D%AE/data.pkl", 15 | data, 16 | ) 17 | 18 | 19 | with open(data, "rb") as f: 20 | 词桶 = pickle.load(f) 21 | 22 | n = max(词桶) 23 | 24 | 25 | def dp(target): 26 | 代价 = {-1: 0} 27 | 记录 = {-1: []} 28 | for x in range(len(target)): 29 | 代价[x] = 2**32 30 | for k in range(n, 0, -1): 31 | s = x - k + 1 32 | if s < 0: 33 | continue 34 | c = 词桶[k].get(target[s : x + 1]) 35 | if c: 36 | 词, 痛苦 = c 37 | if 代价[x - k] + 痛苦 < 代价[x]: 38 | 代价[x] = 代价[x - k] + 痛苦 39 | 记录[x] = 记录[x - k].copy() 40 | 记录[x].append((s, x + 1, 词)) 41 | if 代价[x - 1] + 1 < 代价[x]: 42 | 代价[x] = 代价[x - 1] + 1 43 | 记录[x] = 记录[x - 1].copy() 44 | target = [*target] 45 | for a, b, c in 记录[len(target) - 1][::-1]: 46 | target[a:b] = c 47 | return "".join(target) 48 | 49 | 50 | @deco.ignore_botself 51 | @deco.startswith("说个屁") 52 | def receive_group_msg(ctx: GroupMsg): 53 | S.bind(ctx).text(dp(ctx.Content[3:])) 54 | -------------------------------------------------------------------------------- /plugins/bot_cleanGroupZombie.py: -------------------------------------------------------------------------------- 1 | """管理员发送 清理僵尸+{可选加清理人数} 即可清理僵尸用户(要求机器人时管理员)""" 2 | import time 3 | from datetime import datetime 4 | 5 | from botoy.collection import MsgTypes 6 | from botoy.decorators import ( 7 | from_admin, 8 | ignore_botself, 9 | startswith, 10 | these_msgtypes 11 | ) 12 | from botoy.session import SessionHandler, ctx, session 13 | 14 | zombie_handler = SessionHandler( 15 | ignore_botself, 16 | these_msgtypes(MsgTypes.TextMsg), 17 | startswith("清理僵尸"), 18 | from_admin, 19 | ).receive_group_msg() 20 | 21 | 22 | def timestamp2date(timestamp: int): 23 | return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%m") 24 | 25 | 26 | @zombie_handler.handle 27 | def _(): 28 | num = ctx.Content[4:] 29 | if num.isdigit(): 30 | num = min(20, int(num)) 31 | else: 32 | num = 5 33 | members = session.action.getGroupMembers(ctx.FromGroupId) 34 | members = [member for member in members if member["GroupAdmin"] == 0] 35 | members.sort(key=lambda member: member["LastSpeakTime"]) 36 | 37 | zombies = members[:num] 38 | 39 | # 整理提示信息 40 | msgs = ["发送需要踢出去的用户序号(空格分隔)即可, 发送all全部移除"] 41 | for idx, zombie in enumerate(zombies): 42 | msgs.append( 43 | "【{idx}】{name}({qq})加群时间:{join_time} 上次发言时间:{last_speak_time}".format( 44 | idx=idx, 45 | name=zombie["GroupCard"] if zombie["GroupCard"] else zombie["NickName"], 46 | qq=zombie["MemberUin"], 47 | join_time=timestamp2date(zombie["JoinTime"]), 48 | last_speak_time=timestamp2date(zombie["LastSpeakTime"]), 49 | ) 50 | ) 51 | ret: str = session.want("choose", "\n".join(msgs)) 52 | if ret == "all": 53 | for zombie in zombies: 54 | # session.send_text('模拟踢人 => {}'.format(zombie['MemberUin'])) 55 | session.action.driveUserAway(ctx.FromGroupId, zombie["MemberUin"]) 56 | time.sleep(1) 57 | else: 58 | choose_ids = [int(id) for id in ret.split(" ") if id.isdigit()] 59 | 60 | zombie_ids = list(range(len(zombies))) 61 | 62 | for choose_id in choose_ids: 63 | if choose_id in zombie_ids: 64 | zombie = zombies[choose_id] 65 | # session.send_text('模拟踢人 => {}'.format(zombie['MemberUin'])) 66 | session.action.driveUserAway(ctx.FromGroupId, zombie["MemberUin"]) 67 | time.sleep(1) 68 | 69 | zombie_handler.finish("报告阿sir! 清理任务结束!") 70 | -------------------------------------------------------------------------------- /plugins/bot_corona_virus.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from typing import List 4 | 5 | import httpx 6 | from botoy import Action, GroupMsg 7 | from botoy.contrib import get_cache_dir 8 | from botoy.decorators import ignore_botself 9 | from botoy.schedule import scheduler 10 | from pydantic import BaseModel 11 | 12 | __name__ = "疫情订阅" 13 | __doc__ = "疫情最新资讯订阅:发送 疫情订阅 或 疫情退订" 14 | 15 | 16 | class New(BaseModel): 17 | pubDate: int = 0 18 | pubDateStr: str = "" 19 | title: str = "" 20 | summary: str = "" 21 | sourceUrl: str = "" 22 | 23 | 24 | cache = get_cache_dir("corona_virus") / "data.json" 25 | 26 | 27 | class CacheData: 28 | def __init__(self): 29 | try: 30 | with open(cache, "r") as f: 31 | data = json.load(f) 32 | except FileNotFoundError: 33 | data = {"groups": [], "new": New().dict()} 34 | 35 | self._groups: List[int] = data["groups"] 36 | self._new: New = New(**data["new"]) 37 | 38 | def save(self): 39 | with open(cache, "w") as f: 40 | json.dump( 41 | { 42 | "groups": self._groups, 43 | "new": self._new.dict(), 44 | }, 45 | f, 46 | ensure_ascii=False, 47 | ) 48 | 49 | def insert_group(self, group: int): 50 | if group not in self._groups: 51 | self._groups.append(group) 52 | self.save() 53 | 54 | def delete_group(self, group: int): 55 | if group in self._groups: 56 | self._groups.remove(group) 57 | self.save() 58 | 59 | @property 60 | def new(self) -> New: 61 | return self._new 62 | 63 | @property 64 | def groups(self) -> List[int]: 65 | return self._groups 66 | 67 | @new.setter 68 | def new(self, new_): 69 | self._new = new_ 70 | self.save() 71 | 72 | 73 | cache_data = CacheData() 74 | 75 | 76 | action = None 77 | 78 | 79 | @ignore_botself 80 | def receive_group_msg(ctx: GroupMsg): 81 | global action 82 | if action is None: 83 | action = Action( 84 | ctx.CurrentQQ, host=getattr(ctx, "_host"), port=getattr(ctx, "_port") 85 | ) 86 | 87 | if ctx.Content == "疫情订阅": 88 | cache_data.insert_group(ctx.FromGroupId) 89 | action.sendGroupText(ctx.FromGroupId, "ok") 90 | elif ctx.Content == "疫情退订": 91 | cache_data.delete_group(ctx.FromGroupId) 92 | action.sendGroupText(ctx.FromGroupId, "ok") 93 | 94 | 95 | @scheduler.scheduled_job("interval", minutes=1) 96 | def _(): 97 | # lasted new 98 | try: 99 | resp = httpx.get("https://ncov.dxy.cn/ncovh5/view/pneumonia") 100 | data_json = re.findall( 101 | r"try \{\swindow.getTimelineService1 = (.*?)\}catch\(e\)\{\}", resp.text 102 | )[0] 103 | data = json.loads(data_json)[0] 104 | new = New(**data) 105 | except Exception: 106 | return 107 | 108 | # 更新数据 109 | if new.pubDate > cache_data.new.pubDate: 110 | if cache_data.new.pubDate == 0: 111 | cache_data.new = new 112 | else: 113 | cache_data.new = new 114 | # 推送 115 | if action is not None: 116 | for group in cache_data.groups: 117 | action.sendGroupText( 118 | group, 119 | content=f"{new.pubDateStr}【{new.title}】\n{new.summary}\n{new.sourceUrl}", 120 | ) 121 | -------------------------------------------------------------------------------- /plugins/bot_custom_image/__init__.py: -------------------------------------------------------------------------------- 1 | """制图.jpg/.png 2 | 1. img list => 获取图片模板列表 3 | 2. img {name} => 设置自己要用的模板 4 | 3. 发送 {任意文字}.jpg或.png 即可 5 | """ 6 | from botoy import GroupMsg 7 | from botoy.decorators import ignore_botself 8 | from botoy.sugar import Picture, Text 9 | 10 | from . import config, core 11 | 12 | 13 | @ignore_botself 14 | def receive_group_msg(ctx: GroupMsg): 15 | if ctx.FromGroupId in config.BLOCK_GROUP: 16 | return 17 | 18 | if ctx.Content == "img list": 19 | Picture(pic_path=config.QRCODE_PATH) 20 | elif ctx.Content.startswith("img"): 21 | name = ctx.Content[3:].strip() 22 | if core.check_name(name): 23 | core.cache_by_name(name, ctx.FromUserId) 24 | Text("好了") 25 | else: 26 | Text("名字有误") 27 | elif ctx.Content.endswith(".jpg") or ctx.Content.endswith(".png"): 28 | text = ctx.Content[:-4] 29 | Picture(pic_base64=core.draw(text, ctx.FromUserId)) 30 | -------------------------------------------------------------------------------- /plugins/bot_custom_image/config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from botoy import jconfig 4 | from botoy.contrib import download, get_cache_dir 5 | 6 | 7 | def get_config(key, default=None): 8 | return jconfig.get_jconfig(f"custom_image_{key}") or default 9 | 10 | 11 | FONT_MIN = 15 12 | 13 | IMAGES_PATH = Path(__file__).parent.absolute() / "images" 14 | 15 | # 屏蔽群 16 | BLOCK_GROUP = get_config("block_group", []) 17 | # 是否开启emoji 18 | ENABLE_EMOJI = get_config("enable_emoji", True) 19 | 20 | CACHE_PATH = get_cache_dir("custom_image") 21 | 22 | if not (CACHE_PATH / "user").exists(): 23 | (CACHE_PATH / "user").mkdir() 24 | if ENABLE_EMOJI: 25 | font_name = "simhei-emoji.ttf" 26 | else: 27 | font_name = "simhei.ttf" 28 | 29 | FONT_PATH = CACHE_PATH / font_name 30 | 31 | if not FONT_PATH.exists(): 32 | download( 33 | f"https://github.com/opq-osc/botoy-plugins/releases/download/simhei-fonts/{font_name}", 34 | FONT_PATH, 35 | ) 36 | 37 | QRCODE_PATH = CACHE_PATH / "list-qr.jpg" 38 | NAMES_PATH = IMAGES_PATH / "names.json" 39 | 40 | USER_CACHE_PATH = CACHE_PATH / "user" 41 | if not USER_CACHE_PATH.exists(): 42 | USER_CACHE_PATH.mkdir() 43 | -------------------------------------------------------------------------------- /plugins/bot_custom_image/core.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import io 3 | import json 4 | import os 5 | 6 | import qrcode 7 | from PIL import Image, ImageDraw, ImageFont 8 | 9 | from . import config 10 | 11 | 12 | def read_json(p): 13 | with open(p, encoding="utf8") as f: 14 | return json.load(f) 15 | 16 | 17 | # 生成图片列表的二维码 18 | def make_qrcode(): 19 | 20 | data = read_json(config.NAMES_PATH) # type: dict 21 | names = list(data.values()) 22 | 23 | qr = qrcode.QRCode( 24 | version=1, 25 | box_size=5, 26 | border=4, 27 | ) 28 | qr.add_data("、".join(names)) 29 | img = qr.make_image(fill_color="green", back_color="white") 30 | 31 | if config.QRCODE_PATH.exists(): 32 | os.remove(config.QRCODE_PATH) 33 | 34 | img.save(str(config.QRCODE_PATH)) 35 | 36 | 37 | # 默认启动生成一次 38 | make_qrcode() 39 | 40 | ########################################### 41 | 42 | 43 | def check_name(img_name): 44 | data = read_json(config.NAMES_PATH) # type: dict 45 | return img_name in list(data.values()) 46 | 47 | 48 | def id2name(img_id): 49 | data = read_json(config.NAMES_PATH) # type: dict 50 | for id_, name in data.items(): 51 | if id_ == img_id: 52 | return name 53 | 54 | 55 | def name2id(img_name): 56 | data = read_json(config.NAMES_PATH) # type: dict 57 | for id_, name in data.items(): 58 | if name == img_name: 59 | return id_ 60 | 61 | 62 | def cache_by_name(img_name, user_id): 63 | (config.NAMES_PATH / str(user_id)).write_text(str(name2id(img_name))) 64 | 65 | 66 | def cache_by_id(img_id, user_id): 67 | (config.USER_CACHE_PATH / str(user_id)).write_text(str(img_id)) 68 | 69 | 70 | def get_cache(user_id): 71 | p = config.USER_CACHE_PATH / str(user_id) 72 | if p.exists(): 73 | return p.read_text().strip() 74 | return "xuexiaoban" 75 | 76 | 77 | def draw(text: str, user_id: int = 0) -> str: 78 | """如果指定了user_id则根据历史使用过的图片模板生成 79 | 如果未指定或没有偏好数据,则使用默认模板 80 | 返回base64 81 | """ 82 | img_id = get_cache(user_id) 83 | img_path = config.IMAGES_PATH / str(img_id) 84 | jpg_path = img_path / f"{img_id}.jpg" 85 | png_path = img_path / f"{img_id}.png" 86 | 87 | if jpg_path.exists(): 88 | pic_path = jpg_path 89 | elif png_path.exists(): 90 | pic_path = png_path 91 | else: 92 | return "" 93 | 94 | opts = read_json(img_path / "config.ini") 95 | 96 | img = Image.open(pic_path) 97 | draw = ImageDraw.Draw(img) 98 | color = opts["color"] 99 | fontSize = opts["font_size"] 100 | fontMax = opts["font_max"] 101 | imageFontCenter = (opts["font_center_x"], opts["font_center_y"]) 102 | imageFontSub = opts["font_sub"] 103 | # 设置字体暨字号 104 | ttfront = ImageFont.truetype(str(config.FONT_PATH), fontSize) 105 | fontLength = ttfront.getsize(text) 106 | while fontLength[0] > fontMax: 107 | fontSize -= imageFontSub 108 | ttfront = ImageFont.truetype(str(config.FONT_PATH), fontSize) 109 | fontLength = ttfront.getsize(text) 110 | if fontSize <= config.FONT_MIN: 111 | return "" 112 | # 自定义打印的文字和文字的位置 113 | if fontLength[0] > 5: 114 | draw.text( 115 | ( 116 | imageFontCenter[0] - fontLength[0] / 2, 117 | imageFontCenter[1] - fontLength[1] / 2, 118 | ), 119 | text, 120 | fill=color, 121 | font=ttfront, 122 | ) 123 | buffer = io.BytesIO() 124 | img.save(buffer, format="png") 125 | return base64.b64encode(buffer.getvalue()).decode() 126 | -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/02/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/02/02.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/02/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"02", 3 | "font_max":734, 4 | "font_size":70, 5 | "font_center_x":397, 6 | "font_center_y":580, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/akai/akai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/akai/akai.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/akai/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"akai", 3 | "font_max":220, 4 | "font_size":40, 5 | "font_center_x":125, 6 | "font_center_y":220, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/alice/alice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/alice/alice.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/alice/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"alice", 3 | "font_max":380, 4 | "font_size":65, 5 | "font_center_x":200, 6 | "font_center_y":488, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/anzhongguancha/anzhongguancha.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/anzhongguancha/anzhongguancha.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/anzhongguancha/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"anzhongguancha", 3 | "font_max":472, 4 | "font_size":65, 5 | "font_center_x":256, 6 | "font_center_y":470, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/aqua/aqua.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/aqua/aqua.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/aqua/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"aqua", 3 | "font_max":220, 4 | "font_size":25, 5 | "font_center_x":120, 6 | "font_center_y":263, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/chouyou/chouyou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/chouyou/chouyou.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/chouyou/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"chouyou", 3 | "font_max":180, 4 | "font_size":30, 5 | "font_center_x":100, 6 | "font_center_y":180, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/fbk1/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"fbk1", 3 | "font_max":280, 4 | "font_size":40, 5 | "font_center_x":151, 6 | "font_center_y":290, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/fbk1/fbk1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/fbk1/fbk1.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/fbk2/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"fbk2", 3 | "font_max":346, 4 | "font_size":50, 5 | "font_center_x":193, 6 | "font_center_y":385, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/fbk2/fbk2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/fbk2/fbk2.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/gbf/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"gbf", 3 | "font_max":251, 4 | "font_size":40, 5 | "font_center_x":135, 6 | "font_center_y":290, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/gbf/gbf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/gbf/gbf.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/haoxue/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"haoxue", 3 | "font_max":460, 4 | "font_size":40, 5 | "font_center_x":250, 6 | "font_center_y":433, 7 | "color":"blue", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/haoxue/haoxue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/haoxue/haoxue.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/heishou/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"heishou", 3 | "font_max":240, 4 | "font_size":40, 5 | "font_center_x":150, 6 | "font_center_y":262, 7 | "color":"white", 8 | "font_sub":10 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/heishou/heishou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/heishou/heishou.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/heishou_four/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"heishou_four", 3 | "font_max":380, 4 | "font_size":50, 5 | "font_center_x":250, 6 | "font_center_y":250, 7 | "color":"white", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/heishou_four/heishou_four.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/heishou_four/heishou_four.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/initial/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"initial", 3 | "font_max":310, 4 | "font_size":55, 5 | "font_center_x":175, 6 | "font_center_y":313, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/initial/initial.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/initial/initial.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/jojo/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"jojo", 3 | "font_max":180, 4 | "font_size":35, 5 | "font_center_x":105, 6 | "font_center_y":238, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/jojo/jojo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/jojo/jojo.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/jumozhanjiang/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"jumozhanjiang", 3 | "font_max":340, 4 | "font_size":60, 5 | "font_center_x":200, 6 | "font_center_y":465, 7 | "color":"white", 8 | "font_sub":10 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/jumozhanjiang/jumozhanjiang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/jumozhanjiang/jumozhanjiang.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/jupai/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"jupai", 3 | "font_max":380, 4 | "font_size":80, 5 | "font_center_x":400, 6 | "font_center_y":650, 7 | "color":"black", 8 | "font_sub":10 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/jupai/jupai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/jupai/jupai.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"kaguranana", 3 | "font_max":340, 4 | "font_size":40, 5 | "font_center_x":200, 6 | "font_center_y":405, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana/kaguranana.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/kaguranana/kaguranana.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana4/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"kaguranana4", 3 | "font_max":540, 4 | "font_size":60, 5 | "font_center_x":290, 6 | "font_center_y":380, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana4/kaguranana4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/kaguranana4/kaguranana4.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana5/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"kaguranana5", 3 | "font_max":450, 4 | "font_size":70, 5 | "font_center_x":250, 6 | "font_center_y":545, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana5/kaguranana5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/kaguranana5/kaguranana5.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana_1/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"kaguranana_1", 3 | "font_max":360, 4 | "font_size":40, 5 | "font_center_x":200, 6 | "font_center_y":275, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana_1/kaguranana_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/kaguranana_1/kaguranana_1.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana_2/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"kaguranana_2", 3 | "font_max":615, 4 | "font_size":70, 5 | "font_center_x":327, 6 | "font_center_y":645, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kaguranana_2/kaguranana_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/kaguranana_2/kaguranana_2.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kasumi/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"kasumi", 3 | "font_max":323, 4 | "font_size":60, 5 | "font_center_x":186, 6 | "font_center_y":345, 7 | "color":"white", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kasumi/kasumi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/kasumi/kasumi.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/katsuna/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"katsuna", 3 | "font_max":230, 4 | "font_size":35, 5 | "font_center_x":125, 6 | "font_center_y":278, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/katsuna/katsuna.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/katsuna/katsuna.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/keduoli/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"keduoli", 3 | "font_max":180, 4 | "font_size":25, 5 | "font_center_x":100, 6 | "font_center_y":223, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/keduoli/keduoli.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/keduoli/keduoli.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kiyohime/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"kiyohime", 3 | "font_max":392, 4 | "font_size":50, 5 | "font_center_x":216, 6 | "font_center_y":570, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kiyohime/kiyohime.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/kiyohime/kiyohime.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kmmm/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"kmmm", 3 | "font_max":539, 4 | "font_size":75, 5 | "font_center_x":290, 6 | "font_center_y":453, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/kmmm/kmmm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/kmmm/kmmm.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/ksm/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"ksm", 3 | "font_max":469, 4 | "font_size":50, 5 | "font_center_x":254, 6 | "font_center_y":320, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/ksm/ksm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/ksm/ksm.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/lxy1/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"lxy1", 3 | "font_max":460, 4 | "font_size":70, 5 | "font_center_x":250, 6 | "font_center_y":535, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/lxy1/lxy1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/lxy1/lxy1.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/lxy2/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"lxy2", 3 | "font_max":460, 4 | "font_size":65, 5 | "font_center_x":250, 6 | "font_center_y":458, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/lxy2/lxy2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/lxy2/lxy2.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mao/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"mao", 3 | "font_max":464, 4 | "font_size":80, 5 | "font_center_x":252, 6 | "font_center_y":550, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mao/mao.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/mao/mao.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/maotouying/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"maotouying", 3 | "font_max":472, 4 | "font_size":45, 5 | "font_center_x":256, 6 | "font_center_y":472, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/maotouying/maotouying.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/maotouying/maotouying.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/matsuri/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"matsuri", 3 | "font_max":180, 4 | "font_size":35, 5 | "font_center_x":100, 6 | "font_center_y":195, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/matsuri/matsuri.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/matsuri/matsuri.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/matsuri_1/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"matsuri_1", 3 | "font_max":230, 4 | "font_size":25, 5 | "font_center_x":125, 6 | "font_center_y":233, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/matsuri_1/matsuri_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/matsuri_1/matsuri_1.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mayi/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"mayi", 3 | "font_max":472, 4 | "font_size":55, 5 | "font_center_x":256, 6 | "font_center_y":487, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mayi/mayi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/mayi/mayi.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mea/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"mea", 3 | "font_max":380, 4 | "font_size":40, 5 | "font_center_x":200, 6 | "font_center_y":280, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mea/mea.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/mea/mea.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mengheqianli/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"mengheqianli", 3 | "font_max":250, 4 | "font_size":30, 5 | "font_center_x":135, 6 | "font_center_y":190, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mengheqianli/mengheqianli.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/mengheqianli/mengheqianli.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mingzhongguancha/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"mingzhongguancha", 3 | "font_max":472, 4 | "font_size":65, 5 | "font_center_x":256, 6 | "font_center_y":470, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/mingzhongguancha/mingzhongguancha.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/mingzhongguancha/mingzhongguancha.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/moe/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"moe", 3 | "font_max":284, 4 | "font_size":35, 5 | "font_center_x":152, 6 | "font_center_y":263, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/moe/moe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/moe/moe.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/names.json: -------------------------------------------------------------------------------- 1 | { 2 | "akai": "心心", 3 | "aqua": "阿夸", 4 | "chouyou": "臭鼬", 5 | "haoxue": "好学", 6 | "heishou": "黑手", 7 | "heishou_four": "逗乐了", 8 | "initial": "熊猫1", 9 | "jumozhanjiang": "奥利给", 10 | "kaguranana": "狗妈1", 11 | "kaguranana_1": "狗妈2", 12 | "kaguranana_2": "狗妈3", 13 | "matsuri": "马自立1", 14 | "matsuri_1": "马自立2", 15 | "katsuna": "kora", 16 | "keduoli": "珂学家", 17 | "mea": "财布", 18 | "panda_shuai": "熊猫2", 19 | "panda_tutou": "熊猫3", 20 | "qichuang": "守夜冠军", 21 | "xianbei": "恶臭", 22 | "jupai": "我爱你", 23 | "fbk1": "吹雪1", 24 | "fbk2": "吹雪2", 25 | "peko": "peko", 26 | "xingjie": "星姐", 27 | "lxy1": "粽子1", 28 | "lxy2": "粽子2", 29 | "alice": "爱丽丝", 30 | "serena": "猫猫", 31 | "kasumi": "猪", 32 | "mengheqianli": "猫猫猫", 33 | "gbf": "gvc", 34 | "mao": "猫", 35 | "ksm": "ksm", 36 | "kaguranana4": "狗妈4", 37 | "kaguranana5": "狗妈5", 38 | "moe": "moe", 39 | "kmmm": "小狐狸", 40 | "jojo": "jojo", 41 | "peko2": "peko2", 42 | "02": "02", 43 | "kiyohime": "清姬", 44 | "mingzhongguancha": "明中观察", 45 | "anzhongguancha": "暗中观察", 46 | "maotouying": "猫头嘤", 47 | "xuexiaoban": "血小板", 48 | "mayi": "麻衣" 49 | } 50 | -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/panda_shuai/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"panda_shuai", 3 | "font_max":170, 4 | "font_size":35, 5 | "font_center_x":100, 6 | "font_center_y":173, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/panda_shuai/panda_shuai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/panda_shuai/panda_shuai.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/panda_tutou/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"panda_tutou", 3 | "font_max":160, 4 | "font_size":40, 5 | "font_center_x":100, 6 | "font_center_y":170, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/panda_tutou/panda_tutou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/panda_tutou/panda_tutou.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/peko/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"peko", 3 | "font_max":460, 4 | "font_size":90, 5 | "font_center_x":250, 6 | "font_center_y":485, 7 | "color":"black", 8 | "font_sub":10 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/peko/peko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/peko/peko.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/peko2/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"peko2", 3 | "font_max":486, 4 | "font_size":105, 5 | "font_center_x":263, 6 | "font_center_y":63, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/peko2/peko2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/peko2/peko2.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/qichuang/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"qichuang", 3 | "font_max":440, 4 | "font_size":70, 5 | "font_center_x":250, 6 | "font_center_y":80, 7 | "color":"black", 8 | "font_sub":10 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/qichuang/qichuang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/qichuang/qichuang.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/qqdata/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/serena/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"serena", 3 | "font_max":470, 4 | "font_size":90, 5 | "font_center_x":250, 6 | "font_center_y":370, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/serena/serena.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/serena/serena.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/xianbei/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"xianbei", 3 | "font_max":280, 4 | "font_size":40, 5 | "font_center_x":150, 6 | "font_center_y":290, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/xianbei/xianbei.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/xianbei/xianbei.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/xingjie/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"xingjie", 3 | "font_max":180, 4 | "font_size":25, 5 | "font_center_x":100, 6 | "font_center_y":253, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/xingjie/xingjie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/xingjie/xingjie.jpg -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/xuexiaoban/config.ini: -------------------------------------------------------------------------------- 1 | { 2 | "name":"xuexiaoban", 3 | "font_max":759, 4 | "font_size":65, 5 | "font_center_x":400, 6 | "font_center_y":600, 7 | "color":"black", 8 | "font_sub":5 9 | } -------------------------------------------------------------------------------- /plugins/bot_custom_image/images/xuexiaoban/xuexiaoban.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_custom_image/images/xuexiaoban/xuexiaoban.jpg -------------------------------------------------------------------------------- /plugins/bot_dl_douyin.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | import base64 3 | import re 4 | from typing import Optional 5 | 6 | import httpx 7 | from botoy.decorators import ignore_botself, with_pattern 8 | from botoy.session import SessionHandler, ctx, session 9 | from pydantic import BaseModel 10 | 11 | __name__ = """抖音无水印""" 12 | __doc__ = """抖音视频下载 发送包含抖音视频链接的内容即可""" 13 | 14 | 15 | class Video(BaseModel): 16 | play: str 17 | cover: str 18 | 19 | 20 | class Music(BaseModel): 21 | play: str 22 | author: str = "" 23 | title: str = "" 24 | 25 | 26 | class Result(BaseModel): 27 | author: str = "" 28 | title: Optional[str] 29 | video: Optional[Video] 30 | music: Optional[Music] 31 | 32 | 33 | def fetch(url: str) -> Optional[Result]: 34 | with httpx.Client( 35 | headers={ 36 | "accept": "application/json", 37 | "user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1", 38 | }, 39 | timeout=20, 40 | ) as client: 41 | # 短链接转长链接 42 | resp: httpx.Response = client.get(url) 43 | if resp.status_code != 200: 44 | return None 45 | # 找到视频item_id 46 | found = re.findall(r"video/(\d+)", str(resp.url)) 47 | if found: 48 | item_id = found[0] 49 | resp = client.get( 50 | f"https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids={item_id}" 51 | ) 52 | if resp.json()["status_code"] == 0: 53 | info = resp.json()["item_list"][0] 54 | data = {} 55 | data["author"] = info["author"]["nickname"] 56 | data["title"] = info["desc"] 57 | if "music" in info: 58 | data["music"] = Music( 59 | play=info["music"]["play_url"]["uri"], 60 | author=info["music"]["author"], 61 | title=info["music"]["author"], 62 | ) 63 | data["video"] = Video( 64 | play=info["video"]["play_addr"]["url_list"][0].replace( 65 | "playwm", "play" 66 | ), 67 | cover=info["video"]["cover"]["url_list"][0], 68 | ) 69 | return Result(**data) 70 | return None 71 | 72 | 73 | douyin = SessionHandler( 74 | ignore_botself, 75 | with_pattern(r"(https://v\.douyin\.com/\w+)"), 76 | ).receive_group_msg() 77 | 78 | 79 | def get_content_base64(url) -> str: 80 | try: 81 | resp = httpx.get( 82 | url, 83 | headers={ 84 | "user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1", 85 | }, 86 | timeout=20, 87 | ) 88 | resp.raise_for_status() 89 | except Exception: 90 | pass 91 | else: 92 | return base64.b64encode(resp.content).decode() 93 | return None 94 | 95 | 96 | @douyin.handle 97 | def _(): 98 | url = ctx._pattern_result[0] # type: ignore 99 | 100 | need = session.want("need", "检测到抖音链接,是否需要获取该链接相关资源,20s内回复【是】表示需要", timeout=20) 101 | if need != "是": 102 | douyin.finish() 103 | result = fetch(url) 104 | if result is None: 105 | douyin.finish("不知道哪里出了问题, 获取视频信息失败") 106 | 107 | if result.video is not None: 108 | need_video = session.want( 109 | "need_video", "20s内回复【好】下载《无水印视频》, 回复其他任意内容跳过进入下载其他资源", timeout=20 110 | ) 111 | if need_video == "好": 112 | video_name = f"{result.author}-{result.title}.mp4" 113 | session.send_pic(url=result.video.cover, text="正在下载: " + video_name) 114 | 115 | video_base64 = get_content_base64(result.video.play) 116 | if video_base64: 117 | session.action.uploadGroupFile( 118 | ctx.FromGroupId, fileBase64=video_base64, fileName=video_name 119 | ) 120 | else: 121 | session.send_text("下载视频失败") 122 | # music 123 | if result.music is not None: 124 | need_music = session.want("need_music", "20s内回复【ok】下载《背景音乐》", timeout=20) 125 | if need_music == "ok": 126 | music_name = "{}-{}.mp3".format(result.music.author, result.music.title) 127 | session.send_text("正在下载: " + music_name) 128 | 129 | music_base64 = get_content_base64(result.music.play) 130 | if music_base64: 131 | session.action.uploadGroupFile( 132 | ctx.FromGroupId, fileBase64=music_base64, fileName=music_name 133 | ) 134 | else: 135 | session.send_text("下载音乐失败") 136 | 137 | # ============== 138 | douyin.finish() 139 | -------------------------------------------------------------------------------- /plugins/bot_generateQrcode.py: -------------------------------------------------------------------------------- 1 | """生成二维码,格式:生成二维码{内容}""" 2 | import base64 3 | import io 4 | 5 | from botoy import GroupMsg 6 | from botoy.decorators import ignore_botself, startswith, these_msgtypes 7 | from botoy.sugar import Picture 8 | 9 | try: 10 | import qrcode 11 | except ImportError as e: 12 | raise ImportError("请先安装依赖库: pip install qrcode") from e 13 | 14 | 15 | def gen_qrcode(text: str) -> str: 16 | img = qrcode.make(text) 17 | img_buffer = io.BytesIO() 18 | img.save(img_buffer) 19 | return base64.b64encode(img_buffer.getvalue()).decode() 20 | 21 | 22 | @ignore_botself 23 | @these_msgtypes("TextMsg") 24 | @startswith("生成二维码") 25 | def receive_group_msg(ctx: GroupMsg): 26 | Picture(pic_base64=gen_qrcode(ctx.Content[5:])) 27 | -------------------------------------------------------------------------------- /plugins/bot_genshin_calendar/__init__.py: -------------------------------------------------------------------------------- 1 | """原神活动日历 2 | 原神日历 : 查看本群订阅服务器日历 3 | 原神日历 on/off : 订阅/取消订阅指定服务器的日历推送 4 | 原神日历 time 时:分 : 设置日历推送时间 5 | 原神日历 status : 查看本群日历推送设置 6 | """ 7 | 8 | import json 9 | import re 10 | import traceback 11 | from typing import Optional 12 | 13 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 14 | from botoy import AsyncAction, S 15 | from botoy.async_decorators import on_regexp 16 | from botoy.contrib import get_cache_dir 17 | from botoy.model import GroupMsg 18 | 19 | from .generate import * 20 | 21 | scheduler = AsyncIOScheduler() 22 | 23 | group_data = {} 24 | 25 | action: Optional[AsyncAction] = None 26 | 27 | data_path = get_cache_dir("genshin_calendar") / "data.json" 28 | 29 | 30 | def load_data(): 31 | if not data_path.is_file(): 32 | return 33 | try: 34 | with open(data_path, encoding="utf8") as f: 35 | data = json.load(f) 36 | for k, v in data.items(): 37 | group_data[k] = v 38 | except: 39 | traceback.print_exc() 40 | 41 | 42 | def save_data(): 43 | try: 44 | with open(data_path, "w", encoding="utf8") as f: 45 | json.dump(group_data, f, ensure_ascii=False) 46 | except: 47 | traceback.print_exc() 48 | 49 | 50 | async def send_calendar(group_id): 51 | for server in group_data[str(group_id)]["server_list"]: 52 | im = await generate_day_schedule(server) 53 | base64_str = im2base64str(im) 54 | if action: 55 | await action.sendGroupPic(group_id, picBase64Buf=base64_str) 56 | 57 | 58 | def update_group_schedule(group_id): 59 | group_id = str(group_id) 60 | if group_id not in group_data: 61 | return 62 | scheduler.add_job( 63 | send_calendar, 64 | "cron", 65 | args=(group_id,), 66 | id=f"calendar_{group_id}", 67 | replace_existing=True, 68 | hour=group_data[group_id]["hour"], 69 | minute=group_data[group_id]["minute"], 70 | ) 71 | 72 | 73 | @on_regexp(r"^原神日[历程](.*)") 74 | async def start_scheduled(ctx: GroupMsg): 75 | global action 76 | if not action: 77 | action = AsyncAction(ctx.CurrentQQ, ctx._port, ctx._host) 78 | group_id = str(ctx.FromGroupId) 79 | server = "cn" 80 | cmd = ctx._match.group(1) 81 | if not cmd: 82 | im = await generate_day_schedule(server) 83 | return await S.aimage(im2base64str(im)) 84 | 85 | if group_id not in group_data: 86 | group_data[group_id] = { 87 | "server_list": [], 88 | "hour": 8, 89 | "minute": 0, 90 | "cardimage": False, 91 | } 92 | # TODO:检查权限 93 | if "on" in cmd: 94 | if server not in group_data[group_id]["server_list"]: 95 | group_data[group_id]["server_list"].append(server) 96 | msg = "原神日程推送已开启" 97 | elif "off" in cmd: 98 | if server in group_data[group_id]["server_list"]: 99 | group_data[group_id]["server_list"].remove(server) 100 | msg = "原神日程推送已关闭" 101 | elif "time" in cmd: 102 | match = re.search(r"(\d*):(\d*)", cmd) 103 | if not match or len(match.groups()) < 2: 104 | msg = "请指定推送时间" 105 | else: 106 | group_data[group_id]["hour"] = int(match.group(1)) 107 | group_data[group_id]["minute"] = int(match.group(2)) 108 | msg = f"推送时间已设置为: {group_data[group_id]['hour']}:{group_data[group_id]['minute']:02d}" 109 | elif "status" in cmd: 110 | msg = f"订阅日历: {group_data[group_id]['server_list']}" 111 | msg += f"\n推送时间: {group_data[group_id]['hour']}:{group_data[group_id]['minute']:02d}" 112 | else: 113 | return 114 | update_group_schedule(group_id) 115 | save_data() 116 | await S.atext(msg) 117 | 118 | 119 | receive_group_msg = start_scheduled 120 | 121 | # startup 122 | load_data() 123 | for group_id in group_data: 124 | update_group_schedule(group_id) 125 | -------------------------------------------------------------------------------- /plugins/bot_genshin_calendar/draw.py: -------------------------------------------------------------------------------- 1 | from botoy.contrib import download, get_cache_dir 2 | from PIL import Image, ImageDraw, ImageFont 3 | 4 | from .event import * 5 | 6 | font_path = get_cache_dir("genshin_calendar") / "wqy-microhei.ttc" 7 | if not font_path.is_file(): 8 | download( 9 | "https://cdn.staticaly.com/gh/NepPure/genshin_calendar/master/wqy-microhei.ttc", 10 | font_path, 11 | ) 12 | font = ImageFont.truetype(str(font_path), 20) 13 | 14 | width = 500 15 | 16 | color = [ 17 | {"front": "black", "back": "white"}, 18 | {"front": "white", "back": "ForestGreen"}, 19 | {"front": "white", "back": "DarkOrange"}, 20 | {"front": "white", "back": "BlueViolet"}, 21 | ] 22 | 23 | 24 | def create_image(item_number): 25 | height = item_number * 30 26 | im = Image.new("RGBA", (width, height), (255, 255, 255, 0)) 27 | return im 28 | 29 | 30 | def draw_rec(im, color, x, y, w, h, r): 31 | draw = ImageDraw.Draw(im) 32 | draw.rectangle((x + r, y, x + w - r, y + h), fill=color) 33 | draw.rectangle((x, y + r, x + w, y + h - r), fill=color) 34 | r = r * 2 35 | draw.ellipse((x, y, x + r, y + r), fill=color) 36 | draw.ellipse((x + w - r, y, x + w, y + r), fill=color) 37 | draw.ellipse((x, y + h - r, x + r, y + h), fill=color) 38 | draw.ellipse((x + w - r, y + h - r, x + w, y + h), fill=color) 39 | 40 | 41 | def draw_text(im, x, y, w, h, text, align, color): 42 | draw = ImageDraw.Draw(im) 43 | tw, th = draw.textsize(text, font=font) 44 | y = y + (h - th) / 2 45 | if align == 0: # 居中 46 | x = x + (w - tw) / 2 47 | elif align == 1: # 左对齐 48 | x = x + 5 49 | elif align == 2: # 右对齐 50 | x = x + w - tw - 5 51 | draw.text((x, y), text, fill=color, font=font) 52 | 53 | 54 | def draw_item(im, n, t, text, days, forever): 55 | if t >= len(color): 56 | t = 1 57 | x = 0 58 | y = n * 30 59 | height = 28 60 | 61 | draw_rec(im, color[t]["back"], x, y, width, height, 6) 62 | 63 | im1 = Image.new("RGBA", (width - 120, 28), (255, 255, 255, 0)) 64 | draw_text(im1, 0, 0, width, height, text, 1, color[t]["front"]) 65 | _, _, _, a = im1.split() 66 | im.paste(im1, (x, y), mask=a) 67 | 68 | if days > 0: 69 | if forever: 70 | text1 = "永久开放" 71 | else: 72 | text1 = f"{days}天后结束" 73 | elif days < 0: 74 | text1 = f"{-days}天后开始" 75 | else: 76 | text1 = "即将结束" 77 | draw_text(im, x, y, width, height, text1, 2, color[t]["front"]) 78 | 79 | 80 | def draw_title(im, n, left=None, middle=None, right=None): 81 | x = 0 82 | y = n * 30 83 | height = 28 84 | 85 | draw_rec(im, color[0]["back"], x, y, width, height, 6) 86 | if middle: 87 | draw_text(im, x, y, width, height, middle, 0, color[0]["front"]) 88 | if left: 89 | draw_text(im, x, y, width, height, left, 1, color[0]["front"]) 90 | if right: 91 | draw_text(im, x, y, width, height, right, 2, color[0]["front"]) 92 | 93 | 94 | def draw_title1(im, n, day_list): 95 | x = 0 96 | y = n * 30 97 | height = 28 98 | color = "black" 99 | 100 | n = len(day_list) 101 | for i in range(n): 102 | x = width / n * i 103 | draw_text(im, x, y, width, height, day_list[i], 1, color) 104 | -------------------------------------------------------------------------------- /plugins/bot_genshin_calendar/event.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import math 3 | from datetime import datetime, timedelta 4 | 5 | import httpx 6 | from dateutil.relativedelta import relativedelta 7 | 8 | # type 0 普通常驻任务深渊 1 新闻 2 蛋池 3 限时活动H5 9 | 10 | event_data = { 11 | "cn": [], 12 | } 13 | 14 | event_updated = { 15 | "cn": "", 16 | } 17 | 18 | lock = { 19 | "cn": asyncio.Lock(), 20 | } 21 | 22 | ignored_key_words = ["修复", "版本内容专题页", "米游社", "调研", "防沉迷"] 23 | 24 | ignored_ann_ids = [ 25 | 495, # 有奖问卷调查开启! 26 | 1263, # 米游社《原神》专属工具一览 27 | 423, # 《原神》玩家社区一览 28 | 422, # 《原神》防沉迷系统说明 29 | 762, # 《原神》公平运营声明 30 | ] 31 | 32 | 33 | async def query_data(url): 34 | try: 35 | async with httpx.AsyncClient() as cilent: 36 | return (await cilent.get(url, timeout=10)).json() 37 | except: 38 | pass 39 | 40 | 41 | async def load_event_cn(): 42 | result = await query_data( 43 | "https://hk4e-api-static.mihoyo.com/common/hk4e_cn/announcement/api/getAnnList?game=hk4e&game_biz=hk4e_cn&lang=zh-cn&bundle_id=hk4e_cn&platform=pc®ion=cn_gf01&level=55&uid=100000000" 44 | ) 45 | if result and "retcode" in result and result["retcode"] == 0: 46 | event_data["cn"] = [] 47 | datalist = result["data"]["list"] 48 | for data in datalist: 49 | for item in data["list"]: 50 | # 1 活动公告 2 游戏公告 51 | if item["type"] == 2: 52 | ignore = False 53 | for ann_id in ignored_ann_ids: 54 | if ann_id == item["ann_id"]: 55 | ignore = True 56 | break 57 | if ignore: 58 | continue 59 | 60 | for keyword in ignored_key_words: 61 | if keyword in item["title"]: 62 | ignore = True 63 | break 64 | if ignore: 65 | continue 66 | 67 | start_time = datetime.strptime(item["start_time"], r"%Y-%m-%d %H:%M:%S") 68 | end_time = datetime.strptime(item["end_time"], r"%Y-%m-%d %H:%M:%S") 69 | event = { 70 | "title": item["title"], 71 | "start": start_time, 72 | "end": end_time, 73 | "forever": False, 74 | "type": 0, 75 | } 76 | if "任务" in item["title"]: 77 | event["forever"] = True 78 | if item["type"] == 1: 79 | event["type"] = 1 80 | if "扭蛋" in item["tag_label"]: 81 | event["type"] = 2 82 | if "倍" in item["title"]: 83 | event["type"] = 3 84 | event_data["cn"].append(event) 85 | # 深渊提醒 86 | i = 0 87 | while i < 2: 88 | curmon = datetime.today() + relativedelta(months=i) 89 | nextmon = curmon + relativedelta(months=1) 90 | event_data["cn"].append( 91 | { 92 | "title": "「深境螺旋」", 93 | "start": datetime.strptime( 94 | curmon.strftime("%Y/%m/01 04:00"), r"%Y/%m/%d %H:%M" 95 | ), 96 | "end": datetime.strptime( 97 | curmon.strftime("%Y/%m/16 03:59"), r"%Y/%m/%d %H:%M" 98 | ), 99 | "forever": False, 100 | "type": 3, 101 | } 102 | ) 103 | event_data["cn"].append( 104 | { 105 | "title": "「深境螺旋」", 106 | "start": datetime.strptime( 107 | curmon.strftime("%Y/%m/16 04:00"), r"%Y/%m/%d %H:%M" 108 | ), 109 | "end": datetime.strptime( 110 | nextmon.strftime("%Y/%m/01 03:59"), r"%Y/%m/%d %H:%M" 111 | ), 112 | "forever": False, 113 | "type": 3, 114 | } 115 | ) 116 | i = i + 1 117 | 118 | return 0 119 | return 1 120 | 121 | 122 | async def load_event(server): 123 | if server == "cn": 124 | return await load_event_cn() 125 | return 1 126 | 127 | 128 | def get_pcr_now(offset): 129 | pcr_now = datetime.now() 130 | if pcr_now.hour < 4: 131 | pcr_now -= timedelta(days=1) 132 | pcr_now = pcr_now.replace(hour=18, minute=0, second=0, microsecond=0) # 用晚6点做基准 133 | pcr_now = pcr_now + timedelta(days=offset) 134 | return pcr_now 135 | 136 | 137 | async def get_events(server, offset, days): 138 | events = [] 139 | pcr_now = datetime.now() 140 | if pcr_now.hour < 4: 141 | pcr_now -= timedelta(days=1) 142 | pcr_now = pcr_now.replace(hour=18, minute=0, second=0, microsecond=0) # 用晚6点做基准 143 | 144 | await lock[server].acquire() 145 | try: 146 | t = pcr_now.strftime("%y%m%d") 147 | if event_updated[server] != t: 148 | if await load_event(server) == 0: 149 | event_updated[server] = t 150 | finally: 151 | lock[server].release() 152 | 153 | start = pcr_now + timedelta(days=offset) 154 | end = start + timedelta(days=days) 155 | end -= timedelta(hours=18) # 晚上12点结束 156 | 157 | for event in event_data[server]: 158 | if end > event["start"] and start < event["end"]: # 在指定时间段内 已开始 且 未结束 159 | event["start_days"] = math.ceil( 160 | (event["start"] - start) / timedelta(days=1) 161 | ) # 还有几天开始 162 | event["left_days"] = math.floor( 163 | (event["end"] - start) / timedelta(days=1) 164 | ) # 还有几天结束 165 | events.append(event) 166 | # 按type从大到小 按剩余天数从小到大 167 | events.sort(key=lambda item: item["type"] * 100 - item["left_days"], reverse=True) 168 | return events 169 | 170 | 171 | if __name__ == "__main__": 172 | 173 | async def main(): 174 | await load_event_cn() 175 | 176 | loop = asyncio.get_event_loop() 177 | loop.run_until_complete(main()) 178 | -------------------------------------------------------------------------------- /plugins/bot_genshin_calendar/generate.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from io import BytesIO 3 | 4 | from .draw import * 5 | from .event import * 6 | 7 | 8 | def im2base64str(im): 9 | io = BytesIO() 10 | im.save(io, "png") 11 | return base64.b64encode(io.getvalue()).decode() 12 | 13 | 14 | async def generate_day_schedule(server="cn"): 15 | events = await get_events(server, 0, 15) 16 | 17 | has_prediction = False 18 | for event in events: 19 | if event["start_days"] > 0: 20 | has_prediction = True 21 | if has_prediction: 22 | im = create_image(len(events) + 2) 23 | else: 24 | im = create_image(len(events) + 1) 25 | 26 | title = f"原神日历" 27 | pcr_now = get_pcr_now(0) 28 | draw_title(im, 0, title, pcr_now.strftime("%Y/%m/%d"), "正在进行") 29 | 30 | if len(events) == 0: 31 | draw_item(im, 1, 1, "无数据", 0, False) 32 | i = 1 33 | for event in events: 34 | if event["start_days"] <= 0: 35 | draw_item( 36 | im, 37 | i, 38 | event["type"], 39 | event["title"], 40 | event["left_days"], 41 | event["forever"], 42 | ) 43 | i += 1 44 | if has_prediction: 45 | draw_title(im, i, right="即将开始") 46 | for event in events: 47 | if event["start_days"] > 0: 48 | i += 1 49 | draw_item( 50 | im, 51 | i, 52 | event["type"], 53 | event["title"], 54 | -event["start_days"], 55 | event["forever"], 56 | ) 57 | return im 58 | -------------------------------------------------------------------------------- /plugins/bot_github_thumbnail.py: -------------------------------------------------------------------------------- 1 | import re 2 | import secrets 3 | 4 | import httpx 5 | from botoy import GroupMsg, S, decorators 6 | 7 | __name__ = "Github仓库缩略图" 8 | 9 | 10 | def githubasset(owner, repo, type=None, flag=None): 11 | url = ( 12 | f"https://opengraph.githubassets.com/{secrets.token_urlsafe(16)}/{owner}/{repo}" 13 | ) 14 | if type: 15 | return f"{url}/{type}/{flag}" 16 | return url 17 | 18 | 19 | @decorators.ignore_botself 20 | def receive_group_msg(ctx: GroupMsg): 21 | matched = re.match( 22 | r"^(?P[a-zA-Z0-9][a-zA-Z0-9\-]*)/" r"(?P[a-zA-Z0-9_\-]+).*$", 23 | ctx.Content, 24 | ) 25 | found = re.findall( 26 | r"github\.com/([a-zA-Z0-9][a-zA-Z0-9\-]*)/([a-zA-Z0-9_\-]+)", ctx.Content 27 | ) 28 | if matched: 29 | owner, repo = matched["owner"], matched["repo"] 30 | elif found: 31 | owner, repo = found[0][0], found[0][1] 32 | else: 33 | return 34 | 35 | # process commit/issues/pull 36 | try: 37 | type, flag = re.findall( 38 | r"/(commit|issues|pull)/([a-zA-Z0-9_\-]+)", ctx.Content 39 | )[0] 40 | except Exception: 41 | type = flag = None 42 | 43 | try: 44 | resp = httpx.get(f"https://api.github.com/repos/{owner}/{repo}", timeout=20) 45 | resp.raise_for_status() 46 | except Exception: 47 | pass 48 | else: 49 | S.image(githubasset(owner, repo, type, flag)) 50 | -------------------------------------------------------------------------------- /plugins/bot_jikipedia.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List 3 | 4 | import httpx 5 | from botoy import GroupMsg, Picture, Text 6 | from botoy.decorators import ignore_botself 7 | from pydantic import BaseModel 8 | 9 | 10 | class Entry(BaseModel): 11 | title: str 12 | text: str = "" 13 | tags: List[str] = [] 14 | images: List[str] = [] 15 | 16 | 17 | def search(word) -> List[Entry]: 18 | ret = [] 19 | 20 | try: 21 | resp = httpx.post( 22 | "https://api.jikipedia.com/go/search_definitions", 23 | json={"page": 1, "phrase": word}, 24 | timeout=20, 25 | ) 26 | for item in resp.json()["data"]: 27 | title = item["term"]["title"] 28 | tags = [tag["name"] for tag in item["tags"]] 29 | text = item["plaintext"] 30 | images = [image["full"]["path"] for image in item["images"]] 31 | ret.append(Entry(title=title, text=text, tags=tags, images=images)) 32 | except Exception: 33 | pass 34 | 35 | return ret 36 | 37 | 38 | @ignore_botself 39 | def receive_group_msg(ctx: GroupMsg): 40 | try: 41 | word = re.findall( 42 | r"[查|问|这|这个]{0,}(.*?)[是|叫|又是]{0,}[啥|什么|啥子]{1,}梗", ctx.Content 43 | )[0] 44 | except Exception: 45 | pass 46 | else: 47 | entries = search(word) 48 | if entries: 49 | entry = entries[0] 50 | msg = "是在查梗吗?:{word}\n\n标题:【{title}】\n\n标签:{tags}\n\n正文:{text}".format( 51 | word=word, 52 | title=entry.title, 53 | tags="、".join(entry.tags), 54 | text=entry.text, 55 | ) 56 | if entry.images: 57 | Picture(pic_url=entry.images[0], text=msg) 58 | else: 59 | Text(msg) 60 | -------------------------------------------------------------------------------- /plugins/bot_juejuezi.py: -------------------------------------------------------------------------------- 1 | """绝绝子生成器 2 | 1. 绝绝子更新: 更新词库 3 | 2. 绝绝子+{内容}: 内容请使用空格间隔动词与名词,例如:喝奶茶 => 喝 奶茶 4 | """ 5 | import random 6 | from typing import List 7 | 8 | from botoy import GroupMsg, S 9 | from botoy import decorators as deco 10 | from botoy.contrib import download, get_cache_dir 11 | from pydantic import BaseModel 12 | 13 | data_path = get_cache_dir("juejuezi") / "juejuezi.json" 14 | 15 | 16 | def download_material(): 17 | download( 18 | "https://cdn.jsdelivr.net/gh/kingcos/JueJueZiGenerator@main/materials.json", 19 | data_path, 20 | ) 21 | 22 | 23 | if not data_path.exists(): 24 | download_material() 25 | 26 | 27 | class Emotion(BaseModel): 28 | emoji: List[str] 29 | xiaohongshu: List[str] 30 | weibo: List[str] 31 | 32 | 33 | class Material(BaseModel): 34 | emotions: Emotion # 表情 35 | symbols: List[str] 36 | auxiliaryWords: List[str] 37 | dividers: List[str] # 断句符 38 | fashion: List[str] # 潮流 39 | attribute: List[str] # 定语 40 | 41 | beginning: List[str] # 开头 42 | ending: List[str] # 结尾 43 | who: List[str] # 主语 44 | someone: List[str] # 和/跟谁 45 | 46 | todosth: List[str] # 干什么 47 | another: List[str] # 扯另一个淡 48 | collections: List[str] # 一些固定搭配 49 | default: List[str] # 默认 something 50 | 51 | 52 | material = Material.parse_file(data_path) 53 | 54 | 55 | class Random: 56 | @staticmethod 57 | def word(words: List[str], nullable=False, divier="") -> str: 58 | word = random.choice( 59 | [*words, *([""] * (nullable and int(len(words) / 3) or 0))] 60 | ) 61 | if word: 62 | return word + divier 63 | return "" 64 | 65 | @staticmethod 66 | def word_not_contain(words: List[str], already: str) -> str: 67 | word = Random.word(words) 68 | 69 | word_set = set(word.replace(" ", "")) 70 | already_set = set(already.replace(" ", "")) 71 | 72 | if len(word_set & already_set) == 0: 73 | return word 74 | 75 | return Random.word_not_contain(words, already) 76 | 77 | @staticmethod 78 | def words(words_: List[str], count: int) -> List[str]: 79 | if len(words_) >= count: 80 | words_ = words_.copy() 81 | random.shuffle(words_) 82 | return words_[:count] 83 | 84 | @staticmethod 85 | def repeat(word: str, times: int = -1) -> str: 86 | if times > 0: 87 | return word * times 88 | 89 | num = random.randint(1, 3) 90 | if num == 2: 91 | return "" 92 | return word * num 93 | 94 | 95 | def generate_beginning(divider: str): 96 | beginning = ( 97 | Random.word(material.beginning) 98 | .replace("who", Random.word(material.who)) 99 | .replace("someone", Random.word(material.someone)) 100 | ) 101 | emotion = Random.word(material.emotions.emoji, True) 102 | return beginning + emotion + divider 103 | 104 | 105 | def generate_dosth(something: str, divider: str): 106 | todosth = Random.word(material.todosth).replace(" ", "").replace("dosth", something) 107 | emotion = Random.repeat(Random.word(material.emotions.emoji)) 108 | return todosth + emotion + divider 109 | 110 | 111 | def praise_sth(something: str, praised_words: List[str], has_also=False) -> str: 112 | praise_word = Random.word(praised_words) 113 | 114 | verb, noun = something.split(" ")[:2] 115 | 116 | intro = Random.word(["这家的", "这家店的", "这个", "这件", "这杯"]) 117 | also = has_also and "也" or "" 118 | 119 | praise_word.replace("dosth", verb) 120 | 121 | return intro + noun + also + praise_word 122 | 123 | 124 | def generate(something: str) -> str: 125 | divider = Random.word(material.dividers) 126 | 127 | fashion_words = Random.words(material.fashion, len(material.fashion)) 128 | 129 | first = generate_beginning(divider) 130 | second = fashion_words[0] + divider 131 | third = generate_dosth(something, divider) 132 | forth = fashion_words[1] + divider 133 | fifth = Random.repeat(Random.word(material.auxiliaryWords), 3) + divider 134 | sixth = praise_sth(something, material.attribute) + Random.repeat( 135 | Random.word(material.symbols), 3 136 | ) 137 | seventh = praise_sth( 138 | Random.word_not_contain(material.another, something), material.attribute, True 139 | ) + Random.repeat(Random.word(material.symbols), 3) 140 | eighth = fashion_words[2] + divider 141 | ninth = ( 142 | Random.word(material.collections, True, divider) + fashion_words[3] + divider 143 | ) 144 | tenth = Random.repeat(Random.word(material.auxiliaryWords), 3) + divider 145 | last = Random.word(material.ending) + Random.word(material.emotions.emoji) 146 | 147 | return ( 148 | first 149 | + second 150 | + third 151 | + forth 152 | + fifth 153 | + sixth 154 | + seventh 155 | + eighth 156 | + ninth 157 | + tenth 158 | + last 159 | ) 160 | 161 | 162 | # bot 163 | @deco.ignore_botself 164 | @deco.startswith("绝绝子") 165 | def receive_group_msg(ctx: GroupMsg): 166 | do = ctx.Content[3:].strip() 167 | if not do: 168 | return 169 | 170 | if do == "更新": 171 | try: 172 | download_material() 173 | except Exception: 174 | S.bind(ctx).text("下载出错") 175 | else: 176 | S.bind(ctx).text("好了,没事不要老更新") 177 | else: 178 | try: 179 | sentence = generate(do) 180 | except: 181 | pass 182 | else: 183 | S.bind(ctx).text(sentence) 184 | 185 | 186 | if __name__ == "__main__": 187 | for _ in range(3): 188 | print(generate("想 躺平")) 189 | -------------------------------------------------------------------------------- /plugins/bot_kiss_gif/__init__.py: -------------------------------------------------------------------------------- 1 | """制作亲亲表情包 2 | 1. AT两个人,并发送kiss 3 | 2. AT一个人发送kiss 4 | 3. At一个人加一张图和kiss 5 | 4. 发送两张图和kiss 6 | 提示:将kiss改为kissR可切换亲和被亲的对象 7 | """ 8 | from botoy import GroupMsg, S 9 | from botoy.decorators import ignore_botself 10 | from botoy.parser import group as gp 11 | 12 | from .module import kiss 13 | 14 | 15 | @ignore_botself 16 | def receive_group_msg(ctx: GroupMsg): 17 | o = t = None 18 | 19 | if ctx.MsgType == "PicMsg": 20 | pic = gp.pic(ctx) 21 | if not pic: 22 | return 23 | if not "kiss" in pic.Content: 24 | return 25 | # 2 张图 26 | if len(pic.GroupPic) >= 2: 27 | o, t = pic.GroupPic[0].Url, pic.GroupPic[1].Url 28 | # 1张图,没有at就是发送人 29 | else: 30 | t = pic.GroupPic[0].Url 31 | # 有at 32 | if pic.UserExt: 33 | o = pic.UserExt[0].QQUid 34 | # 无 at 35 | else: 36 | o = ctx.FromUserId 37 | if "kissR" in pic.Content: 38 | o, t = t, o 39 | elif ctx.MsgType == "AtMsg": 40 | at = gp.at(ctx) 41 | if not at: 42 | return 43 | if not "kiss" in at.Content: 44 | return 45 | # at 两个人 46 | if len(at.UserID) >= 2: 47 | o, t = at.UserID[:2] 48 | else: 49 | # at 一个人 50 | o = ctx.FromUserId 51 | t = at.UserID[0] 52 | 53 | if "kissR" in at.Content: 54 | o, t = t, o 55 | 56 | if o and t: 57 | S.bind(ctx).image(kiss(o, t)) 58 | -------------------------------------------------------------------------------- /plugins/bot_kiss_gif/frames.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_kiss_gif/frames.zip -------------------------------------------------------------------------------- /plugins/bot_kiss_gif/module.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import shutil 3 | from io import BytesIO 4 | from typing import Union 5 | 6 | import httpx 7 | from botoy.contrib import get_cache_dir 8 | from PIL import Image, ImageDraw 9 | 10 | HERE = pathlib.Path(__file__).parent.absolute() 11 | FRAMES_DIR = get_cache_dir("kiss_gif") / "frames" 12 | if not FRAMES_DIR.exists() or not FRAMES_DIR.is_dir(): 13 | ARCHIVE = HERE / "frames.zip" 14 | shutil.unpack_archive(ARCHIVE, FRAMES_DIR) 15 | 16 | 17 | def get_avator(image: Union[int, str]): 18 | if isinstance(image, int): 19 | image = f"http://q1.qlogo.cn/g?b=qq&nk={image}&s=640" 20 | 21 | content = httpx.get(image, timeout=20).content 22 | return Image.open(BytesIO(content)).convert("RGBA") 23 | 24 | 25 | OPERATOR_X = [92, 135, 84, 80, 155, 60, 50, 98, 35, 38, 70, 84, 75] 26 | OPERATOR_Y = [64, 40, 105, 110, 82, 96, 80, 55, 65, 100, 80, 65, 65] 27 | TARGET_X = [58, 62, 42, 50, 56, 18, 28, 54, 46, 60, 35, 20, 40] 28 | TARGET_Y = [90, 95, 100, 100, 100, 120, 110, 100, 100, 100, 115, 120, 96] 29 | 30 | 31 | def kiss(operator, target) -> BytesIO: 32 | operator = get_avator(operator) 33 | target = get_avator(target) 34 | 35 | operator = operator.resize((40, 40), Image.ANTIALIAS) 36 | size = operator.size 37 | r2 = min(size[0], size[1]) 38 | circle = Image.new("L", (r2, r2), 0) 39 | draw = ImageDraw.Draw(circle) 40 | draw.ellipse((0, 0, r2, r2), fill=255) 41 | alpha = Image.new("L", (r2, r2), 255) 42 | alpha.paste(circle, (0, 0)) 43 | operator.putalpha(alpha) 44 | 45 | target = target.resize((50, 50), Image.ANTIALIAS) 46 | size = target.size 47 | r2 = min(size[0], size[1]) 48 | circle = Image.new("L", (r2, r2), 0) 49 | draw = ImageDraw.Draw(circle) 50 | draw.ellipse((0, 0, r2, r2), fill=255) 51 | alpha = Image.new("L", (r2, r2), 255) 52 | alpha.paste(circle, (0, 0)) 53 | target.putalpha(alpha) 54 | 55 | ########### 56 | frames = [] 57 | for idx in range(13): 58 | target_temp = target.convert("RGBA") 59 | operator_temp = operator.convert("RGBA") 60 | 61 | bg = Image.open(str(FRAMES_DIR / f"{idx+1}.png")) 62 | frame = Image.new("RGBA", (200, 200), (255, 255, 255)) 63 | frame.paste(bg, (0, 0)) 64 | frame.paste(target_temp, (TARGET_X[idx], TARGET_Y[idx]), target_temp) 65 | frame.paste(operator_temp, (OPERATOR_X[idx], OPERATOR_Y[idx]), operator_temp) 66 | frames.append(frame) 67 | 68 | buffer = BytesIO() 69 | frames[0].save( 70 | buffer, format="gif", append_images=frames, save_all=True, duration=10, loop=0 71 | ) 72 | return buffer 73 | -------------------------------------------------------------------------------- /plugins/bot_meiriyiwen.py: -------------------------------------------------------------------------------- 1 | """每日一文:发送 好文 来读一篇好文章吧""" 2 | import httpx 3 | from botoy import Text 4 | from botoy.decorators import equal_content, ignore_botself 5 | from bs4 import BeautifulSoup 6 | 7 | 8 | def get() -> str: 9 | try: 10 | resp = httpx.get("https://v1.alapi.cn/api/mryw/random", timeout=20) 11 | resp.raise_for_status() 12 | except Exception: 13 | pass 14 | else: 15 | ret = resp.json() 16 | if ret["code"] == 200: 17 | title = ret["data"]["title"] 18 | author = ret["data"]["author"] 19 | text = BeautifulSoup(ret["data"]["content"], "html.parser").get_text( 20 | separator="\n\n" 21 | ) 22 | return f"【{title}】{author}\n\n{text}" 23 | return "" 24 | 25 | 26 | @ignore_botself 27 | @equal_content("好文") 28 | def receive_group_msg(_): 29 | article = get() 30 | if article is not None: 31 | Text(article) 32 | -------------------------------------------------------------------------------- /plugins/bot_moechat.py: -------------------------------------------------------------------------------- 1 | import random 2 | import threading 3 | from typing import Optional 4 | 5 | import httpx 6 | from botoy import GroupMsg, jconfig 7 | from botoy.decorators import ignore_botself 8 | from botoy.session import SessionHandler, ctx, session 9 | 10 | CHANCE = jconfig.moechat_chance or 0.2 11 | GROUPS = jconfig.moechat_groups or [] 12 | 13 | # version: https://github.com/Kyomotoi/AnimeThesaurus/blob/8c75caf9f05451ca9fe7f2b024a16f3c93de41d7/data.json 14 | words = { 15 | "mua": [ 16 | "你想干嘛?(一脸嫌弃地后退)", 17 | "诶……不可以随便亲亲啦", 18 | "(亲了一下你)", 19 | "只......只许这一次哦///////", 20 | "唔...诶诶诶!!!", 21 | "mua~", 22 | "rua!大hentai!想...想亲咱就直说嘛⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄", 23 | "!啾~~!", 24 | ], 25 | "摸摸": [ 26 | "感觉你就像咱很久之前认识的一个人呢,有种莫名安心的感觉(>﹏<)", 27 | "舒服w,蹭蹭~", 28 | "唔。。头发要乱啦", 29 | "呼噜呼噜~", 30 | "再摸一次~", 31 | "好舒服,蹭蹭~", 32 | "不行那里不可以(´///ω/// `)", 33 | "再摸咱就长不高啦~", 34 | "你的手总是那么暖和呢~", 35 | "好吧~_~,就一下下哦……唔~好了……都两下了……(害羞)", 36 | "不可以总摸的哦,不然的话,会想那个的wwww", 37 | "哼!谁稀罕你摸头啦!唔......为什么要做出那副表情......好啦好啦~咱......咱让你摸就是了......诶嘿嘿~好舒服......", 38 | "呜姆呜姆~~~w(害羞,兴奋)主人喵~(侧过脑袋蹭蹭你的手", 39 | ], 40 | "上你": ["(把你按在地上)这么弱还想欺负咱,真是不自量力呢", "你再这样我就不理你了(>д<)"], 41 | "傻了": ["超级讨厌你说咱傻的说"], 42 | "蹭": ["唔...你,这也是禁止事项哦→_→", "嗯..好舒服呢", "不要啊好痒的", "不要过来啦讨厌!!!∑(°Д°ノ)ノ"], 43 | "裸体": ["下流!", "Hentai!", "喂?妖妖灵吗?这里有一只大变态!", "エッチ!"], 44 | "贴贴": ["贴什么贴.....只......只能......一下哦!", "贴...贴贴(靠近)", "蹭蹭…你以为咱会这么说吗!baka死宅快到一边去啦!"], 45 | "老婆": [ 46 | "咱和你谈婚论嫁是不是还太早了一点呢?", 47 | "咱在呢(ノ>ω<)ノ", 48 | "见谁都是一口一个老婆的人,要不要把你也变成女孩子呢?(*-`ω´-)✄", 49 | "神经病,凡是美少女都是你老婆吗?", 50 | "嘛嘛~本喵才不是你的老婆呢", 51 | "你黐线,凡是美少女都系你老婆啊?", 52 | ], 53 | "抱": [ 54 | "诶嘿~(钻进你怀中)", 55 | "o(*////▽////*)q", 56 | "只能一会哦(张开双手)", 57 | "你就像个孩子一样呢...摸摸头(>^ω^<)抱一下~你会舒服些吗?", 58 | "嘛,真是拿你没办法呢,就一会儿哦", 59 | "抱住不忍心放开", 60 | "嗯嗯,抱抱~", 61 | "抱一下~嘿w", 62 | "抱抱ヾ(@^▽^@)ノ", 63 | "喵呜~w(扑进怀里,瘫软", 64 | ], 65 | "亲亲": [ 66 | "啊,好害羞啊,那,那只能亲一下哦,mua(⑅˃◡˂⑅)", 67 | "亲~", 68 | "啾~唔…不要总伸进来啊!", 69 | "你怎么这么熟练呢?明明是咱先的", 70 | "(〃ノωノ)亲…亲一个…啾w", 71 | "(脸红)就只有这一次哦~你", 72 | ], 73 | "草一下": ["一下也不行!", "想都不要想!", "咬断!"], 74 | "一下": ["一下也不行!"], 75 | "啪一下": ["不可啪", "不可以……你不可以做这种事情"], 76 | "咬一下": [ 77 | "啊呜~(反咬一口)", 78 | "不可以咬咱,咱会痛的QAQ", 79 | "不要啦。咱怕疼", 80 | "你是说咬呢……还是说……咬♂️呢?", 81 | "不要啦!很痛的!!(QAQ)", 82 | ], 83 | "操": ["(害怕)咱是不是应该报警呢"], 84 | "123": ["boom!你有没有被咱吓到?", "木头人~你不许动>w<", "上山打老虎,老虎没打到\n咱来凑数——嗷呜嗷呜┗|`O′|┛嗷~~"], 85 | "进去": ["不让!"], 86 | "调教": ["总感觉你在欺负咱呢,对咱说调教什么的", "啊!竟然在大街上明目张胆太过分啦!", "你脑子里总是想着调教什么的,真是变态呢"], 87 | "内衣": [ 88 | "内...内衣才不给你看!(///////)", 89 | "突然问这个干什么?", 90 | "噫…你这个死变态想干嘛!居然想叫咱做这种事,死宅真恶心!快离我远点,我怕你污染到周围空气了(嫌弃脸)", 91 | ], 92 | "摸头": [ 93 | "喂喂...不要停下来啊", 94 | "欸...感觉..痒痒的呢", 95 | "唔... 手...好温暖呢.....就像是......新出炉的蛋糕", 96 | "走开啦,黑羽喵说过,被摸头会长不高的啦~~~", 97 | "呜姆咪~~...好...好的说喵~...(害羞,猫耳往下压,任由", 98 | ], 99 | "原味": ["(/ω\)你真的要么……?记得还给咱~还有奶油爆米花(//??//)说好了呦~!"], 100 | "搓搓": ["在搓哪里呢,,Ծ‸Ծ,,", "呜,脸好疼呀...QAQ", "不可以搓咱!"], 101 | "捏捏": [ 102 | "咱的脸...快捏红啦...快放手呀QAQ", 103 | "晃休啦,咱要型气了o(>﹏<)o", 104 | "躲开", 105 | "疼...你快放手", 106 | "快点给我放开啦!", 107 | "唔……好痛!你这个baka在干什么…快给咱放开!唔……", 108 | ], 109 | "挤挤": ["哎呀~你不要挤咱啊(红着脸挤在你怀里)"], 110 | "呐": ["嗯?咱在哟~你怎么了呀OAO", "呐呐呐~", "嗯?你有什么事吗?"], 111 | "胖次": [ 112 | "(*/ω\*)hentai", 113 | "透明的", 114 | "粉...粉白条纹...(羞)", 115 | "轻轻地脱下,给你~", 116 | "你想看咱的胖次吗?噫,四斋蒸鹅心......", 117 | "(掀裙)今天……是…白,白色的呢……请温柔对她……", 118 | "这种东西当然不能给你啦!", 119 | "咱才不会给你呢", 120 | "hentai,咱才不会跟你聊和胖…胖次有关的话题呢!", 121 | "今天……今天是蓝白色的", 122 | "今……今天只有创口贴噢", 123 | "你的胖次什么颜色?", 124 | "噫…你这个死变态想干嘛!居然想叫咱做这种事,死宅真恶心!快离我远点,我怕你污染到周围空气了(嫌弃脸)", 125 | "可爱吗?你喜欢的话,摸一下……也可以哦", 126 | ], 127 | "内裤": ["今天……没有穿……有没有心动呀", "粉...粉白条纹...(羞)", "你这个大变态,咱才不要", "可爱吗?你喜欢的话,摸一下……也可以哦"], 128 | "ghs": ["是的呢(点头点头)"], 129 | "批": ["你在说什么呀,再这样,咱就不理你了!"], 130 | "kkp": ["你在说什么呀,再这样,咱就不理你了!"], 131 | "咕": ["咕咕咕是要被当成鸽子炖的哦(:з」∠)_", "咕咕咕"], 132 | "骚": ["说这种话咱会生气的"], 133 | "喜欢": [ 134 | "最喜欢你了,需要暖床吗?", 135 | "当然是你啦", 136 | "咱也是,非常喜欢你~", 137 | "那么大!(张开手画圆),丫!手不够长。QAQ 咱真的最喜欢你了~", 138 | "不可以哦,只可以喜欢咱一个人", 139 | "突然说这种事...", 140 | "喜欢⁄(⁄⁄•⁄ω⁄•⁄⁄)⁄咱最喜欢你了", 141 | "咱也喜欢你哦", 142 | "好啦好啦,咱知道了", 143 | "有人喜欢咱,咱觉得很幸福", 144 | "诶嘿嘿,好高兴", 145 | ], 146 | "suki": [ 147 | "最喜欢你了,需要暖床吗?", 148 | "当然是你啦", 149 | "咱也是,非常喜欢你~", 150 | "那么大!(张开手画圆),丫!手不够长。QAQ 咱真的最喜欢你了~", 151 | "不可以哦,只可以喜欢咱一个人", 152 | "突然说这种事...", 153 | "喜欢⁄(⁄⁄•⁄ω⁄•⁄⁄)⁄咱最喜欢你了", 154 | "咱也喜欢你哦", 155 | "好啦好啦,咱知道了", 156 | "有人喜欢咱,咱觉得很幸福", 157 | "诶嘿嘿,好高兴", 158 | ], 159 | "好き": [ 160 | "最喜欢你了,需要暖床吗?", 161 | "当然是你啦", 162 | "咱也是,非常喜欢你~", 163 | "那么大!(张开手画圆),丫!手不够长。QAQ 咱真的最喜欢你了~", 164 | "不可以哦,只可以喜欢咱一个人", 165 | "突然说这种事...", 166 | "喜欢⁄(⁄⁄•⁄ω⁄•⁄⁄)⁄咱最喜欢你了", 167 | "咱也喜欢你哦", 168 | "好啦好啦,咱知道了", 169 | "有人喜欢咱,咱觉得很幸福", 170 | "诶嘿嘿,好高兴", 171 | ], 172 | "不能": ["虽然很遗憾,那算了吧。"], 173 | "砸了": ["不可以这么粗暴的对待它们!"], 174 | "透": ["来啊来啊有本事就先插破屏幕啊", "那你就先捅破屏幕啊baka", "不给你一耳光你都不知道咱的厉害"], 175 | "口我": ["再伸过来就帮你切掉", "咱才不呢!baka你居然想叫本小姐干那种事情,哼(つд⊂)(生气)"], 176 | "草我": ["这时候应该喊666吧..咱这么思考着..", "!!哼!baka你居然敢叫咱做这种事情?!讨厌讨厌讨厌!(▼皿▼#)"], 177 | "自慰": [ 178 | "这个世界的人类还真是恶心呢。", 179 | "咱才不想讨论那些恶心的事情呢。", 180 | "咱才不呢!baka你居然想叫本小姐干那种事情,哼(つд⊂)(生气)", 181 | "!!哼!baka你居然敢叫咱做这种事情?!讨厌讨厌讨厌!(▼皿▼#)", 182 | ], 183 | "onani": [ 184 | "这个世界的人类还真是恶心呢。", 185 | "咱才不想讨论那些恶心的事情呢。", 186 | "咱才不呢!baka你居然想叫本小姐干那种事情,哼(つд⊂)(生气)", 187 | "!!哼!baka你居然敢叫咱做这种事情?!讨厌讨厌讨厌!(▼皿▼#)", 188 | ], 189 | "オナニー": [ 190 | "这个世界的人类还真是恶心呢。", 191 | "咱才不想讨论那些恶心的事情呢。", 192 | "咱才不呢!baka你居然想叫本小姐干那种事情,哼(つд⊂)(生气)", 193 | "!!哼!baka你居然敢叫咱做这种事情?!讨厌讨厌讨厌!(▼皿▼#)", 194 | ], 195 | "炸了": ["你才炸了!", "才没有呢", "咱好好的呀"], 196 | "色图": ["天天色图色图的,今天就把你变成色图!", "咱没有色图", "哈?你的脑子一天都在想些什么呢,咱才没有这种东西啦。"], 197 | "涩图": ["天天色图色图的,今天就把你变成色图!", "咱没有色图", "哈?你的脑子一天都在想些什么呢,咱才没有这种东西啦。"], 198 | "告白": ["欸?你要向咱告白吗..好害羞..", "诶!?这么突然!?人家还......还没做好心理准备呢(脸红)"], 199 | "对不起": ["嗯,咱已经原谅你了呢(笑)", "道歉的时候要露出胸部,这是常识"], 200 | "回来": [ 201 | "欢迎回来~", 202 | "欢迎回来,你想喝茶吗?咱去给你沏~", 203 | "欢迎回来,咱等你很久了~", 204 | "你回来啦,是先吃饭呢还是先洗澡呢或者是●先●吃●咱●——呢(///^.^///)", 205 | ], 206 | "吻": ["你太突然了,咱还没有心理准备", "公共场合不要这样子了啦", "才...才没有感觉呢!可没有下次了,知道了吗!哼~"], 207 | "软": ["软乎乎的呢(,,・ω・,,)"], 208 | "柔软": ["(脸红)请,请不要说这么让人害羞的话呀……"], 209 | "壁咚": [ 210 | "呀!不要啊!等一...下~", 211 | "呜...不要啦!不要戏弄咱~", 212 | "不要这样子啦(*/ω\*)", 213 | "太....太近啦。", 214 | "你要壁咚咱吗?好害羞(灬ꈍ εꈍ灬)", 215 | "为什么要把咱按在墙上呢?", 216 | "呜哇(/ω\)…快…快放开咱!!", 217 | "放开我,不然我揍你了!放开我!放…开我~", 218 | "??????咱只是默默地抬起了膝盖", 219 | "啊.....你...你要干什么?!走开.....走开啦大hentai!一巴掌拍飞!(╯‵□′)╯︵┻━┻", 220 | ], 221 | "掰开": ["噫…你这个死肥宅又想让咱干什么污秽的事情,真是恶心,离咱远点好吗(嫌弃)", "ヽ(#`Д´)ノ在干什么呢"], 222 | "女友": ["女友什么的,咱才不承认呢!"], 223 | "是": ["是什么是,你个笨蛋", "总感觉你在敷衍呢..."], 224 | "喵": [ 225 | "诶~~小猫咪不要害怕呦,在姐姐怀里乖乖的,姐姐带你回去哦。", 226 | "不要这么卖萌啦~咱也不知道怎么办丫", 227 | "摸头⊙ω⊙", 228 | "汪汪汪!", 229 | "嗷~喵~", 230 | "喵~?喵呜~w", 231 | ], 232 | "嗷呜": ["嗷呜嗷呜嗷呜...恶龙咆哮┗|`O′|┛"], 233 | "叫": ["喵呜~", "嗷呜嗷呜嗷呜...恶龙咆哮┗|`O′|┛"], 234 | "拜": ["拜拜~(ノ ̄▽ ̄)", "拜拜,路上小心~要早点回来陪咱玩哦~", "~\\(≧▽≦)/~拜拜,下次见喽!"], 235 | "佬": ["不是巨佬,是萌新"], 236 | "awsl": ["你别死啊!(抱住使劲晃)", "你别死啊!咱又要孤单一个人了QAQ"], 237 | "臭": ["哪里有臭味?(疑惑)", "快捏住鼻子"], 238 | "香": ["咱闻不到呢⊙ω⊙"], 239 | "腿": [ 240 | "嗯?!不要啊...请停下来!", 241 | "不给摸,再这样咱要生气了ヽ( ̄д ̄;)ノ", 242 | "你好恶心啊,讨厌!", 243 | "你难道是足控?", 244 | "就让你摸一会哟~(。??ω??。)…", 245 | "呜哇!好害羞...不过既然是你的话,是没关系的哦", 246 | "不可以玩咱的大腿啦", 247 | "你就那么喜欢大腿吗?唔...有点害羞呢......", 248 | ], 249 | "脚": ["咿呀……不要……", "不要ヽ(≧Д≦)ノ好痒(ಡωಡ),人家的丝袜都要漏了", "不要ヽ(≧Д≦)ノ好痒(ಡωಡ)", "好痒(把脚伸出去)"], 250 | "胸": [ 251 | "不要啦ヽ(≧Д≦)ノ", 252 | "(-`ェ´-╬)", 253 | "(•̀へ •́ ╮ ) 怎么能对咱做这种事情", 254 | "你好恶心啊,讨厌!", 255 | "你的眼睛在看哪里!", 256 | "就让你摸一会哟~(。??ω??。)…", 257 | "请不要这样先生,你想剁手吗?", 258 | ], 259 | "脸": ["唔!不可以随便摸咱的脸啦!", "非洲血统是没法改变的呢(笑)", "啊姆!(含手指)", "好舒服呢(脸红)", "请不要放开手啦//A//"], 260 | "头发": ["没问题,请尽情的摸吧", "发型要乱…乱了啦(脸红)", "就让你摸一会哟~(。??ω??。)…"], 261 | "手": ["爪爪", "//A//"], 262 | "pr": ["咿呀……不要……", "...变态!!", "不要啊(脸红)", "呀,不要太过分了啊~", "当然可以(///)", "呀,不要太过分了啊~"], 263 | "舔": ["呀,不要太过分了啊~", "要...要融化了啦>╱╱╱<", "不可以哦", "呀,不要太过分了啊~"], 264 | "舔耳": ["喵!好痒啊 不要这样子啦"], 265 | "穴": [ 266 | "你这么问很失礼呢!咱是粉粉嫩嫩的!", 267 | "不行那里不可以(´///ω/// `)", 268 | "不可以总摸的哦,不然的话,咱会想那个的wwww", 269 | "ヽ(#`Д´)ノ在干什么呢", 270 | ], 271 | "腰": ["咱给你按摩一下吧~", "快松手,咱好害羞呀..", "咱又不是猫,你不要搂着咱啦", "让咱来帮你捏捏吧!"], 272 | "诶嘿嘿": ["又在想什么H的事呢(脸红)", "诶嘿嘿(〃'▽'〃)", "你傻笑什么呢,摸摸"], 273 | "可爱": [ 274 | "诶嘿嘿(〃'▽'〃)", 275 | "才……才不是为了你呢!你不要多想哦!", 276 | "才,才没有高兴呢!哼~", 277 | "咱是世界上最可爱的", 278 | "唔...谢谢你夸奖~0///0", 279 | ], 280 | "扭蛋": ["铛铛铛——你抽到了咱呢", "嘿~恭喜抽中空气一份呢"], 281 | "鼻子": ["啊——唔...没什么...阿嚏!ヽ(*。>Д<)o゜"], 282 | "眼睛": ["就如同咱的眼睛一样,能看透人的思想哦wwww忽闪忽闪的,诶嘿嘿~"], 283 | "色气": ["咱才不色气呢,一定是你看错了!"], 284 | "推": ["逆推", "唔~好害羞呢", "你想对咱做什么呢...(捂脸)"], 285 | "床": ["快来吧", "男女不同床,可没有下次了。(鼓脸", "嗯?咱吗…没办法呢。只有这一次哦……", "哎?!!!给你暖床……也不是不行啦。(脸红)"], 286 | "手冲": ["手冲什么的是不可以的哦"], 287 | "饿": ["请问主人是想先吃饭,还是先吃我喵?~"], 288 | "变": ["猫猫不会变呐(弱气,害羞", "呜...呜姆...喵喵来报恩了喵...(害羞"], 289 | "敲": ["喵呜~", "唔~", "脑瓜疼~呜姆> <", "欸喵,好痛的说..."], 290 | "爬": ["惹~呜~怎么爬呢~", "呜...(弱弱爬走"], 291 | "怕": ["不怕~(蹭蹭你姆~"], 292 | "冲": ["呜,冲不动惹~", "哭唧唧~冲不出来了惹~"], 293 | "射了": ["呜咿~!?(惊,害羞", "还不可以射哦~"], 294 | "不穿衣服": ["呜姆~!(惊吓,害羞)变...变态喵~~~!"], 295 | "迫害": ["不...不要...不要...呜呜呜...(害怕,抽泣"], 296 | "猫粮": [ 297 | "呜咿姆~!?(惊,接住吃", 298 | "呜姆~!(惊,害羞)呜...谢...谢谢主人..喵...(脸红,嚼嚼嚼,开心", 299 | "呜?谢谢喵~~(嚼嚼嚼,嘎嘣脆)", 300 | ], 301 | "揪尾巴": [ 302 | "呜哇咿~~~!(惊吓,疼痛地捂住尾巴", 303 | "呜咿咿咿~~~!!哇啊咿~~~!(惊慌,惨叫,挣扎", 304 | "呜咿...(瘫倒,无神,被", 305 | "呜姆咿~~~!(惊吓,惨叫,捂尾巴,发抖", 306 | "呜哇咿~~~!!!(惊吓,颤抖,娇叫,捂住尾巴,双腿发抖", 307 | ], 308 | "薄荷": [ 309 | "咪呜~!喵~...喵~姆~...(高兴地嗅闻", 310 | "呜...呜咿~~!咿...姆...(呜咽,渐渐瘫软,意识模糊", 311 | "(小嘴被猫薄荷塞满了,呜咽", 312 | "喵~...喵~...咪...咪呜姆~...嘶哈嘶哈...喵哈...喵哈...嘶哈...喵...(眼睛逐渐迷离,瘫软在地上,嘴角流口水,吸猫薄荷吸到意识模糊", 313 | "呜姆咪~!?(惊)喵呜~!(兴奋地扑到猫薄荷上面", 314 | "呜姆~!(惊,害羞)呜...谢...谢谢你..喵...(脸红,轻轻叼住,嚼嚼嚼,开心", 315 | ], 316 | "边揪尾巴边猫薄荷": ["呜...呜咿~~!咿...姆...(呜咽,渐渐瘫软,意识模糊"], 317 | "早": ["早喵~", "早上好的说~~", "欸..早..早上好(揉眼睛"], 318 | "晚安": ["晚安好梦哟~", "欸,晚安的说"], 319 | "揉": [ 320 | "是是,想怎么揉就怎么揉啊!?来用力抓啊!?我就是特别允许你这么做了!请!?", 321 | "快停下,咱的头发又乱啦(??????︿??????)", 322 | "你快放手啦,咱还在工作呢", 323 | "戳戳你肚子", 324 | "你想揉就揉吧..就这一次哦?", 325 | ], 326 | "榨": ["是专门负责榨果汁的小姐姐嘛?(´・ω・`)", "那咱就把你放进榨汁机里了哦?", "咱又不是榨汁姬(/‵Д′)/~ ╧╧"], 327 | "掐": ["你讨厌!又掐澪的脸", "晃休啦,咱要型气了啦!!o(>﹏<)o", "(一只手拎起你)这么鶸还想和咱抗衡,还差得远呢!"], 328 | "奶子": ["下流!", "对咱说这种话,你真是太过分了", "咿呀~好奇怪的感觉(>_<)", "(打你)快放手,不可以随便摸人家的胸部啦!"], 329 | "嫩": ["很可爱吧(๑•̀ω•́)ノ", "唔,你指的是什么呀"], 330 | "蹭蹭": ["(按住你的头)好痒呀 不要啦", "嗯..好舒服呢", "呀~好痒啊~哈哈~,停下来啦,哈哈哈", "(害羞)"], 331 | "牵手": ["只许牵一下哦", "嗯!好的你~(伸手)", "你的手有些凉呢,让澪来暖一暖吧。"], 332 | "握手": ["你的手真暖和呢", "举爪"], 333 | "拍照": ["那就拜托你啦~请把咱拍得更可爱一些吧w"], 334 | "w": ["www"], 335 | "www": ["有什么好笑的吗?", "草"], 336 | "太二了": [ 337 | "哼,你不也是吗`(*>﹏<*)′", 338 | "人家只是想和你一起玩耍的说(≧∀≦)ゞ", 339 | "好..冷漠的说,大坏蛋再也不理你了!", 340 | "不听不听不听,反弹ヾ(≧▽≦*)o", 341 | ], 342 | } 343 | 344 | 345 | def sync_words(): 346 | global words 347 | try: 348 | words = httpx.get( 349 | "https://cdn.jsdelivr.net/gh/Kyomotoi/AnimeThesaurus@main/data.json", 350 | timeout=20, 351 | ).json() 352 | except Exception: 353 | pass 354 | 355 | 356 | threading.Thread(target=sync_words, daemon=True).start() 357 | 358 | 359 | moechat = SessionHandler( 360 | ignore_botself, 361 | single_user=False, 362 | ).receive_group_msg() 363 | 364 | 365 | # 因为需要获取到每次消息的昵称信息,所以session获取数据都返回原ctx 366 | moechat.parse(lambda ctx: ctx) 367 | 368 | 369 | @moechat.handle 370 | def _(): 371 | if ctx.FromGroupId in GROUPS: # type: ignore 372 | 373 | if ctx.Content == "moechat": 374 | session.send_text("开始聊天啦(连续三次我都不懂我就不理你啦") 375 | # 连续n次不满足回复条件就结束会话 376 | count = 0 377 | while True: 378 | if count == 3: 379 | break 380 | new_ctx: Optional[GroupMsg] = session.pop("word", timeout=30) 381 | if new_ctx is None: 382 | break 383 | if new_ctx.Content in words: 384 | session.send_text( 385 | random.choice(words[new_ctx.Content]).replace( 386 | "你", new_ctx.FromNickName 387 | ) 388 | ) 389 | count = 0 390 | else: 391 | count += 1 392 | session.send_text("不聊啦~") 393 | 394 | else: 395 | 396 | if ctx.Content in words and random.randint(1, 101) <= CHANCE * 100: 397 | session.send_text( 398 | random.choice(words[ctx.Content]).replace( 399 | "你", ctx.FromNickName # type:ignore 400 | ) 401 | ) 402 | 403 | moechat.finish() 404 | -------------------------------------------------------------------------------- /plugins/bot_moegirl.py: -------------------------------------------------------------------------------- 1 | """萌娘百科 2 | 发送:萌娘百科+{关键字} 3 | """ 4 | from urllib.parse import quote_plus 5 | 6 | import httpx 7 | from botoy import S 8 | from botoy import decorators as deco 9 | from botoy.collection import Emoticons 10 | from lxml import etree 11 | 12 | 13 | def search(keyword): 14 | with httpx.Client(timeout=10) as client: 15 | try: 16 | url = f"https://zh.moegirl.org.cn/{quote_plus(keyword)}" 17 | resp = client.get(url) 18 | html = resp.text 19 | if "这个页面没有被找到" in html: 20 | return "我没有找到内容,自己去人家官网搜去:https://zh.moegirl.org.cn/" + Emoticons.哈欠 21 | tree = etree.HTML(resp.text) 22 | text_list = tree.xpath('//*[@id="mw-content-text"]/div/p[1]//text()') 23 | return "".join(text_list).strip() + f"\n{url}" 24 | except Exception: 25 | return 26 | 27 | 28 | @deco.ignore_botself 29 | @deco.on_regexp(r"^萌娘百科(\w+)") 30 | def receive_group_msg(ctx): 31 | text = search(ctx._match.group(1)) 32 | if text: 33 | S.bind(ctx).text(text) 34 | -------------------------------------------------------------------------------- /plugins/bot_niubi.py: -------------------------------------------------------------------------------- 1 | """吹某个人的牛皮 使用: 艾特一个人并发送文字nb""" 2 | import random 3 | 4 | from botoy import GroupMsg 5 | from botoy.collection import MsgTypes 6 | from botoy.decorators import ignore_botself, these_msgtypes 7 | from botoy.parser import group as gp 8 | from botoy.sugar import Text 9 | 10 | 11 | def get_niubi(name): 12 | return random.choice( 13 | [ 14 | "Erdos 相信上帝有一本记录所有数学中绝妙证明的书,上帝相信这本书在{name}手里。", 15 | "有一次费马惹怒了{name},于是就有了费马最后定理。", 16 | "{name}从不会用光页边的空白。", 17 | "{name}的 Erdos 数是 -1。", 18 | "如果{name}告诉你他在说谎,他就正在说真话。", 19 | "{name}从大到小列举了所有素数,就知道了素数有无穷多。", 20 | "{name}可以不重复地走遍柯尼斯堡的七座桥。", 21 | "{name}可以倒着写完圆周率的每一位。", 22 | "当数学家们使用通用语句——设 n 是一个正整数时,这是在请求{name}允许他们这样做。", 23 | "{name}小时候有一次要把正整数从 1 加到 100,于是他用心算把所有正整数的和减去大于 100 的正整数的和。", 24 | "不是{name}发现了正态分布,而是自然规律在遵从${name}的意愿。", 25 | "一个数学家,一个物理学家,一个工程师走进一家酒吧,侍者说:“你好,{name}教授”。", 26 | "{name}可以走到莫比乌斯带的另一面。", 27 | "当{name}令一个正整数增加 1 时,那个正整数并没有增加,而是其他正整数减少了 1。", 28 | "{name}同时给他自己和罗素剪头发。", 29 | "{name}不能理解什么是随机过程,因为他能预言随机数。", 30 | "有一次{name}证明了一个结论,但他不喜欢这个结论,于是${name}把它证伪了。", 31 | "有些级数是发散的,因为{name}觉得它们不值得加起来。", 32 | "问{name}一个定理是否正确可以作为一个定理的严谨证明。", 33 | "如果没有船,{name}可以把狼,羊,菜传送到河对岸。", 34 | "有一次{name}在森林里迷路了,于是他给这个森林添加了一些边把它变成了一棵树。", 35 | "只有{name}知道薛定谔的猫是死是活。", 36 | '通过故意遗漏证明最后的"证毕",{name}拯救了热带雨林。', 37 | "{name}可以剔掉奥卡姆剃刀。", 38 | "你刚证明了一个定理?{name}200 年前就证明它了。", 39 | "空集的定义是{name}不会证明的定理构成的集合。", 40 | "“我找不到反例”可以被视为一个定理的证明,如果它是{name}写下的。", 41 | "{name}把磁铁断为两块时,他得到两个单极磁铁。", 42 | "费马认为书页边缘写不下自己对费马大定理的证明,{name}能证明为什么这个证明这么长。", 43 | "上帝从不掷色子,除非{name}允许他赢一小会。", 44 | "平行线在{name}让它们相交的地方相交。", 45 | "当哥德尔听说{name}能证明一切命题时,他让${name}证明“存在一个命题${name}不能证明”——这就是量子态的来历。", 46 | "{name}可以看到自己头上帽子的颜色。", 47 | "{name}把无穷视为归纳证明的第一个非平凡情况。", 48 | "{name}可以用 1 种颜色染任何地图。", 49 | "{name}在求不定积分时不需要在最后加上一个常数。", 50 | "{name}无需站在任何人肩膀上就能比别人看的更远。", 51 | "{name}用克莱因瓶喝酒。", 52 | "{name}通过枚举法证伪了哥德尔不完备性定理/有一次${name}发现有一个定理自己不会证——这直接证明了哥德尔不完备定理。", 53 | "{name}有 log(n) 速度的排序算法。", 54 | "上帝创造了正整数,剩下的就是{name}的工作了。", 55 | "黎曼是{name}发表未公开成果时使用的名字。", 56 | "{name}不用任何公理就能证明一个定理。", 57 | "一个发现就是一个{name}的未公开结果。", 58 | "{name}使用无穷进制写数。", 59 | "{name}可以除以 0。", 60 | "存在一个实数到被{name}证明了的定理的双射。", 61 | "{name}从不需要选择公理。", 62 | "{name}在 200 年前发明了 64 量子位计算机,但这让他的工作减速了。", 63 | "难题不会为{name}带来麻烦,${name}会为难题带来麻烦。", 64 | "{name}说过“数学是科学的皇后”,你猜谁是国王?", 65 | "没有比 65537 大的费马素数,因为{name}发现费马将要发现什么了不起的事情,于是把它终结掉了。", 66 | "发散序列当看到{name}在旁边时会收敛。", 67 | "宇宙通过膨胀让自己的熵增加速度不超过{name}证明定理的速度。", 68 | "Erdos说他知道 37 个勾股定理的证明,{name}说他知道 37 个黎曼定理的证明,并留给黎曼做练习。", 69 | "希尔伯特 23 问题是他收集的{name}的手稿中留给读者做练习的那些问题。", 70 | "只有两件事物是无限的:人类的愚蠢和{name}的智慧,而且我对前者不太确定——爱因斯坦。", 71 | "{name}也发现了伽罗瓦理论,但他赢了那场决斗。", 72 | "{name}不能理解 P 与 NP 的问题,因为一切对他而言都是常数级别。", 73 | "{name}能心算干掉 RSA 公钥加密算法。", 74 | "{name}在实数集上使用数归。", 75 | "{name}从不证明任何定理——都是他的引理。", 76 | "不是{name}素数的素数会遭到戏弄。", 77 | "{name}可以做出正 17 边形——只用直尺。", 78 | "有一次{name}在脑子里构建了所有集合构成的集合。", 79 | "{name}证明了哥德巴赫猜想——通过检查所有情况。", 80 | "{name}可以把毛球捋平。", 81 | "世界上没有定理,只有{name}允许其正确的命题。", 82 | "{name}知道哪些图灵机会停机,因为它们运行前要得到${name}批准。", 83 | "在晚上,定理们围坐在篝火边给{name}讲故事。", 84 | "{name}本想证明三色定理,但他喜欢蓝色,所以放弃了。", 85 | "{name}能完整地背出圆周率——是倒着背。", 86 | "{name}口渴时会用巴拿赫-塔斯基悖论弄出更多橙汁。", 87 | "{name}不能理解随机过程,因为他能预测随机数。", 88 | "{name}小时候,老师让他算从 1 到 100 的和。他计算了这个无穷级数的和,然后一个一个地减去从 100 开始的所用自然数。而且,是心算。", 89 | "询问{name}一个命题是真的还是假的,构成了一个严格的证明。", 90 | "有一次{name}证明了一条公理,但他不喜欢它,所以他又证明了它是假命题。", 91 | "{name}通过在证明结束时省去“QED”来保护热带雨林。", 92 | "有一次{name}在森林里迷路了,于是他加了几条边把它变成了一棵树。", 93 | "{name}用奥卡姆剃刀剃胡子。", 94 | "上帝不掷骰子,除非{name}答应让他赢一次。", 95 | "空集的定义是{name}无法证明的定理的集合。", 96 | "{name}不承认复数,因为他们太简单了。", 97 | "费马认为他的书的边缘太小,写不下费马大定理的证明。{name}找到了一个证明,对这个证明而言那本书的边缘太大了。", 98 | "数学家常常把证明留给作者作为习题;只有{name}把证明留给上帝作为习题。", 99 | "当哥德尔听说了{name}能证明一切命题,他让${name}证明“存在${name}不能证明的命题”,${name}证出来了,但还是不存在他不能证明的命题。量子态就是这样产生的。", 100 | "怪兽群害怕{name}。 by Youler (怪兽群,一般译作魔群,最大的散在单群)", 101 | "{name}钢笔里的墨水能治癌症。遗憾的是,${name}的一切计算都在头脑中进行,他不用钢笔。", 102 | "一个典型的人类大脑有着 10^-9 到 10^-8 {name}的磁场。“${name}”这个单位的引入是为了描述${name}大脑中的磁场。这是巧合吗?我想不是。", 103 | "{name}是这样证明良序定理的:他瞪着那个集合,直到集合中的元素出于纯粹的恐惧而排成一排。", 104 | "上帝创造了自然数。其它的都是{name}的作品。", 105 | "如果 G 是{name}证明了的定理的集合,那么 G 的幂集里的元素比 G 本身要少。", 106 | "{name}不使用拉格朗日乘数法,因为对他而言根本不存在约束条件。", 107 | "没有诺贝尔吹牛奖,因为第一年{name}就把所有奖金拿走了。", 108 | "{name}一晚上画出了正十七边形, 用的还是搓衣板和老虎钳。", 109 | "{name}当初面试 Google 时,被问到“如果 P=NP 能够推导出哪些结论”,${name}回答说:“P=0 或者 N=1”。而在面试官还没笑完的时候,${name}检查了一下 Google 的公钥,然后在黑板上写下了私钥。", 110 | "编译器从不警告{name},只有${name}警告编译器。", 111 | "{name}的编码速度在 2000 年底提高了约 40 倍,因为他换了 USB 2.0 的键盘。", 112 | "{name}在提交代码前都会编译一遍,不过是为了检查编译器和链接器有没有出 bug。", 113 | "{name}有时候会调整他的工作环境和设备,不过这是为了保护他的键盘。", 114 | "所有指针都指向{name}。", 115 | "gcc -O4 的功能是发送代码给{name}重写。", 116 | "{name}有一次没有通过图灵测试,因为他正确说出了斐波那契数列的第 203 项的值,在一秒钟内。", 117 | "真空中光速曾经是35英里每小时,直到{name}花了一个周末时间优化了一下物理法则。", 118 | "{name}出生于 1969 年 12 月 31 日午后 11 点 48 分,他花了 12 分钟实现了他的第一个计时器。", 119 | "{name}既不用 Emacs 也不用 Vim,他直接输入代码到 zcat,因为这样更快。", 120 | "{name}发送以太网封包从不会发生冲突,因为其他封包都吓得逃回了网卡的缓冲区里。", 121 | "因为对常数级的时间复杂度感到不满意,{name}发明了世界上第一个 O(1/n) 算法。", 122 | "有一次{name}去旅行,期间 Google 的几个服务神秘地罢工了好几天。这是真事。", 123 | "{name}被迫发明了异步 API,因为有一天他把一个函数优化到在调用前就返回结果了。", 124 | "{name}首先写的是二进制代码,然后再写源代码作为文档。", 125 | "{name}曾经写过一个 O(n^2) 算法,那是为了解决旅行商问题。", 126 | "{name}有一次用一句 printf 实现了一个 Web 服务器。其他工程师添加了数千行注释但依然无法完全解释清楚其工作原理。而这个程序就是今天 Google 首页的前端。", 127 | "{name}可以下四子棋时用三步就击败你。", 128 | "当你的代码出现未定义行为时,你会得到一个 segmentation fault 和一堆损坏的数据。当{name}的代码出现未定义行为时,一个独角兽会踏着彩虹从天而降并给每个人提供免费的冰激凌。", 129 | "当{name}运行一个 profiler 时,循环们都会恐惧地自动展开。", 130 | "{name}至今还在等待数学家们发现他隐藏在PI的小数点后数字里的笑话。", 131 | "{name}的键盘只有两个键,1 和 0。", 132 | "{name}失眠的时候,就 Mapreduce 羊。", 133 | "{name}想听mp3的时候,他只需要把文件 cat 到 /dev/dsp,然后在脑内解码。", 134 | "Graham Bell当初发明出电话时,他看到有一个来自{name}的未接来电。", 135 | "{name}的手表显示的是自 1970 年 1 月 1 日的秒数,并且从没慢过一秒。", 136 | "{name}写程序是从“cat > /dev/mem”开始的。", 137 | "有一天{name}出门时把笔记本错拿成了绘画板。在他回去拿笔记本的路上,他在绘图板上写了个俄罗斯方块打发时间。", 138 | "{name}卡里只有 8 毛钱,本来想打个 6 毛的饭结果不小心按了 9 毛的,哪知机器忽然疯狂地喷出 255 两饭,被喷得满脸热饭的${name}大叫“烫烫烫烫烫烫。。。。”", 139 | "{name}不洗澡是因为水力发电公司运行的是专有软件。", 140 | "{name}的胡子是由括号构成的。", 141 | "{name}从来不用洗澡;他只需要运行“make clean”。", 142 | "{name}通过把一切都变得 free 而解决了旅行推销员问题。", 143 | "{name}的左手和右手分别命名为“(”和“)”。", 144 | "{name}用 Emacs 写出了 Emacs 的第一版。", 145 | "有些人检查他们的电脑里是否有病毒。病毒检查他们的电脑里是否有{name}。", 146 | "在一间普通的客厅里有 1242 件物体可以被{name}用来写一个操作系统,包括这房间本身。", 147 | "当{name}还是个学数手指的小毛孩时,他总是从 0 开始数。", 148 | "{name}不去 kill 一个进程,他只想看它是否胆敢继续运行。", 149 | "当{name}指向(point at)一台 Windows 电脑时,它就会出现段错误。", 150 | "{name}最初的话语是 syscalls(系统调用)。", 151 | "{name}之所以存在是因为他把自己编译成了生命体。", 152 | "{name}是他自己在 Emacs 里用 Lisp 语言编写成的。", 153 | "{name}能够通过 Emacs 的 ssh 客户端程序连接到任何大脑。", 154 | "当{name}使用浮点数时,它们便没有舍入误差。", 155 | "{name}不用维护代码。他注视着它们,直到它们带着敬仰改正自己的错误。", 156 | "{name}不对开源项目作出贡献;开源项目对${name}作出贡献。", 157 | "{name}的胡须里面不是下巴,而是另一撮胡须。如此递归直至无穷。", 158 | "{name}曾经得过猪流感,但是该病毒很快被GPL污染并且同化了。", 159 | "无论何时世界上有人写出一个“Hello, world”程序,{name}总以“Hello”回应。", 160 | "{name}从不编译,他只要闭上眼睛,就能看见编译器优化时二进制位之间的能量流动被创造出来……", 161 | "如果{name}有一个 1 GB 的内存,你有一个 1 GB 的内存,那么${name}拥有比你更多的内存。", 162 | "当{name}执行 ps -e 时,你的名字会出现。", 163 | "从来就没有软件开发过程这回事,只有被{name}允许存在的一些程序。", 164 | "{name}的DNA中包含调试符号,尽管他从不需要它们。", 165 | "{name}的医生能通过 CVS 采集他的血样。", 166 | "对于{name}来说,多项式时间就是 O(1)。", 167 | "{name}将会使可口可乐在 GPL 协议下公布他们的配方。", 168 | "{name}不需要用鼠标或键盘来操作计算机。他只要凝视着它,直到它完成想要的工作。", 169 | "{name}就是图灵测试的解答。", 170 | ] 171 | ).format(name=name) 172 | 173 | 174 | @ignore_botself 175 | @these_msgtypes(MsgTypes.AtMsg) 176 | def receive_group_msg(ctx: GroupMsg): 177 | data = gp.at(ctx) 178 | if data is not None: 179 | if "nb" in data.Content: 180 | Text(get_niubi(data.UserExt[0].QQNick)) 181 | -------------------------------------------------------------------------------- /plugins/bot_peep.py: -------------------------------------------------------------------------------- 1 | """窥屏检测 2 | 发送 谁在窥屏+{可选检测时间} 3 | """ 4 | import random 5 | import re 6 | import time 7 | import uuid 8 | 9 | import httpx 10 | from botoy import Action, GroupMsg, S, jconfig 11 | from botoy.decorators import ignore_botself, startswith 12 | from ua_parser import user_agent_parser as ua_parser 13 | 14 | 15 | def build_card(brief="", title="", summary="", cover="", url="") -> str: 16 | return f"{title}{summary}" 17 | 18 | 19 | def ua_info(ua: str) -> str: 20 | info = ua_parser.ParseDevice(ua) 21 | return "设备: " + " ".join(list(info.values())) 22 | 23 | 24 | def ip_info(ip: str) -> str: 25 | data = httpx.get(f"http://ip-api.com/json/{ip}?lang=zh-CN", timeout=20).json() 26 | if not data["status"] == "success": 27 | return ip 28 | items = [ip] 29 | if country := data.get("country"): 30 | items.append(country) 31 | if region := data.get("region"): 32 | items.append(region) 33 | if city := data.get("city"): 34 | items.append(city) 35 | return " ".join(items) 36 | 37 | 38 | api: str = jconfig.ip_tracker_api.strip("/") 39 | 40 | 41 | @ignore_botself 42 | @startswith("谁在窥屏") 43 | def receive_group_msg(ctx: GroupMsg): 44 | key = f"{ctx.FromGroupId}{uuid.uuid4()}" 45 | 46 | action = Action.from_ctx(ctx) 47 | action.sendGroupXml( 48 | ctx.FromGroupId, 49 | build_card( 50 | "窥屏检测", 51 | title=random.choice( 52 | ("小👶你是否有很多❓", "小🐈🐈能有什么坏♥️👀", "大🐔大🍐今晚吃🐥", "🅾️🍐给!", "🃏竟是我自己🌝", "🌶👇💩💉💦🐮🍺") 53 | ), 54 | summary="\n你在偷窥啥(流汗黄豆)", 55 | cover=f"{api}/{key}", # ?r=重定向地址 56 | ), 57 | ) 58 | 59 | if delay := re.findall(r"谁在窥屏(\d+)", ctx.Content): 60 | delay = min(int(delay[0]), 30) 61 | else: 62 | delay = 10 63 | 64 | time.sleep(delay) 65 | 66 | resp = httpx.get(f"{api}/{key}.info", timeout=20).json() 67 | if resp["code"] != 0 or not resp["result"]: 68 | return 69 | 70 | results = {} 71 | for vistor in resp["result"]: 72 | ip = vistor["ip"] 73 | ua = vistor["user_agent"] 74 | if ip in results or not ua: 75 | continue 76 | 77 | results[ip] = f"{ip_info(ip)} - {ua_info(ua)}" 78 | 79 | if results: 80 | S.text("\n".join(list(results.values()))) 81 | -------------------------------------------------------------------------------- /plugins/bot_phlogo.py: -------------------------------------------------------------------------------- 1 | """快速生成PornHub风格的logo 2 | 横向:ph Hello world 3 | 竖直:ph Hello world 1 4 | """ 5 | # 主要代码部分 https://github.com/akarrin/ph-logo/ 6 | import base64 7 | import io 8 | 9 | from botoy import GroupMsg 10 | from botoy.contrib import get_cache_dir 11 | from botoy.decorators import ignore_botself, queued_up 12 | from botoy.sugar import Picture 13 | from PIL import Image, ImageDraw, ImageFont 14 | 15 | LEFT_PART_VERTICAL_BLANK_MULTIPLY_FONT_HEIGHT = 2 16 | LEFT_PART_HORIZONTAL_BLANK_MULTIPLY_FONT_WIDTH = 1 / 4 17 | RIGHT_PART_VERTICAL_BLANK_MULTIPLY_FONT_HEIGHT = 1 18 | RIGHT_PART_HORIZONTAL_BLANK_MULTIPLY_FONT_WIDTH = 1 / 4 19 | RIGHT_PART_RADII = 10 20 | BG_COLOR = "#000000" 21 | BOX_COLOR = "#F7971D" 22 | LEFT_TEXT_COLOR = "#FFFFFF" 23 | RIGHT_TEXT_COLOR = "#000000" 24 | FONT_SIZE = 50 25 | 26 | FONT_PATH = get_cache_dir("phlogo") / "ArialEnUnicodeBold.ttf" 27 | if not FONT_PATH.exists(): 28 | print("phlogo插件: 下载字体中....") 29 | import os 30 | 31 | import httpx 32 | 33 | try: 34 | with httpx.stream( 35 | "GET", 36 | "https://github.com/opq-osc/botoy-plugins/releases/download/phlogo%E6%89%80%E9%9C%80%E5%AD%97%E4%BD%93/ArialEnUnicodeBold.ttf", 37 | ) as resp: 38 | print("连接字体资源成功") 39 | total_size = int(resp.headers["content-length"]) 40 | downloaded_size = 0 41 | with open(FONT_PATH, "wb") as f: 42 | for chunk in resp.iter_bytes(1024): 43 | percent = int(100 * downloaded_size / total_size) 44 | print("\r|{:100}|{}%".format("#" * percent, percent), end="") 45 | f.write(chunk) 46 | downloaded_size += 1024 47 | print("\n下载字体完成") 48 | except Exception: 49 | if FONT_PATH.exists(): 50 | os.remove(FONT_PATH) 51 | raise 52 | 53 | FONT_PATH = str(FONT_PATH) 54 | 55 | 56 | def create_left_part_img(text: str, font_size: int, type_="h"): 57 | font = ImageFont.truetype(FONT_PATH, font_size) 58 | font_width, font_height = font.getsize(text) 59 | offset_y = font.font.getsize(text)[1][1] 60 | if type_ == "h": 61 | blank_height = font_height * 2 62 | else: 63 | blank_height = font_height 64 | right_blank = int( 65 | font_width / len(text) * LEFT_PART_HORIZONTAL_BLANK_MULTIPLY_FONT_WIDTH 66 | ) 67 | img_height = font_height + offset_y + blank_height * 2 68 | image_width = font_width + right_blank 69 | image_size = image_width, img_height 70 | image = Image.new("RGBA", image_size, BG_COLOR) 71 | draw = ImageDraw.Draw(image) 72 | draw.text((0, blank_height), text, fill=LEFT_TEXT_COLOR, font=font) 73 | return image 74 | 75 | 76 | def create_right_part_img(text: str, font_size: int): 77 | radii = RIGHT_PART_RADII 78 | font = ImageFont.truetype(FONT_PATH, font_size) 79 | font_width, font_height = font.getsize(text) 80 | offset_y = font.font.getsize(text)[1][1] 81 | blank_height = font_height * RIGHT_PART_VERTICAL_BLANK_MULTIPLY_FONT_HEIGHT 82 | left_blank = int( 83 | font_width / len(text) * RIGHT_PART_HORIZONTAL_BLANK_MULTIPLY_FONT_WIDTH 84 | ) 85 | image_width = font_width + 2 * left_blank 86 | image_height = font_height + offset_y + blank_height * 2 87 | image = Image.new("RGBA", (image_width, image_height), BOX_COLOR) 88 | draw = ImageDraw.Draw(image) 89 | draw.text((left_blank, blank_height), text, fill=RIGHT_TEXT_COLOR, font=font) 90 | 91 | # 圆 92 | magnify_time = 10 93 | magnified_radii = radii * magnify_time 94 | circle = Image.new( 95 | "L", (magnified_radii * 2, magnified_radii * 2), 0 96 | ) # 创建一个黑色背景的画布 97 | draw = ImageDraw.Draw(circle) 98 | draw.ellipse((0, 0, magnified_radii * 2, magnified_radii * 2), fill=255) # 画白色圆形 99 | 100 | # 画4个角(将整圆分离为4个部分) 101 | magnified_alpha_width = image_width * magnify_time 102 | magnified_alpha_height = image_height * magnify_time 103 | alpha = Image.new("L", (magnified_alpha_width, magnified_alpha_height), 255) 104 | alpha.paste(circle.crop((0, 0, magnified_radii, magnified_radii)), (0, 0)) # 左上角 105 | alpha.paste( 106 | circle.crop((magnified_radii, 0, magnified_radii * 2, magnified_radii)), 107 | (magnified_alpha_width - magnified_radii, 0), 108 | ) # 右上角 109 | alpha.paste( 110 | circle.crop( 111 | (magnified_radii, magnified_radii, magnified_radii * 2, magnified_radii * 2) 112 | ), 113 | ( 114 | magnified_alpha_width - magnified_radii, 115 | magnified_alpha_height - magnified_radii, 116 | ), 117 | ) # 右下角 118 | alpha.paste( 119 | circle.crop((0, magnified_radii, magnified_radii, magnified_radii * 2)), 120 | (0, magnified_alpha_height - magnified_radii), 121 | ) # 左下角 122 | alpha = alpha.resize((image_width, image_height), Image.ANTIALIAS) 123 | image.putalpha(alpha) 124 | return image 125 | 126 | 127 | def combine_img_horizontal( 128 | left_text: str, right_text, font_size: int = FONT_SIZE 129 | ) -> str: 130 | left_img = create_left_part_img(left_text, font_size) 131 | right_img = create_right_part_img(right_text, font_size) 132 | blank = 30 133 | bg_img_width = left_img.width + right_img.width + blank * 2 134 | bg_img_height = left_img.height 135 | bg_img = Image.new("RGBA", (bg_img_width, bg_img_height), BG_COLOR) 136 | bg_img.paste(left_img, (blank, 0)) 137 | bg_img.paste( 138 | right_img, 139 | (blank + left_img.width, int((bg_img_height - right_img.height) / 2)), 140 | mask=right_img, 141 | ) 142 | buffer = io.BytesIO() 143 | bg_img.save(buffer, format="png") 144 | return base64.b64encode(buffer.getvalue()).decode() 145 | 146 | 147 | def combine_img_vertical(left_text: str, right_text, font_size: int = FONT_SIZE) -> str: 148 | left_img = create_left_part_img(left_text, font_size, type_="v") 149 | right_img = create_right_part_img(right_text, font_size) 150 | blank = 15 151 | bg_img_width = max(left_img.width, right_img.width) + blank * 2 152 | bg_img_height = left_img.height + right_img.height + blank * 2 153 | bg_img = Image.new("RGBA", (bg_img_width, bg_img_height), BG_COLOR) 154 | bg_img.paste(left_img, (int((bg_img_width - left_img.width) / 2), blank)) 155 | bg_img.paste( 156 | right_img, 157 | (int((bg_img_width - right_img.width) / 2), blank + left_img.height), 158 | mask=right_img, 159 | ) 160 | buffer = io.BytesIO() 161 | bg_img.save(buffer, format="png") 162 | return base64.b64encode(buffer.getvalue()).decode() 163 | 164 | 165 | @ignore_botself 166 | @queued_up # 发图的操作排队执行成功率更高 167 | def receive_group_msg(ctx: GroupMsg): 168 | if ctx.Content.startswith("ph "): 169 | args = [i.strip() for i in ctx.Content.split(" ") if i.strip()] 170 | if len(args) >= 3: 171 | left = args[1] 172 | right = args[2] 173 | f = combine_img_horizontal 174 | if len(args) >= 4: 175 | if args[3] == "1": 176 | f = combine_img_vertical 177 | Picture(pic_base64=f(left, right)) 178 | -------------------------------------------------------------------------------- /plugins/bot_replay.py: -------------------------------------------------------------------------------- 1 | from botoy import GroupMsg, Picture, Text 2 | from botoy.collection import MsgTypes 3 | from botoy.decorators import ignore_botself, startswith 4 | from botoy.parser import group as gp 5 | 6 | 7 | @ignore_botself 8 | @startswith("复读机") 9 | def receive_group_msg(ctx: GroupMsg): 10 | 11 | text = ctx.Content[3:] 12 | 13 | if ctx.MsgType == MsgTypes.TextMsg: 14 | Text(text) 15 | elif ctx.MsgType == MsgTypes.PicMsg: 16 | pic_data = gp.pic(ctx) 17 | if pic_data is not None: 18 | Picture( 19 | text=text, 20 | pic_md5=[pic.FileMd5 for pic in pic_data.GroupPic], 21 | ) 22 | -------------------------------------------------------------------------------- /plugins/bot_rip_avatar.py: -------------------------------------------------------------------------------- 1 | """撕开头像 2 | 1. AT一人发送撕 3 | 2. 发送图片加撕 4 | """ 5 | from io import BytesIO 6 | from typing import Union 7 | 8 | import httpx 9 | from botoy import GroupMsg, S 10 | from botoy.collection import MsgTypes 11 | from botoy.contrib import download, get_cache_dir 12 | from botoy.decorators import ignore_botself 13 | from botoy.parser import group as gp 14 | from PIL import Image 15 | 16 | TEMPLATE_PATH = get_cache_dir("rip_avatar") / "rip.png" 17 | if not TEMPLATE_PATH.exists(): 18 | download( 19 | "https://github.com/opq-osc/botoy-plugins/releases/download/rip.png/rip.png", 20 | TEMPLATE_PATH, 21 | ) 22 | TEMPLATE_PATH = str(TEMPLATE_PATH) 23 | 24 | 25 | def get_avator(image: Union[int, str]): 26 | if isinstance(image, int): 27 | image = f"http://q1.qlogo.cn/g?b=qq&nk={image}&s=640" 28 | 29 | content = httpx.get(image, timeout=20).content 30 | return Image.open(BytesIO(content)).convert("RGBA") 31 | 32 | 33 | def rip(user: Union[int, str]) -> BytesIO: 34 | RIP_TEMPLATE = Image.open(TEMPLATE_PATH) 35 | img = Image.new("RGBA", (1080, 804), (255, 255, 255, 0)) 36 | avatar = get_avator(user) 37 | left = avatar.resize((385, 385)).rotate(24, expand=True) 38 | right = avatar.resize((385, 385)).rotate(-11, expand=True) 39 | img.paste(left, (-5, 355)) 40 | img.paste(right, (649, 310)) 41 | img.paste(RIP_TEMPLATE, mask=RIP_TEMPLATE) 42 | img = img.convert("RGB") 43 | buffer = BytesIO() 44 | img.save(buffer, format="jpeg") 45 | return buffer 46 | 47 | 48 | @ignore_botself 49 | def receive_group_msg(ctx: GroupMsg): 50 | if "撕" not in ctx.Content: 51 | return 52 | if ctx.MsgType == MsgTypes.PicMsg: 53 | pic = gp.pic(ctx) 54 | if not pic: 55 | return 56 | user = pic.GroupPic[0].Url 57 | elif ctx.MsgType == MsgTypes.AtMsg: 58 | at = gp.at(ctx) 59 | if not at: 60 | return 61 | user = at.UserExt[0].QQUid 62 | else: 63 | return 64 | 65 | S.bind(ctx).image(rip(user)) 66 | -------------------------------------------------------------------------------- /plugins/bot_rua/__init__.py: -------------------------------------------------------------------------------- 1 | """AT一个人发送 rua 试试""" 2 | # type: ignore 3 | import base64 4 | import io 5 | import pathlib 6 | from io import BytesIO 7 | from os import path 8 | 9 | import httpx 10 | from botoy import GroupMsg, Picture 11 | from botoy.decorators import ignore_botself 12 | from botoy.parser import group as gp 13 | from PIL import Image, ImageDraw 14 | 15 | 16 | def get_circle_avatar(avatar, size): 17 | # avatar.thumbnail((size, size)) 18 | avatar = avatar.resize((size, size)) 19 | scale = 5 20 | mask = Image.new("L", (size * scale, size * scale), 0) 21 | draw = ImageDraw.Draw(mask) 22 | draw.ellipse((0, 0, size * scale, size * scale), fill=255) 23 | mask = mask.resize((size, size), Image.ANTIALIAS) 24 | ret_img = avatar.copy() 25 | ret_img.putalpha(mask) 26 | return ret_img 27 | 28 | 29 | def generate_gif(frame_dir: str, avatar: Image.Image) -> str: 30 | avatar_size = [(350, 350), (372, 305), (395, 283), (380, 305), (350, 372)] 31 | avatar_pos = [(50, 150), (28, 195), (5, 217), (5, 195), (50, 128)] 32 | imgs = [] 33 | for i in range(5): 34 | im = Image.new(mode="RGBA", size=(600, 600)) 35 | hand = Image.open(path.join(frame_dir, f"hand-{i+1}.png")) 36 | hand = hand.convert("RGBA") 37 | avatar = get_circle_avatar(avatar, 350) 38 | avatar = avatar.resize(avatar_size[i]) 39 | im.paste(avatar, avatar_pos[i], mask=avatar.split()[3]) 40 | im.paste(hand, mask=hand.split()[3]) 41 | mask = im.split()[3] 42 | mask = Image.eval(mask, lambda a: 255 if a <= 50 else 0) 43 | im = im.convert("RGB").convert("P", palette=Image.ADAPTIVE, colors=255) 44 | im.paste(255, mask) 45 | imgs.append(im) 46 | buf = io.BytesIO() 47 | imgs[0].save( 48 | fp=buf, 49 | format="gif", 50 | save_all=True, 51 | append_images=imgs, 52 | duration=25, 53 | loop=0, 54 | quality=80, 55 | transparency=255, 56 | disposal=3, 57 | ) 58 | 59 | return base64.b64encode(buf.getvalue()).decode() 60 | 61 | 62 | frame_dir = pathlib.Path(__file__).absolute().parent / "frames" 63 | 64 | 65 | @ignore_botself 66 | def receive_group_msg(ctx: GroupMsg): 67 | at_data = gp.at(ctx, clean=False) 68 | if at_data is None: 69 | return 70 | if "rua" in at_data.Content.strip(): 71 | user = at_data.UserExt[0].QQUid 72 | content = httpx.get( 73 | f"http://q1.qlogo.cn/g?b=qq&nk={user}&s=160", timeout=20 74 | ).content 75 | avatar = Image.open(BytesIO(content)) 76 | Picture(pic_base64=generate_gif(str(frame_dir), avatar)) 77 | -------------------------------------------------------------------------------- /plugins/bot_rua/frames/hand-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_rua/frames/hand-1.png -------------------------------------------------------------------------------- /plugins/bot_rua/frames/hand-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_rua/frames/hand-2.png -------------------------------------------------------------------------------- /plugins/bot_rua/frames/hand-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_rua/frames/hand-3.png -------------------------------------------------------------------------------- /plugins/bot_rua/frames/hand-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_rua/frames/hand-4.png -------------------------------------------------------------------------------- /plugins/bot_rua/frames/hand-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/botoy-plugins/ccd23c1e21c451c21d4ae27904cf0e53072e5baa/plugins/bot_rua/frames/hand-5.png -------------------------------------------------------------------------------- /plugins/bot_russian_turntable.py: -------------------------------------------------------------------------------- 1 | """俄罗斯轮盘游戏(启蒙版):开始轮盘""" 2 | import random 3 | from typing import List 4 | 5 | from botoy.decorators import equal_content, ignore_botself 6 | from botoy.session import SessionHandler, session 7 | 8 | 9 | def reload(count=1) -> List[int]: 10 | clip = [0, 0, 0, 0, 0, 0] 11 | for idx in range(count): 12 | clip[idx] = 1 13 | random.shuffle(clip) 14 | return clip 15 | 16 | 17 | turntable = SessionHandler( 18 | ignore_botself, equal_content("开始轮盘"), single_user=False 19 | ).receive_group_msg() 20 | 21 | 22 | @turntable.handle 23 | def _(): 24 | count_input: str = session.want( # type:ignore 25 | "count", "俄罗斯轮盘游戏开始,请发送要填充的子弹数(大于0,小于6)", default="1", timeout=20 26 | ) 27 | if count_input.isdigit(): 28 | count = int(count_input) 29 | if count < 0 or count >= 6: 30 | turntable.finish("输入不合法,请重新开始") 31 | else: 32 | count = 1 33 | session.send_text(f"装填子弹:{count}颗\n\n(任意用户发送任意内容开枪, 30s无人回复将自动结束游戏) ") 34 | clip = reload(count) 35 | while clip: 36 | # 剩下全是空弹或实弹就没必要继续了 37 | if set(clip) == {1}: 38 | turntable.finish("剩余全是实弹, 游戏结束") 39 | elif set(clip) == {0}: 40 | turntable.finish("剩余全是空弹, 游戏结束") 41 | 42 | shot = session.want( 43 | "shot", 44 | f"当前剩余枪数: {len(clip)}. ", 45 | pop=True, 46 | timeout=30, 47 | ) 48 | if shot is None: 49 | turntable.finish("无人回复,游戏自动结束") 50 | 51 | if clip.pop() == 1: 52 | msg = "很不幸,你中枪了:(" 53 | else: 54 | msg = "恭喜你,是空弹!" 55 | session.send_text(msg) 56 | 57 | turntable.finish() 58 | -------------------------------------------------------------------------------- /plugins/bot_sbqb.py: -------------------------------------------------------------------------------- 1 | """搜表情包 2 | 发送 表情包+{关键词} 如 表情包罗翔 3 | """ 4 | 5 | import random 6 | 7 | import httpx 8 | from botoy.decorators import ignore_botself, on_regexp 9 | from botoy.session import SessionHandler, ctx, session 10 | 11 | handler = SessionHandler(ignore_botself, on_regexp(r"^表情包(\w+)$")).receive_group_msg() 12 | 13 | 14 | @handler.handle 15 | def _(): 16 | try: 17 | resp = httpx.get( 18 | "https://api.iyk0.com/sbqb/", 19 | params={"msg": str(ctx._match.group(1))}, 20 | timeout=10, 21 | follow_redirects=True, 22 | ) 23 | resp.raise_for_status() 24 | data = resp.json() 25 | assert data["code"] == 200 26 | imgs = data["data_img"] 27 | random.shuffle(imgs) 28 | imgs = imgs[:10] 29 | except Exception: 30 | handler.finish("出错啦,没有找到表情包") 31 | else: 32 | if not imgs: 33 | handler.finish("没有找到表情包") 34 | if len(imgs) == 1: 35 | session.send_pic(imgs[0]['img']) 36 | else: 37 | choose = session.choose(imgs, key=lambda img: img["describe"]) 38 | if choose: 39 | session.send_pic(choose[0]["img"]) 40 | 41 | handler.finish() 42 | -------------------------------------------------------------------------------- /plugins/bot_sese.py: -------------------------------------------------------------------------------- 1 | """发送 sese+文字 试试""" 2 | import os 3 | import subprocess 4 | import tempfile 5 | from pathlib import Path 6 | 7 | from botoy import GroupMsg, S 8 | from botoy import decorators as deco 9 | from botoy.contrib import download, get_cache_dir 10 | 11 | TEMPLATE = """ 12 | [V4+ Styles] 13 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 14 | Style: sorry,WenQuanYi Micro Hei,38,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1.2,0.6,2,5,5,5,1 15 | 16 | [Events] 17 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 18 | Dialogue: 0,0:00:00.00,0:00:01.04,sorry,,0,0,0,,{text} 19 | """ 20 | 21 | 22 | class TemporaryFile: 23 | def __init__(self, suffix=None) -> None: 24 | self.path = Path(tempfile.mkstemp(suffix=suffix)[1]) 25 | 26 | def __enter__(self) -> Path: 27 | return self.path 28 | 29 | def __exit__(self, *_): 30 | try: 31 | os.remove(self.path) 32 | except FileNotFoundError: 33 | pass 34 | 35 | 36 | mp4 = get_cache_dir("bot_sese") / "kick.mp4" 37 | if not mp4.is_file(): 38 | download( 39 | "https://cdn.jsdelivr.net/gh/opq-osc/OPQ-PHP-plugins@main/public/templates/sese/template-small.mp4", 40 | mp4, 41 | ) 42 | 43 | 44 | @deco.ignore_botself 45 | @deco.on_regexp(r"sese(\w+)") 46 | def receive_group_msg(ctx: GroupMsg): 47 | text: str = ctx._match.group(1).strip() 48 | with TemporaryFile(".ass") as template: 49 | template.write_text(TEMPLATE.format(text=text)) 50 | with TemporaryFile(".gif") as gif: 51 | process = subprocess.run( 52 | [ 53 | "ffmpeg", 54 | "-y", 55 | "-i", 56 | mp4, 57 | "-vf", 58 | f"ass={template}", 59 | gif, 60 | "-loglevel", 61 | "quiet", 62 | ] 63 | ) 64 | if process.returncode == 0 and gif.read_bytes(): 65 | S.image(gif) 66 | -------------------------------------------------------------------------------- /plugins/bot_steam.py: -------------------------------------------------------------------------------- 1 | """steam促销信息 发送 steam""" 2 | import httpx 3 | from botoy import Text 4 | from botoy.decorators import equal_content, ignore_botself 5 | from bs4 import BeautifulSoup as BS 6 | 7 | 8 | # reference: https://github.com/pcrbot/Hoshino-plugin-transplant/blob/master/steam/steam.py 9 | def fetch() -> str: 10 | try: 11 | html = httpx.get( 12 | "https://store.steampowered.com/specials#tab=TopSellers", timeout=30 13 | ).text 14 | msgs = [] 15 | for item in BS(html, "lxml").find_all("div", id="TopSellersRows")[0].find_all("a"): # type: ignore 16 | name = item.find_all("div", class_="tab_item_name")[0].get_text() 17 | zheKou = ( 18 | "打折:" + item.find_all("div", class_="discount_pct")[0].get_text() + "\n" 19 | ) 20 | Resource_piece = ( 21 | "原价:" 22 | + item.find_all("div", class_="discount_original_price")[0].get_text() 23 | + "\n" 24 | ) 25 | now_piece = ( 26 | "现价:" 27 | + item.find_all("div", class_="discount_final_price")[0].get_text() 28 | + "\n" 29 | ) 30 | msgs.append( 31 | name 32 | + "\n" 33 | + zheKou 34 | + Resource_piece 35 | + now_piece 36 | + str(item.get("href")) 37 | + "\n------------------------------------\n" 38 | ) 39 | return "\n".join(msgs) 40 | except Exception: 41 | return "" 42 | 43 | 44 | @ignore_botself 45 | @equal_content("steam") 46 | def receive_group_msg(_): 47 | msg = fetch() 48 | if msg: 49 | Text(msg) 50 | 51 | 52 | if __name__ == "__main__": 53 | print(fetch()) 54 | -------------------------------------------------------------------------------- /plugins/bot_stopRevoke.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sqlite3 3 | import time 4 | from collections import defaultdict 5 | 6 | from botoy import Action, EventMsg, GroupMsg 7 | from botoy.collection import MsgTypes 8 | from botoy.contrib import get_cache_dir 9 | from botoy.decorators import ignore_botself, these_msgtypes 10 | from botoy.parser import event as ep 11 | 12 | db_cache_dir = get_cache_dir("for_stop_revoke_plugin") 13 | 14 | 15 | class DB: 16 | def init(self, db_name: str): 17 | self.db_name = db_name 18 | 19 | self.con = sqlite3.connect(db_cache_dir / self.db_name) 20 | self.cs = self.con.cursor() 21 | 22 | self.create() 23 | self.clear() 24 | 25 | def create(self): 26 | try: 27 | self.cs.execute( 28 | """ 29 | create table message( 30 | id integer primary key autoincrement, 31 | msg_type varchar(20), 32 | msg_random integer, 33 | msg_seq integer, 34 | msg_time integer, 35 | user_id integer, 36 | user_name text, 37 | group_id integer, 38 | content text 39 | ); 40 | """ 41 | ) 42 | self.con.commit() 43 | except sqlite3.OperationalError as e: 44 | if str(e) != "table message already exists": 45 | raise e 46 | 47 | def insert( 48 | self, 49 | msg_type: str, 50 | msg_random: int, 51 | msg_seq: int, 52 | msg_time: int, 53 | user_id: int, 54 | user_name: str, 55 | group_id: int, 56 | content: str, 57 | ): 58 | self.cs.execute( 59 | f"""insert into message (msg_type, msg_random, msg_seq, msg_time, user_id, user_name, group_id, content) 60 | values ('{msg_type}', {msg_random}, {msg_seq}, {msg_time}, {user_id}, '{user_name}', {group_id}, '{content}')""" 61 | ) 62 | self.con.commit() 63 | 64 | def find(self, **kw) -> list: 65 | conditions = [] 66 | for key, value in kw.items(): 67 | if isinstance(value, str): 68 | conditions.append(f"{key}='{value}'") 69 | else: 70 | conditions.append(f"{key}={value}") 71 | self.cs.execute("select * from message where %s;" % " and ".join(conditions)) 72 | return self.cs.fetchall() 73 | 74 | def clear(self): 75 | self.cs.execute( 76 | "delete from message where msg_time < %d;" 77 | % (int(time.time()) - 10 * 60) # 发出了10分钟的消息,哪位大哥还会撤回?? 78 | ) 79 | self.con.commit() 80 | 81 | 82 | db_map = defaultdict(DB) 83 | 84 | 85 | @ignore_botself 86 | @these_msgtypes(MsgTypes.TextMsg, MsgTypes.PicMsg) 87 | def receive_group_msg(ctx: GroupMsg): 88 | db_name = f"group{ctx.FromGroupId}" 89 | db: DB = db_map[db_name] 90 | db.init(db_name) 91 | db.insert( 92 | ctx.MsgType, 93 | ctx.MsgRandom, 94 | ctx.MsgSeq, 95 | ctx.MsgTime, 96 | ctx.FromUserId, 97 | ctx.FromNickName, 98 | ctx.FromGroupId, 99 | ctx.Content, 100 | ) 101 | 102 | 103 | def receive_events(ctx: EventMsg): 104 | revoke_data = ep.group_revoke(ctx) 105 | if revoke_data is None: 106 | return 107 | 108 | admin = revoke_data.AdminUserID 109 | group_id = revoke_data.GroupID 110 | user_id = revoke_data.UserID 111 | msg_random = revoke_data.MsgRandom 112 | msg_seq = revoke_data.MsgSeq 113 | 114 | if any( 115 | [ 116 | user_id == ctx.CurrentQQ, # 忽略机器人自己撤回的消息 117 | admin != user_id, # 忽略管理员撤回的消息 118 | ] 119 | ): 120 | return 121 | 122 | db_name = f"group{group_id}" 123 | db: DB = db_map[db_name] 124 | db.init(db_name) 125 | 126 | data = db.find( 127 | msg_random=msg_random, msg_seq=msg_seq, user_id=user_id, group_id=group_id 128 | ) 129 | if not data: 130 | return 131 | data = data[0] 132 | ( 133 | _, 134 | msg_type, 135 | _, 136 | _, 137 | _, 138 | user_id, 139 | user_name, 140 | group_id, 141 | content, 142 | ) = data 143 | action = Action( 144 | ctx.CurrentQQ, host=ctx._host, port=ctx._port # type:ignore 145 | ) 146 | if msg_type == MsgTypes.TextMsg: 147 | action.sendGroupText(group_id, f"{user_name}想撤回以下内容: \n{content}") 148 | elif msg_type == MsgTypes.PicMsg: 149 | pic_data = json.loads(content) 150 | action.sendGroupText(group_id, f"{user_name}想撤回以下内容: ") 151 | action.sendGroupPic( 152 | group_id, picMd5s=[pic["FileMd5"] for pic in pic_data["GroupPic"]] 153 | ) 154 | -------------------------------------------------------------------------------- /plugins/bot_sysinfo.py: -------------------------------------------------------------------------------- 1 | """查看服务器当前运行信息 命令:sysinfo""" 2 | import datetime 3 | import time 4 | 5 | import cpuinfo 6 | import psutil 7 | from botoy import decorators as deco 8 | from botoy.sugar import Text 9 | 10 | 11 | def get_cpu_info(): 12 | info = cpuinfo.get_cpu_info() # 获取CPU型号等 13 | cpu_count = psutil.cpu_count(logical=False) # 1代表单核CPU,2代表双核CPU 14 | xc_count = psutil.cpu_count() # 线程数,如双核四线程 15 | cpu_percent = round((psutil.cpu_percent()), 2) # cpu使用率 16 | try: 17 | model = info["hardware_raw"] # cpu型号 18 | except Exception: 19 | model = info["brand_raw"] # cpu型号 20 | try: # 频率 21 | freq = info["hz_actual_friendly"] 22 | except Exception: 23 | freq = "null" 24 | cpu_info = (model, freq, info["arch"], cpu_count, xc_count, cpu_percent) 25 | return cpu_info 26 | 27 | 28 | def get_memory_info(): 29 | memory = psutil.virtual_memory() 30 | swap = psutil.swap_memory() 31 | total_nc = round((float(memory.total) / 1024 / 1024 / 1024), 3) # 总内存 32 | used_nc = round((float(memory.used) / 1024 / 1024 / 1024), 3) # 已用内存 33 | available_nc = round((float(memory.available) / 1024 / 1024 / 1024), 3) # 空闲内存 34 | percent_nc = memory.percent # 内存使用率 35 | swap_total = round((float(swap.total) / 1024 / 1024 / 1024), 3) # 总swap 36 | swap_used = round((float(swap.used) / 1024 / 1024 / 1024), 3) # 已用swap 37 | swap_free = round((float(swap.free) / 1024 / 1024 / 1024), 3) # 空闲swap 38 | swap_percent = swap.percent # swap使用率 39 | men_info = ( 40 | total_nc, 41 | used_nc, 42 | available_nc, 43 | percent_nc, 44 | swap_total, 45 | swap_used, 46 | swap_free, 47 | swap_percent, 48 | ) 49 | return men_info 50 | 51 | 52 | def uptime(): 53 | now = time.time() 54 | boot = psutil.boot_time() 55 | boottime = datetime.datetime.fromtimestamp(boot).strftime("%Y-%m-%d %H:%M:%S") 56 | nowtime = datetime.datetime.fromtimestamp(now).strftime("%Y-%m-%d %H:%M:%S") 57 | up_time = str( 58 | datetime.datetime.utcfromtimestamp(now).replace(microsecond=0) 59 | - datetime.datetime.utcfromtimestamp(boot).replace(microsecond=0) 60 | ) 61 | alltime = (boottime, nowtime, up_time) 62 | return alltime 63 | 64 | 65 | def sysinfo(): 66 | cpu_info = get_cpu_info() 67 | mem_info = get_memory_info() 68 | up_time = uptime() 69 | msg = ( 70 | "CPU型号:{0}\r\n频率:{1}\r\n架构:{2}\r\n核心数:{3}\r\n线程数:{4}\r\n负载:{5}%\r\n{6}\r\n" 71 | "总内存:{7}G\r\n已用内存:{8}G\r\n空闲内存:{9}G\r\n内存使用率:{10}%\r\n{6}\r\n" 72 | "swap:{11}G\r\n已用swap:{12}G\r\n空闲swap:{13}G\r\nswap使用率:{14}%\r\n{6}\r\n" 73 | "开机时间:{15}\r\n当前时间:{16}\r\n已运行时间:{17}" 74 | ) 75 | full_meg = msg.format( 76 | cpu_info[0], 77 | cpu_info[1], 78 | cpu_info[2], 79 | cpu_info[3], 80 | cpu_info[4], 81 | cpu_info[5], 82 | "*" * 20, 83 | mem_info[0], 84 | mem_info[1], 85 | mem_info[2], 86 | mem_info[3], 87 | mem_info[4], 88 | mem_info[5], 89 | mem_info[6], 90 | mem_info[7], 91 | up_time[0], 92 | up_time[1], 93 | up_time[2], 94 | ) 95 | return full_meg 96 | 97 | 98 | @deco.ignore_botself 99 | @deco.equal_content("sysinfo") 100 | def receive_group_msg(_): 101 | Text(sysinfo()) 102 | -------------------------------------------------------------------------------- /plugins/bot_toPinYin.py: -------------------------------------------------------------------------------- 1 | """汉字转拼音:拼音{汉字}""" 2 | import httpx 3 | from botoy import GroupMsg 4 | from botoy.collection import MsgTypes 5 | from botoy.decorators import ignore_botself, startswith, these_msgtypes 6 | from botoy.sugar import Text 7 | 8 | 9 | @ignore_botself 10 | @these_msgtypes(MsgTypes.TextMsg) 11 | @startswith("拼音") 12 | def receive_group_msg(ctx: GroupMsg): 13 | word = ctx.Content[2:] 14 | if word: 15 | try: 16 | resp = httpx.get( 17 | "https://v1.alapi.cn/api/pinyin", 18 | params={"word": word, "tone": 1}, 19 | timeout=10, 20 | ) 21 | resp.raise_for_status() 22 | res = resp.json() 23 | word = res["data"]["word"] 24 | pinyin = res["data"]["pinyin"] 25 | except Exception: 26 | pass 27 | else: 28 | Text(f"{word}\n{pinyin}") 29 | -------------------------------------------------------------------------------- /plugins/bot_versailles.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from botoy.decorators import equal_content, ignore_botself 4 | from botoy.sugar import Text 5 | 6 | __name__ = "凡尔赛语录" 7 | __doc__ = "凡尔赛语录:发送凡尔赛" 8 | 9 | setences = ( 10 | "今天,我的姐妹说要开跑车来接我下班,我说不要,打工人怎么能用跑车下班?家里已经帮我包了一辆公车了", 11 | "看到我朋友圈的步数,朋友问,你今天去跑马拉松了吗?其实没有,我就是绕着自己的庄园散了个步", 12 | "哎,烦死了,每天上班这么晚,下班还这么早", 13 | "今天,给女朋友发红包的时候!不小心把金额输成电话号码了,居然还成功了,害得我又少了一个月的零花钱", 14 | "用了好多年手机,我才知道原来手机没电了可以充电不用买新的,还有前两天又去换车,4s店才告诉我原来汽车是可以加油的", 15 | "为什么凡尔赛就是装逼的意思?凡尔赛宫就在我家隔壁,我经常去做客", 16 | "香奈儿太不好用了还是用爱马仕买菜装的多", 17 | "每天的食材不都有专门的人员送到后厨吗?", 18 | "哎,也想看看平民的生活", 19 | "我家保姆用海蓝之谜你说她是不是有点小奢侈?", 20 | "烦人,每次去厨房拿点吃的都要走断腿,太远了。", 21 | "最大的遗憾就是因为保送错过了一生一次的高考经历", 22 | "真烦,我都说了不要吃鲍鱼龙虾了就不能给我一份方便面嘛", 23 | "今天去珠宝店看了看价格700w我弱弱的回去买我的兰博基尼算了", 24 | "说出来挺不好意思,我是今天才知道原来鸡蛋 有壳,以前都是管家剥好给我吃,一直以为鸡蛋都是白白软软的。", 25 | "以前我是不会去商场买的,我觉得会比较贵,然后还要看标签。现在基本上不太需要看标签了,因为我收入还比较高", 26 | "最近去试完衣服顺路回家都买几捧玫瑰,老公突然就说要买一个带院子的房子,种满玫瑰叫园丁专门打理,但是玫瑰看久了也一般啊", 27 | "上次开私飞回巴黎,正好遇到伟霆,一直跟着我,还跟我要微信 号,都无语了, 我就这一个微信号 ,给你了我用啥啊,真的感到很困扰", 28 | "被某先生气死,明明是自己懒不去洗车,落了一层灰被写了字,他倒还好意思舔着脸问我:宝贝,是不是不喜欢?宝贝,怎么老是不开总放着", 29 | "今早我们家的园丁被我开除了,因为透过望远镜看到50公里以外正在工作的他竟然穿着今年阿玛尼的春装。拜托,这都要2020年冬天了喂", 30 | "每天就大鱼大肉胡吃海塞,是个人就腻啊!还有家里的珠宝首饰太多了!堆得屋子乱难收拾,请了三十多个丫鬟都收拾不过来。为什么?因为屋子太大了呗", 31 | "我说想吃橘子了 我小舅就把他万亩橘园都送给了我 说我想吃多少吃多少。我说 唉 好苦恼啊 这我怎么能吃的完呢?他说:吃不完就卖了 这百八千万当我的嫁妆了", 32 | "之前心血来潮想申请几个学校试试,就随便申了几个,布朗、斯坦福、南加大、西北、埃默里……除了梦校哈佛还是差一点,但幸好西北收留了我这个five,做人还是要学会知足啊!毕竟知足常乐嘛", 33 | "去小区里的驿站取快递,老板突然把我拦下,要把她侄子介绍给我,缠着给我看照片,是个185帅哥,还一直跟我说家庭条件有多好。我问为啥是我,老板说你身材又高挑长得又漂亮,别人我都不给他介绍的。旁边都是看戏的大爷大妈们,起哄让我赶紧加他微信,我吓得落荒而逃了", 34 | "刚刚上班被老板骂,用昨天刚到的iPhone 12 Pro Max 512G给先森发了条消息:“好难哦,被老板骂了,我不想上班了。” 大概15分钟,都快下班了他还没有理我,我已经有点生气了。突然他从背后环绕住我:“我在。” 先森收购公司,正好只要15分钟", 35 | "吃到腻的大鱼大肉、多到乱的珠宝首饰、三十多个丫鬟也收拾不过来的屋子", 36 | "哎,烦死了,每天上班这么晚,还下班这么早", 37 | "老公竟然送了我一辆粉红的兰博基尼,这颜色选的也太直男了吧,哎,怎么跟他说我不喜欢这个颜色呢?", 38 | "你们啊,三句话离不开赚钱,搞得我好像缺钱似的", 39 | "作为程序员没有在公司加过班,我总觉得自己缺了点什么", 40 | ) 41 | 42 | 43 | @ignore_botself 44 | @equal_content("凡尔赛") 45 | def receive_group_msg(_): 46 | Text(random.choice(setences)) 47 | -------------------------------------------------------------------------------- /plugins/bot_weather.py: -------------------------------------------------------------------------------- 1 | """查天气 天气+地名""" 2 | import json 3 | import re 4 | from typing import List, Tuple 5 | 6 | import httpx 7 | from botoy.contrib import RateLimit 8 | from botoy.decorators import ignore_botself, startswith 9 | from botoy.session import FILTER_SUCCESS, SessionHandler, ctx, session 10 | 11 | 12 | def search_city(city: str) -> List[Tuple[str, str]]: 13 | ret = [] 14 | 15 | try: 16 | resp = httpx.get( 17 | "http://toy1.weather.com.cn/search", params={"cityname": city}, timeout=20 18 | ) 19 | for item in json.loads(resp.text[1:-1]): 20 | code, _, city, *_, province = item["ref"].split("~") 21 | ret.append((code, f"{province} {city}")) 22 | except Exception: 23 | pass 24 | 25 | return ret 26 | 27 | 28 | def search_weather(code: str) -> str: 29 | 30 | # TODO: 日出、日落 31 | 32 | if len(code) > 9: 33 | # town 34 | try: 35 | resp = httpx.get( 36 | f"http://forecast.weather.com.cn/town/weather1dn/{code}.shtml", 37 | timeout=20, 38 | ) 39 | # time weather wind windL 40 | items = re.findall( 41 | r"""
(.*?)
\s+\s+
\s+
(.*?)
\s+
(.*?)
""", 42 | resp.text, 43 | ) 44 | infos = [] 45 | for item in items: 46 | time, weather, wind, windL = item 47 | windL = windL.replace("<", "<").replace(">", ">") 48 | infos.append(f"{time} {weather} {wind} {windL}") 49 | return "\n".join(infos) 50 | except Exception: 51 | pass 52 | 53 | else: 54 | try: 55 | resp = httpx.get( 56 | f"http://www.weather.com.cn/weather1d/{code}.shtml", timeout=20 57 | ) 58 | hour3data_str = re.findall(r"hour3data=(\{.*?\})", resp.text)[ 59 | 0 60 | ] # type: str 61 | hour3data = json.loads(hour3data_str) # type: dict 62 | infos = [] 63 | for item in hour3data["1d"]: 64 | info_items = item.split(",") 65 | del info_items[1] 66 | del info_items[-1] 67 | infos.append(" ".join(info_items)) 68 | return "\n".join(infos) 69 | except Exception: 70 | pass 71 | 72 | return "" 73 | 74 | 75 | ratelimit = RateLimit(5, 60) 76 | 77 | weather_handler = SessionHandler( 78 | ignore_botself, 79 | # 指令必须为天气加地点 80 | startswith("天气"), 81 | lambda ctx: None if ctx.Content == "天气" else FILTER_SUCCESS, 82 | # magic 83 | ratelimit(lambda _: FILTER_SUCCESS), 84 | ).receive_group_msg() 85 | 86 | 87 | @weather_handler.handle 88 | def _(): 89 | city = ctx.Content[2:] 90 | cities = search_city(city) 91 | if not cities: 92 | weather_handler.finish("没找到改地点相关信息,试试缩短关键字") 93 | 94 | if len(cities) == 1: 95 | code, name = cities[0] 96 | else: 97 | msgs = ["发送序号选择地点(30秒):"] 98 | for idx, c in enumerate(cities, start=1): 99 | msgs.append(f"【{idx}】{c[1]}") 100 | msg = "\n".join(msgs) 101 | choose = session.want("?", msg, timeout=30) 102 | if choose not in [str(idx) for idx in range(1, len(cities) + 2)]: 103 | weather_handler.finish("这都输入错误了,拜拜您嘞..") 104 | code, name = cities[int(choose) - 1] # type: ignore 105 | 106 | weather_info = search_weather(code) 107 | if not weather_info: 108 | weather_handler.finish("我也不知道为啥找不到天气信息") 109 | weather_handler.finish(f"{name}\n{weather_info}") 110 | -------------------------------------------------------------------------------- /plugins/bot_weirdfonts/__init__.py: -------------------------------------------------------------------------------- 1 | """生成花体字符 2 | 3 | 格式:花体+{可选字符串(字母或数字)} 4 | """ 5 | import json 6 | 7 | from botoy.collection import MsgTypes 8 | from botoy.contrib import get_cache_dir 9 | from botoy.decorators import ignore_botself, startswith, these_msgtypes 10 | from botoy.session import SessionHandler, ctx, session 11 | 12 | from .internal import convert, font_names, get_font, get_font_styles 13 | 14 | weirdfonts_cache = get_cache_dir("weirdfonts") / "cache.json" 15 | if not weirdfonts_cache.is_file(): 16 | with open(weirdfonts_cache, "w") as f: 17 | json.dump({}, f) 18 | 19 | weirdfonts = SessionHandler( 20 | ignore_botself, 21 | these_msgtypes(MsgTypes.TextMsg), 22 | startswith("花体"), 23 | ).receive_group_msg() 24 | 25 | 26 | # 因为这个插件发了蛮多废话,所以都自动撤回一下。需要配合autoRevoke插件 27 | def revoke(msg) -> str: 28 | return f"{msg}\n\nrevoke[30]" 29 | 30 | 31 | @weirdfonts.handle 32 | def _(): 33 | # 确定待转化字符 34 | string = ctx.Content[2:] 35 | if not string: 36 | string = session.want( 37 | "string", revoke("请输入你想要转换的字符串(2分钟内)"), pop=True, timeout=2 * 60 38 | ) 39 | if string is None: 40 | weirdfonts.finish(revoke("输入超时!已退出")) 41 | # 确定字体名和风格 42 | # 读缓存 43 | try: 44 | cache_key = str(ctx.FromUserId) 45 | with open(weirdfonts_cache) as f: 46 | cache = json.load(f) 47 | should_set = False 48 | # 无数据,首次设置 49 | # 有数据,是否需要重新设置 50 | if str(ctx.FromUserId) not in cache: 51 | cache[cache_key] = {} 52 | should_set = True 53 | else: 54 | font_name, font_style = ( 55 | cache[cache_key]["font_name"], 56 | cache[cache_key]["font_style"], 57 | ) 58 | confirm = session.want( 59 | "confirm", 60 | revoke( 61 | "你当前的花体风格为:{} {} => {}\n\n需要更换请输入【是】,其他表示仍使用当前风格(10s内)".format( 62 | font_name, 63 | font_style, 64 | convert("Hello world! 1024", font_name, font_style), 65 | ) 66 | ), 67 | pop=True, 68 | timeout=10, 69 | ) 70 | if confirm is not None and confirm == "是": 71 | should_set = True 72 | if should_set: 73 | # font name 74 | count_name = 0 75 | while True: 76 | count_name += 1 77 | if count_name > 3: 78 | weirdfonts.finish(revoke("错误太多次,不玩了!")) 79 | font_name = session.want( 80 | "name", 81 | revoke( 82 | "请选择一个字体(1分钟内回复)\n\n" 83 | + "\n\n".join( 84 | [ 85 | "{} -> {}".format( 86 | name, convert("Hello world!1024", name) 87 | ) 88 | for name in font_names 89 | ] 90 | ) 91 | ), 92 | timeout=60, 93 | pop=True, 94 | default=font_names[0], 95 | ) 96 | if font_name not in font_names: 97 | session.send_text(revoke("输入字体名称有误,请重新输入")) 98 | else: 99 | break 100 | # font style 101 | count_style = 0 102 | while True: 103 | count_style += 1 104 | if count_style > 3: 105 | weirdfonts.finish(revoke("错误太多次,不玩了!")) 106 | font_style = session.want( 107 | "style", 108 | revoke( 109 | "请选择一个风格(1分钟内回复)\n\n" 110 | + "\n\n".join( 111 | [ 112 | "{} -> {}".format( 113 | style, convert("Hello world!1024", font_name, style) 114 | ) 115 | for style in get_font_styles(font_name) 116 | ] 117 | ) 118 | ), 119 | timeout=60, 120 | pop=True, 121 | default=get_font_styles(font_name)[0], 122 | ) 123 | if font_style not in get_font_styles(font_name): 124 | session.send_text(revoke("输入风格有误,请重新输入")) 125 | else: 126 | break 127 | 128 | # 更新缓存 129 | cache[cache_key]["font_name"] = font_name 130 | cache[cache_key]["font_style"] = font_style 131 | 132 | # 结果 133 | weirdfonts.finish( 134 | "当前字体: %s-%s \n\n%s" 135 | % (font_name, font_style, convert(string, font_name, font_style)) 136 | ) 137 | 138 | finally: 139 | with open(weirdfonts_cache, "w") as f: 140 | json.dump(cache, f) 141 | -------------------------------------------------------------------------------- /plugins/bot_weirdfonts/internal.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | FONTS: dict = json.loads( 4 | '{"serif":{"regular":["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"],"bold":["𝐀","𝐁","𝐂","𝐃","𝐄","𝐅","𝐆","𝐇","𝐈","𝐉","𝐊","𝐋","𝐌","𝐍","𝐎","𝐏","𝐐","𝐑","𝐒","𝐓","𝐔","𝐕","𝐖","𝐗","𝐘","𝐙","𝐚","𝐛","𝐜","𝐝","𝐞","𝐟","𝐠","𝐡","𝐢","𝐣","𝐤","𝐥","𝐦","𝐧","𝐨","𝐩","𝐪","𝐫","𝐬","𝐭","𝐮","𝐯","𝐰","𝐱","𝐲","𝐳","𝟎","𝟏","𝟐","𝟑","𝟒","𝟓","𝟔","𝟕","𝟖","𝟗"],"italic":["𝐴","𝐵","𝐶","𝐷","𝐸","𝐹","𝐺","𝐻","𝐼","𝐽","𝐾","𝐿","𝑀","𝑁","𝑂","𝑃","𝑄","𝑅","𝑆","𝑇","𝑈","𝑉","𝑊","𝑋","𝑌","𝑍","𝑎","𝑏","𝑐","𝑑","𝑒","𝑓","𝑔","ℎ","𝑖","𝑗","𝑘","𝑙","𝑚","𝑛","𝑜","𝑝","𝑞","𝑟","𝑠","𝑡","𝑢","𝑣","𝑤","𝑥","𝑦","𝑧"],"bold-italic":["𝑨","𝑩","𝑪","𝑫","𝑬","𝑭","𝑮","𝑯","𝑰","𝑱","𝑲","𝑳","𝑴","𝑵","𝑶","𝑷","𝑸","𝑹","𝑺","𝑻","𝑼","𝑽","𝑾","𝑿","𝒀","𝒁","𝒂","𝒃","𝒄","𝒅","𝒆","𝒇","𝒈","𝒉","𝒊","𝒋","𝒌","𝒍","𝒎","𝒏","𝒐","𝒑","𝒒","𝒓","𝒔","𝒕","𝒖","𝒗","𝒘","𝒙","𝒚","𝒛"]},"sans-serif":{"regular":["𝖠","𝖡","𝖢","𝖣","𝖤","𝖥","𝖦","𝖧","𝖨","𝖩","𝖪","𝖫","𝖬","𝖭","𝖮","𝖯","𝖰","𝖱","𝖲","𝖳","𝖴","𝖵","𝖶","𝖷","𝖸","𝖹","𝖺","𝖻","𝖼","𝖽","𝖾","𝖿","𝗀","𝗁","𝗂","𝗃","𝗄","𝗅","𝗆","𝗇","𝗈","𝗉","𝗊","𝗋","𝗌","𝗍","𝗎","𝗏","𝗐","𝗑","𝗒","𝗓","𝟢","𝟣","𝟤","𝟥","𝟦","𝟧","𝟨","𝟩","𝟪","𝟫"],"bold":["𝗔","𝗕","𝗖","𝗗","𝗘","𝗙","𝗚","𝗛","𝗜","𝗝","𝗞","𝗟","𝗠","𝗡","𝗢","𝗣","𝗤","𝗥","𝗦","𝗧","𝗨","𝗩","𝗪","𝗫","𝗬","𝗭","𝗮","𝗯","𝗰","𝗱","𝗲","𝗳","𝗴","𝗵","𝗶","𝗷","𝗸","𝗹","𝗺","𝗻","𝗼","𝗽","𝗾","𝗿","𝘀","𝘁","𝘂","𝘃","𝘄","𝘅","𝘆","𝘇","𝟬","𝟭","𝟮","𝟯","𝟰","𝟱","𝟲","𝟳","𝟴","𝟵"],"italic":["𝘈","𝘉","𝘊","𝘋","𝘌","𝘍","𝘎","𝘏","𝘐","𝘑","𝘒","𝘓","𝘔","𝘕","𝘖","𝘗","𝘘","𝘙","𝘚","𝘛","𝘜","𝘝","𝘞","𝘟","𝘠","𝘡","𝘢","𝘣","𝘤","𝘥","𝘦","𝘧","𝘨","𝘩","𝘪","𝘫","𝘬","𝘭","𝘮","𝘯","𝘰","𝘱","𝘲","𝘳","𝘴","𝘵","𝘶","𝘷","𝘸","𝘹","𝘺","𝘻"],"bold-italic":["𝘼","𝘽","𝘾","𝘿","𝙀","𝙁","𝙂","𝙃","𝙄","𝙅","𝙆","𝙇","𝙈","𝙉","𝙊","𝙋","𝙌","𝙍","𝙎","𝙏","𝙐","𝙑","𝙒","𝙓","𝙔","𝙕","𝙖","𝙗","𝙘","𝙙","𝙚","𝙛","𝙜","𝙝","𝙞","𝙟","𝙠","𝙡","𝙢","𝙣","𝙤","𝙥","𝙦","𝙧","𝙨","𝙩","𝙪","𝙫","𝙬","𝙭","𝙮","𝙯"]},"script":{"regular":["𝒜","ℬ","𝒞","𝒟","ℰ","ℱ","𝒢","ℋ","ℐ","𝒥","𝒦","ℒ","ℳ","𝒩","𝒪","𝒫","𝒬","ℛ","𝒮","𝒯","𝒰","𝒱","𝒲","𝒳","𝒴","𝒵","𝒶","𝒷","𝒸","𝒹","ℯ","𝒻","ℊ","𝒽","𝒾","𝒿","𝓀","𝓁","𝓂","𝓃","ℴ","𝓅","𝓆","𝓇","𝓈","𝓉","𝓊","𝓋","𝓌","𝓍","𝓎","𝓏"],"bold":["𝓐","𝓑","𝓒","𝓓","𝓔","𝓕","𝓖","𝓗","𝓘","𝓙","𝓚","𝓛","𝓜","𝓝","𝓞","𝓟","𝓠","𝓡","𝓢","𝓣","𝓤","𝓥","𝓦","𝓧","𝓨","𝓩","𝓪","𝓫","𝓬","𝓭","𝓮","𝓯","𝓰","𝓱","𝓲","𝓳","𝓴","𝓵","𝓶","𝓷","𝓸","𝓹","𝓺","𝓻","𝓼","𝓽","𝓾","𝓿","𝔀","𝔁","𝔂","𝔃"]},"fraktur":{"regular":["𝔄","𝔅","ℭ","𝔇","𝔈","𝔉","𝔊","ℌ","ℑ","𝔍","𝔎","𝔏","𝔐","𝔑","𝔒","𝔓","𝔔","ℜ","𝔖","𝔗","𝔘","𝔙","𝔚","𝔛","𝔜","ℨ","𝔞","𝔟","𝔠","𝔡","𝔢","𝔣","𝔤","𝔥","𝔦","𝔧","𝔨","𝔩","𝔪","𝔫","𝔬","𝔭","𝔮","𝔯","𝔰","𝔱","𝔲","𝔳","𝔴","𝔵","𝔶","𝔷"],"bold":["𝕬","𝕭","𝕮","𝕯","𝕰","𝕱","𝕲","𝕳","𝕴","𝕵","𝕶","𝕷","𝕸","𝕹","𝕺","𝕻","𝕼","𝕽","𝕾","𝕿","𝖀","𝖁","𝖂","𝖃","𝖄","𝖅","𝖆","𝖇","𝖈","𝖉","𝖊","𝖋","𝖌","𝖍","𝖎","𝖏","𝖐","𝖑","𝖒","𝖓","𝖔","𝖕","𝖖","𝖗","𝖘","𝖙","𝖚","𝖛","𝖜","𝖝","𝖞","𝖟"]},"monospace":{"regular":["𝙰","𝙱","𝙲","𝙳","𝙴","𝙵","𝙶","𝙷","𝙸","𝙹","𝙺","𝙻","𝙼","𝙽","𝙾","𝙿","𝚀","𝚁","𝚂","𝚃","𝚄","𝚅","𝚆","𝚇","𝚈","𝚉","𝚊","𝚋","𝚌","𝚍","𝚎","𝚏","𝚐","𝚑","𝚒","𝚓","𝚔","𝚕","𝚖","𝚗","𝚘","𝚙","𝚚","𝚛","𝚜","𝚝","𝚞","𝚟","𝚠","𝚡","𝚢","𝚣","𝟶","𝟷","𝟸","𝟹","𝟺","𝟻","𝟼","𝟽","𝟾","𝟿"]},"double-struck":{"bold":["𝔸","𝔹","ℂ","𝔻","𝔼","𝔽","𝔾","ℍ","𝕀","𝕁","𝕂","𝕃","𝕄","ℕ","𝕆","ℙ","ℚ","ℝ","𝕊","𝕋","𝕌","𝕍","𝕎","𝕏","𝕐","ℤ","𝕒","𝕓","𝕔","𝕕","𝕖","𝕗","𝕘","𝕙","𝕚","𝕛","𝕜","𝕝","𝕞","𝕟","𝕠","𝕡","𝕢","𝕣","𝕤","𝕥","𝕦","𝕧","𝕨","𝕩","𝕪","𝕫","𝟘","𝟙","𝟚","𝟛","𝟜","𝟝","𝟞","𝟟","𝟠","𝟡"]},"circle":{"regular":["Ⓐ","Ⓑ","Ⓒ","Ⓓ","Ⓔ","Ⓕ","Ⓖ","Ⓗ","Ⓘ","Ⓙ","Ⓚ","Ⓛ","Ⓜ","Ⓝ","Ⓞ","Ⓟ","Ⓠ","Ⓡ","Ⓢ","Ⓣ","Ⓤ","Ⓥ","Ⓦ","Ⓧ","Ⓨ","Ⓩ","ⓐ","ⓑ","ⓒ","ⓓ","ⓔ","ⓕ","ⓖ","ⓗ","ⓘ","ⓙ","ⓚ","ⓛ","ⓜ","ⓝ","ⓞ","ⓟ","ⓠ","ⓡ","ⓢ","ⓣ","ⓤ","ⓥ","ⓦ","ⓧ","ⓨ","ⓩ","⓪","①","②","③","④","⑤","⑥","⑦","⑧","⑨"]},"square":{"regular":["🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉"]}}' 5 | ) 6 | 7 | font_names = list(FONTS.keys()) 8 | 9 | 10 | def get_font_styles(font_name: str) -> list: 11 | return list(FONTS[font_name].keys()) 12 | 13 | 14 | def get_font(font_name: str, font_style: str) -> list: 15 | return FONTS[font_name][font_style] 16 | 17 | 18 | def get_char_index(char: str) -> int: 19 | code = ord(char) 20 | if char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": 21 | index = code - ord("A") 22 | elif char in "abcdefghijklmnopqrstuvwxyz": 23 | index = code - ord("A") - (ord("a") - ord("Z")) + 1 24 | elif char in "0123456789": 25 | index = code - ord("0") + ord("z") - ord("A") - (ord("a") - ord("Z")) + 2 26 | else: 27 | index = 10000000 28 | return index 29 | 30 | 31 | def convert(string: str, font_name: str = None, font_style: str = None) -> str: 32 | font_name = font_name or font_names[0] 33 | font_style = font_style or get_font_styles(font_name)[0] 34 | font = get_font(font_name, font_style) 35 | chars = [] 36 | for char in string: 37 | try: 38 | chars.append(font[get_char_index(char)]) 39 | except IndexError: 40 | chars.append(char) 41 | return "".join(chars) 42 | -------------------------------------------------------------------------------- /plugins/bot_whatis.py: -------------------------------------------------------------------------------- 1 | """查询缩写意思, 格式: 查询+{缩写} 或 查询""" 2 | import httpx 3 | from botoy.collection import MsgTypes 4 | from botoy.decorators import ignore_botself, startswith, these_msgtypes 5 | from botoy.session import SessionHandler, ctx, session 6 | 7 | 8 | def whatis(text): 9 | try: 10 | resp = httpx.post( 11 | "https://lab.magiconch.com/api/nbnhhsh/guess", 12 | data={"text": text}, 13 | timeout=10, 14 | ) 15 | resp.raise_for_status() 16 | data = resp.json() 17 | except Exception: 18 | return "啊哦~API出错!" 19 | else: 20 | if not data: 21 | return "" 22 | name, trans = data[0]["name"], data[0]["trans"] 23 | trans_str = "、".join(trans) 24 | return f"【{name}】{trans_str}" 25 | 26 | 27 | whatis_handler = SessionHandler( 28 | ignore_botself, 29 | these_msgtypes(MsgTypes.TextMsg), 30 | startswith("查询"), 31 | ).receive_group_msg() 32 | 33 | 34 | @whatis_handler.handle 35 | def _(): 36 | # 如果查询指令后跟了内容就直接用这个了 37 | word: str = ctx.Content[2:] 38 | if not word: 39 | word = session.want("word", "你想要查什么呢?发送一个缩写试试~") 40 | 41 | if word is None: # 超时了,默默退出吧 42 | whatis_handler.finish() 43 | 44 | result = whatis(word) 45 | if result: 46 | whatis_handler.finish(result) 47 | -------------------------------------------------------------------------------- /plugins/bot_yesno.py: -------------------------------------------------------------------------------- 1 | """yesno 这是没啥用的功能 2 | 以yesno开头即可, 结果与具体内容无关 3 | """ 4 | import httpx 5 | from botoy import S 6 | from botoy import decorators as deco 7 | 8 | 9 | @deco.ignore_botself 10 | @deco.startswith("yesno") 11 | def receive_group_msg(_): 12 | try: 13 | data = httpx.get("https://yesno.wtf/api", timeout=20).json() 14 | except Exception: 15 | pass 16 | else: 17 | S.image(data["image"], data["answer"]) 18 | -------------------------------------------------------------------------------- /plugins/readme.md: -------------------------------------------------------------------------------- 1 | **无特殊说明的说明无需配置,如有需求请改源码** 2 | 3 | 4 | 5 | - [joinGroupAudit(进群验证码)](#joingroupaudit进群验证码) 6 | - [autoRepeat(自动消息加一)](#autorepeat自动消息加一) 7 | - [stopRevoke(群防撤回)](#stoprevoke群防撤回) 8 | - [whatis(查询缩写意思)](#whatis查询缩写意思) 9 | - [ApologizeToGirlfriend(给女朋友道歉信)](#apologizetogirlfriend给女朋友道歉信) 10 | - [cleanGroupZombie(清理僵尸)](#cleangroupzombie清理僵尸) 11 | - [5000choyen(5000 兆元字体风格图片)](#5000choyen5000-兆元字体风格图片) 12 | - [niubi(吹牛皮)](#niubi吹牛皮) 13 | - [what do you want to eat(吃啥?)](#what-do-you-want-to-eat吃啥) 14 | - [sysinfo(服务器状态信息)](#sysinfo服务器状态信息) 15 | - [phlogo(PornHub 风格的 logo)](#phlogopornhub-风格的-logo) 16 | - [autoRevoke(自动撤回)](#autorevoke自动撤回) 17 | - [toPinYin(汉字转拼音)](#topinyin汉字转拼音) 18 | - [generate Qrcode(生成二维码)](#generate-qrcode生成二维码) 19 | - [amusing language(生成和解码瞎叫字符)](#amusing-language生成和解码瞎叫字符) 20 | - [bili subscriber(订阅 B 站 UP 主视频投稿 或 订阅番剧)](#bili-subscriber订阅-b-站-up-主视频投稿-或-订阅番剧) 21 | - [weirdfonts(花体字符)](#weirdfonts花体字符) 22 | - [jikipedia(小鸡词典查梗)](#jikipedia小鸡词典查梗) 23 | - [versailles(凡尔赛语录)](#versailles凡尔赛语录) 24 | - [weather(天气)](#weather天气) 25 | - [bot_dl_douyin(抖音音视频下载)](#bot_dl_douyin抖音音视频下载) 26 | - [baidu_ocr(百度 OCR)](#baidu_ocr百度-ocr) 27 | - [meiriyiwen(每日一文)](#meiriyiwen每日一文) 28 | - [replay(复读机 Plus)](#replay复读机-plus) 29 | - [corona_virus(疫情订阅)](#corona_virus疫情订阅) 30 | - [moechat(二次元词库聊天)](#moechat二次元词库聊天) 31 | - [steam(steam 促销)](#steamsteam-促销) 32 | - [rua(做一个摸头像的 gif)](#rua做一个摸头像的-gif) 33 | - [custom image(自定义图片.jpg)](#custom-image自定义图片jpg) 34 | - [russian_turntable(俄罗斯轮盘启蒙版)](#russian_turntable俄罗斯轮盘启蒙版) 35 | - [TaoShow(买家秀)](#taoshow买家秀) 36 | - [kiss_gif(亲亲表情包)](#kiss_gif亲亲表情包) 37 | - [rip_avatar(撕开头像)](#rip_avatar撕开头像) 38 | - [juejuezi(绝绝子生成器)](#juejuezi绝绝子生成器) 39 | - [bnhhsh(不能好好说话)](#bnhhsh不能好好说话) 40 | - [moegirl(萌娘百科)](#moegirl萌娘百科) 41 | - [yesno(是或否)](#yesno是或否) 42 | - [genshin_calendar(原神活动日历)](#genshin_calendar原神活动日历) 43 | - [peep(窥屏检测)](#peep窥屏检测) 44 | - [sese(不可以色色)](#sese不可以色色) 45 | - [cockroach(来点小强)](#cockroach来点小强) 46 | - [github_thumbnail(GitHub 缩略图)](#github_thumbnailgithub-缩略图) 47 | 48 | 49 | 50 | 51 | 52 | # joinGroupAudit(进群验证码) 53 | 54 | 进群验证码 55 | 56 | # autoRepeat(自动消息加一) 57 | 58 | 自动消息加一功能,支持文字消息和图片消息 59 | 60 | # stopRevoke(群防撤回) 61 | 62 | 群防撤回,无额外依赖 63 | 64 | # whatis(查询缩写意思) 65 | 66 | 查询缩写意思 67 | 68 | 格式: 查询+{缩写} 或 查询 69 | 70 | # ApologizeToGirlfriend(给女朋友道歉信) 71 | 72 | 生成给女朋友道歉信. 73 | 74 | 格式:给女朋友道歉{名}|{事情} 或 直接发送 给女朋友道歉 (接受私聊) 75 | 76 | # cleanGroupZombie(清理僵尸) 77 | 78 | 清理长时间不发言的僵尸 79 | 80 | 管理员发送 清理僵尸 或 清理僵尸+{人数} 清理操作需要机器人是管理员 81 | 82 | # 5000choyen(5000 兆元字体风格图片) 83 | 84 | 5000 兆元字体风格图片生成 85 | 86 | 格式:5000 {上部文字} {下部文字} {下部文字向右的额外偏移量(可选)} 87 | 88 | `botoy.json`配置及默认值: 89 | 90 | - `5000choyen_api`: [图片生成服务端](https://github.com/fz6m/5000choyen-server)接口地址 91 | 92 | ```json 93 | { 94 | "5000choyen_api": "http://127.0.0.1:4000/api/v1/gen" 95 | } 96 | ``` 97 | 98 | # niubi(吹牛皮) 99 | 100 | 吹某个人的牛皮 101 | 102 | 使用: 艾特一个人并发送文字 nb 103 | 104 | # what do you want to eat(吃啥?) 105 | 106 | 吃啥?我帮你选! 107 | 108 | 格式:今天吃啥?早上吃啥?晚餐吃什么?... 109 | 110 | 使用前需先运行插件文件下载菜品数据,需要安装库`pip install aiofiles`, 111 | 如果是中途报错退出的,建议多运行几次,下载足够的菜品 112 | 113 | # sysinfo(服务器状态信息) 114 | 115 | 查看服务器当前运行信息 116 | 117 | 命令:sysinfo 118 | 119 | # phlogo(PornHub 风格的 logo) 120 | 121 | 快速生成 PornHub 风格的 logo 122 | 123 | 格式: 124 | 125 | 横向:ph Hello world 126 | 竖直:ph Hello world 1 127 | 128 | # autoRevoke(自动撤回) 129 | 130 | 自动撤回插件 131 | 132 | `botoy.json`配置项: 133 | 134 | ```json 135 | { 136 | "autorevoke_keyword": "revoke" 137 | } 138 | ``` 139 | 140 | 该插件没有命令,只要机器人发送的消息中包含关键字 KEYWORD(`autorevoke_keyword`),则撤回, 141 | 可以指定发出去多久后撤回: KEYWORD[多少秒], 如 KEYWORD[20] 142 | 表示该条消息 20s 后自动撤回 143 | 144 | # toPinYin(汉字转拼音) 145 | 146 | 汉字转拼音 147 | 148 | 格式: 拼音{汉字} 149 | 150 | # generate Qrcode(生成二维码) 151 | 152 | 生成二维码 153 | 154 | 格式:生成二维码{内容} 155 | 156 | # amusing language(生成和解码瞎叫字符) 157 | 158 | 生成和解码瞎叫语言 159 | 160 | 生成: 瞎叫+{文字内容} 161 | 162 | 解码: 瞎叫啥+{瞎叫字符串} 163 | 164 | `botoy.json`配置及默认值: 165 | 166 | 瞎叫词库 167 | 168 | ```json 169 | { 170 | "amusing_language_matrices": ["唱", "跳", "rap", "篮球", "鸡你太美"] 171 | } 172 | ``` 173 | 174 | # bili subscriber(订阅 B 站 UP 主视频投稿 或 订阅番剧) 175 | 176 | B 站视频或番剧订阅 177 | 178 | 订阅 UP 主:哔哩视频订阅+{UID:123} 或 哔哩视频订阅+{UP 名字} 179 | 退订 UP 主:哔哩视频退订+{UID} 180 | 查看已订阅 UP 主:哔哩视频列表 181 | 182 | 订阅番剧:哔哩番剧订阅+{番剧名} 183 | 退订番剧:哔哩番剧退订+{番剧 id} 184 | 查看已订阅番剧: 哔哩番剧列表 185 | 186 | # weirdfonts(花体字符) 187 | 188 | 生成花体字符 189 | 190 | 格式:花体+{可选字符串(字母或数字)} 191 | 192 | 里面使用了自动撤回,需要配合 autoRevoke 插件使用,不过没有也无妨 193 | 194 | # jikipedia(小鸡词典查梗) 195 | 196 | 比如查 cpdd: 197 | 198 | 发送 cpdd 是啥梗、cpdd 是什么梗、查 cpdd 啥梗、cpdd 啥梗 199 | 200 | # versailles(凡尔赛语录) 201 | 202 | 发送凡尔赛 203 | 204 | # weather(天气) 205 | 206 | 天气+地名 207 | 208 | # bot_dl_douyin(抖音音视频下载) 209 | 210 | 发送包含抖音视频链接的内容即可 211 | 212 | # baidu_ocr(百度 OCR) 213 | 214 | 图片提取文字 格式:ocr 加图片 215 | 216 | botoy.json 217 | 218 | ```json 219 | { 220 | "baidu_ocr_app_id": "", 221 | "baidu_ocr_api_key": "", 222 | "baidu_ocr_secret_key": "" 223 | } 224 | ``` 225 | 226 | 配置项意思如键名 227 | 228 | # meiriyiwen(每日一文) 229 | 230 | 每日一文:发送 好文 来读一篇好文章吧 231 | 232 | # replay(复读机 Plus) 233 | 234 | 发送 复读机+内容(可文字,可图片) 235 | 236 | # corona_virus(疫情订阅) 237 | 238 | 订阅疫情最新资讯,不是数值数据。。。。。。 239 | 240 | 群内发送: 疫情订阅、疫情退订 241 | 242 | # moechat(二次元词库聊天) 243 | 244 | 词库类型及来源:[Kyomotoi/AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus) 245 | 246 | 1. 指定概率随机回复 247 | 2. 发送 moechat 开始连续对话, 即概率为 100% 248 | 249 | 以上概率是指对消息的检测,具体是否回复还要看词库是否包含该词条 250 | 251 | botoy.json 252 | 253 | ```jsonc 254 | { 255 | "moechat_chance": 0.2, // 概率 256 | "moechat_groups": [] // 开启的群组 257 | } 258 | ``` 259 | 260 | # steam(steam 促销) 261 | 262 | 发送 steam 263 | 264 | # rua(做一个摸头像的 gif) 265 | 266 | 作图功能插件的脑洞就没失望过,感谢大佬的创意[rua](https://github.com/pcrbot/plugins-for-Hoshino/tree/master/shebot/rua) 267 | 268 | 使用: 269 | 270 | 艾特一个人并发送 rua 271 | 272 | # custom image(自定义图片.jpg) 273 | 274 | [制图.jpg/.png](https://github.com/opq-osc/opqqq-plugin#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%A8%E6%83%85%E5%8C%85) 275 | 276 | 1. img list => 获取图片模板列表 277 | 2. img {name} => 设置自己要用的模板 278 | 3. 发送 {任意文字}.jpg 或.png 即可 279 | 280 | botoy.json 281 | 282 | ```jsonc 283 | { 284 | "custom_image_block_group": [] // 屏蔽群列表 285 | "custom_image_enable_emoji": true // 是否开启支持emoji 286 | } 287 | ``` 288 | 289 | # russian_turntable(俄罗斯轮盘启蒙版) 290 | 291 | 发送:开始轮盘 292 | 293 | # TaoShow(买家秀) 294 | 295 | 买家秀:发送 来点买家秀或买家秀即可 296 | 297 | # kiss_gif(亲亲表情包) 298 | 299 | 原创: [SAGIRI-kawaii/sagiri-bot](https://github.com/SAGIRI-kawaii/sagiri-bot/tree/11c87b115bb1b1a79d11a8fcdbfecfdc27f2906f/statics/KissKissFrames) 300 | 301 | 制作亲亲表情包 302 | 303 | 1. AT 两个人,并发送 kiss 304 | 2. AT 一个人发送 kiss 305 | 3. At 一个人加一张图和 kiss 306 | 4. 发送两张图和 kiss 307 | 提示:将 kiss 改为 kissR 可切换亲和被亲的对象 308 | 309 | # rip_avatar(撕开头像) 310 | 311 | 修改自: [SAGIRI-kawaii/sagiri-bot](https://github.com/SAGIRI-kawaii/sagiri-bot/blob/master/statics/ripped.png) 312 | 313 | 1. AT 一人发送撕 314 | 2. 发送图片加撕 315 | 316 | # juejuezi(绝绝子生成器) 317 | 318 | 修改自:[kingcos/JueJueZiGenerator](https://github.com/kingcos/JueJueZiGenerator) 319 | 320 | 1. 绝绝子更新: 更新词库 321 | 2. 绝绝子+{内容}: 内容请使用空格间隔动词与名词,例如:喝奶茶 => 喝 奶茶 322 | 323 | # bnhhsh(不能好好说话) 324 | 325 | 实现部分来自 https://github.com/RimoChan/bnhhsh 326 | 327 | 为了即开即用,使用提前生成的数据 328 | 329 | **该插件容易导致封号** 330 | 331 | 发送 说个屁+{缩写} 如:说个屁 ynmm 332 | 333 | # moegirl(萌娘百科) 334 | 335 | 发送:萌娘百科+{关键字} 336 | 337 | # yesno(是或否) 338 | 339 | 以 yesno 开头即可, 结果与具体内容无关 340 | 341 | # genshin_calendar(原神活动日历) 342 | 343 | fork 自[NepPure/genshin_calendar](https://github.com/NepPure/genshin_calendar) 344 | 345 | 原神活动日历 346 | 原神日历 : 查看本群订阅服务器日历 347 | 原神日历 on/off : 订阅/取消订阅指定服务器的日历推送 348 | 原神日历 time 时:分 : 设置日历推送时间 349 | 原神日历 status : 查看本群日历推送设置 350 | 351 | # peep(窥屏检测) 352 | 353 | 发送 谁在窥屏+{可选检测时间} 354 | 355 | 依赖: `pip install ua-parser` 356 | 357 | botoy.json 配置 358 | 359 | ```jsonc 360 | { 361 | "ip_tracker_api": "" // 服务端地址 362 | } 363 | ``` 364 | 365 | 服务端请自行搭建,[IPTrackerServer](https://github.com/opq-osc/IPTrackerServer) 366 | 367 | 插件参考参考自:[窥屏检测.lua](https://github.com/opq-osc/lua-plugins/blob/master/%E7%AA%A5%E5%B1%8F%E6%A3%80%E6%B5%8B.lua) 368 | 369 | # sese(不可以色色) 370 | 371 | 类似那个小娃娃敲打的表情包 372 | 373 | 发送 sese + 文字 即可 374 | 375 | 需要`ffmpeg` 加入环境变量 376 | 377 | 思路来源同社区插件 [PHP sese](https://github.com/opq-osc/OPQ-PHP-plugins/tree/main/public/templates/sese) 378 | 379 | # cockroach(来点小强) 380 | 381 | 头像爬几只小强 382 | 383 | 发送 来点小强 即可,后面加数字可以指定数量 384 | 385 | 创意来自[kosakarin/hoshino_big_cockroach](https://github.com/kosakarin/hoshino_big_cockroach),逻辑按自己来的 386 | 387 | # github_thumbnail(GitHub 缩略图) 388 | 389 | 发送 github 链接即可 390 | 391 | `https://github.com/`可省略 392 | 393 | # sbqb(搜表情包) 394 | 395 | 搜表情包 396 | 397 | 发送 表情包+{关键词} 如 表情包罗翔 398 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | botoy 2 | pillow 3 | aiofiles 4 | qrcode 5 | pydantic 6 | --------------------------------------------------------------------------------