nonebot-plugin-bili2mp4 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: nonebot-plugin-bili2mp4
3
+ Version: 0.1.0
4
+ Summary: 在指定群内将B站小程序/分享链接解析转MP4后发送的NoneBot2插件
5
+ Author-email: j1udu <2312410042@stu.suda.edu.cn>
6
+ Project-URL: Homepage, https://github.com/j1udu/nonebot-plugin-bili2mp4
7
+ Project-URL: Repository, https://github.com/j1udu/nonebot-plugin-bili2mp4
8
+ Project-URL: Bug Reports, https://github.com/j1udu/nonebot-plugin-bili2mp4/issues
9
+ Keywords: nonebot,nonebot2,bilibili,video,download,mp4
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Requires-Python: <4.0,>=3.8
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: nonebot2>=2.0.0
20
+ Requires-Dist: nonebot-adapter-onebot>=2.0.0
21
+ Requires-Dist: pydantic>=1.10.0
22
+ Requires-Dist: yt-dlp>=2023.3.4
23
+ Requires-Dist: aiofiles>=0.8.0
24
+ Requires-Dist: loguru>=0.6.0
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=7.0.0; extra == "test"
27
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
28
+ Provides-Extra: dev
29
+ Requires-Dist: black>=22.0.0; extra == "dev"
30
+ Requires-Dist: isort>=5.10.0; extra == "dev"
31
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
32
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
34
+ Requires-Dist: pre-commit>=2.20.0; extra == "dev"
35
+
36
+ <div align="center">
37
+ <a href="https://v2.nonebot.dev/store"><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/nbp_logo.png" width="180" height="180" alt="NoneBotPluginLogo"></a>
38
+ <br>
39
+ <p><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/NoneBotPlugin.svg" width="240" alt="NoneBotPluginText"></p>
40
+ </div>
41
+
42
+ <div align="center">
43
+
44
+ # nonebot-plugin-bili2mp4
45
+
46
+ _✨ NoneBot2 插件,自动将B站视频转换为MP4并分享 ✨_
47
+
48
+
49
+ <a href="./LICENSE">
50
+ <img src="https://img.shields.io/github/license/j1udu/nonebot-plugin-bili2mp4.svg" alt="license">
51
+ </a>
52
+ <a href="https://pypi.python.org/pypi/nonebot-plugin-bili2mp4">
53
+ <img src="https://img.shields.io/pypi/v/nonebot-plugin-bili2mp4.svg" alt="pypi">
54
+ </a>
55
+ <img src="https://img.shields.io/badge/python-3.8+-blue.svg" alt="python">
56
+
57
+ </div>
58
+
59
+ 这是一个用于 NoneBot2 的插件,可以在指定的QQ群中自动检测B站分享链接和小程序卡片,并将这些B站视频自动下载并转换为MP4格式后发送到群中。
60
+ ## 📖 介绍
61
+
62
+ nonebot-plugin-bili2mp4 是一个用于 NoneBot2 的插件,主要功能包括:
63
+
64
+ - 自动检测群聊中的B站视频分享链接和小程序卡片识别并转换为MP4格式发到群里
65
+ - 支持识别B站短链接
66
+ - 支持控制开启的群
67
+ - 支持自定义视频清晰度、大小限制等参数
68
+ - 支持设置B站Cookie以获取更高清晰度或者大会员限定视频
69
+
70
+ ## 💿 安装
71
+
72
+ <details open>
73
+ <summary>使用 nb-cli 安装</summary>
74
+
75
+ 在 nonebot2 项目的根目录下打开命令行,输入以下指令:
76
+ ```bash
77
+ nb plugin install nonebot-plugin-bili2mp4
78
+ ```
79
+ </details>
80
+
81
+ <details open>
82
+ <summary>使用包管理器安装</summary>
83
+
84
+ 在 nonebot2 项目的根目录下,打开命令行,根据你使用的包管理器,输入相应的安装命令
85
+ <details open>
86
+ <summary>pip</summary>
87
+
88
+ ```bash
89
+ pip install nonebot-plugin-bili2mp4
90
+ ```
91
+ </details>
92
+
93
+ 打开 nonebot2 项目根目录下的 pyproject.toml 文件, 在 [tool.nonebot] 部分追加写入
94
+ ```toml
95
+ plugins = ["nonebot_plugin_bili2mp4"]
96
+ ```
97
+ </details>
98
+
99
+ ## 📦 依赖
100
+
101
+ <details open>
102
+ <summary>yt-dlp</summary>
103
+
104
+ 如果创建项目时使用了虚拟环境请在虚拟环境内安装依赖
105
+ ```bash
106
+ pip install yt-dlp
107
+ ```
108
+ </details>
109
+
110
+ <details open>
111
+ <summary>ffmpeg</summary>
112
+
113
+ 插件依赖FFmpeg进行视频格式转换,需要手动安装:
114
+ **Windows:**
115
+ 1. 访问 [FFmpeg官网](https://ffmpeg.org/) 下载Windows版本
116
+ 2. 解压下载的压缩包到任意目录(如 `C:\ffmpeg`)
117
+ 3. 将 `ffmpeg.exe` 所在目录添加到系统环境变量PATH中
118
+ 4. 在命令行中运行 `ffmpeg -version` 验证安装是否成功
119
+
120
+ **Linux :**
121
+ ```bash
122
+ sudo apt update
123
+ sudo apt install ffmpeg
124
+ ```
125
+
126
+ **macOS:**
127
+ ```bash
128
+ brew install ffmpeg
129
+ ```
130
+ </details>
131
+
132
+ ## ⚙️ 配置
133
+
134
+ 在 nonebot2 项目的`.env`文件中添加下表中的必填配置
135
+
136
+ | 配置项 | 必填 | 默认值 | 说明 |
137
+ |:-----:|:----:|:----:|:----:|
138
+ | super_admins | 是 | [] | 管理员QQ号列表 |
139
+
140
+
141
+ ## 🎉 使用
142
+
143
+ ### 指令表
144
+ | 指令 | 说明 |
145
+ |:-----:|:-----:|
146
+ | fhelp | 显示所有管理员可用命令的帮助信息 |
147
+ | 转换 <群号> | 开启指定群的B站视频转换功能 |
148
+ | 停止转换 <群号> | 停止指定群的B站视频转换功能 |
149
+ | 设置B站COOKIE <cookie字符串> | 设置B站Cookie |
150
+ | 清除B站COOKIE | 清除已设置的B站Cookie |
151
+ | 设置清晰度 <数字> | 设置视频清晰度 |
152
+ | 设置最大大小 <数字>MB | 设置视频大小限制 |
153
+ | 查看参数 | 查看当前配置参数 |
154
+ | 查看转换列表 | 查看已开启转换功能的群列表 |
155
+
156
+ **注**:
157
+ 以上指令均需管理员私聊bot
158
+
159
+ Cookie可以不设置,设置大会员账号的cookie可以获取更高清晰度或者大会员限定视频
160
+
161
+ Cookie中至少需要包含SESSDATA、bili_jct、DedeUserID和buvid3/buvid4四个字段
162
+ ## 效果图
163
+ <img src="nonebot_plugin_bili2mp4/images/picture1.png" width="500">
164
+ <img src="nonebot_plugin_bili2mp4/images/picture2.png" width="500">
@@ -0,0 +1,129 @@
1
+ <div align="center">
2
+ <a href="https://v2.nonebot.dev/store"><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/nbp_logo.png" width="180" height="180" alt="NoneBotPluginLogo"></a>
3
+ <br>
4
+ <p><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/NoneBotPlugin.svg" width="240" alt="NoneBotPluginText"></p>
5
+ </div>
6
+
7
+ <div align="center">
8
+
9
+ # nonebot-plugin-bili2mp4
10
+
11
+ _✨ NoneBot2 插件,自动将B站视频转换为MP4并分享 ✨_
12
+
13
+
14
+ <a href="./LICENSE">
15
+ <img src="https://img.shields.io/github/license/j1udu/nonebot-plugin-bili2mp4.svg" alt="license">
16
+ </a>
17
+ <a href="https://pypi.python.org/pypi/nonebot-plugin-bili2mp4">
18
+ <img src="https://img.shields.io/pypi/v/nonebot-plugin-bili2mp4.svg" alt="pypi">
19
+ </a>
20
+ <img src="https://img.shields.io/badge/python-3.8+-blue.svg" alt="python">
21
+
22
+ </div>
23
+
24
+ 这是一个用于 NoneBot2 的插件,可以在指定的QQ群中自动检测B站分享链接和小程序卡片,并将这些B站视频自动下载并转换为MP4格式后发送到群中。
25
+ ## 📖 介绍
26
+
27
+ nonebot-plugin-bili2mp4 是一个用于 NoneBot2 的插件,主要功能包括:
28
+
29
+ - 自动检测群聊中的B站视频分享链接和小程序卡片识别并转换为MP4格式发到群里
30
+ - 支持识别B站短链接
31
+ - 支持控制开启的群
32
+ - 支持自定义视频清晰度、大小限制等参数
33
+ - 支持设置B站Cookie以获取更高清晰度或者大会员限定视频
34
+
35
+ ## 💿 安装
36
+
37
+ <details open>
38
+ <summary>使用 nb-cli 安装</summary>
39
+
40
+ 在 nonebot2 项目的根目录下打开命令行,输入以下指令:
41
+ ```bash
42
+ nb plugin install nonebot-plugin-bili2mp4
43
+ ```
44
+ </details>
45
+
46
+ <details open>
47
+ <summary>使用包管理器安装</summary>
48
+
49
+ 在 nonebot2 项目的根目录下,打开命令行,根据你使用的包管理器,输入相应的安装命令
50
+ <details open>
51
+ <summary>pip</summary>
52
+
53
+ ```bash
54
+ pip install nonebot-plugin-bili2mp4
55
+ ```
56
+ </details>
57
+
58
+ 打开 nonebot2 项目根目录下的 pyproject.toml 文件, 在 [tool.nonebot] 部分追加写入
59
+ ```toml
60
+ plugins = ["nonebot_plugin_bili2mp4"]
61
+ ```
62
+ </details>
63
+
64
+ ## 📦 依赖
65
+
66
+ <details open>
67
+ <summary>yt-dlp</summary>
68
+
69
+ 如果创建项目时使用了虚拟环境请在虚拟环境内安装依赖
70
+ ```bash
71
+ pip install yt-dlp
72
+ ```
73
+ </details>
74
+
75
+ <details open>
76
+ <summary>ffmpeg</summary>
77
+
78
+ 插件依赖FFmpeg进行视频格式转换,需要手动安装:
79
+ **Windows:**
80
+ 1. 访问 [FFmpeg官网](https://ffmpeg.org/) 下载Windows版本
81
+ 2. 解压下载的压缩包到任意目录(如 `C:\ffmpeg`)
82
+ 3. 将 `ffmpeg.exe` 所在目录添加到系统环境变量PATH中
83
+ 4. 在命令行中运行 `ffmpeg -version` 验证安装是否成功
84
+
85
+ **Linux :**
86
+ ```bash
87
+ sudo apt update
88
+ sudo apt install ffmpeg
89
+ ```
90
+
91
+ **macOS:**
92
+ ```bash
93
+ brew install ffmpeg
94
+ ```
95
+ </details>
96
+
97
+ ## ⚙️ 配置
98
+
99
+ 在 nonebot2 项目的`.env`文件中添加下表中的必填配置
100
+
101
+ | 配置项 | 必填 | 默认值 | 说明 |
102
+ |:-----:|:----:|:----:|:----:|
103
+ | super_admins | 是 | [] | 管理员QQ号列表 |
104
+
105
+
106
+ ## 🎉 使用
107
+
108
+ ### 指令表
109
+ | 指令 | 说明 |
110
+ |:-----:|:-----:|
111
+ | fhelp | 显示所有管理员可用命令的帮助信息 |
112
+ | 转换 <群号> | 开启指定群的B站视频转换功能 |
113
+ | 停止转换 <群号> | 停止指定群的B站视频转换功能 |
114
+ | 设置B站COOKIE <cookie字符串> | 设置B站Cookie |
115
+ | 清除B站COOKIE | 清除已设置的B站Cookie |
116
+ | 设置清晰度 <数字> | 设置视频清晰度 |
117
+ | 设置最大大小 <数字>MB | 设置视频大小限制 |
118
+ | 查看参数 | 查看当前配置参数 |
119
+ | 查看转换列表 | 查看已开启转换功能的群列表 |
120
+
121
+ **注**:
122
+ 以上指令均需管理员私聊bot
123
+
124
+ Cookie可以不设置,设置大会员账号的cookie可以获取更高清晰度或者大会员限定视频
125
+
126
+ Cookie中至少需要包含SESSDATA、bili_jct、DedeUserID和buvid3/buvid4四个字段
127
+ ## 效果图
128
+ <img src="nonebot_plugin_bili2mp4/images/picture1.png" width="500">
129
+ <img src="nonebot_plugin_bili2mp4/images/picture2.png" width="500">
@@ -0,0 +1,32 @@
1
+ from nonebot.plugin import PluginMetadata
2
+
3
+ from .config import Config
4
+
5
+ __plugin_meta__ = PluginMetadata(
6
+ name="nonebot_plugin_bili2mp4",
7
+ description="在指定群内自动将B站小程序/分享链接解析并下载为MP4后发送。支持私聊管理开启/停止、设置B站Cookie、清晰度与大小限制。",
8
+ usage=(
9
+ "超级管理员私聊命令:\n"
10
+ "1) 转换<群号>\n"
11
+ "2) 停止转换<群号>\n"
12
+ "3) 设置B站COOKIE <cookie字符串>\n"
13
+ "4) 清除B站COOKIE\n"
14
+ "5) 设置清晰度<数字>(如 720/1080,0 代表不限制)\n"
15
+ "6) 设置最大大小<数字>MB(0 代表不限制)\n"
16
+ "7) 查看参数 / 查看转换列表\n"
17
+ "说明:启用的群里检测到B站分享(含小程序卡片)将尝试下载并发送MP4;需要时可设置Cookie。"
18
+ ),
19
+ type="application",
20
+ config=Config,
21
+ homepage="https://github.com/shengwang52005/nonebot_plugin_bili2mp4",
22
+ supported_adapters={"~onebot.v11"},
23
+ extra={},
24
+ )
25
+
26
+ # 延迟导入__main__模块,避免测试时出错
27
+ try:
28
+ from . import __main__
29
+
30
+ except (ImportError, ValueError):
31
+ # 在测试环境中可能无法导入,这是正常的
32
+ pass
@@ -0,0 +1,740 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ import re
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ import time
11
+ import urllib.request
12
+ from typing import List, Optional, Set, Tuple
13
+ from urllib.parse import parse_qs, unquote, urlparse
14
+
15
+ from loguru import logger
16
+ from nonebot import on_message
17
+ from nonebot.adapters.onebot.v11 import (
18
+ Bot,
19
+ Event,
20
+ GroupMessageEvent,
21
+ Message,
22
+ MessageSegment,
23
+ PrivateMessageEvent,
24
+ )
25
+ from nonebot.plugin import get_plugin_config
26
+
27
+ from .config import Config
28
+
29
+ # 可调的 ffmpeg 检测超时(秒),如遇到 Windows 首次运行较慢,可在启动前设置:
30
+ # PowerShell: $env:BILI2MP4_FFMPEG_TIMEOUT="30"
31
+ FFMPEG_CHECK_TIMEOUT = int(os.getenv("BILI2MP4_FFMPEG_TIMEOUT", "30"))
32
+
33
+ # 配置加载
34
+ plugin_config = get_plugin_config(Config)
35
+ super_admins: List[int] = plugin_config.super_admins or []
36
+ logger.info(f"nonebot_plugin_bili2mp4 初始化:超管={super_admins}")
37
+
38
+ # FFmpeg 设置
39
+ FFMPEG_DIR: Optional[str] = None
40
+
41
+
42
+ def _setup_ffmpeg() -> None:
43
+ """设置 FFmpeg 路径"""
44
+ global FFMPEG_DIR
45
+
46
+ # 硬编码路径
47
+ hardcoded_path = r"C:\Users\Administrator\Desktop\nonebot\yousa\.venv\ffmpeg\bin"
48
+
49
+ if os.path.isdir(hardcoded_path):
50
+ # 尝试使用硬编码路径
51
+ ffmpeg_path = os.path.join(
52
+ hardcoded_path, "ffmpeg.exe" if os.name == "nt" else "ffmpeg"
53
+ )
54
+ if os.path.isfile(ffmpeg_path):
55
+ FFMPEG_DIR = hardcoded_path
56
+ os.environ["PATH"] = (
57
+ hardcoded_path + os.pathsep + os.environ.get("PATH", "")
58
+ )
59
+ logger.info(f"[ffmpeg] 使用硬编码目录: {hardcoded_path}")
60
+ return
61
+
62
+ # 回退到系统路径
63
+ ffmpeg_path = shutil.which("ffmpeg")
64
+ if ffmpeg_path:
65
+ FFMPEG_DIR = os.path.dirname(ffmpeg_path)
66
+ logger.info(f"[ffmpeg] 使用系统路径: {ffmpeg_path}")
67
+ return
68
+
69
+ logger.warning("[ffmpeg] 未找到 ffmpeg,请确保已安装并配置正确")
70
+
71
+
72
+ _setup_ffmpeg()
73
+
74
+ # 数据目录与持久化
75
+ PLUGIN_NAME = "nonebot_plugin_bili2mp4"
76
+
77
+
78
+ def _get_data_dir() -> str:
79
+ try:
80
+ from nonebot_plugin_localstore import get_plugin_data_dir # type: ignore
81
+
82
+ path = str(get_plugin_data_dir())
83
+ os.makedirs(path, exist_ok=True)
84
+ logger.debug(f"使用 nonebot-plugin-localstore 数据目录: {path}")
85
+ return path
86
+ except Exception:
87
+ base = os.path.join(os.getcwd(), "data", PLUGIN_NAME)
88
+ os.makedirs(base, exist_ok=True)
89
+ logger.debug(f"使用回退数据目录: {base}")
90
+ return base
91
+
92
+
93
+ DATA_DIR = _get_data_dir()
94
+ STATE_PATH = os.path.join(DATA_DIR, "state.json")
95
+ DOWNLOAD_DIR = os.path.join(DATA_DIR, "downloads")
96
+ COOKIE_FILE_PATH = os.path.join(DATA_DIR, "bili_cookies.txt")
97
+ os.makedirs(DOWNLOAD_DIR, exist_ok=True)
98
+
99
+ # 全局状态
100
+ enabled_groups: Set[int] = set()
101
+ bilibili_cookie: str = ""
102
+ max_height: int = 0 # 0 表示不限制(示例:720/1080/2160)
103
+ max_filesize_mb: int = 0 # 0 表示不限制
104
+ # 简单去重:正在处理中的 key = f"{group_id}|{url}"
105
+ _processing: Set[str] = set()
106
+
107
+
108
+ def _save_state() -> None:
109
+ try:
110
+ data = {
111
+ "enabled_groups": sorted(list(enabled_groups)),
112
+ "bilibili_cookie": bilibili_cookie or "",
113
+ "max_height": int(max_height),
114
+ "max_filesize_mb": int(max_filesize_mb),
115
+ }
116
+ with open(STATE_PATH, "w", encoding="utf-8") as f:
117
+ json.dump(data, f, ensure_ascii=False, indent=2)
118
+ logger.debug(f"状态已保存: {STATE_PATH}")
119
+ except Exception as e:
120
+ logger.error(f"保存状态失败: {e}")
121
+
122
+
123
+ def _load_state() -> None:
124
+ global enabled_groups, bilibili_cookie, max_height, max_filesize_mb
125
+ try:
126
+ if os.path.exists(STATE_PATH):
127
+ with open(STATE_PATH, "r", encoding="utf-8") as f:
128
+ data = json.load(f)
129
+ enabled_groups = {int(g) for g in data.get("enabled_groups", [])}
130
+ bilibili_cookie = str(data.get("bilibili_cookie", "") or "")
131
+ max_height = int(data.get("max_height", 0) or 0)
132
+ max_filesize_mb = int(data.get("max_filesize_mb", 0) or 0)
133
+ logger.info(
134
+ f"已加载状态: 启用群数={len(enabled_groups)},Cookie={bool(bilibili_cookie)}"
135
+ )
136
+ return
137
+ except Exception as e:
138
+ logger.warning(f"读取状态失败,使用默认空状态: {e}")
139
+
140
+ # 默认值
141
+ enabled_groups = set()
142
+ bilibili_cookie = ""
143
+ max_height = 0
144
+ max_filesize_mb = 0
145
+ _save_state()
146
+
147
+
148
+ _load_state()
149
+
150
+ # 更宽松的域名匹配(含 m.bilibili.com、t.bilibili.com 等)
151
+ BILI_URL_RE = re.compile(
152
+ r"(https?://(?:[\w-]+\.)?(?:bilibili\.com|b23\.tv)/[^\s\"'<>]+)",
153
+ flags=re.IGNORECASE,
154
+ )
155
+
156
+
157
+ def _find_urls_in_text(text: str) -> List[str]:
158
+ urls = []
159
+ for m in BILI_URL_RE.findall(text or ""):
160
+ if m not in urls:
161
+ urls.append(m)
162
+ # 兼容 schema 中的 url 参数
163
+ try:
164
+ parsed = urlparse(text)
165
+ if parsed and parsed.query:
166
+ qs = parse_qs(parsed.query)
167
+ for key in ("url", "qqdocurl", "jumpUrl", "webpageUrl"):
168
+ for v in qs.get(key, []):
169
+ v = unquote(v)
170
+ for u in BILI_URL_RE.findall(v):
171
+ if u not in urls:
172
+ urls.append(u)
173
+ except Exception:
174
+ pass
175
+ return urls
176
+
177
+
178
+ def _walk_strings(obj) -> List[str]:
179
+ out: List[str] = []
180
+ try:
181
+ if isinstance(obj, dict):
182
+ for v in obj.values():
183
+ out.extend(_walk_strings(v))
184
+ elif isinstance(obj, list):
185
+ for it in obj:
186
+ out.extend(_walk_strings(it))
187
+ elif isinstance(obj, str):
188
+ out.append(obj)
189
+ except Exception:
190
+ pass
191
+ return out
192
+
193
+
194
+ def _extract_bili_urls_from_event(event: GroupMessageEvent) -> List[str]:
195
+ urls: List[str] = []
196
+ try:
197
+ for seg in event.message:
198
+ # 1) 纯文本
199
+ if seg.type == "text":
200
+ txt = seg.data.get("text", "")
201
+ for u in _find_urls_in_text(txt):
202
+ if u not in urls:
203
+ urls.append(u)
204
+ # 2) JSON 卡片
205
+ elif seg.type == "json":
206
+ raw = seg.data.get("data") or seg.data.get("content") or ""
207
+ for u in _find_urls_in_text(raw):
208
+ if u not in urls:
209
+ urls.append(u)
210
+ try:
211
+ obj = json.loads(raw)
212
+ for s in _walk_strings(obj):
213
+ for u in _find_urls_in_text(s):
214
+ if u not in urls:
215
+ urls.append(u)
216
+ except Exception:
217
+ pass
218
+ # 3) XML 卡片
219
+ elif seg.type == "xml":
220
+ raw = seg.data.get("data") or seg.data.get("content") or ""
221
+ for u in _find_urls_in_text(raw):
222
+ if u not in urls:
223
+ urls.append(u)
224
+ # 4) 分享卡片
225
+ elif seg.type == "share":
226
+ u = seg.data.get("url") or ""
227
+ for u2 in _find_urls_in_text(u):
228
+ if u2 not in urls:
229
+ urls.append(u2)
230
+ else:
231
+ s = str(seg)
232
+ for u in _find_urls_in_text(s):
233
+ if u not in urls:
234
+ urls.append(u)
235
+ except Exception as e:
236
+ logger.debug(f"提取链接异常: {e}")
237
+ return urls
238
+
239
+
240
+ # 群消息监听(只在启用群里生效)
241
+ group_listener = on_message(priority=100, block=False)
242
+
243
+
244
+ @group_listener.handle()
245
+ async def handle_group(bot: Bot, event: Event):
246
+ if not isinstance(event, GroupMessageEvent):
247
+ return
248
+
249
+ group_id = int(event.group_id)
250
+ if group_id not in enabled_groups:
251
+ return # 未开启转换的群聊里保持静默
252
+
253
+ urls = _extract_bili_urls_from_event(event)
254
+ if not urls:
255
+ logger.debug(f"[bili2mp4] 群{group_id} 未在该消息中发现B站链接")
256
+ return
257
+
258
+ url = urls[0]
259
+ key = f"{group_id}|{url}"
260
+ if key in _processing:
261
+ logger.debug(f"[bili2mp4] 已在处理中,忽略重复: {key}")
262
+ return
263
+ _processing.add(key)
264
+ logger.info(f"[bili2mp4] 检测到B站链接")
265
+
266
+ async def work():
267
+ try:
268
+ await _download_and_send(bot, group_id, url)
269
+ except Exception as e:
270
+ # 失败时静默(仅日志)
271
+ logger.error(f"[bili2mp4] 处理失败:{e}")
272
+ finally:
273
+ _processing.discard(key)
274
+
275
+ asyncio.create_task(work())
276
+
277
+
278
+ # 私聊控制(超级管理员)
279
+ ctrl_listener = on_message(priority=50, block=False)
280
+
281
+ CMD_ENABLE_RE = re.compile(r"^转换\s*(\d+)$", flags=re.IGNORECASE)
282
+ CMD_DISABLE_RE = re.compile(r"^停止转换\s*(\d+)$", flags=re.IGNORECASE)
283
+ CMD_SET_COOKIE_RE = re.compile(r"^设置B站COOKIE\s+(.+)$", flags=re.S)
284
+ CMD_CLEAR_COOKIE = {"清除B站COOKIE", "删除B站COOKIE"}
285
+
286
+
287
+ async def _handle_group_command(
288
+ bot: Bot, event: PrivateMessageEvent, text: str
289
+ ) -> bool:
290
+ """处理群相关命令"""
291
+ global enabled_groups
292
+
293
+ # 开启群
294
+ m = CMD_ENABLE_RE.fullmatch(text)
295
+ if m:
296
+ gid = int(m.group(1))
297
+ if gid in enabled_groups:
298
+ await bot.send(event, Message(f"ℹ️ 群 {gid} 已开启转换"))
299
+ else:
300
+ enabled_groups.add(gid)
301
+ _save_state()
302
+ await bot.send(event, Message(f"✅ 已开启群 {gid} 的B站视频转换"))
303
+ return True
304
+
305
+ # 关闭群
306
+ m = CMD_DISABLE_RE.fullmatch(text)
307
+ if m:
308
+ gid = int(m.group(1))
309
+ if gid in enabled_groups:
310
+ enabled_groups.discard(gid)
311
+ _save_state()
312
+ await bot.send(event, Message(f"🛑 已停止群 {gid} 的B站视频转换"))
313
+ else:
314
+ await bot.send(event, Message(f"ℹ️ 群 {gid} 未开启转换"))
315
+ return True
316
+
317
+ # 查看列表
318
+ if text in CMD_LIST:
319
+ if enabled_groups:
320
+ sorted_g = sorted(list(enabled_groups))
321
+ await bot.send(
322
+ event, Message("当前已开启转换的群:" + ", ".join(map(str, sorted_g)))
323
+ )
324
+ else:
325
+ await bot.send(event, Message("暂无开启转换的群"))
326
+ return True
327
+
328
+ return False
329
+
330
+
331
+ async def _handle_config_command(
332
+ bot: Bot, event: PrivateMessageEvent, text: str
333
+ ) -> bool:
334
+ """处理配置相关命令"""
335
+ global bilibili_cookie, max_height, max_filesize_mb
336
+
337
+ # 设置Cookie
338
+ m = CMD_SET_COOKIE_RE.fullmatch(text)
339
+ if m:
340
+ bilibili_cookie = m.group(1).strip()
341
+ _save_state()
342
+ await bot.send(event, Message("✅ 已设置B站 Cookie"))
343
+ return True
344
+
345
+ # 清除Cookie
346
+ if text in CMD_CLEAR_COOKIE:
347
+ bilibili_cookie = ""
348
+ _save_state()
349
+ await bot.send(event, Message("🧹 已清除B站 Cookie"))
350
+ return True
351
+
352
+ # 设置清晰度(高度)
353
+ m = CMD_SET_HEIGHT_RE.fullmatch(text)
354
+ if m:
355
+ h = int(m.group(1))
356
+ if h < 0:
357
+ h = 0
358
+ max_height = h
359
+ _save_state()
360
+ await bot.send(
361
+ event, Message(f"⏱ 清晰度已设置为 {'不限制' if h == 0 else f'<= {h}p'}")
362
+ )
363
+ return True
364
+
365
+ # 设置最大大小(MB)
366
+ m = CMD_SET_MAXSIZE_RE.fullmatch(text)
367
+ if m:
368
+ lim = int(m.group(1))
369
+ if lim < 0:
370
+ lim = 0
371
+ max_filesize_mb = lim
372
+ _save_state()
373
+ await bot.send(
374
+ event,
375
+ Message(f"📦 文件大小限制为 {'不限制' if lim == 0 else f'<= {lim}MB'}"),
376
+ )
377
+ return True
378
+
379
+ # 查看参数
380
+ if text in CMD_SHOW_PARAMS:
381
+ await bot.send(
382
+ event,
383
+ Message(
384
+ f"参数:清晰度<= {max_height or '不限'};大小<= {str(max_filesize_mb) + 'MB' if max_filesize_mb else '不限'};"
385
+ f"Cookie={'已设置' if bool(bilibili_cookie) else '未设置'};启用群数={len(enabled_groups)}"
386
+ ),
387
+ )
388
+ return True
389
+
390
+ return False
391
+
392
+
393
+ @ctrl_listener.handle()
394
+ async def handle_private(bot: Bot, event: Event):
395
+ if not isinstance(event, PrivateMessageEvent):
396
+ return
397
+
398
+ try:
399
+ uid = int(event.user_id)
400
+ except Exception:
401
+ return
402
+ if uid not in super_admins:
403
+ return
404
+
405
+ text = (event.get_message() or Message()).extract_plain_text().strip()
406
+ if not text:
407
+ return
408
+
409
+ # 帮助
410
+ if text == "fhelp":
411
+ await bot.send(event, Message(_get_help_message()))
412
+ return
413
+
414
+ # 处理群相关命令
415
+ if await _handle_group_command(bot, event, text):
416
+ return
417
+
418
+ # 处理配置相关命令
419
+ if await _handle_config_command(bot, event, text):
420
+ return
421
+
422
+ # 未匹配其他命令
423
+ return
424
+
425
+
426
+ def _build_browser_like_headers() -> dict:
427
+ # 避免 412:使用常见浏览器头,并固定 Referer
428
+ return {
429
+ "User-Agent": (
430
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
431
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
432
+ "Chrome/120.0.0.0 Safari/537.36"
433
+ ),
434
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
435
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
436
+ "Referer": "https://www.bilibili.com/",
437
+ "Origin": "https://www.bilibili.com",
438
+ "Connection": "keep-alive",
439
+ "Upgrade-Insecure-Requests": "1",
440
+ "Sec-Fetch-Site": "same-origin",
441
+ "Sec-Fetch-Mode": "navigate",
442
+ "Sec-Fetch-Dest": "document",
443
+ }
444
+
445
+
446
+ def _expand_short_url(u: str, timeout: float = 8.0) -> str:
447
+ try:
448
+ host = urlparse(u).hostname or ""
449
+ if host.lower() not in {"b23.tv", "www.b23.tv"}:
450
+ return u
451
+ # 优先 HEAD,失败再 GET
452
+ hdrs = {
453
+ "User-Agent": _build_browser_like_headers()["User-Agent"],
454
+ "Referer": "https://www.bilibili.com/",
455
+ }
456
+ try:
457
+ req = urllib.request.Request(u, headers=hdrs, method="HEAD")
458
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
459
+ final = resp.geturl()
460
+ return final or u
461
+ except Exception:
462
+ req = urllib.request.Request(u, headers=hdrs, method="GET")
463
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
464
+ final = resp.geturl()
465
+ return final or u
466
+ except Exception as e:
467
+ logger.debug(f"短链展开失败,使用原链接({u}):{e}")
468
+ return u
469
+
470
+
471
+ def _ensure_cookiefile(cookie_string: str) -> Optional[str]:
472
+ """
473
+ 将 Cookie 字符串转为 Netscape 格式,供 yt-dlp 使用。
474
+ """
475
+ cookie_string = (cookie_string or "").strip().strip(";")
476
+ if not cookie_string:
477
+ # 清除旧文件
478
+ if os.path.exists(COOKIE_FILE_PATH):
479
+ try:
480
+ if os.path.exists(COOKIE_FILE_PATH):
481
+ os.remove(COOKIE_FILE_PATH)
482
+ except Exception:
483
+ pass
484
+ return None
485
+
486
+ # 解析 Cookie 键值对
487
+ pairs = []
488
+ for part in cookie_string.split(";"):
489
+ part = part.strip()
490
+ if "=" not in part:
491
+ continue
492
+ k, v = part.split("=", 1)
493
+ if k and v:
494
+ pairs.append((k.strip(), v.strip()))
495
+
496
+ if not pairs:
497
+ return None
498
+
499
+ # 生成 Netscape 格式 Cookie 文件
500
+ expiry = int(time.time()) + 180 * 24 * 3600 # 180 天
501
+ lines = [
502
+ "# Netscape HTTP Cookie File",
503
+ "# Generated by nonebot_plugin_bili2mp4",
504
+ "",
505
+ ]
506
+
507
+ for k, v in pairs:
508
+ # domain include_subdomains path secure expiry name value
509
+ lines.append(f".bilibili.com\tTRUE\t/\tFALSE\t{expiry}\t{k}\t{v}")
510
+
511
+ try:
512
+ with open(COOKIE_FILE_PATH, "w", encoding="utf-8") as f:
513
+ f.write("\n".join(lines) + "\n")
514
+ logger.info(f"Cookie 已设置")
515
+ return COOKIE_FILE_PATH
516
+ except Exception:
517
+ return None
518
+
519
+
520
+ async def _download_and_send(bot: Bot, group_id: int, url: str) -> None:
521
+ # 执行下载(阻塞IO放后台线程)
522
+ try:
523
+ path, title = await asyncio.to_thread(
524
+ _download_with_ytdlp,
525
+ url,
526
+ bilibili_cookie,
527
+ DOWNLOAD_DIR,
528
+ max_height,
529
+ max_filesize_mb,
530
+ )
531
+ except (ImportError, RuntimeError):
532
+ return
533
+ except Exception as e:
534
+ logger.error(f"下载异常:{e}")
535
+ return
536
+
537
+ # 检查文件大小和分辨率
538
+ if not _check_video_file(path):
539
+ return
540
+
541
+ # 发送视频
542
+ await _send_video_with_timeout(bot, group_id, path, title)
543
+
544
+
545
+ def _check_video_file(path: str) -> bool:
546
+ """检查视频文件大小和分辨率"""
547
+ try:
548
+ # 检查文件大小
549
+ if max_filesize_mb and os.path.exists(path):
550
+ size_mb = os.path.getsize(path) / (1024 * 1024)
551
+ if size_mb > max_filesize_mb:
552
+ if os.path.exists(path):
553
+ os.remove(path)
554
+ return False
555
+
556
+ # 检查视频分辨率
557
+ if os.path.exists(path):
558
+ result = subprocess.run(
559
+ [
560
+ "ffprobe",
561
+ "-v",
562
+ "error",
563
+ "-select_streams",
564
+ "v:0",
565
+ "-show_entries",
566
+ "stream=width,height",
567
+ "-of",
568
+ "csv=p=0",
569
+ path,
570
+ ],
571
+ capture_output=True,
572
+ text=True,
573
+ )
574
+ if result.returncode == 0:
575
+ width, height = result.stdout.strip().split(",")
576
+ return True
577
+ except Exception:
578
+ return False
579
+
580
+
581
+ async def _send_video_with_timeout(
582
+ bot: Bot, group_id: int, path: str, title: str
583
+ ) -> None:
584
+ """发送视频,带超时处理"""
585
+ try:
586
+ await bot.send_group_msg(
587
+ group_id=group_id,
588
+ message=MessageSegment.video(file=path)
589
+ + Message(f"\n{title or 'B站视频'}"),
590
+ )
591
+ logger.info("视频已发送到群")
592
+ except Exception as e:
593
+ error_msg = str(e)
594
+ # 完全忽略超时相关的错误
595
+ if not ("timeout" in error_msg.lower() and "websocket" in error_msg.lower()):
596
+ logger.error(f"发送视频失败:{e}")
597
+ finally:
598
+ # 清理文件以节省空间
599
+ try:
600
+ if os.path.exists(path):
601
+ os.remove(path)
602
+ except Exception:
603
+ pass
604
+
605
+
606
+ def _build_format_candidates(height_limit: int, size_limit_mb: int) -> List[str]:
607
+ """构建格式候选列表"""
608
+ h = height_limit if height_limit and height_limit > 0 else None
609
+
610
+ if not h:
611
+ return ["bv*+ba/best"]
612
+
613
+ # 根据清晰度限制构建格式候选
614
+ format_map = {
615
+ 1080: [
616
+ f"bv*[height>=1080]+ba/best",
617
+ f"bv*[height>=720]+ba/best",
618
+ "bv*+ba/best",
619
+ ],
620
+ 720: [f"bv*[height>=720]+ba/best", f"bv*[height>=480]+ba/best", "bv*+ba/best"],
621
+ 480: [f"bv*[height>=480]+ba/best", "bv*+ba/best"],
622
+ }
623
+
624
+ # 根据高度选择最适合的格式列表
625
+ for threshold, formats in sorted(format_map.items(), reverse=True):
626
+ if h >= threshold:
627
+ return formats
628
+
629
+ # 默认格式
630
+ return ["bv*+ba/best"]
631
+
632
+
633
+ def _download_with_ytdlp(
634
+ url: str, cookie: str, out_dir: str, height_limit: int, size_limit_mb: int
635
+ ) -> Tuple[str, str]:
636
+ try:
637
+ from yt_dlp import YoutubeDL # type: ignore
638
+ from yt_dlp.utils import DownloadError # type: ignore
639
+ except Exception:
640
+ raise ImportError("yt_dlp not installed")
641
+
642
+ # 展开 b23 短链,确保首个请求命中 bilibili.com 域(Cookie 生效)
643
+ final_url = _expand_short_url(url)
644
+
645
+ # 构建 Cookie 文件(若配置了 Cookie)
646
+ cookiefile = _ensure_cookiefile(cookie)
647
+
648
+ # 逐个尝试不同的格式表达式(从最严格到最宽松)
649
+ candidates = _build_format_candidates(height_limit, size_limit_mb)
650
+ last_err: Optional[Exception] = None
651
+
652
+ for i, fmt in enumerate(candidates):
653
+ headers = _build_browser_like_headers()
654
+ ydl_opts = {
655
+ "format": fmt,
656
+ "outtmpl": os.path.join(out_dir, "%(title).80s [%(id)s].%(ext)s"),
657
+ "noplaylist": True,
658
+ "merge_output_format": "mp4",
659
+ "quiet": False, # 改为False以获取更多调试信息
660
+ "no_warnings": False,
661
+ "http_headers": headers,
662
+ # 更换客户端有助于过检;失败可回退为 web
663
+ "extractor_args": {
664
+ "bili": {
665
+ "player_client": ["android", "web"], # 添加web客户端提高兼容性
666
+ "lang": ["zh-CN"],
667
+ }
668
+ },
669
+ }
670
+
671
+ # 告诉 yt-dlp ffmpeg 在哪里(如果可用)
672
+ if FFMPEG_DIR:
673
+ ydl_opts["ffmpeg_location"] = FFMPEG_DIR
674
+
675
+ # 设置 Cookie
676
+ if cookiefile:
677
+ ydl_opts["cookiefile"] = cookiefile
678
+ logger.info(f"使用 cookiefile: {cookiefile}")
679
+ elif cookie:
680
+ headers["Cookie"] = cookie
681
+ logger.info("使用 Cookie header")
682
+
683
+ try:
684
+ with YoutubeDL(ydl_opts) as ydl:
685
+ info = ydl.extract_info(final_url, download=True)
686
+ title = info.get("title") or "B站视频"
687
+
688
+ # 获取下载信息
689
+ height = info.get("height", 0)
690
+ logger.info(f"下载完成: {title} ({height}p)")
691
+
692
+ # 定位文件
693
+ final_path = _locate_final_file(ydl, info)
694
+ if not final_path or not os.path.exists(final_path):
695
+ raise RuntimeError("未找到已下载的视频文件,可能未安装 ffmpeg")
696
+ return final_path, title
697
+ except DownloadError as e:
698
+ last_err = e
699
+ continue
700
+ except Exception as e:
701
+ last_err = e
702
+ continue
703
+
704
+ if last_err:
705
+ raise RuntimeError(str(last_err))
706
+ raise RuntimeError("无法下载该视频")
707
+
708
+
709
+ def _locate_final_file(ydl, info) -> Optional[str]:
710
+ # 优先从下载项中取
711
+ for key in ("requested_downloads", "requested_formats"):
712
+ arr = info.get(key)
713
+ if isinstance(arr, list):
714
+ for it in arr:
715
+ fp = it.get("filepath")
716
+ if fp and os.path.exists(fp):
717
+ return fp
718
+ # 兼容字段
719
+ for key in ("filepath", "_filename"):
720
+ fp = info.get(key)
721
+ if fp and os.path.exists(fp):
722
+ return fp
723
+ # 预测合并后 mp4
724
+ base = ydl.prepare_filename(info)
725
+ root, _ = os.path.splitext(base)
726
+ candidate = root + ".mp4"
727
+ if os.path.exists(candidate):
728
+ return candidate
729
+ # 兜底:按视频ID在目录中搜
730
+ vid = info.get("id") or ""
731
+ if vid:
732
+ dirpath = os.path.dirname(base) or os.getcwd()
733
+ try:
734
+ files = [os.path.join(dirpath, f) for f in os.listdir(dirpath) if vid in f]
735
+ if files:
736
+ files.sort(key=lambda p: os.path.getmtime(p), reverse=True)
737
+ return files[0]
738
+ except Exception:
739
+ pass
740
+ return None
@@ -0,0 +1,6 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class Config(BaseModel):
5
+ # 超级管理员(可私聊控制本插件)
6
+ super_admins: list[int] = Field(default=[])
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: nonebot-plugin-bili2mp4
3
+ Version: 0.1.0
4
+ Summary: 在指定群内将B站小程序/分享链接解析转MP4后发送的NoneBot2插件
5
+ Author-email: j1udu <2312410042@stu.suda.edu.cn>
6
+ Project-URL: Homepage, https://github.com/j1udu/nonebot-plugin-bili2mp4
7
+ Project-URL: Repository, https://github.com/j1udu/nonebot-plugin-bili2mp4
8
+ Project-URL: Bug Reports, https://github.com/j1udu/nonebot-plugin-bili2mp4/issues
9
+ Keywords: nonebot,nonebot2,bilibili,video,download,mp4
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Requires-Python: <4.0,>=3.8
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: nonebot2>=2.0.0
20
+ Requires-Dist: nonebot-adapter-onebot>=2.0.0
21
+ Requires-Dist: pydantic>=1.10.0
22
+ Requires-Dist: yt-dlp>=2023.3.4
23
+ Requires-Dist: aiofiles>=0.8.0
24
+ Requires-Dist: loguru>=0.6.0
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=7.0.0; extra == "test"
27
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
28
+ Provides-Extra: dev
29
+ Requires-Dist: black>=22.0.0; extra == "dev"
30
+ Requires-Dist: isort>=5.10.0; extra == "dev"
31
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
32
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
34
+ Requires-Dist: pre-commit>=2.20.0; extra == "dev"
35
+
36
+ <div align="center">
37
+ <a href="https://v2.nonebot.dev/store"><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/nbp_logo.png" width="180" height="180" alt="NoneBotPluginLogo"></a>
38
+ <br>
39
+ <p><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/NoneBotPlugin.svg" width="240" alt="NoneBotPluginText"></p>
40
+ </div>
41
+
42
+ <div align="center">
43
+
44
+ # nonebot-plugin-bili2mp4
45
+
46
+ _✨ NoneBot2 插件,自动将B站视频转换为MP4并分享 ✨_
47
+
48
+
49
+ <a href="./LICENSE">
50
+ <img src="https://img.shields.io/github/license/j1udu/nonebot-plugin-bili2mp4.svg" alt="license">
51
+ </a>
52
+ <a href="https://pypi.python.org/pypi/nonebot-plugin-bili2mp4">
53
+ <img src="https://img.shields.io/pypi/v/nonebot-plugin-bili2mp4.svg" alt="pypi">
54
+ </a>
55
+ <img src="https://img.shields.io/badge/python-3.8+-blue.svg" alt="python">
56
+
57
+ </div>
58
+
59
+ 这是一个用于 NoneBot2 的插件,可以在指定的QQ群中自动检测B站分享链接和小程序卡片,并将这些B站视频自动下载并转换为MP4格式后发送到群中。
60
+ ## 📖 介绍
61
+
62
+ nonebot-plugin-bili2mp4 是一个用于 NoneBot2 的插件,主要功能包括:
63
+
64
+ - 自动检测群聊中的B站视频分享链接和小程序卡片识别并转换为MP4格式发到群里
65
+ - 支持识别B站短链接
66
+ - 支持控制开启的群
67
+ - 支持自定义视频清晰度、大小限制等参数
68
+ - 支持设置B站Cookie以获取更高清晰度或者大会员限定视频
69
+
70
+ ## 💿 安装
71
+
72
+ <details open>
73
+ <summary>使用 nb-cli 安装</summary>
74
+
75
+ 在 nonebot2 项目的根目录下打开命令行,输入以下指令:
76
+ ```bash
77
+ nb plugin install nonebot-plugin-bili2mp4
78
+ ```
79
+ </details>
80
+
81
+ <details open>
82
+ <summary>使用包管理器安装</summary>
83
+
84
+ 在 nonebot2 项目的根目录下,打开命令行,根据你使用的包管理器,输入相应的安装命令
85
+ <details open>
86
+ <summary>pip</summary>
87
+
88
+ ```bash
89
+ pip install nonebot-plugin-bili2mp4
90
+ ```
91
+ </details>
92
+
93
+ 打开 nonebot2 项目根目录下的 pyproject.toml 文件, 在 [tool.nonebot] 部分追加写入
94
+ ```toml
95
+ plugins = ["nonebot_plugin_bili2mp4"]
96
+ ```
97
+ </details>
98
+
99
+ ## 📦 依赖
100
+
101
+ <details open>
102
+ <summary>yt-dlp</summary>
103
+
104
+ 如果创建项目时使用了虚拟环境请在虚拟环境内安装依赖
105
+ ```bash
106
+ pip install yt-dlp
107
+ ```
108
+ </details>
109
+
110
+ <details open>
111
+ <summary>ffmpeg</summary>
112
+
113
+ 插件依赖FFmpeg进行视频格式转换,需要手动安装:
114
+ **Windows:**
115
+ 1. 访问 [FFmpeg官网](https://ffmpeg.org/) 下载Windows版本
116
+ 2. 解压下载的压缩包到任意目录(如 `C:\ffmpeg`)
117
+ 3. 将 `ffmpeg.exe` 所在目录添加到系统环境变量PATH中
118
+ 4. 在命令行中运行 `ffmpeg -version` 验证安装是否成功
119
+
120
+ **Linux :**
121
+ ```bash
122
+ sudo apt update
123
+ sudo apt install ffmpeg
124
+ ```
125
+
126
+ **macOS:**
127
+ ```bash
128
+ brew install ffmpeg
129
+ ```
130
+ </details>
131
+
132
+ ## ⚙️ 配置
133
+
134
+ 在 nonebot2 项目的`.env`文件中添加下表中的必填配置
135
+
136
+ | 配置项 | 必填 | 默认值 | 说明 |
137
+ |:-----:|:----:|:----:|:----:|
138
+ | super_admins | 是 | [] | 管理员QQ号列表 |
139
+
140
+
141
+ ## 🎉 使用
142
+
143
+ ### 指令表
144
+ | 指令 | 说明 |
145
+ |:-----:|:-----:|
146
+ | fhelp | 显示所有管理员可用命令的帮助信息 |
147
+ | 转换 <群号> | 开启指定群的B站视频转换功能 |
148
+ | 停止转换 <群号> | 停止指定群的B站视频转换功能 |
149
+ | 设置B站COOKIE <cookie字符串> | 设置B站Cookie |
150
+ | 清除B站COOKIE | 清除已设置的B站Cookie |
151
+ | 设置清晰度 <数字> | 设置视频清晰度 |
152
+ | 设置最大大小 <数字>MB | 设置视频大小限制 |
153
+ | 查看参数 | 查看当前配置参数 |
154
+ | 查看转换列表 | 查看已开启转换功能的群列表 |
155
+
156
+ **注**:
157
+ 以上指令均需管理员私聊bot
158
+
159
+ Cookie可以不设置,设置大会员账号的cookie可以获取更高清晰度或者大会员限定视频
160
+
161
+ Cookie中至少需要包含SESSDATA、bili_jct、DedeUserID和buvid3/buvid4四个字段
162
+ ## 效果图
163
+ <img src="nonebot_plugin_bili2mp4/images/picture1.png" width="500">
164
+ <img src="nonebot_plugin_bili2mp4/images/picture2.png" width="500">
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ nonebot_plugin_bili2mp4/__init__.py
4
+ nonebot_plugin_bili2mp4/__main__.py
5
+ nonebot_plugin_bili2mp4/config.py
6
+ nonebot_plugin_bili2mp4.egg-info/PKG-INFO
7
+ nonebot_plugin_bili2mp4.egg-info/SOURCES.txt
8
+ nonebot_plugin_bili2mp4.egg-info/dependency_links.txt
9
+ nonebot_plugin_bili2mp4.egg-info/requires.txt
10
+ nonebot_plugin_bili2mp4.egg-info/top_level.txt
11
+ nonebot_plugin_bili2mp4/images/picture1.png
12
+ nonebot_plugin_bili2mp4/images/picture2.png
@@ -0,0 +1,18 @@
1
+ nonebot2>=2.0.0
2
+ nonebot-adapter-onebot>=2.0.0
3
+ pydantic>=1.10.0
4
+ yt-dlp>=2023.3.4
5
+ aiofiles>=0.8.0
6
+ loguru>=0.6.0
7
+
8
+ [dev]
9
+ black>=22.0.0
10
+ isort>=5.10.0
11
+ flake8>=4.0.0
12
+ pytest>=7.0.0
13
+ pytest-asyncio>=0.21.0
14
+ pre-commit>=2.20.0
15
+
16
+ [test]
17
+ pytest>=7.0.0
18
+ pytest-asyncio>=0.21.0
@@ -0,0 +1 @@
1
+ nonebot_plugin_bili2mp4
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nonebot-plugin-bili2mp4"
7
+ version = "0.1.0"
8
+ description = "在指定群内将B站小程序/分享链接解析转MP4后发送的NoneBot2插件"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8,<4.0"
11
+ authors = [
12
+ {name = "j1udu", email = "2312410042@stu.suda.edu.cn"},
13
+ ]
14
+
15
+ keywords = ["nonebot", "nonebot2", "bilibili", "video", "download", "mp4"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.8",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ ]
25
+ dependencies = [
26
+ "nonebot2>=2.0.0",
27
+ "nonebot-adapter-onebot>=2.0.0",
28
+ "pydantic>=1.10.0",
29
+ "yt-dlp>=2023.3.4",
30
+ "aiofiles>=0.8.0",
31
+ "loguru>=0.6.0",
32
+ ]
33
+ [tool.setuptools.package-data]
34
+ "nonebot_plugin_bili2mp4" = ["images/*"]
35
+ [project.optional-dependencies]
36
+ test = [
37
+ "pytest>=7.0.0",
38
+ "pytest-asyncio>=0.21.0",
39
+ ]
40
+ dev = [
41
+ "black>=22.0.0",
42
+ "isort>=5.10.0",
43
+ "flake8>=4.0.0",
44
+ "pytest>=7.0.0",
45
+ "pytest-asyncio>=0.21.0",
46
+ "pre-commit>=2.20.0",
47
+ ]
48
+
49
+ [project.urls]
50
+ Homepage = "https://github.com/j1udu/nonebot-plugin-bili2mp4"
51
+ Repository = "https://github.com/j1udu/nonebot-plugin-bili2mp4"
52
+ "Bug Reports" = "https://github.com/j1udu/nonebot-plugin-bili2mp4/issues"
53
+
54
+ [tool.setuptools.packages.find]
55
+ where = ["."]
56
+ include = ["nonebot_plugin_bili2mp4*"]
57
+
58
+ [tool.black]
59
+ line-length = 88
60
+ target-version = ["py38"]
61
+ include = '\.pyi?$'
62
+ extend-exclude = '''
63
+ /(
64
+ # directories
65
+ \.eggs
66
+ | \.git
67
+ | \.hg
68
+ | \.mypy_cache
69
+ | \.tox
70
+ | \.venv
71
+ | build
72
+ | dist
73
+ )/
74
+ '''
75
+
76
+ [tool.isort]
77
+ profile = "black"
78
+ line_length = 88
79
+ length_sort = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+