WHEW. Added placeholders in embeds and quotes, added docs about it to explained features. Wrote a placeholder system and fixed some bugs
This commit is contained in:
		
							
								
								
									
										132
									
								
								src/NadekoBot/DataStructures/Replacements/ReplacementBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/NadekoBot/DataStructures/Replacements/ReplacementBuilder.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Music;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.DataStructures.Replacements
 | 
			
		||||
{
 | 
			
		||||
    public class ReplacementBuilder
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
 | 
			
		||||
        private ConcurrentDictionary<string, Func<string>> _reps = new ConcurrentDictionary<string, Func<string>>();
 | 
			
		||||
        private ConcurrentDictionary<Regex, Func<Match, string>> _regex = new ConcurrentDictionary<Regex, Func<Match, string>>();
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder()
 | 
			
		||||
        {
 | 
			
		||||
            WithRngRegex();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, IGuild g, DiscordSocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            return this.WithUser(usr)
 | 
			
		||||
                .WithChannel(ch)
 | 
			
		||||
                .WithServer(g)
 | 
			
		||||
                .WithClient(client);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithDefault(ICommandContext ctx) =>
 | 
			
		||||
            WithDefault(ctx.User, ctx.Channel, ctx.Guild, (DiscordSocketClient)ctx.Client);
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithClient(DiscordSocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            _reps.TryAdd("%mention%", () => $"<@{client.CurrentUser.Id}>");
 | 
			
		||||
            _reps.TryAdd("%shardid%", () => client.ShardId.ToString());
 | 
			
		||||
            _reps.TryAdd("%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithServer(IGuild g)
 | 
			
		||||
        {
 | 
			
		||||
            _reps.TryAdd("%sid%", () => g == null ? "DM" : g.Id.ToString());
 | 
			
		||||
            _reps.TryAdd("%server%", () => g == null ? "DM" : g.Name);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithChannel(IMessageChannel ch)
 | 
			
		||||
        {
 | 
			
		||||
            _reps.TryAdd("%channel%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
 | 
			
		||||
            _reps.TryAdd("%chname%", () => ch.Name);
 | 
			
		||||
            _reps.TryAdd("%cid%", () => ch?.Id.ToString());
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithUser(IUser user)
 | 
			
		||||
        {
 | 
			
		||||
            _reps.TryAdd("%user%", () => user.Mention);
 | 
			
		||||
            _reps.TryAdd("%userfull%", () => user.ToString());
 | 
			
		||||
            _reps.TryAdd("%username%", () => user.Username);
 | 
			
		||||
            _reps.TryAdd("%userdiscrim%", () => user.Discriminator);
 | 
			
		||||
            _reps.TryAdd("%id%", () => user.Id.ToString());
 | 
			
		||||
            _reps.TryAdd("%uid%", () => user.Id.ToString());
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithStats(DiscordSocketClient c)
 | 
			
		||||
        {
 | 
			
		||||
            _reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
 | 
			
		||||
            _reps.TryAdd("%users%", () => c.Guilds.Sum(s => s.Users.Count).ToString());
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithMusic(MusicService ms)
 | 
			
		||||
        {
 | 
			
		||||
            _reps.TryAdd("%playing%", () =>
 | 
			
		||||
            {
 | 
			
		||||
                var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
 | 
			
		||||
                if (cnt != 1) return cnt.ToString();
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var mp = ms.MusicPlayers.FirstOrDefault();
 | 
			
		||||
                    return mp.Value.CurrentSong.SongInfo.Title;
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    return "No songs";
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            _reps.TryAdd("%queued%", () => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString());
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReplacementBuilder WithRngRegex()
 | 
			
		||||
        {
 | 
			
		||||
            var rng = new NadekoRandom();
 | 
			
		||||
            _regex.TryAdd(rngRegex, (match) =>
 | 
			
		||||
            {
 | 
			
		||||
                int from = 0;
 | 
			
		||||
                int.TryParse(match.Groups["from"].ToString(), out from);
 | 
			
		||||
 | 
			
		||||
                int to = 0;
 | 
			
		||||
                int.TryParse(match.Groups["to"].ToString(), out to);
 | 
			
		||||
 | 
			
		||||
                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()
 | 
			
		||||
        {
 | 
			
		||||
            return new Replacer(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								src/NadekoBot/DataStructures/Replacements/Replacer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/NadekoBot/DataStructures/Replacements/Replacer.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.DataStructures.Replacements
 | 
			
		||||
{
 | 
			
		||||
    public class Replacer
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
 | 
			
		||||
        private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
 | 
			
		||||
 | 
			
		||||
        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 item in _replacements)
 | 
			
		||||
            {
 | 
			
		||||
                if (input.Contains(item.Key))
 | 
			
		||||
                    input = input.Replace(item.Key, item.Text());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach (var item in _regex)
 | 
			
		||||
            {
 | 
			
		||||
                input = item.Regex.Replace(input, (m) => item.Replacement(m));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return input;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Replace(CREmbed embedData)
 | 
			
		||||
        {
 | 
			
		||||
            embedData.PlainText = Replace(embedData.PlainText);
 | 
			
		||||
            embedData.Description = Replace(embedData.Description);
 | 
			
		||||
            embedData.Title = Replace(embedData.Title);
 | 
			
		||||
 | 
			
		||||
            if (embedData.Fields != null)
 | 
			
		||||
                foreach (var f in embedData.Fields)
 | 
			
		||||
                {
 | 
			
		||||
                    f.Name = Replace(f.Name);
 | 
			
		||||
                    f.Value = Replace(f.Value);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            if (embedData.Footer != null)
 | 
			
		||||
                embedData.Footer.Text = Replace(embedData.Footer.Text);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -33,11 +33,11 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    {
 | 
			
		||||
                        var config = uow.BotConfig.GetOrCreate();
 | 
			
		||||
 | 
			
		||||
                        _service.RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses;
 | 
			
		||||
                        config.RotatingStatuses = !config.RotatingStatuses;
 | 
			
		||||
                        uow.Complete();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (_service.RotatingStatuses)
 | 
			
		||||
                if (_service.BotConfig.RotatingStatuses)
 | 
			
		||||
                    await ReplyConfirmLocalized("ropl_enabled").ConfigureAwait(false);
 | 
			
		||||
                else
 | 
			
		||||
                    await ReplyConfirmLocalized("ropl_disabled").ConfigureAwait(false);
 | 
			
		||||
@@ -52,7 +52,6 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    var config = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                    var toAdd = new PlayingStatus { Status = status };
 | 
			
		||||
                    config.RotatingStatusMessages.Add(toAdd);
 | 
			
		||||
                    _service.RotatingStatusMessages.Add(toAdd);
 | 
			
		||||
                    await uow.CompleteAsync();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -63,13 +62,13 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task ListPlaying()
 | 
			
		||||
            {
 | 
			
		||||
                if (!_service.RotatingStatusMessages.Any())
 | 
			
		||||
                if (!_service.BotConfig.RotatingStatusMessages.Any())
 | 
			
		||||
                    await ReplyErrorLocalized("ropl_not_set").ConfigureAwait(false);
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    var i = 1;
 | 
			
		||||
                    await ReplyConfirmLocalized("ropl_list",
 | 
			
		||||
                            string.Join("\n\t", _service.RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}")))
 | 
			
		||||
                            string.Join("\n\t", _service.BotConfig.RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}")))
 | 
			
		||||
                        .ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +89,6 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                        return;
 | 
			
		||||
                    msg = config.RotatingStatusMessages[index].Status;
 | 
			
		||||
                    config.RotatingStatusMessages.RemoveAt(index);
 | 
			
		||||
                    _service.RotatingStatusMessages.RemoveAt(index);
 | 
			
		||||
                    await uow.CompleteAsync();
 | 
			
		||||
                }
 | 
			
		||||
                await ReplyConfirmLocalized("reprm", msg).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NadekoBot.DataStructures.Replacements;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Utility
 | 
			
		||||
{
 | 
			
		||||
@@ -66,19 +67,14 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                if (quote == null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                CREmbed crembed;
 | 
			
		||||
                if (CREmbed.TryParse(quote.Text, out crembed))
 | 
			
		||||
                if (CREmbed.TryParse(quote.Text, out var crembed))
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "")
 | 
			
		||||
                            .ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        _log.Warn("Sending CREmbed failed");
 | 
			
		||||
                        _log.Warn(ex);
 | 
			
		||||
                    }
 | 
			
		||||
                    new ReplacementBuilder()
 | 
			
		||||
                        .WithDefault(Context)
 | 
			
		||||
                        .Build()
 | 
			
		||||
                        .Replace(crembed);
 | 
			
		||||
                    await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "")
 | 
			
		||||
                        .ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                await Context.Channel.SendMessageAsync($"`#{quote.Id}` 📣 " + quote.Text.SanitizeMentions());
 | 
			
		||||
@@ -118,29 +114,27 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                { 
 | 
			
		||||
                    var qfromid = uow.Quotes.Get(id);
 | 
			
		||||
                    CREmbed crembed;
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                    if (qfromid == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        await Context.Channel.SendErrorAsync(GetText("quotes_notfound"));
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (CREmbed.TryParse(qfromid.Text, out crembed))
 | 
			
		||||
                    else if (CREmbed.TryParse(qfromid.Text, out var crembed))
 | 
			
		||||
                    {
 | 
			
		||||
                        try 
 | 
			
		||||
                        {
 | 
			
		||||
                            await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "")
 | 
			
		||||
                                .ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                        catch (Exception ex)
 | 
			
		||||
                        {
 | 
			
		||||
                            _log.Warn("Sending CREmbed failed");
 | 
			
		||||
                            _log.Warn(ex);    
 | 
			
		||||
                        } 
 | 
			
		||||
                        return;
 | 
			
		||||
                        new ReplacementBuilder()
 | 
			
		||||
                            .WithDefault(Context)
 | 
			
		||||
                            .Build()
 | 
			
		||||
                            .Replace(crembed);
 | 
			
		||||
 | 
			
		||||
                        await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "")
 | 
			
		||||
                            .ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    else { await Context.Channel.SendMessageAsync($"`#{qfromid.Id}` 🗯️ " + qfromid.Keyword.ToLowerInvariant().SanitizeMentions() + ":  " +
 | 
			
		||||
                                                       qfromid.Text.SanitizeMentions()); }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        await Context.Channel.SendMessageAsync($"`#{qfromid.Id}` 🗯️ " + qfromid.Keyword.ToLowerInvariant().SanitizeMentions() + ":  " +
 | 
			
		||||
                                                    qfromid.Text.SanitizeMentions());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            }        
 | 
			
		||||
                          
 | 
			
		||||
 
 | 
			
		||||
@@ -199,7 +199,7 @@ namespace NadekoBot
 | 
			
		||||
                var muteService = new MuteService(Client, AllGuildConfigs, Db);
 | 
			
		||||
                var ratelimitService = new SlowmodeService(Client, AllGuildConfigs);
 | 
			
		||||
                var protectionService = new ProtectionService(Client, AllGuildConfigs, muteService);
 | 
			
		||||
                var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService);
 | 
			
		||||
                var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService, Db);
 | 
			
		||||
                var gameVcService = new GameVoiceChannelService(Client, Db, AllGuildConfigs);
 | 
			
		||||
                var autoAssignRoleService = new AutoAssignRoleService(Client, AllGuildConfigs);
 | 
			
		||||
                var logCommandService = new LogCommandService(Client, Strings, AllGuildConfigs, Db, muteService, protectionService);
 | 
			
		||||
 
 | 
			
		||||
@@ -1377,15 +1377,6 @@
 | 
			
		||||
  <data name="typeadd_usage" xml:space="preserve">
 | 
			
		||||
    <value>`{0}typeadd wordswords`</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="poll_cmd" xml:space="preserve">
 | 
			
		||||
    <value>poll</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="poll_desc" xml:space="preserve">
 | 
			
		||||
    <value>Creates a poll which requires users to send the number of the voting option to the bot.</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="poll_usage" xml:space="preserve">
 | 
			
		||||
    <value>`{0}poll Question?;Answer1;Answ 2;A_3`</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="pollend_cmd" xml:space="preserve">
 | 
			
		||||
    <value>pollend</value>
 | 
			
		||||
  </data>
 | 
			
		||||
@@ -2583,13 +2574,13 @@
 | 
			
		||||
  <data name="togethertube_usage" xml:space="preserve">
 | 
			
		||||
    <value>`{0}totube`</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="publicpoll_cmd" xml:space="preserve">
 | 
			
		||||
    <value>publicpoll ppoll</value>
 | 
			
		||||
  <data name="poll_cmd" xml:space="preserve">
 | 
			
		||||
    <value>poll ppoll</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="publicpoll_desc" xml:space="preserve">
 | 
			
		||||
  <data name="poll_desc" xml:space="preserve">
 | 
			
		||||
    <value>Creates a public poll which requires users to type a number of the voting option in the channel command is ran in.</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="publicpoll_usage" xml:space="preserve">
 | 
			
		||||
  <data name="poll_usage" xml:space="preserve">
 | 
			
		||||
    <value>`{0}ppoll Question?;Answer1;Answ 2;A_3`</value>
 | 
			
		||||
  </data>
 | 
			
		||||
  <data name="autotranslang_cmd" xml:space="preserve">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +1,65 @@
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.DataStructures.Replacements;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Services.Music;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    //todo 99 - Could make a placeholder service, which can work for any module
 | 
			
		||||
    //and have replacements which are dependent on the types provided in the constructor
 | 
			
		||||
    public class PlayingRotateService
 | 
			
		||||
    {
 | 
			
		||||
        public List<PlayingStatus> RotatingStatusMessages { get; }
 | 
			
		||||
        public volatile bool RotatingStatuses;
 | 
			
		||||
        private readonly Timer _t;
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
        private readonly BotConfig _bc;
 | 
			
		||||
        private readonly MusicService _music;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly Replacer _rep;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
        public BotConfig BotConfig { get; private set; } //todo load whole botconifg, not just for this service when you have the time
 | 
			
		||||
 | 
			
		||||
        private class TimerState
 | 
			
		||||
        {
 | 
			
		||||
            public int Index { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public PlayingRotateService(DiscordSocketClient client, BotConfig bc, MusicService music)
 | 
			
		||||
        public PlayingRotateService(DiscordSocketClient client, BotConfig bc, MusicService music, DbService db)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _bc = bc;
 | 
			
		||||
            BotConfig = bc;
 | 
			
		||||
            _music = music;
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _rep = new ReplacementBuilder()
 | 
			
		||||
                .WithClient(client)
 | 
			
		||||
                .WithStats(client)
 | 
			
		||||
                .WithMusic(music)
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            RotatingStatusMessages = _bc.RotatingStatusMessages;
 | 
			
		||||
            RotatingStatuses = _bc.RotatingStatuses;
 | 
			
		||||
            
 | 
			
		||||
            _t = new Timer(async (objState) =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        BotConfig = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                    }
 | 
			
		||||
                    var state = (TimerState)objState;
 | 
			
		||||
                    if (!RotatingStatuses)
 | 
			
		||||
                    if (!BotConfig.RotatingStatuses)
 | 
			
		||||
                        return;
 | 
			
		||||
                    if (state.Index >= RotatingStatusMessages.Count)
 | 
			
		||||
                    if (state.Index >= BotConfig.RotatingStatusMessages.Count)
 | 
			
		||||
                        state.Index = 0;
 | 
			
		||||
 | 
			
		||||
                    if (!RotatingStatusMessages.Any())
 | 
			
		||||
                    if (!BotConfig.RotatingStatusMessages.Any())
 | 
			
		||||
                        return;
 | 
			
		||||
                    var status = RotatingStatusMessages[state.Index++].Status;
 | 
			
		||||
                    var status = BotConfig.RotatingStatusMessages[state.Index++].Status;
 | 
			
		||||
                    if (string.IsNullOrWhiteSpace(status))
 | 
			
		||||
                        return;
 | 
			
		||||
                    PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(_client, _music)));
 | 
			
		||||
                    ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(client)));
 | 
			
		||||
 | 
			
		||||
                    status = _rep.Replace(status);
 | 
			
		||||
 | 
			
		||||
                    try { await client.SetGameAsync(status).ConfigureAwait(false); }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
@@ -66,31 +72,5 @@ namespace NadekoBot.Services.Administration
 | 
			
		||||
                }
 | 
			
		||||
            }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Dictionary<string, Func<DiscordSocketClient, MusicService, string>> PlayingPlaceholders { get; } =
 | 
			
		||||
            new Dictionary<string, Func<DiscordSocketClient, MusicService, string>> {
 | 
			
		||||
                    { "%servers%", (c, ms) => c.Guilds.Count.ToString()},
 | 
			
		||||
                    { "%users%", (c, ms) => c.Guilds.Sum(s => s.Users.Count).ToString()},
 | 
			
		||||
                    { "%playing%", (c, ms) => {
 | 
			
		||||
                            var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
 | 
			
		||||
                            if (cnt != 1) return cnt.ToString();
 | 
			
		||||
                            try {
 | 
			
		||||
                                var mp = ms.MusicPlayers.FirstOrDefault();
 | 
			
		||||
                                return mp.Value.CurrentSong.SongInfo.Title;
 | 
			
		||||
                            }
 | 
			
		||||
                            catch {
 | 
			
		||||
                                return "No songs";
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    { "%queued%", (c, ms) => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
 | 
			
		||||
                    { "%time%", (c, ms) => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        public Dictionary<string, Func<DiscordSocketClient, string>> ShardSpecificPlaceholders { get; } =
 | 
			
		||||
            new Dictionary<string, Func<DiscordSocketClient, string>> {
 | 
			
		||||
                    { "%shardid%", (client) => client.ShardId.ToString()},
 | 
			
		||||
                    { "%shardguilds%", (client) => client.Guilds.Count.ToString()},
 | 
			
		||||
            };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ using AngleSharp.Dom.Html;
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NadekoBot.DataStructures.Replacements;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System;
 | 
			
		||||
@@ -15,62 +16,12 @@ namespace NadekoBot.Services.CustomReactions
 | 
			
		||||
{
 | 
			
		||||
    public static class Extensions
 | 
			
		||||
    {
 | 
			
		||||
        public static Dictionary<string, Func<IUserMessage, string, string>> responsePlaceholders = new Dictionary<string, Func<IUserMessage, string, string>>()
 | 
			
		||||
        {
 | 
			
		||||
            {"%target%", (ctx, trigger) => { return ctx.Content.Substring(trigger.Length).Trim().SanitizeMentions(); } },
 | 
			
		||||
            {"%rnduser%", (ctx, client) => {
 | 
			
		||||
                //var ch = ctx.Channel as ITextChannel;
 | 
			
		||||
                //if(ch == null)
 | 
			
		||||
                //    return "";
 | 
			
		||||
 | 
			
		||||
                //var g = ch.Guild as SocketGuild;
 | 
			
		||||
                //if(g == null)
 | 
			
		||||
                //    return "";
 | 
			
		||||
                //try {
 | 
			
		||||
                //    var usr = g.Users.Skip(new NadekoRandom().Next(0, g.Users.Count)).FirstOrDefault();
 | 
			
		||||
                //    return usr.Mention;
 | 
			
		||||
                //}
 | 
			
		||||
                //catch {
 | 
			
		||||
                return "[%rnduser% is temp. disabled]";
 | 
			
		||||
                //}
 | 
			
		||||
 | 
			
		||||
                //var users = g.Users.ToArray();
 | 
			
		||||
 | 
			
		||||
                //return users[new NadekoRandom().Next(0, users.Length-1)].Mention;
 | 
			
		||||
            } },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        public static Dictionary<string, Func<IUserMessage, DiscordSocketClient, string>> placeholders = new Dictionary<string, Func<IUserMessage, DiscordSocketClient, string>>()
 | 
			
		||||
        {
 | 
			
		||||
            {"%mention%", (ctx, client) => { return $"<@{client.CurrentUser.Id}>"; } },
 | 
			
		||||
            {"%user%", (ctx, client) => { return ctx.Author.Mention; } },
 | 
			
		||||
            //{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
 | 
			
		||||
        private static readonly Regex imgRegex = new Regex("%(img|image):(?<tag>.*?)%", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
        private static readonly NadekoRandom rng = new NadekoRandom();
 | 
			
		||||
 | 
			
		||||
        public static Dictionary<Regex, Func<Match, Task<string>>> regexPlaceholders = new Dictionary<Regex, Func<Match, Task<string>>>()
 | 
			
		||||
        {
 | 
			
		||||
            { rngRegex, (match) => {
 | 
			
		||||
                int from = 0;
 | 
			
		||||
                int.TryParse(match.Groups["from"].ToString(), out from);
 | 
			
		||||
 | 
			
		||||
                int to = 0;
 | 
			
		||||
                int.TryParse(match.Groups["to"].ToString(), out to);
 | 
			
		||||
 | 
			
		||||
                if(from == 0 && to == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    return Task.FromResult(rng.Next(0, 11).ToString());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if(from >= to)
 | 
			
		||||
                    return Task.FromResult(string.Empty);
 | 
			
		||||
 | 
			
		||||
                return Task.FromResult(rng.Next(from,to+1).ToString());
 | 
			
		||||
            } },
 | 
			
		||||
            { imgRegex, async (match) => {
 | 
			
		||||
                var tag = match.Groups["tag"].ToString();
 | 
			
		||||
                if(string.IsNullOrWhiteSpace(tag))
 | 
			
		||||
@@ -96,29 +47,24 @@ namespace NadekoBot.Services.CustomReactions
 | 
			
		||||
 | 
			
		||||
        private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordSocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var ph in placeholders)
 | 
			
		||||
            {
 | 
			
		||||
                if (str.Contains(ph.Key))
 | 
			
		||||
                    str = str.ToLowerInvariant().Replace(ph.Key, ph.Value(ctx, client));
 | 
			
		||||
            }
 | 
			
		||||
            var rep = new ReplacementBuilder()
 | 
			
		||||
                .WithUser(ctx.Author)
 | 
			
		||||
                .WithClient(client)
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            str = rep.Replace(str.ToLowerInvariant());
 | 
			
		||||
 | 
			
		||||
            return str;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static async Task<string> ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordSocketClient client, string resolvedTrigger)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var ph in placeholders)
 | 
			
		||||
            {
 | 
			
		||||
                var lowerKey = ph.Key.ToLowerInvariant();
 | 
			
		||||
                if (str.Contains(lowerKey))
 | 
			
		||||
                    str = str.Replace(lowerKey, ph.Value(ctx, client));
 | 
			
		||||
            }
 | 
			
		||||
            var rep = new ReplacementBuilder()
 | 
			
		||||
                .WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client)
 | 
			
		||||
                .WithOverride("%target%", () => ctx.Content.Substring(resolvedTrigger.Length).Trim())
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            foreach (var ph in responsePlaceholders)
 | 
			
		||||
            {
 | 
			
		||||
                var lowerKey = ph.Key.ToLowerInvariant();
 | 
			
		||||
                if (str.Contains(lowerKey))
 | 
			
		||||
                    str = str.Replace(lowerKey, ph.Value(ctx, resolvedTrigger));
 | 
			
		||||
            }
 | 
			
		||||
            str = rep.Replace(str.ToLowerInvariant());
 | 
			
		||||
 | 
			
		||||
            foreach (var ph in regexPlaceholders)
 | 
			
		||||
            {
 | 
			
		||||
@@ -133,17 +79,24 @@ namespace NadekoBot.Services.CustomReactions
 | 
			
		||||
        public static Task<string > ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client)
 | 
			
		||||
            => cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client));
 | 
			
		||||
 | 
			
		||||
        public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage context, DiscordSocketClient client, CustomReactionsService crs)
 | 
			
		||||
        public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, CustomReactionsService crs)
 | 
			
		||||
        {
 | 
			
		||||
            var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel;
 | 
			
		||||
            var channel = cr.DmResponse ? await ctx.Author.CreateDMChannelAsync() : ctx.Channel;
 | 
			
		||||
 | 
			
		||||
            crs.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old);
 | 
			
		||||
 | 
			
		||||
            if (CREmbed.TryParse(cr.Response, out CREmbed crembed))
 | 
			
		||||
            {
 | 
			
		||||
                return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "");
 | 
			
		||||
                var rep = new ReplacementBuilder()
 | 
			
		||||
                    .WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client)
 | 
			
		||||
                    .WithOverride("%target%", () => ctx.Content.Substring(cr.Trigger.ResolveTriggerString(ctx, client).Length).Trim())
 | 
			
		||||
                    .Build();
 | 
			
		||||
 | 
			
		||||
                rep.Replace(crembed);
 | 
			
		||||
 | 
			
		||||
                return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "");
 | 
			
		||||
            }
 | 
			
		||||
            return await channel.SendMessageAsync((await cr.ResponseWithContextAsync(context, client)).SanitizeMentions());
 | 
			
		||||
            return await channel.SendMessageAsync((await cr.ResponseWithContextAsync(ctx, client)).SanitizeMentions());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NadekoBot.DataStructures.Replacements;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
@@ -45,15 +46,17 @@ namespace NadekoBot.Services
 | 
			
		||||
 | 
			
		||||
                    if (channel == null) //maybe warn the server owner that the channel is missing
 | 
			
		||||
                        return;
 | 
			
		||||
                    CREmbed embedData;
 | 
			
		||||
                    if (CREmbed.TryParse(conf.ChannelByeMessageText, out embedData))
 | 
			
		||||
 | 
			
		||||
                    var rep = new ReplacementBuilder()
 | 
			
		||||
                        .WithDefault(user, channel, user.Guild, _client)
 | 
			
		||||
                        .Build();
 | 
			
		||||
 | 
			
		||||
                    if (CREmbed.TryParse(conf.ChannelByeMessageText, out var embedData))
 | 
			
		||||
                    {
 | 
			
		||||
                        embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                        embedData.Description = embedData.Description?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                        embedData.Title = embedData.Title?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                        rep.Replace(embedData);
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                            var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText?.SanitizeMentions() ?? "").ConfigureAwait(false);
 | 
			
		||||
                            if (conf.AutoDeleteByeMessagesTimer > 0)
 | 
			
		||||
                            {
 | 
			
		||||
                                toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
 | 
			
		||||
@@ -63,7 +66,7 @@ namespace NadekoBot.Services
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                        var msg = rep.Replace(conf.ChannelByeMessageText);
 | 
			
		||||
                        if (string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                            return;
 | 
			
		||||
                        try
 | 
			
		||||
@@ -98,16 +101,16 @@ namespace NadekoBot.Services
 | 
			
		||||
                        var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId);
 | 
			
		||||
                        if (channel != null) //maybe warn the server owner that the channel is missing
 | 
			
		||||
                        {
 | 
			
		||||
                            var rep = new ReplacementBuilder()
 | 
			
		||||
                                .WithDefault(user, channel, user.Guild, _client)
 | 
			
		||||
                                .Build();
 | 
			
		||||
 | 
			
		||||
                            CREmbed embedData;
 | 
			
		||||
                            if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
 | 
			
		||||
                            if (CREmbed.TryParse(conf.ChannelGreetMessageText, out var embedData))
 | 
			
		||||
                            {
 | 
			
		||||
                                embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                embedData.Description = embedData.Description?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                rep.Replace(embedData);
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                                    var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText?.SanitizeMentions() ?? "").ConfigureAwait(false);
 | 
			
		||||
                                    if (conf.AutoDeleteGreetMessagesTimer > 0)
 | 
			
		||||
                                    {
 | 
			
		||||
                                        toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
 | 
			
		||||
@@ -117,7 +120,7 @@ namespace NadekoBot.Services
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                var msg = rep.Replace(conf.ChannelGreetMessageText);
 | 
			
		||||
                                if (!string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                                {
 | 
			
		||||
                                    try
 | 
			
		||||
@@ -140,21 +143,22 @@ namespace NadekoBot.Services
 | 
			
		||||
 | 
			
		||||
                        if (channel != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            CREmbed embedData;
 | 
			
		||||
                            if (CREmbed.TryParse(conf.DmGreetMessageText, out embedData))
 | 
			
		||||
                            var rep = new ReplacementBuilder()
 | 
			
		||||
                                .WithDefault(user, channel, user.Guild, _client)
 | 
			
		||||
                                .Build();
 | 
			
		||||
                            if (CREmbed.TryParse(conf.DmGreetMessageText, out var embedData))
 | 
			
		||||
                            {
 | 
			
		||||
                                embedData.PlainText = embedData.PlainText?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                embedData.Description = embedData.Description?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
 | 
			
		||||
                                rep.Replace(embedData);
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                                    await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText?.SanitizeMentions() ?? "").ConfigureAwait(false);
 | 
			
		||||
                                }
 | 
			
		||||
                                catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                var msg = conf.DmGreetMessageText.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                var msg = rep.Replace(conf.DmGreetMessageText);
 | 
			
		||||
                                if (!string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                                {
 | 
			
		||||
                                    await channel.SendConfirmAsync(msg).ConfigureAwait(false);
 | 
			
		||||
@@ -409,4 +413,4 @@ namespace NadekoBot.Services
 | 
			
		||||
            ChannelByeMessageText = g.ChannelByeMessageText,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.DataStructures.Replacements;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
@@ -20,13 +21,6 @@ namespace NadekoBot.Services.Utility
 | 
			
		||||
 | 
			
		||||
        public string RemindMessageFormat { get; }
 | 
			
		||||
 | 
			
		||||
        public readonly IDictionary<string, Func<Reminder, string>> _replacements = new Dictionary<string, Func<Reminder, string>>
 | 
			
		||||
            {
 | 
			
		||||
                { "%message%" , (r) => r.Message },
 | 
			
		||||
                { "%user%", (r) => $"<@!{r.UserId}>" },
 | 
			
		||||
                { "%target%", (r) =>  r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly CancellationTokenSource cancelSource;
 | 
			
		||||
        private readonly CancellationToken cancelAllToken;
 | 
			
		||||
@@ -82,11 +76,13 @@ namespace NadekoBot.Services.Utility
 | 
			
		||||
                if (ch == null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                await ch.SendMessageAsync(
 | 
			
		||||
                    _replacements.Aggregate(RemindMessageFormat,
 | 
			
		||||
                        (cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
 | 
			
		||||
                                             .SanitizeMentions()
 | 
			
		||||
                        ).ConfigureAwait(false); //it works trust me
 | 
			
		||||
                var rep = new ReplacementBuilder()
 | 
			
		||||
                    .WithOverride("%user%", () => $"<@!{r.UserId}>")
 | 
			
		||||
                    .WithOverride("%message%", () => r.Message)
 | 
			
		||||
                    .WithOverride("%target%", () => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>")
 | 
			
		||||
                    .Build();
 | 
			
		||||
 | 
			
		||||
                await ch.SendMessageAsync(rep.Replace(RemindMessageFormat).SanitizeMentions()).ConfigureAwait(false); //it works trust me
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
            finally
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user