Typereaders finished, cleanup
This commit is contained in:
parent
3d3871f903
commit
0bacb1f780
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user