bot: add system member list commands
This commit is contained in:
		| @@ -97,9 +97,9 @@ namespace PluralKit.Bot | |||||||
|  |  | ||||||
|             // Deliberately wrapping in an async function *without* awaiting, we don't want to "block" since this'd hold up the main loop |             // Deliberately wrapping in an async function *without* awaiting, we don't want to "block" since this'd hold up the main loop | ||||||
|             // These handlers return Task so we gotta be careful not to return the Task itself (which would then be awaited) - kinda weird design but eh |             // These handlers return Task so we gotta be careful not to return the Task itself (which would then be awaited) - kinda weird design but eh | ||||||
|             _client.MessageReceived += async (msg) => MessageReceived(msg); |             _client.MessageReceived += async (msg) => MessageReceived(msg).CatchException(HandleRuntimeError); | ||||||
|             _client.ReactionAdded += async (message, channel, reaction) => _proxy.HandleReactionAddedAsync(message, channel, reaction); |             _client.ReactionAdded += async (message, channel, reaction) => _proxy.HandleReactionAddedAsync(message, channel, reaction).CatchException(HandleRuntimeError); | ||||||
|             _client.MessageDeleted += async (message, channel) => _proxy.HandleMessageDeletedAsync(message, channel); |             _client.MessageDeleted += async (message, channel) => _proxy.HandleMessageDeletedAsync(message, channel).CatchException(HandleRuntimeError); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private async Task UpdatePeriodic() |         private async Task UpdatePeriodic() | ||||||
| @@ -110,7 +110,7 @@ namespace PluralKit.Bot | |||||||
|  |  | ||||||
|         private async Task Ready() |         private async Task Ready() | ||||||
|         { |         { | ||||||
|             _updateTimer = new Timer((_) => Task.Run(this.UpdatePeriodic), null, 0, 60*1000); |             _updateTimer = new Timer((_) => this.UpdatePeriodic(), null, 0, 60*1000); | ||||||
|  |  | ||||||
|             Console.WriteLine($"Shard #{_client.ShardId} connected to {_client.Guilds.Sum(g => g.Channels.Count)} channels in {_client.Guilds.Count} guilds."); |             Console.WriteLine($"Shard #{_client.ShardId} connected to {_client.Guilds.Sum(g => g.Channels.Count)} channels in {_client.Guilds.Count} guilds."); | ||||||
|             Console.WriteLine($"PluralKit started as {_client.CurrentUser.Username}#{_client.CurrentUser.Discriminator} ({_client.CurrentUser.Id})."); |             Console.WriteLine($"PluralKit started as {_client.CurrentUser.Username}#{_client.CurrentUser.Discriminator} ({_client.CurrentUser.Id})."); | ||||||
| @@ -129,7 +129,7 @@ namespace PluralKit.Bot | |||||||
|                     } else if (exception is TimeoutException) { |                     } else if (exception is TimeoutException) { | ||||||
|                         await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} Operation timed out. Try being faster next time :)"); |                         await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} Operation timed out. Try being faster next time :)"); | ||||||
|                     } else { |                     } else { | ||||||
|                         HandleRuntimeError(ctx.Message as SocketMessage, (_result as ExecuteResult?)?.Exception); |                         HandleRuntimeError((_result as ExecuteResult?)?.Exception); | ||||||
|                     } |                     } | ||||||
|                 } else if ((_result.Error == CommandError.BadArgCount || _result.Error == CommandError.MultipleMatches) && cmd.IsSpecified) { |                 } else if ((_result.Error == CommandError.BadArgCount || _result.Error == CommandError.MultipleMatches) && cmd.IsSpecified) { | ||||||
|                     await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}\n**Usage: **pk;{cmd.Value.Remarks}"); |                     await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}\n**Usage: **pk;{cmd.Value.Remarks}"); | ||||||
| @@ -141,7 +141,6 @@ namespace PluralKit.Bot | |||||||
|  |  | ||||||
|         private async Task MessageReceived(SocketMessage _arg) |         private async Task MessageReceived(SocketMessage _arg) | ||||||
|         { |         { | ||||||
|             try { |  | ||||||
|             // Ignore system messages (member joined, message pinned, etc) |             // Ignore system messages (member joined, message pinned, etc) | ||||||
|             var arg = _arg as SocketUserMessage; |             var arg = _arg as SocketUserMessage; | ||||||
|             if (arg == null) return; |             if (arg == null) return; | ||||||
| @@ -164,13 +163,9 @@ namespace PluralKit.Bot | |||||||
|                 // If not, try proxying anyway |                 // If not, try proxying anyway | ||||||
|                 await _proxy.HandleMessageAsync(arg); |                 await _proxy.HandleMessageAsync(arg); | ||||||
|             } |             } | ||||||
|             } catch (Exception e) { |  | ||||||
|                 // Generic exception handler |  | ||||||
|                 HandleRuntimeError(_arg, e); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private void HandleRuntimeError(SocketMessage arg, Exception e) |         private void HandleRuntimeError(Exception e) | ||||||
|         { |         { | ||||||
|             Console.Error.WriteLine(e); |             Console.Error.WriteLine(e); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -33,7 +33,6 @@ namespace PluralKit.Bot.Commands | |||||||
|  |  | ||||||
|             var system = await Systems.Create(systemName); |             var system = await Systems.Create(systemName); | ||||||
|             await Systems.Link(system, Context.User.Id); |             await Systems.Link(system, Context.User.Id); | ||||||
|  |  | ||||||
|             await Context.Channel.SendMessageAsync($"{Emojis.Success} Your system has been created. Type `pk;system` to view it, and type `pk;help` for more information about commands you can use now."); |             await Context.Channel.SendMessageAsync($"{Emojis.Success} Your system has been created. Type `pk;system` to view it, and type `pk;help` for more information about commands you can use now."); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -91,6 +90,50 @@ namespace PluralKit.Bot.Commands | |||||||
|             await Context.Channel.SendMessageAsync($"{Emojis.Success} System deleted."); |             await Context.Channel.SendMessageAsync($"{Emojis.Success} System deleted."); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         [Group("list")] | ||||||
|  |         public class SystemListCommands: ModuleBase<PKCommandContext> { | ||||||
|  |             public MemberStore Members { get; set; } | ||||||
|  |  | ||||||
|  |             [Command] | ||||||
|  |             public async Task MemberShortList() { | ||||||
|  |                 var system = Context.GetContextEntity<PKSystem>() ?? Context.SenderSystem; | ||||||
|  |                 if (system == null) Context.RaiseNoSystemError(); | ||||||
|  |  | ||||||
|  |                 var members = await Members.GetBySystem(system); | ||||||
|  |                 var embedTitle = system.Name != null ? $"Members of {system.Name} (`{system.Hid}`)" : $"Members of `{system.Hid}`"; | ||||||
|  |                 await Context.Paginate<PKMember>( | ||||||
|  |                     members.ToList(), | ||||||
|  |                     25, | ||||||
|  |                     embedTitle, | ||||||
|  |                     (eb, ms) => eb.Description = string.Join("\n", ms.Select((m) => $"[`{m.Hid}`] **{m.Name}** *({m.Prefix ?? ""}text{m.Suffix ?? ""})*")) | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Command("full")] | ||||||
|  |             [Alias("big", "details", "long")] | ||||||
|  |             public async Task MemberLongList() { | ||||||
|  |                 var system = Context.GetContextEntity<PKSystem>() ?? Context.SenderSystem; | ||||||
|  |                 if (system == null) Context.RaiseNoSystemError(); | ||||||
|  |  | ||||||
|  |                 var members = await Members.GetBySystem(system); | ||||||
|  |                 var embedTitle = system.Name != null ? $"Members of {system.Name} (`{system.Hid}`)" : $"Members of `{system.Hid}`"; | ||||||
|  |                 await Context.Paginate<PKMember>( | ||||||
|  |                     members.ToList(), | ||||||
|  |                     10, | ||||||
|  |                     embedTitle, | ||||||
|  |                     (eb, ms) => { | ||||||
|  |                         foreach (var member in ms) { | ||||||
|  |                             var profile = $"**ID**: {member.Hid}"; | ||||||
|  |                             if (member.Pronouns != null) profile += $"\n**Pronouns**: {member.Pronouns}"; | ||||||
|  |                             if (member.Birthday != null) profile += $"\n**Birthdate**: {member.BirthdayString}"; | ||||||
|  |                             if (member.Description != null) profile += $"\n\n{member.Description}"; | ||||||
|  |                             eb.AddField(member.Name, profile); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public override async Task<PKSystem> ReadContextParameterAsync(string value) |         public override async Task<PKSystem> ReadContextParameterAsync(string value) | ||||||
|         { |         { | ||||||
|             var res = await new PKSystemTypeReader().ReadAsync(Context, value, _services); |             var res = await new PKSystemTypeReader().ReadAsync(Context, value, _services); | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								PluralKit/Bot/ContextUtils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								PluralKit/Bot/ContextUtils.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Discord; | ||||||
|  | using Discord.Commands; | ||||||
|  | using Discord.WebSocket; | ||||||
|  |  | ||||||
|  | namespace PluralKit.Bot { | ||||||
|  |     public static class ContextUtils { | ||||||
|  |                 public static async Task<bool> PromptYesNo(this ICommandContext ctx, IUserMessage message, TimeSpan? timeout = null) { | ||||||
|  |             await message.AddReactionsAsync(new[] {new Emoji(Emojis.Success), new Emoji(Emojis.Error)}); | ||||||
|  |             var reaction = await ctx.AwaitReaction(message, ctx.User, (r) => r.Emote.Name == Emojis.Success || r.Emote.Name == Emojis.Error, timeout ?? TimeSpan.FromMinutes(1)); | ||||||
|  |             return reaction.Emote.Name == Emojis.Success; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static async Task<SocketReaction> AwaitReaction(this ICommandContext ctx, IUserMessage message, IUser user = null, Func<SocketReaction, bool> predicate = null, TimeSpan? timeout = null) { | ||||||
|  |             var tcs = new TaskCompletionSource<SocketReaction>(); | ||||||
|  |             Task Inner(Cacheable<IUserMessage, ulong> _message, ISocketMessageChannel _channel, SocketReaction reaction) { | ||||||
|  |                 if (message.Id != _message.Id) return Task.CompletedTask; // Ignore reactions for different messages | ||||||
|  |                 if (user != null && user.Id != reaction.UserId) return Task.CompletedTask; // Ignore messages from other users if a user was defined | ||||||
|  |                 if (predicate != null && !predicate.Invoke(reaction)) return Task.CompletedTask; // Check predicate | ||||||
|  |                 tcs.SetResult(reaction); | ||||||
|  |                 return Task.CompletedTask; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             (ctx.Client as BaseSocketClient).ReactionAdded += Inner; | ||||||
|  |             try { | ||||||
|  |                 return await (tcs.Task.TimeoutAfter(timeout)); | ||||||
|  |             } finally { | ||||||
|  |                 (ctx.Client as BaseSocketClient).ReactionAdded -= Inner; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static async Task<IUserMessage> AwaitMessage(this ICommandContext ctx, IMessageChannel channel, IUser user = null, Func<SocketMessage, bool> predicate = null, TimeSpan? timeout = null) { | ||||||
|  |             var tcs = new TaskCompletionSource<IUserMessage>(); | ||||||
|  |             Task Inner(SocketMessage msg) { | ||||||
|  |                 if (channel != msg.Channel) return Task.CompletedTask; // Ignore messages in a different channel | ||||||
|  |                 if (user != null && user != msg.Author) return Task.CompletedTask; // Ignore messages from other users | ||||||
|  |                 if (predicate != null && !predicate.Invoke(msg)) return Task.CompletedTask; // Check predicate | ||||||
|  |  | ||||||
|  |                 (ctx.Client as BaseSocketClient).MessageReceived -= Inner; | ||||||
|  |                 tcs.SetResult(msg as IUserMessage); | ||||||
|  |                  | ||||||
|  |                 return Task.CompletedTask; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             (ctx.Client as BaseSocketClient).MessageReceived += Inner; | ||||||
|  |             return await (tcs.Task.TimeoutAfter(timeout)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static async Task Paginate<T>(this ICommandContext ctx, ICollection<T> items, int itemsPerPage, string title, Action<EmbedBuilder, IEnumerable<T>> renderer) { | ||||||
|  |             var pageCount = (items.Count / itemsPerPage) + 1; | ||||||
|  |             Embed MakeEmbedForPage(int page) { | ||||||
|  |                 var eb = new EmbedBuilder(); | ||||||
|  |                 eb.Title = pageCount > 1 ? $"[{page+1}/{pageCount}] {title}" : title; | ||||||
|  |                 renderer(eb, items.Skip(page*itemsPerPage).Take(itemsPerPage)); | ||||||
|  |                 return eb.Build(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var msg = await ctx.Channel.SendMessageAsync(embed: MakeEmbedForPage(0)); | ||||||
|  |             var botEmojis = new[] { new Emoji("\u23EA"), new Emoji("\u2B05"), new Emoji("\u27A1"), new Emoji("\u23E9"), new Emoji(Emojis.Error) }; | ||||||
|  |             await msg.AddReactionsAsync(botEmojis); | ||||||
|  |  | ||||||
|  |             try { | ||||||
|  |                 var currentPage = 0; | ||||||
|  |                 while (true) { | ||||||
|  |                     var reaction = await ctx.AwaitReaction(msg, ctx.User, timeout: TimeSpan.FromMinutes(5)); | ||||||
|  |  | ||||||
|  |                     // Increment/decrement page counter based on which reaction was clicked | ||||||
|  |                     if (reaction.Emote.Name == "\u23EA") currentPage = 0; // << | ||||||
|  |                     if (reaction.Emote.Name == "\u2B05") currentPage = (currentPage - 1) % pageCount; // < | ||||||
|  |                     if (reaction.Emote.Name == "\u27A1") currentPage = (currentPage + 1) % pageCount; // > | ||||||
|  |                     if (reaction.Emote.Name == "\u23E9") currentPage = pageCount - 1; // >> | ||||||
|  |                     if (reaction.Emote.Name == Emojis.Error) break; // X | ||||||
|  |                      | ||||||
|  |                     // If we can, remove the user's reaction (so they can press again quickly) | ||||||
|  |                     if (await ctx.HasPermission(ChannelPermission.ManageMessages) && reaction.User.IsSpecified) await msg.RemoveReactionAsync(reaction.Emote, reaction.User.Value); | ||||||
|  |                      | ||||||
|  |                     // Edit the embed with the new page | ||||||
|  |                     await msg.ModifyAsync((mp) => mp.Embed = MakeEmbedForPage(currentPage)); | ||||||
|  |                 } | ||||||
|  |             } catch (TimeoutException) { | ||||||
|  |                 // "escape hatch", clean up as if we hit X | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (await ctx.HasPermission(ChannelPermission.ManageMessages)) await msg.RemoveAllReactionsAsync(); | ||||||
|  |             else await msg.RemoveReactionsAsync(ctx.Client.CurrentUser, botEmojis); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static async Task<ChannelPermissions> Permissions(this ICommandContext ctx) { | ||||||
|  |             if (ctx.Channel is IGuildChannel) { | ||||||
|  |                 var gu = await ctx.Guild.GetCurrentUserAsync(); | ||||||
|  |                 return gu.GetPermissions(ctx.Channel as IGuildChannel); | ||||||
|  |             } | ||||||
|  |             return ChannelPermissions.DM; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static async Task<bool> HasPermission(this ICommandContext ctx, ChannelPermission permission) => (await Permissions(ctx)).Has(permission); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,5 +1,7 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
| using System.Data; | using System.Data; | ||||||
|  | using System.Linq; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Dapper; | using Dapper; | ||||||
| using Discord; | using Discord; | ||||||
| @@ -168,50 +170,6 @@ namespace PluralKit.Bot | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static class ContextExt { |  | ||||||
|         public static async Task<bool> PromptYesNo(this ICommandContext ctx, IUserMessage message, TimeSpan? timeout = null) { |  | ||||||
|             await message.AddReactionsAsync(new[] {new Emoji(Emojis.Success), new Emoji(Emojis.Error)}); |  | ||||||
|             var reaction = await ctx.AwaitReaction(message, ctx.User, (r) => r.Emote.Name == Emojis.Success || r.Emote.Name == Emojis.Error, timeout ?? TimeSpan.FromMinutes(1)); |  | ||||||
|             return reaction.Emote.Name == Emojis.Success; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static async Task<SocketReaction> AwaitReaction(this ICommandContext ctx, IUserMessage message, IUser user = null, Func<SocketReaction, bool> predicate = null, TimeSpan? timeout = null) { |  | ||||||
|             var tcs = new TaskCompletionSource<SocketReaction>(); |  | ||||||
|             Task Inner(Cacheable<IUserMessage, ulong> _message, ISocketMessageChannel _channel, SocketReaction reaction) { |  | ||||||
|                 if (message.Id != _message.Id) return Task.CompletedTask; // Ignore reactions for different messages |  | ||||||
|                 if (user != null && user.Id != reaction.UserId) return Task.CompletedTask; // Ignore messages from other users if a user was defined |  | ||||||
|                 if (predicate != null && !predicate.Invoke(reaction)) return Task.CompletedTask; // Check predicate |  | ||||||
|                 tcs.SetResult(reaction); |  | ||||||
|                 return Task.CompletedTask; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             (ctx.Client as BaseSocketClient).ReactionAdded += Inner; |  | ||||||
|             try { |  | ||||||
|                 return await (tcs.Task.TimeoutAfter(timeout)); |  | ||||||
|             } finally { |  | ||||||
|                 (ctx.Client as BaseSocketClient).ReactionAdded -= Inner; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static async Task<IUserMessage> AwaitMessage(this ICommandContext ctx, IMessageChannel channel, IUser user = null, Func<SocketMessage, bool> predicate = null, TimeSpan? timeout = null) { |  | ||||||
|             var tcs = new TaskCompletionSource<IUserMessage>(); |  | ||||||
|             Task Inner(SocketMessage msg) { |  | ||||||
|                 if (channel != msg.Channel) return Task.CompletedTask; // Ignore messages in a different channel |  | ||||||
|                 if (user != null && user != msg.Author) return Task.CompletedTask; // Ignore messages from other users |  | ||||||
|                 if (predicate != null && !predicate.Invoke(msg)) return Task.CompletedTask; // Check predicate |  | ||||||
|                 tcs.SetResult(msg as IUserMessage); |  | ||||||
|                 return Task.CompletedTask; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             (ctx.Client as BaseSocketClient).MessageReceived += Inner; |  | ||||||
|             try { |  | ||||||
|                 return await (tcs.Task.TimeoutAfter(timeout)); |  | ||||||
|             } finally { |  | ||||||
|                 (ctx.Client as BaseSocketClient).MessageReceived -= Inner; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     class PKError : Exception |     class PKError : Exception | ||||||
|     { |     { | ||||||
|         public PKError(string message) : base(message) |         public PKError(string message) : base(message) | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| using System; | using System; | ||||||
| using Dapper.Contrib.Extensions; | using Dapper.Contrib.Extensions; | ||||||
|  |  | ||||||
| namespace PluralKit { | namespace PluralKit | ||||||
|  | { | ||||||
|     [Table("systems")] |     [Table("systems")] | ||||||
|     public class PKSystem { |     public class PKSystem | ||||||
|  |     { | ||||||
|         [Key] |         [Key] | ||||||
|         public int Id { get; set; } |         public int Id { get; set; } | ||||||
|         public string Hid { get; set; } |         public string Hid { get; set; } | ||||||
| @@ -17,18 +19,30 @@ namespace PluralKit { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     [Table("members")] |     [Table("members")] | ||||||
|     public class PKMember { |     public class PKMember | ||||||
|  |     { | ||||||
|         public int Id { get; set; } |         public int Id { get; set; } | ||||||
|         public string Hid { get; set; } |         public string Hid { get; set; } | ||||||
|         public int System { get; set; } |         public int System { get; set; } | ||||||
|         public string Color { get; set; } |         public string Color { get; set; } | ||||||
|         public string AvatarUrl { get; set; } |         public string AvatarUrl { get; set; } | ||||||
|         public string Name { get; set; } |         public string Name { get; set; } | ||||||
|         public DateTime Date { get; set; } |         public DateTime? Birthday { get; set; } | ||||||
|         public string Pronouns { get; set; } |         public string Pronouns { get; set; } | ||||||
|         public string Description { get; set; } |         public string Description { get; set; } | ||||||
|         public string Prefix { get; set; } |         public string Prefix { get; set; } | ||||||
|         public string Suffix { get; set; } |         public string Suffix { get; set; } | ||||||
|         public DateTime Created { get; set; } |         public DateTime Created { get; set; } | ||||||
|  |  | ||||||
|  |         /// Returns a formatted string representing the member's birthday, taking into account that a year of "0001" is hidden | ||||||
|  |         public string BirthdayString | ||||||
|  |         { | ||||||
|  |             get | ||||||
|  |             { | ||||||
|  |                 if (Birthday == null) return null; | ||||||
|  |                 if (Birthday?.Year == 1) return Birthday?.ToString("MMMM dd"); | ||||||
|  |                 return Birthday?.ToString("MMMM dd, yyyy"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -125,7 +125,7 @@ namespace PluralKit { | |||||||
|                 msg.System = system; |                 msg.System = system; | ||||||
|                 msg.Member = member; |                 msg.Member = member; | ||||||
|                 return msg; |                 return msg; | ||||||
|             }, new { Id = id })).First(); |             }, new { Id = id })).FirstOrDefault(); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         public async Task Delete(ulong id) { |         public async Task Delete(ulong id) { | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								PluralKit/TaskUtils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								PluralKit/TaskUtils.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | using System; | ||||||
|  | using System.Threading; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | namespace PluralKit { | ||||||
|  |     public static class TaskUtils { | ||||||
|  |         public static async Task CatchException(this Task task, Action<Exception> handler) { | ||||||
|  |             try { | ||||||
|  |                 await task; | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 handler(e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan? timeout) { | ||||||
|  |             // https://stackoverflow.com/a/22078975 | ||||||
|  |             using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { | ||||||
|  |                 var completedTask = await Task.WhenAny(task, Task.Delay(timeout ?? TimeSpan.FromMilliseconds(-1), timeoutCancellationTokenSource.Token)); | ||||||
|  |                 if (completedTask == task) { | ||||||
|  |                     timeoutCancellationTokenSource.Cancel(); | ||||||
|  |                     return await task;  // Very important in order to propagate exceptions | ||||||
|  |                 } else { | ||||||
|  |                     throw new TimeoutException(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -29,19 +29,6 @@ namespace PluralKit | |||||||
|             if (str.Length < maxLength) return str; |             if (str.Length < maxLength) return str; | ||||||
|             return str.Substring(0, maxLength - ellipsis.Length) + ellipsis; |             return str.Substring(0, maxLength - ellipsis.Length) + ellipsis; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan? timeout) { |  | ||||||
|             // https://stackoverflow.com/a/22078975 |  | ||||||
|             using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { |  | ||||||
|                 var completedTask = await Task.WhenAny(task, Task.Delay(timeout ?? TimeSpan.FromMilliseconds(-1), timeoutCancellationTokenSource.Token)); |  | ||||||
|                 if (completedTask == task) { |  | ||||||
|                     timeoutCancellationTokenSource.Cancel(); |  | ||||||
|                     return await task;  // Very important in order to propagate exceptions |  | ||||||
|                 } else { |  | ||||||
|                     throw new TimeoutException(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static class Emojis { |     public static class Emojis { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user