Redesigned the DiscordBotCore by splitting it into multiple projects. Created a WebUI and preparing to remove the DiscordBot application
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -454,4 +454,5 @@ $RECYCLE.BIN/
|
||||
!.vscode/extensions.json
|
||||
|
||||
/DiscordBotWebUI/Data
|
||||
/DiscordBot/Data
|
||||
/DiscordBot/Data
|
||||
/WebUI/Data
|
||||
@@ -7,7 +7,6 @@ using System.Threading.Tasks;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
using DiscordBotCore.Plugin;
|
||||
|
||||
namespace DiscordBot.Bot.Actions
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
namespace DiscordBot.Bot.Actions;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Threading.Tasks;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
namespace DiscordBot.Bot.Actions;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ using DiscordBot.Utilities;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace DiscordBot.Bot.Actions;
|
||||
|
||||
@@ -5,7 +5,6 @@ using DiscordBot.Bot.Actions.Extra;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
namespace DiscordBot.Bot.Actions;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ using DiscordBot.Bot.Actions.Extra;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
namespace DiscordBot.Bot.Actions;
|
||||
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace DiscordBotCore.Others.Settings;
|
||||
namespace DiscordBotCore.Configuration;
|
||||
|
||||
public class CustomSettingsDictionary : CustomSettingsDictionaryBase<string, object>
|
||||
public class Configuration : ConfigurationBase
|
||||
{
|
||||
private bool _EnableAutoAddOnGetWithDefault;
|
||||
private CustomSettingsDictionary(string diskLocation, bool enableAutoAddOnGetWithDefault): base(diskLocation)
|
||||
private readonly bool _EnableAutoAddOnGetWithDefault;
|
||||
private Configuration(ILogger logger, string diskLocation, bool enableAutoAddOnGetWithDefault): base(logger, diskLocation)
|
||||
{
|
||||
_EnableAutoAddOnGetWithDefault = enableAutoAddOnGetWithDefault;
|
||||
}
|
||||
@@ -47,7 +42,7 @@ public class CustomSettingsDictionary : CustomSettingsDictionaryBase<string, obj
|
||||
return value;
|
||||
}
|
||||
|
||||
public override async Task LoadFromFile()
|
||||
public override async void LoadFromFile()
|
||||
{
|
||||
if (!File.Exists(_DiskLocation))
|
||||
{
|
||||
@@ -109,10 +104,10 @@ public class CustomSettingsDictionary : CustomSettingsDictionaryBase<string, obj
|
||||
/// </summary>
|
||||
/// <param name="baseFile">The file location</param>
|
||||
/// <param name="enableAutoAddOnGetWithDefault">Set this to true if you want to update the dictionary with default values on get</param>
|
||||
internal static async Task<CustomSettingsDictionary> CreateFromFile(string baseFile, bool enableAutoAddOnGetWithDefault)
|
||||
public static Configuration CreateFromFile(ILogger logger, string baseFile, bool enableAutoAddOnGetWithDefault)
|
||||
{
|
||||
var settings = new CustomSettingsDictionary(baseFile, enableAutoAddOnGetWithDefault);
|
||||
await settings.LoadFromFile();
|
||||
var settings = new Configuration(logger, baseFile, enableAutoAddOnGetWithDefault);
|
||||
settings.LoadFromFile();
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Mime;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.Others.Settings;
|
||||
namespace DiscordBotCore.Configuration;
|
||||
|
||||
public abstract class CustomSettingsDictionaryBase<TKey,TValue> : ICustomSettingsDictionary<TKey,TValue>
|
||||
public abstract class ConfigurationBase : IConfiguration
|
||||
{
|
||||
protected readonly IDictionary<TKey,TValue> _InternalDictionary = new Dictionary<TKey, TValue>();
|
||||
protected readonly IDictionary<string, object> _InternalDictionary = new Dictionary<string, object>();
|
||||
protected readonly string _DiskLocation;
|
||||
protected readonly ILogger _Logger;
|
||||
|
||||
protected CustomSettingsDictionaryBase(string diskLocation)
|
||||
protected ConfigurationBase(ILogger logger, string diskLocation)
|
||||
{
|
||||
this._DiskLocation = diskLocation;
|
||||
this._Logger = logger;
|
||||
}
|
||||
|
||||
public virtual void Add(TKey key, TValue value)
|
||||
public virtual void Add(string key, object value)
|
||||
{
|
||||
if (_InternalDictionary.ContainsKey(key))
|
||||
return;
|
||||
@@ -27,27 +27,27 @@ public abstract class CustomSettingsDictionaryBase<TKey,TValue> : ICustomSetting
|
||||
_InternalDictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public virtual void Set(TKey key, TValue value)
|
||||
public virtual void Set(string key, object value)
|
||||
{
|
||||
_InternalDictionary[key] = value;
|
||||
}
|
||||
|
||||
public virtual TValue Get(TKey key)
|
||||
public virtual object Get(string key)
|
||||
{
|
||||
return _InternalDictionary[key];
|
||||
}
|
||||
|
||||
public virtual T Get<T>(TKey key, T defaultValue)
|
||||
public virtual T Get<T>(string key, T defaulobject)
|
||||
{
|
||||
if (_InternalDictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
return defaulobject;
|
||||
}
|
||||
|
||||
public virtual T? Get<T>(TKey key)
|
||||
public virtual T? Get<T>(string key)
|
||||
{
|
||||
if (_InternalDictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
@@ -57,7 +57,7 @@ public abstract class CustomSettingsDictionaryBase<TKey,TValue> : ICustomSetting
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual IDictionary<TSubKey, TSubValue> GetDictionary<TSubKey, TSubValue>(TKey key)
|
||||
public virtual IDictionary<TSubKey, TSubValue> GetDictionary<TSubKey, TSubValue>(string key)
|
||||
{
|
||||
if (_InternalDictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
@@ -78,7 +78,7 @@ public abstract class CustomSettingsDictionaryBase<TKey,TValue> : ICustomSetting
|
||||
return new Dictionary<TSubKey, TSubValue>();
|
||||
}
|
||||
|
||||
public virtual List<T> GetList<T>(TKey key, List<T> defaultValue)
|
||||
public virtual List<T> GetList<T>(string key, List<T> defaulobject)
|
||||
{
|
||||
if(_InternalDictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
@@ -96,17 +96,17 @@ public abstract class CustomSettingsDictionaryBase<TKey,TValue> : ICustomSetting
|
||||
return list;
|
||||
}
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Key '{key}' not found in settings dictionary. Adding default value.", LogType.Warning);
|
||||
_Logger.Log($"Key '{key}' not found in settings dictionary. Adding default value.", LogType.Warning);
|
||||
|
||||
return defaultValue;
|
||||
return defaulobject;
|
||||
}
|
||||
|
||||
public virtual void Remove(TKey key)
|
||||
public virtual void Remove(string key)
|
||||
{
|
||||
_InternalDictionary.Remove(key);
|
||||
}
|
||||
|
||||
public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
public virtual IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
return _InternalDictionary.GetEnumerator();
|
||||
}
|
||||
@@ -116,52 +116,52 @@ public abstract class CustomSettingsDictionaryBase<TKey,TValue> : ICustomSetting
|
||||
_InternalDictionary.Clear();
|
||||
}
|
||||
|
||||
public virtual bool ContainsKey(TKey key)
|
||||
public virtual bool ContainsKey(string key)
|
||||
{
|
||||
return _InternalDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<KeyValuePair<TKey, TValue>> Where(Func<KeyValuePair<TKey, TValue>, bool> predicate)
|
||||
public virtual IEnumerable<KeyValuePair<string, object>> Where(Func<KeyValuePair<string, object>, bool> predicate)
|
||||
{
|
||||
return _InternalDictionary.Where(predicate);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<KeyValuePair<TKey, TValue>> Where(Func<KeyValuePair<TKey, TValue>, int, bool> predicate)
|
||||
public virtual IEnumerable<KeyValuePair<string, object>> Where(Func<KeyValuePair<string, object>, int, bool> predicate)
|
||||
{
|
||||
return _InternalDictionary.Where(predicate);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<TKey, TValue>, TResult> selector)
|
||||
public virtual IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<string, object>, TResult> selector)
|
||||
{
|
||||
return _InternalDictionary.Select(selector);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<TKey, TValue>, int, TResult> selector)
|
||||
public virtual IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<string, object>, int, TResult> selector)
|
||||
{
|
||||
return _InternalDictionary.Select(selector);
|
||||
}
|
||||
|
||||
public virtual KeyValuePair<TKey, TValue> FirstOrDefault(Func<KeyValuePair<TKey, TValue>, bool> predicate)
|
||||
public virtual KeyValuePair<string, object> FirstOrDefault(Func<KeyValuePair<string, object>, bool> predicate)
|
||||
{
|
||||
return _InternalDictionary.FirstOrDefault(predicate);
|
||||
}
|
||||
|
||||
public virtual KeyValuePair<TKey, TValue> FirstOrDefault()
|
||||
public virtual KeyValuePair<string, object> FirstOrDefault()
|
||||
{
|
||||
return _InternalDictionary.FirstOrDefault();
|
||||
}
|
||||
|
||||
public virtual bool ContainsAllKeys(params TKey[] keys)
|
||||
public virtual bool ContainsAllKeys(params string[] keys)
|
||||
{
|
||||
return keys.All(ContainsKey);
|
||||
}
|
||||
|
||||
public virtual bool TryGetValue(TKey key, out TValue? value)
|
||||
public virtual bool TryGetValue(string key, out object? value)
|
||||
{
|
||||
return _InternalDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public abstract Task SaveToFile();
|
||||
|
||||
public abstract Task LoadFromFile();
|
||||
}
|
||||
public abstract void LoadFromFile();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,62 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
namespace DiscordBotCore.Configuration;
|
||||
|
||||
namespace DiscordBotCore.Others.Settings;
|
||||
|
||||
internal interface ICustomSettingsDictionary<TKey,TValue>
|
||||
public interface IConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds an element to the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="value">The value</param>
|
||||
void Add(TKey key, TValue value);
|
||||
void Add(string key, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a key in the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="value">The value</param>
|
||||
void Set(TKey key, TValue value);
|
||||
void Set(string key, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a key in the custom settings dictionary. If the T type is different then the TValue type, it will try to convert it.
|
||||
/// Gets the value of a key in the custom settings dictionary. If the T type is different then the object type, it will try to convert it.
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="defaultValue">The default value to be returned if the searched value is not found</param>
|
||||
/// <param name="defaulobject">The default value to be returned if the searched value is not found</param>
|
||||
/// <typeparam name="T">The type of the returned value</typeparam>
|
||||
/// <returns></returns>
|
||||
T Get<T>(TKey key, T defaultValue);
|
||||
T Get<T>(string key, T defaulobject);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a key in the custom settings dictionary. If the T type is different then the TValue type, it will try to convert it.
|
||||
/// Gets the value of a key in the custom settings dictionary. If the T type is different then the object type, it will try to convert it.
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <typeparam name="T">The type of the returned value</typeparam>
|
||||
/// <returns></returns>
|
||||
T? Get<T>(TKey key);
|
||||
T? Get<T>(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of values from the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="defaultValue">The default list to be returned if nothing is found</param>
|
||||
/// <param name="defaulobject">The default list to be returned if nothing is found</param>
|
||||
/// <typeparam name="T">The type of the returned value</typeparam>
|
||||
/// <returns></returns>
|
||||
List<T> GetList<T>(TKey key, List<T> defaultValue);
|
||||
List<T> GetList<T>(string key, List<T> defaulobject);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a key from the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
void Remove(TKey key);
|
||||
void Remove(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Get the enumerator of the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
|
||||
IEnumerator<KeyValuePair<string, object>> GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Clear the custom settings dictionary
|
||||
@@ -68,51 +64,51 @@ internal interface ICustomSettingsDictionary<TKey,TValue>
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <returns></returns>
|
||||
bool ContainsKey(TKey key);
|
||||
bool ContainsKey(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Filter the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<KeyValuePair<TKey, TValue>> Where(Func<KeyValuePair<TKey, TValue>, bool> predicate);
|
||||
IEnumerable<KeyValuePair<string, object>> Where(Func<KeyValuePair<string, object>, bool> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Filter the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate</param>
|
||||
IEnumerable<KeyValuePair<TKey, TValue>> Where(Func<KeyValuePair<TKey, TValue>, int, bool> predicate);
|
||||
IEnumerable<KeyValuePair<string, object>> Where(Func<KeyValuePair<string, object>, int, bool> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Filter the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="selector">The predicate</param>
|
||||
IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<TKey, TValue>, TResult> selector);
|
||||
IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<string, object>, TResult> selector);
|
||||
|
||||
/// <summary>
|
||||
/// Filter the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="selector">The predicate</param>
|
||||
IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<TKey, TValue>, int, TResult> selector);
|
||||
IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<string, object>, int, TResult> selector);
|
||||
|
||||
/// <summary>
|
||||
/// Get the first element of the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate</param>
|
||||
KeyValuePair<TKey, TValue> FirstOrDefault(Func<KeyValuePair<TKey, TValue>, bool> predicate);
|
||||
KeyValuePair<string, object> FirstOrDefault(Func<KeyValuePair<string, object>, bool> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Get the first element of the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
KeyValuePair<TKey, TValue> FirstOrDefault();
|
||||
KeyValuePair<string, object> FirstOrDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the custom settings dictionary contains all the keys
|
||||
/// </summary>
|
||||
/// <param name="keys">A list of keys</param>
|
||||
/// <returns></returns>
|
||||
bool ContainsAllKeys(params TKey[] keys);
|
||||
bool ContainsAllKeys(params string[] keys);
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the value of a key in the custom settings dictionary
|
||||
@@ -120,7 +116,7 @@ internal interface ICustomSettingsDictionary<TKey,TValue>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="value">The value</param>
|
||||
/// <returns></returns>
|
||||
bool TryGetValue(TKey key, out TValue? value);
|
||||
bool TryGetValue(string key, out object? value);
|
||||
|
||||
/// <summary>
|
||||
/// Save the custom settings dictionary to a file
|
||||
@@ -132,5 +128,5 @@ internal interface ICustomSettingsDictionary<TKey,TValue>
|
||||
/// Load the custom settings dictionary from a file
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task LoadFromFile();
|
||||
}
|
||||
void LoadFromFile();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,11 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DiscordBotCore.Database;
|
||||
namespace DiscordBotCore.Database.Sqlite;
|
||||
|
||||
public class SqlDatabase
|
||||
{
|
||||
@@ -19,7 +15,6 @@ public class SqlDatabase
|
||||
{
|
||||
var connectionString = $"Data Source={fileName}";
|
||||
_Connection = new SqliteConnection(connectionString);
|
||||
|
||||
}
|
||||
|
||||
|
||||
9
DiscordBotCore.Logging/DiscordBotCore.Logging.csproj
Normal file
9
DiscordBotCore.Logging/DiscordBotCore.Logging.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.Interfaces.Logger;
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
public interface ILogMessage
|
||||
{
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.Interfaces.Logger;
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using DiscordBotCore.Interfaces.Logger;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.Logging
|
||||
namespace DiscordBotCore.Logging
|
||||
{
|
||||
internal sealed class LogMessage : ILogMessage
|
||||
{
|
||||
9
DiscordBotCore.Logging/LogType.cs
Normal file
9
DiscordBotCore.Logging/LogType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
public enum LogType
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Critical
|
||||
}
|
||||
@@ -1,26 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using DiscordBotCore.Interfaces.Logger;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.Logging;
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
public sealed class Logger : ILogger
|
||||
{
|
||||
private FileStream _LogFileStream;
|
||||
private FileStream _logFileStream;
|
||||
|
||||
private readonly List<string> _LogMessageProperties = typeof(ILogMessage).GetProperties().Select(p => p.Name).ToList();
|
||||
private Action<string, LogType>? _OutFunction;
|
||||
private readonly List<string> _logMessageProperties = typeof(ILogMessage).GetProperties().Select(p => p.Name).ToList();
|
||||
private Action<string, LogType>? _outFunction;
|
||||
public string LogMessageFormat { get ; set; }
|
||||
|
||||
public Logger(string logFolder, string logMessageFormat, Action<string, LogType>? outFunction = null)
|
||||
{
|
||||
this.LogMessageFormat = logMessageFormat;
|
||||
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;
|
||||
_logFileStream = File.Open(logFile, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
this._outFunction = outFunction ?? DefaultLogFunction;
|
||||
}
|
||||
|
||||
private void DefaultLogFunction(string message, LogType logType)
|
||||
@@ -36,10 +29,10 @@ public sealed class Logger : ILogger
|
||||
private string GenerateLogMessage(ILogMessage message)
|
||||
{
|
||||
string messageAsString = new string(LogMessageFormat);
|
||||
foreach (var prop in _LogMessageProperties)
|
||||
foreach (var prop in _logMessageProperties)
|
||||
{
|
||||
Type messageType = typeof(ILogMessage);
|
||||
messageAsString = messageAsString.Replace("{" + prop + "}", messageType?.GetProperty(prop)?.GetValue(message)?.ToString());
|
||||
messageAsString = messageAsString.Replace("{" + prop + "}", messageType.GetProperty(prop)?.GetValue(message)?.ToString());
|
||||
}
|
||||
|
||||
return messageAsString;
|
||||
@@ -48,21 +41,21 @@ public sealed class Logger : ILogger
|
||||
private async void LogToFile(string message)
|
||||
{
|
||||
byte[] messageAsBytes = System.Text.Encoding.ASCII.GetBytes(message);
|
||||
await _LogFileStream.WriteAsync(messageAsBytes, 0, messageAsBytes.Length);
|
||||
await _logFileStream.WriteAsync(messageAsBytes, 0, messageAsBytes.Length);
|
||||
|
||||
byte[] newLine = System.Text.Encoding.ASCII.GetBytes(Environment.NewLine);
|
||||
await _LogFileStream.WriteAsync(newLine, 0, newLine.Length);
|
||||
await _logFileStream.WriteAsync(newLine, 0, newLine.Length);
|
||||
|
||||
await _LogFileStream.FlushAsync();
|
||||
await _logFileStream.FlushAsync();
|
||||
}
|
||||
|
||||
private string GenerateLogMessage(ILogMessage message, string customFormat)
|
||||
{
|
||||
string messageAsString = customFormat;
|
||||
foreach (var prop in _LogMessageProperties)
|
||||
foreach (var prop in _logMessageProperties)
|
||||
{
|
||||
Type messageType = typeof(ILogMessage);
|
||||
messageAsString = messageAsString.Replace("{" + prop + "}", messageType?.GetProperty(prop)?.GetValue(message)?.ToString());
|
||||
messageAsString = messageAsString.Replace("{" + prop + "}", messageType.GetProperty(prop)?.GetValue(message)?.ToString());
|
||||
}
|
||||
|
||||
return messageAsString;
|
||||
@@ -71,14 +64,14 @@ public sealed class Logger : ILogger
|
||||
public void Log(ILogMessage message, string format)
|
||||
{
|
||||
string messageAsString = GenerateLogMessage(message, format);
|
||||
_OutFunction?.Invoke(messageAsString, message.LogMessageType);
|
||||
_outFunction?.Invoke(messageAsString, message.LogMessageType);
|
||||
LogToFile(messageAsString);
|
||||
}
|
||||
|
||||
public void Log(ILogMessage message)
|
||||
{
|
||||
string messageAsString = GenerateLogMessage(message);
|
||||
_OutFunction?.Invoke(messageAsString, message.LogMessageType);
|
||||
_outFunction?.Invoke(messageAsString, message.LogMessageType);
|
||||
LogToFile(messageAsString);
|
||||
|
||||
}
|
||||
@@ -86,23 +79,23 @@ public sealed class Logger : ILogger
|
||||
public void Log(string message) => Log(new LogMessage(message, string.Empty, LogType.Info));
|
||||
public void Log(string message, LogType logType, string format) => Log(new LogMessage(message, logType), format);
|
||||
public void Log(string message, LogType logType) => Log(new LogMessage(message, logType));
|
||||
public void Log(string message, object Sender) => Log(new LogMessage(message, Sender));
|
||||
public void Log(string message, object Sender, LogType type) => Log(new LogMessage(message, Sender, type));
|
||||
public void LogException(Exception exception, object Sender, bool logFullStack = false) => Log(LogMessage.CreateFromException(exception, Sender, logFullStack));
|
||||
public void Log(string message, object sender) => Log(new LogMessage(message, sender));
|
||||
public void Log(string message, object sender, LogType type) => Log(new LogMessage(message, sender, type));
|
||||
public void LogException(Exception exception, object sender, bool logFullStack = false) => Log(LogMessage.CreateFromException(exception, sender, logFullStack));
|
||||
|
||||
public void SetOutFunction(Action<string, LogType> outFunction)
|
||||
{
|
||||
this._OutFunction = outFunction;
|
||||
this._outFunction = outFunction;
|
||||
}
|
||||
public string GetLogsHistory()
|
||||
{
|
||||
string fileName = _LogFileStream.Name;
|
||||
string fileName = _logFileStream.Name;
|
||||
|
||||
_LogFileStream.Flush();
|
||||
_LogFileStream.Close();
|
||||
_logFileStream.Flush();
|
||||
_logFileStream.Close();
|
||||
|
||||
string[] logs = File.ReadAllLines(fileName);
|
||||
_LogFileStream = File.Open(fileName, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
_logFileStream = File.Open(fileName, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
|
||||
return string.Join(Environment.NewLine, logs);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
25
DiscordBotCore.Networking/FileDownloader.cs
Normal file
25
DiscordBotCore.Networking/FileDownloader.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using DiscordBotCore.Networking.Helpers;
|
||||
|
||||
namespace DiscordBotCore.Networking;
|
||||
|
||||
public class FileDownloader
|
||||
{
|
||||
private readonly string _DownloadUrl;
|
||||
private readonly string _DownloadLocation;
|
||||
|
||||
private readonly HttpClient _HttpClient;
|
||||
|
||||
public FileDownloader(string downloadUrl, string downloadLocation)
|
||||
{
|
||||
_DownloadUrl = downloadUrl;
|
||||
_DownloadLocation = downloadLocation;
|
||||
|
||||
_HttpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task DownloadFile(Action<float> progressCallback)
|
||||
{
|
||||
await using var fileStream = new FileStream(_DownloadLocation, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
await _HttpClient.DownloadFileAsync(_DownloadUrl, fileStream, new Progress<float>(progressCallback));
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.Online.Helpers;
|
||||
namespace DiscordBotCore.Networking.Helpers;
|
||||
|
||||
internal static class OnlineFunctions
|
||||
{
|
||||
@@ -22,7 +15,7 @@ internal static class OnlineFunctions
|
||||
/// <exception cref="ArgumentOutOfRangeException">Triggered if <paramref name="bufferSize" /> is less then or equal to 0</exception>
|
||||
/// <exception cref="InvalidOperationException">Triggered if <paramref name="stream" /> is not readable</exception>
|
||||
/// <exception cref="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception>
|
||||
public static async Task CopyToOtherStreamAsync(
|
||||
private static async Task CopyToOtherStreamAsync(
|
||||
this Stream stream, Stream destination, int bufferSize,
|
||||
IProgress<long>? progress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -95,16 +88,4 @@ internal static class OnlineFunctions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read contents of a file as string from specified URL
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to read from</param>
|
||||
/// <param name="cancellation">The cancellation token</param>
|
||||
/// <returns></returns>
|
||||
internal static async Task<string> DownloadStringAsync(string url, CancellationToken cancellation = default)
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
return await client.GetStringAsync(url, cancellation);
|
||||
}
|
||||
}
|
||||
89
DiscordBotCore.Networking/ParallelDownloadExecutor.cs
Normal file
89
DiscordBotCore.Networking/ParallelDownloadExecutor.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using DiscordBotCore.Networking.Helpers;
|
||||
|
||||
namespace DiscordBotCore.Networking;
|
||||
|
||||
public class ParallelDownloadExecutor
|
||||
{
|
||||
private readonly List<Task> _listOfTasks;
|
||||
private readonly HttpClient _httpClient;
|
||||
private Action? OnFinishAction { get; set; }
|
||||
|
||||
public ParallelDownloadExecutor(List<Task> listOfTasks)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_listOfTasks = listOfTasks;
|
||||
}
|
||||
|
||||
public ParallelDownloadExecutor()
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_listOfTasks = new List<Task>();
|
||||
}
|
||||
|
||||
public async Task StartTasks()
|
||||
{
|
||||
await Task.WhenAll(_listOfTasks);
|
||||
OnFinishAction?.Invoke();
|
||||
}
|
||||
|
||||
public async Task ExecuteAllTasks(int maxDegreeOfParallelism = 4)
|
||||
{
|
||||
using var semaphore = new SemaphoreSlim(maxDegreeOfParallelism);
|
||||
|
||||
var tasks = _listOfTasks.Select(async task =>
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
await task;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
OnFinishAction?.Invoke();
|
||||
}
|
||||
|
||||
public void SetFinishAction(Action action)
|
||||
{
|
||||
OnFinishAction = action;
|
||||
}
|
||||
|
||||
public void AddTask(string downloadLink, string downloadLocation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(downloadLink) || string.IsNullOrEmpty(downloadLocation))
|
||||
throw new ArgumentException("Download link or location cannot be null or empty.");
|
||||
|
||||
if (Directory.Exists(Path.GetDirectoryName(downloadLocation)) == false)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(downloadLocation));
|
||||
}
|
||||
|
||||
var task = CreateDownloadTask(downloadLink, downloadLocation, null);
|
||||
_listOfTasks.Add(task);
|
||||
}
|
||||
|
||||
public void AddTask(string downloadLink, string downloadLocation, Action<float> progressCallback)
|
||||
{
|
||||
if (string.IsNullOrEmpty(downloadLink) || string.IsNullOrEmpty(downloadLocation))
|
||||
throw new ArgumentException("Download link or location cannot be null or empty.");
|
||||
|
||||
if (Directory.Exists(Path.GetDirectoryName(downloadLocation)) == false)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(downloadLocation));
|
||||
}
|
||||
|
||||
var task = CreateDownloadTask(downloadLink, downloadLocation, new Progress<float>(progressCallback));
|
||||
_listOfTasks.Add(task);
|
||||
}
|
||||
|
||||
private Task CreateDownloadTask(string downloadLink, string downloadLocation, IProgress<float> progress)
|
||||
{
|
||||
var fileStream = new FileStream(downloadLocation, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
return _httpClient.DownloadFileAsync(downloadLink, fileStream, progress);
|
||||
}
|
||||
|
||||
}
|
||||
21
DiscordBotCore.PluginCore/DiscordBotCore.PluginCore.csproj
Normal file
21
DiscordBotCore.PluginCore/DiscordBotCore.PluginCore.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.17.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Helpers\Execution\DbSlashCommand\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,26 @@
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
|
||||
public class DbCommandExecutingArgument : IDbCommandExecutingArgument
|
||||
{
|
||||
public SocketCommandContext Context { get; init; }
|
||||
public string CleanContent { get; init; }
|
||||
public string CommandUsed { get; init; }
|
||||
public string[]? Arguments { get; init; }
|
||||
public ILogger Logger { get; init; }
|
||||
public DirectoryInfo PluginBaseDirectory { get; init; }
|
||||
|
||||
public DbCommandExecutingArgument(ILogger logger, SocketCommandContext context, string cleanContent, string commandUsed, string[]? arguments, DirectoryInfo pluginBaseDirectory)
|
||||
{
|
||||
this.Logger = logger;
|
||||
this.Context = context;
|
||||
this.CleanContent = cleanContent;
|
||||
this.CommandUsed = commandUsed;
|
||||
this.Arguments = arguments;
|
||||
this.PluginBaseDirectory = pluginBaseDirectory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
|
||||
public interface IDbCommandExecutingArgument
|
||||
{
|
||||
ILogger Logger { get; init; }
|
||||
string CleanContent { get; init; }
|
||||
string CommandUsed { get; init; }
|
||||
string[]? Arguments { get; init; }
|
||||
|
||||
SocketCommandContext Context { get; init; }
|
||||
public DirectoryInfo PluginBaseDirectory { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
|
||||
public class DbEventExecutingArgument : IDbEventExecutingArgument
|
||||
{
|
||||
public ILogger Logger { get; }
|
||||
public DiscordSocketClient Client { get; }
|
||||
public string BotPrefix { get; }
|
||||
public DirectoryInfo PluginBaseDirectory { get; }
|
||||
|
||||
public DbEventExecutingArgument(ILogger logger, DiscordSocketClient client, string botPrefix, DirectoryInfo pluginBaseDirectory)
|
||||
{
|
||||
Logger = logger;
|
||||
Client = client;
|
||||
BotPrefix = botPrefix;
|
||||
PluginBaseDirectory = pluginBaseDirectory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
|
||||
public interface IDbEventExecutingArgument
|
||||
{
|
||||
public ILogger Logger { get; }
|
||||
public DiscordSocketClient Client { get; }
|
||||
public string BotPrefix { get; }
|
||||
public DirectoryInfo PluginBaseDirectory { get; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace DiscordBotCore.PluginCore.Helpers;
|
||||
|
||||
public interface IInternalActionOption
|
||||
{
|
||||
string OptionName { get; set; }
|
||||
string OptionDescription { get; set; }
|
||||
List<InternalActionOption> SubOptions { get; set; }
|
||||
}
|
||||
23
DiscordBotCore.PluginCore/Helpers/InternalActionOption.cs
Normal file
23
DiscordBotCore.PluginCore/Helpers/InternalActionOption.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace DiscordBotCore.PluginCore.Helpers;
|
||||
|
||||
public class InternalActionOption : IInternalActionOption
|
||||
{
|
||||
public string OptionName { get; set; }
|
||||
public string OptionDescription { get; set; }
|
||||
|
||||
public List<InternalActionOption> SubOptions { get; set; }
|
||||
|
||||
public InternalActionOption(string optionName, string optionDescription, List<InternalActionOption> subOptions)
|
||||
{
|
||||
OptionName = optionName;
|
||||
OptionDescription = optionDescription;
|
||||
SubOptions = subOptions;
|
||||
}
|
||||
|
||||
public InternalActionOption(string optionName, string optionDescription)
|
||||
{
|
||||
OptionName = optionName;
|
||||
OptionDescription = optionDescription;
|
||||
SubOptions = new List<InternalActionOption>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace DiscordBotCore.PluginCore.Helpers;
|
||||
|
||||
public enum InternalActionRunType
|
||||
{
|
||||
OnStartup,
|
||||
OnCall,
|
||||
OnStartupAndCall
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginCore.Helpers;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
|
||||
namespace DiscordBotCore.Interfaces;
|
||||
namespace DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
public interface IDbCommand
|
||||
{
|
||||
@@ -35,16 +36,12 @@ public interface IDbCommand
|
||||
/// <summary>
|
||||
/// The main body of the command. This is what is executed when user calls the command in Server
|
||||
/// </summary>
|
||||
/// <param name="args">The disocrd Context</param>
|
||||
void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
}
|
||||
/// <param name="args">The Discord Context</param>
|
||||
Task ExecuteServer(IDbCommandExecutingArgument args) => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// The main body of the command. This is what is executed when user calls the command in DM
|
||||
/// </summary>
|
||||
/// <param name="args">The disocrd Context</param>
|
||||
void ExecuteDm(DbCommandExecutingArguments args)
|
||||
{
|
||||
}
|
||||
/// <param name="args">The Discord Context</param>
|
||||
Task ExecuteDm(IDbCommandExecutingArgument args) => Task.CompletedTask;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.PluginCore.Helpers;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
|
||||
namespace DiscordBotCore.Interfaces;
|
||||
namespace DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
public interface IDbEvent
|
||||
{
|
||||
@@ -17,6 +18,6 @@ public interface IDbEvent
|
||||
/// <summary>
|
||||
/// The method that is invoked when the event is loaded into memory
|
||||
/// </summary>
|
||||
/// <param name="client">The discord bot client</param>
|
||||
void Start(DiscordSocketClient client);
|
||||
/// <param name="args">The arguments for the start method</param>
|
||||
Task Start(IDbEventExecutingArgument args);
|
||||
}
|
||||
22
DiscordBotCore.PluginCore/Interfaces/IDbSlashCommand.cs
Normal file
22
DiscordBotCore.PluginCore/Interfaces/IDbSlashCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
public interface IDbSlashCommand
|
||||
{
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
bool CanUseDm { get; }
|
||||
bool HasInteraction { get; }
|
||||
|
||||
List<SlashCommandOptionBuilder> Options { get; }
|
||||
|
||||
void ExecuteServer(ILogger logger, SocketSlashCommand context)
|
||||
{ }
|
||||
|
||||
void ExecuteDm(ILogger logger, SocketSlashCommand context) { }
|
||||
|
||||
Task ExecuteInteraction(ILogger logger, SocketInteraction interaction) => Task.CompletedTask;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginCore\DiscordBotCore.PluginCore.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginManagement\DiscordBotCore.PluginManagement.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DiscordBotCore.PluginManagement.Loading.Exceptions;
|
||||
|
||||
public class PluginNotFoundException : Exception
|
||||
{
|
||||
public PluginNotFoundException(string pluginName) : base($"Plugin {pluginName} was not found") { }
|
||||
|
||||
public PluginNotFoundException(string pluginName, string url, string branch) :
|
||||
base ($"Plugin {pluginName} was not found on {url} (branch: {branch}") { }
|
||||
}
|
||||
12
DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs
Normal file
12
DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using DiscordBotCore.PluginCore;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
public interface IPluginLoader
|
||||
{
|
||||
List<IDbCommand> Commands { get; }
|
||||
List<IDbEvent> Events { get; }
|
||||
List<IDbSlashCommand> SlashCommands { get; }
|
||||
Task LoadPlugins();
|
||||
}
|
||||
@@ -1,25 +1,28 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others.Exceptions;
|
||||
using DiscordBotCore.PluginCore;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
using DiscordBotCore.PluginManagement.Loading.Exceptions;
|
||||
|
||||
namespace DiscordBotCore.Loaders;
|
||||
namespace DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
internal class Loader
|
||||
{
|
||||
|
||||
internal delegate void FileLoadedHandler(FileLoaderResult result);
|
||||
|
||||
internal delegate void FileLoadedHandler(string fileName, Exception exception);
|
||||
internal delegate void PluginLoadedHandler(PluginLoaderResult result);
|
||||
|
||||
internal event FileLoadedHandler? OnFileLoadedException;
|
||||
internal event PluginLoadedHandler? OnPluginLoaded;
|
||||
|
||||
private readonly IPluginManager _pluginManager;
|
||||
|
||||
internal Loader(IPluginManager manager)
|
||||
{
|
||||
_pluginManager = manager;
|
||||
}
|
||||
|
||||
internal async Task Load()
|
||||
{
|
||||
var installedPlugins = await Application.CurrentApplication.PluginManager.GetInstalledPlugins();
|
||||
var installedPlugins = await _pluginManager.GetInstalledPlugins();
|
||||
var files = installedPlugins.Where(plugin => plugin.IsEnabled).Select(plugin => plugin.FilePath).ToArray();
|
||||
|
||||
foreach (var file in files)
|
||||
@@ -30,14 +33,13 @@ internal class Loader
|
||||
}
|
||||
catch
|
||||
{
|
||||
OnFileLoadedException?.Invoke(new FileLoaderResult(file, $"Failed to load file {file}"));
|
||||
OnFileLoadedException?.Invoke(file, new Exception($"Failed to load plugin from file {file}"));
|
||||
}
|
||||
}
|
||||
|
||||
await LoadEverythingOfType<IDbEvent>();
|
||||
await LoadEverythingOfType<IDbCommand>();
|
||||
await LoadEverythingOfType<IDbSlashCommand>();
|
||||
await LoadEverythingOfType<ICommandAction>();
|
||||
}
|
||||
|
||||
private Task LoadEverythingOfType<T>()
|
||||
@@ -62,7 +64,6 @@ internal class Loader
|
||||
IDbEvent @event => PluginLoaderResult.FromIDbEvent(@event),
|
||||
IDbCommand command => PluginLoaderResult.FromIDbCommand(command),
|
||||
IDbSlashCommand command => PluginLoaderResult.FromIDbSlashCommand(command),
|
||||
ICommandAction action => PluginLoaderResult.FromICommandAction(action),
|
||||
_ => PluginLoaderResult.FromException(new PluginNotFoundException($"Unknown plugin type {plugin.GetType().FullName}"))
|
||||
};
|
||||
|
||||
192
DiscordBotCore.PluginManagement.Loading/PluginLoader.cs
Normal file
192
DiscordBotCore.PluginManagement.Loading/PluginLoader.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System.Net.Mime;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Configuration;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginCore;
|
||||
using DiscordBotCore.PluginCore.Helpers;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
using DiscordBotCore.Utilities;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
public sealed class PluginLoader : IPluginLoader
|
||||
{
|
||||
private readonly DiscordSocketClient _DiscordClient;
|
||||
private readonly IPluginManager _PluginManager;
|
||||
private readonly ILogger _Logger;
|
||||
private readonly IConfiguration _Configuration;
|
||||
|
||||
public delegate void CommandLoaded(IDbCommand eCommand);
|
||||
public delegate void EventLoaded(IDbEvent eEvent);
|
||||
public delegate void SlashCommandLoaded(IDbSlashCommand eSlashCommand);
|
||||
|
||||
public CommandLoaded? OnCommandLoaded;
|
||||
public EventLoaded? OnEventLoaded;
|
||||
public SlashCommandLoaded? OnSlashCommandLoaded;
|
||||
|
||||
public List<IDbCommand> Commands { get; private set; } = new List<IDbCommand>();
|
||||
public List<IDbEvent> Events { get; private set; } = new List<IDbEvent>();
|
||||
public List<IDbSlashCommand> SlashCommands { get; private set; } = new List<IDbSlashCommand>();
|
||||
|
||||
public PluginLoader(IPluginManager pluginManager, ILogger logger, IConfiguration configuration, DiscordSocketClient discordSocketDiscordClient)
|
||||
{
|
||||
_PluginManager = pluginManager;
|
||||
_DiscordClient = discordSocketDiscordClient;
|
||||
_Logger = logger;
|
||||
_Configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task LoadPlugins()
|
||||
{
|
||||
Commands.Clear();
|
||||
Events.Clear();
|
||||
SlashCommands.Clear();
|
||||
|
||||
_Logger.Log("Loading plugins...", this);
|
||||
|
||||
var loader = new Loader(_PluginManager);
|
||||
|
||||
loader.OnFileLoadedException += FileLoadedException;
|
||||
loader.OnPluginLoaded += OnPluginLoaded;
|
||||
|
||||
await loader.Load();
|
||||
}
|
||||
|
||||
private void FileLoadedException(string fileName, Exception exception)
|
||||
{
|
||||
_Logger.LogException(exception, this);
|
||||
}
|
||||
|
||||
private void InitializeDbCommand(IDbCommand command)
|
||||
{
|
||||
Commands.Add(command);
|
||||
OnCommandLoaded?.Invoke(command);
|
||||
}
|
||||
|
||||
private void InitializeEvent(IDbEvent eEvent)
|
||||
{
|
||||
if (!TryStartEvent(eEvent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Events.Add(eEvent);
|
||||
OnEventLoaded?.Invoke(eEvent);
|
||||
}
|
||||
|
||||
private async void InitializeSlashCommand(IDbSlashCommand slashCommand)
|
||||
{
|
||||
Result result = await TryStartSlashCommand(slashCommand);
|
||||
result.Match(
|
||||
() =>
|
||||
{
|
||||
if (slashCommand.HasInteraction)
|
||||
_DiscordClient.InteractionCreated += interaction => slashCommand.ExecuteInteraction(_Logger, interaction);
|
||||
SlashCommands.Add(slashCommand);
|
||||
OnSlashCommandLoaded?.Invoke(slashCommand);
|
||||
},
|
||||
HandleError
|
||||
);
|
||||
}
|
||||
|
||||
private void HandleError(Exception exception)
|
||||
{
|
||||
_Logger.LogException(exception, this);
|
||||
}
|
||||
|
||||
private void OnPluginLoaded(PluginLoaderResult result)
|
||||
{
|
||||
result.Match(
|
||||
InitializeDbCommand,
|
||||
InitializeEvent,
|
||||
InitializeSlashCommand,
|
||||
HandleError
|
||||
);
|
||||
}
|
||||
|
||||
private bool TryStartEvent(IDbEvent? dbEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dbEvent is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dbEvent));
|
||||
}
|
||||
IDbEventExecutingArgument args = new DbEventExecutingArgument(
|
||||
_Logger,
|
||||
_DiscordClient,
|
||||
_Configuration.Get<string>("prefix"),
|
||||
new DirectoryInfo(Path.Combine(_Configuration.Get<string>("ResourcesPath"), dbEvent.Name)));
|
||||
dbEvent.Start(args);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_Logger.Log($"Error starting event {dbEvent.Name}: {e.Message}", typeof(PluginLoader), LogType.Error);
|
||||
_Logger.LogException(e, typeof(PluginLoader));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Result> TryStartSlashCommand(IDbSlashCommand? dbSlashCommand)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dbSlashCommand is null)
|
||||
{
|
||||
return Result.Failure(new Exception("dbSlashCommand is null"));
|
||||
}
|
||||
|
||||
if (_DiscordClient.Guilds.Count == 0)
|
||||
{
|
||||
return Result.Failure(new Exception("No guilds found"));
|
||||
}
|
||||
|
||||
var builder = new SlashCommandBuilder();
|
||||
builder.WithName(dbSlashCommand.Name);
|
||||
builder.WithDescription(dbSlashCommand.Description);
|
||||
builder.Options = dbSlashCommand.Options;
|
||||
|
||||
if (dbSlashCommand.CanUseDm)
|
||||
builder.WithContextTypes(InteractionContextType.BotDm, InteractionContextType.Guild);
|
||||
else
|
||||
builder.WithContextTypes(InteractionContextType.Guild);
|
||||
|
||||
List<ulong> serverIds = _Configuration.GetList("ServerIds", new List<ulong>());
|
||||
|
||||
foreach(ulong guildId in serverIds)
|
||||
{
|
||||
bool result = await EnableSlashCommandPerGuild(guildId, builder);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return Result.Failure($"Failed to enable slash command {dbSlashCommand.Name} for guild {guildId}");
|
||||
}
|
||||
}
|
||||
|
||||
await _DiscordClient.CreateGlobalApplicationCommandAsync(builder.Build());
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Result.Failure("Error starting slash command");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> EnableSlashCommandPerGuild(ulong guildId, SlashCommandBuilder builder)
|
||||
{
|
||||
SocketGuild? guild = _DiscordClient.GetGuild(guildId);
|
||||
if (guild is null)
|
||||
{
|
||||
_Logger.Log("Failed to get guild with ID " + guildId, typeof(PluginLoader), LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
await guild.CreateApplicationCommandAsync(builder.Build());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using DiscordBotCore.PluginCore;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
using DiscordBotCore.Utilities;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
|
||||
public class PluginLoaderResult
|
||||
{
|
||||
private Option3<IDbCommand, IDbEvent, IDbSlashCommand, Exception> _Result;
|
||||
|
||||
public static PluginLoaderResult FromIDbCommand(IDbCommand command) => new PluginLoaderResult(new Option3<IDbCommand, IDbEvent, IDbSlashCommand, Exception>(command));
|
||||
|
||||
public static PluginLoaderResult FromIDbEvent(IDbEvent dbEvent) => new PluginLoaderResult(new Option3<IDbCommand, IDbEvent, IDbSlashCommand, Exception>(dbEvent));
|
||||
|
||||
public static PluginLoaderResult FromIDbSlashCommand(IDbSlashCommand slashCommand) => new PluginLoaderResult(new Option3<IDbCommand, IDbEvent, IDbSlashCommand, Exception>(slashCommand));
|
||||
|
||||
public static PluginLoaderResult FromException(Exception exception) => new PluginLoaderResult(new Option3<IDbCommand, IDbEvent, IDbSlashCommand, Exception>(exception));
|
||||
private PluginLoaderResult(Option3<IDbCommand, IDbEvent, IDbSlashCommand, Exception> result)
|
||||
{
|
||||
_Result = result;
|
||||
}
|
||||
|
||||
public void Match(Action<IDbCommand> commandAction, Action<IDbEvent> eventAction, Action<IDbSlashCommand> slashCommandAction,
|
||||
Action<Exception> exceptionAction)
|
||||
{
|
||||
_Result.Match(commandAction, eventAction, slashCommandAction, exceptionAction);
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<IDbCommand, TResult> commandFunc, Func<IDbEvent, TResult> eventFunc,
|
||||
Func<IDbSlashCommand, TResult> slashCommandFunc,
|
||||
Func<Exception, TResult> exceptionFunc)
|
||||
{
|
||||
return _Result.Match(commandFunc, eventFunc, slashCommandFunc, exceptionFunc);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Networking\DiscordBotCore.Networking.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Utilities\DiscordBotCore.Utilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,15 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Plugin;
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
namespace DiscordBotCore.Interfaces.PluginManagement;
|
||||
namespace DiscordBotCore.PluginManagement.Helpers;
|
||||
|
||||
public interface IPluginRepository
|
||||
{
|
||||
public Task<List<OnlinePlugin>> GetAllPlugins();
|
||||
public Task<List<OnlinePlugin>> GetAllPlugins(int operatingSystem, bool includeNotApproved);
|
||||
|
||||
public Task<OnlinePlugin?> GetPluginById(int pluginId);
|
||||
public Task<OnlinePlugin?> GetPluginByName(string pluginName);
|
||||
public Task<OnlinePlugin?> GetPluginByName(string pluginName, int operatingSystem, bool includeNotApproved);
|
||||
|
||||
public Task<List<OnlineDependencyInfo>> GetDependenciesForPlugin(int pluginId);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace DiscordBotCore.Interfaces.PluginManagement;
|
||||
namespace DiscordBotCore.PluginManagement.Helpers;
|
||||
|
||||
public interface IPluginRepositoryConfiguration
|
||||
{
|
||||
@@ -6,6 +6,4 @@ public interface IPluginRepositoryConfiguration
|
||||
|
||||
public string PluginRepositoryLocation { get; }
|
||||
public string DependenciesRepositoryLocation { get; }
|
||||
|
||||
|
||||
}
|
||||
@@ -1,31 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces.PluginManagement;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Plugin;
|
||||
using System.Net.Mime;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
using DiscordBotCore.Utilities;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
|
||||
namespace DiscordBotCore.Online.Helpers;
|
||||
namespace DiscordBotCore.PluginManagement.Helpers;
|
||||
|
||||
public class PluginRepository : IPluginRepository
|
||||
{
|
||||
private readonly IPluginRepositoryConfiguration _pluginRepositoryConfiguration;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public PluginRepository(IPluginRepositoryConfiguration pluginRepositoryConfiguration)
|
||||
public PluginRepository(IPluginRepositoryConfiguration pluginRepositoryConfiguration, ILogger logger)
|
||||
{
|
||||
_pluginRepositoryConfiguration = pluginRepositoryConfiguration;
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.BaseAddress = new Uri(_pluginRepositoryConfiguration.BaseUrl);
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<List<OnlinePlugin>> GetAllPlugins()
|
||||
public async Task<List<OnlinePlugin>> GetAllPlugins(int operatingSystem, bool includeNotApproved)
|
||||
{
|
||||
int operatingSystem = OS.GetOperatingSystemInt();
|
||||
bool includeNotApproved = false;
|
||||
|
||||
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.PluginRepositoryLocation,
|
||||
"get-all-plugins", new Dictionary<string, string>
|
||||
{
|
||||
@@ -37,7 +33,6 @@ public class PluginRepository : IPluginRepository
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Application.Log("Failed to get all plugins from the repository", LogType.Warning);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -59,7 +54,6 @@ public class PluginRepository : IPluginRepository
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Application.Log("Failed to get plugin from the repository", LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -69,20 +63,20 @@ public class PluginRepository : IPluginRepository
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public async Task<OnlinePlugin?> GetPluginByName(string pluginName)
|
||||
public async Task<OnlinePlugin?> GetPluginByName(string pluginName, int operatingSystem, bool includeNotApproved)
|
||||
{
|
||||
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.PluginRepositoryLocation,
|
||||
"get-plugin-by-name", new Dictionary<string, string>
|
||||
{
|
||||
{ "pluginName", pluginName },
|
||||
{ "operatingSystem", OS.GetOperatingSystemInt().ToString() },
|
||||
{ "includeNotApproved", "false" }
|
||||
{ "operatingSystem", operatingSystem.ToString() },
|
||||
{ "includeNotApproved", includeNotApproved.ToString() }
|
||||
});
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Application.Log("Failed to get plugin from the repository", LogType.Warning);
|
||||
_logger.Log($"Plugin {pluginName} not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -103,7 +97,7 @@ public class PluginRepository : IPluginRepository
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(url);
|
||||
if(!response.IsSuccessStatusCode)
|
||||
{
|
||||
Application.Log("Failed to get dependencies for plugin from the repository", LogType.Warning);
|
||||
_logger.Log($"Failed to get dependencies for plugin with ID {pluginId}");
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using DiscordBotCore.Interfaces.PluginManagement;
|
||||
|
||||
namespace DiscordBotCore.Online.Helpers;
|
||||
namespace DiscordBotCore.PluginManagement.Helpers;
|
||||
|
||||
public class PluginRepositoryConfiguration : IPluginRepositoryConfiguration
|
||||
{
|
||||
20
DiscordBotCore.PluginManagement/IPluginManager.cs
Normal file
20
DiscordBotCore.PluginManagement/IPluginManager.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement;
|
||||
|
||||
public interface IPluginManager
|
||||
{
|
||||
Task<List<OnlinePlugin>> GetPluginsList();
|
||||
Task<OnlinePlugin?> GetPluginDataByName(string pluginName);
|
||||
Task AppendPluginToDatabase(LocalPlugin pluginData);
|
||||
Task<List<LocalPlugin>> GetInstalledPlugins();
|
||||
Task<bool> IsPluginInstalled(string pluginName);
|
||||
Task<bool> MarkPluginToUninstall(string pluginName);
|
||||
Task UninstallMarkedPlugins();
|
||||
Task<string?> GetDependencyLocation(string dependencyName);
|
||||
Task<string?> GetDependencyLocation(string dependencyName, string pluginName);
|
||||
string GenerateDependencyRelativePath(string pluginName, string dependencyPath);
|
||||
Task InstallPlugin(OnlinePlugin plugin, IProgress<InstallationProgressIndicator> progress);
|
||||
Task<Tuple<Dictionary<string, string>, List<OnlineDependencyInfo>>> GatherInstallDataForPlugin(OnlinePlugin plugin);
|
||||
Task SetEnabledStatus(string pluginName, bool status);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
public class InstallationProgressIndicator
|
||||
{
|
||||
private readonly Dictionary<string, float> _DownloadProgress;
|
||||
|
||||
public InstallationProgressIndicator()
|
||||
{
|
||||
_DownloadProgress = new Dictionary<string, float>();
|
||||
}
|
||||
|
||||
public void SetProgress(string fileName, float progress)
|
||||
{
|
||||
_DownloadProgress[fileName] = progress;
|
||||
}
|
||||
}
|
||||
49
DiscordBotCore.PluginManagement/Models/LocalPlugin.cs
Normal file
49
DiscordBotCore.PluginManagement/Models/LocalPlugin.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
public class LocalPlugin
|
||||
{
|
||||
public string PluginName { get; private set; }
|
||||
public string PluginVersion { get; private set; }
|
||||
public string FilePath { get; private set; }
|
||||
public Dictionary<string, string> ListOfExecutableDependencies {get; private set;}
|
||||
public bool IsMarkedToUninstall {get; internal set;}
|
||||
public bool IsOfflineAdded { get; internal set; }
|
||||
public bool IsEnabled { get; internal set; }
|
||||
|
||||
[JsonConstructor]
|
||||
public LocalPlugin(string pluginName, string pluginVersion, string filePath, Dictionary<string, string> listOfExecutableDependencies, bool isMarkedToUninstall, bool isOfflineAdded, bool isEnabled)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
PluginVersion = pluginVersion;
|
||||
ListOfExecutableDependencies = listOfExecutableDependencies;
|
||||
IsMarkedToUninstall = isMarkedToUninstall;
|
||||
FilePath = filePath;
|
||||
IsOfflineAdded = isOfflineAdded;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
private LocalPlugin(string pluginName, string pluginVersion, string filePath,
|
||||
Dictionary<string, string> listOfExecutableDependencies)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
PluginVersion = pluginVersion;
|
||||
ListOfExecutableDependencies = listOfExecutableDependencies;
|
||||
IsMarkedToUninstall = false;
|
||||
FilePath = filePath;
|
||||
IsOfflineAdded = false;
|
||||
IsEnabled = true;
|
||||
}
|
||||
|
||||
public static LocalPlugin FromOnlineInfo(OnlinePlugin plugin, List<OnlineDependencyInfo> dependencies, string downloadLocation)
|
||||
{
|
||||
LocalPlugin localPlugin = new LocalPlugin(
|
||||
plugin.PluginName, plugin.LatestVersion, downloadLocation,
|
||||
dependencies.Where(dependency => dependency.IsExecutable)
|
||||
.ToDictionary(dependency => dependency.DependencyName, dependency => dependency.DownloadLocation)
|
||||
);
|
||||
|
||||
return localPlugin;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DiscordBotCore.Plugin;
|
||||
namespace DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
public class OnlineDependencyInfo
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DiscordBotCore.Plugin;
|
||||
namespace DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
public class OnlinePlugin
|
||||
{
|
||||
262
DiscordBotCore.PluginManagement/PluginManager.cs
Normal file
262
DiscordBotCore.PluginManagement/PluginManager.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Networking;
|
||||
using DiscordBotCore.PluginManagement.Helpers;
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
using DiscordBotCore.Utilities;
|
||||
using DiscordBotCore.Configuration;
|
||||
using OperatingSystem = DiscordBotCore.Utilities.OperatingSystem;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement;
|
||||
|
||||
public sealed class PluginManager : IPluginManager
|
||||
{
|
||||
private static readonly string _LibrariesBaseFolder = "Libraries";
|
||||
private readonly IPluginRepository _PluginRepository;
|
||||
private readonly ILogger _Logger;
|
||||
private readonly IConfiguration _Configuration;
|
||||
|
||||
public PluginManager(IPluginRepository pluginRepository, ILogger logger, IConfiguration configuration)
|
||||
{
|
||||
_PluginRepository = pluginRepository;
|
||||
_Logger = logger;
|
||||
_Configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task<List<OnlinePlugin>> GetPluginsList()
|
||||
{
|
||||
int os = OperatingSystem.GetOperatingSystemInt();
|
||||
var onlinePlugins = await _PluginRepository.GetAllPlugins(os, false);
|
||||
|
||||
if (!onlinePlugins.Any())
|
||||
{
|
||||
_Logger.Log($"No plugins found for operatingSystem: {OperatingSystem.GetOperatingSystemString((OperatingSystem.OperatingSystemEnum)os)}", LogType.Warning);
|
||||
return [];
|
||||
}
|
||||
|
||||
return onlinePlugins;
|
||||
}
|
||||
|
||||
public async Task<OnlinePlugin?> GetPluginDataByName(string pluginName)
|
||||
{
|
||||
int os = OperatingSystem.GetOperatingSystemInt();
|
||||
var plugin = await _PluginRepository.GetPluginByName(pluginName, os, false);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
_Logger.Log($"Plugin {pluginName} not found in the repository for operating system {OperatingSystem.GetOperatingSystemString((OperatingSystem.OperatingSystemEnum)os)}.", LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private async Task RemovePluginFromDatabase(string pluginName)
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
throw new Exception("Plugin database file not found");
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
|
||||
installedPlugins.RemoveAll(p => p.PluginName == pluginName);
|
||||
await JsonManager.SaveToJsonFile(pluginDatabaseFile, installedPlugins);
|
||||
}
|
||||
|
||||
public async Task AppendPluginToDatabase(LocalPlugin pluginData)
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
throw new Exception("Plugin database file not found");
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
foreach (var dependency in pluginData.ListOfExecutableDependencies)
|
||||
{
|
||||
pluginData.ListOfExecutableDependencies[dependency.Key] = dependency.Value;
|
||||
}
|
||||
|
||||
installedPlugins.Add(pluginData);
|
||||
await JsonManager.SaveToJsonFile(pluginDatabaseFile, installedPlugins);
|
||||
}
|
||||
|
||||
public async Task<List<LocalPlugin>> GetInstalledPlugins()
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
throw new Exception("Plugin database file not found");
|
||||
}
|
||||
|
||||
return await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
}
|
||||
|
||||
public async Task<bool> IsPluginInstalled(string pluginName)
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
throw new Exception("Plugin database file not found");
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
return installedPlugins.Any(plugin => plugin.PluginName == pluginName);
|
||||
}
|
||||
|
||||
public async Task<bool> MarkPluginToUninstall(string pluginName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
List<LocalPlugin> info = installedPlugins.Where(info => info.PluginName == pluginName).ToList();
|
||||
|
||||
if (!info.Any())
|
||||
return false;
|
||||
|
||||
foreach (var item in info)
|
||||
{
|
||||
await RemovePluginFromDatabase(item.PluginName);
|
||||
item.IsMarkedToUninstall = true;
|
||||
await AppendPluginToDatabase(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task UninstallMarkedPlugins()
|
||||
{
|
||||
IEnumerable<LocalPlugin> installedPlugins = (await GetInstalledPlugins()).AsEnumerable();
|
||||
IEnumerable<LocalPlugin> pluginsToRemove = installedPlugins.Where(plugin => plugin.IsMarkedToUninstall).AsEnumerable();
|
||||
|
||||
foreach (var plugin in pluginsToRemove)
|
||||
{
|
||||
await UninstallPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UninstallPlugin(LocalPlugin LocalPlugin)
|
||||
{
|
||||
File.Delete(LocalPlugin.FilePath);
|
||||
|
||||
foreach (var dependency in LocalPlugin.ListOfExecutableDependencies)
|
||||
File.Delete(dependency.Value);
|
||||
|
||||
await RemovePluginFromDatabase(LocalPlugin.PluginName);
|
||||
|
||||
if (Directory.Exists($"{_LibrariesBaseFolder}/{LocalPlugin.PluginName}"))
|
||||
Directory.Delete($"{_LibrariesBaseFolder}/{LocalPlugin.PluginName}", true);
|
||||
}
|
||||
|
||||
public async Task<string?> GetDependencyLocation(string dependencyName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
foreach (var plugin in installedPlugins)
|
||||
{
|
||||
if (plugin.ListOfExecutableDependencies.TryGetValue(dependencyName, out var dependencyPath))
|
||||
{
|
||||
string relativePath = GenerateDependencyRelativePath(plugin.PluginName, dependencyPath);
|
||||
return relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<string?> GetDependencyLocation(string dependencyName, string pluginName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
foreach (var plugin in installedPlugins)
|
||||
{
|
||||
if (plugin.PluginName == pluginName && plugin.ListOfExecutableDependencies.ContainsKey(dependencyName))
|
||||
{
|
||||
string dependencyPath = plugin.ListOfExecutableDependencies[dependencyName];
|
||||
string relativePath = GenerateDependencyRelativePath(pluginName, dependencyPath);
|
||||
return relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GenerateDependencyRelativePath(string pluginName, string dependencyPath)
|
||||
{
|
||||
string relative = $"./{_LibrariesBaseFolder}/{pluginName}/{dependencyPath}";
|
||||
return relative;
|
||||
}
|
||||
|
||||
public async Task InstallPlugin(OnlinePlugin plugin, IProgress<InstallationProgressIndicator> progress)
|
||||
{
|
||||
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.PluginId);
|
||||
string? pluginsFolder = _Configuration.Get<string>("PluginFolder");
|
||||
if (pluginsFolder is null)
|
||||
{
|
||||
throw new Exception("Plugin folder not found");
|
||||
}
|
||||
|
||||
string downloadLocation = $"{pluginsFolder}/{plugin.PluginName}.dll";
|
||||
|
||||
InstallationProgressIndicator installationProgressIndicator = new InstallationProgressIndicator();
|
||||
|
||||
IProgress<float> downloadProgress = new Progress<float>(fileProgress =>
|
||||
{
|
||||
installationProgressIndicator.SetProgress(plugin.PluginName, fileProgress);
|
||||
progress.Report(installationProgressIndicator);
|
||||
});
|
||||
|
||||
FileDownloader fileDownloader = new FileDownloader(plugin.PluginLink, downloadLocation);
|
||||
await fileDownloader.DownloadFile(downloadProgress.Report);
|
||||
|
||||
ParallelDownloadExecutor executor = new ParallelDownloadExecutor();
|
||||
|
||||
foreach (var dependency in dependencies)
|
||||
{
|
||||
string dependencyLocation = GenerateDependencyRelativePath(plugin.PluginName, dependency.DownloadLocation);
|
||||
Action<float> dependencyProgress = new Action<float>(fileProgress =>
|
||||
{
|
||||
installationProgressIndicator.SetProgress(dependency.DependencyName, fileProgress);
|
||||
progress.Report(installationProgressIndicator);
|
||||
});
|
||||
|
||||
executor.AddTask(dependency.DownloadLink, dependencyLocation, dependencyProgress);
|
||||
}
|
||||
|
||||
await executor.ExecuteAllTasks();
|
||||
}
|
||||
|
||||
public async Task<Tuple<Dictionary<string, string>, List<OnlineDependencyInfo>>> GatherInstallDataForPlugin(OnlinePlugin plugin)
|
||||
{
|
||||
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.PluginId);
|
||||
string? pluginsFolder = _Configuration.Get<string>("PluginFolder");
|
||||
if (pluginsFolder is null)
|
||||
{
|
||||
throw new Exception("Plugin folder not found");
|
||||
}
|
||||
string downloadLocation = $"{pluginsFolder}/{plugin.PluginName}.dll";
|
||||
var downloads = new Dictionary<string, string> { { downloadLocation, plugin.PluginLink } };
|
||||
foreach(var dependency in dependencies)
|
||||
{
|
||||
string dependencyLocation = GenerateDependencyRelativePath(plugin.PluginName, dependency.DownloadLocation);
|
||||
downloads.Add(dependencyLocation, dependency.DownloadLink);
|
||||
}
|
||||
|
||||
return (downloads, dependencies).ToTuple();
|
||||
}
|
||||
|
||||
public async Task SetEnabledStatus(string pluginName, bool status)
|
||||
{
|
||||
var plugins = await GetInstalledPlugins();
|
||||
var plugin = plugins.Find(p => p.PluginName == pluginName);
|
||||
|
||||
if (plugin == null)
|
||||
return;
|
||||
|
||||
plugin.IsEnabled = status;
|
||||
|
||||
await RemovePluginFromDatabase(pluginName);
|
||||
await AppendPluginToDatabase(plugin);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO.Compression;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Configuration;
|
||||
|
||||
namespace DiscordBotCore.Others;
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public static class ArchiveManager
|
||||
public class ArchiveManager
|
||||
{
|
||||
private readonly ILogger _Logger;
|
||||
private readonly IConfiguration _Configuration;
|
||||
|
||||
private static readonly string _ArchivesFolder = "./Data/Archives";
|
||||
public ArchiveManager(ILogger logger, IConfiguration configuration)
|
||||
{
|
||||
_Logger = logger;
|
||||
_Configuration = configuration;
|
||||
}
|
||||
|
||||
public static void CreateFromFile(string file, string folder)
|
||||
public void CreateFromFile(string file, string folder)
|
||||
{
|
||||
if (!Directory.Exists(folder))
|
||||
Directory.CreateDirectory(folder);
|
||||
@@ -21,10 +24,8 @@ public static class ArchiveManager
|
||||
if (File.Exists(archiveName))
|
||||
File.Delete(archiveName);
|
||||
|
||||
using(ZipArchive archive = ZipFile.Open(archiveName, ZipArchiveMode.Create))
|
||||
{
|
||||
archive.CreateEntryFromFile(file, Path.GetFileName(file));
|
||||
}
|
||||
using ZipArchive archive = ZipFile.Open(archiveName, ZipArchiveMode.Create);
|
||||
archive.CreateEntryFromFile(file, Path.GetFileName(file));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,9 +34,9 @@ public static class ArchiveManager
|
||||
/// <param name="fileName">The file name in the archive</param>
|
||||
/// <param name="archName">The archive location on the disk</param>
|
||||
/// <returns>An array of bytes that represents the Stream value from the file that was read inside the archive</returns>
|
||||
public static async Task<byte[]?> ReadAllBytes(string fileName, string archName)
|
||||
public async Task<byte[]?> ReadAllBytes(string fileName, string archName)
|
||||
{
|
||||
string? archiveFolderBasePath = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("ArchiveFolder", _ArchivesFolder);
|
||||
string? archiveFolderBasePath = _Configuration.Get<string>("ArchiveFolder");
|
||||
if(archiveFolderBasePath is null)
|
||||
throw new Exception("Archive folder not found");
|
||||
|
||||
@@ -70,9 +71,9 @@ public static class ArchiveManager
|
||||
/// <param name="fileName">The file name that is inside the archive or its full path</param>
|
||||
/// <param name="archFile">The archive location from the PAKs folder</param>
|
||||
/// <returns>A string that represents the content of the file or null if the file does not exists or it has no content</returns>
|
||||
public static async Task<string?> ReadFromPakAsync(string fileName, string archFile)
|
||||
public async Task<string?> ReadFromPakAsync(string fileName, string archFile)
|
||||
{
|
||||
string? archiveFolderBasePath = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("ArchiveFolder", _ArchivesFolder);
|
||||
string? archiveFolderBasePath = _Configuration.Get<string>("ArchiveFolder");
|
||||
if(archiveFolderBasePath is null)
|
||||
throw new Exception("Archive folder not found");
|
||||
|
||||
@@ -105,7 +106,7 @@ public static class ArchiveManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.Error); // Write the error to a file
|
||||
_Logger.LogException(ex, this);
|
||||
await Task.Delay(100);
|
||||
return await ReadFromPakAsync(fileName, archFile);
|
||||
}
|
||||
@@ -119,14 +120,14 @@ public static class ArchiveManager
|
||||
/// <param name="progress">The progress that is updated as a file is processed</param>
|
||||
/// <param name="type">The type of progress</param>
|
||||
/// <returns></returns>
|
||||
public static async Task ExtractArchive(
|
||||
public async Task ExtractArchive(
|
||||
string zip, string folder, IProgress<float> progress,
|
||||
UnzipProgressType type)
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
using var archive = ZipFile.OpenRead(zip);
|
||||
var totalZipFiles = archive.Entries.Count();
|
||||
if (type == UnzipProgressType.PERCENTAGE_FROM_NUMBER_OF_FILES)
|
||||
if (type == UnzipProgressType.PercentageFromNumberOfFiles)
|
||||
{
|
||||
var currentZipFile = 0;
|
||||
foreach (var entry in archive.Entries)
|
||||
@@ -141,7 +142,7 @@ public static class ArchiveManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.Error);
|
||||
_Logger.LogException(ex, this);
|
||||
}
|
||||
|
||||
currentZipFile++;
|
||||
@@ -150,7 +151,7 @@ public static class ArchiveManager
|
||||
progress.Report((float)currentZipFile / totalZipFiles * 100);
|
||||
}
|
||||
}
|
||||
else if (type == UnzipProgressType.PERCENTAGE_FROM_TOTAL_SIZE)
|
||||
else if (type == UnzipProgressType.PercentageFromTotalSize)
|
||||
{
|
||||
ulong zipSize = 0;
|
||||
|
||||
@@ -176,7 +177,7 @@ public static class ArchiveManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.Error);
|
||||
_Logger.LogException(ex, this);
|
||||
}
|
||||
|
||||
await Task.Delay(10);
|
||||
14
DiscordBotCore.Utilities/DiscordBotCore.Utilities.csproj
Normal file
14
DiscordBotCore.Utilities/DiscordBotCore.Utilities.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,14 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DiscordBotCore.Others;
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public static class JsonManager
|
||||
{
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
namespace DiscordBotCore.Others
|
||||
{
|
||||
public class OneOf<T0, T1>
|
||||
public class OneOf<T0, T1>
|
||||
{
|
||||
public T0 Item0 { get; }
|
||||
public T1 Item1 { get; }
|
||||
@@ -131,5 +129,4 @@ namespace DiscordBotCore.Others
|
||||
return Item0 != null ? item0(Item0) : Item1 != null ? item1(Item1) : Item2 != null ? item2(Item2) : item3(Item3);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,40 @@
|
||||
using System;
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
namespace DiscordBotCore.Others;
|
||||
|
||||
public class OS
|
||||
public class OperatingSystem
|
||||
{
|
||||
public enum OperatingSystem : int
|
||||
public enum OperatingSystemEnum : int
|
||||
{
|
||||
Windows = 0,
|
||||
Linux = 1,
|
||||
MacOS = 2
|
||||
MacOs = 2
|
||||
}
|
||||
|
||||
public static OperatingSystem GetOperatingSystem()
|
||||
public static OperatingSystemEnum GetOperatingSystem()
|
||||
{
|
||||
if(System.OperatingSystem.IsLinux()) return OperatingSystem.Linux;
|
||||
if(System.OperatingSystem.IsWindows()) return OperatingSystem.Windows;
|
||||
if(System.OperatingSystem.IsMacOS()) return OperatingSystem.MacOS;
|
||||
if(System.OperatingSystem.IsLinux()) return OperatingSystemEnum.Linux;
|
||||
if(System.OperatingSystem.IsWindows()) return OperatingSystemEnum.Windows;
|
||||
if(System.OperatingSystem.IsMacOS()) return OperatingSystemEnum.MacOs;
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
public static string GetOperatingSystemString(OperatingSystem os)
|
||||
public static string GetOperatingSystemString(OperatingSystemEnum os)
|
||||
{
|
||||
return os switch
|
||||
{
|
||||
OperatingSystem.Windows => "Windows",
|
||||
OperatingSystem.Linux => "Linux",
|
||||
OperatingSystem.MacOS => "MacOS",
|
||||
OperatingSystemEnum.Windows => "Windows",
|
||||
OperatingSystemEnum.Linux => "Linux",
|
||||
OperatingSystemEnum.MacOs => "MacOS",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
public static OperatingSystem GetOperatingSystemFromString(string os)
|
||||
public static OperatingSystemEnum GetOperatingSystemFromString(string os)
|
||||
{
|
||||
return os.ToLower() switch
|
||||
{
|
||||
"windows" => OperatingSystem.Windows,
|
||||
"linux" => OperatingSystem.Linux,
|
||||
"macos" => OperatingSystem.MacOS,
|
||||
"windows" => OperatingSystemEnum.Windows,
|
||||
"linux" => OperatingSystemEnum.Linux,
|
||||
"macos" => OperatingSystemEnum.MacOs,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace DiscordBotCore.Others;
|
||||
|
||||
|
||||
namespace DiscordBotCore.Utilities;
|
||||
public class Option2<T0, T1, TError> where TError : Exception
|
||||
{
|
||||
private readonly int _Index;
|
||||
@@ -12,19 +8,19 @@ public class Option2<T0, T1, TError> where TError : Exception
|
||||
|
||||
private TError Error { get; } = default!;
|
||||
|
||||
private Option2(T0 item0)
|
||||
public Option2(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
_Index = 0;
|
||||
}
|
||||
|
||||
private Option2(T1 item1)
|
||||
public Option2(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
_Index = 1;
|
||||
}
|
||||
|
||||
private Option2(TError error)
|
||||
public Option2(TError error)
|
||||
{
|
||||
Error = error;
|
||||
_Index = 2;
|
||||
@@ -76,6 +72,90 @@ public class Option2<T0, T1, TError> where TError : Exception
|
||||
|
||||
}
|
||||
|
||||
public class Option3<T0, T1, T2, TError> where TError : Exception
|
||||
{
|
||||
private readonly int _Index;
|
||||
|
||||
private T0 Item0 { get; } = default!;
|
||||
private T1 Item1 { get; } = default!;
|
||||
private T2 Item2 { get; } = default!;
|
||||
private TError Error { get; } = default!;
|
||||
|
||||
public Option3(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
_Index = 0;
|
||||
}
|
||||
|
||||
public Option3(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
_Index = 1;
|
||||
}
|
||||
|
||||
public Option3(T2 item2)
|
||||
{
|
||||
Item2 = item2;
|
||||
_Index = 2;
|
||||
}
|
||||
|
||||
public Option3(TError error)
|
||||
{
|
||||
Error = error;
|
||||
_Index = 3;
|
||||
}
|
||||
|
||||
public static implicit operator Option3<T0, T1, T2, TError>(T0 item0) => new Option3<T0, T1, T2, TError>(item0);
|
||||
public static implicit operator Option3<T0, T1, T2, TError>(T1 item1) => new Option3<T0, T1, T2, TError>(item1);
|
||||
public static implicit operator Option3<T0, T1, T2, TError>(T2 item2) => new Option3<T0, T1, T2, TError>(item2);
|
||||
public static implicit operator Option3<T0, T1, T2, TError>(TError error) => new Option3<T0, T1, T2, TError>(error);
|
||||
|
||||
public void Match(Action<T0> item0, Action<T1> item1, Action<T2> item2, Action<TError> error)
|
||||
{
|
||||
switch (_Index)
|
||||
{
|
||||
case 0:
|
||||
item0(Item0);
|
||||
break;
|
||||
case 1:
|
||||
item1(Item1);
|
||||
break;
|
||||
case 2:
|
||||
item2(Item2);
|
||||
break;
|
||||
case 3:
|
||||
error(Error);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<T0, TResult> item0, Func<T1, TResult> item1, Func<T2, TResult> item2, Func<TError, TResult> error)
|
||||
{
|
||||
return _Index switch
|
||||
{
|
||||
0 => item0(Item0),
|
||||
1 => item1(Item1),
|
||||
2 => item2(Item2),
|
||||
3 => error(Error),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _Index switch
|
||||
{
|
||||
0 => $"Option3<{typeof(T0).Name}>: {Item0}",
|
||||
1 => $"Option3<{typeof(T1).Name}>: {Item1}",
|
||||
2 => $"Option3<{typeof(T2).Name}>: {Item2}",
|
||||
3 => $"Option3<{typeof(TError).Name}>: {Error}",
|
||||
_ => "Invalid Option3"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class Option4<T0, T1, T2, T3, TError> where TError : Exception
|
||||
{
|
||||
private readonly int _Index;
|
||||
@@ -87,31 +167,31 @@ public class Option4<T0, T1, T2, T3, TError> where TError : Exception
|
||||
|
||||
private TError Error { get; } = default!;
|
||||
|
||||
internal Option4(T0 item0)
|
||||
public Option4(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
_Index = 0;
|
||||
}
|
||||
|
||||
internal Option4(T1 item1)
|
||||
public Option4(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
_Index = 1;
|
||||
}
|
||||
|
||||
internal Option4(T2 item2)
|
||||
public Option4(T2 item2)
|
||||
{
|
||||
Item2 = item2;
|
||||
_Index = 2;
|
||||
}
|
||||
|
||||
internal Option4(T3 item3)
|
||||
public Option4(T3 item3)
|
||||
{
|
||||
Item3 = item3;
|
||||
_Index = 3;
|
||||
}
|
||||
|
||||
internal Option4(TError error)
|
||||
public Option4(TError error)
|
||||
{
|
||||
Error = error;
|
||||
_Index = 4;
|
||||
@@ -175,5 +255,4 @@ public class Option4<T0, T1, T2, T3, TError> where TError : Exception
|
||||
_ => "Invalid Option4"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace DiscordBotCore.Others;
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public class Result
|
||||
{
|
||||
@@ -79,4 +77,4 @@ public class Result<T>
|
||||
{
|
||||
return _Result.Match(valueFunc, exceptionFunc);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
DiscordBotCore.Utilities/UnzipProgressType.cs
Normal file
7
DiscordBotCore.Utilities/UnzipProgressType.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public enum UnzipProgressType
|
||||
{
|
||||
PercentageFromNumberOfFiles,
|
||||
PercentageFromTotalSize
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
|
||||
namespace DiscordBotCore.API;
|
||||
|
||||
public class ConnectionDetails : IConnectionDetails
|
||||
{
|
||||
public string Host { get; }
|
||||
public int Port { get; }
|
||||
|
||||
public ConnectionDetails(string host, int port)
|
||||
{
|
||||
Host = host;
|
||||
Port = port;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using DiscordBotCore.Online;
|
||||
|
||||
namespace DiscordBotCore.API.Endpoints;
|
||||
|
||||
public class ApiEndpointBase
|
||||
{
|
||||
internal IPluginManager PluginManager { get; }
|
||||
public ApiEndpointBase(IPluginManager pluginManager)
|
||||
{
|
||||
PluginManager = pluginManager;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.API.Endpoints;
|
||||
using DiscordBotCore.API.Endpoints.PluginManagement;
|
||||
using DiscordBotCore.API.Endpoints.SettingsManagement;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
using DiscordBotCore.Others;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace DiscordBotCore.API.Endpoints;
|
||||
|
||||
public class ApiManager
|
||||
{
|
||||
private bool IsRunning { get; set; }
|
||||
private List<IEndpoint> ApiEndpoints { get; }
|
||||
|
||||
public ApiManager()
|
||||
{
|
||||
ApiEndpoints = new List<IEndpoint>();
|
||||
}
|
||||
|
||||
internal void AddBaseEndpoints()
|
||||
{
|
||||
AddEndpoint(new HomeEndpoint());
|
||||
AddEndpoint(new PluginListEndpoint());
|
||||
AddEndpoint(new PluginListInstalledEndpoint());
|
||||
AddEndpoint(new PluginInstallEndpoint(Application.CurrentApplication.PluginManager));
|
||||
|
||||
AddEndpoint(new SettingsChangeEndpoint());
|
||||
AddEndpoint(new SettingsGetEndpoint());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public async void 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();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.API.Endpoints;
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace DiscordBotCore.API.Endpoints;
|
||||
|
||||
internal sealed class EndpointManager
|
||||
{
|
||||
private readonly 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
using DiscordBotCore.Online;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Plugin;
|
||||
|
||||
namespace DiscordBotCore.API.Endpoints.PluginManagement;
|
||||
|
||||
public class PluginInstallEndpoint : ApiEndpointBase, IEndpoint
|
||||
{
|
||||
public PluginInstallEndpoint(IPluginManager pluginManager) : base(pluginManager)
|
||||
{
|
||||
}
|
||||
|
||||
public string Path => "/api/plugin/install";
|
||||
public EndpointType HttpMethod => EndpointType.Post;
|
||||
|
||||
public async Task<ApiResponse> HandleRequest(string? jsonRequest)
|
||||
{
|
||||
Dictionary<string, string> jsonDict = await JsonManager.ConvertFromJson<Dictionary<string, string>>(jsonRequest);
|
||||
string pluginName = jsonDict["pluginName"];
|
||||
|
||||
OnlinePlugin? pluginInfo = await PluginManager.GetPluginDataByName(pluginName);
|
||||
|
||||
if (pluginInfo == null)
|
||||
{
|
||||
return ApiResponse.Fail("Plugin not found.");
|
||||
}
|
||||
|
||||
PluginManager.InstallPluginNoProgress(pluginInfo);
|
||||
return ApiResponse.Ok();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Plugin;
|
||||
|
||||
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.ConvertToJson(onlineInfos, [
|
||||
nameof(OnlinePlugin.PluginName),
|
||||
nameof(OnlinePlugin.PluginAuthor),
|
||||
nameof(OnlinePlugin.PluginDescription)
|
||||
]);
|
||||
|
||||
return ApiResponse.From(response, true);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.API.Endpoints.SettingsManagement;
|
||||
|
||||
public class SettingsChangeEndpoint : IEndpoint
|
||||
{
|
||||
public string Path => "/api/settings/update";
|
||||
public EndpointType HttpMethod => EndpointType.Post;
|
||||
public async Task<ApiResponse> HandleRequest(string? jsonRequest)
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonRequest))
|
||||
{
|
||||
return ApiResponse.Fail("Invalid json string");
|
||||
}
|
||||
|
||||
Dictionary<string, object> jsonDictionary = await JsonManager.ConvertFromJson<Dictionary<string, object>>(jsonRequest);
|
||||
foreach (var (key, value) in jsonDictionary)
|
||||
{
|
||||
Application.CurrentApplication.ApplicationEnvironmentVariables.Set(key, value);
|
||||
}
|
||||
|
||||
return ApiResponse.Ok();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.API.Endpoints.SettingsManagement;
|
||||
|
||||
public class SettingsGetEndpoint : IEndpoint
|
||||
{
|
||||
public string Path => "/api/settings/get";
|
||||
public EndpointType HttpMethod => EndpointType.Get;
|
||||
public async Task<ApiResponse> HandleRequest(string? jsonRequest)
|
||||
{
|
||||
Dictionary<string, object> jsonSettingsDictionary = new Dictionary<string, object>()
|
||||
{
|
||||
{"token", Application.CurrentApplication.ApplicationEnvironmentVariables.Get("token", string.Empty)},
|
||||
{"prefix", Application.CurrentApplication.ApplicationEnvironmentVariables.Get("prefix", string.Empty)},
|
||||
{"serverIds", Application.CurrentApplication.ApplicationEnvironmentVariables.GetList("ServerID", new List<ulong>())}
|
||||
};
|
||||
|
||||
string jsonResponse = await JsonManager.ConvertToJsonString(jsonSettingsDictionary);
|
||||
return ApiResponse.From(jsonResponse, true);
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.API.Sockets.Sockets;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
|
||||
namespace DiscordBotCore.API.Sockets;
|
||||
|
||||
internal class SocketManager
|
||||
{
|
||||
private readonly IConnectionDetails _ConnectionDetails;
|
||||
private List<ISocket> _Sockets = new List<ISocket>();
|
||||
|
||||
public SocketManager(IConnectionDetails connectionDetails)
|
||||
{
|
||||
_ConnectionDetails = connectionDetails;
|
||||
}
|
||||
|
||||
public void RegisterBaseSockets()
|
||||
{
|
||||
Register(new PluginDownloadProgressSocket());
|
||||
}
|
||||
|
||||
public bool Register(ISocket socket)
|
||||
{
|
||||
if (_Sockets.Any(s => s.Path == socket.Path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_Sockets.Add(socket);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Console.WriteLine("Starting sockets ...");
|
||||
foreach (var socket in _Sockets)
|
||||
{
|
||||
Thread thread = new Thread(() => StartSocket(socket));
|
||||
thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private async void StartSocket(ISocket socket)
|
||||
{
|
||||
|
||||
if (!socket.Path.StartsWith("/"))
|
||||
{
|
||||
throw new ArgumentException($"Socket path '{socket.Path}' must start with '/'.");
|
||||
}
|
||||
|
||||
string prefix = $"http://{_ConnectionDetails.Host}:{_ConnectionDetails.Port}{socket.Path}/";
|
||||
Console.WriteLine($"Starting socket with prefix: {prefix}");
|
||||
|
||||
HttpListener listener = new HttpListener();
|
||||
listener.Prefixes.Add(prefix);
|
||||
listener.Start();
|
||||
|
||||
await ConnectionHandler(listener, socket.HandleRequest);
|
||||
}
|
||||
|
||||
|
||||
private async Task ConnectionHandler(HttpListener listener, Func<byte[], int, Task<SocketResponse>> handler)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var context = await listener.GetContextAsync();
|
||||
|
||||
if (context.Request.IsWebSocketRequest)
|
||||
{
|
||||
WebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
|
||||
Application.CurrentApplication.Logger.Log("WebSocket connection established.");
|
||||
await HandleSocket(webSocketContext.WebSocket, handler);
|
||||
}
|
||||
else { break; }
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSocket(WebSocket socket, Func<byte[], int, Task<SocketResponse>> handler)
|
||||
{
|
||||
if (socket.State != WebSocketState.Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[1024 * 4];
|
||||
var receivedData = new List<byte>();
|
||||
WebSocketReceiveResult result;
|
||||
|
||||
do
|
||||
{
|
||||
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
receivedData.AddRange(buffer.Take(result.Count));
|
||||
} while (!result.EndOfMessage);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection ...", CancellationToken.None);
|
||||
Application.CurrentApplication.Logger.Log("WebSocket connection closed.");
|
||||
return;
|
||||
}
|
||||
|
||||
Application.CurrentApplication.Logger.Log("WebSocket message received. Length: " + receivedData.Count);
|
||||
SocketResponse socketResponse = await handler(receivedData.ToArray(), receivedData.Count);
|
||||
ArraySegment<byte> response = new ArraySegment<byte>(socketResponse.Data, 0, socketResponse.Data.Length);
|
||||
byte[]? lastResponse = null;
|
||||
|
||||
while (!socketResponse.CloseConnectionAfterResponse)
|
||||
{
|
||||
if (lastResponse == null || !socketResponse.Data.SequenceEqual(lastResponse))
|
||||
{
|
||||
await socket.SendAsync(response, WebSocketMessageType.Text, socketResponse.EndOfMessage, CancellationToken.None);
|
||||
lastResponse = socketResponse.Data;
|
||||
}
|
||||
|
||||
socketResponse = await handler(receivedData.ToArray(), receivedData.Count);
|
||||
response = new ArraySegment<byte>(socketResponse.Data, 0, socketResponse.Data.Length);
|
||||
}
|
||||
|
||||
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection ...", CancellationToken.None);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
|
||||
namespace DiscordBotCore.API.Sockets;
|
||||
|
||||
internal class SocketResponse
|
||||
{
|
||||
public byte[] Data { get;}
|
||||
public bool EndOfMessage { get; }
|
||||
public bool Success { get; }
|
||||
public bool CloseConnectionAfterResponse { get; set; }
|
||||
|
||||
private SocketResponse(byte[] data, bool endOfMessage, bool success, bool closeConnectionAfterResponse)
|
||||
{
|
||||
Data = data;
|
||||
EndOfMessage = endOfMessage;
|
||||
Success = success;
|
||||
CloseConnectionAfterResponse = closeConnectionAfterResponse;
|
||||
}
|
||||
|
||||
internal static SocketResponse From(byte[] data, bool endOfMessage, bool success, bool closeConnectionAfterResponse)
|
||||
{
|
||||
return new SocketResponse(data, endOfMessage, success, closeConnectionAfterResponse);
|
||||
}
|
||||
|
||||
internal static SocketResponse From(byte[] data, bool endOfMessage)
|
||||
{
|
||||
return new SocketResponse(data, endOfMessage, true, false);
|
||||
}
|
||||
|
||||
internal static SocketResponse From(byte[] data)
|
||||
{
|
||||
return new SocketResponse(data, true, true, false);
|
||||
}
|
||||
|
||||
internal static SocketResponse Fail(bool closeConnection)
|
||||
{
|
||||
return new SocketResponse(new byte[0], true, false, closeConnection);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces.API;
|
||||
|
||||
namespace DiscordBotCore.API.Sockets.Sockets;
|
||||
|
||||
internal class PluginDownloadProgressSocket : ISocket
|
||||
{
|
||||
public string Path => "/plugin/download/progress";
|
||||
public Task<SocketResponse> HandleRequest(byte[] request, int count)
|
||||
{
|
||||
if (!Application.CurrentApplication.PluginManager.InstallingPluginInformation.IsInstalling)
|
||||
{
|
||||
return Task.FromResult(SocketResponse.Fail(true));
|
||||
}
|
||||
|
||||
float value = Application.CurrentApplication.PluginManager.InstallingPluginInformation.InstallationProgress;
|
||||
SocketResponse response = SocketResponse.From(Encoding.UTF8.GetBytes(value.ToString()));
|
||||
response.CloseConnectionAfterResponse = false;
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.API;
|
||||
using DiscordBotCore.API.Endpoints;
|
||||
using DiscordBotCore.API.Sockets;
|
||||
using DiscordBotCore.Bot;
|
||||
using DiscordBotCore.Interfaces.Logger;
|
||||
using DiscordBotCore.Online;
|
||||
using DiscordBotCore.Online.Helpers;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
using DiscordBotCore.Others.Settings;
|
||||
using DiscordBotCore.Plugin;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore
|
||||
{
|
||||
/// <summary>
|
||||
/// The main Application and its components
|
||||
/// </summary>
|
||||
public sealed class Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the current application. This is a singleton class
|
||||
/// </summary>
|
||||
public static Application CurrentApplication { get; private set; } = null!;
|
||||
|
||||
public static bool IsRunning { get; private set; }
|
||||
|
||||
private static readonly string _ConfigFile = "./Data/Resources/config.json";
|
||||
private static readonly string _PluginsDatabaseFile = "./Data/Resources/plugins.json";
|
||||
|
||||
private static readonly string _ResourcesFolder = "./Data/Resources";
|
||||
private static readonly string _PluginsFolder = "./Data/Plugins";
|
||||
private static readonly string _LogsFolder = "./Data/Logs";
|
||||
|
||||
private static readonly string _LogFormat = "{ThrowTime} {SenderName} {Message}";
|
||||
|
||||
public DiscordBotApplication DiscordBotClient { get; set; } = null!;
|
||||
|
||||
public List<ulong> ServerIDs => ApplicationEnvironmentVariables.GetList("ServerID", new List<ulong>());
|
||||
public string PluginDatabase => ApplicationEnvironmentVariables.Get<string>("PluginDatabase", _PluginsDatabaseFile);
|
||||
public CustomSettingsDictionary ApplicationEnvironmentVariables { get; private set; } = null!;
|
||||
public InternalActionManager InternalActionManager { get; private set; } = null!;
|
||||
public PluginManager PluginManager { get; private set; } = null!;
|
||||
public ILogger Logger { get; private set; } = null!;
|
||||
internal ApiManager? ApiManager { get; private set; }
|
||||
internal SocketManager? SocketManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create the application. This method is used to initialize the application. Can not initialize multiple times.
|
||||
/// </summary>
|
||||
public static async Task CreateApplication()
|
||||
{
|
||||
if (CurrentApplication is not null)
|
||||
{
|
||||
CurrentApplication.Logger.Log("Application is already initialized. Reinitialization is not allowed", LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentApplication = new Application();
|
||||
|
||||
Directory.CreateDirectory(_ResourcesFolder);
|
||||
Directory.CreateDirectory(_PluginsFolder);
|
||||
Directory.CreateDirectory(_LogsFolder);
|
||||
|
||||
CurrentApplication.ApplicationEnvironmentVariables = await CustomSettingsDictionary.CreateFromFile(_ConfigFile, true);
|
||||
|
||||
CurrentApplication.ApplicationEnvironmentVariables.Add("PluginFolder", _PluginsFolder);
|
||||
CurrentApplication.ApplicationEnvironmentVariables.Add("ResourceFolder", _ResourcesFolder);
|
||||
CurrentApplication.ApplicationEnvironmentVariables.Add("LogsFolder", _LogsFolder);
|
||||
|
||||
CurrentApplication.Logger = new Logger(_LogsFolder, _LogFormat);
|
||||
|
||||
if (!File.Exists(_PluginsDatabaseFile))
|
||||
{
|
||||
List<PluginInfo> plugins = new();
|
||||
await JsonManager.SaveToJsonFile(_PluginsDatabaseFile, plugins);
|
||||
}
|
||||
|
||||
CurrentApplication.PluginManager = new PluginManager(new PluginRepository(PluginRepositoryConfiguration.Default));
|
||||
|
||||
await CurrentApplication.PluginManager.UninstallMarkedPlugins();
|
||||
|
||||
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();
|
||||
CurrentApplication.ApiManager.InitializeApi();
|
||||
}
|
||||
|
||||
public static void InitializeThreadedSockets()
|
||||
{
|
||||
if (CurrentApplication is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(CurrentApplication.SocketManager is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentApplication.SocketManager = new SocketManager(new ConnectionDetails("localhost", 5055));
|
||||
CurrentApplication.SocketManager.RegisterBaseSockets();
|
||||
CurrentApplication.SocketManager.Start();
|
||||
}
|
||||
|
||||
public static string GetResourceFullPath(string path)
|
||||
{
|
||||
var result = Path.Combine(_ResourcesFolder, path);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetPluginFullPath(string path)
|
||||
{
|
||||
var result = Path.Combine(_PluginsFolder, path);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void Log(string message, LogType logType = LogType.Info)
|
||||
{
|
||||
CurrentApplication.Logger.Log(message, logType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,53 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Loaders;
|
||||
using DiscordBotCore.Configuration;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Permissions;
|
||||
using DiscordBotCore.PluginCore.Helpers;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
using DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
internal class CommandHandler
|
||||
internal class CommandHandler : ICommandHandler
|
||||
{
|
||||
private readonly string _botPrefix;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly CommandService _commandService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPluginLoader _pluginLoader;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Command handler constructor
|
||||
/// </summary>
|
||||
/// <param name="client">The discord bot client</param>
|
||||
/// <param name="pluginLoader">The plugin loader</param>
|
||||
/// <param name="commandService">The discord bot command service</param>
|
||||
/// <param name="botPrefix">The prefix to watch for</param>
|
||||
public CommandHandler(DiscordSocketClient client, CommandService commandService, string botPrefix)
|
||||
/// <param name="logger">The logger</param>
|
||||
public CommandHandler(ILogger logger, IPluginLoader pluginLoader, IConfiguration configuration, CommandService commandService, string botPrefix)
|
||||
{
|
||||
_client = client;
|
||||
_commandService = commandService;
|
||||
_botPrefix = botPrefix;
|
||||
_logger = logger;
|
||||
_pluginLoader = pluginLoader;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method to initialize all commands
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task InstallCommandsAsync()
|
||||
public async Task InstallCommandsAsync(DiscordSocketClient client)
|
||||
{
|
||||
_client.MessageReceived += MessageHandler;
|
||||
_client.SlashCommandExecuted += Client_SlashCommandExecuted;
|
||||
client.MessageReceived += (message) => MessageHandler(client, message);
|
||||
client.SlashCommandExecuted += Client_SlashCommandExecuted;
|
||||
await _commandService.AddModulesAsync(Assembly.GetEntryAssembly(), null);
|
||||
}
|
||||
|
||||
@@ -45,18 +55,18 @@ internal class CommandHandler
|
||||
{
|
||||
try
|
||||
{
|
||||
var plugin = PluginLoader.SlashCommands.FirstOrDefault(p => p.Name == arg.Data.Name);
|
||||
var plugin = _pluginLoader.SlashCommands.FirstOrDefault(p => p.Name == arg.Data.Name);
|
||||
|
||||
if (plugin is null)
|
||||
throw new Exception("Failed to run command !");
|
||||
|
||||
if (arg.Channel is SocketDMChannel)
|
||||
plugin.ExecuteDm(arg);
|
||||
else plugin.ExecuteServer(arg);
|
||||
plugin.ExecuteDm(_logger, arg);
|
||||
else plugin.ExecuteServer(_logger, arg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.LogException(ex, this);
|
||||
_logger.LogException(ex, this);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -67,38 +77,38 @@ internal class CommandHandler
|
||||
/// </summary>
|
||||
/// <param name="Message">The message got from the user in discord chat</param>
|
||||
/// <returns></returns>
|
||||
private async Task MessageHandler(SocketMessage Message)
|
||||
private async Task MessageHandler(DiscordSocketClient socketClient, SocketMessage socketMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Message.Author.IsBot)
|
||||
if (socketMessage.Author.IsBot)
|
||||
return;
|
||||
|
||||
if (Message as SocketUserMessage == null)
|
||||
if (socketMessage as SocketUserMessage == null)
|
||||
return;
|
||||
|
||||
var message = Message as SocketUserMessage;
|
||||
var message = socketMessage as SocketUserMessage;
|
||||
|
||||
if (message is null)
|
||||
return;
|
||||
|
||||
var argPos = 0;
|
||||
|
||||
if (!message.Content.StartsWith(_botPrefix) && !message.HasMentionPrefix(_client.CurrentUser, ref argPos))
|
||||
if (!message.Content.StartsWith(_botPrefix) && !message.HasMentionPrefix(socketClient.CurrentUser, ref argPos))
|
||||
return;
|
||||
|
||||
var context = new SocketCommandContext(_client, message);
|
||||
var context = new SocketCommandContext(socketClient, message);
|
||||
|
||||
await _commandService.ExecuteAsync(context, argPos, null);
|
||||
|
||||
IDbCommand? plugin;
|
||||
var cleanMessage = "";
|
||||
|
||||
if (message.HasMentionPrefix(_client.CurrentUser, ref argPos))
|
||||
if (message.HasMentionPrefix(socketClient.CurrentUser, ref argPos))
|
||||
{
|
||||
var mentionPrefix = "<@" + _client.CurrentUser.Id + ">";
|
||||
var mentionPrefix = "<@" + socketClient.CurrentUser.Id + ">";
|
||||
|
||||
plugin = PluginLoader.Commands!
|
||||
plugin = _pluginLoader.Commands!
|
||||
.FirstOrDefault(plug => plug.Command ==
|
||||
message.Content.Substring(mentionPrefix.Length + 1)
|
||||
.Split(' ')[0] ||
|
||||
@@ -114,7 +124,7 @@ internal class CommandHandler
|
||||
|
||||
else
|
||||
{
|
||||
plugin = PluginLoader.Commands!
|
||||
plugin = _pluginLoader.Commands!
|
||||
.FirstOrDefault(p => p.Command ==
|
||||
message.Content.Split(' ')[0].Substring(_botPrefix.Length) ||
|
||||
p.Aliases is not null &&
|
||||
@@ -138,21 +148,26 @@ internal class CommandHandler
|
||||
if (split.Length > 1)
|
||||
argsClean = string.Join(' ', split, 1, split.Length - 1).Split(' ');
|
||||
|
||||
DbCommandExecutingArguments cmd = new(context, cleanMessage, split[0], argsClean);
|
||||
DbCommandExecutingArgument cmd = new(_logger,
|
||||
context,
|
||||
cleanMessage,
|
||||
split[0],
|
||||
argsClean,
|
||||
new DirectoryInfo(Path.Combine(_configuration.Get<string>("ResourcesFolder"), plugin.Command)));
|
||||
|
||||
Application.CurrentApplication.Logger.Log(
|
||||
_logger.Log(
|
||||
$"User ({context.User.Username}) from Guild \"{context.Guild.Name}\" executed command \"{cmd.CleanContent}\"",
|
||||
this,
|
||||
LogType.Info
|
||||
);
|
||||
|
||||
if (context.Channel is SocketDMChannel)
|
||||
plugin.ExecuteDm(cmd);
|
||||
else plugin.ExecuteServer(cmd);
|
||||
await plugin.ExecuteDm(cmd);
|
||||
else await plugin.ExecuteServer(cmd);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.LogException(ex, this);
|
||||
_logger.LogException(ex, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Configuration;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
public class DiscordBotApplication
|
||||
public class DiscordBotApplication : IDiscordBotApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// The bot prefix
|
||||
/// </summary>
|
||||
private readonly string _BotPrefix;
|
||||
|
||||
/// <summary>
|
||||
/// The bot token
|
||||
/// </summary>
|
||||
private readonly string _BotToken;
|
||||
|
||||
/// <summary>
|
||||
/// The bot client
|
||||
/// </summary>
|
||||
public DiscordSocketClient Client;
|
||||
|
||||
/// <summary>
|
||||
/// The bot command handler
|
||||
/// </summary>
|
||||
private CommandHandler _CommandServiceHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The command service
|
||||
/// </summary>
|
||||
private CommandService _Service;
|
||||
private static readonly string _DefaultPrefix = ";";
|
||||
|
||||
private CommandHandler _CommandServiceHandler;
|
||||
private CommandService _Service;
|
||||
private readonly ILogger _Logger;
|
||||
private readonly IConfiguration _Configuration;
|
||||
private readonly IPluginLoader _PluginLoader;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the bot is ready
|
||||
/// </summary>
|
||||
/// <value> true if the bot is ready, otherwise false </value>
|
||||
private bool IsReady { get; set; }
|
||||
|
||||
public DiscordSocketClient Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The main Boot constructor
|
||||
/// </summary>
|
||||
/// <param name="botToken">The bot token</param>
|
||||
/// <param name="botPrefix">The bot prefix</param>
|
||||
public DiscordBotApplication(string botToken, string botPrefix)
|
||||
public DiscordBotApplication(ILogger logger, IConfiguration configuration, IPluginLoader pluginLoader)
|
||||
{
|
||||
this._BotPrefix = botPrefix;
|
||||
this._BotToken = botToken;
|
||||
this._Logger = logger;
|
||||
this._Configuration = configuration;
|
||||
this._PluginLoader = pluginLoader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,25 +48,24 @@ public class DiscordBotApplication
|
||||
GatewayIntents = GatewayIntents.All
|
||||
};
|
||||
|
||||
Client = new DiscordSocketClient(config);
|
||||
DiscordSocketClient client = new DiscordSocketClient(config);
|
||||
Client = client;
|
||||
|
||||
_Service = new CommandService();
|
||||
|
||||
Client.Log += Log;
|
||||
Client.LoggedIn += LoggedIn;
|
||||
Client.Ready += Ready;
|
||||
Client.Disconnected += Client_Disconnected;
|
||||
client.Log += Log;
|
||||
client.LoggedIn += LoggedIn;
|
||||
client.Ready += Ready;
|
||||
client.Disconnected += Client_Disconnected;
|
||||
|
||||
await Client.LoginAsync(TokenType.Bot, _BotToken);
|
||||
await client.LoginAsync(TokenType.Bot, _Configuration.Get<string>("token"));
|
||||
|
||||
await Client.StartAsync();
|
||||
await client.StartAsync();
|
||||
|
||||
_CommandServiceHandler = new CommandHandler(Client, _Service, _BotPrefix);
|
||||
_CommandServiceHandler = new CommandHandler(_Logger, _PluginLoader, _Configuration, _Service, _Configuration.Get<string>("prefix", _DefaultPrefix));
|
||||
|
||||
await _CommandServiceHandler.InstallCommandsAsync();
|
||||
|
||||
Application.CurrentApplication.DiscordBotClient = this;
|
||||
await _CommandServiceHandler.InstallCommandsAsync(client);
|
||||
|
||||
// wait for the bot to be ready
|
||||
while (!IsReady)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
@@ -97,38 +76,21 @@ public class DiscordBotApplication
|
||||
{
|
||||
if (arg.Message.Contains("401"))
|
||||
{
|
||||
Application.CurrentApplication.ApplicationEnvironmentVariables.Remove("token");
|
||||
Application.CurrentApplication.Logger.Log("The token is invalid.", this, LogType.Critical);
|
||||
await Application.CurrentApplication.ApplicationEnvironmentVariables.SaveToFile();
|
||||
_Configuration.Set("token", string.Empty);
|
||||
_Logger.Log("The token is invalid.", this, LogType.Critical);
|
||||
await _Configuration.SaveToFile();
|
||||
}
|
||||
}
|
||||
|
||||
private Task Ready()
|
||||
{
|
||||
IsReady = true;
|
||||
|
||||
if (Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("CustomStatus"))
|
||||
{
|
||||
var status = Application.CurrentApplication.ApplicationEnvironmentVariables.GetDictionary<string, string>("CustomStatus");
|
||||
string type = status["Type"];
|
||||
string message = status["Message"];
|
||||
ActivityType activityType = type switch
|
||||
{
|
||||
"Playing" => ActivityType.Playing,
|
||||
"Listening" => ActivityType.Listening,
|
||||
"Watching" => ActivityType.Watching,
|
||||
"Streaming" => ActivityType.Streaming,
|
||||
_ => ActivityType.Playing
|
||||
};
|
||||
Client.SetGameAsync(message, null, activityType);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task LoggedIn()
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Successfully Logged In", this);
|
||||
_Logger.Log("Successfully Logged In", this);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -138,12 +100,12 @@ public class DiscordBotApplication
|
||||
{
|
||||
case LogSeverity.Error:
|
||||
case LogSeverity.Critical:
|
||||
Application.CurrentApplication.Logger.Log(message.Message, this, LogType.Error);
|
||||
_Logger.Log(message.Message, this, LogType.Error);
|
||||
break;
|
||||
|
||||
case LogSeverity.Info:
|
||||
case LogSeverity.Debug:
|
||||
Application.CurrentApplication.Logger.Log(message.Message, this, LogType.Info);
|
||||
_Logger.Log(message.Message, this, LogType.Info);
|
||||
|
||||
|
||||
break;
|
||||
|
||||
13
DiscordBotCore/Bot/ICommandHandler.cs
Normal file
13
DiscordBotCore/Bot/ICommandHandler.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
internal interface ICommandHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The method to initialize all commands
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task InstallCommandsAsync(DiscordSocketClient client);
|
||||
}
|
||||
14
DiscordBotCore/Bot/IDiscordBotApplication.cs
Normal file
14
DiscordBotCore/Bot/IDiscordBotApplication.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
public interface IDiscordBotApplication
|
||||
{
|
||||
public DiscordSocketClient Client { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The start method for the bot. This method is used to load the bot
|
||||
/// </summary>
|
||||
Task StartAsync();
|
||||
}
|
||||
@@ -8,10 +8,16 @@
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.15.3" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.1" />
|
||||
<PackageReference Include="Discord.Net" Version="3.17.2" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="UI\Controls\MessageBox.axaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginCore\DiscordBotCore.PluginCore.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginManagement.Loading\DiscordBotCore.PluginManagement.Loading.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginManagement\DiscordBotCore.PluginManagement.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace DiscordBotCore.Interfaces.API;
|
||||
|
||||
public interface IConnectionDetails
|
||||
{
|
||||
public string Host { get; }
|
||||
public int Port { get; }
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.API.Endpoints;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.API.Sockets;
|
||||
|
||||
namespace DiscordBotCore.Interfaces.API;
|
||||
|
||||
internal interface ISocket
|
||||
{
|
||||
public string Path { get; }
|
||||
public Task<SocketResponse> HandleRequest(byte[] request, int count);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
namespace DiscordBotCore.Interfaces;
|
||||
|
||||
public interface ICommandAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the action. It is also used to call the action
|
||||
/// </summary>
|
||||
public string ActionName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of the action
|
||||
/// </summary>
|
||||
public string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An example or a format of how to use the action
|
||||
/// </summary>
|
||||
public string? Usage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Some parameter descriptions. The key is the parameter name and the value is the description. Supports nesting. Only for the Help command
|
||||
/// </summary>
|
||||
public IEnumerable<InternalActionOption> ListOfOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the action. It can be a startup action, a normal action (invoked) or both
|
||||
/// </summary>
|
||||
public InternalActionRunType RunType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies if the action requires another thread to run. This is useful for actions that are blocking the main thread.
|
||||
/// </summary>
|
||||
public bool RequireOtherThread { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The method that is invoked when the action is called.
|
||||
/// </summary>
|
||||
/// <param name="args">The parameters. Its best practice to reflect the parameters described in <see cref="ListOfOptions"/></param>
|
||||
public Task Execute(string[]? args);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace DiscordBotCore.Interfaces;
|
||||
|
||||
public interface IDbSlashCommand
|
||||
{
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
bool CanUseDm { get; }
|
||||
bool HasInteraction { get; }
|
||||
|
||||
List<SlashCommandOptionBuilder> Options { get; }
|
||||
|
||||
void ExecuteServer(SocketSlashCommand context)
|
||||
{ }
|
||||
|
||||
void ExecuteDm(SocketSlashCommand context) { }
|
||||
|
||||
Task ExecuteInteraction(SocketInteraction interaction) => Task.CompletedTask;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace DiscordBotCore.Loaders;
|
||||
|
||||
public class FileLoaderResult
|
||||
{
|
||||
public string PluginName { get; private set; }
|
||||
|
||||
public string ErrorMessage { get; private set; }
|
||||
|
||||
|
||||
public FileLoaderResult(string pluginName, string errorMessage)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public FileLoaderResult(string pluginName)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
ErrorMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Exceptions;
|
||||
|
||||
|
||||
namespace DiscordBotCore.Loaders;
|
||||
|
||||
public sealed class PluginLoader
|
||||
{
|
||||
private readonly DiscordSocketClient _Client;
|
||||
public delegate void CommandLoaded(IDbCommand eCommand);
|
||||
public delegate void EventLoaded(IDbEvent eEvent);
|
||||
public delegate void SlashCommandLoaded(IDbSlashCommand eSlashCommand);
|
||||
public delegate void ActionLoaded(ICommandAction eAction);
|
||||
|
||||
public CommandLoaded? OnCommandLoaded;
|
||||
public EventLoaded? OnEventLoaded;
|
||||
public SlashCommandLoaded? OnSlashCommandLoaded;
|
||||
public ActionLoaded? OnActionLoaded;
|
||||
|
||||
public static List<IDbCommand> Commands { get; private set; } = new List<IDbCommand>();
|
||||
public static List<IDbEvent> Events { get; private set; } = new List<IDbEvent>();
|
||||
public static List<IDbSlashCommand> SlashCommands { get; private set; } = new List<IDbSlashCommand>();
|
||||
public static List<ICommandAction> Actions { get; private set; } = new List<ICommandAction>();
|
||||
|
||||
public PluginLoader(DiscordSocketClient discordSocketClient)
|
||||
{
|
||||
_Client = discordSocketClient;
|
||||
}
|
||||
|
||||
public async Task LoadPlugins()
|
||||
{
|
||||
Commands.Clear();
|
||||
Events.Clear();
|
||||
SlashCommands.Clear();
|
||||
Actions.Clear();
|
||||
|
||||
Application.CurrentApplication.Logger.Log("Loading plugins...", this);
|
||||
|
||||
var loader = new Loader();
|
||||
|
||||
loader.OnFileLoadedException += FileLoadedException;
|
||||
loader.OnPluginLoaded += OnPluginLoaded;
|
||||
|
||||
await loader.Load();
|
||||
}
|
||||
|
||||
private void FileLoadedException(FileLoaderResult result)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(result.ErrorMessage, this, LogType.Error);
|
||||
}
|
||||
|
||||
private async void InitializeCommand(ICommandAction action)
|
||||
{
|
||||
if (action.RunType == InternalActionRunType.OnStartup || action.RunType == InternalActionRunType.OnStartupAndCall)
|
||||
await Application.CurrentApplication.InternalActionManager.StartAction(action, null);
|
||||
|
||||
if(action.RunType == InternalActionRunType.OnCall || action.RunType == InternalActionRunType.OnStartupAndCall)
|
||||
Actions.Add(action);
|
||||
|
||||
OnActionLoaded?.Invoke(action);
|
||||
}
|
||||
|
||||
private void InitializeDbCommand(IDbCommand command)
|
||||
{
|
||||
Commands.Add(command);
|
||||
OnCommandLoaded?.Invoke(command);
|
||||
}
|
||||
|
||||
private void InitializeEvent(IDbEvent eEvent)
|
||||
{
|
||||
if (!eEvent.TryStartEvent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Events.Add(eEvent);
|
||||
OnEventLoaded?.Invoke(eEvent);
|
||||
}
|
||||
|
||||
private async void InitializeSlashCommand(IDbSlashCommand slashCommand)
|
||||
{
|
||||
Result result = await slashCommand.TryStartSlashCommand();
|
||||
result.Match(
|
||||
() =>
|
||||
{
|
||||
if (slashCommand.HasInteraction)
|
||||
_Client.InteractionCreated += slashCommand.ExecuteInteraction;
|
||||
SlashCommands.Add(slashCommand);
|
||||
OnSlashCommandLoaded?.Invoke(slashCommand);
|
||||
},
|
||||
HandleError
|
||||
);
|
||||
}
|
||||
|
||||
private void HandleError(Exception exception)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(exception.Message, this, LogType.Error);
|
||||
}
|
||||
|
||||
private void OnPluginLoaded(PluginLoaderResult result)
|
||||
{
|
||||
result.Match(
|
||||
InitializeDbCommand,
|
||||
InitializeEvent,
|
||||
InitializeSlashCommand,
|
||||
InitializeCommand,
|
||||
HandleError
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using ContextType = Discord.Commands.ContextType;
|
||||
|
||||
namespace DiscordBotCore.Loaders;
|
||||
|
||||
internal static class PluginLoaderExtensions
|
||||
{
|
||||
internal static bool TryStartEvent(this IDbEvent? dbEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dbEvent is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dbEvent));
|
||||
}
|
||||
|
||||
dbEvent.Start(Application.CurrentApplication.DiscordBotClient.Client);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Error starting event {dbEvent.Name}: {e.Message}", typeof(PluginLoader), LogType.Error);
|
||||
Application.CurrentApplication.Logger.LogException(e, typeof(PluginLoader));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<Result> TryStartSlashCommand(this IDbSlashCommand? dbSlashCommand)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dbSlashCommand is null)
|
||||
{
|
||||
return Result.Failure(new Exception("dbSlashCommand is null"));
|
||||
}
|
||||
|
||||
if (Application.CurrentApplication.DiscordBotClient.Client.Guilds.Count == 0)
|
||||
{
|
||||
return Result.Failure(new Exception("No guilds found"));
|
||||
}
|
||||
|
||||
var builder = new SlashCommandBuilder();
|
||||
builder.WithName(dbSlashCommand.Name);
|
||||
builder.WithDescription(dbSlashCommand.Description);
|
||||
builder.Options = dbSlashCommand.Options;
|
||||
|
||||
if (dbSlashCommand.CanUseDm)
|
||||
builder.WithContextTypes(InteractionContextType.BotDm, InteractionContextType.Guild);
|
||||
else
|
||||
builder.WithContextTypes(InteractionContextType.Guild);
|
||||
|
||||
foreach(ulong guildId in Application.CurrentApplication.ServerIDs)
|
||||
{
|
||||
bool result = await EnableSlashCommandPerGuild(guildId, builder);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return Result.Failure($"Failed to enable slash command {dbSlashCommand.Name} for guild {guildId}");
|
||||
}
|
||||
}
|
||||
|
||||
await Application.CurrentApplication.DiscordBotClient.Client.CreateGlobalApplicationCommandAsync(builder.Build());
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Result.Failure("Error starting slash command");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> EnableSlashCommandPerGuild(ulong guildId, SlashCommandBuilder builder)
|
||||
{
|
||||
SocketGuild? guild = Application.CurrentApplication.DiscordBotClient.Client.GetGuild(guildId);
|
||||
if (guild is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Failed to get guild with ID " + guildId, typeof(PluginLoader), LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
await guild.CreateApplicationCommandAsync(builder.Build());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Exceptions;
|
||||
|
||||
namespace DiscordBotCore.Loaders;
|
||||
|
||||
|
||||
public class PluginLoaderResult
|
||||
{
|
||||
private Option4<IDbCommand, IDbEvent, IDbSlashCommand, ICommandAction, Exception> _Result;
|
||||
|
||||
public static PluginLoaderResult FromIDbCommand(IDbCommand command) => new PluginLoaderResult(new Option4<IDbCommand, IDbEvent, IDbSlashCommand, ICommandAction, Exception>(command));
|
||||
|
||||
public static PluginLoaderResult FromIDbEvent(IDbEvent dbEvent) => new PluginLoaderResult(new Option4<IDbCommand, IDbEvent, IDbSlashCommand, ICommandAction, Exception>(dbEvent));
|
||||
|
||||
public static PluginLoaderResult FromIDbSlashCommand(IDbSlashCommand slashCommand) => new PluginLoaderResult(new Option4<IDbCommand, IDbEvent, IDbSlashCommand, ICommandAction, Exception>(slashCommand));
|
||||
|
||||
public static PluginLoaderResult FromICommandAction(ICommandAction commandAction) => new PluginLoaderResult(new Option4<IDbCommand, IDbEvent, IDbSlashCommand, ICommandAction, Exception>(commandAction));
|
||||
|
||||
public static PluginLoaderResult FromException(Exception exception) => new PluginLoaderResult(new Option4<IDbCommand, IDbEvent, IDbSlashCommand, ICommandAction, Exception>(exception));
|
||||
public static PluginLoaderResult FromException(string exception) => new PluginLoaderResult(new Option4<IDbCommand, IDbEvent, IDbSlashCommand, ICommandAction, Exception>(new Exception(message: exception)));
|
||||
|
||||
private PluginLoaderResult(Option4<IDbCommand, IDbEvent, IDbSlashCommand, ICommandAction, Exception> result)
|
||||
{
|
||||
_Result = result;
|
||||
}
|
||||
|
||||
public void Match(Action<IDbCommand> commandAction, Action<IDbEvent> eventAction, Action<IDbSlashCommand> slashCommandAction,
|
||||
Action<ICommandAction> commandActionAction, Action<Exception> exceptionAction)
|
||||
{
|
||||
_Result.Match(commandAction, eventAction, slashCommandAction, commandActionAction, exceptionAction);
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<IDbCommand, TResult> commandFunc, Func<IDbEvent, TResult> eventFunc,
|
||||
Func<IDbSlashCommand, TResult> slashCommandFunc, Func<ICommandAction, TResult> commandActionFunc,
|
||||
Func<Exception, TResult> exceptionFunc)
|
||||
{
|
||||
return _Result.Match(commandFunc, eventFunc, slashCommandFunc, commandActionFunc, exceptionFunc);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DiscordBotCore.Online;
|
||||
|
||||
public class FileDownloader
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public List<Task> ListOfDownloadTasks { get; private set; }
|
||||
private bool IsParallelDownloader { get; set; }
|
||||
|
||||
public Action FinishAction { get; private set; }
|
||||
|
||||
public FileDownloader(bool isParallelDownloader)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
ListOfDownloadTasks = new List<Task>();
|
||||
|
||||
IsParallelDownloader = isParallelDownloader;
|
||||
}
|
||||
|
||||
public FileDownloader(bool isParallelDownloader, Action finishAction)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
ListOfDownloadTasks = new List<Task>();
|
||||
|
||||
IsParallelDownloader = isParallelDownloader;
|
||||
FinishAction = finishAction;
|
||||
}
|
||||
|
||||
public async Task StartDownloadTasks()
|
||||
{
|
||||
if (IsParallelDownloader)
|
||||
{
|
||||
await Task.WhenAll(ListOfDownloadTasks);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var task in ListOfDownloadTasks)
|
||||
{
|
||||
await task;
|
||||
}
|
||||
}
|
||||
|
||||
FinishAction?.Invoke();
|
||||
}
|
||||
|
||||
public void AppendDownloadTask(string downloadLink, string downloadLocation, IProgress<float> progress)
|
||||
{
|
||||
ListOfDownloadTasks.Add(CreateDownloadTask(_httpClient, downloadLink, downloadLocation, progress));
|
||||
}
|
||||
|
||||
public void AppendDownloadTask(string downloadLink, string downloadLocation, Action<float> progressCallback)
|
||||
{
|
||||
ListOfDownloadTasks.Add(CreateDownloadTask(_httpClient, downloadLink, downloadLocation, new Progress<float>(progressCallback)));
|
||||
}
|
||||
|
||||
public static async Task CreateDownloadTask(HttpClient client, string url, string targetPath, IProgress<float> progress)
|
||||
{
|
||||
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
|
||||
var receivedBytes = 0L;
|
||||
|
||||
var targetDirectory = Path.GetDirectoryName(targetPath);
|
||||
if (!string.IsNullOrEmpty(targetDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
}
|
||||
|
||||
using var contentStream = await response.Content.ReadAsStreamAsync();
|
||||
using var fileStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
|
||||
var buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0)
|
||||
{
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
|
||||
receivedBytes += bytesRead;
|
||||
|
||||
if (totalBytes > 0)
|
||||
{
|
||||
float calculatedProgress = (float)receivedBytes / totalBytes;
|
||||
progress.Report(calculatedProgress);
|
||||
}
|
||||
}
|
||||
|
||||
progress.Report(1f);
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces.PluginManagement;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Plugin;
|
||||
|
||||
namespace DiscordBotCore.Online;
|
||||
|
||||
public interface IPluginManager
|
||||
{
|
||||
Task<List<OnlinePlugin>> GetPluginsList();
|
||||
Task<OnlinePlugin?> GetPluginDataByName(string pluginName);
|
||||
Task AppendPluginToDatabase(PluginInfo pluginData);
|
||||
Task<List<PluginInfo>> GetInstalledPlugins();
|
||||
Task<bool> IsPluginInstalled(string pluginName);
|
||||
Task<bool> MarkPluginToUninstall(string pluginName);
|
||||
Task UninstallMarkedPlugins();
|
||||
Task<string?> GetDependencyLocation(string dependencyName);
|
||||
Task<string?> GetDependencyLocation(string dependencyName, string pluginName);
|
||||
string GenerateDependencyRelativePath(string pluginName, string dependencyPath);
|
||||
Task InstallPluginNoProgress(OnlinePlugin plugin);
|
||||
Task<Tuple<Dictionary<string, string>, List<OnlineDependencyInfo>>> GatherInstallDataForPlugin(OnlinePlugin plugin);
|
||||
Task SetEnabledStatus(string pluginName, bool status);
|
||||
}
|
||||
|
||||
public sealed class PluginManager : IPluginManager
|
||||
{
|
||||
private static readonly string _LibrariesBaseFolder = "Libraries";
|
||||
private readonly IPluginRepository _PluginRepository;
|
||||
internal InstallingPluginInformation? InstallingPluginInformation { get; private set; }
|
||||
|
||||
internal PluginManager(IPluginRepository pluginRepository)
|
||||
{
|
||||
_PluginRepository = pluginRepository;
|
||||
}
|
||||
|
||||
public async Task<List<OnlinePlugin>> GetPluginsList()
|
||||
{
|
||||
var onlinePlugins = await _PluginRepository.GetAllPlugins();
|
||||
|
||||
if (!onlinePlugins.Any())
|
||||
{
|
||||
Application.Log("Could not get any plugins from the repository", LogType.Warning);
|
||||
return [];
|
||||
}
|
||||
|
||||
int os = OS.GetOperatingSystemInt();
|
||||
|
||||
var response = onlinePlugins.Where(plugin => plugin.OperatingSystem == os).ToList();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<OnlinePlugin?> GetPluginDataByName(string pluginName)
|
||||
{
|
||||
var plugin = await _PluginRepository.GetPluginByName(pluginName);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
Application.Log("Failed to get plugin from the repository", LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private async Task RemovePluginFromDatabase(string pluginName)
|
||||
{
|
||||
List<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(await File.ReadAllTextAsync(Application.CurrentApplication.PluginDatabase));
|
||||
|
||||
installedPlugins.RemoveAll(p => p.PluginName == pluginName);
|
||||
await JsonManager.SaveToJsonFile(Application.CurrentApplication.PluginDatabase, installedPlugins);
|
||||
}
|
||||
|
||||
public async Task AppendPluginToDatabase(PluginInfo pluginData)
|
||||
{
|
||||
List<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(await File.ReadAllTextAsync(Application.CurrentApplication.PluginDatabase));
|
||||
foreach (var dependency in pluginData.ListOfExecutableDependencies)
|
||||
{
|
||||
pluginData.ListOfExecutableDependencies[dependency.Key] = dependency.Value;
|
||||
}
|
||||
|
||||
installedPlugins.Add(pluginData);
|
||||
await JsonManager.SaveToJsonFile(Application.CurrentApplication.PluginDatabase, installedPlugins);
|
||||
}
|
||||
|
||||
public async Task<List<PluginInfo>> GetInstalledPlugins()
|
||||
{
|
||||
return await JsonManager.ConvertFromJson<List<PluginInfo>>(await File.ReadAllTextAsync(Application.CurrentApplication.PluginDatabase));
|
||||
}
|
||||
|
||||
public async Task<bool> IsPluginInstalled(string pluginName)
|
||||
{
|
||||
List<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(await File.ReadAllTextAsync(Application.CurrentApplication.PluginDatabase));
|
||||
|
||||
return installedPlugins.Any(plugin => plugin.PluginName == pluginName);
|
||||
}
|
||||
|
||||
public async Task<bool> MarkPluginToUninstall(string pluginName)
|
||||
{
|
||||
List<PluginInfo> installedPlugins = await GetInstalledPlugins();
|
||||
List<PluginInfo> info = installedPlugins.Where(info => info.PluginName == pluginName).ToList();
|
||||
|
||||
if (!info.Any())
|
||||
return false;
|
||||
|
||||
foreach (var item in info)
|
||||
{
|
||||
await RemovePluginFromDatabase(item.PluginName);
|
||||
item.IsMarkedToUninstall = true;
|
||||
await AppendPluginToDatabase(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task UninstallMarkedPlugins()
|
||||
{
|
||||
IEnumerable<PluginInfo> installedPlugins = (await GetInstalledPlugins()).AsEnumerable();
|
||||
IEnumerable<PluginInfo> pluginsToRemove = installedPlugins.Where(plugin => plugin.IsMarkedToUninstall).AsEnumerable();
|
||||
|
||||
foreach (var plugin in pluginsToRemove)
|
||||
{
|
||||
await UninstallPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UninstallPlugin(PluginInfo pluginInfo)
|
||||
{
|
||||
File.Delete(pluginInfo.FilePath);
|
||||
|
||||
foreach (var dependency in pluginInfo.ListOfExecutableDependencies)
|
||||
File.Delete(dependency.Value);
|
||||
|
||||
await RemovePluginFromDatabase(pluginInfo.PluginName);
|
||||
|
||||
if (Directory.Exists($"{_LibrariesBaseFolder}/{pluginInfo.PluginName}"))
|
||||
Directory.Delete($"{_LibrariesBaseFolder}/{pluginInfo.PluginName}", true);
|
||||
}
|
||||
|
||||
public async Task<string?> GetDependencyLocation(string dependencyName)
|
||||
{
|
||||
List<PluginInfo> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
foreach (var plugin in installedPlugins)
|
||||
{
|
||||
if (plugin.ListOfExecutableDependencies.TryGetValue(dependencyName, out var dependencyPath))
|
||||
{
|
||||
string relativePath = GenerateDependencyRelativePath(plugin.PluginName, dependencyPath);
|
||||
return relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<string?> GetDependencyLocation(string dependencyName, string pluginName)
|
||||
{
|
||||
List<PluginInfo> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
foreach (var plugin in installedPlugins)
|
||||
{
|
||||
if (plugin.PluginName == pluginName && plugin.ListOfExecutableDependencies.ContainsKey(dependencyName))
|
||||
{
|
||||
string dependencyPath = plugin.ListOfExecutableDependencies[dependencyName];
|
||||
string relativePath = GenerateDependencyRelativePath(pluginName, dependencyPath);
|
||||
return relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GenerateDependencyRelativePath(string pluginName, string dependencyPath)
|
||||
{
|
||||
string relative = $"./{_LibrariesBaseFolder}/{pluginName}/{dependencyPath}";
|
||||
return relative;
|
||||
}
|
||||
|
||||
public async Task InstallPluginNoProgress(OnlinePlugin plugin)
|
||||
{
|
||||
InstallingPluginInformation = new InstallingPluginInformation() { PluginName = plugin.PluginName };
|
||||
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.PluginId);
|
||||
|
||||
int totalSteps = dependencies.Count + 1;
|
||||
float stepFraction = 100f / totalSteps;
|
||||
float currentProgress = 0f;
|
||||
|
||||
InstallingPluginInformation.IsInstalling = true;
|
||||
var progress = currentProgress;
|
||||
IProgress<float> downloadProgress = new Progress<float>(fileProgress =>
|
||||
{
|
||||
InstallingPluginInformation.InstallationProgress = progress + (fileProgress / 100f) * stepFraction;
|
||||
});
|
||||
|
||||
await ServerCom.DownloadFileAsync(plugin.PluginLink,
|
||||
$"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{plugin.PluginName}.dll",
|
||||
downloadProgress);
|
||||
|
||||
currentProgress += stepFraction;
|
||||
|
||||
if (dependencies.Count > 0)
|
||||
{
|
||||
foreach (var dependency in dependencies)
|
||||
{
|
||||
string dependencyLocation = GenerateDependencyRelativePath(plugin.PluginName, dependency.DownloadLocation);
|
||||
await ServerCom.DownloadFileAsync(dependency.DownloadLink, dependencyLocation, downloadProgress);
|
||||
currentProgress += stepFraction;
|
||||
}
|
||||
}
|
||||
|
||||
PluginInfo pluginInfo = PluginInfo.FromOnlineInfo(plugin, dependencies);
|
||||
await AppendPluginToDatabase(pluginInfo);
|
||||
InstallingPluginInformation.IsInstalling = false;
|
||||
}
|
||||
|
||||
public async Task<Tuple<Dictionary<string, string>, List<OnlineDependencyInfo>>> GatherInstallDataForPlugin(OnlinePlugin plugin)
|
||||
{
|
||||
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.PluginId);
|
||||
var downloads = new Dictionary<string, string> { { $"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{plugin.PluginName}.dll", plugin.PluginLink } };
|
||||
foreach(var dependency in dependencies)
|
||||
{
|
||||
string dependencyLocation = GenerateDependencyRelativePath(plugin.PluginName, dependency.DownloadLocation);
|
||||
downloads.Add(dependencyLocation, dependency.DownloadLink);
|
||||
}
|
||||
|
||||
return (downloads, dependencies).ToTuple();
|
||||
}
|
||||
|
||||
public async Task SetEnabledStatus(string pluginName, bool status)
|
||||
{
|
||||
var plugins = await GetInstalledPlugins();
|
||||
var plugin = plugins.Find(p => p.PluginName == pluginName);
|
||||
|
||||
if (plugin == null)
|
||||
return;
|
||||
|
||||
plugin.IsEnabled = status;
|
||||
|
||||
await RemovePluginFromDatabase(pluginName);
|
||||
await AppendPluginToDatabase(plugin);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Online.Helpers;
|
||||
|
||||
namespace DiscordBotCore.Online;
|
||||
|
||||
public static class ServerCom
|
||||
{
|
||||
private static async Task DownloadFileAsync(
|
||||
string URL, string location, IProgress<float>? progress,
|
||||
IProgress<long>? downloadedBytes)
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.Timeout = TimeSpan.FromMinutes(5);
|
||||
if(Directory.Exists(Path.GetDirectoryName(location)) == false)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(location));
|
||||
}
|
||||
using (var file = new FileStream(location, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await client.DownloadFileAsync(URL, file, progress, downloadedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task DownloadFileAsync(string url, string location, IProgress<float> progress)
|
||||
{
|
||||
await DownloadFileAsync(url, location, progress, null);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DiscordBotCore.Others.Actions
|
||||
{
|
||||
public class InternalActionOption
|
||||
{
|
||||
public string OptionName { get; set; }
|
||||
public string OptionDescription { get; set; }
|
||||
|
||||
public List<InternalActionOption> SubOptions { get; set; }
|
||||
|
||||
public InternalActionOption(string optionName, string optionDescription, List<InternalActionOption> subOptions)
|
||||
{
|
||||
OptionName = optionName;
|
||||
OptionDescription = optionDescription;
|
||||
SubOptions = subOptions;
|
||||
}
|
||||
|
||||
public InternalActionOption(string optionName, string optionDescription)
|
||||
{
|
||||
OptionName = optionName;
|
||||
OptionDescription = optionDescription;
|
||||
SubOptions = new List<InternalActionOption>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Loaders;
|
||||
|
||||
namespace DiscordBotCore.Others.Actions;
|
||||
|
||||
public class InternalActionManager
|
||||
{
|
||||
private Dictionary<string, ICommandAction> Actions = new();
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
Actions.Clear();
|
||||
|
||||
PluginLoader.Actions.ForEach(action =>
|
||||
{
|
||||
if (action.RunType == InternalActionRunType.OnCall || action.RunType == InternalActionRunType.OnStartupAndCall)
|
||||
{
|
||||
if (this.Actions.ContainsKey(action.ActionName))
|
||||
{
|
||||
// This should never happen. If it does, log it and return
|
||||
Application.CurrentApplication.Logger.Log($"Action {action.ActionName} already exists", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.Actions.Add(action.ActionName, action);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ICommandAction> GetActions()
|
||||
{
|
||||
return Actions.Values;
|
||||
}
|
||||
|
||||
public bool Exists(string actionName)
|
||||
{
|
||||
return Actions.ContainsKey(actionName);
|
||||
}
|
||||
|
||||
public ICommandAction GetAction(string actionName)
|
||||
{
|
||||
return Actions[actionName];
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(string actionName, params string[]? args)
|
||||
{
|
||||
if (!Actions.ContainsKey(actionName))
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Action {actionName} not found", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Actions[actionName].RunType == InternalActionRunType.OnStartup)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Action {actionName} is not executable", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
await StartAction(Actions[actionName], args);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.CurrentApplication.Logger.LogException(e, this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAction(ICommandAction action, params string[]? args)
|
||||
{
|
||||
if (action.RequireOtherThread)
|
||||
{
|
||||
async void Start() => await action.Execute(args);
|
||||
|
||||
Thread thread = new(Start);
|
||||
thread.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
await action.Execute(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
|
||||
namespace DiscordBotCore.Others;
|
||||
|
||||
public class DbCommandExecutingArguments
|
||||
{
|
||||
|
||||
public SocketCommandContext Context { get; init; }
|
||||
public string CleanContent { get; init; }
|
||||
public string CommandUsed { get; init; }
|
||||
public string[]? Arguments { get; init; }
|
||||
public ISocketMessageChannel Channel => Context.Channel;
|
||||
|
||||
public DbCommandExecutingArguments(
|
||||
SocketCommandContext context, string cleanContent, string commandUsed, string[]? arguments)
|
||||
{
|
||||
this.Context = context;
|
||||
this.CleanContent = cleanContent;
|
||||
this.CommandUsed = commandUsed;
|
||||
this.Arguments = arguments;
|
||||
}
|
||||
|
||||
public DbCommandExecutingArguments(SocketUserMessage? message, DiscordSocketClient client)
|
||||
{
|
||||
Context = new SocketCommandContext(client, message);
|
||||
var pos = 0;
|
||||
if (message.HasMentionPrefix(client.CurrentUser, ref pos))
|
||||
{
|
||||
var mentionPrefix = "<@" + client.CurrentUser.Id + ">";
|
||||
CleanContent = message.Content.Substring(mentionPrefix.Length + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
string? prefix = Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("prefix");
|
||||
CleanContent = message.Content.Substring(prefix?.Length ?? 0);
|
||||
}
|
||||
|
||||
var split = CleanContent.Split(' ');
|
||||
|
||||
string[]? argsClean = null;
|
||||
if (split.Length > 1)
|
||||
argsClean = string.Join(' ', split, 1, split.Length - 1).Split(' ');
|
||||
|
||||
CommandUsed = split[0];
|
||||
Arguments = argsClean;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace DiscordBotCore.Others.Permissions;
|
||||
namespace DiscordBotCore.Others;
|
||||
|
||||
/// <summary>
|
||||
/// A class whith all discord permissions
|
||||
/// </summary>
|
||||
public static class DiscordPermissions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -15,7 +12,7 @@ public static class DiscordPermissions
|
||||
/// <param name="role">The role</param>
|
||||
/// <param name="permission">The permission</param>
|
||||
/// <returns></returns>
|
||||
public static bool hasPermission(this IRole role, GuildPermission permission)
|
||||
public static bool HasPermission(this IRole role, GuildPermission permission)
|
||||
{
|
||||
return role.Permissions.Has(permission);
|
||||
}
|
||||
@@ -30,7 +27,6 @@ public static class DiscordPermissions
|
||||
{
|
||||
return user.Roles.Contains(role);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if user has the specified permission
|
||||
/// </summary>
|
||||
@@ -39,7 +35,7 @@ public static class DiscordPermissions
|
||||
/// <returns></returns>
|
||||
public static bool HasPermission(this SocketGuildUser user, GuildPermission permission)
|
||||
{
|
||||
return user.Roles.Where(role => role.hasPermission(permission)).Any() || user.Guild.Owner == user;
|
||||
return user.Roles.Any(role => role.HasPermission(permission)) || user.Guild.Owner == user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,4 +57,4 @@ public static class DiscordPermissions
|
||||
{
|
||||
return IsAdmin((SocketGuildUser)user);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace DiscordBotCore.Others;
|
||||
|
||||
/// <summary>
|
||||
/// The output log type. This must be used by other loggers in order to provide logging information
|
||||
/// </summary>
|
||||
public enum LogType
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Critical
|
||||
}
|
||||
|
||||
public enum UnzipProgressType
|
||||
{
|
||||
PERCENTAGE_FROM_NUMBER_OF_FILES,
|
||||
PERCENTAGE_FROM_TOTAL_SIZE
|
||||
}
|
||||
|
||||
public enum InternalActionRunType
|
||||
{
|
||||
OnStartup,
|
||||
OnCall,
|
||||
OnStartupAndCall
|
||||
}
|
||||
|
||||
public enum PluginType
|
||||
{
|
||||
UNKNOWN,
|
||||
COMMAND,
|
||||
EVENT,
|
||||
SLASH_COMMAND,
|
||||
ACTION
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace DiscordBotCore.Others.Exceptions;
|
||||
|
||||
public class DependencyNotFoundException : Exception
|
||||
{
|
||||
private string PluginName { get; set; }
|
||||
public DependencyNotFoundException(string message): base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DependencyNotFoundException(string message, string pluginName): base(message)
|
||||
{
|
||||
this.PluginName = pluginName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace DiscordBotCore.Others.Exceptions
|
||||
{
|
||||
public class PluginNotFoundException : Exception
|
||||
{
|
||||
public PluginNotFoundException(string pluginName) : base($"Plugin {pluginName} was not found") { }
|
||||
|
||||
public PluginNotFoundException(string pluginName, string url, string branch) :
|
||||
base ($"Plugin {pluginName} was not found on {url} (branch: {branch}") { }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace DiscordBotCore.Plugin;
|
||||
|
||||
public class InstallingPluginInformation
|
||||
{
|
||||
public bool IsInstalling { get; set; }
|
||||
public required string PluginName { get; set; }
|
||||
public float InstallationProgress { get; set; } = 0.0f;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using DiscordBotCore.Online.Helpers;
|
||||
|
||||
namespace DiscordBotCore.Plugin;
|
||||
|
||||
public class PluginInfo
|
||||
{
|
||||
public string PluginName { get; private set; }
|
||||
public string PluginVersion { get; private set; }
|
||||
public string FilePath { get; private set; }
|
||||
public Dictionary<string, string> ListOfExecutableDependencies {get; private set;}
|
||||
public bool IsMarkedToUninstall {get; internal set;}
|
||||
public bool IsOfflineAdded { get; internal set; }
|
||||
public bool IsEnabled { get; internal set; }
|
||||
|
||||
[JsonConstructor]
|
||||
public PluginInfo(string pluginName, string pluginVersion, Dictionary<string, string> listOfExecutableDependencies, bool isMarkedToUninstall, bool isOfflineAdded, bool isEnabled)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
PluginVersion = pluginVersion;
|
||||
ListOfExecutableDependencies = listOfExecutableDependencies;
|
||||
IsMarkedToUninstall = isMarkedToUninstall;
|
||||
FilePath = $"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{pluginName}.dll";
|
||||
IsOfflineAdded = isOfflineAdded;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
public PluginInfo(string pluginName, string pluginVersion, Dictionary<string, string> listOfExecutableDependencies)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
PluginVersion = pluginVersion;
|
||||
ListOfExecutableDependencies = listOfExecutableDependencies;
|
||||
IsMarkedToUninstall = false;
|
||||
FilePath = $"{Application.CurrentApplication.ApplicationEnvironmentVariables.Get<string>("PluginFolder")}/{pluginName}.dll";
|
||||
IsOfflineAdded = false;
|
||||
IsEnabled = true;
|
||||
}
|
||||
|
||||
public static PluginInfo FromOnlineInfo(OnlinePlugin plugin, List<OnlineDependencyInfo> dependencies)
|
||||
{
|
||||
PluginInfo pluginInfo = new PluginInfo(
|
||||
plugin.PluginName, plugin.LatestVersion,
|
||||
dependencies.Where(dependency => dependency.IsExecutable)
|
||||
.ToDictionary(dependency => dependency.DependencyName, dependency => dependency.DownloadLocation)
|
||||
);
|
||||
|
||||
return pluginInfo;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user