/// <summary>
        /// Gets the latest blog post of the specified type.
        /// </summary>
        /// <param name="type">The type of build to get. Specify <see cref="BuildType.Unknown"/> to get the latest build regardless of type.</param>
        /// <returns>A <see cref="BlogEntry"/> representing the build blog post or null if no build was found.</returns>
        public async Task <BlogEntry> GetLatestBuildPostAsync(BuildType type = BuildType.Unknown)
        {
            BlogEntry post = null;

            try {
                for (int page = 1; page <= 10; page++)
                {
                    // Get page.
                    var doc     = XDocument.Parse(await httpClient.GetStringAsync($"https://blogs.windows.com/windows-insider/feed/?paged={page}"));
                    var entries = from item in doc.Root.Descendants().First(i => i.Name.LocalName == "channel").Elements().Where(i => i.Name.LocalName == "item")
                                  where item.Elements().First(i => i.Name.LocalName == "link").Value.ToLowerInvariant().ContainsAny("insider-preview", "windows-10-build", "windows-11-build")
                                  select item;
                    var posts = entries.Select(async item => await BlogEntry.Create(
                                                   httpClient,
                                                   item.Elements().First(i => i.Name.LocalName == "title").Value,
                                                   item.Elements().First(i => i.Name.LocalName == "link").Value,
                                                   item.Elements().First(i => i.Name.LocalName == "description").Value))
                                .Select(t => t.Result)
                                .Where(i => i != null)
                                .ToList();

                    // Get first post of desired type if a type was specified.
                    if (type == BuildType.Unknown)
                    {
                        post = posts.FirstOrDefault();
                    }
                    else if (type == BuildType.BetaPc)
                    {
                        post = posts.Where(p => p.BuildType == BuildType.BetaPc || p.BuildType == BuildType.DevBetaPc || p.BuildType == BuildType.BetaReleasePreviewPc).FirstOrDefault();
                    }
                    else if (type == BuildType.ReleasePreviewPc)
                    {
                        post = posts.Where(p => p.BuildType == BuildType.ReleasePreviewPc || p.BuildType == BuildType.BetaReleasePreviewPc).FirstOrDefault();
                    }
                    else if (type == BuildType.DevPc)
                    {
                        post = posts.Where(p => p.BuildType == BuildType.DevPc || p.BuildType == BuildType.DevBetaPc).FirstOrDefault();
                    }
                    else
                    {
                        post = posts.Where(p => p.BuildType == type).FirstOrDefault();
                    }
                    if (post != null)
                    {
                        return(post);
                    }
                }
            }
            catch (HttpRequestException ex) {
                LogError($"Exception when getting post for type {type}: {ex}");
                return(null);
            }

            LogError($"Unable to get new post for type {type}");
            return(null);
        }
        public async void SendNewBuildToShard(DiscordSocketClient shard, BlogEntry blogEntry)
        {
            // If the MS server is in this shard, announce there first.
            var msGuild = shard.Guilds.SingleOrDefault(g => g.Id == Constants.MsGuildId);

            if (msGuild != null)
            {
                await SendBuildToGuild(shard, msGuild, blogEntry);
            }

            foreach (var guild in shard.Guilds)
            {
                // Skip MS guild.
                if (guild.Id == Constants.MsGuildId)
                {
                    continue;
                }

                await SendBuildToGuild(shard, guild, blogEntry);
            }
        }
        private async Task SendBuildToGuild(DiscordSocketClient shard, SocketGuild guild, BlogEntry blogEntry)
        {
            var channel = GetSpeakingChannelForSocketGuild(guild);

            if (channel == null)
            {
                LogInfo($"Rolling over {guild.Name} (disabled) ({shard.ShardId}/{Shards.Count - 1})");
                return;
            }

            // Verify we have permission to speak.
            if (guild.CurrentUser?.GetPermissions(channel).SendMessages != true)
            {
                LogInfo($"Rolling over {guild.Name} (no perms) ({shard.ShardId}/{Shards.Count - 1})");
                return;
            }

            // Get all roles.
            var roleDev            = GetRoleForIGuild(guild, RoleType.InsiderDev);
            var roleBeta           = GetRoleForIGuild(guild, RoleType.InsiderBeta);
            var roleReleasePreview = GetRoleForIGuild(guild, RoleType.InsiderReleasePreview);

            var roleText   = string.Empty;
            var typeText   = string.Empty;
            var emotesText = ":smiley_cat:";

            switch (blogEntry.BuildType)
            {
            case BuildType.DevPc:
                roleText    = $"{roleDev?.Mention} ";
                typeText    = " to the Dev Channel";
                emotesText += " :tools:";
                break;

            case BuildType.BetaPc:
                roleText    = $"{roleBeta?.Mention} ";
                typeText    = " to the Beta Channel";
                emotesText += " :paintbrush:";
                break;

            case BuildType.ReleasePreviewPc:
                roleText    = $"{roleReleasePreview?.Mention} ";
                typeText    = " to the Release Preview Channel";
                emotesText += " :package:";
                break;

            case BuildType.BetaReleasePreviewPc:
                roleText    = $"{roleBeta?.Mention} {roleReleasePreview?.Mention} ";
                typeText    = " to the Beta and Release Preview Channels";
                emotesText += " :paintbrush: :package:";
                break;

            case BuildType.DevBetaPc:
                roleText    = $"{roleDev?.Mention} {roleBeta?.Mention} ";
                typeText    = " to the Dev and Beta Channels";
                emotesText += " :tools: :paintbrush:";
                break;

            case BuildType.Server:
                typeText    = " for Server";
                emotesText += " :desktop:";
                break;
            }

            try {
                await StartTyping(channel);

                switch (GetRandomNumber(3))
                {
                default:
                    await channel.SendMessageAsync($"{roleText}{blogEntry.OSName} Insider Preview Build {blogEntry.BuildNumber} has just been released{typeText}! {emotesText}\n{blogEntry.Link}");

                    break;

                case 1:
                    await channel.SendMessageAsync($"{roleText}{blogEntry.OSName} Insider Preview Build {blogEntry.BuildNumber} has just been released{typeText}! Yes! {emotesText}\n{blogEntry.Link}");

                    break;

                case 2:
                    await channel.SendMessageAsync($"{roleText}Better check for updates now! {blogEntry.OSName} Insider Preview Build {blogEntry.BuildNumber} has just been released{typeText}! {emotesText}\n{blogEntry.Link}");

                    break;
                }
            }
            catch (Exception ex) {
                LogError($"Failed to speak in {guild.Name} ({shard.ShardId}/{Shards.Count - 1}): {ex}");
            }

            // Log server.
            LogInfo($"Spoke in {guild.Name} ({shard.ShardId}/{Shards.Count - 1})");
        }