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

View File

@ -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();
var _ = OnDataReceived(JsonConvert.DeserializeObject<ShardComMessage>(data));
}, StackExchange.Redis.CommandFlags.FireAndForget);
}
public event Func<ShardComMessage, Task> OnDataReceived = delegate { return Task.CompletedTask; };

View File

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

View File

@ -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)
{

View File

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

View File

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

View File

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

View File

@ -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)
{

View File

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

View File

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

View File

@ -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
@ -83,9 +82,8 @@ namespace NadekoBot
CaseSensitiveCommands = false,
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(() =>

View File

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

View File

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

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

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)
{
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);