话痨榜的图片渲染!

This commit is contained in:
Chenric 2024-07-08 13:42:58 +08:00
parent e08e1d2392
commit cff3a951b4
17 changed files with 482 additions and 263 deletions

View File

@ -4,6 +4,7 @@
"apscheduler",
"arclet",
"Arparma",
"bnum",
"cesaa",
"chatdatanum",
"chatrecorder",
@ -15,6 +16,7 @@
"pygal",
"sqlalchemy",
"userinfo",
"whereclause",
"xaxis",
"yaxis"
]

View File

@ -8,12 +8,9 @@ require("nonebot_plugin_alconna")
require("nonebot_plugin_cesaa")
import re
import os
import nonebot_plugin_saa as saa
from pyecharts.charts import Bar
from pyecharts import options as opts
from pyecharts.globals import ThemeType
from typing import Union, Optional, List
from datetime import datetime, timedelta
from arclet.alconna import ArparmaBehavior
@ -50,13 +47,13 @@ from nonebot_plugin_session import Session, SessionIdType, extract_session
# from .function import *
from .config import Config, plugin_config
from .usage import __usage__
from .time import get_datetime_fromisoformat_with_timezone, get_datetime_now_with_timezone,parse_datetime
from .model import UserRankInfo
from .utils import (
get_datetime_fromisoformat_with_timezone,
get_datetime_now_with_timezone,
got_rank,
msg_counter,
persist_id2user_id,
parse_datetime,
get_rank_image
)
__plugin_meta__ = PluginMetadata(
@ -92,9 +89,9 @@ rank_cmd = on_alconna(
Alconna(
"B话榜",
Args["type?", ["今日", "昨日", "本周", "上周", "本月", "上月", "年度", "历史"]][
"time?",
str,
]["group_id?", str],
"time?",str,][
"group_id?", str
],
behaviors=[SameTime()],
),
aliases={"废话榜"},
@ -223,42 +220,45 @@ async def handle_rank(
time_stop=stop,
exclude_id1s=plugin_config.excluded_people,
)
rank = got_rank(msg_counter(messages))
rank2: List[UserRankInfo] = []
ids = await persist_id2user_id([int(i[0]) for i in rank])
for i in range(len(rank)):
rank[i][0] = str(ids[i])
logger.debug(rank[i])
total = sum([i[1] for i in rank])
string: str = ""
nicknames: List = []
for i in rank:
if user_info := await get_user_info(bot, event, user_id=str(i[0])):
(
nicknames.append(user_info.user_displayname)
if user_info.user_displayname
else (
nicknames.append(user_info.user_name)
if user_info.user_name
else nicknames.append(user_info.user_id)
)
user_nickname = user_info.user_displayname\
if user_info.user_displayname\
else user_info.user_name\
if user_info.user_name\
else\
user_info.user_id
user_avatar = await user_info.user_avatar.get_image()\
if user_info.user_avatar\
else open(os.path.dirname(os.path.abspath(__file__))+"/template/avatar/default.jpg", "rb").read()
user = UserRankInfo(**user_info.model_dump(),
user_bnum=i[1],
user_proportion= round(i[1] / total * 100, 2),
user_index= rank.index(i) + 1,
user_nickname=user_nickname,
user_avatar_bytes= user_avatar,
)
else:
nicknames.append(None)
logger.debug(nicknames)
user.user_gender="" if user_info.user_gender == "female" else "" if user_info.user_gender == "male" else "ta"
rank2.append(user)
string: str = ""
for i in range(len(rank)):
index = i + 1
nickname, chatdatanum = nicknames[i], rank[i][1]
str_example = plugin_config.string_format.format(
index=index, nickname=nickname, chatdatanum=chatdatanum
index=rank2[i].user_index,
nickname=rank2[i].user_nickname,
chatdatanum=rank2[i].user_bnum
)
string += str_example
bar = Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
bar.add_xaxis(nicknames)
bar.add_yaxis("B话数量", [i[1] for i in rank]) # type: ignore
bar.render(str(get_cache_file("nonebot_plugin_dialectlist", "cache.html")))
with open(get_cache_file("nonebot_plugin_dialectlist", "cache.html")) as f:
a = f.read()
image = await html_to_pic(a, device_scale_factor=3.2)
image = await get_rank_image(rank2)
await (saa.Text(string) + saa.Image(image)).finish(reply=True)

View File

@ -9,6 +9,7 @@ class ScopedConfig(BaseModel):
timezone: Optional[str] = "Asia/Shanghai"
excluded_self: bool = True
string_format: str = "{index}名:\n{nickname},{chatdatanum}条消息\n" # 消息格式
template_path: str = "./template/rank_template.j2" # 模板路径
visualization: bool = True # 是否可视化
excluded_people: List[str] = [] # 排除的人的QQ号
visualization_type: Literal["饼图", "圆环图", "柱状图"] = "圆环图" # 可视化方案

View File

@ -0,0 +1,11 @@
from typing import Optional, Literal, List, Union
from pydantic import BaseModel
from nonebot_plugin_userinfo import get_user_info, UserInfo
class UserRankInfo(UserInfo):
user_bnum: int
user_proportion: float
user_nickname: str
user_index: Union[int,str]
user_avatar_bytes: bytes

View File

@ -1 +0,0 @@
# TODO 更好的图片渲染,支持自定义模板渲染。

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,10 +1,11 @@
body {
background-image: url('../img/background.jpg');
background-image: url('https://image.anosu.top/pixiv/direct?size=regular');
background-attachment: scroll;
margin: 0;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
width: 1024;
}
.container {
@ -74,8 +75,8 @@ body {
border-radius: 2rem;
/* padding: 20px 40px; */
align-content: center;
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.profile{
@ -88,8 +89,8 @@ body {
}
.profile2{
width: 4%;
height: 4%;
width: 8%;
height: 8%;
border: 3px solid rgba(255, 255, 255, 0.61);
border-radius: 2000px;
background-size: cover;

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>
在 title 里夹一点私货应该不会被发现吧——ddl 真是太厉害啦!
</title>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<div class="container">
<div class="card" >
<p class="center_text">话痨排行榜</p>
</div>
<div class="rank_container">
{% for i in users %}
<div class="rank_card">
<img class="profile" src="{{ cache_path }}/{{ i.user_id }}.jpg"/>
<div class="rank_card_info">
<h1>第{{ i.user_index }}名:{{ i.user_name }}</h1>
<div class="g-container">
<div class="g-progress" style="width: {{ i.user_proportion }}%"></div>
</div>
<p>{{ i.user_gender }}一共废话了{{ i.user_bnum }}句,占比{{ i.user_proportion }}%</p>
</div>
</div>
{% endfor %}
</div>
<div class="card2">
<img class="profile2" src="{{ file_path }}/template/avatar/master.jpg"/>
<p class="center_text2">Designed By ChenXu233</p>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,104 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>
在 title 里夹一点私货应该不会被发现吧——ddl 真是太厉害啦!
</title>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<div class="container">
<div class="card" >
<p class="center_text">话痨排行榜</p>
</div>
<div class="rank_container">
<div class="rank_card">
<img class="profile" src="./cache/头像.jpg"/>
<div class="rank_card_info">
<h1>第一名:ChenXu233</h1>
<div class="g-container">
<div class="g-progress" style="width: 100%"></div>
</div>
<p>ta一共废话了12131231句占比100%</p>
</div>
</div>
<div class="rank_card">
<img class="profile" src="./cache/头像.jpg"/>
<div class="rank_card_info">
<h1>第一名:ChenXu233</h1>
<div class="g-container">
<div class="g-progress"></div>
</div>
<p>ta一共废话了12131231句占比100%</p>
</div>
</div>
<div class="rank_card">
<img class="profile" src="./cache/头像.jpg"/>
<div class="rank_card_info">
<h1>第一名:ChenXu233</h1>
<div class="g-container">
<div class="g-progress"></div>
</div>
<p>ta一共废话了12131231句占比100%</p>
</div>
</div>
<div class="rank_card">
<img class="profile" src="./cache/头像.jpg"/>
<div class="rank_card_info">
<h1>第一名:ChenXu233</h1>
<div class="g-container">
<div class="g-progress"></div>
</div>
<p>ta一共废话了12131231句占比100%</p>
</div>
</div>
<div class="rank_card">
<img class="profile" src="./cache/头像.jpg"/>
<div class="rank_card_info">
<h1>第一名:ChenXu233</h1>
<div class="g-container">
<div class="g-progress"></div>
</div>
<p>ta一共废话了12131231句占比100%</p>
</div>
</div>
<div class="rank_card">
<img class="profile" src="./cache/头像.jpg"/>
<div class="rank_card_info">
<h1>第一名:ChenXu233</h1>
<div class="g-container">
<div class="g-progress"></div>
</div>
<p>ta一共废话了12131231句占比100%</p>
</div>
</div>
<div class="rank_card">
<img class="profile" src="./cache/头像.jpg"/>
<div class="rank_card_info">
<h1>第一名:ChenXu233</h1>
<div class="g-container">
<div class="g-progress" style="width: 100%"></div>
</div>
<p>ta一共废话了12131231句占比100%</p>
</div>
</div>
<div class="rank_card">
<img class="profile" src="./cache/头像.jpg"/>
<div class="rank_card_info">
<h1>第一名:ChenXu233</h1>
<div class="g-container">
<div class="g-progress"></div>
</div>
<p>ta一共废话了12131231句占比100%</p>
</div>
</div>
</div>
<div class="card2">
<img class="profile2" src="./img/头像.jpg"/>
<p class="center_text2">Designed By ChenXu233</p>
</div>
</div>
</body>
</html>

View File

@ -1 +1,80 @@
# TODO 时间处理模块,用于处理时间相关操作。
from datetime import datetime, time, tzinfo
from typing import Optional, Union
from zoneinfo import ZoneInfo
from nonebot.typing import T_State
from nonebot.params import Arg
from nonebot.adapters import Message
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_alconna import AlconnaMatcher
from .config import plugin_config
def parse_datetime(key: str):
"""解析数字,并将结果存入 state 中"""
async def _key_parser(
matcher: AlconnaMatcher,
state: T_State,
input: Union[datetime, Message] = Arg(key),
):
if isinstance(input, datetime):
return
plaintext = input.extract_plain_text()
try:
state[key] = get_datetime_fromisoformat_with_timezone(plaintext)
except ValueError:
await matcher.reject_arg(key, "请输入正确的日期,不然我没法理解呢!")
return _key_parser
def get_datetime_now_with_timezone() -> datetime:
"""获取当前时间,并包含时区信息"""
if plugin_config.timezone:
return datetime.now(ZoneInfo(plugin_config.timezone))
else:
return datetime.now().astimezone()
def get_datetime_fromisoformat_with_timezone(date_string: str) -> datetime:
"""从 ISO-8601 格式字符串中获取时间,并包含时区信息"""
if not plugin_config.timezone:
return datetime.fromisoformat(date_string).astimezone()
raw = datetime.fromisoformat(date_string)
return (
raw.astimezone(ZoneInfo(plugin_config.timezone))
if raw.tzinfo
else raw.replace(tzinfo=ZoneInfo(plugin_config.timezone))
)
def time_astimezone(time: time, tz: Optional[tzinfo] = None) -> time:
"""将 time 对象转换为指定时区的 time 对象
如果 tz None则转换为本地时区
"""
local_time = datetime.combine(datetime.today(), time)
return local_time.astimezone(tz).timetz()
def get_time_fromisoformat_with_timezone(time_string: str) -> time:
"""从 iso8601 格式字符串中获取时间,并包含时区信息"""
if not plugin_config.timezone:
return time_astimezone(time.fromisoformat(time_string))
raw = time.fromisoformat(time_string)
return (
time_astimezone(raw, ZoneInfo(plugin_config.timezone))
if raw.tzinfo
else raw.replace(tzinfo=ZoneInfo(plugin_config.timezone))
)
def get_time_with_scheduler_timezone(time: time) -> time:
"""获取转换到 APScheduler 时区的时间"""
return time_astimezone(time, scheduler.timezone)

View File

@ -1,6 +1,8 @@
from datetime import datetime, time, tzinfo
import os
import unicodedata
from typing import Optional, Dict, List, Union
from zoneinfo import ZoneInfo
from pathlib import Path
from sqlalchemy import or_, select
from sqlalchemy.sql import ColumnElement
@ -14,79 +16,17 @@ from nonebot_plugin_orm import get_session
from nonebot_plugin_session import Session, SessionLevel, extract_session
from nonebot_plugin_session_orm import SessionModel
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from nonebot_plugin_htmlrender import html_to_pic,template_to_pic
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_chatrecorder import MessageRecord
from nonebot_plugin_localstore import get_cache_dir
from nonebot_plugin_alconna import AlconnaMatcher
from .config import plugin_config
from .model import UserRankInfo
def parse_datetime(key: str):
"""解析数字,并将结果存入 state 中"""
async def _key_parser(
matcher: AlconnaMatcher,
state: T_State,
input: Union[datetime, Message] = Arg(key),
):
if isinstance(input, datetime):
return
plaintext = input.extract_plain_text()
try:
state[key] = get_datetime_fromisoformat_with_timezone(plaintext)
except ValueError:
await matcher.reject_arg(key, "请输入正确的日期,不然我没法理解呢!")
return _key_parser
def get_datetime_now_with_timezone() -> datetime:
"""获取当前时间,并包含时区信息"""
if plugin_config.timezone:
return datetime.now(ZoneInfo(plugin_config.timezone))
else:
return datetime.now().astimezone()
def get_datetime_fromisoformat_with_timezone(date_string: str) -> datetime:
"""从 ISO-8601 格式字符串中获取时间,并包含时区信息"""
if not plugin_config.timezone:
return datetime.fromisoformat(date_string).astimezone()
raw = datetime.fromisoformat(date_string)
return (
raw.astimezone(ZoneInfo(plugin_config.timezone))
if raw.tzinfo
else raw.replace(tzinfo=ZoneInfo(plugin_config.timezone))
)
def time_astimezone(time: time, tz: Optional[tzinfo] = None) -> time:
"""将 time 对象转换为指定时区的 time 对象
如果 tz None则转换为本地时区
"""
local_time = datetime.combine(datetime.today(), time)
return local_time.astimezone(tz).timetz()
def get_time_fromisoformat_with_timezone(time_string: str) -> time:
"""从 iso8601 格式字符串中获取时间,并包含时区信息"""
if not plugin_config.timezone:
return time_astimezone(time.fromisoformat(time_string))
raw = time.fromisoformat(time_string)
return (
time_astimezone(raw, ZoneInfo(plugin_config.timezone))
if raw.tzinfo
else raw.replace(tzinfo=ZoneInfo(plugin_config.timezone))
)
def get_time_with_scheduler_timezone(time: time) -> time:
"""获取转换到 APScheduler 时区的时间"""
return time_astimezone(time, scheduler.timezone)
cache_path = get_cache_dir("nonebot_plugin_dialectlist")
# 暂时不做考虑
# def admin_permission():
@ -154,7 +94,7 @@ def msg_counter(msg_list: List[MessageRecord]) -> Dict[str, int]:
return lst
def got_rank(msg_dict: Dict[str, int]) -> List[List[Union[str, int]]]:
def got_rank(msg_dict: Dict[str, int]) -> List:
"""### 获得排行榜
Args:
@ -175,34 +115,37 @@ def got_rank(msg_dict: Dict[str, int]) -> List[List[Union[str, int]]]:
plugin_config.get_num, len(rank)
)
)
break
break
return rank
# def remove_control_characters(string: str) -> str:
# """### 将字符串中的控制符去除
def remove_control_characters(string: str) -> str:
"""### 将字符串中的控制符去除
# Args:
# string (str): 需要去除的字符串
Args:
string (str): 需要去除的字符串
# Returns:
# (str): 经过处理的字符串
# """
# return "".join(ch for ch in string if unicodedata.category(ch)[0] != "C")
Returns:
(str): 经过处理的字符串
"""
return "".join(ch for ch in string if unicodedata.category(ch)[0] != "C")
# async def render_template_pic(self) -> bytes:
# if plugin_config.dialectlist_visualization_type == "圆环图":
# view = pygal.Pie(inner_radius=0.6, style=style)
# elif plugin_config.dialectlist_visualization_type == "饼图":
# view = pygal.Pie(style=style)
# else:
# view = pygal.Bar(style=style)
# view.title = "消息可视化"
# for i, j in zip(self.rank, await self.get_nickname_list()): # type: ignore
# view.add(str(j), int(i[1]))
# png: bytes = view.render_to_png() # type: ignore
# self.img = png
# return png
async def get_rank_image(rank: List[UserRankInfo]) -> bytes:
for i in rank:
if i.user_avatar:
try:
user_avatar = i.user_avatar_bytes
except NotImplementedError:
user_avatar = open(os.path.dirname(os.path.abspath(__file__))+"/template/avatar/default.jpg", "rb").read()
# if not os.path.exists(cache_path / str(i.user_id)):
with open(cache_path / (str(i.user_id) + ".jpg"), "wb") as f:
f.write(user_avatar)
if plugin_config.template_path[:2] == './':
path = os.path.dirname(os.path.abspath(__file__)) + plugin_config.template_path[1:]
else:
path = plugin_config.template_path
path_dir, filename = os.path.split(path)
logger.debug(os.path.dirname(os.path.abspath(__file__)) + plugin_config.template_path[1:])
return await template_to_pic(path_dir,filename,{'users': rank, 'cache_path': cache_path, 'file_path': os.path.dirname(os.path.abspath(__file__))})

283
pdm.lock
View File

@ -5,7 +5,7 @@
groups = ["default", "dev"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.1"
content_hash = "sha256:fdb46c5e66cf97af4806bb28cc369a5cfee536532eb9a1b05d581d5856ffcf6d"
content_hash = "sha256:74fd9abb0888f1ded1c2cef549dd45e68a73c42bab5d7e37c1d3f2e5880a4cdb"
[[package]]
name = "aiofiles"
@ -39,7 +39,7 @@ name = "annotated-types"
version = "0.7.0"
requires_python = ">=3.8"
summary = "Reusable constraint types to use with typing.Annotated"
groups = ["default"]
groups = ["default", "dev"]
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
@ -50,7 +50,7 @@ name = "anyio"
version = "4.4.0"
requires_python = ">=3.8"
summary = "High level compatibility layer for multiple asynchronous event loop implementations"
groups = ["default"]
groups = ["default", "dev"]
dependencies = [
"exceptiongroup>=1.0.2; python_version < \"3.11\"",
"idna>=2.8",
@ -109,6 +109,21 @@ files = [
{file = "arclet_alconna_tools-0.7.6.tar.gz", hash = "sha256:7cb7dc54c1c2198529c63227739423401051b8489374f1a7a3efa0c4e70b2a22"},
]
[[package]]
name = "arrow"
version = "1.3.0"
requires_python = ">=3.8"
summary = "Better dates & times for Python"
groups = ["dev"]
dependencies = [
"python-dateutil>=2.7.0",
"types-python-dateutil>=2.8.10",
]
files = [
{file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"},
{file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"},
]
[[package]]
name = "backports-tarfile"
version = "1.2.0"
@ -121,6 +136,19 @@ files = [
{file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"},
]
[[package]]
name = "binaryornot"
version = "0.4.4"
summary = "Ultra-lightweight pure Python package to check if a file is binary or text."
groups = ["dev"]
dependencies = [
"chardet>=3.0.2",
]
files = [
{file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"},
{file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"},
]
[[package]]
name = "cachetools"
version = "5.3.3"
@ -132,6 +160,17 @@ files = [
{file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"},
]
[[package]]
name = "cashews"
version = "7.1.0"
requires_python = ">=3.8"
summary = "cache tools with async power"
groups = ["dev"]
files = [
{file = "cashews-7.1.0-py3-none-any.whl", hash = "sha256:b7c1ae4d49df6fdbff88e5025d3c1156515f58724c5b96fc9a9d081afada82a8"},
{file = "cashews-7.1.0.tar.gz", hash = "sha256:058df55a39cb15697d331e7e41c2882b58d0d323f5671316105cc78668af7705"},
]
[[package]]
name = "certifi"
version = "2024.6.2"
@ -189,6 +228,17 @@ files = [
{file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
]
[[package]]
name = "chardet"
version = "5.2.0"
requires_python = ">=3.7"
summary = "Universal encoding detector for Python 3"
groups = ["dev"]
files = [
{file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
{file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
]
[[package]]
name = "charset-normalizer"
version = "3.3.2"
@ -250,7 +300,7 @@ name = "click"
version = "8.1.7"
requires_python = ">=3.7"
summary = "Composable command line interface toolkit"
groups = ["default"]
groups = ["default", "dev"]
dependencies = [
"colorama; platform_system == \"Windows\"",
]
@ -264,13 +314,34 @@ name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
groups = ["default"]
groups = ["default", "dev"]
marker = "sys_platform == \"win32\" or platform_system == \"Windows\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "cookiecutter"
version = "2.6.0"
requires_python = ">=3.7"
summary = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template."
groups = ["dev"]
dependencies = [
"Jinja2<4.0.0,>=2.7",
"arrow",
"binaryornot>=0.4.4",
"click<9.0.0,>=7.0",
"python-slugify>=4.0.0",
"pyyaml>=5.3.1",
"requests>=2.23.0",
"rich",
]
files = [
{file = "cookiecutter-2.6.0-py3-none-any.whl", hash = "sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d"},
{file = "cookiecutter-2.6.0.tar.gz", hash = "sha256:db21f8169ea4f4fdc2408d48ca44859349de2647fbe494a9d6c3edfc0542c21c"},
]
[[package]]
name = "cryptography"
version = "42.0.8"
@ -316,6 +387,16 @@ files = [
{file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"},
]
[[package]]
name = "distlib"
version = "0.3.8"
summary = "Distribution utilities"
groups = ["dev"]
files = [
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
]
[[package]]
name = "dnspython"
version = "2.6.1"
@ -372,7 +453,7 @@ name = "exceptiongroup"
version = "1.2.1"
requires_python = ">=3.7"
summary = "Backport of PEP 654 (exception groups)"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
@ -417,6 +498,17 @@ files = [
{file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"},
]
[[package]]
name = "filelock"
version = "3.15.4"
requires_python = ">=3.8"
summary = "A platform independent file lock."
groups = ["dev"]
files = [
{file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"},
{file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"},
]
[[package]]
name = "filetype"
version = "1.2.0"
@ -469,7 +561,7 @@ name = "h11"
version = "0.14.0"
requires_python = ">=3.7"
summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
groups = ["default"]
groups = ["default", "dev"]
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@ -480,7 +572,7 @@ name = "httpcore"
version = "1.0.5"
requires_python = ">=3.8"
summary = "A minimal low-level HTTP client."
groups = ["default"]
groups = ["default", "dev"]
dependencies = [
"certifi",
"h11<0.15,>=0.13",
@ -526,7 +618,7 @@ name = "httpx"
version = "0.27.0"
requires_python = ">=3.8"
summary = "The next generation HTTP client."
groups = ["default"]
groups = ["default", "dev"]
dependencies = [
"anyio",
"certifi",
@ -635,7 +727,7 @@ name = "jinja2"
version = "3.1.4"
requires_python = ">=3.7"
summary = "A very fast and expressive template engine."
groups = ["default"]
groups = ["default", "dev"]
dependencies = [
"MarkupSafe>=2.0",
]
@ -723,7 +815,7 @@ name = "markupsafe"
version = "2.1.5"
requires_python = ">=3.7"
summary = "Safely add untrusted strings to HTML/XML markup."
groups = ["default"]
groups = ["default", "dev"]
files = [
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
@ -836,6 +928,33 @@ files = [
{file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
]
[[package]]
name = "nb-cli"
version = "1.4.1"
requires_python = "<4.0,>=3.9"
summary = "CLI for nonebot2"
groups = ["dev"]
dependencies = [
"anyio<5.0,>=3.6",
"cashews<8.0,>=6.0",
"click~=8.1",
"cookiecutter~=2.2",
"httpx~=0.18",
"jinja2~=3.0",
"noneprompt<1.0.0,>=0.1.9",
"pydantic!=2.5.0,!=2.5.1,<3.0.0,>=1.10.0",
"pyfiglet<2.0.0,>=1.0.1",
"tomlkit~=0.10",
"typing-extensions~=4.4",
"virtualenv~=20.21",
"watchfiles~=0.16",
"wcwidth~=0.2",
]
files = [
{file = "nb_cli-1.4.1-py3-none-any.whl", hash = "sha256:57b6111773202bce29c0520f4a281edb8a7643fa33692d4afc70ca5b51b10f70"},
{file = "nb_cli-1.4.1.tar.gz", hash = "sha256:908dd4cbbf66bf46fe879c23ad1377332f63385cebca1912b627aa686d1816f3"},
]
[[package]]
name = "nepattern"
version = "0.7.4"
@ -1119,6 +1238,20 @@ files = [
{file = "nonebot2-2.3.1.tar.gz", hash = "sha256:ac5a1a1759f15310e9183b606ce6bdbe52a90287bf36a69201be548e23d41e6c"},
]
[[package]]
name = "noneprompt"
version = "0.1.9"
requires_python = ">=3.8,<4.0"
summary = "Prompt toolkit for console interaction"
groups = ["dev"]
dependencies = [
"prompt-toolkit<4.0.0,>=3.0.19",
]
files = [
{file = "noneprompt-0.1.9-py3-none-any.whl", hash = "sha256:a54f1e6a19a3da2dedf7f365f80420e9ae49326a0ffe60a8a9c7afdee6b6eeb3"},
{file = "noneprompt-0.1.9.tar.gz", hash = "sha256:338b8bb89a8d22ef35f1dedb3aa7c1b228cf139973bdc43c5ffc3eef64457db9"},
]
[[package]]
name = "orjson"
version = "3.10.5"
@ -1167,6 +1300,17 @@ files = [
{file = "pkginfo-1.11.1.tar.gz", hash = "sha256:2e0dca1cf4c8e39644eed32408ea9966ee15e0d324c62ba899a393b3c6b467aa"},
]
[[package]]
name = "platformdirs"
version = "4.2.2"
requires_python = ">=3.8"
summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
groups = ["dev"]
files = [
{file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
{file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
]
[[package]]
name = "playwright"
version = "1.44.0"
@ -1201,6 +1345,20 @@ files = [
{file = "prettytable-3.10.0.tar.gz", hash = "sha256:9665594d137fb08a1117518c25551e0ede1687197cf353a4fdc78d27e1073568"},
]
[[package]]
name = "prompt-toolkit"
version = "3.0.47"
requires_python = ">=3.7.0"
summary = "Library for building powerful interactive command lines in Python"
groups = ["dev"]
dependencies = [
"wcwidth",
]
files = [
{file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"},
{file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"},
]
[[package]]
name = "pycparser"
version = "2.22"
@ -1218,7 +1376,7 @@ name = "pydantic"
version = "2.7.4"
requires_python = ">=3.8"
summary = "Data validation using Python type hints"
groups = ["default"]
groups = ["default", "dev"]
dependencies = [
"annotated-types>=0.4.0",
"pydantic-core==2.18.4",
@ -1234,7 +1392,7 @@ name = "pydantic-core"
version = "2.18.4"
requires_python = ">=3.8"
summary = "Core functionality for Pydantic validation and serialization"
groups = ["default"]
groups = ["default", "dev"]
dependencies = [
"typing-extensions!=4.7.0,>=4.6.0",
]
@ -1325,6 +1483,17 @@ files = [
{file = "pyee-11.1.0.tar.gz", hash = "sha256:b53af98f6990c810edd9b56b87791021a8f54fd13db4edd1142438d44ba2263f"},
]
[[package]]
name = "pyfiglet"
version = "1.0.2"
requires_python = ">=3.9"
summary = "Pure-python FIGlet implementation"
groups = ["dev"]
files = [
{file = "pyfiglet-1.0.2-py3-none-any.whl", hash = "sha256:889b351d79c99e50a3f619c8f8e6ffdb27fd8c939fc43ecbd7559bd57d5f93ea"},
{file = "pyfiglet-1.0.2.tar.gz", hash = "sha256:758788018ab8faaddc0984e1ea05ff330d3c64be663c513cc1f105f6a3066dab"},
]
[[package]]
name = "pygal"
version = "3.0.4"
@ -1375,6 +1544,20 @@ files = [
{file = "pymdown_extensions-10.8.1.tar.gz", hash = "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940"},
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
summary = "Extensions to the standard Python datetime module"
groups = ["dev"]
dependencies = [
"six>=1.5",
]
files = [
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[[package]]
name = "python-dotenv"
version = "1.0.1"
@ -1411,6 +1594,20 @@ files = [
{file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
]
[[package]]
name = "python-slugify"
version = "8.0.4"
requires_python = ">=3.7"
summary = "A Python slugify application that also handles Unicode"
groups = ["dev"]
dependencies = [
"text-unidecode>=1.3",
]
files = [
{file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"},
{file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"},
]
[[package]]
name = "pytz"
version = "2024.1"
@ -1438,7 +1635,7 @@ name = "pyyaml"
version = "6.0.1"
requires_python = ">=3.6"
summary = "YAML parser and emitter for Python"
groups = ["default"]
groups = ["default", "dev"]
files = [
{file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
{file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
@ -1658,7 +1855,7 @@ name = "six"
version = "1.16.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
summary = "Python 2 and 3 compatibility utilities"
groups = ["default"]
groups = ["default", "dev"]
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
@ -1669,7 +1866,7 @@ name = "sniffio"
version = "1.3.1"
requires_python = ">=3.7"
summary = "Sniff out which async library your code is running under"
groups = ["default"]
groups = ["default", "dev"]
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@ -1779,6 +1976,16 @@ files = [
{file = "tarina-0.5.2.tar.gz", hash = "sha256:7d5d93d73422e97b2409e6a43bf4d11296fe2dac90a5b4dcbc19e56bc1b55298"},
]
[[package]]
name = "text-unidecode"
version = "1.3"
summary = "The most basic Text::Unidecode port"
groups = ["dev"]
files = [
{file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
]
[[package]]
name = "tomli"
version = "2.0.1"
@ -1791,6 +1998,17 @@ files = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "tomlkit"
version = "0.12.5"
requires_python = ">=3.7"
summary = "Style preserving TOML library"
groups = ["dev"]
files = [
{file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"},
{file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"},
]
[[package]]
name = "twine"
version = "5.1.0"
@ -1830,12 +2048,23 @@ files = [
{file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
]
[[package]]
name = "types-python-dateutil"
version = "2.9.0.20240316"
requires_python = ">=3.8"
summary = "Typing stubs for python-dateutil"
groups = ["dev"]
files = [
{file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"},
{file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"},
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
groups = ["default"]
groups = ["default", "dev"]
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
@ -2012,12 +2241,28 @@ files = [
{file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"},
]
[[package]]
name = "virtualenv"
version = "20.26.3"
requires_python = ">=3.7"
summary = "Virtual Python Environment builder"
groups = ["dev"]
dependencies = [
"distlib<1,>=0.3.7",
"filelock<4,>=3.12.2",
"platformdirs<5,>=3.9.1",
]
files = [
{file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"},
{file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"},
]
[[package]]
name = "watchfiles"
version = "0.22.0"
requires_python = ">=3.8"
summary = "Simple, modern and high performance file watching and code reload in python."
groups = ["default"]
groups = ["default", "dev"]
dependencies = [
"anyio>=3.0.0",
]
@ -2079,7 +2324,7 @@ files = [
name = "wcwidth"
version = "0.2.13"
summary = "Measures the displayed width of unicode strings in a terminal"
groups = ["default"]
groups = ["default", "dev"]
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},

View File

@ -1,7 +1,7 @@
[project]
name = "nonebot_plugin_dialectlist"
version = "2.0.4"
description = "Default template for PDM package"
version = "2.1.4"
description = "看看你群群友有多能说"
authors = [
{name = "Chen_Xu233", email = "woyerpa@outlook.com"},
]
@ -27,6 +27,7 @@ dev = [
"ruff>=0.4.9",
"setuptools>=70.0.0",
"twine>=5.1.0",
"nb-cli>=0.7.6"
]
[tool.pdm]

View File

@ -4,7 +4,9 @@ with sync_playwright() as p:
for browser_type in [p.chromium, p.firefox, p.webkit]:
browser = browser_type.launch(headless=True)
page = browser.new_page()
page.goto('file:///H:/Bot/%E7%8E%B0%E5%BD%B9Bot/mybot/src/nonebot_plugin_dialectlist/nonebot_plugin_dialectlist/temple/rank_temple.html')
page.screenshot(path=f'screenshot-{browser_type.name}.png',full_page=True)
page.goto(
"file:///H:/Bot/%E7%8E%B0%E5%BD%B9Bot/mybot/src/nonebot_plugin_dialectlist/nonebot_plugin_dialectlist/temple/rank_temple.html"
)
page.screenshot(path=f"screenshot-{browser_type.name}.png", full_page=True)
print(page.title())
browser.close()
browser.close()