private async Task ReactionReply(SocketGuildUser user, IReadOnlyCollection <WarnedUser> warnings, Embed embed, int warnCount, Server server, string reason) { Emoji[] emojis = GlobalProperties.EmojisOneThroughNine(); var data = new ReactionCallbackData("", embed, false, false, TimeSpan.FromSeconds(300)); var callbacks = new List <(IEmote, Func <SocketCommandContext, SocketReaction, Task>)>(); for (int j = 0; j < warnCount; j++) { int j1 = j; callbacks.Add((emojis[j], async(c, r) => { var uwArgs = new ModeratorEventArgs(server, Context.Guild, user, (SocketGuildUser)Context.User, reason, null); KaguyaEvents.TriggerUnwarn(uwArgs); await DatabaseQueries.DeleteAsync(warnings.ElementAt(j1)); await c.Channel.SendMessageAsync($"{r.User.Value.Mention} " + $"`Successfully removed warning #{j1 + 1}`"); })); } data.SetCallbacks(callbacks); await InlineReactionReplyAsync(data); }
protected virtual async Task ReplyInteractiveAsync(ServiceResponse response, string titleSuccess) { var messageBuilder = new StringBuilder(); messageBuilder.AppendLine(response.Message); var callbackCounter = 1; foreach (var resultInterActiveCallback in response.InterActiveCallbacks) { var emoji = EmojiService.Get(callbackCounter); messageBuilder.AppendLine(emoji.Name + " " + resultInterActiveCallback.Key); callbackCounter++; } var embed = BuildEmbed(LocalizationService.Get(typeof(i18n), "Base_Messages_Reply_Interactive"), messageBuilder.ToString(), Color.Orange); var reply = new ReactionCallbackData(string.Empty, embed, TimeSpan.FromSeconds(30)); callbackCounter = 1; foreach (var resultInterActiveCallback in response.InterActiveCallbacks) { var emoji = EmojiService.Get(callbackCounter); reply.WithCallback(emoji, c => ReplyWithInteractive(resultInterActiveCallback.Value, titleSuccess)); callbackCounter++; } await InlineReactionReplyAsync(reply); }
public async Task Command([Remainder] string text) { Server server = await DatabaseQueries.GetOrCreateServerAsync(Context.Guild.Id); IEnumerable <Quote> quotes = server.Quotes; int quoteCount = quotes?.Count() ?? 0; if (quoteCount > 0) { if (server.Quotes.Any(x => x.Text.Equals(text))) { var cEmbed = new KaguyaEmbedBuilder(EmbedColor.YELLOW) { Description = "A quote with the same text already exists. Do you want to create this one anyway?" }; var data = new ReactionCallbackData("", cEmbed.Build(), true, true, TimeSpan.FromSeconds(120)); data.AddCallBack(GlobalProperties.CheckMarkEmoji(), async(c, r) => { await InsertQuote(Context, server, text); }); data.AddCallBack(GlobalProperties.NoEntryEmoji(), async(c, r) => { await SendBasicErrorEmbedAsync("Okay, no action will be taken."); }); await InlineReactionReplyAsync(data); return; } } await InsertQuote(Context, server, text); }
public async Task <RuntimeResult> Language() { if (FergunClient.Languages.Count <= 1) { return(FergunResult.FromError(Locate("NoAvailableLanguages"))); } var guild = GetGuildConfig() ?? new GuildConfig(Context.Guild.Id); bool hasReacted = false; IUserMessage message = null; string languages = ""; var callbacks = new List <(IEmote, Func <SocketCommandContext, SocketReaction, Task>)>(); EmbedBuilder builder = null; int i = 0; foreach (var language in FergunClient.Languages) { callbacks.Add((new Emoji($"{i + 1}\ufe0f\u20e3"), async(context, reaction) => await HandleLanguageUpdateAsync(language.Key))); languages += $"{i + 1}. {Format.Bold(language.Value.EnglishName)} ({language.Value.NativeName})\n"; i++; } builder = new EmbedBuilder() .WithTitle(Locate("LanguageSelection")) .WithDescription($"{Locate("LanguagePrompt")}\n\n{languages}") .WithColor(FergunClient.Config.EmbedColor); ReactionCallbackData data = new ReactionCallbackData(null, builder.Build(), false, false, TimeSpan.FromMinutes(1), async context => await HandleLanguageUpdateAsync(null)).AddCallbacks(callbacks); message = await InlineReactionReplyAsync(data); return(FergunResult.FromSuccess()); async Task HandleLanguageUpdateAsync(string newLanguage) { if (hasReacted || guild.Language == newLanguage) { return; } hasReacted = true; if (newLanguage == null) { await message !.ModifyAsync(x => x.Embed = builder.WithDescription($"❌ {Locate("ReactTimeout")}").Build()); return; } guild.Language = newLanguage; FergunClient.Database.InsertOrUpdateDocument(Constants.GuildConfigCollection, guild); await _logService.LogAsync(new LogMessage(LogSeverity.Verbose, "Command", $"Language: Updated language to: \"{newLanguage}\" in {Context.Guild.Name}")); await message !.ModifyAsync(x => x.Embed = builder.WithTitle(Locate("LanguageSelection")).WithDescription($"✅ {Locate("NewLanguage")}").Build()); } }
// if the user supplies a tagname that doesn't exist, search the database to see if there are // any tags containing the user-supplied text // if there are any results from the database, post a list of the results & add emoji reactions // corresponding to each item in the list // when the user selects a emoji, it will pass the corresponding tagName and any extra passed // parameters to the RetryCommandUsingFoundTag function // afterwards, save a pairing of the calling user object and the searchResults message into a // dictionary, for use by the RetryCommandUsingFoundTag function private async Task FindTagAndRetry(string functionToRetry, params string[] args) { var tagName = args[0]; var searchResponse = await DatabaseTags.SearchTagsInDatabase(Context, tagName); // if single result, just use that if (searchResponse.Count == 1) { await RetryCommandUsingFoundTag(searchResponse[0], functionToRetry, args); return; } // if the single-result check didn't catch but there are results if (searchResponse.Any()) { string[] numbers = new[] { "0⃣", "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣" }; var numberEmojis = new List <Emoji>(); EmbedBuilder embedBuilder = new EmbedBuilder(); StringBuilder stringBuilder = new StringBuilder(); // add the number of emojis we need to the emojis list, and build our string-list of search results for (int i = 0; i < searchResponse.Count && i < numbers.Length; i++) { numberEmojis.Add(new Emoji(numbers[i])); stringBuilder.AppendLine($"{numbers[i]} - {searchResponse[i]}"); } embedBuilder.WithDescription(stringBuilder.ToString()); embedBuilder.WithColor(Color.Blue); // build a message and add reactions to it // reactions will be watched, and the one selected will fire the HandleFindTagReactionResult method, passing // that reaction's corresponding tagname and the function passed into this parameter var messageContents = new ReactionCallbackData("Did you mean... ", embedBuilder.Build()); for (int i = 0; i < searchResponse.Count; i++) { var counter = i; messageContents.AddCallBack(numberEmojis[counter], (c, r) => RetryCommandUsingFoundTag(searchResponse[counter], functionToRetry, args)); } var message = await InlineReactionReplyAsync(messageContents); // add calling user and searchResults embed to a dict as a pair // this way we can hold multiple users' reaction messages and operate on them separately _dictFindTagUserEmbedPairs.Add(Context.User, message); } else { await ReplyAsync("I can't find any tags like what you're looking for."); } }
public async Task <IUserMessage> SendMessageWithReactionCallbacksAsync(SocketCommandContext context, ReactionCallbackData reactionCallbackData, bool fromSourceUser = true) { var criterion = new Criteria <SocketReaction>(); if (fromSourceUser) { criterion.AddCriterion(new EnsureReactionFromSourceUserCriterion()); } var callback = new InlineReactionCallback(this, context, reactionCallbackData, criterion); await callback.DisplayAsync().ConfigureAwait(false); return(callback.Message); }
private static ReactionCallbackData GenerateColorRole(ColorRole colorRole) { var embedBuilder = new EmbedBuilder() .WithTitle($"Color Role") .WithColor(new Color(colorRole.r, colorRole.g, colorRole.b)) .WithDescription( $"Do you like this color? HEX: {colorRole.HexColor.ToUpper()} / RGB: {colorRole.r}, {colorRole.g}, {colorRole.b}" + Environment.NewLine + "React with your choice." ); var rcbd = new ReactionCallbackData("", embedBuilder.Build(), true, true, reactDuration, async c => await ReactionEndedAsync(c, colorRole).ConfigureAwait(false)); foreach (IEmote answerEmoji in colorRole.Answers.Select(x => x.AnswerEmoji)) { _ = rcbd.WithCallback(answerEmoji, (c, r) => ColorPicker(r, colorRole)); } return(rcbd); }
private static ReactionCallbackData GeneratePoll(Poll poll) { string answers = string.Join(Environment.NewLine, poll.Answers.Select(x => $"{x.AnswerEmoji} {x.Answer}")); var embedBuilder = new EmbedBuilder() .WithTitle($"New Poll: {poll.Question}") .WithColor(new Color(20, 20, 20)) .WithDescription( "- Pick an option by clicking on the corresponding Emoji" + Environment.NewLine + "- Only your first pick counts!" + Environment.NewLine + $"- You have {pollDuration.Humanize()} to cast your vote" ) .AddField("Pick one", answers); var rcbd = new ReactionCallbackData("", embedBuilder.Build(), false, true, true, pollDuration, async c => await PollEndedAsync(c, poll).ConfigureAwait(false)); foreach (var answerEmoji in poll.Answers.Select(x => x.AnswerEmoji)) { rcbd.WithCallback(answerEmoji, (c, r) => AddVoteCount(r, poll)); } return(rcbd); }
public async Task Test_OptionsReply(int count) { string[] numbers = new[] { "0⃣", "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣" }; var numberEmojis = new List <Emoji>(); for (int i = 0; i < numbers.Length; i++) { numberEmojis.Add(new Emoji(numbers[i])); } var one = new Emoji("1?"); var reply = new ReactionCallbackData("Option Count: " + count); for (int i = 1; i <= count; i++) { var counter = i; reply.WithCallback(numberEmojis[counter], c => c.Channel.SendMessageAsync("You have choosen option: " + counter)); } await InlineReactionReplyAsync(reply); }
private async Task MultipleMatchingRolesHandler(SocketGuild guild, string roleName, IReadOnlyCollection <SocketRole> roles) { List <SocketRole> matchingRoles = roles.Where(x => x.Name.ToLower() == roleName.ToLower()).ToList(); int matchCount = matchingRoles.Count; if (matchCount > 9) { matchCount = 9; } var emojis = new Emoji[] { new Emoji("1⃣"), new Emoji("2⃣"), new Emoji("3⃣"), new Emoji("4⃣"), new Emoji("5⃣"), new Emoji("6⃣"), new Emoji("7⃣"), new Emoji("8⃣"), new Emoji("9⃣") }; var embed = new KaguyaEmbedBuilder { Description = $"I found `{matchCount.ToWords()}` roles that match this name. Please " + $"select the role that you want to display.", Fields = new List <EmbedFieldBuilder>() }; var callbacks = new List <(IEmote, Func <SocketCommandContext, SocketReaction, Task>)>(); for (int i = 0; i < matchCount; i++) { int i1 = i; if (i1 == matchCount) { i1 = matchCount - 1; } SocketRole role = matchingRoles.ElementAt(i1); List <GuildPermission> rolePerms = matchingRoles[i].Permissions.ToList(); IEnumerable <SocketGuildUser> usersWithRole = guild.Users.Where(x => x.Roles.Contains(role)); embed.Fields.Add(new EmbedFieldBuilder { Name = $"Role #{i + 1}", Value = $"Exact Name: `{role.Name}`\nNumber of users who have the role: {usersWithRole.Count()}" + $"\nPermissions: `{rolePerms.Count}`\n" + $"Created: `{role.CreatedAt.Humanize()}`\n" + $"Position in role list (higher number = higher position): `{role.Position}`" }); callbacks.Add((emojis[i], async(c, r) => { var pager = new PaginatedMessage { Pages = Pages(guild, role), Color = Color.Blue }; await PagedReplyAsync(pager, new ReactionList { Backward = true, First = true, Forward = true, Jump = true, Last = true, Trash = true }); } )); } var data = new ReactionCallbackData("", embed.Build(), false, false, TimeSpan.FromSeconds(120), c => c.Channel.SendMessageAsync("Role selection has timed out. Please try again.")); data.SetCallbacks(callbacks); await InlineReactionReplyAsync(data); }
public async Task Challenge(IGuildUser user) { if (user == Context.User) { await ReplyAsync("You cannot choose yourself."); return; } if (user.IsBot) { await ReplyAsync("You can only challenge a human."); return; } if (user.RoleIds.Any(x => x == 347300513110294528)) { await ReplyAsync("You cannot challenge a VIP in this server"); return; } var lastDaySeries = seriesService.SeriesHistory.Reverse().TakeWhile(x => (DateTime.Now - x.Date).TotalHours < 12); var playerIds = new[] { user.Id.ToString(), Context.User.Id.ToString() }; var conflictSeries = lastDaySeries.FirstOrDefault(x => playerIds.Contains(x.WinnerId) && playerIds.Contains(x.LoserId)); if (conflictSeries != default) { await ReplyAsync($"You already played this person less than a day ago, " + $"please wait for {(conflictSeries.Date.AddHours(12) - DateTime.Now).AsRoundedDuration()}."); return; } var player1 = rankingService.SeriesPlayers.FirstOrDefault(x => x.Id == Context.User.Id.ToString()); var player2 = rankingService.SeriesPlayers.FirstOrDefault(x => x.Id == user.Id.ToString()); var player1Elo = player1?.Elo ?? 1200; var player2Elo = player2?.Elo ?? 1200; var lower = player1Elo < player2Elo ? player1 : player2; if ((player1Elo > 1300 || player2Elo > 1300) && Math.Abs(player1Elo - player2Elo) >= 300) { if ((lower?.Points ?? 0) < 5) { await ReplyAsync( "If a player's elo is more than 1300 and the Elo difference of the players is more than 300, " + "the lower Elo player needs to have at least 5 points earned to challenge." ); return; } } var challengeEmbed = new EmbedBuilder() .WithColor(Color.Orange) .WithAuthor(Context.User) .WithTitle("Series Challenge") .WithDescription( Context.User.Mention + " is challenging " + user.Mention + " on a series.\n" + "Engineer is banned by default unless [rule 7](https://mobilekoth.github.io/system) is applied.\n" + "Please decide if to ban more towers. Both must agree or there will be no other towers banned.\n" + "<:ban:683258477421920342> Ban Towers ⭕ No Ban Towers 🚶 Reject Challenge" ) .WithFooter($"Both of you have {TowerBanManager.MAX_SESSION_SECONDS} seconds to decide."); bool leftAgree = false, rightAgree = false, denied = false; int voteCount = 0; var reactionCallBackData = new ReactionCallbackData( string.Empty, embed: challengeEmbed.Build(), false, // Expires after use true, // Single use per user TimeSpan.FromSeconds(TowerBanManager.MAX_SESSION_SECONDS), async timeOut => { if (voteCount == 2 || denied) { return; } await ReplyAsync(embed: new EmbedBuilder() .WithColor(Color.Orange) .WithDescription($"{Context.User.Mention} {user.Mention} Challenge session has timed out.") .Build()); }).WithCallback(BanEmote, async(c, e) => { if (!(e.UserId == Context.User.Id || e.UserId == user.Id) || denied) { return; } leftAgree = leftAgree || e.UserId == Context.User.Id; rightAgree = rightAgree || e.UserId == user.Id; voteCount++; await handleVote(); }) .WithCallback(new Emoji("⭕"), async(c, e) => { if (!(e.UserId == Context.User.Id || e.UserId == user.Id) || denied) { return; } voteCount++; await handleVote(); }) .WithCallback(new Emoji("🚶"), async(c, e) => { if (!(e.UserId == Context.User.Id || e.UserId == user.Id) || denied) { return; } if (e.UserId == Context.User.Id || e.UserId == user.Id) { voteCount++; denied = true; await ReplyAsync(embed: new EmbedBuilder() .WithColor(Color.Orange) .WithDescription($"{e.User.Value.Mention} has rejected the challenge") .Build()); } }); async Task handleVote() { if (voteCount != 2) { return; } if (leftAgree && rightAgree) { await BanTowerSession(user); } else { await ReplyAsync(embed : new EmbedBuilder() .WithColor(Color.Orange) .WithDescription( $"{Context.User.Mention} {user.Mention} " + $"There is at least 1 disagreement to have towers banned. " + $"You may begin your series with no tower banned." ) .Build()); } } await InlineReactionReplyAsync(reactionCallBackData, false); }
public async Task RemoveRole([Remainder] string targetRole) { var roles = new List <SocketRole>(); roles = Context.Guild.Roles.Where(r => r.Name.ToLower() == targetRole.ToLower()).ToList(); if (roles.Count > 1) { var emojis = new Emoji[] { new Emoji("1⃣"), new Emoji("2⃣"), new Emoji("3⃣"), new Emoji("4⃣"), new Emoji("5⃣"), new Emoji("6⃣"), new Emoji("7⃣"), new Emoji("8⃣"), new Emoji("9⃣") }; var embed = new KaguyaEmbedBuilder { Description = $"I found `{roles.Count.ToWords()}` roles that match this name. Please " + $"select the role that you want to delete, or use the ⛔ reaction " + $"to delete all roles with this name.", Fields = new List <EmbedFieldBuilder>() }; var callbacks = new List <(IEmote, Func <SocketCommandContext, SocketReaction, Task>)>(); for (int i = 0; i < roles.Count; i++) { int roleIndex = i + 1; SocketRole role = roles.ElementAt(i); embed.Fields.Add(new EmbedFieldBuilder { Name = $"Role #{roleIndex}", Value = $"Exact Name: `{role.Name}`\n" + $"Number of users who have this role: " + $"`{Context.Guild.Users.Count(x => x.Roles.Contains(role))}`\n" + $"Permissions: `{roles.Count}`\n" + $"Created: `{role.CreatedAt.Humanize()}`\n" + $"Position in role list (higher number = higher position): `{role.Position}`" }); callbacks.Add((emojis[i], async(c, r) => { await role.DeleteAsync(); await ReplyAsync($"{Context.User.Mention} `Successfully deleted Role #{roleIndex}`"); } )); } callbacks.Add((new Emoji("⛔"), async(c, r) => { foreach (SocketRole role in roles) { await role.DeleteAsync(); } await ReplyAsync($"{Context.User.Mention} Successfully deleted `{roles.Count.ToWords()}` roles."); } )); var data = new ReactionCallbackData("", embed.Build(), false, false, TimeSpan.FromSeconds(120)); data.SetCallbacks(callbacks); await InlineReactionReplyAsync(data); } else if (roles.Count == 1) { SocketRole role = roles.First(); var embed = new KaguyaEmbedBuilder { Description = $"{Context.User.Mention} Successfully deleted role `{role.Name}`" }; await role.DeleteAsync(); await ReplyAsync(embed : embed.Build()); } else { var embed = new KaguyaEmbedBuilder { Description = $"I could not find the specified role." }; embed.SetColor(EmbedColor.RED); await ReplyAsync(embed : embed.Build()); } }
/// <summary> /// Sends a Message with reaction callback /// </summary> /// <param name="context"></param> /// <param name="callbacks"></param> /// <param name="fromSourceUser"></param> /// <returns></returns> public async Task <IUserMessage> SendMessageWithReactionCallbacksAsync(SocketCommandContext context, ReactionCallbackData callbacks, bool fromSourceUser = true) { if (callbacks.Text == null) { throw new Exception("Input text must not be null. Use an empty string instead."); } var criterion = new Criteria <SocketReaction>(); if (fromSourceUser) { criterion.AddCriterion(new EnsureReactionFromSourceUserCriterion()); } var callback = new InlineReactionCallback(new InteractiveService(Discord.GetShardFor(context.Guild)), context, callbacks, criterion); await callback.DisplayAsync().ConfigureAwait(false); return(callback.Message); }
public async Task GetInfo([Remainder, Name("termi")] string query) { var normalized = StringUtils.NormalizeQuery(query); if (normalized == null) { await ReplyAsync($"Termi **{query}** nuk u gjet."); return; } var obj = JsonUtils.LookupObject(Data, normalized); if (obj != null) { await ReplyAsync( $"Informatat për **{query}**", embed : JsonUtils.EmbedObject(obj)); return; } const int threshold = 8; const int count = 3; // No match, try finding suggestions. var matches = JsonUtils.FindClosest(Data, normalized, threshold, count); if (matches.Length != 0) { var emojis = new[] { "\u0031\u20E3", "\u0032\u20E3", "\u0033\u20E3" }; // Give suggestions and listen for reactions. var text = $"Termi **{query}** nuk u gjet.\n\n" + "Mos keni menduar për ndonjërën nga:\n" + matches.Select((match, i) => i + 1 + ") " + match["_label"]).Join("\n"); var callback = new ReactionCallbackData( text, embed: null, expiresAfterUse: true, singleUsePerUser: true, timeout: TimeSpan.FromSeconds(30d)); for (var i = 0; i < matches.Length; i++) { var term = matches[i]["_label"].ToString(); callback.WithCallback( new Emoji(emojis[i]), async(c, r) => { var newObj = TryLookup(term); if (newObj != null) { await c.Channel.SendMessageAsync( $"Informatat për **{term}**", embed: JsonUtils.EmbedObject(newObj)); } else { await c.Channel.SendMessageAsync("Fatkeqësisht ka ndodhur një gabim. Ju lutem provoni përsëri."); } }); } await InlineReactionReplyAsync(callback, fromSourceUser : true); } else { // No suggestions. await ReplyAsync($"Termi **{query}** nuk u gjet."); } }
public async Task Command() { User user = await DatabaseQueries.GetOrCreateUserAsync(Context.User.Id); Reminder[] reminders = user.Reminders.Where(x => x.Expiration > DateTime.Now.ToOADate()).ToArray(); var embed = new KaguyaEmbedBuilder(); int i = 0; if (!(reminders.Length == 0)) { foreach (Reminder reminder in reminders) { i++; string expirationStr = DateTime.FromOADate(reminder.Expiration).Humanize(false); var fSb = new StringBuilder(); fSb.AppendLine($"Reminder: `{reminder.Text}`"); fSb.AppendLine($"Expires: `{expirationStr}`"); var field = new EmbedFieldBuilder { IsInline = false, Name = $"#{i}", Value = fSb.ToString() }; embed.AddField(field); } embed.Footer = new EmbedFooterBuilder { Text = "To delete a reminder, click the corresponding reaction." }; } else { var field = new EmbedFieldBuilder { Name = "No reminders active", Value = "You currently don't have any active reminders." }; embed.AddField(field); } int j = 0; var data = new ReactionCallbackData("", embed.Build()); foreach (Reminder reminder in reminders) { data.AddCallBack(GlobalProperties.EmojisOneThroughNine()[j], async(c, r) => { await DatabaseQueries.DeleteAsync(reminder); await ReplyAsync($"{Context.User.Mention} Successfully deleted reminder #{j}."); }); j++; } await InlineReactionReplyAsync(data); }
public async Task GetQuestion(int id = -100) { // question which will be fetch from server database Question question = null; // if question was already seen bool wasSeen = false; // Get server informations Guild guild = Guilds.GetGuild(Context.Guild.Id); // Get user account UserAccount userAccount = UserAccounts.GetUserAccount(Context.User.Id, guild.UserAccounts, guild.Categories); // check if question id is valid if (id >= 0) { // check if user already seen this question if (userAccount.SeenQuestionsIds.Contains(id)) { wasSeen = true; } // get question with id from database question = Questions.GetQuestion(id, guild.Categories); } else { Random random = new Random(DateTime.Now.Millisecond); List <Question> allQuestions = new List <Question>(); // Get maximum question id foreach (Category category in guild.Categories) { allQuestions.AddRange(category.Questions); // and all questions as well } // get all unseen questions IEnumerable <Question> unseenQuestions = from q in allQuestions where !userAccount.SeenQuestionsIds.Contains(q.Id) select q; int unseenQuestionsCount = unseenQuestions.ToList().Count; // if list of unseen questions is empty if (unseenQuestionsCount == 0) { // send random question wasSeen = true; question = allQuestions.ElementAt(random.Next(0, allQuestions.Count)); } else { // send unseen question question = unseenQuestions.ElementAt(random.Next(0, unseenQuestionsCount)); } id = question.Id; } // Create embed for message with question EmbedBuilder embedBuild = new EmbedBuilder { Title = $"Pytanie #{question.Id}", Description = question.Description }; // add possible answers for (int i = 0; i < question.PossibleAnswers.Count; i++) { embedBuild.AddField(Util.AnswersEmojis.ToList()[i].Name, question.PossibleAnswers[i], true); } // add image if (question.ImageURL != null) { embedBuild.ImageUrl = question.ImageURL; } // build embed Embed embed = embedBuild.Build(); // ReactionCallbackData(message content, embed, expires after first use, if command should react to only one answer from 1 user, // command timeout, what to do if command expires) ReactionCallbackData reactionData = new ReactionCallbackData("", embed, true, true, TimeSpan.FromSeconds(120), (c) => Timeout(c.Channel)); // Iterate all possible answers and add answer placeholder for (int i = 0; i < question.PossibleAnswers.Count; i++) { // check if this iteration answer is right if (i == question.RightAnswer) { // Add answer placecholder with information what if user select this answer // reactionData.WithCallback(answer emoji, what to do if user select answer) // CheckScore(if question was seen, is this good answer, question id, question category, user account, source channel) reactionData.WithCallback(Util.AnswersEmojis.ToList()[i], (c, r) => CheckScore(wasSeen, true, id, GetCategoryContainingQuestionId(id, guild.Categories), userAccount, c.Channel)); } else { reactionData.WithCallback(Util.AnswersEmojis.ToList()[i], (c, r) => CheckScore(wasSeen, false, id, GetCategoryContainingQuestionId(id, guild.Categories), userAccount, c.Channel)); } } // Send quiz message and await user selection await InlineReactionReplyAsync(reactionData, true); }
/// <summary> /// Sends a message with reaction callback /// </summary> /// <param name="context"></param> /// <param name="callbacks"></param> /// <param name="fromSourceUser"></param> /// <returns></returns> public async Task <IUserMessage> SendMessageWithReactionCallbacksAsync(SocketCommandContext context, ReactionCallbackData callbacks, bool fromSourceUser = true) { var criterion = new Criteria <SocketReaction>(); if (fromSourceUser) { criterion.AddCriterion(new EnsureReactionFromSourceUserCriterion()); } var callback = new InlineReactionCallback(new InteractiveService(Discord.GetShardFor(context.Guild)), context, callbacks, criterion); await callback.DisplayAsync().ConfigureAwait(false); return(callback.Message); }
/// <summary> /// Sends a Message that will do a custom action upon reactions /// </summary> /// <param name="data">The main settings used for the Message</param> /// <param name="fromSourceUser">True = Only the user who invoked this method can invoke the callback</param> /// <returns>The Message sent</returns> public Task <IUserMessage> InlineReactionReplyAsync(ReactionCallbackData data, bool fromSourceUser = true) { return(Interactive.SendMessageWithReactionCallbacksAsync(SocketContext(), data, fromSourceUser)); }
/// <summary> /// Searches the specified <see cref="SearchProvider" /> for the provided <see cref="query" />. /// This method also adds the song to the guild's player queue and will even join the user's voice /// channel automatically. /// </summary> /// <param name="context"></param> /// <param name="query">The song to search for, user input.</param> /// <param name="playFirst"></param> /// <param name="provider"></param> /// <returns></returns> public async Task <ReactionCallbackData> SearchAndPlayAsync(ShardedCommandContext context, string query, bool playFirst = false, SearchProvider provider = SearchProvider.YOU_TUBE) { User user = await DatabaseQueries.GetOrCreateUserAsync(context.User.Id); Server server = await DatabaseQueries.GetOrCreateServerAsync(context.Guild.Id); LavaNode node = ConfigProperties.LavaNode; SocketVoiceChannel curVc = (context.User as SocketGuildUser).VoiceChannel; await ConsoleLogger.LogAsync($"Found node and voice channel for guild {context.Guild.Id}.", LogLvl.TRACE); if (curVc == null) { await context.Channel.SendMessageAsync($"{context.User.Mention} You must be in a voice " + "channel to use this command."); await ConsoleLogger.LogAsync("User was not in voice channel, cancelling music search operation.", LogLvl.TRACE); return(null); } SearchResponse result = provider switch { SearchProvider.YOU_TUBE => await node.SearchYouTubeAsync(query), SearchProvider.SOUNDCLOUD => await node.SearchSoundCloudAsync(query), _ => await node.SearchAsync(query) }; if (provider == SearchProvider.TWITCH) { const string PROVIDER_URL = "www.twitch.tv"; string errorString = "Your search returned no results. Ensure you are only " + "typing the name of the streamer who you want to watch or a direct link to their stream.\n\n" + "Note: The streamer must be live for this feature to work."; if (!query.ToLower().Contains(PROVIDER_URL)) { result = await node.SearchAsync($"https://{PROVIDER_URL}/{query}"); if (result.Tracks.Count == 0) { await context.Channel.SendBasicErrorEmbedAsync(errorString); await ConsoleLogger.LogAsync($"No livestream found for search {query} in guild {context.Guild.Id}.", LogLvl.TRACE); return(null); } } else { if ((await node.SearchAsync($"https://{PROVIDER_URL}/{query.Split('\\').Last()}")).Tracks.Count == 0 && (await node.SearchAsync(query)).Tracks.Count == 0) { await context.Channel.SendBasicErrorEmbedAsync(errorString); await ConsoleLogger.LogAsync($"No livestream found for search {query} in guild {context.Guild.Id}.", LogLvl.TRACE); return(null); } } } var tracks = new List <LavaTrack>(); if (user.IsPremium || server.IsPremium) { if (result.Tracks.Any()) { tracks.AddRange(result.Tracks); } } else { // Limit track duration to 10 minutes for non-premium servers/users. if (result.Tracks.Any()) { tracks.AddRange(result.Tracks.Where(x => x.Duration.TotalMinutes < 10).ToList()); } } if (!tracks.Any()) { string suppString = user.IsPremium ? "" : "If you are " + $"not a [Kaguya Premium Subscriber]({ConfigProperties.KAGUYA_STORE_URL}), " + "you are only limited to playing songs less than `10 minutes` in duration."; await context.Channel.SendBasicErrorEmbedAsync($"Your requested search returned no results. {suppString}"); await ConsoleLogger.LogAsync("Search request returned no usable " + $"results in guild {Context.Guild.Id} for query {query}", LogLvl.TRACE); } var fields = new List <EmbedFieldBuilder>(); var callbacks = new List <(IEmote, Func <SocketCommandContext, SocketReaction, Task>)>(); Emoji[] emojiNums = GlobalProperties.EmojisOneThroughNine(); LavaPlayer player = node.HasPlayer(context.Guild) ? node.GetPlayer(context.Guild) : await node.JoinAsync(curVc); await ConsoleLogger.LogAsync($"Player found for guild {context.Guild.Id}. Connected to voice channel.", LogLvl.TRACE); #region If the track is a livestream: if (tracks.Any(x => x.IsStream)) { LavaTrack trackSel = tracks.First(x => x.IsStream); // Gathers the first stream from the collection. string twitchName = (await ConfigProperties.TwitchApi.V5.Users.GetUserByNameAsync(trackSel.Author)).Matches[0].DisplayName; string playString = player.PlayerState == PlayerState.Playing ? $"Queued stream into position {player.Queue.Count}." : $"Now playing `{twitchName}`'s stream."; if (player.PlayerState == PlayerState.Playing) { try { player.Queue.Enqueue(trackSel); await ConsoleLogger.LogAsync($"Enqueued livestream {trackSel.Title} in guild {context.Guild.Id}", LogLvl.TRACE); } catch (Exception e) { await ConsoleLogger.LogAsync("An exception was thrown when trying to enqueue the livestream " + $"{trackSel.Title} in guild {Context.Guild.Id}.\n" + $"Exception Message: {e.Message}\n" + $"Stack Trace: {e.StackTrace}", LogLvl.WARN); } } else { try { await player.PlayAsync(trackSel); await ConsoleLogger.LogAsync($"Playing livestream {trackSel.Title} in guild {context.Guild.Id}", LogLvl.TRACE); } catch (Exception e) { await ConsoleLogger.LogAsync("An exception was thrown when trying to play track " + $"{trackSel.Title} in guild {Context.Guild.Id}.\n" + $"Exception Message: {e.Message}\n" + $"Stack Trace: {e.StackTrace}", LogLvl.WARN); } } var field = new EmbedFieldBuilder { Name = $"`{twitchName}`'s Stream", Value = $"{playString}\n" // We get rid of backticks for formatting. }; var embed = new KaguyaEmbedBuilder { Fields = new List <EmbedFieldBuilder> { field } }; await context.Channel.SendEmbedAsync(embed); return(null); } #endregion #region If we have chosen to only play the default track (via $play). if (playFirst && tracks.Any()) { LavaTrack trackSel = tracks[0]; var field = new EmbedFieldBuilder { Name = "Track #1.", Value = $"Title: `{trackSel.Title.Replace("`", "")}`\n" + // We get rid of backticks for formatting. $"Duration: `{trackSel.Duration.Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 3)}`\n" + $"Uploader: `{trackSel.Author}`" }; string playString = player.PlayerState == PlayerState.Playing && !player.Track.IsStream ? $"Queued track #1 into position {player.Queue.Count + 1}." : "Now playing track #1."; if (player.PlayerState == PlayerState.Playing) { if (player.Queue.Count() == 50 && !server.IsPremium) { await ConsoleLogger.LogAsync($"Queue is full in {context.Guild.Id}, sending error.", LogLvl.TRACE); await SendBasicErrorEmbedAsync("Your queue is full! `50 songs` is the maximum " + $"for non [Kaguya Premium]({ConfigProperties.KAGUYA_STORE_URL}) " + "servers."); } else { player.Queue.Enqueue(trackSel); await ConsoleLogger.LogAsync($"Enqueued track {trackSel.Title} in guild {context.Guild.Id}.", LogLvl.TRACE); if (player.Track.IsStream) { await player.SkipAsync(); await ConsoleLogger.LogAsync($"Skipped livestream to play incoming track in guild {context.Guild.Id}.", LogLvl.TRACE); } } } else { try { await player.PlayAsync(trackSel); await ConsoleLogger.LogAsync($"Playing track {trackSel.Title} in guild {context.Guild.Id}", LogLvl.TRACE); } catch (Exception e) { await ConsoleLogger.LogAsync("An exception was thrown when trying to play track " + $"{trackSel.Title} in guild {Context.Guild.Id}.\n" + $"Exception Message: {e.Message}\n" + $"Stack Trace: {e.StackTrace}", LogLvl.WARN); } } if (player.Volume == 0 && player.PlayerState == PlayerState.Playing) { await player.UpdateVolumeAsync(75); // Sets the volume back to default if it is muted. await ConsoleLogger.LogAsync($"Automatically set player volume to 75 in guild {context.Guild.Id}.", LogLvl.TRACE); } var embed = new KaguyaEmbedBuilder { Title = $"Kaguya Music {Centvrio.Emoji.Music.Notes}", Description = playString, ThumbnailUrl = await trackSel.FetchArtworkAsync(), Fields = new List <EmbedFieldBuilder> { field } }; await SendEmbedAsync(embed, context); return(null); } #endregion int h = tracks.Count; for (int i = 0; i < (h < 7 ? h : 7); i++) { int i1 = i; LavaTrack trackSel = tracks[i]; var field = new EmbedFieldBuilder { Name = $"Track {i1 + 1}.", Value = $"Title: `{trackSel.Title.Replace("`", "")}`\n" + // We get rid of backticks for formatting. $"Duration: `{trackSel.Duration.Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 3)}`\n" + $"Uploader: `{trackSel.Author}`" }; fields.Add(field); callbacks.Add((emojiNums[i], async(c, r) => { string playString = player.PlayerState == PlayerState.Playing && !player.Track.IsStream ? $"Queued track #{i1 + 1} into position {player.Queue.Count + 1}." : $"Now playing track #{i1 + 1}."; if (player.PlayerState == PlayerState.Playing) { if (player.Queue.Count() == 50 && !server.IsPremium) { await ConsoleLogger.LogAsync($"Queue was full in guild {context.Guild.Id}. Sending error message.", LogLvl.TRACE); await SendBasicErrorEmbedAsync("Your queue is full! `50 songs` is the maximum " + $"for non [Kaguya Premium]({ConfigProperties.KAGUYA_STORE_URL}) " + "servers."); return; } player.Queue.Enqueue(trackSel); await ConsoleLogger.LogAsync($"Enqueued track {trackSel} in guild {context.Guild.Id}", LogLvl.TRACE); if (player.Track.IsStream) { await player.SkipAsync(); await ConsoleLogger.LogAsync("Automatically skipped livestream to play" + $" incoming track in guild {context.Guild.Id}", LogLvl.TRACE); } } else { try { await player.PlayAsync(trackSel); await ConsoleLogger.LogAsync($"Playing track {trackSel.Title} in guild {context.Guild.Id}", LogLvl.TRACE); } catch (Exception e) { await ConsoleLogger.LogAsync("An exception was thrown when trying to play track " + $"{trackSel.Title} in guild {Context.Guild.Id}.\n" + $"Exception Message: {e.Message}\n" + $"Stack Trace: {e.StackTrace}", LogLvl.WARN); } } if (player.Volume == 0 && player.PlayerState == PlayerState.Playing) { await player.UpdateVolumeAsync(75); // Sets the volume back to default if it is muted. await ConsoleLogger.LogAsync($"Automatically set volume to 75 in guild {context.Guild.Id}", LogLvl.TRACE); } var embed = new KaguyaEmbedBuilder { Title = $"Kaguya Music {Centvrio.Emoji.Music.Notes}", Description = playString, ThumbnailUrl = await trackSel.FetchArtworkAsync(), Fields = new List <EmbedFieldBuilder> { field } }; await SendEmbedAsync(embed); } )); } callbacks.Add((GlobalProperties.NoEntryEmoji(), async(c, r) => { await c.Message.DeleteAsync(); await r.Message.Value.DeleteAsync(); })); string s = tracks.Count == 1 ? "" : "s"; var songDisplayEmbed = new KaguyaEmbedBuilder { Title = "Kaguya Music Search Results", Description = $" I found {tracks.Count} track{s} from {provider}, " + $"{(tracks.Count > 5 ? "but here are the top 5" : "here they are")}. " + "Please select a track to play.", Fields = fields }; var data = new ReactionCallbackData("", songDisplayEmbed.Build(), false, false, TimeSpan.FromSeconds(60)); data.SetCallbacks(callbacks); return(data); } }
public async Task HelpCommand() { var timeoutSeconds = 60; var embed = new EmbedBuilder() .WithColor(Color.Orange) .WithTitle("❓ User Guide") .WithDescription("Here is the list of command modules.\n" + "A module is a catergory for a group of related commands.\n" + $"Press the corresponding emote or enter `{prefix}help <module>` to view the commands in a module.\n") .WithFooter($"Press the respective emote to expand the module list (expire in {timeoutSeconds} seconds)."); var moduleEmoteOrders = commands.Modules .OrderBy(x => x.Remarks ?? "Module Z") .Zip(EmojiPresets.Numbers.Skip(1), (x, y) => new KeyValuePair <Emoji, ModuleInfo>(y, x)); embed.Fields = getOriginalFields(); IUserMessage msg = default; var reactionCallbacksData = new ReactionCallbackData(string.Empty, embed.Build(), false, false, TimeSpan.FromSeconds(timeoutSeconds), c => onExpire()); foreach (var item in moduleEmoteOrders) { reactionCallbacksData = reactionCallbacksData.WithCallback(item.Key, (c, r) => { embed.Description = $"Enter `{prefix}help {prefix}<command>` to view the details of a command"; embed.Fields = getOriginalFields(); embed.Fields.Single(x => x.Name.Contains(item.Value.Name)).Value = GetCommnadListFormatted(moduleEmoteOrders.Single(x => x.Key.Name == r.Emote.Name).Value); modifyHelp(c, r.MessageId, embed.Build(), r); return(Task.CompletedTask); }); } msg = await InlineReactionReplyAsync(reactionCallbacksData); List <EmbedFieldBuilder> getOriginalFields() => moduleEmoteOrders.Select(x => new EmbedFieldBuilder() .WithName($"{x.Key.Name} {x.Value.Name}") .WithValue(x.Value.Summary ?? "In Development".MarkdownCodeBlock())) .ToList(); async Task onExpire() { var expireEmbed = Context.Channel.GetMessageAsync(msg.Id).Result.Embeds.First() .ToEmbedBuilder() .WithFooter("Emote interactive expired"); expireEmbed.Description = expireEmbed.Description.Replace("Press the corresponding emote or e", "E"); _ = msg.RemoveAllReactionsAsync(); await msg.ModifyAsync(x => x.Embed = expireEmbed.Build()); } void modifyHelp(SocketCommandContext modifyingContext, ulong messageId, Embed expandedEmbed, SocketReaction reaction) { var reactionMsg = (IUserMessage)modifyingContext.Channel.GetMessageAsync(messageId).Result; reactionMsg.RemoveReactionAsync(reaction.Emote, reaction.User.Value); reactionMsg.ModifyAsync(x => { x.Embed = expandedEmbed; }); } }
public async Task <RuntimeResult> Privacy() { string listToShow = ""; string[] configList = Locate("PrivacyConfigList").Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None); for (int i = 0; i < configList.Length; i++) { listToShow += $"**{i + 1}.** {configList[i]}\n"; } var userConfig = GuildUtils.UserConfigCache.GetValueOrDefault(Context.User.Id, new UserConfig(Context.User.Id)); string valueList = Locate(userConfig !.IsOptedOutSnipe); IUserMessage message = null; var builder = new EmbedBuilder() .WithTitle(Locate("PrivacyPolicy")) .AddField(Locate("WhatDataWeCollect"), Locate("WhatDataWeCollectList")) .AddField(Locate("WhenWeCollectData"), Locate("WhenWeCollectDataList")) .AddField(Locate("PrivacyConfig"), Locate("PrivacyConfigInfo")) .AddField(Locate("Option"), listToShow, true) .AddField(Locate("Value"), valueList, true) .WithColor(FergunClient.Config.EmbedColor); var data = new ReactionCallbackData(null, builder.Build(), false, false, TimeSpan.FromMinutes(5)) .AddCallBack(new Emoji("1️⃣"), async(_, reaction) => { userConfig.IsOptedOutSnipe = !userConfig.IsOptedOutSnipe; await HandleReactionAsync(reaction); }) .AddCallBack(new Emoji("❌"), async(_, reaction) => { await message.TryDeleteAsync(); }); message = await InlineReactionReplyAsync(data); return(FergunResult.FromSuccess()); async Task HandleReactionAsync(SocketReaction reaction) { FergunClient.Database.InsertOrUpdateDocument(Constants.UserConfigCollection, userConfig); GuildUtils.UserConfigCache[Context.User.Id] = userConfig; valueList = Locate(userConfig.IsOptedOutSnipe); if (reaction.Emote.Name == "1️⃣" && userConfig.IsOptedOutSnipe) { var toPurge = FergunClient.MessageCache.Where(p => p.Value.Author.Id == reaction.UserId).ToList(); var removed = toPurge.Count(p => FergunClient.MessageCache.TryRemove(p.Key, out _)); if (removed > 0) { await _logService.LogAsync(new LogMessage(LogSeverity.Verbose, "Command", $"Privacy: Removed {removed} deleted/edited messages from the cache from user {reaction.UserId}.")); } } builder.Fields[4] = new EmbedFieldBuilder { Name = Locate("Value"), Value = valueList, IsInline = true }; _ = message !.RemoveReactionAsync(reaction.Emote, reaction.UserId); await message.ModifyAsync(x => x.Embed = builder.Build()); } }