vrchatapi-async 1.20.8.dev1__tar.gz → 1.20.8.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/PKG-INFO +47 -1
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/README.md +46 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/setup.py +93 -1
- vrchatapi_async-1.20.8.dev2/tests/test_websocket.py +450 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/__init__.py +1 -1
- vrchatapi_async-1.20.8.dev2/vrchatapi/websocket.py +650 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi_async.egg-info/PKG-INFO +47 -1
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi_async.egg-info/SOURCES.txt +2 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/LICENSE +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/pyproject.toml +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/setup.cfg +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/tests/test_async_sdk.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/__init__.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/authentication_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/avatars_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/calendar_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/economy_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/favorites_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/files_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/friends_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/groups_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/instances_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/inventory_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/invite_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/jams_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/miscellaneous_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/notifications_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/playermoderation_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/prints_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/props_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/users_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api/worlds_api.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api_client.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/api_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/configuration.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/exceptions.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/__init__.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/account_deletion_log.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/add_favorite_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/add_group_gallery_image_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/admin_asset_bundle.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/admin_unity_package.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/age_verification_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/agreement.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/agreement_code.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/agreement_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/agreement_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_access_logs_urls.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_announcement.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_audio_config.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_avatar_perf_limiter.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_constants.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_constants_groups.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_constants_instance.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_constants_instancepopulationbrackets.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_constants_instancepopulationbracketscrowded.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_constants_instancepopulationbracketsfew.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_constants_instancepopulationbracketsmany.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_constants_language.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_download_url_list.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_events.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_ios_version.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_min_supported_client_build_number.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_config_offline_analysis.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/api_health.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar_moderation.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar_moderation_created.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar_moderation_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar_performance.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar_published_listings_inner.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar_style.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar_styles.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/avatar_unity_package_url_object.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/badge.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/balance.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/ban_group_member_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/boop_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_day_of_week.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_access.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_category.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_discovery.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_discovery_inclusion.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_discovery_scope.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_frequency.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_occurrence_kind.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_platform.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_recurrence.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_recurrence_end.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_recurrence_end_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/calendar_event_user_interest.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/change_user_tags_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/change_world_tags_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/content_filter.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_avatar_moderation_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_avatar_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_calendar_event_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_file_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_file_version_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_group_announcement_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_group_gallery_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_group_invite_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_group_post_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_group_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_group_role_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_instance_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_jam_submission_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_listing_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_product_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_prop_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/create_world_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/current_user.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/current_user_platform_history_inner.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/current_user_presence.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/decline_group_invite_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/developer_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/disable2_fa_result.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/discord_details.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/dynamic_content_row.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/earnings_metrics.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/earnings_metrics_totals.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/economy_account.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/economy_balances.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/economy_payout.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/economy_payout_eligibility.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/economy_payout_list.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/economy_payout_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/equip_inventory_item_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/error.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/favorite.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/favorite_group.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/favorite_group_limits.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/favorite_group_visibility.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/favorite_limits.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/favorite_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/favorited_world.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/feedback.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/file.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/file_analysis.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/file_analysis_avatar_stats.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/file_data.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/file_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/file_upload_url.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/file_version.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/file_version_upload_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/finish_file_data_upload_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/follow_calendar_event_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/friend_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/get_group_posts200_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/get_user_group_instances200_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_access_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_announcement.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_audit_log_entry.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_gallery.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_gallery_file_order.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_gallery_file_order_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_gallery_image.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_instance.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_join_request_action.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_join_state.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_member.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_member_limited_user.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_member_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_my_member.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_permission.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_permissions.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_post.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_post_visibility.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_privacy.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_role.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_role_template.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_role_template_values.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_role_template_values_roles.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_search_sort.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_transferable.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_transferable_requirements.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/group_user_visibility.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/image_animation_style.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/image_loop_style.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/image_mask.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/image_purpose.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/info_push.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/info_push_data.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/info_push_data_article.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/info_push_data_article_content.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/info_push_data_clickable.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/instance.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/instance_content_settings.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/instance_platforms.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/instance_region.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/instance_short_name_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/instance_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_consumption_results.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_default_attributes_value.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_default_attributes_value_validator.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_drop.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_equip_slot.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_flag.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_item.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_item_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_metadata.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_notification_details.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_spawn.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_template.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/inventory_user_attributes.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/invite_message.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/invite_message_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/invite_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/invite_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/jam.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/jam_state_change_dates.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/jam_submission.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/join_group_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/license.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/license_action.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/license_group.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/license_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/limited_group.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/limited_unity_package.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/limited_user_friend.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/limited_user_groups.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/limited_user_instance.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/limited_user_search.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/limited_world.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/mime_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/model_print.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/moderate_user_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/moderation_report.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/mutual_friend.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/mutuals.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_detail_invite.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_detail_invite_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_detail_request_invite.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_detail_request_invite_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_detail_vote_to_kick.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_v2.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_v2_details_boop.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_v2_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/notification_v2_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/ok_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/ok_status2.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/order_option.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/order_option_short.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/paginated_calendar_event_list.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/paginated_group_audit_log_entry_list.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/paginated_moderation_report_list.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/past_display_name.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/pending2_fa_result.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/performance_limiter_info.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/performance_ratings.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/permission.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/permission_data.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/platform_build_info.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/player_moderation.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/player_moderation_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/print_files.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/private_profile.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/private_profile_activity.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_listing.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_listing_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_listing_variant.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_purchase.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_purchase_history.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_purchase_location_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_purchase_product.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_purchase_purchase_context.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_purchase_record.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/product_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/profile_represented_group.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/prop.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/prop_publish_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/prop_unity_package.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/public_profile.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/purchase_context_data.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/purchase_product_listing_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/region.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/register_user_account_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/release_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/report_category.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/report_reason.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/represented_group.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/request_invite_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/respond_group_join_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/respond_notification_v2_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/reward_badge.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/reward_redemption.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/reward_redemption_data.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/reward_redemption_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/reward_redemption_result.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/search_group_members200_response.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/seller_eligibility.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/sent_notification.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/service_queue_stats.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/service_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/share_inventory_item_direct_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/sort_option.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/sort_option_product_purchase.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/store.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/store_context.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/store_shelf.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/store_type.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/store_view.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/submit_moderation_report_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/submit_moderation_report_request_details.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/subscription.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/subscription_period.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/success.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/success_flag.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/tilia_kyc.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/tilia_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/tilia_tos.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/token_bundle.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/transaction.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/transaction_agreement.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/transaction_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/transaction_steam_info.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/transaction_steam_wallet_info.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/transfer_group_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/tutorial_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/two_factor_auth_code.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/two_factor_email_code.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/two_factor_recovery_codes.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/two_factor_recovery_codes_otp_inner.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/unity_package.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_asset_review_notes_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_avatar_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_calendar_event_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_favorite_group_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_group_gallery_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_group_member_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_group_representation_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_group_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_group_role_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_inventory_item_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_invite_message_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_listing_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_product_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_prop_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_tilia_tos_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_user_badge_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_user_note_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_user_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/update_world_request.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user_credits_eligible.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user_exists.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user_note.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user_note_target_user.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user_state.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user_subscription.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/user_subscription_eligible.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/verify2_fa_email_code_result.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/verify2_fa_result.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/verify_auth_token_result.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/world.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/world_metadata.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/models/world_publish_status.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/py.typed +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi/rest.py +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi_async.egg-info/dependency_links.txt +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi_async.egg-info/requires.txt +0 -0
- {vrchatapi_async-1.20.8.dev1 → vrchatapi_async-1.20.8.dev2}/vrchatapi_async.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vrchatapi-async
|
|
3
|
-
Version: 1.20.8.
|
|
3
|
+
Version: 1.20.8.dev2
|
|
4
4
|
Summary: VRChat API Library for Python (async version)
|
|
5
5
|
Home-page: https://github.com/hi94740/vrchatapi-python-async
|
|
6
6
|
Author: hi94740
|
|
@@ -134,4 +134,50 @@ Compared to [the synchronous SDK](https://github.com/vrchatapi/vrchatapi-python)
|
|
|
134
134
|
## Contributing
|
|
135
135
|
|
|
136
136
|
Contributions are welcome, but do not add features that should be handled by the OpenAPI specification.
|
|
137
|
+
|
|
138
|
+
## Websocket (Pipeline) API
|
|
139
|
+
|
|
140
|
+
The SDK also ships a hand-written async client for VRChat's real-time
|
|
141
|
+
WebSocket (Pipeline) API (`wss://pipeline.vrchat.cloud`), which pushes invites,
|
|
142
|
+
friend requests, friend online/offline events, user and group updates to the
|
|
143
|
+
authenticated client. The connection is receive-only; every message's
|
|
144
|
+
double-encoded `content` field is automatically unpacked into a plain dict.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import asyncio
|
|
148
|
+
|
|
149
|
+
import vrchatapi
|
|
150
|
+
from vrchatapi.api import authentication_api
|
|
151
|
+
from vrchatapi.websocket import VRChatWebSocket
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def main():
|
|
155
|
+
configuration = vrchatapi.Configuration(username="username", password="password")
|
|
156
|
+
async with vrchatapi.ApiClient(configuration) as api_client:
|
|
157
|
+
api_client.user_agent = "ExampleProgram/0.0.1 my@email.com"
|
|
158
|
+
await authentication_api.AuthenticationApi(api_client).get_current_user()
|
|
159
|
+
|
|
160
|
+
# Reads the auth cookie + User-Agent from the logged-in client.
|
|
161
|
+
ws = VRChatWebSocket.from_client(api_client)
|
|
162
|
+
|
|
163
|
+
@ws.on("friend-online")
|
|
164
|
+
async def on_friend_online(event):
|
|
165
|
+
print("online:", event.content["user"]["displayName"])
|
|
166
|
+
|
|
167
|
+
# Or consume the same stream as an async iterator:
|
|
168
|
+
# async for event in ws:
|
|
169
|
+
# print(event.type, event.content)
|
|
170
|
+
|
|
171
|
+
await ws.run() # blocks; reconnects automatically by default
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`VRChatWebSocket` supports callback registration (`ws.on("friend-online", ...)`
|
|
175
|
+
/ `@ws.on(...)`, a catch-all `ws.on_event(...)`), an async iterator over the
|
|
176
|
+
same event stream, error/connect/disconnect/reconnect hooks, automatic
|
|
177
|
+
reconnection with exponential backoff (`auto_reconnect=False` disables it), and
|
|
178
|
+
a configurable application-level heartbeat (default 30s;
|
|
179
|
+
`heartbeat_interval=None` disables it). See
|
|
180
|
+
[examples/examples-source/websocket.py](examples/examples-source/websocket.py)
|
|
181
|
+
and the [Websocket API reference](https://vrchat.community/websocket) for
|
|
182
|
+
details.
|
|
137
183
|
|
|
@@ -103,3 +103,49 @@ Compared to [the synchronous SDK](https://github.com/vrchatapi/vrchatapi-python)
|
|
|
103
103
|
## Contributing
|
|
104
104
|
|
|
105
105
|
Contributions are welcome, but do not add features that should be handled by the OpenAPI specification.
|
|
106
|
+
|
|
107
|
+
## Websocket (Pipeline) API
|
|
108
|
+
|
|
109
|
+
The SDK also ships a hand-written async client for VRChat's real-time
|
|
110
|
+
WebSocket (Pipeline) API (`wss://pipeline.vrchat.cloud`), which pushes invites,
|
|
111
|
+
friend requests, friend online/offline events, user and group updates to the
|
|
112
|
+
authenticated client. The connection is receive-only; every message's
|
|
113
|
+
double-encoded `content` field is automatically unpacked into a plain dict.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
import asyncio
|
|
117
|
+
|
|
118
|
+
import vrchatapi
|
|
119
|
+
from vrchatapi.api import authentication_api
|
|
120
|
+
from vrchatapi.websocket import VRChatWebSocket
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def main():
|
|
124
|
+
configuration = vrchatapi.Configuration(username="username", password="password")
|
|
125
|
+
async with vrchatapi.ApiClient(configuration) as api_client:
|
|
126
|
+
api_client.user_agent = "ExampleProgram/0.0.1 my@email.com"
|
|
127
|
+
await authentication_api.AuthenticationApi(api_client).get_current_user()
|
|
128
|
+
|
|
129
|
+
# Reads the auth cookie + User-Agent from the logged-in client.
|
|
130
|
+
ws = VRChatWebSocket.from_client(api_client)
|
|
131
|
+
|
|
132
|
+
@ws.on("friend-online")
|
|
133
|
+
async def on_friend_online(event):
|
|
134
|
+
print("online:", event.content["user"]["displayName"])
|
|
135
|
+
|
|
136
|
+
# Or consume the same stream as an async iterator:
|
|
137
|
+
# async for event in ws:
|
|
138
|
+
# print(event.type, event.content)
|
|
139
|
+
|
|
140
|
+
await ws.run() # blocks; reconnects automatically by default
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`VRChatWebSocket` supports callback registration (`ws.on("friend-online", ...)`
|
|
144
|
+
/ `@ws.on(...)`, a catch-all `ws.on_event(...)`), an async iterator over the
|
|
145
|
+
same event stream, error/connect/disconnect/reconnect hooks, automatic
|
|
146
|
+
reconnection with exponential backoff (`auto_reconnect=False` disables it), and
|
|
147
|
+
a configurable application-level heartbeat (default 30s;
|
|
148
|
+
`heartbeat_interval=None` disables it). See
|
|
149
|
+
[examples/examples-source/websocket.py](examples/examples-source/websocket.py)
|
|
150
|
+
and the [Websocket API reference](https://vrchat.community/websocket) for
|
|
151
|
+
details.
|
|
@@ -107,6 +107,52 @@ Compared to [the synchronous SDK](https://github.com/vrchatapi/vrchatapi-python)
|
|
|
107
107
|
|
|
108
108
|
Contributions are welcome, but do not add features that should be handled by the OpenAPI specification.
|
|
109
109
|
|
|
110
|
+
## Websocket (Pipeline) API
|
|
111
|
+
|
|
112
|
+
The SDK also ships a hand-written async client for VRChat's real-time
|
|
113
|
+
WebSocket (Pipeline) API (`wss://pipeline.vrchat.cloud`), which pushes invites,
|
|
114
|
+
friend requests, friend online/offline events, user and group updates to the
|
|
115
|
+
authenticated client. The connection is receive-only; every message's
|
|
116
|
+
double-encoded `content` field is automatically unpacked into a plain dict.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import asyncio
|
|
120
|
+
|
|
121
|
+
import vrchatapi
|
|
122
|
+
from vrchatapi.api import authentication_api
|
|
123
|
+
from vrchatapi.websocket import VRChatWebSocket
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def main():
|
|
127
|
+
configuration = vrchatapi.Configuration(username="username", password="password")
|
|
128
|
+
async with vrchatapi.ApiClient(configuration) as api_client:
|
|
129
|
+
api_client.user_agent = "ExampleProgram/0.0.1 my@email.com"
|
|
130
|
+
await authentication_api.AuthenticationApi(api_client).get_current_user()
|
|
131
|
+
|
|
132
|
+
# Reads the auth cookie + User-Agent from the logged-in client.
|
|
133
|
+
ws = VRChatWebSocket.from_client(api_client)
|
|
134
|
+
|
|
135
|
+
@ws.on("friend-online")
|
|
136
|
+
async def on_friend_online(event):
|
|
137
|
+
print("online:", event.content["user"]["displayName"])
|
|
138
|
+
|
|
139
|
+
# Or consume the same stream as an async iterator:
|
|
140
|
+
# async for event in ws:
|
|
141
|
+
# print(event.type, event.content)
|
|
142
|
+
|
|
143
|
+
await ws.run() # blocks; reconnects automatically by default
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`VRChatWebSocket` supports callback registration (`ws.on("friend-online", ...)`
|
|
147
|
+
/ `@ws.on(...)`, a catch-all `ws.on_event(...)`), an async iterator over the
|
|
148
|
+
same event stream, error/connect/disconnect/reconnect hooks, automatic
|
|
149
|
+
reconnection with exponential backoff (`auto_reconnect=False` disables it), and
|
|
150
|
+
a configurable application-level heartbeat (default 30s;
|
|
151
|
+
`heartbeat_interval=None` disables it). See
|
|
152
|
+
[examples/examples-source/websocket.py](examples/examples-source/websocket.py)
|
|
153
|
+
and the [Websocket API reference](https://vrchat.community/websocket) for
|
|
154
|
+
details.
|
|
155
|
+
|
|
110
156
|
The version of the OpenAPI document: 1.20.8
|
|
111
157
|
Contact: vrchatapi.lpv0t@aries.fyi
|
|
112
158
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
@@ -124,7 +170,7 @@ from setuptools import setup, find_packages # noqa: H301
|
|
|
124
170
|
# prerequisite: setuptools
|
|
125
171
|
# http://pypi.python.org/pypi/setuptools
|
|
126
172
|
NAME = "vrchatapi-async"
|
|
127
|
-
VERSION = "1.20.
|
|
173
|
+
VERSION = "1.20.8dev2"
|
|
128
174
|
PYTHON_REQUIRES = ">= 3.10"
|
|
129
175
|
REQUIRES = [
|
|
130
176
|
"python-dateutil >= 2.8.2",
|
|
@@ -254,6 +300,52 @@ Compared to [the synchronous SDK](https://github.com/vrchatapi/vrchatapi-python)
|
|
|
254
300
|
## Contributing
|
|
255
301
|
|
|
256
302
|
Contributions are welcome, but do not add features that should be handled by the OpenAPI specification.
|
|
303
|
+
|
|
304
|
+
## Websocket (Pipeline) API
|
|
305
|
+
|
|
306
|
+
The SDK also ships a hand-written async client for VRChat's real-time
|
|
307
|
+
WebSocket (Pipeline) API (`wss://pipeline.vrchat.cloud`), which pushes invites,
|
|
308
|
+
friend requests, friend online/offline events, user and group updates to the
|
|
309
|
+
authenticated client. The connection is receive-only; every message's
|
|
310
|
+
double-encoded `content` field is automatically unpacked into a plain dict.
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
import asyncio
|
|
314
|
+
|
|
315
|
+
import vrchatapi
|
|
316
|
+
from vrchatapi.api import authentication_api
|
|
317
|
+
from vrchatapi.websocket import VRChatWebSocket
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
async def main():
|
|
321
|
+
configuration = vrchatapi.Configuration(username="username", password="password")
|
|
322
|
+
async with vrchatapi.ApiClient(configuration) as api_client:
|
|
323
|
+
api_client.user_agent = "ExampleProgram/0.0.1 my@email.com"
|
|
324
|
+
await authentication_api.AuthenticationApi(api_client).get_current_user()
|
|
325
|
+
|
|
326
|
+
# Reads the auth cookie + User-Agent from the logged-in client.
|
|
327
|
+
ws = VRChatWebSocket.from_client(api_client)
|
|
328
|
+
|
|
329
|
+
@ws.on("friend-online")
|
|
330
|
+
async def on_friend_online(event):
|
|
331
|
+
print("online:", event.content["user"]["displayName"])
|
|
332
|
+
|
|
333
|
+
# Or consume the same stream as an async iterator:
|
|
334
|
+
# async for event in ws:
|
|
335
|
+
# print(event.type, event.content)
|
|
336
|
+
|
|
337
|
+
await ws.run() # blocks; reconnects automatically by default
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
`VRChatWebSocket` supports callback registration (`ws.on("friend-online", ...)`
|
|
341
|
+
/ `@ws.on(...)`, a catch-all `ws.on_event(...)`), an async iterator over the
|
|
342
|
+
same event stream, error/connect/disconnect/reconnect hooks, automatic
|
|
343
|
+
reconnection with exponential backoff (`auto_reconnect=False` disables it), and
|
|
344
|
+
a configurable application-level heartbeat (default 30s;
|
|
345
|
+
`heartbeat_interval=None` disables it). See
|
|
346
|
+
[examples/examples-source/websocket.py](examples/examples-source/websocket.py)
|
|
347
|
+
and the [Websocket API reference](https://vrchat.community/websocket) for
|
|
348
|
+
details.
|
|
257
349
|
""", # noqa: E501
|
|
258
350
|
package_data={"vrchatapi": ["py.typed"]},
|
|
259
351
|
)
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"""Tests for the hand-written VRChat WebSocket (Pipeline) client.
|
|
2
|
+
|
|
3
|
+
The pipeline is emulated with a real local aiohttp websocket server so the
|
|
4
|
+
tests exercise the actual transport (handshake, query authToken, User-Agent,
|
|
5
|
+
heartbeat frames, close handling) end to end.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import time
|
|
11
|
+
from types import SimpleNamespace
|
|
12
|
+
|
|
13
|
+
import aiohttp
|
|
14
|
+
import pytest
|
|
15
|
+
from aiohttp import web
|
|
16
|
+
|
|
17
|
+
from vrchatapi.websocket import (
|
|
18
|
+
VRChatEvent,
|
|
19
|
+
VRChatWebSocket,
|
|
20
|
+
VRChatWebSocketError,
|
|
21
|
+
get_auth_cookie,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def friend_online_frame(user_id="usr_x", display_name="Alice"):
|
|
26
|
+
return json.dumps(
|
|
27
|
+
{
|
|
28
|
+
"type": "friend-online",
|
|
29
|
+
"content": json.dumps(
|
|
30
|
+
{
|
|
31
|
+
"user": {
|
|
32
|
+
"id": user_id,
|
|
33
|
+
"displayName": display_name,
|
|
34
|
+
"location": "offline",
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
),
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def notification_frame():
|
|
43
|
+
return json.dumps(
|
|
44
|
+
{
|
|
45
|
+
"type": "notification",
|
|
46
|
+
"content": json.dumps(
|
|
47
|
+
{"type": "friendRequest", "senderUserId": "usr_y"}
|
|
48
|
+
),
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MockPipelineServer:
|
|
54
|
+
"""Tiny aiohttp server mimicking the VRChat pipeline websocket."""
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
self.connections = [] # (query, headers) per connection
|
|
58
|
+
self.received = [] # text frames sent by the client
|
|
59
|
+
self.frames = [] # frames sent on unscripted connections
|
|
60
|
+
self.close_after_frames = False
|
|
61
|
+
self.script = {} # connection index -> (frames, close_after_send)
|
|
62
|
+
self.app = web.Application()
|
|
63
|
+
self.app.router.add_get("/", self._ws_handler)
|
|
64
|
+
|
|
65
|
+
def set_script(self, conn_index, frames, close_after_send=True):
|
|
66
|
+
self.script[conn_index] = (frames, close_after_send)
|
|
67
|
+
|
|
68
|
+
async def start(self):
|
|
69
|
+
self.runner = web.AppRunner(self.app)
|
|
70
|
+
await self.runner.setup()
|
|
71
|
+
self.site = web.TCPSite(self.runner, "127.0.0.1", 0)
|
|
72
|
+
await self.site.start()
|
|
73
|
+
port = self.site._server.sockets[0].getsockname()[1]
|
|
74
|
+
self.base_url = f"http://127.0.0.1:{port}"
|
|
75
|
+
|
|
76
|
+
async def stop(self):
|
|
77
|
+
await self.runner.cleanup()
|
|
78
|
+
|
|
79
|
+
async def _ws_handler(self, request):
|
|
80
|
+
self.connections.append((dict(request.query), request.headers))
|
|
81
|
+
index = len(self.connections) - 1
|
|
82
|
+
frames, close_after = self.script.get(
|
|
83
|
+
index, (self.frames, self.close_after_frames)
|
|
84
|
+
)
|
|
85
|
+
ws = web.WebSocketResponse()
|
|
86
|
+
await ws.prepare(request)
|
|
87
|
+
for frame in frames:
|
|
88
|
+
await ws.send_str(frame)
|
|
89
|
+
if close_after:
|
|
90
|
+
await ws.close()
|
|
91
|
+
return ws
|
|
92
|
+
async for msg in ws:
|
|
93
|
+
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
94
|
+
self.received.append(msg.data)
|
|
95
|
+
elif msg.type == aiohttp.WSMsgType.ERROR:
|
|
96
|
+
break
|
|
97
|
+
elif msg.type in (aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSED):
|
|
98
|
+
break
|
|
99
|
+
return ws
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.fixture
|
|
103
|
+
async def pipeline_server():
|
|
104
|
+
srv = MockPipelineServer()
|
|
105
|
+
await srv.start()
|
|
106
|
+
yield srv
|
|
107
|
+
await srv.stop()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def make_ws(server, **kwargs):
|
|
111
|
+
defaults = dict(
|
|
112
|
+
auth_token="authcookie_test",
|
|
113
|
+
endpoint=server.base_url,
|
|
114
|
+
auto_reconnect=False,
|
|
115
|
+
heartbeat_interval=None,
|
|
116
|
+
)
|
|
117
|
+
defaults.update(kwargs)
|
|
118
|
+
return VRChatWebSocket(**defaults)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Connection
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.asyncio
|
|
127
|
+
async def test_connect_sends_auth_token_and_user_agent(pipeline_server):
|
|
128
|
+
pipeline_server.set_script(0, [], close_after_send=True)
|
|
129
|
+
ws = VRChatWebSocket(
|
|
130
|
+
auth_token="authcookie_test",
|
|
131
|
+
user_agent="TestApp/1.0 me@example.com",
|
|
132
|
+
endpoint=pipeline_server.base_url,
|
|
133
|
+
auto_reconnect=False,
|
|
134
|
+
heartbeat_interval=None,
|
|
135
|
+
)
|
|
136
|
+
await ws.run()
|
|
137
|
+
|
|
138
|
+
query, headers = pipeline_server.connections[0]
|
|
139
|
+
assert query["authToken"] == "authcookie_test"
|
|
140
|
+
assert headers.get("User-Agent") == "TestApp/1.0 me@example.com"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.mark.asyncio
|
|
144
|
+
async def test_auth_token_is_url_encoded(pipeline_server):
|
|
145
|
+
pipeline_server.set_script(0, [], close_after_send=True)
|
|
146
|
+
token = "authcookie_a b&c=d"
|
|
147
|
+
ws = VRChatWebSocket(
|
|
148
|
+
auth_token=token,
|
|
149
|
+
endpoint=pipeline_server.base_url,
|
|
150
|
+
auto_reconnect=False,
|
|
151
|
+
heartbeat_interval=None,
|
|
152
|
+
)
|
|
153
|
+
await ws.run()
|
|
154
|
+
|
|
155
|
+
# The server sees the decoded original token; without URL encoding the
|
|
156
|
+
# unquoted "&" / "=" would have split the query string.
|
|
157
|
+
query, _ = pipeline_server.connections[0]
|
|
158
|
+
assert query["authToken"] == token
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
# Decoding
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@pytest.mark.asyncio
|
|
167
|
+
async def test_content_decoding_matrix():
|
|
168
|
+
ws = VRChatWebSocket(auth_token="x", heartbeat_interval=None)
|
|
169
|
+
seen = []
|
|
170
|
+
|
|
171
|
+
@ws.on_event
|
|
172
|
+
async def on_event(event):
|
|
173
|
+
seen.append(event)
|
|
174
|
+
|
|
175
|
+
def frame(msg_type, content=None):
|
|
176
|
+
payload = {"type": msg_type}
|
|
177
|
+
if content is not None:
|
|
178
|
+
payload["content"] = content
|
|
179
|
+
return json.dumps(payload)
|
|
180
|
+
|
|
181
|
+
assert (
|
|
182
|
+
await ws._dispatch_message(
|
|
183
|
+
frame("notification", json.dumps({"type": "friendRequest", "senderUserId": "usr_x"}))
|
|
184
|
+
)
|
|
185
|
+
is True
|
|
186
|
+
)
|
|
187
|
+
assert await ws._dispatch_message(frame("see-notification", "not_abc")) is True
|
|
188
|
+
assert await ws._dispatch_message(frame("clear-notification")) is True
|
|
189
|
+
assert (
|
|
190
|
+
await ws._dispatch_message(
|
|
191
|
+
frame("notification-v2-delete", json.dumps({"notificationIds": ["n1", "n2"]}))
|
|
192
|
+
)
|
|
193
|
+
is True
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
assert [e.type for e in seen] == [
|
|
197
|
+
"notification",
|
|
198
|
+
"see-notification",
|
|
199
|
+
"clear-notification",
|
|
200
|
+
"notification-v2-delete",
|
|
201
|
+
]
|
|
202
|
+
assert seen[0].content == {"type": "friendRequest", "senderUserId": "usr_x"}
|
|
203
|
+
assert isinstance(seen[0], VRChatEvent)
|
|
204
|
+
assert seen[1].content == "not_abc"
|
|
205
|
+
assert seen[1].raw_content == "not_abc"
|
|
206
|
+
assert seen[2].content is None
|
|
207
|
+
assert seen[2].raw_content is None
|
|
208
|
+
assert seen[3].content == {"notificationIds": ["n1", "n2"]}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@pytest.mark.asyncio
|
|
212
|
+
async def test_malformed_content_reports_error_and_keeps_connection():
|
|
213
|
+
ws = VRChatWebSocket(auth_token="x", heartbeat_interval=None)
|
|
214
|
+
errors = []
|
|
215
|
+
|
|
216
|
+
@ws.on_error
|
|
217
|
+
async def on_error(exc):
|
|
218
|
+
errors.append(exc)
|
|
219
|
+
|
|
220
|
+
# content is not valid JSON but the connection itself stays up.
|
|
221
|
+
assert await ws._dispatch_message('{"type":"notification","content":"nope"}') is True
|
|
222
|
+
assert len(errors) == 1
|
|
223
|
+
assert isinstance(errors[0], VRChatWebSocketError)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@pytest.mark.asyncio
|
|
227
|
+
async def test_server_error_message_closes_connection():
|
|
228
|
+
ws = VRChatWebSocket(auth_token="x", heartbeat_interval=None)
|
|
229
|
+
errors = []
|
|
230
|
+
|
|
231
|
+
@ws.on_error
|
|
232
|
+
async def on_error(exc):
|
|
233
|
+
errors.append(exc)
|
|
234
|
+
|
|
235
|
+
assert await ws._dispatch_message('{"err":"Error finding user usr_x"}') is False
|
|
236
|
+
assert len(errors) == 1
|
|
237
|
+
assert isinstance(errors[0], VRChatWebSocketError)
|
|
238
|
+
assert "Error finding user" in str(errors[0])
|
|
239
|
+
assert errors[0].raw == '{"err":"Error finding user usr_x"}'
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
# Callbacks
|
|
244
|
+
# ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@pytest.mark.asyncio
|
|
248
|
+
async def test_callback_dispatch_and_catch_all(pipeline_server):
|
|
249
|
+
pipeline_server.set_script(
|
|
250
|
+
0, [friend_online_frame(), notification_frame()], close_after_send=True
|
|
251
|
+
)
|
|
252
|
+
ws = make_ws(pipeline_server)
|
|
253
|
+
typed, catch_all = [], []
|
|
254
|
+
|
|
255
|
+
@ws.on("friend-online")
|
|
256
|
+
async def on_friend_online(event):
|
|
257
|
+
typed.append(event.content["user"]["displayName"])
|
|
258
|
+
|
|
259
|
+
@ws.on_event
|
|
260
|
+
async def on_event(event):
|
|
261
|
+
catch_all.append(event.type)
|
|
262
|
+
|
|
263
|
+
await ws.run()
|
|
264
|
+
|
|
265
|
+
assert typed == ["Alice"]
|
|
266
|
+
assert catch_all == ["friend-online", "notification"]
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@pytest.mark.asyncio
|
|
270
|
+
async def test_off_removes_handler(pipeline_server):
|
|
271
|
+
pipeline_server.set_script(0, [friend_online_frame()], close_after_send=True)
|
|
272
|
+
ws = make_ws(pipeline_server)
|
|
273
|
+
calls = []
|
|
274
|
+
|
|
275
|
+
@ws.on("friend-online")
|
|
276
|
+
async def handler(event):
|
|
277
|
+
calls.append(event)
|
|
278
|
+
|
|
279
|
+
ws.off("friend-online", handler)
|
|
280
|
+
await ws.run()
|
|
281
|
+
|
|
282
|
+
assert calls == []
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# Async iterator
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@pytest.mark.asyncio
|
|
291
|
+
async def test_async_iterator_yields_events(pipeline_server):
|
|
292
|
+
pipeline_server.set_script(
|
|
293
|
+
0, [friend_online_frame(), notification_frame()], close_after_send=False
|
|
294
|
+
)
|
|
295
|
+
ws = make_ws(pipeline_server)
|
|
296
|
+
await ws.connect()
|
|
297
|
+
|
|
298
|
+
collected = []
|
|
299
|
+
|
|
300
|
+
async def consume():
|
|
301
|
+
async for event in ws:
|
|
302
|
+
collected.append(event.type)
|
|
303
|
+
if len(collected) == 2:
|
|
304
|
+
await ws.close()
|
|
305
|
+
|
|
306
|
+
await consume()
|
|
307
|
+
|
|
308
|
+
assert collected == ["friend-online", "notification"]
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
# Errors / lifecycle
|
|
313
|
+
# ---------------------------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@pytest.mark.asyncio
|
|
317
|
+
async def test_server_error_ends_run(pipeline_server):
|
|
318
|
+
pipeline_server.set_script(
|
|
319
|
+
0, ['{"err":"Error finding user usr_x"}'], close_after_send=True
|
|
320
|
+
)
|
|
321
|
+
ws = make_ws(pipeline_server)
|
|
322
|
+
errors = []
|
|
323
|
+
|
|
324
|
+
@ws.on_error
|
|
325
|
+
async def on_error(exc):
|
|
326
|
+
errors.append(exc)
|
|
327
|
+
|
|
328
|
+
await ws.run()
|
|
329
|
+
|
|
330
|
+
assert len(errors) == 1
|
|
331
|
+
assert isinstance(errors[0], VRChatWebSocketError)
|
|
332
|
+
assert not ws.is_connected
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# ---------------------------------------------------------------------------
|
|
336
|
+
# Heartbeat
|
|
337
|
+
# ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@pytest.mark.asyncio
|
|
341
|
+
async def test_heartbeat_frames_are_sent(pipeline_server):
|
|
342
|
+
pipeline_server.set_script(0, [], close_after_send=False)
|
|
343
|
+
ws = make_ws(pipeline_server, heartbeat_interval=0.05)
|
|
344
|
+
|
|
345
|
+
task = asyncio.create_task(ws.run())
|
|
346
|
+
try:
|
|
347
|
+
deadline = time.monotonic() + 10
|
|
348
|
+
while not pipeline_server.received and time.monotonic() < deadline:
|
|
349
|
+
await asyncio.sleep(0.02)
|
|
350
|
+
finally:
|
|
351
|
+
await ws.close()
|
|
352
|
+
await task
|
|
353
|
+
|
|
354
|
+
assert pipeline_server.received, "server never received a heartbeat frame"
|
|
355
|
+
heartbeats = [
|
|
356
|
+
json.loads(frame)
|
|
357
|
+
for frame in pipeline_server.received
|
|
358
|
+
if json.loads(frame).get("type") == "heartbeat"
|
|
359
|
+
]
|
|
360
|
+
assert heartbeats, pipeline_server.received
|
|
361
|
+
assert heartbeats[0]["connected"] is True
|
|
362
|
+
assert heartbeats[0]["nonce"]
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# ---------------------------------------------------------------------------
|
|
366
|
+
# Reconnect
|
|
367
|
+
# ---------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@pytest.mark.asyncio
|
|
371
|
+
async def test_auto_reconnect_after_drop(pipeline_server):
|
|
372
|
+
# First connection drops immediately; the client must reconnect and
|
|
373
|
+
# receive the event sent on the second connection.
|
|
374
|
+
pipeline_server.set_script(0, [], close_after_send=True)
|
|
375
|
+
pipeline_server.set_script(1, [friend_online_frame()], close_after_send=False)
|
|
376
|
+
|
|
377
|
+
ws = make_ws(
|
|
378
|
+
pipeline_server,
|
|
379
|
+
auto_reconnect=True,
|
|
380
|
+
reconnect_max_delay=0.05,
|
|
381
|
+
)
|
|
382
|
+
received = []
|
|
383
|
+
got = asyncio.Event()
|
|
384
|
+
reconnects = []
|
|
385
|
+
|
|
386
|
+
@ws.on("friend-online")
|
|
387
|
+
async def on_friend_online(event):
|
|
388
|
+
received.append(event.content["user"]["displayName"])
|
|
389
|
+
got.set()
|
|
390
|
+
|
|
391
|
+
@ws.on_reconnect
|
|
392
|
+
async def on_reconnect(delay):
|
|
393
|
+
reconnects.append(delay)
|
|
394
|
+
|
|
395
|
+
task = asyncio.create_task(ws.run())
|
|
396
|
+
try:
|
|
397
|
+
await asyncio.wait_for(got.wait(), timeout=10)
|
|
398
|
+
finally:
|
|
399
|
+
await ws.close()
|
|
400
|
+
await task
|
|
401
|
+
|
|
402
|
+
assert len(pipeline_server.connections) >= 2
|
|
403
|
+
assert received == ["Alice"]
|
|
404
|
+
assert reconnects
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# ---------------------------------------------------------------------------
|
|
408
|
+
# from_client / auth cookie
|
|
409
|
+
# ---------------------------------------------------------------------------
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class _FakeCookieJar:
|
|
413
|
+
def __init__(self, cookies):
|
|
414
|
+
self._cookies = cookies
|
|
415
|
+
|
|
416
|
+
def __iter__(self):
|
|
417
|
+
return iter(self._cookies)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class _FakeApiClient:
|
|
421
|
+
def __init__(self, cookies, user_agent="MyApp/1.0 me@example.com"):
|
|
422
|
+
self.rest_client = SimpleNamespace(cookie_jar=_FakeCookieJar(cookies))
|
|
423
|
+
self.user_agent = user_agent
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def test_from_client_reads_auth_cookie_and_user_agent():
|
|
427
|
+
client = _FakeApiClient(
|
|
428
|
+
[
|
|
429
|
+
SimpleNamespace(name="twoFactorAuth", value="2fa-value"),
|
|
430
|
+
SimpleNamespace(name="auth", value="authcookie_123"),
|
|
431
|
+
]
|
|
432
|
+
)
|
|
433
|
+
ws = VRChatWebSocket.from_client(client)
|
|
434
|
+
assert ws._auth_token == "authcookie_123"
|
|
435
|
+
assert ws._user_agent == "MyApp/1.0 me@example.com"
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def test_from_client_raises_without_auth_cookie():
|
|
439
|
+
client = _FakeApiClient([])
|
|
440
|
+
with pytest.raises(ValueError, match="auth"):
|
|
441
|
+
VRChatWebSocket.from_client(client)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def test_get_auth_cookie_returns_none_without_rest_client():
|
|
445
|
+
assert get_auth_cookie(SimpleNamespace()) is None
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def test_constructor_requires_auth_token():
|
|
449
|
+
with pytest.raises(ValueError, match="auth_token"):
|
|
450
|
+
VRChatWebSocket(auth_token="")
|