支持查询用户名遗产

This commit is contained in:
xtaodada 2022-11-04 16:08:45 +08:00
parent 356ccafe6c
commit aa74550fa0
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
4 changed files with 236 additions and 2 deletions

85
defs/fragment.py Normal file
View File

@ -0,0 +1,85 @@
import contextlib
import re
from datetime import datetime
from bs4 import BeautifulSoup
from init import request
from models.fragment import AuctionStatus, UserName, TON_TO_USD_RATE, Price
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 == [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)

87
models/fragment.py Normal file
View File

@ -0,0 +1,87 @@
from datetime import datetime, timezone, timedelta
from enum import Enum
from typing import Optional
from pydantic import BaseModel
TON_TO_USD_RATE = {"rate": 1.61}
class Price(BaseModel):
ton: int
@property
def usd(self) -> float:
return self.ton * TON_TO_USD_RATE["rate"]
@property
def text(self) -> str:
return f"{self.ton} TON ~ ${round(self.usd, 2)}"
class AuctionStatus(Enum):
Available = "Available"
OnAuction = "On auction"
Sold = "Sold"
Sale = "For sale"
ComingSoon = "Coming soon"
Unavailable = "Unavailable"
@property
def text(self) -> str:
if self.value == "Available":
return "等待出价"
elif self.value == "On auction":
return "正在拍卖"
elif self.value == "Sold":
return "已售出"
elif self.value == "For sale":
return "正在出售"
elif self.value == "Coming soon":
return "即将拍卖"
elif self.value == "Unavailable":
return "暂时不会出售"
class UserName(BaseModel):
name: str
now_price: Optional[Price]
end_time: Optional[datetime]
purchaser: Optional[str]
status: AuctionStatus
@property
def end_human_time(self) -> str:
diff = self.end_time - datetime.now(timezone.utc)
args = []
if diff.days:
args.append(f"{diff.days}")
if diff.seconds // 3600:
args.append(f"{diff.seconds // 3600}")
if diff.seconds % 3600 // 60:
args.append(f"{diff.seconds % 3600 // 60}")
if diff.seconds % 60:
args.append(f"{diff.seconds % 60}")
return " ".join(args)
@property
def strf_end_time(self) -> str:
return (self.end_time + timedelta(hours=8)).strftime("%Y-%m-%d %H:%M:%S")
@property
def text(self) -> str:
text = f"用户名:@{self.name}\n" \
f"状态:{self.status.text}\n"
if self.status == AuctionStatus.Available:
text += f"最低出价:{self.now_price.text}\n"
elif self.status == AuctionStatus.OnAuction:
text += f"目前最高出价:{self.now_price.text}\n" \
f"距离拍卖结束:{self.end_human_time}\n"
elif self.status == AuctionStatus.Sold:
text += f"售出价格:{self.now_price.text}\n" \
f"最终买家:<a href='https://tonapi.io/account/{self.purchaser}'>{self.purchaser[:12]}...</a>\n" \
f"售出时间:{self.strf_end_time}\n"
elif self.status == AuctionStatus.Sale:
text += f"售价:{self.now_price.text}\n" \
f"距离出售结束:{self.end_human_time}\n"
return text

61
modules/fragment.py Normal file
View File

@ -0,0 +1,61 @@
import re
from pyrogram import Client
from pyrogram.types import InlineQuery, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardMarkup, \
InlineKeyboardButton
from defs.fragment import parse_fragment, NotAvailable
QUERY_PATTERN = re.compile(r"^@\w[a-zA-Z0-9_]{3,32}$")
@Client.on_inline_query()
async def fragment_inline(_, inline_query: InlineQuery):
username = inline_query.query
if not username.startswith("@"):
username = f"@{username}"
if not QUERY_PATTERN.match(username):
return await inline_query.answer(
results=[],
switch_pm_text="请输入 @username 来查询遗产",
switch_pm_parameter="start",
cache_time=0,
)
username = username[1:]
try:
user = await parse_fragment(username)
text = user.text
except NotAvailable:
text = f"用户名:@{username}\n" \
f"状态:暂未开放购买\n"
except Exception:
text = ""
if not text:
return await inline_query.answer(
results=[],
switch_pm_text="查询失败了 ~ 呜呜呜",
switch_pm_parameter="start",
cache_time=0,
)
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
)

View File

@ -1,5 +1,5 @@
pyrogram==2.0.57
tgcrypto
pyrogram==2.0.59
tgcrypto==1.2.4
httpx
pillow
cashews
@ -18,3 +18,4 @@ lxml
sqlalchemy
sqlmodel
aiosqlite
pydantic