/// <summary>
        /// Initializes a new <see cref="NinjaCatCommandContext" /> class with the provided client and message.
        /// </summary>
        /// <param name="client">The underlying client.</param>
        /// <param name="msg">The underlying message.</param>
        public NinjaCatCommandContext(NinjaCatDiscordClient client, SocketUserMessage msg)
        {
            if (client == null || msg == null)
            {
                throw new ArgumentNullException();
            }

            Client  = client;
            Message = msg;
        }
Example #2
0
        private async void SendMessageShardAsync(NinjaCatDiscordClient client, DiscordSocketClient shard, string message)
        {
            // Announce in the specified channel of each guild.
            foreach (var guild in shard.Guilds)
            {
                // Get channel.
                var channel = client.GetSpeakingChannelForSocketGuild(guild);

                // If the channel is null, continue on to the next guild.
                if (channel == null)
                {
                    client.LogOutput($"ROLLING OVER SERVER (NO SPEAKING): {guild.Name}");
                    continue;
                }

                // Verify we have permission to speak.
                if (!guild.CurrentUser.GetPermissions(channel).SendMessages)
                {
                    client.LogOutput($"ROLLING OVER SERVER (NO PERMS): {guild.Name}");
                    continue;
                }

                try
                {
                    // Wait 2 seconds.
                    await Task.Delay(TimeSpan.FromSeconds(2));

                    // Bot is typing, with added pause for realism.
                    await Context.Channel.TriggerTypingAsync();

                    await Task.Delay(TimeSpan.FromSeconds(2));

                    // Send message.
                    await channel.SendMessageAsync($"Announcement from **{Constants.OwnerName}** (bot owner):\n{message}");
                }
                catch (Exception ex)
                {
                    client.LogOutput($"FAILURE IN SPEAKING FOR {guild.Name}: {ex}");
                }

                // Log server.
                client.LogOutput($"SPOKEN IN SERVER: {guild.Name}");
            }
        }
        private async void SendMessageShardAsync(NinjaCatDiscordClient client, DiscordSocketClient shard, string message)
        {
            // Announce in the specified channel of each guild.
            foreach (var guild in shard.Guilds)
            {
                // Get channel.
                var channel = client.GetSpeakingChannelForSocketGuild(guild);

                // If the channel is null, continue on to the next guild.
                if (channel == null)
                {
                    client.LogInfo($"Rolling over server (disabled) {guild.Name}");
                    continue;
                }

                // Verify we have permission to speak.
                if (!guild.CurrentUser.GetPermissions(channel).SendMessages)
                {
                    client.LogInfo($"Rolling over server (no perms) {guild.Name}");
                    continue;
                }

                try {
                    // Wait 2 seconds.
                    await Task.Delay(TimeSpan.FromSeconds(2));

                    // Bot is typing, with added pause for realism.
                    await Context.Channel.TriggerTypingAsync();

                    await Task.Delay(TimeSpan.FromSeconds(2));

                    // Send message.
                    await channel.SendMessageAsync($"Announcement from **{Constants.OwnerName}** (bot owner):\n{message}");
                }
                catch (Exception ex) {
                    client.LogError($"Failed to speak in {guild.Name}: {ex}");
                }
                client.LogInfo($"Spoke in server {guild.Name}");
            }
        }
        /// <summary>
        /// Starts the bot.
        /// </summary>
        private async Task Start()
        {
            // Create Discord client.
            client = new NinjaCatDiscordClient();

            // Create command service and map.
            var commands   = new CommandService();
            var commandMap = new ServiceCollection();

            // Load commands from assembly.
            await commands.AddModulesAsync(Assembly.GetEntryAssembly());

            // Certain things are to be done when the bot joins a guild.
            client.JoinedGuild += async(guild) =>
            {
                // Pause for 5 seconds.
                await Task.Delay(TimeSpan.FromSeconds(5));

                // Check to see if this is a bot farm.
                if (await CheckBotGuild(guild))
                {
                    return;
                }

                // Create variable for speaking channel mention.
                var speakingChannel = string.Empty;

                // Get speaking channel.
                var channel = client.GetSpeakingChannelForSocketGuild(guild);

                // Update server count.
                await UpdateSiteServerCountAsync();

                // Does the bot have permission to message? If not return.
                if (!channel.Guild.CurrentUser.GetPermissions(channel).SendMessages)
                {
                    return;
                }

                // Get the mention if speaking is enabled.
                if (channel != null)
                {
                    speakingChannel = channel.Mention;
                }

                // Bot is typing in default channel.
                await channel.TriggerTypingAsync();

                // Pause for realism.
                await Task.Delay(TimeSpan.FromSeconds(1));

                // Dev began Oct 2. 2016.
                // Is a speaking channel set?
                if (!string.IsNullOrEmpty(speakingChannel))
                {
                    // Select and send message.
                    switch (client.GetRandomNumber(2))
                    {
                    default:
                        await channel.SendMessageAsync($"{Constants.AboutMessage1}\n\n" +
                                                       $"By default, I'll speak in {speakingChannel}, but you can change it with the **{Constants.CommandPrefix}{Constants.SetChannelCommand}** command.");

                        break;

                    case 1:
                        await channel.SendMessageAsync($"{Constants.AboutMessage2}\n\n" +
                                                       $"I'll speak in {speakingChannel} by default, but it can be changed with the **{Constants.CommandPrefix}{Constants.SetChannelCommand}** command.");

                        break;
                    }
                }
                else
                {
                    // Select and send message.
                    switch (client.GetRandomNumber(2))
                    {
                    default:
                        await channel.SendMessageAsync(Constants.AboutMessage1);

                        break;

                    case 1:
                        await channel.SendMessageAsync(Constants.AboutMessage2);

                        break;
                    }
                }
            };

            // Update count on guild leave.
            client.LeftGuild += async(guild) => await UpdateSiteServerCountAsync();

            // Listen for messages.
            client.MessageReceived += async(message) =>
            {
                // Get the message and check to see if it is a user message.
                var msg = message as IUserMessage;
                if (msg == null)
                {
                    return;
                }

                // Keeps track of where the command begins.
                var pos = 0;

                // Attempt to parse a command.
                if (msg.HasStringPrefixLower(Constants.CommandPrefix, ref pos))
                {
                    var result = await commands.ExecuteAsync(new CommandContext(client, msg), msg.Content.Substring(pos));

                    if (!result.IsSuccess)
                    {
                        // Is the command just unknown? If so, return.
                        if (result.Error == CommandError.UnknownCommand)
                        {
                            return;
                        }

                        // Bot is typing.
                        await msg.Channel.TriggerTypingAsync();

                        // Pause for realism and send message.
                        await Task.Delay(TimeSpan.FromSeconds(0.75));

                        await msg.Channel.SendMessageAsync($"I'm sorry, but something happened. Error: {result.ErrorReason}");
                    }
                    return;
                }
            };

            // Log in to Discord. Token is stored in the Credentials class.
            await client.LoginAsync(TokenType.Bot, Credentials.DiscordToken);

            await client.StartAsync();

            // Check for bot guilds.
            foreach (var shard in client.Shards)
            {
#pragma warning disable 4014
                shard.Connected += async() =>
                {
                    foreach (var guild in shard.Guilds)
                    {
                        CheckBotGuild(guild);
                    }
                    await Task.CompletedTask;
                };
#pragma warning restore 4014
            }

            // Log in to Twitter.
            Auth.SetUserCredentials(Credentials.TwitterConsumerKey, Credentials.TwitterConsumerSecret,
                                    Credentials.TwitterAccessToken, Credentials.TwitterAccessSecret);

            // Create Twitter stream to follow @donasarkar.
            var donaUser = User.GetUserFromScreenName("windowsinsider");
            var stream   = Tweetinvi.Stream.CreateFilteredStream();
            stream.AddFollow(donaUser);

#if DEBUG
            // Used for testing tweets.
            var goldfishUser = User.GetUserFromScreenName("goldfishx64");
            stream.AddFollow(goldfishUser);
#endif

            // Listen for incoming tweets from Dona.
            stream.MatchingTweetReceived += async(s, e) =>
            {
                // Get tweet.
                var tweet = e.Tweet.RetweetedTweet ?? e.Tweet;

                // If the tweet is a reply or if it doesn't belong to a known user, ignore it.
                if (tweet.CreatedBy.Id != donaUser.Id || !string.IsNullOrEmpty(tweet.InReplyToScreenName))
                {
                    return;
                }

                // Log tweet.
                client.LogOutput($"TWEET: {tweet.FullText}");

                // Is it a no-build tweet from Dona?
                if ((tweet.FullText.ToLowerInvariant().Contains("no build") || tweet.FullText.ToLowerInvariant().Contains("no new build") ||
                     tweet.FullText.ToLowerInvariant().Contains("not releasing") || tweet.FullText.ToLowerInvariant().Contains("not flighting")) && tweet.Urls.Count == 0)
                {
                    // Log tweet.
                    client.LogOutput($"TWEET CONFIRMED: NO BUILDS TODAY");

                    // Send message to guilds.
                    foreach (var shard in client.Shards)
                    {
                        SendNoBuildsToShard(shard);
                    }
                }
                else
                {
                    // Try to get a blogs URL.
                    var fullUrl = string.Empty;
                    foreach (var url in tweet.Urls)
                    {
                        for (int t = 0; t < 3; t++)
                        {
                            // Create the HttpClient.
                            using (var httpClient = new HttpClient())
                            {
                                // Retry up to three times.
                                for (int i = 0; i < 3; i++)
                                {
                                    // Get full URL.
                                    var tempUrl  = url.ExpandedURL;
                                    var response = await httpClient.GetAsync(tempUrl);

                                    // If the response was a redirect, try again up to 10 times.
                                    var count = 10;
                                    while ((response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.MovedPermanently || response.StatusCode == HttpStatusCode.Moved) ||
                                           count < 10)
                                    {
                                        tempUrl  = response.Headers.Location.ToString();
                                        response = await httpClient.GetAsync(tempUrl);

                                        count++;
                                    }

                                    // Check to see if the full URL was gotten.
                                    if (response.RequestMessage.RequestUri.ToString().Contains("blogs.windows.com/windowsexperience") && response.RequestMessage.RequestUri.ToString().Contains("insider-preview-build"))
                                    {
                                        fullUrl = response.RequestMessage.RequestUri.ToString();
                                        break;
                                    }
                                    else
                                    {
                                        client.LogOutput($"URLFETCH ERROR: URL wasn't right.");
                                    }

                                    // Did the request fail? Log the error and retry.
                                    if (!response.IsSuccessStatusCode)
                                    {
                                        client.LogOutput($"URLFETCH ERROR: {response.StatusCode}");
                                    }
                                }
                            }

                            // Check to see if URL has what it takes. If not, retry in 5 minutes.
                            if (!string.IsNullOrEmpty(fullUrl) && fullUrl.Contains("blogs.windows.com/windowsexperience") && fullUrl.Contains("insider-preview-build"))
                            {
                                break;
                            }

                            // Clear URL.
                            fullUrl = string.Empty;

                            // Wait 10 minutes.
                            await Task.Delay(TimeSpan.FromMinutes(10));
                        }

                        // Check to see if URL has what it takes. If not, retry in 5 minutes.
                        if (!string.IsNullOrEmpty(fullUrl) && fullUrl.Contains("blogs.windows.com/windowsexperience") && fullUrl.Contains("insider-preview-build"))
                        {
                            break;
                        }

                        // Clear URL.
                        fullUrl = string.Empty;
                    }

                    // If URL is invalid, return.
                    if (string.IsNullOrWhiteSpace(fullUrl))
                    {
                        return;
                    }

                    // Get build numbers. If empty, ignore the tweet.
                    var build  = Regex.Match(fullUrl, @"\d{5,}").Value;
                    var buildM = Regex.Match(fullUrl, @"\d{5,}", RegexOptions.RightToLeft).Value;
                    if (string.IsNullOrWhiteSpace(build))
                    {
                        return;
                    }

                    // Log tweet.
                    client.LogOutput($"TWEET CONFIRMED: NEW BUILD");

                    // Create variables.
                    var ring     = string.Empty;
                    var platform = string.Empty;

                    // Check for fast or slow, or both.
                    if (tweet.FullText.ToLowerInvariant().Contains("fast") && tweet.FullText.ToLowerInvariant().Contains("slow"))
                    {
                        ring = " to both the Fast and Slow rings";
                    }
                    else if (tweet.FullText.ToLowerInvariant().Contains("fast"))
                    {
                        ring = " to the Fast ring" + (fullUrl.ToLowerInvariant().Contains("skip-ahead") ? " (Skip Ahead)" : "");
                    }
                    else if (tweet.FullText.ToLowerInvariant().Contains("slow"))
                    {
                        ring = " to the Slow ring";
                    }

                    // Check for PC or mobile, or both.
                    if (fullUrl.ToLowerInvariant().Contains("pc") && fullUrl.ToLowerInvariant().Contains("mobile") && fullUrl.ToLowerInvariant().Contains("server"))
                    {
                        platform = " for PC, Server, and Mobile";
                    }
                    else if (fullUrl.ToLowerInvariant().Contains("pc") && fullUrl.ToLowerInvariant().Contains("mobile"))
                    {
                        platform = " for both PC and Mobile";
                    }
                    else if (fullUrl.ToLowerInvariant().Contains("pc") && fullUrl.ToLowerInvariant().Contains("server"))
                    {
                        platform = " for both PC and Server";
                    }
                    else if (fullUrl.ToLowerInvariant().Contains("mobile") && fullUrl.ToLowerInvariant().Contains("server"))
                    {
                        platform = " for both Server and Mobile";
                    }
                    else if (fullUrl.ToLowerInvariant().Contains("pc"))
                    {
                        platform = " for PC";
                    }
                    else if (fullUrl.ToLowerInvariant().Contains("mobile"))
                    {
                        platform = " for Mobile";
                    }
                    else if (fullUrl.ToLowerInvariant().Contains("server"))
                    {
                        platform = " for Server";
                    }

                    // Send build to guilds.
                    foreach (var shard in client.Shards)
                    {
                        SendNewBuildToShard(shard, build, buildM, ring + platform, fullUrl);
                    }
                }
            };

            // Listen for stop.
            stream.StreamStopped += (s, e) =>
            {
                // Log error.
                client.LogOutput($"TWEET STREAM STOPPED: {e.Exception}");
            };

            // Create timer for POSTing server count.
            var serverCountTimer = new Timer(async(e) => await UpdateSiteServerCountAsync(), null, TimeSpan.FromMinutes(1), TimeSpan.FromHours(1));

            // Create timer for game play status of builds.
            var buildPlayTimer = new Timer(async(e) => await client.UpdateGameAsync(), null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(30));

            // Start the stream.
            stream.StartStreamMatchingAllConditions();
        }
        /// <summary>
        /// Starts the bot.
        /// </summary>
        private async Task Start()
        {
            client = new NinjaCatDiscordClient();

            // Certain things are to be done when the bot joins a guild.

            /*client.JoinedGuild += async (guild) => {
             *  // Pause for 5 seconds.
             *  await Task.Delay(TimeSpan.FromSeconds(5));
             *
             *  // Check to see if this is a bot farm.
             *  if (await CheckBotGuild(guild))
             *      return;
             *
             *  // Dev began Oct 2. 2016.
             * };*/

            // Log in to Discord. Token is stored in the Credentials class.
            await client.StartBotAsync();

            // Start checking for new builds.
            timerBuild = new Timer(async(s) => {
                // Builds generally release between 10AM and 5PM PST. Do not check outside these times.
                //     if (DateTime.UtcNow.Hour < 17 && !string.IsNullOrWhiteSpace(client.CurrentUrl))
                //     return;

                // If we cannot get the new post, try again later.
                var post = await client.GetLatestBuildPostAsync();
                if (post == null)
                {
                    return;
                }

                // Have we ever seen a post yet? This prevents false announcements if the bot has never seen a post before.
                if (string.IsNullOrWhiteSpace(client.CurrentUrl))
                {
                    client.CurrentUrl = post.Link;
                    client.SaveSettings();
                    client.LogInfo($"Saved post as new latest build: {post.Link}");
                    return;
                }

                // Is the latest post the same? If so, no need to announce it.
                if (client.CurrentUrl == post.Link)
                {
                    return;
                }

                // Stop timer.
                timerBuild.Change(TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(-1));
                client.LogInfo($"New build received");

                // Save post.
                client.CurrentUrl = post.Link;
                client.SaveSettings();

                // Send build to guilds.
                foreach (var shard in client.Shards)
                {
                    client.SendNewBuildToShard(shard, post);
                }
                await client.UpdateGameAsync();

                // Restart timer.
                timerBuild.Change(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
            }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));

            // Wait a minute for bot to start up.
            await Task.Delay(TimeSpan.FromMinutes(1));

            // Create thread for updating game.
            var serverCountThread = new Thread(new ThreadStart(async() => {
                while (true)
                {
                    await client.UpdateGameAsync();
                    await Task.Delay(TimeSpan.FromHours(24));
                }
            }));

            serverCountThread.Start();

            // Wait forever.
            await Task.Delay(-1);
        }