Example #1
0
        public static string RetrieveFromKeychain(string Filename)
        {
            // Get the first line so we miss any formatting
            try {
                string RawKey = System.IO.File.ReadAllLines(
                    System.IO.Path.Combine(Utils.BaseFilepath, "keychain", Filename)
                    )[0];
                // Clean token
                string Key = RawKey.Replace("\n", "");

                return(Key);
            } catch (System.IO.FileNotFoundException) {
                if (Filename == "Token.txt")
                {
                    // Sorry, but the script is totally useless without a token
                    AuxillaryLogger.Log(new LogMessage(LogSeverity.Critical, "Login",
                                                       "Failed to retrieve bot token from token.txt in keychain folder. " +
                                                       "The script must now exit. Refer to the README to see how to set up " +
                                                       "a bot and get its user account token."));
                    Utils.Exit();
                }
                else
                {
                    // The other API keys are somewhat optional
                    AuxillaryLogger.Log(new LogMessage(LogSeverity.Warning, "Keychain retrieval",
                                                       $"Failed to retrieve token/API key '{Filename}'. " +
                                                       "Some functions of the bot may be unavailable as a result."));
                }
                return(null);
            } catch (Exception e) {
                // If don't know what happened, then let the user know.
                // Some exceptions which aren't covered here should be obvious to any user
                // e.g. UnauthorizedAccessException/SecurityException
                AuxillaryLogger.Log(new LogMessage(LogSeverity.Error, "Keychain retrieval",
                                                   $"In attempting to retrieve data from the keychain, an unknown error occured.\n" +
                                                   $"Error details are as follows: \"{e.ToString()} - {e.Message}\""));
                return(null);
            }
        }
Example #2
0
            // The actual code which connects our name determinant function to renaming the channel.
            // I didn't want to make the function which determines the name async, as it's more of a utility,
            // so all of the async work is done here.
            public static async Task UpdateVoiceChannel(IVoiceChannel Channel)
            {
                // If the setting is off - don't bother
                if (!Settings.GetGuildSetting <bool>(Channel.Guild, "RenameVoiceChannels"))
                {
                    return;
                }
                // Don't mess with the AFK channel. Nobody plays games in it anyway...hence AFK
                if (Channel.Id == Channel.Guild.AFKChannelId)
                {
                    return;
                }
                string NewName = GetVoiceChannelName(await Channel.GetUsersAsync().Flatten());

                try {
                    await Channel.ModifyAsync(x => x.Name = NewName);
                }
                catch (Exception e) {
                    // If my checks haven't caught someone trying to (most likely) change the voice channel name to something
                    // that it shouldn't be, then we can handle that to avoid crashing the bot
                    AuxillaryLogger.Log(LogSeverity.Error, "VoiceChannelRename",
                                        $"Failed to change the voice channel's name to: {NewName} ({e.ToString()}: {e.Message})");
                }
            }
Example #3
0
        public async Task Google([Remainder] string Query)
        {
            // First and foremost check that the command is enabled:
            if (!Settings.GetGuildSetting <bool>(Context.Guild, "AllowExtraCommands"))
            {
                return;
            }

            // Start time. Later in the embed I want to display search time elapsed,
            // this is the first step of being able to do that.
            var SearchStart = DateTime.UtcNow;

            /* Google's CSE API is utilized by querying the following URL:
             * https://www.googleapis.com/customsearch/v1?parameters
             * You can include such paramaters as (notably) query, API key, custom engine to use,
             * count, data filters and (not as notably) heuristics to provide personalized search
             * results. We won't be using those as this command is going to be used by all sorts
             * of people, in theory. */

            if (String.IsNullOrEmpty(Keychain.GoogleAPIKey))
            {
                AuxillaryLogger.Log(new LogMessage(LogSeverity.Error, "ExtraCommands.cs",
                                                   "A Google query was requested but no API key is present in Clu/keychain/searchkey.txt." +
                                                   " Aborting..."));
                return;
            }

            // Probably the first thing we want to do is anchor in a message that we'll edit later with the embed.
            // Imagine a scenario where a user requests a search and somehow this function hangs for 10 minutes,
            // perhaps due to an API outage, bad connection, some other Web mystery...
            // then once everyone has forgotten about it, the bot posts a random message and nobody has any idea why
            var EmbedMessage = await ReplyAsync("Searching...");

            // Now, we should construct the URL:
            string SearchURL = "https://www.googleapis.com/customsearch/v1" +
                               $"?q={Query}" + // Our query
                               "&cx=011947631902407852034:gq02yx0e1mq" +
                                               // cx: A custom search engine I made that searches the whole web, to reduce setup
                                               // Don't think you can get in trouble for spam on this, unlike API keys,
                                               // so I've left mine in here. If I find out that you can, I'll have to add
                                               // some instructions for making your own in the README in keychain/
                               "&prettyPrint=false" +
                                               // prettyPrint: Disable indendations/line breaks for increased performance
                               "&fields=items(title,snippet,link)" +
                                               // fields: Filter out everything except the results' titles and descriptions.
                                               // Increases performance. Currently hardcoded. May change later.
                               $"&num=3" +
                                               // num: Limit the number of results to how many the user wants
                                               // Defaults to three. Should increase performance.
                               $"&key={Keychain.GoogleAPIKey ?? String.Empty}";
            // key: And finally, add our API key. It can be null, so I've put this here for safety,
            // although the above if statement should prevent a search query from getting
            // this far without any API key.

            // Prepare ourselves to decompress the gzip which we will request
            // Requesting gzip moves a considerable amount of work from the network
            // and then gives it to the CPU. Since network is usually the bottleneck here,
            // there should be substantial performance benefits.
            HttpClientHandler Handler = new HttpClientHandler()
            {
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
            };
            string StringResponse = String.Empty;

            using (var HC = new HttpClient(Handler))
            {
                // Tell Google to send us gzip
                HC.DefaultRequestHeaders.Add("User-Agent", "CluSearch (gzip)");
                HC.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");

                // Pre-declare so we can use outside of try-catch later
                var Response = await HC.GetAsync(new Uri(SearchURL));

                // Try and read it to a string
                try {
                    // Ensure everything went OK (in try block for obvious reasons)
                    // Throws HttpRequestException for abnormal status codes
                    Response.EnsureSuccessStatusCode();
                    // Get the string content of our response
                    StringResponse = await Response.Content.ReadAsStringAsync();
                }
                catch (HttpRequestException) {
                    // With a proper web exception we can provide a more appropriate response
                    AuxillaryLogger.Log(
                        new LogMessage(LogSeverity.Error, "ExtraCommands.cs",
                                       "The HTTP request to Google's CSE API failed with" +
                                       $"status code {Response.StatusCode.ToString()}."));
                    throw;     // Apparently this maintains the call stack
                } catch (Exception e) {
                    // Else, if we have no idea what happened, just print a generic message
                    AuxillaryLogger.Log(
                        new LogMessage(LogSeverity.Error, "ExtraCommands.cs",
                                       "An unknown error occured in querying the Google CSE API.\n" +
                                       $"{e.Message}: {e.ToString()} (status: {Response.StatusCode.ToString()}"));
                    throw;
                }
            }

            var JSON = JObject.Parse(StringResponse);
            // Serialize it into our Result class which has fields Title and Snippet
            List <Result> Results = JsonConvert.DeserializeObject <List <Result> >(
                JSON["items"].ToString()
                );

            // Get our elapsed time
            TimeSpan SearchTimeTaken = DateTime.UtcNow.Subtract(SearchStart);

            AuxillaryLogger.Log(new LogMessage(LogSeverity.Debug, "ExtraCommands.cs", "Making embed..."));
            // Generate our embed
            // See https://cdn.discordapp.com/attachments/84319995256905728/252292324967710721/embed.png
            // for what all the fields are and how they end up looking
            var Embed = new EmbedBuilder()
                        .WithAuthor(new EmbedAuthorBuilder()
                                                               // Set the little icon in the top left to be the Google logo
                                    .WithIconUrl("http://i.imgur.com/EgnqiMG.png")
                                    .WithName($"\"{Query}\"")) // You need a name for the icon to show up :/
                                                               // Add time stats at the bottom
                        .WithFooter(new EmbedFooterBuilder()
                                    .WithText($"Search completed in {SearchTimeTaken.Milliseconds}ms."))
                        // Set the URL that our title will point to as the actual Google results page
                        // for the given query, should the user wish to know more.
                        // Points to my CSE so as to ensure results are (for the most part) the same.
                        .WithUrl("https://cse.google.co.uk/cse/" +
                                 "publicurl?cx=011947631902407852034:gq02yx0e1mq" +
                                 $"&q={Query.Replace(" ", "%20")}")
                        // We must use %20 rather than spaces, otherwise the embed's URL is considered
                        // to be invalid by Discord, and a BadRequest error will result if we try
                        // and push it through.
                        .WithColor(RandomColor);

            foreach (Result r in Results)
            {
                Embed.AddField(
                    // Add our results to the embed
                    r.Title,
                    $"({URLPreview(r.Link)}) " + r.Snippet.Replace("\n", "")
                    // And get rid of the newlines in snippets...they're everywhere...seeing one word lines :(
                    // We have wrap anyway
                    // Also have a URL preview to give the results a bit of context
                    );
            }

            // Finally, edit our message w/ embed:
            await EmbedMessage.ModifyAsync(m => {
                m.Content = string.Empty;
                m.Embed   = Embed.Build();
            });
        }
Example #4
0
        public static async Task InitializeGuild(SocketGuild guild, IUser botUser)
        {
            AuxillaryLogger.Log(LogSeverity.Info, "Settings", $"Initializing settings for guild {guild.Name}...");
            var StartTime = DateTime.UtcNow;

            // Helper function to setup guild; didn't want to make LoadSettings() async so it's not called from there
            MakeSettingsInstance(guild);
            var SettingsChannel = (ITextChannel)guild.TextChannels.Where(c => c.Name == "clu-bot-settings").FirstOrDefault();

            if (SettingsChannel == null)
            {
                SettingsChannel = (ITextChannel)await guild.CreateTextChannelAsync("clu-bot-settings");

                await SettingsChannel.AddPermissionOverwriteAsync(
                    guild.EveryoneRole, OverwritePermissions.DenyAll(SettingsChannel)
                    );
            }

            // Don't await getting the message every time in the function call in the loop, get it once and for all
            var Messages = await SettingsChannel.GetMessagesAsync().Flatten();

            // Also, we can clean out the channel here
            foreach (IMessage m in Messages)
            {
                if (m.Author.Id != botUser.Id)
                {
                    await m.DeleteAsync();

                    await Task.Delay(2000);
                }
            }

            foreach (DeserializedBotSetting Setting in _BaseSettings)
            {
                var PossiblyExistingMessage = (IUserMessage)SettingInChannel(Setting, Messages);
                if (PossiblyExistingMessage == null)
                {
                    // Performance diagnostics and that
                    AuxillaryLogger.Log(LogSeverity.Verbose, "Settings", "\tEncountered missing settings message, posting... (+6 seconds)");
                    var SettingsMessage = await SettingsChannel.SendMessageAsync(
                        Setting.Description + $" (default: {Setting.DefaultValueStr})"
                        );

                    // I've introduced sleep statements at various places to keep the rate-limits at bay...
                    // as it's a setup function, speed isn't of the essence.
                    await Task.Delay(2000);

                    // RestMessage inherits from IUserMessage, so we can now get to a real type
                    // No method for this as the resultant type will vary...
                    switch (Setting.ValueType)
                    {
                    case ValueData.Bool:
                        // Add relevant reactions
                        await SettingsMessage.AddReactionAsync(new Emoji("✔"));

                        await Task.Delay(2000);

                        await SettingsMessage.AddReactionAsync(new Emoji("❌"));

                        await Task.Delay(2000);

                        // The constructor will add it to the relevant lists. No need to assign.
                        // In addition, the guild isn't passed because it's already implied by the message
                        new GuildBotSettingYN(Setting, SettingsMessage);
                        break;

                    case ValueData.Role:
                    case ValueData.Roles:
                    case ValueData.User:
                    case ValueData.Users:
                        throw new NotImplementedException();

                    default:
                        throw new ArgumentException($"Could not determine what type a message with ValueDataString {Setting.ValueTypeString} should be remade into!");
                    }
                }
                else
                {
                    // Here's why it returns an IMessage, not a bool
                    // If already found, but no reactions for some reason, add them
                    var ReactionDataTick = await PossiblyExistingMessage.GetReactionUsersAsync("✔");

                    if (!ReactionDataTick.Any(u => u.Id == botUser.Id))
                    {
                        AuxillaryLogger.Log(LogSeverity.Verbose, "Settings", "\tEncountered missing bot reaction, adding... (+2 seconds)");
                        await PossiblyExistingMessage.AddReactionAsync(new Emoji("✔"));

                        await Task.Delay(2000);
                    }

                    var ReactionDataCross = await PossiblyExistingMessage.GetReactionUsersAsync("❌");

                    if (!ReactionDataCross.Any(u => u.Id == botUser.Id))
                    {
                        AuxillaryLogger.Log(LogSeverity.Verbose, "Settings", "\tEncountered missing bot reaction, adding... (+2 seconds)");
                        await PossiblyExistingMessage.AddReactionAsync(new Emoji("❌"));

                        await Task.Delay(2000);
                    }
                    // Once corrected, create setting objects
                    switch (Setting.ValueType)
                    {
                    case ValueData.Bool:
                        new GuildBotSettingYN(Setting, PossiblyExistingMessage);
                        break;

                    case ValueData.Role:
                    case ValueData.Roles:
                    case ValueData.User:
                    case ValueData.Users:
                        throw new NotImplementedException();

                    default:
                        throw new ArgumentException($"Could not determine what type a message with ValueDataString {Setting.ValueTypeString} should be remade into!");
                    }
                }
            }
            double TimeDelta = Math.Round((DateTime.UtcNow - StartTime).TotalSeconds, 3);

            AuxillaryLogger.Log(LogSeverity.Info, "Settings",
                                $"Completed settings initialization for guild {guild.Name} in {TimeDelta} seconds."
                                );
        }