Files
jellyfin-plugin-danmu/Jellyfin.Plugin.Danmu/Scrapers/Youku/Youku.cs
2025-11-21 21:28:25 +08:00

273 lines
8.3 KiB
C#
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Web;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using Microsoft.Extensions.Logging;
using Jellyfin.Plugin.Danmu.Scrapers.Entity;
using System.Collections.Generic;
using Jellyfin.Plugin.Danmu.Core.Extensions;
using System.Text.Json;
using Jellyfin.Plugin.Danmu.Scrapers.Youku.Entity;
namespace Jellyfin.Plugin.Danmu.Scrapers.Youku;
public class Youku : AbstractScraper
{
public const string ScraperProviderName = "优酷";
public const string ScraperProviderId = "YoukuID";
private readonly YoukuApi _api;
public Youku(ILoggerFactory logManager)
: base(logManager.CreateLogger<Youku>())
{
_api = new YoukuApi(logManager);
}
public override int DefaultOrder => 3;
public override bool DefaultEnable => true;
public override string Name => "优酷";
public override string ProviderName => ScraperProviderName;
public override string ProviderId => ScraperProviderId;
public override uint HashPrefix => 12;
public override async Task<List<ScraperSearchInfo>> Search(BaseItem item)
{
var list = new List<ScraperSearchInfo>();
var isMovieItemType = item is MediaBrowser.Controller.Entities.Movies.Movie;
var searchName = this.NormalizeSearchName(item.Name);
var videos = await this._api.SearchAsync(searchName, CancellationToken.None).ConfigureAwait(false);
foreach (var video in videos)
{
var videoId = video.ID;
var title = video.Title;
var pubYear = video.Year;
if (isMovieItemType && video.Type != "movie")
{
continue;
}
if (!isMovieItemType && video.Type == "movie")
{
continue;
}
// 检测标题是否相似(越大越相似)
var score = searchName.Distance(title);
if (score < 0.7)
{
continue;
}
list.Add(new ScraperSearchInfo()
{
Id = $"{videoId}",
Name = title,
Category = video.Type == "movie" ? "电影" : "电视剧",
Year = pubYear,
EpisodeSize = video.Total,
});
}
return list;
}
public override async Task<string?> SearchMediaId(BaseItem item)
{
var isMovieItemType = item is MediaBrowser.Controller.Entities.Movies.Movie;
var searchName = this.NormalizeSearchName(item.Name);
var videos = await this._api.SearchAsync(searchName, CancellationToken.None).ConfigureAwait(false);
foreach (var video in videos)
{
var videoId = video.ID;
var title = video.Title;
var pubYear = video.Year;
if (isMovieItemType && video.Type != "movie")
{
continue;
}
if (!isMovieItemType && video.Type == "movie")
{
continue;
}
// 检测标题是否相似(越大越相似)
var score = searchName.Distance(title);
if (score < 0.7)
{
log.LogDebug("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
continue;
}
// 检测年份是否一致
var itemPubYear = item.ProductionYear ?? 0;
if (itemPubYear > 0 && pubYear > 0 && itemPubYear != pubYear)
{
log.LogDebug("[{0}] 发行年份不一致,忽略处理. Youku{1} jellyfin: {2}", title, pubYear, itemPubYear);
continue;
}
return $"{videoId}";
}
return null;
}
public override async Task<ScraperMedia?> GetMedia(BaseItem item, string id)
{
if (string.IsNullOrEmpty(id))
{
return null;
}
id = HttpUtility.UrlDecode(id);
var video = await _api.GetVideoAsync(id, CancellationToken.None).ConfigureAwait(false);
if (video == null)
{
log.LogInformation("[{0}]获取不到视频信息id={1}", this.Name, id);
return null;
}
var isMovieItemType = item is MediaBrowser.Controller.Entities.Movies.Movie;
var media = new ScraperMedia();
if (video.Videos != null && video.Videos.Count > 0)
{
foreach (var ep in video.Videos)
{
media.Episodes.Add(new ScraperEpisode() { Id = $"{ep.ID}", CommentId = $"{ep.ID}" });
}
}
// 优酷的id包括非法的=符号会导致jellyfin自动删除这里做下encode
if (isMovieItemType)
{
media.Id = HttpUtility.UrlEncode(media.Episodes.Count > 0 ? $"{media.Episodes[0].Id}" : "");
media.CommentId = media.Episodes.Count > 0 ? $"{media.Episodes[0].CommentId}" : "";
}
else
{
media.Id = HttpUtility.UrlEncode(id);
}
return media;
}
public override async Task<ScraperEpisode?> GetMediaEpisode(BaseItem item, string id)
{
if (string.IsNullOrEmpty(id))
{
return null;
}
id = HttpUtility.UrlDecode(id);
return new ScraperEpisode() { Id = id, CommentId = id };
}
public override async Task<ScraperDanmaku?> GetDanmuContent(BaseItem item, string commentId)
{
return await this.GetDanmuContentInternal(commentId, false).ConfigureAwait(false);
}
public override async Task<List<ScraperSearchInfo>> SearchForApi(string keyword)
{
var list = new List<ScraperSearchInfo>();
var videos = await this._api.SearchAsync(keyword, CancellationToken.None).ConfigureAwait(false);
foreach (var video in videos)
{
var videoId = video.ID;
var title = video.Title;
var pubYear = video.Year;
list.Add(new ScraperSearchInfo()
{
Id = $"{videoId}",
Name = title,
Category = video.Type == "movie" ? "电影" : "电视剧",
Year = pubYear,
EpisodeSize = video.Total,
});
}
return list;
}
public override async Task<List<ScraperEpisode>> GetEpisodesForApi(string id)
{
var list = new List<ScraperEpisode>();
var video = await this._api.GetVideoAsync(id, CancellationToken.None).ConfigureAwait(false);
if (video == null)
{
return list;
}
if (video.Videos != null && video.Videos.Count > 0)
{
foreach (var ep in video.Videos)
{
list.Add(new ScraperEpisode() { Id = $"{ep.ID}", CommentId = $"{ep.ID}", Title = ep.Title });
}
}
return list;
}
public override async Task<ScraperDanmaku?> DownloadDanmuForApi(string commentId)
{
return await this.GetDanmuContentInternal(commentId, true).ConfigureAwait(false);
}
private async Task<ScraperDanmaku?> GetDanmuContentInternal(string commentId, bool isParallel)
{
if (string.IsNullOrEmpty(commentId))
{
return null;
}
var comments = await _api.GetDanmuContentAsync(commentId, isParallel, 3, CancellationToken.None).ConfigureAwait(false);
var danmaku = new ScraperDanmaku
{
ChatId = 1000,
ChatServer = "acs.youku.com"
};
foreach (var comment in comments)
{
try
{
var danmakuText = new ScraperDanmakuText
{
Progress = (int)comment.Playat,
Mode = 1,
MidHash = $"[youku]{comment.Uid}",
Id = comment.ID,
Content = comment.Content
};
var property = JsonSerializer.Deserialize<YoukuCommentProperty>(comment.Propertis);
if (property != null)
{
danmakuText.Color = property.Color;
}
danmaku.Items.Add(danmakuText);
}
catch (Exception ex)
{
log.LogWarning(ex, "Failed to parse comment: {CommentId}", comment.ID);
}
}
return danmaku;
}
}