@sophiacyl/dsh-plugin-waves 0.0.0-stage → 0.1.0-beta.1
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.
- package/DSH-GO-VERIFICATION.md +57 -0
- package/ICON.png +0 -0
- package/LICENSE +674 -0
- package/README.md +251 -2
- package/cordis.patch.yml +8 -0
- package/install.ps1 +0 -0
- package/install.sh +209 -0
- package/lib/client.js +1511 -0
- package/lib/host/aliashint.js +70 -0
- package/lib/host/attachments.js +25 -0
- package/lib/host/command.js +34 -0
- package/lib/host/consts.js +16 -0
- package/lib/host/dataguide.js +35 -0
- package/lib/host/imagebridge.js +41 -0
- package/lib/host/native-result.js +91 -0
- package/lib/host/native.js +191 -0
- package/lib/host/proxy.js +59 -0
- package/lib/host/results.js +67 -0
- package/lib/host/service.js +263 -0
- package/lib/host/tool.js +185 -0
- package/lib/host/troubleshoot.js +31 -0
- package/lib/host/worldbook.js +80 -0
- package/lib/index.js +74 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +136 -4
- package/service/ICON.png +0 -0
- package/service/ai/AI_DATA.md +221 -0
- package/service/knowledge/additional/manual.jsonl +5 -0
- package/service/knowledge/wiki/baike.jsonl +1231 -0
- package/service/knowledge/wiki/chengjiu.jsonl +36 -0
- package/service/knowledge/wiki/gonglve.jsonl +383 -0
- package/service/knowledge/wiki/huodong.jsonl +427 -0
- package/service/knowledge/wiki/renwu.jsonl +412 -0
- package/service/knowledge/wiki/tujian.jsonl +2889 -0
- package/service/knowledge/wiki/zixun.jsonl +47 -0
- package/service/plugins/__init__.py +0 -0
- package/service/pyproject.toml +40 -0
- package/service/rover/__init__.py +0 -0
- package/service/rover/ai_core/__init__.py +0 -0
- package/service/rover/ai_core/models.py +50 -0
- package/service/rover/ai_core/rag/__init__.py +5 -0
- package/service/rover/ai_core/rag/knowledge.py +23 -0
- package/service/rover/ai_core/register.py +86 -0
- package/service/rover/ai_core/resource.py +6 -0
- package/service/rover/ai_core/skills/__init__.py +0 -0
- package/service/rover/ai_core/skills/operations.py +8 -0
- package/service/rover/api.py +720 -0
- package/service/rover/api_render.py +161 -0
- package/service/rover/app_life.py +154 -0
- package/service/rover/aps.py +47 -0
- package/service/rover/avatar.py +85 -0
- package/service/rover/bot.py +133 -0
- package/service/rover/builtin.py +152 -0
- package/service/rover/cards/__init__.py +26 -0
- package/service/rover/cards/_shared.py +129 -0
- package/service/rover/cards/char_list.py +258 -0
- package/service/rover/cards/char_panel.py +1353 -0
- package/service/rover/cards/echo_list.py +206 -0
- package/service/rover/cards/matrix_roles.py +25 -0
- package/service/rover/cards/rank_boards.py +714 -0
- package/service/rover/cards/rank_total.py +664 -0
- package/service/rover/compat.py +215 -0
- package/service/rover/config.py +163 -0
- package/service/rover/custom_panel.py +220 -0
- package/service/rover/data_store.py +31 -0
- package/service/rover/gss.py +19 -0
- package/service/rover/handler.py +191 -0
- package/service/rover/help/__init__.py +0 -0
- package/service/rover/help/draw_new_plugin_help.py +332 -0
- package/service/rover/help/model.py +16 -0
- package/service/rover/help/utils.py +7 -0
- package/service/rover/hooks.py +420 -0
- package/service/rover/html_actions.py +236 -0
- package/service/rover/i18n.py +19 -0
- package/service/rover/knowledge.py +235 -0
- package/service/rover/logger.py +102 -0
- package/service/rover/main.py +33 -0
- package/service/rover/media.py +222 -0
- package/service/rover/message_models.py +23 -0
- package/service/rover/models.py +92 -0
- package/service/rover/native_media.py +64 -0
- package/service/rover/ocr.py +79 -0
- package/service/rover/pool.py +43 -0
- package/service/rover/restart.py +36 -0
- package/service/rover/segment.py +143 -0
- package/service/rover/server.py +131 -0
- package/service/rover/status/__init__.py +0 -0
- package/service/rover/status/models.py +10 -0
- package/service/rover/status/plugin_status.py +17 -0
- package/service/rover/subscribe.py +148 -0
- package/service/rover/sv.py +403 -0
- package/service/rover/templates/char_list.html +182 -0
- package/service/rover/templates/char_panel.html +465 -0
- package/service/rover/templates/echo_list.html +221 -0
- package/service/rover/templates/rank_all_total.html +193 -0
- package/service/rover/templates/rank_board.html +107 -0
- package/service/rover/templates/rank_level_total.html +57 -0
- package/service/rover/templates/rank_matrix_total.html +66 -0
- package/service/rover/templates/rank_phantom_total.html +180 -0
- package/service/rover/templates/rank_slash_total.html +38 -0
- package/service/rover/trigger.py +98 -0
- package/service/rover/utils/__init__.py +0 -0
- package/service/rover/utils/boardcast/__init__.py +0 -0
- package/service/rover/utils/boardcast/models.py +15 -0
- package/service/rover/utils/cookie_manager/__init__.py +0 -0
- package/service/rover/utils/cookie_manager/qrlogin.py +24 -0
- package/service/rover/utils/database/__init__.py +0 -0
- package/service/rover/utils/database/base_models.py +1771 -0
- package/service/rover/utils/database/models.py +80 -0
- package/service/rover/utils/database/startup.py +60 -0
- package/service/rover/utils/default_bg/bg.jpg +0 -0
- package/service/rover/utils/download_resource/__init__.py +0 -0
- package/service/rover/utils/download_resource/download_core.py +164 -0
- package/service/rover/utils/download_resource/download_file.py +46 -0
- package/service/rover/utils/fonts/__init__.py +0 -0
- package/service/rover/utils/fonts/fonts.py +33 -0
- package/service/rover/utils/image/__init__.py +0 -0
- package/service/rover/utils/image/convert.py +51 -0
- package/service/rover/utils/image/image_tools.py +30 -0
- package/service/rover/utils/image/utils.py +70 -0
- package/service/rover/utils/plugins_config/__init__.py +0 -0
- package/service/rover/utils/plugins_config/gs_config.py +506 -0
- package/service/rover/utils/plugins_config/models.py +182 -0
- package/service/rover/web_app.py +3 -0
- package/service/rover/webconsole/__init__.py +0 -0
- package/service/rover/webconsole/mount_app.py +26 -0
- package/service/tools/migrate.py +351 -0
- package/service/tools/regression.py +87 -0
- package/service/tools/smoke.py +63 -0
- package/service/tools/update_plugins.py +212 -0
- package/service/tools/verify_actions.py +104 -0
- package/service/tools/verify_card.py +82 -0
- package/service/tools/wwdata.py +665 -0
- package/tests/compat.test.js +181 -0
- package/tests/imagebridge.test.js +58 -0
- package/tests/media.test.js +68 -0
- package/tests/native.test.js +127 -0
- package/tests/runtime.test.js +69 -0
- package/tests/service.test.js +48 -0
|
@@ -0,0 +1,1771 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import asyncio
|
|
3
|
+
import sqlite3
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Dict,
|
|
7
|
+
List,
|
|
8
|
+
Type,
|
|
9
|
+
TypeVar,
|
|
10
|
+
Callable,
|
|
11
|
+
Optional,
|
|
12
|
+
Sequence,
|
|
13
|
+
Awaitable,
|
|
14
|
+
)
|
|
15
|
+
from functools import wraps
|
|
16
|
+
from typing_extensions import ParamSpec, Concatenate
|
|
17
|
+
|
|
18
|
+
from sqlmodel import Field, SQLModel, col, and_, delete, select, update
|
|
19
|
+
from sqlalchemy import exc, text, event, create_engine
|
|
20
|
+
from sqlalchemy.exc import OperationalError
|
|
21
|
+
from sqlalchemy.pool import NullPool
|
|
22
|
+
from sqlalchemy.engine import Engine
|
|
23
|
+
from sqlalchemy.ext.asyncio import (
|
|
24
|
+
AsyncSession,
|
|
25
|
+
async_sessionmaker, # type: ignore
|
|
26
|
+
create_async_engine,
|
|
27
|
+
)
|
|
28
|
+
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
29
|
+
from sqlalchemy.sql.expression import func, null, true
|
|
30
|
+
|
|
31
|
+
from rover.logger import logger
|
|
32
|
+
from rover.data_store import get_res_path
|
|
33
|
+
|
|
34
|
+
T_BaseModel = TypeVar("T_BaseModel", bound="BaseModel")
|
|
35
|
+
T_BaseIDModel = TypeVar("T_BaseIDModel", bound="BaseIDModel")
|
|
36
|
+
T_User = TypeVar("T_User", bound="User")
|
|
37
|
+
T_Bind = TypeVar("T_Bind", bound="Bind")
|
|
38
|
+
T_Push = TypeVar("T_Push", bound="Push")
|
|
39
|
+
T_Cache = TypeVar("T_Cache", bound="Cache")
|
|
40
|
+
T = TypeVar("T")
|
|
41
|
+
P = ParamSpec("P")
|
|
42
|
+
R = TypeVar("R")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
DB_CONFIG_DEFAULT: Dict[str, Any] = {
|
|
46
|
+
"db_type": "SQLite",
|
|
47
|
+
"db_driver": "aiomysql",
|
|
48
|
+
"db_custom_url": "",
|
|
49
|
+
"db_host": "localhost",
|
|
50
|
+
"db_port": "3306",
|
|
51
|
+
"db_user": "root",
|
|
52
|
+
"db_password": "root",
|
|
53
|
+
"db_name": "WavesData",
|
|
54
|
+
"db_pool_size": 5,
|
|
55
|
+
"db_echo": False,
|
|
56
|
+
"db_pool_recycle": 1500,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _load_db_config() -> Dict[str, Any]:
|
|
61
|
+
"""读取 configs/database_config.json, 缺失项回落默认值。"""
|
|
62
|
+
conf = dict(DB_CONFIG_DEFAULT)
|
|
63
|
+
path = get_res_path("configs") / "database_config.json"
|
|
64
|
+
if not path.exists():
|
|
65
|
+
return conf
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.warning(f"[数据库] 配置读取失败, 使用默认配置: {e}")
|
|
71
|
+
return conf
|
|
72
|
+
|
|
73
|
+
if not isinstance(raw, dict):
|
|
74
|
+
return conf
|
|
75
|
+
|
|
76
|
+
for key in DB_CONFIG_DEFAULT:
|
|
77
|
+
if key not in raw:
|
|
78
|
+
continue
|
|
79
|
+
value = raw[key]
|
|
80
|
+
# 配置文件既可能是裸值, 也可能是 {"data": 值} 的配置项结构
|
|
81
|
+
if isinstance(value, dict):
|
|
82
|
+
value = value.get("data", conf[key])
|
|
83
|
+
if value is not None:
|
|
84
|
+
conf[key] = value
|
|
85
|
+
return conf
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_db_config_data = _load_db_config()
|
|
89
|
+
|
|
90
|
+
db_host: str = _db_config_data["db_host"]
|
|
91
|
+
db_port: str = _db_config_data["db_port"]
|
|
92
|
+
db_user: str = _db_config_data["db_user"]
|
|
93
|
+
db_password: str = _db_config_data["db_password"]
|
|
94
|
+
db_name: str = _db_config_data["db_name"]
|
|
95
|
+
|
|
96
|
+
db_pool_size: Optional[int] = _db_config_data["db_pool_size"]
|
|
97
|
+
db_echo: bool = _db_config_data["db_echo"]
|
|
98
|
+
db_pool_recycle: int = _db_config_data["db_pool_recycle"]
|
|
99
|
+
|
|
100
|
+
db_custom_url = _db_config_data["db_custom_url"]
|
|
101
|
+
db_type: str = _db_config_data["db_type"]
|
|
102
|
+
|
|
103
|
+
db_driver: str = _db_config_data["db_driver"]
|
|
104
|
+
|
|
105
|
+
_db_type = db_type.lower()
|
|
106
|
+
db_config = {
|
|
107
|
+
"pool_recycle": db_pool_recycle,
|
|
108
|
+
"echo": db_echo,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
DB_PATH = get_res_path() / "WavesData.db"
|
|
112
|
+
|
|
113
|
+
sync_url, engine, finally_url = "", "", ""
|
|
114
|
+
async_maker: async_sessionmaker[AsyncSession] = None # type: ignore
|
|
115
|
+
server_engine = None
|
|
116
|
+
_db_init_lock = asyncio.Lock()
|
|
117
|
+
_db_initialized = False
|
|
118
|
+
sqlite_semaphore = None
|
|
119
|
+
|
|
120
|
+
if _db_type == "sqlite":
|
|
121
|
+
sync_url = "sqlite:///"
|
|
122
|
+
base_url = "sqlite+aiosqlite:///"
|
|
123
|
+
db_url = str(DB_PATH)
|
|
124
|
+
# del db_config['pool_size']
|
|
125
|
+
elif _db_type == "mysql":
|
|
126
|
+
sync_url = "mysql+pymysql://"
|
|
127
|
+
base_url = f"mysql+{db_driver}://"
|
|
128
|
+
db_hp = f"{db_host}:{db_port}" if db_port else db_host
|
|
129
|
+
db_url = f"{db_user}:{db_password}@{db_hp}/"
|
|
130
|
+
elif _db_type == "postgresql":
|
|
131
|
+
sync_url = "postgresql+psycopg://"
|
|
132
|
+
base_url = "postgresql+asyncpg://"
|
|
133
|
+
db_hp = f"{db_host}:{db_port}" if db_port else db_host
|
|
134
|
+
db_url = f"{db_user}:{db_password}@{db_hp}/"
|
|
135
|
+
elif _db_type == "自定义":
|
|
136
|
+
base_url = ""
|
|
137
|
+
db_url = db_custom_url
|
|
138
|
+
else:
|
|
139
|
+
base_url = db_type
|
|
140
|
+
db_url = db_custom_url
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async def init_database():
|
|
144
|
+
global _db_initialized, engine, finally_url, async_maker, sqlite_semaphore
|
|
145
|
+
|
|
146
|
+
if _db_initialized:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
async with _db_init_lock:
|
|
150
|
+
if _db_initialized:
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
logger.info("[数据库] 开始初始化...")
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
if _db_type == "sqlite":
|
|
157
|
+
# sqlite 显式 NullPool, 并发上限由下方 semaphore 控制
|
|
158
|
+
db_config.update(
|
|
159
|
+
{
|
|
160
|
+
"connect_args": {"check_same_thread": False},
|
|
161
|
+
"poolclass": NullPool,
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
engine = create_async_engine(f"{base_url}{db_url}", **db_config)
|
|
165
|
+
finally_url = f"{base_url}{db_url}"
|
|
166
|
+
|
|
167
|
+
@event.listens_for(engine.sync_engine, "connect")
|
|
168
|
+
def set_sqlite_pragma(dbapi_connection: sqlite3.Connection, connection_record):
|
|
169
|
+
cursor = dbapi_connection.cursor()
|
|
170
|
+
cursor.execute("PRAGMA journal_mode=WAL")
|
|
171
|
+
cursor.execute("PRAGMA synchronous=NORMAL")
|
|
172
|
+
cursor.execute("PRAGMA busy_timeout=15000")
|
|
173
|
+
cursor.close()
|
|
174
|
+
|
|
175
|
+
# NullPool 下信号量是唯一并发闸门
|
|
176
|
+
sqlite_semaphore = asyncio.Semaphore(8)
|
|
177
|
+
else:
|
|
178
|
+
db_config.update(
|
|
179
|
+
{
|
|
180
|
+
"pool_size": db_pool_size,
|
|
181
|
+
"max_overflow": 10,
|
|
182
|
+
"pool_timeout": 30,
|
|
183
|
+
"isolation_level": "AUTOCOMMIT",
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
server_engine: Engine | None = None
|
|
187
|
+
try:
|
|
188
|
+
if _db_type == "mysql":
|
|
189
|
+
server_engine = create_engine(f"{sync_url}{db_url}", **db_config)
|
|
190
|
+
|
|
191
|
+
with server_engine.connect() as conn:
|
|
192
|
+
t1 = f"CREATE DATABASE IF NOT EXISTS {db_name} "
|
|
193
|
+
t2 = "CHARACTER SET utf8mb4 COLLATE "
|
|
194
|
+
t3 = "utf8mb4_unicode_ci"
|
|
195
|
+
conn.execute(text(t1 + t2 + t3))
|
|
196
|
+
logger.success(f"[MySQL] 数据库 {db_name} 创建成功或已存在!")
|
|
197
|
+
elif _db_type == "postgresql":
|
|
198
|
+
server_engine = create_engine(f"{sync_url}{db_url}", **db_config)
|
|
199
|
+
try:
|
|
200
|
+
with server_engine.connect() as conn:
|
|
201
|
+
t = f"CREATE DATABASE {db_name} WITH ENCODING "
|
|
202
|
+
t2 = "'UTF8' LC_COLLATE 'en_US.UTF-8' LC_CTYPE "
|
|
203
|
+
t3 = "'en_US.UTF-8' TEMPLATE template0"
|
|
204
|
+
conn.execute(text(t + t2 + t3))
|
|
205
|
+
except exc.ProgrammingError as e:
|
|
206
|
+
if "already exists" in str(e) or "已经存在" in str(e):
|
|
207
|
+
pass
|
|
208
|
+
else:
|
|
209
|
+
raise
|
|
210
|
+
logger.success(f"[PostgreSQL] 数据库 {db_name} 创建成功或已存在!")
|
|
211
|
+
finally:
|
|
212
|
+
if server_engine:
|
|
213
|
+
server_engine.dispose()
|
|
214
|
+
logger.info("[数据库] 临时数据库连接已释放!")
|
|
215
|
+
|
|
216
|
+
# db_config['poolclass'] = NullPool
|
|
217
|
+
finally_url = f"{base_url}{db_url}{db_name}"
|
|
218
|
+
engine = create_async_engine(finally_url, **db_config)
|
|
219
|
+
|
|
220
|
+
async_maker = async_sessionmaker(
|
|
221
|
+
engine,
|
|
222
|
+
expire_on_commit=False,
|
|
223
|
+
close_resets_only=False,
|
|
224
|
+
class_=AsyncSession,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
_db_initialized = True
|
|
228
|
+
except Exception as e: # noqa: E722
|
|
229
|
+
logger.exception(f"[数据库] 连接失败: {e}")
|
|
230
|
+
raise ValueError(f"[数据库] [{base_url}] 连接失败, 请检查配置文件!")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _is_pool_timeout(err: BaseException) -> bool:
|
|
234
|
+
"""连接池耗尽 / checkout 超时:重试只会让等待雪崩,必须立即失败。"""
|
|
235
|
+
if isinstance(err, (TimeoutError, asyncio.TimeoutError)):
|
|
236
|
+
return True
|
|
237
|
+
# 兜底按文案识别 QueuePool 报错
|
|
238
|
+
msg = str(err)
|
|
239
|
+
return "QueuePool limit" in msg or "connection timed out" in msg
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _is_transient_db_error(err: BaseException) -> bool:
|
|
243
|
+
"""仅对 SQLite 锁竞争 / 瞬时 I/O 做有限重试。"""
|
|
244
|
+
if not isinstance(err, OperationalError):
|
|
245
|
+
return False
|
|
246
|
+
msg = str(err).lower()
|
|
247
|
+
if "unable to open database file" in msg:
|
|
248
|
+
return False
|
|
249
|
+
return "database is locked" in msg or "database table is locked" in msg or "busy" in msg or "disk i/o error" in msg
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def with_session(
|
|
253
|
+
func: Callable[Concatenate[Any, AsyncSession, P], Awaitable[R]],
|
|
254
|
+
) -> Callable[Concatenate[Any, P], Awaitable[R]]:
|
|
255
|
+
@wraps(func)
|
|
256
|
+
async def wrapper(self, *args: P.args, **kwargs: P.kwargs):
|
|
257
|
+
max_retries = 3
|
|
258
|
+
last_err: BaseException | None = None
|
|
259
|
+
for attempt in range(max_retries):
|
|
260
|
+
try:
|
|
261
|
+
if sqlite_semaphore:
|
|
262
|
+
async with sqlite_semaphore:
|
|
263
|
+
async with async_maker() as session:
|
|
264
|
+
data = await func(self, session, *args, **kwargs)
|
|
265
|
+
await session.commit()
|
|
266
|
+
return data
|
|
267
|
+
else:
|
|
268
|
+
async with async_maker() as session:
|
|
269
|
+
data = await func(self, session, *args, **kwargs)
|
|
270
|
+
await session.commit()
|
|
271
|
+
return data
|
|
272
|
+
except Exception as e:
|
|
273
|
+
last_err = e
|
|
274
|
+
if _is_pool_timeout(e):
|
|
275
|
+
logger.error(f"[数据库] 连接池耗尽/超时, 停止重试: {e}")
|
|
276
|
+
raise
|
|
277
|
+
if isinstance(e, OperationalError) and "unable to open database file" in str(e):
|
|
278
|
+
logger.error("[数据库] 数据库无法打开, 停止重试")
|
|
279
|
+
raise
|
|
280
|
+
if _is_transient_db_error(e) and attempt < max_retries - 1:
|
|
281
|
+
logger.warning(f"[数据库] 第 {attempt + 1} 次重试失败: {e}")
|
|
282
|
+
await asyncio.sleep(0.5 * (2**attempt))
|
|
283
|
+
continue
|
|
284
|
+
# 业务异常 / 不可恢复:直接抛,禁止静默 return None
|
|
285
|
+
if attempt >= max_retries - 1 and _is_transient_db_error(e):
|
|
286
|
+
logger.error(
|
|
287
|
+
f"[数据库] 重试耗尽仍失败: {e}",
|
|
288
|
+
exc_info=True,
|
|
289
|
+
)
|
|
290
|
+
raise
|
|
291
|
+
|
|
292
|
+
# 理论上到不了这里;兜底保证不返回 None
|
|
293
|
+
if last_err is not None:
|
|
294
|
+
raise last_err
|
|
295
|
+
raise RuntimeError("[数据库] with_session 未知失败")
|
|
296
|
+
|
|
297
|
+
return wrapper # type: ignore
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# https://github.com/tiangolo/sqlmodel/issues/264
|
|
301
|
+
class BaseIDModel(SQLModel):
|
|
302
|
+
id: int = Field(default=None, primary_key=True, title="序号")
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
@with_session
|
|
306
|
+
async def get_distinct_list(
|
|
307
|
+
cls,
|
|
308
|
+
session: AsyncSession,
|
|
309
|
+
column: InstrumentedAttribute[T],
|
|
310
|
+
):
|
|
311
|
+
result = await session.execute(select(column).distinct())
|
|
312
|
+
r = result.all()
|
|
313
|
+
return r
|
|
314
|
+
|
|
315
|
+
@classmethod
|
|
316
|
+
@with_session
|
|
317
|
+
async def batch_insert_data(
|
|
318
|
+
cls,
|
|
319
|
+
session: AsyncSession,
|
|
320
|
+
datas: Sequence["BaseIDModel"],
|
|
321
|
+
):
|
|
322
|
+
session.add_all(datas)
|
|
323
|
+
|
|
324
|
+
@classmethod
|
|
325
|
+
@with_session
|
|
326
|
+
async def batch_insert_data_with_update(
|
|
327
|
+
cls,
|
|
328
|
+
session: AsyncSession,
|
|
329
|
+
datas: Sequence["BaseIDModel"],
|
|
330
|
+
update_key: List[str],
|
|
331
|
+
index_elements: List[str],
|
|
332
|
+
):
|
|
333
|
+
"""
|
|
334
|
+
MySQL需要预先定义约束条件!!
|
|
335
|
+
"""
|
|
336
|
+
if not datas:
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
values_to_insert = [data.model_dump() for data in datas]
|
|
340
|
+
if _db_type == "sqlite":
|
|
341
|
+
from sqlalchemy.dialects.sqlite import insert
|
|
342
|
+
|
|
343
|
+
stmt = insert(cls)
|
|
344
|
+
update_stmt = stmt.on_conflict_do_update(
|
|
345
|
+
index_elements=index_elements,
|
|
346
|
+
set_={k: stmt.excluded[k] for k in update_key},
|
|
347
|
+
)
|
|
348
|
+
elif _db_type == "postgresql":
|
|
349
|
+
from sqlalchemy.dialects.postgresql import insert
|
|
350
|
+
|
|
351
|
+
stmt = insert(cls)
|
|
352
|
+
update_stmt = stmt.on_conflict_do_update(
|
|
353
|
+
index_elements=index_elements,
|
|
354
|
+
set_={k: stmt.excluded[k] for k in update_key},
|
|
355
|
+
)
|
|
356
|
+
elif _db_type == "mysql":
|
|
357
|
+
from sqlalchemy.dialects.mysql import insert
|
|
358
|
+
|
|
359
|
+
stmt = insert(cls)
|
|
360
|
+
update_dict = {col: getattr(stmt.inserted, col) for col in update_key}
|
|
361
|
+
update_stmt = stmt.on_duplicate_key_update(**update_dict)
|
|
362
|
+
else:
|
|
363
|
+
raise ValueError(f"[数据库] 不支持 {_db_type} 数据库!")
|
|
364
|
+
|
|
365
|
+
await session.execute(update_stmt, values_to_insert)
|
|
366
|
+
|
|
367
|
+
@classmethod
|
|
368
|
+
@with_session
|
|
369
|
+
async def update_data_by_data(
|
|
370
|
+
cls,
|
|
371
|
+
session: AsyncSession,
|
|
372
|
+
select_data: Dict,
|
|
373
|
+
update_data: Dict,
|
|
374
|
+
) -> int:
|
|
375
|
+
"""📝简单介绍:
|
|
376
|
+
|
|
377
|
+
基类的数据更新方法
|
|
378
|
+
|
|
379
|
+
🌱参数:
|
|
380
|
+
|
|
381
|
+
🔹select_data (`Dict`):
|
|
382
|
+
寻找数据条件, 例如`{"user_id": `event.bot_id`}`
|
|
383
|
+
|
|
384
|
+
🔹`update_data (`Dict`)`:
|
|
385
|
+
要更新的数据
|
|
386
|
+
|
|
387
|
+
🚀使用范例:
|
|
388
|
+
|
|
389
|
+
`await WavesUser.update_data_by_data(`
|
|
390
|
+
`select_data={"user_id": `event.bot_id`}, `
|
|
391
|
+
`update_data={"bot_id": 'onebot', "uid": '22'}`
|
|
392
|
+
`)`
|
|
393
|
+
|
|
394
|
+
✅返回值:
|
|
395
|
+
|
|
396
|
+
🔸`int`: 成功为0, 失败为-1(未找到数据则无法更新)
|
|
397
|
+
"""
|
|
398
|
+
sql = update(cls)
|
|
399
|
+
for k, v in select_data.items():
|
|
400
|
+
sql = sql.where(getattr(cls, k) == v)
|
|
401
|
+
|
|
402
|
+
if update_data:
|
|
403
|
+
query = sql.values(**update_data)
|
|
404
|
+
query.execution_options(synchronize_session="fetch")
|
|
405
|
+
await session.execute(query)
|
|
406
|
+
return 0
|
|
407
|
+
return -1
|
|
408
|
+
|
|
409
|
+
@classmethod
|
|
410
|
+
def get_gameid_name(cls, game_name: Optional[str] = None) -> str:
|
|
411
|
+
"""📝简单介绍:
|
|
412
|
+
|
|
413
|
+
快速获取uid的列名
|
|
414
|
+
|
|
415
|
+
🌱参数:
|
|
416
|
+
|
|
417
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
418
|
+
假设传入`None`会返回`uid`,而传入`sr`会返回`sr_uid`
|
|
419
|
+
特殊的, 传入`gs`也会返回`uid`!
|
|
420
|
+
|
|
421
|
+
注意: 仅接受「模型上真实存在的游戏 UID 列」对应短名,
|
|
422
|
+
否则在校验阶段抛出 ``ValueError``。
|
|
423
|
+
|
|
424
|
+
🚀使用范例:
|
|
425
|
+
|
|
426
|
+
`await WavesUser.get_gameid_name('sr')`
|
|
427
|
+
|
|
428
|
+
✅返回值:
|
|
429
|
+
|
|
430
|
+
🔸`str`: 游戏uid对应列名,默认为`uid`
|
|
431
|
+
"""
|
|
432
|
+
if not game_name or game_name == "gs":
|
|
433
|
+
col_name = "uid"
|
|
434
|
+
else:
|
|
435
|
+
col_name = f"{game_name}_uid"
|
|
436
|
+
|
|
437
|
+
# 入口统一校验:所有 getattr(cls, get_gameid_name(...)) 依赖此保证
|
|
438
|
+
if not hasattr(cls, col_name):
|
|
439
|
+
raise ValueError(
|
|
440
|
+
f"{cls.__name__} 不存在字段 {col_name!r} "
|
|
441
|
+
f"(game_name={game_name!r}); "
|
|
442
|
+
f"game_name 只能是模型上真实存在的游戏 UID 列短名"
|
|
443
|
+
)
|
|
444
|
+
return col_name
|
|
445
|
+
|
|
446
|
+
@classmethod
|
|
447
|
+
@with_session
|
|
448
|
+
async def full_insert_data(cls, session: AsyncSession, **data) -> int:
|
|
449
|
+
"""📝简单介绍:
|
|
450
|
+
|
|
451
|
+
数据库基类基础插入数据方法
|
|
452
|
+
|
|
453
|
+
🌱参数:
|
|
454
|
+
|
|
455
|
+
🔹`**data`
|
|
456
|
+
插入的数据, 入参列名等于数据即可
|
|
457
|
+
|
|
458
|
+
🚀使用范例:
|
|
459
|
+
|
|
460
|
+
`await WavesUser.full_insert_data(uid='123',cookie='233', ...)`
|
|
461
|
+
|
|
462
|
+
✅返回值:
|
|
463
|
+
|
|
464
|
+
🔸`int`: 恒为0
|
|
465
|
+
"""
|
|
466
|
+
session.add(cls(**data))
|
|
467
|
+
return 0
|
|
468
|
+
|
|
469
|
+
@classmethod
|
|
470
|
+
@with_session
|
|
471
|
+
async def delete_row(
|
|
472
|
+
cls: Type[T_BaseIDModel],
|
|
473
|
+
session: AsyncSession,
|
|
474
|
+
**data,
|
|
475
|
+
) -> int:
|
|
476
|
+
"""
|
|
477
|
+
✅返回值:
|
|
478
|
+
|
|
479
|
+
🔸`int`: 如为1则删除成功,否则删除失败(数据不存在)
|
|
480
|
+
"""
|
|
481
|
+
row_data = await cls.select_rows(**data)
|
|
482
|
+
logger.trace(f"[数据库] 即将删除{row_data}")
|
|
483
|
+
if row_data:
|
|
484
|
+
for row in row_data:
|
|
485
|
+
await session.delete(row)
|
|
486
|
+
return 1
|
|
487
|
+
else:
|
|
488
|
+
return 0
|
|
489
|
+
|
|
490
|
+
@classmethod
|
|
491
|
+
@with_session
|
|
492
|
+
async def select_rows(
|
|
493
|
+
cls: Type[T_BaseIDModel],
|
|
494
|
+
session: AsyncSession,
|
|
495
|
+
distinct: bool = False,
|
|
496
|
+
**data,
|
|
497
|
+
):
|
|
498
|
+
"""📝简单介绍:
|
|
499
|
+
|
|
500
|
+
数据库基类基础选择数据方法
|
|
501
|
+
|
|
502
|
+
🌱参数:
|
|
503
|
+
|
|
504
|
+
🔹`**data`
|
|
505
|
+
选择的数据, 入参列名等于数据即可
|
|
506
|
+
|
|
507
|
+
🚀使用范例:
|
|
508
|
+
|
|
509
|
+
`await WavesUser.base_select_data(uid='100740568')`
|
|
510
|
+
|
|
511
|
+
✅返回值:
|
|
512
|
+
|
|
513
|
+
🔸`Optional[List[T_BaseIDModel]]`: 选中全部符合条件的数据,或者为`None`
|
|
514
|
+
"""
|
|
515
|
+
stmt = select(cls)
|
|
516
|
+
for k, v in data.items():
|
|
517
|
+
stmt = stmt.where(getattr(cls, k) == v)
|
|
518
|
+
if distinct:
|
|
519
|
+
stmt = stmt.distinct()
|
|
520
|
+
result = await session.execute(stmt)
|
|
521
|
+
data = result.scalars().all()
|
|
522
|
+
logger.trace(f"[数据库] 选择 {data}")
|
|
523
|
+
return data
|
|
524
|
+
|
|
525
|
+
@classmethod
|
|
526
|
+
async def base_select_data(cls: Type[T_BaseIDModel], **data) -> Optional[T_BaseIDModel]:
|
|
527
|
+
"""📝简单介绍:
|
|
528
|
+
|
|
529
|
+
数据库基类基础选择数据方法
|
|
530
|
+
|
|
531
|
+
🌱参数:
|
|
532
|
+
|
|
533
|
+
🔹`**data`
|
|
534
|
+
选择的数据, 入参列名等于数据即可
|
|
535
|
+
|
|
536
|
+
🚀使用范例:
|
|
537
|
+
|
|
538
|
+
`await WavesUser.base_select_data(uid='100740568')`
|
|
539
|
+
|
|
540
|
+
✅返回值:
|
|
541
|
+
|
|
542
|
+
🔸`Optional[T_BaseIDModel]`: 选中符合条件的第一个数据,或者为`None`
|
|
543
|
+
"""
|
|
544
|
+
data = await cls.select_rows(**data)
|
|
545
|
+
return data[0] if data else None
|
|
546
|
+
|
|
547
|
+
@classmethod
|
|
548
|
+
async def data_exist(cls, **data) -> bool:
|
|
549
|
+
"""📝简单介绍:
|
|
550
|
+
|
|
551
|
+
数据库基类基础判定数据是否存在的方法
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
🚀使用范例:
|
|
555
|
+
|
|
556
|
+
`await WavesUser.data_exist(uid='100740568')`
|
|
557
|
+
|
|
558
|
+
✅返回值:
|
|
559
|
+
|
|
560
|
+
🔸`bool`: 存在为`True`
|
|
561
|
+
"""
|
|
562
|
+
return bool(await cls.base_select_data(**data))
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
class BaseBotIDModel(BaseIDModel):
|
|
566
|
+
bot_id: str = Field(title="平台")
|
|
567
|
+
|
|
568
|
+
@classmethod
|
|
569
|
+
@with_session
|
|
570
|
+
async def update_data_by_uid_without_bot_id(
|
|
571
|
+
cls,
|
|
572
|
+
session: AsyncSession,
|
|
573
|
+
uid: str,
|
|
574
|
+
game_name: Optional[str] = None,
|
|
575
|
+
**data,
|
|
576
|
+
) -> int:
|
|
577
|
+
"""📝简单介绍:
|
|
578
|
+
|
|
579
|
+
基类方法,通过传入uid查找并更新数据,无需bot_id
|
|
580
|
+
|
|
581
|
+
🌱参数:
|
|
582
|
+
|
|
583
|
+
🔹uid (`str`):
|
|
584
|
+
根据该入参寻找相应数据
|
|
585
|
+
|
|
586
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
587
|
+
根据该入参寻找相应列名
|
|
588
|
+
|
|
589
|
+
🚀使用范例:
|
|
590
|
+
|
|
591
|
+
`await WavesUser.update_data_by_uid_without_bot_id(uid, cookie='2')`
|
|
592
|
+
|
|
593
|
+
✅返回值:
|
|
594
|
+
|
|
595
|
+
🔸`int`: 成功为`0`, 失败为`-1`
|
|
596
|
+
"""
|
|
597
|
+
sql = update(cls).where(
|
|
598
|
+
getattr(cls, cls.get_gameid_name(game_name)) == uid,
|
|
599
|
+
)
|
|
600
|
+
if data is not None:
|
|
601
|
+
query = sql.values(**data)
|
|
602
|
+
query.execution_options(synchronize_session="fetch")
|
|
603
|
+
await session.execute(query)
|
|
604
|
+
return 0
|
|
605
|
+
return -1
|
|
606
|
+
|
|
607
|
+
@classmethod
|
|
608
|
+
@with_session
|
|
609
|
+
async def update_data_by_xx(
|
|
610
|
+
cls,
|
|
611
|
+
session: AsyncSession,
|
|
612
|
+
by: Dict[str, Any],
|
|
613
|
+
**data,
|
|
614
|
+
) -> int:
|
|
615
|
+
"""📝简单介绍:
|
|
616
|
+
|
|
617
|
+
基类方法,通过传入`by`和`**data`查找并更新数据
|
|
618
|
+
|
|
619
|
+
🌱参数:
|
|
620
|
+
|
|
621
|
+
🔹by (`Dict[str, Any]`)
|
|
622
|
+
根据该入参寻找相应数据
|
|
623
|
+
|
|
624
|
+
🔹**data
|
|
625
|
+
根据该入参修改数据
|
|
626
|
+
|
|
627
|
+
🚀使用范例:
|
|
628
|
+
|
|
629
|
+
`await WavesUser.update_data_by_xx({'uid': '233'}, cookie=ck)`
|
|
630
|
+
|
|
631
|
+
✅返回值:
|
|
632
|
+
|
|
633
|
+
🔸`int`: 成功为`0`, 失败为`-1`
|
|
634
|
+
"""
|
|
635
|
+
sql = update(cls)
|
|
636
|
+
for i in by:
|
|
637
|
+
sql = sql.where(getattr(cls, i) == by[i])
|
|
638
|
+
if data is not None:
|
|
639
|
+
query = sql.values(**data)
|
|
640
|
+
query.execution_options(synchronize_session="fetch")
|
|
641
|
+
await session.execute(query)
|
|
642
|
+
return 0
|
|
643
|
+
return -1
|
|
644
|
+
|
|
645
|
+
@classmethod
|
|
646
|
+
@with_session
|
|
647
|
+
async def update_data_by_uid(
|
|
648
|
+
cls,
|
|
649
|
+
session: AsyncSession,
|
|
650
|
+
uid: str,
|
|
651
|
+
bot_id: Optional[str] = None,
|
|
652
|
+
game_name: Optional[str] = None,
|
|
653
|
+
**data,
|
|
654
|
+
) -> int:
|
|
655
|
+
"""📝简单介绍:
|
|
656
|
+
|
|
657
|
+
基类方法,通过传入`uid`和`bot_id`查找并更新数据
|
|
658
|
+
|
|
659
|
+
🌱参数:
|
|
660
|
+
|
|
661
|
+
🔹uid (`str`)
|
|
662
|
+
根据该入参寻找相应数据
|
|
663
|
+
|
|
664
|
+
🔹bot_id (`str`)
|
|
665
|
+
根据该入参寻找相应数据
|
|
666
|
+
|
|
667
|
+
🔹game_name (`Optional[str]`, 默认是 `None`)
|
|
668
|
+
根据该入参修改寻找列名
|
|
669
|
+
|
|
670
|
+
🔹**data
|
|
671
|
+
根据该入参修改数据
|
|
672
|
+
|
|
673
|
+
🚀使用范例:
|
|
674
|
+
|
|
675
|
+
`await WavesUser.update_data_by_uid(uid, 'onebot', cookie='2')`
|
|
676
|
+
|
|
677
|
+
✅返回值:
|
|
678
|
+
|
|
679
|
+
🔸`int`: 成功为`0`, 失败为`-1`
|
|
680
|
+
"""
|
|
681
|
+
uid_name = cls.get_gameid_name(game_name)
|
|
682
|
+
if not await cls.data_exist(**{uid_name: uid}):
|
|
683
|
+
data[uid_name] = uid
|
|
684
|
+
return await cls.full_insert_data(bot_id=bot_id, **data)
|
|
685
|
+
|
|
686
|
+
sql = update(cls).where(and_(getattr(cls, uid_name) == uid))
|
|
687
|
+
|
|
688
|
+
if bot_id is not None:
|
|
689
|
+
sql = sql.where(and_(cls.bot_id == bot_id))
|
|
690
|
+
|
|
691
|
+
if data is not None:
|
|
692
|
+
query = sql.values(**data)
|
|
693
|
+
query.execution_options(synchronize_session="fetch")
|
|
694
|
+
await session.execute(query)
|
|
695
|
+
return 0
|
|
696
|
+
return -1
|
|
697
|
+
|
|
698
|
+
@classmethod
|
|
699
|
+
@with_session
|
|
700
|
+
async def get_all_data(
|
|
701
|
+
cls: Type[T_BaseIDModel],
|
|
702
|
+
session: AsyncSession,
|
|
703
|
+
):
|
|
704
|
+
rdata = await session.execute(select(cls))
|
|
705
|
+
data = rdata.scalars().all()
|
|
706
|
+
return data
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class BaseModel(BaseBotIDModel):
|
|
710
|
+
user_id: str = Field(title="账号")
|
|
711
|
+
|
|
712
|
+
################################
|
|
713
|
+
# 基本的增删改查 #
|
|
714
|
+
################################
|
|
715
|
+
|
|
716
|
+
@classmethod
|
|
717
|
+
@with_session
|
|
718
|
+
async def select_data_list(
|
|
719
|
+
cls: Type[T_BaseModel],
|
|
720
|
+
session: AsyncSession,
|
|
721
|
+
user_id: str,
|
|
722
|
+
bot_id: Optional[str] = None,
|
|
723
|
+
):
|
|
724
|
+
"""📝简单介绍:
|
|
725
|
+
|
|
726
|
+
基类的数据选择方法
|
|
727
|
+
|
|
728
|
+
🌱参数:
|
|
729
|
+
|
|
730
|
+
🔹user_id (`str`):
|
|
731
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
732
|
+
|
|
733
|
+
🔹bot_id (`Optional[str]`, 默认是 `None`):
|
|
734
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
735
|
+
|
|
736
|
+
🚀使用范例:
|
|
737
|
+
|
|
738
|
+
`await WavesUser.select_data(user_id='444888', bot_id='onebot')`
|
|
739
|
+
|
|
740
|
+
✅返回值:
|
|
741
|
+
|
|
742
|
+
🔸`Optional[Sequence[T_BaseModel]]`: 选中符合条件的全部数据,不存在则为`None`
|
|
743
|
+
"""
|
|
744
|
+
if bot_id is None:
|
|
745
|
+
sql = select(cls).where(cls.user_id == user_id)
|
|
746
|
+
else:
|
|
747
|
+
sql = select(cls).where(and_(cls.user_id == user_id, cls.bot_id == bot_id))
|
|
748
|
+
result = await session.execute(sql)
|
|
749
|
+
data = result.scalars().all()
|
|
750
|
+
return data if data else None
|
|
751
|
+
|
|
752
|
+
@classmethod
|
|
753
|
+
async def select_data(
|
|
754
|
+
cls: Type[T_BaseModel],
|
|
755
|
+
user_id: str,
|
|
756
|
+
bot_id: Optional[str] = None,
|
|
757
|
+
) -> Optional[T_BaseModel]:
|
|
758
|
+
"""📝简单介绍:
|
|
759
|
+
|
|
760
|
+
基类的数据选择方法
|
|
761
|
+
|
|
762
|
+
🌱参数:
|
|
763
|
+
|
|
764
|
+
🔹user_id (`str`):
|
|
765
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
766
|
+
|
|
767
|
+
🔹bot_id (`Optional[str]`, 默认是 `None`):
|
|
768
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
769
|
+
|
|
770
|
+
🚀使用范例:
|
|
771
|
+
|
|
772
|
+
`await WavesUser.select_data(user_id='444888', bot_id='onebot')`
|
|
773
|
+
|
|
774
|
+
✅返回值:
|
|
775
|
+
|
|
776
|
+
🔸`Optional[T_BaseModel]`: 选中符合条件的第一个数据,不存在则为`None`
|
|
777
|
+
"""
|
|
778
|
+
data = await cls.select_data_list(user_id, bot_id)
|
|
779
|
+
return data[0] if data else None
|
|
780
|
+
|
|
781
|
+
@classmethod
|
|
782
|
+
@with_session
|
|
783
|
+
async def insert_data(
|
|
784
|
+
cls: Type[T_BaseModel],
|
|
785
|
+
session: AsyncSession,
|
|
786
|
+
user_id: str,
|
|
787
|
+
bot_id: str,
|
|
788
|
+
**data,
|
|
789
|
+
) -> int:
|
|
790
|
+
"""📝简单介绍:
|
|
791
|
+
|
|
792
|
+
基类的数据插入方法
|
|
793
|
+
|
|
794
|
+
🌱参数:
|
|
795
|
+
|
|
796
|
+
🔹user_id (`str`):
|
|
797
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
798
|
+
|
|
799
|
+
🔹bot_id (`str`):
|
|
800
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
801
|
+
|
|
802
|
+
🔹`**data`:
|
|
803
|
+
要插入的数据
|
|
804
|
+
|
|
805
|
+
🚀使用范例:
|
|
806
|
+
|
|
807
|
+
`await WavesUser.insert_data(user_id='4', bot_id='onebot', uid='22')`
|
|
808
|
+
|
|
809
|
+
✅返回值:
|
|
810
|
+
|
|
811
|
+
🔸`int`: 恒为0
|
|
812
|
+
"""
|
|
813
|
+
cond = {"user_id": user_id, "bot_id": bot_id}
|
|
814
|
+
if "mys_id" in data:
|
|
815
|
+
cond["mys_id"] = data["mys_id"]
|
|
816
|
+
elif "uid" in data:
|
|
817
|
+
cond["uid"] = data["uid"]
|
|
818
|
+
if await cls.data_exist(**cond):
|
|
819
|
+
await cls.update_data(user_id, bot_id, **data)
|
|
820
|
+
else:
|
|
821
|
+
session.add(cls(user_id=user_id, bot_id=bot_id, **data))
|
|
822
|
+
return 0
|
|
823
|
+
|
|
824
|
+
@classmethod
|
|
825
|
+
@with_session
|
|
826
|
+
async def delete_data(
|
|
827
|
+
cls: Type[T_BaseModel],
|
|
828
|
+
session: AsyncSession,
|
|
829
|
+
user_id: str,
|
|
830
|
+
bot_id: str,
|
|
831
|
+
**data,
|
|
832
|
+
) -> int:
|
|
833
|
+
"""📝简单介绍:
|
|
834
|
+
|
|
835
|
+
基类的数据删除方法
|
|
836
|
+
|
|
837
|
+
🌱参数:
|
|
838
|
+
|
|
839
|
+
🔹user_id (`str`):
|
|
840
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
841
|
+
|
|
842
|
+
🔹bot_id (`str`):
|
|
843
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
844
|
+
|
|
845
|
+
🚀使用范例:
|
|
846
|
+
|
|
847
|
+
`await WavesUser.delete_data(user_id='4', bot_id='onebot', uid='22')`
|
|
848
|
+
|
|
849
|
+
✅返回值:
|
|
850
|
+
|
|
851
|
+
🔸`int`: 恒为0
|
|
852
|
+
"""
|
|
853
|
+
await session.delete(cls(user_id=user_id, bot_id=bot_id, **data))
|
|
854
|
+
return 0
|
|
855
|
+
|
|
856
|
+
@classmethod
|
|
857
|
+
@with_session
|
|
858
|
+
async def update_data(
|
|
859
|
+
cls: Type[T_BaseModel],
|
|
860
|
+
session: AsyncSession,
|
|
861
|
+
user_id: str,
|
|
862
|
+
bot_id: str,
|
|
863
|
+
**data,
|
|
864
|
+
) -> int:
|
|
865
|
+
"""📝简单介绍:
|
|
866
|
+
|
|
867
|
+
基类的数据更新方法
|
|
868
|
+
|
|
869
|
+
🌱参数:
|
|
870
|
+
|
|
871
|
+
🔹user_id (`str`):
|
|
872
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
873
|
+
|
|
874
|
+
🔹bot_id (`str`):
|
|
875
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
876
|
+
|
|
877
|
+
🔹`**data`:
|
|
878
|
+
要更新的数据
|
|
879
|
+
|
|
880
|
+
🚀使用范例:
|
|
881
|
+
|
|
882
|
+
`await WavesUser.update_data(user_id='4', bot_id='onebot', uid='22')`
|
|
883
|
+
|
|
884
|
+
✅返回值:
|
|
885
|
+
|
|
886
|
+
🔸`int`: 成功为0, 失败为-1(未找到数据则无法更新)
|
|
887
|
+
"""
|
|
888
|
+
sql = update(cls).where(and_(cls.user_id == user_id, cls.bot_id == bot_id))
|
|
889
|
+
if data is not None:
|
|
890
|
+
query = sql.values(**data)
|
|
891
|
+
query.execution_options(synchronize_session="fetch")
|
|
892
|
+
await session.execute(query)
|
|
893
|
+
return 0
|
|
894
|
+
return -1
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
class Bind(BaseModel):
|
|
898
|
+
group_id: Optional[str] = Field(title="群号")
|
|
899
|
+
|
|
900
|
+
################################
|
|
901
|
+
# 额外的扩展方法 #
|
|
902
|
+
################################
|
|
903
|
+
@classmethod
|
|
904
|
+
async def get_uid_list_by_game(
|
|
905
|
+
cls: Type[T_Bind],
|
|
906
|
+
user_id: str,
|
|
907
|
+
bot_id: str,
|
|
908
|
+
game_name: Optional[str] = None,
|
|
909
|
+
) -> Optional[List[str]]:
|
|
910
|
+
"""📝简单介绍:
|
|
911
|
+
|
|
912
|
+
基础`Bind`类的扩展方法, 根据传入的`bot_id`和`user_id`拿到绑定的uid列表
|
|
913
|
+
|
|
914
|
+
🌱参数:
|
|
915
|
+
|
|
916
|
+
🔹user_id (`str`):
|
|
917
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
918
|
+
|
|
919
|
+
🔹bot_id (`str`):
|
|
920
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
921
|
+
|
|
922
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
923
|
+
根据该入参寻找相应列名
|
|
924
|
+
|
|
925
|
+
🚀使用范例:
|
|
926
|
+
|
|
927
|
+
`await WavesUser.get_uid_list_by_game(user_id='4', bot_id='onebot')`
|
|
928
|
+
|
|
929
|
+
✅返回值:
|
|
930
|
+
|
|
931
|
+
🔸`Optional[List[str]]`: 如果有数据则为uid的列表,无则为`None`
|
|
932
|
+
"""
|
|
933
|
+
result = await cls.select_data(user_id, bot_id)
|
|
934
|
+
if result is None:
|
|
935
|
+
return None
|
|
936
|
+
|
|
937
|
+
uid = getattr(result, cls.get_gameid_name(game_name))
|
|
938
|
+
if uid is None:
|
|
939
|
+
return None
|
|
940
|
+
else:
|
|
941
|
+
uid_list = uid.split("_")
|
|
942
|
+
|
|
943
|
+
if uid_list:
|
|
944
|
+
return uid_list
|
|
945
|
+
else:
|
|
946
|
+
return None
|
|
947
|
+
|
|
948
|
+
@classmethod
|
|
949
|
+
async def get_uid_by_game(
|
|
950
|
+
cls: Type[T_Bind],
|
|
951
|
+
user_id: str,
|
|
952
|
+
bot_id: str,
|
|
953
|
+
game_name: Optional[str] = None,
|
|
954
|
+
) -> Optional[str]:
|
|
955
|
+
"""📝简单介绍:
|
|
956
|
+
|
|
957
|
+
基础`Bind`类的扩展方法, 根据传入的`bot_id`和`user_id`拿到单个绑定的uid
|
|
958
|
+
|
|
959
|
+
🌱参数:
|
|
960
|
+
|
|
961
|
+
🔹user_id (`str`):
|
|
962
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
963
|
+
|
|
964
|
+
🔹bot_id (`str`):
|
|
965
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
966
|
+
|
|
967
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
968
|
+
根据该入参寻找相应列名
|
|
969
|
+
|
|
970
|
+
🚀使用范例:
|
|
971
|
+
|
|
972
|
+
`await WavesUser.get_uid_by_game(user_id='4', bot_id='onebot')`
|
|
973
|
+
|
|
974
|
+
✅返回值:
|
|
975
|
+
|
|
976
|
+
🔸`Optional[str]`: 如果有绑定数据则返回当前绑定uid, 没有则为`None`
|
|
977
|
+
"""
|
|
978
|
+
result = await cls.get_uid_list_by_game(user_id, bot_id, game_name)
|
|
979
|
+
if result is None or not result:
|
|
980
|
+
return None
|
|
981
|
+
return result[0]
|
|
982
|
+
|
|
983
|
+
@classmethod
|
|
984
|
+
async def bind_exists(
|
|
985
|
+
cls: Type[T_Bind],
|
|
986
|
+
user_id: str,
|
|
987
|
+
bot_id: str,
|
|
988
|
+
) -> bool:
|
|
989
|
+
"""
|
|
990
|
+
查询当前user_id是否已有绑定数据
|
|
991
|
+
"""
|
|
992
|
+
return bool(await cls.select_data(user_id, bot_id))
|
|
993
|
+
|
|
994
|
+
@classmethod
|
|
995
|
+
async def insert_uid(
|
|
996
|
+
cls: Type[T_Bind],
|
|
997
|
+
user_id: str,
|
|
998
|
+
bot_id: str,
|
|
999
|
+
uid: str,
|
|
1000
|
+
group_id: Optional[str] = None,
|
|
1001
|
+
lenth_limit: Optional[int] = None,
|
|
1002
|
+
is_digit: Optional[bool] = True,
|
|
1003
|
+
game_name: Optional[str] = None,
|
|
1004
|
+
) -> int:
|
|
1005
|
+
"""📝简单介绍:
|
|
1006
|
+
|
|
1007
|
+
基础`Bind`类的扩展方法, 为给定的`user_id`和`bot_id`插入一条uid绑定数据
|
|
1008
|
+
|
|
1009
|
+
可支持多uid的绑定, 如果绑定多个uid, 则数据库中uid列将会用`_`分割符相连接
|
|
1010
|
+
|
|
1011
|
+
可以使用`cls.get_uid_list_by_game()`方法获取相应多绑定uid列表
|
|
1012
|
+
|
|
1013
|
+
或者使用`cls.get_uid_by_game()`方法获得当前绑定uid(单个)
|
|
1014
|
+
|
|
1015
|
+
🌱参数:
|
|
1016
|
+
|
|
1017
|
+
🔹user_id (`str`):
|
|
1018
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
1019
|
+
|
|
1020
|
+
🔹bot_id (`str`):
|
|
1021
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
1022
|
+
|
|
1023
|
+
🔹uid (`str`):
|
|
1024
|
+
将要插入的uid数据
|
|
1025
|
+
|
|
1026
|
+
🔹group_id (`Optional[str]`, 默认是 `None`):
|
|
1027
|
+
将要插入的群组数据,为绑定uid提供群组绑定
|
|
1028
|
+
|
|
1029
|
+
🔹lenth_limit (`Optional[int]`, 默认是 `None`):
|
|
1030
|
+
如果有传该参数, 当uid位数不等于该参数、或uid位数为0的时候, 返回`-1`
|
|
1031
|
+
|
|
1032
|
+
🔹is_digit (`Optional[bool]`, 默认是 `True`):
|
|
1033
|
+
如果有传该参数, 当uid不为全数字的时候, 返回`-3`
|
|
1034
|
+
|
|
1035
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
1036
|
+
根据该入参寻找相应列名
|
|
1037
|
+
|
|
1038
|
+
🚀使用范例:
|
|
1039
|
+
|
|
1040
|
+
`await WavesBind.insert_uid(qid, ev.bot_id, uid, ev.group_id, 9)`
|
|
1041
|
+
|
|
1042
|
+
✅返回值:
|
|
1043
|
+
|
|
1044
|
+
🔸`int`: 如果该UID已绑定, 则返回`-2`, 成功则为`0`, 合法校验失败为`-3`或`-1`
|
|
1045
|
+
"""
|
|
1046
|
+
result = await cls.get_uid_list_by_game(user_id, bot_id, game_name)
|
|
1047
|
+
|
|
1048
|
+
result = [i for i in result if i] if result else None
|
|
1049
|
+
|
|
1050
|
+
if lenth_limit:
|
|
1051
|
+
if len(uid) != lenth_limit:
|
|
1052
|
+
return -1
|
|
1053
|
+
|
|
1054
|
+
if is_digit:
|
|
1055
|
+
if not uid.isdigit():
|
|
1056
|
+
return -3
|
|
1057
|
+
if not uid:
|
|
1058
|
+
return -1
|
|
1059
|
+
|
|
1060
|
+
if result is None and not await cls.bind_exists(user_id, bot_id):
|
|
1061
|
+
return await cls.insert_data(
|
|
1062
|
+
user_id,
|
|
1063
|
+
bot_id,
|
|
1064
|
+
**{cls.get_gameid_name(game_name): uid, "group_id": group_id},
|
|
1065
|
+
)
|
|
1066
|
+
elif result is None:
|
|
1067
|
+
new_uid = uid
|
|
1068
|
+
elif uid in result:
|
|
1069
|
+
return -2
|
|
1070
|
+
else:
|
|
1071
|
+
result.append(uid)
|
|
1072
|
+
new_uid = "_".join(result)
|
|
1073
|
+
await cls.update_data(
|
|
1074
|
+
user_id,
|
|
1075
|
+
bot_id,
|
|
1076
|
+
**{cls.get_gameid_name(game_name): new_uid},
|
|
1077
|
+
)
|
|
1078
|
+
return 0
|
|
1079
|
+
|
|
1080
|
+
@classmethod
|
|
1081
|
+
async def delete_uid(
|
|
1082
|
+
cls: Type[T_Bind],
|
|
1083
|
+
user_id: str,
|
|
1084
|
+
bot_id: str,
|
|
1085
|
+
uid: str,
|
|
1086
|
+
game_name: Optional[str] = None,
|
|
1087
|
+
) -> int:
|
|
1088
|
+
"""📝简单介绍:
|
|
1089
|
+
|
|
1090
|
+
基础`Bind`类的扩展方法, 根据给定的`user_id`和`bot_id`和`uid`删除一个uid
|
|
1091
|
+
|
|
1092
|
+
该方法不会删除行,如果只有一个uid会置空,如果同时绑定多个uid只会删除其中一个
|
|
1093
|
+
|
|
1094
|
+
🌱参数:
|
|
1095
|
+
|
|
1096
|
+
🔹user_id (`str`):
|
|
1097
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
1098
|
+
|
|
1099
|
+
🔹bot_id (`str`):
|
|
1100
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
1101
|
+
|
|
1102
|
+
🔹uid (`str`):
|
|
1103
|
+
将要删除的uid数据
|
|
1104
|
+
|
|
1105
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
1106
|
+
根据该入参寻找相应列名
|
|
1107
|
+
|
|
1108
|
+
🚀使用范例:
|
|
1109
|
+
|
|
1110
|
+
`await WavesBind.delete_uid(qid, ev.bot_id, uid)`
|
|
1111
|
+
|
|
1112
|
+
✅返回值:
|
|
1113
|
+
|
|
1114
|
+
🔸`int`: 失败为`-1`, 成功为`0`
|
|
1115
|
+
"""
|
|
1116
|
+
result = await cls.get_uid_list_by_game(user_id, bot_id, game_name)
|
|
1117
|
+
if result is None:
|
|
1118
|
+
return -1
|
|
1119
|
+
|
|
1120
|
+
if uid not in result:
|
|
1121
|
+
return -1
|
|
1122
|
+
|
|
1123
|
+
result.remove(uid)
|
|
1124
|
+
|
|
1125
|
+
result = [i for i in result if i] if result else []
|
|
1126
|
+
new_uid = "_".join(result)
|
|
1127
|
+
|
|
1128
|
+
if not new_uid:
|
|
1129
|
+
new_uid = None
|
|
1130
|
+
|
|
1131
|
+
await cls.update_data(
|
|
1132
|
+
user_id,
|
|
1133
|
+
bot_id,
|
|
1134
|
+
**{cls.get_gameid_name(game_name): new_uid},
|
|
1135
|
+
)
|
|
1136
|
+
return 0
|
|
1137
|
+
|
|
1138
|
+
@classmethod
|
|
1139
|
+
@with_session
|
|
1140
|
+
async def get_all_uid_list_by_game(
|
|
1141
|
+
cls: Type[T_Bind],
|
|
1142
|
+
session: AsyncSession,
|
|
1143
|
+
bot_id: str,
|
|
1144
|
+
game_name: Optional[str] = None,
|
|
1145
|
+
) -> List[str]:
|
|
1146
|
+
"""📝简单介绍:
|
|
1147
|
+
|
|
1148
|
+
基础`Bind`类的扩展方法, 根据给定的`bot_id`获取全部user绑定的uid列表
|
|
1149
|
+
|
|
1150
|
+
🌱参数:
|
|
1151
|
+
|
|
1152
|
+
🔹bot_id (`str`):
|
|
1153
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
1154
|
+
|
|
1155
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
1156
|
+
根据该入参寻找相应列名
|
|
1157
|
+
|
|
1158
|
+
🚀使用范例:
|
|
1159
|
+
|
|
1160
|
+
`await WavesBind.get_all_uid_list_by_game(ev.bot_id)`
|
|
1161
|
+
|
|
1162
|
+
✅返回值:
|
|
1163
|
+
|
|
1164
|
+
🔸`List[str]`: 一个uid的列表, 如果没有任何用户的绑定信息将返回`[]`
|
|
1165
|
+
"""
|
|
1166
|
+
sql = select(cls).where(cls.bot_id == bot_id)
|
|
1167
|
+
result = await session.execute(sql)
|
|
1168
|
+
data = result.scalars().all()
|
|
1169
|
+
uid_list: List[str] = []
|
|
1170
|
+
for item in data:
|
|
1171
|
+
uid = getattr(item, cls.get_gameid_name(game_name))
|
|
1172
|
+
if uid is not None and uid:
|
|
1173
|
+
game_uid_list: List[str] = uid.split("_")
|
|
1174
|
+
uid_list.extend(game_uid_list)
|
|
1175
|
+
return uid_list
|
|
1176
|
+
|
|
1177
|
+
@classmethod
|
|
1178
|
+
async def switch_uid_by_game(
|
|
1179
|
+
cls: Type[T_Bind],
|
|
1180
|
+
user_id: str,
|
|
1181
|
+
bot_id: str,
|
|
1182
|
+
uid: Optional[str] = None,
|
|
1183
|
+
game_name: Optional[str] = None,
|
|
1184
|
+
) -> int:
|
|
1185
|
+
"""📝简单介绍:
|
|
1186
|
+
|
|
1187
|
+
基础`Bind`类的扩展方法, 根据给定的`bot_id`和`user_id`定位数据,并切换当前uid
|
|
1188
|
+
|
|
1189
|
+
如果不传uid参数则默认切换下个uid
|
|
1190
|
+
|
|
1191
|
+
🌱参数:
|
|
1192
|
+
|
|
1193
|
+
🔹user_id (`str`):
|
|
1194
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
1195
|
+
|
|
1196
|
+
🔹bot_id (`str`):
|
|
1197
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
1198
|
+
|
|
1199
|
+
🔹uid (`Optional[str]`, 默认是 `None`):
|
|
1200
|
+
将要切换的uid数据, 可以不传
|
|
1201
|
+
|
|
1202
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
1203
|
+
根据该入参寻找相应列名
|
|
1204
|
+
|
|
1205
|
+
🚀使用范例:
|
|
1206
|
+
|
|
1207
|
+
`await WavesBind.switch_uid_by_game(qid, ev.bot_id, uid)`
|
|
1208
|
+
|
|
1209
|
+
✅返回值:
|
|
1210
|
+
|
|
1211
|
+
🔸`int`:
|
|
1212
|
+
|
|
1213
|
+
成功返回`0`
|
|
1214
|
+
|
|
1215
|
+
如果不存在绑定记录,则返回`-1`
|
|
1216
|
+
|
|
1217
|
+
如果传了UID但是不存在绑定列表,则返回`-2`
|
|
1218
|
+
|
|
1219
|
+
如果绑定UID列表不足2个,返回`-3`
|
|
1220
|
+
"""
|
|
1221
|
+
uid_list = await cls.get_uid_list_by_game(user_id, bot_id, game_name)
|
|
1222
|
+
if not uid_list:
|
|
1223
|
+
return -1
|
|
1224
|
+
elif len(uid_list) <= 1:
|
|
1225
|
+
return -3
|
|
1226
|
+
elif uid is None or not uid:
|
|
1227
|
+
uid = uid_list[1]
|
|
1228
|
+
old_uid = uid_list[0]
|
|
1229
|
+
uid_list.remove(uid)
|
|
1230
|
+
uid_list.remove(old_uid)
|
|
1231
|
+
uid_list.insert(0, uid)
|
|
1232
|
+
uid_list.append(old_uid)
|
|
1233
|
+
elif uid not in uid_list:
|
|
1234
|
+
return -2
|
|
1235
|
+
else:
|
|
1236
|
+
uid_list.remove(uid)
|
|
1237
|
+
uid_list.insert(0, uid)
|
|
1238
|
+
await cls.update_data(
|
|
1239
|
+
user_id,
|
|
1240
|
+
bot_id,
|
|
1241
|
+
**{cls.get_gameid_name(game_name): "_".join(uid_list)},
|
|
1242
|
+
)
|
|
1243
|
+
return 0
|
|
1244
|
+
|
|
1245
|
+
@classmethod
|
|
1246
|
+
async def get_bind_group_list(cls: Type[T_Bind], user_id: str, bot_id: str) -> List[str]:
|
|
1247
|
+
"""获取传入`user_id`和`bot_id`对应的绑定群列表"""
|
|
1248
|
+
data: Optional["Bind"] = await cls.select_data(user_id, bot_id)
|
|
1249
|
+
return data.group_id.split("_") if data and data.group_id else []
|
|
1250
|
+
|
|
1251
|
+
@classmethod
|
|
1252
|
+
async def get_bind_group(cls: Type[T_Bind], user_id: str, bot_id: str) -> Optional[str]:
|
|
1253
|
+
"""获取传入`user_id`和`bot_id`对应的绑定群(如多个则返回第一个)"""
|
|
1254
|
+
data = await cls.get_bind_group_list(user_id, bot_id)
|
|
1255
|
+
return data[0] if data else None
|
|
1256
|
+
|
|
1257
|
+
@classmethod
|
|
1258
|
+
@with_session
|
|
1259
|
+
async def get_group_all_uid(cls: Type[T_Bind], session: AsyncSession, group_id: str):
|
|
1260
|
+
"""根据传入`group_id`获取该群号下所有绑定`uid`列表"""
|
|
1261
|
+
result = await session.scalars(select(cls).where(col(cls.group_id).contains(group_id)))
|
|
1262
|
+
data = result.all()
|
|
1263
|
+
return data[0] if data else None
|
|
1264
|
+
|
|
1265
|
+
|
|
1266
|
+
class User(BaseModel):
|
|
1267
|
+
cookie: str = Field(default=None, title="Cookie")
|
|
1268
|
+
stoken: Optional[str] = Field(default=None, title="Stoken")
|
|
1269
|
+
status: Optional[str] = Field(default=None, title="状态")
|
|
1270
|
+
push_switch: str = Field(default="off", title="全局推送开关")
|
|
1271
|
+
sign_switch: str = Field(default="off", title="自动签到")
|
|
1272
|
+
|
|
1273
|
+
@classmethod
|
|
1274
|
+
@with_session
|
|
1275
|
+
async def select_data_by_uid(
|
|
1276
|
+
cls: Type[T_User],
|
|
1277
|
+
session: AsyncSession,
|
|
1278
|
+
uid: str,
|
|
1279
|
+
game_name: Optional[str] = None,
|
|
1280
|
+
):
|
|
1281
|
+
"""📝简单介绍:
|
|
1282
|
+
|
|
1283
|
+
基础`User`类的数据选择方法
|
|
1284
|
+
|
|
1285
|
+
🌱参数:
|
|
1286
|
+
|
|
1287
|
+
🔹uid (`str`):
|
|
1288
|
+
传入的用户uid, 一般是该游戏的用户唯一识别id
|
|
1289
|
+
|
|
1290
|
+
🚀使用范例:
|
|
1291
|
+
|
|
1292
|
+
`await WavesUser.select_data_by_uid(uid='100740568')`
|
|
1293
|
+
|
|
1294
|
+
✅返回值:
|
|
1295
|
+
|
|
1296
|
+
🔸`Optional[T_BaseModel]`: 选中符合条件的第一个数据,不存在则为`None`
|
|
1297
|
+
"""
|
|
1298
|
+
result = await session.execute(
|
|
1299
|
+
select(cls).where(
|
|
1300
|
+
getattr(cls, cls.get_gameid_name(game_name)) == uid,
|
|
1301
|
+
)
|
|
1302
|
+
)
|
|
1303
|
+
data = result.scalars().all()
|
|
1304
|
+
return data[0] if data else None
|
|
1305
|
+
|
|
1306
|
+
@classmethod
|
|
1307
|
+
@with_session
|
|
1308
|
+
async def get_user_all_data_by_user_id(cls: Type[T_User], session: AsyncSession, user_id: str):
|
|
1309
|
+
"""📝简单介绍:
|
|
1310
|
+
|
|
1311
|
+
基础`User`类的数据选择方法, 获取该`user_id`绑定的全部数据实例
|
|
1312
|
+
|
|
1313
|
+
🌱参数:
|
|
1314
|
+
|
|
1315
|
+
🔹user_id (`str`):
|
|
1316
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
1317
|
+
|
|
1318
|
+
🚀使用范例:
|
|
1319
|
+
|
|
1320
|
+
`await WavesUser.get_user_all_data_by_user_id(user_id='2333')`
|
|
1321
|
+
|
|
1322
|
+
✅返回值:
|
|
1323
|
+
|
|
1324
|
+
🔸`Optional[T_BaseModel]`: 选中符合条件的数据列表,不存在则为`None`
|
|
1325
|
+
"""
|
|
1326
|
+
result = await session.execute(select(cls).where(cls.user_id == user_id))
|
|
1327
|
+
data = result.scalars().all()
|
|
1328
|
+
return data if data else None
|
|
1329
|
+
|
|
1330
|
+
@classmethod
|
|
1331
|
+
async def get_user_attr(
|
|
1332
|
+
cls: Type[T_User],
|
|
1333
|
+
user_id: str,
|
|
1334
|
+
bot_id: str,
|
|
1335
|
+
attr: str,
|
|
1336
|
+
) -> Optional[Any]:
|
|
1337
|
+
"""📝简单介绍:
|
|
1338
|
+
|
|
1339
|
+
根据传入的`user_id`和`bot_id`选择数据实例,然后返回数据的某个属性的值
|
|
1340
|
+
|
|
1341
|
+
🌱参数:
|
|
1342
|
+
|
|
1343
|
+
🔹user_id (`str`):
|
|
1344
|
+
传入的用户id, 例如QQ号, 一般直接取`event.user_id`
|
|
1345
|
+
|
|
1346
|
+
🔹bot_id (`str`):
|
|
1347
|
+
传入的bot_id, 例如`onebot`, 一般直接取`event.bot_id`
|
|
1348
|
+
|
|
1349
|
+
🔹attr (`str`):
|
|
1350
|
+
想要获取的该数据的属性
|
|
1351
|
+
|
|
1352
|
+
🚀使用范例:
|
|
1353
|
+
|
|
1354
|
+
`await cls.get_user_attr(user_id, bot_id, 'cookie')`
|
|
1355
|
+
|
|
1356
|
+
✅返回值:
|
|
1357
|
+
|
|
1358
|
+
🔸`Optional[Any]`: 可能是任何值,如果没获取到数据则为`None`
|
|
1359
|
+
"""
|
|
1360
|
+
result = await cls.select_data(user_id, bot_id)
|
|
1361
|
+
return getattr(result, attr) if result else None
|
|
1362
|
+
|
|
1363
|
+
@classmethod
|
|
1364
|
+
async def get_user_attr_by_uid(
|
|
1365
|
+
cls: Type[T_User],
|
|
1366
|
+
uid: str,
|
|
1367
|
+
attr: str,
|
|
1368
|
+
game_name: Optional[str] = None,
|
|
1369
|
+
) -> Optional[Any]:
|
|
1370
|
+
"""根据传入的`uid`选择数据实例,然后返回数据的`attr`属性的值
|
|
1371
|
+
|
|
1372
|
+
如果没获取到数据则为`None`
|
|
1373
|
+
"""
|
|
1374
|
+
result = await cls.select_data_by_uid(uid, game_name)
|
|
1375
|
+
return getattr(result, attr) if result else None
|
|
1376
|
+
|
|
1377
|
+
@classmethod
|
|
1378
|
+
async def get_user_attr_by_user_id(
|
|
1379
|
+
cls: Type[T_User],
|
|
1380
|
+
user_id: str,
|
|
1381
|
+
attr: str,
|
|
1382
|
+
) -> Optional[Any]:
|
|
1383
|
+
"""根据传入的`user_id`选择数据实例,然后返回数据的`attr`属性的值
|
|
1384
|
+
|
|
1385
|
+
如果没获取到数据则为`None`
|
|
1386
|
+
"""
|
|
1387
|
+
result = await cls.select_data(user_id)
|
|
1388
|
+
return getattr(result, attr) if result else None
|
|
1389
|
+
|
|
1390
|
+
@classmethod
|
|
1391
|
+
@with_session
|
|
1392
|
+
async def mark_invalid(cls: Type[T_User], session: AsyncSession, cookie: str, mark: str):
|
|
1393
|
+
"""令一个cookie所对应数据的`status`值为传入的mark
|
|
1394
|
+
|
|
1395
|
+
例如:mark值可以是`error`, 标记该Cookie已失效
|
|
1396
|
+
"""
|
|
1397
|
+
sql = update(cls).where(and_(cls.cookie == cookie, true())).values(status=mark)
|
|
1398
|
+
await session.execute(sql)
|
|
1399
|
+
return True
|
|
1400
|
+
|
|
1401
|
+
@classmethod
|
|
1402
|
+
async def get_user_cookie_by_uid(cls: Type[T_User], uid: str, game_name: Optional[str] = None) -> Optional[str]:
|
|
1403
|
+
"""根据传入的`uid`选择数据实例,然后返回该数据的`cookie`值
|
|
1404
|
+
|
|
1405
|
+
如果没获取到数据则为`None`
|
|
1406
|
+
"""
|
|
1407
|
+
return await cls.get_user_attr_by_uid(uid, "cookie", game_name)
|
|
1408
|
+
|
|
1409
|
+
@classmethod
|
|
1410
|
+
async def get_user_cookie_by_user_id(cls: Type[T_User], user_id: str, bot_id: str) -> Optional[str]:
|
|
1411
|
+
"""根据传入的`user_id`选择数据实例,然后返回该数据的`cookie`值
|
|
1412
|
+
|
|
1413
|
+
如果没获取到数据则为`None`
|
|
1414
|
+
"""
|
|
1415
|
+
return await cls.get_user_attr(user_id, bot_id, "cookie")
|
|
1416
|
+
|
|
1417
|
+
@classmethod
|
|
1418
|
+
async def get_user_stoken_by_uid(cls: Type[T_User], uid: str, game_name: Optional[str] = None) -> Optional[str]:
|
|
1419
|
+
"""根据传入的`uid`选择数据实例,然后返回该数据的`stoken`值
|
|
1420
|
+
|
|
1421
|
+
如果没获取到数据则为`None`
|
|
1422
|
+
"""
|
|
1423
|
+
return await cls.get_user_attr_by_uid(uid, "stoken", game_name)
|
|
1424
|
+
|
|
1425
|
+
@classmethod
|
|
1426
|
+
async def get_user_stoken_by_user_id(cls: Type[T_User], user_id: str, bot_id: str) -> Optional[str]:
|
|
1427
|
+
"""根据传入的`user_id`选择数据实例,然后返回该数据的`stoken`值
|
|
1428
|
+
|
|
1429
|
+
如果没获取到数据则为`None`
|
|
1430
|
+
"""
|
|
1431
|
+
return await cls.get_user_attr(user_id, bot_id, "stoken")
|
|
1432
|
+
|
|
1433
|
+
@classmethod
|
|
1434
|
+
async def cookie_validate(cls: Type[T_User], uid: str, game_name: Optional[str] = None) -> bool:
|
|
1435
|
+
"""根据传入的`uid`选择数据实例, 校验数据中的`cookie`是否有效
|
|
1436
|
+
|
|
1437
|
+
方法是判断数据中的`status`是否为空值, 如果没有异常标记, 则为`True`
|
|
1438
|
+
"""
|
|
1439
|
+
data = await cls.get_user_attr_by_uid(uid, "status", game_name)
|
|
1440
|
+
if not data:
|
|
1441
|
+
return True
|
|
1442
|
+
else:
|
|
1443
|
+
return False
|
|
1444
|
+
|
|
1445
|
+
@classmethod
|
|
1446
|
+
@with_session
|
|
1447
|
+
async def get_switch_open_list(cls: Type[T_User], session: AsyncSession, switch_name: str) -> List[T_User]:
|
|
1448
|
+
"""📝简单介绍:
|
|
1449
|
+
|
|
1450
|
+
根据表定义的结构, 根据传入的`switch_name`, 寻找表数据中的该列
|
|
1451
|
+
|
|
1452
|
+
如果不存在该列,则选中`cls.push_switch`该列
|
|
1453
|
+
|
|
1454
|
+
对所有数据中,该列满足`!= 'off'`的数据,标记为有效数据
|
|
1455
|
+
|
|
1456
|
+
返回有效数据的列表
|
|
1457
|
+
|
|
1458
|
+
🌱参数:
|
|
1459
|
+
|
|
1460
|
+
🔹switch_name (`str`):
|
|
1461
|
+
寻找表数据列名
|
|
1462
|
+
|
|
1463
|
+
🚀使用范例:
|
|
1464
|
+
|
|
1465
|
+
`await WavesUser.get_switch_open_list('sign_switch')`
|
|
1466
|
+
|
|
1467
|
+
✅返回值:
|
|
1468
|
+
|
|
1469
|
+
🔸`List[T_User]`: 有效数据的列表, 如没有则为`[]`
|
|
1470
|
+
"""
|
|
1471
|
+
_switch = getattr(cls, switch_name, cls.push_switch)
|
|
1472
|
+
sql = select(cls).filter(and_(_switch != "off", true()))
|
|
1473
|
+
data = await session.execute(sql)
|
|
1474
|
+
data_list = data.scalars().all()
|
|
1475
|
+
return [user for user in data_list]
|
|
1476
|
+
|
|
1477
|
+
@classmethod
|
|
1478
|
+
@with_session
|
|
1479
|
+
async def get_all_user(
|
|
1480
|
+
cls: Type[T_User],
|
|
1481
|
+
session: AsyncSession,
|
|
1482
|
+
without_error: bool = True,
|
|
1483
|
+
) -> Sequence[T_User]:
|
|
1484
|
+
"""📝简单介绍:
|
|
1485
|
+
|
|
1486
|
+
基础`User`类的扩展方法, 获取到全部的数据列表
|
|
1487
|
+
|
|
1488
|
+
不一定会返回该表中所有的数据, 返回的数据中`cookie`必定存在值
|
|
1489
|
+
|
|
1490
|
+
🌱参数:
|
|
1491
|
+
|
|
1492
|
+
🔹without_error (`bool`, 默认是 `True`):
|
|
1493
|
+
如果为`True`, 则会排除返回列表中存在`status != None`的数据
|
|
1494
|
+
|
|
1495
|
+
🚀使用范例:
|
|
1496
|
+
|
|
1497
|
+
`await WavesUser.get_all_user()`
|
|
1498
|
+
|
|
1499
|
+
✅返回值:
|
|
1500
|
+
|
|
1501
|
+
🔸`List[T_User]`: 有效数据的列表, 如没有则为`[]`
|
|
1502
|
+
"""
|
|
1503
|
+
if without_error:
|
|
1504
|
+
sql = select(cls).where(cls.status == null(), cls.cookie != null(), cls.cookie != "")
|
|
1505
|
+
else:
|
|
1506
|
+
sql = select(cls).where(cls.cookie != null(), cls.cookie != "")
|
|
1507
|
+
result = await session.execute(sql)
|
|
1508
|
+
data = result.scalars().all()
|
|
1509
|
+
return data
|
|
1510
|
+
|
|
1511
|
+
@classmethod
|
|
1512
|
+
async def get_all_cookie(cls) -> List[str]:
|
|
1513
|
+
"""获得表数据中全部的`cookie`列表"""
|
|
1514
|
+
data = await cls.get_all_user()
|
|
1515
|
+
return [_u.cookie for _u in data if _u.cookie]
|
|
1516
|
+
|
|
1517
|
+
@classmethod
|
|
1518
|
+
async def get_all_stoken(cls) -> List[str]:
|
|
1519
|
+
"""获得表数据中全部的`stoken`列表"""
|
|
1520
|
+
data = await cls.get_all_user()
|
|
1521
|
+
return [_u.stoken for _u in data if _u.stoken]
|
|
1522
|
+
|
|
1523
|
+
@classmethod
|
|
1524
|
+
async def get_all_error_cookie(cls) -> List[str]:
|
|
1525
|
+
"""获得表数据中,`status != None`情况下的所有`cookie`列表
|
|
1526
|
+
|
|
1527
|
+
也就是全部失效CK的列表
|
|
1528
|
+
"""
|
|
1529
|
+
data = await cls.get_all_user()
|
|
1530
|
+
return [_u.cookie for _u in data if _u.cookie and _u.status]
|
|
1531
|
+
|
|
1532
|
+
@classmethod
|
|
1533
|
+
async def get_all_push_user_list(cls: Type[T_User]) -> List[T_User]:
|
|
1534
|
+
"""获得表数据中全部的`push_switch != off`的数据列表"""
|
|
1535
|
+
data = await cls.get_all_user()
|
|
1536
|
+
return [user for user in data if user.push_switch != "off"]
|
|
1537
|
+
|
|
1538
|
+
@classmethod
|
|
1539
|
+
async def get_all_sign_user_list(cls: Type[T_User]) -> List[T_User]:
|
|
1540
|
+
"""获得表数据中全部的`sign_switch!= off`的数据列表"""
|
|
1541
|
+
data = await cls.get_all_user()
|
|
1542
|
+
return [user for user in data if user.sign_switch != "off"]
|
|
1543
|
+
|
|
1544
|
+
@classmethod
|
|
1545
|
+
async def get_push_user_list(
|
|
1546
|
+
cls: Type[T_User],
|
|
1547
|
+
push_title: Optional[str] = None,
|
|
1548
|
+
) -> List[T_User]:
|
|
1549
|
+
"""获得表数据中全部的`{push_title}_push_switch!= off`的数据列表"""
|
|
1550
|
+
if push_title is None:
|
|
1551
|
+
return await cls.get_all_push_user_list()
|
|
1552
|
+
data = await cls.get_all_user()
|
|
1553
|
+
return [user for user in data if getattr(user, f"{push_title}_push_switch") != "off"]
|
|
1554
|
+
|
|
1555
|
+
@classmethod
|
|
1556
|
+
async def get_sign_user_list(
|
|
1557
|
+
cls: Type[T_User],
|
|
1558
|
+
sign_title: Optional[str] = None,
|
|
1559
|
+
) -> List[T_User]:
|
|
1560
|
+
"""获得表数据中全部的`{sign_title}_sign_switch!= off`的数据列表"""
|
|
1561
|
+
if sign_title is None:
|
|
1562
|
+
return await cls.get_all_sign_user_list()
|
|
1563
|
+
data = await cls.get_all_user()
|
|
1564
|
+
return [user for user in data if getattr(user, f"{sign_title}_sign_switch") != "off"]
|
|
1565
|
+
|
|
1566
|
+
@classmethod
|
|
1567
|
+
async def user_exists(cls, uid: str, game_name: Optional[str] = None) -> bool:
|
|
1568
|
+
"""根据传入`uid`,判定数据是否存在"""
|
|
1569
|
+
data = await cls.select_data_by_uid(uid, game_name)
|
|
1570
|
+
return True if data else False
|
|
1571
|
+
|
|
1572
|
+
@classmethod
|
|
1573
|
+
@with_session
|
|
1574
|
+
async def get_random_cookie(
|
|
1575
|
+
cls: Type[T_User],
|
|
1576
|
+
session: AsyncSession,
|
|
1577
|
+
uid: str,
|
|
1578
|
+
cache_model: Optional[Type["Cache"]] = None,
|
|
1579
|
+
condition: Optional[Dict[str, str]] = None,
|
|
1580
|
+
game_name: Optional[str] = None,
|
|
1581
|
+
) -> Optional[str]:
|
|
1582
|
+
"""📝简单介绍:
|
|
1583
|
+
|
|
1584
|
+
基础`User`类的扩展方法, 返回一个随机的cookie
|
|
1585
|
+
|
|
1586
|
+
如果该uid存在绑定cookie, 则返回他绑定的cookie
|
|
1587
|
+
|
|
1588
|
+
如果传入了`cache_model`并且该uid存在cache_model定义的表数据中,返回该cookie
|
|
1589
|
+
|
|
1590
|
+
如果定义了condition, 则选取随机cookie时遵照此规则
|
|
1591
|
+
|
|
1592
|
+
🌱参数:
|
|
1593
|
+
|
|
1594
|
+
🔹uid (`str`):
|
|
1595
|
+
传入的用户uid, 一般是该游戏的用户唯一识别id
|
|
1596
|
+
|
|
1597
|
+
🔹cache_model (`Optional[Type["Cache"]]`, 默认是 `None`):
|
|
1598
|
+
继承基础`Cache`缓存表的模型, 例如`WavesCache`
|
|
1599
|
+
|
|
1600
|
+
🔹condition (`Optional[Dict[str, str]]`, 默认是 `None`):
|
|
1601
|
+
字典结构, 寻找表中符合`key == value`条件的值
|
|
1602
|
+
|
|
1603
|
+
🔹game_name (`Optional[str]`, 默认是 `None`):
|
|
1604
|
+
根据该入参寻找相应列名
|
|
1605
|
+
|
|
1606
|
+
🚀使用范例:
|
|
1607
|
+
|
|
1608
|
+
`await WavesUser.get_random_cookie(
|
|
1609
|
+
uid, WavesCache, {'region': server}, game_name='sr'
|
|
1610
|
+
)`
|
|
1611
|
+
|
|
1612
|
+
✅返回值:
|
|
1613
|
+
|
|
1614
|
+
🔸`Optional[str]`: 如找到符合条件的cookie则返回,没有则为`None`
|
|
1615
|
+
"""
|
|
1616
|
+
# 有绑定自己CK 并且该CK有效的前提下,优先使用自己CK
|
|
1617
|
+
if await cls.user_exists(uid, game_name) and await cls.cookie_validate(uid, game_name):
|
|
1618
|
+
return await cls.get_user_cookie_by_uid(uid, game_name)
|
|
1619
|
+
|
|
1620
|
+
# 自动刷新缓存
|
|
1621
|
+
# await self.delete_error_cache()
|
|
1622
|
+
# 获得缓存库Ck
|
|
1623
|
+
if cache_model is not None:
|
|
1624
|
+
cache_data = await cache_model.select_cache_cookie(uid, game_name)
|
|
1625
|
+
if cache_data is not None:
|
|
1626
|
+
return cache_data
|
|
1627
|
+
|
|
1628
|
+
# 随机取CK
|
|
1629
|
+
if condition:
|
|
1630
|
+
for i in condition:
|
|
1631
|
+
sql = select(cls).where(getattr(cls, i) == condition[i]).order_by(func.random())
|
|
1632
|
+
data = await session.execute(sql)
|
|
1633
|
+
user_list = data.scalars().all()
|
|
1634
|
+
break
|
|
1635
|
+
else:
|
|
1636
|
+
user_list = await cls.get_all_user()
|
|
1637
|
+
else:
|
|
1638
|
+
user_list = await cls.get_all_user()
|
|
1639
|
+
|
|
1640
|
+
for user in user_list:
|
|
1641
|
+
if not user.status and user.cookie:
|
|
1642
|
+
if cache_model:
|
|
1643
|
+
# 进入缓存
|
|
1644
|
+
await cache_model.insert_cache_data(
|
|
1645
|
+
user.cookie,
|
|
1646
|
+
**{cls.get_gameid_name(game_name): uid},
|
|
1647
|
+
)
|
|
1648
|
+
return user.cookie
|
|
1649
|
+
continue
|
|
1650
|
+
else:
|
|
1651
|
+
return None
|
|
1652
|
+
|
|
1653
|
+
@classmethod
|
|
1654
|
+
@with_session
|
|
1655
|
+
async def delete_user_data_by_uid(cls, session: AsyncSession, uid: str, game_name: Optional[str] = None) -> bool:
|
|
1656
|
+
"""根据给定的`uid`获取数据后, 删除整行数据
|
|
1657
|
+
|
|
1658
|
+
如果该数据存在, 删除后返回`True`, 不存在, 则返回`False`
|
|
1659
|
+
"""
|
|
1660
|
+
if await cls.user_exists(uid, game_name):
|
|
1661
|
+
sql = delete(cls).where(getattr(cls, cls.get_gameid_name(game_name)) == uid)
|
|
1662
|
+
await session.execute(sql)
|
|
1663
|
+
return True
|
|
1664
|
+
return False
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
class Cache(BaseIDModel):
|
|
1668
|
+
cookie: str = Field(default=None, title="Cookie")
|
|
1669
|
+
|
|
1670
|
+
@classmethod
|
|
1671
|
+
@with_session
|
|
1672
|
+
async def select_cache_cookie(
|
|
1673
|
+
cls: Type[T_Cache],
|
|
1674
|
+
session: AsyncSession,
|
|
1675
|
+
uid: str,
|
|
1676
|
+
game_name: Optional[str],
|
|
1677
|
+
) -> Optional[str]:
|
|
1678
|
+
"""根据给定的`uid`获取表中存在缓存的`cookie`并返回"""
|
|
1679
|
+
sql = select(cls).where(getattr(cls, cls.get_gameid_name(game_name)) == uid)
|
|
1680
|
+
result = await session.execute(sql)
|
|
1681
|
+
data = result.scalars().all()
|
|
1682
|
+
return data[0].cookie if len(data) >= 1 else None
|
|
1683
|
+
|
|
1684
|
+
@classmethod
|
|
1685
|
+
@with_session
|
|
1686
|
+
async def delete_error_cache(cls: Type[T_Cache], session: AsyncSession, user: Type["User"]) -> bool:
|
|
1687
|
+
"""根据给定的`user`模型中, 查找该模型所有数据的status
|
|
1688
|
+
|
|
1689
|
+
若`status != None`, 则代表该数据cookie有问题
|
|
1690
|
+
|
|
1691
|
+
查找`Cache`表中该cookie对应数据行,并删除
|
|
1692
|
+
|
|
1693
|
+
恒返回`True`
|
|
1694
|
+
"""
|
|
1695
|
+
data = await user.get_all_error_cookie()
|
|
1696
|
+
for cookie in data:
|
|
1697
|
+
sql = delete(cls).where(and_(cls.cookie == cookie, true()))
|
|
1698
|
+
await session.execute(sql)
|
|
1699
|
+
return True
|
|
1700
|
+
|
|
1701
|
+
@classmethod
|
|
1702
|
+
@with_session
|
|
1703
|
+
async def delete_all_cache(cls, session: AsyncSession, user: Type["User"]) -> bool:
|
|
1704
|
+
"""删除整个表的数据
|
|
1705
|
+
|
|
1706
|
+
根据给定的`user`模型中, 查找该模型所有数据的status
|
|
1707
|
+
|
|
1708
|
+
若`status == limit30`, 则代表该数据cookie限制可能已回复
|
|
1709
|
+
|
|
1710
|
+
清除`User`表中该类cookie的status, 令其重新为`None`
|
|
1711
|
+
"""
|
|
1712
|
+
sql = (
|
|
1713
|
+
update(user)
|
|
1714
|
+
.where(and_(user.status == "limit30"))
|
|
1715
|
+
.values(status=None)
|
|
1716
|
+
.execution_options(synchronize_session="fetch")
|
|
1717
|
+
)
|
|
1718
|
+
empty_sql = delete(cls)
|
|
1719
|
+
await session.execute(sql)
|
|
1720
|
+
await session.execute(empty_sql)
|
|
1721
|
+
return True
|
|
1722
|
+
|
|
1723
|
+
@classmethod
|
|
1724
|
+
@with_session
|
|
1725
|
+
async def refresh_cache(cls, session: AsyncSession, uid: str, game_name: Optional[str] = None) -> bool:
|
|
1726
|
+
"""删除指定`uid`的数据行"""
|
|
1727
|
+
await session.execute(delete(cls).where(getattr(cls, cls.get_gameid_name(game_name)) == uid))
|
|
1728
|
+
return True
|
|
1729
|
+
|
|
1730
|
+
@classmethod
|
|
1731
|
+
@with_session
|
|
1732
|
+
async def insert_cache_data(cls, session: AsyncSession, cookie: str, **data) -> bool:
|
|
1733
|
+
"""新增指定`cookie`的数据行, `**data`为数据"""
|
|
1734
|
+
new_data = cls(cookie=cookie, **data)
|
|
1735
|
+
session.add(new_data)
|
|
1736
|
+
return True
|
|
1737
|
+
|
|
1738
|
+
|
|
1739
|
+
class Push(BaseBotIDModel):
|
|
1740
|
+
@classmethod
|
|
1741
|
+
@with_session
|
|
1742
|
+
async def select_data_by_uid(
|
|
1743
|
+
cls: Type[T_Push],
|
|
1744
|
+
session: AsyncSession,
|
|
1745
|
+
uid: str,
|
|
1746
|
+
game_name: Optional[str] = None,
|
|
1747
|
+
) -> Optional[T_Push]:
|
|
1748
|
+
"""📝简单介绍:
|
|
1749
|
+
|
|
1750
|
+
基础`Push`类的数据选择方法
|
|
1751
|
+
|
|
1752
|
+
🌱参数:
|
|
1753
|
+
|
|
1754
|
+
🔹uid (`str`):
|
|
1755
|
+
传入的用户uid, 一般是该游戏的用户唯一识别id
|
|
1756
|
+
|
|
1757
|
+
🚀使用范例:
|
|
1758
|
+
|
|
1759
|
+
`await WavesPush.select_data_by_uid(uid='100740568')`
|
|
1760
|
+
|
|
1761
|
+
✅返回值:
|
|
1762
|
+
|
|
1763
|
+
🔸`Optional[T_BaseModel]`: 选中符合条件的第一个数据,不存在则为`None`
|
|
1764
|
+
"""
|
|
1765
|
+
result = await session.execute(
|
|
1766
|
+
select(cls).where(
|
|
1767
|
+
getattr(cls, cls.get_gameid_name(game_name)) == uid,
|
|
1768
|
+
)
|
|
1769
|
+
)
|
|
1770
|
+
data = result.scalars().all()
|
|
1771
|
+
return data[0] if data else None
|