From 06be7bed0c640c21503f75ce834a6fde2fc905ba Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Tue, 10 Oct 2023 21:24:28 +0200 Subject: Initial commit --- .../Controllers/BeatmapRepositoryController.cs | 24 ++++++ .../Events/AccountData/BotData.cs | 11 +++ .../Events/State/BeatmapSetInfo.cs | 11 +++ .../OsuFederatedBeatmapApi.csproj | 21 +++++ OsuFederatedBeatmapApi/Program.cs | 41 ++++++++++ .../Properties/launchSettings.json | 32 ++++++++ .../Services/FederatedBeatmapApiBot.cs | 92 ++++++++++++++++++++++ .../FederatedBeatmapApiBotAccountDataService.cs | 60 ++++++++++++++ .../FederatedBeatmapApiBotConfiguration.cs | 8 ++ .../appsettings.Development.json | 24 ++++++ OsuFederatedBeatmapApi/appsettings.json | 10 +++ OsuFederatedBeatmapApi/deps.nix | 43 ++++++++++ 12 files changed, 377 insertions(+) create mode 100644 OsuFederatedBeatmapApi/Controllers/BeatmapRepositoryController.cs create mode 100644 OsuFederatedBeatmapApi/Events/AccountData/BotData.cs create mode 100644 OsuFederatedBeatmapApi/Events/State/BeatmapSetInfo.cs create mode 100644 OsuFederatedBeatmapApi/OsuFederatedBeatmapApi.csproj create mode 100644 OsuFederatedBeatmapApi/Program.cs create mode 100644 OsuFederatedBeatmapApi/Properties/launchSettings.json create mode 100644 OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBot.cs create mode 100644 OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotAccountDataService.cs create mode 100644 OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotConfiguration.cs create mode 100644 OsuFederatedBeatmapApi/appsettings.Development.json create mode 100644 OsuFederatedBeatmapApi/appsettings.json create mode 100644 OsuFederatedBeatmapApi/deps.nix (limited to 'OsuFederatedBeatmapApi') diff --git a/OsuFederatedBeatmapApi/Controllers/BeatmapRepositoryController.cs b/OsuFederatedBeatmapApi/Controllers/BeatmapRepositoryController.cs new file mode 100644 index 0000000..6c2bd31 --- /dev/null +++ b/OsuFederatedBeatmapApi/Controllers/BeatmapRepositoryController.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; +using OsuFederatedBeatmapApi.Events.State; + +namespace OsuFederatedBeatmapApi.Controllers; + +[ApiController] +[Route("/")] +public class BeatmapRepositoryController(ILogger logger, ) : ControllerBase { + + [HttpGet("/beatmapset/all/info")] + public async IAsyncEnumerable GetAllInfo() { + + } + + [HttpGet("/beatmapset/{id:int}/info")] + public IEnumerable Get(int id) { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} diff --git a/OsuFederatedBeatmapApi/Events/AccountData/BotData.cs b/OsuFederatedBeatmapApi/Events/AccountData/BotData.cs new file mode 100644 index 0000000..0c9744a --- /dev/null +++ b/OsuFederatedBeatmapApi/Events/AccountData/BotData.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace OsuFederatedBeatmapApi.Events.AccountData; + +public class BotData { + [JsonPropertyName("control_room")] + public string ControlRoom { get; set; } = ""; + + [JsonPropertyName("log_room")] + public string? LogRoom { get; set; } = ""; +} diff --git a/OsuFederatedBeatmapApi/Events/State/BeatmapSetInfo.cs b/OsuFederatedBeatmapApi/Events/State/BeatmapSetInfo.cs new file mode 100644 index 0000000..2c9367e --- /dev/null +++ b/OsuFederatedBeatmapApi/Events/State/BeatmapSetInfo.cs @@ -0,0 +1,11 @@ +using LibMatrix; +using LibMatrix.EventTypes; + +namespace OsuFederatedBeatmapApi.Events.State; + +[MatrixEvent(EventName = "gay.rory.beatmap_api.beatmap_set_info")] +public class BeatmapSetInfo : StateEvent { + + + +} diff --git a/OsuFederatedBeatmapApi/OsuFederatedBeatmapApi.csproj b/OsuFederatedBeatmapApi/OsuFederatedBeatmapApi.csproj new file mode 100644 index 0000000..9c307b6 --- /dev/null +++ b/OsuFederatedBeatmapApi/OsuFederatedBeatmapApi.csproj @@ -0,0 +1,21 @@ + + + + net7.0 + enable + enable + preview + + + + + + + + + + + + + + diff --git a/OsuFederatedBeatmapApi/Program.cs b/OsuFederatedBeatmapApi/Program.cs new file mode 100644 index 0000000..27175f1 --- /dev/null +++ b/OsuFederatedBeatmapApi/Program.cs @@ -0,0 +1,41 @@ +using LibMatrix.Services; +using LibMatrix.Utilities.Bot; +using OsuFederatedBeatmapApi.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddScoped(x => + new TieredStorageService( + cacheStorageProvider: new FileStorageProvider("bot_data/cache/"), + dataStorageProvider: new FileStorageProvider("bot_data/data/") + ) +); + +builder.Services.AddRoryLibMatrixServices(); +builder.Services.AddBot(withCommands: true); + +builder.Services.AddSingleton(); +builder.Services.AddHostedService(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) { + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/OsuFederatedBeatmapApi/Properties/launchSettings.json b/OsuFederatedBeatmapApi/Properties/launchSettings.json new file mode 100644 index 0000000..d0a30b4 --- /dev/null +++ b/OsuFederatedBeatmapApi/Properties/launchSettings.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:38546", + "sslPort": 44338 + } + }, + "profiles": { + "Development": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5075", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Local": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5075", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Local" + } + } + } +} diff --git a/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBot.cs b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBot.cs new file mode 100644 index 0000000..2b39d93 --- /dev/null +++ b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBot.cs @@ -0,0 +1,92 @@ +using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Services; +using LibMatrix.Utilities.Bot.Interfaces; + +namespace OsuFederatedBeatmapApi.Services; + +public class FederatedBeatmapApiBot(AuthenticatedHomeserverGeneric hs, + ILogger logger, + FederatedBeatmapApiBotConfiguration configuration, + HomeserverResolverService hsResolver, + FederatedBeatmapApiBotAccountDataService accountDataService) : IHostedService { + private readonly IEnumerable _commands; + + private Task _listenerTask; + + /// Triggered when the application host is ready to start the service. + /// Indicates that the start process has been aborted. + public async Task StartAsync(CancellationToken cancellationToken) { + _listenerTask = Run(cancellationToken); + logger.LogInformation("Bot started!"); + } + + private async Task Run(CancellationToken cancellationToken) { + Directory.GetFiles("bot_data/cache").ToList().ForEach(File.Delete); + + var syncHelper = new SyncHelper(hs); + + List admins = new(); + +#pragma warning disable CS4014 // We don't care if this doesn't wait + Task.Run(async () => { + while (!cancellationToken.IsCancellationRequested) { + var controlRoomMembers = accountDataService.ControlRoom.GetMembersAsync(); + await foreach (var member in controlRoomMembers) { + if ((member.TypedContent as RoomMemberEventContent)? + .Membership == "join") admins.Add(member.UserId); + } + + await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken); + } + }, cancellationToken); +#pragma warning restore CS4014 + + syncHelper.InviteReceivedHandlers.Add(async Task (args) => { + var inviteEvent = + args.Value.InviteState.Events.FirstOrDefault(x => + x.Type == "m.room.member" && x.StateKey == hs.UserId); + logger.LogInformation("Got invite to {RoomId} by {Sender} with reason: {Reason}", args.Key, inviteEvent!.Sender, + (inviteEvent.TypedContent as RoomMemberEventContent)!.Reason); + if (inviteEvent.Sender.EndsWith(":rory.gay") || inviteEvent!.Sender.EndsWith(":conduit.rory.gay") || admins.Contains(inviteEvent.Sender)) { + try { + var senderProfile = await hs.GetProfileAsync(inviteEvent.Sender); + await hs.GetRoom(args.Key).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!"); + } + catch (Exception e) { + logger.LogError("{}", e.ToString()); + await hs.GetRoom(args.Key).LeaveAsync(reason: "I was unable to join the room: " + e); + } + } + }); + + syncHelper.TimelineEventHandlers.Add(async @event => { + var room = hs.GetRoom(@event.RoomId); + try { + logger.LogInformation( + "Got timeline event in {}: {}", @event.RoomId, @event.ToJson(indent: true, ignoreNull: true)); + + if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message }) { } + } + catch (Exception e) { + logger.LogError("{}", e.ToString()); + await accountDataService.ControlRoom.SendMessageEventAsync( + MessageFormatter.FormatException($"Exception handling event {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); + await accountDataService.LogRoom.SendMessageEventAsync( + MessageFormatter.FormatException($"Exception handling event {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); + await using var stream = new MemoryStream(e.ToString().AsBytes().ToArray()); + await accountDataService.ControlRoom.SendFileAsync("error.log.cs", stream); + await accountDataService.LogRoom.SendFileAsync("error.log.cs", stream); + } + }); + } + + /// Triggered when the application host is performing a graceful shutdown. + /// Indicates that the shutdown process should no longer be graceful. + public async Task StopAsync(CancellationToken cancellationToken) { + logger.LogInformation("Shutting down bot!"); + } +} diff --git a/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotAccountDataService.cs b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotAccountDataService.cs new file mode 100644 index 0000000..0f4aa6a --- /dev/null +++ b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotAccountDataService.cs @@ -0,0 +1,60 @@ +using LibMatrix; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using LibMatrix.RoomTypes; +using OsuFederatedBeatmapApi.Events.AccountData; + +namespace OsuFederatedBeatmapApi.Services; + +public class FederatedBeatmapApiBotAccountDataService(ILogger logger, AuthenticatedHomeserverGeneric hs, FederatedBeatmapApiBotConfiguration configuration) { + private const string BotDataKey = "gay.rory.federated_beatmap_repository_bot_data"; + + public GenericRoom LogRoom; + public GenericRoom ControlRoom; + private BotData botData; + + public async Task LoadAccountData() { + try { + botData = await hs.GetAccountDataAsync(BotDataKey); + } + catch (Exception e) { + if (e is not MatrixException { ErrorCode: "M_NOT_FOUND" }) { + logger.LogError("{}", e.ToString()); + throw; + } + + botData = new BotData(); + var creationContent = CreateRoomRequest.CreatePrivate(hs, name: "Beatmap Repository - Control room", roomAliasName: "beatmap-repo-control-room"); + creationContent.Invite = configuration.Admins; + creationContent.CreationContent["type"] = "gay.rory.federated_beatmap_repository.control_room"; + + botData.ControlRoom = (await hs.CreateRoom(creationContent)).RoomId; + + //set access rules to allow joining via control room + creationContent.InitialState.Add(new StateEvent { + Type = "m.room.join_rules", + StateKey = "", + TypedContent = new RoomJoinRulesEventContent { + JoinRule = "knock_restricted", + Allow = new() { + new RoomJoinRulesEventContent.AllowEntry { + Type = "m.room_membership", + RoomId = botData.ControlRoom + } + } + } + }); + + creationContent.Name = "Beatmap Repository - Log room"; + creationContent.RoomAliasName = "beatmap-repo-log-room"; + creationContent.CreationContent["type"] = "gay.rory.media_moderator_poc.log_room"; + botData.LogRoom = (await hs.CreateRoom(creationContent)).RoomId; + + await hs.SetAccountData(BotDataKey, botData); + } + + LogRoom = hs.GetRoom(botData.LogRoom ?? botData.ControlRoom); + ControlRoom = hs.GetRoom(botData.ControlRoom); + } +} diff --git a/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotConfiguration.cs b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotConfiguration.cs new file mode 100644 index 0000000..b77cd75 --- /dev/null +++ b/OsuFederatedBeatmapApi/Services/FederatedBeatmapApiBotConfiguration.cs @@ -0,0 +1,8 @@ +namespace OsuFederatedBeatmapApi.Services; + +public class FederatedBeatmapApiBotConfiguration { + public FederatedBeatmapApiBotConfiguration(IConfiguration config) => config.GetRequiredSection("BeatmapApiBot").Bind(this); + + public List Admins { get; set; } = new(); + public bool DemoMode { get; set; } = false; +} diff --git a/OsuFederatedBeatmapApi/appsettings.Development.json b/OsuFederatedBeatmapApi/appsettings.Development.json new file mode 100644 index 0000000..600efc3 --- /dev/null +++ b/OsuFederatedBeatmapApi/appsettings.Development.json @@ -0,0 +1,24 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "LibMatrixBot": { + // The homeserver to connect to + "Homeserver": "rory.gay", + // The access token to use + "AccessToken": "syt_xxxxxxxxxxxxxxxxx", + // The command prefix + "Prefix": "?" + }, + "MediaMod": { + // List of people who should be invited to the control room + "Admins": [ + "@emma:conduit.rory.gay", + "@emma:rory.gay" + ] + } +} diff --git a/OsuFederatedBeatmapApi/appsettings.json b/OsuFederatedBeatmapApi/appsettings.json new file mode 100644 index 0000000..cbabb33 --- /dev/null +++ b/OsuFederatedBeatmapApi/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/OsuFederatedBeatmapApi/deps.nix b/OsuFederatedBeatmapApi/deps.nix new file mode 100644 index 0000000..66affc3 --- /dev/null +++ b/OsuFederatedBeatmapApi/deps.nix @@ -0,0 +1,43 @@ +{ fetchNuGet }: [ + (fetchNuGet { pname = "ArcaneLibs"; version = "1.0.0-preview6437853305.78f6d30"; sha256 = "1m9gfkparqvzm7m28p5avr6dnnsqrazd81m36hlyaikfmvbhlfni"; }) + (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.linux-x64"; version = "7.0.8"; sha256 = "1w4x1spgb4x57qn3d1m9a3gyqnwwfk952kaxg8bqrki20a7qkhl4"; }) + (fetchNuGet { pname = "Microsoft.AspNetCore.OpenApi"; version = "7.0.10"; sha256 = "091gn10yd444p6x0ya4j1gqchpnlkml3whly3scczskp2fg5i0qf"; }) + (fetchNuGet { pname = "Microsoft.Extensions.ApiDescription.Server"; version = "6.0.5"; sha256 = "1pi2bm3cm0a7jzqzmfc2r7bpcdkmk3hhjfvb2c81j7wl7xdw3624"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Configuration"; version = "7.0.0"; sha256 = "0n1grglxql9llmrsbbnlz5chx8mxrb5cpvjngm0hfyrkgzcwz90d"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Abstractions"; version = "7.0.0"; sha256 = "1as8cygz0pagg17w22nsf6mb49lr2mcl1x8i3ad1wi8lyzygy1a3"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Binder"; version = "7.0.3"; sha256 = "1n59jk6kqqy5f0gfx99b4j2m2clylznvxj1dwm1fjn3gmh2pi35v"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Configuration.CommandLine"; version = "7.0.0"; sha256 = "1pmgjrvwdzqrxjb24cg3fd624r64lgywbqc9symd5hyl4175pwk8"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Configuration.EnvironmentVariables"; version = "7.0.0"; sha256 = "0nhh7rnh45s39x8sjn88czg7nyfpry85pkm0g619j8b468zj8nb4"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Configuration.FileExtensions"; version = "7.0.0"; sha256 = "1fk7dcz6gfhd1k1d8ksz22rnjvj1waqjzk29ym4i3dz73rsq8j1i"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Json"; version = "7.0.0"; sha256 = "05zjmrpp99l128wijp1fy8asskc11ls871qaqr4mjnz3gbfycxnj"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Configuration.UserSecrets"; version = "7.0.0"; sha256 = "0ks7lcyvfvr3ar36f5gp89bnnblxzic5vawppfcrvhw1ivas4mp1"; }) + (fetchNuGet { pname = "Microsoft.Extensions.DependencyInjection"; version = "7.0.0"; sha256 = "121zs4jp8iimgbpzm3wsglhjwkc06irg1pxy8c1zcdlsg34cfq1p"; }) + (fetchNuGet { pname = "Microsoft.Extensions.DependencyInjection.Abstractions"; version = "7.0.0"; sha256 = "181d7mp9307fs17lyy42f8cxnjwysddmpsalky4m0pqxcimnr6g7"; }) + (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Abstractions"; version = "7.0.0"; sha256 = "0ff20yklyjgyjzdyv7sybczgqhgd557m05dbwxzjznr0x41b180d"; }) + (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Physical"; version = "7.0.0"; sha256 = "1f1h0l47abw0spssd64qkhgd7b54pyzslyb586zp21milimcfmgv"; }) + (fetchNuGet { pname = "Microsoft.Extensions.FileSystemGlobbing"; version = "7.0.0"; sha256 = "1812vnkn8n0i4yr3k5azcxcfx1bbpcsmms95rdyxjfrzfksr05ai"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Hosting"; version = "7.0.1"; sha256 = "1044pm14ark2d7xiqll1cykawmp6m2f3ba9w6nfw0r3a2wfbmnxn"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Hosting.Abstractions"; version = "7.0.0"; sha256 = "1h5szfsr1dalsvdj9c18y6362853chisfns0hfpsq44hz0pr8j9q"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Logging"; version = "7.0.0"; sha256 = "1bqd3pqn5dacgnkq0grc17cgb2i0w8z1raw12nwm3p3zhrfcvgxf"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Logging.Abstractions"; version = "7.0.1"; sha256 = "0xv3sqc1lbx5j4yy6g2w3kakzvrpwqs2ihax6lqasj5sz5map6fk"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Logging.Configuration"; version = "7.0.0"; sha256 = "1f5fhpvzwyrwxh3g1ry027s4skmklf6mbm2w0p13h0x6fbmxcb24"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Logging.Console"; version = "7.0.0"; sha256 = "1m8ri2m3vlv9vzk0068jkrx0vkk4sqmk1kxmn8pc3wys38d38qaf"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Logging.Debug"; version = "7.0.0"; sha256 = "14p7hrhdd58fxdhjbyjwmlzr00vs03bmns3sf2f6alsgpvbf2h1i"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Logging.EventLog"; version = "7.0.0"; sha256 = "0q1cgi456shngxs70ar0ibshpm5qk8whw369jrl6xdxnf1vxkkq8"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Logging.EventSource"; version = "7.0.0"; sha256 = "11rskmrijf6xv78slm38zywj6l3wjlm017kijhan1kfg56f1kvdk"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Options"; version = "7.0.1"; sha256 = "0ghz4y4gxnf2vw8yvhz9nkw21p6q2qqwh19phkk1xwxywyilr3mq"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Options.ConfigurationExtensions"; version = "7.0.0"; sha256 = "1liyprh0zha2vgmqh92n8kkjz61zwhr7g16f0gmr297z2rg1j5pj"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Primitives"; version = "7.0.0"; sha256 = "1b4km9fszid9vp2zb3gya5ni9fn8bq62bzaas2ck2r7gs0sdys80"; }) + (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.linux-x64"; version = "7.0.8"; sha256 = "0hp2dsjxg2q3wqnrs8mhfna7pyn9yq55n4rqdydyz2v4d6nsp00l"; }) + (fetchNuGet { pname = "Microsoft.OpenApi"; version = "1.2.3"; sha256 = "07b19k89whj69j87afkz86gp9b3iybw8jqwvlgcn43m7fb2y99rr"; }) + (fetchNuGet { pname = "Microsoft.OpenApi"; version = "1.4.3"; sha256 = "1q21vcxlnxkl6kgi2jn6bnl9nlr8bcs892qx4xbqmhhfpxxknkmy"; }) + (fetchNuGet { pname = "Swashbuckle.AspNetCore"; version = "6.5.0"; sha256 = "0k61chpz5j59s1yax28vx0mppx20ff8vg8grwja112hfrzj1f45n"; }) + (fetchNuGet { pname = "Swashbuckle.AspNetCore.Swagger"; version = "6.5.0"; sha256 = "1s6axf6fin8sss3bvzp0s039rxrx71vx4rl559miw12bz3lld8kc"; }) + (fetchNuGet { pname = "Swashbuckle.AspNetCore.SwaggerGen"; version = "6.5.0"; sha256 = "0hq93gy5vyrigpdk9lhqwxglxwkbxa8ydllwcqs4bwfcsspzrs83"; }) + (fetchNuGet { pname = "Swashbuckle.AspNetCore.SwaggerUI"; version = "6.5.0"; sha256 = "17hx7kc187higm0gk67dndng3n7932sn3fwyj48l45cvyr3025h7"; }) + (fetchNuGet { pname = "System.Diagnostics.DiagnosticSource"; version = "7.0.1"; sha256 = "1ajh5y33apcypz5pnvzxsx44n95ciskzpvbxk0kfg7n9li3nfs1v"; }) + (fetchNuGet { pname = "System.Diagnostics.EventLog"; version = "7.0.0"; sha256 = "16p8z975dnzmncfifa9gw9n3k9ycpr2qvz7lglpghsvx0fava8k9"; }) + (fetchNuGet { pname = "System.Text.Encodings.Web"; version = "7.0.0"; sha256 = "1151hbyrcf8kyg1jz8k9awpbic98lwz9x129rg7zk1wrs6vjlpxl"; }) + (fetchNuGet { pname = "System.Text.Json"; version = "7.0.0"; sha256 = "0scb0lp7wbgcinaa4kqiqs7b8i5nx4ppfad81138jiwd1sl37pyp"; }) + (fetchNuGet { pname = "Unidecode.NET"; version = "2.1.0"; sha256 = "11v6xcc529dx8pghkyrdbhhn3gilphjqkc7z73nighfqdz24gz4x"; }) +] -- cgit 1.4.1