diff --git a/vrchat_dart/CHANGELOG.md b/vrchat_dart/CHANGELOG.md index 00d1a60e..aa381629 100644 --- a/vrchat_dart/CHANGELOG.md +++ b/vrchat_dart/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.0.0 + +- BREAKING: Websocket messages are the generated models from `vrchat_dart_generated` 1.23.0. `VrcStreamingEvent` and the hand-written streaming models are removed. `vrcEventStream` emits the model for each `type`, or `VrcStreamingError` for a pipeline error frame. +- Updates `vrchat_dart_generated` to 1.23.0 + ## 4.1.0 - Updates `vrchat_dart_generated` to 1.22.0 diff --git a/vrchat_dart/example/bin/main.dart b/vrchat_dart/example/bin/main.dart index 0430979a..8c1602c4 100644 --- a/vrchat_dart/example/bin/main.dart +++ b/vrchat_dart/example/bin/main.dart @@ -140,32 +140,21 @@ void main() async { api.streaming.start(); } -void handleVrcEvent(VrcStreamingEvent event) { - final String message; - switch (event.type) { - case VrcStreamingEventType.error: - case VrcStreamingEventType.friendOnline: - case VrcStreamingEventType.friendOffline: - case VrcStreamingEventType.friendActive: - case VrcStreamingEventType.friendAdd: - case VrcStreamingEventType.friendDelete: - case VrcStreamingEventType.friendUpdate: - case VrcStreamingEventType.friendLocation: - case VrcStreamingEventType.userUpdate: - case VrcStreamingEventType.userLocation: - case VrcStreamingEventType.notificationReceived: - case VrcStreamingEventType.notificationResponse: - case VrcStreamingEventType.notificationHide: - message = '${event.type.name}: ${jsonEncode(event)}'; - case VrcStreamingEventType.notificationSeen: - event as NotificationSeenEvent; - message = 'NotificationSeen: ${event.notificationId}'; - case VrcStreamingEventType.notificationClear: - message = 'NotificationClear'; - case VrcStreamingEventType.unknown: - event as UnknownEvent; - message = 'Unknown [VrcStreamingEvent] received: ${event.rawString}'; +void handleVrcEvent(Object event) { + switch (event) { + case VrcStreamingError(:final message): + print('Websocket error: $message'); + case WebsocketMessageUnknown(:final type): + print('Unknown websocket message: $type'); + case WebsocketFriendOnline(:final userId, :final location): + print('Friend online: $userId at $location'); + case WebsocketSeeNotification(:final content): + print('Notification seen: $content'); + case WebsocketHideNotification(:final content): + print('Notification hidden: $content'); + case WebsocketClearNotification(): + print('Notifications cleared'); + default: + print(event); } - - print(message); } diff --git a/vrchat_dart/lib/src/model/streaming/serializers.dart b/vrchat_dart/lib/src/model/streaming/serializers.dart deleted file mode 100644 index 1561bcd7..00000000 --- a/vrchat_dart/lib/src/model/streaming/serializers.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:vrchat_dart/vrchat_dart.dart'; - -/// Convert a world object from the streaming service -class StreamedWorldConverter - extends JsonConverter?> { - /// Constructor - const StreamedWorldConverter(); - - @override - World? fromJson(Map? json) { - if (json == null || json.isEmpty) { - return null; - } - - return World.fromJson(json); - } - - @override - Map? toJson(World? object) => object?.toJson(); -} diff --git a/vrchat_dart/lib/src/model/streaming/streamed_current_user.dart b/vrchat_dart/lib/src/model/streaming/streamed_current_user.dart deleted file mode 100644 index 7cde9404..00000000 --- a/vrchat_dart/lib/src/model/streaming/streamed_current_user.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:vrchat_dart_generated/vrchat_dart_generated.dart'; -import 'package:meta/meta.dart'; - -part 'streamed_current_user.g.dart'; - -/// A user object representing the signed in user streamed from the VRChat websocket connection -@immutable -@JsonSerializable() -class StreamedCurrentUser { - /// The user's bio - final String? bio; - - /// The user's avatar id - final String? currentAvatar; - - /// The user's avatar image url - final String currentAvatarImageUrl; - - /// The user's avatar thumbnail image url - final String currentAvatarThumbnailImageUrl; - - /// The user's display name - final String displayName; - - /// The user's fallbackAvatar - final String? fallbackAvatar; - - /// The user's id - final String id; - - /// The user's icon URL - final String? iconUrl; - - /// The user's status - final UserStatus status; - - /// The user's status description - final String statusDescription; - - /// The user's tags - final List tags; - - /// The user's username - final String? username; - - /// Create a [StreamedCurrentUser] - const StreamedCurrentUser({ - this.bio, - this.currentAvatar, - required this.currentAvatarImageUrl, - required this.currentAvatarThumbnailImageUrl, - required this.displayName, - this.fallbackAvatar, - required this.id, - this.iconUrl, - required this.status, - required this.statusDescription, - required this.tags, - this.username, - }); - - /// Create a [StreamedCurrentUser] from json - factory StreamedCurrentUser.fromJson(Map json) => - _$StreamedCurrentUserFromJson(json); - - /// Convert a [StreamedCurrentUser] to json - Map toJson() => _$StreamedCurrentUserToJson(this); -} diff --git a/vrchat_dart/lib/src/model/streaming/streamed_current_user.g.dart b/vrchat_dart/lib/src/model/streaming/streamed_current_user.g.dart deleted file mode 100644 index 052ec308..00000000 --- a/vrchat_dart/lib/src/model/streaming/streamed_current_user.g.dart +++ /dev/null @@ -1,51 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ignore_for_file: document_ignores, require_trailing_commas, rexios_lints/not_null_assertion - -part of 'streamed_current_user.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -StreamedCurrentUser _$StreamedCurrentUserFromJson(Map json) => - StreamedCurrentUser( - bio: json['bio'] as String?, - currentAvatar: json['currentAvatar'] as String?, - currentAvatarImageUrl: json['currentAvatarImageUrl'] as String, - currentAvatarThumbnailImageUrl: - json['currentAvatarThumbnailImageUrl'] as String, - displayName: json['displayName'] as String, - fallbackAvatar: json['fallbackAvatar'] as String?, - id: json['id'] as String, - iconUrl: json['iconUrl'] as String?, - status: $enumDecode(_$UserStatusEnumMap, json['status']), - statusDescription: json['statusDescription'] as String, - tags: (json['tags'] as List).map((e) => e as String).toList(), - username: json['username'] as String?, - ); - -Map _$StreamedCurrentUserToJson( - StreamedCurrentUser instance, -) => { - 'bio': instance.bio, - 'currentAvatar': instance.currentAvatar, - 'currentAvatarImageUrl': instance.currentAvatarImageUrl, - 'currentAvatarThumbnailImageUrl': instance.currentAvatarThumbnailImageUrl, - 'displayName': instance.displayName, - 'fallbackAvatar': instance.fallbackAvatar, - 'id': instance.id, - 'iconUrl': instance.iconUrl, - 'status': _$UserStatusEnumMap[instance.status]!, - 'statusDescription': instance.statusDescription, - 'tags': instance.tags, - 'username': instance.username, -}; - -const _$UserStatusEnumMap = { - UserStatus.active: 'active', - UserStatus.joinMe: 'join me', - UserStatus.askMe: 'ask me', - UserStatus.busy: 'busy', - UserStatus.offline: 'offline', -}; diff --git a/vrchat_dart/lib/src/model/streaming/vrc_streaming_event.dart b/vrchat_dart/lib/src/model/streaming/vrc_streaming_event.dart deleted file mode 100644 index e49af507..00000000 --- a/vrchat_dart/lib/src/model/streaming/vrc_streaming_event.dart +++ /dev/null @@ -1,483 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:vrchat_dart/src/model/streaming/serializers.dart'; -import 'package:vrchat_dart_generated/vrchat_dart_generated.dart'; -import 'package:vrchat_dart/src/model/streaming/streamed_current_user.dart'; -import 'package:meta/meta.dart'; - -part 'vrc_streaming_event.g.dart'; - -/// Enum of all possible types of [VrcStreamingEvent] -enum VrcStreamingEventType { - /// [UnknownEvent] type - unknown, - - /// [ErrorEvent] type - error, - - /// [FriendOnlineEvent] type - friendOnline, - - /// [FriendOfflineEvent] type - friendOffline, - - /// [FriendActiveEvent] type - friendActive, - - /// [FriendAddEvent] type - friendAdd, - - /// [FriendDeleteEvent] type - friendDelete, - - /// [FriendUpdateEvent] type - friendUpdate, - - /// [FriendLocationEvent] type - friendLocation, - - /// [UserUpdateEvent] type - userUpdate, - - /// [UserLocationEvent] type - userLocation, - - /// [NotificationReceivedEvent] type - notificationReceived, - - /// [NotificationSeenEvent] type - notificationSeen, - - /// [NotificationResponseEvent] type - notificationResponse, - - /// [NotificationHideEvent] type - notificationHide, - - /// [NotificationClearEvent] type - notificationClear, -} - -/// Convenience methods to call on [VrcStreamingEventType]s -extension VrcStreamingEventTypeExtension on VrcStreamingEventType { - /// Create a [VrcStreamingEventType] from a type string received from VRChat - static VrcStreamingEventType fromTypeString(String typeString) { - switch (typeString) { - case 'friend-online': - return VrcStreamingEventType.friendOnline; - case 'friend-offline': - return VrcStreamingEventType.friendOffline; - case 'friend-active': - return VrcStreamingEventType.friendActive; - case 'friend-add': - return VrcStreamingEventType.friendAdd; - case 'friend-delete': - return VrcStreamingEventType.friendDelete; - case 'friend-update': - return VrcStreamingEventType.friendUpdate; - case 'friend-location': - return VrcStreamingEventType.friendLocation; - case 'user-update': - return VrcStreamingEventType.userUpdate; - case 'user-location': - return VrcStreamingEventType.userLocation; - case 'notification': - return VrcStreamingEventType.notificationReceived; - case 'see-notification': - return VrcStreamingEventType.notificationSeen; - case 'response-notification': - return VrcStreamingEventType.notificationResponse; - case 'hide-notification': - return VrcStreamingEventType.notificationHide; - case 'clear-notification': - return VrcStreamingEventType.notificationClear; - default: - return VrcStreamingEventType.unknown; - } - } -} - -/// Base class for [VrcStreamingEvent]s -@immutable -abstract class VrcStreamingEvent { - /// The type of [VrcStreamingEvent] received - VrcStreamingEventType get type; - - /// Constructor - const VrcStreamingEvent(); -} - -/// These shouldn't happen unless VRChat adds more events -/// -/// If you end up getting [UnknownEvent]s please create an issue on GitHub -/// -class UnknownEvent extends VrcStreamingEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.unknown; - - /// The raw response from the api - final String rawString; - - /// Create an [UnknownEvent] with the given [rawString] - const UnknownEvent({required this.rawString}); -} - -/// An error message returned from the server -class ErrorEvent extends VrcStreamingEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.error; - - /// Create an [ErrorEvent] with the given [message] - const ErrorEvent({required this.message}); - - /// The error message - final String message; -} - -/// Base class for [FriendEvent]s -abstract class FriendEvent extends VrcStreamingEvent { - /// The [userId] of the user this event is about - final String userId; - - /// Create a [FriendEvent] with the given [userId] - const FriendEvent({required this.userId}); -} - -/// Base class for [FriendEvent]s that contain a user object -abstract class FriendEventWithUser extends FriendEvent { - /// The [user] object of the user this event is about - final User user; - - /// Create a [FriendEventWithUser] with the given [userId] and [user] - const FriendEventWithUser({required super.userId, required this.user}); -} - -/// Base class for [UserEvent]s -abstract class UserEvent extends VrcStreamingEvent { - /// The [userId] of the user this event is about - final String userId; - - /// Create a [UserEvent] with the given [userId] - const UserEvent({required this.userId}); -} - -/// Base class for [NotificationEvent]s -abstract class NotificationEvent extends VrcStreamingEvent {} - -/// A [FriendOnlineEvent] is sent when one of the user’s friends has gone -/// online in-game. Note that the [world] field will be an empty object when the -/// friend is on orange/red, or is in a private world. -@JsonSerializable() -class FriendOnlineEvent extends FriendEventWithUser { - @override - VrcStreamingEventType get type => VrcStreamingEventType.friendOnline; - - /// The [world] the user joined - @StreamedWorldConverter() - final World? world; - - /// `worldId:locationId` - final String? location; - - /// `locationId` - final String? instance; - - /// If the current user can request invite on this friend - final bool canRequestInvite; - - /// Create a [FriendOnlineEvent] - const FriendOnlineEvent({ - required super.userId, - required super.user, - required this.world, - required this.location, - required this.instance, - required this.canRequestInvite, - }); - - /// Create a [FriendOnlineEvent] from json - factory FriendOnlineEvent.fromJson(Map json) => - _$FriendOnlineEventFromJson(json); - - /// Convert a [FriendOnlineEvent] to json - Map toJson() => _$FriendOnlineEventToJson(this); -} - -/// A [FriendOfflineEvent] is sent when one of the user’s friends has gone -/// offline. -@JsonSerializable() -class FriendOfflineEvent extends FriendEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.friendOffline; - - /// Create a [FriendOnlineEvent] with the given [userId] - const FriendOfflineEvent({required super.userId}); - - /// Create a [FriendOfflineEvent] from json - factory FriendOfflineEvent.fromJson(Map json) => - _$FriendOfflineEventFromJson(json); - - /// Convert a [FriendOfflineEvent] to json - Map toJson() => _$FriendOfflineEventToJson(this); -} - -/// A [FriendActiveEvent] is sent when one of the user’s friends is active on -/// the website. -@JsonSerializable() -class FriendActiveEvent extends FriendEventWithUser { - @override - VrcStreamingEventType get type => VrcStreamingEventType.friendActive; - - /// Create a [FriendActiveEvent] with the given [userId] and [user] - const FriendActiveEvent({required super.userId, required super.user}); - - /// Create a [FriendActiveEvent] from json - factory FriendActiveEvent.fromJson(Map json) => - _$FriendActiveEventFromJson(json); - - /// Convert a [FriendActiveEvent] to json - Map toJson() => _$FriendActiveEventToJson(this); -} - -/// A [FriendAddEvent] is sent when the user has either accepted a friend -/// request, or has had one of their friend requests accepted. -@JsonSerializable() -class FriendAddEvent extends FriendEventWithUser { - @override - VrcStreamingEventType get type => VrcStreamingEventType.friendAdd; - - /// Create a [FriendAddEvent] with the given [userId] and [user] - const FriendAddEvent({required super.userId, required super.user}); - - /// Create a [FriendAddEvent] from json - factory FriendAddEvent.fromJson(Map json) => - _$FriendAddEventFromJson(json); - - /// Convert a [FriendAddEvent] to json - Map toJson() => _$FriendAddEventToJson(this); -} - -/// A [FriendDeleteEvent] is sent when the user has either been removed as -/// a friend, or has removed someone else as a friend. -@JsonSerializable() -class FriendDeleteEvent extends FriendEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.friendDelete; - - /// Create a [FriendDeleteEvent] with the given [userId] - const FriendDeleteEvent({required super.userId}); - - /// Create a [FriendDeleteEvent] from json - factory FriendDeleteEvent.fromJson(Map json) => - _$FriendDeleteEventFromJson(json); - - /// Convert a [FriendDeleteEvent] to json - Map toJson() => _$FriendDeleteEventToJson(this); -} - -/// A [FriendUpdateEvent] is sent when something about one of the user’s -/// friends profile has changed. -@JsonSerializable() -class FriendUpdateEvent extends FriendEventWithUser { - @override - VrcStreamingEventType get type => VrcStreamingEventType.friendUpdate; - - /// Create a [FriendUpdateEvent] with the given [userId] and [user] - const FriendUpdateEvent({required super.userId, required super.user}); - - /// Create a [FriendUpdateEvent] from json - factory FriendUpdateEvent.fromJson(Map json) => - _$FriendUpdateEventFromJson(json); - - /// Convert a [FriendUpdateEvent] to json - Map toJson() => _$FriendUpdateEventToJson(this); -} - -/// A [FriendLocationEvent] is sent when one of the user’s friends has changed -/// instances. Note that the [world] field will be an empty object when the -/// friend is on orange/red, or is in a private world. -@JsonSerializable() -class FriendLocationEvent extends FriendEventWithUser { - @override - VrcStreamingEventType get type => VrcStreamingEventType.friendLocation; - - /// The [world] the user joined - @StreamedWorldConverter() - final World? world; - - /// `worldId:locationId` - final String? location; - - /// `locationId` - final String? instance; - - /// If the current user can request invite on this friend - final bool canRequestInvite; - - /// Create a [FriendLocationEvent] - const FriendLocationEvent({ - required super.userId, - required super.user, - required this.world, - required this.location, - required this.instance, - required this.canRequestInvite, - }); - - /// Create a [FriendLocationEvent] from json - factory FriendLocationEvent.fromJson(Map json) => - _$FriendLocationEventFromJson(json); - - /// Convert a [FriendLocationEvent] to json - Map toJson() => _$FriendLocationEventToJson(this); -} - -/// A [UserUpdateEvent] is sent when something regarding the user has been -/// updated. Note that the [user] object is not a [LimitedUser] object, even -/// though it has similarities. It’s missing [developerType], [friendKey], -/// [isFriend], [lastPlatform] and [location]. It also has an extra -/// [currentAvatar] field. -@JsonSerializable() -class UserUpdateEvent extends UserEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.userUpdate; - - /// The [user] object - final StreamedCurrentUser user; - - /// Create a [UserUpdateEvent] with the given [userId] and [user] - const UserUpdateEvent({required super.userId, required this.user}); - - /// Create a [UserUpdateEvent] from json - factory UserUpdateEvent.fromJson(Map json) => - _$UserUpdateEventFromJson(json); - - /// Convert a [UserUpdateEvent] to json - Map toJson() => _$UserUpdateEventToJson(this); -} - -/// A [UserLocationEvent] is sent when the user has changed instances. -@JsonSerializable() -class UserLocationEvent extends UserEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.userLocation; - - /// The [world] the user joined - final World? world; - - /// `worldId:locationId` - final String location; - - /// `locationId` - final String instance; - - /// Create a [UserLocationEvent] - const UserLocationEvent({ - required super.userId, - required this.world, - required this.location, - required this.instance, - }); - - /// Create a [UserLocationEvent] from json - factory UserLocationEvent.fromJson(Map json) => - _$UserLocationEventFromJson(json); - - /// Convert a [UserLocationEvent] to json - Map toJson() => _$UserLocationEventToJson(this); -} - -/// A [NotificationReceivedEvent] carries a Notification object, and are -/// used by Invites, Friend Requests and other in-game notifications. -@JsonSerializable() -class NotificationReceivedEvent extends NotificationEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.notificationReceived; - - /// The [Notification] object - final Notification notification; - - /// Create a [NotificationReceivedEvent] with the given [notification] - NotificationReceivedEvent({required this.notification}); - - /// Create a [NotificationReceivedEvent] from json - factory NotificationReceivedEvent.fromJson(Map json) => - _$NotificationReceivedEventFromJson(json); - - /// Convert a [NotificationReceivedEvent] to json - Map toJson() => _$NotificationReceivedEventToJson(this); -} - -/// A [NotificationSeenEvent] is sent when the client should mark a -/// specific notification as seen. -class NotificationSeenEvent extends NotificationEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.notificationSeen; - - /// The id of the [Notification] that was seen - final String notificationId; - - /// Create a [NotificationSeenEvent] with the given [notificationId] - NotificationSeenEvent({required this.notificationId}); -} - -/// The [NotificationResponseEvent] is used to respond to a previously -/// sent event. It carries an ID of the response notification [responseId], -/// whereby the data is required to be fetched from the API. It also carries -/// [notificationId] to indicate which notification this is a response to. -@JsonSerializable() -class NotificationResponseEvent extends NotificationEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.notificationResponse; - - /// The id of the [Notification] this event responds to - final String notificationId; - - /// The id of the [User] receiving this event - final String receiverId; - - /// The response notification id - final String responseId; - - /// Create a [NotificationResponseEvent] - NotificationResponseEvent({ - required this.notificationId, - required this.receiverId, - required this.responseId, - }); - - /// Create a [NotificationResponseEvent] from json - factory NotificationResponseEvent.fromJson(Map json) => - _$NotificationResponseEventFromJson(json); - - /// Convert a [NotificationResponseEvent] to json - Map toJson() => _$NotificationResponseEventToJson(this); -} - -/// A [NotificationHideEvent] is sent when the client should hide a notification. -@JsonSerializable() -class NotificationHideEvent extends NotificationEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.notificationHide; - - /// The id of the [Notification] to hide - final String notificationId; - - /// Create a [NotificationHideEvent] with the given [notificationId] - NotificationHideEvent({required this.notificationId}); - - /// Create a [NotificationHideEvent] from json - factory NotificationHideEvent.fromJson(Map json) => - _$NotificationHideEventFromJson(json); - - /// Convert a [NotificationHideEvent] to json - Map toJson() => _$NotificationHideEventToJson(this); -} - -/// A [NotificationClearEvent] is sent when the client should clear all notifications. -class NotificationClearEvent extends NotificationEvent { - @override - VrcStreamingEventType get type => VrcStreamingEventType.notificationClear; - - /// Create a [NotificationClearEvent] - NotificationClearEvent(); -} diff --git a/vrchat_dart/lib/src/model/streaming/vrc_streaming_event.g.dart b/vrchat_dart/lib/src/model/streaming/vrc_streaming_event.g.dart deleted file mode 100644 index 9ca8481b..00000000 --- a/vrchat_dart/lib/src/model/streaming/vrc_streaming_event.g.dart +++ /dev/null @@ -1,156 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ignore_for_file: document_ignores, require_trailing_commas, rexios_lints/not_null_assertion - -part of 'vrc_streaming_event.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -FriendOnlineEvent _$FriendOnlineEventFromJson(Map json) => - FriendOnlineEvent( - userId: json['userId'] as String, - user: User.fromJson(json['user'] as Map), - world: const StreamedWorldConverter().fromJson( - json['world'] as Map?, - ), - location: json['location'] as String?, - instance: json['instance'] as String?, - canRequestInvite: json['canRequestInvite'] as bool, - ); - -Map _$FriendOnlineEventToJson(FriendOnlineEvent instance) => - { - 'userId': instance.userId, - 'user': instance.user, - 'world': const StreamedWorldConverter().toJson(instance.world), - 'location': instance.location, - 'instance': instance.instance, - 'canRequestInvite': instance.canRequestInvite, - }; - -FriendOfflineEvent _$FriendOfflineEventFromJson(Map json) => - FriendOfflineEvent(userId: json['userId'] as String); - -Map _$FriendOfflineEventToJson(FriendOfflineEvent instance) => - {'userId': instance.userId}; - -FriendActiveEvent _$FriendActiveEventFromJson(Map json) => - FriendActiveEvent( - userId: json['userId'] as String, - user: User.fromJson(json['user'] as Map), - ); - -Map _$FriendActiveEventToJson(FriendActiveEvent instance) => - {'userId': instance.userId, 'user': instance.user}; - -FriendAddEvent _$FriendAddEventFromJson(Map json) => - FriendAddEvent( - userId: json['userId'] as String, - user: User.fromJson(json['user'] as Map), - ); - -Map _$FriendAddEventToJson(FriendAddEvent instance) => - {'userId': instance.userId, 'user': instance.user}; - -FriendDeleteEvent _$FriendDeleteEventFromJson(Map json) => - FriendDeleteEvent(userId: json['userId'] as String); - -Map _$FriendDeleteEventToJson(FriendDeleteEvent instance) => - {'userId': instance.userId}; - -FriendUpdateEvent _$FriendUpdateEventFromJson(Map json) => - FriendUpdateEvent( - userId: json['userId'] as String, - user: User.fromJson(json['user'] as Map), - ); - -Map _$FriendUpdateEventToJson(FriendUpdateEvent instance) => - {'userId': instance.userId, 'user': instance.user}; - -FriendLocationEvent _$FriendLocationEventFromJson(Map json) => - FriendLocationEvent( - userId: json['userId'] as String, - user: User.fromJson(json['user'] as Map), - world: const StreamedWorldConverter().fromJson( - json['world'] as Map?, - ), - location: json['location'] as String?, - instance: json['instance'] as String?, - canRequestInvite: json['canRequestInvite'] as bool, - ); - -Map _$FriendLocationEventToJson( - FriendLocationEvent instance, -) => { - 'userId': instance.userId, - 'user': instance.user, - 'world': const StreamedWorldConverter().toJson(instance.world), - 'location': instance.location, - 'instance': instance.instance, - 'canRequestInvite': instance.canRequestInvite, -}; - -UserUpdateEvent _$UserUpdateEventFromJson(Map json) => - UserUpdateEvent( - userId: json['userId'] as String, - user: StreamedCurrentUser.fromJson(json['user'] as Map), - ); - -Map _$UserUpdateEventToJson(UserUpdateEvent instance) => - {'userId': instance.userId, 'user': instance.user}; - -UserLocationEvent _$UserLocationEventFromJson(Map json) => - UserLocationEvent( - userId: json['userId'] as String, - world: json['world'] == null - ? null - : World.fromJson(json['world'] as Map), - location: json['location'] as String, - instance: json['instance'] as String, - ); - -Map _$UserLocationEventToJson(UserLocationEvent instance) => - { - 'userId': instance.userId, - 'world': instance.world, - 'location': instance.location, - 'instance': instance.instance, - }; - -NotificationReceivedEvent _$NotificationReceivedEventFromJson( - Map json, -) => NotificationReceivedEvent( - notification: Notification.fromJson( - json['notification'] as Map, - ), -); - -Map _$NotificationReceivedEventToJson( - NotificationReceivedEvent instance, -) => {'notification': instance.notification}; - -NotificationResponseEvent _$NotificationResponseEventFromJson( - Map json, -) => NotificationResponseEvent( - notificationId: json['notificationId'] as String, - receiverId: json['receiverId'] as String, - responseId: json['responseId'] as String, -); - -Map _$NotificationResponseEventToJson( - NotificationResponseEvent instance, -) => { - 'notificationId': instance.notificationId, - 'receiverId': instance.receiverId, - 'responseId': instance.responseId, -}; - -NotificationHideEvent _$NotificationHideEventFromJson( - Map json, -) => NotificationHideEvent(notificationId: json['notificationId'] as String); - -Map _$NotificationHideEventToJson( - NotificationHideEvent instance, -) => {'notificationId': instance.notificationId}; diff --git a/vrchat_dart/lib/src/streaming/vrc_streaming.dart b/vrchat_dart/lib/src/streaming/vrc_streaming.dart index 2ab63aa5..d252c7e6 100644 --- a/vrchat_dart/lib/src/streaming/vrc_streaming.dart +++ b/vrchat_dart/lib/src/streaming/vrc_streaming.dart @@ -1,21 +1,27 @@ import 'dart:async'; import 'dart:convert'; +import 'package:meta/meta.dart'; import 'package:vrchat_dart/src/model/api/vrc_response.dart'; import 'package:vrchat_dart/src/model/vrchat_user_agent.dart'; import 'package:vrchat_dart_generated/vrchat_dart_generated.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; -import 'package:vrchat_dart/src/model/streaming/vrc_streaming_event.dart'; import 'package:vrchat_dart/src/streaming/connect/connect.dart'; /// Streaming for data updates from the VRChat websocket API class VrcStreaming { static const _defaultBaseUrl = 'wss://pipeline.vrchat.cloud'; - final _vrcEventStreamController = - StreamController.broadcast(); - - /// Stream of [VrcStreamingEvent]. Call [start] to start the stream. - StreamView get vrcEventStream => + final _vrcEventStreamController = StreamController.broadcast(); + + /// Stream of websocket messages. Call [start] to start the stream. + /// + /// Each event is the generated model for that `type`. Double-encoded + /// `content` is decoded first, so a `friend-online` message arrives as + /// [WebsocketFriendOnline] rather than [WebsocketFriendOnlineEncoded]. + /// `notification-v2` arrives as [NotificationV2]. An unrecognized `type` + /// arrives as [WebsocketMessageUnknown]. A pipeline error frame arrives as + /// [VrcStreamingError]. + StreamView get vrcEventStream => StreamView(_vrcEventStreamController.stream); final VrchatUserAgent _userAgent; @@ -71,51 +77,94 @@ class VrcStreaming { void _handleWebsocketEvent(dynamic websocketEvent) { final Map json = jsonDecode(websocketEvent); - if (json.containsKey('err')) { - _vrcEventStreamController.add(ErrorEvent(message: json['err'])); - return; - } - final String typeString = json['type']; - final eventType = VrcStreamingEventTypeExtension.fromTypeString(typeString); - - final VrcStreamingEvent event; - switch (eventType) { - case VrcStreamingEventType.unknown: - // Shouldn't be able to get here with an event type of error, but all cases - // must be covered - case VrcStreamingEventType.error: - event = UnknownEvent(rawString: websocketEvent); - case VrcStreamingEventType.friendOnline: - event = FriendOnlineEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.friendOffline: - event = FriendOfflineEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.friendActive: - event = FriendActiveEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.friendAdd: - event = FriendAddEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.friendDelete: - event = FriendDeleteEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.friendUpdate: - event = FriendUpdateEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.friendLocation: - event = FriendLocationEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.userUpdate: - event = UserUpdateEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.userLocation: - event = UserLocationEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.notificationReceived: - final content = jsonDecode(json['content']); - content['details'] = jsonEncode(content['details']); - event = NotificationReceivedEvent.fromJson({'notification': content}); - case VrcStreamingEventType.notificationSeen: - event = NotificationSeenEvent(notificationId: json['content']); - case VrcStreamingEventType.notificationResponse: - event = NotificationResponseEvent.fromJson(jsonDecode(json['content'])); - case VrcStreamingEventType.notificationHide: - event = NotificationHideEvent(notificationId: json['content']); - case VrcStreamingEventType.notificationClear: - event = NotificationClearEvent(); - } - _vrcEventStreamController.add(event); + _vrcEventStreamController.add(_decodeVrcStreamingMessage(json)); + } +} + +/// An error frame from the websocket pipeline, `{"err": "..."}`. +/// +/// The specification does not describe these messages. +@immutable +class VrcStreamingError { + /// The error text from the pipeline. + final String message; + + /// Create a [VrcStreamingError] with the pipeline [message]. + const VrcStreamingError({required this.message}); + + @override + String toString() => message; +} + +final _decodedContentModels = )>{ + 'content-refresh': WebsocketContentRefresh.fromJson, + 'friend-active': WebsocketFriendActive.fromJson, + 'friend-add': WebsocketFriendAdd.fromJson, + 'friend-delete': WebsocketFriendDelete.fromJson, + 'friend-location': WebsocketFriendLocation.fromJson, + 'friend-offline': WebsocketFriendOffline.fromJson, + 'friend-online': WebsocketFriendOnline.fromJson, + 'friend-update': WebsocketFriendUpdate.fromJson, + 'group-joined': WebsocketGroupJoined.fromJson, + 'group-left': WebsocketGroupLeft.fromJson, + 'group-member-updated': WebsocketGroupMemberUpdated.fromJson, + 'group-role-updated': WebsocketGroupRoleUpdated.fromJson, + 'instance-queue-joined': WebsocketInstanceQueueJoined.fromJson, + 'instance-queue-ready': WebsocketInstanceQueueReady.fromJson, + 'notification': _decodeWebsocketNotification, + 'notification-v2': NotificationV2.fromJson, + 'notification-v2-delete': WebsocketNotificationV2Delete.fromJson, + 'notification-v2-update': WebsocketNotificationV2Update.fromJson, + 'response-notification': WebsocketResponseNotification.fromJson, + 'user-badge-assigned': WebsocketUserBadgeAssigned.fromJson, + 'user-badge-unassigned': WebsocketUserBadgeUnassigned.fromJson, + 'user-location': WebsocketUserLocation.fromJson, + 'user-update': WebsocketUserUpdate.fromJson, +}; + +/// Decode one websocket frame into the generated model for its `type`. +Object _decodeVrcStreamingMessage(Map json) { + if (json.containsKey('err')) { + return VrcStreamingError(message: '${json['err']}'); + } + + final type = json['type']; + if (type is! String) { + return const VrcStreamingError(message: 'Websocket message has no type'); + } + + switch (type) { + case 'clear-notification': + return WebsocketClearNotification.fromJson(json); + case 'hide-notification': + return WebsocketHideNotification.fromJson(json); + case 'see-notification': + return WebsocketSeeNotification.fromJson(json); + } + + final decodeContent = _decodedContentModels[type]; + if (decodeContent == null) { + return WebsocketMessageUnknown.fromJson(json); + } + final content = jsonDecode(json['content'] as String) as Map; + return decodeContent(content); +} + +Object _decodeWebsocketNotification(Map content) { + switch (content['type']) { + case 'boop': + return WebsocketNotificationDetailBoop.fromJson(content); + case 'invite': + return WebsocketNotificationDetailInvite.fromJson(content); + case 'invite-response': + return WebsocketNotificationDetailInviteResponse.fromJson(content); + case 'request-invite': + return WebsocketNotificationDetailRequestInvite.fromJson(content); + case 'request-invite-response': + return WebsocketNotificationDetailRequestInviteResponse.fromJson(content); + case 'vote-to-kick': + return WebsocketNotificationDetailVoteToKick.fromJson(content); + default: + return WebsocketNotificationUnknown.fromJson(content); } } diff --git a/vrchat_dart/lib/vrchat_dart.dart b/vrchat_dart/lib/vrchat_dart.dart index 65bc05cb..aa4c667d 100644 --- a/vrchat_dart/lib/vrchat_dart.dart +++ b/vrchat_dart/lib/vrchat_dart.dart @@ -5,8 +5,6 @@ export 'package:vrchat_dart_generated/vrchat_dart_generated.dart'; // model export 'src/model/api/limited_user.dart'; export 'src/model/api/vrc_response.dart'; -export 'src/model/streaming/streamed_current_user.dart'; -export 'src/model/streaming/vrc_streaming_event.dart'; export 'src/model/vrchat_user_agent.dart'; // convenience diff --git a/vrchat_dart/pubspec.yaml b/vrchat_dart/pubspec.yaml index 4f04ad6e..91b49d5a 100644 --- a/vrchat_dart/pubspec.yaml +++ b/vrchat_dart/pubspec.yaml @@ -1,13 +1,13 @@ name: vrchat_dart description: This is a Dart client that interacts with the unofficial VRChat API homepage: https://github.com/vrchatapi/vrchatapi-dart -version: 4.1.0 +version: 5.0.0 environment: sdk: ^3.8.0 dependencies: - vrchat_dart_generated: ^1.22.0 + vrchat_dart_generated: ^1.23.0 dio_cookie_manager: ^3.0.0 cookie_jar: ^4.0.1 dio: ^5.2.0