public async Task Help(int page = 1) { if (page <= 0) { throw new DiscordCommandException("Number too low", $"{Context.User.Mention}, you can't go to {(page == 0 ? "the zeroth" : "a negative")} page"); } const int NUM_PER_PAGE = 5; int totalPages = (int)Math.Ceiling(HelpHelper.AllCommands.Count / (double)NUM_PER_PAGE); if (page > totalPages) { throw new DiscordCommandException("Number too high", $"{Context.User.Mention}, you can't go to page {page}, there are only {totalPages}"); } ReactionMessageHelper.CreatePaginatedMessage(Context, await ReplyAsync(embed: buildPage(page)), totalPages, page, m => { return(Task.FromResult(((string)null, buildPage(m.CurrentPage)))); }); Embed buildPage(int num) { EmbedBuilder result = new EmbedBuilder(); result.WithTitle("Help"); result.WithFooter($"Page {num} of {totalPages}"); var commands = HelpHelper.AllCommands.Skip((num - 1) * NUM_PER_PAGE).Take(NUM_PER_PAGE); foreach (var c in commands.OrderBy(c => c.Command)) { result.AddField(c.ToString(), c.Summary ?? "*No help text provided*"); } return(result.Build()); } }
public async Task MultiCustom([Remainder] string input) { var emoji = input.Split(' '); var message = await ReplyAsync("Select one or more reactions"); ReactionMessageHelper.CreateReactionMessage(Context, message, emoji.Select(x => new KeyValuePair <string, Func <ReactionMessage, Task> >(x, async r => await ReplyAsync($"{x} selected"))).ToDictionary(x => x.Key, x => x.Value), true); }
public async Task Pages() { var message = await ReplyAsync("Page 1"); ReactionMessageHelper.CreatePaginatedMessage(Context, message, 10, 1, m => { return(Task.FromResult(($"Page {m.CurrentPage}", (Embed)null))); });
public async Task SellEmoji([Summary("Which market to sell emoji in, either \"global\" or the \"local\" server market")] Market market, [Summary("Which emoji you want to sell")] string emoji, [Summary("How much you want to sell the emoji for")] long price) { if (!EmojiHelper.IsValidEmoji(emoji)) { throw new DiscordCommandException("Bad emoji", $"{emoji} cannot be bought or sold"); } var inventory = Context.GetInventory(Context.CallerProfile); if (price <= 0) { throw new DiscordCommandException("Number too low", $"{Context.User.Mention}, you can't sell things for {(price == 0 ? "" : "less than ")}no money"); } if (!inventory.HasEmoji(emoji)) { throw new DiscordCommandException("Nothing to sell", $"{Context.User.Mention}, you don't have any {emoji} to sell"); } var message = await ReplyAsync($"{Context.User.Mention}, are you sure you want to sell {emoji} for {Context.Money(price)}?"); ReactionMessageHelper.CreateConfirmReactionMessage(Context, message, async r => { Context.ClearCachedValues(); inventory = Context.GetInventory(Context.User); if (!inventory.HasEmoji(emoji)) { await message.ModifyAsync(mod => { mod.Content = ""; EmbedBuilder builder = new EmbedBuilder(); builder.WithColor(Color.Red); builder.WithTitle(Strings.SomethingChanged); builder.WithDescription($"{Context.User.Mention}, you no longer have any {emoji} to sell"); mod.Embed = builder.Build(); }); return; } var(toSell, index) = inventory.Enumerate(emoji).Select((e, i) => (e, i)).OrderBy(e => e.e.Transactions.Count).First(); inventory.RemoveEmoji(emoji, index); MarketHelper.AddListing(Context, MarketId(market), toSell, price); inventory.Save(); await message.ModifyAsync(m => m.Content = $"{Context.WhatDoICall(Context.User)} listed their {emoji} for {Context.Money(price)} in {Context.GetMarketName(MarketId(market))}"); }, async r => { await message.ModifyAsync(m => m.Content = $"Cancelled listing of {emoji}"); }); }
private async Task Client_ReactionAdded(Cacheable <IUserMessage, ulong> cachedMessage, ISocketMessageChannel channel, SocketReaction reaction) { using var scope = MainProvider.CreateScope(); var message = await cachedMessage.GetOrDownloadAsync(); await ReactionMessageHelper.HandleReactionMessage(channel, Client.CurrentUser, reaction, message); await scope.ServiceProvider.GetRequiredService <IssueConfirmationHelper>().HandleMessageReaction(channel, reaction, message); await scope.ServiceProvider.GetRequiredService <IssueHelper>().HandleLogMessageReaction(channel, reaction, reaction.User.IsSpecified ? reaction.User.Value : null, message); }
public async Task Buy([Summary("How many boxes to buy")] int count = 1, [Summary("The type of box to buy")] string type = "normal") { var availableVarieties = LootBoxHelper.GetAllLootBoxNames(Context.Guild.Id); if (!availableVarieties.Contains(type)) { throw new DiscordCommandException("Bad lootbox type", $"{type} isn't a lootbox you can buy, try {(availableVarieties.Count == 1 ? "\"" : "one of these:\n")}{string.Join(", ", availableVarieties)}{(availableVarieties.Count == 1 ? "\"" : "")}"); } var variety = LootBoxHelper.GetAllLootBoxes(Context)[type]; var inventory = Context.GetInventory(Context.User); if (inventory.Currency < variety.Cost) { throw new DiscordCommandException("Not enough currency", $"{Context.WhatDoICall(Context.User)}, you can't afford to buy one"); } int actualCount = (int)Math.Min(inventory.Currency / variety.Cost, count); long cost = actualCount * variety.Cost; string text; if (count > actualCount) { text = $"{Context.User.Mention}, you can only afford {(actualCount == 1 ? "one" : actualCount.ToString())} box{(actualCount == 1 ? "" : "es")}, do you still want to purchase {(actualCount == 1 ? "it" : "them")} for {Context.Money(cost)}?"; } else { text = $"{Context.User.Mention}, are you sure you want to buy {(actualCount == 1 ? "one" : actualCount.ToString())} box{(actualCount == 1 ? "" : "es")} for {Context.Money(cost)}?"; } var message = await Context.Channel.SendMessageAsync(text); ReactionMessageHelper.CreateConfirmReactionMessage(Context, message, async onOkay => { var modify = message.ModifyAsync(m => m.Content = $"{Context.WhatDoICall(Context.User)} bought {(actualCount == 1 ? "one" : actualCount.ToString())} box{(actualCount == 1 ? "" : "es")} for {Context.Money(cost)}"); inventory.Currency -= cost; inventory.AddLoot(type, actualCount); inventory.Save(); await modify; }, async onDecline => { await message.ModifyAsync(m => m.Content = $"Lootbox purchase cancelled"); }); }
public async Task MultiReactionMessage() { var message = await ReplyAsync("Test multi generic reaction message"); ReactionMessageHelper.CreateCustomReactionMessage(Context, message, async(r, s) => await ReplyAsync($"[Multi] Received reaction {s}"), true); }
public async Task ReactionMessage() { var message = await ReplyAsync("Test generic reaction message"); ReactionMessageHelper.CreateCustomReactionMessage(Context, message, async(r, s) => await ReplyAsync($"Received reaction {s}")); }
public async Task ConfirmMessage() { var message = await ReplyAsync("Test confirm message"); ReactionMessageHelper.CreateConfirmReactionMessage(Context, message, async r => await ReplyAsync("Positive response received"), async r => await ReplyAsync("Negative response received")); }
public async Task ViewListings([Summary("Which market to see listings in, either \"global\" or the \"local\" server market")] Market market, [Summary("Which emoji to see listings of, or \"all\"")] string emoji = "all", [Summary("How to sort the listings. Try \"pricy\" or \"cheap\""), Remainder] string sorting = "lowest") { string[] HIGH_TO_LOW = { "highest", "highest first", "highest to lowest", "greatest to least", "expensive", "pricy", "g2l", "h2l" }; string[] LOW_TO_HIGH = { "lowest", "lowest first", "lowest to highest", "least to greatest", "cheap", "affordable", "l2g", "l2h" }; var m = MarketHelper.GetOrCreate(Context.MarketCollection, MarketId(market)); IEnumerable <(string e, Listing l)> listings; if (emoji == "all") { listings = m.Listings.SelectMany(kv => kv.Value.Select(l => (kv.Key, l))); if (listings.Count() == 0) { throw new DiscordCommandException("Nothing to show", $"There are no listings in {Context.GetMarketName(MarketId(market))}"); } } else { if (!EmojiHelper.IsValidEmoji(emoji)) { throw new DiscordCommandException("Bad emoji", $"{emoji} cannot be bought or sold"); } if (!m.Listings.ContainsKey(emoji)) { throw new DiscordCommandException("Nothing to show", $"There are no listings for {emoji} in {Context.GetMarketName(MarketId(market))}"); } listings = m.Listings[emoji].Select(l => (emoji, l)); } if (HIGH_TO_LOW.Contains(sorting.Trim())) { listings = listings.OrderByDescending(p => p.l.Price); } else { listings = listings.OrderBy(p => p.l.Price); } string title = $"{(emoji == "all" ? "All listings" : emoji)} in {Context.GetMarketName(MarketId(market))}"; const int NUM_PER_PAGE = 10; int totalPages = (listings.Count() + NUM_PER_PAGE - 1) / NUM_PER_PAGE; Embed getPage(int page) { EmbedBuilder builder = new EmbedBuilder(); StringBuilder stringBuilder = new StringBuilder(); List <string> contents = new List <string>(); foreach (var(e, listing) in listings.Skip((page - 1) * NUM_PER_PAGE).Take(NUM_PER_PAGE)) { stringBuilder.Append($"{e} for {Context.Money(listing.Price)} by {(market == Market.G ? Context.Bot.Client.GetUser(listing.SellerId).ToString() : Context.WhatDoICall(listing.SellerId))}\n"); } builder.AddField(new EmbedFieldBuilder().WithName(title).WithValue(stringBuilder.ToString())); builder.Footer = new EmbedFooterBuilder().WithText($"Page {page} of {totalPages}"); return(builder.Build()); } var message = await ReplyAsync(embed : getPage(1)); ReactionMessageHelper.CreatePaginatedMessage(Context, message, totalPages, 1, pg => Task.FromResult(("", getPage(pg.CurrentPage)))); }
public async Task Sell([Summary("What kind of thing you want to sell, either stocks or crypto")] string type, [Summary("The name of the thing you want to sell")] string name, [Summary("How many you want to sell")] long amount = 1) { var symbolType = StockAPIHelper.GetSymbolTypeFromString(type); name = name.ToLower(); if (amount <= 0) { throw new DiscordCommandException("Number too low", $"{Context.User.Mention}, you can't sell {(amount == 0 ? "" : "less than ")}no {(symbolType == SymbolType.Crypto ? name.ToUpper() : "shares")}"); } var profile = Context.CallerProfile; var investments = symbolType == SymbolType.Stock ? profile.Investments.Stocks.Active : profile.Investments.Crypto.Active; var investmentsInName = investments.GetValueOrDefault(name); if (investmentsInName == null || investmentsInName.Count == 0) { throw new DiscordCommandException("Nothing to sell", $"{Context.User.Mention}, you don't have any investments in {name.ToUpper()}"); } SymbolInfo info = await StockAPIHelper.GetSymbolInfo(name, symbolType); long toSell = Math.Min(investmentsInName.Sum(x => x.Amount), amount); long price = (long)Math.Ceiling(info.LatestPrice); long totalSellAmount = toSell * price; string message; if (toSell >= amount) { message = $"{Context.User.Mention}, do you want to sell {toSell} {(symbolType == SymbolType.Crypto ? "" : $"{(toSell == 1 ? "one share" : "shares")} in ")}{name.ToUpper()} for {Context.Money(price * toSell)}?"; } else { message = $"{Context.User.Mention}, you currently have {toSell} {(symbolType == SymbolType.Crypto ? "" : $"{(toSell == 1 ? "one share" : "shares")} in ")}{name.ToUpper()}. Do you still want to sell {(toSell == 1 ? "it" : "them")} for {Context.Money(price * toSell)}?"; } ReactionMessageHelper.CreateConfirmReactionMessage(Context, await ReplyAsync(message), onSell, onReject); async Task onSell(ReactionMessage m) { Context.ClearCachedValues(); profile = Context.CallerProfile; investments = symbolType == SymbolType.Stock ? profile.Investments.Stocks.Active : profile.Investments.Crypto.Active; investmentsInName = investments.GetValueOrDefault(name); if (investmentsInName == null) { await m.Message.ModifyAsync(mod => { mod.Content = ""; EmbedBuilder builder = new EmbedBuilder(); builder.WithColor(Color.Red); builder.WithTitle(Strings.SomethingChanged); builder.WithDescription($"{Context.User.Mention}, you no longer have any investments in {name.ToUpper()}"); mod.Embed = builder.Build(); }); return; } if (investmentsInName.Sum(x => x.Amount) < toSell) { await m.Message.ModifyAsync(mod => { mod.Content = ""; EmbedBuilder builder = new EmbedBuilder(); builder.WithColor(Color.Red); builder.WithTitle(Strings.SomethingChanged); builder.WithDescription($"{Context.User.Mention}, you no longer have {toSell} {(symbolType == SymbolType.Crypto ? "" : $"{(toSell == 1 ? "one share" : "shares")} in ")}{name.ToUpper()}"); mod.Embed = builder.Build(); }); return; } var sold = new List <Investment>(); Investment remainderToReturn = null; Investment remainderToStore = null; long totalPurchaseAmount = 0; long stillToSell = toSell; foreach (var inv in investmentsInName.OrderByDescending(x => Math.Abs(price - x.PurchasePrice))) { if (stillToSell >= inv.Amount) { totalPurchaseAmount += inv.Amount * (long)Math.Ceiling(inv.PurchasePrice); stillToSell -= inv.Amount; sold.Add(inv); } else { totalPurchaseAmount += stillToSell * (long)Math.Ceiling(inv.PurchasePrice); remainderToStore = new Investment { Amount = stillToSell, PurchasePrice = inv.PurchasePrice, PurchaseTimestamp = inv.PurchaseTimestamp, }; remainderToReturn = new Investment { Amount = inv.Amount - stillToSell, PurchasePrice = inv.PurchasePrice, PurchaseTimestamp = inv.PurchaseTimestamp, }; stillToSell = 0; break; } if (stillToSell == 0) { break; } } if (stillToSell > 0) { await m.Message.ModifyAsync(mod => { mod.Content = ""; EmbedBuilder builder = new EmbedBuilder(); builder.WithColor(Color.Red); builder.WithTitle(Strings.SomethingChanged); builder.WithDescription($"{Context.User.Mention}, you no longer have {toSell} {(symbolType == SymbolType.Crypto ? "" : $"{(toSell == 1 ? "one share" : "shares")} in ")}{name.ToUpper()}"); mod.Embed = builder.Build(); }); return; } var oldInvestments = (symbolType == SymbolType.Stock) ? profile.Investments.Stocks.Old : profile.Investments.Crypto.Old; List <Investment> oldInvestmentsInName; if (!oldInvestments.ContainsKey(name)) { oldInvestmentsInName = new List <Investment>(); oldInvestments.Add(name, oldInvestmentsInName); } else { oldInvestmentsInName = oldInvestments[name]; } profile.Currency += totalSellAmount; foreach (var inv in sold) { investmentsInName.Remove(inv); inv.SellPrice = info.LatestPrice; inv.SellTimestamp = DateTimeOffset.Now; oldInvestmentsInName.Add(inv); } if (remainderToReturn != null) { investmentsInName.Add(remainderToReturn); } if (remainderToStore != null) { remainderToStore.SellPrice = info.LatestPrice; remainderToStore.SellTimestamp = DateTimeOffset.Now; oldInvestmentsInName.Add(remainderToStore); } Context.UserCollection.Update(profile); long diff = totalSellAmount - totalPurchaseAmount; string modify; if (diff != 0) { modify = $"{Context.WhatDoICall(Context.User)} sold {toSell} {(symbolType == SymbolType.Crypto ? "" : $"{(toSell == 1 ? "one share" : "shares")} in ")}{name.ToUpper()} for {Context.Money(totalSellAmount)}, {(diff > 0 ? "earning" : "losing")} {Math.Abs(diff)}. That's a {Math.Abs(diff / (double)totalPurchaseAmount):P2} {(diff > 0 ? "gain" : "loss")}"; } else { modify = $"{Context.WhatDoICall(Context.User)} sold {toSell} {(symbolType == SymbolType.Crypto ? "" : $"{(toSell == 1 ? "one share" : "shares")} in ")}{name.ToUpper()} for {Context.Money(totalSellAmount)}, breaking even"; } await m.Message.ModifyAsync(p => p.Content = modify); } async Task onReject(ReactionMessage m) { await m.Message.ModifyAsync(p => p.Content = $"Sale of {(symbolType == SymbolType.Crypto ? "" : $"{ (toSell == 1 ? "one share" : "shares")} in ")}{name.ToUpper()} canceled"); } }
public async Task Buy([Summary("What kind of thing you want to buy, either stocks or crypto")] string type, [Summary("The name of the thing you want to buy")] string name, [Summary("How many you want to buy")] long amount = 1) { var symbolType = StockAPIHelper.GetSymbolTypeFromString(type); name = name.ToLower(); if (amount <= 0) { throw new DiscordCommandException("Number too low", $"{Context.User.Mention}, you can't purchase {(amount == 0 ? "" : "less than ")}no {(symbolType == SymbolType.Crypto ? name.ToUpper() : "shares")}"); } var profile = Context.CallerProfile; var info = await StockAPIHelper.GetSymbolInfo(name, symbolType); if (profile.Currency < info.LatestPrice) { throw new DiscordCommandException("Not enough currency", $"{Context.User.Mention}, you need {Context.Money((long) info.LatestPrice - profile.Currency)} more to buy a single {(symbolType == SymbolType.Crypto ? name.ToUpper() : "share")}"); } long price = ((long)Math.Ceiling(info.LatestPrice)); long canBuy = profile.Currency / price; long toBuy = Math.Min(canBuy, amount); string message; if (canBuy >= amount) { message = $"{Context.User.Mention}, do you want to purchase {toBuy} {(symbolType == SymbolType.Crypto ? "" : $"{(toBuy == 1 ? "one share" : "shares")} in ")}{name.ToUpper()} for {Context.Money(price * toBuy)}? You currently have {Context.Money(profile.Currency)}."; } else { message = $"{Context.User.Mention}, you currently have {Context.Money(profile.Currency)}, that's only enough to buy {toBuy} {(symbolType == SymbolType.Crypto ? "" : $"{(toBuy == 1 ? "one share" : "shares")} in ")}{name.ToUpper()}. Do you still want to buy {(toBuy == 1 ? "it" : "them")} for {Context.Money(price * toBuy)}?"; } ReactionMessageHelper.CreateConfirmReactionMessage(Context, await ReplyAsync(message), onPurchase, onReject); async Task onPurchase(ReactionMessage m) { Context.ClearCachedValues(); profile = Context.CallerProfile; if (profile.Currency < price * toBuy) { await m.Message.ModifyAsync(mod => { mod.Content = ""; EmbedBuilder builder = new EmbedBuilder(); builder.WithColor(Color.Red); builder.WithTitle(Strings.SomethingChanged); builder.WithDescription($"{Context.User.Mention}, you no longer have enough to buy {toBuy} {(symbolType == SymbolType.Crypto ? "" : $"{(toBuy == 1 ? "one share" : "shares")} in ")}{name.ToUpper()}"); mod.Embed = builder.Build(); }); return; } profile.Currency -= price * toBuy; var investments = symbolType == SymbolType.Stock ? profile.Investments.Stocks.Active : profile.Investments.Crypto.Active; if (!investments.ContainsKey(name)) { investments.Add(name, new List <Investment>()); } investments[name].Add(new Investment { Amount = toBuy, PurchasePrice = info.LatestPrice, PurchaseTimestamp = DateTimeOffset.Now }); Context.UserCollection.Update(profile); await m.Message.ModifyAsync(properties => properties.Content = $"{Context.WhatDoICall(Context.User)} bought {toBuy} {(symbolType == SymbolType.Crypto ? "" : $"{(toBuy == 1 ? "one share" : "shares")} in ")}{name.ToUpper()} for {Context.Money(price * toBuy)}"); } async Task onReject(ReactionMessage m) { await m.Message.ModifyAsync(properties => properties.Content = $"Purchase of {(symbolType == SymbolType.Crypto ? "" : $"{ (toBuy == 1 ? "one share" : "shares")} in ")}{name.ToUpper()} canceled"); } }
public async Task Open([Summary("How many boxes to open")] int count = 1, [Summary("The type of box to open")] string type = "normal") { var availableVarieties = LootBoxHelper.GetAllLootBoxNames(Context.Guild.Id); if (!availableVarieties.Contains(type)) { throw new DiscordCommandException("Bad lootbox type", $"{type} isn't a lootbox you can buy, try {(availableVarieties.Count == 1 ? "\"" : "one of these:\n")}{string.Join(", ", availableVarieties)}{(availableVarieties.Count == 1 ? "\"" : "")}"); } if (count > 5) { count = 5; await ReplyAsync($"{Context.User.Mention}, you can only open 5 boxes at a time"); } var inventory = Context.GetInventory(Context.User); var variety = LootBoxHelper.GetAllLootBoxes(Context)[type]; int available = inventory.GetNumLootBoxes(type); if (available >= count) { inventory.RemoveBoxes(type, count); await Open(inventory, variety, count); return; } if (available == 0 && inventory.Currency < variety.Cost) { throw new DiscordCommandException("Not enough currency", $"{Context.User.Mention}, you don't have any to open and can't afford to buy one"); } int needToBuy = count - available; int canBuy = (int)Math.Min(inventory.Currency / variety.Cost, needToBuy); if (canBuy == 0) { inventory.RemoveBoxes(type, available); await ReplyAsync($"{Context.User.Mention}, you only had {available} to open"); await Open(inventory, variety, available); return; } long cost = canBuy * variety.Cost; int toOpen = available + canBuy; string text; if (needToBuy > canBuy) { text = $"{Context.User.Mention}, you {(available == 0 ? "don't have any" : $"have {(available == 1 ? "one" : available.ToString())}")} right now, and you can only afford {(canBuy == 1 ? "one" : canBuy.ToString())} box{(canBuy == 1 ? "" : "es")}, do you still want to purchase {(canBuy == 1 ? "it" : "them")} for {Context.Money(cost)} and open {(toOpen == 1 ? "one" : toOpen.ToString())}?"; } else { text = $"{Context.User.Mention}, you {(available == 0 ? "don't have any" : $"have {(available == 1 ? "one" : available.ToString())}")}, are you sure you want to buy {(canBuy == 1 ? "one" : canBuy.ToString())} box{(canBuy == 1 ? "" : "es")} for {Context.Money(cost)} and open the {(toOpen == 1 ? "one" : toOpen.ToString())}?"; } var message = await Context.Channel.SendMessageAsync(text); ReactionMessageHelper.CreateConfirmReactionMessage(Context, message, async onOkay => { Context.ClearCachedValues(); inventory = Context.GetInventory(Context.User); if (inventory.GetNumLootBoxes(type) < available) { await message.ModifyAsync(mod => { mod.Content = ""; EmbedBuilder builder = new EmbedBuilder(); builder.WithColor(Color.Red); builder.WithTitle(Strings.SomethingChanged); builder.WithDescription($"{Context.WhatDoICall(Context.User)}, you can no longer have the {(available == 1 ? "one" : available.ToString())} box{(available == 1 ? "" : "es")} to open"); mod.Embed = builder.Build(); }); return; } if (inventory.Currency < cost) { await message.ModifyAsync(mod => { mod.Content = ""; EmbedBuilder builder = new EmbedBuilder(); builder.WithColor(Color.Red); builder.WithTitle(Strings.SomethingChanged); builder.WithDescription($"{Context.WhatDoICall(Context.User)}, you can no longer afford to buy the {(canBuy == 1 ? "one" : canBuy.ToString())}"); mod.Embed = builder.Build(); }); return; } var modify = message.ModifyAsync(m => m.Content = $"{Context.WhatDoICall(Context.User)} bought {(canBuy == 1 ? "one" : canBuy.ToString())} box{(canBuy == 1 ? "" : "es")} for {Context.Money(cost)}"); inventory.Currency -= cost; if (available > 0) { inventory.RemoveBoxes(type, available); } await Open(inventory, variety, toOpen); await modify; }, async onDecline => { await message.ModifyAsync(m => m.Content = $"Lootbox purchase cancelled"); }); return; }
private async Task Client_ReactionAdded(Cacheable <IUserMessage, ulong> cachedMessage, ISocketMessageChannel channel, SocketReaction reaction) { var message = await cachedMessage.GetOrDownloadAsync(); await ReactionMessageHelper.HandleReactionMessage(channel, Client.CurrentUser, reaction, message); }