├── utils ├── __init__.py ├── file │ ├── __init_.py │ ├── files.py │ └── fileManage.py ├── gTime.py ├── myLog.py ├── apiHandler.py └── botVip.py ├── config ├── log.exp │ ├── VipUser.json │ └── AfdWebhook.json └── config.exp.json ├── requirements.txt ├── img ├── 643fce843df92.png ├── 643fceb15227a.png ├── 643fcec9808af.png ├── 643fcf10cb956.png ├── 643fd191e4690.png ├── 643fd1a45f307.png ├── 643fd1afc1a49.png ├── 643fd23c6dff9.png ├── 643fd345357d4.png ├── 643fd4979854c.png ├── 643fd4a6a408b.png ├── 643fd53a4804f.png ├── 643fd60fbabe7.png ├── 643fd65f46258.png ├── 643fd6ef0ec7e.png ├── 643fd7012ccc0.png ├── 643fd71a104c1.png ├── 643ffb22e93a1.png └── 643ffb303e7c4.png ├── .gitignore ├── start.py ├── LICENSE ├── api.py ├── main.py └── README.md /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/file/__init_.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/log.exp/VipUser.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{} 3 | } -------------------------------------------------------------------------------- /config/log.exp/AfdWebhook.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{}, 3 | "user":{} 4 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==23.1.0 2 | aiohttp==3.8.1 3 | khl.py==0.3.7 4 | -------------------------------------------------------------------------------- /img/643fce843df92.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fce843df92.png -------------------------------------------------------------------------------- /img/643fceb15227a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fceb15227a.png -------------------------------------------------------------------------------- /img/643fcec9808af.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fcec9808af.png -------------------------------------------------------------------------------- /img/643fcf10cb956.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fcf10cb956.png -------------------------------------------------------------------------------- /img/643fd191e4690.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd191e4690.png -------------------------------------------------------------------------------- /img/643fd1a45f307.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd1a45f307.png -------------------------------------------------------------------------------- /img/643fd1afc1a49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd1afc1a49.png -------------------------------------------------------------------------------- /img/643fd23c6dff9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd23c6dff9.png -------------------------------------------------------------------------------- /img/643fd345357d4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd345357d4.png -------------------------------------------------------------------------------- /img/643fd4979854c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd4979854c.png -------------------------------------------------------------------------------- /img/643fd4a6a408b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd4a6a408b.png -------------------------------------------------------------------------------- /img/643fd53a4804f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd53a4804f.png -------------------------------------------------------------------------------- /img/643fd60fbabe7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd60fbabe7.png -------------------------------------------------------------------------------- /img/643fd65f46258.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd65f46258.png -------------------------------------------------------------------------------- /img/643fd6ef0ec7e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd6ef0ec7e.png -------------------------------------------------------------------------------- /img/643fd7012ccc0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd7012ccc0.png -------------------------------------------------------------------------------- /img/643fd71a104c1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643fd71a104c1.png -------------------------------------------------------------------------------- /img/643ffb22e93a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643ffb22e93a1.png -------------------------------------------------------------------------------- /img/643ffb303e7c4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musnows/Kook-Afd-Webhook-Bot/HEAD/img/643ffb303e7c4.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # config file 2 | config.json 3 | 4 | # log file 5 | log/ 6 | *.log 7 | 8 | # python file 9 | __pycache__/ 10 | 11 | # idea 12 | .idea/ 13 | .vscode/ -------------------------------------------------------------------------------- /utils/gTime.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime,timedelta,timezone 2 | 3 | def getTime(format_str='%y-%m-%d %H:%M:%S'): 4 | """获取当前时间,默认格式为 `23-01-01 00:00:00`""" 5 | utc_dt = datetime.now(timezone.utc) # 获取当前时间 6 | bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8))) # 转换为北京时间 7 | return bj_dt.strftime(format_str) 8 | -------------------------------------------------------------------------------- /config/config.exp.json: -------------------------------------------------------------------------------- 1 | { 2 | "bot": { 3 | "token": "bot webhook/websocket token", 4 | "verify_token": "bot webhook verify token", 5 | "encrypt": "bot webhook encrypt token", 6 | "webhook_port": 40000, 7 | "ws": false, 8 | "info": "set 'ws' to false if using webhook" 9 | }, 10 | "channel": { 11 | "debug_ch": "channel id for sending debug msg" 12 | } 13 | } -------------------------------------------------------------------------------- /start.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from aiohttp import web 3 | from main import bot,_log 4 | from api import app 5 | 6 | # 屏蔽报错 7 | # ignore warning 'DeprecationWarning: There is no current event loop' 8 | import warnings 9 | warnings.filterwarnings("ignore", category=DeprecationWarning) 10 | 11 | if __name__ == '__main__': 12 | HOST,PORT = '0.0.0.0',14726 13 | _log.info(f"[START] service start at {HOST}:{PORT}") 14 | asyncio.get_event_loop().run_until_complete( 15 | asyncio.gather(web._run_app(app, host=HOST, port=PORT), bot.start())) 16 | -------------------------------------------------------------------------------- /utils/file/files.py: -------------------------------------------------------------------------------- 1 | from .fileManage import FileManage 2 | from ..myLog import _log 3 | 4 | # 配置相关 5 | config = FileManage("./config/config.json", True) 6 | """机器人配置文件""" 7 | AfdWebhook = FileManage("./log/AfdWebhook.json") 8 | """爱发电的wh请求""" 9 | 10 | # vip相关 11 | VipUser = FileManage("./log/VipUser.json") 12 | """vip 用户列表/抽奖记录""" 13 | VipUserDict = VipUser['data'] 14 | """vip 用户列表""" 15 | 16 | # 实例化一个khl的bot,方便其他模组调用 17 | from khl import Bot,Cert 18 | bot = Bot(token=config['bot']['token']) # websocket 19 | """main bot""" 20 | if not config['bot']['ws']: # webhook 21 | bot = Bot(cert=Cert(token=config['bot']['token'], 22 | verify_token=config['bot']['verify_token'], 23 | encrypt_key=config['bot']['encrypt']), 24 | port=config['bot']['webhook_port']) # webhook 25 | """main bot""" 26 | _log.info(f"Loading all files") # 走到这里代表所有文件都打开了 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 musnow 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 | -------------------------------------------------------------------------------- /utils/myLog.py: -------------------------------------------------------------------------------- 1 | import logging # 采用logging来替换所有print 2 | LOGGER_NAME = "botlog" 3 | LOGGER_FILE = "bot.log" 4 | 5 | # 只打印info以上的日志(debug低于info) 6 | logging.basicConfig(level=logging.INFO, 7 | format="[%(asctime)s] %(levelname)s:%(filename)s:%(funcName)s:%(lineno)d | %(message)s", 8 | datefmt="%y-%m-%d %H:%M:%S") 9 | # 获取一个logger对象 10 | _log = logging.getLogger(LOGGER_NAME) 11 | # 实例化控制台handler和文件handler,同时输出到控制台和文件 12 | # cmd_handler = logging.StreamHandler() # 默认设置里面,就会往控制台打印信息;自己又加一个,导致打印俩次 13 | file_handler = logging.FileHandler(LOGGER_FILE, mode="a", encoding="utf-8") 14 | fmt = logging.Formatter(fmt="[%(asctime)s] %(levelname)s:%(filename)s:%(funcName)s:%(lineno)d | %(message)s", 15 | datefmt="%y-%m-%d %H:%M:%S") 16 | file_handler.setFormatter(fmt) 17 | _log.addHandler(file_handler) 18 | 19 | 20 | # 在控制台打印msg内容,用作日志 21 | from khl import Message,PrivateMessage 22 | def logMsg(msg: Message) -> None: 23 | try: 24 | # 系统消息id,直接退出,不记录 25 | if msg.author_id == "3900775823":return 26 | # 私聊用户没有频道和服务器id 27 | if isinstance(msg, PrivateMessage): 28 | _log.info( 29 | f"PrivateMsg | Au:{msg.author_id} {msg.author.username}#{msg.author.identify_num} | {msg.content}") 30 | else: 31 | _log.info( 32 | f"G:{msg.ctx.guild.id} | C:{msg.ctx.channel.id} | Au:{msg.author_id} {msg.author.username}#{msg.author.identify_num} = {msg.content}" 33 | ) 34 | except: 35 | _log.exception("Exception occurred") -------------------------------------------------------------------------------- /api.py: -------------------------------------------------------------------------------- 1 | import json 2 | from aiohttp import web 3 | 4 | from utils.gTime import getTime 5 | from utils.myLog import _log 6 | from utils.apiHandler import afd_request 7 | 8 | # 初始化节点 9 | routes = web.RouteTableDef() 10 | 11 | 12 | # 基础返回 13 | @routes.get('/') 14 | async def hello_world(request): # put application's code here 15 | _log.info(f"request | root-url") 16 | return web.Response(body=json.dumps( 17 | { 18 | 'code': 0, 19 | 'message': f'Hello! Get recv at {getTime()}' 20 | }, 21 | indent=2, 22 | sort_keys=True, 23 | ensure_ascii=False), 24 | status=200, 25 | content_type='application/json') 26 | 27 | 28 | # 爱发电的wh 29 | @routes.post('/afd') 30 | async def aifadian_webhook(request): 31 | _log.info(f"request | /afd") 32 | try: 33 | ret = await afd_request(request) 34 | return web.Response(body=json.dumps(ret, indent=2, sort_keys=True, ensure_ascii=False), 35 | content_type='application/json') 36 | except: 37 | _log.exception("Exception in /afd") 38 | return web.Response(body=json.dumps({ 39 | "ec": 0, 40 | "em": "err ouccer" 41 | }, indent=2, sort_keys=True, ensure_ascii=False), 42 | status=503, 43 | content_type='application/json') 44 | 45 | 46 | 47 | 48 | app = web.Application() 49 | app.add_routes(routes) 50 | if __name__ == '__main__': 51 | try: # host需要设置成0.0.0.0,否则只有本地才能访问 52 | HOST,PORT = '0.0.0.0',14726 53 | _log.info(f"API Service Start at {HOST}:{PORT}") 54 | web.run_app(app, host=HOST, port=PORT) 55 | except: 56 | _log.exception("Exception occur") -------------------------------------------------------------------------------- /utils/apiHandler.py: -------------------------------------------------------------------------------- 1 | import json 2 | from khl.card import CardMessage,Card,Module,Element,Types 3 | 4 | from .myLog import _log 5 | from .file.files import AfdWebhook,config,bot 6 | from .botVip import add_vip 7 | 8 | def get_order_id_dict(custom_order_id:str)->dict: 9 | """解析custom_order_id""" 10 | index = custom_order_id.find(':') 11 | user_id = custom_order_id[:index] 12 | day = custom_order_id[index+1:] 13 | day = int(day) 14 | return {"uid":user_id,"day":day} 15 | 16 | # 测试一下这个函数 17 | # print(get_order_id_dict("1342354:30")) 18 | 19 | async def afd_request(request): 20 | """爱发电webhook处理函数""" 21 | # 获取参数信息 22 | body = await request.content.read() 23 | params = json.loads(body.decode('UTF8')) 24 | # 插入到日志中 25 | global AfdWebhook 26 | if "data" not in AfdWebhook: 27 | AfdWebhook["data"] = [] 28 | AfdWebhook['data'].append(params) 29 | # 构造text 30 | text = "" 31 | if 'plan_title' in params['data']['order']: 32 | text = f"商品 {params['data']['order']['plan_title']}\n" 33 | user_id = params['data']['order']['user_id'] # afd用户id 34 | user_id = user_id[0:6] 35 | text += f"用户 {user_id}\n" 36 | for i in params['data']['order']['sku_detail']: 37 | text += f"发电了{i['count']}个 {i['name']}\n" 38 | text += f"共计 {params['data']['order']['total_amount']} 猿\n" 39 | # 处理自定义订单编号 40 | if 'custom_order_id' in params['data']['order']: 41 | text += f"自定义订单ID {params['data']['order']['custom_order_id']}" 42 | # kook用户id:vip天数 43 | order_id = params['data']['order']['custom_order_id'] 44 | order_id = get_order_id_dict(order_id) 45 | _log.info(f"[afd] {str(order_id)}") 46 | if 'user' not in AfdWebhook: 47 | AfdWebhook['user'] = {} 48 | if order_id['uid'] not in AfdWebhook['user']: 49 | AfdWebhook['user'][order_id['uid']] = {} 50 | # 配置信息 51 | AfdWebhook['user'][order_id['uid']][params['data']['order']['out_trade_no']] = { 52 | "days":order_id['day'], 53 | "plan_id":params['data']['order']['plan_id'], 54 | "plan_title":params['data']['order']['plan_title'], 55 | "amount":params['data']['order']['total_amount'] 56 | } 57 | AfdWebhook.save() 58 | # 添加vip用户 59 | await add_vip(order_id['uid'],order_id['day']) 60 | else: 61 | _log.warning(f"[afd] no custom_order_id in afd webhook") 62 | 63 | # 将订单编号中间部分改为# 64 | trno = params['data']['order']['out_trade_no'] 65 | trno_f = trno[0:8] 66 | trno_b = trno[-4:] 67 | trno_f += "####" 68 | trno_f += trno_b 69 | # 构造卡片 70 | cm = CardMessage() 71 | c = Card(Module.Header(f"爱发电有新动态啦!"), Module.Context(Element.Text(f"订单号: {trno_f}")), Module.Divider(), 72 | Module.Section(Element.Text(text, Types.Text.KMD))) 73 | cm.append(c) 74 | _log.debug(json.dumps(cm)) 75 | # 发送到指定频道 76 | debug_ch = await bot.client.fetch_public_channel(config['channel']['debug_ch']) 77 | await bot.client.send(debug_ch, cm) 78 | _log.info(f"trno:{params['data']['order']['out_trade_no']} | afd-cm-send") 79 | # 返回状态码 80 | return {"ec": 200, "em": "success"} 81 | -------------------------------------------------------------------------------- /utils/file/fileManage.py: -------------------------------------------------------------------------------- 1 | import json 2 | import aiofiles 3 | from ..myLog import _log 4 | 5 | FileList = [] 6 | """files need to write into storage""" 7 | 8 | def open_file(path): 9 | with open(path, 'r', encoding='utf-8') as f: 10 | tmp = json.load(f) 11 | return tmp 12 | 13 | 14 | async def write_file_aio(path: str, value): 15 | async with aiofiles.open(path, 'w', encoding='utf-8') as f: 16 | await f.write(json.dumps(value, indent=2, sort_keys=True, ensure_ascii=False)) 17 | 18 | 19 | def write_file(path: str, value): 20 | with open(path, 'w', encoding='utf-8') as fw2: 21 | json.dump(value, fw2, indent=2, sort_keys=True, ensure_ascii=False) 22 | 23 | 24 | async def save_all_file(is_Aio=True): 25 | """save all file in FileList 26 | """ 27 | for i in FileList: 28 | try: 29 | if is_Aio: 30 | await i.save_aio() 31 | else: 32 | i.save() 33 | except: 34 | _log.exception(f"Save.All.File | {i.path}") 35 | 36 | _log.info(f"Save.All.File | save finished") 37 | 38 | 39 | # 文件管理类 40 | class FileManage: 41 | # 初始化构造 42 | def __init__(self, path: str, read_only: bool = False) -> None: 43 | with open(path, 'r', encoding='utf-8') as f: 44 | tmp = json.load(f) 45 | self.value = tmp # 值 46 | self.type = type(tmp) # 值的类型 47 | self.path = path # 值的文件路径 48 | self.Ronly = read_only # 是否只读 49 | #将自己存全局变量里面 50 | if not read_only: 51 | global FileList # 如果不是只读,那就存list里面 52 | FileList.append(self) 53 | 54 | # []操作符重载 55 | def __getitem__(self, index): 56 | return self.value[index] 57 | 58 | # 打印重载 59 | def __str__(self) -> str: 60 | return str(self.value) 61 | 62 | # 删除成员 63 | def __delitem__(self, index): 64 | del self.value[index] 65 | 66 | # 长度 67 | def __len__(self): 68 | return len(self.value) 69 | 70 | # 索引赋值 x[i] = 1 71 | def __setitem__(self, index, value): 72 | self.value[index] = value 73 | 74 | # 迭代 75 | def __iter__(self): 76 | return self.value.__iter__() 77 | 78 | def __next__(self): 79 | return self.value.__next__() 80 | 81 | # 比较== 82 | def __eq__(self, i): 83 | if isinstance(i, FileManage): 84 | return self.value.__eq__(i.value) 85 | else: 86 | return self.value.__eq__(i) 87 | 88 | # 比较!= 89 | def __ne__(self, i): 90 | if isinstance(i, FileManage): 91 | return self.value.__ne__(i.value) 92 | else: 93 | return self.value.__ne__(i) 94 | 95 | # 获取成员 96 | def get_instance(self): 97 | return self.value 98 | 99 | # 遍历dict 100 | def items(self): 101 | return self.value.items() 102 | 103 | # 追加 104 | def append(self, i): 105 | self.value.append(i) 106 | 107 | # list的删除 108 | def remove(self, i): 109 | self.value.remove(i) 110 | 111 | def keys(self): 112 | return self.value.keys() 113 | 114 | # 保存 115 | def save(self): 116 | with open(self.path, 'w', encoding='utf-8') as fw: 117 | json.dump(self.value, fw, indent=2, sort_keys=True, ensure_ascii=False) 118 | 119 | # 异步保存 120 | async def save_aio(self): 121 | async with aiofiles.open(self.path, 'w', encoding='utf-8') as f: #这里必须用dumps 122 | await f.write(json.dumps(self.value, indent=2, sort_keys=True, ensure_ascii=False)) -------------------------------------------------------------------------------- /utils/botVip.py: -------------------------------------------------------------------------------- 1 | import time 2 | import copy 3 | from typing import Union 4 | from datetime import datetime,timedelta,timezone 5 | from khl import Bot,Message 6 | from khl.card import CardMessage,Card,Module,Element,Types 7 | 8 | from .file.files import bot,VipUserDict,VipUser,_log 9 | 10 | DAY_TIMES = 86400 11 | """24H in secons""" 12 | 13 | def vip_time_remain(user_id)->float: 14 | """ 15 | get time remain of vip, return in seconds 16 | """ 17 | # 时间差值 18 | timeout = VipUserDict[user_id]['time'] - time.time() 19 | return timeout 20 | 21 | async def vip_time_remain_cm(times): 22 | """获取vip时间剩余卡片消息""" 23 | cm = CardMessage() 24 | c1 = Card(color='#e17f89') 25 | c1.append(Module.Section(Element.Text('您的「vip会员」还剩', Types.Text.KMD))) 26 | c1.append(Module.Divider()) 27 | c1.append(Module.Countdown(datetime.now() + timedelta(seconds=times), mode=Types.CountdownMode.DAY)) 28 | cm.append(c1) 29 | return cm 30 | 31 | def get_none_vip_cm(): 32 | """card msg info user not vip""" 33 | c = Card(color='#e17f89') 34 | c.append(Module.Section(Element.Text("您并非vip用户", Types.Text.KMD))) 35 | cm = CardMessage(c) 36 | return cm 37 | 38 | # 检查用户vip是否失效或者不是vip 39 | async def vip_ck(msg:Union[Message,str])->bool: 40 | """ 41 | params can be: 42 | * `msg:Message` check & inform user if they aren't vip 43 | * `author_id:str` will not send reply, just check_if_vip 44 | 45 | retuns: 46 | * True: is vip 47 | * False: not vip 48 | """ 49 | # 判断是否为msg对象并获取用户id 50 | is_msg = isinstance(msg, Message) 51 | user_id = msg if not is_msg else msg.author_id 52 | cm = CardMessage() if not is_msg else get_none_vip_cm() 53 | 54 | # 检查 55 | if user_id in VipUserDict: 56 | # 用户的vip是否过期? 57 | if time.time() > VipUserDict[user_id]['time']: 58 | del VipUserDict[user_id] 59 | # 如果是消息,那就发送提示 60 | if is_msg: 61 | await msg.reply(cm) 62 | _log.info(f"[vip-ck] Au:{user_id} msg.reply | vip out of date") 63 | return False 64 | else: #没有过期,返回真 65 | _log.info(f"[vip-ck] Au:{user_id} is vip") 66 | return True 67 | # 用户不是vip 68 | else: 69 | if is_msg: #如果是消息,那就发送提示 70 | await msg.reply(cm) 71 | _log.info(f"[vip-ck] Au:{user_id} msg.reply | not vip") 72 | return False 73 | 74 | 75 | async def add_vip(user_id:str,day:int,user_name=""): 76 | """ 77 | - 提供用户id和天数,新增vip用户 78 | - 如果用户已存在,则添加vip时长 79 | """ 80 | global VipUserDict 81 | if user_id not in VipUserDict: 82 | VipUserDict[user_id] ={"name":user_name,"time":time.time()} 83 | if not user_name: 84 | user = await bot.client.fetch_user(user_id) 85 | VipUserDict[user_id]["name"] = f"{user.username}#{user.identify_num}" 86 | # time字段是初始化vip时间+剩余时间,也就是vip时间截止的时间戳 87 | VipUserDict[user_id]["time"] += day * DAY_TIMES 88 | _log.info(f"[vip-add] Au:{user_id} | day:{day} | time:{VipUserDict[user_id]['time']}") 89 | 90 | 91 | async def fetch_vip_user(): 92 | """获取当前vip用户列表(这会剔除过期vip)""" 93 | global VipUserDict,VipUser 94 | vipuserdict_temp = copy.deepcopy(VipUserDict) 95 | text = "" 96 | for u, ifo in vipuserdict_temp.items(): 97 | if await vip_ck(u): # vip-ck会主动修改dict 98 | time = vip_time_remain(u) 99 | time = format(time / DAY_TIMES, '.2f') 100 | # 通过/86400计算出大概的天数 101 | text += f"{u}_{ifo['name']}\t = {time}\n" 102 | 103 | if vipuserdict_temp != VipUserDict: 104 | #将修改存放到文件中 105 | VipUser.save() 106 | _log.info(f"[vip-r] update VipUserDict") 107 | 108 | return text -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import traceback 4 | import aiohttp 5 | 6 | from khl import Bot,Message,Channel 7 | from khl.card import CardMessage,Card,Module,Element,Types 8 | 9 | from utils.gTime import getTime 10 | from utils.file.files import bot,AfdWebhook,VipUserDict,config 11 | from utils.file.fileManage import save_all_file 12 | from utils.myLog import _log,logMsg 13 | from utils import botVip 14 | 15 | debug_ch:Channel 16 | """日志频道""" 17 | kook_base_url = "https://www.kookapp.cn" 18 | kook_headers = {f'Authorization': f"Bot {config['bot']['token']}"} 19 | 20 | async def bot_offline(): 21 | """下线机器人""" 22 | url = kook_base_url + "/api/v3/user/offline" 23 | async with aiohttp.ClientSession() as session: 24 | async with session.post(url, headers=kook_headers) as response: 25 | res = json.loads(await response.text()) 26 | _log.debug(res) 27 | return res 28 | 29 | 30 | # 每5分钟保存一次文件 31 | @bot.task.add_interval(minutes=5) 32 | async def save_file_task(): 33 | try: 34 | await save_all_file() 35 | except: 36 | err_cur = f"ERR! [{getTime()}] [Save.File.Task]\n```\n{traceback.format_exc()}\n```" 37 | _log.exception("ERR in Save_File_Task") 38 | await bot.client.send(debug_ch, err_cur) # type: ignore 39 | 40 | # 看看机器人活着不 41 | @bot.command(name='alive',case_sensitive=False) 42 | async def alive_cmd(msg:Message,*arg): 43 | logMsg(msg) 44 | await msg.reply(f"bot alive here") 45 | 46 | # 获取vip剩余时间 47 | @bot.command(name='vip',case_sensitive=False) 48 | async def vip_cmd(msg:Message,*arg): 49 | logMsg(msg) 50 | try: 51 | if not await botVip.vip_ck(msg): 52 | return 53 | time_remain = botVip.vip_time_remain(msg.author_id) 54 | cm = await botVip.vip_time_remain_cm(time_remain) 55 | await msg.reply(cm) 56 | except: 57 | _log.exception(f"Err in vip") 58 | 59 | # 获取vip列表 60 | @bot.command(name='vip-l',case_sensitive=False) 61 | async def vip_list_cmd(msg:Message,*arg): 62 | logMsg(msg) 63 | try: 64 | text = await botVip.fetch_vip_user() 65 | if not text: 66 | text="当前没有vip用户" 67 | cm = CardMessage(Card(Module.Section(Element.Text(text,Types.Text.KMD)))) 68 | await msg.reply(cm) 69 | except: 70 | _log.exception(f"Err in vip-l") 71 | 72 | # 测试你是否为vip 73 | @bot.command(name='vip-test',case_sensitive=False) 74 | async def vip_test_cmd(msg:Message,*arg): 75 | logMsg(msg) 76 | try: 77 | if await botVip.vip_ck(msg): 78 | cm = CardMessage(Card(Module.Section(Element.Text("您是vip!",Types.Text.KMD)))) 79 | await msg.reply(cm) 80 | except: 81 | _log.exception(f"Err in vip-test") 82 | 83 | # 商店 84 | @bot.command(name='shop',case_sensitive=False) 85 | async def shop_cmd(msg:Message,*arg): 86 | logMsg(msg) 87 | try: 88 | cm = CardMessage() 89 | c =Card(Module.Section(Element.Text("欢迎选购机器人Vip",Types.Text.KMD))) 90 | # ------------- 91 | # vip商品1,周vip 92 | vip_item_link1 = "https://afdian.net/order/create?product_type=1&plan_id=9aea871c304911ed8ec452540025c377&sku=%5B%7B%22sku_id%22%3A%229aed6edc304911edbeb552540025c377%22,%22count%22%3A1%7D%5D" 93 | # 添加上自定义订单号的字符串 94 | vip_item_link1+= f"&custom_order_id={msg.author_id}:7" 95 | c.append( 96 | Module.Section( 97 | Element.Text("周vip", Types.Text.KMD), 98 | Element.Button("购买", vip_item_link1, Types.Click.LINK))) 99 | # ------------- 100 | 101 | # vip商品2,月vip 102 | vip_item_link2 = "https://afdian.net/order/create?product_type=1&plan_id=ff2949022e9611ed89d452540025c377&sku=%5B%7B%22sku_id%22%3A%22ff2bb4f82e9611ed83ac52540025c377%22,%22count%22%3A1%7D%5D" 103 | # 添加上自定义订单号的字符串 104 | vip_item_link2+= f"&custom_order_id={msg.author_id}:30" 105 | c.append( 106 | Module.Section( 107 | Element.Text("月vip", Types.Text.KMD), 108 | Element.Button("购买", vip_item_link2, Types.Click.LINK))) 109 | 110 | cm.append(c) 111 | await msg.reply(cm,is_temp=True) # 临时消息,所以这个按钮只有当前用户可以点 112 | except: 113 | _log.exception(f"Err in shop") 114 | 115 | 116 | @bot.command(name='kill') 117 | async def KillBot(msg: Message,at_text="", *arg): 118 | logMsg(msg) 119 | try: 120 | if not at_text: 121 | return await msg.reply(f"必须at机器人才能下线 `/kill @机器人`") 122 | 123 | # 保存所有文件 124 | await save_all_file(False) 125 | await msg.reply(f"[KILL] 保存全局变量成功,bot下线") 126 | res = "webhook" 127 | if config['bot']['ws']: 128 | res = await bot_offline() # 调用接口下线bot 129 | _log.info(f"KILL | bot-off: {res}\n") 130 | os._exit(0) # 退出程序 131 | except: 132 | _log.exception(f"Err in kill") 133 | 134 | 135 | @bot.on_startup 136 | async def startup_task(bot:Bot): 137 | """启动任务""" 138 | try: 139 | global debug_ch 140 | debug_ch = await bot.client.fetch_public_channel(config['channel']['debug_ch']) 141 | _log.info(f"[BOT.START] fetch channel success") 142 | except: 143 | _log.exception(f"[BOT.START] Err") 144 | os.abort() 145 | 146 | if __name__ == '__main__': 147 | _log.info(f"[BOT] start at {getTime()}") 148 | bot.run() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |