Moved channel creation to event handler
This commit is contained in:
parent
b9cc41030c
commit
13e3434c38
3 changed files with 108 additions and 185 deletions
|
@ -1,69 +0,0 @@
|
||||||
using Discord.Rest;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Module.VoteTempChannel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Keeps track of existing channels and expiry information. Manages data persistence.
|
|
||||||
/// </summary>
|
|
||||||
class ChannelManager
|
|
||||||
{
|
|
||||||
readonly VoteTempChannel _out;
|
|
||||||
readonly DiscordSocketClient _client;
|
|
||||||
|
|
||||||
readonly CancellationTokenSource _token;
|
|
||||||
readonly Task _bgTask;
|
|
||||||
|
|
||||||
public ChannelManager(VoteTempChannel module, DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
_out = module;
|
|
||||||
_client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Channel entry manipulation
|
|
||||||
/// <summary>
|
|
||||||
/// Creates the temporary channel.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ApplicationException">
|
|
||||||
/// Various causes. Send exception message to log and channel if thrown.
|
|
||||||
/// </exception>
|
|
||||||
public async Task<RestTextChannel> CreateChannelAndEntryAsync(SocketGuild guild, Configuration info)
|
|
||||||
{
|
|
||||||
lock (_trackedChannels)
|
|
||||||
{
|
|
||||||
// Disregard if already in cache. (How did we get here?)
|
|
||||||
if (_trackedChannels.ContainsKey(guild.Id)) return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
RestTextChannel newCh = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
newCh = await guild.CreateTextChannelAsync(info.TempChannelName);
|
|
||||||
}
|
|
||||||
catch (Discord.Net.HttpException ex)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Failed to create the channel. Internal error message: " + ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newCh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the given guild's temporary channel as up for immediate expiration.
|
|
||||||
/// Use this to properly remove a temporary channel.
|
|
||||||
/// </summary>
|
|
||||||
public async Task SetChannelEarlyExpiry(SocketGuild guild)
|
|
||||||
{
|
|
||||||
lock (_trackedChannels)
|
|
||||||
{
|
|
||||||
if (!_trackedChannels.ContainsKey(guild.Id)) return; // how did we even get here?
|
|
||||||
_trackedChannels[guild.Id] = (DateTimeOffset.UtcNow, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,133 +10,23 @@ using System.Threading.Tasks;
|
||||||
namespace Noikoio.RegexBot.Module.VoteTempChannel
|
namespace Noikoio.RegexBot.Module.VoteTempChannel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Entry point" for VoteTempChannel feature.
|
/// Enables users to vote for the creation of a temporary channel.
|
||||||
/// Handles activation command depending on guild state. Also holds information on
|
/// Deletes the channel after a set period of inactivity.
|
||||||
/// temporary channels currently active.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class VoteTempChannel : BotModule
|
class VoteTempChannel : BotModule
|
||||||
{
|
{
|
||||||
Task _backgroundWorker;
|
Task _backgroundWorker;
|
||||||
CancellationTokenSource _backgroundWorkerCancel;
|
CancellationTokenSource _backgroundWorkerCancel;
|
||||||
ChannelManager _chMgr;
|
|
||||||
internal VoteStore _votes;
|
|
||||||
|
|
||||||
public VoteTempChannel(DiscordSocketClient client) : base(client)
|
public VoteTempChannel(DiscordSocketClient client) : base(client)
|
||||||
{
|
{
|
||||||
_chMgr = new ChannelManager(this, client);
|
client.MessageReceived += VoteChecking;
|
||||||
_votes = new VoteStore();
|
client.MessageReceived += TemporaryChannelActivityCheck;
|
||||||
|
|
||||||
client.JoinedGuild += GuildEnter;
|
|
||||||
client.GuildAvailable += GuildEnter;
|
|
||||||
client.LeftGuild += GuildLeave;
|
|
||||||
client.MessageReceived += Client_MessageReceived;
|
|
||||||
|
|
||||||
_backgroundWorkerCancel = new CancellationTokenSource();
|
_backgroundWorkerCancel = new CancellationTokenSource();
|
||||||
_backgroundWorker = Task.Factory.StartNew(BackgroundCheckingTask, _backgroundWorkerCancel.Token,
|
_backgroundWorker = Task.Factory.StartNew(BackgroundCheckingTask, _backgroundWorkerCancel.Token,
|
||||||
TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GuildEnter(SocketGuild arg)
|
|
||||||
{
|
|
||||||
var conf = GetState<Configuration>(arg.Id);
|
|
||||||
if (conf != null) await _chMgr.RecheckExpiryInformation(arg, conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task GuildLeave(SocketGuild arg)
|
|
||||||
{
|
|
||||||
_chMgr.DropCacheEntry(arg);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles all vote logic
|
|
||||||
private async Task Client_MessageReceived(SocketMessage arg)
|
|
||||||
{
|
|
||||||
if (arg.Author.IsBot) return;
|
|
||||||
if (arg.Channel is IDMChannel) return;
|
|
||||||
var guild = (arg.Channel as SocketTextChannel)?.Guild;
|
|
||||||
if (guild == null) return;
|
|
||||||
var conf = GetConfig(guild.Id);
|
|
||||||
if (conf == null) return;
|
|
||||||
|
|
||||||
if (!arg.Content.StartsWith(conf.VoteCommand, StringComparison.InvariantCultureIgnoreCase)) return;
|
|
||||||
|
|
||||||
var voteResult = _votes.AddVote(guild.Id, arg.Author.Id, out int voteCount);
|
|
||||||
if (voteResult == VoteStatus.FailCooldown)
|
|
||||||
{
|
|
||||||
await arg.Channel.SendMessageAsync(":x: Cooldown in effect. Try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const string VoteError = ":x: You have already placed your vote.";
|
|
||||||
|
|
||||||
if (_chMgr.HasExistingTemporaryChannel(guild, conf))
|
|
||||||
{
|
|
||||||
// Ignore votes not coming from the temporary channel itself.
|
|
||||||
if (!string.Equals(arg.Channel.Name, conf.TempChannelName, StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
_votes.DelVote(guild.Id, arg.Author.Id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (voteResult == VoteStatus.FailVotedAlready)
|
|
||||||
{
|
|
||||||
await arg.Channel.SendMessageAsync(VoteError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await HandleVote_TempChannelExists(arg, guild, conf, voteCount);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (voteResult == VoteStatus.FailVotedAlready)
|
|
||||||
{
|
|
||||||
await arg.Channel.SendMessageAsync(VoteError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await HandleVote_TempChannelNotExists(arg, guild, conf, voteCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleVote_TempChannelNotExists(SocketMessage arg, SocketGuild guild, Configuration conf, int voteCount)
|
|
||||||
{
|
|
||||||
bool threshold = voteCount >= conf.VotePassThreshold;
|
|
||||||
RestTextChannel newCh = null;
|
|
||||||
|
|
||||||
if (threshold)
|
|
||||||
{
|
|
||||||
newCh = await _chMgr.CreateChannelAndEntryAsync(guild, conf);
|
|
||||||
_votes.ClearVotes(guild.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
await arg.Channel.SendMessageAsync(":white_check_mark: Channel creation vote has been counted."
|
|
||||||
+ (threshold ? $"\n<#{newCh.Id}> is now available!" : ""));
|
|
||||||
if (newCh != null)
|
|
||||||
await newCh.SendMessageAsync($"Welcome to <#{newCh.Id}>!"
|
|
||||||
+ "\nPlease note that this channel is temporary and *will* be deleted at a later time.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleVote_TempChannelExists(SocketMessage arg, SocketGuild guild, Configuration conf, int voteCount)
|
|
||||||
{
|
|
||||||
// It's been checked that the incoming message originated from the temporary channel itself before coming here.
|
|
||||||
if (!_chMgr.IsUpForRenewal(guild, conf))
|
|
||||||
{
|
|
||||||
// TODO consider changing 'renewal' to 'extension' in other references, because the word makes more sense
|
|
||||||
if (conf.ChannelExtendDuration != TimeSpan.Zero)
|
|
||||||
await arg.Channel.SendMessageAsync(":x: Cannot currently vote for a time extension. Try again later.");
|
|
||||||
else
|
|
||||||
await arg.Channel.SendMessageAsync(":x: This channel's duration may not be extended.");
|
|
||||||
_votes.ClearVotes(guild.Id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool threshold = voteCount >= conf.VotePassThreshold;
|
|
||||||
if (threshold)
|
|
||||||
{
|
|
||||||
_votes.ClearVotes(guild.Id);
|
|
||||||
await _chMgr.ExtendChannelExpirationAsync(guild, conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
await arg.Channel.SendMessageAsync(":white_check_mark: Extension vote has been counted."
|
|
||||||
+ (threshold ? "\nThis channel's duration has been extended." : ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<object> CreateInstanceState(JToken configSection)
|
public override Task<object> CreateInstanceState(JToken configSection)
|
||||||
{
|
{
|
||||||
|
@ -148,6 +38,103 @@ namespace Noikoio.RegexBot.Module.VoteTempChannel
|
||||||
throw new RuleImportException("Configuration not of a valid type.");
|
throw new RuleImportException("Configuration not of a valid type.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Listens for voting commands.
|
||||||
|
/// </summary>
|
||||||
|
private async Task VoteChecking(SocketMessage arg)
|
||||||
|
{
|
||||||
|
if (arg.Author.IsBot) return;
|
||||||
|
var guild = (arg.Channel as SocketTextChannel)?.Guild;
|
||||||
|
if (guild == null) return;
|
||||||
|
var conf = GetState<GuildInformation>(guild.Id);
|
||||||
|
if (conf == null) return;
|
||||||
|
|
||||||
|
// Only check the designated voting channel
|
||||||
|
if (!string.Equals(arg.Channel.Name, conf.Config.VotingChannel,
|
||||||
|
StringComparison.InvariantCultureIgnoreCase)) return;
|
||||||
|
|
||||||
|
// Check if command invoked
|
||||||
|
if (!arg.Content.StartsWith(conf.Config.VoteCommand, StringComparison.InvariantCultureIgnoreCase)) return;
|
||||||
|
|
||||||
|
// Check if we're accepting votes. Locking here; background task may be using this.
|
||||||
|
bool cooldown;
|
||||||
|
bool voteCounted = false;
|
||||||
|
string newChannelName = null;
|
||||||
|
lock (conf)
|
||||||
|
{
|
||||||
|
if (conf.GetTemporaryChannel(guild) != null) return; // channel exists, nothing to vote for
|
||||||
|
cooldown = conf.Voting.IsInCooldown();
|
||||||
|
if (!cooldown)
|
||||||
|
{
|
||||||
|
voteCounted = conf.Voting.AddVote(arg.Author.Id, out var voteCount);
|
||||||
|
if (voteCount >= conf.Config.VotePassThreshold)
|
||||||
|
{
|
||||||
|
newChannelName = conf.Config.TempChannelName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare new temporary channel while we're still locking state
|
||||||
|
if (newChannelName != null) conf.TempChannelLastActivity = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cooldown)
|
||||||
|
{
|
||||||
|
await arg.Channel.SendMessageAsync(":x: Cooldown in effect. Try again later.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!voteCounted)
|
||||||
|
{
|
||||||
|
await arg.Channel.SendMessageAsync(":x: You have already voted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newChannelName != null)
|
||||||
|
{
|
||||||
|
RestTextChannel newCh;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newCh = await guild.CreateTextChannelAsync(newChannelName);
|
||||||
|
}
|
||||||
|
catch (Discord.Net.HttpException ex)
|
||||||
|
{
|
||||||
|
await Log($"Failed to create temporary channel: {ex.Message}");
|
||||||
|
await arg.Channel.SendMessageAsync(":x: Failed to create new channel! Notify the bot operator.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await newCh.SendMessageAsync($"Welcome to <#{newCh.Id}>!"
|
||||||
|
+ "\nBe aware that this channel is temporary and **will** be deleted later.");
|
||||||
|
newChannelName = newCh.Id.ToString(); // For use in the confirmation message
|
||||||
|
}
|
||||||
|
|
||||||
|
await arg.Channel.SendMessageAsync(":white_check_mark: Channel creation vote has been counted."
|
||||||
|
+ (newChannelName != null ? $"\n<#{newChannelName}> has been created!" : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Listens for any message sent to the temporary channel.
|
||||||
|
/// Updates the corresponding internal value.
|
||||||
|
/// </summary>
|
||||||
|
private Task TemporaryChannelActivityCheck(SocketMessage arg)
|
||||||
|
{
|
||||||
|
if (arg.Author.IsBot) return Task.CompletedTask;
|
||||||
|
var guild = (arg.Channel as SocketTextChannel)?.Guild;
|
||||||
|
if (guild == null) return Task.CompletedTask;
|
||||||
|
var conf = GetState<GuildInformation>(guild.Id);
|
||||||
|
if (conf == null) return Task.CompletedTask;
|
||||||
|
|
||||||
|
lock (conf)
|
||||||
|
{
|
||||||
|
var tch = conf.GetTemporaryChannel(guild);
|
||||||
|
if (arg.Channel.Name == tch.Name)
|
||||||
|
{
|
||||||
|
conf.TempChannelLastActivity = DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
#region Background tasks
|
#region Background tasks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Two functions: Removes expired channels, and announces expired votes.
|
/// Two functions: Removes expired channels, and announces expired votes.
|
||||||
|
|
|
@ -23,12 +23,17 @@ namespace Noikoio.RegexBot.Module.VoteTempChannel
|
||||||
/// Counts a user vote.
|
/// Counts a user vote.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>False if the user already has a vote counted.</returns>
|
/// <returns>False if the user already has a vote counted.</returns>
|
||||||
public bool AddVote(ulong id)
|
public bool AddVote(ulong id, out int voteCount)
|
||||||
{
|
{
|
||||||
// Mark the start of a new session, if applicable.
|
// Mark the start of a new session, if applicable.
|
||||||
if (_votes.Count == 0) _initialVoteTime = DateTimeOffset.UtcNow;
|
if (_votes.Count == 0) _initialVoteTime = DateTimeOffset.UtcNow;
|
||||||
if (_votes.Contains(id)) return false;
|
if (_votes.Contains(id))
|
||||||
|
{
|
||||||
|
voteCount = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
_votes.Add(id);
|
_votes.Add(id);
|
||||||
|
voteCount = _votes.Count;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue