Skip to content
Merged

up #1

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2025 饰乐
Copyright (c) 2024 Les Freire

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

<div align="center">

![:name](https://count.getloli.com/@astrbot_plugin_parser?name=astrbot_plugin_parser&theme=minecraft&padding=6&offset=0&align=top&scale=1&pixelated=1&darkmode=auto)

# astrbot_plugin_parser

_✨ 链接解析器 ✨_

[![License](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue.svg)](https://www.python.org/)
[![AstrBot](https://img.shields.io/badge/AstrBot-3.4%2B-orange.svg)](https://github.com/Soulter/AstrBot)
[![GitHub](https://img.shields.io/badge/作者-Zhalslar-blue)](https://github.com/Zhalslar)

</div>

## 📖 介绍

| 平台 | 触发的消息形态 | 视频 | 图集 | 音频 |
| ------- | --------------------------------- | ---- | ---- | ---- |
| B 站 | av 号/BV 号/链接/短链/卡片/小程序 | ✅​ | ✅​ | ✅​ |
| 抖音 | 链接(分享链接,兼容电脑端链接) | ✅​ | ✅​ | ❌️ |
| 微博 | 链接(博文,视频,show, 文章) | ✅​ | ✅​ | ❌️ |
| 小红书 | 链接(含短链)/卡片 | ✅​ | ✅​ | ❌️ |
| 快手 | 链接(包含标准链接和短链) | ✅​ | ✅​ | ❌️ |
| acfun | 链接 | ✅​ | ❌️ | ❌️ |
| youtube | 链接(含短链) | ✅​ | ❌️ | ✅​ |
| tiktok | 链接 | ✅​ | ❌️ | ❌️ |
| twitter | 链接 | ✅​ | ✅​ | ❌️ |

## 🎨 效果图

插件默认启用 PIL 实现的通用媒体卡片渲染,效果图如下

<div align="center">

<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-parser/refs/heads/resources/resources/renderdamine/video.png" width="160" />
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-parser/refs/heads/resources/resources/renderdamine/9_pic.png" width="160" />
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-parser/refs/heads/resources/resources/renderdamine/4_pic.png" width="160" />
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-parser/refs/heads/resources/resources/renderdamine/repost_video.png" width="160" />
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-parser/refs/heads/resources/resources/renderdamine/repost_2_pic.png" width="160" />

</div>

## 💿 安装

直接在astrbot的插件市场搜索astrbot_plugin_parser,点击安装,等待完成即可

## ⚙️ 配置

请在astrbot的插件配置面板查看并修改

## 🎉 使用

| 指令 | 权限 | 说明 |
| :------: | :-------------------: | :---------------: |
| 开启解析 | ADMIN | 开启解析 |
| 关闭解析 | ADMIN | 关闭解析 |
| bm | - | 下载 B 站音频 |
| ym | - | 下载 youtube 音频 |
| blogin | ADMIN | 扫码获取 B 站凭证 |

## 🧩 扩展

插件支持自定义解析器,通过继承 `BaseParser` 类并实现 `platform`, `handle` 即可。

示例解析器请看 [示例解析器](https://github.com/Zhalslar/astrbot_plugin_parser/blob/main/core/parsers/example.py)

## 🎉 致谢

本项目核心代码来自[nonebot-plugin-parser](https://github.com/fllesser/nonebot-plugin-parser),请前往原仓库给作者点个Star!
160 changes: 160 additions & 0 deletions _conf_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
{
"disabled_sessions": {
"description": "关闭解析的会话",
"type": "list",
"hint": "在会话中使用命令 “开启解析” 和 “关闭解析” 来设置某会话的解析状态",
"default": []
},
"enable_platforms": {
"description": "启用解析的平台",
"type": "list",
"hint": "",
"options": [
"A站",
"B站",
"微博",
"小红书",
"抖音",
"快手",
"NGA",
"TikTok",
"推特",
"油管"
],
"default": [
"A站",
"B站",
"微博",
"小红书",
"抖音",
"快手",
"NGA",
"TikTok",
"推特",
"油管"
]
},
"forward_contents": {
"description": "转发媒体内容",
"type": "bool",
"hint": "是否将解析到的图片/视频/音频作为合并转发消息发送",
"default": true
},
"upload_audio": {
"description": "上传音频文件",
"type": "bool",
"hint": "是否将解析到的音频文件上传到群文件",
"default": false
},
"max_size": {
"description": "资源最大大小",
"type": "int",
"hint": "允许下载的音视频最大体积,单位 MB",
"default": 90
},
"max_duration": {
"description": "资源最大时长",
"type": "int",
"hint": "允许下载的音视频最大时长,单位秒",
"default": 480
},
"download_timeout": {
"description": "下载请求超时时间",
"type": "int",
"hint": "下载视频、音频等较大文件时的请求超时时间,单位秒。 建议设置大一点,视频、音频较大时下载耗时较长",
"default": 280
},
"common_timeout": {
"description": "普通请求超时时间",
"type": "int",
"hint": "普通请求超时时间,单位秒。用于一些普通的请求 ",
"default": 15
},
"bili_ck": {
"description": "Bilibili Cookies",
"type": "text",
"hint": "用于B站解析的登录Cookies,留空则使用无登录状态",
"default": ""
},
"bili_video_codecs": {
"description": "B站视频编码",
"type": "string",
"hint": "优先下载的编码类型,可选:AVC、AV1、HEV",
"options": [
"AVC",
"AV1",
"HEV"
],
"default": "AVC"
},
"bili_video_quality": {
"description": "B站视频分辨率",
"type": "string",
"hint": "下载B站视频的分辨率",
"options": [
"_360P",
"_480P",
"_720P",
"_1080P",
"_1080P_PLUS",
"_1080P_60",
"_4K",
"HDR",
"DOLBY",
"_8K",
"AI_REPAIR"
],
"default": "_720P"
},
"ytb_ck": {
"description": "YouTube Cookies",
"type": "text",
"hint": "用于YouTube解析的登录Cookies,留空则使用无登录状态",
"default": ""
},
"proxy": {
"description": "代理地址",
"type": "string",
"hint": "如 http://127.0.0.1:7890,留空则直连。仅作用于 youtube, tiktok 解析",
"default": ""
},
"emoji_cdn": {
"description": "Pilmoji 表情 CDN",
"type": "string",
"hint": "渲染表情使用的 CDN 地址,一般无需修改",
"default": "https://cdn.jsdelivr.net/npm/emoji-datasource-facebook@14.0.0/img/facebook/64/"
},
"emoji_style": {
"description": "Pilmoji 表情样式",
"type": "string",
"hint": "可选:APPLE、FACEBOOK、GOOGLE、TWITTER",
"options": [
"APPLE",
"FACEBOOK",
"GOOGLE",
"TWITTER"
],
"default": "FACEBOOK"
},
"clean_cron": {
"description": "自动清理缓存的触发周期",
"type": "string",
"hint": "使用 Cron 表达式(分 时 日 月 周)定义。例如:“30 2 * * *” 表示每天 2:30 。留空表示禁用自动清理",
"default": "30 2 * * *"
},
"data_dir": {
"description": "数据目录",
"type": "string",
"invisible": true
},
"cache_dir": {
"description": "缓存目录",
"type": "string",
"invisible": true
},
"ytb_cookies_file": {
"description": "YouTube Cookies 文件",
"type": "string",
"invisible": true
}
}
Empty file added core/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions core/clean.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import asyncio
import zoneinfo
from pathlib import Path

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger

from astrbot.api import logger
from astrbot.core.config.astrbot_config import AstrBotConfig
from astrbot.core.star.context import Context

from .utils import safe_unlink


class CacheCleaner:
"""
每天固定时间自动清理插件缓存目录的调度器封装。
"""
JOBNAME = "CacheCleaner"
def __init__(self, context: Context, config: AstrBotConfig):
self.clean_cron = config["clean_cron"]
self.cache_dir = Path(config["cache_dir"])

tz = context.get_config().get("timezone")
self.timezone = (
zoneinfo.ZoneInfo(tz) if tz else zoneinfo.ZoneInfo("Asia/Shanghai")
)
self.scheduler = AsyncIOScheduler(timezone=self.timezone)
self.scheduler.start()

self.register_task()

logger.info(f"{self.JOBNAME} 已启动,任务周期:{self.clean_cron}")

def register_task(self):
try:
self.trigger = CronTrigger.from_crontab(self.clean_cron)
self.scheduler.add_job(
func=self._clean_plugin_cache,
trigger=self.trigger,
name=f"{self.JOBNAME}_scheduler",
max_instances=1,
)
except Exception as e:
logger.error(f"[{self.JOBNAME}] Cron 格式错误:{e}")

async def _clean_plugin_cache(self) -> None:
"""真正的清理逻辑。"""
try:
files = [f for f in self.cache_dir.iterdir() if f.is_file()]
if not files:
logger.info("No cache files to clean.")
return

await asyncio.gather(*(safe_unlink(f) for f in files))
logger.info(f"Successfully cleaned {len(files)} cache files.")
except Exception:
logger.exception("Error while cleaning cache files.")

async def stop(self):
self.scheduler.remove_all_jobs()
logger.info(f"[{self.JOBNAME}] 已停止")
39 changes: 39 additions & 0 deletions core/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from enum import Enum
from typing import Final

COMMON_HEADER: Final[dict[str, str]] = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36"
)
}

IOS_HEADER: Final[dict[str, str]] = {
"User-Agent": (
"Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) "
"Version/16.6 Mobile/15E148 Safari/604.1 Edg/132.0.0.0"
)
}

ANDROID_HEADER: Final[dict[str, str]] = {
"User-Agent": (
"Mozilla/5.0 (Linux; Android 15; SM-G998B) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/132.0.0.0 Mobile Safari/537.36 Edg/132.0.0.0"
)
}


class PlatformEnum(str, Enum):
ACFUN = "acfun"
BILIBILI = "bilibili"
DOUYIN = "douyin"
KUAISHOU = "kuaishou"
NGA = "nga"
TIKTOK = "tiktok"
TWITTER = "twitter"
WEIBO = "weibo"
XIAOHONGSHU = "xiaohongshu"
YOUTUBE = "youtube"

def __str__(self) -> str:
return self.value
Loading