feat: identify, presence update commands
This commit is contained in:
parent
b3bf3a7c16
commit
6c335568f5
11 changed files with 194 additions and 68 deletions
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="DiscordProjectSettings">
|
<component name="DiscordProjectSettings">
|
||||||
<option name="show" value="ASK" />
|
<option name="show" value="PROJECT_FILES" />
|
||||||
<option name="description" value="" />
|
<option name="description" value="" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -1,53 +0,0 @@
|
||||||
using System.Text.Json;
|
|
||||||
using Foxcord.Gateway.Events.Dispatch;
|
|
||||||
using Foxcord.Rest;
|
|
||||||
using Foxcord.Rest.Types;
|
|
||||||
|
|
||||||
namespace Foxcord.Gateway;
|
|
||||||
|
|
||||||
public partial class DiscordGatewayClient
|
|
||||||
{
|
|
||||||
private IDispatch ParseDispatchEvent(string rawType, JsonElement rawPayload)
|
|
||||||
{
|
|
||||||
switch (rawType)
|
|
||||||
{
|
|
||||||
case DispatchEventTypeName.Ready:
|
|
||||||
return rawPayload.Deserialize<ReadyEvent>(_jsonSerializerOptions)!;
|
|
||||||
case DispatchEventTypeName.GuildCreate:
|
|
||||||
return rawPayload.Deserialize<GuildCreateEvent>(_jsonSerializerOptions)!;
|
|
||||||
case DispatchEventTypeName.MessageCreate:
|
|
||||||
return rawPayload.Deserialize<MessageCreateEvent>(_jsonSerializerOptions)!;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(rawType), $"Unknown dispatch event '{rawType}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleDispatch(IDispatch dispatch, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
switch (dispatch)
|
|
||||||
{
|
|
||||||
case ReadyEvent ready:
|
|
||||||
_logger.Debug("Received READY! API version: {Version}, user: {UserId}, shard: {Id}/{Total}",
|
|
||||||
ready.Version, ready.User.Id, ready.Shard.ShardId, ready.Shard.NumShards);
|
|
||||||
break;
|
|
||||||
case GuildCreateEvent guildCreate:
|
|
||||||
_logger.Debug("Received guild create for guild {Id} / {Name}", guildCreate.Id, guildCreate.Name);
|
|
||||||
break;
|
|
||||||
case MessageCreateEvent m:
|
|
||||||
_logger.Debug("Received message create from {User} in {Channel}. Content: {Content}", m.Author.Tag,
|
|
||||||
m.ChannelId, m.Content);
|
|
||||||
if (m.Content == "!ping")
|
|
||||||
{
|
|
||||||
var rest = new DiscordRestClient(_logger,
|
|
||||||
new DiscordRestClientOptions { Token = _token["Bot ".Length..] });
|
|
||||||
await rest.CreateMessageAsync(m.ChannelId,
|
|
||||||
new CreateMessageParams(Content: $"Pong! Latency: {Latency.TotalMilliseconds}ms"), ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
_logger.Debug("Received dispatch event {DispatchType}", dispatch.GetType().Name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using Foxcord.Gateway.Events.Commands;
|
||||||
|
using Foxcord.Gateway.Events.Dispatch;
|
||||||
|
using Foxcord.Rest;
|
||||||
|
using Foxcord.Rest.Types;
|
||||||
|
|
||||||
namespace Foxcord.Gateway;
|
namespace Foxcord.Gateway;
|
||||||
|
|
||||||
public partial class DiscordGatewayClient
|
public partial class DiscordGatewayClient
|
||||||
|
@ -11,7 +17,51 @@ public partial class DiscordGatewayClient
|
||||||
private async Task HandleHeartbeatRequest(CancellationToken ct = default)
|
private async Task HandleHeartbeatRequest(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
_logger.Information("Early heartbeat requested, sending heartbeat");
|
_logger.Information("Early heartbeat requested, sending heartbeat");
|
||||||
await WritePacket(new GatewayPacket { Opcode = GatewayOpcode.Heartbeat, Payload = _lastSequence }, ct);
|
await SendCommandAsync(new HeartbeatCommand(_lastSequence), ct);
|
||||||
_lastHeartbeatSend = DateTimeOffset.UtcNow;
|
_lastHeartbeatSend = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IDispatch ParseDispatchEvent(string rawType, JsonElement rawPayload)
|
||||||
|
{
|
||||||
|
switch (rawType)
|
||||||
|
{
|
||||||
|
case DispatchEventTypeName.Ready:
|
||||||
|
return rawPayload.Deserialize<ReadyEvent>(_jsonSerializerOptions)!;
|
||||||
|
case DispatchEventTypeName.GuildCreate:
|
||||||
|
return rawPayload.Deserialize<GuildCreateEvent>(_jsonSerializerOptions)!;
|
||||||
|
case DispatchEventTypeName.MessageCreate:
|
||||||
|
return rawPayload.Deserialize<MessageCreateEvent>(_jsonSerializerOptions)!;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(rawType), $"Unknown dispatch event '{rawType}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleDispatch(IDispatch dispatch, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
switch (dispatch)
|
||||||
|
{
|
||||||
|
case ReadyEvent ready:
|
||||||
|
_logger.Debug("Received READY! API version: {Version}, user: {UserId}, shard: {Id}/{Total}",
|
||||||
|
ready.Version, ready.User.Id, ready.Shard.ShardId, ready.Shard.NumShards);
|
||||||
|
break;
|
||||||
|
case GuildCreateEvent guildCreate:
|
||||||
|
_logger.Debug("Received guild create for guild {Id} / {Name}", guildCreate.Id, guildCreate.Name);
|
||||||
|
break;
|
||||||
|
case MessageCreateEvent m:
|
||||||
|
_logger.Debug("Received message create from {User} in {Channel}. Content: {Content}", m.Author.Tag,
|
||||||
|
m.ChannelId, m.Content);
|
||||||
|
if (m.Content == "!ping")
|
||||||
|
{
|
||||||
|
var rest = new DiscordRestClient(_logger,
|
||||||
|
new DiscordRestClientOptions { Token = _token["Bot ".Length..] });
|
||||||
|
await rest.CreateMessageAsync(m.ChannelId,
|
||||||
|
new CreateMessageParams(Content: $"Pong! Latency: {Latency.TotalMilliseconds}ms"), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_logger.Debug("Received dispatch event {DispatchType}", dispatch.GetType().Name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Foxcord.Gateway.Events;
|
using Foxcord.Gateway.Events;
|
||||||
|
using Foxcord.Gateway.Events.Commands;
|
||||||
using Foxcord.Serialization;
|
using Foxcord.Serialization;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
@ -18,9 +19,12 @@ public partial class DiscordGatewayClient
|
||||||
private readonly string _token;
|
private readonly string _token;
|
||||||
private readonly Uri _gatewayUri;
|
private readonly Uri _gatewayUri;
|
||||||
private readonly GatewayIntent _intents;
|
private readonly GatewayIntent _intents;
|
||||||
|
private readonly IdentifyProperties? _properties;
|
||||||
|
private readonly int[]? _shardInfo;
|
||||||
|
private readonly PresenceUpdateCommand? _initialPresence;
|
||||||
private readonly JsonSerializerOptions _jsonSerializerOptions = JsonSerializerExtensions.CreateSerializer();
|
private readonly JsonSerializerOptions _jsonSerializerOptions = JsonSerializerExtensions.CreateSerializer();
|
||||||
|
|
||||||
private long? _lastSequence = null;
|
private long? _lastSequence;
|
||||||
private DateTimeOffset _lastHeartbeatSend = DateTimeOffset.UnixEpoch;
|
private DateTimeOffset _lastHeartbeatSend = DateTimeOffset.UnixEpoch;
|
||||||
private DateTimeOffset _lastHeartbeatAck = DateTimeOffset.UnixEpoch;
|
private DateTimeOffset _lastHeartbeatAck = DateTimeOffset.UnixEpoch;
|
||||||
public TimeSpan Latency => _lastHeartbeatAck - _lastHeartbeatSend;
|
public TimeSpan Latency => _lastHeartbeatAck - _lastHeartbeatSend;
|
||||||
|
@ -35,6 +39,9 @@ public partial class DiscordGatewayClient
|
||||||
};
|
};
|
||||||
_gatewayUri = uriBuilder.Uri;
|
_gatewayUri = uriBuilder.Uri;
|
||||||
_intents = opts.Intents;
|
_intents = opts.Intents;
|
||||||
|
_properties = opts.IdentifyProperties;
|
||||||
|
_shardInfo = opts.Shards;
|
||||||
|
_initialPresence = opts.InitialPresence;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectionStatus Status { get; private set; } = ConnectionStatus.Dead;
|
public ConnectionStatus Status { get; private set; } = ConnectionStatus.Dead;
|
||||||
|
@ -44,7 +51,6 @@ public partial class DiscordGatewayClient
|
||||||
/// <c>ct</c> is stored by the client and cancelling it will close the connection.
|
/// <c>ct</c> is stored by the client and cancelling it will close the connection.
|
||||||
/// The caller must pause indefinitely or the bot will shut down immediately.
|
/// The caller must pause indefinitely or the bot will shut down immediately.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemberNotNull("_ws")]
|
|
||||||
public async Task ConnectAsync(CancellationToken ct = default)
|
public async Task ConnectAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (Status != ConnectionStatus.Dead)
|
if (Status != ConnectionStatus.Dead)
|
||||||
|
@ -59,7 +65,7 @@ public partial class DiscordGatewayClient
|
||||||
|
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
cts.CancelAfter(TimeSpan.FromSeconds(30));
|
cts.CancelAfter(TimeSpan.FromSeconds(30));
|
||||||
var (rawHelloPacketType, rawHelloPacket) = await ReadPacket(cts.Token);
|
var (rawHelloPacketType, rawHelloPacket) = await ReadPacketAsync(cts.Token);
|
||||||
if (rawHelloPacketType == WebSocketMessageType.Close)
|
if (rawHelloPacketType == WebSocketMessageType.Close)
|
||||||
throw new DiscordGatewayRequestError("First packet received was a close message");
|
throw new DiscordGatewayRequestError("First packet received was a close message");
|
||||||
|
|
||||||
|
@ -73,10 +79,17 @@ public partial class DiscordGatewayClient
|
||||||
var __ = ReceiveLoopAsync(ct);
|
var __ = ReceiveLoopAsync(ct);
|
||||||
|
|
||||||
_logger.Debug("Sending IDENTIFY");
|
_logger.Debug("Sending IDENTIFY");
|
||||||
await WritePacket(new GatewayPacket
|
await WritePacketAsync(new GatewayPacket
|
||||||
{
|
{
|
||||||
Opcode = GatewayOpcode.Identify,
|
Opcode = GatewayOpcode.Identify,
|
||||||
Payload = new IdentifyEvent { Token = _token, Intents = _intents }
|
Payload = new IdentifyEvent
|
||||||
|
{
|
||||||
|
Token = _token,
|
||||||
|
Intents = _intents,
|
||||||
|
Properties = _properties ?? new IdentifyProperties(),
|
||||||
|
Shards = _shardInfo,
|
||||||
|
Presence = _initialPresence?.ToPayload()
|
||||||
|
}
|
||||||
}, ct);
|
}, ct);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -98,7 +111,7 @@ public partial class DiscordGatewayClient
|
||||||
{
|
{
|
||||||
_logger.Debug("Sending heartbeat with sequence {Sequence}", _lastSequence);
|
_logger.Debug("Sending heartbeat with sequence {Sequence}", _lastSequence);
|
||||||
_lastHeartbeatSend = DateTimeOffset.UtcNow;
|
_lastHeartbeatSend = DateTimeOffset.UtcNow;
|
||||||
await WritePacket(new GatewayPacket { Opcode = GatewayOpcode.Heartbeat, Payload = _lastSequence }, ct);
|
await SendCommandAsync(new HeartbeatCommand(_lastSequence), ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +121,7 @@ public partial class DiscordGatewayClient
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var (type, packet) = await ReadPacket(ct);
|
var (type, packet) = await ReadPacketAsync(ct);
|
||||||
if (type == WebSocketMessageType.Close || packet == null)
|
if (type == WebSocketMessageType.Close || packet == null)
|
||||||
{
|
{
|
||||||
// TODO: close websocket
|
// TODO: close websocket
|
||||||
|
@ -146,14 +159,17 @@ public partial class DiscordGatewayClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask WritePacket(GatewayPacket packet, CancellationToken ct = default)
|
public async ValueTask SendCommandAsync(IGatewayCommand command, CancellationToken ct = default) =>
|
||||||
|
await WritePacketAsync(command.ToGatewayPacket(), ct);
|
||||||
|
|
||||||
|
private async ValueTask WritePacketAsync(GatewayPacket packet, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
using var buf = MemoryPool<byte>.Shared.Rent(BufferSize);
|
using var buf = MemoryPool<byte>.Shared.Rent(BufferSize);
|
||||||
var json = JsonSerializer.SerializeToUtf8Bytes(packet, _jsonSerializerOptions);
|
var json = JsonSerializer.SerializeToUtf8Bytes(packet, _jsonSerializerOptions);
|
||||||
await _ws!.SendAsync(json.AsMemory(), WebSocketMessageType.Text, true, ct);
|
await _ws!.SendAsync(json.AsMemory(), WebSocketMessageType.Text, true, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<(WebSocketMessageType type, GatewayPacket? packet)> ReadPacket(
|
private async ValueTask<(WebSocketMessageType type, GatewayPacket? packet)> ReadPacketAsync(
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
using var buf = MemoryPool<byte>.Shared.Rent(BufferSize);
|
using var buf = MemoryPool<byte>.Shared.Rent(BufferSize);
|
||||||
|
@ -163,10 +179,10 @@ public partial class DiscordGatewayClient
|
||||||
if (res.EndOfMessage)
|
if (res.EndOfMessage)
|
||||||
return DeserializePacket(res, buf.Memory.Span[..res.Count]);
|
return DeserializePacket(res, buf.Memory.Span[..res.Count]);
|
||||||
|
|
||||||
return await DeserializeMultipleBuffer(res, buf);
|
return await DeserializeMultipleBufferAsync(res, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(WebSocketMessageType type, GatewayPacket packet)> DeserializeMultipleBuffer(
|
private async Task<(WebSocketMessageType type, GatewayPacket packet)> DeserializeMultipleBufferAsync(
|
||||||
ValueWebSocketReceiveResult res, IMemoryOwner<byte> buf)
|
ValueWebSocketReceiveResult res, IMemoryOwner<byte> buf)
|
||||||
{
|
{
|
||||||
await using var stream = new MemoryStream(BufferSize * 4);
|
await using var stream = new MemoryStream(BufferSize * 4);
|
||||||
|
@ -228,4 +244,10 @@ public class DiscordGatewayClientOptions
|
||||||
public required string Token { get; init; }
|
public required string Token { get; init; }
|
||||||
public required string Uri { get; init; }
|
public required string Uri { get; init; }
|
||||||
public required GatewayIntent Intents { get; init; }
|
public required GatewayIntent Intents { get; init; }
|
||||||
|
public IdentifyProperties? IdentifyProperties { get; init; }
|
||||||
|
public int? ShardId { get; init; }
|
||||||
|
public int? ShardCount { get; init; }
|
||||||
|
public PresenceUpdateCommand? InitialPresence { get; init; }
|
||||||
|
|
||||||
|
internal int[]? Shards => ShardId != null && ShardCount != null ? [ShardId.Value, ShardCount.Value] : null;
|
||||||
}
|
}
|
13
Foxcord/Gateway/Events/Commands/HeartbeatCommand.cs
Normal file
13
Foxcord/Gateway/Events/Commands/HeartbeatCommand.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
namespace Foxcord.Gateway.Events.Commands;
|
||||||
|
|
||||||
|
public record HeartbeatCommand(long? Sequence) : IGatewayCommand
|
||||||
|
{
|
||||||
|
public GatewayPacket ToGatewayPacket()
|
||||||
|
{
|
||||||
|
return new GatewayPacket
|
||||||
|
{
|
||||||
|
Opcode = GatewayOpcode.Heartbeat,
|
||||||
|
Payload = Sequence
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
6
Foxcord/Gateway/Events/Commands/IGatewayCommand.cs
Normal file
6
Foxcord/Gateway/Events/Commands/IGatewayCommand.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Foxcord.Gateway.Events.Commands;
|
||||||
|
|
||||||
|
public interface IGatewayCommand
|
||||||
|
{
|
||||||
|
public GatewayPacket ToGatewayPacket();
|
||||||
|
}
|
46
Foxcord/Gateway/Events/Commands/PresenceUpdateCommand.cs
Normal file
46
Foxcord/Gateway/Events/Commands/PresenceUpdateCommand.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
using Foxcord.Models;
|
||||||
|
|
||||||
|
namespace Foxcord.Gateway.Events.Commands;
|
||||||
|
|
||||||
|
public class PresenceUpdateCommand : IGatewayCommand
|
||||||
|
{
|
||||||
|
public long? Since { get; init; }
|
||||||
|
public Activity[] Activities { get; init; } = [];
|
||||||
|
public PresenceStatusType Status { get; init; }
|
||||||
|
public bool Afk { get; init; }
|
||||||
|
|
||||||
|
public GatewayPacket ToGatewayPacket()
|
||||||
|
{
|
||||||
|
return new GatewayPacket
|
||||||
|
{
|
||||||
|
Opcode = GatewayOpcode.PresenceUpdate,
|
||||||
|
Payload = ToPayload()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public PresenceUpdatePayload ToPayload()
|
||||||
|
{
|
||||||
|
var status = Status switch
|
||||||
|
{
|
||||||
|
PresenceStatusType.Online => "online",
|
||||||
|
PresenceStatusType.Dnd => "dnd",
|
||||||
|
PresenceStatusType.Idle => "idle",
|
||||||
|
PresenceStatusType.Invisible => "invisible",
|
||||||
|
PresenceStatusType.Offline => "offline",
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
|
||||||
|
return new PresenceUpdatePayload(Since, Activities, status, Afk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PresenceUpdatePayload(long? Since, Activity[] Activities, string Status, bool Afk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PresenceStatusType
|
||||||
|
{
|
||||||
|
Online,
|
||||||
|
Dnd,
|
||||||
|
Idle,
|
||||||
|
Invisible,
|
||||||
|
Offline
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Foxcord.Gateway.Events.Commands;
|
||||||
|
|
||||||
namespace Foxcord.Gateway.Events;
|
namespace Foxcord.Gateway.Events;
|
||||||
|
|
||||||
public class IdentifyEvent : IGatewayEvent
|
public class IdentifyEvent : IGatewayEvent
|
||||||
|
@ -5,6 +8,10 @@ public class IdentifyEvent : IGatewayEvent
|
||||||
public required string Token { get; init; }
|
public required string Token { get; init; }
|
||||||
public IdentifyProperties Properties { get; init; } = new();
|
public IdentifyProperties Properties { get; init; } = new();
|
||||||
public GatewayIntent Intents { get; init; }
|
public GatewayIntent Intents { get; init; }
|
||||||
|
public PresenceUpdateCommand.PresenceUpdatePayload? Presence { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int[]? Shards { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class IdentifyProperties
|
public class IdentifyProperties
|
||||||
|
|
24
Foxcord/Models/Activity.cs
Normal file
24
Foxcord/Models/Activity.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
namespace Foxcord.Models;
|
||||||
|
|
||||||
|
public record Activity(
|
||||||
|
string Name,
|
||||||
|
ActivityType Type,
|
||||||
|
string? Url = null,
|
||||||
|
DateTimeOffset CreatedAt = default,
|
||||||
|
ActivityTimestamps? Timestamps = null,
|
||||||
|
ActivityEmoji? Emoji = null,
|
||||||
|
string? State = null);
|
||||||
|
|
||||||
|
public record ActivityTimestamps(int? Start = null, int? End = null);
|
||||||
|
|
||||||
|
public record ActivityEmoji(string Name, Snowflake? Id = null, bool? Animated = null);
|
||||||
|
|
||||||
|
public enum ActivityType
|
||||||
|
{
|
||||||
|
Playing = 0,
|
||||||
|
Streaming = 1,
|
||||||
|
Listening = 2,
|
||||||
|
Watching = 3,
|
||||||
|
Custom = 4,
|
||||||
|
Competing = 5,
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Foxcord.Models;
|
namespace Foxcord.Models;
|
||||||
|
|
||||||
public class User
|
public record User
|
||||||
{
|
{
|
||||||
public Snowflake Id { get; init; }
|
public Snowflake Id { get; init; }
|
||||||
public required string Username { get; init; }
|
public required string Username { get; init; }
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
using Foxcord.Gateway;
|
using Foxcord.Gateway;
|
||||||
|
using Foxcord.Gateway.Events;
|
||||||
|
using Foxcord.Gateway.Events.Commands;
|
||||||
|
using Foxcord.Models;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Foxcord.Rest;
|
using Foxcord.Rest;
|
||||||
|
|
||||||
|
@ -18,7 +21,15 @@ var gateway = new DiscordGatewayClient(Log.Logger, new DiscordGatewayClientOptio
|
||||||
{
|
{
|
||||||
Token = Environment.GetEnvironmentVariable("TOKEN")!,
|
Token = Environment.GetEnvironmentVariable("TOKEN")!,
|
||||||
Uri = gatewayBot.Url,
|
Uri = gatewayBot.Url,
|
||||||
Intents = GatewayIntent.Guilds | GatewayIntent.GuildMessages | GatewayIntent.MessageContent
|
Intents = GatewayIntent.Guilds | GatewayIntent.GuildMessages | GatewayIntent.MessageContent,
|
||||||
|
InitialPresence = new PresenceUpdateCommand
|
||||||
|
{
|
||||||
|
Activities = [new Activity("balls", ActivityType.Custom, State: "gay gay homosexual gay")]
|
||||||
|
},
|
||||||
|
IdentifyProperties = new IdentifyProperties
|
||||||
|
{
|
||||||
|
Browser = "Discord iOS"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await gateway.ConnectAsync();
|
await gateway.ConnectAsync();
|
||||||
|
|
Loading…
Reference in a new issue