Typereaders finished, cleanup

This commit is contained in:
Master Kwoth 2017-10-10 00:04:02 +02:00
parent 3d3871f903
commit 0bacb1f780
19 changed files with 436 additions and 274 deletions

View File

@ -3,26 +3,26 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using NadekoBot.Services;
using StackExchange.Redis;
namespace NadekoBot.Common.ShardCom namespace NadekoBot.Common.ShardCom
{ {
public class ShardComClient 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) public async Task Send(ShardComMessage data)
{ {
var sub = _cache.Redis.GetSubscriber();
var msg = JsonConvert.SerializeObject(data); var msg = JsonConvert.SerializeObject(data);
using (var client = new UdpClient())
{ await sub.PublishAsync("shardcoord_send", msg).ConfigureAwait(false);
var bytes = Encoding.UTF8.GetBytes(msg);
await client.SendAsync(bytes, bytes.Length, IPAddress.Loopback.ToString(), port).ConfigureAwait(false);
}
} }
} }
} }

View File

@ -4,35 +4,26 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using NadekoBot.Services;
namespace NadekoBot.Common.ShardCom 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() 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)); var _ = OnDataReceived(JsonConvert.DeserializeObject<ShardComMessage>(data));
} }, StackExchange.Redis.CommandFlags.FireAndForget);
});
}
public void Dispose()
{
_client.Dispose();
} }
public event Func<ShardComMessage, Task> OnDataReceived = delegate { return Task.CompletedTask; }; public event Func<ShardComMessage, Task> OnDataReceived = delegate { return Task.CompletedTask; };

View File

@ -9,7 +9,7 @@ using Discord.WebSocket;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
public class CommandTypeReader : NadekoTypeReader public class CommandTypeReader : NadekoTypeReader<CommandInfo>
{ {
public CommandTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds) 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) 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) 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) if (cmd.IsSuccess)
{ {
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name)); return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name));

View File

@ -7,7 +7,7 @@ using Discord.WebSocket;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
public class GuildDateTimeTypeReader : NadekoTypeReader public class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime>
{ {
public GuildDateTimeTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds) public GuildDateTimeTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
{ {

View File

@ -4,10 +4,11 @@ using System.Threading.Tasks;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Core.Common.TypeReaders; using NadekoBot.Core.Common.TypeReaders;
using Discord;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
public class GuildTypeReader : NadekoTypeReader public class GuildTypeReader : NadekoTypeReader<IGuild>
{ {
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;

View File

@ -8,7 +8,7 @@ using Discord.WebSocket;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
public class ModuleTypeReader : NadekoTypeReader public class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
{ {
private readonly CommandService _cmds; 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; private readonly CommandService _cmds;

View File

@ -3,7 +3,8 @@ using Discord.WebSocket;
namespace NadekoBot.Core.Common.TypeReaders 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 DiscordSocketClient _client;
private readonly CommandService _cmds; private readonly CommandService _cmds;

View File

@ -10,7 +10,7 @@ namespace NadekoBot.Common.TypeReaders
/// <summary> /// <summary>
/// Used instead of bool for more flexible keywords for true/false only in the permission module /// Used instead of bool for more flexible keywords for true/false only in the permission module
/// </summary> /// </summary>
public class PermissionActionTypeReader : NadekoTypeReader public class PermissionActionTypeReader : NadekoTypeReader<PermissionAction>
{ {
public PermissionActionTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds) public PermissionActionTypeReader(DiscordSocketClient client, CommandService cmds) : base(client, cmds)
{ {

View File

@ -41,7 +41,7 @@ namespace NadekoBot.Modules.Administration.Services
_rep = new ReplacementBuilder() _rep = new ReplacementBuilder()
.WithClient(client) .WithClient(client)
.WithStats(client) .WithStats(client)
//todo type readers //todo how to add music to replacement builder?
//.WithMusic(music) //.WithMusic(music)
.Build(); .Build();

View File

@ -5,19 +5,20 @@ namespace NadekoBot.Services.Impl
{ {
public class RedisCache : IDataCache public class RedisCache : IDataCache
{ {
private ulong _botid;
public ConnectionMultiplexer Redis { get; } public ConnectionMultiplexer Redis { get; }
private readonly IDatabase _db; private readonly IDatabase _db;
public RedisCache(ulong botId) public RedisCache()
{ {
_botid = botId;
Redis = ConnectionMultiplexer.Connect("127.0.0.1"); Redis = ConnectionMultiplexer.Connect("127.0.0.1");
Redis.PreserveAsyncOrder = false; Redis.PreserveAsyncOrder = false;
_db = Redis.GetDatabase(); _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) public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key)
{ {
byte[] x = await _db.StringGetAsync("image_" + key); byte[] x = await _db.StringGetAsync("image_" + key);

View File

@ -17,14 +17,11 @@ using NadekoBot.Extensions;
using System.Collections.Generic; using System.Collections.Generic;
using NadekoBot.Common; using NadekoBot.Common;
using NadekoBot.Common.ShardCom; using NadekoBot.Common.ShardCom;
using NadekoBot.Common.TypeReaders;
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using StackExchange.Redis; using StackExchange.Redis;
using Newtonsoft.Json; 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 namespace NadekoBot
{ {
public class NadekoBot public class NadekoBot
@ -57,8 +54,9 @@ namespace NadekoBot
private readonly ShardComClient _comClient; private readonly ShardComClient _comClient;
private readonly BotConfig _botConfig; 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) if (shardId < 0)
throw new ArgumentOutOfRangeException(nameof(shardId)); throw new ArgumentOutOfRangeException(nameof(shardId));
@ -67,6 +65,7 @@ namespace NadekoBot
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
TerribleElevatedPermissionCheck(); TerribleElevatedPermissionCheck();
Cache = new RedisCache();
Credentials = new BotCredentials(); Credentials = new BotCredentials();
_db = new DbService(Credentials); _db = new DbService(Credentials);
Client = new DiscordSocketClient(new DiscordSocketConfig Client = new DiscordSocketClient(new DiscordSocketConfig
@ -84,8 +83,7 @@ namespace NadekoBot
DefaultRunMode = RunMode.Sync, DefaultRunMode = RunMode.Sync,
}); });
port = port ?? Credentials.ShardRunPort; _comClient = new ShardComClient(Cache);
_comClient = new ShardComClient(port.Value);
using (var uow = _db.UnitOfWork) using (var uow = _db.UnitOfWork)
{ {
@ -94,7 +92,7 @@ namespace NadekoBot
ErrorColor = new Color(Convert.ToUInt32(_botConfig.ErrorColor, 16)); ErrorColor = new Color(Convert.ToUInt32(_botConfig.ErrorColor, 16));
} }
SetupShard(parentProcessId, port.Value); SetupShard(parentProcessId);
#if GLOBAL_NADEKO #if GLOBAL_NADEKO
Client.Log += Client_Log; Client.Log += Client_Log;
@ -144,7 +142,7 @@ namespace NadekoBot
.AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this .AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this
.AddManual<NadekoBot>(this) .AddManual<NadekoBot>(this)
.AddManual<IUnitOfWork>(uow) .AddManual<IUnitOfWork>(uow)
.AddManual<IDataCache>(new RedisCache(Client.CurrentUser.Id)); .AddManual<IDataCache>(Cache);
Services.LoadFrom(Assembly.GetAssembly(typeof(CommandHandler))); Services.LoadFrom(Assembly.GetAssembly(typeof(CommandHandler)));
@ -156,7 +154,7 @@ namespace NadekoBot
Services.Unload(typeof(IUnitOfWork)); // unload it after the startup Services.Unload(typeof(IUnitOfWork)); // unload it after the startup
} }
private IEnumerable<NadekoTypeReader> LoadTypeReaders(Assembly assembly) private IEnumerable<object> LoadTypeReaders(Assembly assembly)
{ {
Type[] allTypes; Type[] allTypes;
try try
@ -166,18 +164,30 @@ namespace NadekoBot
catch (ReflectionTypeLoadException ex) catch (ReflectionTypeLoadException ex)
{ {
Console.WriteLine(ex.LoaderExceptions[0]); Console.WriteLine(ex.LoaderExceptions[0]);
return Enumerable.Empty<NadekoTypeReader>(); return Enumerable.Empty<object>();
} }
var filteredTypes = allTypes var filteredTypes = allTypes
.Where(x => x.IsSubclassOf(typeof(NadekoTypeReader)) .Where(x => x.IsSubclassOf(typeof(TypeReader))
&& x.BaseType.GetGenericArguments().Length > 0
&& !x.IsAbstract); && !x.IsAbstract);
var toReturn = new List<NadekoTypeReader>(); var toReturn = new List<object>();
foreach (var ft in filteredTypes) foreach (var ft in filteredTypes)
{ {
//:yayyy: //:yayyy:
var x = (NadekoTypeReader)Activator.CreateInstance(ft, Client, CommandService); var x = (TypeReader)Activator.CreateInstance(ft, Client, CommandService);
CommandService.AddTypeReader(x.GetType(), x); //@.@ 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); toReturn.Add(x);
_log.Info("Loaded {0} typereader.", x.GetType().Name); _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) if (Client.ShardId == 0)
{ {
ShardCoord = new ShardsCoordinator(port); ShardCoord = new ShardsCoordinator(Cache);
return; return;
} }
new Thread(new ThreadStart(() => new Thread(new ThreadStart(() =>

View File

@ -19,19 +19,28 @@ namespace NadekoBot.Services
private readonly Logger _log; private readonly Logger _log;
private readonly ShardComServer _comServer; private readonly ShardComServer _comServer;
private readonly int _port;
private readonly int _curProcessId; private readonly int _curProcessId;
public ShardsCoordinator(int port) public ShardsCoordinator(IDataCache cache)
{ {
LogSetup.SetupLogger(); LogSetup.SetupLogger();
_creds = new BotCredentials(); _creds = new BotCredentials();
_shardProcesses = new Process[_creds.TotalShards]; _shardProcesses = new Process[_creds.TotalShards];
Statuses = new ShardComMessage[_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.Start();
_comServer.OnDataReceived += _comServer_OnDataReceived; _comServer.OnDataReceived += _comServer_OnDataReceived;
@ -54,8 +63,10 @@ namespace NadekoBot.Services
var p = Process.Start(new ProcessStartInfo() var p = Process.Start(new ProcessStartInfo()
{ {
FileName = _creds.ShardRunCommand, 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); await Task.Delay(5000);
} }
} }

View File

@ -21,6 +21,21 @@ namespace NadekoBot.Extensions
{ {
public static class 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) public static async Task<string> ReplaceAsync(this Regex regex, string input, Func<Match, Task<string>> replacementFn)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();

View File

@ -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);
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -2,42 +2,26 @@
using Discord.Commands; using Discord.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.WebSocket; using Discord.WebSocket;
using System.Threading;
using NadekoBot.Common;
using NadekoBot.Common.Attributes; using NadekoBot.Common.Attributes;
using NadekoBot.Common.Collections; using NadekoBot.Modules.Gambling.Common;
using NLog; using NadekoBot.Modules.Gambling.Services;
using System.Collections.Concurrent; using NadekoBot.Modules.Gambling.Common.CurrencyEvents;
using System.Collections.Generic;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Gambling namespace NadekoBot.Modules.Gambling
{ {
//todo mess, needs unload thing too - refactor
public partial class Gambling public partial class Gambling
{ {
[Group] [Group]
public class CurrencyEventsCommands : NadekoSubmodule public class CurrencyEventsCommands : NadekoSubmodule<CurrencyEventsService>
{ {
public enum CurrencyEvent public enum CurrencyEvent
{ {
Reaction, Reaction,
SneakyGameStatus 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 DiscordSocketClient _client;
private readonly IBotConfigProvider _bc; private readonly IBotConfigProvider _bc;
private readonly CurrencyService _cs; 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 (num < 10 || num > 600)
if (arg == null || arg < 5)
num = 60; 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; 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 try
{ {
var title = GetText("sneakygamestatus_title"); var title = GetText("sneakygamestatus_title");
var desc = GetText("sneakygamestatus_desc", Format.Bold(100.ToString()) + _bc.BotConfig.CurrencySign, Format.Bold(num.ToString())); var desc = GetText("sneakygamestatus_desc",
await context.Channel.SendConfirmAsync(title, desc).ConfigureAwait(false); Format.Bold(100.ToString()) + _bc.BotConfig.CurrencySign,
Format.Bold(num.ToString()));
await context.Channel.SendConfirmAsync(title, desc)
.ConfigureAwait(false);
} }
catch catch
{ {
// ignored // 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) public async Task ReactionEvent(ICommandContext context, int amount)
@ -137,144 +80,11 @@ namespace NadekoBot.Modules.Gambling
var title = GetText("reaction_title"); var title = GetText("reaction_title");
var desc = GetText("reaction_desc", _bc.BotConfig.CurrencySign, Format.Bold(amount.ToString()) + _bc.BotConfig.CurrencySign); var desc = GetText("reaction_desc", _bc.BotConfig.CurrencySign, Format.Bold(amount.ToString()) + _bc.BotConfig.CurrencySign);
var footer = GetText("reaction_footer", 24); var footer = GetText("reaction_footer", 24);
var re = new ReactionEvent(_bc.BotConfig, _client, _cs, amount);
var msg = await context.Channel.SendConfirmAsync(title, var msg = await context.Channel.SendConfirmAsync(title,
desc, footer: footer) desc, footer: footer)
.ConfigureAwait(false); .ConfigureAwait(false);
await re.Start(msg, context);
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();
} }
} }
} }

View 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();
}
}
}
}

View File

@ -6,12 +6,9 @@ namespace NadekoBot
{ {
public static Task Main(string[] args) 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; return new NadekoBot(shardId, parentProcessId).RunAndBlockAsync(args);
if (int.TryParse(args[2], out var outPort))
port = outPort;
return new NadekoBot(shardId, parentProcessId, outPort).RunAndBlockAsync(args);
} }
else else
return new NadekoBot(0, 0).RunAndBlockAsync(args); return new NadekoBot(0, 0).RunAndBlockAsync(args);