mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Lots more stuff
This commit is contained in:
@@ -7,7 +7,7 @@ using PreconditionResult = Discord.Commands.PreconditionResult;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public class CommandHandler : INService, IReadyExecutor
|
||||
public class CommandHandler : INService, IReadyExecutor, ICommandHandler
|
||||
{
|
||||
private const int GLOBAL_COMMANDS_COOLDOWN = 750;
|
||||
|
||||
|
@@ -1,78 +0,0 @@
|
||||
#nullable disable
|
||||
using LinqToDB.Common;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public class DbService
|
||||
{
|
||||
private readonly IBotCredsProvider _creds;
|
||||
|
||||
// these are props because creds can change at runtime
|
||||
private string DbType => _creds.GetCreds().Db.Type.ToLowerInvariant().Trim();
|
||||
private string ConnString => _creds.GetCreds().Db.ConnectionString;
|
||||
|
||||
public DbService(IBotCredsProvider creds)
|
||||
{
|
||||
LinqToDBForEFTools.Initialize();
|
||||
Configuration.Linq.DisableQueryCache = true;
|
||||
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public async Task SetupAsync()
|
||||
{
|
||||
var dbType = DbType;
|
||||
var connString = ConnString;
|
||||
|
||||
await using var context = CreateRawDbContext(dbType, connString);
|
||||
|
||||
// make sure sqlite db is in wal journal mode
|
||||
if (context is SqliteContext)
|
||||
{
|
||||
await context.Database.ExecuteSqlRawAsync("PRAGMA journal_mode=WAL");
|
||||
}
|
||||
|
||||
await context.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
private static NadekoContext CreateRawDbContext(string dbType, string connString)
|
||||
{
|
||||
switch (dbType)
|
||||
{
|
||||
case "postgresql":
|
||||
case "postgres":
|
||||
case "pgsql":
|
||||
return new PostgreSqlContext(connString);
|
||||
case "mysql":
|
||||
return new MysqlContext(connString);
|
||||
case "sqlite":
|
||||
return new SqliteContext(connString);
|
||||
default:
|
||||
throw new NotSupportedException($"The database provide type of '{dbType}' is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private NadekoContext GetDbContextInternal()
|
||||
{
|
||||
var dbType = DbType;
|
||||
var connString = ConnString;
|
||||
|
||||
var context = CreateRawDbContext(dbType, connString);
|
||||
if (context is SqliteContext)
|
||||
{
|
||||
var conn = context.Database.GetDbConnection();
|
||||
conn.Open();
|
||||
using var com = conn.CreateCommand();
|
||||
com.CommandText = "PRAGMA synchronous=OFF";
|
||||
com.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
public NadekoContext GetDbContext()
|
||||
=> GetDbContextInternal();
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface IBehaviorHandler
|
||||
{
|
||||
Task<bool> AddAsync(ICustomBehavior behavior);
|
||||
Task AddRangeAsync(IEnumerable<ICustomBehavior> behavior);
|
||||
Task<bool> RemoveAsync(ICustomBehavior behavior);
|
||||
Task RemoveRangeAsync(IEnumerable<ICustomBehavior> behs);
|
||||
|
||||
Task<bool> RunExecOnMessageAsync(SocketGuild guild, IUserMessage usrMsg);
|
||||
Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg);
|
||||
Task<bool> RunPreCommandAsync(ICommandContext context, CommandInfo cmd);
|
||||
ValueTask RunPostCommandAsync(ICommandContext ctx, string moduleName, CommandInfo cmd);
|
||||
Task RunOnNoCommandAsync(SocketGuild guild, IUserMessage usrMsg);
|
||||
void Initialize();
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface ICoordinator
|
||||
{
|
||||
bool RestartBot();
|
||||
void Die(bool graceful);
|
||||
bool RestartShard(int shardId);
|
||||
IList<ShardStatus> GetAllShardStatuses();
|
||||
int GetGuildCount();
|
||||
Task Reload();
|
||||
}
|
||||
|
||||
public class ShardStatus
|
||||
{
|
||||
public ConnectionState ConnectionState { get; set; }
|
||||
public DateTime LastUpdate { get; set; }
|
||||
public int ShardId { get; set; }
|
||||
public int GuildCount { get; set; }
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface ICustomBehavior
|
||||
: IExecOnMessage,
|
||||
IInputTransformer,
|
||||
IExecPreCommand,
|
||||
IExecNoCommand,
|
||||
IExecPostCommand
|
||||
{
|
||||
|
||||
}
|
@@ -1,81 +0,0 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.Configs;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface IEmbedBuilderService
|
||||
{
|
||||
IEmbedBuilder Create(ICommandContext ctx = null);
|
||||
IEmbedBuilder Create(EmbedBuilder eb);
|
||||
}
|
||||
|
||||
public class EmbedBuilderService : IEmbedBuilderService, INService
|
||||
{
|
||||
private readonly BotConfigService _botConfigService;
|
||||
|
||||
public EmbedBuilderService(BotConfigService botConfigService)
|
||||
=> _botConfigService = botConfigService;
|
||||
|
||||
public IEmbedBuilder Create(ICommandContext ctx = null)
|
||||
=> new DiscordEmbedBuilderWrapper(_botConfigService.Data);
|
||||
|
||||
public IEmbedBuilder Create(EmbedBuilder embed)
|
||||
=> new DiscordEmbedBuilderWrapper(_botConfigService.Data, embed);
|
||||
}
|
||||
|
||||
public sealed class DiscordEmbedBuilderWrapper : IEmbedBuilder
|
||||
{
|
||||
private readonly BotConfig _botConfig;
|
||||
private EmbedBuilder embed;
|
||||
|
||||
public DiscordEmbedBuilderWrapper(in BotConfig botConfig, EmbedBuilder embed = null)
|
||||
{
|
||||
_botConfig = botConfig;
|
||||
this.embed = embed ?? new EmbedBuilder();
|
||||
}
|
||||
|
||||
public IEmbedBuilder WithDescription(string desc)
|
||||
=> Wrap(embed.WithDescription(desc));
|
||||
|
||||
public IEmbedBuilder WithTitle(string title)
|
||||
=> Wrap(embed.WithTitle(title));
|
||||
|
||||
public IEmbedBuilder AddField(string title, object value, bool isInline = false)
|
||||
=> Wrap(embed.AddField(title, value, isInline));
|
||||
|
||||
public IEmbedBuilder WithFooter(string text, string iconUrl = null)
|
||||
=> Wrap(embed.WithFooter(text, iconUrl));
|
||||
|
||||
public IEmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null)
|
||||
=> Wrap(embed.WithAuthor(name, iconUrl, url));
|
||||
|
||||
public IEmbedBuilder WithUrl(string url)
|
||||
=> Wrap(embed.WithUrl(url));
|
||||
|
||||
public IEmbedBuilder WithImageUrl(string url)
|
||||
=> Wrap(embed.WithImageUrl(url));
|
||||
|
||||
public IEmbedBuilder WithThumbnailUrl(string url)
|
||||
=> Wrap(embed.WithThumbnailUrl(url));
|
||||
|
||||
public IEmbedBuilder WithColor(EmbedColor color)
|
||||
=> color switch
|
||||
{
|
||||
EmbedColor.Ok => Wrap(embed.WithColor(_botConfig.Color.Ok.ToDiscordColor())),
|
||||
EmbedColor.Pending => Wrap(embed.WithColor(_botConfig.Color.Pending.ToDiscordColor())),
|
||||
EmbedColor.Error => Wrap(embed.WithColor(_botConfig.Color.Error.ToDiscordColor())),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(color), "Unsupported EmbedColor type")
|
||||
};
|
||||
|
||||
public IEmbedBuilder WithDiscordColor(Color color)
|
||||
=> Wrap(embed.WithColor(color));
|
||||
|
||||
public Embed Build()
|
||||
=> embed.Build();
|
||||
|
||||
private IEmbedBuilder Wrap(EmbedBuilder eb)
|
||||
{
|
||||
embed = eb;
|
||||
return this;
|
||||
}
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface IGoogleApiService
|
||||
{
|
||||
IReadOnlyDictionary<string, string> Languages { get; }
|
||||
|
||||
Task<IEnumerable<string>> GetVideoLinksByKeywordAsync(string keywords, int count = 1);
|
||||
Task<IEnumerable<(string Name, string Id, string Url)>> GetVideoInfosByKeywordAsync(string keywords, int count = 1);
|
||||
Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1);
|
||||
Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1, string user = null);
|
||||
Task<IEnumerable<string>> GetPlaylistTracksAsync(string playlistId, int count = 50);
|
||||
Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds);
|
||||
Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage);
|
||||
|
||||
Task<string> ShortenUrl(string url);
|
||||
Task<string> ShortenUrl(Uri url);
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
#nullable disable
|
||||
using System.Globalization;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface ILocalization
|
||||
{
|
||||
CultureInfo DefaultCultureInfo { get; }
|
||||
ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; }
|
||||
|
||||
CultureInfo GetCultureInfo(IGuild guild);
|
||||
CultureInfo GetCultureInfo(ulong? guildId);
|
||||
void RemoveGuildCulture(IGuild guild);
|
||||
void RemoveGuildCulture(ulong guildId);
|
||||
void ResetDefaultCulture();
|
||||
void SetDefaultCulture(CultureInfo ci);
|
||||
void SetGuildCulture(IGuild guild, CultureInfo ci);
|
||||
void SetGuildCulture(ulong guildId, CultureInfo ci);
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// All services must implement this interface in order to be auto-discovered by the DI system
|
||||
/// </summary>
|
||||
public interface INService
|
||||
{
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface IStatsService
|
||||
{
|
||||
/// <summary>
|
||||
/// The author of the bot.
|
||||
/// </summary>
|
||||
string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of commands ran since startup.
|
||||
/// </summary>
|
||||
long CommandsRan { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Discord framework used by the bot.
|
||||
/// </summary>
|
||||
string Library { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of messages seen by the bot since startup.
|
||||
/// </summary>
|
||||
long MessageCounter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The rate of messages the bot sees every second.
|
||||
/// </summary>
|
||||
double MessagesPerSecond { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of text channels the bot can see.
|
||||
/// </summary>
|
||||
long TextChannels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of voice channels the bot can see.
|
||||
/// </summary>
|
||||
long VoiceChannels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets for how long the bot has been up since startup.
|
||||
/// </summary>
|
||||
TimeSpan GetUptime();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted string of how long the bot has been up since startup.
|
||||
/// </summary>
|
||||
/// <param name="separator">The formatting separator.</param>
|
||||
string GetUptimeString(string separator = ", ");
|
||||
|
||||
/// <summary>
|
||||
/// Gets total amount of private memory currently in use by the bot, in Megabytes.
|
||||
/// </summary>
|
||||
double GetPrivateMemoryMegabytes();
|
||||
}
|
@@ -6,13 +6,6 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface IBotCredsProvider
|
||||
{
|
||||
public void Reload();
|
||||
public IBotCredentials GetCreds();
|
||||
public void ModifyCredsFile(Action<Creds> func);
|
||||
}
|
||||
|
||||
public sealed class BotCredsProvider : IBotCredsProvider
|
||||
{
|
||||
private const string CREDS_FILE_NAME = "creds.yml";
|
||||
@@ -94,7 +87,7 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
{
|
||||
if (Environment.OSVersion.Platform == PlatformID.Unix)
|
||||
{
|
||||
_creds.RestartCommand = new()
|
||||
_creds.RestartCommand = new RestartConfig()
|
||||
{
|
||||
Args = "dotnet",
|
||||
Cmd = "NadekoBot.dll -- {0}"
|
||||
@@ -102,7 +95,7 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
}
|
||||
else
|
||||
{
|
||||
_creds.RestartCommand = new()
|
||||
_creds.RestartCommand = new RestartConfig()
|
||||
{
|
||||
Args = "NadekoBot.exe",
|
||||
Cmd = "{0}"
|
||||
@@ -122,7 +115,7 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
}
|
||||
}
|
||||
|
||||
public void ModifyCredsFile(Action<Creds> func)
|
||||
public void ModifyCredsFile(Action<IBotCredentials> func)
|
||||
{
|
||||
var ymlData = File.ReadAllText(CREDS_FILE_NAME);
|
||||
var creds = Yaml.Deserializer.Deserialize<Creds>(ymlData);
|
||||
@@ -163,8 +156,8 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
OsuApiKey = oldCreds.OsuApiKey,
|
||||
CleverbotApiKey = oldCreds.CleverbotApiKey,
|
||||
TotalShards = oldCreds.TotalShards <= 1 ? 1 : oldCreds.TotalShards,
|
||||
Patreon = new(oldCreds.PatreonAccessToken, null, null, oldCreds.PatreonCampaignId),
|
||||
Votes = new(oldCreds.VotesUrl, oldCreds.VotesToken, string.Empty, string.Empty),
|
||||
Patreon = new Creds.PatreonSettings(oldCreds.PatreonAccessToken, null, null, oldCreds.PatreonCampaignId),
|
||||
Votes = new Creds.VotesSettings(oldCreds.VotesUrl, oldCreds.VotesToken, string.Empty, string.Empty),
|
||||
BotListToken = oldCreds.BotListToken,
|
||||
RedisOptions = oldCreds.RedisOptions,
|
||||
LocationIqApiKey = oldCreds.LocationIqApiKey,
|
||||
|
@@ -1,66 +0,0 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.Configs;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Settings service for bot-wide configuration.
|
||||
/// </summary>
|
||||
public sealed class BotConfigService : ConfigServiceBase<BotConfig>
|
||||
{
|
||||
private const string FILE_PATH = "data/bot.yml";
|
||||
private static readonly TypedKey<BotConfig> _changeKey = new("config.bot.updated");
|
||||
public override string Name { get; } = "bot";
|
||||
|
||||
public BotConfigService(IConfigSeria serializer, IPubSub pubSub)
|
||||
: base(FILE_PATH, serializer, pubSub, _changeKey)
|
||||
{
|
||||
AddParsedProp("color.ok", bs => bs.Color.Ok, Rgba32.TryParseHex, ConfigPrinters.Color);
|
||||
AddParsedProp("color.error", bs => bs.Color.Error, Rgba32.TryParseHex, ConfigPrinters.Color);
|
||||
AddParsedProp("color.pending", bs => bs.Color.Pending, Rgba32.TryParseHex, ConfigPrinters.Color);
|
||||
AddParsedProp("help.text", bs => bs.HelpText, ConfigParsers.String, ConfigPrinters.ToString);
|
||||
AddParsedProp("help.dmtext", bs => bs.DmHelpText, ConfigParsers.String, ConfigPrinters.ToString);
|
||||
AddParsedProp("console.type", bs => bs.ConsoleOutputType, Enum.TryParse, ConfigPrinters.ToString);
|
||||
AddParsedProp("locale", bs => bs.DefaultLocale, ConfigParsers.Culture, ConfigPrinters.Culture);
|
||||
AddParsedProp("prefix", bs => bs.Prefix, ConfigParsers.String, ConfigPrinters.ToString);
|
||||
AddParsedProp("checkforupdates", bs => bs.CheckForUpdates, bool.TryParse, ConfigPrinters.ToString);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
private void Migrate()
|
||||
{
|
||||
if (data.Version < 2)
|
||||
ModifyConfig(c => c.Version = 2);
|
||||
|
||||
if (data.Version < 3)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 3;
|
||||
c.Blocked.Modules = c.Blocked.Modules?.Select(static x
|
||||
=> string.Equals(x,
|
||||
"ActualCustomReactions",
|
||||
StringComparison.InvariantCultureIgnoreCase)
|
||||
? "ACTUALEXPRESSIONS"
|
||||
: x)
|
||||
.Distinct()
|
||||
.ToHashSet();
|
||||
});
|
||||
}
|
||||
|
||||
if (data.Version < 4)
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 4;
|
||||
c.CheckForUpdates = true;
|
||||
});
|
||||
|
||||
if(data.Version < 5)
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 5;
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Searches;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System.Globalization;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Custom setting value parsers for types which don't have them by default
|
||||
/// </summary>
|
||||
public static class ConfigParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Default string parser. Passes input to output and returns true.
|
||||
/// </summary>
|
||||
public static bool String(string input, out string output)
|
||||
{
|
||||
output = input;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool Culture(string input, out CultureInfo output)
|
||||
{
|
||||
try
|
||||
{
|
||||
output = new(input);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool InsensitiveEnum<T>(string input, out T output)
|
||||
where T: struct
|
||||
=> Enum.TryParse(input, true, out output);
|
||||
}
|
||||
|
||||
public static class ConfigPrinters
|
||||
{
|
||||
public static string ToString<TAny>(TAny input)
|
||||
=> input.ToString();
|
||||
|
||||
public static string Culture(CultureInfo culture)
|
||||
=> culture.Name;
|
||||
|
||||
public static string Color(Rgba32 color)
|
||||
=> ((uint)((color.B << 0) | (color.G << 8) | (color.R << 16))).ToString("X6");
|
||||
}
|
@@ -1,201 +0,0 @@
|
||||
using NadekoBot.Common.Configs;
|
||||
using NadekoBot.Common.Yml;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Base service for all settings services
|
||||
/// </summary>
|
||||
/// <typeparam name="TSettings">Type of the settings</typeparam>
|
||||
public abstract class ConfigServiceBase<TSettings> : IConfigService
|
||||
where TSettings : ICloneable<TSettings>, new()
|
||||
{
|
||||
// FUTURE config arrays are not copied - they're not protected from mutations
|
||||
public TSettings Data
|
||||
=> data.Clone();
|
||||
|
||||
public abstract string Name { get; }
|
||||
protected readonly string _filePath;
|
||||
protected readonly IConfigSeria _serializer;
|
||||
protected readonly IPubSub _pubSub;
|
||||
private readonly TypedKey<TSettings> _changeKey;
|
||||
|
||||
protected TSettings data;
|
||||
|
||||
private readonly Dictionary<string, Func<TSettings, string, bool>> _propSetters = new();
|
||||
private readonly Dictionary<string, Func<object>> _propSelectors = new();
|
||||
private readonly Dictionary<string, Func<object, string>> _propPrinters = new();
|
||||
private readonly Dictionary<string, string?> _propComments = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initialized an instance of <see cref="ConfigServiceBase{TSettings}" />
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the file where the settings are serialized/deserialized to and from</param>
|
||||
/// <param name="serializer">Serializer which will be used</param>
|
||||
/// <param name="pubSub">Pubsub implementation for signaling when settings are updated</param>
|
||||
/// <param name="changeKey">Key used to signal changed event</param>
|
||||
protected ConfigServiceBase(
|
||||
string filePath,
|
||||
IConfigSeria serializer,
|
||||
IPubSub pubSub,
|
||||
TypedKey<TSettings> changeKey)
|
||||
{
|
||||
_filePath = filePath;
|
||||
_serializer = serializer;
|
||||
_pubSub = pubSub;
|
||||
_changeKey = changeKey;
|
||||
|
||||
data = new();
|
||||
Load();
|
||||
_pubSub.Sub(_changeKey, OnChangePublished);
|
||||
}
|
||||
|
||||
private void PublishChange()
|
||||
=> _pubSub.Pub(_changeKey, data);
|
||||
|
||||
private ValueTask OnChangePublished(TSettings newData)
|
||||
{
|
||||
data = newData;
|
||||
OnStateUpdate();
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads data from disk. If file doesn't exist, it will be created with default values
|
||||
/// </summary>
|
||||
protected void Load()
|
||||
{
|
||||
// if file is deleted, regenerate it with default values
|
||||
if (!File.Exists(_filePath))
|
||||
{
|
||||
data = new();
|
||||
Save();
|
||||
}
|
||||
|
||||
data = _serializer.Deserialize<TSettings>(File.ReadAllText(_filePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads new data and publishes the new state
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
Load();
|
||||
_pubSub.Pub(_changeKey, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Doesn't do anything by default. This method will be executed after
|
||||
/// <see cref="data" /> is reloaded from <see cref="_filePath" /> or new data is recieved
|
||||
/// from the publish event
|
||||
/// </summary>
|
||||
protected virtual void OnStateUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
var strData = _serializer.Serialize(data);
|
||||
File.WriteAllText(_filePath, strData);
|
||||
}
|
||||
|
||||
protected void AddParsedProp<TProp>(
|
||||
string key,
|
||||
Expression<Func<TSettings, TProp>> selector,
|
||||
SettingParser<TProp> parser,
|
||||
Func<TProp, string> printer,
|
||||
Func<TProp, bool>? checker = null)
|
||||
{
|
||||
checker ??= _ => true;
|
||||
key = key.ToLowerInvariant();
|
||||
_propPrinters[key] = obj => printer((TProp)obj);
|
||||
_propSelectors[key] = () => selector.Compile()(data)!;
|
||||
_propSetters[key] = Magic(selector, parser, checker);
|
||||
_propComments[key] = ((MemberExpression)selector.Body).Member.GetCustomAttribute<CommentAttribute>()?.Comment;
|
||||
}
|
||||
|
||||
private Func<TSettings, string, bool> Magic<TProp>(
|
||||
Expression<Func<TSettings, TProp>> selector,
|
||||
SettingParser<TProp> parser,
|
||||
Func<TProp, bool> checker)
|
||||
=> (target, input) =>
|
||||
{
|
||||
if (!parser(input, out var value))
|
||||
return false;
|
||||
|
||||
if (!checker(value))
|
||||
return false;
|
||||
|
||||
object targetObject = target;
|
||||
var expr = (MemberExpression)selector.Body;
|
||||
var prop = (PropertyInfo)expr.Member;
|
||||
|
||||
var expressions = new List<MemberExpression>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
expr = expr.Expression as MemberExpression;
|
||||
if (expr is null)
|
||||
break;
|
||||
|
||||
expressions.Add(expr);
|
||||
}
|
||||
|
||||
foreach (var memberExpression in expressions.AsEnumerable().Reverse())
|
||||
{
|
||||
var localProp = (PropertyInfo)memberExpression.Member;
|
||||
targetObject = localProp.GetValue(targetObject)!;
|
||||
}
|
||||
|
||||
prop.SetValue(targetObject, value, null);
|
||||
return true;
|
||||
};
|
||||
|
||||
public IReadOnlyList<string> GetSettableProps()
|
||||
=> _propSetters.Keys.ToList();
|
||||
|
||||
public string? GetSetting(string prop)
|
||||
{
|
||||
prop = prop.ToLowerInvariant();
|
||||
if (!_propSelectors.TryGetValue(prop, out var selector) || !_propPrinters.TryGetValue(prop, out var printer))
|
||||
return null;
|
||||
|
||||
return printer(selector());
|
||||
}
|
||||
|
||||
public string? GetComment(string prop)
|
||||
{
|
||||
if (_propComments.TryGetValue(prop, out var comment))
|
||||
return comment;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool SetProperty(TSettings target, string key, string value)
|
||||
=> _propSetters.TryGetValue(key.ToLowerInvariant(), out var magic) && magic(target, value);
|
||||
|
||||
public bool SetSetting(string prop, string newValue)
|
||||
{
|
||||
var success = true;
|
||||
ModifyConfig(bs =>
|
||||
{
|
||||
success = SetProperty(bs, prop, newValue);
|
||||
});
|
||||
|
||||
if (success)
|
||||
PublishChange();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public void ModifyConfig(Action<TSettings> action)
|
||||
{
|
||||
var copy = Data;
|
||||
action(copy);
|
||||
data = copy;
|
||||
Save();
|
||||
PublishChange();
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface IConfigMigrator
|
||||
{
|
||||
public void EnsureMigrated();
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interface that all services which deal with configs should implement
|
||||
/// </summary>
|
||||
public interface IConfigService
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the config
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads new data and publishes the new state
|
||||
/// </summary>
|
||||
void Reload();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of props you can set
|
||||
/// </summary>
|
||||
/// <returns>List of props</returns>
|
||||
IReadOnlyList<string> GetSettableProps();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified property
|
||||
/// </summary>
|
||||
/// <param name="prop">Prop name</param>
|
||||
/// <returns>Value of the prop</returns>
|
||||
string GetSetting(string prop);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified property
|
||||
/// </summary>
|
||||
/// <param name="prop">Prop name</param>
|
||||
/// <returns>Value of the prop</returns>
|
||||
string GetComment(string prop);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the specified property
|
||||
/// </summary>
|
||||
/// <param name="prop">Property to set</param>
|
||||
/// <param name="newValue">Value to set the property to</param>
|
||||
/// <returns>Success</returns>
|
||||
bool SetSetting(string prop, string newValue);
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate which describes a parser which can convert string input into given data type
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">Data type to convert string to</typeparam>
|
||||
public delegate bool SettingParser<TData>(string input, out TData output);
|
@@ -1,16 +0,0 @@
|
||||
#nullable disable
|
||||
using System.Globalization;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Defines methods to retrieve and reload bot strings
|
||||
/// </summary>
|
||||
public interface IBotStrings
|
||||
{
|
||||
string GetText(string key, ulong? guildId = null, params object[] data);
|
||||
string GetText(string key, CultureInfo locale, params object[] data);
|
||||
void Reload();
|
||||
CommandStrings GetCommandStrings(string commandName, ulong? guildId = null);
|
||||
CommandStrings GetCommandStrings(string commandName, CultureInfo cultureInfo);
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
#nullable disable
|
||||
using System.Globalization;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public static class BotStringsExtensions
|
||||
{
|
||||
// this one is for pipe fun, see PipeExtensions.cs
|
||||
public static string GetText(this IBotStrings strings, in LocStr str, in ulong guildId)
|
||||
=> strings.GetText(str.Key, guildId, str.Params);
|
||||
|
||||
public static string GetText(this IBotStrings strings, in LocStr str, ulong? guildId = null)
|
||||
=> strings.GetText(str.Key, guildId, str.Params);
|
||||
|
||||
public static string GetText(this IBotStrings strings, in LocStr str, CultureInfo culture)
|
||||
=> strings.GetText(str.Key, culture, str.Params);
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Implemented by classes which provide localized strings in their own ways
|
||||
/// </summary>
|
||||
public interface IBotStringsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets localized string
|
||||
/// </summary>
|
||||
/// <param name="localeName">Language name</param>
|
||||
/// <param name="key">String key</param>
|
||||
/// <returns>Localized string</returns>
|
||||
string GetText(string localeName, string key);
|
||||
|
||||
/// <summary>
|
||||
/// Reloads string cache
|
||||
/// </summary>
|
||||
void Reload();
|
||||
|
||||
/// <summary>
|
||||
/// Gets command arg examples and description
|
||||
/// </summary>
|
||||
/// <param name="localeName">Language name</param>
|
||||
/// <param name="commandName">Command name</param>
|
||||
CommandStrings GetCommandStrings(string localeName, string commandName);
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Basic interface used for classes implementing strings loading mechanism
|
||||
/// </summary>
|
||||
public interface IStringsSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all response strings
|
||||
/// </summary>
|
||||
/// <returns>Dictionary(localename, Dictionary(key, response))</returns>
|
||||
Dictionary<string, Dictionary<string, string>> GetResponseStrings();
|
||||
|
||||
Dictionary<string, Dictionary<string, CommandStrings>> GetCommandStrings();
|
||||
}
|
@@ -101,7 +101,9 @@ public class BotStrings : IBotStrings
|
||||
=> _stringsProvider.Reload();
|
||||
}
|
||||
|
||||
public class CommandStrings
|
||||
|
||||
public sealed class CommandStrings
|
||||
: ICommandStrings
|
||||
{
|
||||
[YamlMember(Alias = "desc")]
|
||||
public string Desc { get; set; }
|
||||
|
@@ -40,11 +40,11 @@ public class LocalFileStringsSource : IStringsSource
|
||||
return outputDict;
|
||||
}
|
||||
|
||||
public Dictionary<string, Dictionary<string, CommandStrings>> GetCommandStrings()
|
||||
public Dictionary<string, Dictionary<string, ICommandStrings>> GetCommandStrings()
|
||||
{
|
||||
var deserializer = new DeserializerBuilder().Build();
|
||||
|
||||
var outputDict = new Dictionary<string, Dictionary<string, CommandStrings>>();
|
||||
var outputDict = new Dictionary<string, Dictionary<string, CommandStrings>>().;
|
||||
foreach (var file in Directory.GetFiles(_commandsPath))
|
||||
{
|
||||
try
|
||||
|
@@ -27,7 +27,7 @@ public class MemoryBotStringsProvider : IBotStringsProvider
|
||||
commandStrings = _source.GetCommandStrings();
|
||||
}
|
||||
|
||||
public CommandStrings GetCommandStrings(string localeName, string commandName)
|
||||
public ICommandStrings GetCommandStrings(string localeName, string commandName)
|
||||
{
|
||||
if (commandStrings.TryGetValue(localeName, out var langStrings)
|
||||
&& langStrings.TryGetValue(commandName, out var strings))
|
||||
|
Reference in New Issue
Block a user