Update to use GuildConfiguration; small other tweaks

This commit is contained in:
Noi 2020-07-19 19:07:23 -07:00
parent 6d7db85310
commit 23dd15d741
2 changed files with 93 additions and 106 deletions

View file

@ -35,7 +35,7 @@ namespace BirthdayBot.UserInterface
new CommandDocumentation(new string[] { "when" }, "Displays the given user's birthday information.", null); new CommandDocumentation(new string[] { "when" }, "Displays the given user's birthday information.", null);
#endregion #endregion
private async Task CmdWhen(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser) private async Task CmdWhen(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{ {
// Requires a parameter // Requires a parameter
if (param.Length == 1) if (param.Length == 1)
@ -53,8 +53,7 @@ namespace BirthdayBot.UserInterface
SocketGuildUser searchTarget = null; SocketGuildUser searchTarget = null;
ulong searchId = 0; if (!TryGetUserId(search, out ulong searchId)) // ID lookup
if (!TryGetUserId(search, out searchId)) // ID lookup
{ {
// name lookup without discriminator // name lookup without discriminator
foreach (var searchuser in reqChannel.Guild.Users) foreach (var searchuser in reqChannel.Guild.Users)
@ -76,9 +75,8 @@ namespace BirthdayBot.UserInterface
return; return;
} }
var users = Instance.GuildCache[reqChannel.Guild.Id].Users; var searchTargetData = await GuildUserConfiguration.LoadAsync(reqChannel.Guild.Id, searchId);
var searchTargetData = users.FirstOrDefault(u => u.UserId == searchTarget.Id); if (!searchTargetData.IsKnown)
if (searchTargetData == null)
{ {
await reqChannel.SendMessageAsync("I do not have birthday information for that user."); await reqChannel.SendMessageAsync("I do not have birthday information for that user.");
return; return;
@ -93,10 +91,10 @@ namespace BirthdayBot.UserInterface
} }
// Creates a file with all birthdays. // Creates a file with all birthdays.
private async Task CmdList(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser) private async Task CmdList(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{ {
// For now, we're restricting this command to moderators only. This may turn into an option later. // For now, we're restricting this command to moderators only. This may turn into an option later.
if (!Instance.GuildCache[reqChannel.Guild.Id].IsUserModerator(reqUser)) if (!gconf.IsBotModerator(reqUser))
{ {
// Do not add detailed usage information to this error message. // Do not add detailed usage information to this error message.
await reqChannel.SendMessageAsync(":x: Only bot moderators may use this command."); await reqChannel.SendMessageAsync(":x: Only bot moderators may use this command.");
@ -120,7 +118,7 @@ namespace BirthdayBot.UserInterface
return; return;
} }
var bdlist = await LoadList(reqChannel.Guild, false); var bdlist = await GetSortedUsersAsync(reqChannel.Guild);
var filepath = Path.GetTempPath() + "birthdaybot-" + reqChannel.Guild.Id; var filepath = Path.GetTempPath() + "birthdaybot-" + reqChannel.Guild.Id;
string fileoutput; string fileoutput;
@ -158,13 +156,13 @@ namespace BirthdayBot.UserInterface
// "Recent and upcoming birthdays" // "Recent and upcoming birthdays"
// The 'recent' bit removes time zone ambiguity and spares us from extra time zone processing here // The 'recent' bit removes time zone ambiguity and spares us from extra time zone processing here
private async Task CmdUpcoming(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser) private async Task CmdUpcoming(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{ {
var now = DateTimeOffset.UtcNow; var now = DateTimeOffset.UtcNow;
var search = DateIndex(now.Month, now.Day) - 8; // begin search 8 days prior to current date UTC var search = DateIndex(now.Month, now.Day) - 8; // begin search 8 days prior to current date UTC
if (search <= 0) search = 366 - Math.Abs(search); if (search <= 0) search = 366 - Math.Abs(search);
var query = await LoadList(reqChannel.Guild, true); var query = await GetSortedUsersAsync(reqChannel.Guild);
var output = new StringBuilder(); var output = new StringBuilder();
var resultCount = 0; var resultCount = 0;
@ -219,20 +217,15 @@ namespace BirthdayBot.UserInterface
/// Fetches all guild birthdays and places them into an easily usable structure. /// Fetches all guild birthdays and places them into an easily usable structure.
/// Users currently not in the guild are not included in the result. /// Users currently not in the guild are not included in the result.
/// </summary> /// </summary>
private async Task<List<ListItem>> LoadList(SocketGuild guild, bool escapeFormat) private async Task<List<ListItem>> GetSortedUsersAsync(SocketGuild guild)
{ {
var ping = Instance.GuildCache[guild.Id].AnnouncePing; using var db = await Database.OpenConnectionAsync();
using var c = db.CreateCommand();
using (var db = await BotConfig.DatabaseSettings.OpenConnectionAsync()) c.CommandText = "select user_id, birth_month, birth_day from " + GuildUserConfiguration.BackingTable
{
using (var c = db.CreateCommand())
{
c.CommandText = "select user_id, birth_month, birth_day from " + GuildUserSettings.BackingTable
+ " where guild_id = @Gid order by birth_month, birth_day"; + " where guild_id = @Gid order by birth_month, birth_day";
c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guild.Id; c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guild.Id;
c.Prepare(); c.Prepare();
using (var r = await c.ExecuteReaderAsync()) using var r = await c.ExecuteReaderAsync();
{
var result = new List<ListItem>(); var result = new List<ListItem>();
while (await r.ReadAsync()) while (await r.ReadAsync())
{ {
@ -241,7 +234,7 @@ namespace BirthdayBot.UserInterface
var day = r.GetInt32(2); var day = r.GetInt32(2);
var guildUser = guild.GetUser(id); var guildUser = guild.GetUser(id);
if (guildUser == null) continue; // Skip users not in guild if (guildUser == null) continue; // Skip user not in guild
result.Add(new ListItem() result.Add(new ListItem()
{ {
@ -254,9 +247,6 @@ namespace BirthdayBot.UserInterface
} }
return result; return result;
} }
}
}
}
private string ListExportNormal(SocketGuildChannel channel, IEnumerable<ListItem> list) private string ListExportNormal(SocketGuildChannel channel, IEnumerable<ListItem> list)
{ {
@ -295,7 +285,7 @@ namespace BirthdayBot.UserInterface
result.Append(','); result.Append(',');
if (user.Nickname != null) result.Append(user.Nickname); if (user.Nickname != null) result.Append(user.Nickname);
result.Append(','); result.Append(',');
result.Append($"{Common.MonthNames[item.BirthMonth]}-{item.BirthDay.ToString("00")}"); result.Append($"{Common.MonthNames[item.BirthMonth]}-{item.BirthDay:00}");
result.Append(','); result.Append(',');
result.Append(item.BirthMonth); result.Append(item.BirthMonth);
result.Append(','); result.Append(',');

View file

@ -1,4 +1,5 @@
using Discord.WebSocket; using BirthdayBot.Data;
using Discord.WebSocket;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -11,7 +12,7 @@ namespace BirthdayBot.UserInterface
{ {
private static readonly string ConfErrorPostfix = private static readonly string ConfErrorPostfix =
$" Refer to the `{CommandPrefix}help-config` command for information on this command's usage."; $" Refer to the `{CommandPrefix}help-config` command for information on this command's usage.";
private delegate Task ConfigSubcommand(string[] param, SocketTextChannel reqChannel); private delegate Task ConfigSubcommand(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel);
private readonly Dictionary<string, ConfigSubcommand> _subcommands; private readonly Dictionary<string, ConfigSubcommand> _subcommands;
private readonly Dictionary<string, CommandHandler> _usercommands; private readonly Dictionary<string, CommandHandler> _usercommands;
@ -57,11 +58,10 @@ namespace BirthdayBot.UserInterface
"Perform certain commands on behalf of another user.", null); "Perform certain commands on behalf of another user.", null);
#endregion #endregion
private async Task CmdConfigDispatch(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser) private async Task CmdConfigDispatch(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{ {
// Ignore those without the proper permissions. // Ignore those without the proper permissions.
// Requires either the manage guild permission or to be in the moderators role if (!gconf.IsBotModerator(reqUser))
if (!Instance.GuildCache[reqUser.Guild.Id].IsUserModerator(reqUser))
{ {
await reqChannel.SendMessageAsync(":x: This command may only be used by bot moderators."); await reqChannel.SendMessageAsync(":x: This command may only be used by bot moderators.");
return; return;
@ -73,7 +73,7 @@ namespace BirthdayBot.UserInterface
return; return;
} }
// Special case: Restrict 'modrole' to only guild managers // Special case: Restrict 'modrole' to only guild managers, not mods
if (string.Equals(param[1], "modrole", StringComparison.OrdinalIgnoreCase) && !reqUser.GuildPermissions.ManageGuild) if (string.Equals(param[1], "modrole", StringComparison.OrdinalIgnoreCase) && !reqUser.GuildPermissions.ManageGuild)
{ {
await reqChannel.SendMessageAsync(":x: This command may only be used by those with the `Manage Server` permission."); await reqChannel.SendMessageAsync(":x: This command may only be used by those with the `Manage Server` permission.");
@ -86,13 +86,13 @@ namespace BirthdayBot.UserInterface
if (_subcommands.TryGetValue(confparam[0], out ConfigSubcommand h)) if (_subcommands.TryGetValue(confparam[0], out ConfigSubcommand h))
{ {
await h(confparam, reqChannel); await h(confparam, gconf, reqChannel);
} }
} }
#region Configuration sub-commands #region Configuration sub-commands
// Birthday role set // Birthday role set
private async Task ScmdRole(string[] param, SocketTextChannel reqChannel) private async Task ScmdRole(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel)
{ {
if (param.Length != 2) if (param.Length != 2)
{ {
@ -112,13 +112,14 @@ namespace BirthdayBot.UserInterface
} }
else else
{ {
Instance.GuildCache[guild.Id].UpdateRole(role.Id); gconf.RoleId = role.Id;
await gconf.UpdateAsync();
await reqChannel.SendMessageAsync($":white_check_mark: The birthday role has been set as **{role.Name}**."); await reqChannel.SendMessageAsync($":white_check_mark: The birthday role has been set as **{role.Name}**.");
} }
} }
// Ping setting // Ping setting
private async Task ScmdPing(string[] param, SocketTextChannel reqChannel) private async Task ScmdPing(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel)
{ {
const string InputErr = ":x: You must specify either `off` or `on` in this setting."; const string InputErr = ":x: You must specify either `off` or `on` in this setting.";
if (param.Length != 2) if (param.Length != 2)
@ -146,26 +147,25 @@ namespace BirthdayBot.UserInterface
return; return;
} }
Instance.GuildCache[reqChannel.Guild.Id].UpdateAnnouncePing(setting); gconf.AnnouncePing = setting;
await gconf.UpdateAsync();
await reqChannel.SendMessageAsync(result); await reqChannel.SendMessageAsync(result);
} }
// Announcement channel set // Announcement channel set
private async Task ScmdChannel(string[] param, SocketTextChannel reqChannel) private async Task ScmdChannel(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel)
{ {
if (param.Length == 1) if (param.Length == 1) // No extra parameter. Unset announcement channel.
{ {
// No extra parameter. Unset announcement channel.
var gi = Instance.GuildCache[reqChannel.Guild.Id];
// Extra detail: Show a unique message if a channel hadn't been set prior. // Extra detail: Show a unique message if a channel hadn't been set prior.
if (!gi.AnnounceChannelId.HasValue) if (!gconf.AnnounceChannelId.HasValue)
{ {
await reqChannel.SendMessageAsync(":x: There is no announcement channel set. Nothing to unset."); await reqChannel.SendMessageAsync(":x: There is no announcement channel set. Nothing to unset.");
return; return;
} }
gi.UpdateAnnounceChannel(null); gconf.AnnounceChannelId = null;
await gconf.UpdateAsync();
await reqChannel.SendMessageAsync(":white_check_mark: The announcement channel has been unset."); await reqChannel.SendMessageAsync(":white_check_mark: The announcement channel has been unset.");
} }
else else
@ -204,7 +204,8 @@ namespace BirthdayBot.UserInterface
} }
// Update the value // Update the value
Instance.GuildCache[reqChannel.Guild.Id].UpdateAnnounceChannel(chId); gconf.AnnounceChannelId = chId;
await gconf.UpdateAsync();
// Report the success // Report the success
await reqChannel.SendMessageAsync($":white_check_mark: The announcement channel is now set to <#{chId}>."); await reqChannel.SendMessageAsync($":white_check_mark: The announcement channel is now set to <#{chId}>.");
@ -212,7 +213,7 @@ namespace BirthdayBot.UserInterface
} }
// Moderator role set // Moderator role set
private async Task ScmdModRole(string[] param, SocketTextChannel reqChannel) private async Task ScmdModRole(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel)
{ {
if (param.Length != 2) if (param.Length != 2)
{ {
@ -228,27 +229,26 @@ namespace BirthdayBot.UserInterface
} }
else else
{ {
Instance.GuildCache[guild.Id].UpdateModeratorRole(role.Id); gconf.ModeratorRole = role.Id;
await gconf.UpdateAsync();
await reqChannel.SendMessageAsync($":white_check_mark: The moderator role is now **{role.Name}**."); await reqChannel.SendMessageAsync($":white_check_mark: The moderator role is now **{role.Name}**.");
} }
} }
// Guild default time zone set/unset // Guild default time zone set/unset
private async Task ScmdZone(string[] param, SocketTextChannel reqChannel) private async Task ScmdZone(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel)
{ {
if (param.Length == 1) if (param.Length == 1) // No extra parameter. Unset guild default time zone.
{ {
// No extra parameter. Unset guild default time zone.
var gi = Instance.GuildCache[reqChannel.Guild.Id];
// Extra detail: Show a unique message if there is no set zone. // Extra detail: Show a unique message if there is no set zone.
if (!gi.AnnounceChannelId.HasValue) if (!gconf.AnnounceChannelId.HasValue)
{ {
await reqChannel.SendMessageAsync(":x: A default zone is not set. Nothing to unset."); await reqChannel.SendMessageAsync(":x: A default zone is not set. Nothing to unset.");
return; return;
} }
gi.UpdateTimeZone(null); gconf.TimeZone = null;
await gconf.UpdateAsync();
await reqChannel.SendMessageAsync(":white_check_mark: The default time zone preference has been removed."); await reqChannel.SendMessageAsync(":white_check_mark: The default time zone preference has been removed.");
} }
else else
@ -266,7 +266,8 @@ namespace BirthdayBot.UserInterface
} }
// Update value // Update value
Instance.GuildCache[reqChannel.Guild.Id].UpdateTimeZone(zone); gconf.TimeZone = zone;
await gconf.UpdateAsync();
// Report the success // Report the success
await reqChannel.SendMessageAsync($":white_check_mark: The server's time zone has been set to **{zone}**."); await reqChannel.SendMessageAsync($":white_check_mark: The server's time zone has been set to **{zone}**.");
@ -274,7 +275,7 @@ namespace BirthdayBot.UserInterface
} }
// Block/unblock individual non-manager users from using commands. // Block/unblock individual non-manager users from using commands.
private async Task ScmdBlock(string[] param, SocketTextChannel reqChannel) private async Task ScmdBlock(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel)
{ {
if (param.Length != 2) if (param.Length != 2)
{ {
@ -284,43 +285,41 @@ namespace BirthdayBot.UserInterface
bool doBan = param[0].ToLower() == "block"; // true = block, false = unblock bool doBan = param[0].ToLower() == "block"; // true = block, false = unblock
ulong inputId; if (!TryGetUserId(param[1], out ulong inputId))
if (!TryGetUserId(param[1], out inputId))
{ {
await reqChannel.SendMessageAsync(BadUserError); await reqChannel.SendMessageAsync(BadUserError);
return; return;
} }
var gi = Instance.GuildCache[reqChannel.Guild.Id]; var isBanned = await gconf.IsUserBlockedAsync(inputId);
var isBanned = await gi.IsUserBlockedAsync(inputId);
if (doBan) if (doBan)
{ {
if (!isBanned) if (!isBanned)
{ {
await gi.BlockUserAsync(inputId); await gconf.BlockUserAsync(inputId);
await reqChannel.SendMessageAsync(":white_check_mark: User has been blocked."); await reqChannel.SendMessageAsync(":white_check_mark: User has been blocked.");
} }
else else
{ {
// TODO bug: this is incorrectly always displayed when in moderated mode
await reqChannel.SendMessageAsync(":white_check_mark: User is already blocked."); await reqChannel.SendMessageAsync(":white_check_mark: User is already blocked.");
} }
} }
else else
{ {
if (isBanned) if (await gconf.UnblockUserAsync(inputId))
{ {
await gi.UnbanUserAsync(inputId);
await reqChannel.SendMessageAsync(":white_check_mark: User is now unblocked."); await reqChannel.SendMessageAsync(":white_check_mark: User is now unblocked.");
} }
else else
{ {
await reqChannel.SendMessageAsync(":white_check_mark: The specified user has not been blocked."); await reqChannel.SendMessageAsync(":white_check_mark: The specified user is not blocked.");
} }
} }
} }
// "moderated on/off" - Sets/unsets moderated mode. // "moderated on/off" - Sets/unsets moderated mode.
private async Task ScmdModerated(string[] param, SocketTextChannel reqChannel) private async Task ScmdModerated(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel)
{ {
if (param.Length != 2) if (param.Length != 2)
{ {
@ -334,26 +333,24 @@ namespace BirthdayBot.UserInterface
else if (parameter == "off") modSet = false; else if (parameter == "off") modSet = false;
else else
{ {
await reqChannel.SendMessageAsync(":x: Expected `on` or `off` as a parameter." + ConfErrorPostfix); await reqChannel.SendMessageAsync(":x: Expecting `on` or `off` as a parameter." + ConfErrorPostfix);
return; return;
} }
var gi = Instance.GuildCache[reqChannel.Guild.Id]; if (gconf.IsModerated == modSet)
var currentSet = gi.IsModerated;
gi.UpdateModeratedMode(modSet);
if (currentSet == modSet)
{ {
await reqChannel.SendMessageAsync($":white_check_mark: Moderated mode is already {parameter}."); await reqChannel.SendMessageAsync($":white_check_mark: Moderated mode is already {parameter}.");
} }
else else
{ {
gconf.IsModerated = modSet;
await gconf.UpdateAsync();
await reqChannel.SendMessageAsync($":white_check_mark: Moderated mode has been turned {parameter}."); await reqChannel.SendMessageAsync($":white_check_mark: Moderated mode has been turned {parameter}.");
} }
} }
// Sets/unsets custom announcement message. // Sets/unsets custom announcement message.
private async Task ScmdAnnounceMsg(string[] param, SocketTextChannel reqChannel) private async Task ScmdAnnounceMsg(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel)
{ {
var plural = param[0].ToLower().EndsWith("pl"); var plural = param[0].ToLower().EndsWith("pl");
@ -370,17 +367,21 @@ namespace BirthdayBot.UserInterface
clear = true; clear = true;
} }
Instance.GuildCache[reqChannel.Guild.Id].UpdateAnnounceMessage(newmsg, plural); (string, string) update;
const string report = ":white_check_mark: The {0} birthday announcement message has been {1}."; if (!plural) update = (newmsg, gconf.AnnounceMessages.Item2);
await reqChannel.SendMessageAsync(string.Format(report, plural ? "plural" : "singular", clear ? "reset" : "updated")); else update = (gconf.AnnounceMessages.Item1, newmsg);
gconf.AnnounceMessages = update;
await gconf.UpdateAsync();
await reqChannel.SendMessageAsync(string.Format(":white_check_mark: The {0} birthday announcement message has been {1}.",
plural ? "plural" : "singular", clear ? "reset" : "updated"));
} }
#endregion #endregion
// Execute command as another user // Execute command as another user
private async Task CmdOverride(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser) private async Task CmdOverride(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{ {
// Moderators only. As with config, silently drop if this check fails. // Moderators only. As with config, silently drop if this check fails.
if (!Instance.GuildCache[reqUser.Guild.Id].IsUserModerator(reqUser)) return; if (!gconf.IsBotModerator(reqUser)) return;
if (param.Length != 3) if (param.Length != 3)
{ {
@ -389,8 +390,7 @@ namespace BirthdayBot.UserInterface
} }
// Second parameter: determine the user to act as // Second parameter: determine the user to act as
ulong user = 0; if (!TryGetUserId(param[1], out ulong user))
if (!TryGetUserId(param[1], out user))
{ {
await reqChannel.SendMessageAsync(BadUserError, embed: DocOverride.UsageEmbed); await reqChannel.SendMessageAsync(BadUserError, embed: DocOverride.UsageEmbed);
return; return;
@ -416,8 +416,7 @@ namespace BirthdayBot.UserInterface
// Add command prefix to input, just in case. // Add command prefix to input, just in case.
overparam[0] = CommandPrefix + overparam[0].ToLower(); overparam[0] = CommandPrefix + overparam[0].ToLower();
} }
CommandHandler action = null; if (!_usercommands.TryGetValue(cmdsearch, out CommandHandler action))
if (!_usercommands.TryGetValue(cmdsearch, out action))
{ {
await reqChannel.SendMessageAsync($":x: `{cmdsearch}` is not an overridable command.", embed: DocOverride.UsageEmbed); await reqChannel.SendMessageAsync($":x: `{cmdsearch}` is not an overridable command.", embed: DocOverride.UsageEmbed);
return; return;
@ -425,15 +424,14 @@ namespace BirthdayBot.UserInterface
// Preparations complete. Run the command. // Preparations complete. Run the command.
await reqChannel.SendMessageAsync($"Executing `{cmdsearch.ToLower()}` on behalf of {overuser.Nickname ?? overuser.Username}:"); await reqChannel.SendMessageAsync($"Executing `{cmdsearch.ToLower()}` on behalf of {overuser.Nickname ?? overuser.Username}:");
await action.Invoke(overparam, reqChannel, overuser); await action.Invoke(overparam, gconf, reqChannel, overuser);
} }
// Publicly available command that immediately processes the current guild, // Publicly available command that immediately processes the current guild,
private async Task CmdTest(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser) private async Task CmdTest(string[] param, GuildConfiguration gconf, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{ {
// Moderators only. As with config, silently drop if this check fails. // Moderators only. As with config, silently drop if this check fails.
if (!Instance.GuildCache[reqUser.Guild.Id].IsUserModerator(reqUser)) return; if (!gconf.IsBotModerator(reqUser)) return;
// TODO fix this or incorporate into final output - checking existence in guild cache is a step in the process
if (param.Length != 1) if (param.Length != 1)
{ {
@ -472,8 +470,7 @@ namespace BirthdayBot.UserInterface
if (rmatch.Success) input = rmatch.Groups["snowflake"].Value; if (rmatch.Success) input = rmatch.Groups["snowflake"].Value;
// Attempt to get role by ID, or null // Attempt to get role by ID, or null
ulong rid; if (ulong.TryParse(input, out ulong rid))
if (ulong.TryParse(input, out rid))
{ {
return guild.GetRole(rid); return guild.GetRole(rid);
} }