支持同步收藏夹到频道

This commit is contained in:
xtaodada 2023-08-18 21:28:16 +08:00
parent a93093ea80
commit 6a23509ae3
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
20 changed files with 349 additions and 43 deletions

View File

@ -2,9 +2,6 @@
api_id = ID_HERE
api_hash = HASH_HERE
[plugins]
root = modules
[proxy]
enabled = False
hostname = 127.0.0.1
@ -19,8 +16,12 @@ lofter_channel = 0
lofter_channel_username = username
splash_channel = 0
splash_channel_username = username
bilifav_id = 0
bilifav_channel = 0
bilifav_channel_username = username
[api]
amap_key = ABCD
bili_cookie = ABCD
bili_auth_user = 777000,111000
bili_auth_chat = 777000,111000

View File

@ -217,7 +217,9 @@ async def take_screenshot(info: Dict) -> Optional[BytesIO]:
return None
async def audio_download(a: Audio, m: Message):
async def audio_download(
a: Audio, m: Message, push_id: int = None
) -> Optional[Message]:
try:
info = await a.get_info()
download_url_data = await a.get_download_url()
@ -243,12 +245,12 @@ async def audio_download(a: Audio, m: Message):
text = f"<b>{info['title']}</b>\n\n简介过长,无法显示\n\nhttps://www.bilibili.com/audio/au{a.get_auid()}"
else:
text = f"<b>{info['title']}</b>\n\nhttps://www.bilibili.com/audio/au{a.get_auid()}"
await bot.send_audio(
chat_id=m.chat.id,
msg = await bot.send_audio(
chat_id=push_id or m.chat.id,
audio=media,
caption=text,
parse_mode=ParseMode.HTML,
reply_to_message_id=m.reply_to_message_id,
reply_to_message_id=m.reply_to_message_id if not push_id else None,
thumb=thumb,
title=info.get("title"),
duration=info.get("duration"),
@ -256,12 +258,17 @@ async def audio_download(a: Audio, m: Message):
)
except BilibiliDownloaderError as e:
await fail_edit(m, e.MSG)
return
except Exception as e:
logger.exception("Downloading audio failed")
await fail_edit(m, f"下载/上传失败:{e}")
return
with contextlib.suppress(Exception):
await m.delete()
return msg
async def go_download(v: Video, p_num: int, m: Message):
async def go_download(v: Video, p_num: int, m: Message, task: bool = True):
video_path = cache_dir / f"{v.get_aid()}_{p_num}.mp4"
safe_remove(video_path)
flv_temp_path = cache_dir / f"{v.get_aid()}_{p_num}_temp.flv"
@ -298,6 +305,7 @@ async def go_download(v: Video, p_num: int, m: Message):
)
if result != 0:
raise FFmpegError
if task:
bot.loop.create_task(go_upload(v, p_num, m))
except BilibiliDownloaderError as e:
await fail_edit(m, e.MSG)
@ -328,7 +336,9 @@ async def go_upload_progress(current: int, total: int, m: Message):
await message_edit(total, current, chunk, chunk_time, m, "上传")
async def go_upload(v: Video, p_num: int, m: Message):
async def go_upload(
v: Video, p_num: int, m: Message, push_id: int = None
) -> Optional[Message]:
video_path = cache_dir / f"{v.get_aid()}_{p_num}.mp4"
if not video_path.exists():
await fail_edit(m, "视频文件不存在")
@ -346,8 +356,8 @@ async def go_upload(v: Video, p_num: int, m: Message):
video_jpg = None
caption = f"https://b23.tv/{v.get_bvid()}"
logger.info(f"Uploading {video_path}")
await bot.send_video(
chat_id=m.chat.id,
msg = await bot.send_video(
chat_id=push_id or m.chat.id,
video=str(video_path),
caption=caption,
parse_mode=ParseMode.HTML,
@ -358,7 +368,7 @@ async def go_upload(v: Video, p_num: int, m: Message):
supports_streaming=True,
progress=go_upload_progress,
progress_args=(m,),
reply_to_message_id=m.reply_to_message_id,
reply_to_message_id=m.reply_to_message_id if not push_id else None,
)
logger.info(f"Upload {video_path} success")
except BilibiliDownloaderError as e:
@ -376,3 +386,4 @@ async def go_upload(v: Video, p_num: int, m: Message):
del UPLOAD_MESSAGE_MAP[m.id]
with contextlib.suppress(Exception):
await m.delete()
return msg

104
defs/bilibili_fav.py Normal file
View File

@ -0,0 +1,104 @@
import time
from enum import Enum
from typing import Optional
from bilibili_api.favorite_list import FavoriteList
from pydantic import BaseModel, ValidationError
from pyrogram.types import Message
from defs.bilibili import credential, create_video, create_audio
from defs.bilibili_download import go_download, go_upload, audio_download
from defs.glover import bilifav_id, bilifav_channel
from models.models.bilifav import BiliFav
from models.services.bilifav import BiliFavAction
from init import logger
fav = FavoriteList(media_id=bilifav_id, credential=credential)
class BilibiliFavException(Exception):
pass
class MediaType(int, Enum):
video = 2
audio = 12
class Media(BaseModel):
id: int
bvid: str
type: MediaType
title: str
cover: Optional[str]
""" 封面 """
intro: Optional[str]
""" 简介 """
async def process_video(data: Media, m: Message):
"""处理视频"""
video = create_video(data.bvid)
await go_download(video, 0, m, task=False)
msg = await go_upload(video, 0, m, push_id=bilifav_channel)
if not msg:
raise BilibiliFavException
video_db = BiliFav(
id=data.id,
bv_id=data.bvid.lower(),
type=data.type.value,
title=data.title,
cover=data.cover,
message_id=msg.id,
file_id=msg.video.file_id,
timestamp=int(time.time()),
)
await BiliFavAction.add_bili_fav(video_db)
async def process_audio(data: Media, m: Message):
"""处理音频"""
audio = create_audio(f"au{data.id}")
msg = await audio_download(audio, m, push_id=bilifav_channel)
if not msg:
raise BilibiliFavException
audio_db = BiliFav(
id=data.id,
bv_id=data.bvid.lower(),
type=data.type.value,
title=data.title,
cover=data.cover,
message_id=msg.id,
file_id=msg.audio.file_id,
timestamp=int(time.time()),
)
await BiliFavAction.add_bili_fav(audio_db)
async def check_update(m: Message):
"""检查收藏夹是否更新"""
logger.info("Check bilibili favorite list")
try:
info = await fav.get_content()
except Exception as e:
logger.exception("Check bilibili favorite list failed")
await m.edit(f"获取收藏夹信息失败:{e}")
return
for media in info.get("medias", [])[::-1]:
try:
data = Media(**media)
except ValidationError as _:
logger.exception("Validate media failed")
continue
if await BiliFavAction.get_by_bv_id(data.bvid):
continue
n = await m.reply(f"处理 {data.type.name} {data.bvid} 中...")
try:
if data.type == MediaType.video:
await process_video(data, n)
elif data.type == MediaType.audio:
await process_audio(data, n)
except BilibiliFavException:
continue
await m.edit("收藏夹数据获取完毕")
logger.info("Check bilibili favorite list success")

View File

@ -5,7 +5,7 @@ from typing import Optional
from bs4 import BeautifulSoup
from init import request
from models.fragment import (
from models.services.fragment import (
AuctionStatus,
UserName,
TON_TO_USD_RATE,

View File

@ -13,9 +13,13 @@ lofter_channel: int = 0
lofter_channel_username: str = ""
splash_channel: int = 0
splash_channel_username: str = ""
bilifav_id: int = 0
bilifav_channel: int = 0
bilifav_channel_username: str = ""
# [api]
amap_key: str = ""
bili_auth_user_str: str = ""
bili_auth_chat_str: str = ""
config = RawConfigParser()
config.read("config.ini")
api_id = config.getint("pyrogram", "api_id", fallback=api_id)
@ -30,12 +34,20 @@ splash_channel = config.getint("post", "splash_channel", fallback=splash_channel
splash_channel_username = config.get(
"post", "splash_channel_username", fallback=splash_channel_username
)
bilifav_id = config.getint("post", "bilifav_id", fallback=bilifav_id)
bilifav_channel = config.getint("post", "bilifav_channel", fallback=bilifav_channel)
bilifav_channel_username = config.get(
"post", "bilifav_channel_username", fallback=bilifav_channel_username
)
amap_key = config.get("api", "amap_key", fallback=amap_key)
bili_auth_user_str = config.get("api", "bili_auth_user", fallback=bili_auth_user_str)
bili_auth_chat_str = config.get("api", "bili_auth_chat", fallback=bili_auth_chat_str)
try:
bili_auth_user: List[int] = list(map(int, bili_auth_user_str.split(",")))
bili_auth_chat: List[int] = list(map(int, bili_auth_chat_str.split(",")))
except ValueError:
bili_auth_user: List[int] = []
bili_auth_chat: List[int] = []
try:
ipv6 = bool(strtobool(ipv6))
except ValueError:

View File

@ -17,7 +17,7 @@ from pyrogram.types import (
)
from defs.glover import lofter_channel_username
from models.lofter import LofterPost as LofterPostModel
from models.services.lofter import LofterPost as LofterPostModel
from init import request

View File

@ -12,7 +12,7 @@ from pyrogram.types import Message
from defs.glover import lofter_channel
from defs.lofter import lofter_link
from models.lofter import LofterPost as LofterPostModel
from models.services.lofter import LofterPost as LofterPostModel
from models.models.lofter import Lofter as LofterModel
from init import request, bot

View File

@ -12,7 +12,7 @@ from defs.request import cache_file
from init import bot, request, logger
from models.models.splash import Splash as SplashModel
from models.apis.splash import Splash as SplashApi
from models.splash import SplashService
from models.services.splash import SplashService
async def get_splash() -> List[SplashApi]:

View File

14
models/models/bilifav.py Normal file
View File

@ -0,0 +1,14 @@
from sqlmodel import SQLModel, Field
class BiliFav(SQLModel, table=True):
__table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
id: int = Field(primary_key=True)
bv_id: str = Field()
type: int = Field(default=2)
title: str = Field()
cover: str = Field()
message_id: int = Field(default=0)
file_id: str = Field()
timestamp: int = Field(default=0)

View File

View File

@ -0,0 +1,42 @@
from typing import cast, Optional
from sqlalchemy import select
from sqlmodel.ext.asyncio.session import AsyncSession
from init import sqlite
from models.models.bilifav import BiliFav
class BiliFavAction:
@staticmethod
async def get_by_id(id_: int) -> Optional[BiliFav]:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
statement = select(BiliFav).where(BiliFav.id == id_)
results = await session.exec(statement)
return post[0] if (post := results.first()) else None
@staticmethod
async def get_by_bv_id(bv_id: str) -> Optional[BiliFav]:
if not bv_id:
return None
async with sqlite.session() as session:
session = cast(AsyncSession, session)
statement = select(BiliFav).where(BiliFav.bv_id == bv_id.lower())
results = await session.exec(statement)
return post[0] if (post := results.first()) else None
@staticmethod
async def add_bili_fav(bili_fav: BiliFav):
async with sqlite.session() as session:
session = cast(AsyncSession, session)
session.add(bili_fav)
await session.commit()
@staticmethod
async def update_bili_fav(bili_fav: BiliFav):
async with sqlite.session() as session:
session = cast(AsyncSession, session)
session.add(bili_fav)
await session.commit()
await session.refresh(bili_fav)

View File

@ -99,7 +99,7 @@ class FragmentSubText(Enum):
class FragmentSub:
@staticmethod
async def subscribe(cid: int, username: str):
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
data = Fragment(cid=cid, username=username)
session.add(data)
@ -107,14 +107,14 @@ class FragmentSub:
@staticmethod
async def unsubscribe(data: Fragment):
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
await session.delete(data)
await session.commit()
@staticmethod
async def get_by_cid_and_username(cid: int, username: str) -> Optional[Fragment]:
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
statement = (
select(Fragment)
@ -126,7 +126,7 @@ class FragmentSub:
@staticmethod
async def get_by_cid(cid: int) -> List[Fragment]:
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
statement = select(Fragment).where(Fragment.cid == cid)
results = await session.exec(statement)
@ -134,7 +134,7 @@ class FragmentSub:
@staticmethod
async def get_all() -> List[Fragment]:
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
statement = select(Fragment)
results = await session.exec(statement)

View File

@ -10,7 +10,7 @@ from models.models.lofter import Lofter
class LofterPost:
@staticmethod
async def get_by_post_and_user_id(user_id: str, post_id: str) -> Optional[Lofter]:
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
if user_id != "0":
check = Lofter.post_id == post_id and Lofter.user_id == user_id
@ -26,7 +26,7 @@ class LofterPost:
@staticmethod
async def add_post(post: Lofter):
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
session.add(post)
await session.commit()

View File

@ -10,7 +10,7 @@ from models.models.splash import Splash
class SplashService:
@staticmethod
async def get_by_splash_id(splash_id: int) -> Optional[Splash]:
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
check = Splash.id == splash_id
statement = select(Splash).where(check)
@ -19,7 +19,7 @@ class SplashService:
@staticmethod
async def get_all_splashes() -> List[Optional[Splash]]:
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
statement = select(Splash)
results = await session.exec(statement)
@ -27,14 +27,14 @@ class SplashService:
@staticmethod
async def add_splash(splash: Splash):
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
session.add(splash)
await session.commit()
@staticmethod
async def update_splash(splash: Splash):
async with sqlite.Session() as session:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
session.add(splash)
await session.commit()

View File

@ -1,9 +1,11 @@
from sqlmodel import SQLModel
from models.models.bilifav import BiliFav
from models.models.lofter import Lofter
from models.models.fragment import Fragment
from models.models.splash import Splash
__all__ = ["Lofter", "Fragment", "Sqlite"]
__all__ = ["BiliFav", "Lofter", "Fragment", "Splash", "Sqlite"]
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker
@ -13,15 +15,11 @@ from sqlmodel.ext.asyncio.session import AsyncSession
class Sqlite:
def __init__(self):
self.engine = create_async_engine("sqlite+aiosqlite:///data/data.db")
self.Session = sessionmaker(bind=self.engine, class_=AsyncSession)
self.session = sessionmaker(bind=self.engine, class_=AsyncSession)
async def create_db_and_tables(self):
async with self.engine.begin() as session:
await session.run_sync(SQLModel.metadata.create_all)
async def get_session(self):
async with self.Session() as session:
yield session
def stop(self):
self.Session.close_all()
self.session.close_all()

View File

@ -12,7 +12,7 @@ from defs.bilibili import (
check_and_refresh_credential,
)
from defs.button import gen_button, Button
from defs.glover import bili_auth_user
from defs.glover import bili_auth_user, bili_auth_chat
from init import bot
from scheduler import scheduler
@ -21,7 +21,7 @@ from scheduler import scheduler
filters.incoming
& filters.text
& filters.regex(r"av(\d{1,12})|BV(1[A-Za-z0-9]{2}4.1.7[A-Za-z0-9]{2})|b23.tv")
& ~(filters.command(["download"]) & filters.user(bili_auth_user))
& ~(filters.command(["download", "bilibili_fav"]) & filters.user(bili_auth_user))
)
async def bili_resolve(_: Client, message: Message):
"""
@ -37,7 +37,9 @@ async def bili_resolve(_: Client, message: Message):
if video_info:
image = await binfo_image_create(video_info)
buttons = [Button(0, "Link", "https://b23.tv/" + video_info["bvid"])]
if message.from_user and message.from_user.id in bili_auth_user:
if (message.from_user and message.from_user.id in bili_auth_user) or (
message.chat and message.chat.id in bili_auth_chat
):
buttons.append(Button(1, "Download", "download_" + video_info["bvid"]))
await message.reply_photo(
image,

View File

@ -5,14 +5,15 @@ from pyrogram.types import Message, CallbackQuery
from defs.bilibili import b23_extract, create_video, create_audio
from defs.bilibili_download import go_download, audio_download
from defs.glover import bili_auth_user
from defs.glover import bili_auth_user, bilifav_channel_username, bili_auth_chat
from init import bot
from models.services.bilifav import BiliFavAction
@bot.on_message(
filters.incoming
& filters.text
& filters.user(bili_auth_user)
& (filters.user(bili_auth_user) | filters.chat(bili_auth_chat))
& filters.command(["download"])
)
async def bili_download_resolve(_: Client, message: Message):
@ -29,6 +30,13 @@ async def bili_download_resolve(_: Client, message: Message):
p_num = p_.search(message.text)
p_num = int(p_num[0][2:]) if p_num else 0
video = create_video(video_number)
if video_db := await BiliFavAction.get_by_bv_id(video.get_bvid()):
await message.reply_video(
video_db.file_id,
caption=f"详细信息https://t.me/{bilifav_channel_username}/{video_db.message_id}",
quote=True,
)
raise ContinuePropagation
m = await message.reply("开始获取视频数据", quote=True)
bot.loop.create_task(go_download(video, p_num, m))
@ -51,11 +59,22 @@ async def bili_download_resolve_cb(_: Client, callback_query: CallbackQuery):
if not callback_query.from_user:
await callback_query.answer("请私聊机器人")
return
if callback_query.from_user.id not in bili_auth_user:
await callback_query.answer("你没有权限使用此功能")
if (
callback_query.message.chat.id not in bili_auth_chat
and callback_query.from_user.id not in bili_auth_user
):
await callback_query.answer("你没有权限")
return
video_number = callback_query.matches[0].group(1)
video = create_video(video_number)
if video_db := await BiliFavAction.get_by_bv_id(video.get_bvid()):
await callback_query.answer("找到缓存")
await callback_query.message.reply_video(
video_db.file_id,
caption=f"详细信息https://t.me/{bilifav_channel_username}/{video_db.message_id}",
quote=True,
)
raise ContinuePropagation
m = await callback_query.message.reply("开始获取视频数据", quote=True)
bot.loop.create_task(go_download(video, 0, m))
await callback_query.answer("开始下载")

103
modules/bilibili_fav.py Normal file
View File

@ -0,0 +1,103 @@
import re
import time
from pyrogram import filters, Client, ContinuePropagation
from pyrogram.types import Message, CallbackQuery
from defs.bilibili import b23_extract, create_video, create_audio
from defs.bilibili_download import go_download, go_upload, audio_download
from defs.bilibili_fav import check_update
from defs.glover import admin, bilifav_channel
from init import bot, logger
from models.models.bilifav import BiliFav
from models.services.bilifav import BiliFavAction
async def process_audio(video_number: str, message: Message):
id_ = int(video_number[2:])
if await BiliFavAction.get_by_id(id_):
await message.reply("该音频已经存在")
raise ContinuePropagation
audio = create_audio(video_number)
info = await audio.get_info()
m = await message.reply("开始获取音频数据", quote=True)
msg = await audio_download(audio, m, push_id=bilifav_channel)
if not msg:
raise ContinuePropagation
audio_db = BiliFav(
id=id_,
bv_id=info.get("bvid", "").lower(),
type=12,
title=info.get("title", ""),
cover=info.get("cover", ""),
message_id=msg.id,
file_id=msg.audio.file_id,
timestamp=int(time.time()),
)
await BiliFavAction.add_bili_fav(audio_db)
async def process_video(video_number: str, p_num: int, message: Message):
video = create_video(video_number)
if await BiliFavAction.get_by_bv_id(video.get_bvid()):
await message.edit("该视频已经存在")
raise ContinuePropagation
info = await video.get_info()
id_ = info.get("aid", 0)
if not id_:
await message.edit("未找到视频 AV 号")
raise ContinuePropagation
m = await message.reply("开始获取视频数据", quote=True)
await go_download(video, p_num, m, task=False)
msg = await go_upload(video, p_num, m, push_id=bilifav_channel)
if not msg:
raise ContinuePropagation
audio_db = BiliFav(
id=id_,
bv_id=info.get("bvid", "").lower(),
type=2,
title=info.get("title", ""),
cover=info.get("pic", ""),
message_id=msg.id,
file_id=msg.video.file_id,
timestamp=int(time.time()),
)
await BiliFavAction.add_bili_fav(audio_db)
@bot.on_message(
filters.incoming
& filters.text
& filters.user(admin)
& filters.command(["bilibili_fav"])
)
async def bilibili_fav_parse(_: Client, message: Message):
if len(message.command) <= 1:
m = await message.reply("正在获取收藏夹数据", quote=True)
await check_update(m)
return
if "b23.tv" in message.text:
message.text = await b23_extract(message.text)
p = re.compile(r"av(\d{1,12})|BV(1[A-Za-z0-9]{2}4.1.7[A-Za-z0-9]{2})|au(\d{1,12})")
video_number = p.search(message.text)
if video_number:
video_number = video_number[0]
else:
await message.reply("未找到视频 BV 号、 AV 或 AU 号")
raise ContinuePropagation
p_ = re.compile(r"p=(\d{1,3})")
p_num = p_.search(message.text)
p_num = int(p_num[0][2:]) if p_num else 0
m = await message.reply("开始获取数据", quote=True)
try:
if video_number.startswith("au"):
await process_audio(video_number, m)
else:
await process_video(video_number, p_num, m)
except ContinuePropagation:
raise ContinuePropagation
except Exception as e:
logger.exception("Processing bilibili favorite single push failed")
await m.edit(f"处理失败: {e}")
await m.edit("处理完成")
raise ContinuePropagation

View File

@ -12,7 +12,7 @@ from pyrogram.types import (
Message,
)
from models.fragment import FragmentSubText, FragmentSub, AuctionStatus
from models.services.fragment import FragmentSubText, FragmentSub, AuctionStatus
from defs.fragment import parse_fragment, NotAvailable, parse_sub
from init import bot
from scheduler import scheduler, add_delete_message_job