From f3eea863b6b8a2da0d0c07cd4c311af851ec959b Mon Sep 17 00:00:00 2001 From: djkcyl Date: Tue, 27 Jun 2023 21:12:06 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=A0=81=E9=97=AE=E9=A2=98=EF=BC=88=E5=BA=94=E8=AF=A5=E5=90=A7?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E4=BF=AE=E6=94=B9=E6=B5=8F=E8=A7=88=E5=99=A8?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=B8=BA=E6=8C=81=E4=B9=85=E5=8C=96=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=BB=A5=E7=95=99=E5=AD=98=20cookie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- haruka_bot/config.py | 1 + haruka_bot/utils/browser.py | 88 +++++++++++++++++++++---------------- haruka_bot/utils/captcha.py | 3 +- haruka_bot/version.py | 2 +- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/haruka_bot/config.py b/haruka_bot/config.py index 976afa7..db7345f 100644 --- a/haruka_bot/config.py +++ b/haruka_bot/config.py @@ -18,6 +18,7 @@ class Config(BaseSettings): haruka_dynamic_at: bool = False haruka_screenshot_style: str = "mobile" haruka_captcha_address: str = "https://captcha-cd.ngworks.cn" + haruka_browser_ua: Optional[str] = None haruka_dynamic_timeout: int = 30 haruka_dynamic_font_source: str = "system" haruka_dynamic_font: Optional[str] = "Noto Sans CJK SC" diff --git a/haruka_bot/utils/browser.py b/haruka_bot/utils/browser.py index 152b921..73a841d 100644 --- a/haruka_bot/utils/browser.py +++ b/haruka_bot/utils/browser.py @@ -7,35 +7,61 @@ from nonebot.log import logger from playwright.__main__ import main -from playwright.async_api import Browser, async_playwright +from playwright.async_api import BrowserContext, async_playwright from ..config import plugin_config from .fonts_provider import fill_font from .captcha import resolve_captcha +from ..utils import get_path -_browser: Optional[Browser] = None +_browser: Optional[BrowserContext] = None mobile_js = Path(__file__).parent.joinpath("mobile.js") -async def init_browser(proxy=plugin_config.haruka_proxy, **kwargs) -> Browser: +async def init_browser(proxy=plugin_config.haruka_proxy, **kwargs) -> BrowserContext: if proxy: kwargs["proxy"] = {"server": proxy} global _browser p = await async_playwright().start() - _browser = await p.chromium.launch(**kwargs) + browser_data = Path(get_path("browser")) + browser_data.mkdir(parents=True, exist_ok=True) + browser_context = await p.chromium.launch_persistent_context( + browser_data, + user_agent=plugin_config.haruka_browser_ua + or ( + ( + "Mozilla/5.0 (Linux; Android 10; RMX1911) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36" + ) + if plugin_config.haruka_screenshot_style.lower() == "mobile" + else None + ), + device_scale_factor=2, + **kwargs, + ) + if plugin_config.haruka_screenshot_style.lower() != "mobile": + await browser_context.add_cookies( + [ + { + "name": "hit-dyn-v2", + "value": "1", + "domain": ".bilibili.com", + "path": "/", + } + ] + ) + _browser = browser_context return _browser -async def get_browser() -> Browser: +async def get_browser() -> BrowserContext: global _browser - if _browser is None or not _browser.is_connected(): + if not _browser or _browser.browser is None or not _browser.browser.is_connected(): _browser = await init_browser() return _browser -async def get_dynamic_screenshot( - dynamic_id, style=plugin_config.haruka_screenshot_style -): +async def get_dynamic_screenshot(dynamic_id, style=plugin_config.haruka_screenshot_style): """获取动态截图""" if style.lower() == "mobile": return await get_dynamic_screenshot_mobile(dynamic_id) @@ -47,14 +73,8 @@ async def get_dynamic_screenshot_mobile(dynamic_id): """移动端动态截图""" url = f"https://m.bilibili.com/dynamic/{dynamic_id}" browser = await get_browser() - page = await browser.new_page( - device_scale_factor=2, - user_agent=( - "Mozilla/5.0 (Linux; Android 10; RMX1911) AppleWebKit/537.36 " - "(KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36" - ), - viewport={"width": 460, "height": 780}, - ) + page = await browser.new_page() + await page.set_viewport_size({"width": 460, "height": 780}) try: await page.route(re.compile("^https://static.graiax/fonts/(.+)$"), fill_font) if plugin_config.haruka_captcha_address: @@ -79,6 +99,13 @@ async def get_dynamic_screenshot_mobile(dynamic_id): # "dyn.style.fontFamily='Noto Sans CJK SC, sans-serif';" # "dyn.style.overflowWrap='break-word'" # ) + + await page.wait_for_load_state(state="domcontentloaded", timeout=20000) + if "opus" in page.url: + await page.wait_for_selector(".opus-module-author", state="visible") + else: + await page.wait_for_selector(".dyn-header__author__face", state="visible") + await page.add_script_tag(path=mobile_js) await page.evaluate( @@ -100,9 +127,7 @@ async def get_dynamic_screenshot_mobile(dynamic_id): need_wait = ["imageComplete", "fontsLoaded"] await asyncio.gather(*[page.wait_for_function(f"{i}()") for i in need_wait]) - card = await page.query_selector( - ".opus-modules" if "opus" in page.url else ".dyn-card" - ) + card = await page.query_selector(".opus-modules" if "opus" in page.url else ".dyn-card") assert card clip = await card.bounding_box() assert clip @@ -118,21 +143,9 @@ 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() + + page = await browser.new_page() + await page.set_viewport_size({"width": 2560, "height": 1080}) try: await page.goto( url, @@ -156,7 +169,7 @@ async def get_dynamic_screenshot_pc(dynamic_id): logger.exception(f"截取动态时发生错误:{url}") return await page.screenshot(full_page=True) finally: - await context.close() + await page.close() def install(): @@ -201,6 +214,5 @@ async def check_playwright_env(): await p.chromium.launch() except Exception: raise ImportError( - "加载失败,Playwright 依赖不全," - "解决方法:https://haruka-bot.sk415.icu/faq.html#playwright-依赖不全" + "加载失败,Playwright 依赖不全," "解决方法:https://haruka-bot.sk415.icu/faq.html#playwright-依赖不全" ) diff --git a/haruka_bot/utils/captcha.py b/haruka_bot/utils/captcha.py index 7ab79cd..bfc586c 100644 --- a/haruka_bot/utils/captcha.py +++ b/haruka_bot/utils/captcha.py @@ -118,8 +118,7 @@ async def captcha_result_callback(response: Response): logger.debug(f"[Captcha] Geetest result: {geetest_result}") if "验证成功" in geetest_result: logger.success("[Captcha] 极验网页 Tip 验证成功") - await page.wait_for_timeout(1000) - await page.wait_for_load_state("domcontentloaded") + await page.wait_for_timeout(2000) else: logger.warning("[Captcha] 极验验证失败,正在重试") diff --git a/haruka_bot/version.py b/haruka_bot/version.py index f090986..33ada48 100644 --- a/haruka_bot/version.py +++ b/haruka_bot/version.py @@ -1,4 +1,4 @@ from packaging.version import Version -__version__ = "1.6.0post2" +__version__ = "1.6.0post3" VERSION = Version(__version__) From acdc08ab8498cd55c57915e8e1cbe53167af0ecd Mon Sep 17 00:00:00 2001 From: djkcyl Date: Tue, 27 Jun 2023 21:48:09 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9B=B4=E6=96=B9?= =?UTF-8?q?=E4=BE=BF=E7=9A=84uid=E6=8F=90=E5=8F=96=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20UID:XXX=E3=80=81b23=E9=93=BE=E6=8E=A5=E3=80=81?= =?UTF-8?q?=E5=85=A8=E5=90=8D=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- haruka_bot/utils/__init__.py | 82 +++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/haruka_bot/utils/__init__.py b/haruka_bot/utils/__init__.py index 5836b7e..79d08cf 100644 --- a/haruka_bot/utils/__init__.py +++ b/haruka_bot/utils/__init__.py @@ -1,7 +1,10 @@ import asyncio import sys +import re +import contextlib from pathlib import Path from typing import Union +from bilireq.utils import get import httpx import nonebot @@ -52,18 +55,79 @@ async def uid_check( uid: str = ArgPlainText("uid"), ): uid = uid.strip() - if not uid.isdecimal(): - await matcher.finish("UID 必须为纯数字") + if extract := await uid_extract(uid): + uid = extract + else: + await matcher.finish("未找到该 UP,请输入正确的 UP 群内昵称、UP 名、UP UID或 UP 首页链接") matcher.set_arg("uid", Message(uid)) +async def b23_extract(text: str): + if "b23.tv" not in text and "b23.wtf" not in text: + return None + if not (b23 := re.compile(r"b23.(tv|wtf)[\\/]+(\w+)").search(text)): + return None + try: + url = f"https://b23.tv/{b23[2]}" + for _ in range(3): + with contextlib.suppress(Exception): + resp = await httpx.AsyncClient().get(url, follow_redirects=True) + break + else: + return None + url = resp.url + logger.debug(f"b23.tv url: {url}") + return str(url) + except TypeError: + return None + + +async def search_user(keyword: str): + """ + 搜索用户 + """ + url = "https://api.bilibili.com/x/web-interface/search/type" + data = {"keyword": keyword, "search_type": "bili_user"} + resp = await get(url, params=data) + logger.debug(resp) + return resp + + +async def uid_extract(text: str): + logger.debug(f"[UID Extract] Original Text: {text}") + b23_msg = await b23_extract(text) if "b23.tv" in text else None + message = b23_msg or text + logger.debug(f"[UID Extract] b23 extract: {message}") + pattern = re.compile("^[0-9]*$|bilibili.com/([0-9]*)") + if match := pattern.search(message): + logger.debug(f"[UID Extract] Digit or Url: {match}") + match = match[1] or match[0] + return str(match) + elif message.startswith("UID:"): + pattern = re.compile("^\\d+") + if match := pattern.search(message[4:]): + logger.debug(f"[UID Extract] UID: {match}") + return str(match[0]) + else: + text_u = text.strip(""""'“”‘’""") + if text_u != text: + logger.debug(f"[UID Extract] Text is a Quoted Digit: {text_u}") + logger.debug(f"[UID Extract] Searching UID in BiliBili: {text_u}") + resp = await search_user(text_u) + logger.debug(f"[UID Extract] Search result: {resp}") + if resp and resp["numResults"]: + for result in resp["result"]: + if result["uname"] == text_u: + logger.debug(f"[UID Extract] Found User: {result}") + return str(result["mid"]) + logger.debug("[UID Extract] No User found") + + async def _guild_admin(bot: Bot, event: GuildMessageEvent): roles = set( role["role_name"] for role in ( - await bot.get_guild_member_profile( - guild_id=event.guild_id, user_id=event.user_id - ) + await bot.get_guild_member_profile(guild_id=event.guild_id, user_id=event.user_id) )["roles"] ) return bool(roles & set(plugin_config.haruka_guild_admin_roles)) @@ -95,9 +159,7 @@ async def permission_check( raise FinishedException -async def group_only( - matcher: Matcher, event: PrivateMessageEvent, command: str = RawCommand() -): +async def group_only(matcher: Matcher, event: PrivateMessageEvent, command: str = RawCommand()): await matcher.finish(f"只有群里才能{command}") @@ -189,9 +251,7 @@ async def _safe_send(bot, send_type, type_id, message): async def get_type_id(event: Union[MessageEvent, ChannelDestroyedNoticeEvent]): - if isinstance(event, GuildMessageEvent) or isinstance( - event, ChannelDestroyedNoticeEvent - ): + if isinstance(event, GuildMessageEvent) or isinstance(event, ChannelDestroyedNoticeEvent): from ..database import DB as db return await db.get_guild_type_id(event.guild_id, event.channel_id) From 62a6723a1c9c198a9f126ef941581021e20ae36f Mon Sep 17 00:00:00 2001 From: djkcyl Date: Wed, 28 Jun 2023 09:57:36 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E5=A2=9E=E5=8A=A0=E2=80=9C=E5=B7=B2?= =?UTF-8?q?=E5=BC=80=E6=92=AD=E2=80=9D=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- haruka_bot/config.py | 4 +- haruka_bot/plugins/__init__.py | 2 +- haruka_bot/plugins/live/live_now.py | 23 ++++ haruka_bot/plugins/pusher/dynamic_pusher.py | 21 ++-- haruka_bot/plugins/pusher/live_pusher.py | 14 +-- haruka_bot/plugins/sub/add_sub.py | 2 +- haruka_bot/utils/__init__.py | 15 +-- haruka_bot/utils/captcha.py | 2 +- haruka_bot/utils/mobile.js | 124 +++++++++++++------- pyproject.toml | 2 +- 10 files changed, 136 insertions(+), 73 deletions(-) create mode 100644 haruka_bot/plugins/live/live_now.py diff --git a/haruka_bot/config.py b/haruka_bot/config.py index db7345f..57b2c14 100644 --- a/haruka_bot/config.py +++ b/haruka_bot/config.py @@ -29,9 +29,7 @@ class Config(BaseSettings): @validator("haruka_interval", "haruka_live_interval", "haruka_dynamic_interval") def non_negative(cls, v: int, field: ModelField): """定时器为负返回默认值""" - if v < 1: - return field.default - return v + return field.default if v < 1 else v class Config: extra = "ignore" diff --git a/haruka_bot/plugins/__init__.py b/haruka_bot/plugins/__init__.py index 41cefcf..3408891 100644 --- a/haruka_bot/plugins/__init__.py +++ b/haruka_bot/plugins/__init__.py @@ -3,7 +3,7 @@ from . import help # noqa: F401 from .at import at_off, at_on # noqa: F401 from .dynamic import dynamic_off, dynamic_on # noqa: F401 -from .live import live_off, live_on # noqa: F401 from .permission import permission_off, permission_on # noqa: F401 from .pusher import dynamic_pusher, live_pusher # noqa: F401 +from .live import live_off, live_on, live_now # noqa: F401 from .sub import add_sub, delete_sub, sub_list # noqa: F401 diff --git a/haruka_bot/plugins/live/live_now.py b/haruka_bot/plugins/live/live_now.py new file mode 100644 index 0000000..f8793b1 --- /dev/null +++ b/haruka_bot/plugins/live/live_now.py @@ -0,0 +1,23 @@ +from nonebot.adapters.onebot.v11.event import MessageEvent + +from ...database import DB as db +from ...utils import get_type_id, on_command, permission_check, to_me + +from ..pusher.live_pusher import status + +live_now = on_command("已开播", rule=to_me(), priority=5) +live_now.__doc__ = """已开播""" + +live_now.handle()(permission_check) + + +@live_now.handle() +async def _(event: MessageEvent): + """返回已开播的直播间""" + subs = await db.get_sub_list(event.message_type, await get_type_id(event)) + if now_live := [sub for sub in subs if status.get(str(sub.uid)) == 1]: + await live_now.finish( + f"共有{len(now_live)}个主播正在直播:\n\n" + + "\n".join([f"{await db.get_name(sub.uid)}({sub.uid})" for sub in now_live]) + ) + await live_now.finish("当前没有正在直播的主播") diff --git a/haruka_bot/plugins/pusher/dynamic_pusher.py b/haruka_bot/plugins/pusher/dynamic_pusher.py index 020994a..7805b51 100644 --- a/haruka_bot/plugins/pusher/dynamic_pusher.py +++ b/haruka_bot/plugins/pusher/dynamic_pusher.py @@ -70,13 +70,20 @@ async def dy_sched(): dynamic_id = int(dynamic.extend.dyn_id_str) if dynamic_id > offset[uid]: logger.info(f"检测到新动态({dynamic_id}):{name}({uid})") - url = f"https://t.bilibili.com/{dynamic_id}" image = await get_dynamic_screenshot(dynamic_id) + url = f"https://t.bilibili.com/{dynamic_id}" if image is None: logger.debug(f"动态不存在,已跳过:{url}") + offset[uid] = dynamic_id return - elif dynamic.card_type == DynamicType.live_rcmd: - logger.debug(f"直播推荐动态,已跳过:{url}") + elif dynamic.card_type in [ + DynamicType.live_rcmd, + DynamicType.live, + DynamicType.ad, + DynamicType.banner, + ]: + logger.debug(f"无需推送的动态 {dynamic.card_type},已跳过:{url}") + offset[uid] = dynamic_id return type_msg = { @@ -90,8 +97,7 @@ async def dy_sched(): } message = ( f"{name} {type_msg.get(dynamic.card_type, type_msg[0])}:\n" - + MessageSegment.image(image) - + f"\n{url}" + f"{MessageSegment.image(image)}\n{url}" ) push_list = await db.get_push_list(uid, "dynamic") @@ -123,10 +129,7 @@ def dynamic_lisener(event): if plugin_config.haruka_dynamic_interval == 0: scheduler.add_listener( dynamic_lisener, - EVENT_JOB_EXECUTED - | EVENT_JOB_ERROR - | EVENT_JOB_MISSED - | EVENT_SCHEDULER_STARTED, + EVENT_JOB_EXECUTED | EVENT_JOB_ERROR | EVENT_JOB_MISSED | EVENT_SCHEDULER_STARTED, ) else: scheduler.add_job( diff --git a/haruka_bot/plugins/pusher/live_pusher.py b/haruka_bot/plugins/pusher/live_pusher.py index a2b1b86..63c0cd4 100644 --- a/haruka_bot/plugins/pusher/live_pusher.py +++ b/haruka_bot/plugins/pusher/live_pusher.py @@ -20,6 +20,7 @@ async def live_sched(): return logger.debug(f"爬取直播列表,目前开播{sum(status.values())}人,总共{len(uids)}人") res = await get_rooms_info_by_uids(uids, reqtype="web", proxies=PROXIES) + print(res) if not res: return for uid, info in res.items(): @@ -34,17 +35,12 @@ async def live_sched(): name = info["uname"] if new_status: # 开播 - room_id = info["short_id"] if info["short_id"] else info["room_id"] - url = "https://live.bilibili.com/" + str(room_id) + room_id = info["short_id"] or info["room_id"] + url = f"https://live.bilibili.com/{room_id}" title = info["title"] - cover = ( - info["cover_from_user"] if info["cover_from_user"] else info["keyframe"] - ) + cover = info["cover_from_user"] or info["keyframe"] logger.info(f"检测到开播:{name}({uid})") - - live_msg = ( - f"{name} 正在直播:\n{title}\n" + MessageSegment.image(cover) + f"\n{url}" - ) + live_msg = f"{name} 正在直播:\n{title}\n{MessageSegment.image(cover)}" + f"\n{url}" else: # 下播 logger.info(f"检测到下播:{name}({uid})") if not plugin_config.haruka_live_off_notify: # 没开下播推送 diff --git a/haruka_bot/plugins/sub/add_sub.py b/haruka_bot/plugins/sub/add_sub.py index 03effcf..0f1e36b 100644 --- a/haruka_bot/plugins/sub/add_sub.py +++ b/haruka_bot/plugins/sub/add_sub.py @@ -34,7 +34,7 @@ async def _(event: MessageEvent, uid: str = ArgPlainText("uid")): try: name = (await get_user_info(uid, reqtype="web", proxies=PROXIES))["name"] except ResponseCodeError as e: - if e.code == -400 or e.code == -404: + if e.code in [-400, -404]: await add_sub.finish("UID不存在,注意UID不是房间号") elif e.code == -412: await add_sub.finish("操作过于频繁IP暂时被风控,请半小时后再尝试") diff --git a/haruka_bot/utils/__init__.py b/haruka_bot/utils/__init__.py index 79d08cf..daf16a5 100644 --- a/haruka_bot/utils/__init__.py +++ b/haruka_bot/utils/__init__.py @@ -26,10 +26,12 @@ from nonebot.params import ArgPlainText, CommandArg, RawCommand from nonebot.permission import SUPERUSER, Permission from nonebot.rule import Rule -from nonebot_plugin_guild_patch import ChannelDestroyedNoticeEvent, GuildMessageEvent from ..config import plugin_config +require("nonebot_plugin_guild_patch") +from nonebot_plugin_guild_patch import ChannelDestroyedNoticeEvent, GuildMessageEvent # noqa + def get_path(*other): """获取数据文件绝对路径""" @@ -45,8 +47,7 @@ async def handle_uid( matcher: Matcher, command_arg: Message = CommandArg(), ): - uid = command_arg.extract_plain_text().strip() - if uid: + if command_arg.extract_plain_text().strip(): matcher.set_arg("uid", command_arg) @@ -124,12 +125,12 @@ async def uid_extract(text: str): async def _guild_admin(bot: Bot, event: GuildMessageEvent): - roles = set( + roles = { role["role_name"] for role in ( await bot.get_guild_member_profile(guild_id=event.guild_id, user_id=event.user_id) )["roles"] - ) + } return bool(roles & set(plugin_config.haruka_guild_admin_roles)) @@ -191,7 +192,7 @@ async def _safe_send(bot, send_type, type_id, message): ) else: result = await bot.call_api( - "send_" + send_type + "_msg", + f"send_{send_type}_msg", **{ "message": message, "user_id" if send_type == "private" else "group_id": type_id, @@ -251,7 +252,7 @@ async def _safe_send(bot, send_type, type_id, message): async def get_type_id(event: Union[MessageEvent, ChannelDestroyedNoticeEvent]): - if isinstance(event, GuildMessageEvent) or isinstance(event, ChannelDestroyedNoticeEvent): + if isinstance(event, (GuildMessageEvent, ChannelDestroyedNoticeEvent)): from ..database import DB as db return await db.get_guild_type_id(event.guild_id, event.channel_id) diff --git a/haruka_bot/utils/captcha.py b/haruka_bot/utils/captcha.py index bfc586c..762de06 100644 --- a/haruka_bot/utils/captcha.py +++ b/haruka_bot/utils/captcha.py @@ -107,7 +107,6 @@ async def captcha_result_callback(response: Response): } await captcha_image.click(position=Position(**real_click_points)) await page.wait_for_timeout(800) - captcha_image_body = "" await page.click("text=确认") geetest_up = await page.wait_for_selector(".geetest_up", state="visible") if not geetest_up: @@ -118,6 +117,7 @@ async def captcha_result_callback(response: Response): logger.debug(f"[Captcha] Geetest result: {geetest_result}") if "验证成功" in geetest_result: logger.success("[Captcha] 极验网页 Tip 验证成功") + captcha_image_body = "" await page.wait_for_timeout(2000) else: logger.warning("[Captcha] 极验验证失败,正在重试") diff --git a/haruka_bot/utils/mobile.js b/haruka_bot/utils/mobile.js index c4ac520..b024b28 100644 --- a/haruka_bot/utils/mobile.js +++ b/haruka_bot/utils/mobile.js @@ -5,7 +5,7 @@ * @LastEditTime: 2023-01-13 01:35:34 * @Description: 用于初始化手机动态页面的样式以及图片大小 */ -async function getMobileStyle() { +async function getMobileStyle(expandImage = false) { // 删除 dom 的对象, 可以自行添加 ( className 需要增加 '.' 为前缀, id 需要增加 '#' 为前缀) const deleteDoms = { // 关注 dom @@ -22,6 +22,8 @@ async function getMobileStyle() { openAppDialogDoms: [".openapp-dialog"], // 评论区 dom commentsDoms: [".v-switcher"], + // 打开商品 dom + openGoodsDoms: [".bm-link-card-goods__one__action", ".dyn-goods__one__action"], } // 遍历对象的值, 并将多数组扁平化, 再遍历进行删除操作 @@ -34,19 +36,6 @@ async function getMobileStyle() { const contentDom = document.querySelector(".opus-module-content"); contentDom && contentDom.classList.remove("limit"); - // // 新版动态需要给 bm-pics-block 的父级元素设置 flex 以及 column - // const newContainerDom = document.querySelector(".bm-pics-block")?.parentElement; - // if (newContainerDom) { - // // 设置为 flex - // newContainerDom.style.display = "flex"; - // // 设置为竖向排列 - // newContainerDom.style.flexDirection = "column"; - // // flex - 垂直居中 - // newContainerDom.style.justifyContent = "center"; - // // flex - 水平居中 - // newContainerDom.style.alignItems = "center"; - // } - // 设置 mopus 的 paddingTop 为 0 const mOpusDom = document.querySelector(".m-opus"); if (mOpusDom) { @@ -60,42 +49,95 @@ async function getMobileStyle() { dynCardDom.style.fontFamily = "unset"; } - // // 找到图标容器dom - // const containerDom = document.querySelector(".bm-pics-block__container"); - // if (containerDom) { - // // 先把默认 padding-left 置为0 - // containerDom.style.paddingLeft = "0"; - // // 先把默认 padding-right 置为0 - // containerDom.style.paddingRight = "0"; - // // 设置 flex 模式下以列形式排列 - // containerDom.style.flexDirection = "column"; - // // 设置 flex 模式下每个容器间隔15px - // containerDom.style.gap = "15px"; - // // flex - 垂直居中 - // containerDom.style.justifyContent = "center"; - // // flex - 水平居中 - // containerDom.style.alignItems = "center"; - // } + // 获取图片容器的所有 dom 数组 + const imageItemDoms = Array.from(document.querySelectorAll(".bm-pics-block__item")); - // 获取图片容器的所有 dom - const imageItemDoms = document.querySelectorAll(".bm-pics-block__item"); + // 获取图片长宽比例 + const getImageRatio = (url) => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = url; + img.onload = () => { + resolve(img.height / img.width); + } + img.onerror = () => { + reject(); + } + }) + }; + + // 图片长宽比例的数组 + const ratioList = []; + // TODO: 算法待优化 // 异步遍历图片 dom - Array.from(imageItemDoms).map(async (item) => { - // // 获取屏幕比例的 90% 宽度 - // const clientWidth = window.innerWidth * 0.9; - // // 先把默认 margin 置为 0 - // item.style.margin = "0"; - // // 宽度默认撑满屏幕宽度 90%; - // item.style.width = `${clientWidth}px`; + await Promise.all(imageItemDoms.map(async (item) => { // 获取原app中图片的src const imgSrc = item.firstChild.src; // 判断是否有 @ 符 const imgSrcAtIndex = imgSrc.indexOf("@"); // 将所有图片转换为 .webp 格式节省加载速度, 并返回给原来的 image 标签 item.firstChild.src = imgSrcAtIndex !== -1 ? imgSrc.slice(0, imgSrcAtIndex + 1) + ".webp" : imgSrc; - // // 设置自动高度 - // item.style.height = "auto"; + // 获取图片的宽高比 + ratioList.push(await getImageRatio(item.firstChild.src)); + })).then(() => { + // 判断 ratioList 中超过 1 的个数为 3 的倍数 且 ratioList 的长度大于 3 + const isAllOneLength = ratioList.filter(item => item >= 0.9 && item <= 1.1).length; + const isAllOne = ratioList.length === 9 ? isAllOneLength > ratioList.length / 2 : isAllOneLength > 0 && isAllOneLength % 3 === 0 && ratioList.length > 3; + // 说明可能为组装的拼图, 如果不是则放大为大图 + if (!isAllOne) { + // 找到图标容器dom + const containerDom = document.querySelector(".bm-pics-block__container"); + if (containerDom) { + // 先把默认 padding-left 置为0 + containerDom.style.paddingLeft = "0"; + // 先把默认 padding-right 置为0 + containerDom.style.paddingRight = "0"; + // 设置 flex 模式下以列形式排列 + containerDom.style.flexDirection = "column"; + // 设置 flex 模式下每个容器间隔15px + containerDom.style.gap = "15px"; + // flex - 垂直居中 + containerDom.style.justifyContent = "center"; + // flex - 水平居中 + containerDom.style.alignItems = "center"; + } + + // 新版动态需要给 bm-pics-block 的父级元素设置 flex 以及 column + const newContainerDom = document.querySelector(".bm-pics-block")?.parentElement; + if (newContainerDom) { + // 设置为 flex + newContainerDom.style.display = "flex"; + // 设置为竖向排列 + newContainerDom.style.flexDirection = "column"; + // flex - 垂直居中 + newContainerDom.style.justifyContent = "center"; + // flex - 水平居中 + newContainerDom.style.alignItems = "center"; + } + + imageItemDoms.forEach(item => { + // 获取屏幕比例的 90% 宽度 + const clientWidth = window.innerWidth * 0.9; + // 先把默认 margin 置为 0 + item.style.margin = "0"; + // 宽度默认撑满屏幕宽度 90%; + item.style.width = `${clientWidth}px`; + // 设置自动高度 + item.style.height = "auto"; + }) + } else { + imageItemDoms.forEach(async (item) => { + // 获取当前图片标签的 src + const imgSrc = item.firstChild.src; + // 获取 @ 符的索引 + const imgSrcAtIndex = imgSrc.indexOf("@"); + // 获取图片比例 + const ratio = await getImageRatio(item.firstChild.src); + // 如果比例大于 3 即为长图, 则获取 header 图 + item.firstChild.src = ratio > 3 ? imgSrc.slice(0, imgSrcAtIndex + 1) + "260w_260h_!header.webp" : imgSrc.slice(0, imgSrcAtIndex + 1) + "260w_260h_1e_1c.webp"; + }); + } }) } diff --git a/pyproject.toml b/pyproject.toml index 25bcada..ff933ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dev = [ adapters = [ { name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" } ] -plugins = ["haruka_bot", "nonebot_plugin_gocqhttp"] +plugins = ["haruka_bot", "nonebot_plugin_gocqhttp", "nonebot_plugin_guild_patch"] plugin_dirs = [] builtin_plugins = [] From 9cd859cc5075f725d8de5530c588525b56dd7620 Mon Sep 17 00:00:00 2001 From: djkcyl Date: Wed, 28 Jun 2023 11:00:48 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BC=80=E6=92=AD?= =?UTF-8?q?=E6=97=B6=E9=95=BF=EF=BC=8C=E5=BC=80=E6=92=AD=E5=88=86=E5=8C=BA?= =?UTF-8?q?=E6=8F=90=E9=86=92=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=88=AA=E5=9B=BE?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E5=92=8C=E6=8A=A5=E9=94=99=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- haruka_bot/plugins/pusher/dynamic_pusher.py | 4 +- haruka_bot/plugins/pusher/live_pusher.py | 21 +- haruka_bot/utils/__init__.py | 22 ++ haruka_bot/utils/browser.py | 216 +++++++++++--------- 4 files changed, 162 insertions(+), 101 deletions(-) diff --git a/haruka_bot/plugins/pusher/dynamic_pusher.py b/haruka_bot/plugins/pusher/dynamic_pusher.py index 7805b51..a544f38 100644 --- a/haruka_bot/plugins/pusher/dynamic_pusher.py +++ b/haruka_bot/plugins/pusher/dynamic_pusher.py @@ -70,11 +70,10 @@ async def dy_sched(): dynamic_id = int(dynamic.extend.dyn_id_str) if dynamic_id > offset[uid]: logger.info(f"检测到新动态({dynamic_id}):{name}({uid})") - image = await get_dynamic_screenshot(dynamic_id) + image, err = await get_dynamic_screenshot(dynamic_id) url = f"https://t.bilibili.com/{dynamic_id}" if image is None: logger.debug(f"动态不存在,已跳过:{url}") - offset[uid] = dynamic_id return elif dynamic.card_type in [ DynamicType.live_rcmd, @@ -97,6 +96,7 @@ async def dy_sched(): } message = ( f"{name} {type_msg.get(dynamic.card_type, type_msg[0])}:\n" + f"{f'动态图片可能截图异常:{err}' if err else ''}\n" f"{MessageSegment.image(image)}\n{url}" ) diff --git a/haruka_bot/plugins/pusher/live_pusher.py b/haruka_bot/plugins/pusher/live_pusher.py index 63c0cd4..c60e3cd 100644 --- a/haruka_bot/plugins/pusher/live_pusher.py +++ b/haruka_bot/plugins/pusher/live_pusher.py @@ -1,12 +1,15 @@ +import time + from bilireq.live import get_rooms_info_by_uids from nonebot.adapters.onebot.v11.message import MessageSegment from nonebot.log import logger from ...config import plugin_config from ...database import DB as db -from ...utils import PROXIES, safe_send, scheduler +from ...utils import PROXIES, safe_send, scheduler, calc_time_total status = {} +live_time = {} @scheduler.scheduled_job( @@ -20,7 +23,6 @@ async def live_sched(): return logger.debug(f"爬取直播列表,目前开播{sum(status.values())}人,总共{len(uids)}人") res = await get_rooms_info_by_uids(uids, reqtype="web", proxies=PROXIES) - print(res) if not res: return for uid, info in res.items(): @@ -35,17 +37,28 @@ async def live_sched(): name = info["uname"] if new_status: # 开播 + live_time[uid] = info["live_time"] room_id = info["short_id"] or info["room_id"] url = f"https://live.bilibili.com/{room_id}" title = info["title"] cover = info["cover_from_user"] or info["keyframe"] + area_parent = info["area_v2_parent_name"] + area = info["area_v2_name"] + room_area = f"{area_parent} / {area}" logger.info(f"检测到开播:{name}({uid})") - live_msg = f"{name} 正在直播:\n{title}\n{MessageSegment.image(cover)}" + f"\n{url}" + live_msg = ( + f"{name} 开播啦!\n分区:{room_area}\n标题:{title}\n{MessageSegment.image(cover)}\n{url}" + ) else: # 下播 logger.info(f"检测到下播:{name}({uid})") if not plugin_config.haruka_live_off_notify: # 没开下播推送 continue - live_msg = f"{name} 下播了" + live_time_msg = ( + f",本次直播时长 {calc_time_total(time.time() - live_time[uid])}。" + if live_time[uid] + else "。" + ) + live_msg = f"{name} 下播了{live_time_msg}" # 推送 push_list = await db.get_push_list(uid, "live") diff --git a/haruka_bot/utils/__init__.py b/haruka_bot/utils/__init__.py index daf16a5..e2c8961 100644 --- a/haruka_bot/utils/__init__.py +++ b/haruka_bot/utils/__init__.py @@ -2,6 +2,7 @@ import sys import re import contextlib +import datetime from pathlib import Path from typing import Union from bilireq.utils import get @@ -176,6 +177,27 @@ async def _to_me() -> bool: return Rule(_to_me) +def calc_time_total(t): + t = int(t * 1000) + if t < 5000: + return f"{t} 毫秒" + + timedelta = datetime.timedelta(seconds=t // 1000) + day = timedelta.days + hour, mint, sec = tuple(int(n) for n in str(timedelta).split(",")[-1].split(":")) + + total = "" + if day: + total += f"{day} 天 " + if hour: + total += f"{hour} 小时 " + if mint: + total += f"{mint} 分钟 " + if sec and not day and not hour: + total += f"{sec} 秒 " + return total + + async def safe_send(bot_id, send_type, type_id, message, at=False): """发送出现错误时, 尝试重新发送, 并捕获异常且不会中断运行""" diff --git a/haruka_bot/utils/browser.py b/haruka_bot/utils/browser.py index 73a841d..613cd1c 100644 --- a/haruka_bot/utils/browser.py +++ b/haruka_bot/utils/browser.py @@ -1,4 +1,5 @@ import asyncio +import contextlib import os import re import sys @@ -7,7 +8,7 @@ from nonebot.log import logger from playwright.__main__ import main -from playwright.async_api import BrowserContext, async_playwright +from playwright.async_api import BrowserContext, async_playwright, Page from ..config import plugin_config from .fonts_provider import fill_font @@ -63,113 +64,134 @@ async def get_browser() -> BrowserContext: async def get_dynamic_screenshot(dynamic_id, style=plugin_config.haruka_screenshot_style): """获取动态截图""" - if style.lower() == "mobile": - return await get_dynamic_screenshot_mobile(dynamic_id) - else: - return await get_dynamic_screenshot_pc(dynamic_id) - - -async def get_dynamic_screenshot_mobile(dynamic_id): + image: Optional[bytes] = None + err = "" + for i in range(3): + browser = await get_browser() + page = await browser.new_page() + try: + if style.lower() == "mobile": + page, clip = await get_dynamic_screenshot_mobile(dynamic_id, page) + else: + page, clip = await get_dynamic_screenshot_pc(dynamic_id, page) + clip["height"] = min(clip["height"], 32766) + return ( + await page.screenshot(clip=clip, full_page=True, type="jpeg", quality=98), + err, + ) + except TimeoutError: + logger.warning(f"截图超时,重试 {i + 1}/3") + err = "截图超时" + except Notfound: + logger.error(f"动态 {dynamic_id} 不存在") + err = "动态不存在" + except AssertionError: + logger.error(f"动态 {dynamic_id} 截图失败") + err = "网页元素获取失败" + image = await page.screenshot(full_page=True, type="jpeg", quality=80) + except Exception as e: + if "bilibili.com/404" in page.url: + logger.error(f"动态 {dynamic_id} 不存在") + err = "动态不存在" + break + elif "waiting until" in str(e): + logger.error(f"动态 {dynamic_id} 截图超时") + err = "截图超时" + else: + logger.exception(f"动态 {dynamic_id} 截图失败") + err = "截图失败" + with contextlib.suppress(Exception): + image = await page.screenshot(full_page=True, type="jpeg", quality=80) + finally: + with contextlib.suppress(Exception): + await page.close() + return image, err + + +async def get_dynamic_screenshot_mobile(dynamic_id, page: Page): """移动端动态截图""" url = f"https://m.bilibili.com/dynamic/{dynamic_id}" - browser = await get_browser() - page = await browser.new_page() await page.set_viewport_size({"width": 460, "height": 780}) - try: - await page.route(re.compile("^https://static.graiax/fonts/(.+)$"), fill_font) - if plugin_config.haruka_captcha_address: - page = await resolve_captcha(url, page) - else: - await page.goto( - url, - wait_until="networkidle", - timeout=plugin_config.haruka_dynamic_timeout * 1000, - ) - # 动态被删除或者进审核了 - if page.url == "https://m.bilibili.com/404": - return None - # await page.add_script_tag( - # content= - # # 去除打开app按钮 - # "document.getElementsByClassName('m-dynamic-float-openapp').forEach(v=>v.remove());" - # # 去除关注按钮 - # "document.getElementsByClassName('dyn-header__following').forEach(v=>v.remove());" - # # 修复字体与换行问题 - # "const dyn=document.getElementsByClassName('dyn-card')[0];" - # "dyn.style.fontFamily='Noto Sans CJK SC, sans-serif';" - # "dyn.style.overflowWrap='break-word'" - # ) - - await page.wait_for_load_state(state="domcontentloaded", timeout=20000) - if "opus" in page.url: - await page.wait_for_selector(".opus-module-author", state="visible") - else: - await page.wait_for_selector(".dyn-header__author__face", state="visible") - - await page.add_script_tag(path=mobile_js) - - await page.evaluate( - f'setFont("{plugin_config.haruka_dynamic_font}", ' - f'"{plugin_config.haruka_dynamic_font_source}")' - if plugin_config.haruka_dynamic_font - else "setFont()" + await page.route(re.compile("^https://static.graiax/fonts/(.+)$"), fill_font) + if plugin_config.haruka_captcha_address: + page = await resolve_captcha(url, page) + else: + await page.goto( + url, + wait_until="networkidle", + timeout=plugin_config.haruka_dynamic_timeout * 1000, ) - await page.wait_for_function("getMobileStyle()") + # 动态被删除或者进审核了 + if page.url == "https://m.bilibili.com/404": + raise Notfound + # await page.add_script_tag( + # content= + # # 去除打开app按钮 + # "document.getElementsByClassName('m-dynamic-float-openapp').forEach(v=>v.remove());" + # # 去除关注按钮 + # "document.getElementsByClassName('dyn-header__following').forEach(v=>v.remove());" + # # 修复字体与换行问题 + # "const dyn=document.getElementsByClassName('dyn-card')[0];" + # "dyn.style.fontFamily='Noto Sans CJK SC, sans-serif';" + # "dyn.style.overflowWrap='break-word'" + # ) + + await page.wait_for_load_state(state="domcontentloaded", timeout=20000) + if "opus" in page.url: + await page.wait_for_selector(".opus-module-author", state="visible") + else: + await page.wait_for_selector(".dyn-header__author__face", state="visible") - await page.wait_for_load_state("networkidle") - await page.wait_for_load_state("domcontentloaded") + await page.add_script_tag(path=mobile_js) - await page.wait_for_timeout( - 200 if plugin_config.haruka_dynamic_font_source == "remote" else 50 - ) + await page.evaluate( + f'setFont("{plugin_config.haruka_dynamic_font}", ' + f'"{plugin_config.haruka_dynamic_font_source}")' + if plugin_config.haruka_dynamic_font + else "setFont()" + ) + await page.wait_for_function("getMobileStyle()") - # 判断字体是否加载完成 - need_wait = ["imageComplete", "fontsLoaded"] - await asyncio.gather(*[page.wait_for_function(f"{i}()") for i in need_wait]) + await page.wait_for_load_state("networkidle") + await page.wait_for_load_state("domcontentloaded") - card = await page.query_selector(".opus-modules" if "opus" in page.url else ".dyn-card") - assert card - clip = await card.bounding_box() - assert clip - return await page.screenshot(clip=clip, full_page=True) - except Exception: - logger.exception(f"截取动态时发生错误:{url}") - return await page.screenshot(full_page=True) - finally: - await page.close() + await page.wait_for_timeout( + 200 if plugin_config.haruka_dynamic_font_source == "remote" else 50 + ) + + # 判断字体是否加载完成 + need_wait = ["imageComplete", "fontsLoaded"] + await asyncio.gather(*[page.wait_for_function(f"{i}()") for i in need_wait]) + + card = await page.query_selector(".opus-modules" if "opus" in page.url else ".dyn-card") + assert card + clip = await card.bounding_box() + assert clip + return page, clip -async def get_dynamic_screenshot_pc(dynamic_id): +async def get_dynamic_screenshot_pc(dynamic_id, page: Page): """电脑端动态截图""" url = f"https://t.bilibili.com/{dynamic_id}" - browser = await get_browser() - - page = await browser.new_page() await page.set_viewport_size({"width": 2560, "height": 1080}) - try: - await page.goto( - url, - wait_until="networkidle", - timeout=plugin_config.haruka_dynamic_timeout * 1000, - ) - # 动态被删除或者进审核了 - 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: - logger.exception(f"截取动态时发生错误:{url}") - return await page.screenshot(full_page=True) - finally: - await page.close() + await page.goto( + url, + wait_until="networkidle", + timeout=plugin_config.haruka_dynamic_timeout * 1000, + ) + # 动态被删除或者进审核了 + if page.url == "https://www.bilibili.com/404": + raise Notfound + 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 page, clip def install(): @@ -216,3 +238,7 @@ async def check_playwright_env(): raise ImportError( "加载失败,Playwright 依赖不全," "解决方法:https://haruka-bot.sk415.icu/faq.html#playwright-依赖不全" ) + + +class Notfound(Exception): + pass From e97153a19dc098f330612576439a36f461a1a1e4 Mon Sep 17 00:00:00 2001 From: djkcyl Date: Thu, 29 Jun 2023 10:55:02 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=A4=A7=E5=9B=BE=E6=A8=A1=E5=BC=8F=E8=AE=BE=E5=AE=9A=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8A=A8=E6=80=81=E6=8B=89=E5=8F=96=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E7=9A=84=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/usage/settings.md | 32 +++++++++++++++++++++ haruka_bot/config.py | 1 + haruka_bot/plugins/pusher/dynamic_pusher.py | 2 +- haruka_bot/utils/browser.py | 4 ++- haruka_bot/utils/mobile.js | 4 +-- 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/docs/usage/settings.md b/docs/usage/settings.md index 63e34a3..4d6751a 100644 --- a/docs/usage/settings.md +++ b/docs/usage/settings.md @@ -117,6 +117,28 @@ HARUKA_DYNAMIC_AT=True HARUKA_SCREENSHOT_STYLE=pc ``` +## HARUKA_CAPTCHA_ADDRESS + +默认值: + +验证码地址,用于解决动态截图验证码问题。 +(如果你不知道这是什么,请忽略) + +```yml +HARUKA_CAPTCHA_ADDRESS=https://captcha-cd.ngworks.cn +``` + +## HARUKA_BROWSER_UA + +默认值:"" + +自定义浏览器 UA +(如果你不知道这是什么,请忽略) + +```yml +HARUKA_BROWSER_UA="Mozilla/5.0 (Linux; Android 10; Redmi K30 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.210 Mobile Safari/537.36" +``` + ## HARUKA_DYNAMIC_TIMEOUT 默认值:10 @@ -138,6 +160,16 @@ HARUKA_DYNAMIC_TIMEOUT=30 HARUKA_DYNAMIC_FONT="Microsoft YaHei" ``` +## HARUKA_DYNAMIC_BIG_IMAGE + +默认值:False + +是否使用大图模式,大图模式下会将动态图片扩展至页宽。 + +```json +HARUKA_DYNAMIC_BIG_IMAGE=True +``` + ## HARUKA_COMMAND_PREFIX 默认值:"" diff --git a/haruka_bot/config.py b/haruka_bot/config.py index 57b2c14..0303531 100644 --- a/haruka_bot/config.py +++ b/haruka_bot/config.py @@ -22,6 +22,7 @@ class Config(BaseSettings): haruka_dynamic_timeout: int = 30 haruka_dynamic_font_source: str = "system" haruka_dynamic_font: Optional[str] = "Noto Sans CJK SC" + haruka_dynamic_big_image: bool = False haruka_command_prefix: str = "" # 频道管理员身份组 haruka_guild_admin_roles: List[str] = ["频道主", "超级管理员"] diff --git a/haruka_bot/plugins/pusher/dynamic_pusher.py b/haruka_bot/plugins/pusher/dynamic_pusher.py index a544f38..156008c 100644 --- a/haruka_bot/plugins/pusher/dynamic_pusher.py +++ b/haruka_bot/plugins/pusher/dynamic_pusher.py @@ -43,7 +43,7 @@ async def dy_sched(): ).list except AioRpcError as e: if e.code() == StatusCode.DEADLINE_EXCEEDED: - logger.error("爬取动态超时,将在下个轮询中重试") + logger.error(f"爬取动态超时,将在下个轮询中重试:{e.code()} {e.details()}") return raise diff --git a/haruka_bot/utils/browser.py b/haruka_bot/utils/browser.py index 613cd1c..969f77a 100644 --- a/haruka_bot/utils/browser.py +++ b/haruka_bot/utils/browser.py @@ -150,7 +150,9 @@ async def get_dynamic_screenshot_mobile(dynamic_id, page: Page): if plugin_config.haruka_dynamic_font else "setFont()" ) - await page.wait_for_function("getMobileStyle()") + await page.wait_for_function( + f"getMobileStyle({'true' if plugin_config.haruka_dynamic_big_image else 'false'})" + ) await page.wait_for_load_state("networkidle") await page.wait_for_load_state("domcontentloaded") diff --git a/haruka_bot/utils/mobile.js b/haruka_bot/utils/mobile.js index b024b28..30a9b2b 100644 --- a/haruka_bot/utils/mobile.js +++ b/haruka_bot/utils/mobile.js @@ -9,7 +9,7 @@ async function getMobileStyle(expandImage = false) { // 删除 dom 的对象, 可以自行添加 ( className 需要增加 '.' 为前缀, id 需要增加 '#' 为前缀) const deleteDoms = { // 关注 dom - followDoms: [".dyn-header__following", ".easy-follow-btn"], + followDoms: [".dyn-header__following", ".easy-follow-btn", ".dyn-orig-author__right"], // 分享 dom shareDoms: [".dyn-share"], // 打开程序 dom @@ -85,7 +85,7 @@ async function getMobileStyle(expandImage = false) { const isAllOneLength = ratioList.filter(item => item >= 0.9 && item <= 1.1).length; const isAllOne = ratioList.length === 9 ? isAllOneLength > ratioList.length / 2 : isAllOneLength > 0 && isAllOneLength % 3 === 0 && ratioList.length > 3; // 说明可能为组装的拼图, 如果不是则放大为大图 - if (!isAllOne) { + if (!isAllOne && useImageBig) { // 找到图标容器dom const containerDom = document.querySelector(".bm-pics-block__container"); if (containerDom) { From 1c96554ab7effed6c436c2134a270f379cf074b2 Mon Sep 17 00:00:00 2001 From: djkcyl Date: Thu, 29 Jun 2023 15:21:57 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20python=203.8=20typing?= =?UTF-8?q?=20=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- haruka_bot/utils/captcha.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/haruka_bot/utils/captcha.py b/haruka_bot/utils/captcha.py index 762de06..11e3c31 100644 --- a/haruka_bot/utils/captcha.py +++ b/haruka_bot/utils/captcha.py @@ -1,5 +1,5 @@ import contextlib -from typing import Optional +from typing import Optional, List import httpx from nonebot.log import logger @@ -13,9 +13,9 @@ class CaptchaData(BaseModel): captcha_id: str - points: list[list[int]] - rectangles: list[list[int]] - yolo_data: list[list[int]] + points: List[List[int]] + rectangles: List[List[int]] + yolo_data: List[List[int]] time: int @@ -97,7 +97,7 @@ async def captcha_result_callback(response: Response): assert captcha_req.data last_captcha_id = captcha_req.data.captcha_id if captcha_req.data: - click_points: list[list[int]] = captcha_req.data.points + click_points: List[List[int]] = captcha_req.data.points logger.warning(f"[Captcha] 识别到 {len(click_points)} 个坐标,正在点击") # 根据原图大小和截图大小计算缩放比例,然后计算出正确的需要点击的位置 for point in click_points: From b2d978c55d28109dbe2944ed0131d2e5d793cfc9 Mon Sep 17 00:00:00 2001 From: djkcyl Date: Thu, 29 Jun 2023 15:55:49 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E5=BC=83=E7=94=A8=20pc=20=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E6=88=AA=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/usage/settings.md | 4 ++-- haruka_bot/config.py | 7 +++++++ haruka_bot/utils/browser.py | 9 +++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/usage/settings.md b/docs/usage/settings.md index 4d6751a..80a76a2 100644 --- a/docs/usage/settings.md +++ b/docs/usage/settings.md @@ -107,7 +107,7 @@ HARUKA_LIVE_INTERVAL=20 HARUKA_DYNAMIC_AT=True ``` -## HARUKA_SCREENSHOT_STYLE + ## HARUKA_CAPTCHA_ADDRESS diff --git a/haruka_bot/config.py b/haruka_bot/config.py index 0303531..d58e311 100644 --- a/haruka_bot/config.py +++ b/haruka_bot/config.py @@ -1,5 +1,6 @@ from typing import List, Optional +from loguru import logger from nonebot import get_driver from pydantic import BaseSettings, validator from pydantic.fields import ModelField @@ -32,6 +33,12 @@ def non_negative(cls, v: int, field: ModelField): """定时器为负返回默认值""" return field.default if v < 1 else v + @validator("haruka_screenshot_style") + def screenshot_style(cls, v: str): + if v != "mobile": + logger.warning("截图样式目前只支持 mobile,pc 样式现已被弃用") + return "mobile" + class Config: extra = "ignore" diff --git a/haruka_bot/utils/browser.py b/haruka_bot/utils/browser.py index 969f77a..99f45a9 100644 --- a/haruka_bot/utils/browser.py +++ b/haruka_bot/utils/browser.py @@ -70,10 +70,11 @@ async def get_dynamic_screenshot(dynamic_id, style=plugin_config.haruka_screensh browser = await get_browser() page = await browser.new_page() try: - if style.lower() == "mobile": - page, clip = await get_dynamic_screenshot_mobile(dynamic_id, page) - else: - page, clip = await get_dynamic_screenshot_pc(dynamic_id, page) + # if style.lower() == "mobile": + # page, clip = await get_dynamic_screenshot_mobile(dynamic_id, page) + # else: + # page, clip = await get_dynamic_screenshot_pc(dynamic_id, page) + page, clip = await get_dynamic_screenshot_mobile(dynamic_id, page) clip["height"] = min(clip["height"], 32766) return ( await page.screenshot(clip=clip, full_page=True, type="jpeg", quality=98),