mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
* Replacement mechanism reworked
* Services can now register their own replacements * Possible bugs and/or backwards-incompatible behavior
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Nadeko.Bot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public interface ICurrencyProvider
|
public interface ICurrencyProvider
|
||||||
{
|
{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
using OneOf;
|
using OneOf;
|
||||||
using OneOf.Types;
|
using OneOf.Types;
|
||||||
|
|
||||||
namespace Nadeko.Bot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public interface IPermissionChecker
|
public interface IPermissionChecker
|
||||||
{
|
{
|
||||||
|
@@ -39,4 +39,5 @@
|
|||||||
<Link>responses.en-US.json</Link>
|
<Link>responses.en-US.json</Link>
|
||||||
</AdditionalFiles>
|
</AdditionalFiles>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using NadekoBot.Common;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
public ILocalization _localization { get; set; }
|
public ILocalization _localization { get; set; }
|
||||||
public IEmbedBuilderService _eb { get; set; }
|
public IEmbedBuilderService _eb { get; set; }
|
||||||
public INadekoInteractionService _inter { get; set; }
|
public INadekoInteractionService _inter { get; set; }
|
||||||
|
public IReplacementService repSvc { get; set; }
|
||||||
|
|
||||||
protected string prefix
|
protected string prefix
|
||||||
=> _cmdHandler.GetPrefix(ctx.Guild);
|
=> _cmdHandler.GetPrefix(ctx.Guild);
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface IReplacementPatternStore : INService
|
||||||
|
{
|
||||||
|
IReadOnlyDictionary<string, ReplacementInfo> Replacements { get; }
|
||||||
|
IReadOnlyDictionary<string, RegexReplacementInfo> RegexReplacements { get; }
|
||||||
|
|
||||||
|
ValueTask<Guid?> Register(string token, Func<ValueTask<string>> repFactory);
|
||||||
|
ValueTask<Guid?> Register<T1>(string token, Func<T1, ValueTask<string>> repFactory);
|
||||||
|
ValueTask<Guid?> Register<T1, T2>(string token, Func<T1, T2, ValueTask<string>> repFactory);
|
||||||
|
|
||||||
|
ValueTask<Guid?> Register(string token, Func<string> repFactory);
|
||||||
|
ValueTask<Guid?> Register<T1>(string token, Func<T1, string> repFactory);
|
||||||
|
ValueTask<Guid?> Register<T1, T2>(string token, Func<T1, T2, string> repFactory);
|
||||||
|
|
||||||
|
ValueTask<Guid?> Register(Regex regex, Func<Match, string> repFactory);
|
||||||
|
ValueTask<Guid?> Register<T1>(Regex regex, Func<Match, T1, string> repFactory);
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface IReplacementService
|
||||||
|
{
|
||||||
|
ValueTask<string?> ReplaceAsync(string input, ReplacementContext repCtx);
|
||||||
|
ValueTask<SmartText> ReplaceAsync(SmartText input, ReplacementContext repCtx);
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed class ReplacementContext
|
||||||
|
{
|
||||||
|
public DiscordSocketClient? Client { get; }
|
||||||
|
public IGuild? Guild { get; }
|
||||||
|
public IMessageChannel? Channel { get; }
|
||||||
|
public IUser[]? Users { get; }
|
||||||
|
|
||||||
|
private readonly List<ReplacementInfo> _overrides = new();
|
||||||
|
private readonly HashSet<string> _tokens = new();
|
||||||
|
|
||||||
|
public IReadOnlyList<ReplacementInfo> Overrides
|
||||||
|
=> _overrides.AsReadOnly();
|
||||||
|
|
||||||
|
private readonly List<RegexReplacementInfo> _regexOverrides = new();
|
||||||
|
private readonly HashSet<string> _regexPatterns = new();
|
||||||
|
|
||||||
|
public IReadOnlyList<RegexReplacementInfo> RegexOverrides
|
||||||
|
=> _regexOverrides.AsReadOnly();
|
||||||
|
|
||||||
|
public ReplacementContext(ICommandContext cmdContext) : this(cmdContext.Client as DiscordSocketClient,
|
||||||
|
cmdContext.Guild,
|
||||||
|
cmdContext.Channel,
|
||||||
|
cmdContext.User)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementContext(
|
||||||
|
DiscordSocketClient? client = null,
|
||||||
|
IGuild? guild = null,
|
||||||
|
IMessageChannel? channel = null,
|
||||||
|
params IUser[]? users)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
Guild = guild;
|
||||||
|
Channel = channel;
|
||||||
|
Users = users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementContext WithOverride(string key, Func<ValueTask<string>> repFactory)
|
||||||
|
{
|
||||||
|
if (_tokens.Add(key))
|
||||||
|
{
|
||||||
|
_overrides.Add(new(key, repFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementContext WithOverride(string key, Func<string> repFactory)
|
||||||
|
=> WithOverride(key, () => new ValueTask<string>(repFactory()));
|
||||||
|
|
||||||
|
|
||||||
|
public ReplacementContext WithOverride(Regex regex, Func<Match, ValueTask<string>> repFactory)
|
||||||
|
{
|
||||||
|
if (_regexPatterns.Add(regex.ToString()))
|
||||||
|
{
|
||||||
|
_regexOverrides.Add(new(regex, repFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementContext WithOverride(Regex regex, Func<Match, string> repFactory)
|
||||||
|
=> WithOverride(regex, (Match m) => new ValueTask<string>(repFactory(m)));
|
||||||
|
}
|
57
src/Nadeko.Bot.Common/Replacements/Impl/ReplacementInfo.cs
Normal file
57
src/Nadeko.Bot.Common/Replacements/Impl/ReplacementInfo.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed class ReplacementInfo
|
||||||
|
{
|
||||||
|
private readonly Delegate _del;
|
||||||
|
public IReadOnlyCollection<Type> InputTypes { get; }
|
||||||
|
public string Token { get; }
|
||||||
|
|
||||||
|
private static readonly Func<ValueTask<string?>> _falllbackFunc = static () => default;
|
||||||
|
|
||||||
|
public ReplacementInfo(string token, Delegate del)
|
||||||
|
{
|
||||||
|
_del = del;
|
||||||
|
InputTypes = del.GetMethodInfo().GetParameters().Select(x => x.ParameterType).ToArray().AsReadOnly();
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetValueAsync(params object?[]? objs)
|
||||||
|
=> await (ValueTask<string?>)(_del.DynamicInvoke(objs) ?? _falllbackFunc);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> Token.GetHashCode();
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
=> obj is ReplacementInfo ri && ri.Token == Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RegexReplacementInfo
|
||||||
|
{
|
||||||
|
private readonly Delegate _del;
|
||||||
|
public IReadOnlyCollection<Type> InputTypes { get; }
|
||||||
|
|
||||||
|
public Regex Regex { get; }
|
||||||
|
public string Pattern { get; }
|
||||||
|
|
||||||
|
private static readonly Func<Match, ValueTask<string?>> _falllbackFunc = static _ => default;
|
||||||
|
|
||||||
|
public RegexReplacementInfo(Regex regex, Delegate del)
|
||||||
|
{
|
||||||
|
_del = del;
|
||||||
|
InputTypes = del.GetMethodInfo().GetParameters().Select(x => x.ParameterType).ToArray().AsReadOnly();
|
||||||
|
Regex = regex;
|
||||||
|
Pattern = Regex.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetValueAsync(Match m, params object?[]? objs)
|
||||||
|
=> await ((Func<Match, ValueTask<string?>>)(_del.DynamicInvoke(objs) ?? _falllbackFunc))(m);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> Regex.GetHashCode();
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
=> obj is RegexReplacementInfo ri && ri.Pattern == Pattern;
|
||||||
|
}
|
@@ -0,0 +1,130 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using OneOf;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed partial class ReplacementPatternStore : IReplacementPatternStore, INService
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<Guid, OneOf<string, Regex>> _guids = new();
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, ReplacementInfo> _defaultReplacements = new();
|
||||||
|
private readonly ConcurrentDictionary<string, RegexReplacementInfo> _regexReplacements = new();
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, ReplacementInfo> Replacements
|
||||||
|
=> _defaultReplacements.AsReadOnly();
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, RegexReplacementInfo> RegexReplacements
|
||||||
|
=> _regexReplacements.AsReadOnly();
|
||||||
|
|
||||||
|
public ReplacementPatternStore()
|
||||||
|
{
|
||||||
|
WithClient();
|
||||||
|
WithChannel();
|
||||||
|
WithServer();
|
||||||
|
WithUsers();
|
||||||
|
WithDefault();
|
||||||
|
WithRegex();
|
||||||
|
}
|
||||||
|
|
||||||
|
// private async ValueTask<string> InternalReplace(string input, ReplacementContexta repCtx)
|
||||||
|
// {
|
||||||
|
// // multiple executions vs single execution per replacement
|
||||||
|
// var minIndex = -1;
|
||||||
|
// var index = -1;
|
||||||
|
// foreach (var rep in _replacements)
|
||||||
|
// {
|
||||||
|
// while ((index = input.IndexOf(rep.Key, StringComparison.InvariantCulture)) != -1 && index > minIndex)
|
||||||
|
// {
|
||||||
|
// var valueToInsert = await rep.Value(repCtx);
|
||||||
|
// input = input[..index] + valueToInsert +input[(index + rep.Key.Length)..];
|
||||||
|
// minIndex = (index + valueToInsert.Length);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return input;
|
||||||
|
// }
|
||||||
|
|
||||||
|
private ValueTask<Guid?> InternalRegister(string token, Delegate repFactory)
|
||||||
|
{
|
||||||
|
if (!token.StartsWith('%') || !token.EndsWith('%'))
|
||||||
|
{
|
||||||
|
Log.Warning(
|
||||||
|
"""
|
||||||
|
Invalid replacement token: {Token}
|
||||||
|
Tokens have to start and end with a '%', ex: %mytoken%
|
||||||
|
""",
|
||||||
|
token);
|
||||||
|
return new(default(Guid?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_defaultReplacements.TryAdd(token, new ReplacementInfo(token, repFactory)))
|
||||||
|
{
|
||||||
|
var guid = Guid.NewGuid();
|
||||||
|
_guids[guid] = token;
|
||||||
|
return new(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(default(Guid?));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<Guid?> Register(string token, Func<ValueTask<string>> repFactory)
|
||||||
|
=> InternalRegister(token, repFactory);
|
||||||
|
|
||||||
|
public ValueTask<Guid?> Register<T1>(string token, Func<T1, ValueTask<string>> repFactory)
|
||||||
|
=> InternalRegister(token, repFactory);
|
||||||
|
|
||||||
|
public ValueTask<Guid?> Register<T1, T2>(string token, Func<T1, T2, ValueTask<string>> repFactory)
|
||||||
|
=> InternalRegister(token, repFactory);
|
||||||
|
|
||||||
|
public ValueTask<Guid?> Register(string token, Func<string> repFactory)
|
||||||
|
=> InternalRegister(token, () => new ValueTask<string>(repFactory()));
|
||||||
|
|
||||||
|
public ValueTask<Guid?> Register<T1>(string token, Func<T1, string> repFactory)
|
||||||
|
=> InternalRegister(token, (T1 a) => new ValueTask<string>(repFactory(a)));
|
||||||
|
|
||||||
|
public ValueTask<Guid?> Register<T1, T2>(string token, Func<T1, T2, string> repFactory)
|
||||||
|
=> InternalRegister(token, (T1 a, T2 b) => new ValueTask<string>(repFactory(a, b)));
|
||||||
|
|
||||||
|
|
||||||
|
private ValueTask<Guid?> InternalRegexRegister(Regex regex, Delegate repFactory)
|
||||||
|
{
|
||||||
|
var regexPattern = regex.ToString();
|
||||||
|
if (!regexPattern.StartsWith('%') || !regexPattern.EndsWith('%'))
|
||||||
|
{
|
||||||
|
Log.Warning(
|
||||||
|
"""
|
||||||
|
Invalid replacement pattern: {Token}
|
||||||
|
Tokens have to start and end with a '%', ex: %mytoken%
|
||||||
|
""",
|
||||||
|
regex);
|
||||||
|
return new(default(Guid?));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_regexReplacements.TryAdd(regexPattern, new RegexReplacementInfo(regex, repFactory)))
|
||||||
|
{
|
||||||
|
var guid = Guid.NewGuid();
|
||||||
|
_guids[guid] = regex;
|
||||||
|
return new(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(default(Guid?));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<Guid?> Register(Regex regex, Func<Match, string> repFactory)
|
||||||
|
=> InternalRegexRegister(regex, () => (Match m) => new ValueTask<string>(repFactory(m)));
|
||||||
|
|
||||||
|
public ValueTask<Guid?> Register<T1>(Regex regex, Func<Match, T1, string> repFactory)
|
||||||
|
=> InternalRegexRegister(regex, (T1 a) => (Match m) => new ValueTask<string>(repFactory(m, a)));
|
||||||
|
|
||||||
|
public bool Unregister(Guid guid)
|
||||||
|
{
|
||||||
|
if (_guids.TryRemove(guid, out var pattern))
|
||||||
|
{
|
||||||
|
return pattern.Match(
|
||||||
|
token => _defaultReplacements.TryRemove(token, out _),
|
||||||
|
regex => _regexReplacements.TryRemove(regex.ToString(), out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,113 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed partial class ReplacementPatternStore
|
||||||
|
{
|
||||||
|
private static readonly Regex _rngRegex = new(@"%rng(?:(?<from>(?:-)?\d+)-(?<to>(?:-)?\d+))?%",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
|
||||||
|
private void WithDefault()
|
||||||
|
{
|
||||||
|
Register("%bot.time%",
|
||||||
|
static ()
|
||||||
|
=> DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithClient()
|
||||||
|
{
|
||||||
|
Register("%bot.status%", static (DiscordSocketClient client) => client.Status.ToString());
|
||||||
|
Register("%bot.latency%", static (DiscordSocketClient client) => client.Latency.ToString());
|
||||||
|
Register("%bot.name%", static (DiscordSocketClient client) => client.CurrentUser.Username);
|
||||||
|
Register("%bot.fullname%", static (DiscordSocketClient client) => client.CurrentUser.ToString());
|
||||||
|
Register("%bot.discrim%", static (DiscordSocketClient client) => client.CurrentUser.Discriminator);
|
||||||
|
Register("%bot.id%", static (DiscordSocketClient client) => client.CurrentUser.Id.ToString());
|
||||||
|
Register("%bot.avatar%",
|
||||||
|
static (DiscordSocketClient client) => client.CurrentUser.RealAvatarUrl().ToString());
|
||||||
|
|
||||||
|
Register("%bot.mention%", static (DiscordSocketClient client) => client.CurrentUser.Mention);
|
||||||
|
|
||||||
|
Register("%shard.servercount%", static (DiscordSocketClient c) => c.Guilds.Count.ToString());
|
||||||
|
Register("%shard.usercount%",
|
||||||
|
static (DiscordSocketClient c) => c.Guilds.Sum(g => g.MemberCount).ToString());
|
||||||
|
Register("%shard.id%", static (DiscordSocketClient c) => c.ShardId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithServer()
|
||||||
|
{
|
||||||
|
Register("%server%", static (IGuild g) => g.Name);
|
||||||
|
Register("%server.id%", static (IGuild g) => g.Id.ToString());
|
||||||
|
Register("%server.name%", static (IGuild g) => g.Name);
|
||||||
|
Register("%server.icon%", static (IGuild g) => g.IconUrl);
|
||||||
|
Register("%server.members%", static (IGuild g) => g.ApproximateMemberCount?.ToString() ?? "?");
|
||||||
|
Register("%server.boosters%", static (IGuild g) => g.PremiumSubscriptionCount.ToString());
|
||||||
|
Register("%server.boost_level%", static (IGuild g) => ((int)g.PremiumTier).ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithChannel()
|
||||||
|
{
|
||||||
|
Register("%channel%", static (IMessageChannel ch) => ch.Name);
|
||||||
|
Register("%channel.mention%",
|
||||||
|
static (IMessageChannel ch) => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
|
||||||
|
Register("%channel.name%", static (IMessageChannel ch) => ch.Name);
|
||||||
|
Register("%channel.id%", static (IMessageChannel ch) => ch.Id.ToString());
|
||||||
|
Register("%channel.created%",
|
||||||
|
static (IMessageChannel ch) => ch.CreatedAt.ToString("HH:mm dd.MM.yyyy"));
|
||||||
|
Register("%channel.nsfw%",
|
||||||
|
static (IMessageChannel ch) => (ch as ITextChannel)?.IsNsfw.ToString() ?? "-");
|
||||||
|
Register("%channel.topic%", static (IMessageChannel ch) => (ch as ITextChannel)?.Topic ?? "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithUsers()
|
||||||
|
{
|
||||||
|
Register("%user%",
|
||||||
|
static (IUser[] users) => string.Join(" ", users.Select(user => user.Mention)));
|
||||||
|
Register("%user.mention%",
|
||||||
|
static (IUser[] users) => string.Join(" ", users.Select(user => user.Mention)));
|
||||||
|
Register("%user.fullname%",
|
||||||
|
static (IUser[] users) => string.Join(" ", users.Select(user => user.ToString())));
|
||||||
|
Register("%user.name%",
|
||||||
|
static (IUser[] users) => string.Join(" ", users.Select(user => user.Username)));
|
||||||
|
Register("%user.discrim%",
|
||||||
|
static (IUser[] users) => string.Join(" ", users.Select(user => user.Discriminator)));
|
||||||
|
Register("%user.avatar%",
|
||||||
|
static (IUser[] users)
|
||||||
|
=> string.Join(" ", users.Select(user => user.RealAvatarUrl().ToString())));
|
||||||
|
Register("%user.id%",
|
||||||
|
static (IUser[] users) => string.Join(" ", users.Select(user => user.Id.ToString())));
|
||||||
|
Register("%user.created_time%",
|
||||||
|
static (IUser[] users)
|
||||||
|
=> string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))));
|
||||||
|
Register("%user.created_date%",
|
||||||
|
static (IUser[] users)
|
||||||
|
=> string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))));
|
||||||
|
Register("%user.joined_time%",
|
||||||
|
static (IUser[] users) => string.Join(" ",
|
||||||
|
users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")));
|
||||||
|
Register("%user.joined_date%",
|
||||||
|
static (IUser[] users) => string.Join(" ",
|
||||||
|
users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithRegex()
|
||||||
|
{
|
||||||
|
Register(_rngRegex,
|
||||||
|
match =>
|
||||||
|
{
|
||||||
|
var rng = new NadekoRandom();
|
||||||
|
if (!int.TryParse(match.Groups["from"].ToString(), out var from))
|
||||||
|
from = 0;
|
||||||
|
if (!int.TryParse(match.Groups["to"].ToString(), out var to))
|
||||||
|
to = 0;
|
||||||
|
|
||||||
|
if (from == 0 && to == 0)
|
||||||
|
return rng.Next(0, 11).ToString();
|
||||||
|
|
||||||
|
if (from >= to)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return rng.Next(from, to + 1).ToString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
137
src/Nadeko.Bot.Common/Replacements/Impl/ReplacementService.cs
Normal file
137
src/Nadeko.Bot.Common/Replacements/Impl/ReplacementService.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed class ReplacementService : IReplacementService, INService
|
||||||
|
{
|
||||||
|
private readonly IReplacementPatternStore _repReg;
|
||||||
|
|
||||||
|
public ReplacementService(IReplacementPatternStore repReg)
|
||||||
|
{
|
||||||
|
_repReg = repReg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<SmartText> ReplaceAsync(SmartText input, ReplacementContext repCtx)
|
||||||
|
{
|
||||||
|
var reps = GetReplacementsForContext(repCtx);
|
||||||
|
var regReps = GetRegexReplacementsForContext(repCtx);
|
||||||
|
|
||||||
|
var inputData = GetInputData(repCtx);
|
||||||
|
var rep = new Replacer(reps.Values, regReps.Values, inputData);
|
||||||
|
|
||||||
|
return await rep.ReplaceAsync(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<string?> ReplaceAsync(string input, ReplacementContext repCtx)
|
||||||
|
{
|
||||||
|
var reps = GetReplacementsForContext(repCtx);
|
||||||
|
var regReps = GetRegexReplacementsForContext(repCtx);
|
||||||
|
|
||||||
|
var inputData = GetInputData(repCtx);
|
||||||
|
var rep = new Replacer(reps.Values, regReps.Values, inputData);
|
||||||
|
|
||||||
|
return await rep.ReplaceAsync(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object[] GetInputData(ReplacementContext repCtx)
|
||||||
|
{
|
||||||
|
var obj = new List<object>();
|
||||||
|
if (repCtx.Client is not null)
|
||||||
|
obj.Add(repCtx.Client);
|
||||||
|
|
||||||
|
if (repCtx.Guild is not null)
|
||||||
|
obj.Add(repCtx.Guild);
|
||||||
|
|
||||||
|
if (repCtx.Users is not null)
|
||||||
|
obj.Add(repCtx.Users);
|
||||||
|
|
||||||
|
if (repCtx.Channel is not null)
|
||||||
|
obj.Add(repCtx.Channel);
|
||||||
|
|
||||||
|
return obj.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<string, ReplacementInfo> GetReplacementsForContext(ReplacementContext repCtx)
|
||||||
|
{
|
||||||
|
var reps = GetOriginalReplacementsForContext(repCtx);
|
||||||
|
foreach (var ovrd in repCtx.Overrides)
|
||||||
|
{
|
||||||
|
reps.Remove(ovrd.Token);
|
||||||
|
reps.TryAdd(ovrd.Token, ovrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<string, RegexReplacementInfo> GetRegexReplacementsForContext(ReplacementContext repCtx)
|
||||||
|
{
|
||||||
|
var reps = GetOriginalRegexReplacementsForContext(repCtx);
|
||||||
|
foreach (var ovrd in repCtx.RegexOverrides)
|
||||||
|
{
|
||||||
|
reps.Remove(ovrd.Pattern);
|
||||||
|
reps.TryAdd(ovrd.Pattern, ovrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<string, ReplacementInfo> GetOriginalReplacementsForContext(ReplacementContext repCtx)
|
||||||
|
{
|
||||||
|
var objs = new List<object>();
|
||||||
|
if (repCtx.Client is not null)
|
||||||
|
{
|
||||||
|
objs.Add(repCtx.Client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repCtx.Channel is not null)
|
||||||
|
{
|
||||||
|
objs.Add(repCtx.Channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repCtx.Users is not null)
|
||||||
|
{
|
||||||
|
objs.Add(repCtx.Users);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repCtx.Guild is not null)
|
||||||
|
{
|
||||||
|
objs.Add(repCtx.Guild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var types = objs.Map(x => x.GetType()).OrderBy(x => x.Name).ToHashSet();
|
||||||
|
|
||||||
|
return _repReg.Replacements
|
||||||
|
.Values
|
||||||
|
.Where(rep => rep.InputTypes.All(t => types.Any(x => x.IsAssignableTo((t)))))
|
||||||
|
.ToDictionary(rep => rep.Token, rep => rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<string, RegexReplacementInfo> GetOriginalRegexReplacementsForContext(ReplacementContext repCtx)
|
||||||
|
{
|
||||||
|
var objs = new List<object>();
|
||||||
|
if (repCtx.Client is not null)
|
||||||
|
{
|
||||||
|
objs.Add(repCtx.Client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repCtx.Channel is not null)
|
||||||
|
{
|
||||||
|
objs.Add(repCtx.Channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repCtx.Users is not null)
|
||||||
|
{
|
||||||
|
objs.Add(repCtx.Users);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repCtx.Guild is not null)
|
||||||
|
{
|
||||||
|
objs.Add(repCtx.Guild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var types = objs.Map(x => x.GetType()).OrderBy(x => x.Name).ToHashSet();
|
||||||
|
|
||||||
|
return _repReg.RegexReplacements
|
||||||
|
.Values
|
||||||
|
.Where(rep => rep.InputTypes.All(t => types.Any(x => x.IsAssignableTo((t)))))
|
||||||
|
.ToDictionary(rep => rep.Pattern, rep => rep);
|
||||||
|
}
|
||||||
|
}
|
138
src/Nadeko.Bot.Common/Replacements/Impl/Replacer.cs
Normal file
138
src/Nadeko.Bot.Common/Replacements/Impl/Replacer.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed partial class Replacer
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<ReplacementInfo> _reps;
|
||||||
|
private readonly IEnumerable<RegexReplacementInfo> _regexReps;
|
||||||
|
private readonly object[] _inputData;
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\%[\p{L}\p{N}\._]*[\p{L}\p{N}]+[\p{L}\p{N}\._]*\%")]
|
||||||
|
private static partial Regex TokenExtractionRegex();
|
||||||
|
|
||||||
|
public Replacer(IEnumerable<ReplacementInfo> reps, IEnumerable<RegexReplacementInfo> regexReps, object[] inputData)
|
||||||
|
{
|
||||||
|
_reps = reps;
|
||||||
|
_inputData = inputData;
|
||||||
|
_regexReps = regexReps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<string?> ReplaceAsync(string? input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
return input;
|
||||||
|
|
||||||
|
var matches = TokenExtractionRegex().IsMatch(input);
|
||||||
|
|
||||||
|
if (matches)
|
||||||
|
{
|
||||||
|
foreach (var rep in _reps)
|
||||||
|
{
|
||||||
|
if (input.Contains(rep.Token, StringComparison.InvariantCulture))
|
||||||
|
{
|
||||||
|
var objs = GetParams(rep.InputTypes);
|
||||||
|
input = input.Replace(rep.Token, await rep.GetValueAsync(objs), StringComparison.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var rep in _regexReps)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
var objs = GetParams(rep.InputTypes);
|
||||||
|
var match = rep.Regex.Match(input);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
sb.Append(input, 0, match.Index)
|
||||||
|
.Append(await rep.GetValueAsync(match, objs));
|
||||||
|
|
||||||
|
var lastIndex = match.Index + match.Length;
|
||||||
|
sb.Append(input, lastIndex, input.Length - lastIndex);
|
||||||
|
input = sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object?[]? GetParams(IReadOnlyCollection<Type> inputTypes)
|
||||||
|
{
|
||||||
|
if (inputTypes.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var objs = new List<object>();
|
||||||
|
foreach (var t in inputTypes)
|
||||||
|
{
|
||||||
|
var datum = _inputData.FirstOrDefault(x => x.GetType().IsAssignableTo(t));
|
||||||
|
if (datum is not null)
|
||||||
|
objs.Add(datum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objs.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<SmartText> ReplaceAsync(SmartText data)
|
||||||
|
=> data switch
|
||||||
|
{
|
||||||
|
SmartEmbedText embedData => await ReplaceAsync(embedData) with
|
||||||
|
{
|
||||||
|
PlainText = await ReplaceAsync(embedData.PlainText),
|
||||||
|
Color = embedData.Color
|
||||||
|
},
|
||||||
|
SmartPlainText plain => await ReplaceAsync(plain),
|
||||||
|
SmartEmbedTextArray arr => await ReplaceAsync(arr),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task<SmartEmbedTextArray> ReplaceAsync(SmartEmbedTextArray embedArr)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
Embeds = await embedArr.Embeds.Map(async e => await ReplaceAsync(e) with
|
||||||
|
{
|
||||||
|
Color = e.Color
|
||||||
|
}).WhenAll(),
|
||||||
|
Content = await ReplaceAsync(embedArr.Content)
|
||||||
|
};
|
||||||
|
|
||||||
|
private async ValueTask<SmartPlainText> ReplaceAsync(SmartPlainText plain)
|
||||||
|
=> await ReplaceAsync(plain.Text);
|
||||||
|
|
||||||
|
private async Task<T> ReplaceAsync<T>(T embedData) where T : SmartEmbedTextBase, new()
|
||||||
|
{
|
||||||
|
var newEmbedData = new T
|
||||||
|
{
|
||||||
|
Description = await ReplaceAsync(embedData.Description),
|
||||||
|
Title = await ReplaceAsync(embedData.Title),
|
||||||
|
Thumbnail = await ReplaceAsync(embedData.Thumbnail),
|
||||||
|
Image = await ReplaceAsync(embedData.Image),
|
||||||
|
Url = await ReplaceAsync(embedData.Url),
|
||||||
|
Author = embedData.Author is null
|
||||||
|
? null
|
||||||
|
: new()
|
||||||
|
{
|
||||||
|
Name = await ReplaceAsync(embedData.Author.Name),
|
||||||
|
IconUrl = await ReplaceAsync(embedData.Author.IconUrl)
|
||||||
|
},
|
||||||
|
Fields = await Task.WhenAll(embedData
|
||||||
|
.Fields?
|
||||||
|
.Map(async f => new SmartTextEmbedField
|
||||||
|
{
|
||||||
|
Name = await ReplaceAsync(f.Name),
|
||||||
|
Value = await ReplaceAsync(f.Value),
|
||||||
|
Inline = f.Inline
|
||||||
|
}) ?? []),
|
||||||
|
Footer = embedData.Footer is null
|
||||||
|
? null
|
||||||
|
: new()
|
||||||
|
{
|
||||||
|
Text = await ReplaceAsync(embedData.Footer.Text),
|
||||||
|
IconUrl = await ReplaceAsync(embedData.Footer.IconUrl)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return newEmbedData;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,164 +0,0 @@
|
|||||||
#nullable disable
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
|
||||||
|
|
||||||
public class ReplacementBuilder
|
|
||||||
{
|
|
||||||
private static readonly Regex _rngRegex = new("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%",
|
|
||||||
RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Regex, Func<Match, string>> _regex = new();
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, Func<string>> _reps = new();
|
|
||||||
|
|
||||||
public ReplacementBuilder()
|
|
||||||
=> WithRngRegex();
|
|
||||||
|
|
||||||
public ReplacementBuilder WithDefault(
|
|
||||||
IUser usr,
|
|
||||||
IMessageChannel ch,
|
|
||||||
SocketGuild g,
|
|
||||||
DiscordSocketClient client)
|
|
||||||
=> WithUser(usr).WithChannel(ch).WithServer(client, g).WithClient(client);
|
|
||||||
|
|
||||||
public ReplacementBuilder WithDefault(ICommandContext ctx)
|
|
||||||
=> WithDefault(ctx.User, ctx.Channel, ctx.Guild as SocketGuild, (DiscordSocketClient)ctx.Client);
|
|
||||||
|
|
||||||
public ReplacementBuilder WithMention(DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
_reps.TryAdd("%bot.mention%", () => client.CurrentUser.Mention);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithClient(DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
WithMention(client);
|
|
||||||
|
|
||||||
_reps.TryAdd("%bot.status%", () => client.Status.ToString());
|
|
||||||
_reps.TryAdd("%bot.latency%", () => client.Latency.ToString());
|
|
||||||
_reps.TryAdd("%bot.name%", () => client.CurrentUser.Username);
|
|
||||||
_reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString());
|
|
||||||
_reps.TryAdd("%bot.time%",
|
|
||||||
() => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
|
||||||
_reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator);
|
|
||||||
_reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString());
|
|
||||||
_reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl().ToString());
|
|
||||||
|
|
||||||
WithStats(client);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithServer(DiscordSocketClient client, SocketGuild g)
|
|
||||||
{
|
|
||||||
_reps.TryAdd("%server%", () => g is null ? "DM" : g.Name);
|
|
||||||
_reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString());
|
|
||||||
_reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name);
|
|
||||||
_reps.TryAdd("%server.icon%", () => g is null ? null : g.IconUrl);
|
|
||||||
_reps.TryAdd("%server.members%", () => g is { } sg ? sg.MemberCount.ToString() : "?");
|
|
||||||
_reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
|
|
||||||
_reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());
|
|
||||||
// todo fix
|
|
||||||
// _reps.TryAdd("%server.time%",
|
|
||||||
// () =>
|
|
||||||
// {
|
|
||||||
// var to = TimeZoneInfo.Local;
|
|
||||||
// if (g is not null)
|
|
||||||
// {
|
|
||||||
// if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
|
|
||||||
// to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ")
|
|
||||||
// + to.StandardName.GetInitials();
|
|
||||||
// });
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithChannel(IMessageChannel ch)
|
|
||||||
{
|
|
||||||
_reps.TryAdd("%channel%", () => ch.Name);
|
|
||||||
_reps.TryAdd("%channel.mention%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
|
|
||||||
_reps.TryAdd("%channel.name%", () => ch.Name);
|
|
||||||
_reps.TryAdd("%channel.id%", () => ch.Id.ToString());
|
|
||||||
_reps.TryAdd("%channel.created%", () => ch.CreatedAt.ToString("HH:mm dd.MM.yyyy"));
|
|
||||||
_reps.TryAdd("%channel.nsfw%", () => (ch as ITextChannel)?.IsNsfw.ToString() ?? "-");
|
|
||||||
_reps.TryAdd("%channel.topic%", () => (ch as ITextChannel)?.Topic ?? "-");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithUser(IUser user)
|
|
||||||
{
|
|
||||||
WithManyUsers(new[] { user });
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithManyUsers(IEnumerable<IUser> users)
|
|
||||||
{
|
|
||||||
_reps.TryAdd("%user%", () => string.Join(" ", users.Select(user => user.Mention)));
|
|
||||||
_reps.TryAdd("%user.mention%", () => string.Join(" ", users.Select(user => user.Mention)));
|
|
||||||
_reps.TryAdd("%user.fullname%", () => string.Join(" ", users.Select(user => user.ToString())));
|
|
||||||
_reps.TryAdd("%user.name%", () => string.Join(" ", users.Select(user => user.Username)));
|
|
||||||
_reps.TryAdd("%user.discrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
|
|
||||||
_reps.TryAdd("%user.avatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl().ToString())));
|
|
||||||
_reps.TryAdd("%user.id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
|
||||||
_reps.TryAdd("%user.created_time%",
|
|
||||||
() => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))));
|
|
||||||
_reps.TryAdd("%user.created_date%",
|
|
||||||
() => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))));
|
|
||||||
_reps.TryAdd("%user.joined_time%",
|
|
||||||
() => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")));
|
|
||||||
_reps.TryAdd("%user.joined_date%",
|
|
||||||
() => string.Join(" ",
|
|
||||||
users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReplacementBuilder WithStats(DiscordSocketClient c)
|
|
||||||
{
|
|
||||||
_reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
|
|
||||||
_reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
|
||||||
_reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithRngRegex()
|
|
||||||
{
|
|
||||||
var rng = new NadekoRandom();
|
|
||||||
_regex.TryAdd(_rngRegex,
|
|
||||||
match =>
|
|
||||||
{
|
|
||||||
if (!int.TryParse(match.Groups["from"].ToString(), out var from))
|
|
||||||
from = 0;
|
|
||||||
if (!int.TryParse(match.Groups["to"].ToString(), out var to))
|
|
||||||
to = 0;
|
|
||||||
|
|
||||||
if (from == 0 && to == 0)
|
|
||||||
return rng.Next(0, 11).ToString();
|
|
||||||
|
|
||||||
if (from >= to)
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
return rng.Next(from, to + 1).ToString();
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithOverride(string key, Func<string> output)
|
|
||||||
{
|
|
||||||
_reps.AddOrUpdate(key, output, delegate { return output; });
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Replacer Build()
|
|
||||||
=> new(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray());
|
|
||||||
|
|
||||||
public ReplacementBuilder WithProviders(IEnumerable<IPlaceholderProvider> phProviders)
|
|
||||||
{
|
|
||||||
foreach (var provider in phProviders)
|
|
||||||
foreach (var ovr in provider.GetPlaceholders())
|
|
||||||
_reps.TryAdd(ovr.Name, ovr.Func);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,93 +0,0 @@
|
|||||||
#nullable disable
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
|
||||||
|
|
||||||
public class Replacer
|
|
||||||
{
|
|
||||||
private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
|
|
||||||
private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
|
|
||||||
|
|
||||||
public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
|
|
||||||
{
|
|
||||||
_replacements = replacements;
|
|
||||||
_regex = regex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Replace(string input)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
|
||||||
return input;
|
|
||||||
|
|
||||||
foreach (var (key, text) in _replacements)
|
|
||||||
{
|
|
||||||
if (input.Contains(key))
|
|
||||||
input = input.Replace(key, text(), StringComparison.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in _regex)
|
|
||||||
input = item.Regex.Replace(input, m => item.Replacement(m));
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SmartText Replace(SmartText data)
|
|
||||||
=> data switch
|
|
||||||
{
|
|
||||||
SmartEmbedText embedData => Replace(embedData) with
|
|
||||||
{
|
|
||||||
PlainText = Replace(embedData.PlainText),
|
|
||||||
Color = embedData.Color
|
|
||||||
},
|
|
||||||
SmartPlainText plain => Replace(plain),
|
|
||||||
SmartEmbedTextArray arr => Replace(arr),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
|
|
||||||
};
|
|
||||||
|
|
||||||
private SmartEmbedTextArray Replace(SmartEmbedTextArray embedArr)
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
Embeds = embedArr.Embeds.Map(e => Replace(e) with
|
|
||||||
{
|
|
||||||
Color = e.Color
|
|
||||||
}),
|
|
||||||
Content = Replace(embedArr.Content)
|
|
||||||
};
|
|
||||||
|
|
||||||
private SmartPlainText Replace(SmartPlainText plain)
|
|
||||||
=> Replace(plain.Text);
|
|
||||||
|
|
||||||
private T Replace<T>(T embedData) where T: SmartEmbedTextBase, new()
|
|
||||||
{
|
|
||||||
var newEmbedData = new T
|
|
||||||
{
|
|
||||||
Description = Replace(embedData.Description),
|
|
||||||
Title = Replace(embedData.Title),
|
|
||||||
Thumbnail = Replace(embedData.Thumbnail),
|
|
||||||
Image = Replace(embedData.Image),
|
|
||||||
Url = Replace(embedData.Url),
|
|
||||||
Author = embedData.Author is null
|
|
||||||
? null
|
|
||||||
: new()
|
|
||||||
{
|
|
||||||
Name = Replace(embedData.Author.Name),
|
|
||||||
IconUrl = Replace(embedData.Author.IconUrl)
|
|
||||||
},
|
|
||||||
Fields = embedData.Fields?.Map(f => new SmartTextEmbedField
|
|
||||||
{
|
|
||||||
Name = Replace(f.Name),
|
|
||||||
Value = Replace(f.Value),
|
|
||||||
Inline = f.Inline
|
|
||||||
}),
|
|
||||||
Footer = embedData.Footer is null
|
|
||||||
? null
|
|
||||||
: new()
|
|
||||||
{
|
|
||||||
Text = Replace(embedData.Footer.Text),
|
|
||||||
IconUrl = Replace(embedData.Footer.IconUrl)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return newEmbedData;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -30,7 +30,7 @@ public sealed record SmartEmbedArrayElementText : SmartEmbedTextBase
|
|||||||
|
|
||||||
public sealed record SmartEmbedText : SmartEmbedTextBase
|
public sealed record SmartEmbedText : SmartEmbedTextBase
|
||||||
{
|
{
|
||||||
public string PlainText { get; init; }
|
public string? PlainText { get; init; }
|
||||||
|
|
||||||
public uint Color { get; init; } = 7458112;
|
public uint Color { get; init; } = 7458112;
|
||||||
|
|
||||||
@@ -54,15 +54,15 @@ public sealed record SmartEmbedText : SmartEmbedTextBase
|
|||||||
|
|
||||||
public abstract record SmartEmbedTextBase : SmartText
|
public abstract record SmartEmbedTextBase : SmartText
|
||||||
{
|
{
|
||||||
public string Title { get; init; }
|
public string? Title { get; init; }
|
||||||
public string Description { get; init; }
|
public string? Description { get; init; }
|
||||||
public string Url { get; init; }
|
public string? Url { get; init; }
|
||||||
public string Thumbnail { get; init; }
|
public string? Thumbnail { get; init; }
|
||||||
public string Image { get; init; }
|
public string? Image { get; init; }
|
||||||
|
|
||||||
public SmartTextEmbedAuthor Author { get; init; }
|
public SmartTextEmbedAuthor? Author { get; init; }
|
||||||
public SmartTextEmbedFooter Footer { get; init; }
|
public SmartTextEmbedFooter? Footer { get; init; }
|
||||||
public SmartTextEmbedField[] Fields { get; init; }
|
public SmartTextEmbedField[]? Fields { get; init; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool IsValid
|
public bool IsValid
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
|
|
||||||
@@ -11,15 +12,18 @@ public class AdministrationService : INService
|
|||||||
public ConcurrentDictionary<ulong, bool> DeleteMessagesOnCommandChannels { get; }
|
public ConcurrentDictionary<ulong, bool> DeleteMessagesOnCommandChannels { get; }
|
||||||
|
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
private readonly IReplacementService _repSvc;
|
||||||
private readonly ILogCommandService _logService;
|
private readonly ILogCommandService _logService;
|
||||||
|
|
||||||
public AdministrationService(
|
public AdministrationService(
|
||||||
IBot bot,
|
IBot bot,
|
||||||
CommandHandler cmdHandler,
|
CommandHandler cmdHandler,
|
||||||
DbService db,
|
DbService db,
|
||||||
|
IReplacementService repSvc,
|
||||||
ILogCommandService logService)
|
ILogCommandService logService)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
|
_repSvc = repSvc;
|
||||||
_logService = logService;
|
_logService = logService;
|
||||||
|
|
||||||
DeleteMessagesOnCommand = new(bot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
|
DeleteMessagesOnCommand = new(bot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
|
||||||
@@ -148,10 +152,10 @@ public class AdministrationService : INService
|
|||||||
if (msg is not IUserMessage umsg || msg.Author.Id != context.Client.CurrentUser.Id)
|
if (msg is not IUserMessage umsg || msg.Author.Id != context.Client.CurrentUser.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithDefault(context).Build();
|
var repCtx = new ReplacementContext(context);
|
||||||
|
|
||||||
var text = SmartText.CreateFrom(input);
|
var text = SmartText.CreateFrom(input);
|
||||||
text = rep.Replace(text);
|
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||||
|
|
||||||
await umsg.EditAsync(text);
|
await umsg.EditAsync(text);
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ using NadekoBot.Common.ModuleBehaviors;
|
|||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
using NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Services;
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
@@ -18,16 +19,19 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
private readonly GreetGrouper<IGuildUser> _greets = new();
|
private readonly GreetGrouper<IGuildUser> _greets = new();
|
||||||
private readonly GreetGrouper<IUser> _byes = new();
|
private readonly GreetGrouper<IUser> _byes = new();
|
||||||
private readonly BotConfigService _bss;
|
private readonly BotConfigService _bss;
|
||||||
|
private readonly IReplacementService _repSvc;
|
||||||
|
|
||||||
public GreetService(
|
public GreetService(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
IBot bot,
|
IBot bot,
|
||||||
DbService db,
|
DbService db,
|
||||||
BotConfigService bss)
|
BotConfigService bss,
|
||||||
|
IReplacementService repSvc)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_client = client;
|
_client = client;
|
||||||
_bss = bss;
|
_bss = bss;
|
||||||
|
_repSvc = repSvc;
|
||||||
|
|
||||||
_guildConfigsCache = new(bot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
|
_guildConfigsCache = new(bot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
|
||||||
|
|
||||||
@@ -81,11 +85,12 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var toSend = SmartText.CreateFrom(conf.BoostMessage);
|
var toSend = SmartText.CreateFrom(conf.BoostMessage);
|
||||||
var rep = new ReplacementBuilder().WithDefault(user, channel, user.Guild, _client).Build();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var toDelete = await channel.SendAsync(rep.Replace(toSend));
|
var newContent = await _repSvc.ReplaceAsync(toSend,
|
||||||
|
new(client: _client, guild: user.Guild, channel: channel, users: user));
|
||||||
|
var toDelete = await channel.SendAsync(newContent);
|
||||||
if (conf.BoostMessageDeleteAfter > 0)
|
if (conf.BoostMessageDeleteAfter > 0)
|
||||||
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
|
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
|
||||||
}
|
}
|
||||||
@@ -177,23 +182,31 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
if (!users.Any())
|
if (!users.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithChannel(channel)
|
// var rep = new ReplacementBuilder().WithChannel(channel)
|
||||||
.WithClient(_client)
|
// .WithClient(_client)
|
||||||
.WithServer(_client, (SocketGuild)channel.Guild)
|
// .WithServer(_client, (SocketGuild)channel.Guild)
|
||||||
.WithManyUsers(users)
|
// .WithManyUsers(users)
|
||||||
.Build();
|
// .Build();
|
||||||
|
|
||||||
|
var repCtx = new ReplacementContext(client: _client,
|
||||||
|
guild: channel.Guild,
|
||||||
|
channel: channel,
|
||||||
|
users: users.ToArray());
|
||||||
|
|
||||||
var text = SmartText.CreateFrom(conf.ChannelByeMessageText);
|
var text = SmartText.CreateFrom(conf.ChannelByeMessageText);
|
||||||
text = rep.Replace(text);
|
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var toDelete = await channel.SendAsync(text);
|
var toDelete = await channel.SendAsync(text);
|
||||||
if (conf.AutoDeleteByeMessagesTimer > 0)
|
if (conf.AutoDeleteByeMessagesTimer > 0)
|
||||||
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
||||||
}
|
}
|
||||||
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions || ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions ||
|
||||||
|
ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}", channel.GuildId);
|
Log.Warning(ex,
|
||||||
|
"Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}",
|
||||||
|
channel.GuildId);
|
||||||
await SetBye(channel.GuildId, channel.Id, false);
|
await SetBye(channel.GuildId, channel.Id, false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -210,23 +223,31 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
if (users.Count == 0)
|
if (users.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithChannel(channel)
|
// var rep = new ReplacementBuilder()
|
||||||
.WithClient(_client)
|
// .WithChannel(channel)
|
||||||
.WithServer(_client, (SocketGuild)channel.Guild)
|
// .WithClient(_client)
|
||||||
.WithManyUsers(users)
|
// .WithServer(_client, (SocketGuild)channel.Guild)
|
||||||
.Build();
|
// .WithManyUsers(users)
|
||||||
|
// .Build();
|
||||||
|
|
||||||
|
var repCtx = new ReplacementContext(client: _client,
|
||||||
|
guild: channel.Guild,
|
||||||
|
channel: channel,
|
||||||
|
users: users.ToArray());
|
||||||
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
|
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
|
||||||
text = rep.Replace(text);
|
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var toDelete = await channel.SendAsync(text);
|
var toDelete = await channel.SendAsync(text);
|
||||||
if (conf.AutoDeleteGreetMessagesTimer > 0)
|
if (conf.AutoDeleteGreetMessagesTimer > 0)
|
||||||
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
||||||
}
|
}
|
||||||
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions || ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions ||
|
||||||
|
ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}", channel.GuildId);
|
Log.Warning(ex,
|
||||||
|
"Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}",
|
||||||
|
channel.GuildId);
|
||||||
await SetGreet(channel.GuildId, channel.Id, false);
|
await SetGreet(channel.GuildId, channel.Id, false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -254,13 +275,14 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rep = new ReplacementBuilder()
|
// var rep = new ReplacementBuilder()
|
||||||
.WithUser(user)
|
// .WithUser(user)
|
||||||
.WithServer(_client, (SocketGuild)user.Guild)
|
// .WithServer(_client, (SocketGuild)user.Guild)
|
||||||
.Build();
|
// .Build();
|
||||||
|
|
||||||
|
var repCtx = new ReplacementContext(client: _client, guild: user.Guild, users: user);
|
||||||
var text = SmartText.CreateFrom(conf.DmGreetMessageText);
|
var text = SmartText.CreateFrom(conf.DmGreetMessageText);
|
||||||
text = rep.Replace(text);
|
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||||
|
|
||||||
if (text is SmartPlainText pt)
|
if (text is SmartPlainText pt)
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
|
|
||||||
@@ -9,7 +10,8 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
|
|||||||
{
|
{
|
||||||
private readonly BotConfigService _bss;
|
private readonly BotConfigService _bss;
|
||||||
private readonly SelfService _selfService;
|
private readonly SelfService _selfService;
|
||||||
private readonly Replacer _rep;
|
private readonly IReplacementService _repService;
|
||||||
|
// private readonly Replacer _rep;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
@@ -18,15 +20,15 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
|
|||||||
DbService db,
|
DbService db,
|
||||||
BotConfigService bss,
|
BotConfigService bss,
|
||||||
IEnumerable<IPlaceholderProvider> phProviders,
|
IEnumerable<IPlaceholderProvider> phProviders,
|
||||||
SelfService selfService)
|
SelfService selfService,
|
||||||
|
IReplacementService repService)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_bss = bss;
|
_bss = bss;
|
||||||
_selfService = selfService;
|
_selfService = selfService;
|
||||||
|
_repService = repService;
|
||||||
_client = client;
|
_client = client;
|
||||||
|
|
||||||
if (client.ShardId == 0)
|
|
||||||
_rep = new ReplacementBuilder().WithClient(client).WithProviders(phProviders).Build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
@@ -56,7 +58,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
|
|||||||
? rotatingStatuses[index = 0]
|
? rotatingStatuses[index = 0]
|
||||||
: rotatingStatuses[index++];
|
: rotatingStatuses[index++];
|
||||||
|
|
||||||
var statusText = _rep.Replace(playingStatus.Status);
|
var statusText = await _repService.ReplaceAsync(playingStatus.Status, new (client: _client));
|
||||||
await _selfService.SetGameAsync(statusText, (ActivityType)playingStatus.Type);
|
await _selfService.SetGameAsync(statusText, (ActivityType)playingStatus.Type);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
using Nadeko.Common.Medusa;
|
using Nadeko.Common.Medusa;
|
||||||
@@ -506,9 +506,10 @@ public partial class Administration
|
|||||||
[OwnerOnly]
|
[OwnerOnly]
|
||||||
public async Task SetGame(ActivityType type, [Leftover] string game = null)
|
public async Task SetGame(ActivityType type, [Leftover] string game = null)
|
||||||
{
|
{
|
||||||
var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
// var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
||||||
|
|
||||||
await _service.SetGameAsync(game is null ? game : rep.Replace(game), type);
|
var repCtx = new ReplacementContext(ctx);
|
||||||
|
await _service.SetGameAsync(game is null ? game : await repSvc.ReplaceAsync(game, repCtx), type);
|
||||||
|
|
||||||
await ReplyConfirmLocalizedAsync(strs.set_game);
|
await ReplyConfirmLocalizedAsync(strs.set_game);
|
||||||
}
|
}
|
||||||
@@ -538,7 +539,8 @@ public partial class Administration
|
|||||||
if (server is null)
|
if (server is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
// var repSvc = new ReplacementBuilder().WithDefault(Context).Build();
|
||||||
|
var repCtx = new ReplacementContext(Context);
|
||||||
|
|
||||||
if (ids[1].ToUpperInvariant().StartsWith("C:", StringComparison.InvariantCulture))
|
if (ids[1].ToUpperInvariant().StartsWith("C:", StringComparison.InvariantCulture))
|
||||||
{
|
{
|
||||||
@@ -547,7 +549,7 @@ public partial class Administration
|
|||||||
if (ch is null)
|
if (ch is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
text = rep.Replace(text);
|
text = await repSvc.ReplaceAsync(text, repCtx);
|
||||||
await ch.SendAsync(text);
|
await ch.SendAsync(text);
|
||||||
}
|
}
|
||||||
else if (ids[1].ToUpperInvariant().StartsWith("U:", StringComparison.InvariantCulture))
|
else if (ids[1].ToUpperInvariant().StartsWith("U:", StringComparison.InvariantCulture))
|
||||||
@@ -558,7 +560,7 @@ public partial class Administration
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var ch = await user.CreateDMChannelAsync();
|
var ch = await user.CreateDMChannelAsync();
|
||||||
text = rep.Replace(text);
|
text = await repSvc.ReplaceAsync(text, repCtx);
|
||||||
await ch.SendAsync(text);
|
await ch.SendAsync(text);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -1,26 +1,25 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Administration.Services;
|
namespace NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
public sealed class GuildTimezoneService : ITimezoneService, INService
|
public sealed class GuildTimezoneService : ITimezoneService, IReadyExecutor, INService
|
||||||
{
|
{
|
||||||
public static ConcurrentDictionary<ulong, GuildTimezoneService> AllServices { get; } = new();
|
|
||||||
private readonly ConcurrentDictionary<ulong, TimeZoneInfo> _timezones;
|
private readonly ConcurrentDictionary<ulong, TimeZoneInfo> _timezones;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
private readonly IReplacementPatternStore _repStore;
|
||||||
|
|
||||||
public GuildTimezoneService(DiscordSocketClient client, IBot bot, DbService db)
|
public GuildTimezoneService(IBot bot, DbService db, IReplacementPatternStore repStore)
|
||||||
{
|
{
|
||||||
_timezones = bot.AllGuildConfigs.Select(GetTimzezoneTuple)
|
_timezones = bot.AllGuildConfigs.Select(GetTimzezoneTuple)
|
||||||
.Where(x => x.Timezone is not null)
|
.Where(x => x.Timezone is not null)
|
||||||
.ToDictionary(x => x.GuildId, x => x.Timezone)
|
.ToDictionary(x => x.GuildId, x => x.Timezone)
|
||||||
.ToConcurrent();
|
.ToConcurrent();
|
||||||
|
|
||||||
var curUser = client.CurrentUser;
|
|
||||||
if (curUser is not null)
|
|
||||||
AllServices.TryAdd(curUser.Id, this);
|
|
||||||
_db = db;
|
_db = db;
|
||||||
|
_repStore = repStore;
|
||||||
|
|
||||||
bot.JoinedGuild += Bot_JoinedGuild;
|
bot.JoinedGuild += Bot_JoinedGuild;
|
||||||
}
|
}
|
||||||
@@ -75,4 +74,22 @@ public sealed class GuildTimezoneService : ITimezoneService, INService
|
|||||||
|
|
||||||
public TimeZoneInfo GetTimeZoneOrUtc(ulong? guildId)
|
public TimeZoneInfo GetTimeZoneOrUtc(ulong? guildId)
|
||||||
=> GetTimeZoneOrDefault(guildId) ?? TimeZoneInfo.Utc;
|
=> GetTimeZoneOrDefault(guildId) ?? TimeZoneInfo.Utc;
|
||||||
|
|
||||||
|
public Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
_repStore.Register("%server.time%",
|
||||||
|
(IGuild g) =>
|
||||||
|
{
|
||||||
|
var to = TimeZoneInfo.Local;
|
||||||
|
if (g is not null)
|
||||||
|
{
|
||||||
|
to = GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ")
|
||||||
|
+ to.StandardName.GetInitials();
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -425,7 +425,7 @@ public partial class Administration
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
||||||
var embed = _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time);
|
var embed = await _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time);
|
||||||
if (embed is not null)
|
if (embed is not null)
|
||||||
await guildUser.SendAsync(embed);
|
await guildUser.SendAsync(embed);
|
||||||
}
|
}
|
||||||
@@ -490,7 +490,7 @@ public partial class Administration
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
||||||
var embed = _service.GetBanUserDmEmbed(Context, user, defaultMessage, msg, null);
|
var embed = await _service.GetBanUserDmEmbed(Context, user, defaultMessage, msg, null);
|
||||||
if (embed is not null)
|
if (embed is not null)
|
||||||
await user.SendAsync(embed);
|
await user.SendAsync(embed);
|
||||||
}
|
}
|
||||||
@@ -586,7 +586,7 @@ public partial class Administration
|
|||||||
private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
|
private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
|
||||||
{
|
{
|
||||||
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), reason));
|
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), reason));
|
||||||
var embed = _service.GetBanUserDmEmbed(Context, (IGuildUser)ctx.User, defaultMessage, reason, duration);
|
var embed = await _service.GetBanUserDmEmbed(Context, (IGuildUser)ctx.User, defaultMessage, reason, duration);
|
||||||
|
|
||||||
if (embed is null)
|
if (embed is null)
|
||||||
await ConfirmLocalizedAsync(strs.banmsg_disabled);
|
await ConfirmLocalizedAsync(strs.banmsg_disabled);
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Common.TypeReaders.Models;
|
using NadekoBot.Common.TypeReaders.Models;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
@@ -18,6 +19,7 @@ public class UserPunishService : INService, IReadyExecutor
|
|||||||
private readonly BlacklistService _blacklistService;
|
private readonly BlacklistService _blacklistService;
|
||||||
private readonly BotConfigService _bcs;
|
private readonly BotConfigService _bcs;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly IReplacementService _repSvc;
|
||||||
|
|
||||||
public event Func<Warning, Task> OnUserWarned = static delegate { return Task.CompletedTask; };
|
public event Func<Warning, Task> OnUserWarned = static delegate { return Task.CompletedTask; };
|
||||||
|
|
||||||
@@ -26,13 +28,15 @@ public class UserPunishService : INService, IReadyExecutor
|
|||||||
DbService db,
|
DbService db,
|
||||||
BlacklistService blacklistService,
|
BlacklistService blacklistService,
|
||||||
BotConfigService bcs,
|
BotConfigService bcs,
|
||||||
DiscordSocketClient client)
|
DiscordSocketClient client,
|
||||||
|
IReplacementService repSvc)
|
||||||
{
|
{
|
||||||
_mute = mute;
|
_mute = mute;
|
||||||
_db = db;
|
_db = db;
|
||||||
_blacklistService = blacklistService;
|
_blacklistService = blacklistService;
|
||||||
_bcs = bcs;
|
_bcs = bcs;
|
||||||
_client = client;
|
_client = client;
|
||||||
|
_repSvc = repSvc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
@@ -524,7 +528,7 @@ public class UserPunishService : INService, IReadyExecutor
|
|||||||
.FirstOrDefaultAsyncLinqToDB();
|
.FirstOrDefaultAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SmartText GetBanUserDmEmbed(
|
public Task<SmartText> GetBanUserDmEmbed(
|
||||||
ICommandContext context,
|
ICommandContext context,
|
||||||
IGuildUser target,
|
IGuildUser target,
|
||||||
string defaultMessage,
|
string defaultMessage,
|
||||||
@@ -538,7 +542,7 @@ public class UserPunishService : INService, IReadyExecutor
|
|||||||
banReason,
|
banReason,
|
||||||
duration);
|
duration);
|
||||||
|
|
||||||
public SmartText GetBanUserDmEmbed(
|
public async Task<SmartText> GetBanUserDmEmbed(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
SocketGuild guild,
|
SocketGuild guild,
|
||||||
IGuildUser moderator,
|
IGuildUser moderator,
|
||||||
@@ -551,7 +555,7 @@ public class UserPunishService : INService, IReadyExecutor
|
|||||||
|
|
||||||
banReason = string.IsNullOrWhiteSpace(banReason) ? "-" : banReason;
|
banReason = string.IsNullOrWhiteSpace(banReason) ? "-" : banReason;
|
||||||
|
|
||||||
var replacer = new ReplacementBuilder().WithServer(client, guild)
|
var repCtx = new ReplacementContext(client, guild)
|
||||||
.WithOverride("%ban.mod%", () => moderator.ToString())
|
.WithOverride("%ban.mod%", () => moderator.ToString())
|
||||||
.WithOverride("%ban.mod.fullname%", () => moderator.ToString())
|
.WithOverride("%ban.mod.fullname%", () => moderator.ToString())
|
||||||
.WithOverride("%ban.mod.name%", () => moderator.Username)
|
.WithOverride("%ban.mod.name%", () => moderator.Username)
|
||||||
@@ -563,8 +567,8 @@ public class UserPunishService : INService, IReadyExecutor
|
|||||||
.WithOverride("%reason%", () => banReason)
|
.WithOverride("%reason%", () => banReason)
|
||||||
.WithOverride("%ban.reason%", () => banReason)
|
.WithOverride("%ban.reason%", () => banReason)
|
||||||
.WithOverride("%ban.duration%",
|
.WithOverride("%ban.duration%",
|
||||||
() => duration?.ToString(@"d\.hh\:mm") ?? "perma")
|
() => duration?.ToString(@"d\.hh\:mm") ?? "perma");
|
||||||
.Build();
|
|
||||||
|
|
||||||
// if template isn't set, use the old message style
|
// if template isn't set, use the old message style
|
||||||
if (string.IsNullOrWhiteSpace(template))
|
if (string.IsNullOrWhiteSpace(template))
|
||||||
@@ -590,6 +594,6 @@ public class UserPunishService : INService, IReadyExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
var output = SmartText.CreateFrom(template);
|
var output = SmartText.CreateFrom(template);
|
||||||
return replacer.Replace(output);
|
return await _repSvc.ReplaceAsync(output, repCtx);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.NadekoExpressions;
|
namespace NadekoBot.Modules.NadekoExpressions;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ public static class NadekoExpressionExtensions
|
|||||||
public static async Task<IUserMessage> Send(
|
public static async Task<IUserMessage> Send(
|
||||||
this NadekoExpression cr,
|
this NadekoExpression cr,
|
||||||
IUserMessage ctx,
|
IUserMessage ctx,
|
||||||
|
IReplacementService repSvc,
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
bool sanitize)
|
bool sanitize)
|
||||||
{
|
{
|
||||||
@@ -32,16 +34,18 @@ public static class NadekoExpressionExtensions
|
|||||||
|
|
||||||
var canMentionEveryone = (ctx.Author as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
|
var canMentionEveryone = (ctx.Author as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
|
||||||
|
|
||||||
var rep = new ReplacementBuilder()
|
var repCtx = new ReplacementContext(client: client,
|
||||||
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild as SocketGuild, client)
|
guild: (ctx.Channel as ITextChannel)?.Guild as SocketGuild,
|
||||||
|
channel: ctx.Channel,
|
||||||
|
users: ctx.Author
|
||||||
|
)
|
||||||
.WithOverride("%target%",
|
.WithOverride("%target%",
|
||||||
() => canMentionEveryone
|
() => canMentionEveryone
|
||||||
? ctx.Content[substringIndex..].Trim()
|
? ctx.Content[substringIndex..].Trim()
|
||||||
: ctx.Content[substringIndex..].Trim().SanitizeMentions(true))
|
: ctx.Content[substringIndex..].Trim().SanitizeMentions(true));
|
||||||
.Build();
|
|
||||||
|
|
||||||
var text = SmartText.CreateFrom(cr.Response);
|
var text = SmartText.CreateFrom(cr.Response);
|
||||||
text = rep.Replace(text);
|
text = await repSvc.ReplaceAsync(text, repCtx);
|
||||||
|
|
||||||
return await channel.SendAsync(text, sanitize);
|
return await channel.SendAsync(text, sanitize);
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,6 @@ using NadekoBot.Db;
|
|||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Nadeko.Bot.Common;
|
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
@@ -75,6 +74,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
|||||||
private readonly IBot _bot;
|
private readonly IBot _bot;
|
||||||
private readonly IPubSub _pubSub;
|
private readonly IPubSub _pubSub;
|
||||||
private readonly IEmbedBuilderService _eb;
|
private readonly IEmbedBuilderService _eb;
|
||||||
|
private readonly IReplacementService _repSvc;
|
||||||
private readonly Random _rng;
|
private readonly Random _rng;
|
||||||
|
|
||||||
private bool ready;
|
private bool ready;
|
||||||
@@ -88,6 +88,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
|||||||
ICommandHandler cmd,
|
ICommandHandler cmd,
|
||||||
IPubSub pubSub,
|
IPubSub pubSub,
|
||||||
IEmbedBuilderService eb,
|
IEmbedBuilderService eb,
|
||||||
|
IReplacementService repSvc,
|
||||||
IPermissionChecker permChecker)
|
IPermissionChecker permChecker)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
@@ -97,6 +98,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
|||||||
_bot = bot;
|
_bot = bot;
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_eb = eb;
|
_eb = eb;
|
||||||
|
_repSvc = repSvc;
|
||||||
_permChecker = permChecker;
|
_permChecker = permChecker;
|
||||||
_rng = new NadekoRandom();
|
_rng = new NadekoRandom();
|
||||||
|
|
||||||
@@ -275,7 +277,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sentMsg = await expr.Send(msg, _client, false);
|
var sentMsg = await expr.Send(msg, _repSvc, _client, false);
|
||||||
|
|
||||||
var reactions = expr.GetReactions();
|
var reactions = expr.GetReactions();
|
||||||
foreach (var reaction in reactions)
|
foreach (var reaction in reactions)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Nadeko.Bot.Common;
|
using NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling.Common;
|
namespace NadekoBot.Modules.Gambling.Common;
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Nadeko.Bot.Common;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using NadekoBot.Modules.Games.Common;
|
using NadekoBot.Modules.Games.Common;
|
||||||
|
@@ -5,7 +5,7 @@ using NadekoBot.Modules.Help.Services;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Nadeko.Bot.Common;
|
using NadekoBot.Common;
|
||||||
using Nadeko.Common.Medusa;
|
using Nadeko.Common.Medusa;
|
||||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
@@ -56,15 +56,14 @@ public sealed class Help : NadekoModule<HelpService>
|
|||||||
return default;
|
return default;
|
||||||
|
|
||||||
var clientId = await _lazyClientId.Value;
|
var clientId = await _lazyClientId.Value;
|
||||||
var r = new ReplacementBuilder().WithDefault(Context)
|
var repCtx = new ReplacementContext(Context)
|
||||||
.WithOverride("{0}", () => clientId.ToString())
|
.WithOverride("{0}", () => clientId.ToString())
|
||||||
.WithOverride("{1}", () => prefix)
|
.WithOverride("{1}", () => prefix)
|
||||||
.WithOverride("%prefix%", () => prefix)
|
.WithOverride("%prefix%", () => prefix)
|
||||||
.WithOverride("%bot.prefix%", () => prefix)
|
.WithOverride("%bot.prefix%", () => prefix);
|
||||||
.Build();
|
|
||||||
|
|
||||||
var text = SmartText.CreateFrom(botSettings.HelpText);
|
var text = SmartText.CreateFrom(botSettings.HelpText);
|
||||||
return r.Replace(text);
|
return await repSvc.ReplaceAsync(text, repCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
@@ -1,42 +1,34 @@
|
|||||||
#nullable disable
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Help.Services;
|
namespace NadekoBot.Modules.Help.Services;
|
||||||
|
|
||||||
public class HelpService : IExecNoCommand, INService
|
public class HelpService(BotConfigService bss, IReplacementService repSvc) : IExecNoCommand, INService
|
||||||
{
|
{
|
||||||
private readonly BotConfigService _bss;
|
public async Task ExecOnNoCommandAsync(IGuild? guild, IUserMessage msg)
|
||||||
|
|
||||||
public HelpService(BotConfigService bss)
|
|
||||||
{
|
{
|
||||||
_bss = bss;
|
var settings = bss.Data;
|
||||||
}
|
|
||||||
|
|
||||||
public Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
|
||||||
{
|
|
||||||
var settings = _bss.Data;
|
|
||||||
if (guild is null)
|
if (guild is null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(settings.DmHelpText) || settings.DmHelpText == "-")
|
if (string.IsNullOrWhiteSpace(settings.DmHelpText) || settings.DmHelpText == "-")
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
|
|
||||||
// only send dm help text if it contains one of the keywords, if they're specified
|
// only send dm help text if it contains one of the keywords, if they're specified
|
||||||
// if they're not, then reply to every DM
|
// if they're not, then reply to every DM
|
||||||
if (settings.DmHelpTextKeywords is not null &&
|
if (settings.DmHelpTextKeywords is not null &&
|
||||||
!settings.DmHelpTextKeywords.Any(k => msg.Content.Contains(k)))
|
!settings.DmHelpTextKeywords.Any(k => msg.Content.Contains(k)))
|
||||||
return Task.CompletedTask;
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithOverride("%prefix%", () => _bss.Data.Prefix)
|
var repCtx = new ReplacementContext(guild: guild, channel: msg.Channel, users: msg.Author)
|
||||||
.WithOverride("%bot.prefix%", () => _bss.Data.Prefix)
|
.WithOverride("%prefix%", () => bss.Data.Prefix)
|
||||||
.WithUser(msg.Author)
|
.WithOverride("%bot.prefix%", () => bss.Data.Prefix);
|
||||||
.Build();
|
|
||||||
|
|
||||||
var text = SmartText.CreateFrom(settings.DmHelpText);
|
var text = SmartText.CreateFrom(settings.DmHelpText);
|
||||||
text = rep.Replace(text);
|
text = await repSvc.ReplaceAsync(text, repCtx);
|
||||||
|
|
||||||
return msg.Channel.SendAsync(text);
|
await msg.Channel.SendAsync(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,7 +6,7 @@ using System.Text.Json;
|
|||||||
|
|
||||||
namespace NadekoBot.Modules.Searches;
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
// todo fix stock/crypto
|
// todo fix stock
|
||||||
public sealed class DefaultStockDataService : IStockDataService, INService
|
public sealed class DefaultStockDataService : IStockDataService, INService
|
||||||
{
|
{
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
@@ -83,7 +83,6 @@ public sealed class DefaultStockDataService : IStockDataService, INService
|
|||||||
PrepareHeaderForMatch = args => args.Header.Humanize(LetterCasing.Title)
|
PrepareHeaderForMatch = args => args.Header.Humanize(LetterCasing.Title)
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo this needs testing
|
|
||||||
public async Task<IReadOnlyCollection<CandleData>> GetCandleDataAsync(string query)
|
public async Task<IReadOnlyCollection<CandleData>> GetCandleDataAsync(string query)
|
||||||
{
|
{
|
||||||
using var http = _httpClientFactory.CreateClient();
|
using var http = _httpClientFactory.CreateClient();
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
@@ -28,6 +29,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
|||||||
private readonly IPubSub _pubSub;
|
private readonly IPubSub _pubSub;
|
||||||
private readonly IEmbedBuilderService _eb;
|
private readonly IEmbedBuilderService _eb;
|
||||||
private readonly SearchesConfigService _config;
|
private readonly SearchesConfigService _config;
|
||||||
|
private readonly IReplacementService _repSvc;
|
||||||
|
|
||||||
public TypedKey<List<StreamData>> StreamsOnlineKey { get; }
|
public TypedKey<List<StreamData>> StreamsOnlineKey { get; }
|
||||||
public TypedKey<List<StreamData>> StreamsOfflineKey { get; }
|
public TypedKey<List<StreamData>> StreamsOfflineKey { get; }
|
||||||
@@ -50,7 +52,8 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
|||||||
IBot bot,
|
IBot bot,
|
||||||
IPubSub pubSub,
|
IPubSub pubSub,
|
||||||
IEmbedBuilderService eb,
|
IEmbedBuilderService eb,
|
||||||
SearchesConfigService config)
|
SearchesConfigService config,
|
||||||
|
IReplacementService repSvc)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_client = client;
|
_client = client;
|
||||||
@@ -58,6 +61,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
|||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_eb = eb;
|
_eb = eb;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_repSvc = repSvc;
|
||||||
|
|
||||||
_streamTracker = new(httpFactory, creds);
|
_streamTracker = new(httpFactory, creds);
|
||||||
|
|
||||||
@@ -275,11 +279,13 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
|||||||
if (textChannel is null)
|
if (textChannel is null)
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithOverride("%user%", () => fs.Username)
|
var repCtx = new ReplacementContext(guild: textChannel.Guild, client: _client)
|
||||||
.WithOverride("%platform%", () => fs.Type.ToString())
|
.WithOverride("%platform%", () => fs.Type.ToString());
|
||||||
.Build();
|
|
||||||
|
|
||||||
var message = string.IsNullOrWhiteSpace(fs.Message) ? "" : rep.Replace(fs.Message);
|
|
||||||
|
var message = string.IsNullOrWhiteSpace(fs.Message)
|
||||||
|
? ""
|
||||||
|
: await _repSvc.ReplaceAsync(fs.Message, repCtx);
|
||||||
|
|
||||||
var msg = await textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream, false), message);
|
var msg = await textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream, false), message);
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
#nullable disable warnings
|
#nullable disable warnings
|
||||||
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.Yml;
|
using NadekoBot.Common.Yml;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
@@ -99,10 +100,10 @@ public partial class Utility
|
|||||||
if (quote is null)
|
if (quote is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
var repCtx = new ReplacementContext(Context);
|
||||||
|
|
||||||
var text = SmartText.CreateFrom(quote.Text);
|
var text = SmartText.CreateFrom(quote.Text);
|
||||||
text = rep.Replace(text);
|
text = await repSvc.ReplaceAsync(text, repCtx);
|
||||||
|
|
||||||
await ctx.Channel.SendAsync($"`#{quote.Id}` 📣 " + text, true);
|
await ctx.Channel.SendAsync($"`#{quote.Id}` 📣 " + text, true);
|
||||||
}
|
}
|
||||||
@@ -193,7 +194,7 @@ public partial class Utility
|
|||||||
|
|
||||||
Quote quote;
|
Quote quote;
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
var repCtx = new ReplacementContext(Context);
|
||||||
|
|
||||||
await using (var uow = _db.GetDbContext())
|
await using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
@@ -212,7 +213,7 @@ public partial class Utility
|
|||||||
|
|
||||||
|
|
||||||
var text = SmartText.CreateFrom(quote.Text);
|
var text = SmartText.CreateFrom(quote.Text);
|
||||||
text = rep.Replace(text);
|
text = await repSvc.ReplaceAsync(text, repCtx);
|
||||||
await ctx.Channel.SendAsync(infoText + text, true);
|
await ctx.Channel.SendAsync(infoText + text, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
|||||||
private const int MAX_REPEATERS = 5;
|
private const int MAX_REPEATERS = 5;
|
||||||
|
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
private readonly IReplacementService _repSvc;
|
||||||
private readonly IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly LinkedList<RunningRepeater> _repeaterQueue;
|
private readonly LinkedList<RunningRepeater> _repeaterQueue;
|
||||||
@@ -22,9 +24,11 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
|||||||
public RepeaterService(
|
public RepeaterService(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
DbService db,
|
DbService db,
|
||||||
|
IReplacementService repSvc,
|
||||||
IBotCredentials creds)
|
IBotCredentials creds)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
|
_repSvc = repSvc;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
_client = client;
|
_client = client;
|
||||||
|
|
||||||
@@ -195,8 +199,13 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
|||||||
var channel = _client.GetChannel(repeater.ChannelId) as ITextChannel;
|
var channel = _client.GetChannel(repeater.ChannelId) as ITextChannel;
|
||||||
if (channel is null)
|
if (channel is null)
|
||||||
{
|
{
|
||||||
try { channel = await _client.Rest.GetChannelAsync(repeater.ChannelId) as ITextChannel; }
|
try
|
||||||
catch { }
|
{
|
||||||
|
channel = await _client.Rest.GetChannelAsync(repeater.ChannelId) as ITextChannel;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel is null)
|
if (channel is null)
|
||||||
@@ -247,12 +256,15 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rep = new ReplacementBuilder().WithDefault(guild.CurrentUser, channel, guild, _client).Build();
|
var repCtx = new ReplacementContext(client: _client,
|
||||||
|
guild: guild,
|
||||||
|
channel: channel,
|
||||||
|
users: guild.CurrentUser);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var text = SmartText.CreateFrom(repeater.Message);
|
var text = SmartText.CreateFrom(repeater.Message);
|
||||||
text = rep.Replace(text);
|
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||||
|
|
||||||
var newMsg = await channel.SendAsync(text);
|
var newMsg = await channel.SendAsync(text);
|
||||||
_ = newMsg.AddReactionAsync(new Emoji("🔄"));
|
_ = newMsg.AddReactionAsync(new Emoji("🔄"));
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
|
||||||
using Microsoft.CodeAnalysis.Scripting;
|
|
||||||
using NadekoBot.Modules.Utility.Services;
|
using NadekoBot.Modules.Utility.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -61,11 +59,12 @@ public partial class Utility : NadekoModule
|
|||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public async Task Say(ITextChannel channel, [Leftover] SmartText message)
|
public async Task Say(ITextChannel channel, [Leftover] SmartText message)
|
||||||
{
|
{
|
||||||
var rep = new ReplacementBuilder()
|
// var rep = new ReplacementBuilder()
|
||||||
.WithDefault(ctx.User, channel, (SocketGuild)ctx.Guild, (DiscordSocketClient)ctx.Client)
|
// .WithDefault(ctx.User, channel, (SocketGuild)ctx.Guild, (DiscordSocketClient)ctx.Client)
|
||||||
.Build();
|
// .Build();
|
||||||
|
|
||||||
message = rep.Replace(message);
|
var repCtx = new ReplacementContext(Context);
|
||||||
|
message = await repSvc.ReplaceAsync(message, repCtx);
|
||||||
|
|
||||||
await channel.SendAsync(message, !((IGuildUser)ctx.User).GuildPermissions.MentionEveryone);
|
await channel.SendAsync(message, !((IGuildUser)ctx.User).GuildPermissions.MentionEveryone);
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#nullable disable warnings
|
#nullable disable warnings
|
||||||
using NadekoBot.Modules.Xp.Services;
|
using NadekoBot.Modules.Xp.Services;
|
||||||
using Nadeko.Bot.Db.Models;
|
using Nadeko.Bot.Db.Models;
|
||||||
using Nadeko.Bot.Common;
|
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using NadekoBot.Modules.Patronage;
|
using NadekoBot.Modules.Patronage;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using Nadeko.Bot.Common;
|
using NadekoBot.Modules.Xp.Services;
|
||||||
using NadekoBot.Modules.Xp.Services;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Xp;
|
namespace NadekoBot.Modules.Xp;
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
using Nadeko.Bot.Common;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling;
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
@@ -1,8 +1,5 @@
|
|||||||
using Nadeko.Bot.Common;
|
using NadekoBot.Modules.Permissions.Common;
|
||||||
using NadekoBot.Modules.Permissions.Common;
|
|
||||||
using NadekoBot.Modules.Permissions.Services;
|
using NadekoBot.Modules.Permissions.Services;
|
||||||
using OneOf;
|
|
||||||
using OneOf.Types;
|
|
||||||
|
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user