Added API to DiscordBotCore

This commit is contained in:
2024-11-02 15:43:35 +02:00
parent bd3f79430b
commit f2a9982d41
13 changed files with 343 additions and 17 deletions

View File

@@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using DiscordBotCore.API.Endpoints;
using DiscordBotCore.API.Endpoints.PluginManagement;
using DiscordBotCore.Interfaces.API;
using DiscordBotCore.Others;
using Microsoft.AspNetCore.Builder;
namespace DiscordBotCore.API;
public class ApiManager
{
private bool IsRunning { get; set; }
private List<IEndpoint> ApiEndpoints { get; }
public ApiManager()
{
ApiEndpoints = new List<IEndpoint>();
}
public Result AddEndpoint(IEndpoint endpoint)
{
if (ApiEndpoints.Contains(endpoint) || ApiEndpoints.Exists(x => x.Path == endpoint.Path))
{
return Result.Failure("Endpoint already exists");
}
ApiEndpoints.Add(endpoint);
return Result.Success();
}
public void RemoveEndpoint(string endpointPath)
{
this.ApiEndpoints.RemoveAll(endpoint => endpoint.Path == endpointPath);
}
public bool EndpointExists(string endpointPath)
{
return this.ApiEndpoints.Exists(endpoint => endpoint.Path == endpointPath);
}
internal void AddBaseEndpoints()
{
AddEndpoint(new HomeEndpoint());
AddEndpoint(new PluginListEndpoint());
}
public async Task InitializeApi()
{
if (IsRunning)
return;
IsRunning = true;
var builder = WebApplication.CreateBuilder();
var app = builder.Build();
app.UseRouting();
EndpointManager manager = new EndpointManager(app);
foreach(IEndpoint endpoint in this.ApiEndpoints)
{
manager.MapEndpoint(endpoint);
}
await app.RunAsync();
}
}

View File

@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using DiscordBotCore.Others;
namespace DiscordBotCore.API;
public class ApiResponse
{
public string Message { get; set; }
public bool Success { get; set; }
private ApiResponse(string message, bool success)
{
Message = message;
Success = success;
}
public static ApiResponse From(string message, bool success)
{
return new ApiResponse(message, success);
}
public static ApiResponse Fail(string message)
{
return new ApiResponse(message, false);
}
public static ApiResponse Ok()
{
return new ApiResponse(string.Empty, true);
}
public async Task<string> ToJson() => await JsonManager.ConvertToJsonString(this);
}

View File

@@ -0,0 +1,80 @@
using System.IO;
using System.Text;
using DiscordBotCore.Interfaces.API;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace DiscordBotCore.API;
internal sealed class EndpointManager
{
private WebApplication _appBuilder;
internal EndpointManager(WebApplication appBuilder)
{
_appBuilder = appBuilder;
}
internal void MapEndpoint(IEndpoint endpoint)
{
switch (endpoint.HttpMethod)
{
case EndpointType.Get:
_appBuilder.MapGet(endpoint.Path, async context =>
{
//convert the context to a string
string jsonRequest = string.Empty;
if (context.Request.Body.CanRead)
{
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8);
jsonRequest = await reader.ReadToEndAsync();
}
var response = await endpoint.HandleRequest(jsonRequest);
await context.Response.WriteAsync(await response.ToJson());
});
break;
case EndpointType.Put:
_appBuilder.MapPut(endpoint.Path, async context =>
{
string jsonRequest = string.Empty;
if (context.Request.Body.CanRead)
{
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8);
jsonRequest = await reader.ReadToEndAsync();
}
var response = await endpoint.HandleRequest(jsonRequest);
await context.Response.WriteAsync(await response.ToJson());
});
break;
case EndpointType.Post:
_appBuilder.MapPost(endpoint.Path, async context =>
{
string jsonRequest = string.Empty;
if (context.Request.Body.CanRead)
{
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8);
jsonRequest = await reader.ReadToEndAsync();
}
var response = await endpoint.HandleRequest(jsonRequest);
await context.Response.WriteAsync(await response.ToJson());
});
break;
case EndpointType.Delete:
_appBuilder.MapDelete(endpoint.Path, async context =>
{
string jsonRequest = string.Empty;
if (context.Request.Body.CanRead)
{
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8);
jsonRequest = await reader.ReadToEndAsync();
}
var response = await endpoint.HandleRequest(jsonRequest);
await context.Response.WriteAsync(await response.ToJson());
});
break;
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.API;
using DiscordBotCore.Others;
using Microsoft.AspNetCore.Http;
namespace DiscordBotCore.API.Endpoints;
internal class HomeEndpoint : IEndpoint
{
private static readonly string _HomeMessage = "Welcome to the DiscordBot API.";
public string Path => "/";
EndpointType IEndpoint.HttpMethod => EndpointType.Get;
public async Task<ApiResponse> HandleRequest(string? jsonText)
{
string response = _HomeMessage;
if (jsonText != string.Empty)
{
var json = await JsonManager.ConvertFromJson<Dictionary<string,string>>(jsonText!);
response += $"\n\nYou sent the following JSON:\n{string.Join("\n", json.Select(x => $"{x.Key}: {x.Value}"))}";
}
return ApiResponse.From(response, true);
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.API;
using DiscordBotCore.Others;
using DiscordBotCore.Plugin;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
namespace DiscordBotCore.API.Endpoints.PluginManagement;
public class InstallPluginEndpoint : IEndpoint
{
public string Path => "/api/plugin/install";
public EndpointType HttpMethod => EndpointType.Put;
public async Task<ApiResponse> HandleRequest(string? jsonRequest)
{
Dictionary<string, string> jsonDict = await JsonManager.ConvertFromJson<Dictionary<string, string>>(jsonRequest);
string pluginName = jsonDict["pluginName"];
PluginOnlineInfo? pluginInfo = await Application.CurrentApplication.PluginManager.GetPluginDataByName(pluginName);
if (pluginInfo == null)
{
return ApiResponse.Fail("Plugin not found.");
}
await Application.CurrentApplication.PluginManager.InstallPlugin(pluginInfo, null);
return ApiResponse.Ok();
}
}

View File

@@ -0,0 +1,18 @@
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.API;
using DiscordBotCore.Others;
using Microsoft.AspNetCore.Http;
namespace DiscordBotCore.API.Endpoints.PluginManagement;
public class PluginListEndpoint : IEndpoint
{
public string Path => "/api/plugin/list/online";
public EndpointType HttpMethod => EndpointType.Get;
public async Task<ApiResponse> HandleRequest(string? jsonRequest)
{
var onlineInfos = await Application.CurrentApplication.PluginManager.GetPluginsList();
var response = await JsonManager.ConvertToJsonString(onlineInfos);
return ApiResponse.From(response, true);
}
}

View File

@@ -0,0 +1,17 @@
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.API;
using DiscordBotCore.Others;
namespace DiscordBotCore.API.Endpoints.PluginManagement;
public class PluginListInstalledEndpoint : IEndpoint
{
public string Path => "/api/plugin/list/local";
public EndpointType HttpMethod => EndpointType.Get;
public async Task<ApiResponse> HandleRequest(string? jsonRequest)
{
var listInstalled = await Application.CurrentApplication.PluginManager.GetInstalledPlugins();
var response = await JsonManager.ConvertToJsonString(listInstalled);
return ApiResponse.From(response, true);
}
}

View File

@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using DiscordBotCore.API;
using DiscordBotCore.Bot;
using DiscordBotCore.Interfaces.Logger;
using DiscordBotCore.Online;
@@ -45,6 +46,7 @@ namespace DiscordBotCore
public InternalActionManager InternalActionManager { get; private set; } = null!;
public PluginManager PluginManager { get; private set; } = null!;
public ILogger Logger { get; private set; } = null!;
public ApiManager? ApiManager { get; private set; }
/// <summary>
/// Create the application. This method is used to initialize the application. Can not initialize multiple times.
@@ -90,9 +92,32 @@ namespace DiscordBotCore
CurrentApplication.InternalActionManager = new InternalActionManager();
await CurrentApplication.InternalActionManager.Initialize();
IsRunning = true;
}
/// <summary>
/// Initialize the API in a separate thread
/// </summary>
public static void InitializeThreadedApi()
{
if (CurrentApplication is null)
{
return;
}
if(CurrentApplication.ApiManager is not null)
{
return;
}
CurrentApplication.ApiManager = new ApiManager();
CurrentApplication.ApiManager.AddBaseEndpoints();
Thread apiThread = new Thread(() => _ = CurrentApplication.ApiManager.InitializeApi());
apiThread.Start();
}
public static string GetResourceFullPath(string path)
{
var result = Path.Combine(_ResourcesFolder, path);

View File

@@ -1,19 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<FileAlignment>512</FileAlignment>
<DebugType>portable</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Config.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="BlankWindow1.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.15.3" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
@@ -21,4 +11,7 @@
<ItemGroup>
<UpToDateCheckInput Remove="UI\Controls\MessageBox.axaml" />
</ItemGroup>
<ItemGroup>
<Folder Include="API\Services\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using DiscordBotCore.API;
using DiscordBotCore.Others;
namespace DiscordBotCore.Interfaces.API;
public enum EndpointType
{
Get,
Post,
Put,
Delete
}
public interface IEndpoint
{
public string Path { get; }
public EndpointType HttpMethod { get; }
public Task<ApiResponse> HandleRequest(string? jsonRequest);
}

View File

@@ -18,7 +18,7 @@ public sealed class Logger : ILogger
public Logger(string logFolder, string logMessageFormat, Action<string, LogType>? outFunction = null)
{
this.LogMessageFormat = logMessageFormat;
var logFile = logFolder + DateTime.Now.ToString("yyyy-MM-dd") + ".log";
var logFile = Path.Combine(logFolder, $"{DateTime.Now:yyyy-MM-dd}.log");
_LogFileStream = File.Open(logFile, FileMode.Append, FileAccess.Write, FileShare.Read);
this._OutFunction = outFunction ?? DefaultLogFunction;
}

View File

@@ -8,6 +8,20 @@ namespace DiscordBotCore.Others;
public static class JsonManager
{
public static async Task<string> ConvertToJsonString<T>(T Data)
{
var str = new MemoryStream();
await JsonSerializer.SerializeAsync(str, Data, typeof(T), new JsonSerializerOptions
{
WriteIndented = false,
});
var result = Encoding.ASCII.GetString(str.ToArray());
await str.FlushAsync();
str.Close();
return result;
}
/// <summary>
/// Save to JSON file
/// </summary>