// Creates an actual Discord embed with the assumption that all fields in the input are within the character constraints
        public static DiscordEmbed BuildDiscordEmbed(DiscordLinkEmbed embedData)
        {
            DiscordEmbedBuilder builder = new DiscordEmbedBuilder();

            builder.WithTitle(embedData.Title);
            builder.WithDescription(embedData.Description);
            builder.WithFooter(embedData.Footer);
            builder.WithColor(EmbedColor);

            if (!string.IsNullOrEmpty(embedData.Thumbnail))
            {
                try
                {
                    builder.WithThumbnail(embedData.Thumbnail);
                }
                catch (UriFormatException e)
                {
                    Logger.Debug("Failed to include thumbnail in Server Info embed. Error: " + e);
                }
            }

            foreach (DiscordLinkEmbedField field in embedData.Fields)
            {
                builder.AddField(field.Title, field.Text);
            }

            return(builder.Build());
        }
Example #2
0
            public static void FormatTrades(string matchedName, bool isItem, StoreOfferList groupedBuyOffers, StoreOfferList groupedSellOffers, out DiscordLinkEmbed embedContent)
            {
                Func <Tuple <StoreComponent, TradeOffer>, string> getLabel;

                if (isItem)
                {
                    getLabel = t => t.Item1.Parent.Owners.Name;
                }
                else
                {
                    getLabel = t => t.Item2.Stack.Item.DisplayName;
                }
                var fieldEnumerator = TradeOffersToFields(groupedBuyOffers, groupedSellOffers, getLabel);

                // Format message
                DiscordLinkEmbed embed = new DiscordLinkEmbed()
                                         .WithTitle($"Trades for {matchedName}");

                if (groupedSellOffers.Count() > 0 || groupedBuyOffers.Count() > 0)
                {
                    foreach (var stringTuple in fieldEnumerator)
                    {
                        embed.AddField(stringTuple.Item1, stringTuple.Item2);
                    }
                    embed.WithFooter(GetStandardEmbedFooter());
                }
                else
                {
                    embed.WithTitle($"No trade offers found for {matchedName}");
                }
                embedContent = embed;
            }
Example #3
0
        protected override void GetDisplayContent(DiscordTarget target, out List <Tuple <string, DiscordLinkEmbed> > tagAndContent)
        {
            tagAndContent = new List <Tuple <string, DiscordLinkEmbed> >();

            PlayerListChannelLink playerListLink = target as PlayerListChannelLink;

            if (playerListLink == null)
            {
                return;
            }

            string tag     = BaseTag;
            string title   = "Players";
            string content = $"\n{MessageBuilder.Shared.GetPlayerList(playerListLink.UseLoggedInTime)}";

            if (playerListLink.UsePlayerCount == true)
            {
                title = MessageBuilder.Shared.GetPlayerCount() + " Players Online";
            }

            DiscordLinkEmbed embed = new DiscordLinkEmbed()
                                     .WithTitle(title)
                                     .WithDescription(content)
                                     .WithFooter(MessageBuilder.Discord.GetStandardEmbedFooter());

            tagAndContent.Add(new Tuple <string, DiscordLinkEmbed>(tag, embed));
        }
Example #4
0
            public static string EmbedToText(string textContent, DiscordLinkEmbed embedContent)
            {
                string message = "";

                if (!string.IsNullOrEmpty(textContent))
                {
                    message += textContent + "\n\n";
                }
                if (embedContent != null)
                {
                    if (!string.IsNullOrEmpty(embedContent.Title))
                    {
                        message += embedContent.Title + "\n\n";
                    }

                    foreach (DiscordLinkEmbedField field in embedContent.Fields)
                    {
                        message += "**" + field.Title + "**\n" + field.Text + "\n\n";
                    }

                    if (!string.IsNullOrEmpty(embedContent.Footer))
                    {
                        message += embedContent.Footer;
                    }
                }
                return(message.Trim());
            }
        protected override void GetDisplayContent(DiscordTarget target, out List <Tuple <string, DiscordLinkEmbed> > tagAndContent)
        {
            tagAndContent = new List <Tuple <string, DiscordLinkEmbed> >();
            ServerInfoChannel serverInfoChannel = target as ServerInfoChannel;

            if (serverInfoChannel == null)
            {
                return;
            }

            DiscordLinkEmbed content = MessageBuilder.Discord.GetServerInfo(GetServerInfoFlagForChannel(serverInfoChannel));

            tagAndContent.Add(new Tuple <string, DiscordLinkEmbed>(BaseTag, content));
        }
Example #6
0
            public static DiscordLinkEmbed GetVerificationDM(User ecoUser)
            {
                DLConfigData config     = DLConfig.Data;
                ServerInfo   serverInfo = NetworkManager.GetServerInfo();
                string       serverName = MessageUtil.StripTags(!string.IsNullOrWhiteSpace(config.ServerName) ? DLConfig.Data.ServerName : MessageUtil.StripTags(serverInfo.Description));

                DiscordLinkEmbed embed = new DiscordLinkEmbed();

                embed.WithTitle("Account Linking Verification");
                embed.AddField("Initiator", MessageUtil.StripTags(ecoUser.Name));
                embed.AddField("Description", $"Your Eco account has been linked to your Discord account on the server \"{serverName}\".");
                embed.AddField("Action Required", $"If you initiated this action, use the command `{config.DiscordCommandPrefix}verifylink` to verify that these accounts should be linked.");
                embed.WithFooter("If you did not initiate this action, notify a server admin.\nThe account link cannot be used until verified.");
                return(embed);
            }
            async static Task Respond(CommandContext ctx, string textContent, DiscordLinkEmbed embedContent)
            {
                // If needed; split the message into multiple parts
                ICollection <string>       stringParts = MessageUtil.SplitStringBySize(textContent, DLConstants.DISCORD_MESSAGE_CHARACTER_LIMIT);
                ICollection <DiscordEmbed> embedParts  = MessageUtil.BuildDiscordEmbeds(embedContent);

                if (stringParts.Count <= 1 && embedParts.Count <= 1)
                {
                    DiscordEmbed embed = (embedParts.Count >= 1) ? embedParts.First() : null;
                    await ctx.RespondAsync(textContent, isTTS : false, embed);
                }
                else
                {
                    // Either make sure we have permission to use embeds or convert the embed to text
                    foreach (string textMessagePart in stringParts)
                    {
                        await ctx.RespondAsync(textMessagePart, isTTS : false, null);
                    }
                    foreach (DiscordEmbed embedPart in embedParts)
                    {
                        await ctx.RespondAsync(null, isTTS : false, embedPart);
                    }
                }
            }
 private static async Task RespondToCommand(CommandContext ctx, string fullTextContent, DiscordLinkEmbed embedContent = null)
 {
     async static Task Respond(CommandContext ctx, string textContent, DiscordLinkEmbed embedContent)
Example #9
0
            public static DiscordLinkEmbed GetServerInfo(ServerInfoComponentFlag flag)
            {
                var plugin = DiscordLink.Obj;

                if (plugin == null)
                {
                    return(null);
                }

                DLConfigData config     = DLConfig.Data;
                ServerInfo   serverInfo = NetworkManager.GetServerInfo();

                DiscordLinkEmbed embed = new DiscordLinkEmbed();

                embed.WithFooter(GetStandardEmbedFooter());

                if (flag.HasFlag(ServerInfoComponentFlag.Name))
                {
                    embed.WithTitle($"**{MessageUtil.FirstNonEmptyString(config.ServerName, MessageUtil.StripTags(serverInfo.Description), "[Server Title Missing]")} " + "Server Status" + "**\n" + DateTime.Now.ToShortDateString() + " : " + DateTime.Now.ToShortTimeString());
                }
                else
                {
                    DateTime time      = DateTime.Now;
                    int      utcOffset = TimeZoneInfo.Local.GetUtcOffset(time).Hours;
                    embed.WithTitle("**" + "Server Status" + "**\n" + "[" + DateTime.Now.ToString("yyyy-MM-dd : HH:mm", CultureInfo.InvariantCulture) + " UTC " + (utcOffset != 0 ? (utcOffset >= 0 ? "+" : "-") + utcOffset : "") + "]");
                }

                if (flag.HasFlag(ServerInfoComponentFlag.Description))
                {
                    embed.WithDescription(MessageUtil.FirstNonEmptyString(config.ServerDescription, MessageUtil.StripTags(serverInfo.Description), "No server description is available."));
                }

                if (flag.HasFlag(ServerInfoComponentFlag.Logo) && !string.IsNullOrWhiteSpace(config.ServerLogo))
                {
                    embed.WithThumbnail(config.ServerLogo);
                }

                if (flag.HasFlag(ServerInfoComponentFlag.ConnectionInfo))
                {
                    string fieldText = "-- Connection info not configured --";
                    string address   = string.Empty;
                    string port      = string.Empty;
                    if (!string.IsNullOrEmpty(config.ServerAddress))
                    {
                        address = config.ServerAddress;
                    }
                    else if (!string.IsNullOrEmpty(serverInfo.Address))
                    {
                        address = serverInfo.Address;
                    }

                    if (!string.IsNullOrEmpty(address))
                    {
                        port      = serverInfo.GamePort.ToString();
                        fieldText = $"{address}:{port}";
                    }

                    embed.AddField("Connection Info", fieldText);
                }

                if (flag.HasFlag(ServerInfoComponentFlag.PlayerCount))
                {
                    embed.AddField("Online Players Count", $"{UserManager.OnlineUsers.Where(user => user.Client.Connected).Count()}/{serverInfo.TotalPlayers}");
                }

                if (flag.HasFlag(ServerInfoComponentFlag.PlayerList))
                {
                    IEnumerable <string> onlineUsers = UserManager.OnlineUsers.Where(user => user.Client.Connected).Select(user => user.Name);
                    string playerList    = onlineUsers.Count() > 0 ? string.Join("\n", onlineUsers) : "-- No players online --";
                    bool   useOnlineTime = flag.HasFlag(ServerInfoComponentFlag.PlayerListLoginTime);
                    embed.AddField("Online Players", Shared.GetPlayerList(useOnlineTime));
                }

                if (flag.HasFlag(ServerInfoComponentFlag.CurrentTime))
                {
                    TimeSpan timeSinceStartSpan = new TimeSpan(0, 0, (int)serverInfo.TimeSinceStart);
                    embed.AddField("Current Time", $"Day {timeSinceStartSpan.Days + 1} {timeSinceStartSpan.Hours.ToString("00")}:{timeSinceStartSpan.Minutes.ToString("00")}"); // +1 days to get start at day 1 just like ingame
                }

                if (flag.HasFlag(ServerInfoComponentFlag.TimeRemaining))
                {
                    TimeSpan timeRemainingSpan = new TimeSpan(0, 0, (int)serverInfo.TimeLeft);
                    bool     meteorHasHit      = timeRemainingSpan.Seconds < 0;
                    timeRemainingSpan = meteorHasHit ? new TimeSpan(0, 0, 0) : timeRemainingSpan;
                    embed.AddField("Time Left Until Meteor", $"{timeRemainingSpan.Days} Days, {timeRemainingSpan.Hours} hours, {timeRemainingSpan.Minutes} minutes");
                }

                if (flag.HasFlag(ServerInfoComponentFlag.MeteorHasHit))
                {
                    TimeSpan timeRemainingSpan = new TimeSpan(0, 0, (int)serverInfo.TimeLeft);
                    embed.AddField("Meteor Has Hit", timeRemainingSpan.Seconds < 0 ? "Yes" : "No");
                }

                if (flag.HasFlag(ServerInfoComponentFlag.ActiveElectionCount))
                {
                    embed.AddField("Active Elections Count", $"{EcoUtil.ActiveElections.Count()}");
                }

                if (flag.HasFlag(ServerInfoComponentFlag.ActiveElectionList))
                {
                    string electionList = string.Empty;
                    foreach (Election election in EcoUtil.ActiveElections)
                    {
                        electionList += $"{MessageUtil.StripTags(election.Name)} **[{election.TotalVotes} Votes]**\n";
                    }

                    if (string.IsNullOrEmpty(electionList))
                    {
                        electionList = "-- No active elections --";
                    }

                    embed.AddField("Active Elections", electionList);
                }

                if (flag.HasFlag(ServerInfoComponentFlag.LawCount))
                {
                    embed.AddField("Law Count", $"{EcoUtil.ActiveLaws.Count()}");
                }

                if (flag.HasFlag(ServerInfoComponentFlag.LawList))
                {
                    string lawList = string.Empty;
                    foreach (Law law in EcoUtil.ActiveLaws)
                    {
                        lawList += $"{MessageUtil.StripTags(law.Name)}\n";
                    }

                    if (string.IsNullOrEmpty(lawList))
                    {
                        lawList = "-- No active laws --";
                    }

                    embed.AddField("Laws", lawList);
                }

                return(embed);
            }
        protected override void GetDisplayContent(DiscordTarget target, out List <Tuple <string, DiscordLinkEmbed> > tagAndContent)
        {
            tagAndContent = new List <Tuple <string, DiscordLinkEmbed> >();
            DiscordLinkEmbed embed       = new DiscordLinkEmbed();
            List <WorkParty> workParties = Registrars.Get <WorkParty>().All <WorkParty>().NonNull().Where(x => x.State == ProposableState.Active).ToList();

            foreach (WorkParty workParty in workParties)
            {
                string tag = $"{BaseTag} [{workParty.Id}]";
                embed.WithTitle(MessageUtil.StripTags(workParty.Name));
                embed.WithFooter(MessageBuilder.Discord.GetStandardEmbedFooter());

                // Workers
                string workersDesc = string.Empty;
                foreach (Laborer laborer in workParty.Laborers)
                {
                    if (laborer.Citizen == null)
                    {
                        continue;
                    }
                    string creator = (laborer.Citizen == workParty.Creator) ? "Creator" : string.Empty;
                    workersDesc += $"{laborer.Citizen.Name} ({creator})\n";
                }

                if (string.IsNullOrWhiteSpace(workersDesc))
                {
                    workersDesc += "--- No Workers Registered ---";
                }
                embed.AddField("Workers", workersDesc);

                // Work
                foreach (Work work in workParty.Work)
                {
                    string        workDesc    = string.Empty;
                    string        workType    = string.Empty;
                    List <string> workEntries = new List <string>();
                    switch (work)
                    {
                    case LaborWork laborWork:
                    {
                        if (!string.IsNullOrEmpty(laborWork.ShortDescriptionRemainingWork))
                        {
                            workType = $"Labor for {laborWork.Order.Recipe.RecipeName}";
                            workEntries.Add(MessageUtil.StripTags(laborWork.ShortDescriptionRemainingWork));
                        }
                        break;
                    }

                    case WorkOrderWork orderWork:
                    {
                        workType = $"Materials for {orderWork.Order.Recipe.RecipeName}";
                        foreach (TagStack stack in orderWork.Order.MissingIngredients)
                        {
                            string itemName = string.Empty;
                            if (stack.Item != null)
                            {
                                itemName = stack.Item.DisplayName;
                            }
                            else if (stack.StackObject != null)
                            {
                                itemName = stack.StackObject.DisplayName;
                            }
                            workEntries.Add($"{itemName} ({stack.Quantity})");
                        }
                        break;
                    }

                    default:
                        break;
                    }

                    if (workEntries.Count > 0)
                    {
                        foreach (string material in workEntries)
                        {
                            workDesc += $"- {material}\n";
                        }

                        if (!string.IsNullOrWhiteSpace(workDesc))
                        {
                            string percentDone = (work.PercentDone * 100.0f).ToString("N1", CultureInfo.InvariantCulture).Replace(".0", "");
                            embed.AddField($"\n {workType} (Weight: {work.Weight.ToString("F1")}) ({percentDone}% completed) \n", workDesc);
                        }
                    }
                }

                // Payment
                string paymentDesc = string.Empty;
                foreach (Payment payment in workParty.Payment)
                {
                    string desc = string.Empty;
                    switch (payment)
                    {
                    case CurrencyPayment currencyPayment:
                    {
                        float currencyAmountLeft = currencyPayment.Amount - currencyPayment.AmountPaid;
                        if (currencyAmountLeft > 0.0f)
                        {
                            desc = $"Receive **{currencyAmountLeft.ToString("F1")} {currencyPayment.Currency.Name}**"
                                   + (currencyPayment.PayType == PayType.SplitByWorkPercent ? ", split based on work performed" : ", split evenly")
                                   + (currencyPayment.PayAsYouGo ? ", paid as work is performed." : ", paid when the project finishes.");
                        }
                        break;
                    }

                    case GrantTitlePayment titlePayment:
                    {
                        desc = $"Receive title `{MessageUtil.StripTags(titlePayment.Title.Name)}` if work contributed is at least *{titlePayment.MinContributedPercent.ToString("F1")}%*.";
                        break;
                    }

                    case KnowledgeSharePayment knowledgePayment:
                    {
                        if (knowledgePayment.Skills.Entries.Count > 0)
                        {
                            desc = $"Receive knowledge of `{MessageUtil.StripTags(knowledgePayment.ShortDescription())}` if work contributed is at least *{knowledgePayment.MinContributedPercent.ToString("F1")}%*.";
                        }
                        break;
                    }

                    case ReputationPayment reputationPayment:
                    {
                        float reputationAmountLeft = reputationPayment.Amount - reputationPayment.AmountPaid;
                        desc = $"Receive **{reputationAmountLeft.ToString("F1")} reputation** from *{workParty.Creator.Name}*"
                               + (reputationPayment.PayType == PayType.SplitByWorkPercent ? ", split based on work performed" : ", split evenly")
                               + (reputationPayment.PayAsYouGo ? ", paid as work is performed." : ", paid when the project finishes.");
                        break;
                    }

                    default:
                        break;
                    }

                    if (!string.IsNullOrEmpty(desc))
                    {
                        paymentDesc += $"- {desc}\n";
                    }
                }

                if (!string.IsNullOrWhiteSpace(paymentDesc))
                {
                    embed.AddField("Payment", paymentDesc);
                }

                if (embed.Fields.Count > 0)
                {
                    tagAndContent.Add(new Tuple <string, DiscordLinkEmbed>(tag, new DiscordLinkEmbed(embed)));
                }

                embed.ClearFields();
            }
        }
        protected override void GetDisplayContent(DiscordTarget target, out List <Tuple <string, DiscordLinkEmbed> > tagAndContent)
        {
            tagAndContent = new List <Tuple <string, DiscordLinkEmbed> >();
            DiscordLinkEmbed embed = new DiscordLinkEmbed();

            embed.WithFooter(MessageBuilder.Discord.GetStandardEmbedFooter());
            foreach (Election election in EcoUtil.ActiveElections)
            {
                string tag = $"{BaseTag} [{election.Id}]";
                embed.WithTitle(MessageUtil.StripTags(election.Name));

                // Proposer name
                embed.AddField("Proposer", election.Creator.Name);

                // Time left
                embed.AddField("Time Left", TimeFormatter.FormatSpan(election.TimeLeft));

                // Process
                embed.AddField("Process", MessageUtil.StripTags(election.Process.Name));

                // Choices
                if (!election.BooleanElection && election.Choices.Count > 0)
                {
                    string choiceDesc = string.Empty;
                    foreach (ElectionChoice choice in election.Choices)
                    {
                        choiceDesc += $"{choice.Name}\n";
                    }
                    embed.AddField("Choices", choiceDesc);
                }

                // Votes
                string voteDesc = string.Empty;
                if (!election.Process.AnonymousVoting)
                {
                    foreach (RunoffVote vote in election.Votes)
                    {
                        string topChoiceName = null;
                        int    topChoiceID   = vote.RankedVotes.FirstOrDefault();
                        foreach (ElectionChoice choice in election.Choices)
                        {
                            if (choice.ID == topChoiceID)
                            {
                                topChoiceName = choice.Name;
                                break;
                            }
                        }
                        voteDesc += $"{vote.Voter.Name} : {topChoiceName}\n";
                    }
                }
                else
                {
                    voteDesc = "--- Anonymous Voting ---";
                }

                if (string.IsNullOrEmpty(voteDesc))
                {
                    voteDesc = "--- No Votes Recorded ---";
                }

                embed.AddField($"Votes ({election.TotalVotes})", voteDesc);

                if (embed.Fields.Count > 0)
                {
                    tagAndContent.Add(new Tuple <string, DiscordLinkEmbed>(tag, new DiscordLinkEmbed(embed)));
                }

                embed.ClearFields();
            }
        }
Example #12
0
        protected override async Task UpdateInternal(DiscordLink plugin, DLEventType trigger, object data)
        {
            if (DLConfig.Data.TradeChannels.Count <= 0)
            {
                return;
            }
            if (!(data is IEnumerable <List <CurrencyTrade> > accumulatedTrades))
            {
                return;
            }


            // Each entry is the summarized trade events for a player and a store
            foreach (List <CurrencyTrade> accumulatedTradeList in accumulatedTrades)
            {
                if (accumulatedTradeList.Count <= 0)
                {
                    continue;
                }

                CurrencyTrade firstTrade = accumulatedTradeList[0];

                DiscordLinkEmbed embed     = new DiscordLinkEmbed();
                string           leftName  = firstTrade.Citizen.Name;
                string           rightName = (firstTrade.WorldObject as WorldObject).Name;
                embed.WithTitle($"{leftName} traded at {MessageUtil.StripTags(rightName)}");

                // Go through all acumulated trade events and create a summary
                string boughtItemsDesc = string.Empty;
                float  boughtTotal     = 0;
                string soldItemsDesc   = string.Empty;
                float  soldTotal       = 0;
                foreach (CurrencyTrade trade in accumulatedTradeList)
                {
                    if (trade.BoughtOrSold == Shared.Items.BoughtOrSold.Buying)
                    {
                        boughtItemsDesc += trade.NumberOfItems + " X " + trade.ItemUsed.DisplayName + " * " + trade.CurrencyAmount / trade.NumberOfItems + " = " + trade.CurrencyAmount + "\n";
                        boughtTotal     += trade.CurrencyAmount;
                    }
                    else if (trade.BoughtOrSold == Shared.Items.BoughtOrSold.Selling)
                    {
                        soldItemsDesc += trade.NumberOfItems + " X " + trade.ItemUsed.DisplayName + " * " + trade.CurrencyAmount / trade.NumberOfItems + " = " + trade.CurrencyAmount + "\n";
                        soldTotal     += trade.CurrencyAmount;
                    }
                }

                if (!boughtItemsDesc.IsEmpty())
                {
                    boughtItemsDesc += "\nTotal = " + boughtTotal.ToString("n2");
                    embed.AddField("Bought", boughtItemsDesc);
                }

                if (!soldItemsDesc.IsEmpty())
                {
                    soldItemsDesc += "\nTotal = " + soldTotal.ToString("n2");
                    embed.AddField("Sold", soldItemsDesc);
                }

                float subTotal = soldTotal - boughtTotal;
                char  sign     = (subTotal > 0.0f ? '+' : '-');
                embed.AddField("Total", sign + Math.Abs(subTotal).ToString("n2") + " " + MessageUtil.StripTags(firstTrade.Currency.Name));

                // Post the trade summary in all trade channels
                foreach (ChannelLink tradeChannel in DLConfig.Data.TradeChannels)
                {
                    if (!tradeChannel.IsValid())
                    {
                        continue;
                    }
                    DiscordGuild discordGuild = plugin.GuildByNameOrId(tradeChannel.DiscordGuild);
                    if (discordGuild == null)
                    {
                        continue;
                    }
                    DiscordChannel discordChannel = discordGuild.ChannelByNameOrId(tradeChannel.DiscordChannel);
                    if (discordChannel == null)
                    {
                        continue;
                    }

                    _ = DiscordUtil.SendAsync(discordChannel, string.Empty, embed);
                    ++_opsCount;
                }
            }
        }
Example #13
0
        protected override void GetDisplayContent(DiscordTarget target, out List <Tuple <string, DiscordLinkEmbed> > tagAndContent)
        {
            tagAndContent = new List <Tuple <string, DiscordLinkEmbed> >();
            IEnumerable <Currency> currencies = CurrencyManager.Currencies;
            var currencyTradesMap             = DLStorage.WorldData.CurrencyToTradeCountMap;
            CurrencyChannelLink currencyLink  = target as CurrencyChannelLink;

            if (currencyLink == null)
            {
                return;
            }

            void AddCurrencyEntry(Currency currency, List <Tuple <string, DiscordLinkEmbed> > tagAndContent)
            {
                DiscordLinkEmbed embed = new DiscordLinkEmbed();

                embed.WithFooter(MessageBuilder.Discord.GetStandardEmbedFooter());

                // Find and sort relevant accounts
                IEnumerable <BankAccount> accounts = BankAccountManager.Obj.Accounts.Where(acc => acc.GetCurrencyHoldingVal(currency) >= 1).OrderByDescending(acc => acc.GetCurrencyHoldingVal(currency));
                var    currencyEnumerator          = accounts.GetEnumerator();
                string topAccounts = string.Empty;

                for (int i = 0; i < currencyLink.MaxTopCurrencyHolderCount && currencyEnumerator.MoveNext(); ++i)
                {
                    // Some bank accounts (e.g treasury) have no creator
                    // Unbacked currencies has their creator owning infinity
                    float currencyAmount = currencyEnumerator.Current.GetCurrencyHoldingVal(currency);
                    if (currencyEnumerator.Current.Creator == null || currencyAmount == float.PositiveInfinity)
                    {
                        --i;
                        continue;
                    }
                    topAccounts += $"**{currencyEnumerator.Current.GetCurrencyHoldingVal(currency):n0}** - {MessageUtil.StripTags(currencyEnumerator.Current.Name)} *({currencyEnumerator.Current.Creator.Name})*\n";
                }

                // Fetch data
                int    tradesCount      = currencyTradesMap.Keys.Contains(currency.Id) ? currencyTradesMap[currency.Id] : 0;
                string backededItemName = currency.Backed ? $"{currency.BackingItem.DisplayName}" : "Personal";

                // Build message
                string circulationDesc  = $"**Total in circulation**: {currency.Circulation:n0}\n";
                string tradesCountDesc  = currencyLink.UseTradeCount ? $"**Total trades**: {tradesCount}\n" : string.Empty;
                string backedItemDesc   = currencyLink.UseBackingInfo ? $"**Backing**: {backededItemName}\n" : string.Empty;
                string coinsPerItemDesc = (currencyLink.UseBackingInfo && currency.Backed) ? $"**Coins per item**: {currency.CoinsPerItem}\n" : string.Empty;
                string topAccountsDesc  = $"**Top accounts**\n{topAccounts}";

                embed.AddField(MessageUtil.StripTags(currency.Name), $"{circulationDesc}{tradesCountDesc}{backedItemDesc}{coinsPerItemDesc}\n{topAccountsDesc}");
                tagAndContent.Add(new Tuple <string, DiscordLinkEmbed>($"{BaseTag} [{currency.Id}]", embed));
            }

            // Figure out which displays to enable based on config
            bool mintedExists = currencies.Any(c => c.Backed);
            bool useMinted    = currencyLink.UseMintedCurrency == CurrencyTypeDisplayCondition.Always ||
                                (mintedExists && currencyLink.UseMintedCurrency == CurrencyTypeDisplayCondition.MintedExists) ||
                                (!mintedExists && currencyLink.UseMintedCurrency == CurrencyTypeDisplayCondition.NoMintedExists);

            bool usePersonal = currencyLink.UsePersonalCurrency == CurrencyTypeDisplayCondition.Always ||
                               (mintedExists && currencyLink.UsePersonalCurrency == CurrencyTypeDisplayCondition.MintedExists) ||
                               (!mintedExists && currencyLink.UsePersonalCurrency == CurrencyTypeDisplayCondition.NoMintedExists);

            if (useMinted)
            {
                IEnumerable <Currency> mintedCurrencies = currencies.Where(c => c.Backed).OrderByDescending(c => currencyTradesMap.Keys.Contains(c.Id) ? currencyTradesMap[c.Id] : 0);
                var currencyEnumerator = mintedCurrencies.GetEnumerator();
                for (int i = 0; i < currencyLink.MaxMintedCount && currencyEnumerator.MoveNext(); ++i)
                {
                    AddCurrencyEntry(currencyEnumerator.Current, tagAndContent);
                }
            }

            if (usePersonal)
            {
                IEnumerable <Currency> personalCurrencies = currencies.Where(c => !c.Backed).OrderByDescending(c => currencyTradesMap.Keys.Contains(c.Id) ? currencyTradesMap[c.Id] : 0);
                var currencyEnumerator = personalCurrencies.GetEnumerator();
                for (int i = 0; i < currencyLink.MaxPersonalCount && currencyEnumerator.MoveNext(); ++i)
                {
                    AddCurrencyEntry(currencyEnumerator.Current, tagAndContent);
                }
            }
        }
        public static async Task SendDMAsync(DiscordMember targetMember, string textContent, DiscordLinkEmbed embedContent = null)
        {
            try
            {
                // If needed; split the message into multiple parts
                ICollection <string>       stringParts = MessageUtil.SplitStringBySize(textContent, DLConstants.DISCORD_MESSAGE_CHARACTER_LIMIT);
                ICollection <DiscordEmbed> embedParts  = MessageUtil.BuildDiscordEmbeds(embedContent);

                if (stringParts.Count <= 1 && embedParts.Count <= 1)
                {
                    DiscordEmbed embed = (embedParts.Count >= 1) ? embedParts.First() : null;
                    await targetMember.SendMessageAsync(textContent, is_tts : false, embed);
                }
                else
                {
                    foreach (string textMessagePart in stringParts)
                    {
                        await targetMember.SendMessageAsync(textMessagePart, is_tts : false, null);
                    }
                    foreach (DiscordEmbed embedPart in embedParts)
                    {
                        await targetMember.SendMessageAsync(null, is_tts : false, embedPart);
                    }
                }
            }
            catch (Newtonsoft.Json.JsonReaderException e)
            {
                Logger.Debug(e.ToString());
            }
            catch (Exception e)
            {
                Logger.Error($"Error occurred while attempting to send Discord message to user \"{targetMember.DisplayName}\". Error message: " + e);
            }
        }
        public static async Task SendAsync(DiscordChannel channel, string textContent, DiscordLinkEmbed embedContent = null)
        {
            try
            {
                if (!ChannelHasPermission(channel, Permissions.SendMessages))
                {
                    return;
                }

                // Either make sure we have permission to use embeds or convert the embed to text
                string fullTextContent = ChannelHasPermission(channel, Permissions.EmbedLinks) ? textContent : MessageBuilder.Discord.EmbedToText(textContent, embedContent);

                // If needed; split the message into multiple parts
                ICollection <string>       stringParts = MessageUtil.SplitStringBySize(fullTextContent, DLConstants.DISCORD_MESSAGE_CHARACTER_LIMIT);
                ICollection <DiscordEmbed> embedParts  = MessageUtil.BuildDiscordEmbeds(embedContent);

                if (stringParts.Count <= 1 && embedParts.Count <= 1)
                {
                    DiscordEmbed embed = (embedParts.Count >= 1) ? embedParts.First() : null;
                    await channel.SendMessageAsync(fullTextContent, tts : false, embed);
                }
                else
                {
                    foreach (string textMessagePart in stringParts)
                    {
                        await channel.SendMessageAsync(textMessagePart, tts : false, null);
                    }
                    foreach (DiscordEmbed embedPart in embedParts)
                    {
                        await channel.SendMessageAsync(null, tts : false, embedPart);
                    }
                }
            }
            catch (Newtonsoft.Json.JsonReaderException e)
            {
                Logger.Debug(e.ToString());
            }
            catch (Exception e)
            {
                Logger.Error($"Error occurred while attempting to send Discord message to channel \"{channel.Name}\". Error message: " + e);
            }
        }
        public static async Task ModifyAsync(DiscordMessage message, string textContent, DiscordLinkEmbed embedContent = null)
        {
            try
            {
                if (!ChannelHasPermission(message.Channel, Permissions.ManageMessages))
                {
                    return;
                }

                if (embedContent == null)
                {
                    await message.ModifyAsync(textContent);
                }
                else
                {
                    try
                    {
                        // Either make sure we have permission to use embeds or convert the embed to text
                        if (ChannelHasPermission(message.Channel, Permissions.EmbedLinks))
                        {
                            await message.ModifyAsync(textContent, MessageUtil.BuildDiscordEmbed(embedContent)); // TODO: Not safe! May require splitting!
                        }
                        else
                        {
                            await message.ModifyEmbedSuppressionAsync(true); // Remove existing embeds

                            await message.ModifyAsync(MessageBuilder.Discord.EmbedToText(textContent, embedContent));
                        }
                    }
                    catch (Exception e)
                    {
                        Logger.Warning("Failed to modify message. The message may be too long. Error message: " + e);
                    }
                }
            }
            catch (DSharpPlus.Exceptions.ServerErrorException e)
            {
                Logger.Debug(e.ToString());
            }
            catch (Newtonsoft.Json.JsonReaderException e)
            {
                Logger.Debug(e.ToString());
            }
            catch (DSharpPlus.Exceptions.NotFoundException e)
            {
                Logger.Debug(e.ToString());
            }
            catch (Exception e)
            {
                Logger.Error("Error occurred while attempting to modify Discord message. Error message: " + e);
            }
        }
        public static List <DiscordEmbed> BuildDiscordEmbeds(DiscordLinkEmbed fullEmbed)
        {
            List <DiscordEmbed> resultEmbeds = new List <DiscordEmbed>();

            if (fullEmbed == null)
            {
                return(resultEmbeds);
            }

            // Count chars needed for title and footer
            int titleFooterCharCount = 0;

            if (fullEmbed.Title != null)
            {
                titleFooterCharCount += fullEmbed.Title.Length;
            }
            if (fullEmbed.Footer != null)
            {
                titleFooterCharCount += fullEmbed.Footer.Length;
            }

            int totalCharsCount = titleFooterCharCount;

            // Count chars needed for fields and track fields that are too long
            List <bool> needsSplitFields = Enumerable.Repeat(false, fullEmbed.Fields.Count).ToList();

            for (int i = 0; i < fullEmbed.Fields.Count; ++i)
            {
                DiscordLinkEmbedField field = fullEmbed.Fields[i];
                int length = field.Title.Length + field.Text.Length;
                if (length > DLConstants.DISCORD_EMBED_FIELD_CHARACTER_LIMIT)
                {
                    needsSplitFields[i] = true;
                }

                totalCharsCount += length;
            }

            // Early escape if no splitting is needed
            if (totalCharsCount <= DLConstants.DISCORD_EMBED_TOTAL_CHARACTER_LIMIT && needsSplitFields.Count <= 0)
            {
                resultEmbeds.Add(BuildDiscordEmbed(fullEmbed));
                return(resultEmbeds);
            }

            // Split too long fields
            List <DiscordLinkEmbedField> splitFields = new List <DiscordLinkEmbedField>();

            for (int i = 0; i < fullEmbed.Fields.Count; ++i)
            {
                DiscordLinkEmbedField field = fullEmbed.Fields[i];
                if (needsSplitFields[i] == true)
                {
                    IEnumerable <string> splits = SplitStringBySize(field.Text, DLConstants.DISCORD_EMBED_FIELD_CHARACTER_LIMIT);
                    int partCount = 1;
                    foreach (string fieldSplit in splits)
                    {
                        splitFields.Add(new DiscordLinkEmbedField($"{field.Title} ({partCount})", fieldSplit));
                        ++partCount;
                    }
                }
                else
                {
                    splitFields.Add(new DiscordLinkEmbedField(fullEmbed.Fields[i].Title, fullEmbed.Fields[i].Text));
                }
            }

            // Create new embeds that fit within the char limits
            List <DiscordLinkEmbed> splitEmbeds       = new List <DiscordLinkEmbed>();
            DiscordLinkEmbed        splitEmbedBuilder = new DiscordLinkEmbed(fullEmbed);

            splitEmbedBuilder.ClearFields();
            int characterCount = 0;
            int fieldCount     = 0;

            foreach (DiscordLinkEmbedField field in splitFields)
            {
                // If adding the next field would bring us over a limit, split into new embeds
                if (characterCount + field.Text.Length > DLConstants.DISCORD_EMBED_TOTAL_CHARACTER_LIMIT || fieldCount + 1 > DLConstants.DISCORD_EMBED_FIELD_COUNT_LIMIT)
                {
                    splitEmbeds.Add(new DiscordLinkEmbed(splitEmbedBuilder));
                    splitEmbedBuilder.ClearFields();
                    characterCount = 0;
                    fieldCount     = 0;
                }

                splitEmbedBuilder.AddField(field.Title, field.Text);
                characterCount += field.Text.Length;
                ++fieldCount;
            }
            splitEmbeds.Add(splitEmbedBuilder);

            // Convert embeds to actual DSharp Discord embeds
            foreach (DiscordLinkEmbed embedData in splitEmbeds)
            {
                resultEmbeds.Add(BuildDiscordEmbed(embedData));
            }

            return(resultEmbeds);
        }