Ported EntryAutoRole from RegexBot
Untested and missing some error handling at the moment.
This commit is contained in:
parent
9efb35a046
commit
6f7ffda63b
3 changed files with 236 additions and 0 deletions
|
@ -2,6 +2,7 @@
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Kerobot.Common
|
namespace Kerobot.Common
|
||||||
{
|
{
|
||||||
|
@ -117,6 +118,7 @@ namespace Kerobot.Common
|
||||||
/// <param name="id">If known, outputs the ID of the corresponding entity.</param>
|
/// <param name="id">If known, outputs the ID of the corresponding entity.</param>
|
||||||
/// <param name="keepId">Specifies if the internal ID value should be stored if a match is found.</param>
|
/// <param name="keepId">Specifies if the internal ID value should be stored if a match is found.</param>
|
||||||
/// <returns>True if the ID is known.</returns>
|
/// <returns>True if the ID is known.</returns>
|
||||||
|
[Obsolete]
|
||||||
public bool TryResolve(SocketGuild searchGuild, out ulong id, bool keepId, EntityType searchType)
|
public bool TryResolve(SocketGuild searchGuild, out ulong id, bool keepId, EntityType searchType)
|
||||||
{
|
{
|
||||||
if (Id.HasValue)
|
if (Id.HasValue)
|
||||||
|
@ -215,5 +217,35 @@ namespace Kerobot.Common
|
||||||
else
|
else
|
||||||
return $"{pf}{Name}";
|
return $"{pf}{Name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Helper methods
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to find the corresponding role within the given guild.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guild">The guild in which to search for the role.</param>
|
||||||
|
/// <param name="updateMissingID">
|
||||||
|
/// Specifies if this EntityName instance should keep the snowflake ID of the
|
||||||
|
/// corresponding role found in this guild, if it is not already known by this instance.
|
||||||
|
/// </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public SocketRole FindRoleIn(SocketGuild guild, bool updateMissingID = false)
|
||||||
|
{
|
||||||
|
if (this.Type != EntityType.Role)
|
||||||
|
throw new ArgumentException("This EntityName instance must correspond to a Role.");
|
||||||
|
|
||||||
|
bool dirty = false; // flag to update ID if possible regardless of updateMissingID setting
|
||||||
|
if (this.Id.HasValue)
|
||||||
|
{
|
||||||
|
var role = guild.GetRole(Id.Value);
|
||||||
|
if (role != null) return role;
|
||||||
|
else dirty = true; // only set if ID already existed but is now invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = guild.Roles.FirstOrDefault(rq => string.Equals(rq.Name, this.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (r != null && (updateMissingID || dirty)) this.Id = r.Id;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
127
Modules-PublicInstance/EntryRole/EntryRole.cs
Normal file
127
Modules-PublicInstance/EntryRole/EntryRole.cs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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(JToken config)
|
||||||
|
{
|
||||||
|
if (config == null) return null;
|
||||||
|
|
||||||
|
// preserve previously running timers?
|
||||||
|
// research: can GetState be called here or is it undefined?
|
||||||
|
|
||||||
|
if (config.Type != JTokenType.Object)
|
||||||
|
throw new ModuleLoadException("Configuration is not properly defined.");
|
||||||
|
|
||||||
|
return Task.FromResult<object>(new GuildData((JObject)config));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 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)
|
||||||
|
{
|
||||||
|
// Notify of this failure.
|
||||||
|
string failList = "";
|
||||||
|
foreach (var item in gusers) failList += $", {item.Username}#{item.Discriminator}";
|
||||||
|
|
||||||
|
await LogAsync(g.Id, "Unable to find role to apply. (Was the role deleted?) " +
|
||||||
|
"Failed to set role to the following users: " + failList.Substring(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply roles
|
||||||
|
foreach (var item in gusers)
|
||||||
|
{
|
||||||
|
// TODO exception handling and notification on forbidden
|
||||||
|
if (item.Roles.Contains(targetRole)) continue;
|
||||||
|
await item.AddRoleAsync(targetRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
Modules-PublicInstance/EntryRole/GuildData.cs
Normal file
77
Modules-PublicInstance/EntryRole/GuildData.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
using Kerobot.Common;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Kerobot.Modules.EntryRole
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains configuration data as well as per-guild timers for those awaiting role assignment.
|
||||||
|
/// </summary>
|
||||||
|
class GuildData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lock on self.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<ulong, DateTimeOffset> WaitingList { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Role to apply.
|
||||||
|
/// </summary>
|
||||||
|
public EntityName TargetRole { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Time to wait until applying the role, in seconds.
|
||||||
|
/// </summary>
|
||||||
|
public int WaitTime { get; }
|
||||||
|
|
||||||
|
const int WaitTimeMax = 600; // 10 minutes
|
||||||
|
|
||||||
|
public GuildData(JObject conf)
|
||||||
|
{
|
||||||
|
var cfgRole = conf["Role"]?.Value<string>();
|
||||||
|
if (string.IsNullOrWhiteSpace(cfgRole))
|
||||||
|
throw new ModuleLoadException("Role value not specified.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TargetRole = new EntityName(cfgRole);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
throw new ModuleLoadException("Role config value was not properly specified to be a role.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WaitTime = conf["WaitTime"].Value<int>();
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
throw new ModuleLoadException("WaitTime value not specified.");
|
||||||
|
}
|
||||||
|
catch (InvalidCastException)
|
||||||
|
{
|
||||||
|
throw new ModuleLoadException("WaitTime value must be a number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WaitTime > WaitTimeMax)
|
||||||
|
{
|
||||||
|
// don't silently correct it
|
||||||
|
throw new ModuleLoadException($"WaitTime value may not exceed {WaitTimeMax} seconds.");
|
||||||
|
}
|
||||||
|
if (WaitTime < 0)
|
||||||
|
{
|
||||||
|
throw new ModuleLoadException("WaitTime value may not be negative.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitlistAdd(ulong userId)
|
||||||
|
{
|
||||||
|
lock (WaitingList) WaitingList.Add(userId, DateTimeOffset.UtcNow.AddSeconds(WaitTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitlistRemove(ulong userId)
|
||||||
|
{
|
||||||
|
lock (WaitingList) WaitingList.Remove(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue