mirror of
https://github.com/cxfksword/jellyfin-plugin-danmu.git
synced 2026-02-02 17:59:58 +08:00
148
.github/copilot-instructions.md
vendored
Normal file
148
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
# Jellyfin Plugin Danmu - AI 编码助手指南
|
||||
|
||||
## 项目概述
|
||||
一个为 Jellyfin 开发的弹幕自动下载插件,支持从多个视频平台(B站、优酷、爱奇艺、腾讯视频、芒果TV)自动下载和管理中文弹幕。支持 XML 和 ASS 字幕格式。
|
||||
|
||||
**核心技术栈**: C# .NET 9.0, Jellyfin Plugin API, ILRepack(用于依赖合并)
|
||||
|
||||
## 架构:多弹幕源设计模式
|
||||
|
||||
### 弹幕源系统 (`Jellyfin.Plugin.Danmu/Scrapers/`)
|
||||
核心架构是一个**可插拔的弹幕源系统**,每个视频平台都实现 `AbstractScraper`:
|
||||
|
||||
```csharp
|
||||
// 每个平台(Bilibili/、Youku/、Iqiyi/ 等)需要实现:
|
||||
public abstract class AbstractScraper {
|
||||
Task<List<ScraperSearchInfo>> Search(BaseItem item);
|
||||
Task<string?> SearchMediaId(BaseItem item);
|
||||
Task<ScraperMedia?> GetMedia(BaseItem item, string id);
|
||||
Task<ScraperEpisode?> GetMediaEpisode(BaseItem item, string id);
|
||||
Task<ScraperDanmaku?> GetDanmuContent(BaseItem item, string commentId);
|
||||
}
|
||||
```
|
||||
|
||||
**重要说明**:
|
||||
- 弹幕源通过 `Plugin.cs` 中的 `IApplicationHost.GetExports<AbstractScraper>()` 自动发现
|
||||
- 顺序由 `DefaultOrder` 属性和用户配置控制
|
||||
- 每个弹幕源有唯一的 `ProviderId`(如 `BilibiliID`),用于 Jellyfin 元数据存储
|
||||
- 参考 `Scrapers/Bilibili/Bilibili.cs` 的实现示例
|
||||
|
||||
### 事件驱动的弹幕处理
|
||||
插件使用 Jellyfin 的媒体库事件和**防抖队列处理**机制:
|
||||
|
||||
1. `PluginStartup.cs` 订阅 `ILibraryManager.ItemAdded/ItemUpdated` 事件
|
||||
2. 事件在 `LibraryManagerEventsHelper` 中排队,使用 10 秒防抖定时器
|
||||
3. 批量处理:匹配媒体 → 搜索弹幕源 → 下载弹幕
|
||||
4. 结果保存为 `.xml` 文件,与视频文件同目录(如 `movie.mp4` → `movie.xml`)
|
||||
|
||||
**关键类**:
|
||||
- `LibraryManagerEventsHelper.QueueItem()` - 添加项目到处理队列
|
||||
- `LibraryManagerEventsHelper.ProcessQueuedMovieEvents()` - 批量处理电影
|
||||
- `LibraryManagerEventsHelper.ProcessQueuedSeasonEvents()` - 处理电视剧季
|
||||
|
||||
## 开发工作流
|
||||
|
||||
### 构建项目
|
||||
```bash
|
||||
dotnet restore
|
||||
dotnet publish --configuration=Release Jellyfin.Plugin.Danmu/Jellyfin.Plugin.Danmu.csproj
|
||||
```
|
||||
|
||||
输出位置: `Jellyfin.Plugin.Danmu/bin/Release/net9.0/Jellyfin.Plugin.Danmu.dll`
|
||||
|
||||
**ILRepack 集成**: Release 构建会通过 `ILRepack.targets` 自动将依赖(RateLimiter、ComposableAsync、Google.Protobuf、SharpZipLib)合并到单个 DLL。这对 Jellyfin 插件部署至关重要。
|
||||
|
||||
### 运行测试
|
||||
```bash
|
||||
dotnet test --no-restore --verbosity normal
|
||||
```
|
||||
|
||||
测试使用 MSTest 框架和 Moq 进行模拟:
|
||||
- `*ApiTest.cs` - API 响应解析测试
|
||||
- `*Test.cs` - 端到端弹幕源测试
|
||||
- 模拟 `ILibraryManager`、`IFileSystem` 以实现隔离测试
|
||||
|
||||
### 开发环境安装
|
||||
1. 构建 Release 配置
|
||||
2. 创建 `danmu/` 文件夹并复制 `Jellyfin.Plugin.Danmu.dll` 到其中
|
||||
3. 将文件夹移动到 Jellyfin 的 `data/plugins/` 目录
|
||||
4. 重启 Jellyfin
|
||||
|
||||
## 关键模式与约定
|
||||
|
||||
### Provider ID 映射
|
||||
每个弹幕源使用其 `ProviderId` 在 Jellyfin 元数据中存储匹配结果:
|
||||
```csharp
|
||||
item.ProviderIds["BilibiliID"] = "123456"; // 季/媒体 ID
|
||||
// 对于剧集,使用格式:"seasonId_episodeId"
|
||||
```
|
||||
|
||||
**特殊处理**: B站支持 `BV` 视频 ID(用户上传内容)和 `av` ID(UGC视频),以及番剧的 season ID。
|
||||
|
||||
### 弹幕 → ASS 转换 (`Core/Danmaku2Ass/`)
|
||||
`Creater` 类将 XML 弹幕转换为 ASS 字幕,具有:
|
||||
- 碰撞检测(`Collision.cs`)- 使用基于行的跟踪防止重叠
|
||||
- 显示模式(`Display.cs`)- 滚动、顶部锚定、底部锚定
|
||||
- 可配置的字体、速度、透明度、行数
|
||||
|
||||
**重要**: ASS 生成是可选的,通过插件配置 `ToAss = true` 启用。
|
||||
|
||||
### API 端点 (`Controllers/DanmuController.cs`)
|
||||
为外部播放器提供的公共 REST API:
|
||||
- `GET /api/danmu/{id}` - 返回弹幕 URL 元数据
|
||||
- `GET /api/danmu/{id}/raw` - 下载 XML 弹幕文件
|
||||
- `GET /api/danmu/search?keyword=` - 跨所有弹幕源搜索
|
||||
- `GET /api/{site}/danmu/{id}/episodes` - 获取剧集列表
|
||||
- `GET /api/{site}/danmu/{cid}/download` - 通过评论 ID 下载弹幕
|
||||
|
||||
### 配置系统
|
||||
`PluginConfiguration.cs` 使用 XML 序列化,特殊的 `Scrapers` 属性会:
|
||||
1. 合并用户配置和新发现的弹幕源
|
||||
2. 删除已废弃的弹幕源
|
||||
3. 尊重用户定义的顺序和启用/禁用状态
|
||||
|
||||
### 异常处理
|
||||
- `CanIgnoreException` - 表示预期的失败(如未找到弹幕),不应记录错误日志
|
||||
- `FrequentlyRequestException` - 视频平台的速率限制异常
|
||||
|
||||
## 外部依赖与集成
|
||||
|
||||
### Jellyfin Plugin API
|
||||
- 实现 `ISubtitleProvider` 用于字幕搜索 UI
|
||||
- `IPluginServiceRegistrator` 用于依赖注入注册
|
||||
- `BasePlugin<PluginConfiguration>` 用于配置管理
|
||||
- `IScheduledTask` 用于定期媒体库扫描(`ScheduledTasks/ScanLibraryTask.cs`)
|
||||
|
||||
### .NET 包
|
||||
- `RateLimiter` - API 请求节流
|
||||
- `Google.Protobuf` - B站 API protobuf 响应解析
|
||||
- `SharpZipLib` - 弹幕数据解压缩
|
||||
- `ComposableAsync.Core` - 异步工具库
|
||||
|
||||
### 平台专用 API
|
||||
每个弹幕源都有一个 `*Api.cs`(如 `BilibiliApi.cs`)处理:
|
||||
- HTTP 客户端和重试逻辑
|
||||
- 响应反序列化(JSON/Protobuf)
|
||||
- 速率限制和错误处理
|
||||
|
||||
## 命名约定
|
||||
- 弹幕源:小写名称(如 `bilibili`、`youku`)用于用户界面显示
|
||||
- Provider ID:驼峰式 + `ID` 后缀(如 `BilibiliID`)
|
||||
- 事件类型:`EventType.Add`、`EventType.Update`、`EventType.Force`
|
||||
- 文件扩展名:`.xml` 用于弹幕,`.ass` 用于字幕
|
||||
|
||||
## 测试视频平台 API
|
||||
使用测试类如 `BilibiliApiTest.cs` 在集成前验证 API 响应。模拟 `ILibraryManager` 以避免单元测试中的 Jellyfin 依赖。
|
||||
|
||||
## 发布流程
|
||||
1. 打标签:`git tag -a v1.2.3 -m "Release notes"`
|
||||
2. GitHub Actions 自动构建和发布
|
||||
3. `scripts/generate_manifest.py` 更新插件清单并生成校验和
|
||||
4. 通过 `CN_DOMAIN` 环境变量支持国内镜像
|
||||
|
||||
## 常见陷阱
|
||||
- **不要在字幕搜索中直接修改 `item.ProviderIds`** - 使用临时 item 副本以避免持久化错误的元数据
|
||||
- **弹幕源必须在 `ServiceRegistrator.cs` 中注册**才能被发现
|
||||
- **季匹配需要正确的 `IndexNumber`** 用于多季系列
|
||||
- **ILRepack 仅在 Release 构建中运行** - Debug 构建有独立的 DLL
|
||||
- **Jellyfin 的 `LocationType.Virtual`** 项目(没有季文件夹的系列)需要特殊处理
|
||||
6
.github/workflows/beta.yaml
vendored
6
.github/workflows/beta.yaml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 9.0.x
|
||||
python-version: 3.8
|
||||
project: Jellyfin.Plugin.Danmu/Jellyfin.Plugin.Danmu.csproj
|
||||
artifact: danmu
|
||||
@@ -43,9 +43,9 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
dotnet restore ${{ env.project }} --no-cache
|
||||
dotnet publish --nologo --no-restore --configuration=Release --framework=net8.0 ${{ env.project }}
|
||||
dotnet publish --nologo --no-restore --configuration=Release --framework=net9.0 ${{ env.project }}
|
||||
mkdir -p artifacts
|
||||
cp ./Jellyfin.Plugin.Danmu/bin/Release/net8.0/Jellyfin.Plugin.Danmu.dll ./artifacts/
|
||||
cp ./Jellyfin.Plugin.Danmu/bin/Release/net9.0/Jellyfin.Plugin.Danmu.dll ./artifacts/
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- uses: actions/setup-dotnet@v3
|
||||
id: dotnet
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 9.0.x
|
||||
- name: Change default dotnet version
|
||||
run: |
|
||||
echo '{"sdk":{"version": "${{ steps.dotnet.outputs.dotnet-version }}"}}' > ./global.json
|
||||
|
||||
6
.github/workflows/publish.yaml
vendored
6
.github/workflows/publish.yaml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
tags: ["*"]
|
||||
|
||||
env:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 9.0.x
|
||||
python-version: 3.8
|
||||
project: Jellyfin.Plugin.Danmu/Jellyfin.Plugin.Danmu.csproj
|
||||
artifact: danmu
|
||||
@@ -50,9 +50,9 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
dotnet restore ${{ env.project }} --no-cache
|
||||
dotnet publish --nologo --no-restore --configuration=Release --framework=net8.0 -p:Version=${{steps.vars.outputs.VERSION}} ${{ env.project }}
|
||||
dotnet publish --nologo --no-restore --configuration=Release --framework=net9.0 -p:Version=${{steps.vars.outputs.VERSION}} ${{ env.project }}
|
||||
mkdir -p artifacts
|
||||
zip -j ./artifacts/${{ env.artifact }}_${{steps.vars.outputs.VERSION}}.zip ./Jellyfin.Plugin.Danmu/bin/Release/net8.0/Jellyfin.Plugin.Danmu.dll
|
||||
zip -j ./artifacts/${{ env.artifact }}_${{steps.vars.outputs.VERSION}}.zip ./Jellyfin.Plugin.Danmu/bin/Release/net9.0/Jellyfin.Plugin.Danmu.dll
|
||||
- name: Generate manifest
|
||||
run: python3 ./scripts/generate_manifest.py ./artifacts/${{ env.artifact }}_${{steps.vars.outputs.VERSION}}.zip ${GITHUB_REF#refs/*/}
|
||||
env:
|
||||
|
||||
@@ -35,8 +35,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -67,8 +68,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
@@ -101,8 +103,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -135,8 +138,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -169,8 +173,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -205,8 +210,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
|
||||
@@ -27,8 +27,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -61,8 +62,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
|
||||
@@ -37,8 +37,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -72,8 +73,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -109,8 +111,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
@@ -16,6 +16,7 @@ using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
@@ -42,8 +43,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -81,7 +83,8 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
mediaSourceManagerStub.Setup(x => x.GetPathProtocol(It.IsAny<string>())).Returns(MediaBrowser.Model.MediaInfo.MediaProtocol.File);
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var itemRepositoryStub = new Mock<IItemRepository>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -116,8 +119,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
@@ -149,8 +153,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
@@ -185,8 +190,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -220,8 +226,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
@@ -256,8 +263,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -295,8 +303,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
var mediaSourceManagerStub = new Mock<IMediaSourceManager>();
|
||||
mediaSourceManagerStub.Setup(x => x.GetPathProtocol(It.IsAny<string>())).Returns(MediaBrowser.Model.MediaInfo.MediaProtocol.File);
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
|
||||
@@ -28,8 +28,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -63,7 +64,8 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -100,7 +102,8 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
|
||||
@@ -27,8 +27,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -61,8 +62,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -98,8 +100,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
|
||||
@@ -37,8 +37,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -71,8 +72,9 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Movie
|
||||
{
|
||||
@@ -107,7 +109,8 @@ namespace Jellyfin.Plugin.Danmu.Test
|
||||
var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
|
||||
var directoryServiceStub = new Mock<IDirectoryService>();
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
var itemRepositoryStub = new Mock<MediaBrowser.Controller.Persistence.IItemRepository>();
|
||||
var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager);
|
||||
|
||||
var item = new Season
|
||||
{
|
||||
|
||||
@@ -137,6 +137,10 @@ public class ScraperConfigItem
|
||||
|
||||
public class DanmuDownloadOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 弹幕自动匹配下载.
|
||||
/// </summary>
|
||||
public bool EnableAutoDownload { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 检测弹幕数和视频剧集数需要一致才自动下载弹幕.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,16 +25,17 @@
|
||||
|
||||
<fieldset class="verticalSection verticalSection-extrabottompadding">
|
||||
<legend>
|
||||
<h3>弹幕源配置</h3>
|
||||
<h3>弹幕下载配置</h3>
|
||||
</legend>
|
||||
<div class="checkboxList paperList checkboxList-paperList" id="Scrapers" name="Scrapers">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="verticalSection verticalSection-extrabottompadding">
|
||||
<legend>
|
||||
<h3>弹幕匹配下载配置</h3>
|
||||
</legend>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableAutoDownload" name="EnableAutoDownload" type="checkbox"
|
||||
is="emby-checkbox" />
|
||||
<span>弹幕自动匹配下载</span>
|
||||
</label>
|
||||
<div class="fieldDescription">勾选后,有新影片入库会自动匹配下载,不勾选需要自己搜索下载。</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
@@ -46,6 +47,15 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="verticalSection verticalSection-extrabottompadding">
|
||||
<legend>
|
||||
<h3>弹幕源配置</h3>
|
||||
</legend>
|
||||
<div class="checkboxList paperList checkboxList-paperList" id="Scrapers" name="Scrapers">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset id="dandanSection" class="verticalSection verticalSection-extrabottompadding" style="display: none;">
|
||||
<legend>
|
||||
<h3>弹弹play配置</h3>
|
||||
@@ -143,6 +153,7 @@
|
||||
document.querySelector('#AssLineCount').value = config.AssLineCount;
|
||||
document.querySelector('#AssSpeed').value = config.AssSpeed;
|
||||
|
||||
document.querySelector('#EnableAutoDownload').checked = config.DownloadOption.EnableAutoDownload;
|
||||
document.querySelector('#EnableEpisodeCountSame').checked = config.DownloadOption.EnableEpisodeCountSame;
|
||||
|
||||
document.querySelector('#WithRelatedDanmu').checked = config.Dandan.WithRelatedDanmu;
|
||||
@@ -194,6 +205,7 @@
|
||||
|
||||
var download = new Object();
|
||||
download.EnableEpisodeCountSame = document.querySelector('#EnableEpisodeCountSame').checked;
|
||||
download.EnableAutoDownload = document.querySelector('#EnableAutoDownload').checked;
|
||||
config.DownloadOption = download;
|
||||
|
||||
var dandan = new Object();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RootNamespace>Jellyfin.Plugin.Danmu</RootNamespace>
|
||||
<GenerateDocumentationFile>False</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
@@ -21,8 +21,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Jellyfin.Controller" Version="10.9.0" />
|
||||
<PackageReference Include="Jellyfin.Model" Version="10.9.0" />
|
||||
<PackageReference Include="Jellyfin.Controller" Version="10.*-*" />
|
||||
<PackageReference Include="Jellyfin.Model" Version="10.*-*" />
|
||||
<PackageReference Include="RateLimiter" Version="2.2.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -11,6 +11,7 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -27,7 +28,7 @@ public class LibraryManagerEventsHelper : IDisposable
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly MemoryCacheEntryOptions _pendingAddExpiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) };
|
||||
private readonly MemoryCacheEntryOptions _danmuUpdatedExpiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(24*60) };
|
||||
|
||||
private readonly IItemRepository _itemRepository;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger<LibraryManagerEventsHelper> _logger;
|
||||
private readonly Jellyfin.Plugin.Danmu.Core.IFileSystem _fileSystem;
|
||||
@@ -50,11 +51,12 @@ public class LibraryManagerEventsHelper : IDisposable
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
/// <param name="api">The <see cref="BilibiliApi"/>.</param>
|
||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||
public LibraryManagerEventsHelper(ILibraryManager libraryManager, ILoggerFactory loggerFactory, Jellyfin.Plugin.Danmu.Core.IFileSystem fileSystem, ScraperManager scraperManager)
|
||||
public LibraryManagerEventsHelper(IItemRepository itemRepository, ILibraryManager libraryManager, ILoggerFactory loggerFactory, Jellyfin.Plugin.Danmu.Core.IFileSystem fileSystem, ScraperManager scraperManager)
|
||||
{
|
||||
_queuedEvents = new List<LibraryEvent>();
|
||||
_memoryCache = new MemoryCache(new MemoryCacheOptions());
|
||||
|
||||
_itemRepository = itemRepository;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = loggerFactory.CreateLogger<LibraryManagerEventsHelper>();
|
||||
_fileSystem = fileSystem;
|
||||
@@ -282,14 +284,8 @@ public class LibraryManagerEventsHelper : IDisposable
|
||||
var mediaId = await scraper.SearchMediaId(currentItem).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(mediaId))
|
||||
{
|
||||
_logger.LogInformation("[{0}]元数据匹配失败:{1} ({2}),尝试文件匹配", scraper.Name, item.Name, item.ProductionYear);
|
||||
|
||||
mediaId = await scraper.SearchMediaIdByFile((Movie)currentItem).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(mediaId))
|
||||
{
|
||||
_logger.LogInformation("[{0}]文件匹配失败:{1}", scraper.Name, currentItem.Path);
|
||||
continue;
|
||||
}
|
||||
_logger.LogInformation("[{0}]元数据匹配失败:{1} ({2})", scraper.Name, item.Name, item.ProductionYear);
|
||||
continue;
|
||||
}
|
||||
|
||||
var media = await scraper.GetMedia(item, mediaId);
|
||||
@@ -875,7 +871,7 @@ public class LibraryManagerEventsHelper : IDisposable
|
||||
item.ProviderIds[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||
_itemRepository.SaveItems(new[] { item }, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
_logger.LogInformation("更新epid到元数据完成。item数:{0}", queue.Count);
|
||||
@@ -989,7 +985,7 @@ public class LibraryManagerEventsHelper : IDisposable
|
||||
// 保存指定弹幕元数据
|
||||
item.ProviderIds[providerId] = providerVal;
|
||||
|
||||
await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||
_itemRepository.SaveItems(new[] { item }, CancellationToken.None);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Jellyfin.Plugin.Danmu.Model;
|
||||
using Jellyfin.Plugin.Danmu.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
@@ -20,6 +21,14 @@ namespace Jellyfin.Plugin.Danmu
|
||||
private readonly LibraryManagerEventsHelper _libraryManagerEventsHelper;
|
||||
private readonly ILogger<PluginStartup> _logger;
|
||||
|
||||
public PluginConfiguration Config
|
||||
{
|
||||
get
|
||||
{
|
||||
return Plugin.Instance?.Configuration ?? new Configuration.PluginConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginStartup"/> class.
|
||||
/// </summary>
|
||||
@@ -60,6 +69,11 @@ namespace Jellyfin.Plugin.Danmu
|
||||
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
||||
private void LibraryManagerItemAdded(object sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||
{
|
||||
if (!Config.DownloadOption.EnableAutoDownload)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't do anything if it's not a supported media type
|
||||
if (itemChangeEventArgs.Item is not Movie and not Episode and not Series and not Season)
|
||||
{
|
||||
@@ -83,6 +97,11 @@ namespace Jellyfin.Plugin.Danmu
|
||||
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
||||
private void LibraryManagerItemUpdated(object sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||
{
|
||||
if (!Config.DownloadOption.EnableAutoDownload)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't do anything if it's not a supported media type
|
||||
if (itemChangeEventArgs.Item is not Movie and not Episode and not Series and not Season)
|
||||
{
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Bilibili.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.bilibili.com/bangumi/play/ep{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Episode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Bilibili.ExternalId;
|
||||
|
||||
/// <summary>
|
||||
/// External URLs for Danmu.
|
||||
/// </summary>
|
||||
public class ExternalUrlProvider : IExternalUrlProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name => Bilibili.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string> GetExternalUrls(BaseItem item)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case Season season:
|
||||
if (item.TryGetProviderId(Bilibili.ScraperProviderId, out var externalId))
|
||||
{
|
||||
if (externalId.StartsWith("bv", StringComparison.OrdinalIgnoreCase) || externalId.StartsWith("av", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
yield return $"https://www.bilibili.com/{externalId}";
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return $"https://www.bilibili.com/bangumi/play/ss{externalId}";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case Episode episode:
|
||||
if (item.TryGetProviderId(Bilibili.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return $"https://www.bilibili.com/bangumi/play/ep{externalId}";
|
||||
}
|
||||
|
||||
break;
|
||||
case Movie:
|
||||
if (item.TryGetProviderId(Bilibili.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return $"https://www.bilibili.com/bangumi/play/ep{externalId}";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Bilibili.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Movie;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.bilibili.com/bangumi/play/ep{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Movie;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Bilibili.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Season;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.bilibili.com/bangumi/play/ss{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Season;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Dandan.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "#";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Episode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Dandan.ExternalId;
|
||||
|
||||
/// <summary>
|
||||
/// External URLs for Danmu.
|
||||
/// </summary>
|
||||
public class ExternalUrlProvider : IExternalUrlProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name => Dandan.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string> GetExternalUrls(BaseItem item)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case Season season:
|
||||
if (item.TryGetProviderId(Dandan.ScraperProviderId, out var externalId))
|
||||
{
|
||||
yield return $"https://api.dandanplay.net/api/v2/bangumi/{externalId}";
|
||||
}
|
||||
|
||||
break;
|
||||
case Episode episode:
|
||||
if (item.TryGetProviderId(Dandan.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return "#";
|
||||
}
|
||||
|
||||
break;
|
||||
case Movie:
|
||||
if (item.TryGetProviderId(Dandan.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return $"https://api.dandanplay.net/api/v2/bangumi/{externalId}";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,7 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Dandan.ExternalId
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://api.dandanplay.net/api/v2/bangumi/{0}";
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Movie;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Dandan.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://api.dandanplay.net/api/v2/bangumi/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Season;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Iqiyi.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.iqiyi.com/v_{0}.html";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Episode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Iqiyi.ExternalId;
|
||||
|
||||
/// <summary>
|
||||
/// External URLs for Danmu.
|
||||
/// </summary>
|
||||
public class ExternalUrlProvider : IExternalUrlProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name => Iqiyi.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string> GetExternalUrls(BaseItem item)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case Season season:
|
||||
if (item.TryGetProviderId(Iqiyi.ScraperProviderId, out var externalId))
|
||||
{
|
||||
yield return $"https://www.iqiyi.com/v_{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
case Episode episode:
|
||||
if (item.TryGetProviderId(Iqiyi.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return $"https://www.iqiyi.com/v_{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
case Movie:
|
||||
if (item.TryGetProviderId(Iqiyi.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return $"https://www.iqiyi.com/v_{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Iqiyi.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.iqiyi.com/v_{0}.html";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Movie;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Iqiyi.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.iqiyi.com/v_{0}.html";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Season;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "#";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Episode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId;
|
||||
|
||||
/// <summary>
|
||||
/// External URLs for Danmu.
|
||||
/// </summary>
|
||||
public class ExternalUrlProvider : IExternalUrlProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name => Mgtv.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string> GetExternalUrls(BaseItem item)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case Season season:
|
||||
if (item.TryGetProviderId(Mgtv.ScraperProviderId, out var externalId))
|
||||
{
|
||||
yield return $"https://www.mgtv.com/h/{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
case Episode episode:
|
||||
if (item.TryGetProviderId(Mgtv.ScraperProviderId, out externalId))
|
||||
{
|
||||
if (episode.Season?.TryGetProviderId(Mgtv.ScraperProviderId, out var seasonExternalId) == true)
|
||||
{
|
||||
yield return $"https://www.mgtv.com/b/{seasonExternalId}/{externalId}.html";
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return "#";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case Movie:
|
||||
if (item.TryGetProviderId(Mgtv.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return $"https://www.mgtv.com/h/{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.mgtv.com/h/{0}.html";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Movie;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.mgtv.com/h/{0}.html";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Season;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Tencent.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "#";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Episode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Tencent.ExternalId;
|
||||
|
||||
/// <summary>
|
||||
/// External URLs for Danmu.
|
||||
/// </summary>
|
||||
public class ExternalUrlProvider : IExternalUrlProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name => Tencent.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string> GetExternalUrls(BaseItem item)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case Season season:
|
||||
if (item.TryGetProviderId(Tencent.ScraperProviderId, out var externalId))
|
||||
{
|
||||
yield return $"https://v.qq.com/x/cover/{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
case Episode episode:
|
||||
if (item.TryGetProviderId(Tencent.ScraperProviderId, out externalId))
|
||||
{
|
||||
if (episode.Season?.TryGetProviderId(Tencent.ScraperProviderId, out var seasonExternalId) == true)
|
||||
{
|
||||
yield return $"https://v.qq.com/x/cover/{seasonExternalId}/{externalId}.html";
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return "#";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case Movie:
|
||||
if (item.TryGetProviderId(Tencent.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return $"https://v.qq.com/x/cover/{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Tencent.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://v.qq.com/x/cover/{0}.html";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Movie;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Tencent.ExternalId
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://v.qq.com/x/cover/{0}.html";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Season;
|
||||
}
|
||||
|
||||
@@ -21,10 +21,7 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Youku.ExternalId
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://v.youku.com/v_show/id_{0}.html";
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Episode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Youku.ExternalId;
|
||||
|
||||
/// <summary>
|
||||
/// External URLs for Danmu.
|
||||
/// </summary>
|
||||
public class ExternalUrlProvider : IExternalUrlProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name => Youku.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string> GetExternalUrls(BaseItem item)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case Season season:
|
||||
if (item.TryGetProviderId(Youku.ScraperProviderId, out var externalId))
|
||||
{
|
||||
yield return $"https://v.youku.com/v_nextstage/id_{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
case Episode episode:
|
||||
if (item.TryGetProviderId(Youku.ScraperProviderId, out externalId))
|
||||
{
|
||||
var decodeExternalId = HttpUtility.UrlDecode(externalId).Replace("||", "==");
|
||||
yield return $"https://v.youku.com/v_show/id_{decodeExternalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
case Movie:
|
||||
if (item.TryGetProviderId(Youku.ScraperProviderId, out externalId))
|
||||
{
|
||||
yield return $"https://v.youku.com/v_nextstage/id_{externalId}.html";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Jellyfin.Plugin.Danmu.Scrapers;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu
|
||||
{
|
||||
@@ -27,7 +28,7 @@ namespace Jellyfin.Plugin.Danmu
|
||||
});
|
||||
serviceCollection.AddSingleton((ctx) =>
|
||||
{
|
||||
return new LibraryManagerEventsHelper(ctx.GetRequiredService<ILibraryManager>(), ctx.GetRequiredService<ILoggerFactory>(), ctx.GetRequiredService<Jellyfin.Plugin.Danmu.Core.IFileSystem>(), ctx.GetRequiredService<ScraperManager>());
|
||||
return new LibraryManagerEventsHelper(ctx.GetRequiredService<IItemRepository>(), ctx.GetRequiredService<ILibraryManager>(), ctx.GetRequiredService<ILoggerFactory>(), ctx.GetRequiredService<Jellyfin.Plugin.Danmu.Core.IFileSystem>(), ctx.GetRequiredService<ScraperManager>());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ ass格式:
|
||||
|
||||
1. Clone or download this repository
|
||||
|
||||
2. Ensure you have .NET Core SDK 8.0 setup and installed
|
||||
2. Ensure you have .NET Core SDK 9.0 setup and installed
|
||||
|
||||
3. Build plugin with following command.
|
||||
|
||||
@@ -83,7 +83,7 @@ dotnet publish --configuration=Release Jellyfin.Plugin.Danmu/Jellyfin.Plugin.Dan
|
||||
|
||||
1. Build the plugin
|
||||
|
||||
2. Create a folder, like `danmu` and copy `./Jellyfin.Plugin.Danmu/bin/Release/net8.0/Jellyfin.Plugin.Danmu.dll` into it
|
||||
2. Create a folder, like `danmu` and copy `./Jellyfin.Plugin.Danmu/bin/Release/net9.0/Jellyfin.Plugin.Danmu.dll` into it
|
||||
|
||||
3. Move folder `danmu` to jellyfin `data/plugins` folder
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ def generate_version(filepath, version, changelog):
|
||||
return {
|
||||
'version': f"{version}.0",
|
||||
'changelog': changelog,
|
||||
'targetAbi': '10.9.0.0',
|
||||
'targetAbi': '10.11.0.0',
|
||||
'sourceUrl': f'https://github.com/cxfksword/jellyfin-plugin-danmu/releases/download/v{version}/danmu_{version}.0.zip',
|
||||
'checksum': md5sum(filepath),
|
||||
'timestamp': datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
Reference in New Issue
Block a user