Inital bot commit
This commit is contained in:
94
EventBot/Services/CommandHandlingService.cs
Normal file
94
EventBot/Services/CommandHandlingService.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using EventBot.Entities;
|
||||
using EventBot.Misc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EventBot.Services
|
||||
{
|
||||
public class CommandHandlingService
|
||||
{
|
||||
private readonly CommandService _commands;
|
||||
private readonly DiscordSocketClient _discord;
|
||||
private readonly DatabaseService _database;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public event Func<LogMessage, Task> Log;
|
||||
|
||||
public CommandHandlingService(IServiceProvider services)
|
||||
{
|
||||
_commands = services.GetRequiredService<CommandService>();
|
||||
_discord = services.GetRequiredService<DiscordSocketClient>();
|
||||
_database = services.GetRequiredService<DatabaseService>();
|
||||
_services = services;
|
||||
|
||||
// Hook CommandExecuted to handle post-command-execution logic.
|
||||
_commands.CommandExecuted += CommandExecutedAsync;
|
||||
// Hook MessageReceived so we can process each message to see
|
||||
// if it qualifies as a command.
|
||||
_discord.MessageReceived += MessageReceivedAsync;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_commands.AddTypeReader<Event>(new EventTypeReader());
|
||||
_commands.AddTypeReader<EventRole>(new EventRoleTypeReader());
|
||||
// Register modules that are public and inherit ModuleBase<T>.
|
||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
|
||||
}
|
||||
|
||||
public async Task MessageReceivedAsync(SocketMessage rawMessage)
|
||||
{
|
||||
// Ignore system messages, or messages from other bots
|
||||
if (!(rawMessage is SocketUserMessage message)) return;
|
||||
if (message.Source != MessageSource.User) return;
|
||||
|
||||
// This value holds the offset where the prefix ends
|
||||
var argPos = 0;
|
||||
// Perform prefix check. You may want to replace this with
|
||||
// (!message.HasCharPrefix('!', ref argPos))
|
||||
// for a more traditional command format like !help.
|
||||
var context = new SocketCommandContext(_discord, message);
|
||||
GuildConfig guildConfig = context.Guild != null ? _database.GuildConfigs.FirstOrDefault(g => g.GuildId == context.Guild.Id) : null;
|
||||
if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos))
|
||||
if (guildConfig != null)
|
||||
{
|
||||
if (!message.HasStringPrefix(guildConfig.Prefix, ref argPos))
|
||||
return;
|
||||
} else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Log?.Invoke(new LogMessage(LogSeverity.Debug, "CommandHandlingService", $"Got potential command: {message.Content}"));
|
||||
// Perform the execution of the command. In this method,
|
||||
// the command service will perform precondition and parsing check
|
||||
// then execute the command if one is matched.
|
||||
|
||||
await _commands.ExecuteAsync(context, argPos, _services);
|
||||
// Note that normally a result will be returned by this format, but here
|
||||
// we will handle the result in CommandExecutedAsync,
|
||||
}
|
||||
|
||||
public async Task CommandExecutedAsync(Optional<CommandInfo> command, ICommandContext context, IResult result)
|
||||
{
|
||||
// command is unspecified when there was a search failure (command not found); we don't care about these errors
|
||||
if (!command.IsSpecified)
|
||||
return;
|
||||
|
||||
// the command was successful, we don't care about this result, unless we want to log that a command succeeded.
|
||||
if (result.IsSuccess)
|
||||
return;
|
||||
|
||||
// the command failed, let's notify the user that something happened.
|
||||
await context.Channel.SendMessageAsync($"error: {result}");
|
||||
}
|
||||
}
|
||||
}
|
74
EventBot/Services/DatabaseService.cs
Normal file
74
EventBot/Services/DatabaseService.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Discord.WebSocket;
|
||||
using EventBot.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace EventBot.Services
|
||||
{
|
||||
public abstract class DatabaseService: DbContext
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly DiscordSocketClient _discord;
|
||||
|
||||
|
||||
public DbSet<GuildConfig> GuildConfigs { get; set; }
|
||||
public DbSet<Event> Events { get; set; }
|
||||
public DbSet<EventRole> EventRoles { get; set; }
|
||||
public DbSet<EventParticipant> EventParticipants { get; set; }
|
||||
|
||||
public DatabaseService(IServiceProvider services, DbContextOptions options) : base(options)
|
||||
{
|
||||
_services = services;
|
||||
|
||||
_discord = services.GetRequiredService<DiscordSocketClient>();
|
||||
_discord.GuildAvailable += OnGuildAvaivable;
|
||||
}
|
||||
public DatabaseService(IServiceProvider services) : base()
|
||||
{
|
||||
_services = services;
|
||||
|
||||
_discord = services.GetRequiredService<DiscordSocketClient>();
|
||||
_discord.GuildAvailable += OnGuildAvaivable;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseLazyLoadingProxies();
|
||||
#if DEBUG
|
||||
optionsBuilder.UseSqlite("Data Source=blogging.db");
|
||||
#else
|
||||
optionsBuilder.UseMySql(Environment.GetEnvironmentVariable("dbconnection"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Event>().Property(e => e.Type)
|
||||
.HasConversion(new EnumToNumberConverter<Event.EventParticipactionType, int>());
|
||||
}
|
||||
|
||||
protected async Task OnGuildAvaivable(SocketGuild guild)
|
||||
{
|
||||
GuildConfig config = default;
|
||||
if(await GuildConfigs.CountAsync() != 0)
|
||||
config = await GuildConfigs.FirstAsync(g => g.GuildId == guild.Id);
|
||||
if(config == null)
|
||||
{
|
||||
config = new GuildConfig()
|
||||
{
|
||||
GuildId = guild.Id
|
||||
};
|
||||
Add(config);
|
||||
await SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
EventBot/Services/EmoteService.cs
Normal file
46
EventBot/Services/EmoteService.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NeoSmart.Unicode;
|
||||
using System.Linq;
|
||||
using Discord;
|
||||
using DEmoji = Discord.Emoji;
|
||||
using UEmoji = NeoSmart.Unicode.Emoji;
|
||||
|
||||
namespace EventBot.Services
|
||||
{
|
||||
|
||||
public class EmoteService
|
||||
{
|
||||
private IEnumerable<string> emoji;
|
||||
|
||||
public EmoteService()
|
||||
{
|
||||
emoji = UEmoji.All.Select(e => e.Sequence.AsString);
|
||||
}
|
||||
|
||||
public bool TryParse(string input, out IEmote emote)
|
||||
{
|
||||
if(Emote.TryParse(input, out Emote parsedEmote))
|
||||
{
|
||||
emote = parsedEmote;
|
||||
return true;
|
||||
}
|
||||
if(emoji.Contains(input))
|
||||
{
|
||||
emote = new DEmoji(input);
|
||||
return true;
|
||||
}
|
||||
emote = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEmote Parse(string input)
|
||||
{
|
||||
if (!TryParse(input, out IEmote parsed))
|
||||
throw new ArgumentException("Failed to parse emote.");
|
||||
return parsed;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
152
EventBot/Services/EventManagementService.cs
Normal file
152
EventBot/Services/EventManagementService.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
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 EmoteService _emotes;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public EventManagementService(IServiceProvider services)
|
||||
{
|
||||
_discord = services.GetRequiredService<DiscordSocketClient>();
|
||||
_database = services.GetRequiredService<DatabaseService>();
|
||||
_emotes = services.GetRequiredService<EmoteService>();
|
||||
_services = services;
|
||||
|
||||
_discord.ReactionAdded += ReactionAddedAsync;
|
||||
_discord.MessageDeleted += MessageDeletedAsync;
|
||||
}
|
||||
|
||||
public async Task TryJoinEvent(IGuildUser user, EventRole er, string extra, bool extraChecks = true)
|
||||
{
|
||||
if (er.Event.GuildId != user.GuildId)
|
||||
throw new Exception("Cross guild events are fobidden.");
|
||||
if (extraChecks && er.ReamainingOpenings <= 0)
|
||||
throw new Exception("No openings left.");
|
||||
if(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));
|
||||
|
||||
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 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 <emote or id> <extra information>` as following emotes are awaivable:";
|
||||
|
||||
embed.WithFields(@event.Roles
|
||||
.OrderBy(e => e.SortNumber)
|
||||
.Select(e => new EmbedFieldBuilder()
|
||||
.WithName($"{e.Emote} `{e.Id}`: *{e.Title}*`{ (e.MaxParticipants > 0 ? $" - {e.ReamainingOpenings} ramaining" : "")} - {e.ParticipantCount} participating.`")
|
||||
.WithValue($"{e.Description}")
|
||||
));
|
||||
return embed;
|
||||
}
|
||||
|
||||
public async Task MessageDeletedAsync(Cacheable<IMessage, ulong> 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<IUserMessage, ulong> 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(_emotes.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 ocured while processing your reaction: \r\n{ex.GetType()}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
EventBot/Services/MySqlDatabaseService.cs
Normal file
18
EventBot/Services/MySqlDatabaseService.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace EventBot.Services
|
||||
{
|
||||
public class MySqlDatabaseService : DatabaseService
|
||||
{
|
||||
public MySqlDatabaseService(IServiceProvider services, DbContextOptions options) : base(services, options) { }
|
||||
public MySqlDatabaseService(IServiceProvider services) : base(services) { }
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
optionsBuilder.UseMySql(Environment.GetEnvironmentVariable("dbconnection"));
|
||||
}
|
||||
}
|
||||
}
|
19
EventBot/Services/SqliteDatabaseService.cs
Normal file
19
EventBot/Services/SqliteDatabaseService.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace EventBot.Services
|
||||
{
|
||||
public class SqliteDatabaseService : DatabaseService
|
||||
{
|
||||
public SqliteDatabaseService(IServiceProvider services, DbContextOptions options) : base(services, options) { }
|
||||
public SqliteDatabaseService(IServiceProvider services) : base(services) {}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
optionsBuilder.UseSqlite("Data Source=data.db");
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user