Added Discord Bot

This commit is contained in:
Sarah Faey 2022-11-06 19:10:58 +01:00
parent e7a0c9ae68
commit e445b2a181
48 changed files with 1255 additions and 157 deletions

View file

@ -0,0 +1,82 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using System.Reflection;
namespace DiscordBot
{
public class CommandHandler
{
private readonly DiscordSocketClient _client;
private readonly CommandService _commands;
// Retrieve client and CommandService instance via ctor
public CommandHandler(DiscordSocketClient client, CommandService commands)
{
_commands = commands;
_client = client;
}
public async Task InstallCommandsAsync()
{
_client.SlashCommandExecuted += SlashCommandHandler;
_client.ButtonExecuted += MyButtonHandler;
}
private async Task SlashCommandHandler(SocketSlashCommand command)
{
switch (command.Data.Name)
{
case Constants.SlashCommands.FIRST_COMMAND:
await HandleFirstCommand(command);
break;
}
await command.RespondAsync($"You executed {command.Data.Name}");
}
private async Task HandleFirstCommand(SocketSlashCommand command)
{
// We need to extract the user parameter from the command. since we only have one option and it's required, we can just use the first option.
var guildUser = (SocketGuildUser)command.Data.Options.First().Value;
// We remove the everyone role and select the mention of each role.
var roleList = string.Join(",\n", guildUser.Roles.Where(x => !x.IsEveryone).Select(x => x.Mention));
var embedBuiler = new EmbedBuilder()
.WithAuthor(guildUser.ToString(), guildUser.GetAvatarUrl() ?? guildUser.GetDefaultAvatarUrl())
.WithTitle("Roles")
.WithDescription(roleList)
.WithColor(Color.Green)
.WithCurrentTimestamp();
// Now, Let's respond with the embed.
await command.RespondAsync(embed: embedBuiler.Build());
}
public async Task MyButtonHandler(SocketMessageComponent component)
{
string[] ids = component.Data.CustomId.Split('-');
switch(ids[0])
{
case Constants.ComponentIds.SIGN_UP:
//await component.RespondAsync($"{component.User.Mention} has clicked the SignUp button!");
var mb = new ModalBuilder()
.WithTitle("Fav Food")
.WithCustomId("food_menu")
.AddTextInput("What??", "food_name", placeholder:"Pizza")
.AddTextInput("Why??", "food_reason", TextInputStyle.Paragraph,
"Kus it's so tasty");
//await component.RespondWithModalAsync(mb.Build());
await component.RespondAsync("hi", ephemeral: true);
break;
case Constants.ComponentIds.SIGN_OFF:
//await component.RespondAsync($"{component.User.Mention} has clicked the SignOff button!");
break;
}
}
}
}

16
DiscordBot/Constants.cs Normal file
View file

@ -0,0 +1,16 @@
namespace DiscordBot
{
public class Constants
{
public class ComponentIds
{
public const string SIGN_UP = "su";
public const string SIGN_OFF = "so";
}
public class SlashCommands
{
public const string FIRST_COMMAND = "first-command";
}
}
}

View file

@ -0,0 +1,88 @@
using Microsoft.AspNetCore.Mvc;
using SharedClasses.SharedModels;
using DiscordBot.Messages;
using Discord;
using Discord.WebSocket;
using System.Linq;
namespace DiscordBot.Controllers
{
[ApiController]
[Route("[controller]")]
public class RaidController : ControllerBase
{
DiscordSocketClient _client;
public RaidController(DiscordSocketClient client)
{
_client = client;
}
/*{"Title":"Testraid","Description":"This is a test raidnwith multiple lines?","Organizer":"Sarah","Guild":"LIEB","VoiceChat":"ts.lieb.games","RaidId":4,"StartTimeUTC":"2022-11-13T10:24:58.3955622+00:00","EndTimeUTC":"2022-11-14T12:24:58.3955622+00:00","Roles":[{"Name":"Random","Description":"RandomWithBoons","Spots":10,"Users":[]}],"DisocrdMessages":[{"GuildId":666953424734257182,"ChannelId":666954070388637697,"MessageId":0,"WebsiteDatabaseId":2}]}*/
[HttpPost]
[Route("[action]")]
public async Task<ApiRaid> PostRaidMessage(ApiRaid raid)
{
RaidMessage message = new RaidMessage(_client);
return await message.PostRaidMessage(raid);
}
[HttpPost]
[Route("[action]")]
public async Task DeleteRaidMessage(IEnumerable<ApiRaid.DiscordMessage> messages)
{
RaidMessage message = new RaidMessage(_client);
await message.DeleteRaidMessage(messages);
}
[HttpGet]
[Route("[action]")]
public List<DiscordServer> GetServers()
{
List<DiscordServer> servers = new List<DiscordServer>();
foreach(var guild in _client.Guilds)
{
DiscordServer server = new DiscordServer(){
Name = guild.Name,
Id = guild.Id
};
foreach(var channel in guild.Channels)
{
if( channel is SocketTextChannel && channel.Users.Where(u => u.Id == _client.CurrentUser.Id).Any())
{
server.Channels.Add(new DiscordChannel(){
Name = channel.Name,
Id = channel.Id
});
}
}
servers.Add(server);
}
return servers;
}
[HttpPost]
[Route("[action]")]
public async Task SendUserReminder(ApiUserReminder reminder)
{
foreach(ulong userId in reminder.UserIds)
{
await _client.GetUser(userId).SendMessageAsync(reminder.Message);
}
}
[HttpPost]
[Route("[action]")]
public async Task SendChannelReminder(ApiChannelReminder reminder)
{
var channel = _client.GetGuild(reminder.DiscordServerId).GetChannel(reminder.DiscordChannelId);
if (channel != null && channel is IMessageChannel)
{
IMessageChannel messageChannel = channel as IMessageChannel;
await messageChannel.SendMessageAsync(reminder.Message);
}
}
}
}

View file

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;
namespace DiscordBot.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View file

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.7.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SharedClasses\SharedClasses.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,131 @@
using Discord;
using Discord.WebSocket;
using System;
using System.ComponentModel.DataAnnotations;
using SharedClasses.SharedModels;
namespace DiscordBot.Messages
{
public class RaidMessage
{
DiscordSocketClient _client;
public RaidMessage(DiscordSocketClient client)
{
_client = client;
}
public async Task DeleteRaidMessage(IEnumerable<ApiRaid.DiscordMessage> messages)
{
foreach (ApiRaid.DiscordMessage message in messages)
{
var channel = _client.GetGuild(message.GuildId).GetChannel(message.ChannelId);
if (channel != null && channel is IMessageChannel)
{
IMessageChannel messageChannel = channel as IMessageChannel;
if (message.MessageId != 0)
{
await messageChannel.DeleteMessageAsync(message.MessageId);
}
}
}
}
public async Task<ApiRaid> PostRaidMessage(ApiRaid raid)
{
var menuBuilder = new SelectMenuBuilder()
.WithPlaceholder("Select an option")
.WithCustomId("menu-1")
.WithMinValues(1)
.WithMaxValues(1)
.AddOption("Option A", "opt-a", "Option B is lying!")
.AddOption("Option B", "opt-b", "Option A is telling the truth!");
var builder = new ComponentBuilder()
.WithButton("SignUp", $"{Constants.ComponentIds.SIGN_UP}-{raid.RaidId.ToString()}", ButtonStyle.Secondary)
.WithButton("SignOff", $"{Constants.ComponentIds.SIGN_OFF}-{raid.RaidId.ToString()}", ButtonStyle.Secondary);
//.WithSelectMenu(menuBuilder);
MessageComponent components = builder.Build();
Embed raidMessage = CreateRaidMessage(raid);
foreach (ApiRaid.DiscordMessage message in raid.DisocrdMessages)
{
var channel = _client.GetGuild(message.GuildId).GetChannel(message.ChannelId);
if (channel != null && channel is IMessageChannel)
{
IMessageChannel messageChannel = channel as IMessageChannel;
if (message.MessageId != 0)
{
MessageProperties properties = new MessageProperties()
{
Embed = raidMessage,
Components = components
};
await messageChannel.ModifyMessageAsync(message.MessageId, new Action<MessageProperties>(x => x = properties));
}
else
{
IUserMessage sentMessage = await messageChannel.SendMessageAsync(embed: raidMessage, components: components);
message.MessageId = sentMessage.Id;
}
}
}
return raid;
}
public Embed CreateRaidMessage(ApiRaid raid)
{
var embed = new EmbedBuilder()
{
Title = raid.Title,
Description = raid.Description,
Footer = new EmbedFooterBuilder()
{
Text = $"RaidId: {raid.RaidId}"
}
};
AddMessageDetails(raid, ref embed);
AddMessageRoles(raid, ref embed);
return embed.Build();
}
private void AddMessageDetails(ApiRaid raid, ref EmbedBuilder embed)
{
embed.AddField("Date", $"{raid.StartTimeUTC.ToLocalTime().DateTime.ToLongDateString()}");
embed.AddField("Time", $"from: {raid.StartTimeUTC.ToLocalTime().DateTime.ToShortTimeString()} to: {raid.EndTimeUTC.ToLocalTime().DateTime.ToShortTimeString()}");
embed.AddField("Organisator", raid.Organizer, true);
embed.AddField("Guild", raid.Guild, true);
embed.AddField("Voice chat", raid.VoiceChat, true);
}
private void AddMessageRoles(ApiRaid raid, ref EmbedBuilder embed)
{
Dictionary<string, string> fieldList = new Dictionary<string, string>();
embed.AddField("Signed up", $"({raid.Roles.Sum(r => r.Users.Count)}/{raid.Roles.Sum(r => r.Spots)}):");
foreach (ApiRaid.Role role in raid.Roles)
{
//print signed up users
string signedUpUsers = PrintUsers(role);
if (string.IsNullOrEmpty(signedUpUsers)) signedUpUsers = "-";
string fieldName = $"{role.Name}: {role.Description} ({role.Users.Where(u => string.IsNullOrWhiteSpace(u.Status)).Count()}/{role.Spots})";
embed.AddField(fieldName, signedUpUsers);
}
}
private string PrintUsers(ApiRaid.Role role)
{
string rolesString = string.Empty;
foreach (ApiRaid.Role.User user in role.Users.OrderBy(u => u.Status))
{
string status = !string.IsNullOrWhiteSpace(user.Status) ? $" - {user.Status}" : string.Empty;
rolesString += $"\t{user.UserName} ({user.AccountName}) {status}\n";
}
return rolesString;
}
}
}

123
DiscordBot/Program.cs Normal file
View file

@ -0,0 +1,123 @@
using Discord;
using Discord.Commands;
using Discord.Net;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System.Reflection;
namespace DiscordBot
{
internal class Program
{
DiscordSocketClient _client;
public static Task Main(string[] args) => new Program().MainAsync(args);
public async Task MainAsync(string[] args)
{
var dicordConfig = new DiscordSocketConfig()
{
};
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSingleton(dicordConfig);
builder.Services.AddSingleton<DiscordSocketClient>();
builder.Services.AddSingleton<CommandService>();
builder.Services.AddSingleton<CommandHandler>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
_client = app.Services.GetRequiredService<DiscordSocketClient>();
var commandHandler = app.Services.GetRequiredService<CommandHandler>();
await commandHandler.InstallCommandsAsync();
_client.Log += Log;
//_client.Ready += Client_Ready;
// You can assign your bot token to a string, and pass that in to connect.
// This is, however, insecure, particularly if you plan to have your code hosted in a public repository.
var token = "MTAxODE3MDczMDU0Mzg1Nzc3NA.GA2Qzu.__kLICXm-8VLTQD6KzL-uGaPx60Q6fn8K6lE3A";
//var token = "Njc2NzQ4NjM2NjI5MTA2NzE4.Xtu2Ww._2oF7lQtxLLOUhfAIMxocN52dYo";
// Some alternative options would be to keep your token in an Environment Variable or a standalone file.
// var token = Environment.GetEnvironmentVariable("NameOfYourEnvironmentVariable");
// var token = File.ReadAllText("token.txt");
// var token = JsonConvert.DeserializeObject<AConfigurationClass>(File.ReadAllText("config.json")).Token;
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
app.Run();
// Block this task until the program is closed.
await Task.Delay(-1);
}
private Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}
public async Task Client_Ready()
{
// Let's build a guild command! We're going to need a guild so lets just put that in a variable.
var guild = _client.GetGuild(666953424734257182);
// Next, lets create our slash command builder. This is like the embed builder but for slash commands.
var guildCommand = new SlashCommandBuilder();
// Note: Names have to be all lowercase and match the regular expression ^[\w-]{3,32}$
guildCommand.WithName("first-command");
// Descriptions can have a max length of 100.
guildCommand.WithDescription("This is my first guild slash command!");
try
{
// Now that we have our builder, we can call the CreateApplicationCommandAsync method to make our slash command.
await guild.CreateApplicationCommandAsync(guildCommand.Build());
// Using the ready event is a simple implementation for the sake of the example. Suitable for testing and development.
// For a production bot, it is recommended to only run the CreateGlobalApplicationCommandAsync() once for each command.
}
catch (ApplicationCommandException exception)
{
// If our command was invalid, we should catch an ApplicationCommandException. This exception contains the path of the error as well as the error message. You can serialize the Error field in the exception to get a visual of where your error is.
//var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented);
// You can send this error somewhere or just print it to the console, for this example we're just going to print it.
//Console.WriteLine(json);
}
}
}
}

View file

@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:52668",
"sslPort": 44379
}
},
"profiles": {
"DiscordBot": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7240;http://localhost:5240",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,13 @@
namespace DiscordBot
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}