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
|
||||
{
|
||||
class GuildConfiguration
|
||||
class Configuration
|
||||
{
|
||||
public string VoteCommand { get; }
|
||||
public string TempChannelName { get; }
|
||||
public TimeSpan ChannelBaseDuration { get; }
|
||||
public TimeSpan ChannelExtendDuration { get; }
|
||||
public TimeSpan KeepaliveVoteDuration { 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>();
|
||||
if (string.IsNullOrWhiteSpace(VoteCommand))
|
||||
|
@ -39,7 +40,8 @@ namespace Noikoio.RegexBot.Module.VoteTempChannel
|
|||
|
||||
ChannelBaseDuration = ParseTimeConfig(j, "ChannelBaseDuration");
|
||||
ChannelExtendDuration = ParseTimeConfig(j, "ChannelExtendDuration");
|
||||
KeepaliveVoteDuration = ParseTimeConfig(j, "KeepaliveVoteDuration");
|
||||
VotingDuration = ParseTimeConfig(j, "VotingDuration");
|
||||
VotingCooldown = ParseTimeConfig(j, "VotingCooldown");
|
||||
}
|
||||
|
||||
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