Merge branch 'Genshin-bots:master' into master
1
.gitignore
vendored
@ -666,6 +666,7 @@ config.json
|
||||
res_data
|
||||
GsData.db
|
||||
data
|
||||
core_help.jpg
|
||||
gsuid_core/plugins/*
|
||||
!gsuid_core/plugins/core_command
|
||||
!gsuid_core/plugins/gs_test.py
|
||||
|
@ -23,7 +23,7 @@ repos:
|
||||
- id: flake8
|
||||
|
||||
- repo: https://github.com/hadialqattan/pycln
|
||||
rev: v2.1.3
|
||||
rev: v2.1.5
|
||||
hooks:
|
||||
- id: pycln
|
||||
|
||||
@ -34,7 +34,7 @@ repos:
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/python-poetry/poetry
|
||||
rev: 1.4.0
|
||||
rev: 1.5.0
|
||||
hooks:
|
||||
- id: poetry-check
|
||||
- id: poetry-lock
|
||||
|
68
README.md
@ -7,14 +7,14 @@
|
||||
|
||||
[KimigaiiWuyi/GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID) 的核心部分,平台无关,支持 HTTP/WS 形式调用,便于移植到其他平台以及框架。
|
||||
|
||||
目前仍在开发中。
|
||||
**🎉[详细文档](https://docs.gsuid.gbots.work/#/)**
|
||||
|
||||
## 安装Core
|
||||
|
||||
1. git clone gsuid-core本体
|
||||
|
||||
```shell
|
||||
git clone https://ghproxy.com/https://github.com/Genshin-bots/gsuid-core.git --depth=1 --single-branch
|
||||
git clone https://ghproxy.com/https://github.com/Genshin-bots/gsuid_core.git --depth=1 --single-branch
|
||||
```
|
||||
|
||||
2. 安装poetry
|
||||
@ -26,28 +26,34 @@ pip install poetry
|
||||
3. 安装所需依赖
|
||||
|
||||
```shell
|
||||
# cd进入clone好的文件夹内
|
||||
cd gsuid_core
|
||||
# 安装依赖
|
||||
poetry install
|
||||
```
|
||||
|
||||
4. 安装所需插件
|
||||
4. 安装所需插件(可选)
|
||||
|
||||
```shell
|
||||
# 安装v4 GenshinUID
|
||||
# cd进入插件文件夹内
|
||||
cd plugins
|
||||
# 安装v4 GenshinUID
|
||||
git clone -b v4 https://ghproxy.com/https://github.com/KimigaiiWuyi/GenshinUID.git --depth=1 --single-branch
|
||||
```
|
||||
|
||||
5. 启动gsuid-core
|
||||
5. 启动gsuid_core(早柚核心)
|
||||
|
||||
```shell
|
||||
# 在gsuid_core/genshin_core文件夹内
|
||||
poetry run python core.py
|
||||
# 或者(二选一即可)
|
||||
poetry run core
|
||||
```
|
||||
|
||||
6. 链接其他适配端
|
||||
|
||||
+ 默认core将运行在`localhost:8765`端口上,如有需要可至`config.json`修改。
|
||||
+ 在支持的Bot上(例如NoneBot2、HoshinoBot),安装相应适配插件,启动Bot(如果有修改端口,则需要在启动Bot前修改适配插件相应端口),即可自动连接Core端。
|
||||
+ 在支持的Bot上(例如NoneBot2、HoshinoBot、ZeroBot、YunZaiBot等),安装相应适配插件,启动Bot(如果有修改端口,则需要在启动Bot前修改适配插件相应端口),即可自动连接Core端。
|
||||
|
||||
## Docker部署Core(可选)
|
||||
|
||||
@ -56,7 +62,15 @@ poetry run python core.py
|
||||
1. git clone gsuid-core本体
|
||||
|
||||
```shell
|
||||
git clone https://ghproxy.com/https://github.com/Genshin-bots/gsuid-core.git --depth=1 --single-branch
|
||||
git clone https://ghproxy.com/https://github.com/Genshin-bots/gsuid_core.git --depth=1 --single-branch
|
||||
```
|
||||
|
||||
2. 安装所需插件
|
||||
|
||||
```shell
|
||||
# 安装v4 GenshinUID
|
||||
cd plugins
|
||||
git clone -b v4 https://ghproxy.com/https://github.com/KimigaiiWuyi/GenshinUID.git --depth=1 --single-branch
|
||||
```
|
||||
|
||||
2. 安装所需插件
|
||||
@ -77,6 +91,42 @@ docker-compose up -d
|
||||
- 默认core将运行在`localhost:8765`端口上,Docker部署必须修改`config.json`,如`0.0.0.0:8765`
|
||||
- 如果Bot(例如NoneBot2、HoshinoBot)也是Docker部署的,Core或其插件更新后,可能需要将Core和Bot的容器都重启才生效
|
||||
|
||||
## 配置文件
|
||||
|
||||
修改`gsuid_core/gsuid_core/config.json`,参考如下
|
||||
|
||||
**(注意json不支持`#`,所以不要复制下面的配置到自己的文件中)**
|
||||
|
||||
```json
|
||||
{
|
||||
"HOST": "localhost", # 如需挂载公网修改为`0.0.0.0`
|
||||
"PORT": "8765", # core端口
|
||||
"masters": ["444835641", "111"], # Bot主人,pm为0
|
||||
"superusers": ["123456789"], # 超管,pm为1
|
||||
"sv": {
|
||||
"Core管理": {
|
||||
"priority": 5, # 某个服务的优先级
|
||||
"enabled": true, # 某个服务是否启动
|
||||
"pm": 1, # 某个服务要求的权限等级
|
||||
"black_list": [], # 某个服务的黑名单
|
||||
"area": "ALL", # 某个服务的触发范围
|
||||
"white_list": [] # 某个服务的白名单
|
||||
},
|
||||
},
|
||||
"log": {
|
||||
"level": "DEBUG" # log等级
|
||||
},
|
||||
"command_start": ["/", "*"], # core内所有插件的要求前缀
|
||||
"misfire_grace_time": 90
|
||||
}
|
||||
```
|
||||
|
||||
> 黑名单一旦设置,黑名单中的用户ID将无法访问该服务
|
||||
>
|
||||
> 白名单一旦设置,只有白名单的用户ID能访问该服务
|
||||
>
|
||||
> 服务配置可以通过[网页控制台](https://docs.gsuid.gbots.work/#/WebConsole)实时修改, 如果手动修改`config.json`需要**重启**
|
||||
|
||||
## 编写插件
|
||||
|
||||
|
||||
@ -101,7 +151,9 @@ sv=SV(
|
||||
pm=2, # 权限 0为master,1为superuser,2为群的群主&管理员,3为普通
|
||||
priority=5, # 整组服务的优先级
|
||||
enabled=True, # 是否启用
|
||||
black_list=[] # 黑名单
|
||||
area= 'ALL', # 群聊和私聊均可触发
|
||||
black_list=[], # 黑名单
|
||||
white_list=[], # 白名单
|
||||
)
|
||||
|
||||
@sv.on_prefix('测试')
|
||||
|
@ -1,3 +1,4 @@
|
||||
import random
|
||||
import asyncio
|
||||
from typing import List, Union, Literal, Optional
|
||||
|
||||
@ -7,7 +8,14 @@ from msgspec import json as msgjson
|
||||
from gsuid_core.logger import logger
|
||||
from gsuid_core.gs_logger import GsLogger
|
||||
from gsuid_core.segment import MessageSegment
|
||||
from gsuid_core.utils.image.convert import text2pic
|
||||
from gsuid_core.models import Event, Message, MessageSend
|
||||
from gsuid_core.utils.plugins_config.gs_config import core_plugins_config
|
||||
|
||||
R_enabled = core_plugins_config.get_config('AutoAddRandomText').data
|
||||
R_text = core_plugins_config.get_config('RandomText').data
|
||||
is_text2pic = core_plugins_config.get_config('AutoTextToPic').data
|
||||
text2pic_limit = core_plugins_config.get_config('TextToPicThreshold').data
|
||||
|
||||
|
||||
class _Bot:
|
||||
@ -39,13 +47,33 @@ class _Bot:
|
||||
elif isinstance(message, bytes):
|
||||
message = [MessageSegment.image(message)]
|
||||
elif isinstance(message, List):
|
||||
message = [MessageSegment.node(message)]
|
||||
if all(isinstance(x, str) for x in message):
|
||||
message = [MessageSegment.node(message)]
|
||||
|
||||
_message: List[Message] = message # type: ignore
|
||||
|
||||
if at_sender and sender_id:
|
||||
message.append(MessageSegment.at(sender_id))
|
||||
_message.append(MessageSegment.at(sender_id))
|
||||
|
||||
if R_enabled:
|
||||
result = ''.join(
|
||||
random.choice(R_text)
|
||||
for _ in range(random.randint(1, len(R_text)))
|
||||
)
|
||||
_message.append(MessageSegment.text(result))
|
||||
|
||||
if is_text2pic:
|
||||
if (
|
||||
len(_message) == 1
|
||||
and _message[0].type == 'text'
|
||||
and isinstance(_message[0].data, str)
|
||||
and len(_message[0].data) >= int(text2pic_limit)
|
||||
):
|
||||
img = await text2pic(_message[0].data)
|
||||
_message = [MessageSegment.image(img)]
|
||||
|
||||
send = MessageSend(
|
||||
content=message,
|
||||
content=_message,
|
||||
bot_id=bot_id,
|
||||
bot_self_id=bot_self_id,
|
||||
target_type=target_type,
|
||||
|
@ -1,16 +1,19 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import List, Union, Optional
|
||||
|
||||
gs_data_path = Path(__file__).parents[1] / 'data'
|
||||
|
||||
|
||||
def get_res_path(_path: Optional[str] = None) -> Path:
|
||||
def get_res_path(_path: Optional[Union[str, List]] = None) -> Path:
|
||||
if _path:
|
||||
path = gs_data_path / _path
|
||||
if isinstance(_path, str):
|
||||
path = gs_data_path / _path
|
||||
else:
|
||||
path = gs_data_path.joinpath(*_path)
|
||||
else:
|
||||
path = gs_data_path
|
||||
|
||||
if not path.exists():
|
||||
path.mkdir()
|
||||
path.mkdir(parents=True)
|
||||
|
||||
return path
|
||||
|
@ -1,13 +1,16 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
|
||||
from gsuid_core.aps import scheduler
|
||||
from gsuid_core.logger import logger
|
||||
from gsuid_core.server import GsServer
|
||||
from gsuid_core.help.draw_help import get_help_img
|
||||
|
||||
gss = GsServer()
|
||||
if not gss.is_load:
|
||||
gss.is_load = True
|
||||
gss.load_plugins()
|
||||
asyncio.run(get_help_img())
|
||||
|
||||
repeat_jobs = {}
|
||||
for i in scheduler.get_jobs():
|
||||
|
@ -20,7 +20,7 @@ async def get_user_pml(msg: MessageReceive) -> int:
|
||||
elif msg.user_id in config_superusers:
|
||||
return 1
|
||||
else:
|
||||
return msg.user_pm
|
||||
return msg.user_pm if msg.user_pm >= 1 else 2
|
||||
|
||||
|
||||
async def msg_process(msg: MessageReceive) -> Event:
|
||||
|
197
gsuid_core/help/draw_help.py
Normal file
@ -0,0 +1,197 @@
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Union, Optional
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from gsuid_core.sv import SV
|
||||
|
||||
# from gsuid_core.utils.image.image_tools import get_color_bg
|
||||
from gsuid_core.utils.fonts.fonts import core_font
|
||||
|
||||
TEXT_PATH = Path(__file__).parent / 'texture2d'
|
||||
CORE_HELP_IMG = Path(__file__).parent / 'core_help.jpg'
|
||||
|
||||
plugin_title = 92
|
||||
sv_title = 67
|
||||
|
||||
tag_color = {
|
||||
'prefix': (137, 228, 124),
|
||||
'suffix': (124, 180, 228),
|
||||
'file': (190, 228, 217),
|
||||
'keyword': (217, 228, 254),
|
||||
'fullmatch': (228, 124, 124),
|
||||
'regex': (225, 228, 124),
|
||||
'command': (228, 124, 124),
|
||||
'other': (228, 190, 191),
|
||||
}
|
||||
|
||||
tag_text: Dict[str, str] = {
|
||||
'prefix': '前缀',
|
||||
'suffix': '后缀',
|
||||
'file': '文件',
|
||||
'keyword': '包含',
|
||||
'fullmatch': '完全',
|
||||
'regex': '正则',
|
||||
'command': '命令',
|
||||
'other': '其他',
|
||||
}
|
||||
|
||||
tags: Dict[str, Optional[Image.Image]] = {
|
||||
'prefix': None,
|
||||
'suffix': None,
|
||||
'file': None,
|
||||
'keyword': None,
|
||||
'fullmatch': None,
|
||||
'regex': None,
|
||||
'command': None,
|
||||
'other': None,
|
||||
}
|
||||
|
||||
|
||||
def get_tag(tag_type: str) -> Image.Image:
|
||||
cache = tags[tag_type]
|
||||
if cache is not None:
|
||||
return cache
|
||||
text = tag_text[tag_type]
|
||||
tag = Image.new('RGBA', (60, 40))
|
||||
tag_draw = ImageDraw.Draw(tag)
|
||||
tag_draw.rounded_rectangle((7, 5, 53, 35), 10, tag_color[tag_type])
|
||||
tag_draw.text((30, 20), text, (62, 62, 62), core_font(22), 'mm')
|
||||
tags[tag_type] = tag
|
||||
return tag
|
||||
|
||||
|
||||
def get_command_bg(command: str, tag_type: str):
|
||||
img = Image.new('RGBA', (220, 40))
|
||||
img_draw = ImageDraw.Draw(img)
|
||||
img_draw.rounded_rectangle((6, 5, 160, 35), 10, (230, 202, 167))
|
||||
img_draw.text((83, 20), command, (62, 62, 62), core_font(20), 'mm')
|
||||
tag = get_tag(tag_type)
|
||||
img.paste(tag, (160, 0), tag)
|
||||
return img
|
||||
|
||||
|
||||
def _c(data: Union[int, str, bool]) -> Tuple[int, int, int]:
|
||||
gray_color = (184, 184, 184)
|
||||
|
||||
if isinstance(data, bool):
|
||||
color = tag_color['prefix'] if data else gray_color
|
||||
elif isinstance(data, str):
|
||||
color = (
|
||||
tag_color['prefix']
|
||||
if data == 'ALL'
|
||||
else tag_color['command']
|
||||
if data == 'GROUP'
|
||||
else tag_color['file']
|
||||
)
|
||||
else:
|
||||
colors = list(tag_color.values())
|
||||
if data <= len(colors) and data >= 0:
|
||||
color = colors[data]
|
||||
else:
|
||||
color = tag_color['other']
|
||||
return color
|
||||
|
||||
|
||||
def _t(data: Union[int, str, bool]) -> str:
|
||||
if isinstance(data, bool):
|
||||
text = '开启' if data else '关闭'
|
||||
elif isinstance(data, str):
|
||||
text = '不限' if data == 'ALL' else '群聊' if data == 'GROUP' else '私聊'
|
||||
else:
|
||||
texts = ['主人', '超管', '群主', '管理', '频管', '子管', '正常', '低', '黑']
|
||||
if data <= len(texts) and data >= 0:
|
||||
text = ['主人', '超管', '群主', '管理', '频管', '子管', '正常', '低', '黑'][data]
|
||||
else:
|
||||
text = '最低'
|
||||
return text
|
||||
|
||||
|
||||
def get_plugin_bg(plugin_name: str, sv_list: List[SV]):
|
||||
img_list: List[Image.Image] = []
|
||||
|
||||
for sv in sv_list:
|
||||
sv_img = Image.new(
|
||||
'RGBA',
|
||||
(
|
||||
900,
|
||||
sv_title + ((len(sv.TL) + 3) // 4) * 40,
|
||||
),
|
||||
)
|
||||
sv_img_draw = ImageDraw.Draw(sv_img)
|
||||
for index, trigger_name in enumerate(sv.TL):
|
||||
tg_img = get_command_bg(trigger_name, sv.TL[trigger_name].type)
|
||||
sv_img.paste(
|
||||
tg_img, (6 + 220 * (index % 4), 67 + 40 * (index // 4)), tg_img
|
||||
)
|
||||
|
||||
sv_img_draw.rounded_rectangle((15, 19, 25, 50), 10, (62, 62, 62))
|
||||
sv_img_draw.text((45, 31), sv.name, (62, 62, 62), core_font(36), 'lm')
|
||||
|
||||
sv_img_draw.rounded_rectangle((710, 15, 760, 50), 10, _c(sv.enabled))
|
||||
sv_img_draw.rounded_rectangle((770, 15, 820, 50), 10, _c(sv.pm))
|
||||
sv_img_draw.rounded_rectangle((830, 15, 880, 50), 10, _c(sv.area))
|
||||
|
||||
sv_img_draw.text(
|
||||
(735, 32), _t(sv.enabled), (62, 62, 62), core_font(22), 'mm'
|
||||
)
|
||||
sv_img_draw.text(
|
||||
(795, 32), _t(sv.pm), (62, 62, 62), core_font(22), 'mm'
|
||||
)
|
||||
sv_img_draw.text(
|
||||
(855, 32), _t(sv.area), (62, 62, 62), core_font(22), 'mm'
|
||||
)
|
||||
img_list.append(sv_img)
|
||||
|
||||
img = Image.new(
|
||||
'RGBA',
|
||||
(
|
||||
900,
|
||||
plugin_title + sum([i.size[1] for i in img_list]),
|
||||
),
|
||||
)
|
||||
img_draw = ImageDraw.Draw(img)
|
||||
img_draw.rounded_rectangle((10, 26, 890, 76), 10, (230, 202, 167))
|
||||
img_draw.text((450, 51), plugin_name, (62, 62, 62), core_font(42), 'mm')
|
||||
|
||||
temp = 0
|
||||
for _img in img_list:
|
||||
img.paste(_img, (0, 92 + temp), _img)
|
||||
temp += _img.size[1]
|
||||
|
||||
return img
|
||||
|
||||
|
||||
async def get_help_img() -> Image.Image:
|
||||
from gsuid_core.sv import SL
|
||||
|
||||
content = SL.detail_lst
|
||||
img_list: List[Image.Image] = []
|
||||
for plugin_name in content:
|
||||
plugin_img = get_plugin_bg(plugin_name, content[plugin_name])
|
||||
img_list.append(plugin_img)
|
||||
|
||||
x = 900
|
||||
y = 200 + sum([i.size[1] for i in img_list])
|
||||
# img = await get_color_bg(x, y)
|
||||
img = Image.new('RGBA', (x, y), (255, 255, 255))
|
||||
title = Image.open(TEXT_PATH / 'title.png')
|
||||
|
||||
# white = Image.new('RGBA', img.size, (255, 255, 255, 120))
|
||||
# img.paste(white, (0, 0), white)
|
||||
img.paste(title, (0, 50), title)
|
||||
|
||||
temp = 0
|
||||
for _img in img_list:
|
||||
img.paste(_img, (0, 340 + temp), _img)
|
||||
temp += _img.size[1]
|
||||
|
||||
img = img.convert('RGB')
|
||||
img.save(
|
||||
CORE_HELP_IMG,
|
||||
format='JPEG',
|
||||
quality=80,
|
||||
subsampling=0,
|
||||
)
|
||||
|
||||
return img
|
107
gsuid_core/help/draw_plugin_help.py
Normal file
@ -0,0 +1,107 @@
|
||||
from copy import deepcopy
|
||||
from typing import Dict, List, Tuple, Callable, Optional
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from gsuid_core.data_store import get_res_path
|
||||
from gsuid_core.utils.image.convert import convert_img
|
||||
from gsuid_core.utils.image.image_tools import (
|
||||
crop_center_img,
|
||||
easy_alpha_composite,
|
||||
)
|
||||
|
||||
from .model import PluginHelp
|
||||
|
||||
cache: Dict[str, int] = {}
|
||||
|
||||
|
||||
async def get_help(
|
||||
name: str,
|
||||
sub_text: str,
|
||||
help_data: Dict[str, PluginHelp],
|
||||
bg: Image.Image,
|
||||
icon: Image.Image,
|
||||
badge: Image.Image,
|
||||
banner: Image.Image,
|
||||
button: Image.Image,
|
||||
font: Callable[[int], ImageFont.FreeTypeFont],
|
||||
is_dark: bool = True,
|
||||
text_color: Tuple[int, int, int] = (250, 250, 250),
|
||||
sub_color: Optional[Tuple[int, int, int]] = None,
|
||||
) -> bytes:
|
||||
help_path = get_res_path('help') / f'{name}.jpg'
|
||||
|
||||
if help_path.exists() and name in cache and cache[name]:
|
||||
return await convert_img(Image.open(help_path))
|
||||
|
||||
if sub_color is None and is_dark:
|
||||
sub_color = tuple(x - 50 for x in text_color if x > 50)
|
||||
elif sub_color is None and not is_dark:
|
||||
sub_color = tuple(x + 50 for x in text_color if x < 205)
|
||||
|
||||
title = Image.new('RGBA', (900, 600))
|
||||
icon = icon.resize((300, 300))
|
||||
title.paste(icon, (300, 89), icon)
|
||||
title.paste(badge, (0, 390), badge)
|
||||
badge_s = badge.resize((720, 80))
|
||||
title.paste(badge_s, (90, 480), badge_s)
|
||||
title_draw = ImageDraw.Draw(title)
|
||||
|
||||
title_draw.text((450, 440), f'{name} 帮助', text_color, font(36), 'mm')
|
||||
title_draw.text((450, 520), sub_text, sub_color, font(26), 'mm')
|
||||
|
||||
w, h = 900, 630
|
||||
|
||||
sv_img_list: List[Image.Image] = []
|
||||
for sv_name in help_data:
|
||||
tr_size = len(help_data[sv_name]['data'])
|
||||
y = 100 + ((tr_size + 3) // 4) * 80
|
||||
h += y
|
||||
sv_img = Image.new('RGBA', (900, y))
|
||||
sv_data = help_data[sv_name]['data']
|
||||
sv_desc = help_data[sv_name]['desc']
|
||||
|
||||
bc = deepcopy(banner)
|
||||
bc_draw = ImageDraw.Draw(bc)
|
||||
bc_draw.text((30, 25), sv_name, text_color, font(35), 'lm')
|
||||
size, _ = font(35).getsize(sv_name)
|
||||
bc_draw.text((42 + size, 30), sv_desc, sub_color, font(20), 'lm')
|
||||
sv_img = easy_alpha_composite(sv_img, bc, (0, 10))
|
||||
# sv_img.paste(bc, (0, 10), bc)
|
||||
|
||||
for index, tr in enumerate(sv_data):
|
||||
bt = deepcopy(button)
|
||||
bt_draw = ImageDraw.Draw(bt)
|
||||
if len(tr['name']) > 8:
|
||||
tr_name = tr['name'][:5] + '..'
|
||||
else:
|
||||
tr_name = tr['name']
|
||||
|
||||
bt_draw.text((105, 28), tr_name, text_color, font(26), 'mm')
|
||||
bt_draw.text((105, 51), tr['eg'], sub_color, font(17), 'mm')
|
||||
offset_x = 210 * (index % 4)
|
||||
offset_y = 80 * (index // 4)
|
||||
sv_img = easy_alpha_composite(
|
||||
sv_img, bt, (26 + offset_x, 83 + offset_y)
|
||||
)
|
||||
# sv_img.paste(bt, (26 + offset_x, 83 + offset_y), bt)
|
||||
|
||||
sv_img_list.append(sv_img)
|
||||
|
||||
img = crop_center_img(bg, w, h)
|
||||
img.paste(title, (0, 0), title)
|
||||
temp = 0
|
||||
for _sm in sv_img_list:
|
||||
img.paste(_sm, (0, 600 + temp), _sm)
|
||||
temp += _sm.size[1]
|
||||
|
||||
img = img.convert('RGB')
|
||||
help_path = get_res_path('help') / f'{name}.jpg'
|
||||
img.save(
|
||||
help_path,
|
||||
'JPEG',
|
||||
quality=85,
|
||||
subsampling=0,
|
||||
)
|
||||
cache[name] = 1
|
||||
return await convert_img(img)
|
15
gsuid_core/help/model.py
Normal file
@ -0,0 +1,15 @@
|
||||
from typing import List, TypedDict
|
||||
|
||||
|
||||
class PluginSV(TypedDict):
|
||||
name: str
|
||||
desc: str
|
||||
eg: str
|
||||
need_ck: bool
|
||||
need_sk: bool
|
||||
need_admin: bool
|
||||
|
||||
|
||||
class PluginHelp(TypedDict):
|
||||
desc: str
|
||||
data: List[PluginSV]
|
BIN
gsuid_core/help/texture2d/title.png
Normal file
After Width: | Height: | Size: 62 KiB |
@ -1,8 +1,9 @@
|
||||
from gsuid_core.aps import scheduler
|
||||
from gsuid_core.logger import logger
|
||||
from gsuid_core.utils.plugins_update._plugins import update_all_plugins
|
||||
from gsuid_core.utils.plugins_config.gs_config import core_plugins_config
|
||||
|
||||
from .auto_task import update_core, restart_core, update_all_plugins
|
||||
from .auto_task import update_core, restart_core
|
||||
|
||||
config = core_plugins_config
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
from typing import List
|
||||
|
||||
from gsuid_core.utils.plugins_update.api import PLUGINS_PATH
|
||||
from gsuid_core.utils.plugins_update._plugins import update_from_git
|
||||
from gsuid_core.plugins.core_command.core_restart.restart import (
|
||||
restart_genshinuid,
|
||||
@ -11,12 +10,5 @@ async def update_core() -> List[str]:
|
||||
return update_from_git()
|
||||
|
||||
|
||||
async def update_all_plugins() -> List[str]:
|
||||
log_list = []
|
||||
for plugin in PLUGINS_PATH.iterdir():
|
||||
log_list.extend(update_from_git(0, plugin))
|
||||
return log_list
|
||||
|
||||
|
||||
async def restart_core():
|
||||
await restart_genshinuid('', '', '', False)
|
||||
|
20
gsuid_core/plugins/core_command/core_help/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
from gsuid_core.sv import SV
|
||||
from gsuid_core.bot import Bot
|
||||
from gsuid_core.models import Event
|
||||
from gsuid_core.logger import logger
|
||||
from gsuid_core.utils.image.convert import convert_img
|
||||
from gsuid_core.help.draw_help import CORE_HELP_IMG, get_help_img
|
||||
|
||||
sv_core_help_img = SV('Core帮助')
|
||||
|
||||
|
||||
@sv_core_help_img.on_fullmatch(('core帮助', 'Core帮助'))
|
||||
async def send_core_htlp_msg(bot: Bot, ev: Event):
|
||||
logger.info('[早柚核心] 开始执行[帮助图]')
|
||||
if CORE_HELP_IMG.exists():
|
||||
img = await convert_img(CORE_HELP_IMG)
|
||||
else:
|
||||
img = await get_help_img()
|
||||
img = await convert_img(img)
|
||||
logger.info('[早柚核心] 帮助图获取成功!')
|
||||
await bot.send(img)
|
@ -52,7 +52,7 @@ async def send_restart_msg(bot: Bot, ev: Event):
|
||||
else:
|
||||
send_id = ev.user_id
|
||||
send_type = 'direct'
|
||||
await bot.send('正在执行[gs重启]...')
|
||||
await bot.send('正在执行[core重启]...')
|
||||
await restart_genshinuid(bot.bot_id, send_type, str(send_id))
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@ from pathlib import Path
|
||||
|
||||
from gsuid_core.utils.plugins_config.gs_config import core_plugins_config
|
||||
|
||||
bot_start = Path(__file__).parents[4] / 'core.py'
|
||||
bot_start = Path(__file__).parents[3] / 'core.py'
|
||||
restart_sh_path = Path().cwd() / 'gs_restart.sh'
|
||||
update_log_path = Path(__file__).parent / 'update_log.json'
|
||||
|
||||
|
25
gsuid_core/plugins/core_command/core_update/__init__.py
Normal file
@ -0,0 +1,25 @@
|
||||
from gsuid_core.sv import SV
|
||||
from gsuid_core.bot import Bot
|
||||
from gsuid_core.models import Event
|
||||
from gsuid_core.logger import logger
|
||||
from gsuid_core.utils.plugins_update._plugins import (
|
||||
update_from_git,
|
||||
update_all_plugins,
|
||||
)
|
||||
|
||||
sv_core_config = SV('Core管理', pm=0)
|
||||
|
||||
|
||||
@sv_core_config.on_fullmatch(('core更新'))
|
||||
async def send_core_update_msg(bot: Bot, ev: Event):
|
||||
logger.info('开始执行[更新] 早柚核心')
|
||||
log_list = update_from_git()
|
||||
await bot.send(log_list)
|
||||
|
||||
|
||||
@sv_core_config.on_fullmatch(('core全部更新'))
|
||||
async def send_core_all_update_msg(bot: Bot, ev: Event):
|
||||
logger.info('开始执行[更新] 全部更新')
|
||||
log_list = update_from_git()
|
||||
log_list.extend(await update_all_plugins())
|
||||
await bot.send(log_list)
|
15
gsuid_core/plugins/core_command/core_user/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
from gsuid_core.sv import SV
|
||||
from gsuid_core.bot import Bot
|
||||
from gsuid_core.models import Event
|
||||
|
||||
from .draw_user_card import get_user_card
|
||||
|
||||
core_user_info = SV('core用户信息')
|
||||
|
||||
|
||||
@core_user_info.on_fullmatch(('绑定信息'))
|
||||
async def send_bind_card(bot: Bot, ev: Event):
|
||||
await bot.logger.info('开始执行[查询用户绑定状态]')
|
||||
im = await get_user_card(ev.bot_id, ev.user_id)
|
||||
await bot.logger.info('[查询用户绑定状态]完成!等待图片发送中...')
|
||||
await bot.send(im)
|
133
gsuid_core/plugins/core_command/core_user/draw_user_card.py
Normal file
@ -0,0 +1,133 @@
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Union, Optional
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from gsuid_core.utils.database.api import DBSqla
|
||||
from gsuid_core.utils.fonts.fonts import core_font
|
||||
from gsuid_core.utils.database.models import GsPush
|
||||
from gsuid_core.utils.image.convert import convert_img
|
||||
from gsuid_core.utils.image.image_tools import (
|
||||
get_color_bg,
|
||||
get_qq_avatar,
|
||||
draw_pic_with_ring,
|
||||
easy_alpha_composite,
|
||||
)
|
||||
|
||||
TEXT_PATH = Path(__file__).parent / 'texture2d'
|
||||
|
||||
status_off = Image.open(TEXT_PATH / 'status_off.png')
|
||||
status_on = Image.open(TEXT_PATH / 'status_on.png')
|
||||
|
||||
EN_MAP = {'coin': '宝钱', 'resin': '体力', 'go': '派遣', 'transform': '质变仪'}
|
||||
|
||||
|
||||
async def get_user_card(bot_id: str, user_id: str) -> Union[bytes, str]:
|
||||
get_sqla = DBSqla().get_sqla
|
||||
sqla = get_sqla(bot_id)
|
||||
uid_list: List = await sqla.get_bind_uid_list(user_id)
|
||||
sr_uid_list = await sqla.get_bind_sruid_list(user_id)
|
||||
user_list = await sqla.select_user_all_data_by_user_id(user_id)
|
||||
|
||||
if user_list is None:
|
||||
return '你还没有绑定过UID或者CK!'
|
||||
|
||||
w, h = 750, len(max(uid_list, sr_uid_list)) * 900 + 470
|
||||
|
||||
# 获取背景图片各项参数
|
||||
_id = str(user_id)
|
||||
if _id.startswith('http'):
|
||||
char_pic = await get_qq_avatar(avatar_url=_id)
|
||||
else:
|
||||
char_pic = await get_qq_avatar(qid=_id)
|
||||
char_pic = await draw_pic_with_ring(char_pic, 290)
|
||||
|
||||
img = await get_color_bg(w, h)
|
||||
img_mask = Image.new('RGBA', img.size, (255, 255, 255))
|
||||
title = Image.open(TEXT_PATH / 'user_title.png')
|
||||
title.paste(char_pic, (241, 40), char_pic)
|
||||
|
||||
title_draw = ImageDraw.Draw(title)
|
||||
title_draw.text(
|
||||
(375, 444), f'{bot_id} - {user_id}', (29, 29, 29), core_font(30), 'mm'
|
||||
)
|
||||
img.paste(title, (0, 0), title)
|
||||
|
||||
for index, user_data in enumerate(user_list):
|
||||
user_card = Image.open(TEXT_PATH / 'user_bg.png')
|
||||
user_draw = ImageDraw.Draw(user_card)
|
||||
|
||||
if user_data.uid is not None and user_data.uid != '0':
|
||||
uid_text = f'原神UID {user_data.uid}'
|
||||
user_push_data = await sqla.select_push_data(user_data.uid)
|
||||
else:
|
||||
uid_text = '未发现原神UID'
|
||||
user_push_data = GsPush(bot_id='TEMP')
|
||||
|
||||
user_draw.text(
|
||||
(375, 58),
|
||||
uid_text,
|
||||
(29, 29, 29),
|
||||
font=core_font(36),
|
||||
anchor='mm',
|
||||
)
|
||||
|
||||
if user_data.sr_uid:
|
||||
sruid_text = f'星铁UID {user_data.sr_uid}'
|
||||
else:
|
||||
sruid_text = '未发现星铁UID'
|
||||
|
||||
user_draw.text(
|
||||
(375, 119),
|
||||
sruid_text,
|
||||
(29, 29, 29),
|
||||
font=core_font(36),
|
||||
anchor='mm',
|
||||
)
|
||||
|
||||
x, y = 331, 112
|
||||
b = 175
|
||||
paste_switch(user_card, user_data.cookie, (241, b))
|
||||
paste_switch(user_card, user_data.stoken, (241 + x, b))
|
||||
paste_switch(user_card, user_data.sign_switch, (241, b + y))
|
||||
paste_switch(user_card, user_data.bbs_switch, (241 + x, b + y))
|
||||
paste_switch(user_card, user_data.push_switch, (241, b + 2 * y))
|
||||
paste_switch(user_card, user_data.status, (241 + x, b + 2 * y), True)
|
||||
|
||||
for _index, mode in enumerate(['coin', 'resin', 'go', 'transform']):
|
||||
paste_switch(
|
||||
user_card,
|
||||
getattr(user_push_data, f'{mode}_push'),
|
||||
(241 + _index % 2 * x, b + (_index // 2 + 3) * y),
|
||||
)
|
||||
if getattr(user_push_data, f'{mode}_push') != 'off':
|
||||
user_draw.text(
|
||||
(268 + _index % 2 * x, 168 + 47 + (_index // 2 + 3) * y),
|
||||
f'{getattr(user_push_data, f"{mode}_value")}',
|
||||
(35, 35, 35),
|
||||
font=core_font(15),
|
||||
anchor='lm',
|
||||
)
|
||||
|
||||
sr_sign = user_data.sr_sign_switch
|
||||
sr_push = user_data.sr_push_switch
|
||||
paste_switch(user_card, sr_sign, (241, b + 5 * y))
|
||||
paste_switch(user_card, sr_push, (241 + x, b + 5 * y))
|
||||
|
||||
img.paste(user_card, (0, 500 + index * 870), user_card)
|
||||
|
||||
img = easy_alpha_composite(img_mask, img, (0, 0))
|
||||
return await convert_img(img)
|
||||
|
||||
|
||||
def paste_switch(
|
||||
card: Image.Image,
|
||||
status: Optional[str],
|
||||
pos: Tuple[int, int],
|
||||
is_status: bool = False,
|
||||
):
|
||||
if is_status:
|
||||
pic = status_off if status else status_on
|
||||
else:
|
||||
pic = status_on if status != 'off' and status else status_off
|
||||
card.paste(pic, pos, pic)
|
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
BIN
gsuid_core/plugins/core_command/core_user/texture2d/user_bg.png
Normal file
After Width: | Height: | Size: 194 KiB |
After Width: | Height: | Size: 8.7 KiB |
@ -25,6 +25,8 @@ class MessageSegment:
|
||||
with open(str(img), 'rb') as fp:
|
||||
img = fp.read()
|
||||
else:
|
||||
if img.startswith('http'):
|
||||
return Message(type='image', data=f'link://{img}')
|
||||
if img.startswith('base64://'):
|
||||
return Message(type='image', data=img)
|
||||
with open(img, 'rb') as fp:
|
||||
@ -53,6 +55,10 @@ class MessageSegment:
|
||||
else:
|
||||
if msg.startswith('base64://'):
|
||||
msg_list.append(Message(type='image', data=msg))
|
||||
elif msg.startswith('http'):
|
||||
msg_list.append(
|
||||
Message(type='image', data=f'link://{msg}')
|
||||
)
|
||||
else:
|
||||
msg_list.append(MessageSegment.text(msg))
|
||||
return Message(type='node', data=msg_list)
|
||||
@ -79,8 +85,15 @@ class MessageSegment:
|
||||
elif isinstance(content, bytes):
|
||||
file = content
|
||||
else:
|
||||
with open(content, 'rb') as fp:
|
||||
file = fp.read()
|
||||
if content.startswith('http'):
|
||||
link = content
|
||||
return Message(
|
||||
type='file',
|
||||
data=f'{file_name}|link://{link}',
|
||||
)
|
||||
else:
|
||||
with open(content, 'rb') as fp:
|
||||
file = fp.read()
|
||||
return Message(
|
||||
type='file',
|
||||
data=f'{file_name}|{b64encode(file).decode()}',
|
||||
|
@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from functools import wraps
|
||||
from typing import Dict, List, Tuple, Union, Literal, Callable, Optional
|
||||
|
||||
@ -11,6 +13,7 @@ from gsuid_core.config import core_config
|
||||
class SVList:
|
||||
def __init__(self):
|
||||
self.lst: Dict[str, SV] = {}
|
||||
self.detail_lst: Dict[str, List[SV]] = {}
|
||||
|
||||
@property
|
||||
def get_lst(self):
|
||||
@ -58,6 +61,16 @@ class SV:
|
||||
# sv内包含的触发器
|
||||
self.TL: Dict[str, Trigger] = {}
|
||||
self.is_initialized = True
|
||||
stack = traceback.extract_stack()
|
||||
file = stack[-2].filename
|
||||
path = Path(file)
|
||||
parts = path.parts
|
||||
i = parts.index('plugins')
|
||||
plugins_name = parts[i + 1]
|
||||
if plugins_name not in SL.detail_lst:
|
||||
SL.detail_lst[plugins_name] = [self]
|
||||
else:
|
||||
SL.detail_lst[plugins_name].append(self)
|
||||
|
||||
# 判断sv是否已持久化
|
||||
if name in config_sv:
|
||||
|
65
gsuid_core/tools/gen_help.py
Normal file
@ -0,0 +1,65 @@
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from openpyxl import load_workbook
|
||||
|
||||
sample = {
|
||||
'name': '',
|
||||
'desc': '',
|
||||
'eg': '',
|
||||
'need_ck': False,
|
||||
'need_sk': False,
|
||||
'need_admin': False,
|
||||
}
|
||||
|
||||
result = {}
|
||||
|
||||
HELP_PATH = Path(__file__).parent / 'Help.xlsx'
|
||||
OUTPUT_PATH = Path(__file__).parent / 'Help.json'
|
||||
|
||||
wb = load_workbook(str(HELP_PATH))
|
||||
ws = wb.active
|
||||
|
||||
module_name_str = ''
|
||||
for row in range(2, 999):
|
||||
# 跳过空白行
|
||||
if not ws.cell(row, 2).value:
|
||||
continue
|
||||
|
||||
_sample = copy.deepcopy(sample)
|
||||
|
||||
# 将第一列读取为模块名
|
||||
if ws.cell(row, 1):
|
||||
if ws.cell(row, 1).value is not None:
|
||||
module_name_str = ws.cell(row, 1).value
|
||||
|
||||
# if module_name_str is None and not isinstance(module_name_str, str):
|
||||
# continue
|
||||
|
||||
# 第二列为功能名
|
||||
_sample['name'] = ws.cell(row, 2).value
|
||||
# 第三列为详细信息
|
||||
_sample['desc'] = ws.cell(row, 3).value
|
||||
# 第四列为使用例
|
||||
_sample['eg'] = ws.cell(row, 4).value
|
||||
|
||||
if ws.cell(row, 5).value == '是':
|
||||
_sample['need_ck'] = True
|
||||
|
||||
if ws.cell(row, 6).value == '是':
|
||||
_sample['need_sk'] = True
|
||||
|
||||
if ws.cell(row, 7).value == '是':
|
||||
_sample['need_admin'] = True
|
||||
|
||||
if isinstance(module_name_str, str):
|
||||
module_name = module_name_str.split(' | ')[0]
|
||||
module_desc = module_name_str.split(' | ')[1]
|
||||
if module_name not in result:
|
||||
result[module_name] = {'desc': module_desc, 'data': []}
|
||||
|
||||
result[module_name]['data'].append(_sample)
|
||||
|
||||
with open(OUTPUT_PATH, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, indent=2, ensure_ascii=False)
|
@ -134,4 +134,6 @@ CALENDAR_URL = f'{DRAW_BASE_URL}/calendar'
|
||||
RECEIVE_URL = f'{DRAW_BASE_URL}/post_my_draw'
|
||||
BS_INDEX_URL = f'{DRAW_BASE_URL}/index'
|
||||
|
||||
GET_FP_URL = 'https://public-data-api.mihoyo.com/device-fp/api/getFp'
|
||||
|
||||
_API = locals()
|
||||
|
@ -761,20 +761,24 @@ class RoleCalendar(TypedDict):
|
||||
is_subscribe: bool
|
||||
|
||||
|
||||
class RoleCalendarList(TypedDict):
|
||||
calendar_role: List[RoleCalendar]
|
||||
|
||||
|
||||
MonthlyRoleCalendar = TypedDict(
|
||||
'MonthlyRoleCalendar',
|
||||
{
|
||||
'1': List[RoleCalendar],
|
||||
'2': List[RoleCalendar],
|
||||
'3': List[RoleCalendar],
|
||||
'4': List[RoleCalendar],
|
||||
'5': List[RoleCalendar],
|
||||
'6': List[RoleCalendar],
|
||||
'7': List[RoleCalendar],
|
||||
'8': List[RoleCalendar],
|
||||
'9': List[RoleCalendar],
|
||||
'10': List[RoleCalendar],
|
||||
'11': List[RoleCalendar],
|
||||
'12': List[RoleCalendar],
|
||||
'1': RoleCalendarList,
|
||||
'2': RoleCalendarList,
|
||||
'3': RoleCalendarList,
|
||||
'4': RoleCalendarList,
|
||||
'5': RoleCalendarList,
|
||||
'6': RoleCalendarList,
|
||||
'7': RoleCalendarList,
|
||||
'8': RoleCalendarList,
|
||||
'9': RoleCalendarList,
|
||||
'10': RoleCalendarList,
|
||||
'11': RoleCalendarList,
|
||||
'12': RoleCalendarList,
|
||||
},
|
||||
)
|
||||
|
@ -9,11 +9,12 @@ import uuid
|
||||
import random
|
||||
from abc import abstractmethod
|
||||
from string import digits, ascii_letters
|
||||
from typing import Any, Dict, List, Union, Literal, Optional, cast
|
||||
from typing import Any, Dict, List, Tuple, Union, Literal, Optional, cast
|
||||
|
||||
from aiohttp import TCPConnector, ClientSession, ContentTypeError
|
||||
|
||||
from gsuid_core.logger import logger
|
||||
from gsuid_core.utils.database.api import DBSqla
|
||||
from gsuid_core.utils.plugins_config.gs_config import core_plugins_config
|
||||
|
||||
from .api import _API
|
||||
@ -87,13 +88,17 @@ class BaseMysApi:
|
||||
MAPI = _API
|
||||
is_sr = False
|
||||
RECOGNIZE_SERVER = RECOGNIZE_SERVER
|
||||
chs = {}
|
||||
dbsqla: DBSqla = DBSqla()
|
||||
|
||||
@abstractmethod
|
||||
async def _upass(self, header: Dict):
|
||||
async def _upass(self, header: Dict) -> str:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def _pass(self, gt: str, ch: str, header: Dict):
|
||||
async def _pass(
|
||||
self, gt: str, ch: str, header: Dict
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
@ -106,6 +111,59 @@ class BaseMysApi:
|
||||
async def get_stoken(self, uid: str) -> Optional[str]:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def get_user_fp(self, uid: str) -> Optional[str]:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def get_user_device_id(self, uid: str) -> Optional[str]:
|
||||
...
|
||||
|
||||
def get_device_id(self) -> str:
|
||||
device_id = str(uuid.uuid4()).upper()
|
||||
return device_id
|
||||
|
||||
def generate_seed(self, length: int):
|
||||
characters = '0123456789abcdef'
|
||||
result = ''.join(random.choices(characters, k=length))
|
||||
return result
|
||||
|
||||
async def generate_fp_by_uid(self, uid: str) -> str:
|
||||
seed_id = self.generate_seed(16)
|
||||
seed_time = str(int(time.time() * 1000))
|
||||
ext_fields = f'{{"userAgent":"{self._HEADER["User-Agent"]}",\
|
||||
"browserScreenSize":281520,"maxTouchPoints":5,\
|
||||
"isTouchSupported":true,"browserLanguage":"zh-CN","browserPlat":"iPhone",\
|
||||
"browserTimeZone":"Asia/Shanghai","webGlRender":"Apple GPU",\
|
||||
"webGlVendor":"Apple Inc.",\
|
||||
"numOfPlugins":0,"listOfPlugins":"unknown","screenRatio":3,"deviceMemory":"unknown",\
|
||||
"hardwareConcurrency":"4","cpuClass":"unknown","ifNotTrack":"unknown","ifAdBlock":0,\
|
||||
"hasLiedResolution":1,"hasLiedOs":0,"hasLiedBrowser":0}}'
|
||||
body = {
|
||||
'seed_id': seed_id,
|
||||
'device_id': await self.get_user_device_id(uid),
|
||||
'platform': '5',
|
||||
'seed_time': seed_time,
|
||||
'ext_fields': ext_fields,
|
||||
'app_name': 'account_cn',
|
||||
'device_fp': '38d7ee834d1e9',
|
||||
}
|
||||
HEADER = copy.deepcopy(self._HEADER)
|
||||
res = await self._mys_request(
|
||||
url=self.MAPI['GET_FP_URL'],
|
||||
method='POST',
|
||||
header=HEADER,
|
||||
data=body,
|
||||
)
|
||||
if not isinstance(res, Dict):
|
||||
logger.error(f"获取fp连接失败{res}")
|
||||
return random_hex(13).lower()
|
||||
elif res["data"]["code"] != 200:
|
||||
logger.error(f"获取fp参数不正确{res['data']['msg']}")
|
||||
return random_hex(13).lower()
|
||||
else:
|
||||
return res["data"]["device_fp"]
|
||||
|
||||
async def simple_mys_req(
|
||||
self,
|
||||
URL: str,
|
||||
@ -196,6 +254,113 @@ class BaseMysApi:
|
||||
async with ClientSession(
|
||||
connector=TCPConnector(verify_ssl=ssl_verify)
|
||||
) as client:
|
||||
raw_data = {}
|
||||
uid = None
|
||||
if params and 'role_id' in params:
|
||||
uid = params['role_id']
|
||||
header['x-rpc-device_id'] = await self.get_user_device_id(uid)
|
||||
header['x-rpc-device_fp'] = await self.get_user_fp(uid)
|
||||
|
||||
for _ in range(3):
|
||||
if 'Cookie' in header and header['Cookie'] in self.chs:
|
||||
# header['x-rpc-challenge']=self.chs.pop(header['Cookie'])
|
||||
if self.is_sr:
|
||||
header['x-rpc-challenge'] = self.chs.pop(
|
||||
header['Cookie']
|
||||
)
|
||||
if isinstance(params, Dict):
|
||||
header['DS'] = get_ds_token(
|
||||
'&'.join(
|
||||
[f'{k}={v}' for k, v in params.items()]
|
||||
)
|
||||
)
|
||||
|
||||
header['x-rpc-challenge_game'] = '6' if self.is_sr else '2'
|
||||
header['x-rpc-page'] = (
|
||||
'3.1.3_#/rpg' if self.is_sr else '3.1.3_#/ys'
|
||||
)
|
||||
|
||||
if (
|
||||
'x-rpc-challenge' in header
|
||||
and not header['x-rpc-challenge']
|
||||
):
|
||||
del header['x-rpc-challenge']
|
||||
del header['x-rpc-page']
|
||||
del header['x-rpc-challenge_game']
|
||||
|
||||
print(header)
|
||||
async with client.request(
|
||||
method,
|
||||
url=url,
|
||||
headers=header,
|
||||
params=params,
|
||||
json=data,
|
||||
proxy=self.proxy_url if use_proxy else None,
|
||||
timeout=300,
|
||||
) as resp:
|
||||
try:
|
||||
raw_data = await resp.json()
|
||||
except ContentTypeError:
|
||||
_raw_data = await resp.text()
|
||||
raw_data = {'retcode': -999, 'data': _raw_data}
|
||||
logger.debug(raw_data)
|
||||
|
||||
# 判断retcode
|
||||
if 'retcode' in raw_data:
|
||||
retcode: int = raw_data['retcode']
|
||||
elif 'code' in raw_data:
|
||||
retcode: int = raw_data['code']
|
||||
else:
|
||||
retcode = 0
|
||||
|
||||
# 针对1034做特殊处理
|
||||
if retcode == 1034:
|
||||
if uid and self.is_sr and _ == 0:
|
||||
sqla = self.dbsqla.get_sqla('TEMP')
|
||||
new_fp = await self.generate_fp_by_uid(uid)
|
||||
await sqla.update_user_data(uid, {'fp': new_fp})
|
||||
header['x-rpc-device_fp'] = new_fp
|
||||
if isinstance(params, Dict):
|
||||
header['DS'] = get_ds_token(
|
||||
'&'.join(
|
||||
[f'{k}={v}' for k, v in params.items()]
|
||||
)
|
||||
)
|
||||
else:
|
||||
ch = await self._upass(header)
|
||||
self.chs[header['Cookie']] = ch
|
||||
elif retcode == -10001 and uid:
|
||||
sqla = self.dbsqla.get_sqla('TEMP')
|
||||
new_fp = await self.generate_fp_by_uid(uid)
|
||||
await sqla.update_user_data(uid, {'fp': new_fp})
|
||||
header['x-rpc-device_fp'] = new_fp
|
||||
elif retcode != 0:
|
||||
return retcode
|
||||
else:
|
||||
return raw_data
|
||||
else:
|
||||
return -999
|
||||
|
||||
'''
|
||||
async def _mys_request(
|
||||
self,
|
||||
url: str,
|
||||
method: Literal['GET', 'POST'] = 'GET',
|
||||
header: Dict[str, Any] = _HEADER,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
use_proxy: Optional[bool] = False,
|
||||
) -> Union[Dict, int]:
|
||||
import types
|
||||
import inspect
|
||||
|
||||
async with ClientSession(
|
||||
connector=TCPConnector(verify_ssl=ssl_verify)
|
||||
) as client:
|
||||
if 'Cookie' in header:
|
||||
if header['Cookie'] in self.chs:
|
||||
header['x-rpc-challenge'] = self.chs.pop(header["Cookie"])
|
||||
|
||||
async with client.request(
|
||||
method,
|
||||
url=url,
|
||||
@ -211,22 +376,73 @@ class BaseMysApi:
|
||||
_raw_data = await resp.text()
|
||||
raw_data = {'retcode': -999, 'data': _raw_data}
|
||||
logger.debug(raw_data)
|
||||
|
||||
# 判断retcode
|
||||
if 'retcode' in raw_data:
|
||||
retcode: int = raw_data['retcode']
|
||||
elif 'code' in raw_data:
|
||||
retcode: int = raw_data['code']
|
||||
else:
|
||||
retcode = 0
|
||||
|
||||
# 针对1034做特殊处理
|
||||
if retcode == 1034:
|
||||
await self._upass(header)
|
||||
return retcode
|
||||
try:
|
||||
# 获取ch
|
||||
ch = await self._upass(header)
|
||||
# 记录ck -> ch的对照表
|
||||
if "Cookie" in header:
|
||||
self.chs[header["Cookie"]] = ch
|
||||
# 获取当前的栈帧
|
||||
curframe = inspect.currentframe()
|
||||
# 确保栈帧存在
|
||||
assert curframe
|
||||
# 获取调用者的栈帧
|
||||
calframe = curframe.f_back
|
||||
# 确保调用者的栈帧存在
|
||||
assert calframe
|
||||
# 获取调用者的函数名
|
||||
caller_name = calframe.f_code.co_name
|
||||
# 获取调用者函数的局部变量字典
|
||||
caller_args = inspect.getargvalues(calframe).locals
|
||||
# 获取调用者的参数列表
|
||||
caller_args2 = inspect.getargvalues(calframe).args
|
||||
# # 生成一个字典,键为调用者的参数名,值为对应的局部变量值,如果不存在则为None
|
||||
caller_args3 = {
|
||||
k: caller_args.get(k, None) for k in caller_args2
|
||||
}
|
||||
if caller_name != '_mys_req_get':
|
||||
return await types.FunctionType(
|
||||
calframe.f_code, globals()
|
||||
)(**caller_args3)
|
||||
else:
|
||||
curframe = calframe
|
||||
calframe = curframe.f_back
|
||||
assert calframe
|
||||
caller_name = calframe.f_code.co_name
|
||||
caller_args = inspect.getargvalues(calframe).locals
|
||||
caller_args2 = inspect.getargvalues(calframe).args
|
||||
caller_args3 = {
|
||||
k: caller_args.get(k, None)
|
||||
for k in caller_args2
|
||||
}
|
||||
return await types.FunctionType(
|
||||
calframe.f_code, globals()
|
||||
)(**caller_args3)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
traceback.print_exc()
|
||||
return -999
|
||||
elif retcode != 0:
|
||||
return retcode
|
||||
return raw_data
|
||||
'''
|
||||
|
||||
|
||||
class MysApi(BaseMysApi):
|
||||
async def _pass(self, gt: str, ch: str, header: Dict):
|
||||
async def _pass(
|
||||
self, gt: str, ch: str, header: Dict
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
# 警告:使用该服务(例如某RR等)需要注意风险问题
|
||||
# 本项目不以任何形式提供相关接口
|
||||
# 代码来源:GITHUB项目MIT开源
|
||||
@ -247,13 +463,14 @@ class MysApi(BaseMysApi):
|
||||
|
||||
return validate, ch
|
||||
|
||||
async def _upass(self, header: Dict, is_bbs: bool = False):
|
||||
async def _upass(self, header: Dict, is_bbs: bool = False) -> str:
|
||||
logger.info('[upass] 进入处理...')
|
||||
if is_bbs:
|
||||
raw_data = await self.get_bbs_upass_link(header)
|
||||
else:
|
||||
raw_data = await self.get_upass_link(header)
|
||||
if isinstance(raw_data, int):
|
||||
return False
|
||||
return ''
|
||||
gt = raw_data['data']['gt']
|
||||
ch = raw_data['data']['challenge']
|
||||
|
||||
@ -261,8 +478,13 @@ class MysApi(BaseMysApi):
|
||||
|
||||
if vl:
|
||||
await self.get_header_and_vl(header, ch, vl)
|
||||
if ch:
|
||||
logger.info(f'[upass] 获取ch -> {ch}')
|
||||
return ch
|
||||
else:
|
||||
return ''
|
||||
else:
|
||||
return True
|
||||
return ''
|
||||
|
||||
async def get_upass_link(self, header: Dict) -> Union[int, Dict]:
|
||||
header['DS'] = get_ds_token('is_high=false')
|
||||
@ -958,7 +1180,11 @@ class MysApi(BaseMysApi):
|
||||
'currency': 'CNY',
|
||||
'pay_plat': method,
|
||||
}
|
||||
data = {'order': order, 'sign': gen_payment_sign(order)}
|
||||
data = {
|
||||
'order': order,
|
||||
'special_info': 'topup_center',
|
||||
'sign': gen_payment_sign(order),
|
||||
}
|
||||
HEADER['x-rpc-device_id'] = device_id
|
||||
HEADER['x-rpc-client_type'] = '4'
|
||||
resp = await self._mys_request(
|
||||
|
@ -1,56 +1,15 @@
|
||||
from typing import Dict, Literal, Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from gsuid_core.utils.api.mys import MysApi
|
||||
from gsuid_core.utils.database.api import DBSqla
|
||||
from gsuid_core.utils.plugins_config.gs_config import core_plugins_config
|
||||
|
||||
gsconfig = core_plugins_config
|
||||
|
||||
|
||||
class _MysApi(MysApi):
|
||||
dbsqla: DBSqla = DBSqla()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def _pass(self, gt: str, ch: str, header: Dict):
|
||||
# 警告:使用该服务(例如某RR等)需要注意风险问题
|
||||
# 本项目不以任何形式提供相关接口
|
||||
# 代码来源:GITHUB项目MIT开源
|
||||
_pass_api = gsconfig.get_config('_pass_API').data
|
||||
if _pass_api:
|
||||
data = await self._mys_request(
|
||||
url=f'{_pass_api}>={gt}&challenge={ch}',
|
||||
method='GET',
|
||||
header=header,
|
||||
)
|
||||
if isinstance(data, int):
|
||||
return None, None
|
||||
else:
|
||||
validate = data['data']['validate']
|
||||
ch = data['data']['challenge']
|
||||
else:
|
||||
validate = None
|
||||
|
||||
return validate, ch
|
||||
|
||||
async def _upass(self, header: Dict, is_bbs: bool = False):
|
||||
if is_bbs:
|
||||
raw_data = await self.get_bbs_upass_link(header)
|
||||
else:
|
||||
raw_data = await self.get_upass_link(header)
|
||||
if isinstance(raw_data, int):
|
||||
return False
|
||||
gt = raw_data['data']['gt']
|
||||
ch = raw_data['data']['challenge']
|
||||
|
||||
vl, ch = await self._pass(gt, ch, header)
|
||||
|
||||
if vl:
|
||||
await self.get_header_and_vl(header, ch, vl)
|
||||
else:
|
||||
return True
|
||||
|
||||
async def get_ck(
|
||||
self, uid: str, mode: Literal['OWNER', 'RANDOM'] = 'RANDOM'
|
||||
) -> Optional[str]:
|
||||
@ -62,5 +21,23 @@ class _MysApi(MysApi):
|
||||
async def get_stoken(self, uid: str) -> Optional[str]:
|
||||
return await self.dbsqla.get_sqla('TEMP').get_user_stoken(uid)
|
||||
|
||||
async def get_user_fp(self, uid: str) -> Optional[str]:
|
||||
data = await self.dbsqla.get_sqla('TEMP').get_user_fp(uid)
|
||||
if data is None:
|
||||
data = await self.generate_fp_by_uid(uid)
|
||||
await self.dbsqla.get_sqla('TEMP').update_user_data(
|
||||
uid, {'fp': data}
|
||||
)
|
||||
return data
|
||||
|
||||
async def get_user_device_id(self, uid: str) -> Optional[str]:
|
||||
data = await self.dbsqla.get_sqla('TEMP').get_user_device_id(uid)
|
||||
if data is None:
|
||||
data = self.get_device_id()
|
||||
await self.dbsqla.get_sqla('TEMP').update_user_data(
|
||||
uid, {'device_id': data}
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
mys_api = _MysApi()
|
||||
|
@ -217,7 +217,7 @@ async def _deal_ck(bot_id: str, mes: str, user_id: str) -> str:
|
||||
uid_bind = i['game_role_id']
|
||||
elif i['game_id'] == 6:
|
||||
sr_uid_bind = i['game_role_id']
|
||||
if uid and sr_uid:
|
||||
if uid_bind and sr_uid_bind:
|
||||
break
|
||||
else:
|
||||
if not (uid_bind or sr_uid_bind):
|
||||
@ -236,8 +236,16 @@ async def _deal_ck(bot_id: str, mes: str, user_id: str) -> str:
|
||||
if uid is None:
|
||||
uid = '0'
|
||||
|
||||
device_id = mys_api.get_device_id()
|
||||
fp = await mys_api.generate_fp_by_uid(uid)
|
||||
await sqla.insert_user_data(
|
||||
user_id, uid_bind, sr_uid_bind, account_cookie, app_cookie
|
||||
user_id,
|
||||
uid_bind,
|
||||
sr_uid_bind,
|
||||
account_cookie,
|
||||
app_cookie,
|
||||
fp,
|
||||
device_id,
|
||||
)
|
||||
|
||||
im_list.append(
|
||||
|
@ -2,10 +2,13 @@ import io
|
||||
import json
|
||||
import base64
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from http.cookies import SimpleCookie
|
||||
from typing import Any, List, Tuple, Union, Literal
|
||||
|
||||
import qrcode
|
||||
import aiofiles
|
||||
from qrcode.image.pil import PilImage
|
||||
from qrcode.constants import ERROR_CORRECT_L
|
||||
|
||||
from gsuid_core.bot import Bot
|
||||
@ -15,17 +18,10 @@ from gsuid_core.segment import MessageSegment
|
||||
from gsuid_core.utils.api.mys_api import mys_api
|
||||
from gsuid_core.utils.database.api import DBSqla
|
||||
|
||||
disnote = '''免责声明:您将通过扫码完成获取米游社sk以及ck。
|
||||
本Bot将不会保存您的登录状态。
|
||||
我方仅提供米游社查询及相关游戏内容服务
|
||||
若您的账号封禁、被盗等处罚与我方无关。
|
||||
害怕风险请勿扫码!
|
||||
'''
|
||||
|
||||
get_sqla = DBSqla().get_sqla
|
||||
|
||||
|
||||
def get_qrcode_base64(url):
|
||||
async def get_qrcode_base64(url: str, path: Path, bot_id: str) -> bytes:
|
||||
qr = qrcode.QRCode(
|
||||
version=1,
|
||||
error_correction=ERROR_CORRECT_L,
|
||||
@ -34,11 +30,33 @@ def get_qrcode_base64(url):
|
||||
)
|
||||
qr.add_data(url)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color='black', back_color='white')
|
||||
img_byte = io.BytesIO()
|
||||
img.save(img_byte, format='PNG') # type: ignore
|
||||
img_byte = img_byte.getvalue()
|
||||
return base64.b64encode(img_byte).decode()
|
||||
img = qr.make_image(fill_color=(255, 134, 36), back_color='white')
|
||||
assert isinstance(img, PilImage)
|
||||
|
||||
if bot_id == 'onebot':
|
||||
img = img.resize((700, 700)) # type: ignore
|
||||
img.save( # type: ignore
|
||||
path,
|
||||
format='PNG',
|
||||
save_all=True,
|
||||
append_images=[img],
|
||||
duration=100,
|
||||
loop=0,
|
||||
)
|
||||
async with aiofiles.open(path, 'rb') as fp:
|
||||
img = await fp.read()
|
||||
elif bot_id == 'onebot_v12':
|
||||
img_byte = io.BytesIO()
|
||||
img.save(img_byte, format='PNG') # type: ignore
|
||||
img_byte = img_byte.getvalue()
|
||||
img = f'base64://{base64.b64encode(img_byte).decode()}'
|
||||
return img # type: ignore
|
||||
else:
|
||||
img_byte = io.BytesIO()
|
||||
img.save(img_byte, format='PNG') # type: ignore
|
||||
img = img_byte.getvalue()
|
||||
|
||||
return img
|
||||
|
||||
|
||||
async def refresh(
|
||||
@ -51,15 +69,15 @@ async def refresh(
|
||||
code_data['app_id'], code_data['ticket'], code_data['device']
|
||||
)
|
||||
if isinstance(status_data, int):
|
||||
logger.warning('二维码已过期')
|
||||
logger.warning('[登录]二维码已过期')
|
||||
return False, None
|
||||
if status_data['stat'] == 'Scanned':
|
||||
if not scanned:
|
||||
logger.info('二维码已扫描')
|
||||
logger.info('[登录]二维码已扫描')
|
||||
scanned = True
|
||||
continue
|
||||
if status_data['stat'] == 'Confirmed':
|
||||
logger.info('二维码已确认')
|
||||
logger.info('[登录]二维码已确认')
|
||||
break
|
||||
return True, json.loads(status_data['payload']['raw'])
|
||||
|
||||
@ -73,39 +91,41 @@ async def qrcode_login(bot: Bot, ev: Event, user_id: str) -> str:
|
||||
|
||||
code_data = await mys_api.create_qrcode_url()
|
||||
if isinstance(code_data, int):
|
||||
return await send_msg('链接创建失败...')
|
||||
try:
|
||||
im = []
|
||||
im.append(MessageSegment.text('请使用米游社扫描下方二维码登录:'))
|
||||
im.append(
|
||||
MessageSegment.image(
|
||||
f'base64://{get_qrcode_base64(code_data["url"])}'
|
||||
)
|
||||
return await send_msg('[登录]链接创建失败...')
|
||||
|
||||
path = Path(__file__).parent / f'{user_id}.gif'
|
||||
|
||||
im = []
|
||||
im.append(MessageSegment.text('请使用米游社扫描下方二维码登录:'))
|
||||
im.append(
|
||||
MessageSegment.image(
|
||||
await get_qrcode_base64(code_data['url'], path, ev.bot_id)
|
||||
)
|
||||
im.append(
|
||||
MessageSegment.text(
|
||||
'免责声明:您将通过扫码完成获取米游社sk以及ck。\n'
|
||||
'本Bot将不会保存您的登录状态。\n'
|
||||
'我方仅提供米游社查询及相关游戏内容服务,\n'
|
||||
'若您的账号封禁、被盗等处罚与我方无关。\n'
|
||||
'害怕风险请勿扫码~'
|
||||
)
|
||||
)
|
||||
im.append(
|
||||
MessageSegment.text(
|
||||
'免责声明:您将通过扫码完成获取米游社sk以及ck。\n'
|
||||
'我方仅提供米游社查询及相关游戏内容服务,\n'
|
||||
'若您的账号封禁、被盗等处罚与我方无关。\n'
|
||||
'害怕风险请勿扫码~'
|
||||
)
|
||||
await bot.send(MessageSegment.node(im))
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.warning(f'[扫码登录] {user_id} 图片发送失败')
|
||||
)
|
||||
await bot.send(MessageSegment.node(im))
|
||||
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
|
||||
status, game_token_data = await refresh(code_data)
|
||||
if status:
|
||||
assert game_token_data is not None # 骗过 pyright
|
||||
logger.info('game_token获取成功')
|
||||
logger.info('[登录]game_token获取成功')
|
||||
cookie_token = await mys_api.get_cookie_token(**game_token_data)
|
||||
stoken_data = await mys_api.get_stoken_by_game_token(
|
||||
account_id=int(game_token_data['uid']),
|
||||
game_token=game_token_data['token'],
|
||||
)
|
||||
if isinstance(stoken_data, int):
|
||||
return await send_msg('获取SK失败...')
|
||||
return await send_msg('[登录]获取SK失败...')
|
||||
account_id = game_token_data['uid']
|
||||
stoken = stoken_data['token']['token']
|
||||
mid = stoken_data['user_info']['mid']
|
||||
@ -114,7 +134,7 @@ async def qrcode_login(bot: Bot, ev: Event, user_id: str) -> str:
|
||||
stoken, account_id, app_cookie
|
||||
)
|
||||
if isinstance(ck, int):
|
||||
return await send_msg('获取CK失败...')
|
||||
return await send_msg('[登录]获取CK失败...')
|
||||
ck = ck['cookie_token']
|
||||
cookie_check = f'account_id={account_id};cookie_token={ck}'
|
||||
get_uid = await mys_api.get_mihoyo_bbs_info(account_id, cookie_check)
|
||||
@ -131,26 +151,26 @@ async def qrcode_login(bot: Bot, ev: Event, user_id: str) -> str:
|
||||
if uid_check or sruid_check:
|
||||
pass
|
||||
else:
|
||||
im = f'你的米游社账号{account_id}尚未绑定原神账号,请前往米游社操作!'
|
||||
im = f'[登录]你的米游社账号{account_id}尚未绑定原神账号,请前往米游社操作!'
|
||||
return await send_msg(im)
|
||||
else:
|
||||
im = '请求失败, 请稍后再试...'
|
||||
im = '[登录]请求失败, 请稍后再试...'
|
||||
return await send_msg(im)
|
||||
|
||||
uid_bind = await sqla.get_bind_uid(user_id)
|
||||
sruid_bind = await sqla.get_bind_sruid(user_id)
|
||||
uid_bind_list = await sqla.get_bind_uid_list(user_id)
|
||||
sruid_bind_list = await sqla.get_bind_sruid_list(user_id)
|
||||
# 没有在gsuid绑定uid的情况
|
||||
if not (uid_bind or sruid_bind):
|
||||
logger.warning('game_token获取失败')
|
||||
if not (uid_bind_list or sruid_bind_list):
|
||||
logger.warning('[登录]game_token获取失败')
|
||||
im = '你还没有绑定uid, 请输入[绑定uid123456]绑定你的uid, 再发送[扫码登录]进行绑定'
|
||||
return await send_msg(im)
|
||||
if isinstance(cookie_token, int):
|
||||
return await send_msg('获取CK失败...')
|
||||
return await send_msg('[登录]获取CK失败...')
|
||||
# 比对gsuid数据库和扫码登陆获取到的uid
|
||||
if (
|
||||
str(uid_bind) == uid_check
|
||||
or str(sruid_bind) == str(sruid_check)
|
||||
or str(uid_bind) == account_id
|
||||
uid_check in uid_bind_list
|
||||
or sruid_check in sruid_bind_list
|
||||
or account_id in uid_bind_list
|
||||
):
|
||||
return SimpleCookie(
|
||||
{
|
||||
@ -161,12 +181,12 @@ async def qrcode_login(bot: Bot, ev: Event, user_id: str) -> str:
|
||||
}
|
||||
).output(header='', sep=';')
|
||||
else:
|
||||
logger.warning('game_token获取失败')
|
||||
logger.warning('[登录]game_token获取失败')
|
||||
im = (
|
||||
f'检测到扫码登录UID{uid_check}与绑定UID{uid_bind}不同, '
|
||||
f'检测到扫码登录UID{uid_check}与绑定UID不同, '
|
||||
'gametoken获取失败, 请重新发送[扫码登录]进行登录!'
|
||||
)
|
||||
else:
|
||||
logger.warning('game_token获取失败')
|
||||
im = 'game_token获取失败: 二维码已过期'
|
||||
logger.warning('[登录]game_token获取失败')
|
||||
im = '[登录]game_token获取失败: 二维码已过期'
|
||||
return await send_msg(im)
|
||||
|
@ -1,6 +1,5 @@
|
||||
import re
|
||||
import asyncio
|
||||
import contextlib
|
||||
from typing import Dict, List, Literal, Optional
|
||||
|
||||
from sqlmodel import SQLModel
|
||||
@ -43,13 +42,20 @@ class SQLA:
|
||||
'ALTER TABLE GsBind ADD COLUMN sr_uid TEXT',
|
||||
'ALTER TABLE GsUser ADD COLUMN sr_uid TEXT',
|
||||
'ALTER TABLE GsUser ADD COLUMN sr_region TEXT',
|
||||
'ALTER TABLE GsUser ADD COLUMN fp TEXT',
|
||||
'ALTER TABLE GsUser ADD COLUMN device_id TEXT',
|
||||
'ALTER TABLE GsUser ADD COLUMN sr_sign_switch TEXT DEFAULT "off"',
|
||||
'ALTER TABLE GsUser ADD COLUMN sr_push_switch TEXT DEFAULT "off"',
|
||||
'ALTER TABLE GsUser ADD COLUMN draw_switch TEXT DEFAULT "off"',
|
||||
'ALTER TABLE GsCache ADD COLUMN sr_uid TEXT',
|
||||
]
|
||||
with contextlib.suppress(Exception):
|
||||
async with self.async_session() as session:
|
||||
for _t in exec_list:
|
||||
async with self.async_session() as session:
|
||||
for _t in exec_list:
|
||||
try:
|
||||
await session.execute(text(_t))
|
||||
await session.commit()
|
||||
await session.commit()
|
||||
except: # noqa: E722
|
||||
pass
|
||||
|
||||
#####################
|
||||
# GsBind 部分 #
|
||||
@ -207,6 +213,22 @@ class SQLA:
|
||||
result = await session.execute(sql)
|
||||
return data[0] if (data := result.scalars().all()) else None
|
||||
|
||||
async def select_user_all_data_by_user_id(
|
||||
self, user_id: str
|
||||
) -> Optional[List[GsUser]]:
|
||||
async with self.async_session() as session:
|
||||
async with session.begin():
|
||||
sql = select(GsUser).where(GsUser.user_id == user_id)
|
||||
result = await session.execute(sql)
|
||||
data = result.scalars().all()
|
||||
return data if data else None
|
||||
|
||||
async def select_user_data_by_user_id(
|
||||
self, user_id: str
|
||||
) -> Optional[GsUser]:
|
||||
data = await self.select_user_all_data_by_user_id(user_id)
|
||||
return data[0] if data else None
|
||||
|
||||
async def select_cache_cookie(self, uid: str) -> Optional[str]:
|
||||
async with self.async_session() as session:
|
||||
async with session.begin():
|
||||
@ -228,6 +250,14 @@ class SQLA:
|
||||
await session.execute(sql)
|
||||
return True
|
||||
|
||||
async def get_user_fp(self, uid: str) -> Optional[str]:
|
||||
data = await self.select_user_data(uid)
|
||||
return data.fp if data else None
|
||||
|
||||
async def get_user_device_id(self, uid: str) -> Optional[str]:
|
||||
data = await self.select_user_data(uid)
|
||||
return data.device_id if data else None
|
||||
|
||||
async def insert_cache_data(
|
||||
self,
|
||||
cookie: str,
|
||||
@ -251,6 +281,8 @@ class SQLA:
|
||||
sr_uid: Optional[str] = None,
|
||||
cookie: Optional[str] = None,
|
||||
stoken: Optional[str] = None,
|
||||
fp: Optional[str] = None,
|
||||
device_id: Optional[str] = None,
|
||||
) -> bool:
|
||||
async with self.async_session() as session:
|
||||
async with session.begin():
|
||||
@ -265,6 +297,7 @@ class SQLA:
|
||||
bot_id=self.bot_id,
|
||||
user_id=user_id,
|
||||
sr_uid=sr_uid,
|
||||
fp=fp,
|
||||
)
|
||||
)
|
||||
await session.execute(sql)
|
||||
@ -279,6 +312,7 @@ class SQLA:
|
||||
bot_id=self.bot_id,
|
||||
user_id=user_id,
|
||||
uid=uid,
|
||||
fp=fp,
|
||||
)
|
||||
)
|
||||
await session.execute(sql)
|
||||
@ -301,10 +335,15 @@ class SQLA:
|
||||
sign_switch='off',
|
||||
push_switch='off',
|
||||
bbs_switch='off',
|
||||
draw_switch='off',
|
||||
region=SERVER.get(uid[0], 'cn_gf01') if uid else None,
|
||||
sr_region=SR_SERVER.get(sr_uid[0], None)
|
||||
if sr_uid
|
||||
else None,
|
||||
fp=fp,
|
||||
device_id=device_id,
|
||||
sr_push_switch='off',
|
||||
sr_sign_switch='off',
|
||||
)
|
||||
session.add(user_data)
|
||||
await session.commit()
|
||||
@ -314,13 +353,9 @@ class SQLA:
|
||||
async with self.async_session() as session:
|
||||
async with session.begin():
|
||||
sql = (
|
||||
update(GsUser).where(
|
||||
GsUser.sr_uid == uid, GsUser.bot_id == self.bot_id
|
||||
)
|
||||
update(GsUser).where(GsUser.sr_uid == uid)
|
||||
if self.is_sr
|
||||
else update(GsUser).where(
|
||||
GsUser.uid == uid, GsUser.bot_id == self.bot_id
|
||||
)
|
||||
else update(GsUser).where(GsUser.uid == uid)
|
||||
)
|
||||
if data is not None:
|
||||
query = sql.values(**data)
|
||||
@ -456,10 +491,18 @@ class SQLA:
|
||||
data = await self.select_user_data(uid)
|
||||
return data.cookie if data else None
|
||||
|
||||
async def get_user_cookie_by_user_id(self, user_id: str) -> Optional[str]:
|
||||
data = await self.select_user_data_by_user_id(user_id)
|
||||
return data.cookie if data else None
|
||||
|
||||
async def cookie_validate(self, uid: str) -> bool:
|
||||
data = await self.select_user_data(uid)
|
||||
return True if data and data.status is None else False
|
||||
|
||||
async def get_user_stoken_by_user_id(self, user_id: str) -> Optional[str]:
|
||||
data = await self.select_user_data_by_user_id(user_id)
|
||||
return data.stoken if data and data.stoken else None
|
||||
|
||||
async def get_user_stoken(self, uid: str) -> Optional[str]:
|
||||
data = await self.select_user_data(uid)
|
||||
return data.stoken if data and data.stoken else None
|
||||
@ -528,7 +571,7 @@ class SQLA:
|
||||
return None
|
||||
|
||||
async def get_switch_status_list(
|
||||
self, switch: Literal['push', 'sign', 'bbs']
|
||||
self, switch: Literal['push', 'sign', 'bbs', 'sr_push', 'sr_sign']
|
||||
) -> List[GsUser]:
|
||||
async with self.async_session() as session:
|
||||
async with session.begin():
|
||||
@ -567,11 +610,7 @@ class SQLA:
|
||||
async with self.async_session() as session:
|
||||
async with session.begin():
|
||||
await self.push_exists(uid)
|
||||
sql = (
|
||||
update(GsPush)
|
||||
.where(GsPush.uid == uid, GsPush.bot_id == self.bot_id)
|
||||
.values(**data)
|
||||
)
|
||||
sql = update(GsPush).where(GsPush.uid == uid).values(**data)
|
||||
await session.execute(sql)
|
||||
await session.commit()
|
||||
return True
|
||||
@ -588,9 +627,7 @@ class SQLA:
|
||||
async with self.async_session() as session:
|
||||
async with session.begin():
|
||||
await self.push_exists(uid)
|
||||
sql = select(GsPush).where(
|
||||
GsPush.uid == uid, GsPush.bot_id == self.bot_id
|
||||
)
|
||||
sql = select(GsPush).where(GsPush.uid == uid)
|
||||
result = await session.execute(sql)
|
||||
data = result.scalars().all()
|
||||
return data[0] if len(data) >= 1 else None
|
||||
@ -598,9 +635,7 @@ class SQLA:
|
||||
async def push_exists(self, uid: str) -> bool:
|
||||
async with self.async_session() as session:
|
||||
async with session.begin():
|
||||
sql = select(GsPush).where(
|
||||
GsPush.uid == uid, GsPush.bot_id == self.bot_id
|
||||
)
|
||||
sql = select(GsPush).where(GsPush.uid == uid)
|
||||
result = await session.execute(sql)
|
||||
data = result.scalars().all()
|
||||
if not data:
|
||||
|
@ -28,7 +28,12 @@ class GsUser(SQLModel, table=True):
|
||||
push_switch: str = Field(title='全局推送开关')
|
||||
sign_switch: str = Field(title='自动签到')
|
||||
bbs_switch: str = Field(title='自动米游币')
|
||||
draw_switch: str = Field(title='自动留影叙佳期')
|
||||
sr_push_switch: str = Field(title='星铁全局推送开关')
|
||||
sr_sign_switch: str = Field(title='星铁自动签到')
|
||||
status: Optional[str] = Field(default=None, title='状态')
|
||||
fp: Optional[str] = Field(default=None, title='Fingerprint')
|
||||
device_id: Optional[str] = Field(default=None, title='设备ID')
|
||||
|
||||
|
||||
class GsCache(SQLModel, table=True):
|
||||
@ -45,15 +50,15 @@ class GsPush(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True, title='序号')
|
||||
bot_id: str = Field(title='平台')
|
||||
uid: str = Field(default=None, title='原神UID')
|
||||
coin_push: Optional[str] = Field(title='洞天宝钱推送')
|
||||
coin_value: Optional[int] = Field(title='洞天宝钱阈值')
|
||||
coin_is_push: Optional[str] = Field(title='洞天宝钱是否已推送')
|
||||
resin_push: Optional[str] = Field(title='体力推送')
|
||||
resin_value: Optional[int] = Field(title='体力阈值')
|
||||
resin_is_push: Optional[str] = Field(title='体力是否已推送')
|
||||
go_push: Optional[str] = Field(title='派遣推送')
|
||||
go_value: Optional[int] = Field(title='派遣阈值')
|
||||
go_is_push: Optional[str] = Field(title='派遣是否已推送')
|
||||
transform_push: Optional[str] = Field(title='质变仪推送')
|
||||
transform_value: Optional[int] = Field(title='质变仪阈值')
|
||||
transform_is_push: Optional[str] = Field(title='质变仪是否已推送')
|
||||
coin_push: Optional[str] = Field(title='洞天宝钱推送', default='off')
|
||||
coin_value: Optional[int] = Field(title='洞天宝钱阈值', default=2100)
|
||||
coin_is_push: Optional[str] = Field(title='洞天宝钱是否已推送', default='off')
|
||||
resin_push: Optional[str] = Field(title='体力推送', default='off')
|
||||
resin_value: Optional[int] = Field(title='体力阈值', default=140)
|
||||
resin_is_push: Optional[str] = Field(title='体力是否已推送', default='off')
|
||||
go_push: Optional[str] = Field(title='派遣推送', default='off')
|
||||
go_value: Optional[int] = Field(title='派遣阈值', default=300)
|
||||
go_is_push: Optional[str] = Field(title='派遣是否已推送', default='off')
|
||||
transform_push: Optional[str] = Field(title='质变仪推送', default='off')
|
||||
transform_value: Optional[int] = Field(title='质变仪阈值', default=1000)
|
||||
transform_is_push: Optional[str] = Field(title='质变仪是否已推送', default='off')
|
||||
|
BIN
gsuid_core/utils/default_bg/bg.jpg
Normal file
After Width: | Height: | Size: 180 KiB |
@ -1,4 +1,15 @@
|
||||
from typing import Union
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from gsuid_core.utils.fonts.fonts import core_font
|
||||
from gsuid_core.utils.image.convert import convert_img
|
||||
from gsuid_core.utils.plugins_config.gs_config import core_plugins_config
|
||||
from gsuid_core.utils.image.image_tools import (
|
||||
get_color_bg,
|
||||
draw_center_text_by_line,
|
||||
)
|
||||
|
||||
UID_HINT = '你还没有绑定过uid哦!\n请使用[绑定uid123456]命令绑定!'
|
||||
MYS_HINT = '你还没有绑定过mysid哦!\n请使用[绑定mys1234]命令绑定!'
|
||||
@ -16,6 +27,9 @@ UPDATE_HINT = '''更新失败!更多错误信息请查看控制台...
|
||||
>> [gs强制更新](危险)
|
||||
>> [gs强行强制更新](超级危险)!'''
|
||||
|
||||
TEXT_PATH = Path(__file__).parent / 'image' / 'texture2d'
|
||||
is_pic_error = core_plugins_config.get_config('ChangeErrorToPic').data
|
||||
|
||||
|
||||
def get_error(retcode: Union[int, str]) -> str:
|
||||
if retcode == -51:
|
||||
@ -48,5 +62,54 @@ def get_error(retcode: Union[int, str]) -> str:
|
||||
return '该API需要CK, 查询的用户/UID未绑定CK...'
|
||||
elif retcode == 10104:
|
||||
return 'CK与用户信息不符, 请检查代码实现...'
|
||||
elif retcode == -999:
|
||||
return VERIFY_HINT
|
||||
elif retcode == 125:
|
||||
return '该充值方式暂时不可用!'
|
||||
elif retcode == 126:
|
||||
return '该充值方式不正确!'
|
||||
else:
|
||||
return f'API报错, 错误码为{retcode}!'
|
||||
return f'未知错误, 错误码为{retcode}!'
|
||||
|
||||
|
||||
def get_error_type(retcode: Union[int, str]) -> str:
|
||||
retcode = int(retcode)
|
||||
if retcode in [-51, 10104]:
|
||||
return '绑定信息错误'
|
||||
elif retcode in [-400, 400]:
|
||||
return 'MGGApi错误'
|
||||
else:
|
||||
return 'Api错误'
|
||||
|
||||
|
||||
async def get_error_img(retcode: Union[int, str]) -> Union[bytes, str]:
|
||||
error_message = get_error(retcode)
|
||||
if is_pic_error:
|
||||
error_type = get_error_type(retcode)
|
||||
return await draw_error_img(retcode, error_message, error_type)
|
||||
else:
|
||||
return error_message
|
||||
|
||||
|
||||
async def draw_error_img(
|
||||
retcode: Union[int, str] = 51233,
|
||||
error_message: Optional[str] = None,
|
||||
error_type: Optional[str] = None,
|
||||
) -> bytes:
|
||||
if error_type is None:
|
||||
error_type = 'API报错'
|
||||
if error_message is None:
|
||||
error_message = '未知错误, 请检查控制台输出...'
|
||||
|
||||
error_img = Image.open(TEXT_PATH / 'error_img.png')
|
||||
img = await get_color_bg(
|
||||
*error_img.size, is_full=True, color=(228, 222, 210)
|
||||
)
|
||||
img.paste(error_img, (0, 0), error_img)
|
||||
img_draw = ImageDraw.Draw(img)
|
||||
img_draw.text((350, 646), error_type, 'white', core_font(26), 'mm')
|
||||
img_draw.text((350, 695), f'错误码 {retcode}', 'white', core_font(36), 'mm')
|
||||
draw_center_text_by_line(
|
||||
img_draw, (350, 750), error_message, core_font(30), 'black', 440
|
||||
)
|
||||
return await convert_img(img)
|
||||
|
9
gsuid_core/utils/fonts/fonts.py
Normal file
@ -0,0 +1,9 @@
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import ImageFont
|
||||
|
||||
FONT_ORIGIN_PATH = Path(__file__).parent / 'yuanshen_origin.ttf'
|
||||
|
||||
|
||||
def core_font(size: int) -> ImageFont.FreeTypeFont:
|
||||
return ImageFont.truetype(str(FONT_ORIGIN_PATH), size=size)
|
BIN
gsuid_core/utils/fonts/yuanshen_origin.ttf
Normal file
@ -4,7 +4,10 @@ from base64 import b64encode
|
||||
from typing import Union, overload
|
||||
|
||||
import aiofiles
|
||||
from PIL import Image, ImageFont
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from gsuid_core.utils.fonts.fonts import core_font
|
||||
from gsuid_core.utils.image.image_tools import draw_center_text_by_line
|
||||
|
||||
|
||||
@overload
|
||||
@ -107,3 +110,18 @@ def get_str_size(
|
||||
def get_height(content: str, size: int) -> int:
|
||||
line_count = content.count('\n')
|
||||
return (line_count + 1) * size
|
||||
|
||||
|
||||
async def text2pic(text: str, max_size: int = 600, font_size: int = 24):
|
||||
if text.endswith('\n'):
|
||||
text = text[:-1]
|
||||
|
||||
img = Image.new(
|
||||
'RGB', (max_size, len(text) * font_size // 5), (228, 222, 210)
|
||||
)
|
||||
img_draw = ImageDraw.ImageDraw(img)
|
||||
y = draw_center_text_by_line(
|
||||
img_draw, (50, 0), text, core_font(font_size), 'black', 500, True
|
||||
)
|
||||
img = img.crop((0, 0, 600, int(y + 30)))
|
||||
return await convert_img(img)
|
||||
|
@ -8,7 +8,10 @@ import httpx
|
||||
from httpx import get
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from gsuid_core.data_store import get_res_path
|
||||
|
||||
TEXT_PATH = Path(__file__).parent / 'texture2d'
|
||||
BG_PATH = Path(__file__).parents[1] / 'default_bg'
|
||||
|
||||
|
||||
async def get_pic(url, size: Optional[Tuple[int, int]] = None) -> Image.Image:
|
||||
@ -28,6 +31,36 @@ async def get_pic(url, size: Optional[Tuple[int, int]] = None) -> Image.Image:
|
||||
return pic
|
||||
|
||||
|
||||
def draw_center_text_by_line(
|
||||
img: ImageDraw.ImageDraw,
|
||||
pos: Tuple[int, int],
|
||||
text: str,
|
||||
font: ImageFont.FreeTypeFont,
|
||||
fill: Union[Tuple[int, int, int, int], str],
|
||||
max_length: float,
|
||||
not_center: bool = False,
|
||||
) -> float:
|
||||
pun = "。!?;!?"
|
||||
x, y = pos
|
||||
_, h = font.getsize('X')
|
||||
line = ''
|
||||
lenth = 0
|
||||
anchor = 'la' if not_center else 'mm'
|
||||
for char in text:
|
||||
size, _ = font.getsize(char) # 获取当前字符的宽度
|
||||
lenth += size
|
||||
line += char
|
||||
if lenth < max_length and char not in pun and char != '\n':
|
||||
pass
|
||||
else:
|
||||
img.text((x, y), line, fill, font, anchor)
|
||||
line, lenth = '', 0
|
||||
y += h * 1.55
|
||||
else:
|
||||
img.text((x, y), line, fill, font, anchor)
|
||||
return y
|
||||
|
||||
|
||||
def draw_text_by_line(
|
||||
img: Image.Image,
|
||||
pos: Tuple[int, int],
|
||||
@ -37,7 +70,7 @@ def draw_text_by_line(
|
||||
max_length: float,
|
||||
center=False,
|
||||
line_space: Optional[float] = None,
|
||||
):
|
||||
) -> float:
|
||||
"""
|
||||
在图片上写长段文字, 自动换行
|
||||
max_length单行最大长度, 单位像素
|
||||
@ -63,7 +96,7 @@ def draw_text_by_line(
|
||||
font_size = font.getsize(row)
|
||||
x = math.ceil((img.size[0] - font_size[0]) / 2)
|
||||
draw.text((x, y), row, font=font, fill=fill)
|
||||
row = ""
|
||||
row = ''
|
||||
length = 0
|
||||
y += y_add
|
||||
if row != "":
|
||||
@ -71,6 +104,7 @@ def draw_text_by_line(
|
||||
font_size = font.getsize(row)
|
||||
x = math.ceil((img.size[0] - font_size[0]) / 2)
|
||||
draw.text((x, y), row, font=font, fill=fill)
|
||||
return y
|
||||
|
||||
|
||||
def easy_paste(
|
||||
@ -174,12 +208,42 @@ def crop_center_img(
|
||||
return crop_img
|
||||
|
||||
|
||||
async def get_color_bg(
|
||||
based_w: int,
|
||||
based_h: int,
|
||||
bg_path: Optional[Path] = None,
|
||||
without_mask: bool = False,
|
||||
is_full: bool = False,
|
||||
color: Optional[Tuple[int, int, int]] = None,
|
||||
full_opacity: int = 200,
|
||||
) -> Image.Image:
|
||||
if bg_path is None:
|
||||
bg_path = get_res_path(['GsCore', 'bg'])
|
||||
CI_img = CustomizeImage(bg_path)
|
||||
img = CI_img.get_image(None, based_w, based_h)
|
||||
if color is None:
|
||||
color = CI_img.get_bg_color(img)
|
||||
if is_full:
|
||||
color_img = Image.new('RGBA', (based_w, based_h), color)
|
||||
mask = Image.new(
|
||||
'RGBA', (based_w, based_h), (255, 255, 255, full_opacity)
|
||||
)
|
||||
img.paste(color_img, (0, 0), mask)
|
||||
elif not without_mask:
|
||||
color_mask = Image.new('RGBA', (based_w, based_h), color)
|
||||
enka_mask = Image.open(TEXT_PATH / 'bg_mask.png').resize(
|
||||
(based_w, based_h)
|
||||
)
|
||||
img.paste(color_mask, (0, 0), enka_mask)
|
||||
return img
|
||||
|
||||
|
||||
class CustomizeImage:
|
||||
def __init__(self, bg_path: Path) -> None:
|
||||
self.bg_path = bg_path
|
||||
|
||||
def get_image(
|
||||
self, image: Union[str, Image.Image], based_w: int, based_h: int
|
||||
self, image: Union[str, Image.Image, None], based_w: int, based_h: int
|
||||
) -> Image.Image:
|
||||
# 获取背景图片
|
||||
if isinstance(image, Image.Image):
|
||||
@ -187,7 +251,11 @@ class CustomizeImage:
|
||||
elif image:
|
||||
edit_bg = Image.open(BytesIO(get(image).content)).convert('RGBA')
|
||||
else:
|
||||
path = random.choice(list(self.bg_path.iterdir()))
|
||||
_lst = list(self.bg_path.iterdir())
|
||||
if _lst:
|
||||
path = random.choice(list(self.bg_path.iterdir()))
|
||||
else:
|
||||
path = random.choice(list(BG_PATH.iterdir()))
|
||||
edit_bg = Image.open(path).convert('RGBA')
|
||||
|
||||
# 确定图片的长宽
|
||||
|
BIN
gsuid_core/utils/image/texture2d/bg_mask.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
gsuid_core/utils/image/texture2d/error_img.png
Normal file
After Width: | Height: | Size: 72 KiB |
@ -49,4 +49,11 @@ CONIFG_DEFAULT: Dict[str, GSC] = {
|
||||
'AutoRestartCoreTime': GsListStrConfig(
|
||||
'自动重启Core时间设置', '每晚自动重启Core时间设置(时, 分)', ['4', '40']
|
||||
),
|
||||
'AutoAddRandomText': GsBoolConfig('自动加入随机字符串', '自动加入随机字符串', False),
|
||||
'RandomText': GsStrConfig(
|
||||
'随机字符串列表', '随机字符串列表', 'abcdefghijklmnopqrstuvwxyz'
|
||||
),
|
||||
'ChangeErrorToPic': GsBoolConfig('错误提示转换为图片', '将一部分报错提示转换为图片', True),
|
||||
'AutoTextToPic': GsBoolConfig('自动文字转图', '将所有发送的文字转图', True),
|
||||
'TextToPicThreshold': GsStrConfig('文转图阈值', '开启自动转图后超过该阈值的文字会转成图片', '20'),
|
||||
}
|
||||
|
@ -13,6 +13,14 @@ from .api import CORE_PATH, PLUGINS_PATH, proxy_url, plugins_lib
|
||||
plugins_list: Dict[str, Dict[str, str]] = {}
|
||||
|
||||
|
||||
async def update_all_plugins() -> List[str]:
|
||||
log_list = []
|
||||
for plugin in PLUGINS_PATH.iterdir():
|
||||
if plugin.is_dir():
|
||||
log_list.extend(update_from_git(0, plugin))
|
||||
return log_list
|
||||
|
||||
|
||||
async def refresh_list() -> List[str]:
|
||||
refresh_list = []
|
||||
async with aiohttp.ClientSession() as session:
|
||||
@ -57,7 +65,12 @@ def install_plugins(plugins: Dict[str, str]) -> str:
|
||||
path = PLUGINS_PATH / plugin_name
|
||||
if path.exists():
|
||||
return '该插件已经安装过了!'
|
||||
Repo.clone_from(git_path, path, single_branch=True, depth=1)
|
||||
config = {'single_branch': True, 'depth': 1}
|
||||
|
||||
if plugins['branch'] != 'main':
|
||||
config['branch'] = plugins['branch']
|
||||
|
||||
Repo.clone_from(git_path, path, **config)
|
||||
logger.info(f'插件{plugin_name}安装成功!')
|
||||
return f'插件{plugin_name}安装成功!发送[gs重启]以应用!'
|
||||
|
||||
|
@ -11,11 +11,11 @@ from fastapi_amis_admin.crud import BaseApiOut
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine
|
||||
from fastapi_user_auth.site import AuthAdminSite
|
||||
from fastapi_amis_admin.models.fields import Field
|
||||
from fastapi_amis_admin.admin.site import APIDocsApp
|
||||
from fastapi_amis_admin.admin.settings import Settings
|
||||
from fastapi_user_auth.auth.models import UserRoleLink
|
||||
from fastapi_amis_admin.utils.translation import i18n as _
|
||||
from fastapi import Depends, FastAPI, Request, HTTPException
|
||||
from fastapi_amis_admin.admin.site import FileAdmin, APIDocsApp
|
||||
from fastapi_amis_admin.amis.constants import LevelEnum, DisplayModeEnum
|
||||
from fastapi_user_auth.admin import (
|
||||
FormAdmin,
|
||||
@ -417,4 +417,4 @@ class PluginsManagePage(GsAdminPage):
|
||||
|
||||
|
||||
# 取消注册默认管理类
|
||||
site.unregister_admin(admin.HomeAdmin, APIDocsApp)
|
||||
site.unregister_admin(admin.HomeAdmin, APIDocsApp, FileAdmin)
|
||||
|
491
poetry.lock
generated
@ -33,6 +33,7 @@ uvicorn = ">=0.20.0"
|
||||
websockets = "^10.4"
|
||||
loguru = "^0.6.0"
|
||||
urllib3 = "^1.26.15"
|
||||
mpmath = "^1.3.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
flake8 = "^6.0.0"
|
||||
@ -42,11 +43,14 @@ pre-commit = "^2.21.0"
|
||||
pycln = "^2.1.2"
|
||||
|
||||
|
||||
[tool.poetry.scripts]
|
||||
core = 'gsuid_core.core:main'
|
||||
|
||||
|
||||
[[tool.poetry.source]]
|
||||
name = "mirrors"
|
||||
url = "https://mirrors.bfsu.edu.cn/pypi/web/simple/"
|
||||
default = true
|
||||
secondary = false
|
||||
priority = "default"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
@ -4,7 +4,7 @@ aiofiles==23.1.0 ; python_full_version >= "3.8.1" and python_version < "4.0"
|
||||
aiohttp==3.8.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
aiosignal==1.3.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
aiosqlite==0.19.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
anyio==3.6.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
anyio==3.7.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
apscheduler==3.10.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
async-timeout==4.0.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
attrs==23.1.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
@ -14,33 +14,34 @@ beautifulsoup4==4.12.2 ; python_full_version >= "3.8.1" and python_full_version
|
||||
certifi==2023.5.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
charset-normalizer==3.1.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
click==8.1.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
colorama==0.4.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and platform_system == "Windows" or python_full_version >= "3.8.1" and python_full_version < "4.0.0" and sys_platform == "win32"
|
||||
colorama==0.4.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and (platform_system == "Windows" or sys_platform == "win32")
|
||||
dnspython==2.3.0 ; python_full_version >= "3.8.1" and python_version < "4.0"
|
||||
email-validator==2.0.0.post2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
fastapi-amis-admin==0.5.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
exceptiongroup==1.1.1 ; python_full_version >= "3.8.1" and python_version < "3.11"
|
||||
fastapi-amis-admin==0.5.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
fastapi-user-auth==0.5.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
fastapi==0.95.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
fastapi==0.97.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
frozenlist==1.3.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
gitdb==4.0.10 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
gitpython==3.1.31 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
greenlet==2.0.2 ; python_full_version >= "3.8.1" and platform_machine == "aarch64" and python_full_version < "4.0.0" or python_full_version >= "3.8.1" and platform_machine == "ppc64le" and python_full_version < "4.0.0" or python_full_version >= "3.8.1" and platform_machine == "x86_64" and python_full_version < "4.0.0" or python_full_version >= "3.8.1" and platform_machine == "amd64" and python_full_version < "4.0.0" or python_full_version >= "3.8.1" and platform_machine == "AMD64" and python_full_version < "4.0.0" or python_full_version >= "3.8.1" and platform_machine == "win32" and python_full_version < "4.0.0" or python_full_version >= "3.8.1" and platform_machine == "WIN32" and python_full_version < "4.0.0"
|
||||
greenlet==2.0.2 ; python_full_version >= "3.8.1" and (platform_machine == "win32" or platform_machine == "WIN32" or platform_machine == "AMD64" or platform_machine == "amd64" or platform_machine == "x86_64" or platform_machine == "ppc64le" or platform_machine == "aarch64") and python_full_version < "4.0.0"
|
||||
h11==0.14.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
httpcore==0.17.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
httpx==0.24.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
httpcore==0.17.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
httpx==0.24.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
idna==3.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
loguru==0.6.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
lxml==4.9.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
msgspec==0.15.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
mpmath==1.3.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
msgspec==0.16.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
multidict==6.0.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
passlib==1.7.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
pillow==9.5.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
pydantic==1.10.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
pydantic==1.10.9 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
pypng==0.20220715.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
python-multipart==0.0.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
pytz-deprecation-shim==0.1.0.post0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
pytz==2023.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
qrcode[pil]==7.4.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
setuptools==67.7.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
setuptools==67.8.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
six==1.16.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
smmap==5.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
sniffio==1.3.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
@ -50,11 +51,11 @@ sqlalchemy2-stubs==0.0.2a34 ; python_full_version >= "3.8.1" and python_full_ver
|
||||
sqlalchemy==1.4.41 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
sqlmodel==0.0.8 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
sqlmodelx==0.0.5 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
starlette==0.26.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
typing-extensions==4.5.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
tzdata==2023.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
tzlocal==4.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
urllib3==1.26.15 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
starlette==0.27.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
typing-extensions==4.6.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
tzdata==2023.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and platform_system == "Windows"
|
||||
tzlocal==5.0.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
urllib3==1.26.16 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
uvicorn==0.22.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
websockets==10.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
|
||||
win32-setctime==1.1.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and sys_platform == "win32"
|
||||
|