mirror of
https://github.com/Xtao-Labs/iShotaBot.git
synced 2024-11-30 19:30:41 +00:00
feat: exchange inline query
This commit is contained in:
parent
c20bce5105
commit
b186e7ad2f
@ -21,8 +21,8 @@ class Exchange:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def check_ex(self, message):
|
||||
tlist = message.text.split()
|
||||
async def check_ex(self, text: str):
|
||||
tlist = text.split()
|
||||
if not 2 < len(tlist) < 5:
|
||||
return "help"
|
||||
elif len(tlist) == 3:
|
||||
|
145
defs/fragment.py
145
defs/fragment.py
@ -1,145 +0,0 @@
|
||||
import contextlib
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from init import request
|
||||
from models.services.fragment import (
|
||||
AuctionStatus,
|
||||
UserName,
|
||||
TON_TO_USD_RATE,
|
||||
Price,
|
||||
FragmentSubText,
|
||||
FragmentSub,
|
||||
)
|
||||
|
||||
|
||||
class NotAvailable(Exception):
|
||||
pass
|
||||
|
||||
|
||||
async def get_fragment_html(username: str):
|
||||
try:
|
||||
resp = await request.get(
|
||||
f"https://fragment.com/username/{username}", follow_redirects=False
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
return resp.text
|
||||
except AssertionError as e:
|
||||
raise AssertionError from e
|
||||
except Exception as e:
|
||||
raise NotAvailable from e
|
||||
|
||||
|
||||
def refresh_rate(html: str) -> None:
|
||||
pattern = re.compile(r'"tonRate":"(.+)"}}')
|
||||
with contextlib.suppress(Exception):
|
||||
TON_TO_USD_RATE["rate"] = float(pattern.findall(html)[0])
|
||||
|
||||
|
||||
def parse_user(username: str, html: str) -> UserName:
|
||||
soup = BeautifulSoup(html, "lxml")
|
||||
try:
|
||||
refresh_rate(html)
|
||||
status = AuctionStatus(
|
||||
soup.find("span", {"class": "tm-section-header-status"}).getText()
|
||||
)
|
||||
if status == AuctionStatus.OnAuction and "Highest Bid" not in html:
|
||||
status = AuctionStatus.Available
|
||||
user = UserName(name=username, status=status)
|
||||
if user.status == AuctionStatus.Available:
|
||||
user.now_price = Price(
|
||||
ton=int(
|
||||
soup.find(
|
||||
"div",
|
||||
{"class": "table-cell-value tm-value icon-before icon-ton"},
|
||||
)
|
||||
.getText()
|
||||
.replace(",", "")
|
||||
)
|
||||
)
|
||||
elif user.status in [AuctionStatus.OnAuction, AuctionStatus.Sale]:
|
||||
info = soup.find("div", {"class": "tm-section-box tm-section-bid-info"})
|
||||
user.now_price = Price(
|
||||
ton=int(
|
||||
info.find(
|
||||
"div",
|
||||
{"class": "table-cell-value tm-value icon-before icon-ton"},
|
||||
)
|
||||
.getText()
|
||||
.replace(",", "")
|
||||
)
|
||||
)
|
||||
user.end_time = datetime.fromisoformat(
|
||||
soup.find("time", {"class": "tm-countdown-timer"})["datetime"]
|
||||
)
|
||||
elif user.status == AuctionStatus.Sold:
|
||||
info = soup.find("div", {"class": "tm-section-box tm-section-bid-info"})
|
||||
user.now_price = Price(
|
||||
ton=int(
|
||||
info.find(
|
||||
"div",
|
||||
{"class": "table-cell-value tm-value icon-before icon-ton"},
|
||||
)
|
||||
.getText()
|
||||
.replace(",", "")
|
||||
)
|
||||
)
|
||||
user.purchaser = info.find("a")["href"].split("/")[-1]
|
||||
user.end_time = datetime.fromisoformat(info.find("time")["datetime"])
|
||||
return user
|
||||
except (AttributeError, ValueError) as e:
|
||||
raise NotAvailable from e
|
||||
|
||||
|
||||
async def search_fragment_html(username: str) -> str:
|
||||
try:
|
||||
resp = await request.get(
|
||||
f"https://fragment.com/?query={username}", follow_redirects=False
|
||||
)
|
||||
return resp.text
|
||||
except Exception as e:
|
||||
raise NotAvailable from e
|
||||
|
||||
|
||||
def search_user(username: str, html: str) -> UserName:
|
||||
soup = BeautifulSoup(html, "lxml")
|
||||
try:
|
||||
user = soup.find_all("tr", {"class": "tm-row-selectable"})[0]
|
||||
status = AuctionStatus(
|
||||
user.find("div", {"class": "table-cell-status-thin"}).getText()
|
||||
)
|
||||
return UserName(name=username, status=status)
|
||||
except (AttributeError, ValueError, IndexError) as e:
|
||||
raise NotAvailable from e
|
||||
|
||||
|
||||
async def parse_fragment(username: str) -> UserName:
|
||||
try:
|
||||
html = await get_fragment_html(username)
|
||||
return parse_user(username, html)
|
||||
except AssertionError:
|
||||
html = await search_fragment_html(username)
|
||||
return search_user(username, html)
|
||||
|
||||
|
||||
async def parse_sub(status: FragmentSubText, user: Optional[UserName], cid: int) -> str:
|
||||
if status == FragmentSubText.Subscribe:
|
||||
if user.status == [AuctionStatus.Sold, AuctionStatus.Unavailable]:
|
||||
return "用户名已被卖出或者已被注册,无法订阅"
|
||||
if await FragmentSub.get_by_cid_and_username(cid, user.name):
|
||||
return "已经订阅过了这个用户名"
|
||||
await FragmentSub.subscribe(cid, user.name)
|
||||
return "订阅成功"
|
||||
elif status == FragmentSubText.Unsubscribe:
|
||||
if data := (await FragmentSub.get_by_cid_and_username(cid, user.name)):
|
||||
await FragmentSub.unsubscribe(data)
|
||||
return "取消订阅成功"
|
||||
return "当前没有订阅这个用户名"
|
||||
elif status == FragmentSubText.List:
|
||||
if data := (await FragmentSub.get_by_cid(cid)):
|
||||
return "目前已订阅:\n\n" + "\n".join(
|
||||
[f"{i+1}. @{d.username}" for i, d in enumerate(data)]
|
||||
)
|
||||
return "还没有订阅任何用户名"
|
31
defs/inline_result_filters.py
Normal file
31
defs/inline_result_filters.py
Normal file
@ -0,0 +1,31 @@
|
||||
import re
|
||||
from re import Pattern
|
||||
from typing import Union
|
||||
|
||||
from pyrogram.filters import create
|
||||
from pyrogram.types import Message, CallbackQuery, InlineQuery, PreCheckoutQuery, ChosenInlineResult, Update
|
||||
|
||||
|
||||
def regex(pattern: Union[str, Pattern], flags: int = 0):
|
||||
async def func(flt, _, update: Update):
|
||||
if isinstance(update, Message):
|
||||
value = update.text or update.caption
|
||||
elif isinstance(update, CallbackQuery):
|
||||
value = update.data
|
||||
elif isinstance(update, (InlineQuery, ChosenInlineResult)):
|
||||
value = update.query
|
||||
elif isinstance(update, PreCheckoutQuery):
|
||||
value = update.invoice_payload
|
||||
else:
|
||||
raise ValueError(f"Regex filter doesn't work with {type(update)}")
|
||||
|
||||
if value:
|
||||
update.matches = list(flt.p.finditer(value)) or None
|
||||
|
||||
return bool(update.matches)
|
||||
|
||||
return create(
|
||||
func,
|
||||
"RegexFilter",
|
||||
p=pattern if isinstance(pattern, Pattern) else re.compile(pattern, flags)
|
||||
)
|
@ -12,6 +12,7 @@ from pyrogram.types import (
|
||||
)
|
||||
from pyrogram.utils import unpack_inline_message_id
|
||||
|
||||
from defs import inline_result_filters
|
||||
from init import bot
|
||||
|
||||
geo_dic = {
|
||||
@ -84,10 +85,8 @@ def get_dc_text(dc: int):
|
||||
return f"此会话所在数据中心为: <b>DC{dc}</b>\n" f"该数据中心位于 <b>{geo_dic[str(dc)]}</b>"
|
||||
|
||||
|
||||
@bot.on_chosen_inline_result()
|
||||
@bot.on_chosen_inline_result(inline_result_filters.regex(r"^dc$"))
|
||||
async def dc_choose_callback(_: Client, chosen_inline_result: ChosenInlineResult):
|
||||
if chosen_inline_result.query != "dc":
|
||||
chosen_inline_result.continue_propagation()
|
||||
mid = chosen_inline_result.inline_message_id
|
||||
if not mid:
|
||||
return
|
||||
|
@ -1,6 +1,6 @@
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.enums import ChatType
|
||||
from pyrogram.types import Message
|
||||
from pyrogram.types import Message, InlineQuery, InlineQueryResultArticle, InputTextMessageContent
|
||||
|
||||
from defs.exchange import exchange_client
|
||||
from scheduler import scheduler
|
||||
@ -12,19 +12,15 @@ async def exchange_refresh() -> None:
|
||||
await exchange_client.refresh()
|
||||
|
||||
|
||||
@bot.on_message(
|
||||
filters.incoming & filters.command(["exchange", f"exchange@{bot.me.username}"])
|
||||
)
|
||||
async def exchange_command(_: Client, message: Message):
|
||||
async def get_text(text: str, inline: bool):
|
||||
if not exchange_client.inited:
|
||||
await exchange_client.refresh()
|
||||
if not exchange_client.inited:
|
||||
return await message.reply("获取汇率数据出现错误!")
|
||||
text = await exchange_client.check_ex(message)
|
||||
return "获取汇率数据出现错误!", False
|
||||
text = await exchange_client.check_ex(text)
|
||||
if text == "help":
|
||||
prefix = "" if inline else "/"
|
||||
text = (
|
||||
"该指令可用于查询汇率。\n使用方式举例:\n/exchange USD CNY - 1 USD 等于多少 CNY\n"
|
||||
"/exchange 11 CNY USD - 11 CNY 等于多少 USD"
|
||||
f"该指令可用于查询汇率。\n使用方式举例:\n{prefix}exchange USD CNY - 1 USD 等于多少 CNY\n"
|
||||
f"{prefix}exchange 11 CNY USD - 11 CNY 等于多少 USD"
|
||||
)
|
||||
elif text == "ValueError":
|
||||
text = "金额不合法"
|
||||
@ -37,9 +33,37 @@ async def exchange_command(_: Client, message: Message):
|
||||
elif text == "ToError":
|
||||
text = "不支持的目标币种。"
|
||||
else:
|
||||
return await message.reply(text)
|
||||
return text, True
|
||||
return text, False
|
||||
|
||||
|
||||
@bot.on_message(
|
||||
filters.incoming & filters.command(["exchange", f"exchange@{bot.me.username}"])
|
||||
)
|
||||
async def exchange_command(_: Client, message: Message):
|
||||
if not exchange_client.inited:
|
||||
await exchange_client.refresh()
|
||||
text, success = await get_text(message.text, False)
|
||||
reply_ = await message.reply(text)
|
||||
if message.chat.type == ChatType.PRIVATE:
|
||||
if not success and message.chat.type == ChatType.PRIVATE:
|
||||
await reply_.reply(
|
||||
"支持货币: <code>" + ", ".join(exchange_client.currencies) + "</code>"
|
||||
)
|
||||
|
||||
|
||||
@bot.on_inline_query(filters.regex("^exchange"))
|
||||
async def exchange_inline(_: Client, inline_query: InlineQuery):
|
||||
text, success = await get_text(inline_query.query, True)
|
||||
results = [
|
||||
InlineQueryResultArticle(
|
||||
title="查询汇率数据成功" if success else "查询汇率数据失败",
|
||||
input_message_content=InputTextMessageContent(message_text=text),
|
||||
# reply_markup=InlineKeyboardMarkup(
|
||||
# [[InlineKeyboardButton(text="重试", callback_data="dc")]]
|
||||
# ),
|
||||
)
|
||||
]
|
||||
await inline_query.answer(
|
||||
results=results,
|
||||
cache_time=0,
|
||||
)
|
||||
|
@ -1,120 +0,0 @@
|
||||
import contextlib
|
||||
import re
|
||||
|
||||
from pyrogram import Client, filters, ContinuePropagation
|
||||
from pyrogram.enums import ChatMemberStatus
|
||||
from pyrogram.types import (
|
||||
InlineQuery,
|
||||
InlineQueryResultArticle,
|
||||
InputTextMessageContent,
|
||||
InlineKeyboardMarkup,
|
||||
InlineKeyboardButton,
|
||||
Message,
|
||||
)
|
||||
|
||||
from models.services.fragment import FragmentSubText, FragmentSub, AuctionStatus
|
||||
from defs.fragment import parse_fragment, NotAvailable, parse_sub
|
||||
from init import bot
|
||||
from scheduler import scheduler, add_delete_message_job
|
||||
|
||||
QUERY_PATTERN = re.compile(r"^@\w[a-zA-Z0-9_]{3,32}$")
|
||||
|
||||
|
||||
@bot.on_message(
|
||||
filters.incoming & filters.command(["username", f"username@{bot.me.username}"])
|
||||
)
|
||||
async def fragment_command(client: Client, message: Message):
|
||||
status = None
|
||||
user = None
|
||||
if len(message.command) <= 1:
|
||||
return await message.reply("没有找到要查询的用户名 ...")
|
||||
elif message.command[1] == "订阅列表":
|
||||
status = FragmentSubText.List
|
||||
elif len(message.command) > 2:
|
||||
if message.command[2] not in ["订阅", "退订"]:
|
||||
return await message.reply("只能查询一个用户名 ...")
|
||||
status = FragmentSubText(message.command[2])
|
||||
if status and message.from_user:
|
||||
data = await client.get_chat_member(message.chat.id, message.from_user.id)
|
||||
if data.status not in [ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.OWNER]:
|
||||
rep = await message.reply("You are not an admin of this chat.")
|
||||
add_delete_message_job(rep)
|
||||
raise ContinuePropagation
|
||||
if status == FragmentSubText.List:
|
||||
text = await parse_sub(status, user, message.chat.id)
|
||||
else:
|
||||
username = message.command[1]
|
||||
if not username.startswith("@"):
|
||||
username = f"@{username}"
|
||||
if not QUERY_PATTERN.match(username):
|
||||
return await message.reply("无效的用户名")
|
||||
username = username[1:]
|
||||
try:
|
||||
user = await parse_fragment(username)
|
||||
text = user.text
|
||||
except NotAvailable:
|
||||
text = "解析失败了 ... 请稍后再试"
|
||||
except Exception:
|
||||
text = "查询失败了 ... 请稍后再试"
|
||||
if status and user is not None:
|
||||
text = await parse_sub(status, user, message.chat.id)
|
||||
await message.reply(text)
|
||||
|
||||
|
||||
@bot.on_inline_query(filters=filters.regex(r"^@\w[a-zA-Z0-9_]{3,32}$"))
|
||||
async def fragment_inline(_, inline_query: InlineQuery):
|
||||
username = inline_query.query
|
||||
username = username[1:]
|
||||
try:
|
||||
user = await parse_fragment(username)
|
||||
text = user.text
|
||||
except NotAvailable:
|
||||
text = f"用户名:@{username}\n状态:暂未开放购买\n"
|
||||
except Exception:
|
||||
text = ""
|
||||
if not text:
|
||||
await inline_query.answer(
|
||||
results=[],
|
||||
switch_pm_text="查询失败了 ~ 呜呜呜",
|
||||
switch_pm_parameter="start",
|
||||
cache_time=0,
|
||||
)
|
||||
inline_query.stop_propagation()
|
||||
results = [
|
||||
InlineQueryResultArticle(
|
||||
title=username,
|
||||
input_message_content=InputTextMessageContent(text),
|
||||
url=f"https://fragment.com/username/{username}",
|
||||
description="点击发送详情",
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
"Open", url=f"https://fragment.com/username/{username}"
|
||||
)
|
||||
]
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
await inline_query.answer(
|
||||
results=results,
|
||||
switch_pm_text="查询成功",
|
||||
switch_pm_parameter="start",
|
||||
cache_time=0,
|
||||
)
|
||||
inline_query.stop_propagation()
|
||||
|
||||
|
||||
@scheduler.scheduled_job("cron", hour="8", minute="1", id="fragment.sub")
|
||||
async def fragment_sub() -> None:
|
||||
data = await FragmentSub.get_all()
|
||||
if not data:
|
||||
return
|
||||
for item in data:
|
||||
with contextlib.suppress(NotAvailable, Exception):
|
||||
user = await parse_fragment(item.username)
|
||||
text = user.text
|
||||
if user.status in [AuctionStatus.Sold, AuctionStatus.Unavailable]:
|
||||
await FragmentSub.unsubscribe(item)
|
||||
await bot.send_message(item.cid, text)
|
@ -64,6 +64,11 @@ async def empty_inline(_, inline_query: InlineQuery):
|
||||
input_message_content=InputTextMessageContent("使用 dc 来查询会话数据中心"),
|
||||
description="使用 dc 来查询会话数据中心",
|
||||
),
|
||||
InlineQueryResultArticle(
|
||||
title="exchange",
|
||||
input_message_content=InputTextMessageContent("使用 exchange 来查询汇率数据"),
|
||||
description="使用 exchange 来查询汇率数据",
|
||||
),
|
||||
]
|
||||
return await inline_query.answer(
|
||||
results=results,
|
||||
|
7
pyproject.toml
Normal file
7
pyproject.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "ishotabot"
|
||||
version = "0.1.0"
|
||||
description = "ishotabot"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = []
|
22
requirements.in
Normal file
22
requirements.in
Normal file
@ -0,0 +1,22 @@
|
||||
git+https://github.com/TeamPGM/pyrogram
|
||||
pyrotgcrypto
|
||||
bilibili-api-python
|
||||
httpx
|
||||
pillow
|
||||
cashews
|
||||
coloredlogs
|
||||
qrcode
|
||||
playwright
|
||||
uvicorn
|
||||
jinja2
|
||||
apscheduler
|
||||
pytz
|
||||
beautifulsoup4
|
||||
lxml
|
||||
sqlalchemy
|
||||
sqlmodel
|
||||
aiosqlite
|
||||
aiofiles
|
||||
pydantic
|
||||
lottie
|
||||
atproto
|
206
requirements.txt
206
requirements.txt
@ -1,22 +1,184 @@
|
||||
git+https://github.com/TeamPGM/pyrogram
|
||||
tgcrypto==1.2.5
|
||||
bilibili-api-python==16.2.1
|
||||
httpx
|
||||
pillow
|
||||
cashews
|
||||
coloredlogs
|
||||
qrcode
|
||||
playwright
|
||||
uvicorn
|
||||
jinja2
|
||||
apscheduler
|
||||
pytz
|
||||
beautifulsoup4
|
||||
lxml
|
||||
sqlalchemy
|
||||
sqlmodel
|
||||
aiosqlite
|
||||
aiofiles
|
||||
pydantic
|
||||
lottie
|
||||
atproto
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile requirements.txt -o requirements.out
|
||||
aiofiles==24.1.0
|
||||
# via -r requirements.txt
|
||||
aiohappyeyeballs==2.4.3
|
||||
# via aiohttp
|
||||
aiohttp==3.10.5
|
||||
# via bilibili-api-python
|
||||
aiosignal==1.3.1
|
||||
# via aiohttp
|
||||
aiosqlite==0.20.0
|
||||
# via -r requirements.txt
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyio==4.6.2.post1
|
||||
# via httpx
|
||||
apscheduler==3.10.4
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# bilibili-api-python
|
||||
atproto==0.0.55
|
||||
# via -r requirements.txt
|
||||
attrs==24.2.0
|
||||
# via aiohttp
|
||||
beautifulsoup4==4.12.3
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# bilibili-api-python
|
||||
bilibili-api-python==16.3.0
|
||||
# via -r requirements.txt
|
||||
brotli==1.1.0
|
||||
# via bilibili-api-python
|
||||
cashews==7.4.0
|
||||
# via -r requirements.txt
|
||||
certifi==2024.8.30
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
cffi==1.17.1
|
||||
# via cryptography
|
||||
click==8.1.7
|
||||
# via
|
||||
# atproto
|
||||
# uvicorn
|
||||
colorama==0.4.6
|
||||
# via
|
||||
# bilibili-api-python
|
||||
# click
|
||||
# qrcode
|
||||
# tqdm
|
||||
coloredlogs==15.0.1
|
||||
# via -r requirements.txt
|
||||
cryptography==43.0.3
|
||||
# via atproto
|
||||
dnspython==2.7.0
|
||||
# via atproto
|
||||
frozenlist==1.5.0
|
||||
# via
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
greenlet==3.1.1
|
||||
# via
|
||||
# playwright
|
||||
# sqlalchemy
|
||||
h11==0.14.0
|
||||
# via
|
||||
# httpcore
|
||||
# uvicorn
|
||||
httpcore==1.0.7
|
||||
# via httpx
|
||||
httpx==0.26.0
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# atproto
|
||||
# bilibili-api-python
|
||||
humanfriendly==10.0
|
||||
# via coloredlogs
|
||||
idna==3.10
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
# yarl
|
||||
jinja2==3.1.4
|
||||
# via -r requirements.txt
|
||||
libipld==3.0.0
|
||||
# via atproto
|
||||
lottie==0.7.1
|
||||
# via -r requirements.txt
|
||||
lxml==5.3.0
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# bilibili-api-python
|
||||
markupsafe==3.0.2
|
||||
# via jinja2
|
||||
multidict==6.1.0
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
pillow==10.4.0
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# bilibili-api-python
|
||||
# qrcode-terminal
|
||||
playwright==1.49.0
|
||||
# via -r requirements.txt
|
||||
pyaes==1.6.1
|
||||
# via pyrogram
|
||||
pyasn1==0.6.1
|
||||
# via rsa
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pycryptodomex==3.20.0
|
||||
# via bilibili-api-python
|
||||
pydantic==2.10.2
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# atproto
|
||||
# sqlmodel
|
||||
pydantic-core==2.27.1
|
||||
# via pydantic
|
||||
pyee==12.0.0
|
||||
# via playwright
|
||||
pypng==0.20220715.0
|
||||
# via qrcode
|
||||
pyreadline3==3.5.4
|
||||
# via humanfriendly
|
||||
pyrogram @ git+https://github.com/TeamPGM/pyrogram@7206c0ec074c0e8b7d304dc81a8dbb4aca16b86d
|
||||
# via -r requirements.txt
|
||||
pyrotgcrypto==1.2.7
|
||||
# via -r requirements.txt
|
||||
pysocks==1.7.1
|
||||
# via pyrogram
|
||||
pytz==2024.2
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# apscheduler
|
||||
pyyaml==6.0.2
|
||||
# via bilibili-api-python
|
||||
qrcode==7.4.2
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# bilibili-api-python
|
||||
# qrcode-terminal
|
||||
qrcode-terminal==0.8
|
||||
# via bilibili-api-python
|
||||
rsa==4.9
|
||||
# via bilibili-api-python
|
||||
six==1.16.0
|
||||
# via apscheduler
|
||||
sniffio==1.3.1
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
soupsieve==2.6
|
||||
# via beautifulsoup4
|
||||
sqlalchemy==2.0.36
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# sqlmodel
|
||||
sqlmodel==0.0.22
|
||||
# via -r requirements.txt
|
||||
tqdm==4.66.6
|
||||
# via bilibili-api-python
|
||||
typing-extensions==4.12.2
|
||||
# via
|
||||
# aiosqlite
|
||||
# atproto
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# pyee
|
||||
# qrcode
|
||||
# sqlalchemy
|
||||
tzdata==2024.2
|
||||
# via tzlocal
|
||||
tzlocal==5.2
|
||||
# via apscheduler
|
||||
uvicorn==0.32.1
|
||||
# via -r requirements.txt
|
||||
websockets==13.1
|
||||
# via atproto
|
||||
yarl==1.11.1
|
||||
# via
|
||||
# aiohttp
|
||||
# bilibili-api-python
|
||||
|
Loading…
Reference in New Issue
Block a user