/// <summary> /// Fired whenever someone joins the server. /// <para>Used to log a message to a specific text channel.</para> /// </summary> public async Task UserJoinedServer(SocketGuildUser user) { var guild = user.Guild; //Retrieve guild settings var guildSettings = _botContext.Guilds.AsNoTracking().FirstOrDefault(x => x.GuildId == guild.Id); if (guildSettings is null) { return; } //Check if guild has moderation channel enabled var welcomeChannel = guild.GetTextChannel(guildSettings.WelcomeChannel); if (welcomeChannel == null) { return; } //Check if there is a valid role and give that role to the user if (guildSettings.RoleOnJoin != 0 && Helper.DoesRoleExist(user.Guild, guildSettings.RoleOnJoin) is var role && role != null) { await user.AddRoleAsync(role, new RequestOptions { AuditLogReason = "Auto role on join" }); } //Announce to WelcomeChannel that the user joined the server await welcomeChannel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed("User joined", $"{user} has joined the server!", Color.Green)); }
/// <summary>Removes X(count) messages from chat. /// </summary> public async Task CleanMessagesAsync(int count, SocketCommandContext context) { //We only delete 100 messages at a time to prevent bot from getting overloaded if (count <= 100) { /* Saves all messages user specified in a variable, next * those messages are deleted and a message is sent to the textChannel * saying that X messages were deleted <- this message is deleted 2.3s later */ //Save messages to delete in a variable var messagesToDelete = await context.Channel.GetMessagesAsync(count + 1).FlattenAsync(); //Delete messages to delete await context.Guild.GetTextChannel(context.Channel.Id).DeleteMessagesAsync(messagesToDelete, new RequestOptions() { AuditLogReason = "Clean messages command" }); //Send success message that will disappear after 2300 milliseconds _interactivityService.DelayedSendMessageAndDeleteAsync(context.Channel, null, TimeSpan.FromMilliseconds(2300), null, false, CustomFormats.CreateBasicEmbed("Messages deleted", $":white_check_mark: Deleted **{count}** messages.", 0x268618)); } else { await context.Channel.SendMessageAsync(embed : CustomFormats.CreateErrorEmbed(context.User.Mention + " You cannot delete more than 100 messages at once")); } }
private async Task RoleOnJoinSetup(SocketCommandContext context) { var tmpMessage = await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed( "Role on Join Setup", "Cobra will automatically give the specified role when someone joins the server.\n" + "Please type the name or ID of the role you want to setup as the role on join.\n" + "Type `reset` to reset the role on join thus disabling this functionality.", Color.Blue)); var nextMessageResult = await _interactivityService.NextMessageAsync(x => x.Author == context.User); if (nextMessageResult.IsSuccess) { var msgContent = nextMessageResult.Value.Content; if (msgContent == "reset") { await ChangeRoleOnJoin(context); await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed("Role on join changed", "Role on join was reset.\nYour server doesn't have a role on join setup right now.", Color.DarkMagenta)); } else { var tr = new ExtendedRoleTypeReader(); var readResult = await tr.ReadAsync(context, msgContent, _serviceProvider); if (!readResult.IsSuccess) { await context.Channel.SendMessageAsync( embed : CustomFormats.CreateErrorEmbed("Unable to find role!")); } else { if (readResult.Values.First().Value is IRole role) { await ChangeRoleOnJoin(context, role); await context.Channel.SendMessageAsync( embed : CustomFormats.CreateBasicEmbed("Role on join changed", $"Role on join was set to **{role.Name}**", 0x268618)); } else { await context.Channel.SendMessageAsync( embed : CustomFormats.CreateErrorEmbed("Unable to find role!")); } } } await nextMessageResult.Value.DeleteAsync(); await tmpMessage.DeleteAsync(); } }
private async Task WelcomeChannelSetup(SocketCommandContext context) { var tmpMessage = await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed( "Welcome Channel Setup", "Cobra will send messages to this channel when someone joins/leaves the server.\n" + "Please mention the #textChannel you want to setup as the Welcome Channel.\n" + "Type `reset` to reset the Welcome Channel thus disabling this functionality.", Color.Blue)); var nextMessageResult = await _interactivityService.NextMessageAsync(x => x.Author == context.User); if (nextMessageResult.IsSuccess) { var msgContent = nextMessageResult.Value.Content; if (msgContent == "reset") { await ChangeWelcomeChannel(context); await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed( "Welcome channel changed", "Welcome channel was reset.\nYour server doesn't have a welcome channel setup right now.", Color.DarkMagenta)); } else { if (nextMessageResult.Value.MentionedChannels.Any()) { if (nextMessageResult.Value.MentionedChannels.First() is ITextChannel textChannel) { await ChangeWelcomeChannel(context, textChannel); await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed( "Welcome channel changed", $"Welcome channel is now {textChannel.Mention}", 0x268618)); } else { await context.Channel.SendMessageAsync( embed : CustomFormats.CreateErrorEmbed("Invalid text channel!")); } } else { await context.Channel.SendMessageAsync( embed : CustomFormats.CreateErrorEmbed("No text channels mentioned!")); } } await nextMessageResult.Value.DeleteAsync(); await tmpMessage.DeleteAsync(); } }
/// <summary> Generates a random number. </summary> public static Embed RandomNumber(int minVal, int maxVal) { //If minVal > maxVal, Random.Next will throw an exception //So we switch minVal with maxVal and vice versa. That way we don't get an exception if (minVal > maxVal) { (minVal, maxVal) = (maxVal, minVal); } var randomNumber = new Random().Next(minVal, maxVal); return(CustomFormats.CreateBasicEmbed("Random number", $":game_die: **{randomNumber}**", 0x268618)); }
/// <summary>Generates a random number. /// </summary> public static Embed RandomNumber(int minVal, int maxVal) { //If minVal > maxVal, Random.Next will throw an exception //So we switch minVal with maxVal and vice versa. That way we don't get an exception if (minVal > maxVal) { int tmp = minVal; //temporary variable to store minVal because it will be overwritten with maxVal minVal = maxVal; maxVal = tmp; } var randomNumber = new Random().Next(minVal, maxVal); return(CustomFormats.CreateBasicEmbed("Random number", $":game_die: **{randomNumber}**", 0x268618)); }
/// <summary>Removes specified track from queue and returns an embed. /// </summary> public Embed RemoveFromQueueAsync(IGuild guild, int index, int indexMax) { if (!_lavaNode.HasPlayer(guild)) { return(CustomFormats.CreateErrorEmbed("Could not acquire player.")); } var player = _lavaNode.GetPlayer(guild); /* We decrement 2 to the index, as queue command shows first song in queue with number 2 * and first item in queue has an index of 0 */ index -= 2; if (player.Queue.ElementAt(index) == null) { return(CustomFormats.CreateErrorEmbed("There is no song in queue with specified index!")); } try { /*By default indexMax = 0, so the user has the option to use the command with only 'index' which in turn removes * only 1 song from the queue. If the users chooses to use indexMax as well, then the bot knows that the user * wants to remove a range of songs instead of only 1 song. */ if (indexMax != 0) { //We decrement 2 to the indexMax, as queue command shows first song in queue with number 2 //and first item in queue has an index of 0 indexMax -= 2; int count = indexMax - index; /*We use count+1 because RemoveRange() also counts the first index, for example: * If user wants to remove tracks number 2 to 5, it would only remove tracks 2, 3 and 4 * because count would be = to 3 */ var tracksToRemove = player.Queue.RemoveRange(index, count + 1); return(CustomFormats.CreateBasicEmbed("", $"Removed {tracksToRemove.Count} songs from queue", Color.Blue)); } var trackToRemove = player.Queue.RemoveAt(index); return(CustomFormats.CreateBasicEmbed("", $"Removed {trackToRemove.Title} from queue", Color.Blue)); } catch (Exception ex) { return(CustomFormats.CreateErrorEmbed(ex.Message)); } }
private async Task PrivateChatSetup(SocketCommandContext context) { var tmpMessage = await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed( "Private Chats Setup", "Paste the ID of the category where you want private chats to be created.\n\n" + "If you don't have that category yet, create one category with the name you like and move it to where you would like the private chats to be created." + "Don't change any channel permissions as Cobra takes care of that\n\n" + "Type `reset` to reset the Private Chat category thus disabling this functionality.", Color.Blue)); var nextMessageResult = await _interactivityService.NextMessageAsync(x => x.Author == context.User); if (nextMessageResult.IsSuccess) { var msgContent = nextMessageResult.Value.Content; if (msgContent == "reset") { await ChangePrivateChat(context); await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed( "Private Chat changed", "Private Chat category was reset.\nYour server doesn't have private chats setup right now.", Color.DarkMagenta)); } else { if (ulong.TryParse(msgContent, out var categoryId)) { var category = context.Guild.GetCategoryChannel(categoryId); await ChangePrivateChat(context, category); await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed( "Private Chat changed", $"Private chats will now appear under the category {category.Name}", 0x268618)); } else { await context.Channel.SendMessageAsync( embed : CustomFormats.CreateErrorEmbed("Invalid category id!")); } } await nextMessageResult.Value.DeleteAsync(); await tmpMessage.DeleteAsync(); } }
private async Task PrefixSetup(SocketCommandContext context) { var tmpMessage = await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed( "Prefix Setup", "Please type the new prefix for your guild (type `default` to reset it)", Color.Blue)); var nextMessageResult = await _interactivityService.NextMessageAsync(x => x.Author == context.User); if (nextMessageResult.IsSuccess) { await nextMessageResult.Value.DeleteAsync(); await tmpMessage.DeleteAsync(); await context.Channel.SendMessageAsync(embed : await ChangePrefix(context, nextMessageResult.Value.Content)); } }
/// <summary> /// Unbans specified user from the server. /// </summary> public async Task <Embed> UnbanAsync(IUser user, SocketCommandContext context) { await context.Message.DeleteAsync(); var isBanned = await context.Guild.GetBanAsync(user); if (isBanned == null) { return(CustomFormats.CreateErrorEmbed($"{user} is not banned!")); } _banCache.Set(user.Id, new CacheModel(context.Guild.Id, CacheType.UnbanReject), TimeSpan.FromSeconds(5)); await context.Guild.RemoveBanAsync(user); return(CustomFormats.CreateBasicEmbed($"{user} unbanned", $"{user} was unbanned successfully.", 0x268618)); }
//Fired every time the bot joins a new guild private static async Task Client_JoinedGuild(SocketGuild guild) { if (guild.Owner == null) { return; } try { //We send this message for the guild owner await guild.Owner.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed("Hello, I'm Cobra! 👋", "Thank you for adding me to your server!\nTo get started, type `-setup` in any text channel of your guild." + "\nIf you need help, you can join the [support server](https://discord.gg/pbkdG7gYeu).", 0x268618)); } catch (HttpException) { //If the user doesn't have DM's enabled, catch the error } }
/// <summary> Updates guilds' prefix. </summary> /// <param name="context"> The command context. </param> /// <param name="prefix"> The new prefix. </param> public async Task <Embed> ChangePrefix(SocketCommandContext context, string prefix) { var guildSettings = await _botContext.GetGuildSettings(context.Guild.Id); //If user input == default if (prefix == "default") { //Check if the guild has custom prefix var currentPrefix = guildSettings.CustomPrefix; //If the guild doesn't have custom prefix, return if (currentPrefix == null) { return(CustomFormats.CreateErrorEmbed("Bot prefix is already the default one!")); } //If they have a custom prefix, set it to null guildSettings.CustomPrefix = null; await _botContext.SaveChangesAsync(); return(CustomFormats.CreateBasicEmbed("Custom prefix changed", "Bot prefix was reset to: **-**", (uint)268618)); } //If user input is longer than 5, return if (prefix.Length > 5) { return(CustomFormats.CreateErrorEmbed("Bot prefix can't be longer than 5 characters!")); } //If every check passes, we add the new custom prefix to the database guildSettings.CustomPrefix = prefix; await _botContext.SaveChangesAsync(); return(CustomFormats.CreateBasicEmbed("Custom prefix changed", $"Cobra's prefix is now: **{prefix}**", 0x268618)); }
/// <summary>Fired whenever someone leaves the server. /// <para>Used to log a message to a specific text channel.</para> /// </summary> public async Task UserLeftServer(SocketGuildUser user) { var guild = user.Guild; //Retrieve guild settings var guildSettings = _botContext.Guilds.AsNoTracking().FirstOrDefault(x => x.GuildId == guild.Id); if (guildSettings is null) { return; } //Check if guild has moderation channel enabled var welcomeChannel = guild.GetTextChannel(guildSettings.WelcomeChannel); if (welcomeChannel == null) { return; } //If we do have a valid channel, announce that the user left the server await welcomeChannel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed("User left", $"{user} has left the server!", Color.DarkGrey)); }
/// <summary> Gives/removes role from specified user. </summary> /// <param name="user"> User to update role. </param> /// <param name="operation"> Operation (+ adds role, - removes role). </param> /// <param name="role"> Role to give/remove from user. </param> public static async Task <Embed> UpdateRoleAsync(IGuildUser user, char operation, IRole role) { //Get role which name equals role //var roleToUpdate = Helper.DoesRoleExist(user.Guild, role); var roleToUpdate = role; switch (operation) { case '+': await user.AddRoleAsync(roleToUpdate); return(CustomFormats.CreateBasicEmbed("Role added", $"Role {roleToUpdate.Name} was successfully added to {user.Username}", 0x268618)); case '-': await user.RemoveRoleAsync(roleToUpdate); return(CustomFormats.CreateBasicEmbed("Role removed", $"Role {roleToUpdate.Name} was successfully removed from {user.Username}", 0x268618)); default: return(CustomFormats.CreateErrorEmbed("Invalid operation! Available operations are **+** (add) and **-** (remove).")); } }
/// <summary> /// Changes slowmode for specified text channel. /// </summary> public static async Task SlowmodeAsync(ITextChannel channel, int interval, SocketCommandContext context) { await((SocketTextChannel)channel).ModifyAsync(x => x.SlowModeInterval = interval); await context.Channel.SendMessageAsync( embed : CustomFormats.CreateBasicEmbed("Slowmode changed", "", 0x268618)); }
/// <summary>Plays the requested song or adds it to the queue. /// <para>It also joins the voice channel if the bot isn't already joined.</para> /// </summary> public async Task PlayAsync(SocketCommandContext context, string query) { var user = (SocketGuildUser)context.User; var guild = context.Guild; //Check If User Is Connected To Voice channel. if (user.VoiceChannel == null) { await context.Channel.SendMessageAsync( embed : CustomFormats.CreateErrorEmbed("You must be connected to a voice channel!")); return; } //Check the guild has a player available. if (!_lavaNode.HasPlayer(guild)) { //If it doesn't, then it means the bot isn't connected to a voice channel, //so we make the bot join a voice channel in order for play command to work var voiceState = (IVoiceState)context.User; var textChannel = (ITextChannel)context.Channel; await _lavaNode.JoinAsync(voiceState.VoiceChannel, textChannel); } try { //Get the player for that guild. var player = _lavaNode.GetPlayer(guild); LavaTrack track; //Find The Youtube Track the User requested. var search = Uri.IsWellFormedUriString(query, UriKind.Absolute) ? await _lavaNode.SearchAsync(query) : await _lavaNode.SearchYouTubeAsync(query); //If we couldn't find anything, tell the user. if (search.LoadStatus == LoadStatus.NoMatches) { await context.Channel.SendMessageAsync( embed : CustomFormats.CreateErrorEmbed($"No results found for {query}.")); return; } if (search.LoadStatus == LoadStatus.LoadFailed) { await context.Channel.SendMessageAsync(embed : CustomFormats.CreateErrorEmbed("**Failed to load song!**")); return; } //If results derive from search results (ex: ytsearch: some song) if (search.LoadStatus == LoadStatus.SearchResult) { //Then load the first track of the search results track = search.Tracks[0]; //If the Bot is already playing music, or if it is paused but still has music in the playlist, Add the requested track to the queue. if (player.Track != null && (player.PlayerState is PlayerState.Playing || player.PlayerState is PlayerState.Paused)) { player.Queue.Enqueue(track); await player.TextChannel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed("Track queued", $"**{track.Title}** has been added to queue. [{context.User.Mention}]\nPosition in queue: `{player.Queue.Count}`", Color.Blue)); return; } //Player was not playing anything, so lets play the requested track. await player.PlayAsync(track); return; } //If results derive from a playlist, //If the Bot is already playing music, or if it is paused but still has music in the playlist if (player.Track != null && (player.PlayerState is PlayerState.Playing || player.PlayerState is PlayerState.Paused)) { //Then add all the playlist songs to the queue for (int i = 0; i < search.Tracks.Count; i++) { track = search.Tracks.ElementAt(i); player.Queue.Enqueue(track); } //And send a message saying that X tracks have been added to queue await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed("Tracks queued", $"**{search.Tracks.Count} tracks** have been added to queue.", Color.Blue)); return; } //If the player isn't playing anything //Then add all the songs EXCLUDING the first one, because we will play that one next for (int i = 1; i < search.Tracks.Count; i++) { track = search.Tracks.ElementAt(i); player.Queue.Enqueue(track); } //After adding every song except the first, we retrieve the first track track = search.Tracks[0]; //And ask the player to play it await player.PlayAsync(track); //If there is more than 1 song on search results (playlist) then say that we added every track in the playlist to the queue if (search.Tracks.Count - 1 > 0) //-1 because there will always be at least 1 song { //Send a message saying that X other tracks have been added to queue await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed("", $"{search.Tracks.Count - 1} tracks have been added to queue.", Color.Blue)); } } //If after all the checks we did, something still goes wrong. Tell the user about it so they can report it back to us. catch (Exception ex) { await context.Channel.SendMessageAsync(embed : CustomFormats.CreateErrorEmbed(ex.Message)); } }
/// <summary>Returns an embed containing the player queue. /// </summary> public async Task QueueAsync(SocketCommandContext context) { var guild = context.Guild; //Checks if bot is connected to a voice channel if (!_lavaNode.HasPlayer(guild)) { await context.Channel.SendMessageAsync(embed : CustomFormats.CreateErrorEmbed("Could not acquire player.")); return; } //Bot is connected to voice channel, so we get the player associated with the guild var player = _lavaNode.GetPlayer(guild); //If player isn't playing, then we return if (!(player.PlayerState is PlayerState.Playing)) { await context.Channel.SendMessageAsync(embed : CustomFormats.CreateErrorEmbed("I'm not playing anything right now.")); return; } //If there are no more songs in queue except for the current playing song, we return with a reply //saying the currently playing song and that no more songs are queued if (player.Queue.Count < 1 && player.Track != null) { await context.Channel.SendMessageAsync(embed : CustomFormats.CreateBasicEmbed("", $"**Now playing: {player.Track.Title}**\nNo more songs queued.", Color.Blue)); return; } try { //After checking if we have tracks in the queue //We save the count of tracks in queue to tracksCount variable var tracksCount = player.Queue.Count; /* We calculate the maximum items per page we want * this will return the minimum number, either 10 or tracksCount*/ var maxItemsPerPage = Math.Min(10, tracksCount); //We calculate how many pages we'll have (used to initialize array) var maxPages = tracksCount / maxItemsPerPage; //We initialize an array with size of maxPages var pages = new PageBuilder[maxPages]; var trackNum = 2; //trackNum == 2 because we're not including the first track //We itterate through all the pages we need for (int i = 0; i < maxPages; i++) { var descriptionBuilder = new StringBuilder(); //We take X items, equal to the number of maxItemsPerPage, so we don't overflow the embed max description length var tracks = player.Queue.Skip(i).Take(maxItemsPerPage); //We itterate through the tracks taken on the previous instruction foreach (var track in tracks) { //We create the description for each page descriptionBuilder.Append($"{trackNum}: [{track.Title}]({track.Url})\n"); trackNum++; } //We create the page, with the description created on the previous loop pages[i] = new PageBuilder().WithTitle($"Now playing: {player.Track?.Title}") .WithDescription($"{descriptionBuilder}").WithColor(Color.Blue); } //We create the paginator to send var paginator = new StaticPaginatorBuilder() .WithUsers(context.User) .WithFooter(PaginatorFooter.PageNumber) .WithEmotes(new Dictionary <IEmote, PaginatorAction>() { { new Emoji("⏮️"), PaginatorAction.SkipToStart }, { new Emoji("⬅️"), PaginatorAction.Backward }, { new Emoji("➡️"), PaginatorAction.Forward }, { new Emoji("⏭️"), PaginatorAction.SkipToEnd } }) .WithPages(pages) .WithTimoutedEmbed(pages[0].Build().Embed.ToEmbedBuilder()) .Build(); //Send the paginator to the text channel await _interactivityService.SendPaginatorAsync(paginator, context.Channel, TimeSpan.FromSeconds(150)); } catch (Exception ex) { await context.Channel.SendMessageAsync(embed : CustomFormats.CreateErrorEmbed(ex.Message)); } }