Oborozuki.Fumi.Abstractions
0.1.0-dev.2
dotnet add package Oborozuki.Fumi.Abstractions --version 0.1.0-dev.2
NuGet\Install-Package Oborozuki.Fumi.Abstractions -Version 0.1.0-dev.2
<PackageReference Include="Oborozuki.Fumi.Abstractions" Version="0.1.0-dev.2" />
<PackageVersion Include="Oborozuki.Fumi.Abstractions" Version="0.1.0-dev.2" />
<PackageReference Include="Oborozuki.Fumi.Abstractions" />
paket add Oborozuki.Fumi.Abstractions --version 0.1.0-dev.2
#r "nuget: Oborozuki.Fumi.Abstractions, 0.1.0-dev.2"
#:package Oborozuki.Fumi.Abstractions@0.1.0-dev.2
#addin nuget:?package=Oborozuki.Fumi.Abstractions&version=0.1.0-dev.2&prerelease
#tool nuget:?package=Oborozuki.Fumi.Abstractions&version=0.1.0-dev.2&prerelease
Oborozuki.Fumi.Abstractions
The contracts a Shoko Server plugin uses to write to Fumi's audit log and to send notifications through its hub, with actions the recipients can take. It carries none of the plugin that stores and delivers them: that is Fumi itself, which the server loads beside your plugin.
Getting the plugin
Fumi is installed into Shoko from its release manifest: in the Web UI, go to Settings → Plugin Management → Repositories, add a repository with this URL, then find Fumi under Browse.
https://cdn.oborozuki.moe/oborozuki/fumi/metadata/manifest.json
Using Fumi from your plugin
Name Fumi as a plugin dependency, so the server loads it before your plugin, and reference the contracts, published as Oborozuki.Fumi.Abstractions (the assembly and namespaces are Fumi.Abstractions). How you reference them depends on whether your plugin needs Fumi or only does more with it.
When your plugin needs Fumi
Make Fumi a required dependency and exclude the runtime assets: Fumi is always there and supplies the assembly.
<PluginDependency Include="4257a8df-7f8b-42f5-9950-581652f73629" Version=">=0.1.0" />
<PackageReference Include="Oborozuki.Fumi.Abstractions" Version="0.1.0-dev.2" ExcludeAssets="runtime" />
Then take IAuditLog and INotificationHub from the service container like any other service:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Fumi.Abstractions.Audit;
using Fumi.Abstractions.Notifications;
using Shoko.Abstractions.User;
namespace Shoko.Plugin.WatchParty;
public class WatchPartyService(IAuditLog audit, INotificationHub notifications)
{
public async Task Invite(IUser host, IEnumerable<int> guests)
{
await audit.Write(new()
{
Source = "Watch Party",
Category = "parties",
Action = "party.started",
Summary = $"{host.Username} started a watch party.",
ActorUserID = host.LocalID,
});
await notifications.Send(new()
{
Source = "Watch Party",
Title = $"Join the watch party started by {host.Username}?",
Audience = NotificationAudience.ForUsers(guests),
ResponseMode = ActionResponseMode.Multiple,
Actions =
[
new() { Name = "join", Label = "Join", Timeout = TimeSpan.FromMinutes(30), Callback = (answer, token) => AddGuest(answer.User, token) },
new() { Name = "decline", Label = "Decline", Timeout = TimeSpan.FromMinutes(30), Callback = (_, _) => Task.CompletedTask },
],
});
}
private Task AddGuest(IUser guest, CancellationToken token)
=> Task.CompletedTask; // Seat the guest in the party.
}
When Fumi adds to your plugin
Make Fumi an optional dependency and ship the assembly with your plugin. While Fumi is installed the server hands your plugin Fumi's copy (a dependency's own assemblies come before your plugin's files), so the contracts are one type for both; without Fumi your own copy loads, the types resolve, and the services are not registered.
<PluginDependency Include="4257a8df-7f8b-42f5-9950-581652f73629" Version=">=0.1.0" Optional="true" />
<PackageReference Include="Oborozuki.Fumi.Abstractions" Version="0.1.0-dev.2" />
Take the services with a default of null: the container passes the default for a parameter it cannot resolve, while a parameter that is only marked nullable, with no default, fails to resolve. Your plugin does its work either way, and records and tells more when Fumi is there:
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Fumi.Abstractions.Audit;
using Fumi.Abstractions.Notifications;
using Microsoft.Extensions.Logging;
namespace Shoko.Plugin.SidecarCleaner;
public class SidecarCleaner(
ILogger<SidecarCleaner> logger,
IAuditLog? audit = null,
INotificationHub? notifications = null)
{
public async Task Clean(string videoPath, CancellationToken token)
{
var removed = new List<string>();
foreach (var sidecar in FindSidecars(videoPath))
{
File.Delete(sidecar);
removed.Add(Path.GetFileName(sidecar));
}
// Always: the server log.
logger.LogInformation("Removed {Count} sidecar file(s) for {Video}.", removed.Count, videoPath);
// With Fumi: a lasting, searchable record of what was removed.
if (audit is not null && removed.Count > 0)
await audit.Write(new()
{
Source = "Sidecar Cleaner",
Category = "files",
Action = "sidecars.removed",
Summary = $"Removed {removed.Count} sidecar file(s) next to {Path.GetFileName(videoPath)}.",
}, token);
// With Fumi: tell the administrators when a lot went at once.
if (notifications is not null && removed.Count >= 20)
await notifications.Send(new()
{
Source = "Sidecar Cleaner",
Title = $"Removed {removed.Count} sidecar files",
Audience = NotificationAudience.Administrators,
}, token);
}
private static IEnumerable<string> FindSidecars(string videoPath)
=> []; // .nfo, .srt, -thumb.jpg … next to the video.
}
Good to know
ActorUserIDnames the actor, andActorDevicethe device of the key they used when you know it; leave both out and Fumi takes the actor the server has for the calling request, hub call or queued job.- Writes and sends made before the server has started wait for it.
IAuditLog.EntryWritten,INotificationHub.SentandINotificationHub.ActionTakenreport what happened, andINotificationHub.TakeActionanswers for a recipient, for a plugin with a surface of its own.
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Newtonsoft.Json (>= 13.0.4)
- Shoko.Abstractions (>= 6.0.0-alpha.114)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-dev.2 | 0 | 10/30/2026 |