diff --git a/.github/workflows/jellyfin-unstable.yml b/.github/workflows/jellyfin-unstable.yml new file mode 100644 index 0000000..a294f0d --- /dev/null +++ b/.github/workflows/jellyfin-unstable.yml @@ -0,0 +1,33 @@ +name: 'Jellyfin (unstable) Plugin' + +on: + push: + branches: + - next + +permissions: + contents: read + packages: read + +jobs: + build: + name: Build Plugin + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8 + - name: Restore NuGet Packages + run: | + dotnet nuget add source --username ${{ github.repository_owner }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name jellyfin-unstable "https://nuget.pkg.github.com/jellyfin/index.json" + dotnet restore + - name: .NET Test + run: dotnet test --configuration Release Jellyfin.Plugin.Bangumi.Test + - name: .NET Publish + run: dotnet publish --configuration Release --output publish Jellyfin.Plugin.Bangumi + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: Jellyfin.Plugin.Bangumi + path: publish/*.dll diff --git a/Jellyfin.Plugin.Bangumi.Test/Jellyfin.Plugin.Bangumi.Test.csproj b/Jellyfin.Plugin.Bangumi.Test/Jellyfin.Plugin.Bangumi.Test.csproj index 669cb88..ab84a18 100644 --- a/Jellyfin.Plugin.Bangumi.Test/Jellyfin.Plugin.Bangumi.Test.csproj +++ b/Jellyfin.Plugin.Bangumi.Test/Jellyfin.Plugin.Bangumi.Test.csproj @@ -1,15 +1,14 @@  - net6.0 + net8.0 false - - + - + diff --git a/Jellyfin.Plugin.Bangumi.Test/Mock/MockedLibraryManager.cs b/Jellyfin.Plugin.Bangumi.Test/Mock/MockedLibraryManager.cs index b2da45a..e7ba962 100644 --- a/Jellyfin.Plugin.Bangumi.Test/Mock/MockedLibraryManager.cs +++ b/Jellyfin.Plugin.Bangumi.Test/Mock/MockedLibraryManager.cs @@ -28,7 +28,7 @@ public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, I throw new NotImplementedException(); } - public IEnumerable ResolvePaths(IEnumerable files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType = null) + public IEnumerable ResolvePaths(IEnumerable files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, CollectionType? collectionType = null) { throw new NotImplementedException(); } @@ -103,6 +103,21 @@ public BaseItem GetItemById(Guid id) throw new NotImplementedException(); } + public T GetItemById(Guid id) where T : BaseItem + { + throw new NotImplementedException(); + } + + public T GetItemById(Guid id, Guid userId) where T : BaseItem + { + throw new NotImplementedException(); + } + + public T GetItemById(Guid id, User user) where T : BaseItem + { + throw new NotImplementedException(); + } + public Task> GetIntros(BaseItem item, User user) { throw new NotImplementedException(); @@ -113,12 +128,12 @@ public void AddParts(IEnumerable rules, IEnumerable Sort(IEnumerable items, User user, IEnumerable sortBy, SortOrder sortOrder) + public IEnumerable Sort(IEnumerable items, User user, IEnumerable sortBy, SortOrder sortOrder) { throw new NotImplementedException(); } - public IEnumerable Sort(IEnumerable items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy) + public IEnumerable Sort(IEnumerable items, User user, IEnumerable<(ItemSortBy OrderBy, SortOrder SortOrder)> orderBy) { throw new NotImplementedException(); } @@ -153,22 +168,22 @@ public BaseItem RetrieveItem(Guid id) throw new NotImplementedException(); } - public string GetContentType(BaseItem item) + public CollectionType? GetContentType(BaseItem item) { throw new NotImplementedException(); } - public string GetInheritedContentType(BaseItem item) + public CollectionType? GetInheritedContentType(BaseItem item) { throw new NotImplementedException(); } - public string GetConfiguredContentType(BaseItem item) + public CollectionType? GetConfiguredContentType(BaseItem item) { throw new NotImplementedException(); } - public string GetConfiguredContentType(string path) + public CollectionType? GetConfiguredContentType(string path) { throw new NotImplementedException(); } @@ -198,27 +213,27 @@ public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bo throw new NotImplementedException(); } - public UserView GetNamedView(User user, string name, Guid parentId, string viewType, string sortName) + public UserView GetNamedView(User user, string name, Guid parentId, CollectionType? viewType, string sortName) { throw new NotImplementedException(); } - public UserView GetNamedView(User user, string name, string viewType, string sortName) + public UserView GetNamedView(User user, string name, CollectionType? viewType, string sortName) { throw new NotImplementedException(); } - public UserView GetNamedView(string name, string viewType, string sortName) + public UserView GetNamedView(string name, CollectionType viewType, string sortName) { throw new NotImplementedException(); } - public UserView GetNamedView(string name, Guid parentId, string viewType, string sortName, string uniqueId) + public UserView GetNamedView(string name, Guid parentId, CollectionType? viewType, string sortName, string uniqueId) { throw new NotImplementedException(); } - public UserView GetShadowView(BaseItem parent, string viewType, string sortName) + public UserView GetShadowView(BaseItem parent, CollectionType? viewType, string sortName) { throw new NotImplementedException(); } @@ -253,7 +268,7 @@ public List GetCollectionFolders(BaseItem item) throw new NotImplementedException(); } - public List GetCollectionFolders(BaseItem item, List allUserRootChildren) + public List GetCollectionFolders(BaseItem item, IEnumerable allUserRootChildren) { throw new NotImplementedException(); } @@ -308,7 +323,7 @@ public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem = throw new NotImplementedException(); } - public Task ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex) + public Task ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex, bool removeOnFailure = true) { throw new NotImplementedException(); } @@ -423,14 +438,14 @@ public BaseItem GetParentItem(Guid? parentId, Guid? userId) throw new NotImplementedException(); } + public void QueueLibraryScan() + { + throw new NotImplementedException(); + } + public AggregateFolder RootFolder { get; } public bool IsScanRunning { get; } public event EventHandler ItemAdded; public event EventHandler ItemUpdated; public event EventHandler ItemRemoved; - - public void QueueLibraryScan() - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/Jellyfin.Plugin.Bangumi.Test/Movie.cs b/Jellyfin.Plugin.Bangumi.Test/Movie.cs index 74c0055..7df094f 100644 --- a/Jellyfin.Plugin.Bangumi.Test/Movie.cs +++ b/Jellyfin.Plugin.Bangumi.Test/Movie.cs @@ -3,10 +3,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Plugin.Bangumi.Providers; using Jellyfin.Plugin.Bangumi.Test.Util; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Jellyfin.Plugin.Bangumi.Test; @@ -86,9 +86,9 @@ private static void AssertMovie(MetadataResult 0 and <= 10, "should return rating info"); - Assert.IsNotNull(result.People.Find(x => x.IsType(PersonType.Actor)), "should have at least one actor"); - Assert.IsNotNull(result.People.Find(x => x.IsType(PersonType.Director)), "should have at least one director"); - Assert.IsNotNull(result.People.Find(x => x.IsType(PersonType.Writer)), "should have at least one writer"); + Assert.IsNotNull(result.People.Find(x => x.IsType(PersonKind.Actor)), "should have at least one actor"); + Assert.IsNotNull(result.People.Find(x => x.IsType(PersonKind.Director)), "should have at least one director"); + Assert.IsNotNull(result.People.Find(x => x.IsType(PersonKind.Writer)), "should have at least one writer"); Assert.AreNotEqual("", result.People?[0].ImageUrl, "person should have image url"); Assert.AreEqual("23119", result.Item.ProviderIds[Constants.ProviderName], "should have plugin provider id"); } diff --git a/Jellyfin.Plugin.Bangumi.Test/Series.cs b/Jellyfin.Plugin.Bangumi.Test/Series.cs index f9696e0..86f0563 100644 --- a/Jellyfin.Plugin.Bangumi.Test/Series.cs +++ b/Jellyfin.Plugin.Bangumi.Test/Series.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Plugin.Bangumi.Providers; using Jellyfin.Plugin.Bangumi.Test.Util; using MediaBrowser.Controller.Providers; @@ -191,9 +192,9 @@ private static void AssertSeries(MetadataResult 0 and <= 10, "should return rating info"); - Assert.IsNotNull(result.People.Find(x => x.IsType(PersonType.Actor)), "should have at least one actor"); - Assert.IsNotNull(result.People.Find(x => x.IsType(PersonType.Director)), "should have at least one director"); - Assert.IsNotNull(result.People.Find(x => x.IsType(PersonType.Writer)), "should have at least one writer"); + Assert.IsNotNull(result.People.Find(x => x.IsType(PersonKind.Actor)), "should have at least one actor"); + Assert.IsNotNull(result.People.Find(x => x.IsType(PersonKind.Director)), "should have at least one director"); + Assert.IsNotNull(result.People.Find(x => x.IsType(PersonKind.Writer)), "should have at least one writer"); Assert.AreNotEqual("", result.People?.Find(x => x.Name.Equals("丸戸史明")).ImageUrl, "person should have image url"); Assert.IsNotNull(result.Item.ProviderIds[Constants.ProviderName], "should have plugin provider id"); } diff --git a/Jellyfin.Plugin.Bangumi.Test/Util/ServiceLocator.cs b/Jellyfin.Plugin.Bangumi.Test/Util/ServiceLocator.cs index 2bd53cf..1de43d8 100644 --- a/Jellyfin.Plugin.Bangumi.Test/Util/ServiceLocator.cs +++ b/Jellyfin.Plugin.Bangumi.Test/Util/ServiceLocator.cs @@ -33,7 +33,7 @@ public static void Init(TestContext context) serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); - new PluginServiceRegistrator().RegisterServices(serviceCollection); + new PluginServiceRegistrator().RegisterServices(serviceCollection, null!); _provider = serviceCollection.BuildServiceProvider(); var plugin = GetService(); diff --git a/Jellyfin.Plugin.Bangumi/Jellyfin.Plugin.Bangumi.csproj b/Jellyfin.Plugin.Bangumi/Jellyfin.Plugin.Bangumi.csproj index 40fe10a..c0690c6 100644 --- a/Jellyfin.Plugin.Bangumi/Jellyfin.Plugin.Bangumi.csproj +++ b/Jellyfin.Plugin.Bangumi/Jellyfin.Plugin.Bangumi.csproj @@ -1,19 +1,18 @@  - net6.0 + net8.0 Jellyfin.Plugin.Bangumi 1.0.0.0 1.0.0.0 enable - true - - + + @@ -24,14 +23,4 @@ - - - - - - - - - - diff --git a/Jellyfin.Plugin.Bangumi/Model/RelatedCharacter.cs b/Jellyfin.Plugin.Bangumi/Model/RelatedCharacter.cs index 03ef1a8..6bdc6b7 100644 --- a/Jellyfin.Plugin.Bangumi/Model/RelatedCharacter.cs +++ b/Jellyfin.Plugin.Bangumi/Model/RelatedCharacter.cs @@ -1,8 +1,12 @@ +#if EMBY +using PersonEntityType = MediaBrowser.Model.Entities.PersonType; +#else +using Jellyfin.Data.Enums; +#endif using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using MediaBrowser.Controller.Entities; -using PersonEntityType = MediaBrowser.Model.Entities.PersonType; namespace Jellyfin.Plugin.Bangumi.Model; @@ -34,7 +38,11 @@ public IEnumerable ToPersonInfos() Name = actor.Name, Role = Name, ImageUrl = actor.DefaultImage, +#if EMBY Type = PersonEntityType.Actor +#else + Type = PersonKind.Actor +#endif }; info.ProviderIds.Add(Constants.ProviderName, $"{actor.Id}"); return info; diff --git a/Jellyfin.Plugin.Bangumi/Model/RelatedPerson.cs b/Jellyfin.Plugin.Bangumi/Model/RelatedPerson.cs index 2d5d2d1..e597769 100644 --- a/Jellyfin.Plugin.Bangumi/Model/RelatedPerson.cs +++ b/Jellyfin.Plugin.Bangumi/Model/RelatedPerson.cs @@ -1,7 +1,12 @@ + using System.Collections.Generic; using System.Text.Json.Serialization; using MediaBrowser.Controller.Entities; +#if EMBY using PersonEntityType = MediaBrowser.Model.Entities.PersonType; +#else +using Jellyfin.Data.Enums; +#endif namespace Jellyfin.Plugin.Bangumi.Model; @@ -9,15 +14,21 @@ public class RelatedPerson { #if EMBY private static readonly Dictionary RelationMap = new() -#else - private static readonly Dictionary RelationMap = new() -#endif { ["导演"] = PersonEntityType.Director, ["制片人"] = PersonEntityType.Producer, ["系列构成"] = PersonEntityType.Composer, ["脚本"] = PersonEntityType.Writer }; +#else + private static readonly Dictionary RelationMap = new() + { + ["导演"] = PersonKind.Director, + ["制片人"] = PersonKind.Producer, + ["系列构成"] = PersonKind.Composer, + ["脚本"] = PersonKind.Writer + }; +#endif public int Id { get; set; } diff --git a/Jellyfin.Plugin.Bangumi/OAuth/OAuthController.cs b/Jellyfin.Plugin.Bangumi/OAuth/OAuthController.cs index 1b877c7..bdd385c 100644 --- a/Jellyfin.Plugin.Bangumi/OAuth/OAuthController.cs +++ b/Jellyfin.Plugin.Bangumi/OAuth/OAuthController.cs @@ -19,21 +19,22 @@ public class OAuthController : ControllerBase private static string? _oAuthPath; private readonly BangumiApi _api; - private readonly ISessionContext _sessionContext; + private readonly IAuthorizationContext _authorizationContext; private readonly OAuthStore _store; - public OAuthController(BangumiApi api, OAuthStore store, ISessionContext sessionContext) + public OAuthController(BangumiApi api, OAuthStore store, IAuthorizationContext authorizationContext) { _api = api; _store = store; - _sessionContext = sessionContext; + _authorizationContext = authorizationContext; } [HttpGet("OAuthState")] - [Authorize("DefaultAuthorization")] + [Authorize] public async Task?> OAuthState() { - var user = await _sessionContext.GetUser(Request); + var authorizationInfo = await _authorizationContext.GetAuthorizationInfo(Request); + var user = authorizationInfo.User; if (user == null) return null; _store.Load(); @@ -59,10 +60,11 @@ public OAuthController(BangumiApi api, OAuthStore store, ISessionContext session } [HttpPost("RefreshOAuthToken")] - [Authorize("DefaultAuthorization")] + [Authorize] public async Task RefreshOAuthToken() { - var user = await _sessionContext.GetUser(Request); + var authorizationInfo = await _authorizationContext.GetAuthorizationInfo(Request); + var user = authorizationInfo.User; if (user == null) return BadRequest(); _store.Load(); @@ -76,10 +78,11 @@ public async Task RefreshOAuthToken() } [HttpDelete("OAuth")] - [Authorize("DefaultAuthorization")] + [Authorize] public async Task DeAuth() { - var user = await _sessionContext.GetUser(Request); + var authorizationInfo = await _authorizationContext.GetAuthorizationInfo(Request); + var user = authorizationInfo.User; if (user == null) return BadRequest(); _store.Load(); diff --git a/Jellyfin.Plugin.Bangumi/PlaybackScrobbler.cs b/Jellyfin.Plugin.Bangumi/PlaybackScrobbler.cs index e82f795..f94a193 100644 --- a/Jellyfin.Plugin.Bangumi/PlaybackScrobbler.cs +++ b/Jellyfin.Plugin.Bangumi/PlaybackScrobbler.cs @@ -10,15 +10,15 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using CollectionType = Jellyfin.Plugin.Bangumi.Model.CollectionType; namespace Jellyfin.Plugin.Bangumi; -public class PlaybackScrobbler : IServerEntryPoint +public class PlaybackScrobbler : IHostedService { // https://github.com/jellyfin/jellyfin/blob/master/Emby.Server.Implementations/Localization/Ratings/jp.csv // https://github.com/jellyfin/jellyfin/blob/master/Emby.Server.Implementations/Localization/Ratings/us.csv @@ -46,13 +46,13 @@ public PlaybackScrobbler(IUserManager userManager, IUserDataManager userDataMana private static PluginConfiguration Configuration => Plugin.Instance!.Configuration; - public void Dispose() + public Task StopAsync(CancellationToken token) { _userDataManager.UserDataSaved -= OnUserDataSaved; - GC.SuppressFinalize(this); + return Task.CompletedTask; } - public Task RunAsync() + public Task StartAsync(CancellationToken token) { _userDataManager.UserDataSaved += OnUserDataSaved; return Task.CompletedTask; diff --git a/Jellyfin.Plugin.Bangumi/PluginServiceRegistrator.cs b/Jellyfin.Plugin.Bangumi/PluginServiceRegistrator.cs index 76124a9..07a7c35 100644 --- a/Jellyfin.Plugin.Bangumi/PluginServiceRegistrator.cs +++ b/Jellyfin.Plugin.Bangumi/PluginServiceRegistrator.cs @@ -1,14 +1,17 @@ using Jellyfin.Plugin.Bangumi.OAuth; -using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Plugins; using Microsoft.Extensions.DependencyInjection; namespace Jellyfin.Plugin.Bangumi; public class PluginServiceRegistrator : IPluginServiceRegistrator { - public void RegisterServices(IServiceCollection serviceCollection) + public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost) { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + + serviceCollection.AddHostedService(); } } \ No newline at end of file diff --git a/Jellyfin.Plugin.Bangumi/ScheduledTask/TokenRefreshTask.cs b/Jellyfin.Plugin.Bangumi/ScheduledTask/TokenRefreshTask.cs index f2a5c3c..841503a 100644 --- a/Jellyfin.Plugin.Bangumi/ScheduledTask/TokenRefreshTask.cs +++ b/Jellyfin.Plugin.Bangumi/ScheduledTask/TokenRefreshTask.cs @@ -1,17 +1,15 @@ -using System; + +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.Bangumi.OAuth; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Activity; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Activity; using MediaBrowser.Model.Tasks; - #if EMBY -using Emby.Notifications; +using MediaBrowser.Model.Logging; #else -using MediaBrowser.Model.Notifications; using Microsoft.Extensions.Logging; using Jellyfin.Data.Entities; #endif @@ -20,17 +18,15 @@ namespace Jellyfin.Plugin.Bangumi.ScheduledTask; public class TokenRefreshTask : IScheduledTask { - private readonly IUserManager _userManager; private readonly IActivityManager _activity; private readonly BangumiApi _api; - private readonly INotificationManager _notification; private readonly OAuthStore _store; + private readonly IUserManager _userManager; - public TokenRefreshTask(IUserManager userManager, IActivityManager activity, INotificationManager notification, BangumiApi api, OAuthStore store) + public TokenRefreshTask(IUserManager userManager, IActivityManager activity, BangumiApi api, OAuthStore store) { _userManager = userManager; _activity = activity; - _notification = notification; _api = api; _store = store; } @@ -89,20 +85,14 @@ public async Task ExecuteAsync(IProgress progress, CancellationToken tok await user.Refresh(_api.GetHttpClient(), token); await user.GetProfile(_api, token); activity.ShortOverview = $"用户 #{user.UserId} 授权刷新成功"; - activity.Severity = MediaBrowser.Model.Logging.LogSeverity.Info; + activity.Severity = LogSeverity.Info; } catch (Exception e) { activity.ShortOverview = $"用户 #{user.UserId} 授权刷新失败: {e.Message}"; - activity.Severity = MediaBrowser.Model.Logging.LogSeverity.Warn; - _notification.SendNotification(new NotificationRequest - { - Title = activity.ShortOverview, - Description = e.StackTrace, - User = _userManager.GetUserById(userId), - Date = DateTime.Now - }); + activity.Severity = LogSeverity.Warn; } + _activity.Create(activity); #else var activity = new ActivityLog("Bangumi 授权", "Bangumi", userId); @@ -117,15 +107,8 @@ public async Task ExecuteAsync(IProgress progress, CancellationToken tok { activity.ShortOverview = $"用户 #{user.UserId} 授权刷新失败: {e.Message}"; activity.LogSeverity = LogLevel.Warning; - await _notification.SendNotification(new NotificationRequest - { - Name = activity.ShortOverview, - Description = e.StackTrace, - Level = NotificationLevel.Warning, - UserIds = new[] { Guid.Parse(guid) }, - Date = DateTime.Now - }, token); } + await _activity.CreateAsync(activity); #endif }