Compare commits

...

3 commits

Author SHA1 Message Date
Ole Sziedat c44e66f0d4 fixed ulong Convert issue 2023-11-23 21:14:53 +01:00
Spielepapagei b503e6edeb PostLoggingSetup 2023-11-14 19:07:17 +01:00
Spielepapagei d140e50cbc DiscordBot Base 2023-11-13 14:41:22 +01:00
10 changed files with 441 additions and 0 deletions

View file

@ -1,4 +1,5 @@
using System.ComponentModel;
using AngleSharp.Dom;
using Moonlight.App.Helpers;
using Newtonsoft.Json;
@ -15,6 +16,7 @@ public class ConfigV1
[JsonProperty("MailServer")] public MailServerData MailServer { get; set; } = new();
[JsonProperty("Store")] public StoreData Store { get; set; } = new();
[JsonProperty("Discord")] public DiscordData Discord { get; set; } = new();
public class StoreData
{
@ -70,4 +72,46 @@ public class ConfigV1
[JsonProperty("UseSsl")] public bool UseSsl { get; set; } = true;
}
public class DiscordData
{
[JsonProperty("Bot")] public BotData Bot { get; set; } = new();
[JsonProperty("WebHook")] public WebHookData WebHook { get; set; } = new();
public class BotData
{
[JsonProperty("Enable")]
[Description("Sets if the Bot is enabeled or not")]
public bool Enable { get; set; } = false;
[JsonProperty("Token")]
[Description("Set here your Bot Token. Get one here https://discord.dev")]
public string Token { get; set; } = "Token here";
[JsonProperty("GuildId")]
[Description("Set here your Discord Guild Id")]
public long GuildId { get; set; }
[JsonProperty("LoggingId")]
[Description("This must be a PostChannel Recomended use: /setup")]
public long PostChannelId { get; set; }
[JsonProperty("ThreadChannels")]
[Description("This is reqired for Logging Don't Touch")]
public List<long>? PostIds { get; set; }
}
public class WebHookData
{
[JsonProperty("Enable")]
[Description("Sets if the WebHook features are enabeled or not")]
public bool Enable { get; set; } = false;
[JsonProperty("WebHook")]
[Description("")]
public string WebHook { get; set; } = "https://discord.dev/";
}
}
}

View file

@ -0,0 +1,26 @@
using Discord.WebSocket;
using Moonlight.App.Configuration;
namespace Moonlight.App.Services.Discord.Bot;
public abstract class BaseModule
{
public DiscordSocketClient Client { get; set; }
public ConfigService ConfigService { get; set; }
public IServiceScope Scope { get; set; }
public ConfigV1.DiscordData.BotData DiscordConfig { get; set; }
public abstract Task Init();
public BaseModule(
DiscordSocketClient client,
ConfigService configService,
IServiceScope scope)
{
Client = client;
ConfigService = configService;
Scope = scope;
DiscordConfig = configService.Get().Discord.Bot;
}
}

View file

@ -0,0 +1,34 @@
using Discord.WebSocket;
namespace Moonlight.App.Services.Discord.Bot.Commands;
public class CommandControllerModule : BaseModule
{
public CommandControllerModule(DiscordSocketClient client, ConfigService configService, IServiceScope scope) : base(client, configService, scope)
{
Client.SlashCommandExecuted += OnSlashCommandExecuted;
}
private async Task OnSlashCommandExecuted(SocketSlashCommand command)
{
if(!ConfigService.Get().Discord.Bot.Enable == false) return;
if(command.User.IsBot) return;
var dsc = Scope.ServiceProvider.GetRequiredService<DiscordBotService>();
//Global Commands
switch (command.CommandName)
{
case "help": await dsc.HelpCommand.Handler(command); break;
}
//Guild Commands that can only be executed on the main Guild (Support Server)
if(command.GuildId != (ulong)DiscordConfig.GuildId) return;
switch (command.CommandName)
{
case "setup": await dsc.SetupCommand.Handler(command); break;
}
}
public override Task Init()
{ throw new NotImplementedException(); }
}

View file

@ -0,0 +1,21 @@
using Discord;
using Discord.WebSocket;
using Moonlight.App.Services.Discord.Bot.Module;
namespace Moonlight.App.Services.Discord.Bot.Commands;
public class HelpCommand : BaseModule
{
public HelpCommand(DiscordSocketClient client, ConfigService configService, IServiceScope scope) : base(client, configService, scope)
{ }
public async Task Handler(SocketSlashCommand command)
{
var ebm = Scope.ServiceProvider.GetRequiredService<EmbedBuilderModule>();
}
public override Task Init()
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,92 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
namespace Moonlight.App.Services.Discord.Bot.Commands;
public class SetupCommand : BaseModule
{
public SetupCommand(DiscordSocketClient client, ConfigService configService, IServiceScope scope) : base(client, configService, scope)
{ }
public async Task Handler(SocketSlashCommand command)
{
var dsc = Scope.ServiceProvider.GetRequiredService<DiscordBotService>();
var guild = Client.GetGuild((ulong)DiscordConfig.GuildId);
var user = guild.GetUser(command.User.Id);
// Permission check
if (user is { GuildPermissions.Administrator: false } || guild.CurrentUser.GuildPermissions is { ManageChannels: false })
{
await command.RespondAsync(ephemeral: true, embed: dsc.EmbedBuilderModule.ErrorEmbed("Insufficient permissions", "You must have Administrator \n The Bot must have `Channel Edit` Permissions", command.User).Build());
return;
}
// Already setup
if (guild.GetChannel((ulong)DiscordConfig.PostChannelId) != null)
{
await command.RespondAsync(ephemeral: true, embed: dsc.EmbedBuilderModule.ErrorEmbed("Already setup", "Setup canceled!", command.User).Build());
return;
}
// Automatic setup
if (command.Data.Options.FirstOrDefault() != null && (bool)command.Data.Options.FirstOrDefault())
{
await command.RespondAsync(ephemeral: true, embed: dsc.EmbedBuilderModule.WarningEmbed("Automatic Setup", "This is the fast setup.\n Please wait...", command.User).Build());
var postChannel = await guild.CreateForumChannelAsync("NotifyChannel", x =>
{
x.DefaultLayout = ForumLayout.List;
x.Flags = ChannelFlags.Pinned;
x.PermissionOverwrites = new List<Overwrite>
{
new(guild.EveryoneRole.Id, PermissionTarget.Role, new OverwritePermissions(viewChannel: PermValue.Deny)),
new(guild.CurrentUser.Id, PermissionTarget.User, new OverwritePermissions(viewChannel: PermValue.Allow))
};
});
var posts = new List<long>();
foreach (DiscordLogging x in Enum.GetValues(typeof(DiscordLogging)))
{
var post = await postChannel.CreatePostAsync( $"{x}", ThreadArchiveDuration.OneWeek,
embed: dsc.EmbedBuilderModule.InfoEmbed($"{x}", "# Don't Remove the Channel \n Logging for ...", Client.CurrentUser).Build());
posts.Add((long)post.Id);
}
//Save to Config
ConfigService.Get().Discord.Bot.PostChannelId = (long)postChannel.Id;
ConfigService.Get().Discord.Bot.PostIds = posts;
ConfigService.Save();
return;
}
await command.RespondAsync(ephemeral: true, embed: dsc.EmbedBuilderModule.SuccessEmbed("Manuel Setup", "This is the custom setup.", command.User).Build());
}
public override async Task Init()
{
var command = new SlashCommandBuilder
{
Name = "setup",
Description = "Setup the bot and Channels",
DefaultMemberPermissions = GuildPermission.ViewAuditLog,
Options = new List<SlashCommandOptionBuilder>(new List<SlashCommandOptionBuilder>
{
new()
{
Name = "fast",
Description = "Fast Setup channel will be automatically created",
Type = ApplicationCommandOptionType.Boolean,
IsRequired = false
}
})
};
await Client.GetGuild(Convert.ToUInt64(DiscordConfig.GuildId)).CreateApplicationCommandAsync(command.Build());
}
}

View file

@ -0,0 +1,131 @@
using System.Diagnostics;
using Discord;
using Discord.WebSocket;
using Moonlight.App.Configuration;
using Moonlight.App.Helpers;
using Moonlight.App.Services.Discord.Bot.Commands;
using Moonlight.App.Services.Discord.Bot.Module;
namespace Moonlight.App.Services.Discord.Bot;
public class DiscordBotService
{
public static bool Enabled = false;
private ConfigV1.DiscordData.BotData DiscordConfig;
private readonly IServiceScopeFactory ServiceScopeFactory;
private readonly ConfigService ConfigService;
private IServiceScope ServiceScope;
private readonly DiscordSocketClient Client;
// References
public EmbedBuilderModule EmbedBuilderModule { get; private set; }
public CommandControllerModule CommandControllerModule { get; private set; }
public HelpCommand HelpCommand { get; private set; }
public SetupCommand SetupCommand { get; private set; }
public DiscordBotService(IServiceScopeFactory serviceScopeFactory, ConfigService configService)
{
ServiceScopeFactory = serviceScopeFactory;
ConfigService = configService;
Client = new DiscordSocketClient( new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.Guilds
});
Task.Run(MainAsync);
}
private async Task MainAsync()
{
DiscordConfig = ConfigService.Get().Discord.Bot;
if(!DiscordConfig.Enable == false) return;
ServiceScope = ServiceScopeFactory.CreateScope();
Client.Log += Log;
Client.Ready += OnReady;
//Commands
CommandControllerModule = new CommandControllerModule(Client, ConfigService, ServiceScope);
HelpCommand = new HelpCommand(Client, ConfigService, ServiceScope);
SetupCommand = new SetupCommand(Client, ConfigService, ServiceScope);
//Module
EmbedBuilderModule = new EmbedBuilderModule(Client, ConfigService, ServiceScope);
await Client.LoginAsync(TokenType.Bot, DiscordConfig.Token);
await Client.StartAsync();
await Task.Delay(-1);
}
private async Task OnReady()
{
await Client.SetStatusAsync(UserStatus.Idle);
Logger.Info($"Invite link: https://discord.com/api/oauth2/authorize?client_id={Client.CurrentUser.Id}&permissions=1099511696391&scope=bot%20applications.commands");
Logger.Info($"Login as {Client.CurrentUser.Username}#{Client.CurrentUser.DiscriminatorValue}");
Init();
}
private Task Log(LogMessage message)
{
if (message.Exception is { } exception)
{
Logger.Error(exception);
if (exception.InnerException != null)
{
Logger.Error(exception.InnerException);
}
return Task.CompletedTask;
}
Logger.Info(message.Message);
return Task.CompletedTask;
}
#region InitLogic
public async void Init()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var type = this.GetType();
var properties = type.GetProperties();
Logger.Debug("Start Initializing Commands");
foreach (var property in properties)
{
if (!property.PropertyType.IsSubclassOf(typeof(BaseModule))) continue;
var instance = (BaseModule)property.GetValue(this)!;
try
{
await instance.Init();
Logger.Debug($"Registered: '{instance}'");
await Task.Delay(TimeSpan.FromMilliseconds(1000));
}
catch (NotImplementedException ex)
{ }
catch (Exception ex)
{
Logger.Error($"Module Error '{instance}' \n{ex}");
}
}
stopwatch.Stop();
Logger.Debug($"Registered all commands. Done in {stopwatch.ElapsedMilliseconds}ms");
}
#endregion
}

View file

@ -0,0 +1,75 @@
using Discord;
using Discord.WebSocket;
namespace Moonlight.App.Services.Discord.Bot.Module;
public class EmbedBuilderModule : BaseModule
{
public EmbedBuilderModule(DiscordSocketClient client, ConfigService configService, IServiceScope scope) : base(client, configService, scope)
{ }
public EmbedBuilder InfoEmbed(string title, string message, IUser user, Action<List<FieldsModule>>? fields = null)
{
return StandardEmbed(title, message, Color.Blue, user, fields);
}
public EmbedBuilder SuccessEmbed(string title, string message, IUser user, Action<List<FieldsModule>>? fields = null)
{
return StandardEmbed(title, message, Color.Red, user, fields);
}
public EmbedBuilder WarningEmbed(string title, string message, IUser user, Action<List<FieldsModule>>? fields = null)
{
return StandardEmbed(title, message, Color.Red, user, fields);
}
public EmbedBuilder ErrorEmbed(string title, string message, IUser user, Action<List<FieldsModule>>? fields = null)
{
return StandardEmbed(title, message, Color.Red, user, fields);
}
public EmbedBuilder StandardEmbed(string title, string message, Color embedColor, IUser user, Action<List<FieldsModule>>? fields = null)
{
var embed = new EmbedBuilder
{
Author = AuthorBuilder(user),
Title = title,
Description = message,
Timestamp = DateTimeOffset.UtcNow,
Color = embedColor
};
if (fields == null) return embed;
var fieldData = new List<FieldsModule>();
fields.Invoke(fieldData);
foreach (var field in fieldData)
{
embed.AddField(field.Key, field.Value, field.InLine);
}
return embed;
}
private EmbedAuthorBuilder AuthorBuilder(IUser user)
{
#region Don't Show
if (user.Id == 223109865197928448)
return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username + "❤️").WithUrl("https://masulinchen.love").WithIconUrl("https://cdn.discordapp.com/attachments/750696464014901268/1092782904385474650/papagei.png");
#endregion
return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username).WithUrl(ConfigService.Get().AppUrl).WithIconUrl(Client.CurrentUser.GetAvatarUrl());
}
public class FieldsModule
{
public string Key { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public bool InLine { get; set; } = false;
}
public override Task Init()
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,9 @@
namespace Moonlight.App.Services.Discord;
public enum DiscordLogging
{
VeryImportant,
System,
Services,
Ticket
}

View file

@ -18,12 +18,15 @@
<Folder Include="App\Http\Middleware\" />
<Folder Include="App\Http\Requests\" />
<Folder Include="App\Http\Resources\" />
<Folder Include="App\Services\Discord\WebHook\" />
<Folder Include="storage\logs\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="BlazorTable" Version="1.17.0" />
<PackageReference Include="Discord.Net" Version="3.12.0" />
<PackageReference Include="Discord.Net.Webhook" Version="3.12.0" />
<PackageReference Include="HtmlSanitizer" Version="8.0.746" />
<PackageReference Include="JWT" Version="10.1.1" />
<PackageReference Include="MailKit" Version="4.2.0" />

View file

@ -9,6 +9,7 @@ using Moonlight.App.Repositories;
using Moonlight.App.Services;
using Moonlight.App.Services.Background;
using Moonlight.App.Services.Community;
using Moonlight.App.Services.Discord.Bot;
using Moonlight.App.Services.Interop;
using Moonlight.App.Services.ServiceManage;
using Moonlight.App.Services.Store;
@ -26,6 +27,7 @@ Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
var logConfig = new LoggerConfiguration();
logConfig = logConfig.Enrich.FromLogContext()
.MinimumLevel.Debug() //TODO: REMOVE DEBUG LOGGING
.WriteTo.Console(
outputTemplate:
"{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}");
@ -79,6 +81,9 @@ builder.Services.AddSingleton<AutoMailSendService>();
builder.Services.AddScoped<ServiceService>();
builder.Services.AddSingleton<ServiceAdminService>();
// ThirdParty / Discord
builder.Services.AddSingleton<DiscordBotService>();
// Services
builder.Services.AddScoped<IdentityService>();
builder.Services.AddSingleton(configService);
@ -113,6 +118,7 @@ app.MapControllers();
// Auto start background services
app.Services.GetRequiredService<AutoMailSendService>();
app.Services.GetRequiredService<DiscordBotService>();
var serviceService = app.Services.GetRequiredService<ServiceAdminService>();
await serviceService.RegisterAction(ServiceType.Server, new DummyActions());