Skip to content

Commit

Permalink
feature/http-interactions part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
l7ssha committed Sep 28, 2023
1 parent 30c4bad commit 64f2727
Show file tree
Hide file tree
Showing 11 changed files with 61 additions and 46 deletions.
11 changes: 1 addition & 10 deletions lib/nyxx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -192,16 +192,7 @@ export 'src/models/voice/voice_region.dart' show VoiceRegion;
export 'src/models/role.dart' show PartialRole, Role, RoleTags;
export 'src/models/gateway/gateway.dart' show GatewayBot, GatewayConfiguration, SessionStartLimit;
export 'src/models/gateway/event.dart'
show
DispatchEvent,
GatewayEvent,
HeartbeatAckEvent,
HeartbeatEvent,
HelloEvent,
InvalidSessionEvent,
RawDispatchEvent,
ReconnectEvent,
UnknownDispatchEvent;
show DispatchEvent, Event, HeartbeatAckEvent, HeartbeatEvent, HelloEvent, InvalidSessionEvent, RawDispatchEvent, ReconnectEvent, UnknownDispatchEvent;
export 'src/models/gateway/opcode.dart' show Opcode;
export 'src/models/gateway/events/application_command.dart' show ApplicationCommandPermissionsUpdateEvent;
export 'src/models/gateway/events/auto_moderation.dart'
Expand Down
6 changes: 3 additions & 3 deletions lib/src/event_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import 'package:nyxx/src/utils/iterable_extension.dart';

mixin EventRestMixin implements Nyxx {
@internal
StreamController<DispatchEvent> onEventController = StreamController.broadcast();
StreamController<Event> onEventController = StreamController.broadcast();

Stream<DispatchEvent> get onEvent => onEventController.stream;
Stream<Event> get onEvent => onEventController.stream;

/// A [Stream] of [InteractionCreateEvent]s received by this client.
Stream<InteractionCreateEvent> get onInteractionCreate => onEvent.whereType<InteractionCreateEvent>();
Expand All @@ -33,7 +33,7 @@ mixin EventRestMixin implements Nyxx {
mixin EventMixin implements Nyxx, EventRestMixin {
/// A [Stream] of gateway dispatch events received by this client.
@override
Stream<DispatchEvent> get onEvent => (this as NyxxGateway).gateway.events;
Stream<Event> get onEvent => (this as NyxxGateway).gateway.events;

/// A [Stream] of [DispatchEvent]s which are unknown to the current version of nyxx.
Stream<UnknownDispatchEvent> get onUnknownEvent => onEvent.whereType<UnknownDispatchEvent>();
Expand Down
2 changes: 1 addition & 1 deletion lib/src/gateway/event_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:nyxx/src/models/gateway/opcode.dart';
/// An internal class which allows the shard runner to parse gateway events
/// without having a reference to the client's [GatewayManager].
mixin class EventParser {
GatewayEvent parseGatewayEvent(Map<String, Object?> raw, {Duration? heartbeatLatency}) {
Event parseGatewayEvent(Map<String, Object?> raw, {Duration? heartbeatLatency}) {
final mapping = {
Opcode.dispatch.value: parseDispatch,
Opcode.heartbeat.value: parseHeartbeat,
Expand Down
16 changes: 7 additions & 9 deletions lib/src/gateway/gateway.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class Gateway extends GatewayManager with EventParser {
final StreamController<ShardMessage> _messagesController = StreamController.broadcast();

/// A stream of dispatch events received from all shards.
Stream<DispatchEvent> get events => messages.map((message) {
Stream<Event> get events => messages.map((message) {
if (message is! EventReceived) {
return null;
}
Expand Down Expand Up @@ -259,7 +259,7 @@ class Gateway extends GatewayManager with EventParser {
/// Throws an error if the shard handling events for [guildId] is not in this [Gateway] instance.
Shard shardFor(Snowflake guildId) => shards.singleWhere((shard) => shard.id == shardIdFor(guildId));

DispatchEvent parseDispatchEvent(RawDispatchEvent raw) {
Event parseDispatchEvent(RawDispatchEvent raw) {
final mapping = {
'READY': parseReady,
'RESUMED': parseResumed,
Expand Down Expand Up @@ -949,14 +949,12 @@ class Gateway extends GatewayManager with EventParser {

// Needed to get proper type promotion.
return switch (interaction.type) {
InteractionType.ping => InteractionCreateEvent<PingInteraction>(gateway: this, interaction: interaction as PingInteraction),
InteractionType.applicationCommand =>
InteractionCreateEvent<ApplicationCommandInteraction>(gateway: this, interaction: interaction as ApplicationCommandInteraction),
InteractionType.messageComponent =>
InteractionCreateEvent<MessageComponentInteraction>(gateway: this, interaction: interaction as MessageComponentInteraction),
InteractionType.modalSubmit => InteractionCreateEvent<ModalSubmitInteraction>(gateway: this, interaction: interaction as ModalSubmitInteraction),
InteractionType.ping => InteractionCreateEvent<PingInteraction>(interaction: interaction as PingInteraction),
InteractionType.applicationCommand => InteractionCreateEvent<ApplicationCommandInteraction>(interaction: interaction as ApplicationCommandInteraction),
InteractionType.messageComponent => InteractionCreateEvent<MessageComponentInteraction>(interaction: interaction as MessageComponentInteraction),
InteractionType.modalSubmit => InteractionCreateEvent<ModalSubmitInteraction>(interaction: interaction as ModalSubmitInteraction),
InteractionType.applicationCommandAutocomplete =>
InteractionCreateEvent<ApplicationCommandAutocompleteInteraction>(gateway: this, interaction: interaction as ApplicationCommandAutocompleteInteraction),
InteractionCreateEvent<ApplicationCommandAutocompleteInteraction>(interaction: interaction as ApplicationCommandAutocompleteInteraction),
} as InteractionCreateEvent<Interaction<dynamic>>;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/gateway/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ abstract class ShardMessage with ToStringHelper {}
/// A shard message sent when an event is received on the Gateway.
class EventReceived extends ShardMessage {
/// The event that was received.
final GatewayEvent event;
final Event event;

/// Create a new [EventReceived].
EventReceived({required this.event});
Expand Down
6 changes: 4 additions & 2 deletions lib/src/gateway/shard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class Shard extends Stream<ShardMessage> implements StreamSink<GatewayMessage> {
} else if (message is EventReceived) {
final event = message.event;

if (event is! RawDispatchEvent) {
if (event is GatewayEvent) {
logger.finer('Receive: ${event.opcode.name}');

switch (event) {
Expand All @@ -72,7 +72,7 @@ class Shard extends Stream<ShardMessage> implements StreamSink<GatewayMessage> {
default:
break;
}
} else {
} else if (event is RawDispatchEvent) {
logger
..fine('Receive event: ${event.name}')
..finer('Seq: ${event.seq}, Data: ${event.payload}');
Expand All @@ -82,6 +82,8 @@ class Shard extends Stream<ShardMessage> implements StreamSink<GatewayMessage> {
} else if (event.name == 'RESUMED') {
logger.info('Reconnected to Gateway');
}
} else {
logger.fine('Receive event: ${event.runtimeType}'); // TODO: Proper logging
}
}
});
Expand Down
8 changes: 4 additions & 4 deletions lib/src/gateway/shard_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,9 @@ class ShardRunner {
}
}

class ShardConnection extends Stream<GatewayEvent> implements StreamSink<Send> {
class ShardConnection extends Stream<Event> implements StreamSink<Send> {
final WebSocket websocket;
final Stream<GatewayEvent> events;
final Stream<Event> events;
final ShardRunner runner;

ShardConnection(this.websocket, this.events, this.runner);
Expand Down Expand Up @@ -271,8 +271,8 @@ class ShardConnection extends Stream<GatewayEvent> implements StreamSink<Send> {
}

@override
StreamSubscription<GatewayEvent> listen(
void Function(GatewayEvent event)? onData, {
StreamSubscription<Event> listen(
void Function(Event event)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
Expand Down
5 changes: 3 additions & 2 deletions lib/src/models/gateway/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import 'package:nyxx/src/utils/to_string_helper/to_string_helper.dart';
/// {@template gateway_event}
/// The base class for all events received from the Gateway.
/// {@endtemplate}
abstract class GatewayEvent with ToStringHelper {
abstract class Event with ToStringHelper {}

abstract class GatewayEvent extends Event {
/// The opcode of this event.
final Opcode opcode;

/// {@macro gateway_event}
GatewayEvent({required this.opcode});
}

Expand Down
4 changes: 2 additions & 2 deletions lib/src/models/gateway/events/interaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import 'package:nyxx/src/models/interaction.dart';
/// {@template interaction_create_event}
/// Emitted when an interaction is received by the client.
/// {@endtemplate}
class InteractionCreateEvent<T extends Interaction<dynamic>> extends DispatchEvent {
class InteractionCreateEvent<T extends Interaction<dynamic>> extends Event {
// The created interaction.
final T interaction;

/// {@macro interaction_create_event}
InteractionCreateEvent({required super.gateway, required this.interaction});
InteractionCreateEvent({required this.interaction});
}
45 changes: 33 additions & 12 deletions lib/src/plugin/http_interactions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,62 @@ import 'package:nyxx/nyxx.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf_io;

import 'package:pinenacl/ed25519.dart';

class HttpInteractions extends NyxxPlugin<NyxxRest> {
@override
String get name => runtimeType.toString();

final String _webhookPath;
final int _port;
final String _host;
final String _publicKey;

HttpInteractions(this._webhookPath, this._host, this._port);
HttpInteractions(this._webhookPath, this._host, this._port, this._publicKey);

@override
FutureOr<void> afterConnect(NyxxRest client) async {
var handler = const shelf.Pipeline()
.addMiddleware(shelf.logRequests())
.addHandler((shelf.Request request) => _echoRequest(request, client));
final handler = const shelf.Pipeline().addMiddleware(shelf.logRequests()).addHandler((shelf.Request request) => _echoRequest(request, client));

var server = await shelf_io.serve(handler, _host, _port);
final server = await shelf_io.serve(handler, _host, _port);
server.autoCompress = true;
}

Future<shelf.Response> _echoRequest(shelf.Request request, NyxxRest client) async {
if (!_webhookPath.startsWith(request.url.toString())) {
return shelf.Response.badRequest();
}

if (request.method != 'POST') {
return shelf.Response.badRequest();
}

final body = jsonDecode(
await request.readAsString()
) as Map<String, dynamic>;
final requestBody = await request.readAsString();

final isRequestValid = await _validateSignature(request.headers['X-Signature-Ed25519']!, request.headers['X-Signature-Timestamp']!, requestBody.trim());
if (!isRequestValid) {
return shelf.Response.unauthorized({});
}

final body = jsonDecode(requestBody) as Map<String, dynamic>;

final interaction = client.interactions.parse(body);
client.onEventController.add(InteractionCreateEvent(interaction: interaction));

if (interaction.type == InteractionType.ping) {
print("Responding to ping!");
print(jsonEncode({"type": 1}));

return shelf.Response.ok(jsonEncode({"type": 1}));
}

return shelf.Response.ok({});
}

client.onEventController.add(
InteractionCreateEvent()
)
Future<bool> _validateSignature(String signature, String timestamp, String requestBody) async {
const encoder = Base16Encoder.instance;

return shelf.Response.badRequest();
return VerifyKey(encoder.decode(_publicKey))
.verify(signature: Signature(encoder.decode(signature)), message: Uint8List.fromList(utf8.encode("$timestamp$requestBody")));
}
}
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ dependencies:
runtime_type: ^1.0.1
meta: ^1.9.1
oauth2: ^2.0.2

shelf: ^1.4.1
pinenacl: ^0.5.0

dev_dependencies:
test: ^1.22.0
Expand Down

0 comments on commit 64f2727

Please sign in to comment.