mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 08:34:27 -05:00 
			
		
		
		
	Fixed around 140 wrong namings and other refactorings which were marked as warnings
This commit is contained in:
		@@ -196,7 +196,7 @@ public sealed class Bot
 | 
			
		||||
 | 
			
		||||
        Task SetClientReady()
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            _= Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                clientReady.TrySetResult(true);
 | 
			
		||||
                try
 | 
			
		||||
@@ -248,7 +248,7 @@ public sealed class Bot
 | 
			
		||||
    private Task Client_JoinedGuild(SocketGuild arg)
 | 
			
		||||
    {
 | 
			
		||||
        Log.Information("Joined server: {GuildName} [{GuildId}]", arg.Name, arg.Id);
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            GuildConfig gc;
 | 
			
		||||
            await using (var uow = _db.GetDbContext())
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
#pragma warning disable all
 | 
			
		||||
// License MIT
 | 
			
		||||
// Source: https://github.com/i3arnon/ConcurrentHashSet
 | 
			
		||||
 | 
			
		||||
@@ -17,8 +18,8 @@ namespace System.Collections.Generic;
 | 
			
		||||
[DebuggerDisplay("Count = {Count}")]
 | 
			
		||||
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T>
 | 
			
		||||
{
 | 
			
		||||
    private const int DefaultCapacity = 31;
 | 
			
		||||
    private const int MaxLockNumber = 1024;
 | 
			
		||||
    private const int DEFAULT_CAPACITY = 31;
 | 
			
		||||
    private const int MAX_LOCK_NUMBER = 1024;
 | 
			
		||||
 | 
			
		||||
    private static int DefaultConcurrencyLevel
 | 
			
		||||
        => PlatformHelper.ProcessorCount;
 | 
			
		||||
@@ -39,8 +40,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
            {
 | 
			
		||||
                AcquireAllLocks(ref acquiredLocks);
 | 
			
		||||
 | 
			
		||||
                for (var i = 0; i < _tables.CountPerLock.Length; i++)
 | 
			
		||||
                    if (_tables.CountPerLock[i] != 0)
 | 
			
		||||
                for (var i = 0; i < tables.CountPerLock.Length; i++)
 | 
			
		||||
                    if (tables.CountPerLock[i] != 0)
 | 
			
		||||
                        return false;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
@@ -83,7 +84,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
            {
 | 
			
		||||
                AcquireAllLocks(ref acquiredLocks);
 | 
			
		||||
 | 
			
		||||
                for (var i = 0; i < _tables.CountPerLock.Length; i++) count += _tables.CountPerLock[i];
 | 
			
		||||
                for (var i = 0; i < tables.CountPerLock.Length; i++) count += tables.CountPerLock[i];
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
@@ -97,8 +98,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
    private readonly IEqualityComparer<T> _comparer;
 | 
			
		||||
    private readonly bool _growLockArray;
 | 
			
		||||
 | 
			
		||||
    private int _budget;
 | 
			
		||||
    private volatile Tables _tables;
 | 
			
		||||
    private int budget;
 | 
			
		||||
    private volatile Tables tables;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Initializes a new instance of the
 | 
			
		||||
@@ -108,7 +109,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
    ///     uses the default comparer for the item type.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public ConcurrentHashSet()
 | 
			
		||||
        : this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer<T>.Default)
 | 
			
		||||
        : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, EqualityComparer<T>.Default)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -175,7 +176,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
    /// </param>
 | 
			
		||||
    /// <exception cref="T:System.ArgumentNullException"><paramref name="comparer" /> is a null reference.</exception>
 | 
			
		||||
    public ConcurrentHashSet(IEqualityComparer<T> comparer)
 | 
			
		||||
        : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer)
 | 
			
		||||
        : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, comparer)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -241,7 +242,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
    ///     <paramref name="concurrencyLevel" /> is less than 1.
 | 
			
		||||
    /// </exception>
 | 
			
		||||
    public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer)
 | 
			
		||||
        : this(concurrencyLevel, DefaultCapacity, false, comparer)
 | 
			
		||||
        : this(concurrencyLevel, DEFAULT_CAPACITY, false, comparer)
 | 
			
		||||
    {
 | 
			
		||||
        if (collection is null) throw new ArgumentNullException(nameof(collection));
 | 
			
		||||
        if (comparer is null) throw new ArgumentNullException(nameof(comparer));
 | 
			
		||||
@@ -296,10 +297,10 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
 | 
			
		||||
        var countPerLock = new int[locks.Length];
 | 
			
		||||
        var buckets = new Node[capacity];
 | 
			
		||||
        _tables = new(buckets, locks, countPerLock);
 | 
			
		||||
        tables = new(buckets, locks, countPerLock);
 | 
			
		||||
 | 
			
		||||
        _growLockArray = growLockArray;
 | 
			
		||||
        _budget = buckets.Length / locks.Length;
 | 
			
		||||
        budget = buckets.Length / locks.Length;
 | 
			
		||||
        _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -313,9 +314,9 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
        {
 | 
			
		||||
            AcquireAllLocks(ref locksAcquired);
 | 
			
		||||
 | 
			
		||||
            var newTables = new Tables(new Node[DefaultCapacity], _tables.Locks, new int[_tables.CountPerLock.Length]);
 | 
			
		||||
            _tables = newTables;
 | 
			
		||||
            _budget = Math.Max(1, newTables.Buckets.Length / newTables.Locks.Length);
 | 
			
		||||
            var newTables = new Tables(new Node[DEFAULT_CAPACITY], tables.Locks, new int[tables.CountPerLock.Length]);
 | 
			
		||||
            tables = newTables;
 | 
			
		||||
            budget = Math.Max(1, newTables.Buckets.Length / newTables.Locks.Length);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
@@ -334,7 +335,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
        var hashcode = _comparer.GetHashCode(item);
 | 
			
		||||
 | 
			
		||||
        // We must capture the _buckets field in a local variable. It is set to a new table on each table resize.
 | 
			
		||||
        var tables = _tables;
 | 
			
		||||
        var tables = this.tables;
 | 
			
		||||
 | 
			
		||||
        var bucketNo = GetBucket(hashcode, tables.Buckets.Length);
 | 
			
		||||
 | 
			
		||||
@@ -368,7 +369,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
 | 
			
		||||
            var count = 0;
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < _tables.Locks.Length && count >= 0; i++) count += _tables.CountPerLock[i];
 | 
			
		||||
            for (var i = 0; i < tables.Locks.Length && count >= 0; i++) count += tables.CountPerLock[i];
 | 
			
		||||
 | 
			
		||||
            if (array.Length - count < arrayIndex || count < 0) //"count" itself or "count + arrayIndex" can overflow
 | 
			
		||||
                throw new ArgumentException(
 | 
			
		||||
@@ -403,7 +404,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
    /// </remarks>
 | 
			
		||||
    public IEnumerator<T> GetEnumerator()
 | 
			
		||||
    {
 | 
			
		||||
        var buckets = _tables.Buckets;
 | 
			
		||||
        var buckets = tables.Buckets;
 | 
			
		||||
 | 
			
		||||
        for (var i = 0; i < buckets.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
@@ -443,7 +444,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
        var hashcode = _comparer.GetHashCode(item);
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            var tables = _tables;
 | 
			
		||||
            var tables = this.tables;
 | 
			
		||||
 | 
			
		||||
            GetBucketAndLockNo(hashcode, out var bucketNo, out var lockNo, tables.Buckets.Length, tables.Locks.Length);
 | 
			
		||||
 | 
			
		||||
@@ -451,7 +452,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
            {
 | 
			
		||||
                // If the table just got resized, we may not be holding the right lock, and must retry.
 | 
			
		||||
                // This should be a rare occurrence.
 | 
			
		||||
                if (tables != _tables) continue;
 | 
			
		||||
                if (tables != this.tables) continue;
 | 
			
		||||
 | 
			
		||||
                Node previous = null;
 | 
			
		||||
                for (var current = tables.Buckets[bucketNo]; current is not null; current = current.Next)
 | 
			
		||||
@@ -481,14 +482,14 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var item in collection) AddInternal(item, _comparer.GetHashCode(item), false);
 | 
			
		||||
 | 
			
		||||
        if (_budget == 0) _budget = _tables.Buckets.Length / _tables.Locks.Length;
 | 
			
		||||
        if (budget == 0) budget = tables.Buckets.Length / tables.Locks.Length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private bool AddInternal(T item, int hashcode, bool acquireLock)
 | 
			
		||||
    {
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            var tables = _tables;
 | 
			
		||||
            var tables = this.tables;
 | 
			
		||||
            GetBucketAndLockNo(hashcode, out var bucketNo, out var lockNo, tables.Buckets.Length, tables.Locks.Length);
 | 
			
		||||
 | 
			
		||||
            var resizeDesired = false;
 | 
			
		||||
@@ -500,7 +501,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
 | 
			
		||||
                // If the table just got resized, we may not be holding the right lock, and must retry.
 | 
			
		||||
                // This should be a rare occurrence.
 | 
			
		||||
                if (tables != _tables) continue;
 | 
			
		||||
                if (tables != this.tables) continue;
 | 
			
		||||
 | 
			
		||||
                // Try to find this item in the bucket
 | 
			
		||||
                Node previous = null;
 | 
			
		||||
@@ -525,7 +526,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
                // It is also possible that GrowTable will increase the budget but won't resize the bucket table.
 | 
			
		||||
                // That happens if the bucket table is found to be poorly utilized due to a bad hash function.
 | 
			
		||||
                //
 | 
			
		||||
                if (tables.CountPerLock[lockNo] > _budget) resizeDesired = true;
 | 
			
		||||
                if (tables.CountPerLock[lockNo] > budget) resizeDesired = true;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
@@ -578,7 +579,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
            AcquireLocks(0, 1, ref locksAcquired);
 | 
			
		||||
 | 
			
		||||
            // Make sure nobody resized the table while we were waiting for lock 0:
 | 
			
		||||
            if (tables != _tables)
 | 
			
		||||
            if (tables != this.tables)
 | 
			
		||||
                // We assume that since the table reference is different, it was already resized (or the budget
 | 
			
		||||
                // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons,
 | 
			
		||||
                // we will have to revisit this logic.
 | 
			
		||||
@@ -593,8 +594,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
            //
 | 
			
		||||
            if (approxCount < tables.Buckets.Length / 4)
 | 
			
		||||
            {
 | 
			
		||||
                _budget = 2 * _budget;
 | 
			
		||||
                if (_budget < 0) _budget = int.MaxValue;
 | 
			
		||||
                budget = 2 * budget;
 | 
			
		||||
                if (budget < 0) budget = int.MaxValue;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@@ -633,7 +634,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
                //
 | 
			
		||||
                // (There is one special case that would allow GrowTable() to be called in the future: 
 | 
			
		||||
                // calling Clear() on the ConcurrentHashSet will shrink the table and lower the budget.)
 | 
			
		||||
                _budget = int.MaxValue;
 | 
			
		||||
                budget = int.MaxValue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Now acquire all other locks for the table
 | 
			
		||||
@@ -642,7 +643,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
            var newLocks = tables.Locks;
 | 
			
		||||
 | 
			
		||||
            // Add more locks
 | 
			
		||||
            if (_growLockArray && tables.Locks.Length < MaxLockNumber)
 | 
			
		||||
            if (_growLockArray && tables.Locks.Length < MAX_LOCK_NUMBER)
 | 
			
		||||
            {
 | 
			
		||||
                newLocks = new object[tables.Locks.Length * 2];
 | 
			
		||||
                Array.Copy(tables.Locks, 0, newLocks, 0, tables.Locks.Length);
 | 
			
		||||
@@ -677,10 +678,10 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Adjust the budget
 | 
			
		||||
            _budget = Math.Max(1, newBuckets.Length / newLocks.Length);
 | 
			
		||||
            budget = Math.Max(1, newBuckets.Length / newLocks.Length);
 | 
			
		||||
 | 
			
		||||
            // Replace tables with the new versions
 | 
			
		||||
            _tables = new(newBuckets, newLocks, newCountPerLock);
 | 
			
		||||
            this.tables = new(newBuckets, newLocks, newCountPerLock);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
@@ -707,14 +708,14 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
 | 
			
		||||
        // Now that we have lock 0, the _locks array will not change (i.e., grow),
 | 
			
		||||
        // and so we can safely read _locks.Length.
 | 
			
		||||
        AcquireLocks(1, _tables.Locks.Length, ref locksAcquired);
 | 
			
		||||
        Debug.Assert(locksAcquired == _tables.Locks.Length);
 | 
			
		||||
        AcquireLocks(1, tables.Locks.Length, ref locksAcquired);
 | 
			
		||||
        Debug.Assert(locksAcquired == tables.Locks.Length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired)
 | 
			
		||||
    {
 | 
			
		||||
        Debug.Assert(fromInclusive <= toExclusive);
 | 
			
		||||
        var locks = _tables.Locks;
 | 
			
		||||
        var locks = tables.Locks;
 | 
			
		||||
 | 
			
		||||
        for (var i = fromInclusive; i < toExclusive; i++)
 | 
			
		||||
        {
 | 
			
		||||
@@ -734,12 +735,12 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
 | 
			
		||||
    {
 | 
			
		||||
        Debug.Assert(fromInclusive <= toExclusive);
 | 
			
		||||
 | 
			
		||||
        for (var i = fromInclusive; i < toExclusive; i++) Monitor.Exit(_tables.Locks[i]);
 | 
			
		||||
        for (var i = fromInclusive; i < toExclusive; i++) Monitor.Exit(tables.Locks[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void CopyToItems(T[] array, int index)
 | 
			
		||||
    {
 | 
			
		||||
        var buckets = _tables.Buckets;
 | 
			
		||||
        var buckets = tables.Buckets;
 | 
			
		||||
        for (var i = 0; i < buckets.Length; i++)
 | 
			
		||||
        for (var current = buckets[i]; current is not null; current = current.Next)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ using System.Collections;
 | 
			
		||||
namespace NadekoBot.Common.Collections;
 | 
			
		||||
 | 
			
		||||
public class IndexedCollection<T> : IList<T>
 | 
			
		||||
    where T : class, IIndexed
 | 
			
		||||
    where T : class, IIndexed 
 | 
			
		||||
{
 | 
			
		||||
    public List<T> Source { get; }
 | 
			
		||||
 | 
			
		||||
@@ -53,6 +53,8 @@ public class IndexedCollection<T> : IList<T>
 | 
			
		||||
 | 
			
		||||
    public void Add(T item)
 | 
			
		||||
    {
 | 
			
		||||
        ArgumentNullException.ThrowIfNull(item);
 | 
			
		||||
        
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            item.Index = Source.Count;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,6 @@ public enum LogType
 | 
			
		||||
    ChannelUpdated,
 | 
			
		||||
    UserPresence,
 | 
			
		||||
    VoicePresence,
 | 
			
		||||
    VoicePresenceTTS,
 | 
			
		||||
    VoicePresenceTts,
 | 
			
		||||
    UserMuted
 | 
			
		||||
}
 | 
			
		||||
@@ -86,7 +86,7 @@ public abstract class NadekoModule : ModuleBase
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() => msg.DeleteAsync());
 | 
			
		||||
            _= Task.Run(() => msg.DeleteAsync());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -111,7 +111,7 @@ public abstract class NadekoModule : ModuleBase
 | 
			
		||||
 | 
			
		||||
        Task MessageReceived(SocketMessage arg)
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            _= Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                if (arg is not SocketUserMessage userMsg
 | 
			
		||||
                    || userMsg.Channel is not ITextChannel
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ public class StoopidTime
 | 
			
		||||
 | 
			
		||||
        if (m.Length == 0) throw new ArgumentException("Invalid string input format.");
 | 
			
		||||
 | 
			
		||||
        var output = string.Empty;
 | 
			
		||||
        var namesAndValues = new Dictionary<string, int>();
 | 
			
		||||
 | 
			
		||||
        foreach (var groupName in _regex.GetGroupNames())
 | 
			
		||||
@@ -35,7 +34,6 @@ public class StoopidTime
 | 
			
		||||
            if (value < 1) throw new ArgumentException($"Invalid {groupName} value.");
 | 
			
		||||
 | 
			
		||||
            namesAndValues[groupName] = value;
 | 
			
		||||
            output += m.Groups[groupName].Value + " " + groupName + " ";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var ts = new TimeSpan((30 * namesAndValues["months"]) + (7 * namesAndValues["weeks"]) + namesAndValues["days"],
 | 
			
		||||
 
 | 
			
		||||
@@ -7,59 +7,59 @@ namespace NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
public class CommentGatheringTypeInspector : TypeInspectorSkeleton
 | 
			
		||||
{
 | 
			
		||||
    private readonly ITypeInspector innerTypeDescriptor;
 | 
			
		||||
    private readonly ITypeInspector _innerTypeDescriptor;
 | 
			
		||||
 | 
			
		||||
    public CommentGatheringTypeInspector(ITypeInspector innerTypeDescriptor)
 | 
			
		||||
        => this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
 | 
			
		||||
        => this._innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
 | 
			
		||||
 | 
			
		||||
    public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
 | 
			
		||||
        => innerTypeDescriptor.GetProperties(type, container).Select(d => new CommentsPropertyDescriptor(d));
 | 
			
		||||
        => _innerTypeDescriptor.GetProperties(type, container).Select(d => new CommentsPropertyDescriptor(d));
 | 
			
		||||
 | 
			
		||||
    private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; }
 | 
			
		||||
 | 
			
		||||
        public Type Type
 | 
			
		||||
            => baseDescriptor.Type;
 | 
			
		||||
            => _baseDescriptor.Type;
 | 
			
		||||
 | 
			
		||||
        public Type TypeOverride
 | 
			
		||||
        {
 | 
			
		||||
            get => baseDescriptor.TypeOverride;
 | 
			
		||||
            set => baseDescriptor.TypeOverride = value;
 | 
			
		||||
            get => _baseDescriptor.TypeOverride;
 | 
			
		||||
            set => _baseDescriptor.TypeOverride = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int Order { get; set; }
 | 
			
		||||
 | 
			
		||||
        public ScalarStyle ScalarStyle
 | 
			
		||||
        {
 | 
			
		||||
            get => baseDescriptor.ScalarStyle;
 | 
			
		||||
            set => baseDescriptor.ScalarStyle = value;
 | 
			
		||||
            get => _baseDescriptor.ScalarStyle;
 | 
			
		||||
            set => _baseDescriptor.ScalarStyle = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool CanWrite
 | 
			
		||||
            => baseDescriptor.CanWrite;
 | 
			
		||||
            => _baseDescriptor.CanWrite;
 | 
			
		||||
 | 
			
		||||
        private readonly IPropertyDescriptor baseDescriptor;
 | 
			
		||||
        private readonly IPropertyDescriptor _baseDescriptor;
 | 
			
		||||
 | 
			
		||||
        public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
 | 
			
		||||
        {
 | 
			
		||||
            this.baseDescriptor = baseDescriptor;
 | 
			
		||||
            this._baseDescriptor = baseDescriptor;
 | 
			
		||||
            Name = baseDescriptor.Name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Write(object target, object value)
 | 
			
		||||
            => baseDescriptor.Write(target, value);
 | 
			
		||||
            => _baseDescriptor.Write(target, value);
 | 
			
		||||
 | 
			
		||||
        public T GetCustomAttribute<T>()
 | 
			
		||||
            where T : Attribute
 | 
			
		||||
            => baseDescriptor.GetCustomAttribute<T>();
 | 
			
		||||
            => _baseDescriptor.GetCustomAttribute<T>();
 | 
			
		||||
 | 
			
		||||
        public IObjectDescriptor Read(object target)
 | 
			
		||||
        {
 | 
			
		||||
            var comment = baseDescriptor.GetCustomAttribute<CommentAttribute>();
 | 
			
		||||
            var comment = _baseDescriptor.GetCustomAttribute<CommentAttribute>();
 | 
			
		||||
            return comment is not null
 | 
			
		||||
                ? new CommentsObjectDescriptor(baseDescriptor.Read(target), comment.Comment)
 | 
			
		||||
                : baseDescriptor.Read(target);
 | 
			
		||||
                ? new CommentsObjectDescriptor(_baseDescriptor.Read(target), comment.Comment)
 | 
			
		||||
                : _baseDescriptor.Read(target);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,22 +9,22 @@ public sealed class CommentsObjectDescriptor : IObjectDescriptor
 | 
			
		||||
    public string Comment { get; }
 | 
			
		||||
 | 
			
		||||
    public object Value
 | 
			
		||||
        => innerDescriptor.Value;
 | 
			
		||||
        => _innerDescriptor.Value;
 | 
			
		||||
 | 
			
		||||
    public Type Type
 | 
			
		||||
        => innerDescriptor.Type;
 | 
			
		||||
        => _innerDescriptor.Type;
 | 
			
		||||
 | 
			
		||||
    public Type StaticType
 | 
			
		||||
        => innerDescriptor.StaticType;
 | 
			
		||||
        => _innerDescriptor.StaticType;
 | 
			
		||||
 | 
			
		||||
    public ScalarStyle ScalarStyle
 | 
			
		||||
        => innerDescriptor.ScalarStyle;
 | 
			
		||||
        => _innerDescriptor.ScalarStyle;
 | 
			
		||||
 | 
			
		||||
    private readonly IObjectDescriptor innerDescriptor;
 | 
			
		||||
    private readonly IObjectDescriptor _innerDescriptor;
 | 
			
		||||
 | 
			
		||||
    public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
 | 
			
		||||
    {
 | 
			
		||||
        this.innerDescriptor = innerDescriptor;
 | 
			
		||||
        this._innerDescriptor = innerDescriptor;
 | 
			
		||||
        Comment = comment;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -17,6 +17,6 @@ public static class SelfAssignableRolesExtensions
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
 | 
			
		||||
    public static IReadOnlyCollection<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
 | 
			
		||||
        => roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray();
 | 
			
		||||
}
 | 
			
		||||
@@ -7,7 +7,7 @@ public class GCChannelId : DbEntity
 | 
			
		||||
    public ulong ChannelId { get; set; }
 | 
			
		||||
 | 
			
		||||
    public override bool Equals(object obj)
 | 
			
		||||
        => obj is GCChannelId gc ? gc.ChannelId == ChannelId : false;
 | 
			
		||||
        => obj is GCChannelId gc && gc.ChannelId == ChannelId;
 | 
			
		||||
 | 
			
		||||
    public override int GetHashCode()
 | 
			
		||||
        => ChannelId.GetHashCode();
 | 
			
		||||
 
 | 
			
		||||
@@ -87,8 +87,8 @@ public partial class Administration : NadekoModule<AdministrationService>
 | 
			
		||||
            channels.Select(x =>
 | 
			
		||||
            {
 | 
			
		||||
                var ch = guild.GetChannel(x.ChannelId)?.ToString() ?? x.ChannelId.ToString();
 | 
			
		||||
                var prefix = x.State ? "✅ " : "❌ ";
 | 
			
		||||
                return prefix + ch;
 | 
			
		||||
                var prefixSign = x.State ? "✅ " : "❌ ";
 | 
			
		||||
                return prefixSign + ch;
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(str))
 | 
			
		||||
@@ -319,7 +319,7 @@ public partial class Administration : NadekoModule<AdministrationService>
 | 
			
		||||
        }
 | 
			
		||||
        else if (time.Time <= TimeSpan.FromDays(7))
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            _= Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                await Task.Delay(time.Time);
 | 
			
		||||
                await msg.DeleteAsync();
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ public class AdministrationService : INService
 | 
			
		||||
 | 
			
		||||
    private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            if (msg.Channel is not SocketTextChannel channel)
 | 
			
		||||
                return;
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [Cmd]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public partial Task DeleteWaifus()
 | 
			
		||||
                => SqlExec(DangerousCommandsService.WaifusDeleteSql);
 | 
			
		||||
                => SqlExec(DangerousCommandsService.WAIFUS_DELETE_SQL);
 | 
			
		||||
 | 
			
		||||
            [Cmd]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
@@ -73,22 +73,22 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [Cmd]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public partial Task DeleteWaifu(ulong userId)
 | 
			
		||||
                => InternalExecSql(DangerousCommandsService.WaifuDeleteSql, userId);
 | 
			
		||||
                => InternalExecSql(DangerousCommandsService.WAIFU_DELETE_SQL, userId);
 | 
			
		||||
 | 
			
		||||
            [Cmd]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public partial Task DeleteCurrency()
 | 
			
		||||
                => SqlExec(DangerousCommandsService.CurrencyDeleteSql);
 | 
			
		||||
                => SqlExec(DangerousCommandsService.CURRENCY_DELETE_SQL);
 | 
			
		||||
 | 
			
		||||
            [Cmd]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public partial Task DeletePlaylists()
 | 
			
		||||
                => SqlExec(DangerousCommandsService.MusicPlaylistDeleteSql);
 | 
			
		||||
                => SqlExec(DangerousCommandsService.MUSIC_PLAYLIST_DELETE_SQL);
 | 
			
		||||
 | 
			
		||||
            [Cmd]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public partial Task DeleteXp()
 | 
			
		||||
                => SqlExec(DangerousCommandsService.XpDeleteSql);
 | 
			
		||||
                => SqlExec(DangerousCommandsService.XP_DELETE_SQL);
 | 
			
		||||
 | 
			
		||||
            [Cmd]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
 
 | 
			
		||||
@@ -8,22 +8,22 @@ namespace NadekoBot.Modules.Administration.Services;
 | 
			
		||||
 | 
			
		||||
public class DangerousCommandsService : INService
 | 
			
		||||
{
 | 
			
		||||
    public const string WaifusDeleteSql = @"DELETE FROM WaifuUpdates;
 | 
			
		||||
    public const string WAIFUS_DELETE_SQL = @"DELETE FROM WaifuUpdates;
 | 
			
		||||
DELETE FROM WaifuItem;
 | 
			
		||||
DELETE FROM WaifuInfo;";
 | 
			
		||||
 | 
			
		||||
    public const string WaifuDeleteSql =
 | 
			
		||||
    public const string WAIFU_DELETE_SQL =
 | 
			
		||||
        @"DELETE FROM WaifuUpdates WHERE UserId=(SELECT Id FROM DiscordUser WHERE UserId={0});
 | 
			
		||||
DELETE FROM WaifuItem WHERE WaifuInfoId=(SELECT Id FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0}));
 | 
			
		||||
UPDATE WaifuInfo SET ClaimerId=NULL WHERE ClaimerId=(SELECT Id FROM DiscordUser WHERE UserId={0});
 | 
			
		||||
DELETE FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0});";
 | 
			
		||||
 | 
			
		||||
    public const string CurrencyDeleteSql =
 | 
			
		||||
    public const string CURRENCY_DELETE_SQL =
 | 
			
		||||
        "UPDATE DiscordUser SET CurrencyAmount=0; DELETE FROM CurrencyTransactions; DELETE FROM PlantedCurrency;";
 | 
			
		||||
 | 
			
		||||
    public const string MusicPlaylistDeleteSql = "DELETE FROM MusicPlaylists;";
 | 
			
		||||
    public const string MUSIC_PLAYLIST_DELETE_SQL = "DELETE FROM MusicPlaylists;";
 | 
			
		||||
 | 
			
		||||
    public const string XpDeleteSql = @"DELETE FROM UserXpStats;
 | 
			
		||||
    public const string XP_DELETE_SQL = @"DELETE FROM UserXpStats;
 | 
			
		||||
UPDATE DiscordUser
 | 
			
		||||
SET ClubId=NULL,
 | 
			
		||||
    IsClubAdmin=0,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ public class GameVoiceChannelService : INService
 | 
			
		||||
 | 
			
		||||
    private Task OnPresenceUpdate(SocketUser socketUser, SocketPresence before, SocketPresence after)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -83,7 +83,7 @@ public class GameVoiceChannelService : INService
 | 
			
		||||
 | 
			
		||||
    private Task OnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -107,7 +107,7 @@ public class GreetService : INService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task OnUserLeft(SocketGuild guild, SocketUser user)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -271,7 +271,7 @@ public class GreetService : INService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task OnUserJoined(IGuildUser user)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior
 | 
			
		||||
        var uow = _db.GetDbContext();
 | 
			
		||||
        _enabledOn = uow.ImageOnlyChannels.ToList()
 | 
			
		||||
                        .GroupBy(x => x.GuildId)
 | 
			
		||||
                        .ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(x => x.ChannelId)))
 | 
			
		||||
                        .ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(y => y.ChannelId)))
 | 
			
		||||
                        .ToConcurrent();
 | 
			
		||||
 | 
			
		||||
        _ = Task.Run(DeleteQueueRunner);
 | 
			
		||||
 
 | 
			
		||||
@@ -126,7 +126,7 @@ public class MuteService : INService
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(reason))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var _ = Task.Run(() => user.SendMessageAsync(embed: _eb.Create()
 | 
			
		||||
        _= Task.Run(() => user.SendMessageAsync(embed: _eb.Create()
 | 
			
		||||
                                                               .WithDescription(
 | 
			
		||||
                                                                   $"You've been muted in {user.Guild} server")
 | 
			
		||||
                                                               .AddField("Mute Type", type.ToString())
 | 
			
		||||
@@ -144,7 +144,7 @@ public class MuteService : INService
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(reason))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var _ = Task.Run(() => user.SendMessageAsync(embed: _eb.Create()
 | 
			
		||||
        _= Task.Run(() => user.SendMessageAsync(embed: _eb.Create()
 | 
			
		||||
                                                               .WithDescription(
 | 
			
		||||
                                                                   $"You've been unmuted in {user.Guild} server")
 | 
			
		||||
                                                               .AddField("Unmute Type", type.ToString())
 | 
			
		||||
@@ -161,7 +161,7 @@ public class MuteService : INService
 | 
			
		||||
 | 
			
		||||
            if (muted is null || !muted.Contains(usr.Id))
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            var _ = Task.Run(() => MuteUser(usr, _client.CurrentUser, reason: "Sticky mute"));
 | 
			
		||||
            _= Task.Run(() => MuteUser(usr, _client.CurrentUser, reason: "Sticky mute"));
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -417,6 +417,9 @@ public class MuteService : INService
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        if (roleId is null)
 | 
			
		||||
                            return;
 | 
			
		||||
                        
 | 
			
		||||
                        RemoveTimerFromDb(guildId, userId, type);
 | 
			
		||||
                        StopTimer(guildId, userId, type);
 | 
			
		||||
                        var guild = _client.GetGuild(guildId);
 | 
			
		||||
 
 | 
			
		||||
@@ -20,36 +20,36 @@ public partial class Administration
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public partial Task Prefix(Set _, [Leftover] string prefix)
 | 
			
		||||
            => Prefix(prefix);
 | 
			
		||||
        public partial Task Prefix(Set _, [Leftover] string newPrefix)
 | 
			
		||||
            => Prefix(newPrefix);
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async partial Task Prefix([Leftover] string prefix)
 | 
			
		||||
        public async partial Task Prefix([Leftover] string toSet)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(prefix))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var oldPrefix = base.prefix;
 | 
			
		||||
            var newPrefix = CmdHandler.SetPrefix(ctx.Guild, prefix);
 | 
			
		||||
            var oldPrefix = prefix;
 | 
			
		||||
            var newPrefix = CmdHandler.SetPrefix(ctx.Guild, toSet);
 | 
			
		||||
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.prefix_new(Format.Code(oldPrefix), Format.Code(newPrefix)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        public async partial Task DefPrefix([Leftover] string prefix = null)
 | 
			
		||||
        public async partial Task DefPrefix([Leftover] string toSet = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(prefix))
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(toSet))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyConfirmLocalizedAsync(strs.defprefix_current(CmdHandler.GetPrefix()));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var oldPrefix = CmdHandler.GetPrefix();
 | 
			
		||||
            var newPrefix = CmdHandler.SetDefaultPrefix(prefix);
 | 
			
		||||
            var newPrefix = CmdHandler.SetDefaultPrefix(toSet);
 | 
			
		||||
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.defprefix_new(Format.Code(oldPrefix), Format.Code(newPrefix)));
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ public class ProtectionService : INService
 | 
			
		||||
 | 
			
		||||
    private Task _client_LeftGuild(SocketGuild guild)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            TryStopAntiRaid(guild.Id);
 | 
			
		||||
            TryStopAntiSpam(guild.Id);
 | 
			
		||||
@@ -202,7 +202,7 @@ public class ProtectionService : INService
 | 
			
		||||
 | 
			
		||||
        if (msg.Channel is not ITextChannel channel)
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,15 +38,15 @@ public class AntiAltStats
 | 
			
		||||
        => _setting.MinAge;
 | 
			
		||||
 | 
			
		||||
    public int Counter
 | 
			
		||||
        => _counter;
 | 
			
		||||
        => counter;
 | 
			
		||||
 | 
			
		||||
    private readonly AntiAltSetting _setting;
 | 
			
		||||
 | 
			
		||||
    private int _counter;
 | 
			
		||||
    private int counter;
 | 
			
		||||
 | 
			
		||||
    public AntiAltStats(AntiAltSetting setting)
 | 
			
		||||
        => _setting = setting;
 | 
			
		||||
 | 
			
		||||
    public void Increment()
 | 
			
		||||
        => Interlocked.Increment(ref _counter);
 | 
			
		||||
        => Interlocked.Increment(ref counter);
 | 
			
		||||
}
 | 
			
		||||
@@ -8,7 +8,7 @@ public partial class Administration
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class PruneCommands : NadekoSubmodule<PruneService>
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
 | 
			
		||||
        private static readonly TimeSpan _twoWeeks = TimeSpan.FromDays(14);
 | 
			
		||||
 | 
			
		||||
        //delets her own messages, no perm required
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -73,11 +73,11 @@ public partial class Administration
 | 
			
		||||
            if (parameter is "-s" or "--safe")
 | 
			
		||||
                await _service.PruneWhere((ITextChannel)ctx.Channel,
 | 
			
		||||
                    count,
 | 
			
		||||
                    m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks && !m.IsPinned);
 | 
			
		||||
                    m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks && !m.IsPinned);
 | 
			
		||||
            else
 | 
			
		||||
                await _service.PruneWhere((ITextChannel)ctx.Channel,
 | 
			
		||||
                    count,
 | 
			
		||||
                    m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks);
 | 
			
		||||
                    m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,7 +13,7 @@ public partial class Administration
 | 
			
		||||
    {
 | 
			
		||||
        public enum Exclude { Excl }
 | 
			
		||||
 | 
			
		||||
        private IServiceProvider _services;
 | 
			
		||||
        private readonly IServiceProvider _services;
 | 
			
		||||
 | 
			
		||||
        public RoleCommands(IServiceProvider services)
 | 
			
		||||
            => _services = services;
 | 
			
		||||
@@ -24,7 +24,7 @@ public partial class Administration
 | 
			
		||||
                ? await ctx.Channel.GetMessageAsync(msgId)
 | 
			
		||||
                : (await ctx.Channel.GetMessagesAsync(2).FlattenAsync()).Skip(1).FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
            if (input.Length % 2 != 0)
 | 
			
		||||
            if (input.Length % 2 != 0 || target is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var all = await input.Chunk(input.Length / 2)
 | 
			
		||||
@@ -35,7 +35,7 @@ public partial class Administration
 | 
			
		||||
                                     var roleResult = await roleReader.ReadAsync(ctx, inputRoleStr, _services);
 | 
			
		||||
                                     if (!roleResult.IsSuccess)
 | 
			
		||||
                                     {
 | 
			
		||||
                                         Log.Warning("Role {0} not found.", inputRoleStr);
 | 
			
		||||
                                         Log.Warning("Role {Role} not found", inputRoleStr);
 | 
			
		||||
                                         return null;
 | 
			
		||||
                                     }
 | 
			
		||||
 | 
			
		||||
@@ -163,7 +163,6 @@ public partial class Administration
 | 
			
		||||
            if (index < 1 || !_service.Get(ctx.Guild.Id, out var rrs) || !rrs.Any() || rrs.Count < index)
 | 
			
		||||
                return;
 | 
			
		||||
            index--;
 | 
			
		||||
            var rr = rrs[index];
 | 
			
		||||
            _service.Remove(ctx.Guild.Id, index);
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.reaction_role_removed(index + 1));
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ public class RoleCommandsService : INService
 | 
			
		||||
                await dl.RemoveReactionAsync(reaction.Emote,
 | 
			
		||||
                    dl.Author,
 | 
			
		||||
                    new() { RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502 });
 | 
			
		||||
                Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
 | 
			
		||||
                Log.Warning("User {Author} is adding unrelated reactions to the reaction roles message", dl.Author);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -58,8 +58,6 @@ public partial class Administration
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
        public async partial Task Sargn(int group, [Leftover] string name = null)
 | 
			
		||||
        {
 | 
			
		||||
            var guser = (IGuildUser)ctx.User;
 | 
			
		||||
 | 
			
		||||
            var set = await _service.SetNameAsync(ctx.Guild.Id, group, name);
 | 
			
		||||
 | 
			
		||||
            if (set)
 | 
			
		||||
@@ -106,7 +104,7 @@ public partial class Administration
 | 
			
		||||
 | 
			
		||||
                    foreach (var kvp in roleGroups)
 | 
			
		||||
                    {
 | 
			
		||||
                        var groupNameText = string.Empty;
 | 
			
		||||
                        string groupNameText;
 | 
			
		||||
                        if (!groups.TryGetValue(kvp.Key, out var name))
 | 
			
		||||
                            groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
 | 
			
		||||
                        else
 | 
			
		||||
 
 | 
			
		||||
@@ -158,7 +158,7 @@ public class SelfAssignedRolesService : INService
 | 
			
		||||
        return success;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public (bool AutoDelete, bool Exclusive, IEnumerable<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
 | 
			
		||||
    public (bool AutoDelete, bool Exclusive, IReadOnlyCollection<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var gc = uow.GuildConfigsForId(guildId, set => set);
 | 
			
		||||
@@ -198,12 +198,12 @@ public class SelfAssignedRolesService : INService
 | 
			
		||||
        return areExclusive;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public (bool Exclusive, IEnumerable<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string> GroupNames
 | 
			
		||||
    public (bool Exclusive, IReadOnlyCollection<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string> GroupNames
 | 
			
		||||
        ) GetRoles(IGuild guild)
 | 
			
		||||
    {
 | 
			
		||||
        var exclusive = false;
 | 
			
		||||
 | 
			
		||||
        IEnumerable<(SelfAssignedRole Model, IRole Role)> roles;
 | 
			
		||||
        IReadOnlyCollection<(SelfAssignedRole Model, IRole Role)> roles;
 | 
			
		||||
        IDictionary<int, string> groupNames;
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
@@ -211,11 +211,12 @@ public class SelfAssignedRolesService : INService
 | 
			
		||||
            exclusive = gc.ExclusiveSelfAssignedRoles;
 | 
			
		||||
            groupNames = gc.SelfAssignableRoleGroupNames.ToDictionary(x => x.Number, x => x.Name);
 | 
			
		||||
            var roleModels = uow.SelfAssignableRoles.GetFromGuild(guild.Id);
 | 
			
		||||
            roles = roleModels.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)));
 | 
			
		||||
            roles = roleModels.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)))
 | 
			
		||||
                              .ToList();
 | 
			
		||||
            uow.SelfAssignableRoles.RemoveRange(roles.Where(x => x.Role is null).Select(x => x.Model).ToArray());
 | 
			
		||||
            uow.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (exclusive, roles.Where(x => x.Role is not null), groupNames);
 | 
			
		||||
        return (exclusive, roles.Where(x => x.Role is not null).ToList(), groupNames);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -183,7 +183,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_UserUpdated(SocketUser before, SocketUser uAfter)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -292,7 +292,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                case LogType.VoicePresence:
 | 
			
		||||
                    channelId = logSetting.LogVoicePresenceId = logSetting.LogVoicePresenceId is null ? cid : default;
 | 
			
		||||
                    break;
 | 
			
		||||
                case LogType.VoicePresenceTTS:
 | 
			
		||||
                case LogType.VoicePresenceTts:
 | 
			
		||||
                    channelId = logSetting.LogVoicePresenceTTSId =
 | 
			
		||||
                        logSetting.LogVoicePresenceTTSId is null ? cid : default;
 | 
			
		||||
                    break;
 | 
			
		||||
@@ -306,7 +306,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_UserVoiceStateUpdated_TTS(SocketUser iusr, SocketVoiceState before, SocketVoiceState after)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -324,7 +324,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                ITextChannel? logChannel;
 | 
			
		||||
                if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.VoicePresenceTTS)) is null)
 | 
			
		||||
                if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.VoicePresenceTts)) is null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var str = string.Empty;
 | 
			
		||||
@@ -351,8 +351,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
        IUser mod,
 | 
			
		||||
        MuteType muteType,
 | 
			
		||||
        string reason)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        => _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -391,15 +390,13 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                // ignored
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void MuteCommands_UserUnmuted(
 | 
			
		||||
        IGuildUser usr,
 | 
			
		||||
        IUser mod,
 | 
			
		||||
        MuteType muteType,
 | 
			
		||||
        string reason)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        => _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -443,11 +440,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                // ignored
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task TriggeredAntiProtection(PunishmentAction action, ProtectionType protection, params IGuildUser[] users)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -517,7 +513,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_GuildUserUpdated(Cacheable<SocketGuildUser, ulong> optBefore, SocketGuildUser after)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -620,7 +616,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_ChannelUpdated(IChannel cbefore, IChannel cafter)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -668,7 +664,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_ChannelDestroyed(IChannel ich)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -706,7 +702,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_ChannelCreated(IChannel ich)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -742,7 +738,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState before, SocketVoiceState after)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -805,7 +801,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_UserLeft(SocketGuild guild, SocketUser usr)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -840,7 +836,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_UserJoined(IGuildUser usr)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -879,7 +875,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_UserUnbanned(IUser usr, IGuild guild)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -914,7 +910,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_UserBanned(IUser usr, IGuild guild)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -951,7 +947,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    private Task _client_MessageDeleted(Cacheable<IMessage, ulong> optMsg, Cacheable<IMessageChannel, ulong> optCh)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -1005,7 +1001,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
        SocketMessage imsg2,
 | 
			
		||||
        ISocketMessageChannel ch)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -1104,7 +1100,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
            case LogType.VoicePresence:
 | 
			
		||||
                id = logSetting.LogVoicePresenceId;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.VoicePresenceTTS:
 | 
			
		||||
            case LogType.VoicePresenceTts:
 | 
			
		||||
                id = logSetting.LogVoicePresenceTTSId;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.UserMuted:
 | 
			
		||||
@@ -1177,7 +1173,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
            case LogType.VoicePresence:
 | 
			
		||||
                newLogSetting.LogVoicePresenceId = null;
 | 
			
		||||
                break;
 | 
			
		||||
            case LogType.VoicePresenceTTS:
 | 
			
		||||
            case LogType.VoicePresenceTts:
 | 
			
		||||
                newLogSetting.LogVoicePresenceTTSId = null;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -131,7 +131,7 @@ public partial class Administration
 | 
			
		||||
                    return l.LogUserPresenceId;
 | 
			
		||||
                case LogType.VoicePresence:
 | 
			
		||||
                    return l.LogVoicePresenceId;
 | 
			
		||||
                case LogType.VoicePresenceTTS:
 | 
			
		||||
                case LogType.VoicePresenceTts:
 | 
			
		||||
                    return l.LogVoicePresenceTTSId;
 | 
			
		||||
                case LogType.UserMuted:
 | 
			
		||||
                    return l.UserMutedId;
 | 
			
		||||
 
 | 
			
		||||
@@ -315,6 +315,9 @@ WHERE GuildId={guildId}
 | 
			
		||||
        if (number <= 0 || (time is not null && time.Time > TimeSpan.FromDays(49)))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        if (punish is PunishmentAction.AddRole && role is null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
 | 
			
		||||
        var toDelete = ps.Where(x => x.Count == number);
 | 
			
		||||
@@ -326,7 +329,7 @@ WHERE GuildId={guildId}
 | 
			
		||||
            Count = number,
 | 
			
		||||
            Punishment = punish,
 | 
			
		||||
            Time = (int?)time?.Time.TotalMinutes ?? 0,
 | 
			
		||||
            RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?)
 | 
			
		||||
            RoleId = punish == PunishmentAction.AddRole ? role!.Id : default(ulong?)
 | 
			
		||||
        });
 | 
			
		||||
        uow.SaveChanges();
 | 
			
		||||
        return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@ public class VcRoleService : INService
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var configWithVcRole = uow.GuildConfigsForId(arg.GuildId, set => set.Include(x => x.VcRoleInfos));
 | 
			
		||||
            var _ = InitializeVcRole(configWithVcRole);
 | 
			
		||||
            _= InitializeVcRole(configWithVcRole);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
@@ -166,7 +166,7 @@ public class VcRoleService : INService
 | 
			
		||||
 | 
			
		||||
        var oldVc = oldState.VoiceChannel;
 | 
			
		||||
        var newVc = newState.VoiceChannel;
 | 
			
		||||
        var _ = Task.Run(() =>
 | 
			
		||||
        _= Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -55,15 +55,15 @@ public static class NadekoExpressionExtensions
 | 
			
		||||
 | 
			
		||||
        if (wordIndex == 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (word.Length < str.Length && str.isValidWordDivider(word.Length))
 | 
			
		||||
            if (word.Length < str.Length && str.IsValidWordDivider(word.Length))
 | 
			
		||||
                return WordPosition.Start;
 | 
			
		||||
        }
 | 
			
		||||
        else if (wordIndex + word.Length == str.Length)
 | 
			
		||||
        {
 | 
			
		||||
            if (str.isValidWordDivider(wordIndex - 1))
 | 
			
		||||
            if (str.IsValidWordDivider(wordIndex - 1))
 | 
			
		||||
                return WordPosition.End;
 | 
			
		||||
        }
 | 
			
		||||
        else if (str.isValidWordDivider(wordIndex - 1) && str.isValidWordDivider(wordIndex + word.Length))
 | 
			
		||||
        else if (str.IsValidWordDivider(wordIndex - 1) && str.IsValidWordDivider(wordIndex + word.Length))
 | 
			
		||||
        {
 | 
			
		||||
            return WordPosition.Middle;
 | 
			
		||||
        }
 | 
			
		||||
@@ -71,7 +71,7 @@ public static class NadekoExpressionExtensions
 | 
			
		||||
        return WordPosition.None;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static bool isValidWordDivider(this in ReadOnlySpan<char> str, int index)
 | 
			
		||||
    private static bool IsValidWordDivider(this in ReadOnlySpan<char> str, int index)
 | 
			
		||||
    {
 | 
			
		||||
        var ch = str[index];
 | 
			
		||||
        if (ch is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '1' and <= '9')
 | 
			
		||||
 
 | 
			
		||||
@@ -44,8 +44,7 @@ public sealed class AnimalRace : IDisposable
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Initialize() //lame name
 | 
			
		||||
    {
 | 
			
		||||
        var _t = Task.Run(async () =>
 | 
			
		||||
        => _ = Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Delay(_options.StartTime * 1000);
 | 
			
		||||
 | 
			
		||||
@@ -59,7 +58,6 @@ public sealed class AnimalRace : IDisposable
 | 
			
		||||
            }
 | 
			
		||||
            finally { _locker.Release(); }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
 | 
			
		||||
    {
 | 
			
		||||
@@ -104,13 +102,13 @@ public sealed class AnimalRace : IDisposable
 | 
			
		||||
                if (user.Bet > 0)
 | 
			
		||||
                    await _currency.AddAsync(user.UserId, "Race refund", user.Bet);
 | 
			
		||||
 | 
			
		||||
            var _sf = OnStartingFailed?.Invoke(this);
 | 
			
		||||
            _ = OnStartingFailed?.Invoke(this);
 | 
			
		||||
            CurrentPhase = Phase.Ended;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var _ = OnStarted?.Invoke(this);
 | 
			
		||||
        var _t = Task.Run(async () =>
 | 
			
		||||
        _ = OnStarted?.Invoke(this);
 | 
			
		||||
        _ = Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            var rng = new NadekoRandom();
 | 
			
		||||
            while (!_users.All(x => x.Progress >= 60))
 | 
			
		||||
@@ -126,7 +124,7 @@ public sealed class AnimalRace : IDisposable
 | 
			
		||||
 | 
			
		||||
                FinishedUsers.AddRange(finished);
 | 
			
		||||
 | 
			
		||||
                var _ignore = OnStateUpdate?.Invoke(this);
 | 
			
		||||
                _ = OnStateUpdate?.Invoke(this);
 | 
			
		||||
                await Task.Delay(2500);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -135,7 +133,7 @@ public sealed class AnimalRace : IDisposable
 | 
			
		||||
                    "Won a Race",
 | 
			
		||||
                    FinishedUsers[0].Bet * (_users.Count - 1));
 | 
			
		||||
 | 
			
		||||
            var _ended = OnEnded?.Invoke(this);
 | 
			
		||||
            _ = OnEnded?.Invoke(this);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            Task _client_MessageReceived(SocketMessage arg)
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(() =>
 | 
			
		||||
                _= Task.Run(() =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
        private readonly ICurrencyService _cs;
 | 
			
		||||
        private readonly DbService _db;
 | 
			
		||||
        private IUserMessage _msg;
 | 
			
		||||
        private IUserMessage msg;
 | 
			
		||||
 | 
			
		||||
        public BlackJackCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConf)
 | 
			
		||||
            : base(gamblingConf)
 | 
			
		||||
@@ -74,12 +74,13 @@ public partial class Gambling
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (_msg is not null)
 | 
			
		||||
                if (msg is not null)
 | 
			
		||||
                {
 | 
			
		||||
                    var _ = _msg.DeleteAsync();
 | 
			
		||||
                    _= msg.DeleteAsync();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var c = bj.Dealer.Cards.Select(x => x.GetEmojiString());
 | 
			
		||||
                var c = bj.Dealer.Cards.Select(x => x.GetEmojiString())
 | 
			
		||||
                          .ToList();
 | 
			
		||||
                var dealerIcon = "❔ ";
 | 
			
		||||
                if (bj.State == Blackjack.GameState.Ended)
 | 
			
		||||
                {
 | 
			
		||||
@@ -102,7 +103,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
                foreach (var p in bj.Players)
 | 
			
		||||
                {
 | 
			
		||||
                    c = p.Cards.Select(x => x.GetEmojiString());
 | 
			
		||||
                    c = p.Cards.Select(x => x.GetEmojiString()).ToList();
 | 
			
		||||
                    cStr = "-\t" + string.Concat(c.Select(x => x[..^1] + " "));
 | 
			
		||||
                    cStr += "\n-\t" + string.Concat(c.Select(x => x.Last() + " "));
 | 
			
		||||
                    var full = $"{p.DiscordUser.ToString().TrimTo(20)} | Bet: {p.Bet} | Value: {p.GetHandValue()}";
 | 
			
		||||
@@ -133,7 +134,7 @@ public partial class Gambling
 | 
			
		||||
                    embed.AddField(full, cStr);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _msg = await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
                msg = await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
@@ -146,7 +147,7 @@ public partial class Gambling
 | 
			
		||||
                ? Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30))
 | 
			
		||||
                : x.DiscordUser.ToString();
 | 
			
		||||
 | 
			
		||||
            var hand = $"{string.Concat(x.Cards.Select(y => "〖" + y.GetEmojiString() + "〗"))}";
 | 
			
		||||
            // var hand = $"{string.Concat(x.Cards.Select(y => "〖" + y.GetEmojiString() + "〗"))}";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            return $"{playerName} | Bet: {x.Bet}\n";
 | 
			
		||||
 
 | 
			
		||||
@@ -35,9 +35,7 @@ public class Blackjack
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Start()
 | 
			
		||||
    {
 | 
			
		||||
        var _ = GameLoop();
 | 
			
		||||
    }
 | 
			
		||||
        => _= GameLoop();
 | 
			
		||||
 | 
			
		||||
    public async Task GameLoop()
 | 
			
		||||
    {
 | 
			
		||||
@@ -89,13 +87,13 @@ public class Blackjack
 | 
			
		||||
            Log.Information("Dealer moves");
 | 
			
		||||
            await DealerMoves();
 | 
			
		||||
            await PrintState();
 | 
			
		||||
            var _ = GameEnded?.Invoke(this);
 | 
			
		||||
            _= GameEnded?.Invoke(this);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Error(ex, "REPORT THE MESSAGE BELOW IN #NadekoLog SERVER PLEASE");
 | 
			
		||||
            State = GameState.Ended;
 | 
			
		||||
            var _ = GameEnded?.Invoke(this);
 | 
			
		||||
            _= GameEnded?.Invoke(this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -127,7 +125,7 @@ public class Blackjack
 | 
			
		||||
            if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true)) return false;
 | 
			
		||||
 | 
			
		||||
            Players.Add(new(user, bet));
 | 
			
		||||
            var _ = PrintState();
 | 
			
		||||
            _= PrintState();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
 
 | 
			
		||||
@@ -28,8 +28,8 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
        OtherPlayerWon
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public const int NumberOfColumns = 7;
 | 
			
		||||
    public const int NumberOfRows = 6;
 | 
			
		||||
    public const int NUMBER_OF_COLUMNS = 7;
 | 
			
		||||
    public const int NUMBER_OF_ROWS = 6;
 | 
			
		||||
 | 
			
		||||
    //public event Func<Connect4Game, Task> OnGameStarted;
 | 
			
		||||
    public event Func<Connect4Game, Task> OnGameStateUpdated;
 | 
			
		||||
@@ -51,7 +51,7 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
        => CurrentPhase == Phase.P2Move ? _players[0].Value : _players[1].Value;
 | 
			
		||||
 | 
			
		||||
    //state is bottom to top, left to right
 | 
			
		||||
    private readonly Field[] _gameState = new Field[NumberOfRows * NumberOfColumns];
 | 
			
		||||
    private readonly Field[] _gameState = new Field[NUMBER_OF_ROWS * NUMBER_OF_COLUMNS];
 | 
			
		||||
    private readonly (ulong UserId, string Username)?[] _players = new (ulong, string)?[2];
 | 
			
		||||
 | 
			
		||||
    private readonly SemaphoreSlim _locker = new(1, 1);
 | 
			
		||||
@@ -81,14 +81,14 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
        _cs = cs;
 | 
			
		||||
 | 
			
		||||
        _rng = new();
 | 
			
		||||
        for (var i = 0; i < NumberOfColumns * NumberOfRows; i++) _gameState[i] = Field.Empty;
 | 
			
		||||
        for (var i = 0; i < NUMBER_OF_COLUMNS * NUMBER_OF_ROWS; i++) _gameState[i] = Field.Empty;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Initialize()
 | 
			
		||||
    {
 | 
			
		||||
        if (CurrentPhase != Phase.Joining)
 | 
			
		||||
            return;
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Delay(15000);
 | 
			
		||||
            await _locker.WaitAsync();
 | 
			
		||||
@@ -165,14 +165,14 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
                  || (_players[1].Value.UserId == userId && CurrentPhase == Phase.P2Move)))
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if (inputCol is < 0 or > NumberOfColumns) //invalid input
 | 
			
		||||
            if (inputCol is < 0 or > NUMBER_OF_COLUMNS) //invalid input
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if (IsColumnFull(inputCol)) //can't play there event?
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            var start = NumberOfRows * inputCol;
 | 
			
		||||
            for (var i = start; i < start + NumberOfRows; i++)
 | 
			
		||||
            var start = NUMBER_OF_ROWS * inputCol;
 | 
			
		||||
            for (var i = start; i < start + NUMBER_OF_ROWS; i++)
 | 
			
		||||
                if (_gameState[i] == Field.Empty)
 | 
			
		||||
                {
 | 
			
		||||
                    _gameState[i] = GetPlayerPiece(userId);
 | 
			
		||||
@@ -182,21 +182,21 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
            //check winnning condition
 | 
			
		||||
            // ok, i'll go from [0-2] in rows (and through all columns) and check upward if 4 are connected
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < NumberOfRows - 3; i++)
 | 
			
		||||
            for (var i = 0; i < NUMBER_OF_ROWS - 3; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (CurrentPhase == Phase.Ended)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                for (var j = 0; j < NumberOfColumns; j++)
 | 
			
		||||
                for (var j = 0; j < NUMBER_OF_COLUMNS; j++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (CurrentPhase == Phase.Ended)
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    var first = _gameState[i + (j * NumberOfRows)];
 | 
			
		||||
                    var first = _gameState[i + (j * NUMBER_OF_ROWS)];
 | 
			
		||||
                    if (first != Field.Empty)
 | 
			
		||||
                        for (var k = 1; k < 4; k++)
 | 
			
		||||
                        {
 | 
			
		||||
                            var next = _gameState[i + k + (j * NumberOfRows)];
 | 
			
		||||
                            var next = _gameState[i + k + (j * NUMBER_OF_ROWS)];
 | 
			
		||||
                            if (next == first)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (k == 3)
 | 
			
		||||
@@ -213,21 +213,21 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // i'll go [0-1] in columns (and through all rows) and check to the right if 4 are connected
 | 
			
		||||
            for (var i = 0; i < NumberOfColumns - 3; i++)
 | 
			
		||||
            for (var i = 0; i < NUMBER_OF_COLUMNS - 3; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (CurrentPhase == Phase.Ended)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                for (var j = 0; j < NumberOfRows; j++)
 | 
			
		||||
                for (var j = 0; j < NUMBER_OF_ROWS; j++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (CurrentPhase == Phase.Ended)
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    var first = _gameState[j + (i * NumberOfRows)];
 | 
			
		||||
                    var first = _gameState[j + (i * NUMBER_OF_ROWS)];
 | 
			
		||||
                    if (first != Field.Empty)
 | 
			
		||||
                        for (var k = 1; k < 4; k++)
 | 
			
		||||
                        {
 | 
			
		||||
                            var next = _gameState[j + ((i + k) * NumberOfRows)];
 | 
			
		||||
                            var next = _gameState[j + ((i + k) * NUMBER_OF_ROWS)];
 | 
			
		||||
                            if (next == first)
 | 
			
		||||
                                if (k == 3)
 | 
			
		||||
                                    EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
 | 
			
		||||
@@ -239,17 +239,17 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //need to check diagonal now
 | 
			
		||||
            for (var col = 0; col < NumberOfColumns; col++)
 | 
			
		||||
            for (var col = 0; col < NUMBER_OF_COLUMNS; col++)
 | 
			
		||||
            {
 | 
			
		||||
                if (CurrentPhase == Phase.Ended)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                for (var row = 0; row < NumberOfRows; row++)
 | 
			
		||||
                for (var row = 0; row < NUMBER_OF_ROWS; row++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (CurrentPhase == Phase.Ended)
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    var first = _gameState[row + (col * NumberOfRows)];
 | 
			
		||||
                    var first = _gameState[row + (col * NUMBER_OF_ROWS)];
 | 
			
		||||
 | 
			
		||||
                    if (first != Field.Empty)
 | 
			
		||||
                    {
 | 
			
		||||
@@ -263,12 +263,12 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
                            var curCol = col - i;
 | 
			
		||||
 | 
			
		||||
                            //check if current values are in range
 | 
			
		||||
                            if (curRow is >= NumberOfRows or < 0)
 | 
			
		||||
                            if (curRow is >= NUMBER_OF_ROWS or < 0)
 | 
			
		||||
                                break;
 | 
			
		||||
                            if (curCol is < 0 or >= NumberOfColumns)
 | 
			
		||||
                            if (curCol is < 0 or >= NUMBER_OF_COLUMNS)
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            var cur = _gameState[curRow + (curCol * NumberOfRows)];
 | 
			
		||||
                            var cur = _gameState[curRow + (curCol * NUMBER_OF_ROWS)];
 | 
			
		||||
                            if (cur == first)
 | 
			
		||||
                                same++;
 | 
			
		||||
                            else break;
 | 
			
		||||
@@ -290,12 +290,12 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
                            var curCol = col + i;
 | 
			
		||||
 | 
			
		||||
                            //check if current values are in range
 | 
			
		||||
                            if (curRow is >= NumberOfRows or < 0)
 | 
			
		||||
                            if (curRow is >= NUMBER_OF_ROWS or < 0)
 | 
			
		||||
                                break;
 | 
			
		||||
                            if (curCol is < 0 or >= NumberOfColumns)
 | 
			
		||||
                            if (curCol is < 0 or >= NUMBER_OF_COLUMNS)
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            var cur = _gameState[curRow + (curCol * NumberOfRows)];
 | 
			
		||||
                            var cur = _gameState[curRow + (curCol * NUMBER_OF_ROWS)];
 | 
			
		||||
                            if (cur == first)
 | 
			
		||||
                                same++;
 | 
			
		||||
                            else break;
 | 
			
		||||
@@ -323,7 +323,7 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
                ResetTimer();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var _ = OnGameStateUpdated?.Invoke(this);
 | 
			
		||||
            _= OnGameStateUpdated?.Invoke(this);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        finally { _locker.Release(); }
 | 
			
		||||
@@ -337,7 +337,7 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        if (CurrentPhase == Phase.Ended)
 | 
			
		||||
            return;
 | 
			
		||||
        var _ = OnGameEnded?.Invoke(this, result);
 | 
			
		||||
        _= OnGameEnded?.Invoke(this, result);
 | 
			
		||||
        CurrentPhase = Phase.Ended;
 | 
			
		||||
 | 
			
		||||
        if (result == Result.Draw)
 | 
			
		||||
@@ -357,8 +357,8 @@ public sealed class Connect4Game : IDisposable
 | 
			
		||||
    //column is full if there are no empty fields
 | 
			
		||||
    private bool IsColumnFull(int column)
 | 
			
		||||
    {
 | 
			
		||||
        var start = NumberOfRows * column;
 | 
			
		||||
        for (var i = start; i < start + NumberOfRows; i++)
 | 
			
		||||
        var start = NUMBER_OF_ROWS * column;
 | 
			
		||||
        for (var i = start; i < start + NUMBER_OF_ROWS; i++)
 | 
			
		||||
            if (_gameState[i] == Field.Empty)
 | 
			
		||||
                return false;
 | 
			
		||||
        return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ public partial class Gambling
 | 
			
		||||
                if (ctx.Channel.Id != arg.Channel.Id)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                _= Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    var success = false;
 | 
			
		||||
                    if (int.TryParse(arg.Content, out var col)) success = await game.Input(arg.Author.Id, col);
 | 
			
		||||
@@ -168,11 +168,11 @@ public partial class Gambling
 | 
			
		||||
            if (game.CurrentPhase is Connect4Game.Phase.P1Move or Connect4Game.Phase.P2Move)
 | 
			
		||||
                sb.AppendLine(GetText(strs.connect4_player_to_move(Format.Bold(game.CurrentPlayer.Username))));
 | 
			
		||||
 | 
			
		||||
            for (var i = Connect4Game.NumberOfRows; i > 0; i--)
 | 
			
		||||
            for (var i = Connect4Game.NUMBER_OF_ROWS; i > 0; i--)
 | 
			
		||||
            {
 | 
			
		||||
                for (var j = 0; j < Connect4Game.NumberOfColumns; j++)
 | 
			
		||||
                for (var j = 0; j < Connect4Game.NUMBER_OF_COLUMNS; j++)
 | 
			
		||||
                {
 | 
			
		||||
                    var cur = game.GameState[i + (j * Connect4Game.NumberOfRows) - 1];
 | 
			
		||||
                    var cur = game.GameState[i + (j * Connect4Game.NUMBER_OF_ROWS) - 1];
 | 
			
		||||
 | 
			
		||||
                    if (cur == Connect4Game.Field.Empty)
 | 
			
		||||
                        sb.Append("⚫"); //black circle
 | 
			
		||||
@@ -185,7 +185,7 @@ public partial class Gambling
 | 
			
		||||
                sb.AppendLine();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < Connect4Game.NumberOfColumns; i++) sb.Append(numbers[i]);
 | 
			
		||||
            for (var i = 0; i < Connect4Game.NUMBER_OF_COLUMNS; i++) sb.Append(numbers[i]);
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -68,10 +68,10 @@ public partial class Gambling
 | 
			
		||||
            if (num > 10)
 | 
			
		||||
                num = 10;
 | 
			
		||||
 | 
			
		||||
            var (ImageStream, ToSend) = await InternalDraw(num, ctx.Guild.Id);
 | 
			
		||||
            await using (ImageStream)
 | 
			
		||||
            var (imageStream, toSend) = await InternalDraw(num, ctx.Guild.Id);
 | 
			
		||||
            await using (imageStream)
 | 
			
		||||
            {
 | 
			
		||||
                await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend);
 | 
			
		||||
                await ctx.Channel.SendFileAsync(imageStream, num + " cards.jpg", toSend);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -83,10 +83,10 @@ public partial class Gambling
 | 
			
		||||
            if (num > 10)
 | 
			
		||||
                num = 10;
 | 
			
		||||
 | 
			
		||||
            var (ImageStream, ToSend) = await InternalDraw(num);
 | 
			
		||||
            await using (ImageStream)
 | 
			
		||||
            var (imageStream, toSend) = await InternalDraw(num);
 | 
			
		||||
            await using (imageStream)
 | 
			
		||||
            {
 | 
			
		||||
                await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend);
 | 
			
		||||
                await ctx.Channel.SendFileAsync(imageStream, num + " cards.jpg", toSend);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ public class GameStatusEvent : ICurrencyEvent
 | 
			
		||||
    public bool PotEmptied { get; private set; }
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
    private readonly IGuild _guild;
 | 
			
		||||
    private IUserMessage _msg;
 | 
			
		||||
    private IUserMessage msg;
 | 
			
		||||
    private readonly ICurrencyService _cs;
 | 
			
		||||
    private readonly long _amount;
 | 
			
		||||
 | 
			
		||||
@@ -32,9 +32,9 @@ public class GameStatusEvent : ICurrencyEvent
 | 
			
		||||
                                                               .Select(x => (char)x)
 | 
			
		||||
                                                               .ToArray();
 | 
			
		||||
 | 
			
		||||
    private readonly object stopLock = new();
 | 
			
		||||
    private readonly object _stopLock = new();
 | 
			
		||||
 | 
			
		||||
    private readonly object potLock = new();
 | 
			
		||||
    private readonly object _potLock = new();
 | 
			
		||||
 | 
			
		||||
    public GameStatusEvent(
 | 
			
		||||
        DiscordSocketClient client,
 | 
			
		||||
@@ -62,9 +62,7 @@ public class GameStatusEvent : ICurrencyEvent
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void EventTimeout(object state)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = StopEvent();
 | 
			
		||||
    }
 | 
			
		||||
        => _ = StopEvent();
 | 
			
		||||
 | 
			
		||||
    private async void OnTimerTick(object state)
 | 
			
		||||
    {
 | 
			
		||||
@@ -83,20 +81,20 @@ public class GameStatusEvent : ICurrencyEvent
 | 
			
		||||
                true);
 | 
			
		||||
 | 
			
		||||
            if (_isPotLimited)
 | 
			
		||||
                await _msg.ModifyAsync(m =>
 | 
			
		||||
                await msg.ModifyAsync(m =>
 | 
			
		||||
                    {
 | 
			
		||||
                        m.Embed = GetEmbed(PotSize).Build();
 | 
			
		||||
                    },
 | 
			
		||||
                    new() { RetryMode = RetryMode.AlwaysRetry });
 | 
			
		||||
 | 
			
		||||
            Log.Information("Awarded {0} users {1} currency.{2}",
 | 
			
		||||
            Log.Information("Awarded {Count} users {Amount} currency.{Remaining}",
 | 
			
		||||
                toAward.Count,
 | 
			
		||||
                _amount,
 | 
			
		||||
                _isPotLimited ? $" {PotSize} left." : "");
 | 
			
		||||
 | 
			
		||||
            if (potEmpty)
 | 
			
		||||
            {
 | 
			
		||||
                var _ = StopEvent();
 | 
			
		||||
                _= StopEvent();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -107,7 +105,7 @@ public class GameStatusEvent : ICurrencyEvent
 | 
			
		||||
 | 
			
		||||
    public async Task StartEvent()
 | 
			
		||||
    {
 | 
			
		||||
        _msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize));
 | 
			
		||||
        msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize));
 | 
			
		||||
        await _client.SetGameAsync(_code);
 | 
			
		||||
        _client.MessageDeleted += OnMessageDeleted;
 | 
			
		||||
        _client.MessageReceived += HandleMessage;
 | 
			
		||||
@@ -117,55 +115,55 @@ public class GameStatusEvent : ICurrencyEvent
 | 
			
		||||
    private IEmbedBuilder GetEmbed(long pot)
 | 
			
		||||
        => _embedFunc(CurrencyEvent.Type.GameStatus, _opts, pot);
 | 
			
		||||
 | 
			
		||||
    private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, Cacheable<IMessageChannel, ulong> cacheable)
 | 
			
		||||
    private async Task OnMessageDeleted(Cacheable<IMessage, ulong> message, Cacheable<IMessageChannel, ulong> cacheable)
 | 
			
		||||
    {
 | 
			
		||||
        if (msg.Id == _msg.Id) await StopEvent();
 | 
			
		||||
        if (message.Id == this.msg.Id) await StopEvent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task StopEvent()
 | 
			
		||||
    {
 | 
			
		||||
        await Task.Yield();
 | 
			
		||||
        lock (stopLock)
 | 
			
		||||
        lock (_stopLock)
 | 
			
		||||
        {
 | 
			
		||||
            if (Stopped)
 | 
			
		||||
                return;
 | 
			
		||||
            Stopped = true;
 | 
			
		||||
            _client.MessageDeleted -= OnMessageDeleted;
 | 
			
		||||
            _client.MessageReceived -= HandleMessage;
 | 
			
		||||
            _client.SetGameAsync(null);
 | 
			
		||||
            _t.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            _timeout?.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            _ = _client.SetGameAsync(null);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var _ = _msg.DeleteAsync();
 | 
			
		||||
                _= msg.DeleteAsync();
 | 
			
		||||
            }
 | 
			
		||||
            catch { }
 | 
			
		||||
 | 
			
		||||
            var os = OnEnded(_guild.Id);
 | 
			
		||||
            _ = OnEnded?.Invoke(_guild.Id);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task HandleMessage(SocketMessage msg)
 | 
			
		||||
    private Task HandleMessage(SocketMessage message)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            if (msg.Author is not IGuildUser gu // no unknown users, as they could be bots, or alts
 | 
			
		||||
            if (message.Author is not IGuildUser gu // no unknown users, as they could be bots, or alts
 | 
			
		||||
                || gu.IsBot // no bots
 | 
			
		||||
                || msg.Content != _code // code has to be the same
 | 
			
		||||
                || message.Content != _code // code has to be the same
 | 
			
		||||
                || (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5) // no recently created accounts
 | 
			
		||||
                return;
 | 
			
		||||
            // there has to be money left in the pot
 | 
			
		||||
            // and the user wasn't rewarded
 | 
			
		||||
            if (_awardedUsers.Add(msg.Author.Id) && TryTakeFromPot())
 | 
			
		||||
            if (_awardedUsers.Add(message.Author.Id) && TryTakeFromPot())
 | 
			
		||||
            {
 | 
			
		||||
                _toAward.Enqueue(msg.Author.Id);
 | 
			
		||||
                _toAward.Enqueue(message.Author.Id);
 | 
			
		||||
                if (_isPotLimited && PotSize < _amount)
 | 
			
		||||
                    PotEmptied = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await msg.DeleteAsync(new() { RetryMode = RetryMode.AlwaysFail });
 | 
			
		||||
                await message.DeleteAsync(new() { RetryMode = RetryMode.AlwaysFail });
 | 
			
		||||
            }
 | 
			
		||||
            catch { }
 | 
			
		||||
        });
 | 
			
		||||
@@ -175,7 +173,7 @@ public class GameStatusEvent : ICurrencyEvent
 | 
			
		||||
    private bool TryTakeFromPot()
 | 
			
		||||
    {
 | 
			
		||||
        if (_isPotLimited)
 | 
			
		||||
            lock (potLock)
 | 
			
		||||
            lock (_potLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (PotSize < _amount)
 | 
			
		||||
                    return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
    public bool PotEmptied { get; private set; }
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
    private readonly IGuild _guild;
 | 
			
		||||
    private IUserMessage _msg;
 | 
			
		||||
    private IEmote _emote;
 | 
			
		||||
    private IUserMessage msg;
 | 
			
		||||
    private IEmote emote;
 | 
			
		||||
    private readonly ICurrencyService _cs;
 | 
			
		||||
    private readonly long _amount;
 | 
			
		||||
 | 
			
		||||
@@ -27,9 +27,9 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
    private readonly EventOptions _opts;
 | 
			
		||||
    private readonly GamblingConfig _config;
 | 
			
		||||
 | 
			
		||||
    private readonly object stopLock = new();
 | 
			
		||||
    private readonly object _stopLock = new();
 | 
			
		||||
 | 
			
		||||
    private readonly object potLock = new();
 | 
			
		||||
    private readonly object _potLock = new();
 | 
			
		||||
 | 
			
		||||
    public ReactionEvent(
 | 
			
		||||
        DiscordSocketClient client,
 | 
			
		||||
@@ -58,9 +58,7 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void EventTimeout(object state)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = StopEvent();
 | 
			
		||||
    }
 | 
			
		||||
        => _= StopEvent();
 | 
			
		||||
 | 
			
		||||
    private async void OnTimerTick(object state)
 | 
			
		||||
    {
 | 
			
		||||
@@ -76,20 +74,20 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
            await _cs.AddBulkAsync(toAward, toAward.Select(_ => "Reaction Event"), toAward.Select(_ => _amount), true);
 | 
			
		||||
 | 
			
		||||
            if (_isPotLimited)
 | 
			
		||||
                await _msg.ModifyAsync(m =>
 | 
			
		||||
                await msg.ModifyAsync(m =>
 | 
			
		||||
                    {
 | 
			
		||||
                        m.Embed = GetEmbed(PotSize).Build();
 | 
			
		||||
                    },
 | 
			
		||||
                    new() { RetryMode = RetryMode.AlwaysRetry });
 | 
			
		||||
 | 
			
		||||
            Log.Information("Awarded {0} users {1} currency.{2}",
 | 
			
		||||
            Log.Information("Awarded {Count} users {Amount} currency.{Remaining}",
 | 
			
		||||
                toAward.Count,
 | 
			
		||||
                _amount,
 | 
			
		||||
                _isPotLimited ? $" {PotSize} left." : "");
 | 
			
		||||
 | 
			
		||||
            if (potEmpty)
 | 
			
		||||
            {
 | 
			
		||||
                var _ = StopEvent();
 | 
			
		||||
                _= StopEvent();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -100,12 +98,12 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
 | 
			
		||||
    public async Task StartEvent()
 | 
			
		||||
    {
 | 
			
		||||
        if (Emote.TryParse(_config.Currency.Sign, out var emote))
 | 
			
		||||
            _emote = emote;
 | 
			
		||||
        if (Emote.TryParse(_config.Currency.Sign, out var parsedEmote))
 | 
			
		||||
            this.emote = parsedEmote;
 | 
			
		||||
        else
 | 
			
		||||
            _emote = new Emoji(_config.Currency.Sign);
 | 
			
		||||
        _msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize));
 | 
			
		||||
        await _msg.AddReactionAsync(_emote);
 | 
			
		||||
            this.emote = new Emoji(_config.Currency.Sign);
 | 
			
		||||
        msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize));
 | 
			
		||||
        await msg.AddReactionAsync(this.emote);
 | 
			
		||||
        _client.MessageDeleted += OnMessageDeleted;
 | 
			
		||||
        _client.ReactionAdded += HandleReaction;
 | 
			
		||||
        _t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
 | 
			
		||||
@@ -114,15 +112,15 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
    private IEmbedBuilder GetEmbed(long pot)
 | 
			
		||||
        => _embedFunc(CurrencyEvent.Type.Reaction, _opts, pot);
 | 
			
		||||
 | 
			
		||||
    private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, Cacheable<IMessageChannel, ulong> cacheable)
 | 
			
		||||
    private async Task OnMessageDeleted(Cacheable<IMessage, ulong> message, Cacheable<IMessageChannel, ulong> cacheable)
 | 
			
		||||
    {
 | 
			
		||||
        if (msg.Id == _msg.Id) await StopEvent();
 | 
			
		||||
        if (message.Id == this.msg.Id) await StopEvent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task StopEvent()
 | 
			
		||||
    {
 | 
			
		||||
        await Task.Yield();
 | 
			
		||||
        lock (stopLock)
 | 
			
		||||
        lock (_stopLock)
 | 
			
		||||
        {
 | 
			
		||||
            if (Stopped)
 | 
			
		||||
                return;
 | 
			
		||||
@@ -133,27 +131,27 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
            _timeout?.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var _ = _msg.DeleteAsync();
 | 
			
		||||
                _= msg.DeleteAsync();
 | 
			
		||||
            }
 | 
			
		||||
            catch { }
 | 
			
		||||
 | 
			
		||||
            var os = OnEnded(_guild.Id);
 | 
			
		||||
            _ = OnEnded?.Invoke(_guild.Id);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task HandleReaction(
 | 
			
		||||
        Cacheable<IUserMessage, ulong> msg,
 | 
			
		||||
        Cacheable<IUserMessage, ulong> message,
 | 
			
		||||
        Cacheable<IMessageChannel, ulong> cacheable,
 | 
			
		||||
        SocketReaction r)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(() =>
 | 
			
		||||
        _= Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
            if (_emote.Name != r.Emote.Name)
 | 
			
		||||
            if (emote.Name != r.Emote.Name)
 | 
			
		||||
                return;
 | 
			
		||||
            if ((r.User.IsSpecified
 | 
			
		||||
                    ? r.User.Value
 | 
			
		||||
                    : null) is not IGuildUser gu // no unknown users, as they could be bots, or alts
 | 
			
		||||
                || msg.Id != _msg.Id // same message
 | 
			
		||||
                || message.Id != this.msg.Id // same message
 | 
			
		||||
                || gu.IsBot // no bots
 | 
			
		||||
                || (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5 // no recently created accounts
 | 
			
		||||
                || (_noRecentlyJoinedServer
 | 
			
		||||
@@ -177,7 +175,7 @@ public class ReactionEvent : ICurrencyEvent
 | 
			
		||||
    private bool TryTakeFromPot()
 | 
			
		||||
    {
 | 
			
		||||
        if (_isPotLimited)
 | 
			
		||||
            lock (potLock)
 | 
			
		||||
            lock (_potLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (PotSize < _amount)
 | 
			
		||||
                    return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,8 @@ public class PlantPickService : INService
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
    private readonly GamblingConfigService _gss;
 | 
			
		||||
 | 
			
		||||
    public readonly ConcurrentHashSet<ulong> _generationChannels = new();
 | 
			
		||||
    private readonly SemaphoreSlim pickLock = new(1, 1);
 | 
			
		||||
    private readonly ConcurrentHashSet<ulong> _generationChannels;
 | 
			
		||||
    private readonly SemaphoreSlim _pickLock = new(1, 1);
 | 
			
		||||
 | 
			
		||||
    public PlantPickService(
 | 
			
		||||
        DbService db,
 | 
			
		||||
@@ -101,6 +101,7 @@ public class PlantPickService : INService
 | 
			
		||||
    ///     Get a random currency image stream, with an optional password sticked onto it.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="pass">Optional password to add to top left corner.</param>
 | 
			
		||||
    /// <param name="extension">Extension of the file, defaults to png</param>
 | 
			
		||||
    /// <returns>Stream of the currency image</returns>
 | 
			
		||||
    public Stream GetRandomCurrencyImage(string pass, out string extension)
 | 
			
		||||
    {
 | 
			
		||||
@@ -140,7 +141,7 @@ public class PlantPickService : INService
 | 
			
		||||
        pass = pass.TrimTo(10, true).ToLowerInvariant();
 | 
			
		||||
        using var img = Image.Load<Rgba32>(curImg, out var format);
 | 
			
		||||
        // choose font size based on the image height, so that it's visible
 | 
			
		||||
        var font = _fonts.NotoSans.CreateFont(img.Height / 12, FontStyle.Bold);
 | 
			
		||||
        var font = _fonts.NotoSans.CreateFont(img.Height / 12.0f, FontStyle.Bold);
 | 
			
		||||
        img.Mutate(x =>
 | 
			
		||||
        {
 | 
			
		||||
            // measure the size of the text to be drawing
 | 
			
		||||
@@ -171,7 +172,7 @@ public class PlantPickService : INService
 | 
			
		||||
        if (!_generationChannels.Contains(channel.Id))
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -245,7 +246,7 @@ public class PlantPickService : INService
 | 
			
		||||
        ulong uid,
 | 
			
		||||
        string pass)
 | 
			
		||||
    {
 | 
			
		||||
        await pickLock.WaitAsync();
 | 
			
		||||
        await _pickLock.WaitAsync();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            long amount;
 | 
			
		||||
@@ -276,7 +277,7 @@ public class PlantPickService : INService
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                // delete all of the plant messages which have just been picked
 | 
			
		||||
                var _ = ch.DeleteMessagesAsync(ids);
 | 
			
		||||
                _= ch.DeleteMessagesAsync(ids);
 | 
			
		||||
            }
 | 
			
		||||
            catch { }
 | 
			
		||||
 | 
			
		||||
@@ -285,7 +286,7 @@ public class PlantPickService : INService
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            pickLock.Release();
 | 
			
		||||
            _pickLock.Release();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,14 +13,10 @@ public class CurrencyRaffleService : INService
 | 
			
		||||
 | 
			
		||||
    public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new();
 | 
			
		||||
    private readonly SemaphoreSlim _locker = new(1, 1);
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly ICurrencyService _cs;
 | 
			
		||||
 | 
			
		||||
    public CurrencyRaffleService(DbService db, ICurrencyService cs)
 | 
			
		||||
    {
 | 
			
		||||
        _db = db;
 | 
			
		||||
        _cs = cs;
 | 
			
		||||
    }
 | 
			
		||||
    public CurrencyRaffleService(ICurrencyService cs)
 | 
			
		||||
        => _cs = cs;
 | 
			
		||||
 | 
			
		||||
    public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(
 | 
			
		||||
        ulong channelId,
 | 
			
		||||
@@ -57,7 +53,7 @@ public class CurrencyRaffleService : INService
 | 
			
		||||
 | 
			
		||||
            if (newGame)
 | 
			
		||||
            {
 | 
			
		||||
                var _t = Task.Run(async () =>
 | 
			
		||||
                _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    await Task.Delay(60000);
 | 
			
		||||
                    await _locker.WaitAsync();
 | 
			
		||||
@@ -68,7 +64,7 @@ public class CurrencyRaffleService : INService
 | 
			
		||||
 | 
			
		||||
                        await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win", won);
 | 
			
		||||
                        Games.Remove(channelId, out _);
 | 
			
		||||
                        var oe = onEnded(winner.DiscordUser, won);
 | 
			
		||||
                        _ = onEnded(winner.DiscordUser, won);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch { }
 | 
			
		||||
                    finally { _locker.Release(); }
 | 
			
		||||
 
 | 
			
		||||
@@ -211,7 +211,7 @@ public partial class Gambling
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                _= Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    await Task.Delay(1000);
 | 
			
		||||
                    _runningUsers.Remove(ctx.User.Id);
 | 
			
		||||
 
 | 
			
		||||
@@ -127,7 +127,7 @@ public sealed class AcrophobiaGame : IDisposable
 | 
			
		||||
                        || !_usersWhoVoted.Add(userId))
 | 
			
		||||
                        break;
 | 
			
		||||
                    ++submissions[toVoteFor];
 | 
			
		||||
                    var _ = Task.Run(() => OnUserVoted(userName));
 | 
			
		||||
                    _= Task.Run(() => OnUserVoted(userName));
 | 
			
		||||
                    return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ public partial class Games
 | 
			
		||||
                if (msg.Channel.Id != ctx.Channel.Id)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                _= Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
                _killTimeout);
 | 
			
		||||
 | 
			
		||||
            CurrentPhase = Phase.Playing;
 | 
			
		||||
            var _ = OnGameStarted?.Invoke(this);
 | 
			
		||||
            _= OnGameStarted?.Invoke(this);
 | 
			
		||||
            var __ = OnRoundStarted?.Invoke(this, CurrentNumber);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
@@ -122,7 +122,7 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
                    {
 | 
			
		||||
                        _killTimer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
                        CurrentPhase = Phase.Ended;
 | 
			
		||||
                        var _ = OnGameEnded?.Invoke(this, userTuple.Name);
 | 
			
		||||
                        _= OnGameEnded?.Invoke(this, userTuple.Name);
 | 
			
		||||
                    }
 | 
			
		||||
                    else // else just start the new round without the user who was the last
 | 
			
		||||
                    {
 | 
			
		||||
@@ -159,7 +159,7 @@ public sealed class NunchiGame : IDisposable
 | 
			
		||||
        {
 | 
			
		||||
            _killTimer.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            CurrentPhase = Phase.Ended;
 | 
			
		||||
            var _ = OnGameEnded?.Invoke(this, _participants.Count > 0 ? _participants.First().Name : null);
 | 
			
		||||
            _= OnGameEnded?.Invoke(this, _participants.Count > 0 ? _participants.First().Name : null);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ public partial class Games
 | 
			
		||||
 | 
			
		||||
            Task _client_MessageReceived(SocketMessage arg)
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                _= Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    if (arg.Channel.Id != ctx.Channel.Id)
 | 
			
		||||
                        return;
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ public class PollRunner
 | 
			
		||||
            if (!Poll.Votes.Add(voteObj))
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            var _ = OnVoted?.Invoke(msg, usr);
 | 
			
		||||
            _= OnVoted?.Invoke(msg, usr);
 | 
			
		||||
        }
 | 
			
		||||
        finally { _locker.Release(); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@ public class TypingGame
 | 
			
		||||
 | 
			
		||||
    private Task AnswerReceived(SocketMessage imsg)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -182,7 +182,7 @@ public class TicTacToe
 | 
			
		||||
 | 
			
		||||
    private Task Client_MessageReceived(SocketMessage msg)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            await _moveLock.WaitAsync();
 | 
			
		||||
            try
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ public partial class Games
 | 
			
		||||
            {
 | 
			
		||||
                if (_service.TicTacToeGames.TryGetValue(channel.Id, out var game))
 | 
			
		||||
                {
 | 
			
		||||
                    var _ = Task.Run(async () =>
 | 
			
		||||
                    _= Task.Run(async () =>
 | 
			
		||||
                    {
 | 
			
		||||
                        await game.Start((IGuildUser)ctx.User);
 | 
			
		||||
                    });
 | 
			
		||||
 
 | 
			
		||||
@@ -24,10 +24,10 @@ public class TriviaGame
 | 
			
		||||
    private readonly ICurrencyService _cs;
 | 
			
		||||
    private readonly TriviaOptions _options;
 | 
			
		||||
 | 
			
		||||
    private CancellationTokenSource _triviaCancelSource;
 | 
			
		||||
    private CancellationTokenSource triviaCancelSource;
 | 
			
		||||
 | 
			
		||||
    private readonly TriviaQuestionPool _questionPool;
 | 
			
		||||
    private int _timeoutCount;
 | 
			
		||||
    private int timeoutCount;
 | 
			
		||||
    private readonly string _quitCommand;
 | 
			
		||||
    private readonly IEmbedBuilderService _eb;
 | 
			
		||||
 | 
			
		||||
@@ -66,7 +66,7 @@ public class TriviaGame
 | 
			
		||||
        while (!ShouldStopGame)
 | 
			
		||||
        {
 | 
			
		||||
            // reset the cancellation source    
 | 
			
		||||
            _triviaCancelSource = new();
 | 
			
		||||
            triviaCancelSource = new();
 | 
			
		||||
            showHowToQuit = !showHowToQuit;
 | 
			
		||||
 | 
			
		||||
            // load question
 | 
			
		||||
@@ -121,7 +121,7 @@ public class TriviaGame
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    //hint
 | 
			
		||||
                    await Task.Delay(_options.QuestionTimer * 1000 / 2, _triviaCancelSource.Token);
 | 
			
		||||
                    await Task.Delay(_options.QuestionTimer * 1000 / 2, triviaCancelSource.Token);
 | 
			
		||||
                    if (!_options.NoHint)
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
@@ -136,9 +136,9 @@ public class TriviaGame
 | 
			
		||||
                        catch (Exception ex) { Log.Warning(ex, "Error editing triva message"); }
 | 
			
		||||
 | 
			
		||||
                    //timeout
 | 
			
		||||
                    await Task.Delay(_options.QuestionTimer * 1000 / 2, _triviaCancelSource.Token);
 | 
			
		||||
                    await Task.Delay(_options.QuestionTimer * 1000 / 2, triviaCancelSource.Token);
 | 
			
		||||
                }
 | 
			
		||||
                catch (TaskCanceledException) { _timeoutCount = 0; } //means someone guessed the answer
 | 
			
		||||
                catch (TaskCanceledException) { timeoutCount = 0; } //means someone guessed the answer
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
@@ -146,7 +146,7 @@ public class TriviaGame
 | 
			
		||||
                _client.MessageReceived -= PotentialGuess;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!_triviaCancelSource.IsCancellationRequested)
 | 
			
		||||
            if (!triviaCancelSource.IsCancellationRequested)
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var embed = _eb.Create()
 | 
			
		||||
@@ -158,7 +158,7 @@ public class TriviaGame
 | 
			
		||||
 | 
			
		||||
                    await Channel.EmbedAsync(embed);
 | 
			
		||||
 | 
			
		||||
                    if (_options.Timeout != 0 && ++_timeoutCount >= _options.Timeout)
 | 
			
		||||
                    if (_options.Timeout != 0 && ++timeoutCount >= _options.Timeout)
 | 
			
		||||
                        await StopGame();
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
@@ -198,7 +198,7 @@ public class TriviaGame
 | 
			
		||||
 | 
			
		||||
    private Task PotentialGuess(SocketMessage imsg)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -218,7 +218,7 @@ public class TriviaGame
 | 
			
		||||
                {
 | 
			
		||||
                    if (GameActive
 | 
			
		||||
                        && CurrentQuestion.IsAnswerCorrect(umsg.Content)
 | 
			
		||||
                        && !_triviaCancelSource.IsCancellationRequested)
 | 
			
		||||
                        && !triviaCancelSource.IsCancellationRequested)
 | 
			
		||||
                    {
 | 
			
		||||
                        Users.AddOrUpdate(guildUser, 1, (_, old) => ++old);
 | 
			
		||||
                        guess = true;
 | 
			
		||||
@@ -227,7 +227,7 @@ public class TriviaGame
 | 
			
		||||
                finally { _guessLock.Release(); }
 | 
			
		||||
 | 
			
		||||
                if (!guess) return;
 | 
			
		||||
                _triviaCancelSource.Cancel();
 | 
			
		||||
                triviaCancelSource.Cancel();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                if (_options.WinRequirement != 0 && Users[guildUser] == _options.WinRequirement)
 | 
			
		||||
 
 | 
			
		||||
@@ -21,9 +21,9 @@ public class TriviaQuestion
 | 
			
		||||
    public string Answer { get; set; }
 | 
			
		||||
 | 
			
		||||
    public string CleanAnswer
 | 
			
		||||
        => _cleanAnswer ?? (_cleanAnswer = Clean(Answer));
 | 
			
		||||
        => cleanAnswer ?? (cleanAnswer = Clean(Answer));
 | 
			
		||||
 | 
			
		||||
    private string _cleanAnswer;
 | 
			
		||||
    private string cleanAnswer;
 | 
			
		||||
 | 
			
		||||
    public TriviaQuestion(
 | 
			
		||||
        string q,
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ public sealed partial class YtLoader
 | 
			
		||||
 | 
			
		||||
    public sealed class YtTrackInfo : TrackInfo
 | 
			
		||||
    {
 | 
			
		||||
        private const string BaseYoutubeUrl = "https://youtube.com/watch?v=";
 | 
			
		||||
        private const string BASE_YOUTUBE_URL = "https://youtube.com/watch?v=";
 | 
			
		||||
        public override string Url { get; }
 | 
			
		||||
        public override string Title { get; }
 | 
			
		||||
        public override TimeSpan Duration { get; }
 | 
			
		||||
@@ -62,7 +62,7 @@ public sealed partial class YtLoader
 | 
			
		||||
        public YtTrackInfo(string title, string videoId, TimeSpan duration)
 | 
			
		||||
        {
 | 
			
		||||
            Title = title;
 | 
			
		||||
            Url = BaseYoutubeUrl + videoId;
 | 
			
		||||
            Url = BASE_YOUTUBE_URL + videoId;
 | 
			
		||||
            Duration = duration;
 | 
			
		||||
 | 
			
		||||
            _videoId = videoId;
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,10 @@ namespace NadekoBot.Modules.Music.Services;
 | 
			
		||||
 | 
			
		||||
public sealed partial class YtLoader
 | 
			
		||||
{
 | 
			
		||||
    private static readonly byte[] YT_RESULT_INITIAL_DATA = Encoding.UTF8.GetBytes("var ytInitialData = ");
 | 
			
		||||
    private static readonly byte[] YT_RESULT_JSON_END = Encoding.UTF8.GetBytes(";<");
 | 
			
		||||
    private static readonly byte[] _ytResultInitialData = Encoding.UTF8.GetBytes("var ytInitialData = ");
 | 
			
		||||
    private static readonly byte[] _ytResultJsonEnd = Encoding.UTF8.GetBytes(";<");
 | 
			
		||||
 | 
			
		||||
    private static readonly string[] durationFormats =
 | 
			
		||||
    private static readonly string[] _durationFormats =
 | 
			
		||||
    {
 | 
			
		||||
        @"m\:ss", @"mm\:ss", @"h\:mm\:ss", @"hh\:mm\:ss", @"hhh\:mm\:ss"
 | 
			
		||||
    };
 | 
			
		||||
@@ -98,7 +98,7 @@ public sealed partial class YtLoader
 | 
			
		||||
            var durationString = elem.GetProperty("lengthText").GetProperty("simpleText").GetString();
 | 
			
		||||
 | 
			
		||||
            if (!TimeSpan.TryParseExact(durationString,
 | 
			
		||||
                    durationFormats,
 | 
			
		||||
                    _durationFormats,
 | 
			
		||||
                    CultureInfo.InvariantCulture,
 | 
			
		||||
                    out var duration))
 | 
			
		||||
            {
 | 
			
		||||
@@ -117,13 +117,13 @@ public sealed partial class YtLoader
 | 
			
		||||
    private Memory<byte> GetScriptResponseSpan(byte[] response)
 | 
			
		||||
    {
 | 
			
		||||
        var responseSpan = response.AsSpan()[140_000..];
 | 
			
		||||
        var startIndex = responseSpan.IndexOf(YT_RESULT_INITIAL_DATA);
 | 
			
		||||
        var startIndex = responseSpan.IndexOf(_ytResultInitialData);
 | 
			
		||||
        if (startIndex == -1)
 | 
			
		||||
            return null; // todo future try selecting html
 | 
			
		||||
        startIndex += YT_RESULT_INITIAL_DATA.Length;
 | 
			
		||||
        startIndex += _ytResultInitialData.Length;
 | 
			
		||||
 | 
			
		||||
        var endIndex =
 | 
			
		||||
            140_000 + startIndex + responseSpan[(startIndex + 20_000)..].IndexOf(YT_RESULT_JSON_END) + 20_000;
 | 
			
		||||
            140_000 + startIndex + responseSpan[(startIndex + 20_000)..].IndexOf(_ytResultJsonEnd) + 20_000;
 | 
			
		||||
        startIndex += 140_000;
 | 
			
		||||
        return response.AsMemory(startIndex, endIndex - startIndex);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -42,9 +42,9 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
        {
 | 
			
		||||
            // just make sure the internal logic runs first
 | 
			
		||||
            // to make sure that some potential indermediate value is not returned
 | 
			
		||||
            lock (locker)
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                return _index;
 | 
			
		||||
                return index;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -53,128 +53,128 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
    {
 | 
			
		||||
        get
 | 
			
		||||
        {
 | 
			
		||||
            lock (locker)
 | 
			
		||||
            lock (_locker)
 | 
			
		||||
            {
 | 
			
		||||
                return _tracks.Count;
 | 
			
		||||
                return tracks.Count;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private LinkedList<QueuedTrackInfo> _tracks;
 | 
			
		||||
    private LinkedList<QueuedTrackInfo> tracks;
 | 
			
		||||
 | 
			
		||||
    private int _index;
 | 
			
		||||
    private int index;
 | 
			
		||||
 | 
			
		||||
    private readonly object locker = new();
 | 
			
		||||
    private readonly object _locker = new();
 | 
			
		||||
 | 
			
		||||
    public MusicQueue()
 | 
			
		||||
    {
 | 
			
		||||
        _index = 0;
 | 
			
		||||
        _tracks = new();
 | 
			
		||||
        index = 0;
 | 
			
		||||
        tracks = new();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IQueuedTrackInfo Enqueue(ITrackInfo trackInfo, string queuer, out int index)
 | 
			
		||||
    public IQueuedTrackInfo Enqueue(ITrackInfo trackInfo, string queuer, out int enqueuedAt)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            var added = new QueuedTrackInfo(trackInfo, queuer);
 | 
			
		||||
            index = _tracks.Count;
 | 
			
		||||
            _tracks.AddLast(added);
 | 
			
		||||
            enqueuedAt = tracks.Count;
 | 
			
		||||
            tracks.AddLast(added);
 | 
			
		||||
            return added;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IQueuedTrackInfo EnqueueNext(ITrackInfo trackInfo, string queuer, out int index)
 | 
			
		||||
    public IQueuedTrackInfo EnqueueNext(ITrackInfo trackInfo, string queuer, out int trackIndex)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            if (_tracks.Count == 0) return Enqueue(trackInfo, queuer, out index);
 | 
			
		||||
            if (tracks.Count == 0) return Enqueue(trackInfo, queuer, out trackIndex);
 | 
			
		||||
 | 
			
		||||
            var currentNode = _tracks.First!;
 | 
			
		||||
            var currentNode = tracks.First!;
 | 
			
		||||
            int i;
 | 
			
		||||
            for (i = 1; i <= _index; i++)
 | 
			
		||||
            for (i = 1; i <= this.index; i++)
 | 
			
		||||
                currentNode = currentNode.Next!; // can't be null because index is always in range of the count
 | 
			
		||||
 | 
			
		||||
            var added = new QueuedTrackInfo(trackInfo, queuer);
 | 
			
		||||
            index = i;
 | 
			
		||||
            trackIndex = i;
 | 
			
		||||
 | 
			
		||||
            _tracks.AddAfter(currentNode, added);
 | 
			
		||||
            tracks.AddAfter(currentNode, added);
 | 
			
		||||
 | 
			
		||||
            return added;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void EnqueueMany(IEnumerable<ITrackInfo> tracks, string queuer)
 | 
			
		||||
    public void EnqueueMany(IEnumerable<ITrackInfo> toEnqueue, string queuer)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var track in tracks)
 | 
			
		||||
            foreach (var track in toEnqueue)
 | 
			
		||||
            {
 | 
			
		||||
                var added = new QueuedTrackInfo(track, queuer);
 | 
			
		||||
                _tracks.AddLast(added);
 | 
			
		||||
                this.tracks.AddLast(added);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IReadOnlyCollection<IQueuedTrackInfo> List()
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            return _tracks.ToList();
 | 
			
		||||
            return tracks.ToList();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IQueuedTrackInfo? GetCurrent(out int index)
 | 
			
		||||
    public IQueuedTrackInfo? GetCurrent(out int currentIndex)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            index = _index;
 | 
			
		||||
            return _tracks.ElementAtOrDefault(_index);
 | 
			
		||||
            currentIndex = index;
 | 
			
		||||
            return tracks.ElementAtOrDefault(index);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Advance()
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            if (++_index >= _tracks.Count)
 | 
			
		||||
                _index = 0;
 | 
			
		||||
            if (++index >= tracks.Count)
 | 
			
		||||
                index = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Clear()
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            _tracks.Clear();
 | 
			
		||||
            tracks.Clear();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool SetIndex(int index)
 | 
			
		||||
    public bool SetIndex(int newIndex)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            if (index < 0 || index >= _tracks.Count)
 | 
			
		||||
            if (newIndex < 0 || newIndex >= tracks.Count)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            _index = index;
 | 
			
		||||
            this.index = newIndex;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void RemoveAtInternal(int index, out IQueuedTrackInfo trackInfo)
 | 
			
		||||
    private void RemoveAtInternal(int remoteAtIndex, out IQueuedTrackInfo trackInfo)
 | 
			
		||||
    {
 | 
			
		||||
        var removedNode = _tracks.First!;
 | 
			
		||||
        var removedNode = tracks.First!;
 | 
			
		||||
        int i;
 | 
			
		||||
        for (i = 0; i < index; i++) removedNode = removedNode.Next!;
 | 
			
		||||
        for (i = 0; i < remoteAtIndex; i++) removedNode = removedNode.Next!;
 | 
			
		||||
 | 
			
		||||
        trackInfo = removedNode.Value;
 | 
			
		||||
        _tracks.Remove(removedNode);
 | 
			
		||||
        tracks.Remove(removedNode);
 | 
			
		||||
 | 
			
		||||
        if (i <= _index)
 | 
			
		||||
            --_index;
 | 
			
		||||
        if (i <= this.index)
 | 
			
		||||
            --this.index;
 | 
			
		||||
 | 
			
		||||
        if (_index < 0)
 | 
			
		||||
            _index = Count;
 | 
			
		||||
        if (this.index < 0)
 | 
			
		||||
            this.index = Count;
 | 
			
		||||
 | 
			
		||||
        // if it was the last song in the queue
 | 
			
		||||
        // // wrap back to start
 | 
			
		||||
@@ -188,10 +188,10 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
 | 
			
		||||
    public void RemoveCurrent()
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            if (_index < _tracks.Count)
 | 
			
		||||
                RemoveAtInternal(_index, out _);
 | 
			
		||||
            if (index < tracks.Count)
 | 
			
		||||
                RemoveAtInternal(index, out _);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -204,29 +204,29 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
        if (to == from)
 | 
			
		||||
            throw new ArgumentException($"{nameof(from)} and {nameof(to)} must be different");
 | 
			
		||||
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            if (from >= Count || to >= Count)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            // update current track index
 | 
			
		||||
            if (from == _index)
 | 
			
		||||
            if (from == index)
 | 
			
		||||
            {
 | 
			
		||||
                // if the song being moved is the current track
 | 
			
		||||
                // it means that it will for sure end up on the destination
 | 
			
		||||
                _index = to;
 | 
			
		||||
                index = to;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                // moving a track from below the current track means 
 | 
			
		||||
                // means it will drop down
 | 
			
		||||
                if (from < _index)
 | 
			
		||||
                    _index--;
 | 
			
		||||
                if (from < index)
 | 
			
		||||
                    index--;
 | 
			
		||||
 | 
			
		||||
                // moving a track to below the current track
 | 
			
		||||
                // means it will rise up
 | 
			
		||||
                if (to <= _index)
 | 
			
		||||
                    _index++;
 | 
			
		||||
                if (to <= index)
 | 
			
		||||
                    index++;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                // if both from and to are below _index - net change is + 1 - 1 = 0
 | 
			
		||||
@@ -236,78 +236,76 @@ public sealed partial class MusicQueue : IMusicQueue
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // get the node which needs to be moved
 | 
			
		||||
            var fromNode = _tracks.First!;
 | 
			
		||||
            var fromNode = tracks.First!;
 | 
			
		||||
            for (var i = 0; i < from; i++)
 | 
			
		||||
                fromNode = fromNode.Next!;
 | 
			
		||||
 | 
			
		||||
            // remove it from the queue
 | 
			
		||||
            _tracks.Remove(fromNode);
 | 
			
		||||
            tracks.Remove(fromNode);
 | 
			
		||||
 | 
			
		||||
            // if it needs to be added as a first node,
 | 
			
		||||
            // add it directly and return
 | 
			
		||||
            if (to == 0)
 | 
			
		||||
            {
 | 
			
		||||
                _tracks.AddFirst(fromNode);
 | 
			
		||||
                tracks.AddFirst(fromNode);
 | 
			
		||||
                return fromNode.Value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // else find the node at the index before the specified target
 | 
			
		||||
            var addAfterNode = _tracks.First!;
 | 
			
		||||
            var addAfterNode = tracks.First!;
 | 
			
		||||
            for (var i = 1; i < to; i++)
 | 
			
		||||
                addAfterNode = addAfterNode.Next!;
 | 
			
		||||
 | 
			
		||||
            // and add after it
 | 
			
		||||
            _tracks.AddAfter(addAfterNode, fromNode);
 | 
			
		||||
            tracks.AddAfter(addAfterNode, fromNode);
 | 
			
		||||
            return fromNode.Value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Shuffle(Random rng)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            var list = _tracks.ToList();
 | 
			
		||||
            var list = tracks.ToList();
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < list.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var struck = rng.Next(i, list.Count);
 | 
			
		||||
                var temp = list[struck];
 | 
			
		||||
                list[struck] = list[i];
 | 
			
		||||
                list[i] = temp;
 | 
			
		||||
                (list[struck], list[i]) = (list[i], list[struck]);
 | 
			
		||||
 | 
			
		||||
                // could preserving the index during shuffling be done better?
 | 
			
		||||
                if (i == _index)
 | 
			
		||||
                    _index = struck;
 | 
			
		||||
                else if (struck == _index)
 | 
			
		||||
                    _index = i;
 | 
			
		||||
                if (i == index)
 | 
			
		||||
                    index = struck;
 | 
			
		||||
                else if (struck == index)
 | 
			
		||||
                    index = i;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _tracks = new(list);
 | 
			
		||||
            tracks = new(list);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool IsLast()
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            return _index == _tracks.Count // if there are no tracks
 | 
			
		||||
                   || _index == _tracks.Count - 1;
 | 
			
		||||
            return index == tracks.Count // if there are no tracks
 | 
			
		||||
                   || index == tracks.Count - 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool TryRemoveAt(int index, out IQueuedTrackInfo? trackInfo, out bool isCurrent)
 | 
			
		||||
    public bool TryRemoveAt(int remoteAt, out IQueuedTrackInfo? trackInfo, out bool isCurrent)
 | 
			
		||||
    {
 | 
			
		||||
        lock (locker)
 | 
			
		||||
        lock (_locker)
 | 
			
		||||
        {
 | 
			
		||||
            isCurrent = false;
 | 
			
		||||
            trackInfo = null;
 | 
			
		||||
 | 
			
		||||
            if (index < 0 || index >= _tracks.Count)
 | 
			
		||||
            if (remoteAt < 0 || remoteAt >= tracks.Count)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if (index == _index) isCurrent = true;
 | 
			
		||||
            if (remoteAt == this.index) isCurrent = true;
 | 
			
		||||
 | 
			
		||||
            RemoveAtInternal(index, out trackInfo);
 | 
			
		||||
            RemoveAtInternal(remoteAt, out trackInfo);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ public sealed class VoiceProxy : IVoiceProxy
 | 
			
		||||
    private const int DELAY_ON_ERROR_MILISECONDS = 200;
 | 
			
		||||
 | 
			
		||||
    public VoiceProxyState State
 | 
			
		||||
        => _gateway switch
 | 
			
		||||
        => gateway switch
 | 
			
		||||
        {
 | 
			
		||||
            { Started: true, Stopped: false } => VoiceProxyState.Started,
 | 
			
		||||
            { Stopped: false } => VoiceProxyState.Created,
 | 
			
		||||
@@ -25,16 +25,16 @@ public sealed class VoiceProxy : IVoiceProxy
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private VoiceGateway _gateway;
 | 
			
		||||
    private VoiceGateway gateway;
 | 
			
		||||
 | 
			
		||||
    public VoiceProxy(VoiceGateway initial)
 | 
			
		||||
        => _gateway = initial;
 | 
			
		||||
        => gateway = initial;
 | 
			
		||||
 | 
			
		||||
    public bool SendPcmFrame(VoiceClient vc, Span<byte> data, int length)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var gw = _gateway;
 | 
			
		||||
            var gw = gateway;
 | 
			
		||||
            if (gw is null || gw.Stopped || !gw.Started) return false;
 | 
			
		||||
 | 
			
		||||
            vc.SendPcmFrame(gw, data, 0, length);
 | 
			
		||||
@@ -55,7 +55,7 @@ public sealed class VoiceProxy : IVoiceProxy
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var gw = _gateway;
 | 
			
		||||
                var gw = gateway;
 | 
			
		||||
                if (gw is null || !gw.ConnectingFinished.Task.IsCompleted)
 | 
			
		||||
                {
 | 
			
		||||
                    ++errorCount;
 | 
			
		||||
@@ -78,8 +78,8 @@ public sealed class VoiceProxy : IVoiceProxy
 | 
			
		||||
        return State != VoiceProxyState.Stopped && errorCount <= MAX_ERROR_COUNT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void SetGateway(VoiceGateway gateway)
 | 
			
		||||
        => _gateway = gateway;
 | 
			
		||||
    public void SetGateway(VoiceGateway newGateway)
 | 
			
		||||
        => gateway = newGateway;
 | 
			
		||||
 | 
			
		||||
    public Task StartSpeakingAsync()
 | 
			
		||||
        => RunGatewayAction(gw => gw.SendSpeakingAsync(VoiceSpeaking.State.Microphone));
 | 
			
		||||
@@ -88,11 +88,11 @@ public sealed class VoiceProxy : IVoiceProxy
 | 
			
		||||
        => RunGatewayAction(gw => gw.SendSpeakingAsync(VoiceSpeaking.State.None));
 | 
			
		||||
 | 
			
		||||
    public async Task StartGateway()
 | 
			
		||||
        => await _gateway.Start();
 | 
			
		||||
        => await gateway.Start();
 | 
			
		||||
 | 
			
		||||
    public Task StopGateway()
 | 
			
		||||
    {
 | 
			
		||||
        if (_gateway is { } gw)
 | 
			
		||||
        if (gateway is { } gw)
 | 
			
		||||
            return gw.StopAsync();
 | 
			
		||||
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,8 @@ public sealed class LocalTrackResolver : ILocalTrackResolver
 | 
			
		||||
                           if (!x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)
 | 
			
		||||
                               && _musicExtensions.Contains(x.Extension.ToUpperInvariant())) return true;
 | 
			
		||||
                           return false;
 | 
			
		||||
                       });
 | 
			
		||||
                       })
 | 
			
		||||
                       .ToList();
 | 
			
		||||
 | 
			
		||||
        var firstFile = files.FirstOrDefault()?.FullName;
 | 
			
		||||
        if (firstFile is null)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ namespace NadekoBot.Modules.Nsfw.Common;
 | 
			
		||||
 | 
			
		||||
public class SearchImageCacher : INService
 | 
			
		||||
{
 | 
			
		||||
    private static readonly ISet<string> defaultTagBlacklist = new HashSet<string>
 | 
			
		||||
    private static readonly ISet<string> _defaultTagBlacklist = new HashSet<string>
 | 
			
		||||
    {
 | 
			
		||||
        "loli",
 | 
			
		||||
        "lolicon",
 | 
			
		||||
@@ -21,7 +21,7 @@ public class SearchImageCacher : INService
 | 
			
		||||
    private readonly Dictionary<Booru, HashSet<string>> _usedTags = new();
 | 
			
		||||
    private readonly IMemoryCache _cache;
 | 
			
		||||
 | 
			
		||||
    private readonly ConcurrentDictionary<(Booru, string), int> maxPages = new();
 | 
			
		||||
    private readonly ConcurrentDictionary<(Booru, string), int> _maxPages = new();
 | 
			
		||||
 | 
			
		||||
    public SearchImageCacher(IHttpClientFactory httpFactory, IMemoryCache cache)
 | 
			
		||||
    {
 | 
			
		||||
@@ -59,7 +59,7 @@ public class SearchImageCacher : INService
 | 
			
		||||
            // Log.Warning("Got no images for {0}, tags: {1}", type, string.Join(", ", tags));
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        Log.Information("Updating {0}...", type);
 | 
			
		||||
        Log.Information("Updating {Type}...", type);
 | 
			
		||||
        lock (_typeLocks[type])
 | 
			
		||||
        {
 | 
			
		||||
            var typeUsedTags = _usedTags[type];
 | 
			
		||||
@@ -75,7 +75,7 @@ public class SearchImageCacher : INService
 | 
			
		||||
            {
 | 
			
		||||
                // if any of the tags is a tag banned by discord
 | 
			
		||||
                // do not put that image in the cache
 | 
			
		||||
                if (defaultTagBlacklist.Overlaps(img.Tags))
 | 
			
		||||
                if (_defaultTagBlacklist.Overlaps(img.Tags))
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                // if image doesn't have a proper absolute uri, skip it
 | 
			
		||||
@@ -190,7 +190,7 @@ public class SearchImageCacher : INService
 | 
			
		||||
            tags = tags[..2];
 | 
			
		||||
 | 
			
		||||
        // use both tags banned by discord and tags banned on the server 
 | 
			
		||||
        if (blacklistedTags.Overlaps(tags) || defaultTagBlacklist.Overlaps(tags))
 | 
			
		||||
        if (blacklistedTags.Overlaps(tags) || _defaultTagBlacklist.Overlaps(tags))
 | 
			
		||||
            return default;
 | 
			
		||||
 | 
			
		||||
        // query for an image
 | 
			
		||||
@@ -223,16 +223,16 @@ public class SearchImageCacher : INService
 | 
			
		||||
        CancellationToken cancel)
 | 
			
		||||
    {
 | 
			
		||||
        var tagStr = string.Join(' ', tags.OrderByDescending(x => x));
 | 
			
		||||
        var page = 0;
 | 
			
		||||
 | 
			
		||||
        var attempt = 0;
 | 
			
		||||
        while (attempt++ <= 10)
 | 
			
		||||
        {
 | 
			
		||||
            if (maxPages.TryGetValue((type, tagStr), out var maxPage))
 | 
			
		||||
            int page;
 | 
			
		||||
            if (_maxPages.TryGetValue((type, tagStr), out var maxPage))
 | 
			
		||||
            {
 | 
			
		||||
                if (maxPage == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Information("Tag {0} yields no result on {1}, skipping.", tagStr, type);
 | 
			
		||||
                    Log.Information("Tag {Tags} yields no result on {Type}, skipping", tagStr, type);
 | 
			
		||||
                    return new();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -247,7 +247,7 @@ public class SearchImageCacher : INService
 | 
			
		||||
 | 
			
		||||
            if (result is null or { Count: 0 })
 | 
			
		||||
            {
 | 
			
		||||
                Log.Information("Tag {0}, page {1} has no result on {2}.",
 | 
			
		||||
                Log.Information("Tag {Tags}, page {Page} has no result on {Type}",
 | 
			
		||||
                    string.Join(", ", tags),
 | 
			
		||||
                    page,
 | 
			
		||||
                    type.ToString());
 | 
			
		||||
@@ -284,7 +284,7 @@ public class SearchImageCacher : INService
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Log.Information("Downloading from {0} (page {1})...", type, page);
 | 
			
		||||
            Log.Information("Downloading from {Type} (page {Page})...", type, page);
 | 
			
		||||
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            var downloader = GetImageDownloader(type, http);
 | 
			
		||||
@@ -293,7 +293,7 @@ public class SearchImageCacher : INService
 | 
			
		||||
            if (images.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                var tagStr = string.Join(' ', tags.OrderByDescending(x => x));
 | 
			
		||||
                maxPages[(type, tagStr)] = page;
 | 
			
		||||
                _maxPages[(type, tagStr)] = page;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return images;
 | 
			
		||||
@@ -305,7 +305,7 @@ public class SearchImageCacher : INService
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Error(ex,
 | 
			
		||||
                "Error downloading an image:\nTags: {0}\nType: {1}\nPage: {2}\nMessage: {3}",
 | 
			
		||||
                "Error downloading an image:\nTags: {Tags}\nType: {Type}\nPage: {Page}\nMessage: {Message}",
 | 
			
		||||
                string.Join(", ", tags),
 | 
			
		||||
                type,
 | 
			
		||||
                page,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@ public sealed class BlacklistService : IEarlyBehavior
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly IPubSub _pubSub;
 | 
			
		||||
    private readonly IBotCredentials _creds;
 | 
			
		||||
    private IReadOnlyList<BlacklistEntry> _blacklist;
 | 
			
		||||
    private IReadOnlyList<BlacklistEntry> blacklist;
 | 
			
		||||
 | 
			
		||||
    private readonly TypedKey<BlacklistEntry[]> blPubKey = new("blacklist.reload");
 | 
			
		||||
    private readonly TypedKey<BlacklistEntry[]> _blPubKey = new("blacklist.reload");
 | 
			
		||||
 | 
			
		||||
    public BlacklistService(DbService db, IPubSub pubSub, IBotCredentials creds)
 | 
			
		||||
    {
 | 
			
		||||
@@ -25,18 +25,18 @@ public sealed class BlacklistService : IEarlyBehavior
 | 
			
		||||
        _creds = creds;
 | 
			
		||||
 | 
			
		||||
        Reload(false);
 | 
			
		||||
        _pubSub.Sub(blPubKey, OnReload);
 | 
			
		||||
        _pubSub.Sub(_blPubKey, OnReload);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ValueTask OnReload(BlacklistEntry[] blacklist)
 | 
			
		||||
    private ValueTask OnReload(BlacklistEntry[] newBlacklist)
 | 
			
		||||
    {
 | 
			
		||||
        _blacklist = blacklist;
 | 
			
		||||
        this.blacklist = newBlacklist;
 | 
			
		||||
        return default;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<bool> RunBehavior(IGuild guild, IUserMessage usrMsg)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var bl in _blacklist)
 | 
			
		||||
        foreach (var bl in blacklist)
 | 
			
		||||
        {
 | 
			
		||||
            if (guild is not null && bl.Type == BlacklistType.Server && bl.ItemId == guild.Id)
 | 
			
		||||
            {
 | 
			
		||||
@@ -68,14 +68,14 @@ public sealed class BlacklistService : IEarlyBehavior
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IReadOnlyList<BlacklistEntry> GetBlacklist()
 | 
			
		||||
        => _blacklist;
 | 
			
		||||
        => blacklist;
 | 
			
		||||
 | 
			
		||||
    public void Reload(bool publish = true)
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var toPublish = uow.Blacklist.AsNoTracking().ToArray();
 | 
			
		||||
        _blacklist = toPublish;
 | 
			
		||||
        if (publish) _pubSub.Pub(blPubKey, toPublish);
 | 
			
		||||
        blacklist = toPublish;
 | 
			
		||||
        if (publish) _pubSub.Pub(_blPubKey, toPublish);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Blacklist(BlacklistType type, ulong id)
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ public class CmdCdService : ILateBlocker, INService
 | 
			
		||||
 | 
			
		||||
            activeCdsForGuild.Add(new() { UserId = user.Id, Command = commandName });
 | 
			
		||||
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            _= Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ public sealed class FilterService : IEarlyBehavior
 | 
			
		||||
 | 
			
		||||
        client.MessageUpdated += (oldData, newMsg, channel) =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            _= Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                var guild = (channel as ITextChannel)?.Guild;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,8 +60,8 @@ public class CryptoService : INService
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        using var _http = _httpFactory.CreateClient();
 | 
			
		||||
                        var strData = await _http.GetStringAsync(
 | 
			
		||||
                        using var http = _httpFactory.CreateClient();
 | 
			
		||||
                        var strData = await http.GetStringAsync(
 | 
			
		||||
                            "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?"
 | 
			
		||||
                            + $"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}"
 | 
			
		||||
                            + "&start=1"
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ public class FeedsService : INService
 | 
			
		||||
        _client = client;
 | 
			
		||||
        _eb = eb;
 | 
			
		||||
 | 
			
		||||
        var _ = Task.Run(TrackFeeds);
 | 
			
		||||
        _= Task.Run(TrackFeeds);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<EmbedBuilder> TrackFeeds()
 | 
			
		||||
 
 | 
			
		||||
@@ -477,7 +477,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        if (!await ValidateQuery(word))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        using var _http = _httpFactory.CreateClient();
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        string res;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -485,7 +485,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
                e =>
 | 
			
		||||
                {
 | 
			
		||||
                    e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12);
 | 
			
		||||
                    return _http.GetStringAsync("https://api.pearson.com/v2/dictionaries/entries?headword="
 | 
			
		||||
                    return http.GetStringAsync("https://api.pearson.com/v2/dictionaries/entries?headword="
 | 
			
		||||
                                                + WebUtility.UrlEncode(word));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -430,8 +430,8 @@ public class SearchesService : INService
 | 
			
		||||
    {
 | 
			
		||||
        var redis = _cache.Redis;
 | 
			
		||||
        var db = redis.GetDatabase();
 | 
			
		||||
        const string STEAM_GAME_IDS_KEY = "steam_names_to_appid";
 | 
			
		||||
        var exists = await db.KeyExistsAsync(STEAM_GAME_IDS_KEY);
 | 
			
		||||
        const string steamGameIdsKey = "steam_names_to_appid";
 | 
			
		||||
        // var exists = await db.KeyExistsAsync(steamGameIdsKey);
 | 
			
		||||
 | 
			
		||||
        // if we didn't get steam name to id map already, get it
 | 
			
		||||
        //if (!exists)
 | 
			
		||||
@@ -448,7 +448,7 @@ public class SearchesService : INService
 | 
			
		||||
        //    }
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        var gamesMap = await _cache.GetOrAddCachedDataAsync(STEAM_GAME_IDS_KEY,
 | 
			
		||||
        var gamesMap = await _cache.GetOrAddCachedDataAsync(steamGameIdsKey,
 | 
			
		||||
            async _ =>
 | 
			
		||||
            {
 | 
			
		||||
                using var http = _httpFactory.CreateClient();
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ public partial class Searches
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class XkcdCommands : NadekoSubmodule
 | 
			
		||||
    {
 | 
			
		||||
        private const string _xkcdUrl = "https://xkcd.com";
 | 
			
		||||
        private const string XKCD_URL = "https://xkcd.com";
 | 
			
		||||
        private readonly IHttpClientFactory _httpFactory;
 | 
			
		||||
 | 
			
		||||
        public XkcdCommands(IHttpClientFactory factory)
 | 
			
		||||
@@ -23,12 +23,12 @@ public partial class Searches
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    using var http = _httpFactory.CreateClient();
 | 
			
		||||
                    var res = await http.GetStringAsync($"{_xkcdUrl}/info.0.json");
 | 
			
		||||
                    var res = await http.GetStringAsync($"{XKCD_URL}/info.0.json");
 | 
			
		||||
                    var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
 | 
			
		||||
                    var embed = _eb.Create()
 | 
			
		||||
                                   .WithOkColor()
 | 
			
		||||
                                   .WithImageUrl(comic.ImageLink)
 | 
			
		||||
                                   .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{comic.Num}")
 | 
			
		||||
                                   .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{XKCD_URL}/{comic.Num}")
 | 
			
		||||
                                   .AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
 | 
			
		||||
                                   .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
 | 
			
		||||
                    var sent = await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
@@ -57,13 +57,13 @@ public partial class Searches
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                using var http = _httpFactory.CreateClient();
 | 
			
		||||
                var res = await http.GetStringAsync($"{_xkcdUrl}/{num}/info.0.json");
 | 
			
		||||
                var res = await http.GetStringAsync($"{XKCD_URL}/{num}/info.0.json");
 | 
			
		||||
 | 
			
		||||
                var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
                               .WithImageUrl(comic.ImageLink)
 | 
			
		||||
                               .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{_xkcdUrl}/{num}")
 | 
			
		||||
                               .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{XKCD_URL}/{num}")
 | 
			
		||||
                               .AddField(GetText(strs.comic_number), comic.Num.ToString(), true)
 | 
			
		||||
                               .AddField(GetText(strs.date), $"{comic.Month}/{comic.Year}", true);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ public class CommandMapService : IInputTransformer, INService
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}");
 | 
			
		||||
                        var _ = Task.Run(async () =>
 | 
			
		||||
                        _= Task.Run(async () =>
 | 
			
		||||
                        {
 | 
			
		||||
                            await Task.Delay(1500);
 | 
			
		||||
                            await toDelete.DeleteAsync(new() { RetryMode = RetryMode.AlwaysRetry });
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ public partial class Utility
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class QuoteCommands : NadekoSubmodule
 | 
			
		||||
    {
 | 
			
		||||
        private const string _prependExport =
 | 
			
		||||
        private const string PREPEND_EXPORT =
 | 
			
		||||
            @"# Keys are keywords, Each key has a LIST of quotes in the following format:
 | 
			
		||||
# - id: Alphanumeric id used for commands related to the quote. (Note, when using .quotesimport, a new id will be generated.) 
 | 
			
		||||
#   an: Author name 
 | 
			
		||||
@@ -279,7 +279,7 @@ public partial class Utility
 | 
			
		||||
            var exprsDict = quotes.GroupBy(x => x.Keyword)
 | 
			
		||||
                                .ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel));
 | 
			
		||||
 | 
			
		||||
            var text = _prependExport + _exportSerializer.Serialize(exprsDict).UnescapeUnicodeCodePoints();
 | 
			
		||||
            var text = PREPEND_EXPORT + _exportSerializer.Serialize(exprsDict).UnescapeUnicodeCodePoints();
 | 
			
		||||
 | 
			
		||||
            await using var stream = await text.ToStream();
 | 
			
		||||
            await ctx.Channel.SendFileAsync(stream, "quote-export.yml");
 | 
			
		||||
 
 | 
			
		||||
@@ -11,20 +11,20 @@ public class StreamRoleService : INService
 | 
			
		||||
{
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
    private readonly ConcurrentDictionary<ulong, StreamRoleSettings> guildSettings;
 | 
			
		||||
    private readonly ConcurrentDictionary<ulong, StreamRoleSettings> _guildSettings;
 | 
			
		||||
 | 
			
		||||
    public StreamRoleService(DiscordSocketClient client, DbService db, Bot bot)
 | 
			
		||||
    {
 | 
			
		||||
        _db = db;
 | 
			
		||||
        _client = client;
 | 
			
		||||
 | 
			
		||||
        guildSettings = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.StreamRole)
 | 
			
		||||
        _guildSettings = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.StreamRole)
 | 
			
		||||
                           .Where(x => x.Value is { Enabled: true })
 | 
			
		||||
                           .ToConcurrent();
 | 
			
		||||
 | 
			
		||||
        _client.GuildMemberUpdated += Client_GuildMemberUpdated;
 | 
			
		||||
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -39,10 +39,10 @@ public class StreamRoleService : INService
 | 
			
		||||
 | 
			
		||||
    private Task Client_GuildMemberUpdated(Cacheable<SocketGuildUser, ulong> cacheable, SocketGuildUser after)
 | 
			
		||||
    {
 | 
			
		||||
        var _ = Task.Run(async () =>
 | 
			
		||||
        _= Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            //if user wasn't streaming or didn't have a game status at all
 | 
			
		||||
            if (guildSettings.TryGetValue(after.Guild.Id, out var setting)) await RescanUser(after, setting);
 | 
			
		||||
            if (_guildSettings.TryGetValue(after.Guild.Id, out var setting)) await RescanUser(after, setting);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
@@ -51,6 +51,7 @@ public class StreamRoleService : INService
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Adds or removes a user from a blacklist or a whitelist in the specified guild.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="listType">List type</param>
 | 
			
		||||
    /// <param name="guild">Guild</param>
 | 
			
		||||
    /// <param name="action">Add or rem action</param>
 | 
			
		||||
    /// <param name="userId">User's Id</param>
 | 
			
		||||
@@ -97,7 +98,6 @@ public class StreamRoleService : INService
 | 
			
		||||
                    var toRemove = streamRoleSettings.Blacklist.FirstOrDefault(x => x.Equals(userObj));
 | 
			
		||||
                    if (toRemove is not null)
 | 
			
		||||
                    {
 | 
			
		||||
                        success = true;
 | 
			
		||||
                        success = streamRoleSettings.Blacklist.Remove(toRemove);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -123,7 +123,7 @@ public class StreamRoleService : INService
 | 
			
		||||
    /// <returns>The keyword set</returns>
 | 
			
		||||
    public async Task<string> SetKeyword(IGuild guild, string keyword)
 | 
			
		||||
    {
 | 
			
		||||
        keyword = keyword?.Trim()?.ToLowerInvariant();
 | 
			
		||||
        keyword = keyword?.Trim().ToLowerInvariant();
 | 
			
		||||
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
@@ -145,7 +145,7 @@ public class StreamRoleService : INService
 | 
			
		||||
    /// <returns>The keyword set</returns>
 | 
			
		||||
    public string GetKeyword(ulong guildId)
 | 
			
		||||
    {
 | 
			
		||||
        if (guildSettings.TryGetValue(guildId, out var outSetting))
 | 
			
		||||
        if (_guildSettings.TryGetValue(guildId, out var outSetting))
 | 
			
		||||
            return outSetting.Keyword;
 | 
			
		||||
 | 
			
		||||
        StreamRoleSettings setting;
 | 
			
		||||
@@ -193,7 +193,8 @@ public class StreamRoleService : INService
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Stops the stream role feature on the specified guild.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="guildId">Guild's Id</param>
 | 
			
		||||
    /// <param name="guild">Guild</param>
 | 
			
		||||
    /// <param name="cleanup">Whether to rescan users</param>
 | 
			
		||||
    public async Task StopStreamRole(IGuild guild, bool cleanup = false)
 | 
			
		||||
    {
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
@@ -205,7 +206,7 @@ public class StreamRoleService : INService
 | 
			
		||||
            await uow.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (guildSettings.TryRemove(guild.Id, out var setting) && cleanup)
 | 
			
		||||
        if (_guildSettings.TryRemove(guild.Id, out _) && cleanup)
 | 
			
		||||
            await RescanUsers(guild);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -231,7 +232,7 @@ public class StreamRoleService : INService
 | 
			
		||||
                if (addRole is null)
 | 
			
		||||
                {
 | 
			
		||||
                    await StopStreamRole(user.Guild);
 | 
			
		||||
                    Log.Warning("Stream role in server {0} no longer exists. Stopping.", setting.AddRoleId);
 | 
			
		||||
                    Log.Warning("Stream role in server {RoleId} no longer exists. Stopping", setting.AddRoleId);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -239,7 +240,7 @@ public class StreamRoleService : INService
 | 
			
		||||
                if (!user.RoleIds.Contains(addRole.Id))
 | 
			
		||||
                {
 | 
			
		||||
                    await user.AddRoleAsync(addRole);
 | 
			
		||||
                    Log.Information("Added stream role to user {0} in {1} server",
 | 
			
		||||
                    Log.Information("Added stream role to user {User} in {Server} server",
 | 
			
		||||
                        user.ToString(),
 | 
			
		||||
                        user.Guild.ToString());
 | 
			
		||||
                }
 | 
			
		||||
@@ -266,7 +267,7 @@ public class StreamRoleService : INService
 | 
			
		||||
                        throw new StreamRoleNotFoundException();
 | 
			
		||||
 | 
			
		||||
                    await user.RemoveRoleAsync(addRole);
 | 
			
		||||
                    Log.Information("Removed stream role from the user {0} in {1} server",
 | 
			
		||||
                    Log.Information("Removed stream role from the user {User} in {Server} server",
 | 
			
		||||
                        user.ToString(),
 | 
			
		||||
                        user.Guild.ToString());
 | 
			
		||||
                }
 | 
			
		||||
@@ -281,7 +282,7 @@ public class StreamRoleService : INService
 | 
			
		||||
 | 
			
		||||
    private async Task RescanUsers(IGuild guild)
 | 
			
		||||
    {
 | 
			
		||||
        if (!guildSettings.TryGetValue(guild.Id, out var setting))
 | 
			
		||||
        if (!_guildSettings.TryGetValue(guild.Id, out var setting))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var addRole = guild.GetRole(setting.AddRoleId);
 | 
			
		||||
@@ -299,5 +300,5 @@ public class StreamRoleService : INService
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void UpdateCache(ulong guildId, StreamRoleSettings setting)
 | 
			
		||||
        => guildSettings.AddOrUpdate(guildId, _ => setting, (_, _) => setting);
 | 
			
		||||
        => _guildSettings.AddOrUpdate(guildId, _ => setting, (_, _) => setting);
 | 
			
		||||
}
 | 
			
		||||
@@ -11,17 +11,14 @@ public class ConverterService : INService
 | 
			
		||||
 | 
			
		||||
    private readonly Timer _currencyUpdater;
 | 
			
		||||
    private readonly TimeSpan _updateInterval = new(12, 0, 0);
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    private readonly IDataCache _cache;
 | 
			
		||||
    private readonly IHttpClientFactory _httpFactory;
 | 
			
		||||
 | 
			
		||||
    public ConverterService(
 | 
			
		||||
        DiscordSocketClient client,
 | 
			
		||||
        DbService db,
 | 
			
		||||
        IDataCache cache,
 | 
			
		||||
        IHttpClientFactory factory)
 | 
			
		||||
    {
 | 
			
		||||
        _db = db;
 | 
			
		||||
        _cache = cache;
 | 
			
		||||
        _httpFactory = factory;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ public partial class Utility : NadekoModule
 | 
			
		||||
 | 
			
		||||
        if (ctx.Guild is not SocketGuild socketGuild)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning("Can't cast guild to socket guild.");
 | 
			
		||||
            Log.Warning("Can't cast guild to socket guild");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -154,7 +154,15 @@ public partial class Utility : NadekoModule
 | 
			
		||||
        var builder = new StringBuilder();
 | 
			
		||||
        var user = who == MeOrBot.Me ? (IGuildUser)ctx.User : ((SocketGuild)ctx.Guild).CurrentUser;
 | 
			
		||||
        var perms = user.GetPermissions((ITextChannel)ctx.Channel);
 | 
			
		||||
        foreach (var p in perms.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any()))
 | 
			
		||||
        foreach (var p in perms.GetType()
 | 
			
		||||
                               .GetProperties()
 | 
			
		||||
                               .Where(static p =>
 | 
			
		||||
                               {
 | 
			
		||||
                                   var method = p.GetGetMethod();
 | 
			
		||||
                                   if (method is null)
 | 
			
		||||
                                       return false;
 | 
			
		||||
                                   return !method.GetParameters().Any();
 | 
			
		||||
                               }))
 | 
			
		||||
            builder.AppendLine($"{p.Name} : {p.GetValue(perms, null)}");
 | 
			
		||||
        await SendConfirmAsync(builder.ToString());
 | 
			
		||||
    }
 | 
			
		||||
@@ -355,7 +363,10 @@ public partial class Utility : NadekoModule
 | 
			
		||||
        if (page < 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var guilds = await Task.Run(() => _client.Guilds.OrderBy(g => g.Name).Skip(page * 15).Take(15));
 | 
			
		||||
        var guilds = _client.Guilds.OrderBy(g => g.Name)
 | 
			
		||||
                            .Skip(page * 15)
 | 
			
		||||
                            .Take(15)
 | 
			
		||||
                            .ToList();
 | 
			
		||||
 | 
			
		||||
        if (!guilds.Any())
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -422,7 +422,7 @@ public class XpService : INService
 | 
			
		||||
        if (socketUser is not SocketGuildUser user || user.IsBot)
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
        var _ = Task.Run(() =>
 | 
			
		||||
        _= Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
            if (before.VoiceChannel is not null) ScanChannelForVoiceXp(before.VoiceChannel);
 | 
			
		||||
 | 
			
		||||
@@ -514,7 +514,7 @@ public class XpService : INService
 | 
			
		||||
        if (arg.Author is not SocketGuildUser user || user.IsBot)
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
        var _ = Task.Run(() =>
 | 
			
		||||
        _= Task.Run(() =>
 | 
			
		||||
        {
 | 
			
		||||
            if (!ShouldTrackXp(user, arg.Channel.Id))
 | 
			
		||||
                return;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,53 +26,53 @@ public class EmbedBuilderService : IEmbedBuilderService, INService
 | 
			
		||||
public sealed class DiscordEmbedBuilderWrapper : IEmbedBuilder
 | 
			
		||||
{
 | 
			
		||||
    private readonly BotConfig _botConfig;
 | 
			
		||||
    private EmbedBuilder _embed;
 | 
			
		||||
    private EmbedBuilder embed;
 | 
			
		||||
 | 
			
		||||
    public DiscordEmbedBuilderWrapper(in BotConfig botConfig, EmbedBuilder embed = null)
 | 
			
		||||
    {
 | 
			
		||||
        _botConfig = botConfig;
 | 
			
		||||
        _embed = embed ?? new EmbedBuilder();
 | 
			
		||||
        this.embed = embed ?? new EmbedBuilder();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder WithDescription(string desc)
 | 
			
		||||
        => Wrap(_embed.WithDescription(desc));
 | 
			
		||||
        => Wrap(embed.WithDescription(desc));
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder WithTitle(string title)
 | 
			
		||||
        => Wrap(_embed.WithTitle(title));
 | 
			
		||||
        => Wrap(embed.WithTitle(title));
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder AddField(string title, object value, bool isInline = false)
 | 
			
		||||
        => Wrap(_embed.AddField(title, value, isInline));
 | 
			
		||||
        => Wrap(embed.AddField(title, value, isInline));
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder WithFooter(string text, string iconUrl = null)
 | 
			
		||||
        => Wrap(_embed.WithFooter(text, iconUrl));
 | 
			
		||||
        => Wrap(embed.WithFooter(text, iconUrl));
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null)
 | 
			
		||||
        => Wrap(_embed.WithAuthor(name, iconUrl, url));
 | 
			
		||||
        => Wrap(embed.WithAuthor(name, iconUrl, url));
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder WithUrl(string url)
 | 
			
		||||
        => Wrap(_embed.WithUrl(url));
 | 
			
		||||
        => Wrap(embed.WithUrl(url));
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder WithImageUrl(string url)
 | 
			
		||||
        => Wrap(_embed.WithImageUrl(url));
 | 
			
		||||
        => Wrap(embed.WithImageUrl(url));
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder WithThumbnailUrl(string url)
 | 
			
		||||
        => Wrap(_embed.WithThumbnailUrl(url));
 | 
			
		||||
        => Wrap(embed.WithThumbnailUrl(url));
 | 
			
		||||
 | 
			
		||||
    public IEmbedBuilder WithColor(EmbedColor color)
 | 
			
		||||
        => color switch
 | 
			
		||||
        {
 | 
			
		||||
            EmbedColor.Ok => Wrap(_embed.WithColor(_botConfig.Color.Ok.ToDiscordColor())),
 | 
			
		||||
            EmbedColor.Pending => Wrap(_embed.WithColor(_botConfig.Color.Pending.ToDiscordColor())),
 | 
			
		||||
            EmbedColor.Error => Wrap(_embed.WithColor(_botConfig.Color.Error.ToDiscordColor())),
 | 
			
		||||
            EmbedColor.Ok => Wrap(embed.WithColor(_botConfig.Color.Ok.ToDiscordColor())),
 | 
			
		||||
            EmbedColor.Pending => Wrap(embed.WithColor(_botConfig.Color.Pending.ToDiscordColor())),
 | 
			
		||||
            EmbedColor.Error => Wrap(embed.WithColor(_botConfig.Color.Error.ToDiscordColor())),
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(color), "Unsupported EmbedColor type")
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    public Embed Build()
 | 
			
		||||
        => _embed.Build();
 | 
			
		||||
        => embed.Build();
 | 
			
		||||
 | 
			
		||||
    private IEmbedBuilder Wrap(EmbedBuilder eb)
 | 
			
		||||
    {
 | 
			
		||||
        _embed = eb;
 | 
			
		||||
        embed = eb;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,25 +7,25 @@ namespace NadekoBot.Services;
 | 
			
		||||
public sealed class BehaviorExecutor : IBehaviourExecutor, INService
 | 
			
		||||
{
 | 
			
		||||
    private readonly IServiceProvider _services;
 | 
			
		||||
    private IEnumerable<ILateExecutor> _lateExecutors;
 | 
			
		||||
    private IEnumerable<ILateBlocker> _lateBlockers;
 | 
			
		||||
    private IEnumerable<IEarlyBehavior> _earlyBehaviors;
 | 
			
		||||
    private IEnumerable<IInputTransformer> _transformers;
 | 
			
		||||
    private IEnumerable<ILateExecutor> lateExecutors;
 | 
			
		||||
    private IEnumerable<ILateBlocker> lateBlockers;
 | 
			
		||||
    private IEnumerable<IEarlyBehavior> earlyBehaviors;
 | 
			
		||||
    private IEnumerable<IInputTransformer> transformers;
 | 
			
		||||
 | 
			
		||||
    public BehaviorExecutor(IServiceProvider services)
 | 
			
		||||
        => _services = services;
 | 
			
		||||
 | 
			
		||||
    public void Initialize()
 | 
			
		||||
    {
 | 
			
		||||
        _lateExecutors = _services.GetServices<ILateExecutor>();
 | 
			
		||||
        _lateBlockers = _services.GetServices<ILateBlocker>();
 | 
			
		||||
        _earlyBehaviors = _services.GetServices<IEarlyBehavior>().OrderByDescending(x => x.Priority);
 | 
			
		||||
        _transformers = _services.GetServices<IInputTransformer>();
 | 
			
		||||
        lateExecutors = _services.GetServices<ILateExecutor>();
 | 
			
		||||
        lateBlockers = _services.GetServices<ILateBlocker>();
 | 
			
		||||
        earlyBehaviors = _services.GetServices<IEarlyBehavior>().OrderByDescending(x => x.Priority);
 | 
			
		||||
        transformers = _services.GetServices<IInputTransformer>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> RunEarlyBehavioursAsync(SocketGuild guild, IUserMessage usrMsg)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var beh in _earlyBehaviors)
 | 
			
		||||
        foreach (var beh in earlyBehaviors)
 | 
			
		||||
            if (await beh.RunBehavior(guild, usrMsg))
 | 
			
		||||
                return true;
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +35,7 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService
 | 
			
		||||
    public async Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg)
 | 
			
		||||
    {
 | 
			
		||||
        var messageContent = usrMsg.Content;
 | 
			
		||||
        foreach (var exec in _transformers)
 | 
			
		||||
        foreach (var exec in transformers)
 | 
			
		||||
        {
 | 
			
		||||
            string newContent;
 | 
			
		||||
            if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent))
 | 
			
		||||
@@ -51,10 +51,10 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> RunLateBlockersAsync(ICommandContext ctx, CommandInfo cmd)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var exec in _lateBlockers)
 | 
			
		||||
        foreach (var exec in lateBlockers)
 | 
			
		||||
            if (await exec.TryBlockLate(ctx, cmd.Module.GetTopLevelModule().Name, cmd))
 | 
			
		||||
            {
 | 
			
		||||
                Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]",
 | 
			
		||||
                Log.Information("Late blocking User [{User}] Command: [{Command}] in [{Module}]",
 | 
			
		||||
                    ctx.User,
 | 
			
		||||
                    cmd.Aliases[0],
 | 
			
		||||
                    exec.GetType().Name);
 | 
			
		||||
@@ -66,7 +66,7 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService
 | 
			
		||||
 | 
			
		||||
    public async Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var exec in _lateExecutors)
 | 
			
		||||
        foreach (var exec in lateExecutors)
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await exec.LateExecute(guild, usrMsg);
 | 
			
		||||
 
 | 
			
		||||
@@ -13,10 +13,10 @@ namespace NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
{
 | 
			
		||||
    private const string SearchEngineId = "018084019232060951019:hs5piey28-e";
 | 
			
		||||
    private const string SEARCH_ENGINE_ID = "018084019232060951019:hs5piey28-e";
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex
 | 
			
		||||
        plRegex = new("(?:youtu\\.be\\/|list=)(?<id>[\\da-zA-Z\\-_]*)", RegexOptions.Compiled);
 | 
			
		||||
        _plRegex = new("(?:youtu\\.be\\/|list=)(?<id>[\\da-zA-Z\\-_]*)", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
    public IReadOnlyDictionary<string, string> Languages { get; } = new Dictionary<string, string>
 | 
			
		||||
    {
 | 
			
		||||
@@ -151,9 +151,9 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
        { "yi", "yi" }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private readonly YouTubeService yt;
 | 
			
		||||
    private readonly UrlshortenerService sh;
 | 
			
		||||
    private readonly CustomsearchService cs;
 | 
			
		||||
    private readonly YouTubeService _yt;
 | 
			
		||||
    private readonly UrlshortenerService _sh;
 | 
			
		||||
    private readonly CustomsearchService _cs;
 | 
			
		||||
 | 
			
		||||
    //private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?<id>[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled);
 | 
			
		||||
    private readonly IBotCredentials _creds;
 | 
			
		||||
@@ -166,9 +166,9 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
 | 
			
		||||
        var bcs = new BaseClientService.Initializer { ApplicationName = "Nadeko Bot", ApiKey = _creds.GoogleApiKey };
 | 
			
		||||
 | 
			
		||||
        yt = new(bcs);
 | 
			
		||||
        sh = new(bcs);
 | 
			
		||||
        cs = new(bcs);
 | 
			
		||||
        _yt = new(bcs);
 | 
			
		||||
        _sh = new(bcs);
 | 
			
		||||
        _cs = new(bcs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
 | 
			
		||||
@@ -180,9 +180,9 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
        if (count <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(count));
 | 
			
		||||
 | 
			
		||||
        var match = plRegex.Match(keywords);
 | 
			
		||||
        var match = _plRegex.Match(keywords);
 | 
			
		||||
        if (match.Length > 1) return new[] { match.Groups["id"].Value };
 | 
			
		||||
        var query = yt.Search.List("snippet");
 | 
			
		||||
        var query = _yt.Search.List("snippet");
 | 
			
		||||
        query.MaxResults = count;
 | 
			
		||||
        query.Type = "playlist";
 | 
			
		||||
        query.Q = keywords;
 | 
			
		||||
@@ -199,7 +199,7 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
 | 
			
		||||
        if (count <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(count));
 | 
			
		||||
        var query = yt.Search.List("snippet");
 | 
			
		||||
        var query = _yt.Search.List("snippet");
 | 
			
		||||
        query.MaxResults = count;
 | 
			
		||||
        query.RelatedToVideoId = id;
 | 
			
		||||
        query.Type = "video";
 | 
			
		||||
@@ -215,7 +215,7 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
        if (count <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(count));
 | 
			
		||||
 | 
			
		||||
        var query = yt.Search.List("snippet");
 | 
			
		||||
        var query = _yt.Search.List("snippet");
 | 
			
		||||
        query.MaxResults = count;
 | 
			
		||||
        query.Q = keywords;
 | 
			
		||||
        query.Type = "video";
 | 
			
		||||
@@ -234,7 +234,7 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
        if (count <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(count));
 | 
			
		||||
 | 
			
		||||
        var query = yt.Search.List("snippet");
 | 
			
		||||
        var query = _yt.Search.List("snippet");
 | 
			
		||||
        query.MaxResults = count;
 | 
			
		||||
        query.Q = keywords;
 | 
			
		||||
        query.Type = "video";
 | 
			
		||||
@@ -256,7 +256,7 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var response = await sh.Url.Insert(new() { LongUrl = url }).ExecuteAsync();
 | 
			
		||||
            var response = await _sh.Url.Insert(new() { LongUrl = url }).ExecuteAsync();
 | 
			
		||||
            return response.Id;
 | 
			
		||||
        }
 | 
			
		||||
        catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.Forbidden)
 | 
			
		||||
@@ -288,7 +288,7 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
            var toGet = count > 50 ? 50 : count;
 | 
			
		||||
            count -= toGet;
 | 
			
		||||
 | 
			
		||||
            var query = yt.PlaylistItems.List("contentDetails");
 | 
			
		||||
            var query = _yt.PlaylistItems.List("contentDetails");
 | 
			
		||||
            query.MaxResults = toGet;
 | 
			
		||||
            query.PlaylistId = playlistId;
 | 
			
		||||
            query.PageToken = nextPageToken;
 | 
			
		||||
@@ -318,7 +318,7 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
            var toGet = remaining > 50 ? 50 : remaining;
 | 
			
		||||
            remaining -= toGet;
 | 
			
		||||
 | 
			
		||||
            var q = yt.Videos.List("contentDetails");
 | 
			
		||||
            var q = _yt.Videos.List("contentDetails");
 | 
			
		||||
            q.Id = string.Join(",", videoIdsList.Take(toGet));
 | 
			
		||||
            videoIdsList = videoIdsList.Skip(toGet).ToList();
 | 
			
		||||
            var items = (await q.ExecuteAsync()).Items;
 | 
			
		||||
@@ -334,9 +334,9 @@ public class GoogleApiService : IGoogleApiService, INService
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(query))
 | 
			
		||||
            throw new ArgumentNullException(nameof(query));
 | 
			
		||||
 | 
			
		||||
        var req = cs.Cse.List();
 | 
			
		||||
        var req = _cs.Cse.List();
 | 
			
		||||
        req.Q = query;
 | 
			
		||||
        req.Cx = SearchEngineId;
 | 
			
		||||
        req.Cx = SEARCH_ENGINE_ID;
 | 
			
		||||
        req.Num = 1;
 | 
			
		||||
        req.Fields = "items(image(contextLink,thumbnailLink),link)";
 | 
			
		||||
        req.SearchType = CseResource.ListRequest.SearchTypeEnum.Image;
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ public class RedisCache : IDataCache
 | 
			
		||||
    private readonly string _redisKey;
 | 
			
		||||
    private readonly EndPoint _redisEndpoint;
 | 
			
		||||
 | 
			
		||||
    private readonly object timelyLock = new();
 | 
			
		||||
    private readonly object _timelyLock = new();
 | 
			
		||||
 | 
			
		||||
    public RedisCache(
 | 
			
		||||
        ConnectionMultiplexer redis,
 | 
			
		||||
@@ -36,77 +36,77 @@ public class RedisCache : IDataCache
 | 
			
		||||
    // can re-use the same image/anime data
 | 
			
		||||
    public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(Uri key)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        byte[] x = await _db.StringGetAsync("image_" + key);
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        byte[] x = await db.StringGetAsync("image_" + key);
 | 
			
		||||
        return (x is not null, x);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task SetImageDataAsync(Uri key, byte[] data)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        return _db.StringSetAsync("image_" + key, data);
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        return db.StringSetAsync("image_" + key, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        string x = await _db.StringGetAsync("anime_" + key);
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        string x = await db.StringGetAsync("anime_" + key);
 | 
			
		||||
        return (x is not null, x);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task SetAnimeDataAsync(string key, string data)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        return _db.StringSetAsync("anime_" + key, data, TimeSpan.FromHours(3));
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        return db.StringSetAsync("anime_" + key, data, TimeSpan.FromHours(3));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<(bool Success, string Data)> TryGetNovelDataAsync(string key)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        string x = await _db.StringGetAsync("novel_" + key);
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        string x = await db.StringGetAsync("novel_" + key);
 | 
			
		||||
        return (x is not null, x);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task SetNovelDataAsync(string key, string data)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        return _db.StringSetAsync("novel_" + key, data, TimeSpan.FromHours(3));
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        return db.StringSetAsync("novel_" + key, data, TimeSpan.FromHours(3));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TimeSpan? AddTimelyClaim(ulong id, int period)
 | 
			
		||||
    {
 | 
			
		||||
        if (period == 0)
 | 
			
		||||
            return null;
 | 
			
		||||
        lock (timelyLock)
 | 
			
		||||
        lock (_timelyLock)
 | 
			
		||||
        {
 | 
			
		||||
            var time = TimeSpan.FromHours(period);
 | 
			
		||||
            var _db = Redis.GetDatabase();
 | 
			
		||||
            if ((bool?)_db.StringGet($"{_redisKey}_timelyclaim_{id}") is null)
 | 
			
		||||
            var db = Redis.GetDatabase();
 | 
			
		||||
            if ((bool?)db.StringGet($"{_redisKey}_timelyclaim_{id}") is null)
 | 
			
		||||
            {
 | 
			
		||||
                _db.StringSet($"{_redisKey}_timelyclaim_{id}", true, time);
 | 
			
		||||
                db.StringSet($"{_redisKey}_timelyclaim_{id}", true, time);
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return _db.KeyTimeToLive($"{_redisKey}_timelyclaim_{id}");
 | 
			
		||||
            return db.KeyTimeToLive($"{_redisKey}_timelyclaim_{id}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void RemoveAllTimelyClaims()
 | 
			
		||||
    {
 | 
			
		||||
        var server = Redis.GetServer(_redisEndpoint);
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        foreach (var k in server.Keys(pattern: $"{_redisKey}_timelyclaim_*"))
 | 
			
		||||
            _db.KeyDelete(k, CommandFlags.FireAndForget);
 | 
			
		||||
            db.KeyDelete(k, CommandFlags.FireAndForget);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        time = _db.KeyTimeToLive($"{_redisKey}_affinity_{userId}");
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        time = db.KeyTimeToLive($"{_redisKey}_affinity_{userId}");
 | 
			
		||||
        if (time is null)
 | 
			
		||||
        {
 | 
			
		||||
            time = TimeSpan.FromMinutes(30);
 | 
			
		||||
            _db.StringSet($"{_redisKey}_affinity_{userId}", true, time);
 | 
			
		||||
            db.StringSet($"{_redisKey}_affinity_{userId}", true, time);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -115,12 +115,12 @@ public class RedisCache : IDataCache
 | 
			
		||||
 | 
			
		||||
    public bool TryAddDivorceCooldown(ulong userId, out TimeSpan? time)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        time = _db.KeyTimeToLive($"{_redisKey}_divorce_{userId}");
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        time = db.KeyTimeToLive($"{_redisKey}_divorce_{userId}");
 | 
			
		||||
        if (time is null)
 | 
			
		||||
        {
 | 
			
		||||
            time = TimeSpan.FromHours(6);
 | 
			
		||||
            _db.StringSet($"{_redisKey}_divorce_{userId}", true, time);
 | 
			
		||||
            db.StringSet($"{_redisKey}_divorce_{userId}", true, time);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -129,42 +129,43 @@ public class RedisCache : IDataCache
 | 
			
		||||
 | 
			
		||||
    public Task SetStreamDataAsync(string url, string data)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        return _db.StringSetAsync($"{_redisKey}_stream_{url}", data, TimeSpan.FromHours(6));
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        return db.StringSetAsync($"{_redisKey}_stream_{url}", data, TimeSpan.FromHours(6));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool TryGetStreamData(string url, out string dataStr)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        dataStr = _db.StringGet($"{_redisKey}_stream_{url}");
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        dataStr = db.StringGet($"{_redisKey}_stream_{url}");
 | 
			
		||||
 | 
			
		||||
        return !string.IsNullOrWhiteSpace(dataStr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TimeSpan? TryAddRatelimit(ulong id, string name, int expireIn)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        if (_db.StringSet($"{_redisKey}_ratelimit_{id}_{name}",
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        if (db.StringSet($"{_redisKey}_ratelimit_{id}_{name}",
 | 
			
		||||
                0, // i don't use the value
 | 
			
		||||
                TimeSpan.FromSeconds(expireIn),
 | 
			
		||||
                When.NotExists))
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        return _db.KeyTimeToLive($"{_redisKey}_ratelimit_{id}_{name}");
 | 
			
		||||
        return db.KeyTimeToLive($"{_redisKey}_ratelimit_{id}_{name}");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public bool TryGetEconomy(out string data)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        if ((data = _db.StringGet($"{_redisKey}_economy")) is not null) return true;
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        data = db.StringGet($"{_redisKey}_economy");
 | 
			
		||||
        if (data is not null) return true;
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void SetEconomy(string data)
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        _db.StringSet($"{_redisKey}_economy", data, TimeSpan.FromMinutes(3));
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
        db.StringSet($"{_redisKey}_economy", data, TimeSpan.FromMinutes(3));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<TOut> GetOrAddCachedDataAsync<TParam, TOut>(
 | 
			
		||||
@@ -174,9 +175,9 @@ public class RedisCache : IDataCache
 | 
			
		||||
        TimeSpan expiry)
 | 
			
		||||
        where TOut : class
 | 
			
		||||
    {
 | 
			
		||||
        var _db = Redis.GetDatabase();
 | 
			
		||||
        var db = Redis.GetDatabase();
 | 
			
		||||
 | 
			
		||||
        var data = await _db.StringGetAsync(key);
 | 
			
		||||
        var data = await db.StringGetAsync(key);
 | 
			
		||||
        if (!data.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            var obj = await factory(param);
 | 
			
		||||
@@ -184,7 +185,7 @@ public class RedisCache : IDataCache
 | 
			
		||||
            if (obj is null)
 | 
			
		||||
                return default;
 | 
			
		||||
 | 
			
		||||
            await _db.StringSetAsync(key, JsonConvert.SerializeObject(obj), expiry);
 | 
			
		||||
            await db.StringSetAsync(key, JsonConvert.SerializeObject(obj), expiry);
 | 
			
		||||
 | 
			
		||||
            return obj;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@ namespace NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
public static class RedisImageExtensions
 | 
			
		||||
{
 | 
			
		||||
    private const string OldCdnUrl = "nadeko-pictures.nyc3.digitaloceanspaces.com";
 | 
			
		||||
    private const string NewCdnUrl = "cdn.nadeko.bot";
 | 
			
		||||
    private const string OLD_CDN_URL = "nadeko-pictures.nyc3.digitaloceanspaces.com";
 | 
			
		||||
    private const string NEW_CDN_URL = "cdn.nadeko.bot";
 | 
			
		||||
 | 
			
		||||
    public static Uri ToNewCdn(this Uri uri)
 | 
			
		||||
        => new(uri.ToString().Replace(OldCdnUrl, NewCdnUrl));
 | 
			
		||||
        => new(uri.ToString().Replace(OLD_CDN_URL, NEW_CDN_URL));
 | 
			
		||||
}
 | 
			
		||||
@@ -58,7 +58,7 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
 | 
			
		||||
 | 
			
		||||
        _client.ChannelCreated += c =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            _= Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                if (c is ITextChannel)
 | 
			
		||||
                    Interlocked.Increment(ref textChannels);
 | 
			
		||||
@@ -71,7 +71,7 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
 | 
			
		||||
 | 
			
		||||
        _client.ChannelDestroyed += c =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            _= Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                if (c is ITextChannel)
 | 
			
		||||
                    Interlocked.Decrement(ref textChannels);
 | 
			
		||||
@@ -84,7 +84,7 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
 | 
			
		||||
 | 
			
		||||
        _client.GuildAvailable += g =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            _= Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                var tc = g.Channels.Count(cx => cx is ITextChannel);
 | 
			
		||||
                var vc = g.Channels.Count - tc;
 | 
			
		||||
@@ -96,7 +96,7 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
 | 
			
		||||
 | 
			
		||||
        _client.JoinedGuild += g =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            _= Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                var tc = g.Channels.Count(cx => cx is ITextChannel);
 | 
			
		||||
                var vc = g.Channels.Count - tc;
 | 
			
		||||
@@ -108,7 +108,7 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
 | 
			
		||||
 | 
			
		||||
        _client.GuildUnavailable += g =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            _= Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                var tc = g.Channels.Count(cx => cx is ITextChannel);
 | 
			
		||||
                var vc = g.Channels.Count - tc;
 | 
			
		||||
@@ -121,7 +121,7 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
 | 
			
		||||
 | 
			
		||||
        _client.LeftGuild += g =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() =>
 | 
			
		||||
            _= Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                var tc = g.Channels.Count(cx => cx is ITextChannel);
 | 
			
		||||
                var vc = g.Channels.Count - tc;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,13 +14,13 @@ public class YtdlOperation
 | 
			
		||||
 | 
			
		||||
    private Process CreateProcess(string[] args)
 | 
			
		||||
    {
 | 
			
		||||
        args = args.Map(arg => arg.Replace("\"", ""));
 | 
			
		||||
        var newArgs = args.Map(arg => (object)arg.Replace("\"", ""));
 | 
			
		||||
        return new()
 | 
			
		||||
        {
 | 
			
		||||
            StartInfo = new()
 | 
			
		||||
            {
 | 
			
		||||
                FileName = "youtube-dl",
 | 
			
		||||
                Arguments = string.Format(_baseArgString, args),
 | 
			
		||||
                Arguments = string.Format(_baseArgString, newArgs),
 | 
			
		||||
                UseShellExecute = false,
 | 
			
		||||
                RedirectStandardError = true,
 | 
			
		||||
                RedirectStandardOutput = true,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,12 @@ namespace NadekoBot.Services;
 | 
			
		||||
/// </summary>
 | 
			
		||||
public sealed class BotConfigService : ConfigServiceBase<BotConfig>
 | 
			
		||||
{
 | 
			
		||||
    private const string FilePath = "data/bot.yml";
 | 
			
		||||
    private static readonly TypedKey<BotConfig> changeKey = new("config.bot.updated");
 | 
			
		||||
    private const string FILE_PATH = "data/bot.yml";
 | 
			
		||||
    private static readonly TypedKey<BotConfig> _changeKey = new("config.bot.updated");
 | 
			
		||||
    public override string Name { get; } = "bot";
 | 
			
		||||
 | 
			
		||||
    public BotConfigService(IConfigSeria serializer, IPubSub pubSub)
 | 
			
		||||
        : base(FilePath, serializer, pubSub, changeKey)
 | 
			
		||||
        : base(FILE_PATH, serializer, pubSub, _changeKey)
 | 
			
		||||
    {
 | 
			
		||||
        AddParsedProp("color.ok", bs => bs.Color.Ok, Rgba32.TryParseHex, ConfigPrinters.Color);
 | 
			
		||||
        AddParsedProp("color.error", bs => bs.Color.Error, Rgba32.TryParseHex, ConfigPrinters.Color);
 | 
			
		||||
 
 | 
			
		||||
@@ -112,11 +112,11 @@ public static class Extensions
 | 
			
		||||
        var wrap = new ReactionEventWrapper(client, msg);
 | 
			
		||||
        wrap.OnReactionAdded += r =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() => reactionAdded(r));
 | 
			
		||||
            _= Task.Run(() => reactionAdded(r));
 | 
			
		||||
        };
 | 
			
		||||
        wrap.OnReactionRemoved += r =>
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(() => reactionRemoved(r));
 | 
			
		||||
            _= Task.Run(() => reactionRemoved(r));
 | 
			
		||||
        };
 | 
			
		||||
        return wrap;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user