Killed history

This commit is contained in:
Kwoth
2021-09-06 21:29:22 +02:00
commit 7aca29ae8a
950 changed files with 366651 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
using CommandLine;
using NadekoBot.Core.Common;
namespace NadekoBot.Core.Modules.Gambling.Common.Events
{
public class EventOptions : INadekoCommandOptions
{
[Option('a', "amount", Required = false, Default = 100, HelpText = "Amount of currency each user receives.")]
public long Amount { get; set; } = 100;
[Option('p', "pot-size", Required = false, Default = 0, HelpText = "The maximum amount of currency that can be rewarded. 0 means no limit.")]
public long PotSize { get; set; } = 0;
//[Option('t', "type", Required = false, Default = "reaction", HelpText = "Type of the event. reaction, gamestatus or joinserver.")]
//public string TypeString { get; set; } = "reaction";
[Option('d', "duration", Required = false, Default = 24, HelpText = "Number of hours the event should run for. Default 24.")]
public int Hours { get; set; } = 24;
public void NormalizeOptions()
{
if (Amount < 0)
Amount = 100;
if (PotSize < 0)
PotSize = 0;
if (Hours <= 0)
Hours = 24;
if (PotSize != 0 && PotSize < Amount)
PotSize = 0;
}
}
}

View File

@@ -0,0 +1,210 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Common.Collections;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
namespace NadekoBot.Core.Modules.Gambling.Common.Events
{
public class GameStatusEvent : ICurrencyEvent
{
private readonly DiscordSocketClient _client;
private readonly IGuild _guild;
private IUserMessage _msg;
private readonly ICurrencyService _cs;
private readonly long _amount;
private long PotSize { get; set; }
public bool Stopped { get; private set; }
public bool PotEmptied { get; private set; } = false;
private readonly Func<CurrencyEvent.Type, EventOptions, long, EmbedBuilder> _embedFunc;
private readonly bool _isPotLimited;
private readonly ITextChannel _channel;
private readonly ConcurrentHashSet<ulong> _awardedUsers = new ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
private readonly Timer _t;
private readonly Timer _timeout = null;
private readonly EventOptions _opts;
private readonly string _code;
public event Func<ulong, Task> OnEnded;
private readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10)
.Concat(Enumerable.Range(65, 26))
.Concat(Enumerable.Range(97, 26))
.Select(x => (char)x)
.ToArray();
public GameStatusEvent(DiscordSocketClient client, ICurrencyService cs,SocketGuild g, ITextChannel ch,
EventOptions opt, Func<CurrencyEvent.Type, EventOptions, long, EmbedBuilder> embedFunc)
{
_client = client;
_guild = g;
_cs = cs;
_amount = opt.Amount;
PotSize = opt.PotSize;
_embedFunc = embedFunc;
_isPotLimited = PotSize > 0;
_channel = ch;
_opts = opt;
// generate code
_code = new string(_sneakyGameStatusChars.Shuffle().Take(5).ToArray());
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0)
{
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
}
}
private void EventTimeout(object state)
{
var _ = StopEvent();
}
private async void OnTimerTick(object state)
{
var potEmpty = PotEmptied;
List<ulong> toAward = new List<ulong>();
while (_toAward.TryDequeue(out var x))
{
toAward.Add(x);
}
if (!toAward.Any())
return;
try
{
await _cs.AddBulkAsync(toAward,
toAward.Select(x => "GameStatus Event"),
toAward.Select(x => _amount),
gamble: true).ConfigureAwait(false);
if (_isPotLimited)
{
await _msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
}
Log.Information("Awarded {0} users {1} currency.{2}",
toAward.Count,
_amount,
_isPotLimited ? $" {PotSize} left." : "");
if (potEmpty)
{
var _ = StopEvent();
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error in OnTimerTick in gamestatusevent");
}
}
public async Task StartEvent()
{
_msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize)).ConfigureAwait(false);
await _client.SetGameAsync(_code).ConfigureAwait(false);
_client.MessageDeleted += OnMessageDeleted;
_client.MessageReceived += HandleMessage;
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
}
private EmbedBuilder GetEmbed(long pot)
{
return _embedFunc(CurrencyEvent.Type.GameStatus, _opts, pot);
}
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
{
if (msg.Id == _msg.Id)
{
await StopEvent().ConfigureAwait(false);
}
}
private readonly object stopLock = new object();
public async Task StopEvent()
{
await Task.Yield();
lock (stopLock)
{
if (Stopped)
return;
Stopped = true;
_client.MessageDeleted -= OnMessageDeleted;
_client.MessageReceived -= HandleMessage;
_client.SetGameAsync(null);
_t.Change(Timeout.Infinite, Timeout.Infinite);
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
try { var _ = _msg.DeleteAsync(); } catch { }
var os = OnEnded(_guild.Id);
}
}
private Task HandleMessage(SocketMessage msg)
{
var _ = Task.Run(async () =>
{
if (!(msg.Author is IGuildUser gu) // no unknown users, as they could be bots, or alts
|| gu.IsBot // no bots
|| msg.Content != _code // code has to be the same
|| (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5) // no recently created accounts
{
return;
}
// there has to be money left in the pot
// and the user wasn't rewarded
if (_awardedUsers.Add(msg.Author.Id) && TryTakeFromPot())
{
_toAward.Enqueue(msg.Author.Id);
if (_isPotLimited && PotSize < _amount)
PotEmptied = true;
}
try
{
await msg.DeleteAsync(new RequestOptions()
{
RetryMode = RetryMode.AlwaysFail
});
}
catch { }
});
return Task.CompletedTask;
}
private readonly object potLock = new object();
private bool TryTakeFromPot()
{
if (_isPotLimited)
{
lock (potLock)
{
if (PotSize < _amount)
return false;
PotSize -= _amount;
return true;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common
{
public interface ICurrencyEvent
{
event Func<ulong, Task> OnEnded;
Task StopEvent();
Task StartEvent();
}
}

View File

@@ -0,0 +1,210 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Common.Collections;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Core.Modules.Gambling.Services;
using Serilog;
namespace NadekoBot.Core.Modules.Gambling.Common.Events
{
public class ReactionEvent : ICurrencyEvent
{
private readonly DiscordSocketClient _client;
private readonly IGuild _guild;
private IUserMessage _msg;
private IEmote _emote;
private readonly ICurrencyService _cs;
private readonly long _amount;
private long PotSize { get; set; }
public bool Stopped { get; private set; }
public bool PotEmptied { get; private set; } = false;
private readonly Func<CurrencyEvent.Type, EventOptions, long, EmbedBuilder> _embedFunc;
private readonly bool _isPotLimited;
private readonly ITextChannel _channel;
private readonly ConcurrentHashSet<ulong> _awardedUsers = new ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
private readonly Timer _t;
private readonly Timer _timeout = null;
private readonly bool _noRecentlyJoinedServer;
private readonly EventOptions _opts;
private readonly GamblingConfig _config;
public event Func<ulong, Task> OnEnded;
public ReactionEvent(DiscordSocketClient client, ICurrencyService cs,
SocketGuild g, ITextChannel ch, EventOptions opt, GamblingConfig config,
Func<CurrencyEvent.Type, EventOptions, long, EmbedBuilder> embedFunc)
{
_client = client;
_guild = g;
_cs = cs;
_amount = opt.Amount;
PotSize = opt.PotSize;
_embedFunc = embedFunc;
_isPotLimited = PotSize > 0;
_channel = ch;
_noRecentlyJoinedServer = false;
_opts = opt;
_config = config;
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0)
{
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
}
}
private void EventTimeout(object state)
{
var _ = StopEvent();
}
private async void OnTimerTick(object state)
{
var potEmpty = PotEmptied;
List<ulong> toAward = new List<ulong>();
while (_toAward.TryDequeue(out var x))
{
toAward.Add(x);
}
if (!toAward.Any())
return;
try
{
await _cs.AddBulkAsync(toAward,
toAward.Select(x => "Reaction Event"),
toAward.Select(x => _amount),
gamble: true).ConfigureAwait(false);
if (_isPotLimited)
{
await _msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
}
Log.Information("Awarded {0} users {1} currency.{2}",
toAward.Count,
_amount,
_isPotLimited ? $" {PotSize} left." : "");
if (potEmpty)
{
var _ = StopEvent();
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error adding bulk currency to users");
}
}
public async Task StartEvent()
{
if (Emote.TryParse(_config.Currency.Sign, out var emote))
{
_emote = emote;
}
else
{
_emote = new Emoji(_config.Currency.Sign);
}
_msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize)).ConfigureAwait(false);
await _msg.AddReactionAsync(_emote).ConfigureAwait(false);
_client.MessageDeleted += OnMessageDeleted;
_client.ReactionAdded += HandleReaction;
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
}
private EmbedBuilder GetEmbed(long pot)
{
return _embedFunc(CurrencyEvent.Type.Reaction, _opts, pot);
}
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
{
if (msg.Id == _msg.Id)
{
await StopEvent().ConfigureAwait(false);
}
}
private readonly object stopLock = new object();
public async Task StopEvent()
{
await Task.Yield();
lock (stopLock)
{
if (Stopped)
return;
Stopped = true;
_client.MessageDeleted -= OnMessageDeleted;
_client.ReactionAdded -= HandleReaction;
_t.Change(Timeout.Infinite, Timeout.Infinite);
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
try { var _ = _msg.DeleteAsync(); } catch { }
var os = OnEnded(_guild.Id);
}
}
private Task HandleReaction(Cacheable<IUserMessage, ulong> msg,
ISocketMessageChannel ch, SocketReaction r)
{
var _ = Task.Run(() =>
{
if (_emote.Name != r.Emote.Name)
return;
var gu = (r.User.IsSpecified ? r.User.Value : null) as IGuildUser;
if (gu == null // no unknown users, as they could be bots, or alts
|| msg.Id != _msg.Id // same message
|| gu.IsBot // no bots
|| (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5 // no recently created accounts
|| (_noRecentlyJoinedServer && // if specified, no users who joined the server in the last 24h
(gu.JoinedAt == null || (DateTime.UtcNow - gu.JoinedAt.Value).TotalDays < 1))) // and no users for who we don't know when they joined
{
return;
}
// there has to be money left in the pot
// and the user wasn't rewarded
if (_awardedUsers.Add(r.UserId) && TryTakeFromPot())
{
_toAward.Enqueue(r.UserId);
if (_isPotLimited && PotSize < _amount)
PotEmptied = true;
}
});
return Task.CompletedTask;
}
private readonly object potLock = new object();
private bool TryTakeFromPot()
{
if (_isPotLimited)
{
lock (potLock)
{
if (PotSize < _amount)
return false;
PotSize -= _amount;
return true;
}
}
return true;
}
}
}