### Shoko API Client Example Usage Source: https://context7.com/shokoanime/shokofin/llms.txt Demonstrates various methods of the ShokoApiClient for interacting with Shoko Server, including getting server version, authentication, file and series information, media data, image retrieval, scrobbling playback, managing user statistics, and custom tags. Typically injected via Dependency Injection. ```csharp // The API client is typically injected via DI public class MyService { private readonly ShokoApiClient _apiClient; public MyService(ShokoApiClient apiClient) { _apiClient = apiClient; } public async Task ExampleUsage() { // Get server version var version = await _apiClient.GetVersion(); Console.WriteLine($"Shoko Server v{version?.Version}"); // Authenticate and get API key var apiKey = await _apiClient.GetApiKey( username: "admin", password: "password", forUser: false // true for per-user keys ); // Get file information by path var files = await _apiClient.GetFileByPath("anime/series/episode.mkv"); foreach (var file in files) { Console.WriteLine($"File ID: {file.Id}"); Console.WriteLine($"Size: {file.Size} bytes"); foreach (var xref in file.CrossReferences) { Console.WriteLine($"Series: {xref.Series.Shoko}"); } } // Get Shoko series information var series = await _apiClient.GetShokoSeries("12345"); Console.WriteLine($"Series: {series?.Name}"); // Get all episodes in a series var episodes = await _apiClient.GetShokoEpisodesInShokoSeries("12345"); foreach (var ep in episodes) { Console.WriteLine($"Episode {ep.AniDB?.EpisodeNumber}: {ep.Name}"); } // Get TMDB movie data var movie = await _apiClient.GetTmdbMovie("550"); Console.WriteLine($"TMDB Movie: {movie?.Title}"); // Get images for a series var images = await _apiClient.GetImagesForShokoSeries("12345"); Console.WriteLine($"Posters: {images?.Posters.Count}"); Console.WriteLine($"Backdrops: {images?.Backdrops.Count}"); // Scrobble playback event bool success = await _apiClient.ScrobbleFile( fileId: "67890", episodeId: "11111", eventName: "play", // play, pause, resume, stop, scrobble progress: 12345678L, // ticks apiKey: "user-api-key" ); // Get/update file user statistics var stats = await _apiClient.GetFileUserStats("67890", "user-api-key"); if (stats != null) { Console.WriteLine($"Watch count: {stats.WatchedCount}"); Console.WriteLine($"Resume position: {stats.ResumePosition}"); } // Update user stats var updatedStats = await _apiClient.PutFileUserStats("67890", new File.UserStats { WatchedCount = 1, ResumePosition = 0, LastWatchedAt = DateTime.UtcNow }, "user-api-key"); // Custom tags management var tags = await _apiClient.GetCustomTags(); var newTag = await _apiClient.CreateCustomTag("My Tag", "Description"); await _apiClient.AddCustomTagToShokoSeries(12345, newTag.Id); // Clear API cache _apiClient.Clear(); } } ``` -------------------------------- ### GET /Shokofin/Host/Version Source: https://context7.com/shokoanime/shokofin/llms.txt Retrieves the current version of the connected Shoko Server. ```APIDOC ## GET /Shokofin/Host/Version ### Description Returns the Shoko Server version. ### Method GET ### Endpoint /Shokofin/Host/Version ### Response #### Success Response (200) - **version** (ComponentVersion) - The version information of the Shoko Server. ``` -------------------------------- ### GET /Shokofin/Host/Image/{ImageSource}/{ImageType}/{ImageId} Source: https://context7.com/shokoanime/shokofin/llms.txt Proxies image requests from the Shoko Server to the Jellyfin client. ```APIDOC ## GET /Shokofin/Host/Image/{ImageSource}/{ImageType}/{ImageId} ### Description Proxies image requests to Shoko Server. ### Method GET ### Endpoint /Shokofin/Host/Image/{ImageSource}/{ImageType}/{ImageId} ### Parameters #### Path Parameters - **ImageSource** (string) - Required - The source of the image (AniDB, TMDB, TvDB, Shoko). - **ImageType** (string) - Required - The type of image (Poster, Fanart, Banner, Thumbnail). - **ImageId** (integer) - Required - The unique identifier for the image. ### Response #### Success Response (200) - **image** (file) - The requested image stream. ``` -------------------------------- ### Configure and Manage Virtual File System Source: https://context7.com/shokoanime/shokofin/llms.txt Enables VFS mode for libraries and provides methods for previewing changes, generating symbolic links, and clearing the cache. ```csharp // VFS is enabled per-library in configuration var libraryConfig = Plugin.Instance.Configuration.Libraries .First(c => c.Id == libraryId); libraryConfig.LibraryOperationMode = LibraryOperationMode.VFS; // VFS generates structure during library scans public class VirtualFileSystemService { // Preview changes before applying public async Task<(HashSet filesBefore, HashSet filesAfter, VirtualFolderInfo? virtualFolder, LinkGenerationResult? result, string vfsPath)> PreviewChangesForLibrary(Guid libraryId, CancellationToken cancellationToken) { // Returns current files, proposed files, and the result of generation } // Generate the VFS structure for a library // Creates symbolic links organized as: // VFS Root/ // LibraryId/ // Show Name/ // Season 01/ // Show Name - S01E01 - Episode Title.mkv -> /actual/path/file.mkv // Configuration options var config = Plugin.Instance.Configuration; config.VFS_Threads = 4; // Concurrent link generation threads config.VFS_AddReleaseGroup = true; // Include release group in filename config.VFS_AddResolution = true; // Include resolution in filename config.VFS_ResolveLinks = true; // Follow existing symlinks config.VFS_Location = VirtualRootLocation.Default; // VirtualRootLocation.Default = ProgramData/Shokofin/VFS // VirtualRootLocation.Custom = config.VFS_CustomLocation } // Access the VFS root path string vfsRoot = Plugin.Instance.VirtualRoot; // e.g., "/config/data/Shokofin/VFS" // Clear VFS cache vfsService.Clear(); ``` -------------------------------- ### Restore and Publish Shokofin Project Source: https://github.com/shokoanime/shokofin/blob/dev/README.md Use these commands to restore project dependencies and publish the Shokofin plugin for release. Ensure you are in the repository's root directory. ```sh dotnet restore Shokofin/Shokofin.csproj ``` ```sh dotnet publish -c Release Shokofin/Shokofin.csproj ``` -------------------------------- ### Implement Series Metadata Provider Source: https://context7.com/shokoanime/shokofin/llms.txt Defines a class implementing IRemoteMetadataProvider to fetch and map series metadata from Shoko Server to Jellyfin. ```csharp // The provider is registered automatically by the plugin // Jellyfin calls GetMetadata when refreshing series metadata public class SeriesProvider : IRemoteMetadataProvider { public string Name => "Shoko"; public int Order => 0; // Priority order // Called by Jellyfin during metadata refresh public async Task> GetMetadata( SeriesInfo info, CancellationToken cancellationToken ) { var result = new MetadataResult(); // Get show info from Shoko via path var showInfo = await _apiManager.GetShowInfoByPath(info.Path); if (showInfo == null) return result; // Get localized titles var (displayTitle, alternateTitle) = TextUtility.GetShowTitles( showInfo, info.MetadataLanguage ); // Create the series item result.Item = new Series { Name = displayTitle, OriginalTitle = alternateTitle, Overview = TextUtility.GetShowDescription(showInfo, info.MetadataLanguage), PremiereDate = showInfo.PremiereDate, EndDate = showInfo.EndDate, Status = showInfo.EndDate > DateTime.UtcNow ? SeriesStatus.Continuing : SeriesStatus.Ended, Tags = showInfo.Tags.ToArray(), Genres = showInfo.Genres.ToArray(), Studios = showInfo.Studios.ToArray(), CommunityRating = showInfo.CommunityRating, OfficialRating = ContentRating.GetContentRating(showInfo, info.MetadataCountryCode) }; // Set provider IDs result.Item.SetProviderId("Shoko", showInfo.InternalId); result.Item.SetProviderId("AniDB", showInfo.AnidbAnimeId); result.Item.SetProviderId("Tmdb", showInfo.TmdbShowId); // Add cast/crew foreach (var person in showInfo.Staff) result.AddPerson(person); result.HasMetadata = true; return result; } } ``` -------------------------------- ### Implement Web API Controllers Source: https://context7.com/shokoanime/shokofin/llms.txt Defines REST endpoints for version retrieval, API key authentication, and image proxying. ```csharp // GET /Shokofin/Host/Version // Returns the Shoko Server version [HttpGet("Version")] public async Task> GetVersionAsync() { var version = await APIClient.GetVersion(); return version ?? StatusCode(502); } // POST /Shokofin/Host/GetApiKey // Authenticates with Shoko Server and returns an API key [HttpPost("GetApiKey")] public async Task> GetApiKeyAsync([FromBody] ApiLoginRequest body) { var apiKey = await APIClient.GetApiKey(body.Username, body.Password, body.UserKey); return apiKey ?? StatusCode(401); } // Request body format: // { // "username": "admin", // "password": "password", // "userKey": false // } // GET /Shokofin/Host/Image/{ImageSource}/{ImageType}/{ImageId} // Proxies image requests to Shoko Server [AllowAnonymous] [ResponseCache(Duration = 3600)] [HttpGet("Image/{ImageSource}/{ImageType}/{ImageId}")] public async Task GetImageAsync( ImageSource imageSource, // AniDB, TMDB, TvDB, Shoko ShokoImageType imageType, // Poster, Fanart, Banner, Thumbnail int imageId ) { var response = await APIClient.GetImageAsync(imageSource, imageType, imageId); return File(await response.Content.ReadAsStreamAsync(), contentType); } ``` -------------------------------- ### Configure Shokofin Plugin Settings Source: https://context7.com/shokoanime/shokofin/llms.txt Access and modify plugin configuration settings for Shoko Server connection, metadata providers, library structure, VFS options, and SignalR real-time events. Remember to save changes using Plugin.Instance.UpdateConfiguration(). ```csharp var config = Plugin.Instance.Configuration; // Connection settings - connect to Shoko Server config.Url = "http://127.0.0.1:8111"; config.Username = "Default"; config.ApiKey = "your-api-key-here"; // Check if connection is usable bool canConnect = config.IsConnectionUsable; // Returns true if Url is valid and ApiKey is set // Configure metadata providers for third-party IDs config.AddAniDBId = true; // Add AniDB IDs to entities config.AddTMDBId = true; // Add TMDB IDs to entities config.AddTvDBId = false; // Add TvDB IDs to entities // Library structure options config.DefaultLibraryStructure = SeriesStructureType.AniDB_Anime; // Options: AniDB_Anime, Shoko_Groups, TMDB_SeriesAndMovies // Virtual File System settings config.DefaultLibraryOperationMode = LibraryOperationMode.VFS; // Options: VFS (recommended), Strict, Default config.VFS_Threads = 4; config.VFS_AddReleaseGroup = true; config.VFS_AddResolution = true; config.VFS_Location = VirtualRootLocation.Default; // Enable SignalR real-time events config.SignalR_AutoConnectEnabled = true; config.SignalR_RefreshEnabled = true; config.SignalR_FileEvents = true; config.SignalR_EventSources = new[] { ProviderName.Shoko, ProviderName.AniDB, ProviderName.TMDB }; // Auto-merge versions after library scan config.AutoMergeVersions = true; // Collection settings config.CollectionGrouping = CollectionCreationType.Movies; config.CollectionMinSizeOfTwo = true; // Save configuration changes Plugin.Instance.UpdateConfiguration(); ``` -------------------------------- ### POST /Shokofin/Host/GetApiKey Source: https://context7.com/shokoanime/shokofin/llms.txt Authenticates with the Shoko Server using provided credentials to obtain an API key. ```APIDOC ## POST /Shokofin/Host/GetApiKey ### Description Authenticates with Shoko Server and returns an API key. ### Method POST ### Endpoint /Shokofin/Host/GetApiKey ### Request Body - **username** (string) - Required - The username for authentication. - **password** (string) - Required - The password for authentication. - **userKey** (boolean) - Required - Flag indicating if a user key is requested. ### Request Example { "username": "admin", "password": "password", "userKey": false } ### Response #### Success Response (200) - **apiKey** (ApiKey) - The generated API key. ``` -------------------------------- ### Configure User Data Synchronization Source: https://context7.com/shokoanime/shokofin/llms.txt Configures user-specific synchronization settings and defines scheduled tasks for importing or exporting watch data. ```csharp // User sync is configured per-user in the plugin settings var userConfig = Plugin.Instance.Configuration.UserList .FirstOrDefault(c => c.UserId == userId && c.EnableSynchronization); // Configure user sync options userConfig.EnableSynchronization = true; userConfig.SyncUserDataOnImport = true; // Sync when items are added userConfig.SyncUserDataAfterPlayback = true; // Sync after playback ends userConfig.SyncUserDataUnderPlayback = true; // Real-time scrobbling userConfig.SyncUserDataUnderPlaybackLive = true; // Live progress sync userConfig.SyncRestrictedVideos = false; // Skip adult content // The UserDataSyncManager handles events automatically: // - SessionManager.PlaybackStart -> starts tracking // - SessionManager.PlaybackStopped -> ends session // - UserDataManager.UserDataSaved -> triggers sync // Manual bulk sync via scheduled tasks public class ImportUserDataTask : IScheduledTask { public string Name => "Import User Data"; public string Category => "Shokofin"; public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) => _userSyncManager.ScanAndSync(SyncDirection.Import, progress, cancellationToken); } // Export user data to Shoko public class ExportUserDataTask : IScheduledTask { public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) => _userSyncManager.ScanAndSync(SyncDirection.Export, progress, cancellationToken); } // Sync directions available: // SyncDirection.Import - Shoko -> Jellyfin // SyncDirection.Export - Jellyfin -> Shoko // SyncDirection.Sync - Bidirectional (newest wins) ``` -------------------------------- ### Configure and Reconstruct Collections Source: https://context7.com/shokoanime/shokofin/llms.txt Configures collection grouping behavior and provides the manager class for triggering collection reconstruction. ```csharp // Configure collection behavior var config = Plugin.Instance.Configuration; config.CollectionGrouping = CollectionCreationType.Movies; // Options: None, Movies (group by series), Shared (group by Shoko groups) config.CollectionMinSizeOfTwo = true; // Require 2+ items for collection config.AutoReconstructCollections = true; // Rebuild after scan // The CollectionManager handles reconstruction public class CollectionManager { // Reconstruct all collections based on current settings public async Task ReconstructCollections( IProgress progress, CancellationToken cancellationToken ) { switch (Plugin.Instance.Configuration.CollectionGrouping) { case CollectionCreationType.None: await CleanupAll(progress, cancellationToken); break; case CollectionCreationType.Movies: await ReconstructMovieSeriesCollections(progress, cancellationToken); break; case CollectionCreationType.Shared: await ReconstructSharedCollections(progress, cancellationToken); break; } } // Get the collections folder public Task GetCollectionsFolder(bool createIfNeeded) => _collection.GetCollectionsFolder(createIfNeeded); } // Collections are named after the Shoko series/group // Items are linked using provider IDs for consistency ``` -------------------------------- ### Manage SignalR Real-time Connections Source: https://context7.com/shokoanime/shokofin/llms.txt Configures automatic connection settings and defines the event manager for handling real-time notifications from Shoko Server. ```csharp // SignalR connection is managed automatically when enabled var config = Plugin.Instance.Configuration; config.SignalR_AutoConnectEnabled = true; config.SignalR_RefreshEnabled = true; config.SignalR_FileEvents = true; config.SignalR_AutoReconnectInSeconds = new[] { 0, 2, 10, 30, 60, 120, 300 }; // The SignalRConnectionManager handles the connection public class SignalRConnectionManager { public bool IsActive => Connection != null; public HubConnectionState State => Connection?.State ?? HubConnectionState.Disconnected; // Events subscribed to: // - metadata:episode.added/updated/removed // - metadata:series.added/updated/removed // - metadata:movie.added/updated/removed // - release:saved // - file:deleted // - file:relocated // Event handlers trigger appropriate Jellyfin actions: private void OnInfoUpdated(SeriesInfoUpdatedEventArgs args) { // Refresh metadata for affected series } private void OnFileDeleted(FileEventArgs args) { // Remove file from library } private void OnFileRelocated(FileMovedEventArgs args) { // Update file path in library } } ``` -------------------------------- ### Customize Collection Section Titles with CSS Source: https://github.com/shokoanime/shokofin/blob/dev/Shokofin/Pages/Settings.html Use this CSS to rename collection sections in the Jellyfin UI. Translate the content values if using a language other than English. ```css .collectionItems .verticalSection:has(div[data-type="Movie"]) .sectionTitle.sectionTitle-cards > span { visibility: hidden; } .collectionItems .verticalSection:has(div[data-type="Movie"]) .sectionTitle.sectionTitle-cards > span::before { visibility: initial; content: "Movies"; } .collectionItems .verticalSection:has(div[data-type="BoxSet"]) .sectionTitle.sectionTitle-cards > span { visibility: hidden; } .collectionItems .verticalSection:has(div[data-type="BoxSet"]) .sectionTitle.sectionTitle-cards > span::before { visibility: initial; content: "Collections"; } ``` -------------------------------- ### Define Scheduled Tasks Source: https://context7.com/shokoanime/shokofin/llms.txt Implements various maintenance and synchronization tasks by inheriting from IScheduledTask. ```csharp // Available scheduled tasks in the "Shokofin" category: // Import user watch data from Shoko to Jellyfin public class ImportUserDataTask : IScheduledTask { public string Name => "Import User Data"; public string Key => "ShokoImportUserData"; } // Export user watch data from Jellyfin to Shoko public class ExportUserDataTask : IScheduledTask { public string Name => "Export User Data"; public string Key => "ShokoExportUserData"; } // Bidirectional sync (newest data wins) public class SyncUserDataTask : IScheduledTask { public string Name => "Sync User Data"; public string Key => "ShokoSyncUserData"; } // Merge multi-version episodes into single entries public class MergeEpisodesTask : IScheduledTask { public string Name => "Merge Episodes"; public string Key => "ShokoMergeEpisodes"; } // Merge multi-version movies into single entries public class MergeMoviesTask : IScheduledTask { public string Name => "Merge Movies"; public string Key => "ShokoMergeMovies"; } // Split previously merged episodes public class SplitEpisodesTask : IScheduledTask { public string Name => "Split Episodes"; public string Key => "ShokoSplitEpisodes"; } // Rebuild collections based on current configuration public class ReconstructCollectionsTask : IScheduledTask { public string Name => "Reconstruct Collections"; public string Key => "ShokoReconstructCollections"; } // Clean up orphaned VFS symbolic links public class CleanupVirtualRootTask : IScheduledTask { public string Name => "Cleanup Virtual Root"; public string Key => "ShokoCleanupVirtualRoot"; } // Clear plugin cache public class ClearPluginCacheTask : IScheduledTask { public string Name => "Clear Plugin Cache"; public string Key => "ShokoClearPluginCache"; } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.