mirror of
https://github.com/LiteyukiStudio/nonebot-plugin-acgnshow.git
synced 2025-02-18 16:47:47 +08:00
✨v0.2,不会接受字数大于5的展览查询,添加展览详细信息展示,返回详细错误信息
This commit is contained in:
parent
80a9b4d565
commit
d5eeea6c80
@ -80,3 +80,4 @@ _✨ 从哔哩哔哩会员购获取展览简易信息 ✨_
|
||||
| 配置项 | 必填 | 默认值 | 说明 |
|
||||
| :---------------: | :--: | :----: | :----------------------------------------------------------: |
|
||||
| ACGNSHOW_PAGESIZE | 否 | 8 | 单个图片的条目数,最大为 20,条目数过大可能导致 Bot 无法发送 |
|
||||
| ACGNSHOW_BGIMAGE_PATH | 否 | 插件内置背景图 | 插件返回图片的背景图目录路径 |
|
@ -10,14 +10,19 @@ usage = """命令格式:
|
||||
展览 <地区> [页码]
|
||||
或
|
||||
<地区>展览 [页码]
|
||||
获取指定地区的展览列表
|
||||
其中地区为省级行政区或地级行政区(不包含后缀)
|
||||
(如北京,福建,平顶山,绍兴,香港...,或海外/全国)
|
||||
|
||||
展览详情 <ID>
|
||||
其中ID为展览列表处返回的ID
|
||||
|
||||
示例:
|
||||
展览 福建 2
|
||||
福建展览 2
|
||||
全国展览
|
||||
海外展览
|
||||
展览详情 86827
|
||||
|
||||
※数据来源于哔哩哔哩会员购,由于API返回结果与实际存在差异,数据可能不准确。"""
|
||||
__author__ = "Asankilp"
|
||||
|
@ -7,6 +7,7 @@ from .util import *
|
||||
|
||||
CITY_API_ROOT = "https://show.bilibili.com/api/ticket/city/list?channel=3"
|
||||
SHOWS_API_ROOT = "https://show.bilibili.com/api/ticket/project/listV2"
|
||||
SHOW_DETAILS_API_ROOT = "https://show.bilibili.com/api/ticket/project/getV2"
|
||||
HEADERS = {
|
||||
"user-agent": "Mozilla/5.0 (Linux; Android 14; 114514YAJU Build/UKQ1.114514.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/125.0.6422.165 Mobile Safari/537.36 BiliApp/7810200 mobi_app/android isNotchWindow/0 NotchHeight=34 mallVersion/7810200 mVersion/242 disable_rcmd/0 7.81.0 os/android model/114514YAJU mobi_app/android build/7810200 channel/bilih5 innerVer/7810210 osVer/14 network/2"
|
||||
}
|
||||
@ -57,28 +58,74 @@ async def get_shows_data(region_id: int, page=1, pagesize=20):
|
||||
shows_data = await resp.json()
|
||||
return shows_data
|
||||
|
||||
async def get_show_details(show_id: int):
|
||||
param = {
|
||||
"id": show_id,
|
||||
"project_id": show_id,
|
||||
"requestSource": "neul-next"
|
||||
}
|
||||
async with ClientSession() as session:
|
||||
async with session.get(SHOW_DETAILS_API_ROOT, headers=HEADERS, params=param) as resp:
|
||||
show_details_data = await resp.json()
|
||||
return show_details_data
|
||||
|
||||
# def process_shows_data_to_text(shows_data: dict):
|
||||
# showlist = []
|
||||
# data = shows_data["data"]
|
||||
# total_pages = data["numPages"]
|
||||
# result = data["result"]
|
||||
# for i in result:
|
||||
# name = i["project_name"]
|
||||
# venue_name = i["venue_name"]
|
||||
# project_id = i["project_id"]
|
||||
# sale_flag = i["sale_flag"]
|
||||
# # start_time = i["start_time"]
|
||||
# start_unix = i["start_unix"]
|
||||
# start_time = convert_timestamp(start_unix)
|
||||
# end_time = i["end_time"]
|
||||
# price_low = i["price_low"] / 100
|
||||
# price_high = i["price_high"] / 100
|
||||
# district_name = i["district_name"]
|
||||
# text = f"名称:{name}\n举办地:{venue_name}\nid:{project_id}\nflag:{sale_flag}\n开始时间:{start_time}\n结束时间:{end_time}\n最低票价:{price_low}\n最高票价:{price_high}\n区名:{district_name}\n\n"
|
||||
# showlist.append(text)
|
||||
# return showlist
|
||||
def process_show_details_data_to_template(show_details_data: dict):
|
||||
data = show_details_data["data"]
|
||||
|
||||
banner_url = "https:"+data["banner"]
|
||||
# banner_url = extract_banner_url(data["performance_image"])
|
||||
|
||||
# 提取事件基本信息
|
||||
name = data["name"]
|
||||
start_time = convert_timestamp(data["start_time"])
|
||||
end_time = convert_timestamp(data["end_time"])
|
||||
|
||||
# 提取场馆信息
|
||||
venue_name = data["venue_info"]["name"]
|
||||
venue_detail = data["venue_info"]["address_detail"]
|
||||
|
||||
# 提取主办方信息
|
||||
organizer = data["merchant"]["company"]
|
||||
|
||||
# 提取实名制,退票等信息
|
||||
is_refund = data["is_refund"]
|
||||
id_bind = data["id_bind"]
|
||||
has_eticket = data["has_eticket"]
|
||||
|
||||
# 提取票务信息
|
||||
ticket_info = []
|
||||
for screen in data.get("screen_list", []):
|
||||
for ticket in screen.get("ticket_list", []):
|
||||
ticket_info.append({
|
||||
"description": ticket.get("desc", ""),
|
||||
"price": ticket.get("price", 0),
|
||||
"sale_start": convert_timestamp(ticket.get("saleStart", 0)),
|
||||
"sale_end": convert_timestamp(ticket.get("saleEnd", 0)),
|
||||
"status": ticket.get("sale_flag", {}).get("display_name", "")
|
||||
})
|
||||
guests_list = data["guests"]
|
||||
if guests_list != None:
|
||||
guests = "、".join(n["name"] for n in guests_list)
|
||||
else:
|
||||
guests = ""
|
||||
|
||||
# 构建返回的字典
|
||||
item_dict = {
|
||||
"banner_url": banner_url,
|
||||
"name": name,
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
"venue_name": venue_name,
|
||||
"venue_detail": venue_detail,
|
||||
"organizer": organizer,
|
||||
"ticket_info": ticket_info,
|
||||
"guests": guests,
|
||||
"is_refund": is_refund,
|
||||
"id_bind": id_bind,
|
||||
"has_eticket": has_eticket
|
||||
}
|
||||
|
||||
return item_dict
|
||||
|
||||
def process_shows_data_to_template(shows_data: dict):
|
||||
showlist = []
|
||||
|
@ -7,7 +7,7 @@ from nonebot_plugin_htmlrender import template_to_pic
|
||||
from nonebot_plugin_alconna import on_alconna
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from arclet.alconna import Alconna, Args
|
||||
from .config import RES_PATH, TEMPLATE_NAME, config
|
||||
from .config import RES_PATH, LIST_TEMPLATE_NAME, DETAILS_TEMPLATE_NAME, config
|
||||
from .util import *
|
||||
from .__init__ import __plugin_meta__
|
||||
|
||||
@ -25,10 +25,43 @@ showcmd.shortcut(
|
||||
"args": ["{region}", "{page}", "{date}"],
|
||||
},
|
||||
)
|
||||
showcmd_details = on_alconna(
|
||||
Alconna(
|
||||
"展览详情",
|
||||
Args["id?", int], # 这里定义了一个必需的 int 参数 "id"
|
||||
)
|
||||
)
|
||||
showcmd_details.shortcut(
|
||||
r"展览详情\s*(?P<id>\d+)", # 正则表达式匹配 "展览详情" 后跟一个整数 ID
|
||||
{
|
||||
"prefix": True,
|
||||
"command": "展览详情",
|
||||
"args": ["{id}"], # 将 ID 参数传递给命令
|
||||
},
|
||||
)
|
||||
|
||||
@showcmd_details.handle()
|
||||
async def get_show_details_cmd(
|
||||
id: Optional[int] = None
|
||||
):
|
||||
show_details = await get_show_details(id)
|
||||
if show_details["errno"] != 0: await UniMessage("发生错误").send() ; return
|
||||
try:
|
||||
show_details_data = process_show_details_data_to_template(show_details)
|
||||
print(show_details_data)
|
||||
template = {
|
||||
"show": show_details_data,
|
||||
"bgimage": choose_random_bgimage(),
|
||||
}
|
||||
pic = await template_to_pic(str(RES_PATH), DETAILS_TEMPLATE_NAME, template)
|
||||
except Exception as e:
|
||||
await UniMessage(f"图片生成时产生错误:\n{str(e)}").send()
|
||||
traceback.print_exc()
|
||||
return
|
||||
await UniMessage.image(raw=pic).send()
|
||||
|
||||
@showcmd.handle()
|
||||
async def find_show(
|
||||
async def find_shows_cmd(
|
||||
region: Optional[str] = None,
|
||||
page: Optional[int] = None,
|
||||
date: Optional[str] = None,
|
||||
@ -36,6 +69,8 @@ async def find_show(
|
||||
if not region:
|
||||
await UniMessage(__plugin_meta__.usage).send()
|
||||
return
|
||||
if len(region) > 5:
|
||||
return
|
||||
if not page:
|
||||
page = 1
|
||||
if not date:
|
||||
@ -55,9 +90,9 @@ async def find_show(
|
||||
"bgimage": choose_random_bgimage(),
|
||||
"global_data": shows_data[1],
|
||||
}
|
||||
pic = await template_to_pic(str(RES_PATH), TEMPLATE_NAME, template)
|
||||
pic = await template_to_pic(str(RES_PATH), LIST_TEMPLATE_NAME, template)
|
||||
except Exception as e:
|
||||
await UniMessage("图片生成时产生未知错误").send()
|
||||
await UniMessage(f"图片生成时产生错误:\n{str(e)}").send()
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
|
@ -3,7 +3,8 @@ from pydantic import BaseModel
|
||||
from nonebot import get_plugin_config
|
||||
|
||||
RES_PATH = Path(__file__).parent / "res"
|
||||
TEMPLATE_NAME = "template.html"
|
||||
LIST_TEMPLATE_NAME = "template.html"
|
||||
DETAILS_TEMPLATE_NAME = "details.html"
|
||||
BGIMAGE_PATH = RES_PATH / "bgimage"
|
||||
|
||||
|
||||
|
@ -7,31 +7,52 @@ body {
|
||||
font-family: "Source Han Sans", sans-serif;
|
||||
}
|
||||
|
||||
.container, .header, .content, .footer, .details, .image {
|
||||
border: 1px solid black;
|
||||
box-sizing: border-box;
|
||||
.background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
filter: blur(40%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 98%;
|
||||
margin: 1%;
|
||||
padding: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border: 1px solid #fff;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(255, 255, 255, 0.8); /* 半透明白色背景 */
|
||||
}
|
||||
|
||||
.header, .designer {
|
||||
.header {
|
||||
border: 1px solid black;
|
||||
padding: 5px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
border: 1px solid black;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.detail_content {
|
||||
display: flex;
|
||||
border: 1px solid black;
|
||||
height: 201px;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.image {
|
||||
@ -39,6 +60,37 @@ body {
|
||||
height: 201px;
|
||||
border: 1px solid rgba(128, 64, 128, 0.5);
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vector_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: bottom;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
margin-bottom: 15px;
|
||||
border: 2px solid #000; /* 为 banner 添加边框 */
|
||||
display: flex;
|
||||
justify-content: center; /* 居中对齐图片 */
|
||||
align-items: center; /* 垂直居中对齐图片 */
|
||||
}
|
||||
|
||||
.banner img {
|
||||
width: 450px;
|
||||
height: 253px;
|
||||
}
|
||||
|
||||
.details .ticket_box {
|
||||
display: inline-block; /* 根据内容的宽度自动调整 */
|
||||
background-color: rgba(255, 255, 255, 0.1); /* 半透明白色背景 */
|
||||
border-radius: 15px; /* 圆角半径 */
|
||||
padding: 20px; /* 内边距 */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 添加阴影以增强视觉效果 */
|
||||
text-align: center; /* 内容水平居中 */
|
||||
width: auto; /* 根据内容宽度自动调整 */
|
||||
}
|
||||
|
||||
.details {
|
||||
@ -46,38 +98,31 @@ body {
|
||||
padding-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative; /* 使子元素绝对定位相对于这个父元素 */
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details .title {
|
||||
font-weight: bold;
|
||||
font-weight: bold; /* 加粗字体 */
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.details .venue_name,
|
||||
.details .guests,
|
||||
.details .placeholder,
|
||||
.details .price,
|
||||
.details .wish,
|
||||
.details .start-time,
|
||||
.details .end-time {
|
||||
.details .placeholder {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.details .sale_flag,
|
||||
.details .id {
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.details .sale_flag {
|
||||
color: red;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.details .id {
|
||||
color: black;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 5px;
|
||||
}
|
||||
@ -85,35 +130,56 @@ body {
|
||||
.details .price {
|
||||
color: #fb7299;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.details .status {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
.details .wish {
|
||||
color: gray;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 60px;
|
||||
left: 60px; /* 适当调整以避免与price重叠 */
|
||||
}
|
||||
|
||||
.details .start-time {
|
||||
bottom: 20px;
|
||||
position: absolute;
|
||||
bottom: 20px; /* 与end-time有一定的高度差异 */
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.details .end-time {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border: 1px solid black;
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.footer .pages,
|
||||
.footer .designer {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
padding: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.footer .pages,
|
||||
.footer .total_results {
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
|
65
nonebot_plugin_acgnshow/res/details.html
Normal file
65
nonebot_plugin_acgnshow/res/details.html
Normal file
@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>展览信息</title>
|
||||
<link rel="stylesheet" href="./css/style.css" />
|
||||
<style>
|
||||
body {
|
||||
background-image: url("{{ bgimage }}");
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="background"></div>
|
||||
<div class="container">
|
||||
<div class="header">展览信息</div>
|
||||
|
||||
<!-- Banner 图 -->
|
||||
<div class="banner">
|
||||
<img src="{{ show.banner_url }}" alt="Banner" style="width: 450px; height: 253px;" />
|
||||
</div>
|
||||
|
||||
<!-- 单个展览内容 -->
|
||||
<div class="detail_content">
|
||||
<div class="details">
|
||||
<div class="title">{{ show.name }}</div>
|
||||
<div class="venue_name">地点:{{ show.venue_name }} {{show.venue_detail}}</div>
|
||||
<div class="start-time">开始时间:{{ show.start_time }}</div>
|
||||
<div class="end-time">结束时间:{{ show.end_time }}</div>
|
||||
<div class="organizer">主办方:{{ show.organizer }}</div>
|
||||
<div class="status">
|
||||
{% macro icon(name) %}
|
||||
<img src="./svg/{{ name }}.svg" class="vector_icon" width="8px" height="8px">
|
||||
{% endmacro %}
|
||||
{% if show.is_refund == 0 %}
|
||||
{{ icon('no') }} 不可退
|
||||
{% endif %}
|
||||
{% if show.id_bind == 1 %}
|
||||
{{ icon('yes') }} 实名制
|
||||
{% endif %}
|
||||
{% if show.has_eticket == True %}
|
||||
{{ icon('yes') }} 电子票/兑换票
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="placeholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="details">
|
||||
{% for ticket in show.ticket_info %}
|
||||
<div class="ticket_box">{{ ticket.description }} {{ ticket.sale_start }} -- {{ ticket.sale_end }} ¥{{ ticket.price / 100 }} ({{ ticket.status }})</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<div class="footer">
|
||||
<div class="designer">Designed by Asankilp?</div>
|
||||
<div class="project_name">nonebot-plugin-acgnshow</div>
|
||||
<div class="notice_text">本页信息仅供参考,具体内容请访问展览官方详情页,并自行检索实际信息</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
1
nonebot_plugin_acgnshow/res/svg/no.svg
Normal file
1
nonebot_plugin_acgnshow/res/svg/no.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724505806097" class="icon" viewBox="0 0 1028 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10250" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.78125" height="200"><path d="M875.086452 153.730058C676.053818-45.302575 353.260522-45.302575 154.128323 153.730058s-199.032634 521.825929 0 720.958129 521.825929 199.032634 720.958129 0 199.032634-521.825929 0-720.958129zM725.836868 725.438604c-9.757478 9.757478-25.488922 9.757478-35.246399 0L514.557604 549.405739 338.624306 725.438604c-9.757478 9.757478-25.488922 9.757478-35.2464 0s-9.757478-25.488922 0-35.2464l176.032865-176.032864-176.032865-175.933299c-9.757478-9.757478-9.757478-25.488922 0-35.246399 9.757478-9.757478 25.488922-9.757478 35.2464 0l176.032864 176.032865 176.032865-176.032865c9.757478-9.757478 25.488922-9.757478 35.246399 0 9.757478 9.757478 9.757478 25.488922 0 35.246399L549.804004 514.15934 725.836868 690.192204c9.657912 9.757478 9.657912 25.488922 0 35.2464z" fill="#F56C6C" p-id="10251"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
nonebot_plugin_acgnshow/res/svg/yes.svg
Normal file
1
nonebot_plugin_acgnshow/res/svg/yes.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724506220274" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13079" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 76.8c-236.8 0-435.2 192-435.2 435.2s192 435.2 435.2 435.2 435.2-192 435.2-435.2S748.8 76.8 512 76.8z m249.6 320L480 704c-12.8 12.8-38.4 12.8-51.2 0L288 556.8c-12.8-12.8-12.8-38.4 0-51.2 12.8-12.8 38.4-12.8 51.2 0l115.2 115.2L704 339.2c12.8-12.8 38.4-12.8 51.2 0 25.6 12.8 25.6 38.4 6.4 57.6z" fill="#68D279" p-id="13080"></path><path d="M710.4 339.2l-256 281.6-115.2-115.2c-12.8-12.8-38.4-12.8-51.2 0-12.8 12.8-12.8 38.4 0 51.2l147.2 140.8c12.8 12.8 38.4 12.8 51.2 0L768 390.4c12.8-12.8 12.8-38.4 0-51.2-19.2-12.8-44.8-12.8-57.6 0z" fill="#FFFFFF" p-id="13081"></path></svg>
|
After Width: | Height: | Size: 913 B |
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import random
|
||||
import datetime
|
||||
import json
|
||||
from pathlib import Path
|
||||
from .config import config
|
||||
|
||||
@ -20,3 +21,8 @@ def convert_timestamp(timestamp) -> str:
|
||||
将时间戳转换为日期格式
|
||||
"""
|
||||
return datetime.datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def extract_banner_url(value) -> str:
|
||||
a = json.loads(value)
|
||||
url = "https:"+a["banner"]["url"]
|
||||
return url
|
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "nonebot-plugin-acgnshow"
|
||||
version = "0.1.5.1"
|
||||
version = "0.2"
|
||||
description = "Nonebot2插件,从哔哩哔哩会员购获取简易展览数据"
|
||||
readme = "README.md"
|
||||
requires-python = "<4.0,>=3.9"
|
||||
@ -10,7 +10,7 @@ dependencies = [
|
||||
"nonebot-plugin-alconna>=0.48.0",
|
||||
"nonebot-plugin-htmlrender>=0.3.2",
|
||||
"jinja2>=3.1.4",
|
||||
"aiohttp>=3.9",
|
||||
"aiohttp>=3.9"
|
||||
]
|
||||
license = { text = "MIT" }
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user