Initial split of the modules
This commit is contained in:
20
NadekoBot.Core/Common/AsyncLazy.cs
Normal file
20
NadekoBot.Core/Common/AsyncLazy.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class AsyncLazy<T> : Lazy<Task<T>>
|
||||
{
|
||||
public AsyncLazy(Func<T> valueFactory) :
|
||||
base(() => Task.Factory.StartNew(valueFactory))
|
||||
{ }
|
||||
|
||||
public AsyncLazy(Func<Task<T>> taskFactory) :
|
||||
base(() => Task.Factory.StartNew(taskFactory).Unwrap())
|
||||
{ }
|
||||
|
||||
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
|
||||
}
|
||||
|
||||
}
|
13
NadekoBot.Core/Common/Attributes/Aliases.cs
Normal file
13
NadekoBot.Core/Common/Attributes/Aliases.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Services.Impl;
|
||||
namespace NadekoBot.Common.Attributes
|
||||
{
|
||||
public class Aliases : AliasAttribute
|
||||
{
|
||||
public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Cmd.Split(' ').Skip(1).ToArray())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
14
NadekoBot.Core/Common/Attributes/Description.cs
Normal file
14
NadekoBot.Core/Common/Attributes/Description.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Services.Impl;
|
||||
|
||||
namespace NadekoBot.Common.Attributes
|
||||
{
|
||||
public class Description : SummaryAttribute
|
||||
{
|
||||
public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Desc)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
14
NadekoBot.Core/Common/Attributes/NadekoCommand.cs
Normal file
14
NadekoBot.Core/Common/Attributes/NadekoCommand.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Services.Impl;
|
||||
|
||||
namespace NadekoBot.Common.Attributes
|
||||
{
|
||||
public class NadekoCommand : CommandAttribute
|
||||
{
|
||||
public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Cmd.Split(' ')[0])
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
14
NadekoBot.Core/Common/Attributes/NadekoModuleAttribute.cs
Normal file
14
NadekoBot.Core/Common/Attributes/NadekoModuleAttribute.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using Discord.Commands;
|
||||
|
||||
namespace NadekoBot.Common.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
sealed class NadekoModuleAttribute : GroupAttribute
|
||||
{
|
||||
public NadekoModuleAttribute(string moduleName) : base(moduleName)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
NadekoBot.Core/Common/Attributes/OwnerOnlyAttribute.cs
Normal file
17
NadekoBot.Core/Common/Attributes/OwnerOnlyAttribute.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Common.Attributes
|
||||
{
|
||||
public class OwnerOnlyAttribute : PreconditionAttribute
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
|
||||
{
|
||||
var creds = (IBotCredentials)services.GetService(typeof(IBotCredentials));
|
||||
|
||||
return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
|
||||
}
|
||||
}
|
||||
}
|
24
NadekoBot.Core/Common/Attributes/Usage.cs
Normal file
24
NadekoBot.Core/Common/Attributes/Usage.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Services.Impl;
|
||||
using System.Linq;
|
||||
using Discord;
|
||||
|
||||
namespace NadekoBot.Common.Attributes
|
||||
{
|
||||
public class Usage : RemarksAttribute
|
||||
{
|
||||
public Usage([CallerMemberName] string memberName="") : base(Usage.GetUsage(memberName))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static string GetUsage(string memberName)
|
||||
{
|
||||
var usage = Localization.LoadCommand(memberName.ToLowerInvariant()).Usage;
|
||||
return string.Join(" or ", usage
|
||||
.Select(x => Format.Code(x)));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
26
NadekoBot.Core/Common/BotConfigEditType.cs
Normal file
26
NadekoBot.Core/Common/BotConfigEditType.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public enum BotConfigEditType
|
||||
{
|
||||
BetflipMultiplier,
|
||||
Betroll100Multiplier,
|
||||
Betroll67Multiplier,
|
||||
Betroll91Multiplier,
|
||||
CurrencyGenerationChance,
|
||||
CurrencyGenerationCooldown,
|
||||
CurrencyName,
|
||||
CurrencyPluralName,
|
||||
CurrencySign,
|
||||
DmHelpString,
|
||||
HelpString,
|
||||
CurrencyDropAmount,
|
||||
CurrencyDropAmountMax,
|
||||
MinimumBetAmount,
|
||||
TriviaCurrencyReward,
|
||||
XpPerMessage,
|
||||
XpMinutesTimeout,
|
||||
|
||||
//ErrorColor, //after i fix the nadekobot.cs static variables
|
||||
//OkColor
|
||||
}
|
||||
}
|
106
NadekoBot.Core/Common/CREmbed.cs
Normal file
106
NadekoBot.Core/Common/CREmbed.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using Discord;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class CREmbed
|
||||
{
|
||||
private static readonly Logger _log;
|
||||
public string PlainText { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public CREmbedFooter Footer { get; set; }
|
||||
public string Thumbnail { get; set; }
|
||||
public string Image { get; set; }
|
||||
public CREmbedField[] Fields { get; set; }
|
||||
public uint Color { get; set; } = 7458112;
|
||||
|
||||
static CREmbed()
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
}
|
||||
|
||||
public bool IsValid =>
|
||||
!string.IsNullOrWhiteSpace(Title) ||
|
||||
!string.IsNullOrWhiteSpace(Description) ||
|
||||
!string.IsNullOrWhiteSpace(Thumbnail) ||
|
||||
!string.IsNullOrWhiteSpace(Image) ||
|
||||
(Footer != null && (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
|
||||
(Fields != null && Fields.Length > 0);
|
||||
|
||||
public EmbedBuilder ToEmbed()
|
||||
{
|
||||
var embed = new EmbedBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Title))
|
||||
embed.WithTitle(Title);
|
||||
if (!string.IsNullOrWhiteSpace(Description))
|
||||
embed.WithDescription(Description);
|
||||
embed.WithColor(new Discord.Color(Color));
|
||||
if (Footer != null)
|
||||
embed.WithFooter(efb =>
|
||||
{
|
||||
efb.WithText(Footer.Text);
|
||||
if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
|
||||
efb.WithIconUrl(Footer.IconUrl);
|
||||
});
|
||||
|
||||
if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
|
||||
embed.WithThumbnailUrl(Thumbnail);
|
||||
if(Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute))
|
||||
embed.WithImageUrl(Image);
|
||||
|
||||
if (Fields != null)
|
||||
foreach (var f in Fields)
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value))
|
||||
embed.AddField(efb => efb.WithName(f.Name).WithValue(f.Value).WithIsInline(f.Inline));
|
||||
}
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
public static bool TryParse(string input, out CREmbed embed)
|
||||
{
|
||||
embed = null;
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var crembed = JsonConvert.DeserializeObject<CREmbed>(input);
|
||||
|
||||
if(crembed.Fields != null && crembed.Fields.Length > 0)
|
||||
foreach (var f in crembed.Fields)
|
||||
{
|
||||
f.Name = f.Name.TrimTo(256);
|
||||
f.Value = f.Value.TrimTo(1024);
|
||||
}
|
||||
if (!crembed.IsValid)
|
||||
return false;
|
||||
|
||||
embed = crembed;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CREmbedField
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public bool Inline { get; set; }
|
||||
}
|
||||
|
||||
public class CREmbedFooter {
|
||||
public string Text { get; set; }
|
||||
public string IconUrl { get; set; }
|
||||
}
|
||||
}
|
773
NadekoBot.Core/Common/Collections/ConcurrentHashSet.cs
Normal file
773
NadekoBot.Core/Common/Collections/ConcurrentHashSet.cs
Normal file
@ -0,0 +1,773 @@
|
||||
// License MIT
|
||||
// Source: https://github.com/i3arnon/ConcurrentHashSet
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace NadekoBot.Common.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a thread-safe hash-based unique collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the items in the collection.</typeparam>
|
||||
/// <remarks>
|
||||
/// All public members of <see cref="ConcurrentHashSet{T}"/> are thread-safe and may be used
|
||||
/// concurrently from multiple threads.
|
||||
/// </remarks>
|
||||
[DebuggerDisplay("Count = {Count}")]
|
||||
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T>
|
||||
{
|
||||
private const int DefaultCapacity = 31;
|
||||
private const int MaxLockNumber = 1024;
|
||||
|
||||
private readonly IEqualityComparer<T> _comparer;
|
||||
private readonly bool _growLockArray;
|
||||
|
||||
private int _budget;
|
||||
private volatile Tables _tables;
|
||||
|
||||
private static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items contained in the <see
|
||||
/// cref="ConcurrentHashSet{T}"/>.
|
||||
/// </summary>
|
||||
/// <value>The number of items contained in the <see
|
||||
/// cref="ConcurrentHashSet{T}"/>.</value>
|
||||
/// <remarks>Count has snapshot semantics and represents the number of items in the <see
|
||||
/// cref="ConcurrentHashSet{T}"/>
|
||||
/// at the moment when Count was accessed.</remarks>
|
||||
public int Count {
|
||||
get {
|
||||
var count = 0;
|
||||
var acquiredLocks = 0;
|
||||
try
|
||||
{
|
||||
AcquireAllLocks(ref acquiredLocks);
|
||||
|
||||
for (var i = 0; i < _tables.CountPerLock.Length; i++)
|
||||
{
|
||||
count += _tables.CountPerLock[i];
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLocks(0, acquiredLocks);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the <see cref="ConcurrentHashSet{T}"/> is empty.
|
||||
/// </summary>
|
||||
/// <value>true if the <see cref="ConcurrentHashSet{T}"/> is empty; otherwise,
|
||||
/// false.</value>
|
||||
public bool IsEmpty {
|
||||
get {
|
||||
var acquiredLocks = 0;
|
||||
try
|
||||
{
|
||||
AcquireAllLocks(ref acquiredLocks);
|
||||
|
||||
for (var i = 0; i < _tables.CountPerLock.Length; i++)
|
||||
{
|
||||
if (_tables.CountPerLock[i] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLocks(0, acquiredLocks);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see
|
||||
/// cref="ConcurrentHashSet{T}"/>
|
||||
/// class that is empty, has the default concurrency level, has the default initial capacity, and
|
||||
/// uses the default comparer for the item type.
|
||||
/// </summary>
|
||||
public ConcurrentHashSet()
|
||||
: this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer<T>.Default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see
|
||||
/// cref="ConcurrentHashSet{T}"/>
|
||||
/// class that is empty, has the specified concurrency level and capacity, and uses the default
|
||||
/// comparer for the item type.
|
||||
/// </summary>
|
||||
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
||||
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
|
||||
/// <param name="capacity">The initial number of elements that the <see
|
||||
/// cref="ConcurrentHashSet{T}"/>
|
||||
/// can contain.</param>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is
|
||||
/// less than 1.</exception>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="capacity"/> is less than
|
||||
/// 0.</exception>
|
||||
public ConcurrentHashSet(int concurrencyLevel, int capacity)
|
||||
: this(concurrencyLevel, capacity, false, EqualityComparer<T>.Default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
||||
/// class that contains elements copied from the specified <see
|
||||
/// cref="T:System.Collections.IEnumerable{T}"/>, has the default concurrency
|
||||
/// level, has the default initial capacity, and uses the default comparer for the item type.
|
||||
/// </summary>
|
||||
/// <param name="collection">The <see
|
||||
/// cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to
|
||||
/// the new
|
||||
/// <see cref="ConcurrentHashSet{T}"/>.</param>
|
||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference.</exception>
|
||||
public ConcurrentHashSet(IEnumerable<T> collection)
|
||||
: this(collection, EqualityComparer<T>.Default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
||||
/// class that is empty, has the specified concurrency level and capacity, and uses the specified
|
||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
|
||||
/// implementation to use when comparing items.</param>
|
||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference.</exception>
|
||||
public ConcurrentHashSet(IEqualityComparer<T> comparer)
|
||||
: this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
||||
/// class that contains elements copied from the specified <see
|
||||
/// cref="T:System.Collections.IEnumerable"/>, has the default concurrency level, has the default
|
||||
/// initial capacity, and uses the specified
|
||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="collection">The <see
|
||||
/// cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to
|
||||
/// the new
|
||||
/// <see cref="ConcurrentHashSet{T}"/>.</param>
|
||||
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
|
||||
/// implementation to use when comparing items.</param>
|
||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference
|
||||
/// (Nothing in Visual Basic). -or-
|
||||
/// <paramref name="comparer"/> is a null reference (Nothing in Visual Basic).
|
||||
/// </exception>
|
||||
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
||||
: this(comparer)
|
||||
{
|
||||
if (collection == null) throw new ArgumentNullException(nameof(collection));
|
||||
|
||||
InitializeFromCollection(collection);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
||||
/// class that contains elements copied from the specified <see cref="T:System.Collections.IEnumerable"/>,
|
||||
/// has the specified concurrency level, has the specified initial capacity, and uses the specified
|
||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
||||
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
|
||||
/// <param name="collection">The <see cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to the new
|
||||
/// <see cref="ConcurrentHashSet{T}"/>.</param>
|
||||
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/> implementation to use
|
||||
/// when comparing items.</param>
|
||||
/// <exception cref="T:System.ArgumentNullException">
|
||||
/// <paramref name="collection"/> is a null reference.
|
||||
/// -or-
|
||||
/// <paramref name="comparer"/> is a null reference.
|
||||
/// </exception>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||
/// <paramref name="concurrencyLevel"/> is less than 1.
|
||||
/// </exception>
|
||||
public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
||||
: this(concurrencyLevel, DefaultCapacity, false, comparer)
|
||||
{
|
||||
if (collection == null) throw new ArgumentNullException(nameof(collection));
|
||||
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
|
||||
|
||||
InitializeFromCollection(collection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
||||
/// class that is empty, has the specified concurrency level, has the specified initial capacity, and
|
||||
/// uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
||||
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
|
||||
/// <param name="capacity">The initial number of elements that the <see
|
||||
/// cref="ConcurrentHashSet{T}"/>
|
||||
/// can contain.</param>
|
||||
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
|
||||
/// implementation to use when comparing items.</param>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||
/// <paramref name="concurrencyLevel"/> is less than 1. -or-
|
||||
/// <paramref name="capacity"/> is less than 0.
|
||||
/// </exception>
|
||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference.</exception>
|
||||
public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer<T> comparer)
|
||||
: this(concurrencyLevel, capacity, false, comparer)
|
||||
{
|
||||
}
|
||||
|
||||
private ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<T> comparer)
|
||||
{
|
||||
if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel));
|
||||
if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
|
||||
|
||||
// The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
|
||||
// any buckets.
|
||||
if (capacity < concurrencyLevel)
|
||||
{
|
||||
capacity = concurrencyLevel;
|
||||
}
|
||||
|
||||
var locks = new object[concurrencyLevel];
|
||||
for (var i = 0; i < locks.Length; i++)
|
||||
{
|
||||
locks[i] = new object();
|
||||
}
|
||||
|
||||
var countPerLock = new int[locks.Length];
|
||||
var buckets = new Node[capacity];
|
||||
_tables = new Tables(buckets, locks, countPerLock);
|
||||
|
||||
_growLockArray = growLockArray;
|
||||
_budget = buckets.Length / locks.Length;
|
||||
_comparer = comparer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified item to the <see cref="ConcurrentHashSet{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <returns>true if the items was added to the <see cref="ConcurrentHashSet{T}"/>
|
||||
/// successfully; false if it already exists.</returns>
|
||||
/// <exception cref="T:System.OverflowException">The <see cref="ConcurrentHashSet{T}"/>
|
||||
/// contains too many items.</exception>
|
||||
public bool Add(T item) =>
|
||||
AddInternal(item, _comparer.GetHashCode(item), true);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the <see cref="ConcurrentHashSet{T}"/>.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
var locksAcquired = 0;
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLocks(0, locksAcquired);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <see cref="ConcurrentHashSet{T}"/> contains the specified
|
||||
/// item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to locate in the <see cref="ConcurrentHashSet{T}"/>.</param>
|
||||
/// <returns>true if the <see cref="ConcurrentHashSet{T}"/> contains the item; otherwise, false.</returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
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 bucketNo = GetBucket(hashcode, tables.Buckets.Length);
|
||||
|
||||
// We can get away w/out a lock here.
|
||||
// The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i].
|
||||
var current = Volatile.Read(ref tables.Buckets[bucketNo]);
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove the item from the <see cref="ConcurrentHashSet{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <returns>true if an item was removed successfully; otherwise, false.</returns>
|
||||
public bool TryRemove(T item)
|
||||
{
|
||||
var hashcode = _comparer.GetHashCode(item);
|
||||
while (true)
|
||||
{
|
||||
var tables = _tables;
|
||||
|
||||
int bucketNo, lockNo;
|
||||
GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.Buckets.Length, tables.Locks.Length);
|
||||
|
||||
lock (tables.Locks[lockNo])
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
Node previous = null;
|
||||
for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next)
|
||||
{
|
||||
Debug.Assert((previous == null && current == tables.Buckets[bucketNo]) || previous.Next == current);
|
||||
|
||||
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
Volatile.Write(ref tables.Buckets[bucketNo], current.Next);
|
||||
}
|
||||
else
|
||||
{
|
||||
previous.Next = current.Next;
|
||||
}
|
||||
|
||||
tables.CountPerLock[lockNo]--;
|
||||
return true;
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>Returns an enumerator that iterates through the <see
|
||||
/// cref="ConcurrentHashSet{T}"/>.</summary>
|
||||
/// <returns>An enumerator for the <see cref="ConcurrentHashSet{T}"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The enumerator returned from the collection is safe to use concurrently with
|
||||
/// reads and writes to the collection, however it does not represent a moment-in-time snapshot
|
||||
/// of the collection. The contents exposed through the enumerator may contain modifications
|
||||
/// made to the collection after <see cref="GetEnumerator"/> was called.
|
||||
/// </remarks>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
var buckets = _tables.Buckets;
|
||||
|
||||
for (var i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
// The Volatile.Read ensures that the load of the fields of 'current' doesn't move before the load from buckets[i].
|
||||
var current = Volatile.Read(ref buckets[i]);
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
yield return current.Item;
|
||||
current = current.Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item) => Add(item);
|
||||
|
||||
bool ICollection<T>.IsReadOnly => false;
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null) throw new ArgumentNullException(nameof(array));
|
||||
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
|
||||
var locksAcquired = 0;
|
||||
try
|
||||
{
|
||||
AcquireAllLocks(ref locksAcquired);
|
||||
|
||||
var count = 0;
|
||||
|
||||
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("The index is equal to or greater than the length of the array, or the number of elements in the set is greater than the available space from index to the end of the destination array.");
|
||||
}
|
||||
|
||||
CopyToItems(array, arrayIndex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLocks(0, locksAcquired);
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item) => TryRemove(item);
|
||||
|
||||
private void InitializeFromCollection(IEnumerable<T> collection)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
AddInternal(item, _comparer.GetHashCode(item), false);
|
||||
}
|
||||
|
||||
if (_budget == 0)
|
||||
{
|
||||
_budget = _tables.Buckets.Length / _tables.Locks.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddInternal(T item, int hashcode, bool acquireLock)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int bucketNo, lockNo;
|
||||
|
||||
var tables = _tables;
|
||||
GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.Buckets.Length, tables.Locks.Length);
|
||||
|
||||
var resizeDesired = false;
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
if (acquireLock)
|
||||
Monitor.Enter(tables.Locks[lockNo], ref lockTaken);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Try to find this item in the bucket
|
||||
Node previous = null;
|
||||
for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next)
|
||||
{
|
||||
Debug.Assert((previous == null && current == tables.Buckets[bucketNo]) || previous.Next == current);
|
||||
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
|
||||
// The item was not found in the bucket. Insert the new item.
|
||||
Volatile.Write(ref tables.Buckets[bucketNo], new Node(item, hashcode, tables.Buckets[bucketNo]));
|
||||
checked
|
||||
{
|
||||
tables.CountPerLock[lockNo]++;
|
||||
}
|
||||
|
||||
//
|
||||
// If the number of elements guarded by this lock has exceeded the budget, resize the bucket table.
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
Monitor.Exit(tables.Locks[lockNo]);
|
||||
}
|
||||
|
||||
//
|
||||
// The fact that we got here means that we just performed an insertion. If necessary, we will grow the table.
|
||||
//
|
||||
// Concurrency notes:
|
||||
// - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks.
|
||||
// - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0
|
||||
// and then verify that the table we passed to it as the argument is still the current table.
|
||||
//
|
||||
if (resizeDesired)
|
||||
{
|
||||
GrowTable(tables);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetBucket(int hashcode, int bucketCount)
|
||||
{
|
||||
var bucketNo = (hashcode & 0x7fffffff) % bucketCount;
|
||||
Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount);
|
||||
return bucketNo;
|
||||
}
|
||||
|
||||
private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount)
|
||||
{
|
||||
bucketNo = (hashcode & 0x7fffffff) % bucketCount;
|
||||
lockNo = bucketNo % lockCount;
|
||||
|
||||
Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount);
|
||||
Debug.Assert(lockNo >= 0 && lockNo < lockCount);
|
||||
}
|
||||
|
||||
private void GrowTable(Tables tables)
|
||||
{
|
||||
const int maxArrayLength = 0X7FEFFFFF;
|
||||
var locksAcquired = 0;
|
||||
try
|
||||
{
|
||||
// The thread that first obtains _locks[0] will be the one doing the resize operation
|
||||
AcquireLocks(0, 1, ref locksAcquired);
|
||||
|
||||
// Make sure nobody resized the table while we were waiting for lock 0:
|
||||
if (tables != _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.
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow.
|
||||
long approxCount = 0;
|
||||
for (var i = 0; i < tables.CountPerLock.Length; i++)
|
||||
{
|
||||
approxCount += tables.CountPerLock[i];
|
||||
}
|
||||
|
||||
//
|
||||
// If the bucket array is too empty, double the budget instead of resizing the table
|
||||
//
|
||||
if (approxCount < tables.Buckets.Length / 4)
|
||||
{
|
||||
_budget = 2 * _budget;
|
||||
if (_budget < 0)
|
||||
{
|
||||
_budget = int.MaxValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by
|
||||
// 2,3,5 or 7. We can consider a different table-sizing policy in the future.
|
||||
var newLength = 0;
|
||||
var maximizeTableSize = false;
|
||||
try
|
||||
{
|
||||
checked
|
||||
{
|
||||
// Double the size of the buckets table and add one, so that we have an odd integer.
|
||||
newLength = tables.Buckets.Length * 2 + 1;
|
||||
|
||||
// Now, we only need to check odd integers, and find the first that is not divisible
|
||||
// by 3, 5 or 7.
|
||||
while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0)
|
||||
{
|
||||
newLength += 2;
|
||||
}
|
||||
|
||||
Debug.Assert(newLength % 2 != 0);
|
||||
|
||||
if (newLength > maxArrayLength)
|
||||
{
|
||||
maximizeTableSize = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
maximizeTableSize = true;
|
||||
}
|
||||
|
||||
if (maximizeTableSize)
|
||||
{
|
||||
newLength = maxArrayLength;
|
||||
|
||||
// We want to make sure that GrowTable will not be called again, since table is at the maximum size.
|
||||
// To achieve that, we set the budget to int.MaxValue.
|
||||
//
|
||||
// (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;
|
||||
}
|
||||
|
||||
// Now acquire all other locks for the table
|
||||
AcquireLocks(1, tables.Locks.Length, ref locksAcquired);
|
||||
|
||||
var newLocks = tables.Locks;
|
||||
|
||||
// Add more locks
|
||||
if (_growLockArray && tables.Locks.Length < MaxLockNumber)
|
||||
{
|
||||
newLocks = new object[tables.Locks.Length * 2];
|
||||
Array.Copy(tables.Locks, 0, newLocks, 0, tables.Locks.Length);
|
||||
for (var i = tables.Locks.Length; i < newLocks.Length; i++)
|
||||
{
|
||||
newLocks[i] = new object();
|
||||
}
|
||||
}
|
||||
|
||||
var newBuckets = new Node[newLength];
|
||||
var newCountPerLock = new int[newLocks.Length];
|
||||
|
||||
// Copy all data into a new table, creating new nodes for all elements
|
||||
for (var i = 0; i < tables.Buckets.Length; i++)
|
||||
{
|
||||
var current = tables.Buckets[i];
|
||||
while (current != null)
|
||||
{
|
||||
var next = current.Next;
|
||||
int newBucketNo, newLockNo;
|
||||
GetBucketAndLockNo(current.Hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length);
|
||||
|
||||
newBuckets[newBucketNo] = new Node(current.Item, current.Hashcode, newBuckets[newBucketNo]);
|
||||
|
||||
checked
|
||||
{
|
||||
newCountPerLock[newLockNo]++;
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the budget
|
||||
_budget = Math.Max(1, newBuckets.Length / newLocks.Length);
|
||||
|
||||
// Replace tables with the new versions
|
||||
_tables = new Tables(newBuckets, newLocks, newCountPerLock);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Release all locks that we took earlier
|
||||
ReleaseLocks(0, locksAcquired);
|
||||
}
|
||||
}
|
||||
|
||||
public int RemoveWhere(Func<T, bool> predicate)
|
||||
{
|
||||
var elems = this.Where(predicate);
|
||||
var removed = 0;
|
||||
foreach (var elem in elems)
|
||||
{
|
||||
if (this.TryRemove(elem))
|
||||
removed++;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
private void AcquireAllLocks(ref int locksAcquired)
|
||||
{
|
||||
// First, acquire lock 0
|
||||
AcquireLocks(0, 1, ref locksAcquired);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired)
|
||||
{
|
||||
Debug.Assert(fromInclusive <= toExclusive);
|
||||
var locks = _tables.Locks;
|
||||
|
||||
for (var i = fromInclusive; i < toExclusive; i++)
|
||||
{
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
Monitor.Enter(locks[i], ref lockTaken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
{
|
||||
locksAcquired++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseLocks(int fromInclusive, int toExclusive)
|
||||
{
|
||||
Debug.Assert(fromInclusive <= toExclusive);
|
||||
|
||||
for (var i = fromInclusive; i < toExclusive; i++)
|
||||
{
|
||||
Monitor.Exit(_tables.Locks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyToItems(T[] array, int index)
|
||||
{
|
||||
var buckets = _tables.Buckets;
|
||||
for (var i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
for (var current = buckets[i]; current != null; current = current.Next)
|
||||
{
|
||||
array[index] = current.Item;
|
||||
index++; //this should never flow, CopyToItems is only called when there's no overflow risk
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Tables
|
||||
{
|
||||
public readonly Node[] Buckets;
|
||||
public readonly object[] Locks;
|
||||
|
||||
public volatile int[] CountPerLock;
|
||||
|
||||
public Tables(Node[] buckets, object[] locks, int[] countPerLock)
|
||||
{
|
||||
Buckets = buckets;
|
||||
Locks = locks;
|
||||
CountPerLock = countPerLock;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Node
|
||||
{
|
||||
public readonly T Item;
|
||||
public readonly int Hashcode;
|
||||
|
||||
public volatile Node Next;
|
||||
|
||||
public Node(T item, int hashcode, Node next)
|
||||
{
|
||||
Item = item;
|
||||
Hashcode = hashcode;
|
||||
Next = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
NadekoBot.Core/Common/Collections/DisposableImutableList.cs
Normal file
77
NadekoBot.Core/Common/Collections/DisposableImutableList.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Common.Collections
|
||||
{
|
||||
public static class DisposableReadOnlyListExtensions
|
||||
{
|
||||
public static IDisposableReadOnlyList<T> AsDisposable<T>(this IReadOnlyList<T> arr) where T : IDisposable
|
||||
=> new DisposableReadOnlyList<T>(arr);
|
||||
|
||||
public static IDisposableReadOnlyList<KeyValuePair<TKey, TValue>> AsDisposable<TKey, TValue>(this IReadOnlyList<KeyValuePair<TKey, TValue>> arr) where TValue : IDisposable
|
||||
=> new DisposableReadOnlyList<TKey, TValue>(arr);
|
||||
}
|
||||
|
||||
public interface IDisposableReadOnlyList<T> : IReadOnlyList<T>, IDisposable
|
||||
{
|
||||
}
|
||||
|
||||
public class DisposableReadOnlyList<T> : IDisposableReadOnlyList<T>
|
||||
where T : IDisposable
|
||||
{
|
||||
private readonly IReadOnlyList<T> _arr;
|
||||
|
||||
public int Count => _arr.Count;
|
||||
|
||||
public T this[int index] => _arr[index];
|
||||
|
||||
public DisposableReadOnlyList(IReadOnlyList<T> arr)
|
||||
{
|
||||
this._arr = arr;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
=> _arr.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> _arr.GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var item in _arr)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DisposableReadOnlyList<T, U> : IDisposableReadOnlyList<KeyValuePair<T, U>>
|
||||
where U : IDisposable
|
||||
{
|
||||
private readonly IReadOnlyList<KeyValuePair<T, U>> _arr;
|
||||
|
||||
public int Count => _arr.Count;
|
||||
|
||||
KeyValuePair<T, U> IReadOnlyList<KeyValuePair<T, U>>.this[int index] => _arr[index];
|
||||
|
||||
public DisposableReadOnlyList(IReadOnlyList<KeyValuePair<T, U>> arr)
|
||||
{
|
||||
this._arr = arr;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<T, U>> GetEnumerator() =>
|
||||
_arr.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
_arr.GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var item in _arr)
|
||||
{
|
||||
item.Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
128
NadekoBot.Core/Common/Collections/IndexedCollection.cs
Normal file
128
NadekoBot.Core/Common/Collections/IndexedCollection.cs
Normal file
@ -0,0 +1,128 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Common.Collections
|
||||
{
|
||||
public class IndexedCollection<T> : IList<T> where T : class, IIndexed
|
||||
{
|
||||
public List<T> Source { get; }
|
||||
private readonly object _locker = new object();
|
||||
|
||||
public IndexedCollection(IEnumerable<T> source)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
Source = source.OrderBy(x => x.Index).ToList();
|
||||
for (var i = 0; i < Source.Count; i++)
|
||||
{
|
||||
if (Source[i].Index != i)
|
||||
Source[i].Index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator List<T>(IndexedCollection<T> x) =>
|
||||
x.Source;
|
||||
|
||||
public IEnumerator<T> GetEnumerator() =>
|
||||
Source.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
Source.GetEnumerator();
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
item.Index = Source.Count;
|
||||
Source.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
Source.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
return Source.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
Source.CopyTo(array, arrayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Remove(T item)
|
||||
{
|
||||
bool removed;
|
||||
lock (_locker)
|
||||
{
|
||||
if (removed = Source.Remove(item))
|
||||
{
|
||||
for (int i = 0; i < Source.Count; i++)
|
||||
{
|
||||
// hm, no idea how ef works, so I don't want to set if it's not changed,
|
||||
// maybe it will try to update db?
|
||||
// But most likely it just compares old to new values, meh.
|
||||
if (Source[i].Index != i)
|
||||
Source[i].Index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
public int Count => Source.Count;
|
||||
public bool IsReadOnly => false;
|
||||
public int IndexOf(T item) => item.Index;
|
||||
|
||||
public virtual void Insert(int index, T item)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
Source.Insert(index, item);
|
||||
for (int i = index; i < Source.Count; i++)
|
||||
{
|
||||
Source[i].Index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void RemoveAt(int index)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
Source.RemoveAt(index);
|
||||
for (int i = index; i < Source.Count; i++)
|
||||
{
|
||||
Source[i].Index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual T this[int index] {
|
||||
get { return Source[index]; }
|
||||
set {
|
||||
lock (_locker)
|
||||
{
|
||||
value.Index = index;
|
||||
Source[index] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
106
NadekoBot.Core/Common/Collections/PoopyRingBuffer.cs
Normal file
106
NadekoBot.Core/Common/Collections/PoopyRingBuffer.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace NadekoBot.Common.Collections
|
||||
{
|
||||
public class PoopyRingBuffer : IDisposable
|
||||
{
|
||||
// readpos == writepos means empty
|
||||
// writepos == readpos - 1 means full
|
||||
|
||||
private byte[] _buffer;
|
||||
public int Capacity { get; }
|
||||
|
||||
private int ReadPos { get; set; } = 0;
|
||||
private int WritePos { get; set; } = 0;
|
||||
|
||||
public int Length => ReadPos <= WritePos
|
||||
? WritePos - ReadPos
|
||||
: Capacity - (ReadPos - WritePos);
|
||||
|
||||
public int RemainingCapacity
|
||||
{
|
||||
get => Capacity - Length - 1;
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||
|
||||
public PoopyRingBuffer(int capacity = 81920 * 100)
|
||||
{
|
||||
this.Capacity = capacity + 1;
|
||||
this._buffer = new byte[this.Capacity];
|
||||
}
|
||||
|
||||
public int Read(byte[] b, int offset, int toRead)
|
||||
{
|
||||
if (WritePos == ReadPos)
|
||||
return 0;
|
||||
|
||||
if (toRead > Length)
|
||||
toRead = Length;
|
||||
|
||||
if (WritePos > ReadPos)
|
||||
{
|
||||
Array.Copy(_buffer, ReadPos, b, offset, toRead);
|
||||
ReadPos += toRead;
|
||||
}
|
||||
else
|
||||
{
|
||||
var toEnd = Capacity - ReadPos;
|
||||
var firstRead = toRead > toEnd ?
|
||||
toEnd :
|
||||
toRead;
|
||||
Array.Copy(_buffer, ReadPos, b, offset, firstRead);
|
||||
ReadPos += firstRead;
|
||||
var secondRead = toRead - firstRead;
|
||||
if (secondRead > 0)
|
||||
{
|
||||
Array.Copy(_buffer, 0, b, offset + firstRead, secondRead);
|
||||
ReadPos = secondRead;
|
||||
}
|
||||
}
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public bool Write(byte[] b, int offset, int toWrite)
|
||||
{
|
||||
while (toWrite > RemainingCapacity)
|
||||
return false;
|
||||
|
||||
if (toWrite == 0)
|
||||
return true;
|
||||
|
||||
if (WritePos < ReadPos)
|
||||
{
|
||||
Array.Copy(b, offset, _buffer, WritePos, toWrite);
|
||||
WritePos += toWrite;
|
||||
}
|
||||
else
|
||||
{
|
||||
var toEnd = Capacity - WritePos;
|
||||
var firstWrite = toWrite > toEnd ?
|
||||
toEnd :
|
||||
toWrite;
|
||||
Array.Copy(b, offset, _buffer, WritePos, firstWrite);
|
||||
var secondWrite = toWrite - firstWrite;
|
||||
if (secondWrite > 0)
|
||||
{
|
||||
Array.Copy(b, offset + firstWrite, _buffer, 0, secondWrite);
|
||||
WritePos = secondWrite;
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePos += firstWrite;
|
||||
if (WritePos == Capacity)
|
||||
WritePos = 0;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_buffer = null;
|
||||
}
|
||||
}
|
||||
}
|
9
NadekoBot.Core/Common/CommandData.cs
Normal file
9
NadekoBot.Core/Common/CommandData.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class CommandData
|
||||
{
|
||||
public string Cmd { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public string[] Usage { get; set; }
|
||||
}
|
||||
}
|
13
NadekoBot.Core/Common/ModuleBehaviors/IEarlyBlocker.cs
Normal file
13
NadekoBot.Core/Common/ModuleBehaviors/IEarlyBlocker.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
namespace NadekoBot.Common.ModuleBehaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// Implemented by modules which block execution before anything is executed
|
||||
/// </summary>
|
||||
public interface IEarlyBlocker
|
||||
{
|
||||
Task<bool> TryBlockEarly(IGuild guild, IUserMessage msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace NadekoBot.Common.ModuleBehaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// Implemented by modules which can execute something and prevent further commands from being executed.
|
||||
/// </summary>
|
||||
public interface IEarlyBlockingExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to execute some logic within some module's service.
|
||||
/// </summary>
|
||||
/// <returns>Whether it should block other command executions after it.</returns>
|
||||
Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg);
|
||||
}
|
||||
}
|
7
NadekoBot.Core/Common/ModuleBehaviors/IEarlyExecutor.cs
Normal file
7
NadekoBot.Core/Common/ModuleBehaviors/IEarlyExecutor.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace NadekoBot.Common.ModuleBehaviors
|
||||
{
|
||||
public interface IEarlyExecutor
|
||||
{
|
||||
|
||||
}
|
||||
}
|
10
NadekoBot.Core/Common/ModuleBehaviors/IINputTransformer.cs
Normal file
10
NadekoBot.Core/Common/ModuleBehaviors/IINputTransformer.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
namespace NadekoBot.Common.ModuleBehaviors
|
||||
{
|
||||
public interface IInputTransformer
|
||||
{
|
||||
Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input);
|
||||
}
|
||||
}
|
12
NadekoBot.Core/Common/ModuleBehaviors/ILateBlocker.cs
Normal file
12
NadekoBot.Core/Common/ModuleBehaviors/ILateBlocker.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace NadekoBot.Common.ModuleBehaviors
|
||||
{
|
||||
public interface ILateBlocker
|
||||
{
|
||||
Task<bool> TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild,
|
||||
IMessageChannel channel, IUser user, string moduleName, string commandName);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace NadekoBot.Common.ModuleBehaviors
|
||||
{
|
||||
public interface ILateBlockingExecutor
|
||||
{
|
||||
|
||||
}
|
||||
}
|
14
NadekoBot.Core/Common/ModuleBehaviors/ILateExecutor.cs
Normal file
14
NadekoBot.Core/Common/ModuleBehaviors/ILateExecutor.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace NadekoBot.Common.ModuleBehaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// Last thing to be executed, won't stop further executions
|
||||
/// </summary>
|
||||
public interface ILateExecutor
|
||||
{
|
||||
Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg);
|
||||
}
|
||||
}
|
154
NadekoBot.Core/Common/NadekoModule.cs
Normal file
154
NadekoBot.Core/Common/NadekoModule.cs
Normal file
@ -0,0 +1,154 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NLog;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Services.Impl;
|
||||
|
||||
namespace NadekoBot.Modules
|
||||
{
|
||||
public abstract class NadekoTopLevelModule : ModuleBase
|
||||
{
|
||||
protected readonly Logger _log;
|
||||
protected CultureInfo _cultureInfo;
|
||||
|
||||
public readonly string ModuleTypeName;
|
||||
public readonly string LowerModuleTypeName;
|
||||
|
||||
public NadekoStrings _strings { get; set; }
|
||||
public CommandHandler _cmdHandler { get; set; }
|
||||
public ILocalization _localization { get; set; }
|
||||
|
||||
public string Prefix => _cmdHandler.GetPrefix(Context.Guild);
|
||||
|
||||
protected NadekoTopLevelModule(bool isTopLevelModule = true)
|
||||
{
|
||||
//if it's top level module
|
||||
ModuleTypeName = isTopLevelModule ? this.GetType().Name : this.GetType().DeclaringType.Name;
|
||||
LowerModuleTypeName = ModuleTypeName.ToLowerInvariant();
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
}
|
||||
|
||||
protected override void BeforeExecute(CommandInfo cmd)
|
||||
{
|
||||
_cultureInfo = _localization.GetCultureInfo(Context.Guild?.Id);
|
||||
}
|
||||
|
||||
//public Task<IUserMessage> ReplyConfirmLocalized(string titleKey, string textKey, string url = null, string footer = null)
|
||||
//{
|
||||
// var title = NadekoBot.ResponsesResourceManager.GetString(titleKey, cultureInfo);
|
||||
// var text = NadekoBot.ResponsesResourceManager.GetString(textKey, cultureInfo);
|
||||
// return Context.Channel.SendConfirmAsync(title, text, url, footer);
|
||||
//}
|
||||
|
||||
//public Task<IUserMessage> ReplyConfirmLocalized(string textKey)
|
||||
//{
|
||||
// var text = NadekoBot.ResponsesResourceManager.GetString(textKey, cultureInfo);
|
||||
// return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + textKey);
|
||||
//}
|
||||
|
||||
//public Task<IUserMessage> ReplyErrorLocalized(string titleKey, string textKey, string url = null, string footer = null)
|
||||
//{
|
||||
// var title = NadekoBot.ResponsesResourceManager.GetString(titleKey, cultureInfo);
|
||||
// var text = NadekoBot.ResponsesResourceManager.GetString(textKey, cultureInfo);
|
||||
// return Context.Channel.SendErrorAsync(title, text, url, footer);
|
||||
//}
|
||||
|
||||
protected string GetText(string key) =>
|
||||
_strings.GetText(key, _cultureInfo, LowerModuleTypeName);
|
||||
|
||||
protected string GetText(string key, params object[] replacements) =>
|
||||
_strings.GetText(key, _cultureInfo, LowerModuleTypeName, replacements);
|
||||
|
||||
public Task<IUserMessage> ErrorLocalized(string textKey, params object[] replacements)
|
||||
{
|
||||
var text = GetText(textKey, replacements);
|
||||
return Context.Channel.SendErrorAsync(text);
|
||||
}
|
||||
|
||||
public Task<IUserMessage> ReplyErrorLocalized(string textKey, params object[] replacements)
|
||||
{
|
||||
var text = GetText(textKey, replacements);
|
||||
return Context.Channel.SendErrorAsync(Context.User.Mention + " " + text);
|
||||
}
|
||||
|
||||
public Task<IUserMessage> ConfirmLocalized(string textKey, params object[] replacements)
|
||||
{
|
||||
var text = GetText(textKey, replacements);
|
||||
return Context.Channel.SendConfirmAsync(text);
|
||||
}
|
||||
|
||||
public Task<IUserMessage> ReplyConfirmLocalized(string textKey, params object[] replacements)
|
||||
{
|
||||
var text = GetText(textKey, replacements);
|
||||
return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + text);
|
||||
}
|
||||
|
||||
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
|
||||
public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
|
||||
{
|
||||
var userInputTask = new TaskCompletionSource<string>();
|
||||
var dsc = (DiscordSocketClient)Context.Client;
|
||||
try
|
||||
{
|
||||
dsc.MessageReceived += MessageReceived;
|
||||
|
||||
if ((await Task.WhenAny(userInputTask.Task, Task.Delay(10000))) != userInputTask.Task)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await userInputTask.Task;
|
||||
}
|
||||
finally
|
||||
{
|
||||
dsc.MessageReceived -= MessageReceived;
|
||||
}
|
||||
|
||||
Task MessageReceived(SocketMessage arg)
|
||||
{
|
||||
var _ = Task.Run(() =>
|
||||
{
|
||||
if (!(arg is SocketUserMessage userMsg) ||
|
||||
!(userMsg.Channel is ITextChannel chan) ||
|
||||
userMsg.Author.Id != userId ||
|
||||
userMsg.Channel.Id != channelId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (userInputTask.TrySetResult(arg.Content))
|
||||
{
|
||||
userMsg.DeleteAfter(1);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class NadekoTopLevelModule<TService> : NadekoTopLevelModule where TService : INService
|
||||
{
|
||||
public TService _service { get; set; }
|
||||
|
||||
public NadekoTopLevelModule(bool isTopLevel = true) : base(isTopLevel)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class NadekoSubmodule : NadekoTopLevelModule
|
||||
{
|
||||
protected NadekoSubmodule() : base(false) { }
|
||||
}
|
||||
|
||||
public abstract class NadekoSubmodule<TService> : NadekoTopLevelModule<TService> where TService : INService
|
||||
{
|
||||
protected NadekoSubmodule() : base(false)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
7
NadekoBot.Core/Common/NadekoModuleExtensions.cs
Normal file
7
NadekoBot.Core/Common/NadekoModuleExtensions.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace NadekoBot.Modules
|
||||
{
|
||||
public static class NadekoModuleExtensions
|
||||
{
|
||||
|
||||
}
|
||||
}
|
62
NadekoBot.Core/Common/NadekoRandom.cs
Normal file
62
NadekoBot.Core/Common/NadekoRandom.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class NadekoRandom : Random
|
||||
{
|
||||
readonly RandomNumberGenerator _rng;
|
||||
|
||||
public NadekoRandom() : base()
|
||||
{
|
||||
_rng = RandomNumberGenerator.Create();
|
||||
}
|
||||
|
||||
public override int Next()
|
||||
{
|
||||
var bytes = new byte[sizeof(int)];
|
||||
_rng.GetBytes(bytes);
|
||||
return Math.Abs(BitConverter.ToInt32(bytes, 0));
|
||||
}
|
||||
|
||||
public override int Next(int maxValue)
|
||||
{
|
||||
if (maxValue <= 0)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
var bytes = new byte[sizeof(int)];
|
||||
_rng.GetBytes(bytes);
|
||||
return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
|
||||
}
|
||||
|
||||
public override int Next(int minValue, int maxValue)
|
||||
{
|
||||
if (minValue > maxValue)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
if (minValue == maxValue)
|
||||
return minValue;
|
||||
var bytes = new byte[sizeof(int)];
|
||||
_rng.GetBytes(bytes);
|
||||
var sign = Math.Sign(BitConverter.ToInt32(bytes, 0));
|
||||
return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue;
|
||||
}
|
||||
|
||||
public override void NextBytes(byte[] buffer)
|
||||
{
|
||||
_rng.GetBytes(buffer);
|
||||
}
|
||||
|
||||
protected override double Sample()
|
||||
{
|
||||
var bytes = new byte[sizeof(double)];
|
||||
_rng.GetBytes(bytes);
|
||||
return Math.Abs(BitConverter.ToDouble(bytes, 0) / double.MaxValue + 1);
|
||||
}
|
||||
|
||||
public override double NextDouble()
|
||||
{
|
||||
var bytes = new byte[sizeof(double)];
|
||||
_rng.GetBytes(bytes);
|
||||
return BitConverter.ToDouble(bytes, 0);
|
||||
}
|
||||
}
|
||||
}
|
18
NadekoBot.Core/Common/NoPublicBotPrecondition.cs
Normal file
18
NadekoBot.Core/Common/NoPublicBotPrecondition.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class NoPublicBot : PreconditionAttribute
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
#if GLOBAL_NADEKo
|
||||
return Task.FromResult(PreconditionResult.FromError("Not available on the public bot"));
|
||||
#else
|
||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
25
NadekoBot.Core/Common/PlatformHelper.cs
Normal file
25
NadekoBot.Core/Common/PlatformHelper.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public static class PlatformHelper
|
||||
{
|
||||
private const int ProcessorCountRefreshIntervalMs = 30000;
|
||||
|
||||
private static volatile int _processorCount;
|
||||
private static volatile int _lastProcessorCountRefreshTicks;
|
||||
|
||||
public static int ProcessorCount {
|
||||
get {
|
||||
var now = Environment.TickCount;
|
||||
if (_processorCount == 0 || (now - _lastProcessorCountRefreshTicks) >= ProcessorCountRefreshIntervalMs)
|
||||
{
|
||||
_processorCount = Environment.ProcessorCount;
|
||||
_lastProcessorCountRefreshTicks = now;
|
||||
}
|
||||
|
||||
return _processorCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
145
NadekoBot.Core/Common/Replacements/ReplacementBuilder.cs
Normal file
145
NadekoBot.Core/Common/Replacements/ReplacementBuilder.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
|
||||
namespace NadekoBot.Common.Replacements
|
||||
{
|
||||
public class ReplacementBuilder
|
||||
{
|
||||
private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
|
||||
private ConcurrentDictionary<string, Func<string>> _reps = new ConcurrentDictionary<string, Func<string>>();
|
||||
private ConcurrentDictionary<Regex, Func<Match, string>> _regex = new ConcurrentDictionary<Regex, Func<Match, string>>();
|
||||
|
||||
public ReplacementBuilder()
|
||||
{
|
||||
WithRngRegex();
|
||||
}
|
||||
|
||||
public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, IGuild g, DiscordSocketClient client)
|
||||
{
|
||||
return this.WithUser(usr)
|
||||
.WithChannel(ch)
|
||||
.WithServer(client, g)
|
||||
.WithClient(client);
|
||||
}
|
||||
|
||||
public ReplacementBuilder WithDefault(ICommandContext ctx) =>
|
||||
WithDefault(ctx.User, ctx.Channel, ctx.Guild, (DiscordSocketClient)ctx.Client);
|
||||
|
||||
public ReplacementBuilder WithClient(DiscordSocketClient client)
|
||||
{
|
||||
_reps.TryAdd("%mention%", () => $"<@{client.CurrentUser.Id}>");
|
||||
_reps.TryAdd("%shardid%", () => client.ShardId.ToString());
|
||||
_reps.TryAdd("%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReplacementBuilder WithServer(DiscordSocketClient client, IGuild g)
|
||||
{
|
||||
|
||||
_reps.TryAdd("%sid%", () => g == null ? "DM" : g.Id.ToString());
|
||||
_reps.TryAdd("%server%", () => g == null ? "DM" : g.Name);
|
||||
//_reps.TryAdd("%server_time%", () =>
|
||||
//{
|
||||
// TimeZoneInfo to = TimeZoneInfo.Local;
|
||||
// if (g != null)
|
||||
// {
|
||||
// if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
|
||||
// to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
||||
// }
|
||||
|
||||
// return TimeZoneInfo.ConvertTime(DateTime.UtcNow,
|
||||
// TimeZoneInfo.Utc,
|
||||
// to).ToString("HH:mm ") + to.StandardName.GetInitials();
|
||||
//});
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReplacementBuilder WithChannel(IMessageChannel ch)
|
||||
{
|
||||
_reps.TryAdd("%channel%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
|
||||
_reps.TryAdd("%chname%", () => ch.Name);
|
||||
_reps.TryAdd("%cid%", () => ch?.Id.ToString());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReplacementBuilder WithUser(IUser user)
|
||||
{
|
||||
_reps.TryAdd("%user%", () => user.Mention);
|
||||
_reps.TryAdd("%userfull%", () => user.ToString());
|
||||
_reps.TryAdd("%username%", () => user.Username);
|
||||
_reps.TryAdd("%userdiscrim%", () => user.Discriminator);
|
||||
_reps.TryAdd("%id%", () => user.Id.ToString());
|
||||
_reps.TryAdd("%uid%", () => user.Id.ToString());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReplacementBuilder WithStats(DiscordSocketClient c)
|
||||
{
|
||||
_reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
|
||||
_reps.TryAdd("%users%", () => c.Guilds.Sum(s => s.Users.Count).ToString());
|
||||
return this;
|
||||
}
|
||||
|
||||
//public ReplacementBuilder WithMusic(MusicService ms)
|
||||
//{
|
||||
// _reps.TryAdd("%playing%", () =>
|
||||
// {
|
||||
// var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.Current.Current != null);
|
||||
// if (cnt != 1) return cnt.ToString();
|
||||
// try
|
||||
// {
|
||||
// var mp = ms.MusicPlayers.FirstOrDefault();
|
||||
// var title = mp.Value?.Current.Current?.Title;
|
||||
// return title ?? "No songs";
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// return "error";
|
||||
// }
|
||||
// });
|
||||
// _reps.TryAdd("%queued%", () => ms.MusicPlayers.Sum(kvp => kvp.Value.QueueArray().Songs.Length).ToString());
|
||||
// return this;
|
||||
//}
|
||||
|
||||
public ReplacementBuilder WithRngRegex()
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
_regex.TryAdd(rngRegex, (match) =>
|
||||
{
|
||||
int from = 0;
|
||||
int.TryParse(match.Groups["from"].ToString(), out from);
|
||||
|
||||
int to = 0;
|
||||
int.TryParse(match.Groups["to"].ToString(), out to);
|
||||
|
||||
if (from == 0 && to == 0)
|
||||
{
|
||||
return rng.Next(0, 11).ToString();
|
||||
}
|
||||
|
||||
if (from >= to)
|
||||
return string.Empty;
|
||||
|
||||
return rng.Next(from, to + 1).ToString();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReplacementBuilder WithOverride(string key, Func<string> output)
|
||||
{
|
||||
_reps.AddOrUpdate(key, output, delegate { return output; });
|
||||
return this;
|
||||
}
|
||||
|
||||
public Replacer Build()
|
||||
{
|
||||
return new Replacer(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
54
NadekoBot.Core/Common/Replacements/Replacer.cs
Normal file
54
NadekoBot.Core/Common/Replacements/Replacer.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NadekoBot.Common.Replacements
|
||||
{
|
||||
public class Replacer
|
||||
{
|
||||
private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
|
||||
private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
|
||||
|
||||
public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
|
||||
{
|
||||
_replacements = replacements;
|
||||
_regex = regex;
|
||||
}
|
||||
|
||||
public string Replace(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return input;
|
||||
|
||||
foreach (var item in _replacements)
|
||||
{
|
||||
if (input.Contains(item.Key))
|
||||
input = input.Replace(item.Key, item.Text());
|
||||
}
|
||||
|
||||
foreach (var item in _regex)
|
||||
{
|
||||
input = item.Regex.Replace(input, (m) => item.Replacement(m));
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
public void Replace(CREmbed embedData)
|
||||
{
|
||||
embedData.PlainText = Replace(embedData.PlainText);
|
||||
embedData.Description = Replace(embedData.Description);
|
||||
embedData.Title = Replace(embedData.Title);
|
||||
|
||||
if (embedData.Fields != null)
|
||||
foreach (var f in embedData.Fields)
|
||||
{
|
||||
f.Name = Replace(f.Name);
|
||||
f.Value = Replace(f.Value);
|
||||
}
|
||||
|
||||
if (embedData.Footer != null)
|
||||
embedData.Footer.Text = Replace(embedData.Footer.Text);
|
||||
}
|
||||
}
|
||||
}
|
19
NadekoBot.Core/Common/Shard0Precondition.cs
Normal file
19
NadekoBot.Core/Common/Shard0Precondition.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class Shard0Precondition : PreconditionAttribute
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
var c = (DiscordSocketClient)context.Client;
|
||||
if (c.ShardId != 0)
|
||||
return Task.FromResult(PreconditionResult.FromError("Must be ran from shard #0"));
|
||||
|
||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||
}
|
||||
}
|
||||
}
|
13
NadekoBot.Core/Common/ShardCom/IShardComMessage.cs
Normal file
13
NadekoBot.Core/Common/ShardCom/IShardComMessage.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using Discord;
|
||||
|
||||
namespace NadekoBot.Common.ShardCom
|
||||
{
|
||||
public class ShardComMessage
|
||||
{
|
||||
public int ShardId { get; set; }
|
||||
public ConnectionState ConnectionState { get; set; }
|
||||
public int Guilds { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
}
|
||||
}
|
28
NadekoBot.Core/Common/ShardCom/ShardComClient.cs
Normal file
28
NadekoBot.Core/Common/ShardCom/ShardComClient.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Common.ShardCom
|
||||
{
|
||||
public class ShardComClient
|
||||
{
|
||||
private int port;
|
||||
|
||||
public ShardComClient(int port)
|
||||
{
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public async Task Send(ShardComMessage data)
|
||||
{
|
||||
var msg = JsonConvert.SerializeObject(data);
|
||||
using (var client = new UdpClient())
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(msg);
|
||||
await client.SendAsync(bytes, bytes.Length, IPAddress.Loopback.ToString(), port).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
NadekoBot.Core/Common/ShardCom/ShardComServer.cs
Normal file
40
NadekoBot.Core/Common/ShardCom/ShardComServer.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Common.ShardCom
|
||||
{
|
||||
public class ShardComServer : IDisposable
|
||||
{
|
||||
private readonly UdpClient _client;
|
||||
|
||||
public ShardComServer(int port)
|
||||
{
|
||||
_client = new UdpClient(port);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var ip = new IPEndPoint(IPAddress.Any, 0);
|
||||
while (true)
|
||||
{
|
||||
var recv = await _client.ReceiveAsync();
|
||||
var data = Encoding.UTF8.GetString(recv.Buffer);
|
||||
var _ = OnDataReceived(JsonConvert.DeserializeObject<ShardComMessage>(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client.Dispose();
|
||||
}
|
||||
|
||||
public event Func<ShardComMessage, Task> OnDataReceived = delegate { return Task.CompletedTask; };
|
||||
}
|
||||
}
|
91
NadekoBot.Core/Common/SocketMessageEventWrapper.cs
Normal file
91
NadekoBot.Core/Common/SocketMessageEventWrapper.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Common
|
||||
{
|
||||
public class ReactionEventWrapper : IDisposable
|
||||
{
|
||||
public IUserMessage Message { get; }
|
||||
public event Action<SocketReaction> OnReactionAdded = delegate { };
|
||||
public event Action<SocketReaction> OnReactionRemoved = delegate { };
|
||||
public event Action OnReactionsCleared = delegate { };
|
||||
|
||||
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
|
||||
{
|
||||
Message = msg ?? throw new ArgumentNullException(nameof(msg));
|
||||
_client = client;
|
||||
|
||||
_client.ReactionAdded += Discord_ReactionAdded;
|
||||
_client.ReactionRemoved += Discord_ReactionRemoved;
|
||||
_client.ReactionsCleared += Discord_ReactionsCleared;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionsCleared?.Invoke();
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionRemoved?.Invoke(reaction);
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionAdded?.Invoke(reaction);
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void UnsubAll()
|
||||
{
|
||||
_client.ReactionAdded -= Discord_ReactionAdded;
|
||||
_client.ReactionRemoved -= Discord_ReactionRemoved;
|
||||
_client.ReactionsCleared -= Discord_ReactionsCleared;
|
||||
OnReactionAdded = null;
|
||||
OnReactionRemoved = null;
|
||||
OnReactionsCleared = null;
|
||||
}
|
||||
|
||||
private bool disposing = false;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
return;
|
||||
disposing = true;
|
||||
UnsubAll();
|
||||
}
|
||||
}
|
||||
}
|
9
NadekoBot.Core/Common/TypeReaders/AddRemove.cs
Normal file
9
NadekoBot.Core/Common/TypeReaders/AddRemove.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace NadekoBot.Common.TypeReaders
|
||||
{
|
||||
public enum AddRemove
|
||||
{
|
||||
Add = 0,
|
||||
Rem = 1,
|
||||
Rm = 1,
|
||||
}
|
||||
}
|
73
NadekoBot.Core/Common/TypeReaders/BotCommandTypeReader.cs
Normal file
73
NadekoBot.Core/Common/TypeReaders/BotCommandTypeReader.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Common.TypeReaders
|
||||
{
|
||||
public class CommandTypeReader : TypeReader
|
||||
{
|
||||
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||
{
|
||||
var _cmds = ((INServiceProvider)services).GetService<CommandService>();
|
||||
var _cmdHandler = ((INServiceProvider)services).GetService<CommandHandler>();
|
||||
input = input.ToUpperInvariant();
|
||||
var prefix = _cmdHandler.GetPrefix(context.Guild);
|
||||
if (!input.StartsWith(prefix.ToUpperInvariant()))
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
|
||||
|
||||
input = input.Substring(prefix.Length);
|
||||
|
||||
var cmd = _cmds.Commands.FirstOrDefault(c =>
|
||||
c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
|
||||
if (cmd == null)
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
|
||||
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
|
||||
}
|
||||
}
|
||||
//todo dependency on the module
|
||||
//public class CommandOrCrTypeReader : CommandTypeReader
|
||||
//{
|
||||
// public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||
// {
|
||||
// input = input.ToUpperInvariant();
|
||||
|
||||
// var _crs = ((INServiceProvider)services).GetService<CustomReactionsService>();
|
||||
|
||||
// if (_crs.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
|
||||
// {
|
||||
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
|
||||
// }
|
||||
// var guild = context.Guild;
|
||||
// if (guild != null)
|
||||
// {
|
||||
// if (_crs.GuildReactions.TryGetValue(guild.Id, out var crs))
|
||||
// {
|
||||
// if (crs.Concat(_crs.GlobalReactions).Any(x => x.Trigger.ToUpperInvariant() == input))
|
||||
// {
|
||||
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// var cmd = await base.Read(context, input, services);
|
||||
// if (cmd.IsSuccess)
|
||||
// {
|
||||
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name));
|
||||
// }
|
||||
// return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
|
||||
// }
|
||||
//}
|
||||
|
||||
public class CommandOrCrInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public CommandOrCrInfo(string input)
|
||||
{
|
||||
this.Name = input;
|
||||
}
|
||||
}
|
||||
}
|
40
NadekoBot.Core/Common/TypeReaders/GuildDateTimeTypeReader.cs
Normal file
40
NadekoBot.Core/Common/TypeReaders/GuildDateTimeTypeReader.cs
Normal file
@ -0,0 +1,40 @@
|
||||
//using System;
|
||||
//using System.Threading.Tasks;
|
||||
//using Discord.Commands;
|
||||
//using NadekoBot.Modules.Administration.Services;
|
||||
|
||||
//namespace NadekoBot.Common.TypeReaders
|
||||
//{
|
||||
// public class GuildDateTimeTypeReader : TypeReader
|
||||
// {
|
||||
// public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||
// {
|
||||
// var _gts = (GuildTimezoneService)services.GetService(typeof(GuildTimezoneService));
|
||||
// if (!DateTime.TryParse(input, out var dt))
|
||||
// return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
|
||||
|
||||
// var tz = _gts.GetTimeZoneOrUtc(context.Guild.Id);
|
||||
|
||||
// return Task.FromResult(TypeReaderResult.FromSuccess(new GuildDateTime(tz, dt)));
|
||||
// }
|
||||
// }
|
||||
|
||||
// public class GuildDateTime
|
||||
// {
|
||||
// public TimeZoneInfo Timezone { get; }
|
||||
// public DateTime CurrentGuildTime { get; }
|
||||
// public DateTime InputTime { get; }
|
||||
// public DateTime InputTimeUtc { get; }
|
||||
|
||||
// private GuildDateTime() { }
|
||||
|
||||
// public GuildDateTime(TimeZoneInfo guildTimezone, DateTime inputTime)
|
||||
// {
|
||||
// var now = DateTime.UtcNow;
|
||||
// Timezone = guildTimezone;
|
||||
// CurrentGuildTime = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.Utc, Timezone);
|
||||
// InputTime = inputTime;
|
||||
// InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
|
||||
// }
|
||||
// }
|
||||
//}
|
30
NadekoBot.Core/Common/TypeReaders/GuildTypeReader.cs
Normal file
30
NadekoBot.Core/Common/TypeReaders/GuildTypeReader.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace NadekoBot.Common.TypeReaders
|
||||
{
|
||||
public class GuildTypeReader : TypeReader
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public GuildTypeReader(DiscordSocketClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
|
||||
{
|
||||
input = input.Trim().ToLowerInvariant();
|
||||
var guilds = _client.Guilds;
|
||||
var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToLowerInvariant() == input) ?? //by id
|
||||
guilds.FirstOrDefault(g => g.Name.Trim().ToLowerInvariant() == input); //by name
|
||||
|
||||
if (guild != null)
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(guild));
|
||||
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No guild by that name or Id found"));
|
||||
}
|
||||
}
|
||||
}
|
27
NadekoBot.Core/Common/TypeReaders/Models/PermissionAction.cs
Normal file
27
NadekoBot.Core/Common/TypeReaders/Models/PermissionAction.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace NadekoBot.Common.TypeReaders.Models
|
||||
{
|
||||
public class PermissionAction
|
||||
{
|
||||
public static PermissionAction Enable => new PermissionAction(true);
|
||||
public static PermissionAction Disable => new PermissionAction(false);
|
||||
|
||||
public bool Value { get; }
|
||||
|
||||
public PermissionAction(bool value)
|
||||
{
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.Value == ((PermissionAction)obj).Value;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
}
|
||||
}
|
56
NadekoBot.Core/Common/TypeReaders/ModuleTypeReader.cs
Normal file
56
NadekoBot.Core/Common/TypeReaders/ModuleTypeReader.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
|
||||
namespace NadekoBot.Common.TypeReaders
|
||||
{
|
||||
public class ModuleTypeReader : TypeReader
|
||||
{
|
||||
private readonly CommandService _cmds;
|
||||
|
||||
public ModuleTypeReader(CommandService cmds)
|
||||
{
|
||||
_cmds = cmds;
|
||||
}
|
||||
|
||||
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
|
||||
{
|
||||
input = input.ToUpperInvariant();
|
||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
|
||||
if (module == null)
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
||||
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(module));
|
||||
}
|
||||
}
|
||||
|
||||
public class ModuleOrCrTypeReader : TypeReader
|
||||
{
|
||||
private readonly CommandService _cmds;
|
||||
|
||||
public ModuleOrCrTypeReader(CommandService cmds)
|
||||
{
|
||||
_cmds = cmds;
|
||||
}
|
||||
|
||||
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
|
||||
{
|
||||
input = input.ToLowerInvariant();
|
||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToLowerInvariant() == input)?.Key;
|
||||
if (module == null && input != "actualcustomreactions")
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
||||
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo
|
||||
{
|
||||
Name = input,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public class ModuleOrCrInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
|
||||
namespace NadekoBot.Common.TypeReaders
|
||||
{
|
||||
/// <summary>
|
||||
/// Used instead of bool for more flexible keywords for true/false only in the permission module
|
||||
/// </summary>
|
||||
public class PermissionActionTypeReader : TypeReader
|
||||
{
|
||||
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
|
||||
{
|
||||
input = input.ToUpperInvariant();
|
||||
switch (input)
|
||||
{
|
||||
case "1":
|
||||
case "T":
|
||||
case "TRUE":
|
||||
case "ENABLE":
|
||||
case "ENABLED":
|
||||
case "ALLOW":
|
||||
case "PERMIT":
|
||||
case "UNBAN":
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Enable));
|
||||
case "0":
|
||||
case "F":
|
||||
case "FALSE":
|
||||
case "DENY":
|
||||
case "DISABLE":
|
||||
case "DISABLED":
|
||||
case "DISALLOW":
|
||||
case "BAN":
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Disable));
|
||||
default:
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Did not receive a valid boolean value"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user