绑定使用图片回复 #61 (#305)

* 🚧 查数据图初版测试

Co-authored-by: C1ystal <m17687496044@163.com>
Co-authored-by: C29H25N3O5 <michaelgu495@gmail.com>

* 🙈 添加一些 ignore 文件

* 🎨 格式化代码

* 🐛 修复格式化导致的样式爆炸

* 💄 优化曲线图观感

* 💄 将雷达图的指示器名称旋转显示

* 💄 查数据图第二版

Co-authored-by: C29H25N3O5 <michaelgu495@gmail.com>

* ✏️ 修复 typo

* 💄 把用户头像文件的引用放到 html 里

* 💄 账户绑定图第一版

Co-authored-by: C1ystal <m17687496044@163.com>
Co-authored-by: C29H25N3O5 <michaelgu495@gmail.com>

* 🚧 模板化测试

*  添加依赖 fastapi

*  通过 FastAPI 提供静态文件

*  添加依赖 jinja2

* 💄 更新数据图模板 (#291)

* feat(template): show actual value

* feat(template): add user avatar

* feat(template): fix radar

* feat(style): fix name container width fixed caused display misplacement

* feat(style): fix vs value wrap display

* feat(template): make check data length in template

* feat(template): update radar data

* feat(jinja): update data

* fix(template): fix typo

* feat(style): prevent sign too long

* feat(template): turn off echarts animation

* chore(deps): add identicon.js

* fix(template): fix typo

* 🙈 更新.gitignore

* 🏗️ 大部分重构为 flex 布局

---------

Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
Co-authored-by: 呵呵です <51957264+shoucandanghehe@users.noreply.github.com>

*  添加依赖 nonebot_plugin_userinfo

*  通过 FastAPI 托管渲染后的模板

*  新增头像 api 使用 playwright 生成

*  修正模板资源文件引用路径
被托管后的正确路径

* 💄 将绑定图模板化

* 💄 重命名变量

* 🚚 重命名资源

*  使用 jinja2 渲染模板

*  使用 playwright 渲染网页

* 🩹 渲染模板时对 IO 进行一些额外处理

*  添加依赖 pillow

* 🚚 修改托管页面的路由路径

* 💬 优化绑定图文案

*  新增获取自身网络位置的方法

* 🍱 更新 unknown.svg

*  新增获取用户头像的方法

*  绑定消息使用图片回复

*  为 identicon api 添加缓存

* 🔥 删除旧文件

* 🚚 重命名模板

* 📄 添加字体 License

* 🙈 更新.gitignore

---------

Co-authored-by: C1ystal <m17687496044@163.com>
Co-authored-by: C29H25N3O5 <michaelgu495@gmail.com>
Co-authored-by: 渣渣120 <WOSHIZHAZHA120@qq.com>
This commit is contained in:
呵呵です
2024-05-02 01:22:33 +08:00
committed by GitHub
parent 80f4316564
commit fd85140c99
62 changed files with 2302 additions and 48 deletions

View File

@@ -4,6 +4,8 @@ from typing import Any
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import AlcMatches, AlconnaMatcher
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
from ..utils.exception import MessageFormatError
from ..utils.recorder import Recorder
@@ -41,7 +43,14 @@ class Processor(ABC):
raise NotImplementedError
@abstractmethod
async def handle_bind(self, platform: str, account: str) -> str:
async def handle_bind(
self,
platform: str,
account: str,
bot_info: UserInfo,
*args: Any, # noqa: ANN401
**kwargs: Any, # noqa: ANN401
) -> UniMessage:
"""处理绑定消息"""
raise NotImplementedError

View File

@@ -6,6 +6,7 @@ from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
from sqlalchemy import func, select
from ...db import query_bind_info
@@ -86,17 +87,16 @@ alc.shortcut('fkosk', {'command': 'io查', 'args': ['我']})
@alc.assign('bind')
async def _(bot: Bot, event: Event, matcher: Matcher, account: User):
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
async def _(bot: Bot, event: Event, matcher: Matcher, account: User, bot_info: UserInfo = BotUserInfo()): # noqa: B008
proc = Processor(event_id=id(event), user=account, command_args=[])
try:
await matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
await (
await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info)
).send()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
await matcher.finish()
@alc.assign('query')

View File

@@ -1,26 +1,33 @@
from collections import defaultdict
from collections.abc import Callable
from datetime import datetime, timedelta, timezone
from hashlib import sha512
from hashlib import md5, sha512
from math import floor
from re import match
from statistics import mean
from typing import Literal
from urllib.parse import urlunparse
from aiofiles import open
from nonebot import get_driver
from nonebot.compat import type_validate_json
from nonebot.utils import run_sync
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_apscheduler import scheduler # type: ignore[import-untyped]
from nonebot_plugin_localstore import get_data_file # type: ignore[import-untyped]
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
from sqlalchemy import select
from zstandard import ZstdCompressor
from ...db import create_or_update_bind
from ...db import BindStatus, create_or_update_bind
from ...utils.avatar import get_avatar
from ...utils.exception import MessageFormatError, RequestError, WhatTheFuckError
from ...utils.host import HostPage, get_self_netloc
from ...utils.render import render
from ...utils.request import splice_url
from ...utils.retry import retry
from ...utils.screenshot import screenshot
from .. import Processor as ProcessorMeta
from .cache import Cache
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE
@@ -65,20 +72,43 @@ class Processor(ProcessorMeta):
def game_platform(self) -> Literal['IO']:
return GAME_TYPE
async def handle_bind(self, platform: str, account: str) -> str:
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
"""处理绑定消息"""
self.command_type = 'bind'
await self.get_user()
if self.user.ID is None:
raise # FIXME: 不知道怎么才能把这类型给变过来了
async with get_session() as session:
return await create_or_update_bind(
bind_status = await create_or_update_bind(
session=session,
chat_platform=platform,
chat_account=account,
game_platform=GAME_TYPE,
game_account=self.user.ID,
)
bot_avatar = await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg')
user_info = await self.get_user_info()
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
async with HostPage(
await render(
'bind.j2.html',
user_avatar=f'https://tetr.io/user-content/avatars/{user_info.data.user.id}.jpg?rv={user_info.data.user.avatar_revision}'
if user_info.data.user.avatar_revision is not None
else f'../../identicon?md5={md5(user_info.data.user.id.encode()).hexdigest()}', # noqa: S324
state='unknown',
bot_avatar=bot_avatar,
game_type=self.game_platform,
user_name=user_info.data.user.username.upper(),
bot_name=bot_info.user_name,
command='io查我',
)
) as page_hash:
message = UniMessage.image(
raw=await screenshot(
urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', ''))
)
)
return message
async def handle_query(self) -> str:
"""处理查询消息"""

View File

@@ -3,6 +3,7 @@ from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo # type: ignore[import-untyped]
from ...db import query_bind_info
from ...utils.exception import HandleNotFinishedError, NeedCatchError
@@ -67,17 +68,29 @@ alc = on_alconna(
@alc.assign('bind')
async def _(bot: Bot, event: Event, matcher: Matcher, account: User):
async def _( # noqa: PLR0913
bot: Bot,
event: Event,
matcher: Matcher,
account: User,
bot_info: UserInfo = BotUserInfo(), # noqa: B008
user_info: UserInfo = EventUserInfo(), # noqa: B008
):
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
try:
await matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
await (
await proc.handle_bind(
platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info, user_info=user_info
)
).send()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
await matcher.finish()
@alc.assign('query')

View File

@@ -3,15 +3,21 @@ from dataclasses import dataclass
from io import StringIO
from re import match
from typing import Literal
from urllib.parse import urlencode
from urllib.parse import urlencode, urlunparse
from lxml import etree
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
from pandas import read_html
from ...db import create_or_update_bind
from ...db import BindStatus, create_or_update_bind
from ...utils.avatar import get_avatar
from ...utils.exception import MessageFormatError, RequestError
from ...utils.host import HostPage, get_self_netloc
from ...utils.render import render
from ...utils.request import Request, splice_url
from ...utils.screenshot import screenshot
from .. import Processor as ProcessorMeta
from ..schemas import BaseUser
from .constant import BASE_URL, GAME_TYPE
@@ -60,18 +66,38 @@ class Processor(ProcessorMeta):
def game_platform(self) -> Literal['TOP']:
return GAME_TYPE
async def handle_bind(self, platform: str, account: str) -> str:
async def handle_bind(self, platform: str, account: str, bot_info: UserInfo, user_info: UserInfo) -> UniMessage:
"""处理绑定消息"""
self.command_type = 'bind'
await self.check_user()
async with get_session() as session:
return await create_or_update_bind(
bind_status = await create_or_update_bind(
session=session,
chat_platform=platform,
chat_account=account,
game_platform=GAME_TYPE,
game_account=self.user.name,
)
bot_avatar = await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg')
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
async with HostPage(
await render(
'bind.j2.html',
user_avatar='../../static/static/logo/top.ico',
state='unknown',
bot_avatar=bot_avatar,
game_type=self.game_platform,
user_name=(await self.get_user_name()).upper(),
bot_name=bot_info.user_name,
command='top查我',
)
) as page_hash:
message = UniMessage.image(
raw=await screenshot(
urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', ''))
)
)
return message
async def handle_query(self) -> str:
"""处理查询消息"""

View File

@@ -5,6 +5,7 @@ from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
from ...db import query_bind_info
from ...utils.exception import HandleNotFinishedError, NeedCatchError, RequestError
@@ -130,17 +131,20 @@ except ImportError:
@alc.assign('bind')
async def _(bot: Bot, event: Event, matcher: Matcher, account: User):
async def _(bot: Bot, event: Event, matcher: Matcher, account: User, bot_info: UserInfo = BotUserInfo()): # noqa: B008
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
try:
await matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
await (
await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info)
).send()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
await matcher.finish()
@alc.assign('query')

View File

@@ -1,15 +1,21 @@
from dataclasses import dataclass
from re import match
from typing import Literal
from urllib.parse import urlencode
from urllib.parse import urlencode, urlunparse
from httpx import TimeoutException
from nonebot.compat import type_validate_json
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
from ...db import create_or_update_bind
from ...db import BindStatus, create_or_update_bind
from ...utils.avatar import get_avatar
from ...utils.exception import MessageFormatError, RequestError
from ...utils.host import HostPage, get_self_netloc
from ...utils.render import render
from ...utils.request import Request, splice_url
from ...utils.screenshot import screenshot
from .. import Processor as ProcessorMeta
from ..schemas import BaseUser
from .constant import BASE_URL, GAME_TYPE
@@ -73,18 +79,39 @@ class Processor(ProcessorMeta):
def game_platform(self) -> Literal['TOS']:
return GAME_TYPE
async def handle_bind(self, platform: str, account: str) -> str:
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
"""处理绑定消息"""
self.command_type = 'bind'
await self.get_user()
async with get_session() as session:
return await create_or_update_bind(
bind_status = await create_or_update_bind(
session=session,
chat_platform=platform,
chat_account=account,
game_platform=GAME_TYPE,
game_account=self.user.unique_identifier,
)
bot_avatar = await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg')
user_info = await self.get_user_info()
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
async with HostPage(
await render(
'bind.j2.html',
user_avatar='../../static/static/logo/tos.ico',
state='unknown',
bot_avatar=bot_avatar,
game_type=self.game_platform,
user_name=user_info.data.name.upper(),
bot_name=bot_info.user_name,
command='茶服查我',
)
) as page_hash:
message = UniMessage.image(
raw=await screenshot(
urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', ''))
)
)
return message
async def handle_query(self) -> str:
"""处理查询消息"""