mirror of
https://github.com/NoiTheCat/WorldTime.git
synced 2024-11-24 01:14:13 +00:00
Pull up CommandsCommon into CommandsText
This commit is contained in:
parent
f8d5aef4aa
commit
b9e05b37f9
2 changed files with 115 additions and 125 deletions
|
@ -1,121 +0,0 @@
|
||||||
using NodaTime;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace WorldTime;
|
|
||||||
|
|
||||||
internal abstract class CommandsCommon {
|
|
||||||
protected readonly Database _database;
|
|
||||||
protected readonly WorldTime _instance;
|
|
||||||
|
|
||||||
private static readonly ReadOnlyDictionary<string, string> _tzNameMap;
|
|
||||||
|
|
||||||
protected const string ErrInvalidZone = ":x: Not a valid zone name."
|
|
||||||
+ " To find your time zone, refer to: <https://kevinnovak.github.io/Time-Zone-Picker/>.";
|
|
||||||
protected const string ErrTargetUserNotFound = ":x: Unable to find the target user.";
|
|
||||||
protected const string ErrNoUserCache = ":warning: Please try the command again.";
|
|
||||||
protected const int MaxSingleLineLength = 750;
|
|
||||||
protected const int MaxSingleOutputLength = 900;
|
|
||||||
|
|
||||||
static CommandsCommon() {
|
|
||||||
Dictionary<string, string> tzNameMap = new(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var name in DateTimeZoneProviders.Tzdb.Ids) tzNameMap.Add(name, name);
|
|
||||||
_tzNameMap = new(tzNameMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandsCommon(Database database, WorldTime instance) {
|
|
||||||
_database = database;
|
|
||||||
_instance = instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string displaying the current time in the given time zone.
|
|
||||||
/// The result begins with four numbers for sorting purposes. Must be trimmed before output.
|
|
||||||
/// </summary>
|
|
||||||
protected static string TzPrint(string zone) {
|
|
||||||
var tzdb = DateTimeZoneProviders.Tzdb;
|
|
||||||
DateTimeZone tz = tzdb.GetZoneOrNull(zone)!;
|
|
||||||
if (tz == null) throw new Exception("Encountered unknown time zone: " + zone);
|
|
||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant().InZone(tz);
|
|
||||||
var sortpfx = now.ToString("MMdd", DateTimeFormatInfo.InvariantInfo);
|
|
||||||
var fullstr = now.ToString("dd'-'MMM' 'HH':'mm' 'x' (UTC'o<g>')'", DateTimeFormatInfo.InvariantInfo);
|
|
||||||
return $"{sortpfx}● `{fullstr}`";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks given time zone input. Returns a valid string for use with NodaTime, or null.
|
|
||||||
/// </summary>
|
|
||||||
protected static string? ParseTimeZone(string tzinput) {
|
|
||||||
if (tzinput.Equals("Asia/Calcutta", StringComparison.OrdinalIgnoreCase)) tzinput = "Asia/Kolkata";
|
|
||||||
if (_tzNameMap.TryGetValue(tzinput, out var name)) return name;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Formats a user's name to a consistent, readable format which makes use of their nickname.
|
|
||||||
/// </summary>
|
|
||||||
protected static string FormatName(SocketGuildUser user) {
|
|
||||||
static string escapeFormattingCharacters(string input) {
|
|
||||||
var result = new StringBuilder();
|
|
||||||
foreach (var c in input) {
|
|
||||||
if (c is '\\' or '_' or '~' or '*' or '@') {
|
|
||||||
result.Append('\\');
|
|
||||||
}
|
|
||||||
result.Append(c);
|
|
||||||
}
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
var username = escapeFormattingCharacters(user.Username);
|
|
||||||
if (user.Nickname != null) {
|
|
||||||
return $"**{escapeFormattingCharacters(user.Nickname)}** ({username}#{user.Discriminator})";
|
|
||||||
}
|
|
||||||
return $"**{username}**#{user.Discriminator}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given user can be considered a guild admin ('Manage Server' is set).
|
|
||||||
/// </summary>
|
|
||||||
protected static bool IsUserAdmin(SocketGuildUser user)
|
|
||||||
=> user.GuildPermissions.Administrator || user.GuildPermissions.ManageGuild;
|
|
||||||
// TODO port modrole feature from BB, implement in here
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the member cache for the specified guild needs to be filled, and sends a request if needed.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Due to a quirk in Discord.Net, the user cache cannot be filled until the command handler is no longer executing,
|
|
||||||
/// regardless of if the request runs on its own thread.
|
|
||||||
/// </remarks>
|
|
||||||
/// <returns>
|
|
||||||
/// True if the guild's members are already downloaded. If false, the command handler must notify the user.
|
|
||||||
/// </returns>
|
|
||||||
protected static async Task<bool> AreUsersDownloadedAsync(SocketGuild guild) {
|
|
||||||
if (HasMostMembersDownloaded(guild)) return true;
|
|
||||||
else {
|
|
||||||
// Event handler hangs if awaited normally or used with Task.Run
|
|
||||||
await Task.Factory.StartNew(guild.DownloadUsersAsync).ConfigureAwait(false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An alternative to <see cref="SocketGuild.HasAllMembers"/>.
|
|
||||||
/// Returns true if *most* members have been downloaded.
|
|
||||||
/// Used as a workaround check due to Discord.Net occasionally unable to actually download all members.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Copied directly from BirthdayBot. Try to coordinate changes between projects...</remarks>
|
|
||||||
private static bool HasMostMembersDownloaded(SocketGuild guild) {
|
|
||||||
if (guild.HasAllMembers) return true;
|
|
||||||
if (guild.MemberCount > 30) {
|
|
||||||
// For guilds of size over 30, require 85% or more of the members to be known
|
|
||||||
// (26/30, 42/50, 255/300, etc)
|
|
||||||
return guild.DownloadedMemberCount >= (int)(guild.MemberCount * 0.85);
|
|
||||||
} else {
|
|
||||||
// For smaller guilds, fail if two or more members are missing
|
|
||||||
return guild.MemberCount - guild.DownloadedMemberCount <= 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
117
CommandsText.cs
117
CommandsText.cs
|
@ -1,9 +1,12 @@
|
||||||
using System.Text;
|
using NodaTime;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace WorldTime;
|
namespace WorldTime;
|
||||||
|
|
||||||
internal class CommandsText : CommandsCommon {
|
internal class CommandsText {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public const string CommandPrefix = "tt.";
|
public const string CommandPrefix = "tt.";
|
||||||
#else
|
#else
|
||||||
|
@ -12,16 +15,32 @@ internal class CommandsText : CommandsCommon {
|
||||||
delegate Task Command(SocketTextChannel channel, SocketGuildUser sender, SocketMessage message);
|
delegate Task Command(SocketTextChannel channel, SocketGuildUser sender, SocketMessage message);
|
||||||
|
|
||||||
private readonly Dictionary<string, Command> _commands;
|
private readonly Dictionary<string, Command> _commands;
|
||||||
|
private readonly Database _database;
|
||||||
|
private readonly WorldTime _instance;
|
||||||
|
|
||||||
private static readonly Regex _userExplicit;
|
private static readonly Regex _userExplicit;
|
||||||
private static readonly Regex _userMention;
|
private static readonly Regex _userMention;
|
||||||
|
private static readonly ReadOnlyDictionary<string, string> _tzNameMap;
|
||||||
|
|
||||||
|
private const string ErrInvalidZone = ":x: Not a valid zone name."
|
||||||
|
+ " To find your time zone, refer to: <https://kevinnovak.github.io/Time-Zone-Picker/>.";
|
||||||
|
private const string ErrTargetUserNotFound = ":x: Unable to find the target user.";
|
||||||
|
private const string ErrNoUserCache = ":warning: Please try the command again.";
|
||||||
|
private const int MaxSingleLineLength = 750;
|
||||||
|
private const int MaxSingleOutputLength = 900;
|
||||||
|
|
||||||
static CommandsText() {
|
static CommandsText() {
|
||||||
_userExplicit = new Regex(@"(.+)#(\d{4})", RegexOptions.Compiled);
|
_userExplicit = new Regex(@"(.+)#(\d{4})", RegexOptions.Compiled);
|
||||||
_userMention = new Regex(@"\!?(\d+)>", RegexOptions.Compiled);
|
_userMention = new Regex(@"\!?(\d+)>", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
Dictionary<string, string> tzNameMap = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var name in DateTimeZoneProviders.Tzdb.Ids) tzNameMap.Add(name, name);
|
||||||
|
_tzNameMap = new(tzNameMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandsText(WorldTime inst, Database db) : base(db, inst) {
|
public CommandsText(WorldTime inst, Database db) {
|
||||||
|
_instance = inst;
|
||||||
|
_database = db;
|
||||||
_commands = new(StringComparer.OrdinalIgnoreCase) {
|
_commands = new(StringComparer.OrdinalIgnoreCase) {
|
||||||
{ "help", CmdHelp },
|
{ "help", CmdHelp },
|
||||||
{ "list", CmdList },
|
{ "list", CmdList },
|
||||||
|
@ -274,4 +293,96 @@ internal class CommandsText : CommandsCommon {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Helper methods
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string displaying the current time in the given time zone.
|
||||||
|
/// The result begins with four numbers for sorting purposes. Must be trimmed before output.
|
||||||
|
/// </summary>
|
||||||
|
private static string TzPrint(string zone) {
|
||||||
|
var tzdb = DateTimeZoneProviders.Tzdb;
|
||||||
|
DateTimeZone tz = tzdb.GetZoneOrNull(zone)!;
|
||||||
|
if (tz == null) throw new Exception("Encountered unknown time zone: " + zone);
|
||||||
|
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant().InZone(tz);
|
||||||
|
var sortpfx = now.ToString("MMdd", DateTimeFormatInfo.InvariantInfo);
|
||||||
|
var fullstr = now.ToString("dd'-'MMM' 'HH':'mm' 'x' (UTC'o<g>')'", DateTimeFormatInfo.InvariantInfo);
|
||||||
|
return $"{sortpfx}● `{fullstr}`";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks given time zone input. Returns a valid string for use with NodaTime, or null.
|
||||||
|
/// </summary>
|
||||||
|
private static string? ParseTimeZone(string tzinput) {
|
||||||
|
if (tzinput.Equals("Asia/Calcutta", StringComparison.OrdinalIgnoreCase)) tzinput = "Asia/Kolkata";
|
||||||
|
if (_tzNameMap.TryGetValue(tzinput, out var name)) return name;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats a user's name to a consistent, readable format which makes use of their nickname.
|
||||||
|
/// </summary>
|
||||||
|
private static string FormatName(SocketGuildUser user) {
|
||||||
|
static string escapeFormattingCharacters(string input) {
|
||||||
|
var result = new StringBuilder();
|
||||||
|
foreach (var c in input) {
|
||||||
|
if (c is '\\' or '_' or '~' or '*' or '@') {
|
||||||
|
result.Append('\\');
|
||||||
|
}
|
||||||
|
result.Append(c);
|
||||||
|
}
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var username = escapeFormattingCharacters(user.Username);
|
||||||
|
if (user.Nickname != null) {
|
||||||
|
return $"**{escapeFormattingCharacters(user.Nickname)}** ({username}#{user.Discriminator})";
|
||||||
|
}
|
||||||
|
return $"**{username}**#{user.Discriminator}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given user can be considered a guild admin ('Manage Server' is set).
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsUserAdmin(SocketGuildUser user)
|
||||||
|
=> user.GuildPermissions.Administrator || user.GuildPermissions.ManageGuild;
|
||||||
|
// TODO port modrole feature from BB, implement in here
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the member cache for the specified guild needs to be filled, and sends a request if needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Due to a quirk in Discord.Net, the user cache cannot be filled until the command handler is no longer executing,
|
||||||
|
/// regardless of if the request runs on its own thread.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the guild's members are already downloaded. If false, the command handler must notify the user.
|
||||||
|
/// </returns>
|
||||||
|
private static async Task<bool> AreUsersDownloadedAsync(SocketGuild guild) {
|
||||||
|
if (HasMostMembersDownloaded(guild)) return true;
|
||||||
|
else {
|
||||||
|
// Event handler hangs if awaited normally or used with Task.Run
|
||||||
|
await Task.Factory.StartNew(guild.DownloadUsersAsync).ConfigureAwait(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An alternative to <see cref="SocketGuild.HasAllMembers"/>.
|
||||||
|
/// Returns true if *most* members have been downloaded.
|
||||||
|
/// Used as a workaround check due to Discord.Net occasionally unable to actually download all members.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Copied directly from BirthdayBot. Try to coordinate changes between projects...</remarks>
|
||||||
|
private static bool HasMostMembersDownloaded(SocketGuild guild) {
|
||||||
|
if (guild.HasAllMembers) return true;
|
||||||
|
if (guild.MemberCount > 30) {
|
||||||
|
// For guilds of size over 30, require 85% or more of the members to be known
|
||||||
|
// (26/30, 42/50, 255/300, etc)
|
||||||
|
return guild.DownloadedMemberCount >= (int)(guild.MemberCount * 0.85);
|
||||||
|
} else {
|
||||||
|
// For smaller guilds, fail if two or more members are missing
|
||||||
|
return guild.MemberCount - guild.DownloadedMemberCount <= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue