Merge branch 'main' into next

This commit is contained in:
felinae98
2022-06-05 16:48:40 +08:00
29 changed files with 864 additions and 208 deletions
+12 -3
View File
@@ -17,7 +17,7 @@ from nonebot.rule import to_me
from nonebot.typing import T_State
from .config import Config
from .platform import check_sub_target, platform_manager
from .platform import Platform, check_sub_target, platform_manager
from .plugin_config import plugin_config
from .types import Category, Target, User
from .utils import parse_text
@@ -108,8 +108,13 @@ def do_add_sub(add_sub: Type[Matcher]):
"platform", _gen_prompt_template("{_prompt}"), [Depends(parse_platform)]
)
async def init_id(state: T_State):
if platform_manager[state["platform"]].has_target:
state["_prompt"] = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”"
cur_platform = platform_manager[state["platform"]]
if cur_platform.has_target:
state["_prompt"] = (
("1." + cur_platform.parse_target_promot + "\n2.")
if cur_platform.parse_target_promot
else ""
) + "请输入订阅用户的id\n查询id获取方法请回复:“查询”"
else:
state["id"] = "default"
state["name"] = await platform_manager[state["platform"]].get_target_name(
@@ -125,6 +130,8 @@ def do_add_sub(add_sub: Type[Matcher]):
raise LookupError
if target == "取消":
raise KeyboardInterrupt
platform = platform_manager[state["platform"]]
target = await platform.parse_target(target)
name = await check_sub_target(state["platform"], target)
if not name:
raise ValueError
@@ -141,6 +148,8 @@ def do_add_sub(add_sub: Type[Matcher]):
await add_sub.finish("已中止订阅")
except (ValueError):
await add_sub.reject("id输入错误")
except (Platform.ParseTargetException):
await add_sub.reject("不能从你的输入中提取出id,请检查你输入的内容是否符合预期")
else:
await add_sub.send(
"即将订阅的用户为:{} {} {}\n如有错误请输入“取消”重新订阅".format(
+83 -1
View File
@@ -1,10 +1,11 @@
import json
import re
from typing import Any, Optional
from ..post import Post
from ..types import Category, RawPost, Tag, Target
from ..utils import http_client
from .platform import CategoryNotSupport, NewMessage
from .platform import CategoryNotSupport, NewMessage, StatusChange
class Bilibili(NewMessage):
@@ -25,6 +26,7 @@ class Bilibili(NewMessage):
schedule_kw = {"seconds": 10}
name = "B站"
has_target = True
parse_target_promot = "请输入用户主页的链接"
async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client:
@@ -36,6 +38,16 @@ class Bilibili(NewMessage):
return None
return res_data["data"]["name"]
async def parse_target(self, target_text: str) -> Target:
if re.match(r"\d+", target_text):
return Target(target_text)
elif match := re.match(
r"(?:https?://)?space\.bilibili\.com/(\d+)", target_text
):
return Target(match.group(1))
else:
raise self.ParseTargetException()
async def get_sub_list(self, target: Target) -> list[RawPost]:
async with http_client() as client:
params = {"host_uid": target, "offset": 0, "need_top": 0}
@@ -143,3 +155,73 @@ class Bilibili(NewMessage):
else:
raise CategoryNotSupport(post_type)
return Post("bilibili", text=text, url=url, pics=pic, target_name=target_name)
class Bilibililive(StatusChange):
# Author : Sichongzou
# Date : 2022-5-18 8:54
# Description : bilibili开播提醒
# E-mail : 1557157806@qq.com
categories = {}
platform_name = "bilibili-live"
enable_tag = True
enabled = True
is_common = True
schedule_type = "interval"
schedule_kw = {"seconds": 10}
name = "Bilibili直播"
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client:
res = await client.get(
"https://api.bilibili.com/x/space/acc/info", params={"mid": target}
)
res_data = json.loads(res.text)
if res_data["code"]:
return None
return res_data["data"]["name"]
async def get_status(self, target: Target):
async with http_client() as client:
params = {"mid": target}
res = await client.get(
"https://api.bilibili.com/x/space/acc/info",
params=params,
timeout=4.0,
)
res_dict = json.loads(res.text)
if res_dict["code"] == 0:
info = {}
info["uid"] = res_dict["data"]["mid"]
info["uname"] = res_dict["data"]["name"]
info["live_state"] = res_dict["data"]["live_room"]["liveStatus"]
info["room_id"] = res_dict["data"]["live_room"]["roomid"]
info["title"] = res_dict["data"]["live_room"]["title"]
info["cover"] = res_dict["data"]["live_room"]["cover"]
return info
else:
return []
def compare_status(self, target: Target, old_status, new_status) -> list[RawPost]:
if (
new_status["live_state"] != old_status["live_state"]
and new_status["live_state"] == 1
):
return [new_status]
else:
return []
async def parse(self, raw_post: RawPost) -> Post:
url = "https://live.bilibili.com/{}".format(raw_post["room_id"])
pic = [raw_post["cover"]]
target_name = raw_post["uname"]
title = raw_post["title"]
return Post(
self.name,
text=title,
url=url,
pics=pic,
target_name=target_name,
compress=True,
)
@@ -1,3 +1,4 @@
import re
from typing import Any, Optional
from ..post import Post
@@ -17,6 +18,7 @@ class NcmArtist(NewMessage):
schedule_kw = {"minutes": 1}
name = "网易云-歌手"
has_target = True
parse_target_promot = "请输入歌手主页(包含数字ID)的链接"
async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client:
@@ -29,6 +31,16 @@ class NcmArtist(NewMessage):
return
return res_data["artist"]["name"]
async def parse_target(self, target_text: str) -> Target:
if re.match(r"^\d+$", target_text):
return Target(target_text)
elif match := re.match(
r"(?:https?://)?music\.163\.com/#/artist\?id=(\d+)", target_text
):
return Target(match.group(1))
else:
raise self.ParseTargetException()
async def get_sub_list(self, target: Target) -> list[RawPost]:
async with http_client() as client:
res = await client.get(
@@ -1,3 +1,4 @@
import re
from typing import Any, Optional
from ..post import Post
@@ -17,6 +18,7 @@ class NcmRadio(NewMessage):
schedule_kw = {"minutes": 10}
name = "网易云-电台"
has_target = True
parse_target_promot = "请输入主播电台主页(包含数字ID)的链接"
async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client:
@@ -30,6 +32,16 @@ class NcmRadio(NewMessage):
return
return res_data["programs"][0]["radio"]["name"]
async def parse_target(self, target_text: str) -> Target:
if re.match(r"^\d+$", target_text):
return Target(target_text)
elif match := re.match(
r"(?:https?://)?music\.163\.com/#/djradio\?id=(\d+)", target_text
):
return Target(match.group(1))
else:
raise self.ParseTargetException()
async def get_sub_list(self, target: Target) -> list[RawPost]:
async with http_client() as client:
res = await client.post(
+71 -67
View File
@@ -1,3 +1,5 @@
import json
import ssl
import time
from abc import ABC, abstractmethod
from collections import defaultdict
@@ -47,6 +49,7 @@ class Platform(metaclass=RegistryABCMeta, base=True):
enable_tag: bool
store: dict[Target, Any]
platform_name: str
parse_target_promot: Optional[str] = None
@abstractmethod
async def get_target_name(self, target: Target) -> Optional[str]:
@@ -58,6 +61,25 @@ class Platform(metaclass=RegistryABCMeta, base=True):
) -> list[tuple[User, list[Post]]]:
...
async def do_fetch_new_post(
self, target: Target, users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]:
try:
return await self.fetch_new_post(target, users)
except httpx.RequestError as err:
logger.warning(
"network connection error: {}, url: {}".format(
type(err), err.request.url
)
)
return []
except ssl.SSLError as err:
logger.warning(f"ssl error: {err}")
return []
except json.JSONDecodeError as err:
logger.warning(f"json error, parsing: {err.doc}")
return []
@abstractmethod
async def parse(self, raw_post: RawPost) -> Post:
...
@@ -73,6 +95,12 @@ class Platform(metaclass=RegistryABCMeta, base=True):
self.reverse_category[val] = key
self.store = dict()
class ParseTargetException(Exception):
pass
async def parse_target(self, target_string: str) -> Target:
return Target(target_string)
@abstractmethod
def get_tags(self, raw_post: RawPost) -> Optional[Collection[Tag]]:
"Return Tag list of given RawPost"
@@ -219,30 +247,22 @@ class NewMessage(MessageProcess, abstract=True):
async def fetch_new_post(
self, target: Target, users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]:
try:
post_list = await self.get_sub_list(target)
new_posts = await self.filter_common_with_diff(target, post_list)
if not new_posts:
return []
else:
for post in new_posts:
logger.info(
"fetch new post from {} {}: {}".format(
self.platform_name,
target if self.has_target else "-",
self.get_id(post),
)
)
res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {}
return res
except httpx.RequestError as err:
logger.warning(
"network connection error: {}, url: {}".format(
type(err), err.request.url
)
)
post_list = await self.get_sub_list(target)
new_posts = await self.filter_common_with_diff(target, post_list)
if not new_posts:
return []
else:
for post in new_posts:
logger.info(
"fetch new post from {} {}: {}".format(
self.platform_name,
target if self.has_target else "-",
self.get_id(post),
)
)
res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {}
return res
class StatusChange(Platform, abstract=True):
@@ -263,30 +283,22 @@ class StatusChange(Platform, abstract=True):
async def fetch_new_post(
self, target: Target, users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]:
try:
new_status = await self.get_status(target)
res = []
if old_status := self.get_stored_data(target):
diff = self.compare_status(target, old_status, new_status)
if diff:
logger.info(
"status changes {} {}: {} -> {}".format(
self.platform_name,
target if self.has_target else "-",
old_status,
new_status,
)
new_status = await self.get_status(target)
res = []
if old_status := self.get_stored_data(target):
diff = self.compare_status(target, old_status, new_status)
if diff:
logger.info(
"status changes {} {}: {} -> {}".format(
self.platform_name,
target if self.has_target else "-",
old_status,
new_status,
)
res = await self.dispatch_user_post(target, diff, users)
self.set_stored_data(target, new_status)
return res
except httpx.RequestError as err:
logger.warning(
"network connection error: {}, url: {}".format(
type(err), err.request.url
)
)
return []
res = await self.dispatch_user_post(target, diff, users)
self.set_stored_data(target, new_status)
return res
class SimplePost(MessageProcess, abstract=True):
@@ -295,29 +307,21 @@ class SimplePost(MessageProcess, abstract=True):
async def fetch_new_post(
self, target: Target, users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]:
try:
new_posts = await self.get_sub_list(target)
if not new_posts:
return []
else:
for post in new_posts:
logger.info(
"fetch new post from {} {}: {}".format(
self.platform_name,
target if self.has_target else "-",
self.get_id(post),
)
)
res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {}
return res
except httpx.RequestError as err:
logger.warning(
"network connection error: {}, url: {}".format(
type(err), err.request.url
)
)
new_posts = await self.get_sub_list(target)
if not new_posts:
return []
else:
for post in new_posts:
logger.info(
"fetch new post from {} {}: {}".format(
self.platform_name,
target if self.has_target else "-",
self.get_id(post),
)
)
res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {}
return res
class NoTargetGroup(Platform, abstract=True):
+13 -3
View File
@@ -28,6 +28,7 @@ class Weibo(NewMessage):
schedule_type = "interval"
schedule_kw = {"seconds": 3}
has_target = True
parse_target_promot = "请输入用户主页(包含数字UID)的链接"
async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client:
@@ -41,6 +42,15 @@ class Weibo(NewMessage):
else:
return None
async def parse_target(self, target_text: str) -> Target:
if re.match(r"\d+", target_text):
return Target(target_text)
elif match := re.match(r"(?:https?://)?weibo\.com/u/(\d+)", target_text):
# 都2202年了应该不会有http了吧,不过还是防一手
return Target(match.group(1))
else:
raise self.ParseTargetException()
async def get_sub_list(self, target: Target) -> list[RawPost]:
async with http_client() as client:
params = {"containerid": "107603" + target}
@@ -133,9 +143,9 @@ class Weibo(NewMessage):
"https://m.weibo.cn/detail/{}".format(info["mid"]), headers=header
)
try:
full_json_text = re.search(
r'"status": ([\s\S]+),\s+"call"', res.text
).group(1)
match = re.search(r'"status": ([\s\S]+),\s+"call"', res.text)
assert match
full_json_text = match.group(1)
info = json.loads(full_json_text)
except:
logger.info(
+2 -1
View File
@@ -14,10 +14,11 @@ class PlugConfig(BaseSettings):
bison_filter_log: bool = False
bison_to_me: bool = True
bison_skip_browser_check: bool = False
bison_use_pic_merge: int = 0 # 多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效
bison_use_pic_merge: int = 0 # 多图片时启用图片合并转发(仅限群)
# 0:不启用;1:首条消息单独发送,剩余照片合并转发;2以及以上:所有消息全部合并转发
bison_resend_times: int = 0
bison_proxy: Optional[str]
bison_ua: str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
class Config:
extra = "ignore"
+1 -1
View File
@@ -59,7 +59,7 @@ async def fetch_and_send(target_type: str):
send_user_list,
)
)
to_send = await platform_manager[target_type].fetch_new_post(
to_send = await platform_manager[target_type].do_fetch_new_post(
target, send_userinfo_list
)
if not to_send:
+5 -6
View File
@@ -4,9 +4,8 @@ import httpx
from ..plugin_config import plugin_config
if plugin_config.bison_proxy:
http_client = functools.partial(
httpx.AsyncClient, proxies=plugin_config.bison_proxy
)
else:
http_client = httpx.AsyncClient
http_client = functools.partial(
httpx.AsyncClient,
proxies=plugin_config.bison_proxy or None,
headers={"user-agent": plugin_config.bison_ua},
)