iShotaBot/defs/bilibili.py

399 lines
13 KiB
Python
Raw Normal View History

2021-11-28 14:55:10 +00:00
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
2022-09-23 08:51:38 +00:00
from defs.browser import get_browser
from headers import bili_headers
2022-09-23 08:51:38 +00:00
2021-11-28 14:55:10 +00:00
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])
2023-01-12 13:19:54 +00:00
next_str = next_str[i - 1 :]
2021-11-28 14:55:10 +00:00
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:
2022-07-27 07:05:09 +00:00
if not p:
continue
2021-11-28 14:55:10 +00:00
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
2021-11-29 05:11:21 +00:00
resp = httpx.head(url, follow_redirects=True)
2021-11-28 14:55:10 +00:00
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":
2023-01-12 13:19:54 +00:00
video_info = httpx.get(
f"http://api.bilibili.com/x/web-interface/view?bvid={cid}"
)
2021-11-28 14:55:10 +00:00
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}"
2023-01-12 13:19:54 +00:00
tiem_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-bold.ttf", 30
)
2021-11-28 14:55:10 +00:00
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"]
2023-01-12 13:19:54 +00:00
title_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-bold.ttf", 25
)
2021-11-28 14:55:10 +00:00
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"]
)
2023-01-12 13:19:54 +00:00
dynamic_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-semibold.ttf", 18
)
2021-11-28 14:55:10 +00:00
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)
2023-01-12 13:19:54 +00:00
info_font = ImageFont.truetype(
f"resources{sep}font{sep}sarasa-mono-sc-bold.ttf", 26
)
2021-11-28 14:55:10 +00:00
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(
2022-07-15 13:56:17 +00:00
f"https://api.bilibili.com/x/space/acc/info?mid={up_mid}",
headers=bili_headers,
2021-11-28 14:55:10 +00:00
).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(
2023-05-21 02:11:47 +00:00
f"https://api.bilibili.com/x/space/wbi/acc/info?mid={up_mid}",
headers=bili_headers,
2021-11-28 14:55:10 +00:00
).json()
up_stat = httpx.get(
2022-07-15 13:56:17 +00:00
f"https://api.bilibili.com/x/relation/stat?vmid={up_mid}",
headers=bili_headers,
2021-11-28 14:55:10 +00:00
).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
)
2023-01-12 13:19:54 +00:00
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
)
2021-11-28 14:55:10 +00:00
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
2022-09-23 08:51:38 +00:00
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()