iShotaBot/defs/bilibili.py

399 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import re
from os import sep
import httpx
import qrcode
import string
from pyrogram import ContinuePropagation
from qrcode.image.pil import PilImage
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
from defs.browser import get_browser
from headers import bili_headers
def cut_text(old_str, cut):
"""
:说明: `get_cut_str`
> 长文本自动换行切分
:参数:
* `str: [type]`: 文本
* `cut: [type]`: 换行宽度需要至少大于20
:返回:
- `List`: 切分后的文本列表
"""
punc = """,、。.?)》】“"';:!·`~%^& """
si = 0
i = 0
next_str = old_str
str_list = []
for s in next_str:
if s in string.printable:
si += 1
else:
si += 2
i += 1
if next_str[0] == "\n":
next_str = next_str[1:]
elif s == "\n":
str_list.append(next_str[: i - 1])
next_str = next_str[i - 1 :]
si = 0
i = 0
continue
if si > cut:
try:
if next_str[i] in punc:
i += 1
if next_str[i] in string.ascii_letters:
for j in range(i, i - 18, -1):
if next_str[j] == " ":
i = j + 1
break
except IndexError:
str_list.append(next_str)
return str_list
str_list.append(next_str[:i])
next_str = next_str[i:]
si = 0
i = 0
str_list.append(next_str)
i = 0
non_wrap_str = []
for p in str_list:
if not p:
continue
if p[-1] == "\n":
p = p[:-1]
non_wrap_str.append(p)
i += 1
return non_wrap_str
async def b23_extract(text):
b23 = re.compile(r"b23.tv\\/(\w+)").search(text)
if not b23:
b23 = re.compile(r"b23.tv/(\w+)").search(text)
try:
assert b23 is not None
url = f"https://b23.tv/{b23[1]}"
except AssertionError:
raise ContinuePropagation
resp = httpx.head(url, follow_redirects=True)
r = str(resp.url)
return r
async def video_info_get(cid):
if cid[:2] == "av":
video_info = httpx.get(
f"http://api.bilibili.com/x/web-interface/view?aid={cid[2:]}"
)
video_info = video_info.json()
elif cid[:2] == "BV":
video_info = httpx.get(
f"http://api.bilibili.com/x/web-interface/view?bvid={cid}"
)
video_info = video_info.json()
else:
return
return video_info
def numf(num: int):
if num < 10000:
view = str(num)
elif num < 100000000:
view = ("%.2f" % (num / 10000)) + ""
else:
view = ("%.2f" % (num / 100000000)) + "亿"
return view
def binfo_image_create(video_info: dict):
bg_y = 0
# 封面
pic_url = video_info["data"]["pic"]
pic_get = httpx.get(pic_url).content
pic_bio = BytesIO(pic_get)
pic = Image.open(pic_bio)
pic = pic.resize((560, 350))
pic_time_box = Image.new("RGBA", (560, 50), (0, 0, 0, 150))
pic.paste(pic_time_box, (0, 300), pic_time_box)
bg_y += 350 + 20
# 时长
minutes, seconds = divmod(video_info["data"]["duration"], 60)
hours, minutes = divmod(minutes, 60)
video_time = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
tiem_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-bold.ttf", 30
)
draw = ImageDraw.Draw(pic)
draw.text((10, 305), video_time, "white", tiem_font)
# 分区
tname = video_info["data"]["tname"]
tname_x, _ = tiem_font.getsize(tname)
draw.text((560 - tname_x - 10, 305), tname, "white", tiem_font)
# 标题
title = video_info["data"]["title"]
title_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-bold.ttf", 25
)
title_cut_str = "\n".join(cut_text(title, 40))
_, title_text_y = title_font.getsize_multiline(title_cut_str)
title_bg = Image.new("RGB", (560, title_text_y + 23), "#F5F5F7")
draw = ImageDraw.Draw(title_bg)
draw.text((15, 10), title_cut_str, "black", title_font)
title_bg_y = title_bg.size[1]
bg_y += title_bg_y
# 简介
dynamic = (
"该视频没有简介" if video_info["data"]["desc"] == "" else video_info["data"]["desc"]
)
dynamic_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-semibold.ttf", 18
)
dynamic_cut_str = "\n".join(cut_text(dynamic, 58))
_, dynamic_text_y = dynamic_font.getsize_multiline(dynamic_cut_str)
dynamic_bg = Image.new("RGB", (560, dynamic_text_y + 24), "#F5F5F7")
draw = ImageDraw.Draw(dynamic_bg)
draw.rectangle((0, 0, 580, dynamic_text_y + 24), "#E1E1E5")
draw.text((10, 10), dynamic_cut_str, "#474747", dynamic_font)
dynamic_bg_y = dynamic_bg.size[1]
bg_y += dynamic_bg_y
# 视频数据
icon_font = ImageFont.truetype(f"resources{sep}font{sep}vanfont.ttf", 46)
icon_color = (247, 145, 185)
info_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-bold.ttf", 26
)
view = numf(video_info["data"]["stat"]["view"]) # 播放 \uE6E6
danmaku = numf(video_info["data"]["stat"]["danmaku"]) # 弹幕 \uE6E7
favorite = numf(video_info["data"]["stat"]["favorite"]) # 收藏 \uE6E1
coin = numf(video_info["data"]["stat"]["coin"]) # 投币 \uE6E4
like = numf(video_info["data"]["stat"]["like"]) # 点赞 \uE6E0
info_bg = Image.new("RGB", (560, 170), "#F5F5F7")
draw = ImageDraw.Draw(info_bg)
draw.text((5 + 10, 20), "\uE6E0", icon_color, icon_font)
draw.text((5 + 64, 27), like, "#474747", info_font)
draw.text((5 + 10 + 180, 20), "\uE6E4", icon_color, icon_font)
draw.text((5 + 64 + 180, 27), coin, "#474747", info_font)
draw.text((5 + 10 + 180 + 180, 20), "\uE6E1", icon_color, icon_font)
draw.text((5 + 64 + 180 + 180, 27), favorite, "#474747", info_font)
draw.text((5 + 100, 93), "\uE6E6", icon_color, icon_font)
draw.text((5 + 154, 100), view, "#474747", info_font)
draw.text((5 + 100 + 210, 93), "\uE6E7", icon_color, icon_font)
draw.text((5 + 154 + 210, 100), danmaku, "#474747", info_font)
info_bg_y = info_bg.size[1]
bg_y += info_bg_y
# UP主
# 等级 0-4 \uE6CB-F 5-6\uE6D0-1
# UP \uE723
if "staff" in video_info["data"]:
up_list = []
for up in video_info["data"]["staff"]:
up_mid = up["mid"]
up_data = httpx.get(
f"https://api.bilibili.com/x/space/acc/info?mid={up_mid}",
headers=bili_headers,
).json()
up_list.append(
{
"name": up["name"],
"up_title": up["title"],
"face": up["face"],
"color": up_data["data"]["vip"]["nickname_color"]
if up_data["data"]["vip"]["nickname_color"] != ""
else "black",
"follower": up["follower"],
"level": up_data["data"]["level"],
}
)
else:
up_mid = video_info["data"]["owner"]["mid"]
up_data = httpx.get(
f"https://api.bilibili.com/x/space/acc/info?mid={up_mid}",
headers=bili_headers,
).json()
up_stat = httpx.get(
f"https://api.bilibili.com/x/relation/stat?vmid={up_mid}",
headers=bili_headers,
).json()
up_list = [
{
"name": up_data["data"]["name"],
"up_title": "UP主",
"face": up_data["data"]["face"],
"color": up_data["data"]["vip"]["nickname_color"]
if up_data["data"]["vip"]["nickname_color"] != ""
else "black",
"follower": up_stat["data"]["follower"],
"level": up_data["data"]["level"],
}
]
up_num = len(up_list)
up_bg = Image.new("RGB", (560, 20 + (up_num * 120) + 20), "#F5F5F7")
draw = ImageDraw.Draw(up_bg)
face_size = (80, 80)
mask = Image.new("RGBA", face_size, color=(0, 0, 0, 0))
mask_draw = ImageDraw.Draw(mask)
mask_draw.ellipse(
(0, 0, face_size[0], face_size[1]), fill=(0, 0, 0, 255) # type: ignore
)
name_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-bold.ttf", 24
)
up_title_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-bold.ttf", 20
)
follower_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-semibold.ttf", 22
)
i = 0
for up in up_list:
if up["level"] == 0:
up_level = "\uE6CB"
level_color = (191, 191, 191)
elif up["level"] == 1:
up_level = "\uE6CC"
level_color = (191, 191, 191)
elif up["level"] == 2:
up_level = "\uE6CD"
level_color = (149, 221, 178)
elif up["level"] == 3:
up_level = "\uE6CE"
level_color = (146, 209, 229)
elif up["level"] == 4:
up_level = "\uE6CF"
level_color = (255, 179, 124)
elif up["level"] == 5:
up_level = "\uE6D0"
level_color = (255, 108, 0)
else:
up_level = "\uE6D1"
level_color = (255, 0, 0)
# 头像
face_url = up["face"]
face_get = httpx.get(face_url).content
face_bio = BytesIO(face_get)
face = Image.open(face_bio)
face.convert("RGB")
face = face.resize(face_size)
up_bg.paste(face, (20, 20 + (i * 120)), mask)
# 名字
draw.text((160, 25 + (i * 120)), up["name"], up["color"], name_font)
name_size_x, _ = name_font.getsize(up["name"])
# 等级
draw.text(
(160 + name_size_x + 10, 16 + (i * 120)), up_level, level_color, icon_font
)
# 身份
up_title_size_x, up_title_size_y = up_title_font.getsize(up["up_title"])
draw.rectangle(
(
60,
10 + (i * 120),
73 + up_title_size_x,
18 + (i * 120) + up_title_size_y,
),
"white",
icon_color,
3,
)
draw.text((67, 13 + (i * 120)), up["up_title"], icon_color, up_title_font)
# 粉丝量
draw.text(
(162, 66 + (i * 120)),
"粉丝 " + numf(up["follower"]),
"#474747",
follower_font,
)
i += 1
up_bg_y = up_bg.size[1]
bg_y += up_bg_y
# 底部栏
baner_bg = Image.new("RGB", (600, 170), icon_color)
draw = ImageDraw.Draw(baner_bg)
# 二维码
qr = qrcode.QRCode(border=1)
qr.add_data("https://b23.tv/" + video_info["data"]["bvid"])
qr_image = qr.make_image(PilImage, fill_color=icon_color, back_color="#F5F5F7")
qr_image = qr_image.resize((140, 140))
baner_bg.paste(qr_image, (50, 10))
# Logo
# LOGO \uE725
logo_font = ImageFont.truetype(f"resources{sep}font{sep}vanfont.ttf", 100)
draw.text((300, 28), "\uE725", "#F5F5F7", logo_font)
bg_y += 170
video = Image.new("RGB", (600, bg_y + 40), "#F5F5F7")
video.paste(pic, (20, 20))
video.paste(title_bg, (20, 390))
video.paste(dynamic_bg, (20, 390 + title_bg_y + 20))
video.paste(info_bg, (20, 390 + title_bg_y + 20 + dynamic_bg_y + 20))
video.paste(up_bg, (20, 390 + title_bg_y + 20 + dynamic_bg_y + 10 + info_bg_y))
video.paste(
baner_bg, (0, 390 + title_bg_y + 20 + dynamic_bg_y + 10 + info_bg_y + up_bg_y)
)
picture = BytesIO()
video.save(picture, format="PNG")
picture.name = "bilibili.png"
return picture
async def get_dynamic_screenshot_pc(dynamic_id):
"""电脑端动态截图"""
url = f"https://t.bilibili.com/{dynamic_id}"
browser = await get_browser()
context = await browser.new_context(
viewport={"width": 2560, "height": 1080},
device_scale_factor=2,
)
await context.add_cookies(
[
{
"name": "hit-dyn-v2",
"value": "1",
"domain": ".bilibili.com",
"path": "/",
}
]
)
page = await context.new_page()
try:
await page.goto(url, wait_until="networkidle", timeout=10000)
# 动态被删除或者进审核了
if page.url == "https://www.bilibili.com/404":
return None
card = await page.query_selector(".card")
assert card
clip = await card.bounding_box()
assert clip
bar = await page.query_selector(".bili-dyn-action__icon")
assert bar
bar_bound = await bar.bounding_box()
assert bar_bound
clip["height"] = bar_bound["y"] - clip["y"]
return await page.screenshot(clip=clip, full_page=True)
except Exception:
print(f"截取动态时发生错误:{url}")
return await page.screenshot(full_page=True)
finally:
await context.close()