mirror of
https://github.com/cxfksword/jellyfin-plugin-danmu.git
synced 2026-02-02 17:59:58 +08:00
78
Jellyfin.Plugin.Danmu.Test/MgtvApiTest.cs
Normal file
78
Jellyfin.Plugin.Danmu.Test/MgtvApiTest.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Plugin.Danmu.Model;
|
||||
using Jellyfin.Plugin.Danmu.Scrapers.Mgtv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Test
|
||||
{
|
||||
|
||||
[TestClass]
|
||||
public class MgtvApiTest : BaseTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestSearch()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var keyword = "大侦探";
|
||||
var api = new MgtvApi(loggerFactory);
|
||||
var result = await api.SearchAsync(keyword, CancellationToken.None);
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void TestGetVideo()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = "310102";
|
||||
var api = new MgtvApi(loggerFactory);
|
||||
var result = await api.GetVideoAsync(id, CancellationToken.None);
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void TestGetDanmu()
|
||||
{
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var cid = "514446";
|
||||
var vid = "18053294";
|
||||
var api = new MgtvApi(loggerFactory);
|
||||
var result = await api.GetDanmuContentAsync(cid, vid, CancellationToken.None);
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
150
Jellyfin.Plugin.Danmu.Test/MgtvTest.cs
Normal file
150
Jellyfin.Plugin.Danmu.Test/MgtvTest.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Plugin.Danmu.Model;
|
||||
using Jellyfin.Plugin.Danmu.Scrapers;
|
||||
using Jellyfin.Plugin.Danmu.Scrapers.Mgtv;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Test
|
||||
{
|
||||
|
||||
[TestClass]
|
||||
public class MgtvTest : BaseTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestAddMovie()
|
||||
{
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var scraperManager = new ScraperManager(loggerFactory);
|
||||
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory, libraryManagerStub.Object));
|
||||
|
||||
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 item = new Movie
|
||||
{
|
||||
Name = "虚颜"
|
||||
};
|
||||
|
||||
var list = new List<LibraryEvent>();
|
||||
list.Add(new LibraryEvent { Item = item, EventType = EventType.Add });
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await libraryManagerEventsHelper.ProcessQueuedMovieEvents(list, EventType.Add);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void TestUpdateMovie()
|
||||
{
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var scraperManager = new ScraperManager(loggerFactory);
|
||||
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory, libraryManagerStub.Object));
|
||||
|
||||
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 item = new Movie
|
||||
{
|
||||
Name = "虚颜",
|
||||
ProviderIds = new Dictionary<string, string>() { { Mgtv.ScraperProviderId, "519236" } },
|
||||
};
|
||||
|
||||
var list = new List<LibraryEvent>();
|
||||
list.Add(new LibraryEvent { Item = item, EventType = EventType.Update });
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await libraryManagerEventsHelper.ProcessQueuedMovieEvents(list, EventType.Update);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void TestAddSeason()
|
||||
{
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var scraperManager = new ScraperManager(loggerFactory);
|
||||
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory, libraryManagerStub.Object));
|
||||
|
||||
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 item = new Season
|
||||
{
|
||||
Name = "大侦探 第八季",
|
||||
ProductionYear = 2023,
|
||||
};
|
||||
|
||||
var list = new List<LibraryEvent>();
|
||||
list.Add(new LibraryEvent { Item = item, EventType = EventType.Add });
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await libraryManagerEventsHelper.ProcessQueuedSeasonEvents(list, EventType.Add);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestGetMedia()
|
||||
{
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var libraryManagerStub = new Mock<ILibraryManager>();
|
||||
var api = new Mgtv(loggerFactory, libraryManagerStub.Object);
|
||||
var media = await api.GetMedia(new Season(), "514446");
|
||||
Console.WriteLine(media);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -30,15 +30,7 @@ namespace Jellyfin.Plugin.Danmu.Core.Extensions
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Int64 ToInt64(this string s)
|
||||
{
|
||||
if (Int64.TryParse(s, out var val))
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static float ToFloat(this string s)
|
||||
{
|
||||
|
||||
@@ -25,17 +25,9 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Iqiyi;
|
||||
|
||||
public class IqiyiApi : AbstractApi
|
||||
{
|
||||
private static readonly object _lock = new object();
|
||||
private static readonly Regex yearReg = new Regex(@"[12][890][0-9][0-9]", RegexOptions.Compiled);
|
||||
private static readonly Regex moviesReg = new Regex(@"<a.*?h5-show-card.*?>([\w\W]+?)</a>", RegexOptions.Compiled);
|
||||
private static readonly Regex trackInfoReg = new Regex(@"data-trackinfo=""(\{[\w\W]+?\})""", RegexOptions.Compiled);
|
||||
private static readonly Regex featureReg = new Regex(@"<div.*?show-feature.*?>([\w\W]+?)</div>", RegexOptions.Compiled);
|
||||
private static readonly Regex unusedReg = new Regex(@"\[.+?\]|\(.+?\)|【.+?】", RegexOptions.Compiled);
|
||||
private static readonly Regex regTvId = new Regex(@"""tvid"":(\d+?),", RegexOptions.Compiled);
|
||||
|
||||
|
||||
private DateTime lastRequestTime = DateTime.Now.AddDays(-1);
|
||||
|
||||
private TimeLimiter _timeConstraint = TimeLimiter.GetFromMaxCountByInterval(1, TimeSpan.FromMilliseconds(1000));
|
||||
private TimeLimiter _delayExecuteConstraint = TimeLimiter.GetFromMaxCountByInterval(1, TimeSpan.FromMilliseconds(100));
|
||||
|
||||
|
||||
53
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Entity/MgtvComment.cs
Normal file
53
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Entity/MgtvComment.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvComment
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
[JsonPropertyName("ids")]
|
||||
public string Ids { get; set; }
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
[JsonPropertyName("uid")]
|
||||
public long Uid { get; set; }
|
||||
[JsonPropertyName("uuid")]
|
||||
public string Uuid { get; set; }
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; }
|
||||
[JsonPropertyName("time")]
|
||||
public int Time { get; set; }
|
||||
[JsonPropertyName("v2_color")]
|
||||
public MgtvCommentColor Color { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class MgtvCommentColor
|
||||
{
|
||||
[JsonPropertyName("color_left")]
|
||||
public MgtvCommentColorRGB ColorLeft { get; set; }
|
||||
[JsonPropertyName("color_right")]
|
||||
public MgtvCommentColorRGB ColorRight { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class MgtvCommentColorRGB
|
||||
{
|
||||
[JsonPropertyName("r")]
|
||||
public int R { get; set; }
|
||||
[JsonPropertyName("g")]
|
||||
public int G { get; set; }
|
||||
[JsonPropertyName("b")]
|
||||
public int B { get; set; }
|
||||
|
||||
public uint HexNumber
|
||||
{
|
||||
get
|
||||
{
|
||||
return (uint)((R << 16) | (G << 8) | (B));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvCommentResult
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public MgtvCommentData Data { get; set; }
|
||||
}
|
||||
|
||||
public class MgtvCommentData
|
||||
{
|
||||
[JsonPropertyName("cdn_list")]
|
||||
public string CdnList { get; set; }
|
||||
[JsonPropertyName("cdn_version")]
|
||||
public string CdnVersion { get; set; }
|
||||
|
||||
public string CdnHost
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(CdnList))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return CdnList.Split(",").First();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvCommentSegmentResult
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public MgtvCommentSegmentData Data { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class MgtvCommentSegmentData
|
||||
{
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; set; }
|
||||
|
||||
[JsonPropertyName("items")]
|
||||
public List<MgtvComment> Items { get; set; }
|
||||
}
|
||||
19
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Entity/MgtvEpisode.cs
Normal file
19
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Entity/MgtvEpisode.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvEpisode
|
||||
{
|
||||
[JsonPropertyName("src_clip_id")]
|
||||
public string SourceClipId { get; set; }
|
||||
[JsonPropertyName("clip_id")]
|
||||
public string ClipId { get; set; }
|
||||
[JsonPropertyName("t1")]
|
||||
public string Title { get; set; }
|
||||
[JsonPropertyName("time")]
|
||||
public string Time { get; set; }
|
||||
[JsonPropertyName("video_id")]
|
||||
public string VideoId { get; set; }
|
||||
[JsonPropertyName("contentType")]
|
||||
public string ContentType { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvEpisodeListRequest
|
||||
{
|
||||
[JsonPropertyName("page_params")]
|
||||
public MgtvPageParams PageParams { get; set; }
|
||||
}
|
||||
|
||||
public class MgtvPageParams
|
||||
{
|
||||
[JsonPropertyName("page_type")]
|
||||
public string PageType { get; set; } = "detail_operation";
|
||||
[JsonPropertyName("page_id")]
|
||||
public string PageId { get; set; } = "vsite_episode_list";
|
||||
[JsonPropertyName("id_type")]
|
||||
public string IdType { get; set; } = "1";
|
||||
[JsonPropertyName("page_size")]
|
||||
public string PageSize { get; set; } = "100";
|
||||
[JsonPropertyName("cid")]
|
||||
public string Cid { get; set; }
|
||||
[JsonPropertyName("lid")]
|
||||
public string Lid { get; set; } = "0";
|
||||
[JsonPropertyName("req_from")]
|
||||
public string ReqFrom { get; set; } = "web_mobile";
|
||||
[JsonPropertyName("page_context")]
|
||||
public string PageContext { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvEpisodeListResult
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public MgtvEpisodeListData Data { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class MgtvEpisodeListData
|
||||
{
|
||||
[JsonPropertyName("total")]
|
||||
public int Total { get; set; }
|
||||
[JsonPropertyName("tab_m")]
|
||||
public List<MgtvEpisodeListTab> Tabs { get; set; }
|
||||
[JsonPropertyName("pageNo")]
|
||||
public int PageNo { get; set; }
|
||||
[JsonPropertyName("pageSize")]
|
||||
public int PageSize { get; set; }
|
||||
|
||||
[JsonPropertyName("list")]
|
||||
public List<MgtvEpisode> List { get; set; }
|
||||
}
|
||||
|
||||
public class MgtvEpisodeListTab
|
||||
{
|
||||
[JsonPropertyName("m")]
|
||||
public string Month { get; set; }
|
||||
}
|
||||
|
||||
118
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Entity/MgtvSearchResult.cs
Normal file
118
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Entity/MgtvSearchResult.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System.Linq;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Jellyfin.Plugin.Danmu.Core.Extensions;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvSearchResult
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public MgtvSearchData Data { get; set; }
|
||||
}
|
||||
|
||||
public class MgtvSearchData
|
||||
{
|
||||
[JsonPropertyName("contents")]
|
||||
public List<MgtvSearchContent> Contents { get; set; }
|
||||
}
|
||||
|
||||
public class MgtvSearchContent
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
[JsonPropertyName("data")]
|
||||
public List<MgtvSearchItem> Data { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class MgtvSearchItem
|
||||
{
|
||||
private static readonly Regex regHtml = new Regex(@"<.+?>", RegexOptions.Compiled);
|
||||
private static readonly Regex regId = new Regex(@"\/b\/(\d+)\/(\d+)", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex regYear = new Regex(@"[12][890][0-9][0-9]", RegexOptions.Compiled);
|
||||
|
||||
[JsonPropertyName("jumpKind")]
|
||||
public string JumpKind { get; set; }
|
||||
[JsonPropertyName("desc")]
|
||||
public List<string> Desc { get; set; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; set; }
|
||||
|
||||
private string _title = string.Empty;
|
||||
[JsonPropertyName("title")]
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return regHtml.Replace(_title, "");
|
||||
}
|
||||
set
|
||||
{
|
||||
_title = value;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
|
||||
public string Id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Url))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var match = regId.Match(Url);
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string TypeName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Desc == null || Desc.Count <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return Desc.First().Split("/").First().Replace("类型:", "").Trim();
|
||||
}
|
||||
}
|
||||
|
||||
public int? Year
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Desc == null || Desc.Count <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = regYear.Match(Desc.First());
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Value.ToInt();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
62
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Entity/MgtvVideo.cs
Normal file
62
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Entity/MgtvVideo.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Jellyfin.Plugin.Danmu.Core.Extensions;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvVideo
|
||||
{
|
||||
private static readonly Regex regHtml = new Regex(@"<.+?>", RegexOptions.Compiled);
|
||||
|
||||
[JsonPropertyName("videoId")]
|
||||
public string Id { get; set; }
|
||||
[JsonPropertyName("videoType")]
|
||||
public int VideoType { get; set; }
|
||||
[JsonPropertyName("typeName")]
|
||||
public string TypeName { get; set; }
|
||||
private string _title = string.Empty;
|
||||
[JsonPropertyName("title")]
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return regHtml.Replace(_title, "");
|
||||
}
|
||||
set
|
||||
{
|
||||
_title = value;
|
||||
}
|
||||
}
|
||||
[JsonPropertyName("time")]
|
||||
public string Time { get; set; }
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
[JsonIgnore]
|
||||
public List<MgtvEpisode> EpisodeList { get; set; }
|
||||
|
||||
public int TotalMinutes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Time))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var arr = Time.Split(":");
|
||||
if (arr.Length == 2)
|
||||
{
|
||||
return (int)Math.Ceiling((arr[0].ToDouble() * 60 + arr[1].ToDouble()) / 60);
|
||||
}
|
||||
if (arr.Length == 3)
|
||||
{
|
||||
return (int)Math.Ceiling((arr[0].ToDouble() * 3600 + arr[1].ToDouble() * 60 + arr[2].ToDouble()) / 60);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
|
||||
public class MgtvVideoInfoResult
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public MgtvVideoInfoData Data { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class MgtvVideoInfoData
|
||||
{
|
||||
[JsonPropertyName("info")]
|
||||
public MgtvVideo Info { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class EpisodeExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => Mgtv.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => Mgtv.ScraperProviderId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "#";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Episode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class MovieExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => Mgtv.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => Mgtv.ScraperProviderId;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId
|
||||
{
|
||||
|
||||
/// <inheritdoc />
|
||||
public class SeasonExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => Mgtv.ScraperProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => Mgtv.ScraperProviderId;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
}
|
||||
240
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Mgtv.cs
Normal file
240
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Mgtv.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System.Web;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Plugin.Danmu.Core;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Jellyfin.Plugin.Danmu.Scrapers.Entity;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using Jellyfin.Plugin.Danmu.Core.Extensions;
|
||||
using System.Text.Json;
|
||||
using Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv;
|
||||
|
||||
public class Mgtv : AbstractScraper
|
||||
{
|
||||
public const string ScraperProviderName = "芒果TV";
|
||||
public const string ScraperProviderId = "MgtvID";
|
||||
|
||||
private readonly MgtvApi _api;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public Mgtv(ILoggerFactory logManager, ILibraryManager libraryManager)
|
||||
: base(logManager.CreateLogger<Mgtv>())
|
||||
{
|
||||
_api = new MgtvApi(logManager);
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public override int DefaultOrder => 6;
|
||||
|
||||
public override bool DefaultEnable => false;
|
||||
|
||||
public override string Name => "芒果TV";
|
||||
|
||||
public override string ProviderName => ScraperProviderName;
|
||||
|
||||
public override string ProviderId => ScraperProviderId;
|
||||
|
||||
|
||||
|
||||
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.TypeName != "电影")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isMovieItemType && video.TypeName == "电影")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检测标题是否相似(越大越相似)
|
||||
var score = searchName.Distance(title);
|
||||
if (score < 0.7)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(new ScraperSearchInfo()
|
||||
{
|
||||
Id = $"{videoId}",
|
||||
Name = title,
|
||||
Category = video.TypeName,
|
||||
Year = pubYear,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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.TypeName != "电影")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isMovieItemType && video.TypeName == "电影")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检测标题是否相似(越大越相似)
|
||||
var score = searchName.Distance(title);
|
||||
if (score < 0.7)
|
||||
{
|
||||
log.LogInformation("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检测年份是否一致
|
||||
var itemPubYear = item.ProductionYear ?? 0;
|
||||
if (itemPubYear > 0 && pubYear > 0 && itemPubYear != pubYear)
|
||||
{
|
||||
log.LogInformation("[{0}] 发行年份不一致,忽略处理. year: {1} jellyfin: {2}", title, pubYear, itemPubYear);
|
||||
continue;
|
||||
}
|
||||
|
||||
return video.Id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public override async Task<ScraperMedia?> GetMedia(BaseItem item, string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var isMovieItemType = item is MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
var video = await _api.GetVideoAsync(id, CancellationToken.None).ConfigureAwait(false);
|
||||
if (video == null)
|
||||
{
|
||||
log.LogInformation("[{0}]获取不到视频信息:id={1}", this.Name, id);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var media = new ScraperMedia();
|
||||
media.Id = id;
|
||||
if (isMovieItemType && video.EpisodeList != null && video.EpisodeList.Count > 0)
|
||||
{
|
||||
media.CommentId = $"{id},{video.EpisodeList[0].VideoId}";
|
||||
}
|
||||
if (video.EpisodeList != null && video.EpisodeList.Count > 0)
|
||||
{
|
||||
foreach (var ep in video.EpisodeList)
|
||||
{
|
||||
media.Episodes.Add(new ScraperEpisode() { Id = $"{ep.VideoId}", CommentId = $"{id},{ep.VideoId}" });
|
||||
}
|
||||
}
|
||||
|
||||
return media;
|
||||
}
|
||||
|
||||
public override async Task<ScraperEpisode?> GetMediaEpisode(BaseItem item, string id)
|
||||
{
|
||||
|
||||
var isMovieItemType = item is MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
if (isMovieItemType)
|
||||
{
|
||||
var video = await _api.GetVideoAsync(id, CancellationToken.None).ConfigureAwait(false);
|
||||
if (video == null || video.EpisodeList == null || video.EpisodeList.Count <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ScraperEpisode() { Id = id, CommentId = $"{id},{video.EpisodeList[0].VideoId}" };
|
||||
}
|
||||
|
||||
|
||||
// 从季信息元数据中,获取cid值
|
||||
// 没SXX季文件夹时,GetParent是Series,有时,GetParent是Season,所以需要通过seasonId中获取
|
||||
var seasonId = ((MediaBrowser.Controller.Entities.TV.Episode)item).FindSeasonId();
|
||||
var season = _libraryManager.GetItemById(seasonId);
|
||||
season.ProviderIds.TryGetValue(ScraperProviderId, out var cid);
|
||||
return new ScraperEpisode() { Id = id, CommentId = $"{cid},{id}" };
|
||||
}
|
||||
|
||||
public override async Task<ScraperDanmaku?> GetDanmuContent(BaseItem item, string commentId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(commentId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var arr = commentId.Split(",");
|
||||
if (arr.Length < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var cid = arr[0];
|
||||
var vid = arr[1];
|
||||
if (string.IsNullOrEmpty(cid) || string.IsNullOrEmpty(vid))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var comments = await _api.GetDanmuContentAsync(cid, vid, CancellationToken.None).ConfigureAwait(false);
|
||||
var danmaku = new ScraperDanmaku();
|
||||
danmaku.ChatId = vid.ToLong();
|
||||
danmaku.ChatServer = "galaxy.bz.mgtv.com";
|
||||
foreach (var comment in comments)
|
||||
{
|
||||
|
||||
var danmakuText = new ScraperDanmakuText();
|
||||
danmakuText.Progress = comment.Time;
|
||||
danmakuText.Mode = 1;
|
||||
danmakuText.MidHash = $"[mgtv]{comment.Uuid}";
|
||||
danmakuText.Id = comment.Id;
|
||||
danmakuText.Content = comment.Content;
|
||||
if (comment.Color != null && comment.Color.ColorLeft != null)
|
||||
{
|
||||
danmakuText.Color = comment.Color.ColorLeft.HexNumber;
|
||||
}
|
||||
|
||||
danmaku.Items.Add(danmakuText);
|
||||
}
|
||||
|
||||
return danmaku;
|
||||
}
|
||||
|
||||
|
||||
private string NormalizeSearchName(string name)
|
||||
{
|
||||
// 去掉可能存在的季名称
|
||||
return Regex.Replace(name, @"\s*第.季", "");
|
||||
}
|
||||
}
|
||||
184
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/MgtvApi.cs
Normal file
184
Jellyfin.Plugin.Danmu/Scrapers/Mgtv/MgtvApi.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using ComposableAsync;
|
||||
using Jellyfin.Plugin.Danmu.Core.Extensions;
|
||||
using Jellyfin.Plugin.Danmu.Scrapers.Entity;
|
||||
using Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Entity;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RateLimiter;
|
||||
|
||||
namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv;
|
||||
|
||||
public class MgtvApi : AbstractApi
|
||||
{
|
||||
private TimeLimiter _timeConstraint = TimeLimiter.GetFromMaxCountByInterval(1, TimeSpan.FromMilliseconds(1000));
|
||||
private TimeLimiter _delayExecuteConstraint = TimeLimiter.GetFromMaxCountByInterval(1, TimeSpan.FromMilliseconds(100));
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MgtvApi"/> class.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public MgtvApi(ILoggerFactory loggerFactory)
|
||||
: base(loggerFactory.CreateLogger<MgtvApi>())
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add("referer", "https://www.mgtv.com/");
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<MgtvSearchItem>> SearchAsync(string keyword, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
return new List<MgtvSearchItem>();
|
||||
}
|
||||
|
||||
var cacheKey = $"search_{keyword}";
|
||||
var expiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) };
|
||||
if (_memoryCache.TryGetValue<List<MgtvSearchItem>>(cacheKey, out var cacheValue))
|
||||
{
|
||||
return cacheValue;
|
||||
}
|
||||
|
||||
await this.LimitRequestFrequently();
|
||||
|
||||
keyword = HttpUtility.UrlEncode(keyword);
|
||||
var url = $"https://mobileso.bz.mgtv.com/msite/search/v2?q={keyword}&pc=30&pn=1&sort=-99&ty=0&du=0&pt=0&corr=1&abroad=0&_support=10000000000000000";
|
||||
var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = new List<MgtvSearchItem>();
|
||||
var searchResult = await response.Content.ReadFromJsonAsync<MgtvSearchResult>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||
if (searchResult != null && searchResult.Data != null && searchResult.Data.Contents != null)
|
||||
{
|
||||
foreach (var content in searchResult.Data.Contents)
|
||||
{
|
||||
if (content.Type != "media")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var item in content.Data)
|
||||
{
|
||||
|
||||
|
||||
result.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_memoryCache.Set<List<MgtvSearchItem>>(cacheKey, result, expiredOption);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<MgtvVideo?> GetVideoAsync(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var cacheKey = $"media_{id}";
|
||||
var expiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) };
|
||||
if (_memoryCache.TryGetValue<MgtvVideo?>(cacheKey, out var video))
|
||||
{
|
||||
return video;
|
||||
}
|
||||
|
||||
var month = "";
|
||||
var idx = 0;
|
||||
var total = 0;
|
||||
var videoInfo = new MgtvVideo() { Id = id };
|
||||
var list = new List<MgtvEpisode>();
|
||||
do
|
||||
{
|
||||
var url = $"https://pcweb.api.mgtv.com/variety/showlist?allowedRC=1&collection_id={id}&month={month}&page=1&_support=10000000";
|
||||
var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<MgtvEpisodeListResult>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||
if (result != null && result.Data != null && result.Data.List != null)
|
||||
{
|
||||
list.AddRange(result.Data.List.Where(x => x.SourceClipId == id));
|
||||
|
||||
total = result.Data.Tabs.Count;
|
||||
idx++;
|
||||
month = idx < total ? result.Data.Tabs[idx].Month : "";
|
||||
}
|
||||
|
||||
// 等待一段时间避免api请求太快
|
||||
await _delayExecuteConstraint;
|
||||
} while (idx < total && !string.IsNullOrEmpty(month));
|
||||
|
||||
videoInfo.EpisodeList = list.OrderBy(x => x.VideoId).ToList();
|
||||
_memoryCache.Set<MgtvVideo?>(cacheKey, videoInfo, expiredOption);
|
||||
return videoInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task<List<MgtvComment>> GetDanmuContentAsync(string cid, string vid, CancellationToken cancellationToken)
|
||||
{
|
||||
var danmuList = new List<MgtvComment>();
|
||||
if (string.IsNullOrEmpty(vid))
|
||||
{
|
||||
return danmuList;
|
||||
}
|
||||
|
||||
// 取视频总时间
|
||||
var url = $"https://pcweb.api.mgtv.com/video/info?allowedRC=1&cid={cid}&vid={vid}&change=3&datatype=1&type=1&_support=10000000";
|
||||
var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var videoInfoResult = await response.Content.ReadFromJsonAsync<MgtvVideoInfoResult>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||
if (videoInfoResult == null || videoInfoResult.Data == null || videoInfoResult.Data.Info == null)
|
||||
{
|
||||
return danmuList;
|
||||
}
|
||||
|
||||
url = $"https://galaxy.bz.mgtv.com/getctlbarrage?version=3.0.0&vid={vid}&abroad=0&pid=0&os&uuid&deviceid=00000000-0000-0000-0000-000000000000&cid={cid}&ticket&mac&platform=0&appVersion=3.0.0&reqtype=form-post&allowedRC=1";
|
||||
response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<MgtvCommentResult>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||
if (result != null && result.Data != null)
|
||||
{
|
||||
var idx = 0;
|
||||
var total = videoInfoResult.Data.Info.TotalMinutes;
|
||||
do
|
||||
{
|
||||
var segmentUrl = $"https://{result.Data.CdnHost}/{result.Data.CdnVersion}/{idx}.json";
|
||||
var segmentResponse = await httpClient.GetAsync(segmentUrl, cancellationToken).ConfigureAwait(false);
|
||||
segmentResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var segmentResult = await segmentResponse.Content.ReadFromJsonAsync<MgtvCommentSegmentResult>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||
if (segmentResult != null && segmentResult.Data != null && segmentResult.Data.Items != null)
|
||||
{
|
||||
// 60秒每segment,为避免弹幕太大,从中间隔抽取最大60秒200条弹幕
|
||||
danmuList.AddRange(segmentResult.Data.Items.ExtractToNumber(200));
|
||||
}
|
||||
|
||||
idx++;
|
||||
// 等待一段时间避免api请求太快
|
||||
await _delayExecuteConstraint;
|
||||
} while (idx < total);
|
||||
}
|
||||
|
||||
return danmuList;
|
||||
}
|
||||
|
||||
protected async Task LimitRequestFrequently()
|
||||
{
|
||||
await this._timeConstraint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class Tencent : AbstractScraper
|
||||
_api = new TencentApi(logManager);
|
||||
}
|
||||
|
||||
public override int DefaultOrder => 3;
|
||||
public override int DefaultOrder => 5;
|
||||
|
||||
public override bool DefaultEnable => false;
|
||||
|
||||
|
||||
@@ -21,23 +21,10 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Tencent;
|
||||
|
||||
public class TencentApi : AbstractApi
|
||||
{
|
||||
private static readonly object _lock = new object();
|
||||
private static readonly Regex yearReg = new Regex(@"[12][890][0-9][0-9]", RegexOptions.Compiled);
|
||||
private static readonly Regex moviesReg = new Regex(@"<a.*?h5-show-card.*?>([\w\W]+?)</a>", RegexOptions.Compiled);
|
||||
private static readonly Regex trackInfoReg = new Regex(@"data-trackinfo=""(\{[\w\W]+?\})""", RegexOptions.Compiled);
|
||||
private static readonly Regex featureReg = new Regex(@"<div.*?show-feature.*?>([\w\W]+?)</div>", RegexOptions.Compiled);
|
||||
private static readonly Regex unusedReg = new Regex(@"\[.+?\]|\(.+?\)|【.+?】", RegexOptions.Compiled);
|
||||
|
||||
private DateTime lastRequestTime = DateTime.Now.AddDays(-1);
|
||||
|
||||
private TimeLimiter _timeConstraint = TimeLimiter.GetFromMaxCountByInterval(1, TimeSpan.FromMilliseconds(1000));
|
||||
private TimeLimiter _delayExecuteConstraint = TimeLimiter.GetFromMaxCountByInterval(1, TimeSpan.FromMilliseconds(100));
|
||||
|
||||
|
||||
protected string _cna = string.Empty;
|
||||
protected string _token = string.Empty;
|
||||
protected string _tokenEnc = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TencentApi"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -21,14 +21,9 @@ namespace Jellyfin.Plugin.Danmu.Scrapers.Youku;
|
||||
|
||||
public class YoukuApi : AbstractApi
|
||||
{
|
||||
private static readonly object _lock = new object();
|
||||
private static readonly Regex yearReg = new Regex(@"[12][890][0-9][0-9]", RegexOptions.Compiled);
|
||||
private static readonly Regex moviesReg = new Regex(@"<a.*?h5-show-card.*?>([\w\W]+?)</a>", RegexOptions.Compiled);
|
||||
private static readonly Regex trackInfoReg = new Regex(@"data-trackinfo=""(\{[\w\W]+?\})""", RegexOptions.Compiled);
|
||||
private static readonly Regex featureReg = new Regex(@"<div.*?show-feature.*?>([\w\W]+?)</div>", RegexOptions.Compiled);
|
||||
private static readonly Regex unusedReg = new Regex(@"\[.+?\]|\(.+?\)|【.+?】", RegexOptions.Compiled);
|
||||
|
||||
private DateTime lastRequestTime = DateTime.Now.AddDays(-1);
|
||||
|
||||
private TimeLimiter _timeConstraint = TimeLimiter.GetFromMaxCountByInterval(1, TimeSpan.FromMilliseconds(1000));
|
||||
private TimeLimiter _delayExecuteConstraint = TimeLimiter.GetFromMaxCountByInterval(1, TimeSpan.FromMilliseconds(100));
|
||||
|
||||
10
README.md
10
README.md
@@ -4,7 +4,7 @@
|
||||
[](https://github.com/cxfksword/jellyfin-plugin-danmu/releases)
|
||||
[](https://github.com/cxfksword/jellyfin-plugin-danmu/main/LICENSE)
|
||||
|
||||
jellyfin弹幕自动下载插件,已支持的弹幕来源:b站,弹弹play,优酷,爱奇艺,腾讯视频。
|
||||
jellyfin弹幕自动下载插件,已支持的弹幕来源:b站,弹弹play,优酷,爱奇艺,腾讯视频,芒果TV。
|
||||
|
||||
支持功能:
|
||||
|
||||
@@ -31,16 +31,14 @@ jellyfin弹幕自动下载插件,已支持的弹幕来源:b站,弹弹play
|
||||
2. 进入`控制台 -> 媒体库`,点击任一媒体库进入配置页,在最下面的`字幕下载`选项中勾选**Danmu**,并保存
|
||||
|
||||
<img src="doc/tutorial.png" width="720px" />
|
||||
|
||||
假如想修正匹配错误的弹幕,请在电影或剧集中使用jellyfin的**修改字幕**功能
|
||||
|
||||
3. 新加入的影片会自动获取弹幕(只匹配番剧和电影视频),旧影片可以通过计划任务**扫描媒体库匹配弹幕**手动执行获取
|
||||
4. 可以在元数据中手动指定匹配的视频ID,如播放链接`https://www.bilibili.com/bangumi/play/ep682965`,对应的视频ID就是`682965`
|
||||
5. 对于电视剧和动画,可以在元数据中指定季ID,如播放链接`https://www.bilibili.com/bangumi/play/ss1564`,对应的季ID就是`1564`,只要集数和b站的集数的一致,并正确填写了集号,每季视频的弹幕会自动获取
|
||||
4. 假如弹幕匹配错误,请在电影或剧集中使用**修改字幕**功能搜索修正
|
||||
5. 对于电视剧或动画,需要保证每季视频集数一致,并正确填写集号,这样每季视频的弹幕才会自动获取
|
||||
6. 同时生成ass弹幕,需要在插件配置中打开,默认是关闭的
|
||||
7. 定时更新需要自己到计划任务中添加定时时间,默认手工执行更新
|
||||
|
||||
> 电影或季元数据也支持手动指定BV号,来匹配UP主上传的视频弹幕。多P视频和剧集是按顺序一一对应匹配的,所以保证jellyfin中剧集有正确的集号很重要
|
||||
> B站电影或季元数据也支持手动指定BV号,来匹配UP主上传的视频弹幕。多P视频和剧集是按顺序一一对应匹配的,所以保证jellyfin中剧集有正确的集号很重要
|
||||
|
||||
## 支持的api接口
|
||||
|
||||
|
||||
Reference in New Issue
Block a user