RegexBot/Modules-SelfHosted/EntryRole/EntryRole.cs
Noikoio c1e8f6699a Moved EntryRole to self-hosted
As it's currently implemented, it may cause problems for larger guilds
if any error is encountered. Additionally, it requires that the bot keep
tabs on all users on entry which may bring unnecessary extra load on the
host.

Additionally, added some error handling.
2019-06-20 17:35:50 -07:00

147 lines
5.1 KiB
C#

using Discord.WebSocket;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Kerobot.Modules.EntryRole
{
/// <summary>
/// Automatically sets a role onto users entering the guild.
/// </summary>
// TODO add persistent role support, make it an option
[KerobotModule]
public class EntryRole : ModuleBase
{
readonly Task _workerTask;
readonly CancellationTokenSource _workerTaskToken; // TODO make use of this when possible
public EntryRole(Kerobot kb) : base(kb)
{
DiscordClient.UserJoined += DiscordClient_UserJoined;
DiscordClient.UserLeft += DiscordClient_UserLeft;
_workerTaskToken = new CancellationTokenSource();
_workerTask = Task.Factory.StartNew(RoleApplyWorker, _workerTaskToken.Token,
TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
private Task DiscordClient_UserJoined(SocketGuildUser arg)
{
GetGuildState<GuildData>(arg.Guild.Id)?.WaitlistAdd(arg.Id);
return Task.CompletedTask;
}
private Task DiscordClient_UserLeft(SocketGuildUser arg)
{
GetGuildState<GuildData>(arg.Guild.Id)?.WaitlistRemove(arg.Id);
return Task.CompletedTask;
}
public override Task<object> CreateGuildStateAsync(ulong guildID, JToken config)
{
if (config == null) return null;
if (config.Type != JTokenType.Object)
throw new ModuleLoadException("Configuration is not properly defined.");
// Attempt to preserve existing timer list on reload
var oldconf = GetGuildState<GuildData>(guildID);
if (oldconf == null) return Task.FromResult<object>(new GuildData((JObject)config));
else return Task.FromResult<object>(new GuildData((JObject)config, oldconf.WaitingList));
}
/// <summary>
/// Main worker task.
/// </summary>
private async Task RoleApplyWorker()
{
while (!_workerTaskToken.IsCancellationRequested)
{
await Task.Delay(5000);
var subworkers = new List<Task>();
foreach (var g in DiscordClient.Guilds)
{
subworkers.Add(RoleApplyGuildSubWorker(g));
}
Task.WaitAll(subworkers.ToArray());
}
}
/// <summary>
/// Guild-specific processing by worker task.
/// </summary>
internal async Task RoleApplyGuildSubWorker(SocketGuild g)
{
var gconf = GetGuildState<GuildData>(g.Id);
if (gconf == null) return;
// Get list of users to be affected
ulong[] userIds;
lock (gconf.WaitingList)
{
if (gconf.WaitingList.Count == 0) return;
var now = DateTimeOffset.UtcNow;
var queryIds = from item in gconf.WaitingList
where item.Value > now
select item.Key;
userIds = queryIds.ToArray();
foreach (var item in userIds) gconf.WaitingList.Remove(item);
}
var gusers = new List<SocketGuildUser>();
foreach (var item in userIds)
{
var gu = g.GetUser(item);
if (gu == null) continue; // silently drop unknown users (is this fine?)
gusers.Add(gu);
}
if (gusers.Count == 0) return;
// Attempt to get role.
var targetRole = gconf.TargetRole.FindRoleIn(g, true);
if (targetRole == null)
{
await ReportFailure(g.Id, "Unable to determine role to be applied. Does it still exist?", gusers);
return;
}
// Apply roles
try
{
foreach (var item in gusers)
{
if (item.Roles.Contains(targetRole)) continue;
await item.AddRoleAsync(targetRole);
}
}
catch (Discord.Net.HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
{
await ReportFailure(g.Id, "Unable to set role due to a permissions issue.", gusers);
}
}
private async Task ReportFailure(ulong gid, string message, IEnumerable<SocketGuildUser> failedUserList)
{
var failList = new StringBuilder();
var count = 0;
foreach (var item in failedUserList) {
failList.Append($", {item.Username}#{item.Discriminator}");
count++;
if (count > 5)
{
failList.Append($"and {count} other(s).");
break;
}
}
failList.Remove(0, 2);
await LogAsync(gid, message + " Failed while attempting to set role on the following users: " + failList.ToString());
}
}
}