Basic Operation working

This commit is contained in:
Michael Chen 2022-01-10 16:10:32 +01:00
parent 191a9d3415
commit bfb060710a
No known key found for this signature in database
GPG Key ID: 1CBC7AA5671437BB
10 changed files with 154 additions and 56 deletions

2
.gitignore vendored
View File

@ -3,6 +3,8 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
MinecraftDiscordBot/config.json
# User-specific files
*.rsuser
*.suo

View File

@ -3,7 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32104.313
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinecraftDiscordBot", "MinecraftDiscordBot\MinecraftDiscordBot.csproj", "{46DBB810-17C0-45E9-BD39-2EE3FE101AC7}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CE65C879-794A-4695-B659-7376FE7DB5E3}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
MinecraftDiscordBot\bin\Debug\net6.0\config.json = MinecraftDiscordBot\bin\Debug\net6.0\config.json
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinecraftDiscordBot", "MinecraftDiscordBot\MinecraftDiscordBot.csproj", "{7A4A00B4-FDD1-461E-B925-1A7F1B185C4A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -11,10 +17,10 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{46DBB810-17C0-45E9-BD39-2EE3FE101AC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46DBB810-17C0-45E9-BD39-2EE3FE101AC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46DBB810-17C0-45E9-BD39-2EE3FE101AC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46DBB810-17C0-45E9-BD39-2EE3FE101AC7}.Release|Any CPU.Build.0 = Release|Any CPU
{7A4A00B4-FDD1-461E-B925-1A7F1B185C4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A4A00B4-FDD1-461E-B925-1A7F1B185C4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A4A00B4-FDD1-461E-B925-1A7F1B185C4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A4A00B4-FDD1-461E-B925-1A7F1B185C4A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace MinecraftDiscordBot;
public class BotConfiguration {
[JsonProperty("token", Required = Required.Always)]
public string Token { get; set; } = default!;
[JsonProperty("port", Required = Required.Always)]
public int Port { get; set; } = default!;
[JsonProperty("channels", Required = Required.Always)]
public IEnumerable<ulong> Channels { get; set; } = default!;
}

View File

@ -1,8 +1,7 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src

View File

@ -0,0 +1,21 @@
using Discord.WebSocket;
using Newtonsoft.Json;
namespace MinecraftDiscordBot;
public abstract class Message {
[JsonProperty("type")]
public abstract string Type { get; }
}
public class TextMessage : Message {
public TextMessage(SocketMessage arg) : this(arg.Author.Username, arg.Content) { }
public TextMessage(string author, string content) {
Author = author;
Content = content;
}
public override string Type => "text";
[JsonProperty("author")]
public string Author { get; }
[JsonProperty("message")]
public string Content { get; }
}

View File

@ -1,14 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.1.0" />
<PackageReference Include="Fleck" Version="1.2.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,101 @@
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using Discord;
using Discord.Rest;
using Discord.WebSocket;
using Fleck;
using Newtonsoft.Json;
using System.Collections.Concurrent;
app.MapGet("/", () => "Hello World!");
namespace MinecraftDiscordBot;
app.Run();
public class Program {
private const string WebSocketSource = "WebSocket";
private readonly object _logLock = new();
private readonly DiscordSocketClient _client = new(new() {
LogLevel = LogSeverity.Verbose
});
private readonly WebSocketServer _wssv;
private readonly BotConfiguration _config;
private readonly HashSet<ulong> _whitelistedChannels;
private readonly ConcurrentDictionary<Guid, IWebSocketConnection> _connections = new();
public Program(BotConfiguration config) {
_config = config;
_client.Log += Log;
_client.MessageReceived += MessageReceived;
_wssv = new WebSocketServer($"ws://0.0.0.0:{config.Port}") {
RestartAfterListenError = true
};
_whitelistedChannels = config.Channels.ToHashSet();
}
public static Task<int> Main(string[] args)
=> JsonConvert.DeserializeObject<BotConfiguration>(File.ReadAllText("config.json")) is BotConfiguration config
? new Program(config).RunAsync()
: throw new InvalidProgramException("Configuration file missing!");
public async Task<int> RunAsync() {
_wssv.Start(socket => {
socket.OnOpen = async () => await SocketOpened(socket);
socket.OnClose = async () => await SocketClosed(socket);
socket.OnMessage = async message => await SocketReceived(socket, message);
});
await _client.LoginAsync(TokenType.Bot, _config.Token);
await _client.StartAsync();
await VerifyTextChannels();
// Block this task until the program is closed.
await Task.Delay(-1);
return 0;
}
private async Task VerifyTextChannels() {
var channels = await Task.WhenAll(_whitelistedChannels.Select(id => _client.GetChannelAsync(id).AsTask()).ToArray());
await Task.WhenAll(channels.Where(i => i is ITextChannel { Guild: RestGuild }).Select(i => ((RestGuild)((ITextChannel)i).Guild).UpdateAsync()));
foreach (var channel in channels) {
if (channel is ITextChannel tchannel) Console.WriteLine($"Whitelisted in channel: {channel.Name} [{channel.Id}] on server {tchannel.Guild.Name} [{tchannel.Guild.Id}]");
else throw new InvalidProgramException($"Cannot use this bot on non-text channel {channel.Name} [{channel.Id}]!");
}
}
private async Task SocketReceived(IWebSocketConnection socket, string message)
=> await Log(new LogMessage(LogSeverity.Info, WebSocketSource, $"[{socket.ConnectionInfo.Id}] Received: {message}")).ConfigureAwait(false);
private async Task SocketClosed(IWebSocketConnection socket) {
if (!_connections.TryRemove(socket.ConnectionInfo.Id, out _))
throw new InvalidProgramException("Could not remove non-existing client!");
await Log(new LogMessage(LogSeverity.Info, WebSocketSource, $"[{socket.ConnectionInfo.Id}] Client disconnected!")).ConfigureAwait(false);
}
private async Task SocketOpened(IWebSocketConnection socket) {
if (!_connections.TryAdd(socket.ConnectionInfo.Id, socket))
throw new InvalidProgramException("Could not add already-existing client!");
await Log(new LogMessage(LogSeverity.Info, WebSocketSource, $"[{socket.ConnectionInfo.Id}] Client connected from {socket.ConnectionInfo.ClientIpAddress}:{socket.ConnectionInfo.ClientPort}!")).ConfigureAwait(false);
}
private async Task MessageReceived(SocketMessage arg) {
if (arg.Author.IsBot) return;
if (IsChannelWhitelisted(arg.Channel))
await Log(new LogMessage(LogSeverity.Info, "Discord", $"[{arg.Author.Username}] {arg.Content}")).ConfigureAwait(false);
await SendToAll(JsonConvert.SerializeObject(new TextMessage(arg)));
}
private bool IsChannelWhitelisted(ISocketMessageChannel channel)
=> _whitelistedChannels.Contains(channel.Id);
private async Task SendToAll(string message) {
async Task SendToClient(KeyValuePair<Guid, IWebSocketConnection> cp) {
try {
await cp.Value.Send(message);
} catch (Exception e) {
await Log(new LogMessage(LogSeverity.Warning, WebSocketSource, $"[{cp.Key}] Sending message failed!", e)).ConfigureAwait(false);
}
}
await Task.WhenAll(_connections.Select(SendToClient).ToArray());
}
private async Task Log(LogMessage msg) {
lock (_logLock)
Console.WriteLine(msg.ToString());
await Task.CompletedTask;
}
}

View File

@ -1,34 +1,10 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:25710",
"sslPort": 0
}
},
"profiles": {
"MinecraftDiscordBot": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5114",
"dotnetRunMessages": true
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
"commandName": "Project"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true
"commandName": "Docker"
}
}
}

View File

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

View File

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