Replaced VoteStore with VotingSession
This commit is contained in:
parent
b9b23e5b02
commit
0a2acf6ed6
3 changed files with 79 additions and 151 deletions
|
@ -5,16 +5,17 @@ using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Module.VoteTempChannel
|
namespace Noikoio.RegexBot.Module.VoteTempChannel
|
||||||
{
|
{
|
||||||
class GuildConfiguration
|
class Configuration
|
||||||
{
|
{
|
||||||
public string VoteCommand { get; }
|
public string VoteCommand { get; }
|
||||||
public string TempChannelName { get; }
|
public string TempChannelName { get; }
|
||||||
public TimeSpan ChannelBaseDuration { get; }
|
public TimeSpan ChannelBaseDuration { get; }
|
||||||
public TimeSpan ChannelExtendDuration { get; }
|
public TimeSpan ChannelExtendDuration { get; }
|
||||||
public TimeSpan KeepaliveVoteDuration { get; }
|
|
||||||
public int VotePassThreshold { get; }
|
public int VotePassThreshold { get; }
|
||||||
|
public TimeSpan VotingDuration { get; }
|
||||||
|
public TimeSpan VotingCooldown { get; }
|
||||||
|
|
||||||
public GuildConfiguration(JObject j)
|
public Configuration(JObject j)
|
||||||
{
|
{
|
||||||
VoteCommand = j["VoteCommand"]?.Value<string>();
|
VoteCommand = j["VoteCommand"]?.Value<string>();
|
||||||
if (string.IsNullOrWhiteSpace(VoteCommand))
|
if (string.IsNullOrWhiteSpace(VoteCommand))
|
||||||
|
@ -39,7 +40,8 @@ namespace Noikoio.RegexBot.Module.VoteTempChannel
|
||||||
|
|
||||||
ChannelBaseDuration = ParseTimeConfig(j, "ChannelBaseDuration");
|
ChannelBaseDuration = ParseTimeConfig(j, "ChannelBaseDuration");
|
||||||
ChannelExtendDuration = ParseTimeConfig(j, "ChannelExtendDuration");
|
ChannelExtendDuration = ParseTimeConfig(j, "ChannelExtendDuration");
|
||||||
KeepaliveVoteDuration = ParseTimeConfig(j, "KeepaliveVoteDuration");
|
VotingDuration = ParseTimeConfig(j, "VotingDuration");
|
||||||
|
VotingCooldown = ParseTimeConfig(j, "VotingCooldown");
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimeSpan ParseTimeConfig(JObject conf, string valueName)
|
private TimeSpan ParseTimeConfig(JObject conf, string valueName)
|
|
@ -1,147 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Module.VoteTempChannel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Handles keeping track of per-guild voting, along with cooldowns.
|
|
||||||
/// </summary>
|
|
||||||
class VoteStore
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Votes are kept track for a total of five minutes beginning with the first vote.
|
|
||||||
* All votes are discarded after five minutes have elapsed, rather than the data
|
|
||||||
* staying for longer as more votes are added.
|
|
||||||
*/
|
|
||||||
private Dictionary<ulong, DateTimeOffset> _cooldown;
|
|
||||||
private Dictionary<ulong, VoteData> _votes;
|
|
||||||
|
|
||||||
private class VoteData
|
|
||||||
{
|
|
||||||
public VoteData()
|
|
||||||
{
|
|
||||||
VotingStart = DateTimeOffset.UtcNow;
|
|
||||||
Voters = new List<ulong>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTimeOffset VotingStart { get; }
|
|
||||||
public List<ulong> Voters { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public VoteStore()
|
|
||||||
{
|
|
||||||
_cooldown = new Dictionary<ulong, DateTimeOffset>();
|
|
||||||
_votes = new Dictionary<ulong, VoteData>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// !! Hardcoded value: votes always expire after 5 minutes.
|
|
||||||
static readonly TimeSpan VoteExpiry = new TimeSpan(0, 5, 0);
|
|
||||||
/// <summary>
|
|
||||||
/// Call before accessing votes. Removes any stale voting entries.
|
|
||||||
/// </summary>
|
|
||||||
private void CleanVoteData()
|
|
||||||
{
|
|
||||||
IEnumerable<Tuple<ulong, DateTimeOffset>> expiredEntries;
|
|
||||||
lock (_votes)
|
|
||||||
{
|
|
||||||
var now = DateTimeOffset.UtcNow;
|
|
||||||
expiredEntries = (from item in _votes
|
|
||||||
where now > item.Value.VotingStart + VoteExpiry
|
|
||||||
select new Tuple<ulong, DateTimeOffset>(item.Key, item.Value.VotingStart))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
lock (_cooldown)
|
|
||||||
{
|
|
||||||
// For expiring votes, set a cooldown that starts at the time the
|
|
||||||
// vote had actually expired.
|
|
||||||
foreach (var item in expiredEntries)
|
|
||||||
{
|
|
||||||
_votes.Remove(item.Item1);
|
|
||||||
_cooldown.Add(item.Item1, item.Item2 + VoteExpiry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// !! Hardcoded value: cooldowns last one hour.
|
|
||||||
static readonly TimeSpan CooldownExpiry = new TimeSpan(1, 0, 0);
|
|
||||||
private bool IsInCooldown(ulong guild)
|
|
||||||
{
|
|
||||||
lock (_cooldown)
|
|
||||||
{
|
|
||||||
// Clean up expired entries first...
|
|
||||||
var now = DateTimeOffset.UtcNow;
|
|
||||||
var expiredEntries = (from item in _cooldown
|
|
||||||
where now > item.Value + CooldownExpiry
|
|
||||||
select item.Key).ToArray();
|
|
||||||
foreach (var item in expiredEntries) _cooldown.Remove(item);
|
|
||||||
|
|
||||||
// And then the actual check:
|
|
||||||
return _cooldown.ContainsKey(guild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetCooldown(ulong guild)
|
|
||||||
{
|
|
||||||
lock (_cooldown) _cooldown.Add(guild, DateTimeOffset.UtcNow);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearCooldown(ulong guild)
|
|
||||||
{
|
|
||||||
lock (_cooldown) _cooldown.Remove(guild);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to log a vote by a given user.
|
|
||||||
/// </summary>
|
|
||||||
public VoteStatus AddVote(ulong guild, ulong user, out int voteCount)
|
|
||||||
{
|
|
||||||
voteCount = -1;
|
|
||||||
if (IsInCooldown(guild)) return VoteStatus.FailCooldown;
|
|
||||||
lock (_votes)
|
|
||||||
{
|
|
||||||
CleanVoteData();
|
|
||||||
VoteData v;
|
|
||||||
if (!_votes.TryGetValue(guild, out v))
|
|
||||||
{
|
|
||||||
v = new VoteData();
|
|
||||||
_votes[guild] = v;
|
|
||||||
}
|
|
||||||
voteCount = v.Voters.Count;
|
|
||||||
|
|
||||||
if (v.Voters.Contains(user)) return VoteStatus.FailVotedAlready;
|
|
||||||
|
|
||||||
v.Voters.Add(user);
|
|
||||||
voteCount++;
|
|
||||||
return VoteStatus.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DelVote(ulong guild, ulong user)
|
|
||||||
{
|
|
||||||
lock (_votes)
|
|
||||||
{
|
|
||||||
if (_votes.TryGetValue(guild, out var v))
|
|
||||||
{
|
|
||||||
v.Voters.Remove(user);
|
|
||||||
if (v.Voters.Count == 0) _votes.Remove(guild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears voting data from within the specified guild.
|
|
||||||
/// </summary>
|
|
||||||
public void ClearVotes(ulong guild)
|
|
||||||
{
|
|
||||||
lock (_votes) _votes.Remove(guild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VoteStatus
|
|
||||||
{
|
|
||||||
Success, FailVotedAlready, FailCooldown
|
|
||||||
}
|
|
||||||
}
|
|
73
Module/VoteTempChannel/VotingSession.cs
Normal file
73
Module/VoteTempChannel/VotingSession.cs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Noikoio.RegexBot.Module.VoteTempChannel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Keeps information on voting sessions and voting cooldowns.
|
||||||
|
/// </summary>
|
||||||
|
class VotingSession
|
||||||
|
{
|
||||||
|
private Configuration _conf;
|
||||||
|
private DateTimeOffset _initialVoteTime;
|
||||||
|
private List<ulong> _votes;
|
||||||
|
|
||||||
|
public DateTimeOffset? CooldownStart { get; private set; }
|
||||||
|
|
||||||
|
public VotingSession()
|
||||||
|
{
|
||||||
|
CooldownStart = null;
|
||||||
|
_votes = new List<ulong>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts a user vote.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>False if the user already has a vote counted.</returns>
|
||||||
|
public bool AddVote(ulong id)
|
||||||
|
{
|
||||||
|
// Mark the start of a new session, if applicable.
|
||||||
|
if (_votes.Count == 0) _initialVoteTime = DateTimeOffset.UtcNow;
|
||||||
|
if (_votes.Contains(id)) return false;
|
||||||
|
_votes.Add(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the voting session has expired.
|
||||||
|
/// To be called by the background task. This automatically resets state.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSessionExpired()
|
||||||
|
{
|
||||||
|
if (_votes.Count == 0) return false;
|
||||||
|
if (DateTimeOffset.UtcNow > _initialVoteTime + _conf.VotingDuration)
|
||||||
|
{
|
||||||
|
// Clear votes. And because we're clearing it due to an expiration, set a cooldown.
|
||||||
|
Reset();
|
||||||
|
StartCooldown();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartCooldown()
|
||||||
|
{
|
||||||
|
CooldownStart = DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInCooldown()
|
||||||
|
{
|
||||||
|
if (!CooldownStart.HasValue) return false;
|
||||||
|
return CooldownStart.Value + _conf.VotingCooldown > DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the object to its initial state.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_votes.Clear();
|
||||||
|
CooldownStart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue