Switch Discord integration to use NetCord instead of Discord.Net (#38400)
This commit is contained in:
@@ -47,7 +47,7 @@ public static class ServerPackaging
|
|||||||
// Python script had Npgsql. though we want Npgsql.dll as well soooo
|
// Python script had Npgsql. though we want Npgsql.dll as well soooo
|
||||||
"Npgsql",
|
"Npgsql",
|
||||||
"Microsoft",
|
"Microsoft",
|
||||||
"Discord",
|
"NetCord",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly List<string> ServerNotExtraAssemblies = new()
|
private static readonly List<string> ServerNotExtraAssemblies = new()
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Discord.Net" />
|
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="NetCord" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />
|
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.Chat.Managers;
|
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Discord.WebSocket;
|
using NetCord.Gateway;
|
||||||
using Robust.Shared.Asynchronous;
|
using Robust.Shared.Asynchronous;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
|
||||||
@@ -59,18 +58,18 @@ public sealed class DiscordChatLink : IPostInjectInit
|
|||||||
_adminChannelId = ulong.Parse(channelId);
|
_adminChannelId = ulong.Parse(channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMessageReceived(SocketMessage message)
|
private void OnMessageReceived(Message message)
|
||||||
{
|
{
|
||||||
if (message.Author.IsBot)
|
if (message.Author.IsBot)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var contents = message.Content.ReplaceLineEndings(" ");
|
var contents = message.Content.ReplaceLineEndings(" ");
|
||||||
|
|
||||||
if (message.Channel.Id == _oocChannelId)
|
if (message.ChannelId == _oocChannelId)
|
||||||
{
|
{
|
||||||
_taskManager.RunOnMainThread(() => _chatManager.SendHookOOC(message.Author.Username, contents));
|
_taskManager.RunOnMainThread(() => _chatManager.SendHookOOC(message.Author.Username, contents));
|
||||||
}
|
}
|
||||||
else if (message.Channel.Id == _adminChannelId)
|
else if (message.ChannelId == _adminChannelId)
|
||||||
{
|
{
|
||||||
_taskManager.RunOnMainThread(() => _chatManager.SendHookAdmin(message.Author.Username, contents));
|
_taskManager.RunOnMainThread(() => _chatManager.SendHookAdmin(message.Author.Username, contents));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
using System.Linq;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Discord;
|
using NetCord;
|
||||||
using Discord.WebSocket;
|
using NetCord.Gateway;
|
||||||
|
using NetCord.Rest;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Reflection;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using LogMessage = Discord.LogMessage;
|
|
||||||
|
|
||||||
namespace Content.Server.Discord.DiscordLink;
|
namespace Content.Server.Discord.DiscordLink;
|
||||||
|
|
||||||
@@ -28,7 +25,7 @@ public sealed class CommandReceivedEventArgs
|
|||||||
/// Information about the message that the command was received from. This includes the message content, author, etc.
|
/// Information about the message that the command was received from. This includes the message content, author, etc.
|
||||||
/// Use this to reply to the message, delete it, etc.
|
/// Use this to reply to the message, delete it, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SocketMessage Message { get; init; } = default!;
|
public Message Message { get; init; } = default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,7 +42,7 @@ public sealed class DiscordLink : IPostInjectInit
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This should not be used directly outside of DiscordLink. So please do not make it public. Use the methods in this class instead.
|
/// This should not be used directly outside of DiscordLink. So please do not make it public. Use the methods in this class instead.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private DiscordSocketClient? _client;
|
private GatewayClient? _client;
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
private ISawmill _sawmillLog = default!;
|
private ISawmill _sawmillLog = default!;
|
||||||
|
|
||||||
@@ -67,7 +64,7 @@ public sealed class DiscordLink : IPostInjectInit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that is raised when a message is received from Discord. This is raised for every message, including commands.
|
/// Event that is raised when a message is received from Discord. This is raised for every message, including commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<SocketMessage>? OnMessageReceived;
|
public event Action<Message>? OnMessageReceived;
|
||||||
|
|
||||||
public void RegisterCommandCallback(Action<CommandReceivedEventArgs> callback, string command)
|
public void RegisterCommandCallback(Action<CommandReceivedEventArgs> callback, string command)
|
||||||
{
|
{
|
||||||
@@ -101,33 +98,34 @@ public sealed class DiscordLink : IPostInjectInit
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_client = new DiscordSocketClient(new DiscordSocketConfig()
|
_client = new GatewayClient(new BotToken(token), new GatewayClientConfiguration()
|
||||||
{
|
{
|
||||||
GatewayIntents = GatewayIntents.Guilds
|
Intents = GatewayIntents.Guilds
|
||||||
| GatewayIntents.GuildMembers
|
| GatewayIntents.GuildUsers
|
||||||
| GatewayIntents.GuildMessages
|
| GatewayIntents.GuildMessages
|
||||||
| GatewayIntents.MessageContent
|
| GatewayIntents.MessageContent
|
||||||
| GatewayIntents.DirectMessages,
|
| GatewayIntents.DirectMessages,
|
||||||
|
Logger = new DiscordSawmillLogger(_sawmillLog),
|
||||||
});
|
});
|
||||||
_client.Log += Log;
|
_client.MessageCreate += OnCommandReceivedInternal;
|
||||||
_client.MessageReceived += OnCommandReceivedInternal;
|
_client.MessageCreate += OnMessageReceivedInternal;
|
||||||
_client.MessageReceived += OnMessageReceivedInternal;
|
|
||||||
|
|
||||||
_botToken = token;
|
_botToken = token;
|
||||||
// Since you cannot change the token while the server is running / the DiscordLink is initialized,
|
// Since you cannot change the token while the server is running / the DiscordLink is initialized,
|
||||||
// we can just set the token without updating it every time the cvar changes.
|
// we can just set the token without updating it every time the cvar changes.
|
||||||
|
|
||||||
_client.Ready += () =>
|
_client.Ready += _ =>
|
||||||
{
|
{
|
||||||
_sawmill.Info("Discord client ready.");
|
_sawmill.Info("Discord client ready.");
|
||||||
return Task.CompletedTask;
|
return default;
|
||||||
};
|
};
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoginAsync(token);
|
await _client.StartAsync();
|
||||||
|
_sawmill.Info("Connected to Discord.");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -143,12 +141,11 @@ public sealed class DiscordLink : IPostInjectInit
|
|||||||
_sawmill.Info("Disconnecting from Discord.");
|
_sawmill.Info("Disconnecting from Discord.");
|
||||||
|
|
||||||
// Unsubscribe from the events.
|
// Unsubscribe from the events.
|
||||||
_client.MessageReceived -= OnCommandReceivedInternal;
|
_client.MessageCreate -= OnCommandReceivedInternal;
|
||||||
_client.MessageReceived -= OnMessageReceivedInternal;
|
_client.MessageCreate -= OnMessageReceivedInternal;
|
||||||
|
|
||||||
await _client.LogoutAsync();
|
await _client.CloseAsync();
|
||||||
await _client.StopAsync();
|
_client.Dispose();
|
||||||
await _client.DisposeAsync();
|
|
||||||
_client = null;
|
_client = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,45 +169,12 @@ public sealed class DiscordLink : IPostInjectInit
|
|||||||
BotPrefix = prefix;
|
BotPrefix = prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoginAsync(string token)
|
private ValueTask OnCommandReceivedInternal(Message message)
|
||||||
{
|
|
||||||
DebugTools.Assert(_client != null);
|
|
||||||
DebugTools.Assert(_client.LoginState == LoginState.LoggedOut);
|
|
||||||
|
|
||||||
await _client.LoginAsync(TokenType.Bot, token);
|
|
||||||
await _client.StartAsync();
|
|
||||||
|
|
||||||
|
|
||||||
_sawmill.Info("Connected to Discord.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FormatLog(LogMessage msg)
|
|
||||||
{
|
|
||||||
return msg.Exception is null
|
|
||||||
? $"{msg.Source}: {msg.Message}"
|
|
||||||
: $"{msg.Source}: {msg.Message}\n{msg.Exception}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Log(LogMessage msg)
|
|
||||||
{
|
|
||||||
var logLevel = msg.Severity switch
|
|
||||||
{
|
|
||||||
LogSeverity.Critical => LogLevel.Fatal,
|
|
||||||
LogSeverity.Error => LogLevel.Error,
|
|
||||||
LogSeverity.Warning => LogLevel.Warning,
|
|
||||||
_ => LogLevel.Debug
|
|
||||||
};
|
|
||||||
|
|
||||||
_sawmillLog.Log(logLevel, FormatLog(msg));
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task OnCommandReceivedInternal(SocketMessage message)
|
|
||||||
{
|
{
|
||||||
var content = message.Content;
|
var content = message.Content;
|
||||||
// If the message doesn't start with the bot prefix, ignore it.
|
// If the message doesn't start with the bot prefix, ignore it.
|
||||||
if (!content.StartsWith(BotPrefix))
|
if (!content.StartsWith(BotPrefix))
|
||||||
return Task.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
|
|
||||||
// Split the message into the command and the arguments.
|
// Split the message into the command and the arguments.
|
||||||
var trimmedInput = content[BotPrefix.Length..].Trim();
|
var trimmedInput = content[BotPrefix.Length..].Trim();
|
||||||
@@ -236,13 +200,13 @@ public sealed class DiscordLink : IPostInjectInit
|
|||||||
Arguments = arguments,
|
Arguments = arguments,
|
||||||
Message = message,
|
Message = message,
|
||||||
});
|
});
|
||||||
return Task.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task OnMessageReceivedInternal(SocketMessage message)
|
private ValueTask OnMessageReceivedInternal(Message message)
|
||||||
{
|
{
|
||||||
OnMessageReceived?.Invoke(message);
|
OnMessageReceived?.Invoke(message);
|
||||||
return Task.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Proxy methods
|
#region Proxy methods
|
||||||
@@ -257,14 +221,18 @@ public sealed class DiscordLink : IPostInjectInit
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channel = _client.GetChannel(channelId) as IMessageChannel;
|
var channel = await _client.Rest.GetChannelAsync(channelId) as TextChannel;
|
||||||
if (channel == null)
|
if (channel == null)
|
||||||
{
|
{
|
||||||
_sawmill.Error("Tried to send a message to Discord but the channel {Channel} was not found.", channel);
|
_sawmill.Error("Tried to send a message to Discord but the channel {Channel} was not found.", channel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await channel.SendMessageAsync(message, allowedMentions: AllowedMentions.None);
|
await channel.SendMessageAsync(new MessageProperties()
|
||||||
|
{
|
||||||
|
AllowedMentions = AllowedMentionsProperties.None,
|
||||||
|
Content = message,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
34
Content.Server/Discord/DiscordLink/DiscordSawmillLogger.cs
Normal file
34
Content.Server/Discord/DiscordLink/DiscordSawmillLogger.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using NetCord.Logging;
|
||||||
|
using NLogLevel = NetCord.Logging.LogLevel;
|
||||||
|
using LogLevel = Robust.Shared.Log.LogLevel;
|
||||||
|
|
||||||
|
namespace Content.Server.Discord.DiscordLink;
|
||||||
|
|
||||||
|
public sealed class DiscordSawmillLogger(ISawmill sawmill) : IGatewayLogger, IRestLogger, IVoiceLogger
|
||||||
|
{
|
||||||
|
private static LogLevel GetLogLevel(NLogLevel logLevel)
|
||||||
|
{
|
||||||
|
return logLevel switch
|
||||||
|
{
|
||||||
|
NLogLevel.Critical => LogLevel.Fatal,
|
||||||
|
NLogLevel.Error => LogLevel.Error,
|
||||||
|
NLogLevel.Warning => LogLevel.Warning,
|
||||||
|
_ => LogLevel.Debug,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void IGatewayLogger.Log<TState>(NetCord.Logging.LogLevel logLevel, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||||
|
{
|
||||||
|
sawmill.Log(GetLogLevel(logLevel), exception, formatter(state, exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRestLogger.Log<TState>(NetCord.Logging.LogLevel logLevel, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||||
|
{
|
||||||
|
sawmill.Log(GetLogLevel(logLevel), exception, formatter(state, exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IVoiceLogger.Log<TState>(NetCord.Logging.LogLevel logLevel, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||||
|
{
|
||||||
|
sawmill.Log(GetLogLevel(logLevel), exception, formatter(state, exception));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,13 +7,13 @@
|
|||||||
<PackageVersion Remove="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
<PackageVersion Remove="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||||
<PackageVersion Remove="Microsoft.EntityFrameworkCore.Design" />
|
<PackageVersion Remove="Microsoft.EntityFrameworkCore.Design" />
|
||||||
<PackageVersion Include="CsvHelper" Version="33.0.1" />
|
<PackageVersion Include="CsvHelper" Version="33.0.1" />
|
||||||
<PackageVersion Include="Discord.Net" Version="3.16.0" />
|
|
||||||
<PackageVersion Include="ImGui.NET" Version="1.87.3" />
|
<PackageVersion Include="ImGui.NET" Version="1.87.3" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageVersion>
|
</PackageVersion>
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.1" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.1" />
|
||||||
|
<PackageVersion Include="NetCord" Version="1.0.0-alpha.388" />
|
||||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" />
|
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" />
|
||||||
<PackageVersion Include="OpenTK" Version="4.7.2" />
|
<PackageVersion Include="OpenTK" Version="4.7.2" />
|
||||||
<PackageVersion Include="Veldrid" Version="4.8.0" />
|
<PackageVersion Include="Veldrid" Version="4.8.0" />
|
||||||
|
|||||||
Reference in New Issue
Block a user