/// <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; }
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); }