Inital bot commit
This commit is contained in:
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
*.swp
|
||||||
|
*.*~
|
||||||
|
project.lock.json
|
||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
nupkg/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
build/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
msbuild.log
|
||||||
|
msbuild.err
|
||||||
|
msbuild.wrn
|
||||||
|
|
||||||
|
# Visual Studio 2015
|
||||||
|
.vs/
|
25
EventBot.sln
Normal file
25
EventBot.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 16.0.28803.452
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBot", "EventBot\EventBot.csproj", "{CADB4675-F1B7-4269-A225-C0ADCCE88C4F}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{CADB4675-F1B7-4269-A225-C0ADCCE88C4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CADB4675-F1B7-4269-A225-C0ADCCE88C4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CADB4675-F1B7-4269-A225-C0ADCCE88C4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CADB4675-F1B7-4269-A225-C0ADCCE88C4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {A81E493A-0CEA-4E01-8609-79D2E9D49D09}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
11
EventBot/Attributes/NoHelpAttribute.cs
Normal file
11
EventBot/Attributes/NoHelpAttribute.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace EventBot.Attributes
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||||
|
class NoHelpAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
44
EventBot/Entities/Event.cs
Normal file
44
EventBot/Entities/Event.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace EventBot.Entities
|
||||||
|
{
|
||||||
|
public class Event
|
||||||
|
{
|
||||||
|
public Event()
|
||||||
|
{
|
||||||
|
Active = true;
|
||||||
|
Opened = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public bool Active { get; set; }
|
||||||
|
public ulong MessageId { get; set; }
|
||||||
|
public ulong MessageChannelId { get; set; }
|
||||||
|
public virtual ICollection<EventRole> Roles { get; set; }
|
||||||
|
public virtual ICollection<EventParticipant> Participants { get; set; }
|
||||||
|
public int ParticipantCount => Participants == null ? 0 : Participants.Count;
|
||||||
|
public DateTime Opened { get; set; }
|
||||||
|
public virtual EventParticipactionType Type { get; set; }
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
[ForeignKey("GuildId")]
|
||||||
|
public virtual GuildConfig Guild { get; set; }
|
||||||
|
public int RemainingOpenings => Roles.Sum(r => r.ReamainingOpenings);
|
||||||
|
|
||||||
|
public enum EventParticipactionType
|
||||||
|
{
|
||||||
|
Unspecified = -1,
|
||||||
|
Quick,
|
||||||
|
Detailed
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
24
EventBot/Entities/EventParticipant.cs
Normal file
24
EventBot/Entities/EventParticipant.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace EventBot.Entities
|
||||||
|
{
|
||||||
|
public class EventParticipant
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int EventRoleId { get; set; }
|
||||||
|
[ForeignKey("EventRoleId")]
|
||||||
|
public virtual EventRole Role { get; set; }
|
||||||
|
public int EventId { get; set; }
|
||||||
|
[ForeignKey("EventId")]
|
||||||
|
public virtual Event Event { get; set; }
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
public string UserData { get; set; }
|
||||||
|
}
|
||||||
|
}
|
34
EventBot/Entities/EventRole.cs
Normal file
34
EventBot/Entities/EventRole.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace EventBot.Entities
|
||||||
|
{
|
||||||
|
public class EventRole
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Emote { get; set; }
|
||||||
|
public int MaxParticipants { get; set; }
|
||||||
|
public int EventId { get; set; }
|
||||||
|
[ForeignKey("EventId")]
|
||||||
|
public virtual Event Event { get; set; }
|
||||||
|
public virtual ICollection<EventParticipant> Participants { get; set; }
|
||||||
|
public int ParticipantCount => Participants == null ? 0 : Participants.Count;
|
||||||
|
public int ReamainingOpenings => MaxParticipants < 0 ? 1 : MaxParticipants - ParticipantCount;
|
||||||
|
|
||||||
|
public int SortNumber
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (MaxParticipants < 0) return int.MaxValue;
|
||||||
|
return MaxParticipants;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
EventBot/Entities/GuildConfig.cs
Normal file
18
EventBot/Entities/GuildConfig.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace EventBot.Entities
|
||||||
|
{
|
||||||
|
public class GuildConfig
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
public string Prefix { get; set; }
|
||||||
|
public ulong EventRoleConfirmationChannelId { get; set; }
|
||||||
|
public ulong ParticipantRoleId { get; set; }
|
||||||
|
public virtual ICollection<Event> Events { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
44
EventBot/EventBot.csproj
Normal file
44
EventBot/EventBot.csproj
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Migrations\20190615141205_Inital.cs" />
|
||||||
|
<Compile Remove="Migrations\20190615141205_Inital.Designer.cs" />
|
||||||
|
<Compile Remove="Migrations\20190615203117_Inital.cs" />
|
||||||
|
<Compile Remove="Migrations\20190615203117_Inital.Designer.cs" />
|
||||||
|
<Compile Remove="Migrations\20190616081659_InitialSetup.cs" />
|
||||||
|
<Compile Remove="Migrations\20190616081659_InitialSetup.Designer.cs" />
|
||||||
|
<Compile Remove="Migrations\20190616175542_ForeginKeyFix.cs" />
|
||||||
|
<Compile Remove="Migrations\20190616175542_ForeginKeyFix.Designer.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Discord.Addons.Interactive" Version="1.0.1" />
|
||||||
|
<PackageReference Include="Discord.Net" Version="2.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.4" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.design" Version="2.2.4" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="2.2.4" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.2.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Unicode.net" Version="0.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="data.db">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Migrations\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
31
EventBot/Misc/EventRoleTypeReader.cs
Normal file
31
EventBot/Misc/EventRoleTypeReader.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Discord.Commands;
|
||||||
|
using EventBot.Entities;
|
||||||
|
using EventBot.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace EventBot.Misc
|
||||||
|
{
|
||||||
|
class EventRoleTypeReader : TypeReader
|
||||||
|
{
|
||||||
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||||
|
{
|
||||||
|
var database = services.GetRequiredService<DatabaseService>();
|
||||||
|
if (context.Guild == null)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.UnmetPrecondition, "Event roles are avaivable only inside guild context."));
|
||||||
|
EventRole er = null;
|
||||||
|
if (int.TryParse(input, out int id))
|
||||||
|
er = database.EventRoles.FirstOrDefault(r => r.Id == id);
|
||||||
|
else
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Event role id is not a valid number."));
|
||||||
|
if(er == null)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ObjectNotFound, "Specified event role was not found."));
|
||||||
|
if(er.Event?.GuildId != context.Guild.Id)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.Exception, "Cross guild event role access is denied."));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromSuccess(er));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
EventBot/Misc/EventTypeReader.cs
Normal file
30
EventBot/Misc/EventTypeReader.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Discord.Commands;
|
||||||
|
using EventBot.Entities;
|
||||||
|
using EventBot.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace EventBot.Misc
|
||||||
|
{
|
||||||
|
class EventTypeReader : TypeReader
|
||||||
|
{
|
||||||
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||||
|
{
|
||||||
|
var events = services.GetRequiredService<EventManagementService>();
|
||||||
|
if (context.Guild == null)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.UnmetPrecondition, "Events are avaivable only inside guild context."));
|
||||||
|
Event ev;
|
||||||
|
if (input == null)
|
||||||
|
ev = events.FindEventBy(context.Guild);
|
||||||
|
else if (int.TryParse(input, out int id))
|
||||||
|
ev = events.FindEventBy(context.Guild, id);
|
||||||
|
else
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Event id is not a number."));
|
||||||
|
|
||||||
|
return Task.FromResult(TypeReaderResult.FromSuccess(ev));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
EventBot/Modules/BasicModule.cs
Normal file
84
EventBot/Modules/BasicModule.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Commands;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
|
using EventBot.Attributes;
|
||||||
|
using EventBot.Services;
|
||||||
|
|
||||||
|
namespace EventBot.Modules
|
||||||
|
{
|
||||||
|
public class BasicModule : ModuleBase<SocketCommandContext>
|
||||||
|
{
|
||||||
|
private readonly DatabaseService _database;
|
||||||
|
public BasicModule(DatabaseService database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RequireUserPermission(GuildPermission.Administrator, Group = "Permission")]
|
||||||
|
[RequireOwner(Group = "Permission")]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[Command("prefix")]
|
||||||
|
[Summary("Gets prefix.")]
|
||||||
|
public async Task PrefixCommand()
|
||||||
|
{
|
||||||
|
var guildConfig = _database.GuildConfigs.FirstOrDefault(g => g.GuildId == Context.Guild.Id);
|
||||||
|
if (guildConfig == null)
|
||||||
|
throw new Exception("No guild config was foumd.");
|
||||||
|
if(guildConfig.Prefix != null)
|
||||||
|
await ReplyAsync($"Current prefix is `{guildConfig.Prefix}`");
|
||||||
|
else
|
||||||
|
await ReplyAsync($"There is no prefix set for this guild.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RequireUserPermission(GuildPermission.Administrator, Group = "Permission")]
|
||||||
|
[RequireOwner(Group = "Permission")]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[Command("prefix")]
|
||||||
|
[Summary("Sets prefix.")]
|
||||||
|
public async Task PrefixCommand(
|
||||||
|
[Summary("New prefix to set")] string newPrefix)
|
||||||
|
{
|
||||||
|
var guildConfig = _database.GuildConfigs.FirstOrDefault(g => g.GuildId == Context.Guild.Id);
|
||||||
|
if (guildConfig == null)
|
||||||
|
throw new Exception("No guild config was foumd.");
|
||||||
|
guildConfig.Prefix = newPrefix;
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Prefix has been set to `{guildConfig.Prefix}`");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Group("help")]
|
||||||
|
public class HelpModule : ModuleBase<SocketCommandContext>
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly CommandService _commands;
|
||||||
|
public HelpModule(CommandService commands)
|
||||||
|
{
|
||||||
|
_commands = commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
[Summary("Lists all commands with there descriptions.")]
|
||||||
|
public async Task DefaultHelpAsync()
|
||||||
|
{
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
|
.WithTitle("Command list")
|
||||||
|
.WithColor(Color.DarkBlue)
|
||||||
|
.WithCurrentTimestamp()
|
||||||
|
.WithFields(_commands.Commands
|
||||||
|
.Where(c => c.Attributes.Where(a => a is NoHelpAttribute || (a is RequireContextAttribute requireContext)).Count() == 0)
|
||||||
|
.Select(c =>
|
||||||
|
new EmbedFieldBuilder()
|
||||||
|
{
|
||||||
|
Name = $"`{string.Join(", ", c.Aliases)} {string.Join(" ", c.Parameters.Select(p => p.IsOptional ? $"[{p.Name}]" : $"<{p.Name}>"))}`",
|
||||||
|
Value = c.Summary
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Context.User.SendMessageAsync(embed: embed.Build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
413
EventBot/Modules/EventModule.cs
Normal file
413
EventBot/Modules/EventModule.cs
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Commands;
|
||||||
|
using EventBot.Services;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
|
using EventBot.Entities;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using NeoSmart.Unicode;
|
||||||
|
|
||||||
|
namespace EventBot.Modules
|
||||||
|
{
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
public class EventModule : ModuleBase<SocketCommandContext>
|
||||||
|
{
|
||||||
|
private readonly EventManagementService _events;
|
||||||
|
private readonly DatabaseService _database;
|
||||||
|
public EventModule(EventManagementService events, DatabaseService database)
|
||||||
|
{
|
||||||
|
_events = events;
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("join")]
|
||||||
|
[Summary("Joins latest or specified event with specified event role.")]
|
||||||
|
public async Task JoinAsync(
|
||||||
|
[Summary("Role emote or role id to join.")] string emoteOrId,
|
||||||
|
[Summary("Extra information that migth be needed by organizers.")] string extraInformation = null,
|
||||||
|
[Summary("Optional event ID for joining event that is not most recent one.")] Event @event = null)
|
||||||
|
{
|
||||||
|
EventRole er;
|
||||||
|
if (!(Context.User is SocketGuildUser guildUser))
|
||||||
|
throw new Exception("This command must be executed inside guild.");
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null & !(int.TryParse(emoteOrId, out int roleId)))
|
||||||
|
throw new Exception("Unable to locate any events for this guild.");
|
||||||
|
else if (@event == null)
|
||||||
|
er = _database.EventRoles.FirstOrDefault(r => r.Id == roleId);
|
||||||
|
else
|
||||||
|
er = @event.Roles.FirstOrDefault(r => r.Emote == emoteOrId);
|
||||||
|
if (er == null)
|
||||||
|
throw new ArgumentException("Invalid emote or event id specified");
|
||||||
|
if (@event.MessageId == 0)
|
||||||
|
throw new Exception("You can't join not opened event.");
|
||||||
|
|
||||||
|
await _events.TryJoinEvent(guildUser, er, extraInformation);
|
||||||
|
await Context.Message.DeleteAsync(); // Protect somewhat sensitive data.
|
||||||
|
}
|
||||||
|
|
||||||
|
[RequireUserPermission(GuildPermission.Administrator, Group = "Permission")]
|
||||||
|
[RequireOwner(Group = "Permission")]
|
||||||
|
[Group("event")]
|
||||||
|
public class EventManagementModule : ModuleBase<SocketCommandContext>
|
||||||
|
{
|
||||||
|
private readonly EventManagementService _events;
|
||||||
|
private readonly DatabaseService _database;
|
||||||
|
private readonly EmoteService _emotes;
|
||||||
|
public EventManagementModule(EventManagementService events, DatabaseService database, EmoteService emotes)
|
||||||
|
{
|
||||||
|
_events = events;
|
||||||
|
_database = database;
|
||||||
|
_emotes = emotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("config logchannel")]
|
||||||
|
[Summary("Sets logging channel for role changes.")]
|
||||||
|
public async Task SetRoleChannelAsync(
|
||||||
|
[Summary("Channel to use for logging.")] IChannel channel)
|
||||||
|
{
|
||||||
|
var guild = _database.GuildConfigs.FirstOrDefault(g => g.GuildId == Context.Guild.Id);
|
||||||
|
if (guild == null)
|
||||||
|
throw new Exception("This command must be executed inside guild.");
|
||||||
|
|
||||||
|
guild.EventRoleConfirmationChannelId = channel.Id;
|
||||||
|
var s = _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Event role changes now will be logged to `{channel.Name}` channel.");
|
||||||
|
await s;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("config partrole")]
|
||||||
|
[Summary("Sets role to assign when they selelct role.")]
|
||||||
|
public async Task SetParticipationRole(
|
||||||
|
[Summary("Role to assign.")] IRole role)
|
||||||
|
{
|
||||||
|
var guild = _database.GuildConfigs.FirstOrDefault(g => g.GuildId == Context.Guild.Id);
|
||||||
|
if (guild == null)
|
||||||
|
throw new Exception("This command must be executed inside guild.");
|
||||||
|
|
||||||
|
guild.ParticipantRoleId = role.Id;
|
||||||
|
var s = _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Event participants will be given `{role.Name}` role.");
|
||||||
|
await s;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("new")]
|
||||||
|
[Summary("Creates new event.")]
|
||||||
|
public async Task NewEvent(
|
||||||
|
[Summary("Title for the event.")] string title,
|
||||||
|
[Summary("Description for the event.")] string description,
|
||||||
|
[Summary("Type of event registration.")] Event.EventParticipactionType type = Event.EventParticipactionType.Quick)
|
||||||
|
{
|
||||||
|
var guild = _database.GuildConfigs.FirstOrDefault(g => g.GuildId == Context.Guild.Id);
|
||||||
|
if (guild == null)
|
||||||
|
throw new Exception("This command must be executed inside guild.");
|
||||||
|
var @event = new Event()
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Description = description,
|
||||||
|
Type = type,
|
||||||
|
Guild = guild
|
||||||
|
};
|
||||||
|
|
||||||
|
_database.Add(@event);
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Created new {@event.Type} event `{title}`, with description of `{description}`. It's ID is `{@event.Id}`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("update title")]
|
||||||
|
[Summary("Updates event title.")]
|
||||||
|
public async Task UpdateEventTitle(
|
||||||
|
[Summary("Title for the event.")] string title,
|
||||||
|
[Summary("Event to update, if not specified, updates latest event.")] Event @event = null)
|
||||||
|
{
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null)
|
||||||
|
throw new Exception("Unable to locate any events for this guild.");
|
||||||
|
@event.Title = title;
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Updated event(`{@event.Id}`) title to `{@event.Title}`");
|
||||||
|
await _events.UpdateEventMessage(@event);
|
||||||
|
}
|
||||||
|
[Command("update description")]
|
||||||
|
[Summary("Updates event description.")]
|
||||||
|
public async Task UpdateEventDescription(
|
||||||
|
[Summary("Description for the event.")] string description,
|
||||||
|
[Summary("Event to update, if not specified, updates latest event.")] Event @event = null)
|
||||||
|
{
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null)
|
||||||
|
throw new Exception("Unable to locate any events for this guild.");
|
||||||
|
@event.Description = description;
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Updated event(`{@event.Id}`) description to `{@event.Description}`");
|
||||||
|
await _events.UpdateEventMessage(@event);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("update type")]
|
||||||
|
[Summary("Updates event type.")]
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (type == Event.EventParticipactionType.Unspecified)
|
||||||
|
return;
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null)
|
||||||
|
throw new Exception("Unable to locate any events for this guild.");
|
||||||
|
if (@event.MessageId != 0 && @event.Type != type)
|
||||||
|
throw new Exception("Can't change event registration type when it's open for registration. Maube you meant to set type to `Unspecified` (-1)");
|
||||||
|
if(@event.Type != type)
|
||||||
|
@event.Type = type;
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Updated event(`{@event.Id}`) type to `{@event.Type}`");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Command("role new")]
|
||||||
|
[Summary("Adds new role to the event.")]
|
||||||
|
public async Task NewEventRole(
|
||||||
|
[Summary("Title for the role.")] string title,
|
||||||
|
[Summary("Description for the role.")] string description,
|
||||||
|
[Summary("Emote for the role.")] string emote,
|
||||||
|
[Summary("Max openings, if number is negative, opening count is unlimited.")] int maxOpenings = -1,
|
||||||
|
[Summary("Event to that role is meant for.")] Event @event = null)
|
||||||
|
{
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null)
|
||||||
|
throw new Exception("Unable to locate any events for this guild.");
|
||||||
|
if(@event.Roles != null && @event.Roles.Count >= 20)
|
||||||
|
throw new Exception("There are too many roles for this event.");
|
||||||
|
if(@event.MessageId != 0)
|
||||||
|
throw new Exception("Can't add new roles to event with open reigstration.");
|
||||||
|
if (!_emotes.TryParse(emote, out IEmote parsedEmote))
|
||||||
|
throw new ArgumentException("Invalid emote provided.");
|
||||||
|
if(@event.Roles != null && @event.Roles.Count(r => r.Emote == parsedEmote.ToString()) > 0)
|
||||||
|
throw new ArgumentException("This emote is already used by other role.");
|
||||||
|
var er = new EventRole()
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Description = description,
|
||||||
|
MaxParticipants = maxOpenings,
|
||||||
|
Emote = parsedEmote.ToString(),
|
||||||
|
Event = @event
|
||||||
|
};
|
||||||
|
_database.Add(er);
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Added event role `{er.Id}` for event `{er.Event.Id}`, title: `{er.Title}`, description: `{er.Description}`, maxPart: `{er.MaxParticipants}`, emote: {er.Emote}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("role update title")]
|
||||||
|
[Summary("Updates role's title")]
|
||||||
|
public async Task UpdateEventRoleTitle(
|
||||||
|
[Summary("Role witch to update.")] EventRole eventRole,
|
||||||
|
[Summary("New title for role.")][Remainder] string title)
|
||||||
|
{
|
||||||
|
if(eventRole == null)
|
||||||
|
throw new Exception("Please provide correct role.");
|
||||||
|
eventRole.Title = title;
|
||||||
|
var s = _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Updated event role `{eventRole.Id}` title to `{eventRole.Title}`");
|
||||||
|
await s;
|
||||||
|
await _events.UpdateEventMessage(eventRole.Event);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("role update desc")]
|
||||||
|
[Summary("Updates role's description.")]
|
||||||
|
public async Task UpdateEventRoleDescription(
|
||||||
|
[Summary("Role witch to update.")] EventRole eventRole,
|
||||||
|
[Summary("New description for role.")][Remainder] string description)
|
||||||
|
{
|
||||||
|
if (eventRole == null)
|
||||||
|
throw new Exception("Please provide correct role.");
|
||||||
|
eventRole.Description = description;
|
||||||
|
var s = _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Updated event role `{eventRole.Id}` description to `{eventRole.Description}`");
|
||||||
|
await s;
|
||||||
|
await _events.UpdateEventMessage(eventRole.Event);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("role update slots")]
|
||||||
|
[Summary("Updates role's maximum participants count.")]
|
||||||
|
public async Task UpdateEventRoleMaxParticipants(
|
||||||
|
[Summary("Role witch to update.")] EventRole eventRole,
|
||||||
|
[Summary("New maximum participant count for role.")] int maxParticipants)
|
||||||
|
{
|
||||||
|
if (eventRole == null)
|
||||||
|
throw new Exception("Please provide correct role.");
|
||||||
|
eventRole.MaxParticipants = maxParticipants;
|
||||||
|
var s = _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Updated event role `{eventRole.Id}` maximum participant count to `{eventRole.MaxParticipants}`");
|
||||||
|
await s;
|
||||||
|
await _events.UpdateEventMessage(eventRole.Event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Command("role update emote")]
|
||||||
|
[Summary("Updates role's emote.")]
|
||||||
|
public async Task UpdateEventRoleEmote(
|
||||||
|
[Summary("Role witch to update.")] EventRole eventRole,
|
||||||
|
[Summary("New emote for the role.")] string emote)
|
||||||
|
{
|
||||||
|
if (eventRole == null)
|
||||||
|
throw new Exception("Please provide correct role.");
|
||||||
|
if (!_emotes.TryParse(emote, out IEmote parsedEmote))
|
||||||
|
throw new ArgumentException("Invalid emote provided.");
|
||||||
|
|
||||||
|
if (eventRole.Event.Roles.Count(r => r.Emote == parsedEmote.ToString()) > 0)
|
||||||
|
throw new ArgumentException("This emote is already used by other role.");
|
||||||
|
eventRole.Emote = parsedEmote.ToString();
|
||||||
|
var s = _database.SaveChangesAsync();
|
||||||
|
await ReplyAsync($"Updated event role `{eventRole.Id}` emote to {eventRole.Emote}");
|
||||||
|
await s;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command()]
|
||||||
|
[Summary("Get info about event.")]
|
||||||
|
public async Task EventInfo(
|
||||||
|
[Summary("Event about witch info is wanted.")] Event @event = null)
|
||||||
|
{
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null)
|
||||||
|
throw new Exception("No events were found for this guild.");
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
|
.WithTitle(@event.Title)
|
||||||
|
.WithDescription(@event.Description)
|
||||||
|
.WithTimestamp(@event.Opened)
|
||||||
|
.WithFooter($"EventId: {@event.Id}; MessageId: {@event.MessageId}; MessageChannelId: {@event.MessageChannelId}")
|
||||||
|
.AddField("Active", @event.Active ? "Yes" : "No", true)
|
||||||
|
.AddField("Type", @event.Type, true)
|
||||||
|
.AddField("Participants", @event.ParticipantCount, true);
|
||||||
|
if (@event.Roles != null)
|
||||||
|
embed.WithFields(@event.Roles.OrderBy(e => e.SortNumber).Select(r => new EmbedFieldBuilder()
|
||||||
|
.WithName($"Id: `{r.Id}` {r.Emote} `{r.Title}` - {r.ParticipantCount} participants; {(r.MaxParticipants < 0 ? "infinite" : r.MaxParticipants.ToString())} max slots.")
|
||||||
|
.WithValue(r.ParticipantCount == 0 ? "There are no participants" : string.Join("\r\n", r.Participants
|
||||||
|
.Select(p => new { Participant = p, User = Context.Guild.GetUser(p.UserId) })
|
||||||
|
.OrderBy(o => o.User.ToString())
|
||||||
|
.Select(o => $"{o.User}{(o.Participant.UserData != null ? $" - `{o.Participant.UserData}`" : "")}")
|
||||||
|
))
|
||||||
|
));
|
||||||
|
|
||||||
|
await ReplyAsync(embed: embed.Build());
|
||||||
|
}
|
||||||
|
[Priority(1)]
|
||||||
|
[Command("role")]
|
||||||
|
[Summary("Gets role info.")]
|
||||||
|
public async Task EventRoleInfo(
|
||||||
|
[Summary("Role about witch info is wanted.")] EventRole eventRole)
|
||||||
|
{
|
||||||
|
if (eventRole == null)
|
||||||
|
throw new Exception("Please provide correct role.");
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
|
.WithTitle($"{eventRole.Emote} {eventRole.Title}")
|
||||||
|
.WithDescription($"{eventRole.Description}")
|
||||||
|
.WithFooter($"EventRoleId: {eventRole.Id}, EventId: {eventRole.Event.Id}")
|
||||||
|
.AddField("Max participants", eventRole.MaxParticipants < 0 ? "infinite" : eventRole.MaxParticipants.ToString(), true)
|
||||||
|
.AddField("Participants", eventRole.ParticipantCount, true);
|
||||||
|
|
||||||
|
var msg = await ReplyAsync(embed: embed.Build());
|
||||||
|
|
||||||
|
if (eventRole.Participants != null && eventRole.Participants.Count > 0)
|
||||||
|
{
|
||||||
|
embed.AddField("Participants", string.Join("\r\n", eventRole.Participants.Select(p => $"id: `{p.Id}` <@{p.UserId}>{(p.UserData != null ? $" - {p.UserData}" : "")}")));
|
||||||
|
await msg.ModifyAsync(m => m.Embed = embed.Build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("open")]
|
||||||
|
[Summary("Open registration for event here.")]
|
||||||
|
public async Task EventOpen(
|
||||||
|
[Summary("Event to open")] Event @event = null)
|
||||||
|
{
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null)
|
||||||
|
throw new Exception("No events were found for this guild.");
|
||||||
|
|
||||||
|
await Context.Message.DeleteAsync();
|
||||||
|
var message = await ReplyAsync(embed: _events.GenerateEventEmbed(@event).Build());
|
||||||
|
@event.MessageId = message.Id;
|
||||||
|
@event.MessageChannelId = message.Channel.Id;
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
|
||||||
|
switch (@event.Type)
|
||||||
|
{
|
||||||
|
case Event.EventParticipactionType.Unspecified:
|
||||||
|
throw new Exception("Event type was unspecified.");
|
||||||
|
case Event.EventParticipactionType.Quick:
|
||||||
|
await message.AddReactionsAsync(@event.Roles.OrderBy(e => e.SortNumber).Select(r => _emotes.Parse(r.Emote)).ToArray());
|
||||||
|
break;
|
||||||
|
case Event.EventParticipactionType.Detailed:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Event type in not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("participant add")]
|
||||||
|
[Summary("Add user to event role. Acts like join command.")]
|
||||||
|
public async Task EventParticipantAdd(
|
||||||
|
[Summary("User id or mention")] IUser user,
|
||||||
|
[Summary("Role emote or role id to join.")] string emoteOrId,
|
||||||
|
[Summary("Extra information that migth be needed by organizers.")] string extraInformation = null,
|
||||||
|
[Summary("Optional event ID for joining event that is not most recent one.")] Event @event = null)
|
||||||
|
{
|
||||||
|
EventRole er;
|
||||||
|
if (!(user is SocketGuildUser guildUser))
|
||||||
|
throw new Exception("This command must be executed inside guild.");
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null & !(int.TryParse(emoteOrId, out int roleId)))
|
||||||
|
throw new Exception("Unable to locate any events for this guild.");
|
||||||
|
else if (@event == null)
|
||||||
|
er = _database.EventRoles.FirstOrDefault(r => r.Id == roleId);
|
||||||
|
else
|
||||||
|
er = @event.Roles.FirstOrDefault(r => r.Emote == emoteOrId);
|
||||||
|
if (er == null)
|
||||||
|
throw new ArgumentException("Invalid emote or event id specified");
|
||||||
|
|
||||||
|
await _events.TryJoinEvent(guildUser, er, extraInformation, false);
|
||||||
|
await Context.Message.DeleteAsync(); // Protect somewhat sensitive data.
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("participant remove")]
|
||||||
|
[Summary("Remove participant from event role.")]
|
||||||
|
public async Task EventParticipantRemove(
|
||||||
|
[Summary("User that is participanting id or mention")] IUser user,
|
||||||
|
[Summary("Event to romove participant from")] Event @event = null)
|
||||||
|
{
|
||||||
|
if (@event == null)
|
||||||
|
@event = _events.FindEventBy(Context.Guild);
|
||||||
|
if (@event == null)
|
||||||
|
throw new Exception("No events were found for this guild.");
|
||||||
|
|
||||||
|
if (!(user is IGuildUser guildUser))
|
||||||
|
throw new Exception("This command must be executed inside guild.");
|
||||||
|
|
||||||
|
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($"They were in `{participant.Role.Title}` role")
|
||||||
|
.WithColor(Color.Red);
|
||||||
|
if (participant.UserData != null)
|
||||||
|
embed.AddField("Provided details", $"`{participant.UserData}`");
|
||||||
|
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
EventBot/Modules/TestingModule.cs
Normal file
18
EventBot/Modules/TestingModule.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Discord.Commands;
|
||||||
|
using EventBot.Attributes;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace EventBot.Modules
|
||||||
|
{
|
||||||
|
public class TestingModule: ModuleBase<SocketCommandContext>
|
||||||
|
{
|
||||||
|
[Command("ping")]
|
||||||
|
[Summary("Test if bot is working.")]
|
||||||
|
[NoHelp]
|
||||||
|
public Task SayAsync()
|
||||||
|
=> ReplyAsync("Pong!");
|
||||||
|
}
|
||||||
|
}
|
72
EventBot/Program.cs
Normal file
72
EventBot/Program.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using EventBot.Services;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.Addons.Interactive;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace EventBot
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
=> new Program().MainAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
|
||||||
|
public async Task MainAsync()
|
||||||
|
{
|
||||||
|
using (var services = ConfigureServices())
|
||||||
|
{
|
||||||
|
var client = services.GetRequiredService<DiscordSocketClient>();
|
||||||
|
client.Log += LogAsync;
|
||||||
|
services.GetRequiredService<CommandService>().Log += LogAsync;
|
||||||
|
services.GetRequiredService<CommandHandlingService>().Log += LogAsync;
|
||||||
|
|
||||||
|
services.GetRequiredService<DatabaseService>();
|
||||||
|
// Tokens should be considered secret data and never hard-coded.
|
||||||
|
// We can read from the environment variable to avoid hardcoding.
|
||||||
|
|
||||||
|
await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
|
||||||
|
await client.StartAsync();
|
||||||
|
|
||||||
|
// Here we initialize the logic required to register our commands.
|
||||||
|
await services.GetRequiredService<CommandHandlingService>().InitializeAsync();
|
||||||
|
|
||||||
|
await Task.Delay(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task LogAsync(LogMessage log)
|
||||||
|
{
|
||||||
|
Console.WriteLine(log.ToString());
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceProvider ConfigureServices()
|
||||||
|
{
|
||||||
|
return new ServiceCollection()
|
||||||
|
.AddSingleton(s => new DiscordSocketClient(new DiscordSocketConfig() {
|
||||||
|
LogLevel = LogSeverity.Debug,
|
||||||
|
MessageCacheSize = 1500
|
||||||
|
}))
|
||||||
|
.AddSingleton<CommandService>()
|
||||||
|
.AddSingleton<CommandHandlingService>()
|
||||||
|
.AddSingleton<EventManagementService>()
|
||||||
|
.AddSingleton<DatabaseService>(sp =>
|
||||||
|
{
|
||||||
|
if (Environment.GetEnvironmentVariable("dbconnection") != null)
|
||||||
|
return new MySqlDatabaseService(sp);
|
||||||
|
return new SqliteDatabaseService(sp);
|
||||||
|
})
|
||||||
|
.AddSingleton<InteractiveService>()
|
||||||
|
.AddSingleton<EmoteService>()
|
||||||
|
//.AddSingleton<HttpClient>()
|
||||||
|
//.AddSingleton<PictureService>()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
|
<PublishDir>bin\Release\netcoreapp2.2\publish\linux</PublishDir>
|
||||||
|
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<_IsPortable>false</_IsPortable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
|
<PublishDir>bin\Release\netcoreapp2.2\publish\win-x86</PublishDir>
|
||||||
|
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<_IsPortable>false</_IsPortable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
16
EventBot/Properties/PublishProfiles/FolderProfile-win.pubxml
Normal file
16
EventBot/Properties/PublishProfiles/FolderProfile-win.pubxml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
|
<PublishDir>bin\Release\netcoreapp2.2\publish\win</PublishDir>
|
||||||
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<_IsPortable>false</_IsPortable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
10
EventBot/Properties/launchSettings.json
Normal file
10
EventBot/Properties/launchSettings.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"EventBot": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"environmentVariables": {
|
||||||
|
"token": "<insert Token here>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
EventBot/data.db
Normal file
BIN
EventBot/data.db
Normal file
Binary file not shown.
Reference in New Issue
Block a user