From e9f59780eda80e0e75f5bd6fcbaa8e0cc0d31179 Mon Sep 17 00:00:00 2001 From: Karolis2011 Date: Thu, 20 Jun 2019 11:19:39 +0300 Subject: [PATCH] Adds role specific channels. Read ME update Updating tile of role will update revelant channels and roles. Added command examples. Fixese leaks --- EventBot/Attributes/ExampleAttribute.cs | 16 ++ EventBot/Entities/EventRole.cs | 5 + EventBot/Entities/GuildConfig.cs | 1 + ...620075923_AutoRolesAndChannels.Designer.cs | 147 ++++++++++++++++++ .../20190620075923_AutoRolesAndChannels.cs | 43 +++++ .../MySqlDatabaseServiceModelSnapshot.cs | 6 + ...620080021_AutoRolesAndChannels.Designer.cs | 146 +++++++++++++++++ .../20190620080021_AutoRolesAndChannels.cs | 43 +++++ .../SqliteDatabaseServiceModelSnapshot.cs | 6 + EventBot/Modules/BasicModule.cs | 5 +- EventBot/Modules/EventModule.cs | 127 +++++++++++++-- EventBot/Program.cs | 2 + EventBot/Properties/launchSettings.json | 4 +- EventBot/Services/DatabaseService.cs | 1 + EventBot/Services/EventManagementService.cs | 64 +++++++- README.md | 1 + 16 files changed, 596 insertions(+), 21 deletions(-) create mode 100644 EventBot/Attributes/ExampleAttribute.cs create mode 100644 EventBot/Migrations/MySql/20190620075923_AutoRolesAndChannels.Designer.cs create mode 100644 EventBot/Migrations/MySql/20190620075923_AutoRolesAndChannels.cs create mode 100644 EventBot/Migrations/Sqlite/20190620080021_AutoRolesAndChannels.Designer.cs create mode 100644 EventBot/Migrations/Sqlite/20190620080021_AutoRolesAndChannels.cs diff --git a/EventBot/Attributes/ExampleAttribute.cs b/EventBot/Attributes/ExampleAttribute.cs new file mode 100644 index 0000000..8f4972b --- /dev/null +++ b/EventBot/Attributes/ExampleAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EventBot.Attributes +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + class ExampleAttribute : Attribute + { + public string Use { get; set; } + public ExampleAttribute(string use) + { + Use = use; + } + } +} diff --git a/EventBot/Entities/EventRole.cs b/EventBot/Entities/EventRole.cs index ce690a2..92a06ba 100644 --- a/EventBot/Entities/EventRole.cs +++ b/EventBot/Entities/EventRole.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text; +using System.Text.RegularExpressions; namespace EventBot.Entities { @@ -15,6 +16,8 @@ namespace EventBot.Entities public string Description { get; set; } public string Emote { get; set; } public int MaxParticipants { get; set; } + public ulong ChannelId { get; set; } + public ulong RoleId { get; set; } public int EventId { get; set; } [ForeignKey("EventId")] public virtual Event Event { get; set; } @@ -22,6 +25,8 @@ namespace EventBot.Entities public int ParticipantCount => Participants == null ? 0 : Participants.Count; public int ReamainingOpenings => MaxParticipants < 0 ? 1 : MaxParticipants - ParticipantCount; + public string ChannelName => Regex.Replace(Title.Replace(' ', '-'), "(?!\\w)", ""); + public int SortNumber { get diff --git a/EventBot/Entities/GuildConfig.cs b/EventBot/Entities/GuildConfig.cs index 77f0413..b0c822e 100644 --- a/EventBot/Entities/GuildConfig.cs +++ b/EventBot/Entities/GuildConfig.cs @@ -12,6 +12,7 @@ namespace EventBot.Entities public string Prefix { get; set; } public ulong EventRoleConfirmationChannelId { get; set; } public ulong ParticipantRoleId { get; set; } + public ulong AutoRoleChannelCategoryId { get; set; } public virtual ICollection Events { get; set; } } diff --git a/EventBot/Migrations/MySql/20190620075923_AutoRolesAndChannels.Designer.cs b/EventBot/Migrations/MySql/20190620075923_AutoRolesAndChannels.Designer.cs new file mode 100644 index 0000000..bc2eb5d --- /dev/null +++ b/EventBot/Migrations/MySql/20190620075923_AutoRolesAndChannels.Designer.cs @@ -0,0 +1,147 @@ +// +using System; +using EventBot.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace EventBot.Migrations.MySql +{ + [DbContext(typeof(MySqlDatabaseService))] + [Migration("20190620075923_AutoRolesAndChannels")] + partial class AutoRolesAndChannels + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("EventBot.Entities.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Description"); + + b.Property("GuildId"); + + b.Property("MessageChannelId"); + + b.Property("MessageId"); + + b.Property("Opened"); + + b.Property("Title"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("EventBot.Entities.EventParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EventId"); + + b.Property("EventRoleId"); + + b.Property("UserData"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.HasIndex("EventRoleId"); + + b.ToTable("EventParticipants"); + }); + + modelBuilder.Entity("EventBot.Entities.EventRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("Description"); + + b.Property("Emote"); + + b.Property("EventId"); + + b.Property("MaxParticipants"); + + b.Property("RoleId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.ToTable("EventRoles"); + }); + + modelBuilder.Entity("EventBot.Entities.GuildConfig", b => + { + b.Property("GuildId") + .ValueGeneratedOnAdd(); + + b.Property("AutoRoleChannelCategoryId"); + + b.Property("EventRoleConfirmationChannelId"); + + b.Property("ParticipantRoleId"); + + b.Property("Prefix"); + + b.HasKey("GuildId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("EventBot.Entities.Event", b => + { + b.HasOne("EventBot.Entities.GuildConfig", "Guild") + .WithMany("Events") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("EventBot.Entities.EventParticipant", b => + { + b.HasOne("EventBot.Entities.Event", "Event") + .WithMany("Participants") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EventBot.Entities.EventRole", "Role") + .WithMany("Participants") + .HasForeignKey("EventRoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("EventBot.Entities.EventRole", b => + { + b.HasOne("EventBot.Entities.Event", "Event") + .WithMany("Roles") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/EventBot/Migrations/MySql/20190620075923_AutoRolesAndChannels.cs b/EventBot/Migrations/MySql/20190620075923_AutoRolesAndChannels.cs new file mode 100644 index 0000000..e06ec07 --- /dev/null +++ b/EventBot/Migrations/MySql/20190620075923_AutoRolesAndChannels.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace EventBot.Migrations.MySql +{ + public partial class AutoRolesAndChannels : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AutoRoleChannelCategoryId", + table: "GuildConfigs", + nullable: false, + defaultValue: 0ul); + + migrationBuilder.AddColumn( + name: "ChannelId", + table: "EventRoles", + nullable: false, + defaultValue: 0ul); + + migrationBuilder.AddColumn( + name: "RoleId", + table: "EventRoles", + nullable: false, + defaultValue: 0ul); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AutoRoleChannelCategoryId", + table: "GuildConfigs"); + + migrationBuilder.DropColumn( + name: "ChannelId", + table: "EventRoles"); + + migrationBuilder.DropColumn( + name: "RoleId", + table: "EventRoles"); + } + } +} diff --git a/EventBot/Migrations/MySql/MySqlDatabaseServiceModelSnapshot.cs b/EventBot/Migrations/MySql/MySqlDatabaseServiceModelSnapshot.cs index 6f9a1b5..9cb3eed 100644 --- a/EventBot/Migrations/MySql/MySqlDatabaseServiceModelSnapshot.cs +++ b/EventBot/Migrations/MySql/MySqlDatabaseServiceModelSnapshot.cs @@ -72,6 +72,8 @@ namespace EventBot.Migrations.MySql b.Property("Id") .ValueGeneratedOnAdd(); + b.Property("ChannelId"); + b.Property("Description"); b.Property("Emote"); @@ -80,6 +82,8 @@ namespace EventBot.Migrations.MySql b.Property("MaxParticipants"); + b.Property("RoleId"); + b.Property("Title"); b.HasKey("Id"); @@ -94,6 +98,8 @@ namespace EventBot.Migrations.MySql b.Property("GuildId") .ValueGeneratedOnAdd(); + b.Property("AutoRoleChannelCategoryId"); + b.Property("EventRoleConfirmationChannelId"); b.Property("ParticipantRoleId"); diff --git a/EventBot/Migrations/Sqlite/20190620080021_AutoRolesAndChannels.Designer.cs b/EventBot/Migrations/Sqlite/20190620080021_AutoRolesAndChannels.Designer.cs new file mode 100644 index 0000000..b662dca --- /dev/null +++ b/EventBot/Migrations/Sqlite/20190620080021_AutoRolesAndChannels.Designer.cs @@ -0,0 +1,146 @@ +// +using System; +using EventBot.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace EventBot.Migrations.Sqlite +{ + [DbContext(typeof(SqliteDatabaseService))] + [Migration("20190620080021_AutoRolesAndChannels")] + partial class AutoRolesAndChannels + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + modelBuilder.Entity("EventBot.Entities.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Description"); + + b.Property("GuildId"); + + b.Property("MessageChannelId"); + + b.Property("MessageId"); + + b.Property("Opened"); + + b.Property("Title"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("EventBot.Entities.EventParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EventId"); + + b.Property("EventRoleId"); + + b.Property("UserData"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.HasIndex("EventRoleId"); + + b.ToTable("EventParticipants"); + }); + + modelBuilder.Entity("EventBot.Entities.EventRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("Description"); + + b.Property("Emote"); + + b.Property("EventId"); + + b.Property("MaxParticipants"); + + b.Property("RoleId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.ToTable("EventRoles"); + }); + + modelBuilder.Entity("EventBot.Entities.GuildConfig", b => + { + b.Property("GuildId") + .ValueGeneratedOnAdd(); + + b.Property("AutoRoleChannelCategoryId"); + + b.Property("EventRoleConfirmationChannelId"); + + b.Property("ParticipantRoleId"); + + b.Property("Prefix"); + + b.HasKey("GuildId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("EventBot.Entities.Event", b => + { + b.HasOne("EventBot.Entities.GuildConfig", "Guild") + .WithMany("Events") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("EventBot.Entities.EventParticipant", b => + { + b.HasOne("EventBot.Entities.Event", "Event") + .WithMany("Participants") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EventBot.Entities.EventRole", "Role") + .WithMany("Participants") + .HasForeignKey("EventRoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("EventBot.Entities.EventRole", b => + { + b.HasOne("EventBot.Entities.Event", "Event") + .WithMany("Roles") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/EventBot/Migrations/Sqlite/20190620080021_AutoRolesAndChannels.cs b/EventBot/Migrations/Sqlite/20190620080021_AutoRolesAndChannels.cs new file mode 100644 index 0000000..d80bede --- /dev/null +++ b/EventBot/Migrations/Sqlite/20190620080021_AutoRolesAndChannels.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace EventBot.Migrations.Sqlite +{ + public partial class AutoRolesAndChannels : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AutoRoleChannelCategoryId", + table: "GuildConfigs", + nullable: false, + defaultValue: 0ul); + + migrationBuilder.AddColumn( + name: "ChannelId", + table: "EventRoles", + nullable: false, + defaultValue: 0ul); + + migrationBuilder.AddColumn( + name: "RoleId", + table: "EventRoles", + nullable: false, + defaultValue: 0ul); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AutoRoleChannelCategoryId", + table: "GuildConfigs"); + + migrationBuilder.DropColumn( + name: "ChannelId", + table: "EventRoles"); + + migrationBuilder.DropColumn( + name: "RoleId", + table: "EventRoles"); + } + } +} diff --git a/EventBot/Migrations/Sqlite/SqliteDatabaseServiceModelSnapshot.cs b/EventBot/Migrations/Sqlite/SqliteDatabaseServiceModelSnapshot.cs index 0ce9158..d5e4ca0 100644 --- a/EventBot/Migrations/Sqlite/SqliteDatabaseServiceModelSnapshot.cs +++ b/EventBot/Migrations/Sqlite/SqliteDatabaseServiceModelSnapshot.cs @@ -71,6 +71,8 @@ namespace EventBot.Migrations.Sqlite b.Property("Id") .ValueGeneratedOnAdd(); + b.Property("ChannelId"); + b.Property("Description"); b.Property("Emote"); @@ -79,6 +81,8 @@ namespace EventBot.Migrations.Sqlite b.Property("MaxParticipants"); + b.Property("RoleId"); + b.Property("Title"); b.HasKey("Id"); @@ -93,6 +97,8 @@ namespace EventBot.Migrations.Sqlite b.Property("GuildId") .ValueGeneratedOnAdd(); + b.Property("AutoRoleChannelCategoryId"); + b.Property("EventRoleConfirmationChannelId"); b.Property("ParticipantRoleId"); diff --git a/EventBot/Modules/BasicModule.cs b/EventBot/Modules/BasicModule.cs index d9011e1..d06009b 100644 --- a/EventBot/Modules/BasicModule.cs +++ b/EventBot/Modules/BasicModule.cs @@ -88,7 +88,7 @@ namespace EventBot.Modules .Aggregate(true, (a, r) => a && r)) .SelectMany(c => c.Aliases.Select(a => new { CI = c, MA = (c.Aliases[0] == a), A = a }).Reverse()) .Select((e, i) => new { Command = e, Index = i }) - .GroupBy(o => o.Index / 20) + .GroupBy(o => o.Index / 15) .Select(g => g.Select(o => o.Command)); var pager = new PaginatedMessage() @@ -130,6 +130,9 @@ namespace EventBot.Modules embed.AddField("Parameters", string.Join("\r\n", command.Parameters.Select(p => $"`{p.FormatParameter()}` *(Type: {p.FormatParameterType()})* - **{p.Summary}** {(p.IsRemainder ? "_No quotes are needed, when providing this parameter._" : "")}" )) ); + var examples = command.Attributes.Where(a => a is ExampleAttribute).Select(a => ((ExampleAttribute)a).Use).ToArray(); + if (examples.Length != 0) + embed.AddField("Examples", $"```{string.Join("\r\n", examples)}```"); await ReplyAsync($"I got this information about command `{commandAlias}`:", embed: embed.Build()); } diff --git a/EventBot/Modules/EventModule.cs b/EventBot/Modules/EventModule.cs index 76f564d..0c4eed3 100644 --- a/EventBot/Modules/EventModule.cs +++ b/EventBot/Modules/EventModule.cs @@ -9,6 +9,7 @@ using System.Linq; using EventBot.Entities; using Discord.WebSocket; using Discord.Addons.Interactive; +using EventBot.Attributes; namespace EventBot.Modules { @@ -28,6 +29,9 @@ namespace EventBot.Modules [Alias("j")] [Name("Join event")] [Summary("Joins latest or specified event with specified event role.")] + [Example("join :slight_smile:")] + [Example("join 5 \"John Smith\"")] + [Example("join :gun: \"Tpr. James\" 2")] public async Task JoinEvent( [Summary("Role emote or role ID to join the most recent event.")] string emoteOrId, [Summary("Extra information that might be needed by organizers.")] string extraInformation = null, @@ -56,6 +60,7 @@ namespace EventBot.Modules [RequireUserPermission(GuildPermission.Administrator, Group = "Permission")] [RequireOwner(Group = "Permission")] [Group("event")] + //[Alias("e")] [Name("Event management")] public class EventManagementModule : InteractiveBase { @@ -67,6 +72,7 @@ namespace EventBot.Modules _database = database; } + [Priority(2)] [Command("config logchannel")] [Name("Configure logging channel")] [Summary("Sets logging channel for role changes.")] @@ -83,6 +89,7 @@ namespace EventBot.Modules await s; } + [Priority(2)] [Command("config partrole")] [Name("Configure participant role")] [Summary("Sets discord role to assign when the user selects a role.")] @@ -99,10 +106,35 @@ namespace EventBot.Modules await s; } + [Priority(2)] + [Command("config category")] + [Name("Configure auto channel category")] + [Summary("Configures auto channel category, where new channels will be created for each role.")] + public async Task ConfigureParticipantCategory( + [Summary("Category to use when making channels for roles.")] ICategoryChannel category = null) + { + var guild = _database.GuildConfigs.FirstOrDefault(g => g.GuildId == Context.Guild.Id); + if (guild == null) + throw new Exception("This command must be executed inside a discord server."); + if (category == null) + { + guild.AutoRoleChannelCategoryId = 0; + await ReplyAsync("No channels and discord roles will be created for event roles."); + } else + { + guild.AutoRoleChannelCategoryId = category.Id; + await ReplyAsync($"Bot will create discord roles and channels for each new role inside `{category.Name}` category."); + } + await _database.SaveChangesAsync(); + } + + [Priority(1)] [Command("new")] [Alias("add", "create")] [Name("Create event")] [Summary("Creates a new event.")] + [Example("event new \"The event\" \"This is going to be a very hard event to organize.\"")] + [Example("event new \"Departmental chaos\" \"This event is about departmental workers rising against heads. Please provide your character name during registration.\" Detailed")] public async Task CreateEvent( [Summary("Title for the event.")] string title, [Summary("Description for the event.")] string description, @@ -124,6 +156,7 @@ namespace EventBot.Modules await ReplyAsync($"Created new {@event.Type} event `{title}`, with description: `{description}`. Its ID is `{@event.Id}`."); } + [Priority(2)] [Command("update title")] [Name("Update event's title")] [Summary("Updates the event's title.")] @@ -142,6 +175,7 @@ namespace EventBot.Modules await ReplyAsync($"Updated event's (`{@event.Id}`) title to `{@event.Title}`"); await _events.UpdateEventMessage(@event); } + [Priority(2)] [Command("update description")] [Alias("update desc")] [Name("Update event's description")] @@ -162,9 +196,12 @@ namespace EventBot.Modules await _events.UpdateEventMessage(@event); } + [Priority(2)] [Command("update type")] [Name("Update event's type")] [Summary("Updates the event type.")] + [Example("event update type Quick")] + [Example("event update type Detailed 3")] public async Task UpdateEventType( [Summary("Type of event registration.")] Event.EventParticipactionType type, [Summary("Event to update, if not specified, updates latest event.")] Event @event = null) @@ -186,10 +223,14 @@ namespace EventBot.Modules } + [Priority(2)] [Command("role new")] [Alias("role add", "role create")] [Name("Add role")] [Summary("Adds a new role to the event.")] + [Example("event role new \"ERT\" \"Emergency response team that recovers the artifact.\" :gun:")] + [Example("event role new \"Ninja\" \"To sneak around various corners of the station.\" :fire: 1")] + [Example("event role new \"Mercenaries\" \"To go on the station and do stuff.\" :fingers_crossed: 6 2")] public async Task NewEventRole( [Summary("Title of the role.")] string title, [Summary("Description of the role.")] string description, @@ -219,11 +260,21 @@ namespace EventBot.Modules Emote = parsedEmote.ToString(), Event = @event }; + if(@event.Guild.AutoRoleChannelCategoryId != 0) + { + var channel = await Context.Guild.CreateTextChannelAsync(er.ChannelName, o => o.CategoryId = @event.Guild.AutoRoleChannelCategoryId); + var role = await Context.Guild.CreateRoleAsync(er.Title); + await channel.AddPermissionOverwriteAsync(role, new OverwritePermissions(viewChannel: PermValue.Allow, sendMessages: PermValue.Allow)); + er.RoleId = role.Id; + er.ChannelId = channel.Id; + } + _database.Add(er); await _database.SaveChangesAsync(); await ReplyAsync($"Added event role `{er.Id}` for event `{er.Event.Id}`, title: `{er.Title}`, description: `{er.Description}`, slots: `{er.MaxParticipants}`, emote: {er.Emote}"); } + [Priority(2)] [Command("role update title")] [Name("Update role's title")] [Summary("Updates the role's title.")] @@ -237,11 +288,16 @@ namespace EventBot.Modules throw new Exception("This event is finalized. Please make a new event."); eventRole.Title = title; var s = _database.SaveChangesAsync(); + if (eventRole.ChannelId != 0) + await Context.Guild.GetTextChannel(eventRole.ChannelId).ModifyAsync(p => p.Name = eventRole.ChannelName); + if (eventRole.RoleId != 0) + await Context.Guild.GetRole(eventRole.RoleId).ModifyAsync(p => p.Name = eventRole.Title); await ReplyAsync($"Updated event role `{eventRole.Id}`'s title to `{eventRole.Title}`"); await s; await _events.UpdateEventMessage(eventRole.Event); } + [Priority(2)] [Command("role update description")] [Alias("role update desc")] [Name("Update role's description")] @@ -261,6 +317,7 @@ namespace EventBot.Modules await _events.UpdateEventMessage(eventRole.Event); } + [Priority(2)] [Command("role update slots")] [Name("Update role's slots")] [Summary("Updates a role's maximum participants count.")] @@ -280,6 +337,7 @@ namespace EventBot.Modules } + [Priority(2)] [Command("role update emote")] [Name("Update role's role")] [Summary("Updates a role's emote.")] @@ -335,6 +393,33 @@ namespace EventBot.Modules await ReplyAsync(embed: embed.Build()); } + + [Priority(2)] + [Command("role delete")] + [Alias("role remove")] + [Name("Delete role")] + [Summary("Deletes role and all information about it.")] + public async Task EventRoleDelete( + [Summary("Role you wish to delete.")] EventRole eventRole) + { + if (!(Context.User is SocketGuildUser guildUser)) + throw new Exception("This command must be executed inside a discord server."); + if (eventRole == null) + throw new Exception("Please provide the correct role, this one does not exist."); + if (eventRole.Event.MessageId != 0) + throw new Exception("Can't remove role from open event."); + + foreach (var p in eventRole.Participants) + await _events.RemoveParticipant(p, guildUser); + if(eventRole.ChannelId != 0) + await Context.Guild.GetTextChannel(eventRole.ChannelId)?.DeleteAsync(); + if (eventRole.RoleId != 0) + await Context.Guild.GetRole(eventRole.RoleId)?.DeleteAsync(); + _database.Remove(eventRole); + await _database.SaveChangesAsync(); + await ReplyAsync($"Role {eventRole.Title} has been deleted, and it's participants removed."); + } + [Priority(1)] [Command("role")] [Alias("role info")] @@ -361,6 +446,7 @@ namespace EventBot.Modules } } + [Priority(1)] [Command("open")] [Alias("start", "begin")] [Name("Open event")] @@ -394,7 +480,9 @@ namespace EventBot.Modules throw new Exception("Event type in not implemented."); } } - + + + [Priority(1)] [Command("close")] [Alias("stop", "end")] [Name("Close event")] @@ -417,6 +505,8 @@ namespace EventBot.Modules await ReplyAsync($"Event's `{@event.Id}` registration has been closed, its registration message will now be normal message."); } + + [Priority(1)] [Command("finalize")] [Alias("archive")] [Name("Archive event")] @@ -441,9 +531,18 @@ namespace EventBot.Modules var user = Context.Guild.GetUser(participant.UserId); await user.RemoveRoleAsync(Context.Guild.GetRole(@event.Guild.ParticipantRoleId)); } + foreach (var role in @event.Roles) + { + if (role.ChannelId != 0) + await Context.Guild.GetTextChannel(role.ChannelId)?.DeleteAsync(); + if (role.RoleId != 0) + await Context.Guild.GetRole(role.RoleId)?.DeleteAsync(); + + } await ReplyAsync($"Everyone's roles have been removed. I hope it was fun!"); } + [Priority(1)] [Command("list")] [Alias("all")] [Name("Lists events")] @@ -478,9 +577,14 @@ namespace EventBot.Modules await PagedReplyAsync(pager); } + + [Priority(2)] [Command("participant add")] [Name("Add participant")] - [Summary("Add user to event role. Acts like join command.")] + [Summary("Add user to event role. Acts like join command. Works even if registration is closed.")] + [Example("participant add \"Mini Moose#6944\" :slight_smile:")] + [Example("participant add Arrow768#3092 5 \"John Smith\"")] + [Example("participant add 183658981019877376 :gun: \"Tpr. James\" 2")] public async Task EventParticipantAdd( [Summary("User ID or discord mention.")] IUser user, [Summary("Role emote or role ID to join.")] string emoteOrId, @@ -507,6 +611,7 @@ namespace EventBot.Modules await Context.Message.DeleteAsync(); // Protect somewhat sensitive data. } + [Priority(2)] [Command("participant remove")] [Alias("participant delete")] [Name("Remove participant")] @@ -521,25 +626,15 @@ namespace EventBot.Modules throw new Exception("No events were found for this discord server."); if (!@event.Active) throw new Exception("This event is finalized. Please make a new event."); - - if (!(user is IGuildUser guildUser)) + if (!(user is IGuildUser guildUser) || !(Context.User is IGuildUser initiator)) throw new Exception("This command must be executed inside a discord server."); - var participant = @event.Participants.FirstOrDefault(p => p.UserId == guildUser.Id); - _database.Remove(participant); - var embed = new EmbedBuilder() - .WithTitle($"{user} been removed from event `{@event.Title}`, by {Context.User}.") - .WithDescription($"Their role was: `{participant.Role.Title}`") - .WithColor(Color.Red); - if (participant.UserData != null) - embed.AddField("Provided details", $"`{participant.UserData}`"); + await _events.RemoveParticipant(guildUser, @event, initiator); await _database.SaveChangesAsync(); + await ReplyAsync($"{guildUser} has been removed from event."); await _events.UpdateEventMessage(@event); - if (@event.Guild.EventRoleConfirmationChannelId != 0) - await (await ((IGuild)Context.Guild).GetTextChannelAsync(@event.Guild.EventRoleConfirmationChannelId)).SendMessageAsync(embed: embed.Build()); - if (@event.Guild.ParticipantRoleId != 0) - await guildUser.RemoveRoleAsync(Context.Guild.GetRole(@event.Guild.ParticipantRoleId)); + } } } diff --git a/EventBot/Program.cs b/EventBot/Program.cs index abae2fe..f882ebf 100644 --- a/EventBot/Program.cs +++ b/EventBot/Program.cs @@ -50,7 +50,9 @@ namespace EventBot { return new ServiceCollection() .AddSingleton(s => new DiscordSocketClient(new DiscordSocketConfig() { +#if DEBUG LogLevel = LogSeverity.Debug, +#endif MessageCacheSize = 1500 })) .AddSingleton() diff --git a/EventBot/Properties/launchSettings.json b/EventBot/Properties/launchSettings.json index 3dacec8..19162a0 100644 --- a/EventBot/Properties/launchSettings.json +++ b/EventBot/Properties/launchSettings.json @@ -3,8 +3,8 @@ "EventBot": { "commandName": "Project", "environmentVariables": { - "dbconnection": "Server=localhost;Database=eventbot;User=root;Password=tux;", - "token": "" + "dbconnection": "Server=server;Database=eventbot;User=eventbot;Password=password;", + "token": "token here" } }, "Docker": { diff --git a/EventBot/Services/DatabaseService.cs b/EventBot/Services/DatabaseService.cs index 20866b7..6e891bb 100644 --- a/EventBot/Services/DatabaseService.cs +++ b/EventBot/Services/DatabaseService.cs @@ -35,6 +35,7 @@ namespace EventBot.Services _discord = services.GetRequiredService(); _discord.GuildAvailable += OnGuildAvaivable; + _discord.JoinedGuild += OnGuildAvaivable; } public DatabaseService(): base() {} diff --git a/EventBot/Services/EventManagementService.cs b/EventBot/Services/EventManagementService.cs index 8b81a0b..6773df2 100644 --- a/EventBot/Services/EventManagementService.cs +++ b/EventBot/Services/EventManagementService.cs @@ -25,6 +25,28 @@ namespace EventBot.Services _discord.ReactionAdded += ReactionAddedAsync; _discord.MessageDeleted += MessageDeletedAsync; + _discord.ChannelDestroyed += ChannelDeleted; + _discord.RoleDeleted += RoleDeleted; + } + + private async Task RoleDeleted(SocketRole role) + { + var eventRole = _database.EventRoles.FirstOrDefault(r => r.RoleId == role.Id); + if(eventRole != null) + { + eventRole.ChannelId = 0; + await _database.SaveChangesAsync(); + } + } + + private async Task ChannelDeleted(SocketChannel channel) + { + var eventRole = _database.EventRoles.FirstOrDefault(r => r.ChannelId == channel.Id); + if (eventRole != null) + { + eventRole.ChannelId = 0; + await _database.SaveChangesAsync(); + } } public async Task TryJoinEvent(IGuildUser user, EventRole er, string extra, bool extraChecks = true) @@ -33,14 +55,16 @@ namespace EventBot.Services throw new Exception("Cross server events are forbidden."); if (extraChecks && er.ReamainingOpenings <= 0) throw new Exception("No openings are left."); - if(er.Event.Participants.Where(p => p.UserId == user.Id).Count() > 0) + if(er.Event.ParticipantCount > 0 && er.Event.Participants.Where(p => p.UserId == user.Id).Count() > 0) throw new Exception("You are already participating."); if(extraChecks && !er.Event.Active) throw new Exception("Event is closed."); if (er.Event.Guild.ParticipantRoleId != 0) await user.AddRoleAsync(user.Guild.GetRole(er.Event.Guild.ParticipantRoleId)); - + if (er.RoleId != 0) + await user.AddRoleAsync(user.Guild.GetRole(er.RoleId)); + var ep = new EventParticipant() { UserId = user.Id, @@ -63,6 +87,42 @@ namespace EventBot.Services await (await user.Guild.GetTextChannelAsync(er.Event.Guild.EventRoleConfirmationChannelId)).SendMessageAsync(embed: embed.Build()); } + public async Task RemoveParticipant(IGuildUser user, Event @event, IGuildUser initiator) + { + var participant = @event.Participants.FirstOrDefault(p => p.UserId == user.Id); + _database.Remove(participant); + var embed = new EmbedBuilder() + .WithTitle($"{user} been removed from event `{@event.Title}`, by {initiator}.") + .WithDescription($"Their role was: `{participant.Role.Title}`") + .WithColor(Color.Red); + if (participant.UserData != null) + embed.AddField("Provided details", $"`{participant.UserData}`"); + if (@event.Guild.EventRoleConfirmationChannelId != 0) + await (await user.Guild.GetTextChannelAsync(@event.Guild.EventRoleConfirmationChannelId)).SendMessageAsync(embed: embed.Build()); + if (@event.Guild.ParticipantRoleId != 0) + await user.RemoveRoleAsync(user.Guild.GetRole(@event.Guild.ParticipantRoleId)); + if (participant.Role.RoleId != 0) + await user.RemoveRoleAsync(user.Guild.GetRole(participant.Role.RoleId)); + } + + public async Task RemoveParticipant(EventParticipant participant, IGuildUser initiator) + { + IGuildUser user = await initiator.Guild.GetUserAsync(participant.UserId); + _database.Remove(participant); + var embed = new EmbedBuilder() + .WithTitle($"{user} been removed from event `{participant.Event.Title}`, by {initiator}.") + .WithDescription($"Their role was: `{participant.Role.Title}`") + .WithColor(Color.Red); + if (participant.UserData != null) + embed.AddField("Provided details", $"`{participant.UserData}`"); + if (participant.Event.Guild.EventRoleConfirmationChannelId != 0) + await (await user.Guild.GetTextChannelAsync(participant.Event.Guild.EventRoleConfirmationChannelId)).SendMessageAsync(embed: embed.Build()); + if (participant.Event.Guild.ParticipantRoleId != 0) + await user.RemoveRoleAsync(user.Guild.GetRole(participant.Event.Guild.ParticipantRoleId)); + } + + public async Task RemoveParticipant(IGuildUser user, EventRole eventRole, IGuildUser initiator) => await RemoveParticipant(user, eventRole.Event, initiator); + public Event FindEventBy(IGuild guild, bool bypassActive = false) { return _database.Events.OrderByDescending(e => e.Opened).FirstOrDefault(e => e.GuildId == guild.Id && (e.Active || bypassActive)); diff --git a/README.md b/README.md index 2cda7e6..72c95d0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This is environment variable for Mysql / MariaDb database connection. Example co ```Server=localhost,123;Database=eventbot;User=root;Password=password;``` +## Making migrations `Add-Migration InitialDatabase -Context MySqlDatabaseService -OutputDir Migrations\MySql` `Add-Migration InitialDatabase -Context SqliteDatabaseService -OutputDir Migrations\Sqlite` \ No newline at end of file