mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 08:34:27 -05:00 
			
		
		
		
	More common refactorings like renaming variables, removing empty statements and unused variables, etc
This commit is contained in:
		@@ -10,7 +10,7 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton
 | 
			
		||||
    private readonly ITypeInspector _innerTypeDescriptor;
 | 
			
		||||
 | 
			
		||||
    public CommentGatheringTypeInspector(ITypeInspector innerTypeDescriptor)
 | 
			
		||||
        => this._innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
 | 
			
		||||
        => _innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
 | 
			
		||||
 | 
			
		||||
    public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
 | 
			
		||||
        => _innerTypeDescriptor.GetProperties(type, container).Select(d => new CommentsPropertyDescriptor(d));
 | 
			
		||||
@@ -43,7 +43,7 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton
 | 
			
		||||
 | 
			
		||||
        public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
 | 
			
		||||
        {
 | 
			
		||||
            this._baseDescriptor = baseDescriptor;
 | 
			
		||||
            _baseDescriptor = baseDescriptor;
 | 
			
		||||
            Name = baseDescriptor.Name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ public sealed class CommentsObjectDescriptor : IObjectDescriptor
 | 
			
		||||
 | 
			
		||||
    public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
 | 
			
		||||
    {
 | 
			
		||||
        this._innerDescriptor = innerDescriptor;
 | 
			
		||||
        _innerDescriptor = innerDescriptor;
 | 
			
		||||
        Comment = comment;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,7 +9,7 @@ public static class QuoteExtensions
 | 
			
		||||
    public static IEnumerable<Quote> GetForGuild(this DbSet<Quote> quotes, ulong guildId)
 | 
			
		||||
        => quotes.AsQueryable().Where(x => x.GuildId == guildId);
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<Quote> GetGroup(
 | 
			
		||||
    public static IReadOnlyCollection<Quote> GetGroup(
 | 
			
		||||
        this DbSet<Quote> quotes,
 | 
			
		||||
        ulong guildId,
 | 
			
		||||
        int page,
 | 
			
		||||
 
 | 
			
		||||
@@ -361,7 +361,7 @@ WHERE GuildId={guildId}
 | 
			
		||||
                  .ToArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public (IEnumerable<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(
 | 
			
		||||
    public (IReadOnlyCollection<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(
 | 
			
		||||
        SocketGuild guild,
 | 
			
		||||
        string people)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ public partial class Gambling
 | 
			
		||||
        [NadekoOptionsAttribute(typeof(RaceOptions))]
 | 
			
		||||
        public partial Task Race(params string[] args)
 | 
			
		||||
        {
 | 
			
		||||
            var (options, success) = OptionsParser.ParseFrom(new RaceOptions(), args);
 | 
			
		||||
            var (options, _) = OptionsParser.ParseFrom(new RaceOptions(), args);
 | 
			
		||||
 | 
			
		||||
            var ar = new AnimalRace(options, _cs, _gamesConf.Data.RaceAnimals.Shuffle());
 | 
			
		||||
            if (!_service.AnimalRaces.TryAdd(ctx.Guild.Id, ar))
 | 
			
		||||
@@ -46,7 +46,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            var count = 0;
 | 
			
		||||
 | 
			
		||||
            Task _client_MessageReceived(SocketMessage arg)
 | 
			
		||||
            Task ClientMessageReceived(SocketMessage arg)
 | 
			
		||||
            {
 | 
			
		||||
                _= Task.Run(() =>
 | 
			
		||||
                {
 | 
			
		||||
@@ -61,9 +61,9 @@ public partial class Gambling
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Task Ar_OnEnded(AnimalRace race)
 | 
			
		||||
            Task ArOnEnded(AnimalRace race)
 | 
			
		||||
            {
 | 
			
		||||
                _client.MessageReceived -= _client_MessageReceived;
 | 
			
		||||
                _client.MessageReceived -= ClientMessageReceived;
 | 
			
		||||
                _service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
 | 
			
		||||
                var winner = race.FinishedUsers[0];
 | 
			
		||||
                if (race.FinishedUsers[0].Bet > 0)
 | 
			
		||||
@@ -77,9 +77,9 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            ar.OnStartingFailed += Ar_OnStartingFailed;
 | 
			
		||||
            ar.OnStateUpdate += Ar_OnStateUpdate;
 | 
			
		||||
            ar.OnEnded += Ar_OnEnded;
 | 
			
		||||
            ar.OnEnded += ArOnEnded;
 | 
			
		||||
            ar.OnStarted += Ar_OnStarted;
 | 
			
		||||
            _client.MessageReceived += _client_MessageReceived;
 | 
			
		||||
            _client.MessageReceived += ClientMessageReceived;
 | 
			
		||||
 | 
			
		||||
            return SendConfirmAsync(GetText(strs.animal_race),
 | 
			
		||||
                GetText(strs.animal_race_starting(options.StartTime)),
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ public partial class Gambling
 | 
			
		||||
            if (!await CheckBetMandatory(amount))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var newBj = new Blackjack(_cs, _db);
 | 
			
		||||
            var newBj = new Blackjack(_cs);
 | 
			
		||||
            Blackjack bj;
 | 
			
		||||
            if (newBj == (bj = _service.Games.GetOrAdd(ctx.Channel.Id, newBj)))
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,16 +21,14 @@ public class Blackjack
 | 
			
		||||
    public GameState State { get; set; } = GameState.Starting;
 | 
			
		||||
    public User CurrentUser { get; private set; }
 | 
			
		||||
 | 
			
		||||
    private TaskCompletionSource<bool> _currentUserMove;
 | 
			
		||||
    private TaskCompletionSource<bool> currentUserMove;
 | 
			
		||||
    private readonly ICurrencyService _cs;
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
 | 
			
		||||
    private readonly SemaphoreSlim _locker = new(1, 1);
 | 
			
		||||
 | 
			
		||||
    public Blackjack(ICurrencyService cs, DbService db)
 | 
			
		||||
    public Blackjack(ICurrencyService cs)
 | 
			
		||||
    {
 | 
			
		||||
        _cs = cs;
 | 
			
		||||
        _db = db;
 | 
			
		||||
        Dealer = new();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -58,7 +56,7 @@ public class Blackjack
 | 
			
		||||
            if (!Players.Any())
 | 
			
		||||
            {
 | 
			
		||||
                State = GameState.Ended;
 | 
			
		||||
                var end = GameEnded?.Invoke(this);
 | 
			
		||||
                _ = GameEnded?.Invoke(this);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -101,14 +99,14 @@ public class Blackjack
 | 
			
		||||
    {
 | 
			
		||||
        var pause = Task.Delay(20000); //10 seconds to decide
 | 
			
		||||
        CurrentUser = usr;
 | 
			
		||||
        _currentUserMove = new();
 | 
			
		||||
        currentUserMove = new();
 | 
			
		||||
        await PrintState();
 | 
			
		||||
        // either wait for the user to make an action and
 | 
			
		||||
        // if he doesn't - stand
 | 
			
		||||
        var finished = await Task.WhenAny(pause, _currentUserMove.Task);
 | 
			
		||||
        var finished = await Task.WhenAny(pause, currentUserMove.Task);
 | 
			
		||||
        if (finished == pause) await Stand(usr);
 | 
			
		||||
        CurrentUser = null;
 | 
			
		||||
        _currentUserMove = null;
 | 
			
		||||
        currentUserMove = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> Join(IUser user, long bet)
 | 
			
		||||
@@ -156,7 +154,7 @@ public class Blackjack
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            u.State = User.UserState.Stand;
 | 
			
		||||
            _currentUserMove.TrySetResult(true);
 | 
			
		||||
            currentUserMove.TrySetResult(true);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
@@ -252,7 +250,7 @@ public class Blackjack
 | 
			
		||||
            else
 | 
			
		||||
                //with double you just get one card, and then you're done
 | 
			
		||||
                u.State = User.UserState.Stand;
 | 
			
		||||
            _currentUserMove.TrySetResult(true);
 | 
			
		||||
            currentUserMove.TrySetResult(true);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
@@ -292,7 +290,7 @@ public class Blackjack
 | 
			
		||||
                // user busted
 | 
			
		||||
                u.State = User.UserState.Bust;
 | 
			
		||||
 | 
			
		||||
            _currentUserMove.TrySetResult(true);
 | 
			
		||||
            currentUserMove.TrySetResult(true);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
    private readonly ICurrencyService _cs;
 | 
			
		||||
    private readonly NadekoRandom _rng;
 | 
			
		||||
 | 
			
		||||
    private Timer _playerTimeoutTimer;
 | 
			
		||||
    private Timer playerTimeoutTimer;
 | 
			
		||||
 | 
			
		||||
    /* [ ][ ][ ][ ][ ][ ]
 | 
			
		||||
     * [ ][ ][ ][ ][ ][ ]
 | 
			
		||||
@@ -133,7 +133,7 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CurrentPhase = Phase.P1Move; //start the game
 | 
			
		||||
            _playerTimeoutTimer = new(async _ =>
 | 
			
		||||
            playerTimeoutTimer = new(async _ =>
 | 
			
		||||
                {
 | 
			
		||||
                    await _locker.WaitAsync();
 | 
			
		||||
                    try
 | 
			
		||||
@@ -330,7 +330,7 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void ResetTimer()
 | 
			
		||||
        => _playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer),
 | 
			
		||||
        => playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer),
 | 
			
		||||
            TimeSpan.FromSeconds(_options.TurnTimer));
 | 
			
		||||
 | 
			
		||||
    private void EndGame(Result result, ulong? winId)
 | 
			
		||||
@@ -369,7 +369,7 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
        OnGameFailedToStart = null;
 | 
			
		||||
        OnGameStateUpdated = null;
 | 
			
		||||
        OnGameEnded = null;
 | 
			
		||||
        _playerTimeoutTimer?.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
        playerTimeoutTimer?.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,19 +11,19 @@ public partial class Gambling
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class Connect4Commands : GamblingSubmodule<GamblingService>
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly string[] numbers =
 | 
			
		||||
        private static readonly string[] _numbers =
 | 
			
		||||
        {
 | 
			
		||||
            ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private int RepostCounter
 | 
			
		||||
        {
 | 
			
		||||
            get => _repostCounter;
 | 
			
		||||
            get => repostCounter;
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                if (value is < 0 or > 7)
 | 
			
		||||
                    _repostCounter = 0;
 | 
			
		||||
                else _repostCounter = value;
 | 
			
		||||
                    repostCounter = 0;
 | 
			
		||||
                else repostCounter = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -32,7 +32,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
        private IUserMessage msg;
 | 
			
		||||
 | 
			
		||||
        private int _repostCounter;
 | 
			
		||||
        private int repostCounter;
 | 
			
		||||
 | 
			
		||||
        public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
 | 
			
		||||
            : base(gamb)
 | 
			
		||||
@@ -59,7 +59,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
                newGame.Dispose();
 | 
			
		||||
                //means game already exists, try to join
 | 
			
		||||
                var joined = await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet);
 | 
			
		||||
                await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -73,9 +73,9 @@ public partial class Gambling
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            game.OnGameStateUpdated += Game_OnGameStateUpdated;
 | 
			
		||||
            game.OnGameFailedToStart += Game_OnGameFailedToStart;
 | 
			
		||||
            game.OnGameEnded += Game_OnGameEnded;
 | 
			
		||||
            _client.MessageReceived += _client_MessageReceived;
 | 
			
		||||
            game.OnGameFailedToStart += GameOnGameFailedToStart;
 | 
			
		||||
            game.OnGameEnded += GameOnGameEnded;
 | 
			
		||||
            _client.MessageReceived += ClientMessageReceived;
 | 
			
		||||
 | 
			
		||||
            game.Initialize();
 | 
			
		||||
            if (options.Bet == 0)
 | 
			
		||||
@@ -83,7 +83,7 @@ public partial class Gambling
 | 
			
		||||
            else
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.connect4_created_bet(options.Bet + CurrencySign));
 | 
			
		||||
 | 
			
		||||
            Task _client_MessageReceived(SocketMessage arg)
 | 
			
		||||
            Task ClientMessageReceived(SocketMessage arg)
 | 
			
		||||
            {
 | 
			
		||||
                if (ctx.Channel.Id != arg.Channel.Id)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
@@ -110,22 +110,22 @@ public partial class Gambling
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Task Game_OnGameFailedToStart(Connect4Game arg)
 | 
			
		||||
            Task GameOnGameFailedToStart(Connect4Game arg)
 | 
			
		||||
            {
 | 
			
		||||
                if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
 | 
			
		||||
                {
 | 
			
		||||
                    _client.MessageReceived -= _client_MessageReceived;
 | 
			
		||||
                    _client.MessageReceived -= ClientMessageReceived;
 | 
			
		||||
                    toDispose.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return ErrorLocalizedAsync(strs.connect4_failed_to_start);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Task Game_OnGameEnded(Connect4Game arg, Connect4Game.Result result)
 | 
			
		||||
            Task GameOnGameEnded(Connect4Game arg, Connect4Game.Result result)
 | 
			
		||||
            {
 | 
			
		||||
                if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
 | 
			
		||||
                {
 | 
			
		||||
                    _client.MessageReceived -= _client_MessageReceived;
 | 
			
		||||
                    _client.MessageReceived -= ClientMessageReceived;
 | 
			
		||||
                    toDispose.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -185,7 +185,7 @@ public partial class Gambling
 | 
			
		||||
                sb.AppendLine();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < Connect4Game.NUMBER_OF_COLUMNS; i++) sb.Append(numbers[i]);
 | 
			
		||||
            for (var i = 0; i < Connect4Game.NUMBER_OF_COLUMNS; i++) sb.Append(_numbers[i]);
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@ public partial class Gambling
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class DiceRollCommands : NadekoSubmodule
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly Regex dndRegex = new(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$",
 | 
			
		||||
        private static readonly Regex _dndRegex = new(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$",
 | 
			
		||||
            RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
        private static readonly Regex fudgeRegex = new(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
 | 
			
		||||
        private static readonly Regex _fudgeRegex = new(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
        private static readonly char[] _fateRolls = { '-', ' ', '+' };
 | 
			
		||||
        private readonly IImageCache _images;
 | 
			
		||||
@@ -115,7 +115,7 @@ public partial class Gambling
 | 
			
		||||
        private async Task InternallDndRoll(string arg, bool ordered)
 | 
			
		||||
        {
 | 
			
		||||
            Match match;
 | 
			
		||||
            if ((match = fudgeRegex.Match(arg)).Length != 0
 | 
			
		||||
            if ((match = _fudgeRegex.Match(arg)).Length != 0
 | 
			
		||||
                && int.TryParse(match.Groups["n1"].ToString(), out var n1)
 | 
			
		||||
                && n1 is > 0 and < 500)
 | 
			
		||||
            {
 | 
			
		||||
@@ -134,7 +134,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
            }
 | 
			
		||||
            else if ((match = dndRegex.Match(arg)).Length != 0)
 | 
			
		||||
            else if ((match = _dndRegex.Match(arg)).Length != 0)
 | 
			
		||||
            {
 | 
			
		||||
                var rng = new NadekoRandom();
 | 
			
		||||
                if (int.TryParse(match.Groups["n1"].ToString(), out n1)
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@ public class GameStatusEvent : ICurrencyEvent
 | 
			
		||||
 | 
			
		||||
    private async Task OnMessageDeleted(Cacheable<IMessage, ulong> message, Cacheable<IMessageChannel, ulong> cacheable)
 | 
			
		||||
    {
 | 
			
		||||
        if (message.Id == this.msg.Id) await StopEvent();
 | 
			
		||||
        if (message.Id == msg.Id) await StopEvent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task StopEvent()
 | 
			
		||||
 
 | 
			
		||||
@@ -99,11 +99,11 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
    public async Task StartEvent()
 | 
			
		||||
    {
 | 
			
		||||
        if (Emote.TryParse(_config.Currency.Sign, out var parsedEmote))
 | 
			
		||||
            this.emote = parsedEmote;
 | 
			
		||||
            emote = parsedEmote;
 | 
			
		||||
        else
 | 
			
		||||
            this.emote = new Emoji(_config.Currency.Sign);
 | 
			
		||||
            emote = new Emoji(_config.Currency.Sign);
 | 
			
		||||
        msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize));
 | 
			
		||||
        await msg.AddReactionAsync(this.emote);
 | 
			
		||||
        await msg.AddReactionAsync(emote);
 | 
			
		||||
        _client.MessageDeleted += OnMessageDeleted;
 | 
			
		||||
        _client.ReactionAdded += HandleReaction;
 | 
			
		||||
        _t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
 | 
			
		||||
@@ -114,7 +114,7 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
 | 
			
		||||
    private async Task OnMessageDeleted(Cacheable<IMessage, ulong> message, Cacheable<IMessageChannel, ulong> cacheable)
 | 
			
		||||
    {
 | 
			
		||||
        if (message.Id == this.msg.Id) await StopEvent();
 | 
			
		||||
        if (message.Id == msg.Id) await StopEvent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task StopEvent()
 | 
			
		||||
@@ -151,7 +151,7 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
            if ((r.User.IsSpecified
 | 
			
		||||
                    ? r.User.Value
 | 
			
		||||
                    : null) is not IGuildUser gu // no unknown users, as they could be bots, or alts
 | 
			
		||||
                || message.Id != this.msg.Id // same message
 | 
			
		||||
                || message.Id != msg.Id // same message
 | 
			
		||||
                || gu.IsBot // no bots
 | 
			
		||||
                || (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5 // no recently created accounts
 | 
			
		||||
                || (_noRecentlyJoinedServer
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ public partial class Gambling
 | 
			
		||||
            Tails = 2
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static readonly NadekoRandom rng = new();
 | 
			
		||||
        private static readonly NadekoRandom _rng = new();
 | 
			
		||||
        private readonly IImageCache _images;
 | 
			
		||||
        private readonly ICurrencyService _cs;
 | 
			
		||||
 | 
			
		||||
@@ -47,9 +47,9 @@ public partial class Gambling
 | 
			
		||||
            var imgs = new Image<Rgba32>[count];
 | 
			
		||||
            for (var i = 0; i < count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var headsArr = _images.Heads[rng.Next(0, _images.Heads.Count)];
 | 
			
		||||
                var tailsArr = _images.Tails[rng.Next(0, _images.Tails.Count)];
 | 
			
		||||
                if (rng.Next(0, 10) < 5)
 | 
			
		||||
                var headsArr = _images.Heads[_rng.Next(0, _images.Heads.Count)];
 | 
			
		||||
                var tailsArr = _images.Tails[_rng.Next(0, _images.Tails.Count)];
 | 
			
		||||
                if (_rng.Next(0, 10) < 5)
 | 
			
		||||
                {
 | 
			
		||||
                    imgs[i] = Image.Load(headsArr);
 | 
			
		||||
                    headCount++;
 | 
			
		||||
@@ -90,21 +90,21 @@ public partial class Gambling
 | 
			
		||||
            BetFlipGuess result;
 | 
			
		||||
            Uri imageToSend;
 | 
			
		||||
            var coins = _images.ImageUrls.Coins;
 | 
			
		||||
            if (rng.Next(0, 1000) <= 499)
 | 
			
		||||
            if (_rng.Next(0, 1000) <= 499)
 | 
			
		||||
            {
 | 
			
		||||
                imageToSend = coins.Heads[rng.Next(0, coins.Heads.Length)];
 | 
			
		||||
                imageToSend = coins.Heads[_rng.Next(0, coins.Heads.Length)];
 | 
			
		||||
                result = BetFlipGuess.Heads;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                imageToSend = coins.Tails[rng.Next(0, coins.Tails.Length)];
 | 
			
		||||
                imageToSend = coins.Tails[_rng.Next(0, coins.Tails.Length)];
 | 
			
		||||
                result = BetFlipGuess.Tails;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            string str;
 | 
			
		||||
            if (guess == result)
 | 
			
		||||
            {
 | 
			
		||||
                var toWin = (long)(amount * _config.BetFlip.Multiplier);
 | 
			
		||||
                var toWin = (long)(amount * Config.BetFlip.Multiplier);
 | 
			
		||||
                str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_guess(toWin + CurrencySign));
 | 
			
		||||
                await _cs.AddAsync(ctx.User, "Betflip Gamble", toWin, false, true);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -100,8 +100,8 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
    [Cmd]
 | 
			
		||||
    public async partial Task Timely()
 | 
			
		||||
    {
 | 
			
		||||
        var val = _config.Timely.Amount;
 | 
			
		||||
        var period = _config.Timely.Cooldown;
 | 
			
		||||
        var val = Config.Timely.Amount;
 | 
			
		||||
        var period = Config.Timely.Cooldown;
 | 
			
		||||
        if (val <= 0 || period <= 0)
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.timely_none);
 | 
			
		||||
@@ -473,7 +473,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var br = new Betroll(_config.BetRoll);
 | 
			
		||||
        var br = new Betroll(Config.BetRoll);
 | 
			
		||||
 | 
			
		||||
        var result = br.Roll();
 | 
			
		||||
 | 
			
		||||
@@ -617,7 +617,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
                 || (pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors)
 | 
			
		||||
                 || (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper))
 | 
			
		||||
        {
 | 
			
		||||
            amount = (long)(amount * _config.BetFlip.Multiplier);
 | 
			
		||||
            amount = (long)(amount * Config.BetFlip.Multiplier);
 | 
			
		||||
            await _cs.AddAsync(ctx.User.Id, "Rps-win", amount, true);
 | 
			
		||||
            embed.WithOkColor();
 | 
			
		||||
            embed.AddField(GetText(strs.won), n(amount));
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,12 @@ namespace NadekoBot.Modules.Gambling.Services;
 | 
			
		||||
 | 
			
		||||
public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
 | 
			
		||||
{
 | 
			
		||||
    private const string FilePath = "data/gambling.yml";
 | 
			
		||||
    private static readonly TypedKey<GamblingConfig> changeKey = new("config.gambling.updated");
 | 
			
		||||
    public override string Name { get; } = "gambling";
 | 
			
		||||
    private const string FILE_PATH = "data/gambling.yml";
 | 
			
		||||
    private static readonly TypedKey<GamblingConfig> _changeKey = new("config.gambling.updated");
 | 
			
		||||
    public override string Name
 | 
			
		||||
        => "gambling";
 | 
			
		||||
 | 
			
		||||
    private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
 | 
			
		||||
    private readonly IEnumerable<WaifuItemModel> _antiGiftSeed = new[]
 | 
			
		||||
    {
 | 
			
		||||
        new WaifuItemModel("🥀", 100, "WiltedRose", true), new WaifuItemModel("✂️", 1000, "Haircut", true),
 | 
			
		||||
        new WaifuItemModel("🧻", 10000, "ToiletPaper", true)
 | 
			
		||||
@@ -18,7 +19,7 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public GamblingConfigService(IConfigSeria serializer, IPubSub pubSub)
 | 
			
		||||
        : base(FilePath, serializer, pubSub, changeKey)
 | 
			
		||||
        : base(FILE_PATH, serializer, pubSub, _changeKey)
 | 
			
		||||
    {
 | 
			
		||||
        AddParsedProp("currency.name", gs => gs.Currency.Name, ConfigParsers.String, ConfigPrinters.ToString);
 | 
			
		||||
        AddParsedProp("currency.sign", gs => gs.Currency.Sign, ConfigParsers.String, ConfigPrinters.ToString);
 | 
			
		||||
@@ -104,7 +105,7 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
 | 
			
		||||
        if (data.Version < 2)
 | 
			
		||||
            ModifyConfig(c =>
 | 
			
		||||
            {
 | 
			
		||||
                c.Waifu.Items = c.Waifu.Items.Concat(antiGiftSeed).ToList();
 | 
			
		||||
                c.Waifu.Items = c.Waifu.Items.Concat(_antiGiftSeed).ToList();
 | 
			
		||||
                c.Version = 2;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,14 @@ namespace NadekoBot.Modules.Gambling.Common;
 | 
			
		||||
 | 
			
		||||
public abstract class GamblingModule<TService> : NadekoModule<TService>
 | 
			
		||||
{
 | 
			
		||||
    protected GamblingConfig _config
 | 
			
		||||
    protected GamblingConfig Config
 | 
			
		||||
        => _lazyConfig.Value;
 | 
			
		||||
 | 
			
		||||
    protected string CurrencySign
 | 
			
		||||
        => _config.Currency.Sign;
 | 
			
		||||
        => Config.Currency.Sign;
 | 
			
		||||
 | 
			
		||||
    protected string CurrencyName
 | 
			
		||||
        => _config.Currency.Name;
 | 
			
		||||
        => Config.Currency.Name;
 | 
			
		||||
 | 
			
		||||
    private readonly Lazy<GamblingConfig> _lazyConfig;
 | 
			
		||||
 | 
			
		||||
@@ -22,15 +22,15 @@ public abstract class GamblingModule<TService> : NadekoModule<TService>
 | 
			
		||||
    private async Task<bool> InternalCheckBet(long amount)
 | 
			
		||||
    {
 | 
			
		||||
        if (amount < 1) return false;
 | 
			
		||||
        if (amount < _config.MinBet)
 | 
			
		||||
        if (amount < Config.MinBet)
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.min_bet_limit(Format.Bold(_config.MinBet.ToString()) + CurrencySign));
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.min_bet_limit(Format.Bold(Config.MinBet.ToString()) + CurrencySign));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (_config.MaxBet > 0 && amount > _config.MaxBet)
 | 
			
		||||
        if (Config.MaxBet > 0 && amount > Config.MaxBet)
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.max_bet_limit(Format.Bold(_config.MaxBet.ToString()) + CurrencySign));
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.max_bet_limit(Format.Bold(Config.MaxBet.ToString()) + CurrencySign));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ public partial class Gambling
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class PlantPickCommands : GamblingSubmodule<PlantPickService>
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ILogCommandService logService;
 | 
			
		||||
        private readonly ILogCommandService _logService;
 | 
			
		||||
 | 
			
		||||
        public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss)
 | 
			
		||||
            : base(gss)
 | 
			
		||||
            => this.logService = logService;
 | 
			
		||||
            => this._logService = logService;
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
@@ -32,7 +32,7 @@ public partial class Gambling
 | 
			
		||||
            if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    logService.AddDeleteIgnore(ctx.Message.Id);
 | 
			
		||||
                    _logService.AddDeleteIgnore(ctx.Message.Id);
 | 
			
		||||
                    await ctx.Message.DeleteAsync();
 | 
			
		||||
                }
 | 
			
		||||
                catch { }
 | 
			
		||||
@@ -49,7 +49,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
 | 
			
		||||
            {
 | 
			
		||||
                logService.AddDeleteIgnore(ctx.Message.Id);
 | 
			
		||||
                _logService.AddDeleteIgnore(ctx.Message.Id);
 | 
			
		||||
                await ctx.Message.DeleteAsync();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +90,7 @@ public partial class Gambling
 | 
			
		||||
            return ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                _ =>
 | 
			
		||||
                {
 | 
			
		||||
                    var items = enabledIn.Skip(page * 9).Take(9);
 | 
			
		||||
                    var items = enabledIn.Skip(page * 9).Take(9).ToList();
 | 
			
		||||
 | 
			
		||||
                    if (!items.Any())
 | 
			
		||||
                        return _eb.Create().WithErrorColor().WithDescription("-");
 | 
			
		||||
 
 | 
			
		||||
@@ -154,7 +154,7 @@ public partial class Gambling
 | 
			
		||||
                {
 | 
			
		||||
                    await using (var uow = _db.GetDbContext())
 | 
			
		||||
                    {
 | 
			
		||||
                        var x = uow.Set<ShopEntryItem>().Remove(item);
 | 
			
		||||
                        uow.Set<ShopEntryItem>().Remove(item);
 | 
			
		||||
                        uow.SaveChanges();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,8 @@ public partial class Gambling
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class SlotCommands : GamblingSubmodule<GamblingService>
 | 
			
		||||
    {
 | 
			
		||||
        private static long _totalBet;
 | 
			
		||||
        private static long _totalPaidOut;
 | 
			
		||||
        private static long totalBet;
 | 
			
		||||
        private static long totalPaidOut;
 | 
			
		||||
 | 
			
		||||
        private static readonly HashSet<ulong> _runningUsers = new();
 | 
			
		||||
 | 
			
		||||
@@ -28,7 +28,7 @@ public partial class Gambling
 | 
			
		||||
        //thanks to judge for helping me with this
 | 
			
		||||
 | 
			
		||||
        private readonly IImageCache _images;
 | 
			
		||||
        private FontProvider _fonts;
 | 
			
		||||
        private readonly FontProvider _fonts;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
 | 
			
		||||
        public SlotCommands(
 | 
			
		||||
@@ -51,8 +51,8 @@ public partial class Gambling
 | 
			
		||||
        public async partial Task SlotStats()
 | 
			
		||||
        {
 | 
			
		||||
            //i remembered to not be a moron
 | 
			
		||||
            var paid = _totalPaidOut;
 | 
			
		||||
            var bet = _totalBet;
 | 
			
		||||
            var paid = totalPaidOut;
 | 
			
		||||
            var bet = totalBet;
 | 
			
		||||
 | 
			
		||||
            if (bet <= 0)
 | 
			
		||||
                bet = 1;
 | 
			
		||||
@@ -120,8 +120,8 @@ public partial class Gambling
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Interlocked.Add(ref _totalBet, amount);
 | 
			
		||||
                Interlocked.Add(ref _totalPaidOut, result.Won);
 | 
			
		||||
                Interlocked.Add(ref totalBet, amount);
 | 
			
		||||
                Interlocked.Add(ref totalPaidOut, result.Won);
 | 
			
		||||
 | 
			
		||||
                long ownedAmount;
 | 
			
		||||
                await using (var uow = _db.GetDbContext())
 | 
			
		||||
@@ -130,12 +130,12 @@ public partial class Gambling
 | 
			
		||||
                                  ?? 0;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out var format))
 | 
			
		||||
                using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out _))
 | 
			
		||||
                {
 | 
			
		||||
                    var numbers = new int[3];
 | 
			
		||||
                    result.Rolls.CopyTo(numbers, 0);
 | 
			
		||||
 | 
			
		||||
                    Color fontColor = _config.Slots.CurrencyFontColor;
 | 
			
		||||
                    Color fontColor = Config.Slots.CurrencyFontColor;
 | 
			
		||||
 | 
			
		||||
                    bgImage.Mutate(x => x.DrawText(new()
 | 
			
		||||
                        {
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,7 @@ public class VoteRewardService : INService, IReadyExecutor
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex, "Critical error loading discords.com vote rewards.");
 | 
			
		||||
                Log.Error(ex, "Critical error loading discords.com vote rewards");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -39,9 +39,9 @@ public partial class Gambling
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async partial Task WaifuClaim(int amount, [Leftover] IUser target)
 | 
			
		||||
        {
 | 
			
		||||
            if (amount < _config.Waifu.MinPrice)
 | 
			
		||||
            if (amount < Config.Waifu.MinPrice)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.waifu_isnt_cheap(_config.Waifu.MinPrice + CurrencySign));
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.waifu_isnt_cheap(Config.Waifu.MinPrice + CurrencySign));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -278,7 +278,7 @@ public partial class Gambling
 | 
			
		||||
        [Priority(1)]
 | 
			
		||||
        public async partial Task WaifuGift(int page = 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (--page < 0 || page > (_config.Waifu.Items.Count - 1) / 9)
 | 
			
		||||
            if (--page < 0 || page > (Config.Waifu.Items.Count - 1) / 9)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var waifuItems = _service.GetWaifuItems();
 | 
			
		||||
@@ -294,7 +294,7 @@ public partial class Gambling
 | 
			
		||||
                              .ToList()
 | 
			
		||||
                              .ForEach(x => embed.AddField(
 | 
			
		||||
                                  $"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
 | 
			
		||||
                                  Format.Bold(x.Price.ToString()) + _config.Currency.Sign,
 | 
			
		||||
                                  Format.Bold(x.Price.ToString()) + Config.Currency.Sign,
 | 
			
		||||
                                  true));
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount);
 | 
			
		||||
 | 
			
		||||
            var wofMultipliers = _config.WheelOfFortune.Multipliers;
 | 
			
		||||
            var wofMultipliers = Config.WheelOfFortune.Multipliers;
 | 
			
		||||
            await SendConfirmAsync(Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign}
 | 
			
		||||
 | 
			
		||||
   『{wofMultipliers[1]}』   『{wofMultipliers[0]}』   『{wofMultipliers[7]}』
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ public class Deck
 | 
			
		||||
        { 13, "King" }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private static Dictionary<string, Func<List<Card>, bool>> _handValues;
 | 
			
		||||
    private static Dictionary<string, Func<List<Card>, bool>> handValues;
 | 
			
		||||
 | 
			
		||||
    public List<Card> CardPool { get; set; }
 | 
			
		||||
    private readonly Random _r = new NadekoRandom();
 | 
			
		||||
@@ -176,7 +176,7 @@ public class Deck
 | 
			
		||||
            return HasStraightFlush(cards) && !IsRoyalFlush(cards);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _handValues = new()
 | 
			
		||||
        handValues = new()
 | 
			
		||||
        {
 | 
			
		||||
            { "Royal Flush", IsRoyalFlush },
 | 
			
		||||
            { "Straight Flush", IsStraightFlush },
 | 
			
		||||
@@ -192,9 +192,10 @@ public class Deck
 | 
			
		||||
 | 
			
		||||
    public static string GetHandValue(List<Card> cards)
 | 
			
		||||
    {
 | 
			
		||||
        if (_handValues is null)
 | 
			
		||||
        if (handValues is null)
 | 
			
		||||
            InitHandValues();
 | 
			
		||||
        foreach (var kvp in _handValues.Where(x => x.Value(cards))) return kvp.Key;
 | 
			
		||||
        
 | 
			
		||||
        foreach (var kvp in handValues.Where(x => x.Value(cards))) return kvp.Key;
 | 
			
		||||
        return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,8 @@ public sealed class AcrophobiaGame : IDisposable
 | 
			
		||||
    public ImmutableArray<char> StartingLetters { get; private set; }
 | 
			
		||||
    public Options Opts { get; }
 | 
			
		||||
 | 
			
		||||
    private readonly Dictionary<AcrophobiaUser, int> submissions = new();
 | 
			
		||||
    private readonly SemaphoreSlim locker = new(1, 1);
 | 
			
		||||
    private readonly Dictionary<AcrophobiaUser, int> _submissions = new();
 | 
			
		||||
    private readonly SemaphoreSlim _locker = new(1, 1);
 | 
			
		||||
    private readonly NadekoRandom _rng;
 | 
			
		||||
 | 
			
		||||
    private readonly HashSet<ulong> _usersWhoVoted = new();
 | 
			
		||||
@@ -55,37 +55,37 @@ public sealed class AcrophobiaGame : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        await OnStarted(this);
 | 
			
		||||
        await Task.Delay(Opts.SubmissionTime * 1000);
 | 
			
		||||
        await locker.WaitAsync();
 | 
			
		||||
        await _locker.WaitAsync();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (submissions.Count == 0)
 | 
			
		||||
            if (_submissions.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                CurrentPhase = Phase.Ended;
 | 
			
		||||
                await OnVotingStarted(this, ImmutableArray.Create<KeyValuePair<AcrophobiaUser, int>>());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (submissions.Count == 1)
 | 
			
		||||
            if (_submissions.Count == 1)
 | 
			
		||||
            {
 | 
			
		||||
                CurrentPhase = Phase.Ended;
 | 
			
		||||
                await OnVotingStarted(this, submissions.ToArray().ToImmutableArray());
 | 
			
		||||
                await OnVotingStarted(this, _submissions.ToArray().ToImmutableArray());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CurrentPhase = Phase.Voting;
 | 
			
		||||
 | 
			
		||||
            await OnVotingStarted(this, submissions.ToArray().ToImmutableArray());
 | 
			
		||||
            await OnVotingStarted(this, _submissions.ToArray().ToImmutableArray());
 | 
			
		||||
        }
 | 
			
		||||
        finally { locker.Release(); }
 | 
			
		||||
        finally { _locker.Release(); }
 | 
			
		||||
 | 
			
		||||
        await Task.Delay(Opts.VoteTime * 1000);
 | 
			
		||||
        await locker.WaitAsync();
 | 
			
		||||
        await _locker.WaitAsync();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            CurrentPhase = Phase.Ended;
 | 
			
		||||
            await OnEnded(this, submissions.ToArray().ToImmutableArray());
 | 
			
		||||
            await OnEnded(this, _submissions.ToArray().ToImmutableArray());
 | 
			
		||||
        }
 | 
			
		||||
        finally { locker.Release(); }
 | 
			
		||||
        finally { _locker.Release(); }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void InitializeStartingLetters()
 | 
			
		||||
@@ -107,26 +107,26 @@ public sealed class AcrophobiaGame : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        var user = new AcrophobiaUser(userId, userName, input.ToLowerInvariant().ToTitleCase());
 | 
			
		||||
 | 
			
		||||
        await locker.WaitAsync();
 | 
			
		||||
        await _locker.WaitAsync();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            switch (CurrentPhase)
 | 
			
		||||
            {
 | 
			
		||||
                case Phase.Submission:
 | 
			
		||||
                    if (submissions.ContainsKey(user) || !IsValidAnswer(input))
 | 
			
		||||
                    if (_submissions.ContainsKey(user) || !IsValidAnswer(input))
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    submissions.Add(user, 0);
 | 
			
		||||
                    _submissions.Add(user, 0);
 | 
			
		||||
                    return true;
 | 
			
		||||
                case Phase.Voting:
 | 
			
		||||
                    AcrophobiaUser toVoteFor;
 | 
			
		||||
                    if (!int.TryParse(input, out var index)
 | 
			
		||||
                        || --index < 0
 | 
			
		||||
                        || index >= submissions.Count
 | 
			
		||||
                        || (toVoteFor = submissions.ToArray()[index].Key).UserId == user.UserId
 | 
			
		||||
                        || index >= _submissions.Count
 | 
			
		||||
                        || (toVoteFor = _submissions.ToArray()[index].Key).UserId == user.UserId
 | 
			
		||||
                        || !_usersWhoVoted.Add(userId))
 | 
			
		||||
                        break;
 | 
			
		||||
                    ++submissions[toVoteFor];
 | 
			
		||||
                    ++_submissions[toVoteFor];
 | 
			
		||||
                    _= Task.Run(() => OnUserVoted(userName));
 | 
			
		||||
                    return true;
 | 
			
		||||
            }
 | 
			
		||||
@@ -135,7 +135,7 @@ public sealed class AcrophobiaGame : IDisposable
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            locker.Release();
 | 
			
		||||
            _locker.Release();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -169,8 +169,8 @@ public sealed class AcrophobiaGame : IDisposable
 | 
			
		||||
        OnUserVoted = null;
 | 
			
		||||
        OnVotingStarted = null;
 | 
			
		||||
        _usersWhoVoted.Clear();
 | 
			
		||||
        submissions.Clear();
 | 
			
		||||
        locker.Dispose();
 | 
			
		||||
        _submissions.Clear();
 | 
			
		||||
        _locker.Dispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class Options : INadekoCommandOptions
 | 
			
		||||
 
 | 
			
		||||
@@ -31,19 +31,19 @@ public partial class Games
 | 
			
		||||
                    game.OnEnded += Game_OnEnded;
 | 
			
		||||
                    game.OnVotingStarted += Game_OnVotingStarted;
 | 
			
		||||
                    game.OnUserVoted += Game_OnUserVoted;
 | 
			
		||||
                    _client.MessageReceived += _client_MessageReceived;
 | 
			
		||||
                    _client.MessageReceived += ClientMessageReceived;
 | 
			
		||||
                    await game.Run();
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    _client.MessageReceived -= _client_MessageReceived;
 | 
			
		||||
                    _client.MessageReceived -= ClientMessageReceived;
 | 
			
		||||
                    _service.AcrophobiaGames.TryRemove(channel.Id, out game);
 | 
			
		||||
                    game.Dispose();
 | 
			
		||||
                    game?.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
            else
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.acro_running);
 | 
			
		||||
 | 
			
		||||
            Task _client_MessageReceived(SocketMessage msg)
 | 
			
		||||
            Task ClientMessageReceived(SocketMessage msg)
 | 
			
		||||
            {
 | 
			
		||||
                if (msg.Channel.Id != ctx.Channel.Id)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ public class ChatterBotService : IEarlyBehavior
 | 
			
		||||
    {
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey))
 | 
			
		||||
            return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory);
 | 
			
		||||
        return new CleverbotIOSession("GAh3wUfzDCpDpdpT", "RStKgqn7tcO9blbrv4KbXM8NDlb7H37C", _httpFactory);
 | 
			
		||||
        return new CleverbotIoSession("GAh3wUfzDCpDpdpT", "RStKgqn7tcO9blbrv4KbXM8NDlb7H37C", _httpFactory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public string PrepareMessage(IUserMessage msg, out IChatterBotSession cleverbot)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,13 @@ public class CleverbotResponse
 | 
			
		||||
    public string Output { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class CleverbotIOCreateResponse
 | 
			
		||||
public class CleverbotIoCreateResponse
 | 
			
		||||
{
 | 
			
		||||
    public string Status { get; set; }
 | 
			
		||||
    public string Nick { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class CleverbotIOAskResponse
 | 
			
		||||
public class CleverbotIoAskResponse
 | 
			
		||||
{
 | 
			
		||||
    public string Status { get; set; }
 | 
			
		||||
    public string Response { get; set; }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ public class OfficialCleverbotSession : IChatterBotSession
 | 
			
		||||
 | 
			
		||||
    private readonly string _apiKey;
 | 
			
		||||
    private readonly IHttpClientFactory _httpFactory;
 | 
			
		||||
    private string _cs;
 | 
			
		||||
    private string cs;
 | 
			
		||||
 | 
			
		||||
    public OfficialCleverbotSession(string apiKey, IHttpClientFactory factory)
 | 
			
		||||
    {
 | 
			
		||||
@@ -21,12 +21,12 @@ public class OfficialCleverbotSession : IChatterBotSession
 | 
			
		||||
    public async Task<string> Think(string input)
 | 
			
		||||
    {
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var dataString = await http.GetStringAsync(string.Format(QueryString, input, _cs ?? ""));
 | 
			
		||||
        var dataString = await http.GetStringAsync(string.Format(QueryString, input, cs ?? ""));
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var data = JsonConvert.DeserializeObject<CleverbotResponse>(dataString);
 | 
			
		||||
 | 
			
		||||
            _cs = data?.Cs;
 | 
			
		||||
            cs = data?.Cs;
 | 
			
		||||
            return data?.Output;
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
@@ -37,7 +37,7 @@ public class OfficialCleverbotSession : IChatterBotSession
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class CleverbotIOSession : IChatterBotSession
 | 
			
		||||
public class CleverbotIoSession : IChatterBotSession
 | 
			
		||||
{
 | 
			
		||||
    private readonly string _key;
 | 
			
		||||
    private readonly string _user;
 | 
			
		||||
@@ -47,7 +47,7 @@ public class CleverbotIOSession : IChatterBotSession
 | 
			
		||||
    private readonly string _createEndpoint = "https://cleverbot.io/1.0/create";
 | 
			
		||||
    private readonly string _askEndpoint = "https://cleverbot.io/1.0/ask";
 | 
			
		||||
 | 
			
		||||
    public CleverbotIOSession(string user, string key, IHttpClientFactory factory)
 | 
			
		||||
    public CleverbotIoSession(string user, string key, IHttpClientFactory factory)
 | 
			
		||||
    {
 | 
			
		||||
        _key = key;
 | 
			
		||||
        _user = user;
 | 
			
		||||
@@ -58,14 +58,14 @@ public class CleverbotIOSession : IChatterBotSession
 | 
			
		||||
 | 
			
		||||
    private async Task<string> GetNick()
 | 
			
		||||
    {
 | 
			
		||||
        using var _http = _httpFactory.CreateClient();
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        using var msg = new FormUrlEncodedContent(new[]
 | 
			
		||||
        {
 | 
			
		||||
            new KeyValuePair<string, string>("user", _user), new KeyValuePair<string, string>("key", _key)
 | 
			
		||||
        });
 | 
			
		||||
        using var data = await _http.PostAsync(_createEndpoint, msg);
 | 
			
		||||
        using var data = await http.PostAsync(_createEndpoint, msg);
 | 
			
		||||
        var str = await data.Content.ReadAsStringAsync();
 | 
			
		||||
        var obj = JsonConvert.DeserializeObject<CleverbotIOCreateResponse>(str);
 | 
			
		||||
        var obj = JsonConvert.DeserializeObject<CleverbotIoCreateResponse>(str);
 | 
			
		||||
        if (obj.Status != "success")
 | 
			
		||||
            throw new OperationCanceledException(obj.Status);
 | 
			
		||||
 | 
			
		||||
@@ -74,15 +74,15 @@ public class CleverbotIOSession : IChatterBotSession
 | 
			
		||||
 | 
			
		||||
    public async Task<string> Think(string input)
 | 
			
		||||
    {
 | 
			
		||||
        using var _http = _httpFactory.CreateClient();
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        using var msg = new FormUrlEncodedContent(new[]
 | 
			
		||||
        {
 | 
			
		||||
            new KeyValuePair<string, string>("user", _user), new KeyValuePair<string, string>("key", _key),
 | 
			
		||||
            new KeyValuePair<string, string>("nick", await _nick), new KeyValuePair<string, string>("text", input)
 | 
			
		||||
        });
 | 
			
		||||
        using var data = await _http.PostAsync(_askEndpoint, msg);
 | 
			
		||||
        using var data = await http.PostAsync(_askEndpoint, msg);
 | 
			
		||||
        var str = await data.Content.ReadAsStringAsync();
 | 
			
		||||
        var obj = JsonConvert.DeserializeObject<CleverbotIOAskResponse>(str);
 | 
			
		||||
        var obj = JsonConvert.DeserializeObject<CleverbotIoAskResponse>(str);
 | 
			
		||||
        if (obj.Status != "success")
 | 
			
		||||
            throw new OperationCanceledException(obj.Status);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@ namespace NadekoBot.Modules.Games.Services;
 | 
			
		||||
 | 
			
		||||
public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
 | 
			
		||||
{
 | 
			
		||||
    private const string FilePath = "data/games.yml";
 | 
			
		||||
    private static readonly TypedKey<GamesConfig> changeKey = new("config.games.updated");
 | 
			
		||||
    private const string FILE_PATH = "data/games.yml";
 | 
			
		||||
    private static readonly TypedKey<GamesConfig> _changeKey = new("config.games.updated");
 | 
			
		||||
    public override string Name { get; } = "games";
 | 
			
		||||
 | 
			
		||||
    public GamesConfigService(IConfigSeria serializer, IPubSub pubSub)
 | 
			
		||||
        : base(FilePath, serializer, pubSub, changeKey)
 | 
			
		||||
        : base(FILE_PATH, serializer, pubSub, _changeKey)
 | 
			
		||||
    {
 | 
			
		||||
        AddParsedProp("trivia.min_win_req",
 | 
			
		||||
            gs => gs.Trivia.MinimumWinReq,
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ namespace NadekoBot.Modules.Games.Services;
 | 
			
		||||
 | 
			
		||||
public class GamesService : INService
 | 
			
		||||
{
 | 
			
		||||
    private const string TypingArticlesPath = "data/typing_articles3.json";
 | 
			
		||||
    private const string TYPING_ARTICLES_PATH = "data/typing_articles3.json";
 | 
			
		||||
 | 
			
		||||
    public ConcurrentDictionary<ulong, GirlRating> GirlRatings { get; } = new();
 | 
			
		||||
 | 
			
		||||
@@ -54,11 +54,11 @@ public class GamesService : INService
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            TypingArticles = JsonConvert.DeserializeObject<List<TypingArticle>>(File.ReadAllText(TypingArticlesPath));
 | 
			
		||||
            TypingArticles = JsonConvert.DeserializeObject<List<TypingArticle>>(File.ReadAllText(TYPING_ARTICLES_PATH));
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning("Error while loading typing articles {0}", ex.ToString());
 | 
			
		||||
            Log.Warning(ex, "Error while loading typing articles: {ErrorMessage}", ex.Message);
 | 
			
		||||
            TypingArticles = new();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -80,7 +80,7 @@ public class GamesService : INService
 | 
			
		||||
            Text = text.SanitizeMentions(true)
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        File.WriteAllText(TypingArticlesPath, JsonConvert.SerializeObject(TypingArticles));
 | 
			
		||||
        File.WriteAllText(TYPING_ARTICLES_PATH, JsonConvert.SerializeObject(TypingArticles));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public string GetEightballResponse(ulong userId, string question)
 | 
			
		||||
@@ -90,7 +90,6 @@ public class GamesService : INService
 | 
			
		||||
                e.Size = question.Length;
 | 
			
		||||
                e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12);
 | 
			
		||||
                return EightBallResponses[_rng.Next(0, EightBallResponses.Count)];
 | 
			
		||||
                ;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
    public TypingArticle RemoveTypingArticle(int index)
 | 
			
		||||
@@ -102,7 +101,7 @@ public class GamesService : INService
 | 
			
		||||
        var removed = articles[index];
 | 
			
		||||
        TypingArticles.RemoveAt(index);
 | 
			
		||||
 | 
			
		||||
        File.WriteAllText(TypingArticlesPath, JsonConvert.SerializeObject(articles));
 | 
			
		||||
        File.WriteAllText(TYPING_ARTICLES_PATH, JsonConvert.SerializeObject(articles));
 | 
			
		||||
        return removed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,11 +15,8 @@ public class GirlRating
 | 
			
		||||
    public AsyncLazy<Stream> Stream { get; }
 | 
			
		||||
    private readonly IImageCache _images;
 | 
			
		||||
 | 
			
		||||
    private readonly IHttpClientFactory _httpFactory;
 | 
			
		||||
 | 
			
		||||
    public GirlRating(
 | 
			
		||||
        IImageCache images,
 | 
			
		||||
        IHttpClientFactory factory,
 | 
			
		||||
        double crazy,
 | 
			
		||||
        double hot,
 | 
			
		||||
        int roll,
 | 
			
		||||
@@ -30,7 +27,6 @@ public class GirlRating
 | 
			
		||||
        Hot = hot;
 | 
			
		||||
        Roll = roll;
 | 
			
		||||
        Advice = advice; // convenient to have it here, even though atm there are only few different ones.
 | 
			
		||||
        _httpFactory = factory;
 | 
			
		||||
 | 
			
		||||
        Stream = new(() =>
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ namespace NadekoBot.Modules.Games.Hangman;
 | 
			
		||||
 | 
			
		||||
public sealed class DefaultHangmanSource : IHangmanSource
 | 
			
		||||
{
 | 
			
		||||
    private IReadOnlyDictionary<string, HangmanTerm[]> _terms = new Dictionary<string, HangmanTerm[]>();
 | 
			
		||||
    private IReadOnlyDictionary<string, HangmanTerm[]> termsDict = new Dictionary<string, HangmanTerm[]>();
 | 
			
		||||
    private readonly Random _rng;
 | 
			
		||||
 | 
			
		||||
    public DefaultHangmanSource()
 | 
			
		||||
@@ -18,7 +18,7 @@ public sealed class DefaultHangmanSource : IHangmanSource
 | 
			
		||||
    {
 | 
			
		||||
        if (!Directory.Exists("data/hangman"))
 | 
			
		||||
        {
 | 
			
		||||
            Log.Error("Hangman game won't work. Folder 'data/hangman' is missing.");
 | 
			
		||||
            Log.Error("Hangman game won't work. Folder 'data/hangman' is missing");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -31,16 +31,16 @@ public sealed class DefaultHangmanSource : IHangmanSource
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex, "Loading {HangmanFile} failed.", file);
 | 
			
		||||
                Log.Error(ex, "Loading {HangmanFile} failed", file);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        _terms = qs;
 | 
			
		||||
        termsDict = qs;
 | 
			
		||||
 | 
			
		||||
        Log.Information("Loaded {HangmanCategoryCount} hangman categories.", qs.Count);
 | 
			
		||||
        Log.Information("Loaded {HangmanCategoryCount} hangman categories", qs.Count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IReadOnlyCollection<string> GetCategories()
 | 
			
		||||
        => _terms.Keys.ToList();
 | 
			
		||||
        => termsDict.Keys.ToList();
 | 
			
		||||
 | 
			
		||||
    public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term)
 | 
			
		||||
    {
 | 
			
		||||
@@ -50,7 +50,7 @@ public sealed class DefaultHangmanSource : IHangmanSource
 | 
			
		||||
            category = cats.ElementAt(_rng.Next(0, cats.Count));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (_terms.TryGetValue(category, out var terms))
 | 
			
		||||
        if (termsDict.TryGetValue(category, out var terms))
 | 
			
		||||
        {
 | 
			
		||||
            term = terms[_rng.Next(0, terms.Length)];
 | 
			
		||||
            return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -28,19 +28,19 @@ public partial class Games
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .AddField("Hangman", Draw(state))
 | 
			
		||||
                         .AddField("Guess", Format.Code(state.Word))
 | 
			
		||||
                         .WithFooter(state.missedLetters.Join(' '));
 | 
			
		||||
                         .WithFooter(state.MissedLetters.Join(' '));
 | 
			
		||||
 | 
			
		||||
            if (state.Phase == HangmanGame.Phase.Ended && state.Failed)
 | 
			
		||||
                return eb.Create()
 | 
			
		||||
                         .WithErrorColor()
 | 
			
		||||
                         .AddField("Hangman", Draw(state))
 | 
			
		||||
                         .AddField("Guess", Format.Code(state.Word))
 | 
			
		||||
                         .WithFooter(state.missedLetters.Join(' '));
 | 
			
		||||
                         .WithFooter(state.MissedLetters.Join(' '));
 | 
			
		||||
            return eb.Create()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .AddField("Hangman", Draw(state))
 | 
			
		||||
                     .AddField("Guess", Format.Code(state.Word))
 | 
			
		||||
                     .WithFooter(state.missedLetters.Join(' '));
 | 
			
		||||
                     .WithFooter(state.MissedLetters.Join(' '));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -104,7 +104,7 @@ public sealed class HangmanGame
 | 
			
		||||
        Phase Phase,
 | 
			
		||||
        string Word,
 | 
			
		||||
        GuessResult GuessResult,
 | 
			
		||||
        List<char> missedLetters,
 | 
			
		||||
        List<char> MissedLetters,
 | 
			
		||||
        string ImageUrl)
 | 
			
		||||
    {
 | 
			
		||||
        public bool Failed
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ public sealed class HangmanService : IHangmanService, ILateExecutor
 | 
			
		||||
    {
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            if (_hangmanGames.TryRemove(channelId, out var game)) return new(true);
 | 
			
		||||
            if (_hangmanGames.TryRemove(channelId, out _)) return new(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
        Ended
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private const int _killTimeout = 20 * 1000;
 | 
			
		||||
    private const int _nextRoundTimeout = 5 * 1000;
 | 
			
		||||
    private const int KILL_TIMEOUT = 20 * 1000;
 | 
			
		||||
    private const int NEXT_ROUND_TIMEOUT = 5 * 1000;
 | 
			
		||||
 | 
			
		||||
    public event Func<NunchiGame, Task> OnGameStarted;
 | 
			
		||||
    public event Func<NunchiGame, int, Task> OnRoundStarted;
 | 
			
		||||
@@ -26,19 +26,19 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
    public Phase CurrentPhase { get; private set; } = Phase.Joining;
 | 
			
		||||
 | 
			
		||||
    public ImmutableArray<(ulong Id, string Name)> Participants
 | 
			
		||||
        => _participants.ToImmutableArray();
 | 
			
		||||
        => participants.ToImmutableArray();
 | 
			
		||||
 | 
			
		||||
    public int ParticipantCount
 | 
			
		||||
        => _participants.Count;
 | 
			
		||||
        => participants.Count;
 | 
			
		||||
 | 
			
		||||
    private readonly SemaphoreSlim _locker = new(1, 1);
 | 
			
		||||
 | 
			
		||||
    private HashSet<(ulong Id, string Name)> _participants = new();
 | 
			
		||||
    private HashSet<(ulong Id, string Name)> participants = new();
 | 
			
		||||
    private readonly HashSet<(ulong Id, string Name)> _passed = new();
 | 
			
		||||
    private Timer _killTimer;
 | 
			
		||||
    private Timer killTimer;
 | 
			
		||||
 | 
			
		||||
    public NunchiGame(ulong creatorId, string creatorName)
 | 
			
		||||
        => _participants.Add((creatorId, creatorName));
 | 
			
		||||
        => participants.Add((creatorId, creatorName));
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> Join(ulong userId, string userName)
 | 
			
		||||
    {
 | 
			
		||||
@@ -48,7 +48,7 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
            if (CurrentPhase != Phase.Joining)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            return _participants.Add((userId, userName));
 | 
			
		||||
            return participants.Add((userId, userName));
 | 
			
		||||
        }
 | 
			
		||||
        finally { _locker.Release(); }
 | 
			
		||||
    }
 | 
			
		||||
@@ -60,13 +60,13 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
        await _locker.WaitAsync();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (_participants.Count < 3)
 | 
			
		||||
            if (participants.Count < 3)
 | 
			
		||||
            {
 | 
			
		||||
                CurrentPhase = Phase.Ended;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _killTimer = new(async _ =>
 | 
			
		||||
            killTimer = new(async _ =>
 | 
			
		||||
                {
 | 
			
		||||
                    await _locker.WaitAsync();
 | 
			
		||||
                    try
 | 
			
		||||
@@ -75,14 +75,14 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
                            return;
 | 
			
		||||
 | 
			
		||||
                        //if some players took too long to type a number, boot them all out and start a new round
 | 
			
		||||
                        _participants = new HashSet<(ulong, string)>(_passed);
 | 
			
		||||
                        participants = new HashSet<(ulong, string)>(_passed);
 | 
			
		||||
                        EndRound();
 | 
			
		||||
                    }
 | 
			
		||||
                    finally { _locker.Release(); }
 | 
			
		||||
                },
 | 
			
		||||
                null,
 | 
			
		||||
                _killTimeout,
 | 
			
		||||
                _killTimeout);
 | 
			
		||||
                KILL_TIMEOUT,
 | 
			
		||||
                KILL_TIMEOUT);
 | 
			
		||||
 | 
			
		||||
            CurrentPhase = Phase.Playing;
 | 
			
		||||
            _= OnGameStarted?.Invoke(this);
 | 
			
		||||
@@ -105,7 +105,7 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
            // if the user is not a member of the race,
 | 
			
		||||
            // or he already successfully typed the number
 | 
			
		||||
            // ignore the input
 | 
			
		||||
            if (!_participants.Contains(userTuple) || !_passed.Add(userTuple))
 | 
			
		||||
            if (!participants.Contains(userTuple) || !_passed.Add(userTuple))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            //if the number is correct
 | 
			
		||||
@@ -113,20 +113,20 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
            {
 | 
			
		||||
                //increment current number
 | 
			
		||||
                ++CurrentNumber;
 | 
			
		||||
                if (_passed.Count == _participants.Count - 1)
 | 
			
		||||
                if (_passed.Count == participants.Count - 1)
 | 
			
		||||
                {
 | 
			
		||||
                    // if only n players are left, and n - 1 type the correct number, round is over
 | 
			
		||||
 | 
			
		||||
                    // if only 2 players are left, game is over
 | 
			
		||||
                    if (_participants.Count == 2)
 | 
			
		||||
                    if (participants.Count == 2)
 | 
			
		||||
                    {
 | 
			
		||||
                        _killTimer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
                        killTimer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
                        CurrentPhase = Phase.Ended;
 | 
			
		||||
                        _= OnGameEnded?.Invoke(this, userTuple.Name);
 | 
			
		||||
                    }
 | 
			
		||||
                    else // else just start the new round without the user who was the last
 | 
			
		||||
                    {
 | 
			
		||||
                        var failure = _participants.Except(_passed).First();
 | 
			
		||||
                        var failure = participants.Except(_passed).First();
 | 
			
		||||
 | 
			
		||||
                        OnUserGuessed?.Invoke(this);
 | 
			
		||||
                        EndRound(failure);
 | 
			
		||||
@@ -148,25 +148,25 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
 | 
			
		||||
    private void EndRound((ulong, string)? failure = null)
 | 
			
		||||
    {
 | 
			
		||||
        _killTimer.Change(_killTimeout, _killTimeout);
 | 
			
		||||
        killTimer.Change(KILL_TIMEOUT, KILL_TIMEOUT);
 | 
			
		||||
        CurrentNumber = new NadekoRandom().Next(0, 100); // reset the counter
 | 
			
		||||
        _passed.Clear(); // reset all users who passed (new round starts)
 | 
			
		||||
        if (failure is not null)
 | 
			
		||||
            _participants.Remove(failure.Value); // remove the dude who failed from the list of players
 | 
			
		||||
            participants.Remove(failure.Value); // remove the dude who failed from the list of players
 | 
			
		||||
 | 
			
		||||
        var __ = OnRoundEnded?.Invoke(this, failure);
 | 
			
		||||
        if (_participants.Count <= 1) // means we have a winner or everyone was booted out
 | 
			
		||||
        if (participants.Count <= 1) // means we have a winner or everyone was booted out
 | 
			
		||||
        {
 | 
			
		||||
            _killTimer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            killTimer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            CurrentPhase = Phase.Ended;
 | 
			
		||||
            _= OnGameEnded?.Invoke(this, _participants.Count > 0 ? _participants.First().Name : null);
 | 
			
		||||
            _= OnGameEnded?.Invoke(this, participants.Count > 0 ? participants.First().Name : null);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CurrentPhase = Phase.WaitingForNextRound;
 | 
			
		||||
        var throwawayDelay = Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Delay(_nextRoundTimeout);
 | 
			
		||||
            await Task.Delay(NEXT_ROUND_TIMEOUT);
 | 
			
		||||
            CurrentPhase = Phase.Playing;
 | 
			
		||||
            var ___ = OnRoundStarted?.Invoke(this, CurrentNumber);
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -38,12 +38,12 @@ public partial class Games
 | 
			
		||||
            try { await ConfirmLocalizedAsync(strs.nunchi_created); }
 | 
			
		||||
            catch { }
 | 
			
		||||
 | 
			
		||||
            nunchi.OnGameEnded += Nunchi_OnGameEnded;
 | 
			
		||||
            nunchi.OnGameEnded += NunchiOnGameEnded;
 | 
			
		||||
            //nunchi.OnGameStarted += Nunchi_OnGameStarted;
 | 
			
		||||
            nunchi.OnRoundEnded += Nunchi_OnRoundEnded;
 | 
			
		||||
            nunchi.OnUserGuessed += Nunchi_OnUserGuessed;
 | 
			
		||||
            nunchi.OnRoundStarted += Nunchi_OnRoundStarted;
 | 
			
		||||
            _client.MessageReceived += _client_MessageReceived;
 | 
			
		||||
            _client.MessageReceived += ClientMessageReceived;
 | 
			
		||||
 | 
			
		||||
            var success = await nunchi.Initialize();
 | 
			
		||||
            if (!success)
 | 
			
		||||
@@ -53,7 +53,7 @@ public partial class Games
 | 
			
		||||
                await ConfirmLocalizedAsync(strs.nunchi_failed_to_start);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Task _client_MessageReceived(SocketMessage arg)
 | 
			
		||||
            Task ClientMessageReceived(SocketMessage arg)
 | 
			
		||||
            {
 | 
			
		||||
                _= Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
@@ -73,11 +73,11 @@ public partial class Games
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Task Nunchi_OnGameEnded(NunchiGame arg1, string arg2)
 | 
			
		||||
            Task NunchiOnGameEnded(NunchiGame arg1, string arg2)
 | 
			
		||||
            {
 | 
			
		||||
                if (_service.NunchiGames.TryRemove(ctx.Guild.Id, out var game))
 | 
			
		||||
                {
 | 
			
		||||
                    _client.MessageReceived -= _client_MessageReceived;
 | 
			
		||||
                    _client.MessageReceived -= ClientMessageReceived;
 | 
			
		||||
                    game.Dispose();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -90,8 +90,8 @@ public partial class Games
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < stats.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var (Index, votes, Text) = stats[i];
 | 
			
		||||
                sb.AppendLine(GetText(strs.poll_result(Index + 1, Format.Bold(Text), Format.Bold(votes.ToString()))));
 | 
			
		||||
                var (index, votes, text) = stats[i];
 | 
			
		||||
                sb.AppendLine(GetText(strs.poll_result(index + 1, Format.Bold(text), Format.Bold(votes.ToString()))));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return eb.WithDescription(sb.ToString())
 | 
			
		||||
 
 | 
			
		||||
@@ -10,19 +10,19 @@ public class TicTacToe
 | 
			
		||||
    private readonly ITextChannel _channel;
 | 
			
		||||
    private readonly IGuildUser[] _users;
 | 
			
		||||
    private readonly int?[,] _state;
 | 
			
		||||
    private Phase _phase;
 | 
			
		||||
    private int _curUserIndex;
 | 
			
		||||
    private Phase phase;
 | 
			
		||||
    private int curUserIndex;
 | 
			
		||||
    private readonly SemaphoreSlim _moveLock;
 | 
			
		||||
 | 
			
		||||
    private IGuildUser _winner;
 | 
			
		||||
    private IGuildUser winner;
 | 
			
		||||
 | 
			
		||||
    private readonly string[] _numbers =
 | 
			
		||||
    {
 | 
			
		||||
        ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private IUserMessage _previousMessage;
 | 
			
		||||
    private Timer _timeoutTimer;
 | 
			
		||||
    private IUserMessage previousMessage;
 | 
			
		||||
    private Timer timeoutTimer;
 | 
			
		||||
    private readonly IBotStrings _strings;
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
    private readonly Options _options;
 | 
			
		||||
@@ -45,7 +45,7 @@ public class TicTacToe
 | 
			
		||||
        _users = new[] { firstUser, null };
 | 
			
		||||
        _state = new int?[,] { { null, null, null }, { null, null, null }, { null, null, null } };
 | 
			
		||||
 | 
			
		||||
        _phase = Phase.Starting;
 | 
			
		||||
        phase = Phase.Starting;
 | 
			
		||||
        _moveLock = new(1, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -81,16 +81,16 @@ public class TicTacToe
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(title))
 | 
			
		||||
            embed.WithTitle(title);
 | 
			
		||||
 | 
			
		||||
        if (_winner is null)
 | 
			
		||||
        if (winner is null)
 | 
			
		||||
        {
 | 
			
		||||
            if (_phase == Phase.Ended)
 | 
			
		||||
            if (phase == Phase.Ended)
 | 
			
		||||
                embed.WithFooter(GetText(strs.ttt_no_moves));
 | 
			
		||||
            else
 | 
			
		||||
                embed.WithFooter(GetText(strs.ttt_users_move(_users[_curUserIndex])));
 | 
			
		||||
                embed.WithFooter(GetText(strs.ttt_users_move(_users[curUserIndex])));
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            embed.WithFooter(GetText(strs.ttt_has_won(_winner)));
 | 
			
		||||
            embed.WithFooter(GetText(strs.ttt_has_won(winner)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return embed;
 | 
			
		||||
@@ -115,7 +115,7 @@ public class TicTacToe
 | 
			
		||||
 | 
			
		||||
    public async Task Start(IGuildUser user)
 | 
			
		||||
    {
 | 
			
		||||
        if (_phase is Phase.Started or Phase.Ended)
 | 
			
		||||
        if (phase is Phase.Started or Phase.Ended)
 | 
			
		||||
        {
 | 
			
		||||
            await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_already_running));
 | 
			
		||||
            return;
 | 
			
		||||
@@ -129,21 +129,21 @@ public class TicTacToe
 | 
			
		||||
 | 
			
		||||
        _users[1] = user;
 | 
			
		||||
 | 
			
		||||
        _phase = Phase.Started;
 | 
			
		||||
        phase = Phase.Started;
 | 
			
		||||
 | 
			
		||||
        _timeoutTimer = new(async _ =>
 | 
			
		||||
        timeoutTimer = new(async _ =>
 | 
			
		||||
            {
 | 
			
		||||
                await _moveLock.WaitAsync();
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (_phase == Phase.Ended)
 | 
			
		||||
                    if (phase == Phase.Ended)
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    _phase = Phase.Ended;
 | 
			
		||||
                    phase = Phase.Ended;
 | 
			
		||||
                    if (_users[1] is not null)
 | 
			
		||||
                    {
 | 
			
		||||
                        _winner = _users[_curUserIndex ^= 1];
 | 
			
		||||
                        var del = _previousMessage?.DeleteAsync();
 | 
			
		||||
                        winner = _users[curUserIndex ^= 1];
 | 
			
		||||
                        var del = previousMessage?.DeleteAsync();
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            await _channel.EmbedAsync(GetEmbed(GetText(strs.ttt_time_expired)));
 | 
			
		||||
@@ -168,7 +168,7 @@ public class TicTacToe
 | 
			
		||||
        _client.MessageReceived += Client_MessageReceived;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        _previousMessage = await _channel.EmbedAsync(GetEmbed(GetText(strs.game_started)));
 | 
			
		||||
        previousMessage = await _channel.EmbedAsync(GetEmbed(GetText(strs.game_started)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private bool IsDraw()
 | 
			
		||||
@@ -187,8 +187,8 @@ public class TicTacToe
 | 
			
		||||
            await _moveLock.WaitAsync();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var curUser = _users[_curUserIndex];
 | 
			
		||||
                if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id)
 | 
			
		||||
                var curUser = _users[curUserIndex];
 | 
			
		||||
                if (phase == Phase.Ended || msg.Author?.Id != curUser.Id)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                if (int.TryParse(msg.Content, out var index)
 | 
			
		||||
@@ -196,69 +196,69 @@ public class TicTacToe
 | 
			
		||||
                    && index <= 9
 | 
			
		||||
                    && _state[index / 3, index % 3] is null)
 | 
			
		||||
                {
 | 
			
		||||
                    _state[index / 3, index % 3] = _curUserIndex;
 | 
			
		||||
                    _state[index / 3, index % 3] = curUserIndex;
 | 
			
		||||
 | 
			
		||||
                    // i'm lazy
 | 
			
		||||
                    if (_state[index / 3, 0] == _state[index / 3, 1] && _state[index / 3, 1] == _state[index / 3, 2])
 | 
			
		||||
                    {
 | 
			
		||||
                        _state[index / 3, 0] = _curUserIndex + 2;
 | 
			
		||||
                        _state[index / 3, 1] = _curUserIndex + 2;
 | 
			
		||||
                        _state[index / 3, 2] = _curUserIndex + 2;
 | 
			
		||||
                        _state[index / 3, 0] = curUserIndex + 2;
 | 
			
		||||
                        _state[index / 3, 1] = curUserIndex + 2;
 | 
			
		||||
                        _state[index / 3, 2] = curUserIndex + 2;
 | 
			
		||||
 | 
			
		||||
                        _phase = Phase.Ended;
 | 
			
		||||
                        phase = Phase.Ended;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (_state[0, index % 3] == _state[1, index % 3]
 | 
			
		||||
                             && _state[1, index % 3] == _state[2, index % 3])
 | 
			
		||||
                    {
 | 
			
		||||
                        _state[0, index % 3] = _curUserIndex + 2;
 | 
			
		||||
                        _state[1, index % 3] = _curUserIndex + 2;
 | 
			
		||||
                        _state[2, index % 3] = _curUserIndex + 2;
 | 
			
		||||
                        _state[0, index % 3] = curUserIndex + 2;
 | 
			
		||||
                        _state[1, index % 3] = curUserIndex + 2;
 | 
			
		||||
                        _state[2, index % 3] = curUserIndex + 2;
 | 
			
		||||
 | 
			
		||||
                        _phase = Phase.Ended;
 | 
			
		||||
                        phase = Phase.Ended;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (_curUserIndex == _state[0, 0]
 | 
			
		||||
                    else if (curUserIndex == _state[0, 0]
 | 
			
		||||
                             && _state[0, 0] == _state[1, 1]
 | 
			
		||||
                             && _state[1, 1] == _state[2, 2])
 | 
			
		||||
                    {
 | 
			
		||||
                        _state[0, 0] = _curUserIndex + 2;
 | 
			
		||||
                        _state[1, 1] = _curUserIndex + 2;
 | 
			
		||||
                        _state[2, 2] = _curUserIndex + 2;
 | 
			
		||||
                        _state[0, 0] = curUserIndex + 2;
 | 
			
		||||
                        _state[1, 1] = curUserIndex + 2;
 | 
			
		||||
                        _state[2, 2] = curUserIndex + 2;
 | 
			
		||||
 | 
			
		||||
                        _phase = Phase.Ended;
 | 
			
		||||
                        phase = Phase.Ended;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (_curUserIndex == _state[0, 2]
 | 
			
		||||
                    else if (curUserIndex == _state[0, 2]
 | 
			
		||||
                             && _state[0, 2] == _state[1, 1]
 | 
			
		||||
                             && _state[1, 1] == _state[2, 0])
 | 
			
		||||
                    {
 | 
			
		||||
                        _state[0, 2] = _curUserIndex + 2;
 | 
			
		||||
                        _state[1, 1] = _curUserIndex + 2;
 | 
			
		||||
                        _state[2, 0] = _curUserIndex + 2;
 | 
			
		||||
                        _state[0, 2] = curUserIndex + 2;
 | 
			
		||||
                        _state[1, 1] = curUserIndex + 2;
 | 
			
		||||
                        _state[2, 0] = curUserIndex + 2;
 | 
			
		||||
 | 
			
		||||
                        _phase = Phase.Ended;
 | 
			
		||||
                        phase = Phase.Ended;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var reason = string.Empty;
 | 
			
		||||
 | 
			
		||||
                    if (_phase == Phase.Ended) // if user won, stop receiving moves
 | 
			
		||||
                    if (phase == Phase.Ended) // if user won, stop receiving moves
 | 
			
		||||
                    {
 | 
			
		||||
                        reason = GetText(strs.ttt_matched_three);
 | 
			
		||||
                        _winner = _users[_curUserIndex];
 | 
			
		||||
                        winner = _users[curUserIndex];
 | 
			
		||||
                        _client.MessageReceived -= Client_MessageReceived;
 | 
			
		||||
                        OnEnded?.Invoke(this);
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (IsDraw())
 | 
			
		||||
                    {
 | 
			
		||||
                        reason = GetText(strs.ttt_a_draw);
 | 
			
		||||
                        _phase = Phase.Ended;
 | 
			
		||||
                        phase = Phase.Ended;
 | 
			
		||||
                        _client.MessageReceived -= Client_MessageReceived;
 | 
			
		||||
                        OnEnded?.Invoke(this);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var sendstate = Task.Run(async () =>
 | 
			
		||||
                    _ = Task.Run(async () =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var del1 = msg.DeleteAsync();
 | 
			
		||||
                        var del2 = _previousMessage?.DeleteAsync();
 | 
			
		||||
                        try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); }
 | 
			
		||||
                        var del2 = previousMessage?.DeleteAsync();
 | 
			
		||||
                        try { previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); }
 | 
			
		||||
                        catch { }
 | 
			
		||||
 | 
			
		||||
                        try { await del1; }
 | 
			
		||||
@@ -270,9 +270,9 @@ public class TicTacToe
 | 
			
		||||
                        }
 | 
			
		||||
                        catch { }
 | 
			
		||||
                    });
 | 
			
		||||
                    _curUserIndex ^= 1;
 | 
			
		||||
                    curUserIndex ^= 1;
 | 
			
		||||
 | 
			
		||||
                    _timeoutTimer.Change(_options.TurnTimer * 1000, Timeout.Infinite);
 | 
			
		||||
                    timeoutTimer.Change(_options.TurnTimer * 1000, Timeout.Infinite);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@ namespace NadekoBot.Modules.Games.Common.Trivia;
 | 
			
		||||
 | 
			
		||||
public class TriviaQuestion
 | 
			
		||||
{
 | 
			
		||||
    public const int maxStringLength = 22;
 | 
			
		||||
    public const int MAX_STRING_LENGTH = 22;
 | 
			
		||||
 | 
			
		||||
    //represents the min size to judge levDistance with
 | 
			
		||||
    private static readonly HashSet<Tuple<int, int>> strictness = new()
 | 
			
		||||
    private static readonly HashSet<Tuple<int, int>> _strictness = new()
 | 
			
		||||
    {
 | 
			
		||||
        new(9, 0), new(14, 1), new(19, 2), new(22, 3)
 | 
			
		||||
    };
 | 
			
		||||
@@ -56,7 +56,7 @@ public class TriviaQuestion
 | 
			
		||||
 | 
			
		||||
    private static bool JudgeGuess(int guessLength, int answerLength, int levDistance)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var level in strictness)
 | 
			
		||||
        foreach (var level in _strictness)
 | 
			
		||||
            if (guessLength <= level.Item1 || answerLength <= level.Item1)
 | 
			
		||||
            {
 | 
			
		||||
                if (levDistance <= level.Item2)
 | 
			
		||||
@@ -78,7 +78,7 @@ public class TriviaQuestion
 | 
			
		||||
        str = Regex.Replace(str, "^\\s+", "");
 | 
			
		||||
        str = Regex.Replace(str, "\\s+$", "");
 | 
			
		||||
        //Trim the really long answers
 | 
			
		||||
        str = str.Length <= maxStringLength ? str : str[..maxStringLength];
 | 
			
		||||
        str = str.Length <= MAX_STRING_LENGTH ? str : str[..MAX_STRING_LENGTH];
 | 
			
		||||
        return str;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,14 +10,14 @@ public class TriviaQuestionPool
 | 
			
		||||
        => _cache.LocalData.PokemonMap;
 | 
			
		||||
 | 
			
		||||
    private readonly IDataCache _cache;
 | 
			
		||||
    private readonly int maxPokemonId;
 | 
			
		||||
    private readonly int _maxPokemonId;
 | 
			
		||||
 | 
			
		||||
    private readonly NadekoRandom _rng = new();
 | 
			
		||||
 | 
			
		||||
    public TriviaQuestionPool(IDataCache cache)
 | 
			
		||||
    {
 | 
			
		||||
        _cache = cache;
 | 
			
		||||
        maxPokemonId = 721; //xd
 | 
			
		||||
        _maxPokemonId = 721; //xd
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TriviaQuestion GetRandomQuestion(HashSet<TriviaQuestion> exclude, bool isPokemon)
 | 
			
		||||
@@ -27,7 +27,7 @@ public class TriviaQuestionPool
 | 
			
		||||
 | 
			
		||||
        if (isPokemon)
 | 
			
		||||
        {
 | 
			
		||||
            var num = _rng.Next(1, maxPokemonId + 1);
 | 
			
		||||
            var num = _rng.Next(1, _maxPokemonId + 1);
 | 
			
		||||
            return new("Who's That Pokémon?",
 | 
			
		||||
                Map[num].ToTitleCase(),
 | 
			
		||||
                "Pokemon",
 | 
			
		||||
@@ -36,7 +36,15 @@ public class TriviaQuestionPool
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        TriviaQuestion randomQuestion;
 | 
			
		||||
        while (exclude.Contains(randomQuestion = Pool[_rng.Next(0, Pool.Length)])) ;
 | 
			
		||||
        while (exclude.Contains(randomQuestion = Pool[_rng.Next(0, Pool.Length)]))
 | 
			
		||||
        { 
 | 
			
		||||
            // if too many questions are excluded, clear the exclusion list and start over
 | 
			
		||||
            if (exclude.Count > Pool.Length / 10 * 9)
 | 
			
		||||
            {
 | 
			
		||||
                exclude.Clear();
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return randomQuestion;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -16,15 +16,15 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
        Q = 2, Queue = 2, Playlist = 2, Pl = 2
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public const string MusicIconUrl = "http://i.imgur.com/nhKS3PT.png";
 | 
			
		||||
    public const string MUSIC_ICON_URL = "http://i.imgur.com/nhKS3PT.png";
 | 
			
		||||
 | 
			
		||||
    private const int LQ_ITEMS_PER_PAGE = 9;
 | 
			
		||||
 | 
			
		||||
    private static readonly SemaphoreSlim voiceChannelLock = new(1, 1);
 | 
			
		||||
    private static readonly SemaphoreSlim _voiceChannelLock = new(1, 1);
 | 
			
		||||
    private readonly ILogCommandService _logService;
 | 
			
		||||
 | 
			
		||||
    public Music(ILogCommandService _logService)
 | 
			
		||||
        => this._logService = _logService;
 | 
			
		||||
    public Music(ILogCommandService logService)
 | 
			
		||||
        => _logService = logService;
 | 
			
		||||
 | 
			
		||||
    private async Task<bool> ValidateAsync()
 | 
			
		||||
    {
 | 
			
		||||
@@ -50,7 +50,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
    private async Task EnsureBotInVoiceChannelAsync(ulong voiceChannelId, IGuildUser botUser = null)
 | 
			
		||||
    {
 | 
			
		||||
        botUser ??= await ctx.Guild.GetCurrentUserAsync();
 | 
			
		||||
        await voiceChannelLock.WaitAsync();
 | 
			
		||||
        await _voiceChannelLock.WaitAsync();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (botUser.VoiceChannel?.Id is null || !_service.TryGetMusicPlayer(ctx.Guild.Id, out _))
 | 
			
		||||
@@ -58,7 +58,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            voiceChannelLock.Release();
 | 
			
		||||
            _voiceChannelLock.Release();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -111,7 +111,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
        {
 | 
			
		||||
            var embed = _eb.Create()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithAuthor(GetText(strs.queued_song) + " #" + (index + 1), MusicIconUrl)
 | 
			
		||||
                           .WithAuthor(GetText(strs.queued_song) + " #" + (index + 1), MUSIC_ICON_URL)
 | 
			
		||||
                           .WithDescription($"{trackInfo.PrettyName()}\n{GetText(strs.queue)} ")
 | 
			
		||||
                           .WithFooter(trackInfo.Platform.ToString());
 | 
			
		||||
 | 
			
		||||
@@ -272,7 +272,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IEmbedBuilder printAction(int curPage)
 | 
			
		||||
        IEmbedBuilder PrintAction(int curPage)
 | 
			
		||||
        {
 | 
			
		||||
            var desc = string.Empty;
 | 
			
		||||
            var current = mp.GetCurrentTrack(out var currentIndex);
 | 
			
		||||
@@ -317,7 +317,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
 | 
			
		||||
            var embed = _eb.Create()
 | 
			
		||||
                           .WithAuthor(GetText(strs.player_queue(curPage + 1, (tracks.Count / LQ_ITEMS_PER_PAGE) + 1)),
 | 
			
		||||
                               MusicIconUrl)
 | 
			
		||||
                               MUSIC_ICON_URL)
 | 
			
		||||
                           .WithDescription(desc)
 | 
			
		||||
                           .WithFooter($"  {mp.PrettyVolume()}  |  🎶 {tracks.Count}  |  ⌛ {mp.PrettyTotalTime()}  ")
 | 
			
		||||
                           .WithOkColor();
 | 
			
		||||
@@ -325,7 +325,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
            return embed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(page, printAction, tracks.Count, LQ_ITEMS_PER_PAGE, false);
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(page, PrintAction, tracks.Count, LQ_ITEMS_PER_PAGE, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // search
 | 
			
		||||
@@ -409,7 +409,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = _eb.Create()
 | 
			
		||||
                       .WithAuthor(GetText(strs.removed_song) + " #" + index, MusicIconUrl)
 | 
			
		||||
                       .WithAuthor(GetText(strs.removed_song) + " #" + index, MUSIC_ICON_URL)
 | 
			
		||||
                       .WithDescription(song.PrettyName())
 | 
			
		||||
                       .WithFooter(song.PrettyInfo())
 | 
			
		||||
                       .WithErrorColor();
 | 
			
		||||
@@ -578,7 +578,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
 | 
			
		||||
        var embed = _eb.Create()
 | 
			
		||||
                       .WithTitle(track.Title.TrimTo(65))
 | 
			
		||||
                       .WithAuthor(GetText(strs.song_moved), MusicIconUrl)
 | 
			
		||||
                       .WithAuthor(GetText(strs.song_moved), MUSIC_ICON_URL)
 | 
			
		||||
                       .AddField(GetText(strs.from_position), $"#{from + 1}", true)
 | 
			
		||||
                       .AddField(GetText(strs.to_position), $"#{to + 1}", true)
 | 
			
		||||
                       .WithOkColor();
 | 
			
		||||
@@ -667,7 +667,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
 | 
			
		||||
        var embed = _eb.Create()
 | 
			
		||||
                       .WithOkColor()
 | 
			
		||||
                       .WithAuthor(GetText(strs.now_playing), MusicIconUrl)
 | 
			
		||||
                       .WithAuthor(GetText(strs.now_playing), MUSIC_ICON_URL)
 | 
			
		||||
                       .WithDescription(currentTrack.PrettyName())
 | 
			
		||||
                       .WithThumbnailUrl(currentTrack.Thumbnail)
 | 
			
		||||
                       .WithFooter(
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ public sealed partial class Music
 | 
			
		||||
        private async Task EnsureBotInVoiceChannelAsync(ulong voiceChannelId, IGuildUser botUser = null)
 | 
			
		||||
        {
 | 
			
		||||
            botUser ??= await ctx.Guild.GetCurrentUserAsync();
 | 
			
		||||
            await voiceChannelLock.WaitAsync();
 | 
			
		||||
            await _voiceChannelLock.WaitAsync();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (botUser.VoiceChannel?.Id is null || !_service.TryGetMusicPlayer(ctx.Guild.Id, out _))
 | 
			
		||||
@@ -31,7 +31,7 @@ public sealed partial class Music
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                voiceChannelLock.Release();
 | 
			
		||||
                _voiceChannelLock.Release();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +50,7 @@ public sealed partial class Music
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = _eb.Create(ctx)
 | 
			
		||||
                           .WithAuthor(GetText(strs.playlists_page(num)), MusicIconUrl)
 | 
			
		||||
                           .WithAuthor(GetText(strs.playlists_page(num)), MUSIC_ICON_URL)
 | 
			
		||||
                           .WithDescription(string.Join("\n",
 | 
			
		||||
                               playlists.Select(r => GetText(strs.playlists(r.Id, r.Name, r.Author, r.Songs.Count)))))
 | 
			
		||||
                           .WithOkColor();
 | 
			
		||||
 
 | 
			
		||||
@@ -189,7 +189,7 @@ public sealed class MusicService : IMusicService
 | 
			
		||||
            _ = lastFinishedMessage?.DeleteAsync();
 | 
			
		||||
            var embed = _eb.Create()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithAuthor(GetText(guildId, strs.finished_song), Music.MusicIconUrl)
 | 
			
		||||
                           .WithAuthor(GetText(guildId, strs.finished_song), Music.MUSIC_ICON_URL)
 | 
			
		||||
                           .WithDescription(trackInfo.PrettyName())
 | 
			
		||||
                           .WithFooter(trackInfo.PrettyTotalTime());
 | 
			
		||||
 | 
			
		||||
@@ -205,7 +205,7 @@ public sealed class MusicService : IMusicService
 | 
			
		||||
            _ = lastPlayingMessage?.DeleteAsync();
 | 
			
		||||
            var embed = _eb.Create()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithAuthor(GetText(guildId, strs.playing_song(index + 1)), Music.MusicIconUrl)
 | 
			
		||||
                           .WithAuthor(GetText(guildId, strs.playing_song(index + 1)), Music.MUSIC_ICON_URL)
 | 
			
		||||
                           .WithDescription(trackInfo.PrettyName())
 | 
			
		||||
                           .WithFooter($"{mp.PrettyVolume()} | {trackInfo.PrettyInfo()}");
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ namespace NadekoBot.Modules.Music.Common;
 | 
			
		||||
 | 
			
		||||
public sealed class MultimediaTimer : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    private LpTimeProcDelegate _lpTimeProc;
 | 
			
		||||
    private LpTimeProcDelegate lpTimeProc;
 | 
			
		||||
    private readonly uint _eventId;
 | 
			
		||||
    private readonly Action<object> _callback;
 | 
			
		||||
    private readonly object _state;
 | 
			
		||||
@@ -18,8 +18,8 @@ public sealed class MultimediaTimer : IDisposable
 | 
			
		||||
        _callback = callback;
 | 
			
		||||
        _state = state;
 | 
			
		||||
 | 
			
		||||
        _lpTimeProc = CallbackInternal;
 | 
			
		||||
        _eventId = timeSetEvent((uint)period, 1, _lpTimeProc, 0, TimerMode.Periodic);
 | 
			
		||||
        lpTimeProc = CallbackInternal;
 | 
			
		||||
        _eventId = timeSetEvent((uint)period, 1, lpTimeProc, 0, TimerMode.Periodic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -58,13 +58,13 @@ public sealed class MultimediaTimer : IDisposable
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     The timeKillEvent function cancels a specified timer event.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="uTimerID">
 | 
			
		||||
    /// <param name="uTimerId">
 | 
			
		||||
    ///     Identifier of the timer event to cancel.
 | 
			
		||||
    ///     This identifier was returned by the timeSetEvent function when the timer event was set up.
 | 
			
		||||
    /// </param>
 | 
			
		||||
    /// <returns>Returns TIMERR_NOERROR if successful or MMSYSERR_INVALPARAM if the specified timer event does not exist.</returns>
 | 
			
		||||
    [DllImport("Winmm.dll")]
 | 
			
		||||
    private static extern int timeKillEvent(uint uTimerID);
 | 
			
		||||
    private static extern int timeKillEvent(uint uTimerId);
 | 
			
		||||
 | 
			
		||||
    private void CallbackInternal(
 | 
			
		||||
        uint uTimerId,
 | 
			
		||||
@@ -76,12 +76,12 @@ public sealed class MultimediaTimer : IDisposable
 | 
			
		||||
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        _lpTimeProc = default;
 | 
			
		||||
        lpTimeProc = default;
 | 
			
		||||
        timeKillEvent(_eventId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private delegate void LpTimeProcDelegate(
 | 
			
		||||
        uint uTimerID,
 | 
			
		||||
        uint uTimerId,
 | 
			
		||||
        uint uMsg,
 | 
			
		||||
        int dwUser,
 | 
			
		||||
        int dw1,
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
 | 
			
		||||
    public float Volume { get; private set; } = 1.0f;
 | 
			
		||||
 | 
			
		||||
    private readonly AdjustVolumeDelegate AdjustVolume;
 | 
			
		||||
    private readonly AdjustVolumeDelegate _adjustVolume;
 | 
			
		||||
    private readonly VoiceClient _vc;
 | 
			
		||||
 | 
			
		||||
    private readonly IMusicQueue _queue;
 | 
			
		||||
@@ -30,8 +30,8 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
    private readonly IVoiceProxy _proxy;
 | 
			
		||||
    private readonly ISongBuffer _songBuffer;
 | 
			
		||||
 | 
			
		||||
    private bool _skipped;
 | 
			
		||||
    private int? _forceIndex;
 | 
			
		||||
    private bool skipped;
 | 
			
		||||
    private int? forceIndex;
 | 
			
		||||
    private readonly Thread _thread;
 | 
			
		||||
    private readonly Random _rng;
 | 
			
		||||
 | 
			
		||||
@@ -48,9 +48,9 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
 | 
			
		||||
        _vc = GetVoiceClient(qualityPreset);
 | 
			
		||||
        if (_vc.BitDepth == 16)
 | 
			
		||||
            AdjustVolume = AdjustVolumeInt16;
 | 
			
		||||
            _adjustVolume = AdjustVolumeInt16;
 | 
			
		||||
        else
 | 
			
		||||
            AdjustVolume = AdjustVolumeFloat32;
 | 
			
		||||
            _adjustVolume = AdjustVolumeFloat32;
 | 
			
		||||
 | 
			
		||||
        _songBuffer = new PoopyBufferImmortalized(_vc.InputLength);
 | 
			
		||||
 | 
			
		||||
@@ -95,9 +95,9 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_skipped)
 | 
			
		||||
            if (skipped)
 | 
			
		||||
            {
 | 
			
		||||
                _skipped = false;
 | 
			
		||||
                skipped = false;
 | 
			
		||||
                _queue.Advance();
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
@@ -182,9 +182,9 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
                {
 | 
			
		||||
                    // doing the skip this way instead of in the condition
 | 
			
		||||
                    // ensures that a song will for sure be skipped
 | 
			
		||||
                    if (_skipped)
 | 
			
		||||
                    if (skipped)
 | 
			
		||||
                    {
 | 
			
		||||
                        _skipped = false;
 | 
			
		||||
                        skipped = false;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
@@ -268,10 +268,9 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
                _ = OnCompleted?.Invoke(this, track);
 | 
			
		||||
 | 
			
		||||
                HandleQueuePostTrack();
 | 
			
		||||
                _skipped = false;
 | 
			
		||||
                skipped = false;
 | 
			
		||||
 | 
			
		||||
                _ = _proxy.StopSpeakingAsync();
 | 
			
		||||
                ;
 | 
			
		||||
 | 
			
		||||
                await Task.Delay(100);
 | 
			
		||||
            }
 | 
			
		||||
@@ -285,16 +284,16 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
        // if nothing is read from the buffer, song is finished
 | 
			
		||||
        if (data.Length == 0) return null;
 | 
			
		||||
 | 
			
		||||
        AdjustVolume(data, Volume);
 | 
			
		||||
        _adjustVolume(data, Volume);
 | 
			
		||||
        return _proxy.SendPcmFrame(vc, data, length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void HandleQueuePostTrack()
 | 
			
		||||
    {
 | 
			
		||||
        if (_forceIndex is { } forceIndex)
 | 
			
		||||
        if (this.forceIndex is { } forceIndex)
 | 
			
		||||
        {
 | 
			
		||||
            _queue.SetIndex(forceIndex);
 | 
			
		||||
            _forceIndex = null;
 | 
			
		||||
            this.forceIndex = null;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -419,7 +418,7 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
    public void Clear()
 | 
			
		||||
    {
 | 
			
		||||
        _queue.Clear();
 | 
			
		||||
        _skipped = true;
 | 
			
		||||
        skipped = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IReadOnlyCollection<IQueuedTrackInfo> GetQueuedTracks()
 | 
			
		||||
@@ -430,7 +429,7 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
 | 
			
		||||
    public void Next()
 | 
			
		||||
    {
 | 
			
		||||
        _skipped = true;
 | 
			
		||||
        skipped = true;
 | 
			
		||||
        IsStopped = false;
 | 
			
		||||
        IsPaused = false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -439,8 +438,8 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
    {
 | 
			
		||||
        if (_queue.SetIndex(index))
 | 
			
		||||
        {
 | 
			
		||||
            _forceIndex = index;
 | 
			
		||||
            _skipped = true;
 | 
			
		||||
            forceIndex = index;
 | 
			
		||||
            skipped = true;
 | 
			
		||||
            IsStopped = false;
 | 
			
		||||
            IsPaused = false;
 | 
			
		||||
            return true;
 | 
			
		||||
@@ -463,7 +462,7 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
        IsKilled = true;
 | 
			
		||||
        IsStopped = true;
 | 
			
		||||
        IsPaused = false;
 | 
			
		||||
        _skipped = true;
 | 
			
		||||
        skipped = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool TryRemoveTrackAt(int index, out IQueuedTrackInfo? trackInfo)
 | 
			
		||||
@@ -472,7 +471,7 @@ public sealed class MusicPlayer : IMusicPlayer
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        if (isCurrent)
 | 
			
		||||
            _skipped = true;
 | 
			
		||||
            skipped = true;
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -91,7 +91,7 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
 | 
			
		||||
            var currentNode = tracks.First!;
 | 
			
		||||
            int i;
 | 
			
		||||
            for (i = 1; i <= this.index; i++)
 | 
			
		||||
            for (i = 1; i <= index; i++)
 | 
			
		||||
                currentNode = currentNode.Next!; // can't be null because index is always in range of the count
 | 
			
		||||
 | 
			
		||||
            var added = new QueuedTrackInfo(trackInfo, queuer);
 | 
			
		||||
@@ -110,7 +110,7 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
            foreach (var track in toEnqueue)
 | 
			
		||||
            {
 | 
			
		||||
                var added = new QueuedTrackInfo(track, queuer);
 | 
			
		||||
                this.tracks.AddLast(added);
 | 
			
		||||
                tracks.AddLast(added);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -156,7 +156,7 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
            if (newIndex < 0 || newIndex >= tracks.Count)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            this.index = newIndex;
 | 
			
		||||
            index = newIndex;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -170,11 +170,11 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
        trackInfo = removedNode.Value;
 | 
			
		||||
        tracks.Remove(removedNode);
 | 
			
		||||
 | 
			
		||||
        if (i <= this.index)
 | 
			
		||||
            --this.index;
 | 
			
		||||
        if (i <= index)
 | 
			
		||||
            --index;
 | 
			
		||||
 | 
			
		||||
        if (this.index < 0)
 | 
			
		||||
            this.index = Count;
 | 
			
		||||
        if (index < 0)
 | 
			
		||||
            index = Count;
 | 
			
		||||
 | 
			
		||||
        // if it was the last song in the queue
 | 
			
		||||
        // // wrap back to start
 | 
			
		||||
@@ -303,7 +303,7 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
            if (remoteAt < 0 || remoteAt >= tracks.Count)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if (remoteAt == this.index) isCurrent = true;
 | 
			
		||||
            if (remoteAt == index) isCurrent = true;
 | 
			
		||||
 | 
			
		||||
            RemoveAtInternal(remoteAt, out trackInfo);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,12 @@ namespace NadekoBot.Modules.Music;
 | 
			
		||||
 | 
			
		||||
public sealed class YtdlYoutubeResolver : IYoutubeResolver
 | 
			
		||||
{
 | 
			
		||||
    private static readonly string[] durationFormats =
 | 
			
		||||
    private static readonly string[] _durationFormats =
 | 
			
		||||
    {
 | 
			
		||||
        "ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex expiryRegex = new(@"(?:[\?\&]expire\=(?<timestamp>\d+))");
 | 
			
		||||
    private static readonly Regex _expiryRegex = new(@"(?:[\?\&]expire\=(?<timestamp>\d+))");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex _simplePlaylistRegex = new(@"&list=(?<id>[\w\-]{12,})", RegexOptions.Compiled);
 | 
			
		||||
@@ -85,7 +85,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver
 | 
			
		||||
            return default;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!TimeSpan.TryParseExact(dataArray[4], durationFormats, CultureInfo.InvariantCulture, out var time))
 | 
			
		||||
        if (!TimeSpan.TryParseExact(dataArray[4], _durationFormats, CultureInfo.InvariantCulture, out var time))
 | 
			
		||||
            time = TimeSpan.Zero;
 | 
			
		||||
 | 
			
		||||
        var thumbnail = Uri.IsWellFormedUriString(dataArray[3], UriKind.Absolute) ? dataArray[3].Trim() : string.Empty;
 | 
			
		||||
@@ -108,7 +108,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver
 | 
			
		||||
 | 
			
		||||
    private static TimeSpan GetExpiry(string streamUrl)
 | 
			
		||||
    {
 | 
			
		||||
        var match = expiryRegex.Match(streamUrl);
 | 
			
		||||
        var match = _expiryRegex.Match(streamUrl);
 | 
			
		||||
        if (match.Success && double.TryParse(match.Groups["timestamp"].ToString(), out var timestamp))
 | 
			
		||||
        {
 | 
			
		||||
            var realExpiry = timestamp.ToUnixTimestamp() - DateTime.UtcNow;
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ public partial class NSFW : NadekoModule<ISearchImagesService>
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task InternalButts(IMessageChannel Channel)
 | 
			
		||||
    private async Task InternalButts(IMessageChannel channel)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -47,7 +47,7 @@ public partial class NSFW : NadekoModule<ISearchImagesService>
 | 
			
		||||
                    await http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}"))[0];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}");
 | 
			
		||||
            await channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}");
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ public class SearchImagesService : ISearchImagesService, INService
 | 
			
		||||
    private readonly IHttpClientFactory _httpFactory;
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
 | 
			
		||||
    private readonly object taglock = new();
 | 
			
		||||
    private readonly object _taglock = new();
 | 
			
		||||
 | 
			
		||||
    public SearchImagesService(
 | 
			
		||||
        DbService db,
 | 
			
		||||
@@ -188,7 +188,7 @@ public class SearchImagesService : ISearchImagesService, INService
 | 
			
		||||
 | 
			
		||||
    public ValueTask<bool> ToggleBlacklistTag(ulong guildId, string tag)
 | 
			
		||||
    {
 | 
			
		||||
        lock (taglock)
 | 
			
		||||
        lock (_taglock)
 | 
			
		||||
        {
 | 
			
		||||
            tag = tag.Trim().ToLowerInvariant();
 | 
			
		||||
            var blacklistedTags = BlacklistedTags.GetOrAdd(guildId, new HashSet<string>());
 | 
			
		||||
@@ -214,7 +214,7 @@ public class SearchImagesService : ISearchImagesService, INService
 | 
			
		||||
 | 
			
		||||
    public ValueTask<string[]> GetBlacklistedTags(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        lock (taglock)
 | 
			
		||||
        lock (_taglock)
 | 
			
		||||
        {
 | 
			
		||||
            if (BlacklistedTags.TryGetValue(guildId, out var tags)) return new(tags.ToArray());
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ public abstract class ImageDownloader<T> : IImageDownloader
 | 
			
		||||
    public Booru Booru { get; }
 | 
			
		||||
    protected readonly HttpClient _http;
 | 
			
		||||
 | 
			
		||||
    protected JsonSerializerOptions _serializerOptions = new()
 | 
			
		||||
    protected readonly JsonSerializerOptions _serializerOptions = new()
 | 
			
		||||
    {
 | 
			
		||||
        PropertyNameCaseInsensitive = true,
 | 
			
		||||
        NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ public sealed class BlacklistService : IEarlyBehavior
 | 
			
		||||
 | 
			
		||||
    private ValueTask OnReload(BlacklistEntry[] newBlacklist)
 | 
			
		||||
    {
 | 
			
		||||
        this.blacklist = newBlacklist;
 | 
			
		||||
        blacklist = newBlacklist;
 | 
			
		||||
        return default;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ public partial class Permissions
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class GlobalPermissionCommands : NadekoSubmodule
 | 
			
		||||
    {
 | 
			
		||||
        private GlobalPermissionService _service;
 | 
			
		||||
        private readonly GlobalPermissionService _service;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
 | 
			
		||||
        public GlobalPermissionCommands(GlobalPermissionService service, DbService db)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ public class CryptoService : INService
 | 
			
		||||
    private readonly IHttpClientFactory _httpFactory;
 | 
			
		||||
    private readonly IBotCredentials _creds;
 | 
			
		||||
 | 
			
		||||
    private readonly SemaphoreSlim getCryptoLock = new(1, 1);
 | 
			
		||||
    private readonly SemaphoreSlim _getCryptoLock = new(1, 1);
 | 
			
		||||
 | 
			
		||||
    public CryptoService(IDataCache cache, IHttpClientFactory httpFactory, IBotCredentials creds)
 | 
			
		||||
    {
 | 
			
		||||
@@ -52,7 +52,7 @@ public class CryptoService : INService
 | 
			
		||||
 | 
			
		||||
    public async Task<List<CryptoResponseData>> CryptoData()
 | 
			
		||||
    {
 | 
			
		||||
        await getCryptoLock.WaitAsync();
 | 
			
		||||
        await _getCryptoLock.WaitAsync();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var fullStrData = await _cache.GetOrAddCachedDataAsync("nadeko:crypto_data",
 | 
			
		||||
@@ -81,7 +81,7 @@ public class CryptoService : INService
 | 
			
		||||
                "",
 | 
			
		||||
                TimeSpan.FromHours(1));
 | 
			
		||||
 | 
			
		||||
            return JsonConvert.DeserializeObject<CryptoResponse>(fullStrData).Data;
 | 
			
		||||
            return JsonConvert.DeserializeObject<CryptoResponse>(fullStrData)?.Data ?? new();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -90,7 +90,7 @@ public class CryptoService : INService
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            getCryptoLock.Release();
 | 
			
		||||
            _getCryptoLock.Release();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,7 +10,7 @@ public partial class Searches
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class FeedCommands : NadekoSubmodule<FeedsService>
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly Regex YtChannelRegex =
 | 
			
		||||
        private static readonly Regex _ytChannelRegex =
 | 
			
		||||
            new(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?<channelid>[a-zA-Z0-9\-]{1,})");
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -18,7 +18,7 @@ public partial class Searches
 | 
			
		||||
        [UserPerm(GuildPerm.ManageMessages)]
 | 
			
		||||
        public partial Task YtUploadNotif(string url, [Leftover] ITextChannel channel = null)
 | 
			
		||||
        {
 | 
			
		||||
            var m = YtChannelRegex.Match(url);
 | 
			
		||||
            var m = _ytChannelRegex.Match(url);
 | 
			
		||||
            if (!m.Success) return ReplyErrorLocalizedAsync(strs.invalid_input);
 | 
			
		||||
 | 
			
		||||
            var channelId = m.Groups["channelid"].Value;
 | 
			
		||||
@@ -38,7 +38,7 @@ public partial class Searches
 | 
			
		||||
                channel ??= (ITextChannel)ctx.Channel;
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var feeds = await FeedReader.ReadAsync(url);
 | 
			
		||||
                    await FeedReader.ReadAsync(url);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -115,7 +115,6 @@ public partial class Searches
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        public async partial Task Osu5(string user, [Leftover] string mode = null)
 | 
			
		||||
        {
 | 
			
		||||
            ;
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
 | 
			
		||||
            {
 | 
			
		||||
                await SendErrorAsync("An osu! API key is required.");
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ public class SearchesService : INService
 | 
			
		||||
    private readonly NadekoRandom _rng;
 | 
			
		||||
    private readonly List<string> _yomamaJokes;
 | 
			
		||||
 | 
			
		||||
    private readonly object yomamaLock = new();
 | 
			
		||||
    private readonly object _yomamaLock = new();
 | 
			
		||||
    private int yomamaJokeIndex;
 | 
			
		||||
 | 
			
		||||
    public SearchesService(
 | 
			
		||||
@@ -224,9 +224,11 @@ public class SearchesService : INService
 | 
			
		||||
 | 
			
		||||
            using var req = new HttpRequestMessage(HttpMethod.Get,
 | 
			
		||||
                "http://api.timezonedb.com/v2.1/get-time-zone?"
 | 
			
		||||
                + $"key={_creds.TimezoneDbApiKey}&format=json&"
 | 
			
		||||
                + "by=position&"
 | 
			
		||||
                + $"lat={geoData.Lat}&lng={geoData.Lon}");
 | 
			
		||||
                + $"key={_creds.TimezoneDbApiKey}"
 | 
			
		||||
                + $"&format=json"
 | 
			
		||||
                + $"&by=position"
 | 
			
		||||
                + $"&lat={geoData.Lat}"
 | 
			
		||||
                + $"&lng={geoData.Lon}");
 | 
			
		||||
            using var geoRes = await http.SendAsync(req);
 | 
			
		||||
            var resString = await geoRes.Content.ReadAsStringAsync();
 | 
			
		||||
            var timeObj = JsonConvert.DeserializeObject<TimeZoneResult>(resString);
 | 
			
		||||
@@ -274,7 +276,7 @@ public class SearchesService : INService
 | 
			
		||||
    public Task<string> GetYomamaJoke()
 | 
			
		||||
    {
 | 
			
		||||
        string joke;
 | 
			
		||||
        lock (yomamaLock)
 | 
			
		||||
        lock (_yomamaLock)
 | 
			
		||||
        {
 | 
			
		||||
            if (yomamaJokeIndex >= _yomamaJokes.Count)
 | 
			
		||||
            {
 | 
			
		||||
@@ -428,8 +430,6 @@ public class SearchesService : INService
 | 
			
		||||
 | 
			
		||||
    public async Task<int> GetSteamAppIdByName(string query)
 | 
			
		||||
    {
 | 
			
		||||
        var redis = _cache.Redis;
 | 
			
		||||
        var db = redis.GetDatabase();
 | 
			
		||||
        const string steamGameIdsKey = "steam_names_to_appid";
 | 
			
		||||
        // var exists = await db.KeyExistsAsync(steamGameIdsKey);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ public partial class Searches
 | 
			
		||||
        [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
        public async partial Task StreamsClear()
 | 
			
		||||
        {
 | 
			
		||||
            var count = _service.ClearAllStreams(ctx.Guild.Id);
 | 
			
		||||
            await _service.ClearAllStreams(ctx.Guild.Id);
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.streams_cleared);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -116,7 +116,7 @@ public sealed class StreamNotificationService : INService
 | 
			
		||||
                            return;
 | 
			
		||||
 | 
			
		||||
                        var deleteGroups = failingStreams.GroupBy(x => x.Type)
 | 
			
		||||
                                                         .ToDictionary(x => x.Key, x => x.Select(x => x.Name).ToList());
 | 
			
		||||
                                                         .ToDictionary(x => x.Key, x => x.Select(y => y.Name).ToList());
 | 
			
		||||
 | 
			
		||||
                        using var uow = _db.GetDbContext();
 | 
			
		||||
                        foreach (var kvp in deleteGroups)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
namespace NadekoBot.Modules.Searches.Services;
 | 
			
		||||
 | 
			
		||||
// public class YtTrackService : INService
 | 
			
		||||
// {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
namespace NadekoBot.Modules.Searches.Common;
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// public class TwitchResponse
 | 
			
		||||
// {
 | 
			
		||||
//     public List<StreamApiData> Data { get; set; }
 | 
			
		||||
//
 | 
			
		||||
//     public class StreamApiData
 | 
			
		||||
//     {
 | 
			
		||||
//         public string Id { get; set; }
 | 
			
		||||
//         public string UserId { get; set; }
 | 
			
		||||
//         public string UserName { get; set; }
 | 
			
		||||
//         public string GameId { get; set; }
 | 
			
		||||
//         public string Type { get; set; }
 | 
			
		||||
//         public string Title { get; set; }
 | 
			
		||||
//         public int ViewerCount { get; set; }
 | 
			
		||||
//         public string Language { get; set; }
 | 
			
		||||
//         public string ThumbnailUrl { get; set; }
 | 
			
		||||
//         public DateTime StartedAt { get; set; }
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
@@ -34,8 +34,6 @@ public partial class Utility
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async partial Task Alias(string trigger, [Leftover] string mapping = null)
 | 
			
		||||
        {
 | 
			
		||||
            var channel = (ITextChannel)ctx.Channel;
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(trigger))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +50,6 @@ public partial class Utility
 | 
			
		||||
                await using (var uow = _db.GetDbContext())
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases));
 | 
			
		||||
                    var toAdd = new CommandAlias { Mapping = mapping, Trigger = trigger };
 | 
			
		||||
                    var tr = config.CommandAliases.FirstOrDefault(x => x.Trigger == trigger);
 | 
			
		||||
                    if (tr is not null)
 | 
			
		||||
                        uow.Set<CommandAlias>().Remove(tr);
 | 
			
		||||
@@ -84,9 +81,9 @@ public partial class Utility
 | 
			
		||||
                    {
 | 
			
		||||
                        var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases));
 | 
			
		||||
                        var toAdd = new CommandAlias { Mapping = mapping, Trigger = trigger };
 | 
			
		||||
                        var toRemove = config.CommandAliases.Where(x => x.Trigger == trigger);
 | 
			
		||||
                        var toRemove = config.CommandAliases.Where(x => x.Trigger == trigger).ToArray();
 | 
			
		||||
                        if (toRemove.Any())
 | 
			
		||||
                            uow.RemoveRange(toRemove.ToArray());
 | 
			
		||||
                            uow.RemoveRange(toRemove);
 | 
			
		||||
                        config.CommandAliases.Add(toAdd);
 | 
			
		||||
                        uow.SaveChanges();
 | 
			
		||||
                    }
 | 
			
		||||
@@ -103,7 +100,6 @@ public partial class Utility
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async partial Task AliasList(int page = 1)
 | 
			
		||||
        {
 | 
			
		||||
            var channel = (ITextChannel)ctx.Channel;
 | 
			
		||||
            page -= 1;
 | 
			
		||||
 | 
			
		||||
            if (page < 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -107,7 +107,7 @@ public partial class Utility
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(value))
 | 
			
		||||
                    value = "-";
 | 
			
		||||
 | 
			
		||||
                if (prop != "currency.sign") value = Format.Code(Format.Sanitize(value?.TrimTo(1000)), "json");
 | 
			
		||||
                if (prop != "currency.sign") value = Format.Code(Format.Sanitize(value.TrimTo(1000)), "json");
 | 
			
		||||
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
@@ -134,7 +134,7 @@ public partial class Utility
 | 
			
		||||
            await ctx.OkAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetPropsAndValuesString(IConfigService config, IEnumerable<string> names)
 | 
			
		||||
        private string GetPropsAndValuesString(IConfigService config, IReadOnlyCollection<string> names)
 | 
			
		||||
        {
 | 
			
		||||
            var propValues = names.Select(pr =>
 | 
			
		||||
            {
 | 
			
		||||
@@ -142,7 +142,7 @@ public partial class Utility
 | 
			
		||||
                if (pr != "currency.sign")
 | 
			
		||||
                    val = val?.TrimTo(28);
 | 
			
		||||
                return val?.Replace("\n", "") ?? "-";
 | 
			
		||||
            });
 | 
			
		||||
            }).ToList();
 | 
			
		||||
 | 
			
		||||
            var strings = names.Zip(propValues, (name, value) => $"{name,-25} = {value}\n");
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -150,7 +150,7 @@ public class PatreonRewardsService : INService
 | 
			
		||||
                           + "?fields%5Bmember%5D=full_name,currently_entitled_amount_cents"
 | 
			
		||||
                           + "&fields%5Buser%5D=social_connections"
 | 
			
		||||
                           + "&include=user";
 | 
			
		||||
                PatreonResponse data = null;
 | 
			
		||||
                PatreonResponse data;
 | 
			
		||||
                do
 | 
			
		||||
                {
 | 
			
		||||
                    var res = await http.GetStringAsync(page);
 | 
			
		||||
 
 | 
			
		||||
@@ -62,12 +62,16 @@ public partial class Utility
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (quotes.Any())
 | 
			
		||||
            {
 | 
			
		||||
                await SendConfirmAsync(GetText(strs.quotes_page(page + 1)),
 | 
			
		||||
                    string.Join("\n",
 | 
			
		||||
                        quotes.Select(q
 | 
			
		||||
                            => $"`#{q.Id}` {Format.Bold(q.Keyword.SanitizeAllMentions()),-20} by {q.AuthorName.SanitizeAllMentions()}")));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.quotes_page_none);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,6 @@ public sealed class RunningRepeater
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Calculate when is the proper time to run the repeater again based on initial time repeater ran.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="repeaterter"></param>
 | 
			
		||||
    /// <param name="initialDateTime">Initial time repeater ran at (or should run at).</param>
 | 
			
		||||
    private DateTime CalculateInitialInterval(DateTime initialDateTime)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ public class ConverterService : INService
 | 
			
		||||
        _httpFactory = factory;
 | 
			
		||||
 | 
			
		||||
        if (client.ShardId == 0)
 | 
			
		||||
            _currencyUpdater = new(async shouldLoad => await UpdateCurrency((bool)shouldLoad),
 | 
			
		||||
            _currencyUpdater = new(async shouldLoad => await UpdateCurrency((bool)shouldLoad!),
 | 
			
		||||
                client.ShardId == 0,
 | 
			
		||||
                TimeSpan.Zero,
 | 
			
		||||
                _updateInterval);
 | 
			
		||||
@@ -55,7 +55,9 @@ public class ConverterService : INService
 | 
			
		||||
                                         .ToArray();
 | 
			
		||||
 | 
			
		||||
                var fileData = JsonConvert.DeserializeObject<ConvertUnit[]>(File.ReadAllText("data/units.json"))
 | 
			
		||||
                                          .Where(x => x.UnitType != "currency");
 | 
			
		||||
                                          ?.Where(x => x.UnitType != "currency");
 | 
			
		||||
                if (fileData is null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var data = JsonConvert.SerializeObject(range.Append(baseType).Concat(fileData).ToList());
 | 
			
		||||
                _cache.Redis.GetDatabase().StringSet("converter_units", data);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ namespace NadekoBot.Modules.Utility.Services;
 | 
			
		||||
 | 
			
		||||
public class VerboseErrorsService : INService
 | 
			
		||||
{
 | 
			
		||||
    private readonly ConcurrentHashSet<ulong> guildsEnabled;
 | 
			
		||||
    private readonly ConcurrentHashSet<ulong> _guildsEnabled;
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly CommandHandler _ch;
 | 
			
		||||
    private readonly HelpService _hs;
 | 
			
		||||
@@ -23,12 +23,12 @@ public class VerboseErrorsService : INService
 | 
			
		||||
 | 
			
		||||
        _ch.CommandErrored += LogVerboseError;
 | 
			
		||||
 | 
			
		||||
        guildsEnabled = new(bot.AllGuildConfigs.Where(x => x.VerboseErrors).Select(x => x.GuildId));
 | 
			
		||||
        _guildsEnabled = new(bot.AllGuildConfigs.Where(x => x.VerboseErrors).Select(x => x.GuildId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task LogVerboseError(CommandInfo cmd, ITextChannel channel, string reason)
 | 
			
		||||
    {
 | 
			
		||||
        if (channel is null || !guildsEnabled.Contains(channel.GuildId))
 | 
			
		||||
        if (channel is null || !_guildsEnabled.Contains(channel.GuildId))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
@@ -60,9 +60,9 @@ public class VerboseErrorsService : INService
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ((bool)enabled) // This doesn't need to be duplicated inside the using block
 | 
			
		||||
            guildsEnabled.Add(guildId);
 | 
			
		||||
            _guildsEnabled.Add(guildId);
 | 
			
		||||
        else
 | 
			
		||||
            guildsEnabled.TryRemove(guildId);
 | 
			
		||||
            _guildsEnabled.TryRemove(guildId);
 | 
			
		||||
 | 
			
		||||
        return (bool)enabled;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,10 @@ public class ClubService : INService
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly IHttpClientFactory _httpFactory;
 | 
			
		||||
 | 
			
		||||
    public ClubService(DbService db, IHttpClientFactory _httpFactory)
 | 
			
		||||
    public ClubService(DbService db, IHttpClientFactory httpFactory)
 | 
			
		||||
    {
 | 
			
		||||
        _db = db;
 | 
			
		||||
        this._httpFactory = _httpFactory;
 | 
			
		||||
        _httpFactory = httpFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool CreateClub(IUser user, string clubName, out ClubInfo club)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,13 @@ namespace NadekoBot.Modules.Xp.Services;
 | 
			
		||||
 | 
			
		||||
public sealed class XpConfigService : ConfigServiceBase<XpConfig>
 | 
			
		||||
{
 | 
			
		||||
    private const string FilePath = "data/xp.yml";
 | 
			
		||||
    private static readonly TypedKey<XpConfig> changeKey = new("config.xp.updated");
 | 
			
		||||
    public override string Name { get; } = "xp";
 | 
			
		||||
    private const string FILE_PATH = "data/xp.yml";
 | 
			
		||||
    private static readonly TypedKey<XpConfig> _changeKey = new("config.xp.updated");
 | 
			
		||||
    public override string Name
 | 
			
		||||
        => "xp";
 | 
			
		||||
 | 
			
		||||
    public XpConfigService(IConfigSeria serializer, IPubSub pubSub)
 | 
			
		||||
        : base(FilePath, serializer, pubSub, changeKey)
 | 
			
		||||
        : base(FILE_PATH, serializer, pubSub, _changeKey)
 | 
			
		||||
    {
 | 
			
		||||
        AddParsedProp("txt.cooldown",
 | 
			
		||||
            conf => conf.MessageXpCooldown,
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ public class XpService : INService
 | 
			
		||||
    private readonly ConcurrentHashSet<ulong> _excludedServers;
 | 
			
		||||
 | 
			
		||||
    private readonly ConcurrentQueue<UserCacheItem> _addMessageXp = new();
 | 
			
		||||
    private XpTemplate _template;
 | 
			
		||||
    private XpTemplate template;
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
 | 
			
		||||
    private readonly TypedKey<bool> _xpTemplateReloadKey;
 | 
			
		||||
@@ -266,15 +266,15 @@ public class XpService : INService
 | 
			
		||||
            {
 | 
			
		||||
                ContractResolver = new RequireObjectPropertiesContractResolver()
 | 
			
		||||
            };
 | 
			
		||||
            _template = JsonConvert.DeserializeObject<XpTemplate>(File.ReadAllText("./data/xp_template.json"),
 | 
			
		||||
            template = JsonConvert.DeserializeObject<XpTemplate>(File.ReadAllText("./data/xp_template.json"),
 | 
			
		||||
                settings);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Error(ex, "Xp template is invalid. Loaded default values");
 | 
			
		||||
            _template = new();
 | 
			
		||||
            template = new();
 | 
			
		||||
            File.WriteAllText("./data/xp_template_backup.json",
 | 
			
		||||
                JsonConvert.SerializeObject(_template, Formatting.Indented));
 | 
			
		||||
                JsonConvert.SerializeObject(template, Formatting.Indented));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -583,7 +583,7 @@ public class XpService : INService
 | 
			
		||||
    public async Task<FullUserStats> GetUserStatsAsync(IGuildUser user)
 | 
			
		||||
    {
 | 
			
		||||
        DiscordUser du;
 | 
			
		||||
        UserXpStats stats = null;
 | 
			
		||||
        UserXpStats stats;
 | 
			
		||||
        int totalXp;
 | 
			
		||||
        int globalRank;
 | 
			
		||||
        int guildRank;
 | 
			
		||||
@@ -691,58 +691,58 @@ public class XpService : INService
 | 
			
		||||
            }.WithFallbackFonts(_fonts.FallBackFonts);
 | 
			
		||||
 | 
			
		||||
            using var img = Image.Load<Rgba32>(_images.XpBackground, out var imageFormat);
 | 
			
		||||
            if (_template.User.Name.Show)
 | 
			
		||||
            if (template.User.Name.Show)
 | 
			
		||||
            {
 | 
			
		||||
                var fontSize = (int)(_template.User.Name.FontSize * 0.9);
 | 
			
		||||
                var fontSize = (int)(template.User.Name.FontSize * 0.9);
 | 
			
		||||
                var username = stats.User.ToString();
 | 
			
		||||
                var usernameFont = _fonts.NotoSans.CreateFont(fontSize, FontStyle.Bold);
 | 
			
		||||
 | 
			
		||||
                var size = TextMeasurer.Measure($"@{username}", new(usernameFont));
 | 
			
		||||
                var scale = 400f / size.Width;
 | 
			
		||||
                if (scale < 1)
 | 
			
		||||
                    usernameFont = _fonts.NotoSans.CreateFont(_template.User.Name.FontSize * scale, FontStyle.Bold);
 | 
			
		||||
                    usernameFont = _fonts.NotoSans.CreateFont(template.User.Name.FontSize * scale, FontStyle.Bold);
 | 
			
		||||
 | 
			
		||||
                img.Mutate(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    x.DrawText(usernameTextOptions,
 | 
			
		||||
                        "@" + username,
 | 
			
		||||
                        usernameFont,
 | 
			
		||||
                        _template.User.Name.Color,
 | 
			
		||||
                        new(_template.User.Name.Pos.X, _template.User.Name.Pos.Y + 8));
 | 
			
		||||
                        template.User.Name.Color,
 | 
			
		||||
                        new(template.User.Name.Pos.X, template.User.Name.Pos.Y + 8));
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //club name
 | 
			
		||||
 | 
			
		||||
            if (_template.Club.Name.Show)
 | 
			
		||||
            if (template.Club.Name.Show)
 | 
			
		||||
            {
 | 
			
		||||
                var clubName = stats.User.Club?.ToString() ?? "-";
 | 
			
		||||
 | 
			
		||||
                var clubFont = _fonts.NotoSans.CreateFont(_template.Club.Name.FontSize, FontStyle.Regular);
 | 
			
		||||
                var clubFont = _fonts.NotoSans.CreateFont(template.Club.Name.FontSize, FontStyle.Regular);
 | 
			
		||||
 | 
			
		||||
                img.Mutate(x => x.DrawText(clubTextOptions,
 | 
			
		||||
                    clubName,
 | 
			
		||||
                    clubFont,
 | 
			
		||||
                    _template.Club.Name.Color,
 | 
			
		||||
                    new(_template.Club.Name.Pos.X + 50, _template.Club.Name.Pos.Y - 8)));
 | 
			
		||||
                    template.Club.Name.Color,
 | 
			
		||||
                    new(template.Club.Name.Pos.X + 50, template.Club.Name.Pos.Y - 8)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.GlobalLevel.Show)
 | 
			
		||||
            if (template.User.GlobalLevel.Show)
 | 
			
		||||
                img.Mutate(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    x.DrawText(stats.Global.Level.ToString(),
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.GlobalLevel.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.GlobalLevel.Color,
 | 
			
		||||
                        new(_template.User.GlobalLevel.Pos.X, _template.User.GlobalLevel.Pos.Y)); //level
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(template.User.GlobalLevel.FontSize, FontStyle.Bold),
 | 
			
		||||
                        template.User.GlobalLevel.Color,
 | 
			
		||||
                        new(template.User.GlobalLevel.Pos.X, template.User.GlobalLevel.Pos.Y)); //level
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            if (_template.User.GuildLevel.Show)
 | 
			
		||||
            if (template.User.GuildLevel.Show)
 | 
			
		||||
                img.Mutate(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    x.DrawText(stats.Guild.Level.ToString(),
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.GuildLevel.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.GuildLevel.Color,
 | 
			
		||||
                        new(_template.User.GuildLevel.Pos.X, _template.User.GuildLevel.Pos.Y));
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(template.User.GuildLevel.FontSize, FontStyle.Bold),
 | 
			
		||||
                        template.User.GuildLevel.Color,
 | 
			
		||||
                        new(template.User.GuildLevel.Pos.X, template.User.GuildLevel.Pos.Y));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -752,53 +752,53 @@ public class XpService : INService
 | 
			
		||||
            var guild = stats.Guild;
 | 
			
		||||
 | 
			
		||||
            //xp bar
 | 
			
		||||
            if (_template.User.Xp.Bar.Show)
 | 
			
		||||
            if (template.User.Xp.Bar.Show)
 | 
			
		||||
            {
 | 
			
		||||
                var xpPercent = global.LevelXp / (float)global.RequiredXp;
 | 
			
		||||
                DrawXpBar(xpPercent, _template.User.Xp.Bar.Global, img);
 | 
			
		||||
                DrawXpBar(xpPercent, template.User.Xp.Bar.Global, img);
 | 
			
		||||
                xpPercent = guild.LevelXp / (float)guild.RequiredXp;
 | 
			
		||||
                DrawXpBar(xpPercent, _template.User.Xp.Bar.Guild, img);
 | 
			
		||||
                DrawXpBar(xpPercent, template.User.Xp.Bar.Guild, img);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.Xp.Global.Show)
 | 
			
		||||
            if (template.User.Xp.Global.Show)
 | 
			
		||||
                img.Mutate(x => x.DrawText($"{global.LevelXp}/{global.RequiredXp}",
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(_template.User.Xp.Global.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(_template.User.Xp.Global.Color),
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(template.User.Xp.Global.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(template.User.Xp.Global.Color),
 | 
			
		||||
                    pen,
 | 
			
		||||
                    new(_template.User.Xp.Global.Pos.X, _template.User.Xp.Global.Pos.Y)));
 | 
			
		||||
                    new(template.User.Xp.Global.Pos.X, template.User.Xp.Global.Pos.Y)));
 | 
			
		||||
 | 
			
		||||
            if (_template.User.Xp.Guild.Show)
 | 
			
		||||
            if (template.User.Xp.Guild.Show)
 | 
			
		||||
                img.Mutate(x => x.DrawText($"{guild.LevelXp}/{guild.RequiredXp}",
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(_template.User.Xp.Guild.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(_template.User.Xp.Guild.Color),
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(template.User.Xp.Guild.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(template.User.Xp.Guild.Color),
 | 
			
		||||
                    pen,
 | 
			
		||||
                    new(_template.User.Xp.Guild.Pos.X, _template.User.Xp.Guild.Pos.Y)));
 | 
			
		||||
                    new(template.User.Xp.Guild.Pos.X, template.User.Xp.Guild.Pos.Y)));
 | 
			
		||||
 | 
			
		||||
            if (stats.FullGuildStats.AwardedXp != 0 && _template.User.Xp.Awarded.Show)
 | 
			
		||||
            if (stats.FullGuildStats.AwardedXp != 0 && template.User.Xp.Awarded.Show)
 | 
			
		||||
            {
 | 
			
		||||
                var sign = stats.FullGuildStats.AwardedXp > 0 ? "+ " : "";
 | 
			
		||||
                var awX = _template.User.Xp.Awarded.Pos.X
 | 
			
		||||
                var awX = template.User.Xp.Awarded.Pos.X
 | 
			
		||||
                          - (Math.Max(0, stats.FullGuildStats.AwardedXp.ToString().Length - 2) * 5);
 | 
			
		||||
                var awY = _template.User.Xp.Awarded.Pos.Y;
 | 
			
		||||
                var awY = template.User.Xp.Awarded.Pos.Y;
 | 
			
		||||
                img.Mutate(x => x.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})",
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(_template.User.Xp.Awarded.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(_template.User.Xp.Awarded.Color),
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(template.User.Xp.Awarded.FontSize, FontStyle.Bold),
 | 
			
		||||
                    Brushes.Solid(template.User.Xp.Awarded.Color),
 | 
			
		||||
                    pen,
 | 
			
		||||
                    new(awX, awY)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //ranking
 | 
			
		||||
            if (_template.User.GlobalRank.Show)
 | 
			
		||||
            if (template.User.GlobalRank.Show)
 | 
			
		||||
                img.Mutate(x => x.DrawText(stats.GlobalRanking.ToString(),
 | 
			
		||||
                    _fonts.UniSans.CreateFont(_template.User.GlobalRank.FontSize, FontStyle.Bold),
 | 
			
		||||
                    _template.User.GlobalRank.Color,
 | 
			
		||||
                    new(_template.User.GlobalRank.Pos.X, _template.User.GlobalRank.Pos.Y)));
 | 
			
		||||
                    _fonts.UniSans.CreateFont(template.User.GlobalRank.FontSize, FontStyle.Bold),
 | 
			
		||||
                    template.User.GlobalRank.Color,
 | 
			
		||||
                    new(template.User.GlobalRank.Pos.X, template.User.GlobalRank.Pos.Y)));
 | 
			
		||||
 | 
			
		||||
            if (_template.User.GuildRank.Show)
 | 
			
		||||
            if (template.User.GuildRank.Show)
 | 
			
		||||
                img.Mutate(x => x.DrawText(stats.GuildRanking.ToString(),
 | 
			
		||||
                    _fonts.UniSans.CreateFont(_template.User.GuildRank.FontSize, FontStyle.Bold),
 | 
			
		||||
                    _template.User.GuildRank.Color,
 | 
			
		||||
                    new(_template.User.GuildRank.Pos.X, _template.User.GuildRank.Pos.Y)));
 | 
			
		||||
                    _fonts.UniSans.CreateFont(template.User.GuildRank.FontSize, FontStyle.Bold),
 | 
			
		||||
                    template.User.GuildRank.Color,
 | 
			
		||||
                    new(template.User.GuildRank.Pos.X, template.User.GuildRank.Pos.Y)));
 | 
			
		||||
 | 
			
		||||
            //time on this level
 | 
			
		||||
 | 
			
		||||
@@ -808,21 +808,21 @@ public class XpService : INService
 | 
			
		||||
                return string.Format(format, offset.Days, offset.Hours, offset.Minutes);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (_template.User.TimeOnLevel.Global.Show)
 | 
			
		||||
                img.Mutate(x => x.DrawText(GetTimeSpent(stats.User.LastLevelUp, _template.User.TimeOnLevel.Format),
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Global.FontSize, FontStyle.Bold),
 | 
			
		||||
                    _template.User.TimeOnLevel.Global.Color,
 | 
			
		||||
                    new(_template.User.TimeOnLevel.Global.Pos.X, _template.User.TimeOnLevel.Global.Pos.Y)));
 | 
			
		||||
            if (template.User.TimeOnLevel.Global.Show)
 | 
			
		||||
                img.Mutate(x => x.DrawText(GetTimeSpent(stats.User.LastLevelUp, template.User.TimeOnLevel.Format),
 | 
			
		||||
                    _fonts.NotoSans.CreateFont(template.User.TimeOnLevel.Global.FontSize, FontStyle.Bold),
 | 
			
		||||
                    template.User.TimeOnLevel.Global.Color,
 | 
			
		||||
                    new(template.User.TimeOnLevel.Global.Pos.X, template.User.TimeOnLevel.Global.Pos.Y)));
 | 
			
		||||
 | 
			
		||||
            if (_template.User.TimeOnLevel.Guild.Show)
 | 
			
		||||
            if (template.User.TimeOnLevel.Guild.Show)
 | 
			
		||||
                img.Mutate(x
 | 
			
		||||
                    => x.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp, _template.User.TimeOnLevel.Format),
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(_template.User.TimeOnLevel.Guild.FontSize, FontStyle.Bold),
 | 
			
		||||
                        _template.User.TimeOnLevel.Guild.Color,
 | 
			
		||||
                        new(_template.User.TimeOnLevel.Guild.Pos.X, _template.User.TimeOnLevel.Guild.Pos.Y)));
 | 
			
		||||
                    => x.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp, template.User.TimeOnLevel.Format),
 | 
			
		||||
                        _fonts.NotoSans.CreateFont(template.User.TimeOnLevel.Guild.FontSize, FontStyle.Bold),
 | 
			
		||||
                        template.User.TimeOnLevel.Guild.Color,
 | 
			
		||||
                        new(template.User.TimeOnLevel.Guild.Pos.X, template.User.TimeOnLevel.Guild.Pos.Y)));
 | 
			
		||||
            //avatar
 | 
			
		||||
 | 
			
		||||
            if (stats.User.AvatarId is not null && _template.User.Icon.Show)
 | 
			
		||||
            if (stats.User.AvatarId is not null && template.User.Icon.Show)
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var avatarUrl = stats.User.RealAvatarUrl();
 | 
			
		||||
@@ -836,10 +836,10 @@ public class XpService : INService
 | 
			
		||||
                            using (var tempDraw = Image.Load(avatarData))
 | 
			
		||||
                            {
 | 
			
		||||
                                tempDraw.Mutate(x => x
 | 
			
		||||
                                                     .Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)
 | 
			
		||||
                                                     .ApplyRoundedCorners(Math.Max(_template.User.Icon.Size.X,
 | 
			
		||||
                                                                              _template.User.Icon.Size.Y)
 | 
			
		||||
                                                                          / 2));
 | 
			
		||||
                                                     .Resize(template.User.Icon.Size.X, template.User.Icon.Size.Y)
 | 
			
		||||
                                                     .ApplyRoundedCorners(Math.Max(template.User.Icon.Size.X,
 | 
			
		||||
                                                                              template.User.Icon.Size.Y)
 | 
			
		||||
                                                                          / 2.0f));
 | 
			
		||||
                                await using (var stream = tempDraw.ToStream())
 | 
			
		||||
                                {
 | 
			
		||||
                                    data = stream.ToArray();
 | 
			
		||||
@@ -851,11 +851,11 @@ public class XpService : INService
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    using var toDraw = Image.Load(data);
 | 
			
		||||
                    if (toDraw.Size() != new Size(_template.User.Icon.Size.X, _template.User.Icon.Size.Y))
 | 
			
		||||
                        toDraw.Mutate(x => x.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y));
 | 
			
		||||
                    if (toDraw.Size() != new Size(template.User.Icon.Size.X, template.User.Icon.Size.Y))
 | 
			
		||||
                        toDraw.Mutate(x => x.Resize(template.User.Icon.Size.X, template.User.Icon.Size.Y));
 | 
			
		||||
 | 
			
		||||
                    img.Mutate(x => x.DrawImage(toDraw,
 | 
			
		||||
                        new Point(_template.User.Icon.Pos.X, _template.User.Icon.Pos.Y),
 | 
			
		||||
                        new Point(template.User.Icon.Pos.X, template.User.Icon.Pos.Y),
 | 
			
		||||
                        1));
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
@@ -864,9 +864,9 @@ public class XpService : INService
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            //club image
 | 
			
		||||
            if (_template.Club.Icon.Show) await DrawClubImage(img, stats);
 | 
			
		||||
            if (template.Club.Icon.Show) await DrawClubImage(img, stats);
 | 
			
		||||
 | 
			
		||||
            img.Mutate(x => x.Resize(_template.OutputSize.X, _template.OutputSize.Y));
 | 
			
		||||
            img.Mutate(x => x.Resize(template.OutputSize.X, template.OutputSize.Y));
 | 
			
		||||
            return ((Stream)img.ToStream(imageFormat), imageFormat);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -880,7 +880,7 @@ public class XpService : INService
 | 
			
		||||
 | 
			
		||||
        var length = info.Length * percent;
 | 
			
		||||
 | 
			
		||||
        float x3 = 0, x4 = 0, y3 = 0, y4 = 0;
 | 
			
		||||
        float x3, x4, y3, y4;
 | 
			
		||||
 | 
			
		||||
        if (info.Direction == XpTemplateDirection.Down)
 | 
			
		||||
        {
 | 
			
		||||
@@ -936,11 +936,10 @@ public class XpService : INService
 | 
			
		||||
                        using (var tempDraw = Image.Load(imgData))
 | 
			
		||||
                        {
 | 
			
		||||
                            tempDraw.Mutate(x => x
 | 
			
		||||
                                                 .Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y)
 | 
			
		||||
                                                 .ApplyRoundedCorners(Math.Max(_template.Club.Icon.Size.X,
 | 
			
		||||
                                                                          _template.Club.Icon.Size.Y)
 | 
			
		||||
                                                 .Resize(template.Club.Icon.Size.X, template.Club.Icon.Size.Y)
 | 
			
		||||
                                                 .ApplyRoundedCorners(Math.Max(template.Club.Icon.Size.X,
 | 
			
		||||
                                                                          template.Club.Icon.Size.Y)
 | 
			
		||||
                                                                      / 2.0f));
 | 
			
		||||
                            ;
 | 
			
		||||
                            await using (var tds = tempDraw.ToStream())
 | 
			
		||||
                            {
 | 
			
		||||
                                data = tds.ToArray();
 | 
			
		||||
@@ -952,12 +951,12 @@ public class XpService : INService
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                using var toDraw = Image.Load(data);
 | 
			
		||||
                if (toDraw.Size() != new Size(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y))
 | 
			
		||||
                    toDraw.Mutate(x => x.Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y));
 | 
			
		||||
                if (toDraw.Size() != new Size(template.Club.Icon.Size.X, template.Club.Icon.Size.Y))
 | 
			
		||||
                    toDraw.Mutate(x => x.Resize(template.Club.Icon.Size.X, template.Club.Icon.Size.Y));
 | 
			
		||||
 | 
			
		||||
                img.Mutate(x => x.DrawImage(
 | 
			
		||||
                    toDraw,
 | 
			
		||||
                    new Point(_template.Club.Icon.Pos.X, _template.Club.Icon.Pos.Y),
 | 
			
		||||
                    new Point(template.Club.Icon.Pos.X, template.Club.Icon.Pos.Y),
 | 
			
		||||
                    1));
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,7 @@ public class XpTemplateUser
 | 
			
		||||
    public XpTemplateText GlobalRank { get; set; }
 | 
			
		||||
    public XpTemplateText GuildRank { get; set; }
 | 
			
		||||
    public XpTemplateTimeOnLevel TimeOnLevel { get; set; }
 | 
			
		||||
    public XpTemplateXP Xp { get; set; }
 | 
			
		||||
    public XpTemplateXp Xp { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class XpTemplateTimeOnLevel
 | 
			
		||||
@@ -108,7 +108,7 @@ public class XpTemplateText
 | 
			
		||||
    public XpTemplatePos Pos { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class XpTemplateXP
 | 
			
		||||
public class XpTemplateXp
 | 
			
		||||
{
 | 
			
		||||
    public XpTemplateXpBar Bar { get; set; }
 | 
			
		||||
    public XpTemplateText Global { get; set; }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@ namespace NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
public class DbService
 | 
			
		||||
{
 | 
			
		||||
    private readonly DbContextOptions<NadekoContext> options;
 | 
			
		||||
    private readonly DbContextOptions<NadekoContext> migrateOptions;
 | 
			
		||||
    private readonly DbContextOptions<NadekoContext> _options;
 | 
			
		||||
    private readonly DbContextOptions<NadekoContext> _migrateOptions;
 | 
			
		||||
 | 
			
		||||
    public DbService(IBotCredentials creds)
 | 
			
		||||
    {
 | 
			
		||||
@@ -20,19 +20,19 @@ public class DbService
 | 
			
		||||
 | 
			
		||||
        var optionsBuilder = new DbContextOptionsBuilder<NadekoContext>();
 | 
			
		||||
        optionsBuilder.UseSqlite(builder.ToString());
 | 
			
		||||
        options = optionsBuilder.Options;
 | 
			
		||||
        _options = optionsBuilder.Options;
 | 
			
		||||
 | 
			
		||||
        optionsBuilder = new();
 | 
			
		||||
        optionsBuilder.UseSqlite(builder.ToString());
 | 
			
		||||
        migrateOptions = optionsBuilder.Options;
 | 
			
		||||
        _migrateOptions = optionsBuilder.Options;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Setup()
 | 
			
		||||
    {
 | 
			
		||||
        using var context = new NadekoContext(options);
 | 
			
		||||
        using var context = new NadekoContext(_options);
 | 
			
		||||
        if (context.Database.GetPendingMigrations().Any())
 | 
			
		||||
        {
 | 
			
		||||
            var mContext = new NadekoContext(migrateOptions);
 | 
			
		||||
            var mContext = new NadekoContext(_migrateOptions);
 | 
			
		||||
            mContext.Database.Migrate();
 | 
			
		||||
            mContext.SaveChanges();
 | 
			
		||||
            mContext.Dispose();
 | 
			
		||||
@@ -44,7 +44,7 @@ public class DbService
 | 
			
		||||
 | 
			
		||||
    private NadekoContext GetDbContextInternal()
 | 
			
		||||
    {
 | 
			
		||||
        var context = new NadekoContext(options);
 | 
			
		||||
        var context = new NadekoContext(_options);
 | 
			
		||||
        context.Database.SetCommandTimeout(60);
 | 
			
		||||
        var conn = context.Database.GetDbConnection();
 | 
			
		||||
        conn.Open();
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ using Google.Apis.Customsearch.v1.Data;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
public interface IGoogleApiService : INService
 | 
			
		||||
public interface IGoogleApiService
 | 
			
		||||
{
 | 
			
		||||
    IReadOnlyDictionary<string, string> Languages { get; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user