nonebot-plugin-varolant 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonebot_plugin_varolant/__init__.py +137 -0
- nonebot_plugin_varolant/config.py +82 -0
- nonebot_plugin_varolant/const.py +106 -0
- nonebot_plugin_varolant/core/database.py +586 -0
- nonebot_plugin_varolant/core/gameinfo.py +234 -0
- nonebot_plugin_varolant/core/monitor.py +146 -0
- nonebot_plugin_varolant/core/mval.py +448 -0
- nonebot_plugin_varolant/core/names.py +219 -0
- nonebot_plugin_varolant/core/qq_login.py +709 -0
- nonebot_plugin_varolant/core/render.py +1916 -0
- nonebot_plugin_varolant/core/store.py +233 -0
- nonebot_plugin_varolant/core/switch.py +73 -0
- nonebot_plugin_varolant/core/wechat_login.py +263 -0
- nonebot_plugin_varolant/core/wegame.py +451 -0
- nonebot_plugin_varolant/core/wegame_login.py +201 -0
- nonebot_plugin_varolant/fontFamily.ttf +0 -0
- nonebot_plugin_varolant/matchers/__init__.py +6 -0
- nonebot_plugin_varolant/matchers/account.py +89 -0
- nonebot_plugin_varolant/matchers/monitor.py +118 -0
- nonebot_plugin_varolant/matchers/shop.py +99 -0
- nonebot_plugin_varolant/matchers/stats.py +623 -0
- nonebot_plugin_varolant/matchers/switch.py +57 -0
- nonebot_plugin_varolant/matchers/val.py +277 -0
- nonebot_plugin_varolant/paths.py +67 -0
- nonebot_plugin_varolant-1.0.0.dist-info/METADATA +256 -0
- nonebot_plugin_varolant-1.0.0.dist-info/RECORD +29 -0
- nonebot_plugin_varolant-1.0.0.dist-info/WHEEL +4 -0
- nonebot_plugin_varolant-1.0.0.dist-info/entry_points.txt +4 -0
- nonebot_plugin_varolant-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from nonebot import get_driver, get_plugin_config, require
|
|
4
|
+
from nonebot.log import logger
|
|
5
|
+
from nonebot.plugin import PluginMetadata
|
|
6
|
+
|
|
7
|
+
from .config import Config
|
|
8
|
+
|
|
9
|
+
__plugin_meta__ = PluginMetadata(
|
|
10
|
+
name="无畏契约助手",
|
|
11
|
+
description="面向 QQ 群的国服无畏契约每日商店、皮肤监控与战绩卡片插件",
|
|
12
|
+
usage="发送「瓦 帮助」查看完整指令图",
|
|
13
|
+
type="application",
|
|
14
|
+
homepage="https://github.com/luoye520ww/nonebot-plugin-varolant",
|
|
15
|
+
config=Config,
|
|
16
|
+
supported_adapters={"~onebot.v11"},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
require("nonebot_plugin_apscheduler")
|
|
20
|
+
|
|
21
|
+
from nonebot_plugin_apscheduler import scheduler # noqa: E402
|
|
22
|
+
|
|
23
|
+
from .core import database, monitor, mval, wegame # noqa: E402
|
|
24
|
+
|
|
25
|
+
plugin_config = get_plugin_config(Config)
|
|
26
|
+
|
|
27
|
+
_MONITOR_JOB_ID = "varolant_daily_monitor"
|
|
28
|
+
_WEGAME_REFRESH_JOB_ID = "varolant_wegame_refresh"
|
|
29
|
+
_APP_REFRESH_JOB_ID = "varolant_app_refresh"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def _refresh_wegame_sessions() -> None:
|
|
33
|
+
refreshed = 0
|
|
34
|
+
for user_id, cfg in await database.list_wegame_configs(due_only=True):
|
|
35
|
+
async def save(updated: dict, uid: str = user_id) -> bool:
|
|
36
|
+
return await database.save_wegame_config(uid, updated)
|
|
37
|
+
|
|
38
|
+
client = wegame.get_client(cfg, save, user_id)
|
|
39
|
+
try:
|
|
40
|
+
if await client.refresh_ticket():
|
|
41
|
+
refreshed += 1
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.warning(f"WeGame 登录态保活失败: {type(e).__name__}: {e}")
|
|
44
|
+
finally:
|
|
45
|
+
await client.close()
|
|
46
|
+
if refreshed:
|
|
47
|
+
logger.debug(f"WeGame 登录态保活完成: {refreshed} 个账号")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def _refresh_app_sessions() -> None:
|
|
51
|
+
refreshed = 0
|
|
52
|
+
for user_id, cfg in await database.list_app_configs(due_only=True):
|
|
53
|
+
try:
|
|
54
|
+
if await mval.refresh_app_account(user_id, cfg):
|
|
55
|
+
refreshed += 1
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.warning(f"掌瓦 App 登录态保活失败: {type(e).__name__}: {e}")
|
|
58
|
+
if refreshed:
|
|
59
|
+
logger.debug(f"掌瓦 App 登录态保活完成: {refreshed} 个账号")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _setup_scheduler() -> None:
|
|
63
|
+
"""按配置把每日监控挂到 apscheduler。"""
|
|
64
|
+
hour, minute = 8, 1
|
|
65
|
+
try:
|
|
66
|
+
hour_s, minute_s = str(plugin_config.monitor_time or "08:01").split(":", 1)
|
|
67
|
+
hour, minute = int(hour_s), int(minute_s)
|
|
68
|
+
if not (0 <= hour <= 23 and 0 <= minute <= 59):
|
|
69
|
+
raise ValueError
|
|
70
|
+
except (ValueError, AttributeError):
|
|
71
|
+
logger.warning(
|
|
72
|
+
f"monitor_time 配置无效: {plugin_config.monitor_time},回退为 08:01"
|
|
73
|
+
)
|
|
74
|
+
hour, minute = 8, 1
|
|
75
|
+
plugin_config.monitor_time = "08:01"
|
|
76
|
+
|
|
77
|
+
from apscheduler.triggers.cron import CronTrigger
|
|
78
|
+
|
|
79
|
+
tz_name = plugin_config.timezone or "Asia/Shanghai"
|
|
80
|
+
try:
|
|
81
|
+
trigger = CronTrigger(hour=hour, minute=minute, timezone=tz_name)
|
|
82
|
+
except Exception:
|
|
83
|
+
logger.warning(f"timezone 配置无效: {tz_name},回退为 Asia/Shanghai")
|
|
84
|
+
tz_name = "Asia/Shanghai"
|
|
85
|
+
plugin_config.timezone = tz_name
|
|
86
|
+
trigger = CronTrigger(hour=hour, minute=minute, timezone="Asia/Shanghai")
|
|
87
|
+
|
|
88
|
+
scheduler.add_job(
|
|
89
|
+
monitor.daily_auto_check,
|
|
90
|
+
trigger=trigger,
|
|
91
|
+
args=[plugin_config],
|
|
92
|
+
id=_MONITOR_JOB_ID,
|
|
93
|
+
replace_existing=True,
|
|
94
|
+
misfire_grace_time=300,
|
|
95
|
+
)
|
|
96
|
+
scheduler.add_job(
|
|
97
|
+
_refresh_wegame_sessions,
|
|
98
|
+
"interval",
|
|
99
|
+
minutes=5,
|
|
100
|
+
id=_WEGAME_REFRESH_JOB_ID,
|
|
101
|
+
replace_existing=True,
|
|
102
|
+
coalesce=True,
|
|
103
|
+
max_instances=1,
|
|
104
|
+
)
|
|
105
|
+
scheduler.add_job(
|
|
106
|
+
_refresh_app_sessions,
|
|
107
|
+
"interval",
|
|
108
|
+
minutes=5,
|
|
109
|
+
id=_APP_REFRESH_JOB_ID,
|
|
110
|
+
replace_existing=True,
|
|
111
|
+
coalesce=True,
|
|
112
|
+
max_instances=1,
|
|
113
|
+
)
|
|
114
|
+
logger.info(f"每日商店监控任务已注册: 每天 {hour:02d}:{minute:02d} ({tz_name})")
|
|
115
|
+
|
|
116
|
+
driver = get_driver()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@driver.on_startup
|
|
120
|
+
async def _on_startup():
|
|
121
|
+
await database.init_db()
|
|
122
|
+
_setup_scheduler()
|
|
123
|
+
asyncio.create_task(_refresh_wegame_sessions())
|
|
124
|
+
asyncio.create_task(_refresh_app_sessions())
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@driver.on_shutdown
|
|
128
|
+
async def _on_shutdown():
|
|
129
|
+
if scheduler.get_job(_MONITOR_JOB_ID):
|
|
130
|
+
scheduler.remove_job(_MONITOR_JOB_ID)
|
|
131
|
+
if scheduler.get_job(_WEGAME_REFRESH_JOB_ID):
|
|
132
|
+
scheduler.remove_job(_WEGAME_REFRESH_JOB_ID)
|
|
133
|
+
if scheduler.get_job(_APP_REFRESH_JOB_ID):
|
|
134
|
+
scheduler.remove_job(_APP_REFRESH_JOB_ID)
|
|
135
|
+
await mval.close_all()
|
|
136
|
+
|
|
137
|
+
from . import matchers # noqa: E402,F401 导入即注册所有命令
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""插件配置定义。
|
|
2
|
+
|
|
3
|
+
插件已内置开箱即用的默认值,无需配置 ``.env``。如有特殊部署需求,
|
|
4
|
+
仍可使用 ``varolant_`` 前缀覆盖对应设置,例如:
|
|
5
|
+
|
|
6
|
+
.. code-block:: ini
|
|
7
|
+
|
|
8
|
+
VAROLANT_DEFAULT_LOGIN_MODE=wx
|
|
9
|
+
VAROLANT_MONITOR_TIME=08:01
|
|
10
|
+
VAROLANT_TIMEZONE=Asia/Shanghai
|
|
11
|
+
VAROLANT_BOT_ID=123456789
|
|
12
|
+
|
|
13
|
+
字段同时兼容不带 ``VAROLANT_`` 前缀的写法(``DEFAULT_LOGIN_MODE=…``)。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field, model_validator
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Config(BaseModel):
|
|
22
|
+
"""varolant 插件配置。"""
|
|
23
|
+
|
|
24
|
+
default_login_mode: str = Field(
|
|
25
|
+
default="wx",
|
|
26
|
+
description="发送「瓦app登录」不带参数时默认使用的掌瓦 App 登录方式(qq / wx)",
|
|
27
|
+
)
|
|
28
|
+
monitor_time: str = Field(
|
|
29
|
+
default="08:01",
|
|
30
|
+
description="每日商店监控的执行时间,格式 HH:MM",
|
|
31
|
+
)
|
|
32
|
+
timezone: str = Field(
|
|
33
|
+
default="Asia/Shanghai",
|
|
34
|
+
description="监控定时任务使用的时区",
|
|
35
|
+
)
|
|
36
|
+
bot_id: str = Field(
|
|
37
|
+
default="",
|
|
38
|
+
description="监控通知使用的机器人 QQ 号;留空则自动取当前在线的第一个机器人",
|
|
39
|
+
)
|
|
40
|
+
login_callback_url: str = Field(
|
|
41
|
+
default="http://connect.qq.com",
|
|
42
|
+
description="QQ 扫码登录的业务回调地址(s_url),一般无需修改",
|
|
43
|
+
)
|
|
44
|
+
login_u1_url: str = Field(
|
|
45
|
+
default="http://connect.qq.com",
|
|
46
|
+
description="QQ 扫码登录轮询的 u1 地址,一般无需修改",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# NoneBot 只会从环境中收集模型真实声明的字段。把带前缀字段显式声明出来,
|
|
50
|
+
# 再在校验后覆盖兼容裸名;仅用 validation_alias 会导致 VAROLANT_* 被静默忽略。
|
|
51
|
+
varolant_default_login_mode: Optional[str] = Field(default=None, exclude=True)
|
|
52
|
+
varolant_monitor_time: Optional[str] = Field(default=None, exclude=True)
|
|
53
|
+
varolant_timezone: Optional[str] = Field(default=None, exclude=True)
|
|
54
|
+
varolant_bot_id: Optional[str] = Field(default=None, exclude=True)
|
|
55
|
+
varolant_login_callback_url: Optional[str] = Field(default=None, exclude=True)
|
|
56
|
+
varolant_login_u1_url: Optional[str] = Field(default=None, exclude=True)
|
|
57
|
+
|
|
58
|
+
@model_validator(mode="after")
|
|
59
|
+
def _apply_prefixed_values(self) -> "Config":
|
|
60
|
+
"""带 ``VAROLANT_`` 前缀的配置优先于兼容裸名。"""
|
|
61
|
+
for name in (
|
|
62
|
+
"default_login_mode",
|
|
63
|
+
"monitor_time",
|
|
64
|
+
"timezone",
|
|
65
|
+
"bot_id",
|
|
66
|
+
"login_callback_url",
|
|
67
|
+
"login_u1_url",
|
|
68
|
+
):
|
|
69
|
+
value = getattr(self, f"varolant_{name}")
|
|
70
|
+
if value is not None:
|
|
71
|
+
setattr(self, name, value)
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def normalize_login_mode(mode: str) -> str:
|
|
76
|
+
"""把用户输入的登录方式归一化成 ``qq`` / ``wx``,无法识别时返回空串。"""
|
|
77
|
+
value = str(mode or "").strip().lower()
|
|
78
|
+
if value in {"qq", "q"}:
|
|
79
|
+
return "qq"
|
|
80
|
+
if value in {"wx", "w", "wechat", "weixin", "微信"}:
|
|
81
|
+
return "wx"
|
|
82
|
+
return ""
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""插件用到的固定常量。
|
|
2
|
+
|
|
3
|
+
这里的 URL、appid、UA 等均来自掌上无畏契约 App 与 QQ / 微信
|
|
4
|
+
开放平台的公开登录链路,属于协议字段,不建议随意改动。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
# 插件自带字体(用于绘制每日商店 / 帮助图片)
|
|
10
|
+
FONT_PATH = Path(__file__).parent / "fontFamily.ttf"
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# QQ 扫码登录(ptlogin2 协议)
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
# xlogin 页面地址,末尾的 time / h5sig 等参数仅影响页面内部行为,
|
|
17
|
+
# 扫码链路主要依赖其中的 s_url / client_id / sign 等字段。
|
|
18
|
+
LOGIN_URL_TEMPLATE = (
|
|
19
|
+
"https://xui.ptlogin2.qq.com/cgi-bin/xlogin?pt_enable_pwd=1&appid=716027609"
|
|
20
|
+
"&pt_3rd_aid=102061775&daid=381&pt_skey_valid=0&style=35&force_qr=1"
|
|
21
|
+
"&autorefresh=1&s_url=http%3A%2F%2Fconnect.qq.com&refer_cgi=m_authorize"
|
|
22
|
+
"&ucheck=1&fall_to_wv=1&status_os=12&redirect_uri=auth%3A%2F%2Ftauth.qq.com%2F"
|
|
23
|
+
"&client_id=102061775&pf=openmobile_android&response_type=token&scope=all"
|
|
24
|
+
"&sdkp=a&sdkv=3.5.17.lite&sign=a6479455d3e49b597350f13f776a6288"
|
|
25
|
+
"&status_machine=MjMxMTdSSzY2Qw%3D%3D&switch=1&time=1763280194"
|
|
26
|
+
"&show_download_ui=true&h5sig=trobryxo8IPM0GaSQH12mowKG-CY65brFzkK7_-9EW4&loginty=6"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
PTQR_SHOW_URL = "https://xui.ptlogin2.qq.com/ssl/ptqrshow"
|
|
30
|
+
PTQR_LOGIN_URL = "https://xui.ptlogin2.qq.com/ssl/ptqrlogin"
|
|
31
|
+
OPENMOBILE_REDIRECT_URL = "https://openmobile.qq.com/oauth2.0/m_get_redirect_url"
|
|
32
|
+
|
|
33
|
+
PTQR_AID = "716027609"
|
|
34
|
+
PTQR_DAID = "381"
|
|
35
|
+
PTQR_THIRD_AID = "102061775"
|
|
36
|
+
|
|
37
|
+
DEFAULT_LOGIN_CALLBACK_URL = "http://connect.qq.com"
|
|
38
|
+
DEFAULT_LOGIN_U1_URL = "http://connect.qq.com"
|
|
39
|
+
|
|
40
|
+
# 掌上无畏契约登录接口
|
|
41
|
+
QQ_LOGIN_BY_QQ_URL = (
|
|
42
|
+
"https://app.mval.qq.com/go/auth/login_by_qq"
|
|
43
|
+
"?source_game_zone=agame&game_zone=agame"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
QQ_HEADERS_TEMPLATE = {
|
|
47
|
+
"Cookie": "clientType=9; openid=null; access_token=null;",
|
|
48
|
+
"User-Agent": (
|
|
49
|
+
"mval/2.4.0.10053 Channel/10068 Manufacturer/Redmi "
|
|
50
|
+
"Mozilla/5.0 (Linux; Android 12; 23117RK66C Build/V417IR; wv) "
|
|
51
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
|
|
52
|
+
"Chrome/101.0.4951.61 Mobile Safari/537.36"
|
|
53
|
+
),
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
"Host": "app.mval.qq.com",
|
|
56
|
+
"Connection": "Keep-Alive",
|
|
57
|
+
"Accept-Encoding": "gzip",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# 二维码扫描阶段使用的模拟器 UA
|
|
61
|
+
EMULATOR_UA = (
|
|
62
|
+
"Mozilla/5.0 (Linux; Android 12; 23117RK66C Build/V417IR; wv) "
|
|
63
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
|
|
64
|
+
"Chrome/101.0.4951.61 Mobile Safari/537.36 tencent_game_emulator"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# 微信扫码登录
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
WECHAT_QRCONNECT_URL = "https://open.weixin.qq.com/connect/sdk/qrconnect"
|
|
72
|
+
WECHAT_LONG_POLL_URL = "https://long.open.weixin.qq.com/connect/l/qrconnect"
|
|
73
|
+
WECHAT_APP_ID = "wxcbb49f1f39656c2a" # 掌上无畏契约 appid
|
|
74
|
+
|
|
75
|
+
WECHAT_TICKET_URL = "https://app.mval.qq.com/go/auth/get_sdk_ticket"
|
|
76
|
+
WECHAT_LOGIN_URL = "https://app.mval.qq.com/go/auth/login_by_wechat"
|
|
77
|
+
|
|
78
|
+
WECHAT_TICKET_UA = (
|
|
79
|
+
"mval/2.10062 Channel/3 Manufacturer/Redmi "
|
|
80
|
+
"Mozilla/5.0 (Linux; Android 12; 22041216C Build/V417IR; wv) "
|
|
81
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
|
|
82
|
+
"Chrome/110.0.5481.154 Mobile Safari/537.36"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
WECHAT_POLL_UA = (
|
|
86
|
+
"mval/2.10053 Channel/10068 Manufacturer/Redmi "
|
|
87
|
+
"Mozilla/5.0 (Linux; Android 12; 23117RK66C Build/V417IR; wv) "
|
|
88
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
|
|
89
|
+
"Chrome/101.0.4951.61 Mobile Safari/537.36"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# 每日商店接口
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
STORE_API_URL = "https://app.mval.qq.com/go/mlol_store/agame/user_store"
|
|
97
|
+
|
|
98
|
+
STORE_API_UA = (
|
|
99
|
+
"mval/2.3.0.10050 Channel/5 Manufacturer/Xiaomi "
|
|
100
|
+
"Mozilla/5.0 (Linux; Android 14; 23078RKD5C Build/UP1A.230905.011; wv) "
|
|
101
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
|
|
102
|
+
"Chrome/140.0.7339.207 Mobile Safari/537.36"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# 登录态失效的错误码
|
|
106
|
+
AUTH_INVALID_CODES = {1001, 1003, 999999}
|