using Discord; using Discord.WebSocket; using EventBot.Entities; using EventBot.Misc; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using System.Linq; namespace EventBot.Services { public class EventManagementService { private readonly DiscordSocketClient _discord; private readonly DatabaseService _database; private readonly IServiceProvider _services; public EventManagementService(IServiceProvider services) { _discord = services.GetRequiredService(); _database = services.GetRequiredService(); _services = 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) { if (er.Event.GuildId != user.GuildId) throw new Exception("Cross server events are forbidden."); if (extraChecks && er.ReamainingOpenings <= 0) throw new Exception("No openings are left."); 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, Event = er.Event, Role = er }; var embed = new EmbedBuilder() .WithTitle($"{user} has joined event `{er.Event.Title}`") .WithDescription($"They have chosen `{er.Title}` role.") .WithColor(Color.Green); if (extra != null && extra != string.Empty) { embed.AddField("Provided details", $"`{extra}`"); ep.UserData = extra; } _database.Add(ep); await _database.SaveChangesAsync(); await UpdateEventMessage(er.Event); if (er.Event.Guild.EventRoleConfirmationChannelId != 0) 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)); } public Event FindEventBy(IGuild guild, int? eventId, bool bypassActive = false) { if (eventId == null) return FindEventBy(guild, bypassActive); return _database.Events.OrderByDescending(e => e.Opened).FirstOrDefault(e => e.GuildId == guild.Id && e.Id == eventId && (e.Active || bypassActive)); } public async Task UpdateEventMessage(Event ev) { if (ev.MessageChannelId == 0 || ev.MessageId == 0) return; var channel = (ITextChannel) _discord.GetChannel(ev.MessageChannelId); var message = (IUserMessage) await channel.GetMessageAsync(ev.MessageId); await message.ModifyAsync(m => m.Embed = GenerateEventEmbed(ev).Build()); } public EmbedBuilder GenerateEventEmbed(Event @event) { var embed = new EmbedBuilder() .WithTitle($"{@event.Title}") .WithDescription(@event.Description) .WithFooter($"EventId: {@event.Id}") .WithColor(Color.Purple) ; if (@event.Type == Event.EventParticipactionType.Quick) embed.Description += "\r\nTo participate in this event react with following emotes:"; if (@event.Type == Event.EventParticipactionType.Detailed) embed.Description += "\r\nTo participate in this event use command `join ` as following emotes are available:"; embed.WithFields(@event.Roles .OrderBy(e => e.SortNumber) .Select(e => new EmbedFieldBuilder() .WithName($"{e.Emote} `{e.Id}`: *{e.Title}*`{ (e.MaxParticipants > 0 ? $" - {e.ReamainingOpenings} remaining" : "")} - {e.ParticipantCount} participating.`") .WithValue($"{e.Description}") )); return embed; } public async Task MessageDeletedAsync(Cacheable message, ISocketMessageChannel socketMessage) { var @event = _database.Events.FirstOrDefault(e => e.MessageId == message.Id); if(@event != null) { @event.MessageId = 0; @event.MessageChannelId = 0; await _database.SaveChangesAsync(); } } public async Task ReactionAddedAsync(Cacheable message, ISocketMessageChannel socketMessage, SocketReaction reaction) { if (!reaction.User.IsSpecified || reaction.User.Value.IsBot) return; var @event = _database.Events.FirstOrDefault(e => e.MessageId == message.Id); if (@event != null) { var role = @event.Roles.FirstOrDefault(r => reaction.Emote.Equals(EmoteHelper.Parse(r.Emote))); if(role != null) { var userMessage = await message.GetOrDownloadAsync(); if (reaction.User.IsSpecified) await userMessage.RemoveReactionAsync(reaction.Emote, reaction.User.Value); try { if (!(reaction.User.GetValueOrDefault() is IGuildUser guildUser)) throw new Exception("Reaction must be made inside guild"); await TryJoinEvent(guildUser, role, null); } catch (Exception ex) { if (reaction.User.IsSpecified) await reaction.User.Value.SendMessageAsync($"Error occured while processing your reaction: \r\n{ex.GetType()}: {ex.Message}"); } } } } } }