Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
0e09aaef5c | |||
29b8c59c9e | |||
bbdd5ce586 | |||
|
6920d1a2b3 | ||
|
82c8313cb9 | ||
|
a6ee52f70e |
@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1
|
|||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CE65C879-794A-4695-B659-7376FE7DB5E3}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CE65C879-794A-4695-B659-7376FE7DB5E3}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
|
build.py = build.py
|
||||||
MinecraftDiscordBot\bin\Debug\net6.0\config.json = MinecraftDiscordBot\bin\Debug\net6.0\config.json
|
MinecraftDiscordBot\bin\Debug\net6.0\config.json = MinecraftDiscordBot\bin\Debug\net6.0\config.json
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
@ -11,8 +11,11 @@ public class BotConfiguration : IBotConfiguration, IBotConfigurator {
|
|||||||
[JsonProperty("token", Required = Required.Always)]
|
[JsonProperty("token", Required = Required.Always)]
|
||||||
[Option('t', "token", HelpText = "The Discord bot token", Required = true)]
|
[Option('t', "token", HelpText = "The Discord bot token", Required = true)]
|
||||||
public string Token { get; init; } = default!;
|
public string Token { get; init; } = default!;
|
||||||
|
[JsonProperty("address", Required = Required.Always)]
|
||||||
|
[Option('a', "address", HelpText = "The connection string for the websocket", Required = true)]
|
||||||
|
public string Address { get; init; } = default!;
|
||||||
[JsonProperty("port", Required = Required.DisallowNull)]
|
[JsonProperty("port", Required = Required.DisallowNull)]
|
||||||
[Option('p', "port", Default = DEFAULT_PORT, HelpText = "The websocket server port")]
|
[Option('p', "port", Default = DEFAULT_PORT, HelpText = "The websocket server listen port")]
|
||||||
public int Port { get; init; } = DEFAULT_PORT;
|
public int Port { get; init; } = DEFAULT_PORT;
|
||||||
[JsonProperty("channels", Required = Required.Always)]
|
[JsonProperty("channels", Required = Required.Always)]
|
||||||
[Option('c', "channel", HelpText = "The list of whitelisted channels", Required = true, Min = 1)]
|
[Option('c', "channel", HelpText = "The list of whitelisted channels", Required = true, Min = 1)]
|
||||||
@ -26,6 +29,9 @@ public class BotConfiguration : IBotConfiguration, IBotConfigurator {
|
|||||||
[JsonProperty("admins", Required = Required.DisallowNull)]
|
[JsonProperty("admins", Required = Required.DisallowNull)]
|
||||||
[Option("admins", Default = new ulong[] { }, HelpText = "The list of bot administrators.")]
|
[Option("admins", Default = new ulong[] { }, HelpText = "The list of bot administrators.")]
|
||||||
public ulong[] Administrators { get; init; } = Array.Empty<ulong>();
|
public ulong[] Administrators { get; init; } = Array.Empty<ulong>();
|
||||||
|
[JsonProperty("logchannel", Required = Required.DisallowNull)]
|
||||||
|
[Option("logchannel", Default = null, HelpText = "Optionally the id of a channel to mirror log to.")]
|
||||||
|
public ulong? LogChannel { get; init; } = null;
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public BotConfiguration Config => this;
|
public BotConfiguration Config => this;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,16 @@ local function runRsCommand(params)
|
|||||||
return textutils.serializeJSON(retvals)
|
return textutils.serializeJSON(retvals)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getPeripheralInfo(side)
|
||||||
|
return {type = peripheral.getType(side), methods = peripheral.getMethods(side), side = side}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getPeripheralList()
|
||||||
|
local pers = {}
|
||||||
|
for i,side in pairs(peripheral.getNames()) do pers[side] = getPeripheralInfo(side) end
|
||||||
|
return pers
|
||||||
|
end
|
||||||
|
|
||||||
-- error: any error during execution
|
-- error: any error during execution
|
||||||
-- return string result
|
-- return string result
|
||||||
local function getResponse(parsed)
|
local function getResponse(parsed)
|
||||||
@ -76,6 +86,8 @@ local function getResponse(parsed)
|
|||||||
return textutils.serializeJSON(item)
|
return textutils.serializeJSON(item)
|
||||||
elseif parsed.method == "command" then
|
elseif parsed.method == "command" then
|
||||||
return runRsCommand(parsed.params)
|
return runRsCommand(parsed.params)
|
||||||
|
elseif parsed.method == "peripherals" then
|
||||||
|
return textutils.serializeJSON(getPeripheralList())
|
||||||
elseif parsed.method == "getonline" then
|
elseif parsed.method == "getonline" then
|
||||||
return textutils.serializeJSON(getPeripheral("playerDetector").getOnlinePlayers())
|
return textutils.serializeJSON(getPeripheral("playerDetector").getOnlinePlayers())
|
||||||
elseif parsed.method == "whereis" then
|
elseif parsed.method == "whereis" then
|
||||||
@ -160,10 +172,6 @@ local function chatEventListener(socket)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getPeripheralInfo(side)
|
|
||||||
return {type = peripheral.getType(side), methods = peripheral.getMethods(side), side = side}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function peripheralDetachEventListener(socket)
|
local function peripheralDetachEventListener(socket)
|
||||||
while true do
|
while true do
|
||||||
event, side = os.pullEvent("peripheral_detach")
|
event, side = os.pullEvent("peripheral_detach")
|
||||||
@ -180,10 +188,52 @@ local function peripheralAttachEventListener(socket)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function listAsSet(list)
|
||||||
|
local asSet = {}
|
||||||
|
for i,elem in pairs(list) do asSet[elem] = true end
|
||||||
|
return asSet
|
||||||
|
end
|
||||||
|
|
||||||
|
local function joinSets(a, b)
|
||||||
|
local joined = {}
|
||||||
|
for elem,exists in pairs(a) do if exists then joined[elem] = true end end
|
||||||
|
for elem,exists in pairs(b) do if exists then joined[elem] = true end end
|
||||||
|
return joined
|
||||||
|
end
|
||||||
|
|
||||||
|
local function playerStatusEventListener(socket)
|
||||||
|
local players = {}
|
||||||
|
while true do
|
||||||
|
local pd = peripheral.find("playerDetector")
|
||||||
|
if not not pd then
|
||||||
|
players = listAsSet(pd.getOnlinePlayers())
|
||||||
|
break
|
||||||
|
end
|
||||||
|
printError("playerDetector not connected!")
|
||||||
|
sleep(5)
|
||||||
|
end
|
||||||
|
while true do
|
||||||
|
local pd = peripheral.find("playerDetector")
|
||||||
|
if not not pd then
|
||||||
|
local newPlayers = listAsSet(pd.getOnlinePlayers())
|
||||||
|
for player,_ in pairs(joinSets(players, newPlayers)) do
|
||||||
|
if players[player] and (not newPlayers[player]) then
|
||||||
|
sendJson(socket, {type = "playerstatus", player = player, status = false})
|
||||||
|
elseif (not players[player]) and newPlayers[player] then
|
||||||
|
sendJson(socket, {type = "playerstatus", player = player, status = true})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
players = newPlayers
|
||||||
|
end
|
||||||
|
sleep(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function eventListeners(socket)
|
local function eventListeners(socket)
|
||||||
parallel.waitForAny(
|
parallel.waitForAny(
|
||||||
termWaiter,
|
termWaiter,
|
||||||
function() chatEventListener(socket) end,
|
function() chatEventListener(socket) end,
|
||||||
|
function() playerStatusEventListener(socket) end,
|
||||||
function() peripheralDetachEventListener(socket) end,
|
function() peripheralDetachEventListener(socket) end,
|
||||||
function() peripheralAttachEventListener(socket) end
|
function() peripheralAttachEventListener(socket) end
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
<Version>1.1.2</Version>
|
<Version>1.1.5</Version>
|
||||||
<Authors>Michael Chen</Authors>
|
<Authors>Michael Chen</Authors>
|
||||||
<Company>$(Authors)</Company>
|
<Company>$(Authors)</Company>
|
||||||
<RepositoryUrl>https://gitlab.com/chenmichael/mcdiscordbot</RepositoryUrl>
|
<RepositoryUrl>https://gitlab.com/chenmichael/mcdiscordbot</RepositoryUrl>
|
||||||
@ -20,11 +20,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageReference Include="Discord.Net" Version="3.1.0" />
|
<PackageReference Include="Discord.Net" Version="3.8.1" />
|
||||||
<PackageReference Include="Fleck" Version="1.2.0" />
|
<PackageReference Include="Fleck" Version="1.2.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace MinecraftDiscordBot.Models;
|
namespace MinecraftDiscordBot.Models;
|
||||||
|
|
||||||
@ -39,14 +40,17 @@ public abstract class Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MessageType(TYPE)]
|
[MessageType(TYPE)]
|
||||||
|
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
|
||||||
public class CapabilityMessage : Message {
|
public class CapabilityMessage : Message {
|
||||||
private const string TYPE = "roles";
|
private const string TYPE = "roles";
|
||||||
public override string Type => TYPE;
|
public override string Type => TYPE;
|
||||||
[JsonProperty("role", Required = Required.Always)]
|
[JsonProperty("role", Required = Required.Always)]
|
||||||
public string[] Role { get; set; } = default!;
|
public string[] Role { get; set; } = default!;
|
||||||
|
public override string ToString() => $"Capabilities: {string.Join(", ", Role)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
[MessageType(TYPE)]
|
[MessageType(TYPE)]
|
||||||
|
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
|
||||||
public class ReplyMessage : Message {
|
public class ReplyMessage : Message {
|
||||||
private const string TYPE = "reply";
|
private const string TYPE = "reply";
|
||||||
public override string Type => TYPE;
|
public override string Type => TYPE;
|
||||||
@ -60,19 +64,35 @@ public class ReplyMessage : Message {
|
|||||||
public int Total { get; set; } = 1;
|
public int Total { get; set; } = 1;
|
||||||
[JsonProperty("success", Required = Required.DisallowNull)]
|
[JsonProperty("success", Required = Required.DisallowNull)]
|
||||||
public ResultState State { get; set; } = ResultState.Successful;
|
public ResultState State { get; set; } = ResultState.Successful;
|
||||||
|
public override string ToString() => $"Reply [{AnswerId}] {State} ({Chunk}/{Total}) Length {Result.Length}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class EventMessage : Message { }
|
public abstract class EventMessage : Message { }
|
||||||
|
|
||||||
[MessageType(TYPE)]
|
[MessageType(TYPE)]
|
||||||
|
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
|
||||||
public class PeripheralDetachEvent : EventMessage {
|
public class PeripheralDetachEvent : EventMessage {
|
||||||
private const string TYPE = "peripheral_detach";
|
private const string TYPE = "peripheral_detach";
|
||||||
public override string Type => TYPE;
|
public override string Type => TYPE;
|
||||||
[JsonProperty("side", Required = Required.Always)]
|
[JsonProperty("side", Required = Required.Always)]
|
||||||
public string Side { get; set; } = default!;
|
public string Side { get; set; } = default!;
|
||||||
|
public override string ToString() => $"Detached '{Side}'!";
|
||||||
}
|
}
|
||||||
|
|
||||||
[MessageType(TYPE)]
|
[MessageType(TYPE)]
|
||||||
|
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
|
||||||
|
public class PlayerStatusEvent : EventMessage {
|
||||||
|
private const string TYPE = "playerstatus";
|
||||||
|
public override string Type => TYPE;
|
||||||
|
[JsonProperty("player", Required = Required.Always)]
|
||||||
|
public string Player { get; set; } = default!;
|
||||||
|
[JsonProperty("status", Required = Required.Always)]
|
||||||
|
public bool Online { get; set; }
|
||||||
|
public override string ToString() => $"{Player} is now {(Online ? "on" : "off")}line!";
|
||||||
|
}
|
||||||
|
|
||||||
|
[MessageType(TYPE)]
|
||||||
|
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
|
||||||
public class PeripheralAttachEvent : EventMessage {
|
public class PeripheralAttachEvent : EventMessage {
|
||||||
private const string TYPE = "peripheral";
|
private const string TYPE = "peripheral";
|
||||||
public override string Type => TYPE;
|
public override string Type => TYPE;
|
||||||
@ -80,6 +100,7 @@ public class PeripheralAttachEvent : EventMessage {
|
|||||||
public string Side => Peripheral.Side;
|
public string Side => Peripheral.Side;
|
||||||
[JsonProperty("peripheral", Required = Required.Always)]
|
[JsonProperty("peripheral", Required = Required.Always)]
|
||||||
public Peripheral Peripheral { get; set; } = default!;
|
public Peripheral Peripheral { get; set; } = default!;
|
||||||
|
public override string ToString() => $"Attached {Peripheral}!";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Peripheral {
|
public class Peripheral {
|
||||||
@ -89,9 +110,11 @@ public class Peripheral {
|
|||||||
public string Type { get; set; } = default!;
|
public string Type { get; set; } = default!;
|
||||||
[JsonProperty("methods", Required = Required.Always)]
|
[JsonProperty("methods", Required = Required.Always)]
|
||||||
public string[] Methods { get; set; } = default!;
|
public string[] Methods { get; set; } = default!;
|
||||||
|
public override string ToString() => $"{Type} at '{Side}'";
|
||||||
}
|
}
|
||||||
|
|
||||||
[MessageType(TYPE)]
|
[MessageType(TYPE)]
|
||||||
|
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
|
||||||
public class ChatEvent : EventMessage {
|
public class ChatEvent : EventMessage {
|
||||||
private const string TYPE = "chat";
|
private const string TYPE = "chat";
|
||||||
public override string Type => TYPE;
|
public override string Type => TYPE;
|
||||||
@ -103,9 +126,11 @@ public class ChatEvent : EventMessage {
|
|||||||
public string UUID { get; set; } = default!;
|
public string UUID { get; set; } = default!;
|
||||||
[JsonProperty("hidden", Required = Required.Always)]
|
[JsonProperty("hidden", Required = Required.Always)]
|
||||||
public bool IsHidden { get; set; }
|
public bool IsHidden { get; set; }
|
||||||
|
public override string ToString() => $"{(IsHidden ? "HIDDEN: " : string.Empty)}[{Username}] {Message} ({UUID})";
|
||||||
}
|
}
|
||||||
|
|
||||||
[MessageType(TYPE)]
|
[MessageType(TYPE)]
|
||||||
|
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
|
||||||
public class RequestMessage : Message {
|
public class RequestMessage : Message {
|
||||||
private const string TYPE = "request";
|
private const string TYPE = "request";
|
||||||
public override string Type => TYPE;
|
public override string Type => TYPE;
|
||||||
@ -121,4 +146,5 @@ public class RequestMessage : Message {
|
|||||||
public string Method { get; set; }
|
public string Method { get; set; }
|
||||||
[JsonProperty("params")]
|
[JsonProperty("params")]
|
||||||
public Dictionary<string, object> Parameters { get; }
|
public Dictionary<string, object> Parameters { get; }
|
||||||
|
public override string ToString() => $"Request [{AnswerId}] {Method}({JsonConvert.SerializeObject(Parameters)})";
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,11 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
public IEnumerable<ActiveChannel> Channels => _channels ?? throw new InvalidProgramException("Channels used before verification!");
|
public IEnumerable<ActiveChannel> Channels => _channels ?? throw new InvalidProgramException("Channels used before verification!");
|
||||||
public ActiveChannel[]? _channels;
|
public ActiveChannel[]? _channels;
|
||||||
private bool disposedValue;
|
private bool disposedValue;
|
||||||
|
private static ITextChannel? LogChannel;
|
||||||
private readonly RootCommandService _computer;
|
private readonly RootCommandService _computer;
|
||||||
|
|
||||||
public static bool OnlineNotifications => true;
|
public static bool OnlineNotifications => true;
|
||||||
|
public const LogSeverity DiscordLogSeverity = LogSeverity.Warning;
|
||||||
private const string ClientScriptName = "MinecraftDiscordBot.ClientScript.lua";
|
private const string ClientScriptName = "MinecraftDiscordBot.ClientScript.lua";
|
||||||
private const string WebhookName = "minecraftbot";
|
private const string WebhookName = "minecraftbot";
|
||||||
public readonly string ClientScript;
|
public readonly string ClientScript;
|
||||||
@ -48,7 +50,7 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
if (stream is null) throw new FileNotFoundException("Client script could not be loaded!");
|
if (stream is null) throw new FileNotFoundException("Client script could not be loaded!");
|
||||||
using var sr = new StreamReader(stream);
|
using var sr = new StreamReader(stream);
|
||||||
return sr.ReadToEnd()
|
return sr.ReadToEnd()
|
||||||
.Replace("$HOST", $"ws://{config.SocketHost}:{config.Port}");
|
.Replace("$HOST", config.Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Broadcast(Func<ITextChannel, Task<IUserMessage>> message)
|
private Task Broadcast(Func<ITextChannel, Task<IUserMessage>> message)
|
||||||
@ -58,6 +60,9 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
_computer = new(this);
|
_computer = new(this);
|
||||||
_computer.ChatMessageReceived += MinecraftMessageReceived;
|
_computer.ChatMessageReceived += MinecraftMessageReceived;
|
||||||
_computer.SocketChanged += ComputerConnectedChanged;
|
_computer.SocketChanged += ComputerConnectedChanged;
|
||||||
|
_computer.PlayerStatusChanged += PlayerStatusChanged;
|
||||||
|
_computer.PeripheralAttached += PeripheralAttached;
|
||||||
|
_computer.PeripheralDetached += PeripheralDetached;
|
||||||
_administrators = config.Administrators.ToHashSet();
|
_administrators = config.Administrators.ToHashSet();
|
||||||
ClientScript = GetClientScript(config);
|
ClientScript = GetClientScript(config);
|
||||||
_client.Log += LogAsync;
|
_client.Log += LogAsync;
|
||||||
@ -70,11 +75,14 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
_whitelistedChannels = config.Channels.ToHashSet();
|
_whitelistedChannels = config.Channels.ToHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ComputerConnectedChanged(object? sender, IWebSocketConnection? e) {
|
private void PlayerStatusChanged(object? sender, PlayerStatusEvent e)
|
||||||
_ = Task.Run(() => Broadcast(i => i.SendMessageAsync(e is not null
|
=> _ = Task.Run(() => Broadcast(i => i.SendMessageAsync($"{e.Player} just {(e.Online ? "joined" : "left")} the server!")));
|
||||||
|
private void PeripheralAttached(object? sender, PeripheralAttachEvent e) => LogInfo("Computer", $"Peripheral {e.Peripheral.Type} was attached on side {e.Side}.");
|
||||||
|
private void PeripheralDetached(object? sender, PeripheralDetachEvent e) => LogInfo("Computer", $"Peripheral on side {e.Side} was detached.");
|
||||||
|
private void ComputerConnectedChanged(object? sender, IWebSocketConnection? e)
|
||||||
|
=> _ = Task.Run(() => Broadcast(i => i.SendMessageAsync(e is not null
|
||||||
? "The Minecraft client is now available!"
|
? "The Minecraft client is now available!"
|
||||||
: "The Minecraft client disconnected!")));
|
: "The Minecraft client disconnected!")));
|
||||||
}
|
|
||||||
private void MinecraftMessageReceived(object? sender, ChatEvent e)
|
private void MinecraftMessageReceived(object? sender, ChatEvent e)
|
||||||
=> Task.Run(() => WebhookBroadcast(i => i.SendMessageAsync(e.Message, username: e.Username, avatarUrl: $"https://crafatar.com/renders/head/{e.UUID}")));
|
=> Task.Run(() => WebhookBroadcast(i => i.SendMessageAsync(e.Message, username: e.Username, avatarUrl: $"https://crafatar.com/renders/head/{e.UUID}")));
|
||||||
private Task<T[]> WebhookBroadcast<T>(Func<DiscordWebhookClient, Task<T>> apply) => Task.WhenAll(Channels.Select(i => apply(new DiscordWebhookClient(i.Webhook))));
|
private Task<T[]> WebhookBroadcast<T>(Func<DiscordWebhookClient, Task<T>> apply) => Task.WhenAll(Channels.Select(i => apply(new DiscordWebhookClient(i.Webhook))));
|
||||||
@ -109,7 +117,12 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> HasValidChannels() {
|
private async Task<bool> HasValidChannels() {
|
||||||
if (await GetValidChannels(_whitelistedChannels).ToArrayAsync() is not { Length: > 0 } channels) {
|
if (_config.LogChannel is ulong logChannelId) {
|
||||||
|
LogChannel = await IsValidChannel(logChannelId);
|
||||||
|
if (LogChannel is null)
|
||||||
|
await LogWarningAsync(BotSource, $"The given log channel ID is not valid '{logChannelId}'!");
|
||||||
|
}
|
||||||
|
if (await GetValidChannels(_whitelistedChannels) is not { Length: > 0 } channels) {
|
||||||
await LogErrorAsync(BotSource, new InvalidOperationException("No valid textchannel was whitelisted!"));
|
await LogErrorAsync(BotSource, new InvalidOperationException("No valid textchannel was whitelisted!"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -127,13 +140,14 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
socket.OnMessage = async message => await SocketReceived(socket, message);
|
socket.OnMessage = async message => await SocketReceived(socket, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
private async IAsyncEnumerable<ITextChannel> GetValidChannels(IEnumerable<ulong> ids) {
|
private async Task<ITextChannel[]> GetValidChannels(IEnumerable<ulong> ids)
|
||||||
foreach (var channelId in ids) {
|
=> (await Task.WhenAll(ids.Select(i => IsValidChannel(i)))).OfType<ITextChannel>().ToArray();
|
||||||
|
private async Task<ITextChannel?> IsValidChannel(ulong channelId) {
|
||||||
var channel = await _client.GetChannelAsync(channelId);
|
var channel = await _client.GetChannelAsync(channelId);
|
||||||
if (channel is not ITextChannel textChannel) {
|
if (channel is not ITextChannel textChannel) {
|
||||||
if (channel is null) await LogWarningAsync(BotSource, $"Channel with id [{channelId}] does not exist!");
|
if (channel is null) await LogWarningAsync(BotSource, $"Channel with id [{channelId}] does not exist!");
|
||||||
else await LogWarningAsync(BotSource, $"Channel is not a text channels and will not be used: {channel.Name} [{channel.Id}]!");
|
else await LogWarningAsync(BotSource, $"Channel is not a text channels and will not be used: {channel.Name} [{channel.Id}]!");
|
||||||
continue;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textChannel.Guild is RestGuild guild) {
|
if (textChannel.Guild is RestGuild guild) {
|
||||||
@ -142,8 +156,7 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
} else {
|
} else {
|
||||||
await LogWarningAsync(BotSource, $"Whitelisted in channel: {channel.Name} [{channel.Id}] on unknown server!");
|
await LogWarningAsync(BotSource, $"Whitelisted in channel: {channel.Name} [{channel.Id}] on unknown server!");
|
||||||
}
|
}
|
||||||
yield return textChannel;
|
return textChannel;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SocketReceived(IWebSocketConnection socket, string message) {
|
private async Task SocketReceived(IWebSocketConnection socket, string message) {
|
||||||
@ -276,11 +289,6 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
public static void LogError(string source, Exception exception) => Log(new(LogSeverity.Error, source, exception?.Message, exception));
|
public static void LogError(string source, Exception exception) => Log(new(LogSeverity.Error, source, exception?.Message, exception));
|
||||||
|
|
||||||
private static async Task LogAsync(LogMessage msg) {
|
private static async Task LogAsync(LogMessage msg) {
|
||||||
Log(msg);
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Log(LogMessage msg) {
|
|
||||||
lock (LogLock) {
|
lock (LogLock) {
|
||||||
var oldColor = Console.ForegroundColor;
|
var oldColor = Console.ForegroundColor;
|
||||||
try {
|
try {
|
||||||
@ -298,7 +306,12 @@ public class Program : IDisposable, ICommandHandler<ResponseType>, IUserRoleMana
|
|||||||
Console.ForegroundColor = oldColor;
|
Console.ForegroundColor = oldColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (msg.Severity <= DiscordLogSeverity && LogChannel is ITextChannel log) {
|
||||||
|
await log.SendMessageAsync($"{msg.Severity}: {msg}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(LogMessage msg) => _ = Task.Run(() => LogAsync(msg));
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing) {
|
protected virtual void Dispose(bool disposing) {
|
||||||
if (!disposedValue) {
|
if (!disposedValue) {
|
||||||
|
@ -3,6 +3,7 @@ using Fleck;
|
|||||||
using MinecraftDiscordBot.Commands;
|
using MinecraftDiscordBot.Commands;
|
||||||
using MinecraftDiscordBot.Models;
|
using MinecraftDiscordBot.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MinecraftDiscordBot.Services;
|
namespace MinecraftDiscordBot.Services;
|
||||||
|
|
||||||
@ -18,13 +19,14 @@ public class RootCommandService : CommandRouter, ITaskWaitSource {
|
|||||||
Chat = new ChatBoxService(this);
|
Chat = new ChatBoxService(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<T> Method<T>(ITaskWaitSource taskSource, string methodName, Func<string, T> parser, CancellationToken ct, Dictionary<string, object>? parameters) {
|
public static async Task<T> Method<T>(ITaskWaitSource taskSource, string methodName, Func<string, T> parser, CancellationToken ct, Dictionary<string, object>? parameters = null) {
|
||||||
var waiter = taskSource.GetWaiter(parser, ct);
|
var waiter = taskSource.GetWaiter(parser, ct);
|
||||||
await taskSource.Send(new RequestMessage(waiter.ID, methodName, parameters));
|
await taskSource.Send(new RequestMessage(waiter.ID, methodName, parameters));
|
||||||
return await waiter.Task;
|
return await waiter.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<ChatEvent>? ChatMessageReceived;
|
public event EventHandler<ChatEvent>? ChatMessageReceived;
|
||||||
|
public event EventHandler<PlayerStatusEvent>? PlayerStatusChanged;
|
||||||
public event EventHandler<PeripheralAttachEvent>? PeripheralAttached;
|
public event EventHandler<PeripheralAttachEvent>? PeripheralAttached;
|
||||||
public event EventHandler<PeripheralDetachEvent>? PeripheralDetached;
|
public event EventHandler<PeripheralDetachEvent>? PeripheralDetached;
|
||||||
public event EventHandler<IWebSocketConnection?>? SocketChanged;
|
public event EventHandler<IWebSocketConnection?>? SocketChanged;
|
||||||
@ -48,6 +50,9 @@ public class RootCommandService : CommandRouter, ITaskWaitSource {
|
|||||||
case ChatEvent msg:
|
case ChatEvent msg:
|
||||||
ChatMessageReceived?.Invoke(this, msg);
|
ChatMessageReceived?.Invoke(this, msg);
|
||||||
break;
|
break;
|
||||||
|
case PlayerStatusEvent msg:
|
||||||
|
PlayerStatusChanged?.Invoke(this, msg);
|
||||||
|
break;
|
||||||
case PeripheralAttachEvent msg:
|
case PeripheralAttachEvent msg:
|
||||||
PeripheralAttached?.Invoke(this, msg);
|
PeripheralAttached?.Invoke(this, msg);
|
||||||
break;
|
break;
|
||||||
@ -97,9 +102,13 @@ public class RootCommandService : CommandRouter, ITaskWaitSource {
|
|||||||
return waiter;
|
return waiter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<Dictionary<string, Peripheral>> GetPeripherals(CancellationToken ct) => Method(this, "peripherals", Deserialize<Dictionary<string, Peripheral>>(), ct);
|
||||||
[CommandHandler("rs", HelpText = "Provides some commands for interacting with the Refined Storage system.")]
|
[CommandHandler("rs", HelpText = "Provides some commands for interacting with the Refined Storage system.")]
|
||||||
public Task<ResponseType> RefinedStorageHandler(SocketUserMessage message, string[] parameters, CancellationToken ct)
|
public Task<ResponseType> RefinedStorageHandler(SocketUserMessage message, string[] parameters, CancellationToken ct)
|
||||||
=> RefinedStorage.HandleCommand(message, parameters, ct);
|
=> RefinedStorage.HandleCommand(message, parameters, ct);
|
||||||
|
[CommandHandler("peripherals", HelpText = "Gets a list of peripherals that are attached.")]
|
||||||
|
public async Task<ResponseType> HandleGetPeripherals(SocketUserMessage message, string[] parameters, CancellationToken ct)
|
||||||
|
=> ResponseType.AsString(string.Join("\n", (await GetPeripherals(ct)).Values.Select(i => $"On side {i.Side}: {i.Type}")));
|
||||||
[CommandHandler("pd", HelpText = "Provides some commands for interacting with the Player Detector.")]
|
[CommandHandler("pd", HelpText = "Provides some commands for interacting with the Player Detector.")]
|
||||||
public Task<ResponseType> PlayerDetectorHandler(SocketUserMessage message, string[] parameters, CancellationToken ct)
|
public Task<ResponseType> PlayerDetectorHandler(SocketUserMessage message, string[] parameters, CancellationToken ct)
|
||||||
=> Players.HandleCommand(message, parameters, ct);
|
=> Players.HandleCommand(message, parameters, ct);
|
||||||
|
32
build.py
32
build.py
@ -2,11 +2,12 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import argparse
|
import argparse
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
import re
|
||||||
|
|
||||||
dockercmd = 'docker'
|
dockercmd = 'docker'
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Create custom recumock images.')
|
parser = argparse.ArgumentParser(description='Create custom recumock images.')
|
||||||
parser.add_argument('tags', metavar='TAG', nargs='+', help='Version tags to build.')
|
parser.add_argument('tags', metavar='TAG', nargs='*', help='Version tags to build.')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@ -15,20 +16,31 @@ platforms = ['linux/amd64', 'linux/arm64', 'linux/arm/v7']
|
|||||||
def pull(image):
|
def pull(image):
|
||||||
subprocess.run([dockercmd, 'pull', baseimage], check=True)
|
subprocess.run([dockercmd, 'pull', baseimage], check=True)
|
||||||
|
|
||||||
def build(image, directory, platforms, build_args = None):
|
def build(images, directory, platforms, build_args = None):
|
||||||
if build_args is None:
|
if build_args is None:
|
||||||
build_args = []
|
build_args = []
|
||||||
build_args = list(chain.from_iterable(['--build-arg', f'{arg}={val}'] for (arg, val) in build_args))
|
build_args = list(chain.from_iterable(['--build-arg', f'{arg}={val}'] for (arg, val) in build_args))
|
||||||
|
tags = list(chain.from_iterable(['-t', image] for image in images))
|
||||||
platformlist = ','.join(platforms)
|
platformlist = ','.join(platforms)
|
||||||
subprocess.run([dockercmd, 'buildx', 'build', '-f', 'MinecraftDiscordBot/Dockerfile', '--platform', platformlist, '-t', image] + build_args + ['--push', directory], check=True)
|
command = [dockercmd, 'buildx', 'build', '-f', 'MinecraftDiscordBot/Dockerfile', '--platform', platformlist, *tags] + build_args + ['--push', directory]
|
||||||
|
print(' '.join(command))
|
||||||
|
subprocess.run(command, check=True)
|
||||||
|
|
||||||
|
def version_from_project():
|
||||||
|
with open(r'MinecraftDiscordBot\MinecraftDiscordBot.csproj', 'r') as f:
|
||||||
|
project = f.read()
|
||||||
|
|
||||||
for tag in args.tags:
|
regex = r"<Version>\s*([^<]*?)\s*<\/Version>"
|
||||||
targetimage = f'chenio/mcdiscordbot:{tag}'
|
matches = re.search(regex, project, re.IGNORECASE)
|
||||||
baseimage = f'mcr.microsoft.com/dotnet/runtime:6.0'
|
if not matches:
|
||||||
|
raise Exception("Could not read version from project file!")
|
||||||
|
return matches.group(1)
|
||||||
|
|
||||||
#print(f'Pulling base image {baseimage}')
|
if len(args.tags) == 0:
|
||||||
#pull(baseimage)
|
args.tags.append(version_from_project())
|
||||||
print(f'Building image {targetimage} from {baseimage}.')
|
|
||||||
build(targetimage, '.', platforms, [('TAG', tag)])
|
|
||||||
|
|
||||||
|
for version in args.tags:
|
||||||
|
parts = version.split('.')
|
||||||
|
tags = list('.'.join(parts[:i]) for i in range(1, len(parts) + 1))
|
||||||
|
tags.append('latest')
|
||||||
|
build([f'chenio/mcdiscordbot:{tag}' for tag in tags], '.', platforms)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user