Typereaders finished, cleanup
This commit is contained in:
		| @@ -3,26 +3,26 @@ using System.Net.Sockets; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using Newtonsoft.Json; | ||||
| using NadekoBot.Services; | ||||
| using StackExchange.Redis; | ||||
|  | ||||
| namespace NadekoBot.Common.ShardCom | ||||
| { | ||||
|     public class ShardComClient | ||||
|     { | ||||
|         private int port; | ||||
|         private readonly IDataCache _cache; | ||||
|  | ||||
|         public ShardComClient(int port) | ||||
|         public ShardComClient(IDataCache cache) | ||||
|         { | ||||
|             this.port = port; | ||||
|             _cache = cache; | ||||
|         } | ||||
|  | ||||
|         public async Task Send(ShardComMessage data) | ||||
|         { | ||||
|             var sub = _cache.Redis.GetSubscriber(); | ||||
|             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); | ||||
|             } | ||||
|  | ||||
|             await sub.PublishAsync("shardcoord_send", msg).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,35 +4,26 @@ using System.Net.Sockets; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using Newtonsoft.Json; | ||||
| using NadekoBot.Services; | ||||
|  | ||||
| namespace NadekoBot.Common.ShardCom | ||||
| { | ||||
|     public class ShardComServer : IDisposable | ||||
|     public class ShardComServer | ||||
|     { | ||||
|         private readonly UdpClient _client; | ||||
|         private readonly IDataCache _cache; | ||||
|  | ||||
|         public ShardComServer(int port) | ||||
|         public ShardComServer(IDataCache cache) | ||||
|         { | ||||
|             _client = new UdpClient(port); | ||||
|             _cache = cache; | ||||
|         } | ||||
|  | ||||
|         public void Start() | ||||
|         { | ||||
|             Task.Run(async () => | ||||
|             var sub = _cache.Redis.GetSubscriber(); | ||||
|             sub.SubscribeAsync("shardcoord_send", (ch, data) => | ||||
|             { | ||||
|                 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(); | ||||
|             }, StackExchange.Redis.CommandFlags.FireAndForget); | ||||
|         } | ||||
|  | ||||
|         public event Func<ShardComMessage, Task> OnDataReceived = delegate { return Task.CompletedTask; }; | ||||
|   | ||||
| @@ -9,7 +9,7 @@ using Discord.WebSocket; | ||||
|  | ||||
| namespace NadekoBot.Common.TypeReaders | ||||
| { | ||||
|     public class CommandTypeReader : NadekoTypeReader | ||||
|     public class CommandTypeReader : NadekoTypeReader<CommandInfo> | ||||
|     { | ||||
|         public CommandTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds) | ||||
|         { | ||||
| @@ -35,10 +35,14 @@ namespace NadekoBot.Common.TypeReaders | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class CommandOrCrTypeReader : CommandTypeReader | ||||
|     public class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo> | ||||
|     { | ||||
|         private readonly DiscordSocketClient _client; | ||||
|         private readonly CommandService _cmds; | ||||
|         public CommandOrCrTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds) | ||||
|         { | ||||
|             _client = client; | ||||
|             _cmds = cmds; | ||||
|         } | ||||
|  | ||||
|         public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | ||||
| @@ -63,7 +67,7 @@ namespace NadekoBot.Common.TypeReaders | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             var cmd = await base.Read(context, input, services); | ||||
|             var cmd = await new CommandTypeReader(_client, _cmds).Read(context, input, services); | ||||
|             if (cmd.IsSuccess) | ||||
|             { | ||||
|                 return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name)); | ||||
|   | ||||
| @@ -7,7 +7,7 @@ using Discord.WebSocket; | ||||
|  | ||||
| namespace NadekoBot.Common.TypeReaders | ||||
| { | ||||
|     public class GuildDateTimeTypeReader : NadekoTypeReader | ||||
|     public class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime> | ||||
|     { | ||||
|         public GuildDateTimeTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds) | ||||
|         { | ||||
|   | ||||
| @@ -4,10 +4,11 @@ using System.Threading.Tasks; | ||||
| using Discord.Commands; | ||||
| using Discord.WebSocket; | ||||
| using NadekoBot.Core.Common.TypeReaders; | ||||
| using Discord; | ||||
|  | ||||
| namespace NadekoBot.Common.TypeReaders | ||||
| { | ||||
|     public class GuildTypeReader : NadekoTypeReader | ||||
|     public class GuildTypeReader : NadekoTypeReader<IGuild> | ||||
|     { | ||||
|         private readonly DiscordSocketClient _client; | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ using Discord.WebSocket; | ||||
|  | ||||
| namespace NadekoBot.Common.TypeReaders | ||||
| { | ||||
|     public class ModuleTypeReader : NadekoTypeReader | ||||
|     public class ModuleTypeReader : NadekoTypeReader<ModuleInfo> | ||||
|     { | ||||
|         private readonly CommandService _cmds; | ||||
|  | ||||
| @@ -28,7 +28,7 @@ namespace NadekoBot.Common.TypeReaders | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class ModuleOrCrTypeReader : NadekoTypeReader | ||||
|     public class ModuleOrCrTypeReader : NadekoTypeReader<ModuleOrCrInfo> | ||||
|     { | ||||
|         private readonly CommandService _cmds; | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,8 @@ using Discord.WebSocket; | ||||
|  | ||||
| namespace NadekoBot.Core.Common.TypeReaders | ||||
| { | ||||
|     public abstract class NadekoTypeReader : TypeReader | ||||
|     public abstract class NadekoTypeReader<T> : TypeReader where | ||||
|         T : class | ||||
|     { | ||||
|         private readonly DiscordSocketClient _client; | ||||
|         private readonly CommandService _cmds; | ||||
|   | ||||
| @@ -10,7 +10,7 @@ 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 : NadekoTypeReader | ||||
|     public class PermissionActionTypeReader : NadekoTypeReader<PermissionAction> | ||||
|     { | ||||
|         public PermissionActionTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds) | ||||
|         { | ||||
|   | ||||
| @@ -41,7 +41,7 @@ namespace NadekoBot.Modules.Administration.Services | ||||
|                 _rep = new ReplacementBuilder() | ||||
|                     .WithClient(client) | ||||
|                     .WithStats(client) | ||||
|                     //todo type readers | ||||
|                     //todo how to add music to replacement builder? | ||||
|                     //.WithMusic(music) | ||||
|                     .Build(); | ||||
|  | ||||
|   | ||||
| @@ -5,19 +5,20 @@ namespace NadekoBot.Services.Impl | ||||
| { | ||||
|     public class RedisCache : IDataCache | ||||
|     { | ||||
|         private ulong _botid; | ||||
|  | ||||
|         public ConnectionMultiplexer Redis { get; } | ||||
|         private readonly IDatabase _db; | ||||
|  | ||||
|         public RedisCache(ulong botId) | ||||
|         public RedisCache() | ||||
|         { | ||||
|             _botid = botId; | ||||
|             Redis = ConnectionMultiplexer.Connect("127.0.0.1"); | ||||
|             Redis.PreserveAsyncOrder = false; | ||||
|             _db = Redis.GetDatabase(); | ||||
|         } | ||||
|  | ||||
|         // things here so far don't need the bot id | ||||
|         // because it's a good thing if different bots  | ||||
|         // which are hosted on the same PC | ||||
|         // can re-use the same image/anime data | ||||
|         public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key) | ||||
|         { | ||||
|             byte[] x = await _db.StringGetAsync("image_" + key); | ||||
|   | ||||
| @@ -17,14 +17,11 @@ using NadekoBot.Extensions; | ||||
| using System.Collections.Generic; | ||||
| using NadekoBot.Common; | ||||
| using NadekoBot.Common.ShardCom; | ||||
| using NadekoBot.Common.TypeReaders; | ||||
| using NadekoBot.Common.TypeReaders.Models; | ||||
| using NadekoBot.Services.Database; | ||||
| using StackExchange.Redis; | ||||
| using Newtonsoft.Json; | ||||
| using System.Runtime.Loader; | ||||
| using NadekoBot.Core.Common.TypeReaders; | ||||
|  | ||||
| //todo Finish the script which autobuilds all projects if they're changed | ||||
| namespace NadekoBot | ||||
| { | ||||
|     public class NadekoBot | ||||
| @@ -57,8 +54,9 @@ namespace NadekoBot | ||||
|         private readonly ShardComClient _comClient; | ||||
|  | ||||
|         private readonly BotConfig _botConfig; | ||||
|         public IDataCache Cache { get; private set; } | ||||
|  | ||||
|         public NadekoBot(int shardId, int parentProcessId, int? port = null) | ||||
|         public NadekoBot(int shardId, int parentProcessId) | ||||
|         { | ||||
|             if (shardId < 0) | ||||
|                 throw new ArgumentOutOfRangeException(nameof(shardId)); | ||||
| @@ -67,6 +65,7 @@ namespace NadekoBot | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|             TerribleElevatedPermissionCheck(); | ||||
|  | ||||
|             Cache = new RedisCache(); | ||||
|             Credentials = new BotCredentials(); | ||||
|             _db = new DbService(Credentials); | ||||
|             Client = new DiscordSocketClient(new DiscordSocketConfig | ||||
| @@ -84,8 +83,7 @@ namespace NadekoBot | ||||
|                 DefaultRunMode = RunMode.Sync, | ||||
|             }); | ||||
|              | ||||
|             port = port ?? Credentials.ShardRunPort; | ||||
|             _comClient = new ShardComClient(port.Value); | ||||
|             _comClient = new ShardComClient(Cache); | ||||
|  | ||||
|             using (var uow = _db.UnitOfWork) | ||||
|             { | ||||
| @@ -94,7 +92,7 @@ namespace NadekoBot | ||||
|                 ErrorColor = new Color(Convert.ToUInt32(_botConfig.ErrorColor, 16)); | ||||
|             } | ||||
|  | ||||
|             SetupShard(parentProcessId, port.Value); | ||||
|             SetupShard(parentProcessId); | ||||
|  | ||||
| #if GLOBAL_NADEKO | ||||
|             Client.Log += Client_Log; | ||||
| @@ -144,7 +142,7 @@ namespace NadekoBot | ||||
|                     .AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this | ||||
|                     .AddManual<NadekoBot>(this) | ||||
|                     .AddManual<IUnitOfWork>(uow) | ||||
|                     .AddManual<IDataCache>(new RedisCache(Client.CurrentUser.Id)); | ||||
|                     .AddManual<IDataCache>(Cache); | ||||
|  | ||||
|                 Services.LoadFrom(Assembly.GetAssembly(typeof(CommandHandler))); | ||||
|  | ||||
| @@ -156,7 +154,7 @@ namespace NadekoBot | ||||
|             Services.Unload(typeof(IUnitOfWork)); // unload it after the startup | ||||
|         } | ||||
|  | ||||
|         private IEnumerable<NadekoTypeReader> LoadTypeReaders(Assembly assembly) | ||||
|         private IEnumerable<object> LoadTypeReaders(Assembly assembly) | ||||
|         { | ||||
|             Type[] allTypes; | ||||
|             try | ||||
| @@ -166,18 +164,30 @@ namespace NadekoBot | ||||
|             catch (ReflectionTypeLoadException ex) | ||||
|             { | ||||
|                 Console.WriteLine(ex.LoaderExceptions[0]); | ||||
|                 return Enumerable.Empty<NadekoTypeReader>(); | ||||
|                 return Enumerable.Empty<object>(); | ||||
|             } | ||||
|             var filteredTypes = allTypes | ||||
|                 .Where(x => x.IsSubclassOf(typeof(NadekoTypeReader)) | ||||
|                 .Where(x => x.IsSubclassOf(typeof(TypeReader))  | ||||
|                     && x.BaseType.GetGenericArguments().Length > 0 | ||||
|                     && !x.IsAbstract); | ||||
|  | ||||
|             var toReturn = new List<NadekoTypeReader>(); | ||||
|             var toReturn = new List<object>(); | ||||
|             foreach (var ft in filteredTypes) | ||||
|             { | ||||
|                 //:yayyy: | ||||
|                 var x = (NadekoTypeReader)Activator.CreateInstance(ft, Client, CommandService); | ||||
|                 CommandService.AddTypeReader(x.GetType(), x); | ||||
|                 var x = (TypeReader)Activator.CreateInstance(ft, Client, CommandService); | ||||
|                 //@.@ XD XDDD | ||||
|                 var baseType = ft.BaseType; | ||||
|                 var typeArgs = baseType.GetGenericArguments(); | ||||
|                 try | ||||
|                 { | ||||
|                     CommandService.AddTypeReader(typeArgs[0], x); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _log.Error(ex); | ||||
|                     throw; | ||||
|                 } | ||||
|                 toReturn.Add(x); | ||||
|                 _log.Info("Loaded {0} typereader.", x.GetType().Name); | ||||
|             } | ||||
| @@ -317,11 +327,11 @@ namespace NadekoBot | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void SetupShard(int parentProcessId, int port) | ||||
|         private void SetupShard(int parentProcessId) | ||||
|         { | ||||
|             if (Client.ShardId == 0) | ||||
|             { | ||||
|                 ShardCoord = new ShardsCoordinator(port); | ||||
|                 ShardCoord = new ShardsCoordinator(Cache); | ||||
|                 return; | ||||
|             } | ||||
|             new Thread(new ThreadStart(() => | ||||
|   | ||||
| @@ -19,19 +19,28 @@ namespace NadekoBot.Services | ||||
|  | ||||
|         private readonly Logger _log; | ||||
|         private readonly ShardComServer _comServer; | ||||
|         private readonly int _port; | ||||
|         private readonly int _curProcessId; | ||||
|  | ||||
|         public ShardsCoordinator(int port) | ||||
|         public ShardsCoordinator(IDataCache cache) | ||||
|         { | ||||
|             LogSetup.SetupLogger(); | ||||
|             _creds = new BotCredentials(); | ||||
|             _shardProcesses = new Process[_creds.TotalShards]; | ||||
|             Statuses = new ShardComMessage[_creds.TotalShards]; | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|             _port = port; | ||||
|  | ||||
|             _comServer = new ShardComServer(port); | ||||
|             for (int i = 0; i < Statuses.Length; i++) | ||||
|             { | ||||
|                 Statuses[i] = new ShardComMessage(); | ||||
|                 var s = Statuses[i]; | ||||
|                 s.ConnectionState = Discord.ConnectionState.Disconnected; | ||||
|                 s.Guilds = 0; | ||||
|                 s.ShardId = i; | ||||
|                 s.Time = DateTime.Now - TimeSpan.FromMinutes(1); | ||||
|             } | ||||
|  | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|  | ||||
|             _comServer = new ShardComServer(cache); | ||||
|             _comServer.Start(); | ||||
|  | ||||
|             _comServer.OnDataReceived += _comServer_OnDataReceived; | ||||
| @@ -54,8 +63,10 @@ namespace NadekoBot.Services | ||||
|                 var p = Process.Start(new ProcessStartInfo() | ||||
|                 { | ||||
|                     FileName = _creds.ShardRunCommand, | ||||
|                     Arguments = string.Format(_creds.ShardRunArguments, i, _curProcessId, _port) | ||||
|                     Arguments = string.Format(_creds.ShardRunArguments, i, _curProcessId, "") | ||||
|                 }); | ||||
|                 // last "" in format is for backwards compatibility | ||||
|                 // because current startup commands have {2} in them probably | ||||
|                 await Task.Delay(5000); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -21,6 +21,21 @@ namespace NadekoBot.Extensions | ||||
| { | ||||
|     public static class Extensions | ||||
|     { | ||||
|         //so ftw | ||||
|         public static bool IsSubclassOfRawGeneric(this Type toCheck, Type generic) | ||||
|         { | ||||
|             while (toCheck != null && toCheck != typeof(object)) | ||||
|             { | ||||
|                 var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; | ||||
|                 if (generic == cur) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
|                 toCheck = toCheck.BaseType; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public static async Task<string> ReplaceAsync(this Regex regex, string input, Func<Match, Task<string>> replacementFn) | ||||
|         { | ||||
|             var sb = new StringBuilder(); | ||||
|   | ||||
| @@ -0,0 +1,12 @@ | ||||
| using Discord; | ||||
| using Discord.Commands; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace NadekoBot.Modules.Gambling.Common | ||||
| { | ||||
|     public abstract class CurrencyEvent | ||||
|     { | ||||
|         public abstract Task Stop(); | ||||
|         public abstract Task Start(IUserMessage msg, ICommandContext channel); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,144 @@ | ||||
| using Discord; | ||||
| using Discord.Commands; | ||||
| using Discord.WebSocket; | ||||
| using NadekoBot.Common.Collections; | ||||
| using NadekoBot.Extensions; | ||||
| using NadekoBot.Services; | ||||
| using NadekoBot.Services.Database.Models; | ||||
| using NLog; | ||||
| using System; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace NadekoBot.Modules.Gambling.Common | ||||
| { | ||||
|     public class ReactionEvent : CurrencyEvent | ||||
|     { | ||||
|         private readonly ConcurrentHashSet<ulong> _reactionAwardedUsers = new ConcurrentHashSet<ulong>(); | ||||
|         private readonly BotConfig _bc; | ||||
|         private readonly Logger _log; | ||||
|         private readonly DiscordSocketClient _client; | ||||
|         private readonly CurrencyService _cs; | ||||
|         private readonly SocketSelfUser _botUser; | ||||
|  | ||||
|         private IUserMessage StartingMessage { get; set; } | ||||
|  | ||||
|         private CancellationTokenSource Source { get; } | ||||
|         private CancellationToken CancelToken { get; } | ||||
|  | ||||
|         private readonly ConcurrentQueue<ulong> _toGiveTo = new ConcurrentQueue<ulong>(); | ||||
|         private readonly int _amount; | ||||
|  | ||||
|         public ReactionEvent(BotConfig bc, DiscordSocketClient client, CurrencyService cs, int amount) | ||||
|         { | ||||
|             _bc = bc; | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|             _client = client; | ||||
|             _cs = cs; | ||||
|             _botUser = client.CurrentUser; | ||||
|             _amount = amount; | ||||
|             Source = new CancellationTokenSource(); | ||||
|             CancelToken = Source.Token; | ||||
|  | ||||
|             var _ = Task.Run(async () => | ||||
|             { | ||||
|                 var users = new List<ulong>(); | ||||
|                 while (!CancelToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     await Task.Delay(1000).ConfigureAwait(false); | ||||
|                     while (_toGiveTo.TryDequeue(out var usrId)) | ||||
|                     { | ||||
|                         users.Add(usrId); | ||||
|                     } | ||||
|  | ||||
|                     if (users.Count > 0) | ||||
|                     { | ||||
|                         await _cs.AddToManyAsync("Reaction Event", _amount, users.ToArray()).ConfigureAwait(false); | ||||
|                     } | ||||
|  | ||||
|                     users.Clear(); | ||||
|                 } | ||||
|             }, CancelToken); | ||||
|         } | ||||
|  | ||||
|         public override async Task Stop() | ||||
|         { | ||||
|             if (StartingMessage != null) | ||||
|                 await StartingMessage.DeleteAsync().ConfigureAwait(false); | ||||
|  | ||||
|             if (!Source.IsCancellationRequested) | ||||
|                 Source.Cancel(); | ||||
|  | ||||
|             _client.MessageDeleted -= MessageDeletedEventHandler; | ||||
|         } | ||||
|  | ||||
|         private Task MessageDeletedEventHandler(Cacheable<IMessage, ulong> msg, ISocketMessageChannel channel) | ||||
|         { | ||||
|             if (StartingMessage?.Id == msg.Id) | ||||
|             { | ||||
|                 _log.Warn("Stopping flower reaction event because message is deleted."); | ||||
|                 var __ = Task.Run(Stop); | ||||
|             } | ||||
|  | ||||
|             return Task.CompletedTask; | ||||
|         } | ||||
|  | ||||
|         public override async Task Start(IUserMessage umsg, ICommandContext context) | ||||
|         { | ||||
|             StartingMessage = umsg; | ||||
|             _client.MessageDeleted += MessageDeletedEventHandler; | ||||
|  | ||||
|             IEmote iemote; | ||||
|             if (Emote.TryParse(_bc.CurrencySign, out var emote)) | ||||
|             { | ||||
|                 iemote = emote; | ||||
|             } | ||||
|             else | ||||
|                 iemote = new Emoji(_bc.CurrencySign); | ||||
|             try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); } | ||||
|             catch | ||||
|             { | ||||
|                 try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); } | ||||
|                 catch | ||||
|                 { | ||||
|                     try { await StartingMessage.DeleteAsync().ConfigureAwait(false); } | ||||
|                     catch { return; } | ||||
|                 } | ||||
|             } | ||||
|             using (StartingMessage.OnReaction(_client, (r) => | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     if (r.UserId == _botUser.Id) | ||||
|                         return; | ||||
|  | ||||
|                     if (r.Emote.Name == iemote.Name && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _reactionAwardedUsers.Add(r.User.Value.Id)) | ||||
|                     { | ||||
|                         _toGiveTo.Enqueue(r.UserId); | ||||
|                     } | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|                     // ignored | ||||
|                 } | ||||
|             })) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     await Task.Delay(TimeSpan.FromHours(24), CancelToken).ConfigureAwait(false); | ||||
|                 } | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|                 if (CancelToken.IsCancellationRequested) | ||||
|                     return; | ||||
|  | ||||
|                 _log.Warn("Stopping flower reaction event because it expired."); | ||||
|                 await Stop(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,99 @@ | ||||
| using Discord; | ||||
| using Discord.Commands; | ||||
| using Discord.WebSocket; | ||||
| using NadekoBot.Common; | ||||
| using NadekoBot.Common.Collections; | ||||
| using NadekoBot.Services; | ||||
| using System; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace NadekoBot.Modules.Gambling.Common.CurrencyEvents | ||||
| { | ||||
|     public class SneakyEvent : CurrencyEvent | ||||
|     { | ||||
|         public event Action OnEnded; | ||||
|         public string Code { get; private set; } = string.Empty; | ||||
|         private readonly ConcurrentHashSet<ulong> _sneakyGameAwardedUsers = new ConcurrentHashSet<ulong>(); | ||||
|  | ||||
|         private readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10) | ||||
|             .Concat(Enumerable.Range(65, 26)) | ||||
|             .Concat(Enumerable.Range(97, 26)) | ||||
|             .Select(x => (char)x) | ||||
|             .ToArray(); | ||||
|  | ||||
|         private readonly CurrencyService _cs; | ||||
|         private readonly DiscordSocketClient _client; | ||||
|         private readonly IBotConfigProvider _bc; | ||||
|         private readonly int _length; | ||||
|  | ||||
|         public SneakyEvent(CurrencyService cs, DiscordSocketClient client, | ||||
|             IBotConfigProvider bc, int len) | ||||
|         { | ||||
|             _cs = cs; | ||||
|             _client = client; | ||||
|             _bc = bc; | ||||
|             _length = len; | ||||
|         } | ||||
|  | ||||
|         public override async Task Start(IUserMessage msg, ICommandContext channel) | ||||
|         { | ||||
|             GenerateCode(); | ||||
|  | ||||
|             //start the event | ||||
|             _client.MessageReceived += SneakyGameMessageReceivedEventHandler; | ||||
|             await _client.SetGameAsync($"type {Code} for " + _bc.BotConfig.CurrencyPluralName) | ||||
|                 .ConfigureAwait(false); | ||||
|             await Task.Delay(_length * 1000).ConfigureAwait(false); | ||||
|             await Stop().ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         private void GenerateCode() | ||||
|         { | ||||
|             var rng = new NadekoRandom(); | ||||
|  | ||||
|             for (var i = 0; i < 5; i++) | ||||
|             { | ||||
|                 Code += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override async Task Stop() | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 _client.MessageReceived -= SneakyGameMessageReceivedEventHandler; | ||||
|                 Code = string.Empty; | ||||
|                 _sneakyGameAwardedUsers.Clear(); | ||||
|                 await _client.SetGameAsync(null).ConfigureAwait(false); | ||||
|             } | ||||
|             catch { } | ||||
|             finally | ||||
|             { | ||||
|  | ||||
|                 OnEnded?.Invoke(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private Task SneakyGameMessageReceivedEventHandler(SocketMessage arg) | ||||
|         { | ||||
|             if (arg.Content == Code && | ||||
|                 _sneakyGameAwardedUsers.Add(arg.Author.Id)) | ||||
|             { | ||||
|                 var _ = Task.Run(async () => | ||||
|                 { | ||||
|                     await _cs.AddAsync(arg.Author, "Sneaky Game Event", 100, false) | ||||
|                         .ConfigureAwait(false); | ||||
|  | ||||
|                     try { await arg.DeleteAsync(new RequestOptions() { RetryMode = RetryMode.AlwaysFail }).ConfigureAwait(false); } | ||||
|                     catch | ||||
|                     { | ||||
|                         // ignored | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             return Task.CompletedTask; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,42 +2,26 @@ | ||||
| using Discord.Commands; | ||||
| using NadekoBot.Extensions; | ||||
| using NadekoBot.Services; | ||||
| using System; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using Discord.WebSocket; | ||||
| using System.Threading; | ||||
| using NadekoBot.Common; | ||||
| using NadekoBot.Common.Attributes; | ||||
| using NadekoBot.Common.Collections; | ||||
| using NLog; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | ||||
| using NadekoBot.Services.Database.Models; | ||||
| using NadekoBot.Modules.Gambling.Common; | ||||
| using NadekoBot.Modules.Gambling.Services; | ||||
| using NadekoBot.Modules.Gambling.Common.CurrencyEvents; | ||||
|  | ||||
| namespace NadekoBot.Modules.Gambling | ||||
| { | ||||
|     //todo mess, needs unload thing too - refactor | ||||
|     public partial class Gambling | ||||
|     { | ||||
|         [Group] | ||||
|         public class CurrencyEventsCommands : NadekoSubmodule | ||||
|         public class CurrencyEventsCommands : NadekoSubmodule<CurrencyEventsService> | ||||
|         { | ||||
|             public enum CurrencyEvent | ||||
|             { | ||||
|                 Reaction, | ||||
|                 SneakyGameStatus | ||||
|             } | ||||
|             //flower reaction event | ||||
|             private static readonly ConcurrentHashSet<ulong> _sneakyGameAwardedUsers = new ConcurrentHashSet<ulong>(); | ||||
|  | ||||
|             private static readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10) | ||||
|                 .Concat(Enumerable.Range(65, 26)) | ||||
|                 .Concat(Enumerable.Range(97, 26)) | ||||
|                 .Select(x => (char)x) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             private string _secretCode = string.Empty; | ||||
|             private readonly DiscordSocketClient _client; | ||||
|             private readonly IBotConfigProvider _bc; | ||||
|             private readonly CurrencyService _cs; | ||||
| @@ -65,68 +49,27 @@ namespace NadekoBot.Modules.Gambling | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             public async Task SneakyGameStatusEvent(ICommandContext context, int? arg) | ||||
|             private async Task SneakyGameStatusEvent(ICommandContext context, int num) | ||||
|             { | ||||
|                 int num; | ||||
|                 if (arg == null || arg < 5) | ||||
|                 if (num < 10 || num > 600) | ||||
|                     num = 60; | ||||
|                 else | ||||
|                     num = arg.Value; | ||||
|  | ||||
|                 if (_secretCode != string.Empty) | ||||
|                 var ev = new SneakyEvent(_cs, _client, _bc, num); | ||||
|                 if (!await _service.StartSneakyEvent(ev, context.Message, context)) | ||||
|                     return; | ||||
|                 var rng = new NadekoRandom(); | ||||
|  | ||||
|                 for (var i = 0; i < 5; i++) | ||||
|                 { | ||||
|                     _secretCode += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)]; | ||||
|                 } | ||||
|                  | ||||
|                 await _client.SetGameAsync($"type {_secretCode} for " + _bc.BotConfig.CurrencyPluralName) | ||||
|                     .ConfigureAwait(false); | ||||
|                 try | ||||
|                 { | ||||
|                     var title = GetText("sneakygamestatus_title"); | ||||
|                     var desc = GetText("sneakygamestatus_desc", Format.Bold(100.ToString()) + _bc.BotConfig.CurrencySign, Format.Bold(num.ToString())); | ||||
|                     await context.Channel.SendConfirmAsync(title, desc).ConfigureAwait(false); | ||||
|                     var desc = GetText("sneakygamestatus_desc",  | ||||
|                         Format.Bold(100.ToString()) + _bc.BotConfig.CurrencySign, | ||||
|                         Format.Bold(num.ToString())); | ||||
|                     await context.Channel.SendConfirmAsync(title, desc) | ||||
|                         .ConfigureAwait(false); | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|                     // ignored | ||||
|                 } | ||||
|  | ||||
|  | ||||
|                 _client.MessageReceived += SneakyGameMessageReceivedEventHandler; | ||||
|                 await Task.Delay(num * 1000); | ||||
|                 _client.MessageReceived -= SneakyGameMessageReceivedEventHandler; | ||||
|  | ||||
|                 var cnt = _sneakyGameAwardedUsers.Count; | ||||
|                 _sneakyGameAwardedUsers.Clear(); | ||||
|                 _secretCode = string.Empty; | ||||
|  | ||||
|                 await _client.SetGameAsync(GetText("sneakygamestatus_end", cnt)) | ||||
|                     .ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             private Task SneakyGameMessageReceivedEventHandler(SocketMessage arg) | ||||
|             { | ||||
|                 if (arg.Content == _secretCode && | ||||
|                     _sneakyGameAwardedUsers.Add(arg.Author.Id)) | ||||
|                 { | ||||
|                     var _ = Task.Run(async () => | ||||
|                     { | ||||
|                         await _cs.AddAsync(arg.Author, "Sneaky Game Event", 100, false) | ||||
|                             .ConfigureAwait(false); | ||||
|  | ||||
|                         try { await arg.DeleteAsync(new RequestOptions() { RetryMode = RetryMode.AlwaysFail }).ConfigureAwait(false); } | ||||
|                         catch | ||||
|                         { | ||||
|                             // ignored | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 return Task.CompletedTask; | ||||
|             } | ||||
|  | ||||
|             public async Task ReactionEvent(ICommandContext context, int amount) | ||||
| @@ -137,144 +80,11 @@ namespace NadekoBot.Modules.Gambling | ||||
|                 var title = GetText("reaction_title"); | ||||
|                 var desc = GetText("reaction_desc", _bc.BotConfig.CurrencySign, Format.Bold(amount.ToString()) + _bc.BotConfig.CurrencySign); | ||||
|                 var footer = GetText("reaction_footer", 24); | ||||
|                 var re = new ReactionEvent(_bc.BotConfig, _client, _cs, amount); | ||||
|                 var msg = await context.Channel.SendConfirmAsync(title, | ||||
|                         desc, footer: footer) | ||||
|                     .ConfigureAwait(false); | ||||
|  | ||||
|                 await new ReactionEvent(_bc.BotConfig, _client, _cs, amount).Start(msg, context); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public abstract class CurrencyEvent | ||||
|     { | ||||
|         public abstract Task Start(IUserMessage msg, ICommandContext channel); | ||||
|     } | ||||
|  | ||||
|     public class ReactionEvent : CurrencyEvent | ||||
|     { | ||||
|         private readonly ConcurrentHashSet<ulong> _reactionAwardedUsers = new ConcurrentHashSet<ulong>(); | ||||
|         private readonly BotConfig _bc; | ||||
|         private readonly Logger _log; | ||||
|         private readonly DiscordSocketClient _client; | ||||
|         private readonly CurrencyService _cs; | ||||
|         private readonly SocketSelfUser _botUser; | ||||
|  | ||||
|         private IUserMessage StartingMessage { get; set; } | ||||
|  | ||||
|         private CancellationTokenSource Source { get; } | ||||
|         private CancellationToken CancelToken { get; } | ||||
|  | ||||
|         private readonly ConcurrentQueue<ulong> _toGiveTo = new ConcurrentQueue<ulong>(); | ||||
|         private readonly int _amount; | ||||
|  | ||||
|         public ReactionEvent(BotConfig bc, DiscordSocketClient client, CurrencyService cs, int amount) | ||||
|         { | ||||
|             _bc = bc; | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|             _client = client; | ||||
|             _cs = cs; | ||||
|             _botUser = client.CurrentUser; | ||||
|             _amount = amount; | ||||
|             Source = new CancellationTokenSource(); | ||||
|             CancelToken = Source.Token; | ||||
|  | ||||
|             var _ = Task.Run(async () => | ||||
|             { | ||||
|  | ||||
|                 var users = new List<ulong>(); | ||||
|                 while (!CancelToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     await Task.Delay(1000).ConfigureAwait(false); | ||||
|                     while (_toGiveTo.TryDequeue(out var usrId)) | ||||
|                     { | ||||
|                         users.Add(usrId); | ||||
|                     } | ||||
|  | ||||
|                     if (users.Count > 0) | ||||
|                     { | ||||
|                         await _cs.AddToManyAsync("Reaction Event", _amount, users.ToArray()).ConfigureAwait(false); | ||||
|                     } | ||||
|  | ||||
|                     users.Clear(); | ||||
|                 } | ||||
|             }, CancelToken); | ||||
|         } | ||||
|  | ||||
|         private async Task End() | ||||
|         { | ||||
|             if(StartingMessage != null) | ||||
|                 await StartingMessage.DeleteAsync().ConfigureAwait(false); | ||||
|  | ||||
|             if(!Source.IsCancellationRequested) | ||||
|                 Source.Cancel(); | ||||
|  | ||||
|             _client.MessageDeleted -= MessageDeletedEventHandler; | ||||
|         } | ||||
|  | ||||
|         private Task MessageDeletedEventHandler(Cacheable<IMessage, ulong> msg, ISocketMessageChannel channel) { | ||||
|             if (StartingMessage?.Id == msg.Id) | ||||
|             { | ||||
|                 _log.Warn("Stopping flower reaction event because message is deleted."); | ||||
|                 var __ = Task.Run(End); | ||||
|             } | ||||
|  | ||||
|             return Task.CompletedTask; | ||||
|         } | ||||
|  | ||||
|         public override async Task Start(IUserMessage umsg, ICommandContext context) | ||||
|         { | ||||
|             StartingMessage = umsg; | ||||
|             _client.MessageDeleted += MessageDeletedEventHandler; | ||||
|  | ||||
|             IEmote iemote; | ||||
|             if (Emote.TryParse(_bc.CurrencySign, out var emote)) | ||||
|             { | ||||
|                 iemote = emote; | ||||
|             } | ||||
|             else | ||||
|                 iemote = new Emoji(_bc.CurrencySign); | ||||
|             try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); } | ||||
|             catch | ||||
|             { | ||||
|                 try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); } | ||||
|                 catch | ||||
|                 { | ||||
|                     try { await StartingMessage.DeleteAsync().ConfigureAwait(false); } | ||||
|                     catch { return; } | ||||
|                 } | ||||
|             } | ||||
|             using (StartingMessage.OnReaction(_client, (r) => | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     if (r.UserId == _botUser.Id) | ||||
|                         return; | ||||
|  | ||||
|                     if (r.Emote.Name == iemote.Name && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _reactionAwardedUsers.Add(r.User.Value.Id)) | ||||
|                     { | ||||
|                         _toGiveTo.Enqueue(r.UserId); | ||||
|                     } | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|                     // ignored | ||||
|                 } | ||||
|             })) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     await Task.Delay(TimeSpan.FromHours(24), CancelToken).ConfigureAwait(false); | ||||
|                 } | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                      | ||||
|                 } | ||||
|                 if (CancelToken.IsCancellationRequested) | ||||
|                     return; | ||||
|  | ||||
|                 _log.Warn("Stopping flower reaction event because it expired."); | ||||
|                 await End(); | ||||
|                 await re.Start(msg, context); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										66
									
								
								NadekoBot.Modules.Gambling/Services/CurrencyEventsService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								NadekoBot.Modules.Gambling/Services/CurrencyEventsService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| using Discord; | ||||
| using Discord.Commands; | ||||
| using NadekoBot.Modules.Gambling.Common; | ||||
| using NadekoBot.Modules.Gambling.Common.CurrencyEvents; | ||||
| using NadekoBot.Services; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace NadekoBot.Modules.Gambling.Services | ||||
| { | ||||
|     public class CurrencyEventsService : INService, IUnloadableService | ||||
|     { | ||||
|         public ConcurrentDictionary<ulong, List<ReactionEvent>> ReactionEvents { get; } | ||||
|  | ||||
|         public SneakyEvent SneakyEvent { get; private set; } = null; | ||||
|         private SemaphoreSlim _sneakyLock = new SemaphoreSlim(1, 1); | ||||
|  | ||||
|         public CurrencyEventsService() | ||||
|         { | ||||
|             ReactionEvents = new ConcurrentDictionary<ulong, List<ReactionEvent>>(); | ||||
|         } | ||||
|  | ||||
|         public async Task<bool> StartSneakyEvent(SneakyEvent ev, IUserMessage msg, ICommandContext ctx) | ||||
|         { | ||||
|             await _sneakyLock.WaitAsync().ConfigureAwait(false); | ||||
|             try | ||||
|             { | ||||
|                 if (SneakyEvent != null) | ||||
|                     return false; | ||||
|  | ||||
|                 SneakyEvent = ev; | ||||
|                 ev.OnEnded += () => SneakyEvent = null; | ||||
|                 var _ = SneakyEvent.Start(msg, ctx).ConfigureAwait(false); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 _sneakyLock.Release(); | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         public async Task Unload() | ||||
|         { | ||||
|             foreach (var kvp in ReactionEvents) | ||||
|             { | ||||
|                 foreach (var ev in kvp.Value) | ||||
|                 { | ||||
|                     try { await ev.Stop().ConfigureAwait(false); } catch { } | ||||
|                 } | ||||
|             } | ||||
|             ReactionEvents.Clear(); | ||||
|  | ||||
|             await _sneakyLock.WaitAsync().ConfigureAwait(false); | ||||
|             try | ||||
|             { | ||||
|                 await SneakyEvent.Stop().ConfigureAwait(false); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 _sneakyLock.Release(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -6,12 +6,9 @@ namespace NadekoBot | ||||
|     { | ||||
|         public static Task Main(string[] args) | ||||
|         { | ||||
|             if (args.Length == 3 && int.TryParse(args[0], out int shardId) && int.TryParse(args[1], out int parentProcessId)) | ||||
|             if (args.Length == 2 && int.TryParse(args[0], out int shardId) && int.TryParse(args[1], out int parentProcessId)) | ||||
|             { | ||||
|                 int? port = null; | ||||
|                 if (int.TryParse(args[2], out var outPort)) | ||||
|                     port = outPort; | ||||
|                 return new NadekoBot(shardId, parentProcessId, outPort).RunAndBlockAsync(args); | ||||
|                 return new NadekoBot(shardId, parentProcessId).RunAndBlockAsync(args); | ||||
|             } | ||||
|             else | ||||
|                 return new NadekoBot(0, 0).RunAndBlockAsync(args); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user