Switch Discord integration to use NetCord instead of Discord.Net (#38400)

This commit is contained in:
Simon
2025-06-17 19:03:24 +02:00
committed by GitHub
parent 22e6aef2c5
commit 9bab47ea32
6 changed files with 75 additions and 74 deletions

View File

@@ -47,7 +47,7 @@ public static class ServerPackaging
// Python script had Npgsql. though we want Npgsql.dll as well soooo
"Npgsql",
"Microsoft",
"Discord",
"NetCord",
};
private static readonly List<string> ServerNotExtraAssemblies = new()

View File

@@ -14,8 +14,8 @@
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
<PackageReference Include="NetCord" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />

View File

@@ -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.Chat;
using Discord.WebSocket;
using NetCord.Gateway;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
@@ -59,18 +58,18 @@ public sealed class DiscordChatLink : IPostInjectInit
_adminChannelId = ulong.Parse(channelId);
}
private void OnMessageReceived(SocketMessage message)
private void OnMessageReceived(Message message)
{
if (message.Author.IsBot)
return;
var contents = message.Content.ReplaceLineEndings(" ");
if (message.Channel.Id == _oocChannelId)
if (message.ChannelId == _oocChannelId)
{
_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));
}

View File

@@ -1,12 +1,9 @@
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Content.Shared.CCVar;
using Discord;
using Discord.WebSocket;
using NetCord;
using NetCord.Gateway;
using NetCord.Rest;
using Robust.Shared.Configuration;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using LogMessage = Discord.LogMessage;
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.
/// Use this to reply to the message, delete it, etc.
/// </summary>
public SocketMessage Message { get; init; } = default!;
public Message Message { get; init; } = default!;
}
/// <summary>
@@ -45,7 +42,7 @@ public sealed class DiscordLink : IPostInjectInit
/// <remarks>
/// This should not be used directly outside of DiscordLink. So please do not make it public. Use the methods in this class instead.
/// </remarks>
private DiscordSocketClient? _client;
private GatewayClient? _client;
private ISawmill _sawmill = default!;
private ISawmill _sawmillLog = default!;
@@ -67,7 +64,7 @@ public sealed class DiscordLink : IPostInjectInit
/// <summary>
/// Event that is raised when a message is received from Discord. This is raised for every message, including commands.
/// </summary>
public event Action<SocketMessage>? OnMessageReceived;
public event Action<Message>? OnMessageReceived;
public void RegisterCommandCallback(Action<CommandReceivedEventArgs> callback, string command)
{
@@ -101,33 +98,34 @@ public sealed class DiscordLink : IPostInjectInit
return;
}
_client = new DiscordSocketClient(new DiscordSocketConfig()
_client = new GatewayClient(new BotToken(token), new GatewayClientConfiguration()
{
GatewayIntents = GatewayIntents.Guilds
| GatewayIntents.GuildMembers
Intents = GatewayIntents.Guilds
| GatewayIntents.GuildUsers
| GatewayIntents.GuildMessages
| GatewayIntents.MessageContent
| GatewayIntents.DirectMessages,
Logger = new DiscordSawmillLogger(_sawmillLog),
});
_client.Log += Log;
_client.MessageReceived += OnCommandReceivedInternal;
_client.MessageReceived += OnMessageReceivedInternal;
_client.MessageCreate += OnCommandReceivedInternal;
_client.MessageCreate += OnMessageReceivedInternal;
_botToken = token;
// 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.
_client.Ready += () =>
_client.Ready += _ =>
{
_sawmill.Info("Discord client ready.");
return Task.CompletedTask;
return default;
};
Task.Run(async () =>
{
try
{
await LoginAsync(token);
await _client.StartAsync();
_sawmill.Info("Connected to Discord.");
}
catch (Exception e)
{
@@ -143,12 +141,11 @@ public sealed class DiscordLink : IPostInjectInit
_sawmill.Info("Disconnecting from Discord.");
// Unsubscribe from the events.
_client.MessageReceived -= OnCommandReceivedInternal;
_client.MessageReceived -= OnMessageReceivedInternal;
_client.MessageCreate -= OnCommandReceivedInternal;
_client.MessageCreate -= OnMessageReceivedInternal;
await _client.LogoutAsync();
await _client.StopAsync();
await _client.DisposeAsync();
await _client.CloseAsync();
_client.Dispose();
_client = null;
}
@@ -172,45 +169,12 @@ public sealed class DiscordLink : IPostInjectInit
BotPrefix = prefix;
}
private async Task LoginAsync(string token)
{
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)
private ValueTask OnCommandReceivedInternal(Message message)
{
var content = message.Content;
// If the message doesn't start with the bot prefix, ignore it.
if (!content.StartsWith(BotPrefix))
return Task.CompletedTask;
return ValueTask.CompletedTask;
// Split the message into the command and the arguments.
var trimmedInput = content[BotPrefix.Length..].Trim();
@@ -236,13 +200,13 @@ public sealed class DiscordLink : IPostInjectInit
Arguments = arguments,
Message = message,
});
return Task.CompletedTask;
return ValueTask.CompletedTask;
}
private Task OnMessageReceivedInternal(SocketMessage message)
private ValueTask OnMessageReceivedInternal(Message message)
{
OnMessageReceived?.Invoke(message);
return Task.CompletedTask;
return ValueTask.CompletedTask;
}
#region Proxy methods
@@ -257,14 +221,18 @@ public sealed class DiscordLink : IPostInjectInit
return;
}
var channel = _client.GetChannel(channelId) as IMessageChannel;
var channel = await _client.Rest.GetChannelAsync(channelId) as TextChannel;
if (channel == null)
{
_sawmill.Error("Tried to send a message to Discord but the channel {Channel} was not found.", channel);
return;
}
await channel.SendMessageAsync(message, allowedMentions: AllowedMentions.None);
await channel.SendMessageAsync(new MessageProperties()
{
AllowedMentions = AllowedMentionsProperties.None,
Content = message,
});
}
#endregion

View 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));
}
}

View File

@@ -7,16 +7,16 @@
<PackageVersion Remove="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageVersion Remove="Microsoft.EntityFrameworkCore.Design" />
<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="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<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="OpenTK" Version="4.7.2" />
<PackageVersion Include="Veldrid" Version="4.8.0" />
<PackageVersion Include="Veldrid.SPIRV" Version="1.0.15" />
</ItemGroup>
</Project>
</Project>