From db5123f259fcc066a9db7d8c2e67284b491e31df Mon Sep 17 00:00:00 2001 From: Eigeen Date: Tue, 7 Mar 2023 22:35:25 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=E6=9F=A5=E7=94=B5=E8=B4=B9=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E5=8A=9F=E8=83=BD=E5=AE=8C=E6=88=90=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=AE=80=E5=8D=95=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __init__.py | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++ config.py | 6 ++ data.py | 185 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 __init__.py create mode 100644 config.py create mode 100644 data.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..2cc9e53 --- /dev/null +++ b/__init__.py @@ -0,0 +1,201 @@ +from nonebot_plugin_apscheduler import scheduler +import nonebot +from nonebot import on_command +from nonebot.rule import Rule +from nonebot.adapters.onebot.v11.adapter import Message, MessageSegment +from nonebot.adapters.onebot.v11 import Message, MessageSegment, Bot, MessageEvent +from nonebot.matcher import Matcher +from nonebot.params import CommandArg +import aiohttp +import json +import re +import asyncio +from pathlib import Path + +from .config import Config +from .data import load_binding_data, BUILDING_TAB, HELP, get_bid_by_bname + + +electricity_check = on_command( + 'eleccheck', rule=Rule(), aliases={'查电费'}, priority=5) + +elec_check_config = Config.parse_obj(nonebot.get_driver().config.dict()) + +data_path = Path().absolute() / "data" / "eleccheck" + +REQ_DATA = {"query_elec_roominfo": {"aid": "0030000000002501", "account": "251277", "room": {"roomid": "", "room": ""}, "floor": { + "floorid": "", "floor": ""}, "area": {"area": "云塘校区", "areaname": "云塘校区"}, "building": {"buildingid": "451", "building": "17栋"}}} +API = 'http://yktwd.csust.edu.cn:8988/web/Common/Tsm.html' +HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50', + 'Referer': 'http://yktwd.csust.edu.cn:8988/web/common/checkEle.html?ticket=2E94FD6F19FB40558D53CC55AE4421E4&from=ehall&cometype=', + 'Content-Type': 'application/x-www-form-urlencoded' +} +RESULT_PATTERN = r'房间剩余电量([\d\.]+)' +re_result = re.compile(RESULT_PATTERN) + +bindings = load_binding_data(str(data_path / 'bindings.json')) + + +async def reload_bindings(): + global bindings + bindings = load_binding_data(str(data_path / 'bindings.json')) + + +async def batch_check(): + """批量检测低电量房间并推送到群聊 + """ + bot = nonebot.get_bot() + if not bindings: + return + + for binding_info in bindings: + group = binding_info['group'] + rooms = binding_info['rooms'] + default_building = binding_info['building'] + result = [] + for r in rooms: + rid = r['roomid'] + bname = r.get('building', default_building) + bid = get_bid_by_bname(bname) + try: + remain = await get_electricity(building_id=bid, building_name=bname, roomid=rid) + except Exception as e: + print('获取信息出错:' + str(e)) + result.append({'roomid': rid, 'remain': remain, 'building': bname}) + await asyncio.sleep(0.5) + + msg = ['电量不足预警:\n'] + for res in result: + elec = float(res['remain']) + if elec <= 50: + msg.append('\n{}{}剩余电量:{}'.format( + res['building'], res['roomid'], res['remain'])) + if len(msg) > 1: + await bot.send_group_msg(group_id=group, message=Message(msg)) + +if bindings: + scheduler.add_job( + batch_check, + 'cron', + hour=15, + minute=0, + second=0, + id='batch_elec_check_and_push' + ) + + +async def get_electricity(building_id: str, building_name: str, roomid: str) -> str: + """通过API获取电量信息 + + Args: + building_id (str): + building_name (str): + roomid (str): + + Raises: + IOError: + RuntimeError: + + Returns: + str: + """ + # 组织参数 + jsondata = REQ_DATA.copy() + jsondata['query_elec_roominfo']['room']['roomid'] = roomid + jsondata['query_elec_roominfo']['room']['room'] = roomid + jsondata['query_elec_roominfo']['building']['buildingid'] = building_id + jsondata['query_elec_roominfo']['building']['building'] = building_name + payload = { + "jsondata": json.dumps(jsondata), + "funname": "synjones.onecard.query.elec.roominfo", + "json": True + } + + # 查询请求 + async with aiohttp.ClientSession() as session: + async with session.post(url=API, data=payload, headers=HEADERS) as res: + if res.status != 200: + raise IOError('接口调用失败: Code ' + res.status) + res_raw = await res.text() + + try: + res_json = json.loads(res_raw) + except json.decoder.JSONDecodeError: + raise IOError('接口调用失败: ' + res_raw) + + if res_json['query_elec_roominfo']['retcode'] != '0': + raise RuntimeError( + '获取失败: ' + res_json['query_elec_roominfo']['errmsg']) + + msg = res_json['query_elec_roominfo']['errmsg'].strip() + # 检查返回结果是否合法,并抽取电量数值 + match = re_result.match(msg) + if not match: + raise RuntimeError( + '获取失败: ' + res_json['query_elec_roominfo']['errmsg']) + + elec_quantity = re_result.findall(msg)[0] + return elec_quantity + + +async def building_list() -> list[str]: + """获取所有楼栋名列表 + + Returns: + list[str]: 楼栋名列表 + """ + buildings = [] + for b in BUILDING_TAB: + buildings.append(b['building']) + return buildings + + +@electricity_check.handle() +async def elec_check_handler(matcher: Matcher, event: MessageEvent, arg: Message = CommandArg()): + if event.message_type != 'group': + await matcher.finish() + + group = event.group_id + # 白名单检查 + if str(group) not in elec_check_config.elec_check_enable: + await matcher.finish() + + # 参数检查 + args = arg.extract_plain_text() + if len(args) == 0: + await matcher.finish(HELP) + args = args.rsplit() + + if args[0] == '楼栋列表': + buildings = await building_list() + msg = '支持的所有楼栋名:\n' + '\n'.join(buildings) + await matcher.finish(msg) + if args[0] == '自动预警配置查询': + await matcher.finish('该功能尚未开放') + if args[0] == '重载': + # TODO: 管理员权限检测 + await reload_bindings() + await matcher.finish('插件配置已重载') + if args[0] == '推送': + # TODO: 管理员权限检测 + await batch_check() + await matcher.finish('已推送所有预警') + + if len(args) < 2: + await matcher.finish(HELP) + + building_name = args[0] + roomid = args[1] + building_id = get_bid_by_bname(building_name) + if not building_id: + await matcher.finish('参数错误:楼栋名不存在') + + try: + electricity = await get_electricity(building_id=building_id, + building_name=building_name, + roomid=roomid) + except Exception as e: + await matcher.finish(str(e)) + + await matcher.finish('{}{}剩余电量:{}'.format(building_name, roomid, electricity)) diff --git a/config.py b/config.py new file mode 100644 index 0000000..35a6525 --- /dev/null +++ b/config.py @@ -0,0 +1,6 @@ +from typing import List +from pydantic import BaseModel, Extra + + +class Config(BaseModel, extra=Extra.ignore): + elec_check_enable: List = [] \ No newline at end of file diff --git a/data.py b/data.py new file mode 100644 index 0000000..2316388 --- /dev/null +++ b/data.py @@ -0,0 +1,185 @@ +import json + +BUILDING_TAB = [{ + "buildingid": "471", + "building": "16栋A区" +}, + { + "buildingid": "472", + "building": "16栋B区" +}, + { + "buildingid": "451", + "building": "17栋" +}, + { + "buildingid": "141", + "building": "弘毅轩1栋A区" +}, + { + "buildingid": "148", + "building": "弘毅轩1栋B区" +}, + { + "buildingid": "197", + "building": "弘毅轩2栋A区1-6楼" +}, + { + "buildingid": "201", + "building": "弘毅轩2栋B区" +}, + { + "buildingid": "205", + "building": "弘毅轩2栋C区" +}, + { + "buildingid": "206", + "building": "弘毅轩2栋D区" +}, + { + "buildingid": "155", + "building": "弘毅轩3栋A区" +}, + { + "buildingid": "183", + "building": "弘毅轩3栋B区" +}, + { + "buildingid": "162", + "building": "弘毅轩4栋A区" +}, + { + "buildingid": "169", + "building": "弘毅轩4栋B区" +}, + { + "buildingid": "450", + "building": "留学生公寓" +}, + { + "buildingid": "176", + "building": "敏行轩1栋A区" +}, + { + "buildingid": "184", + "building": "敏行轩1栋B区" +}, + { + "buildingid": "513", + "building": "敏行轩2栋A区" +}, + { + "buildingid": "520", + "building": "敏行轩2栋B区" +}, + { + "buildingid": "85", + "building": "行健轩1栋A区" +}, + { + "buildingid": "92", + "building": "行健轩1栋B区" +}, + { + "buildingid": "99", + "building": "行健轩2栋A区" +}, + { + "buildingid": "106", + "building": "行健轩2栋B区" +}, + { + "buildingid": "113", + "building": "行健轩3栋A区" +}, + { + "buildingid": "120", + "building": "行健轩3栋B区" +}, + { + "buildingid": "127", + "building": "行健轩4栋A区" +}, + { + "buildingid": "134", + "building": "行健轩4栋B区" +}, + { + "buildingid": "57", + "building": "行健轩5栋A区" +}, + { + "buildingid": "64", + "building": "行健轩5栋B区" +}, + { + "buildingid": "71", + "building": "行健轩6栋A区" +}, + { + "buildingid": "78", + "building": "行健轩6栋B区" +}, + { + "buildingid": "1", + "building": "至诚轩1栋A区" +}, + { + "buildingid": "8", + "building": "至诚轩1栋B区" +}, + { + "buildingid": "15", + "building": "至诚轩2栋A区" +}, + { + "buildingid": "22", + "building": "至诚轩2栋B区" +}, + { + "buildingid": "29", + "building": "至诚轩3栋A区" +}, + { + "buildingid": "36", + "building": "至诚轩3栋B区" +}, + { + "buildingid": "43", + "building": "至诚轩4栋A区" +}, + { + "buildingid": "50", + "building": "至诚轩4栋B区" +}] + +building_names = dict() + +for b in BUILDING_TAB: + building_names[b['building']] = b['buildingid'] + +HELP = """电费查询&预警插件 +可以手动查询电费,低电量余额时自动提醒(需要管理员设置) + +用法: + /查电费 <命令/楼栋名> [宿舍号] + +例: + /查电费 17栋 406 + /查电费 16栋A区 A201 + +可用命令: + 楼栋列表 - 列出所有可使用的楼栋名 + 自动预警配置查询 - N/A + 重载 - 重载插件配置文件(管理员) + 推送 - 手动推送所有预警信息(管理员)""" + +def load_binding_data(data_path: str) -> list[dict[str, str]]: + try: + with open(data_path, 'r', encoding='utf-8') as fp: + return json.load(fp) + except Exception: + return None + +def get_bid_by_bname(name: str) -> str: + return building_names.get(name, '') \ No newline at end of file