Gems/Twitch/Code/Source/TwitchSystemComponent.cpp (653 lines of code) (raw):

/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/EditContext.h> #include <AzCore/std/time.h> #include "TwitchSystemComponent.h" #include "TwitchReflection.h" #include <Twitch_Traits_Platform.h> namespace Twitch { TwitchSystemComponent::TwitchSystemComponent() : m_receiptCounter(0) { } void TwitchSystemComponent::OnSystemTick() { if( m_twitchREST != nullptr) m_twitchREST->FlushEvents(); } void TwitchSystemComponent::Reflect(AZ::ReflectContext* context) { if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context)) { serialize->Class<TwitchSystemComponent, AZ::Component>() ->Version(1); if (AZ::EditContext* ec = serialize->GetEditContext()) { ec->Class<TwitchSystemComponent>("Twitch", "Provides access to Twitch \"Friends\", \"Rich Presence\" APIs") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") // ->Attribute(AZ::Edit::Attributes::Category, "") Set a category ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; } } if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context)) { Internal::Reflect(*behaviorContext); } } void TwitchSystemComponent::SetApplicationID(const AZStd::string& twitchApplicationID) { /* ** THIS CAN ONLY BE SET ONCE!!!!!! */ if (m_applicationID.empty()) { if (IsValidTwitchAppID(twitchApplicationID)) { m_applicationID = twitchApplicationID; } else { AZ_Warning("TwitchSystemComponent::SetApplicationID", false, "Invalid Twitch Application ID! Request ignored!"); } } else { AZ_Warning("TwitchSystemComponent::SetApplicationID", false, "Twitch Application ID is already set! Request ignored!"); } } AZStd::string TwitchSystemComponent::GetApplicationID() const { return m_applicationID; } AZStd::string TwitchSystemComponent::GetSessionID() const { return m_cachedSessionID; } AZStd::string TwitchSystemComponent::GetUserID() const { return m_cachedClientID; } AZStd::string TwitchSystemComponent::GetOAuthToken() const { return m_cachedOAuthToken; } void TwitchSystemComponent::SetUserID(ReceiptID& receipt, const AZStd::string& userId) { // always return a receipt. receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::InvalidParam); if (IsValidFriendID(userId)) { m_cachedClientID = userId; rc = ResultCode::Success; } TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::UserIDNotify, StringValue(userId, receipt, rc)); } void TwitchSystemComponent::SetOAuthToken(ReceiptID& receipt, const AZStd::string& token) { // always return a receipt. receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::InvalidParam); if (IsValidOAuthToken(token)) { m_cachedOAuthToken = token; rc = ResultCode::Success; } TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::OAuthTokenNotify, StringValue(token, receipt, rc)); } void TwitchSystemComponent::RequestUserID(ReceiptID& receipt) { // always return a receipt. receipt.SetID(GetReceipt()); TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::UserIDNotify, StringValue(m_cachedClientID, receipt, ResultCode::Success)); } void TwitchSystemComponent::RequestOAuthToken(ReceiptID& receipt) { // always return a receipt. receipt.SetID(GetReceipt()); TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::OAuthTokenNotify, StringValue(m_cachedOAuthToken, receipt, ResultCode::Success)); } void TwitchSystemComponent::GetUser(ReceiptID& receipt) { receipt.SetID(GetReceipt()); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetUser, UserInfoValue(UserInfo(), receipt, ResultCode::TwitchRESTError)); } else { m_twitchREST->GetUser(receipt); } } void TwitchSystemComponent::ResetFriendsNotificationCount(ReceiptID& receipt, const AZStd::string& friendID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidFriendID(friendID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::ResetFriendsNotificationCountNotify, Int64Value(0, receipt, rc)); } else { m_twitchREST->ResetFriendsNotificationCount(receipt, friendID); } } void TwitchSystemComponent::GetFriendNotificationCount(ReceiptID& receipt, const AZStd::string& friendID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidFriendID(friendID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetFriendNotificationCount, Int64Value(0, receipt, rc)); } else { m_twitchREST->GetFriendNotificationCount(receipt, friendID); } } void TwitchSystemComponent::GetFriendRecommendations(ReceiptID& receipt, const AZStd::string& friendID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidFriendID(friendID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetFriendRecommendations, FriendRecommendationValue(FriendRecommendationList(), receipt, rc)); } else { m_twitchREST->GetFriendRecommendations(receipt, friendID); } } void TwitchSystemComponent::GetFriends(ReceiptID& receipt, const AZStd::string& friendID, const AZStd::string& cursor) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidFriendID(friendID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetFriends, GetFriendValue(GetFriendReturn(), receipt, rc)); } else { m_twitchREST->GetFriends(receipt, friendID, cursor); } } void TwitchSystemComponent::GetFriendStatus(ReceiptID& receipt, const AZStd::string& sourceFriendID, const AZStd::string& targetFriendID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if ((!sourceFriendID.empty() && !IsValidFriendID(sourceFriendID)) || !IsValidFriendID(targetFriendID)) { rc = ResultCode::InvalidParam; } if(rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetFriendStatus, FriendStatusValue(FriendStatus(), receipt, rc)); } else { m_twitchREST->GetFriendStatus(receipt, sourceFriendID, targetFriendID); } } void TwitchSystemComponent::AcceptFriendRequest(ReceiptID& receipt, const AZStd::string& friendID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedClientID.empty() || m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidFriendID(friendID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::AcceptFriendRequest, Int64Value(0, receipt, rc)); } else { m_twitchREST->AcceptFriendRequest(receipt, friendID); } } void TwitchSystemComponent::GetFriendRequests(ReceiptID& receipt, const AZStd::string& cursor) { receipt.SetID(GetReceipt()); if (m_cachedClientID.empty() || m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetFriendRequests, FriendRequestValue(FriendRequestResult(), receipt, ResultCode::TwitchRESTError)); } else { m_twitchREST->GetFriendRequests(receipt, cursor); } } void TwitchSystemComponent::CreateFriendRequest(ReceiptID& receipt, const AZStd::string& friendID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedClientID.empty() || m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidFriendID(friendID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::CreateFriendRequest, Int64Value(0, receipt, rc)); } else { m_twitchREST->CreateFriendRequest(receipt, friendID); } } void TwitchSystemComponent::DeclineFriendRequest(ReceiptID& receipt, const AZStd::string& friendID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedClientID.empty() || m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidFriendID(friendID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::DeclineFriendRequest, Int64Value(0, receipt, rc)); } else { m_twitchREST->DeclineFriendRequest(receipt, friendID); } } void TwitchSystemComponent::UpdatePresenceStatus(ReceiptID& receipt, PresenceAvailability availability, PresenceActivityType activityType, const AZStd::string& gameContext) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedClientID.empty() || m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if ((activityType == PresenceActivityType::Playing) && !m_twitchREST->IsValidGameContext(gameContext) ) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::UpdatePresenceStatus, Int64Value(0, receipt, rc)); } else { m_twitchREST->UpdatePresenceStatus(receipt, availability, activityType, gameContext); } } void TwitchSystemComponent::GetPresenceStatusofFriends(ReceiptID& receipt) { receipt.SetID(GetReceipt()); if (m_cachedClientID.empty() || m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetPresenceStatusofFriends, PresenceStatusValue(PresenceStatusList(), receipt, ResultCode::TwitchRESTError)); } else { m_twitchREST->GetPresenceStatusofFriends(receipt); } } void TwitchSystemComponent::GetPresenceSettings(ReceiptID& receipt) { receipt.SetID(GetReceipt()); if (m_cachedClientID.empty() || m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetPresenceSettings, PresenceSettingsValue(PresenceSettings(), receipt, ResultCode::TwitchRESTError)); } else { m_twitchREST->GetPresenceSettings(receipt); } } void TwitchSystemComponent::UpdatePresenceSettings(ReceiptID& receipt, bool isInvisible, bool shareActivity) { receipt.SetID(GetReceipt()); if (m_cachedClientID.empty() || m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::UpdatePresenceSettings, PresenceSettingsValue(PresenceSettings(), receipt, ResultCode::TwitchRESTError)); } else { m_twitchREST->UpdatePresenceSettings(receipt, isInvisible, shareActivity); } } void TwitchSystemComponent::GetChannel(ReceiptID& receipt) { receipt.SetID(GetReceipt()); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetChannel, ChannelInfoValue(ChannelInfo(), receipt, ResultCode::TwitchRESTError)); } else { m_twitchREST->GetChannel(receipt); } } void TwitchSystemComponent::GetChannelbyID(ReceiptID& receipt, const AZStd::string& channelID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetChannelbyID, ChannelInfoValue(ChannelInfo(), receipt, rc)); } else { m_twitchREST->GetChannelbyID(receipt, channelID); } } void TwitchSystemComponent::UpdateChannel(ReceiptID& receipt, const ChannelUpdateInfo & channelUpdateInfo) { receipt.SetID(GetReceipt()); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::UpdateChannel, ChannelInfoValue(ChannelInfo(), receipt, ResultCode::TwitchRESTError)); } else { m_twitchREST->UpdateChannel(receipt, channelUpdateInfo); } } void TwitchSystemComponent::GetChannelEditors(ReceiptID& receipt, const AZStd::string& channelID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetChannelEditors, UserInfoListValue(UserInfoList(), receipt, rc)); } else { m_twitchREST->GetChannelEditors(receipt, channelID); } } void TwitchSystemComponent::GetChannelFollowers(ReceiptID& receipt, const AZStd::string& channelID, const AZStd::string& cursor, AZ::u64 offset) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetChannelFollowers, FollowerResultValue(FollowerResult(), receipt, rc)); } else { m_twitchREST->GetChannelFollowers(receipt, channelID, cursor, offset); } } void TwitchSystemComponent::GetChannelTeams(ReceiptID& receipt, const AZStd::string& channelID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetChannelTeams, ChannelTeamValue(TeamInfoList(), receipt, rc)); } else { m_twitchREST->GetChannelTeams(receipt, channelID); } } void TwitchSystemComponent::GetChannelSubscribers(ReceiptID& receipt, const AZStd::string& channelID, AZ::u64 offset) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetChannelSubscribers, SubscriberValue(Subscription(), receipt, rc)); } else { m_twitchREST->GetChannelSubscribers(receipt, channelID, offset); } } void TwitchSystemComponent::CheckChannelSubscriptionbyUser(ReceiptID& receipt, const AZStd::string& channelID, const AZStd::string& userID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID) || !IsValidFriendID(userID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::CheckChannelSubscriptionbyUser, SubscriberbyUserValue(SubscriberInfo(), receipt, rc)); } else { m_twitchREST->CheckChannelSubscriptionbyUser(receipt, channelID, userID); } } void TwitchSystemComponent::GetChannelVideos(ReceiptID& receipt, const AZStd::string& channelID, BroadCastType boradcastType, const AZStd::string& language, AZ::u64 offset) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::GetChannelVideos, VideoReturnValue(VideoReturn(), receipt, rc)); } else { m_twitchREST->GetChannelVideos(receipt, channelID, boradcastType, language, offset); } } void TwitchSystemComponent::StartChannelCommercial(ReceiptID& receipt, const AZStd::string& channelID, CommercialLength length) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::StartChannelCommercial, StartChannelCommercialValue(StartChannelCommercialResult(), receipt, rc)); } else { m_twitchREST->StartChannelCommercial(receipt, channelID, length); } } void TwitchSystemComponent::ResetChannelStreamKey(ReceiptID& receipt, const AZStd::string& channelID) { receipt.SetID(GetReceipt()); ResultCode rc(ResultCode::Success); if (m_cachedOAuthToken.empty() || m_twitchREST == nullptr) { rc = ResultCode::TwitchRESTError; } else if (!IsValidChannelID(channelID)) { rc = ResultCode::InvalidParam; } if (rc != ResultCode::Success) { TwitchNotifyBus::QueueBroadcast(&TwitchNotifyBus::Events::ResetChannelStreamKey, ChannelInfoValue(ChannelInfo(), receipt, rc)); } else { m_twitchREST->ResetChannelStreamKey(receipt, channelID); } } void TwitchSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("TwitchService")); } void TwitchSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC("TwitchService")); } void TwitchSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) { (void)required; } void TwitchSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) { (void)dependent; } void TwitchSystemComponent::Init() { #if AZ_TRAIT_TWITCH_INITIALIZE_SDK // You must define the Twitch Application Client ID m_twitchREST = ITwitchREST::Alloc(); // each time we create a interface we need a new session id, however this should not change during the life span of this object. AZ::Uuid sessionid(AZ::Uuid::Create()); char temp[128]; sessionid.ToString(temp, 128, false, false); m_cachedSessionID = AZStd::string(temp); #endif // AZ_TRAIT_TWITCH_INITIALIZE_SDK } void TwitchSystemComponent::Activate() { TwitchRequestBus::Handler::BusConnect(); AZ::SystemTickBus::Handler::BusConnect(); } void TwitchSystemComponent::Deactivate() { AZ::SystemTickBus::Handler::BusDisconnect(); TwitchRequestBus::Handler::BusDisconnect(); } AZ::u64 TwitchSystemComponent::GetReceipt() { return ++m_receiptCounter; } bool TwitchSystemComponent::IsValidString(const AZStd::string& str, AZStd::size_t minLength, AZStd::size_t maxLength) const { bool isValid = false; /* ** From Twitch (Chris Adoretti <cadoretti@twitch.tv>) 3/14/17: ** I think its a safe bet though to make sure the string is alpha-numeric + dashes ** for now if that helps (0-9, a-f, A-F, -). We don't have a max length yet. ** The minimum length is 1. */ if ((str.size() >= minLength) && (str.size() < maxLength)) { AZStd::size_t found = str.find_first_not_of("0123456789abcdefABCDEF-"); if (found == AZStd::string::npos) { isValid = true; } } return isValid; } bool TwitchSystemComponent::IsValidTwitchAppID(const AZStd::string& twitchAppID) const { static const AZStd::size_t kMinIDLength = 24; // min id length static const AZStd::size_t kMaxIDLength = 64; // max id length bool isValid = false; if ((twitchAppID.size() >= kMinIDLength) && (twitchAppID.size() < kMaxIDLength)) { AZStd::size_t found = twitchAppID.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyz"); if (found == AZStd::string::npos) { isValid = true; } } return isValid; } bool TwitchSystemComponent::IsValidFriendID(const AZStd::string& friendID) const { // The min id length should be 1 // The max id length will be huge, since there is no official max length, this will allow for a large id. return IsValidString(friendID, 1, 256); } bool TwitchSystemComponent::IsValidOAuthToken(const AZStd::string& oAuthToken) const { // Twitch OAuth tokens are exactly length 30 if (oAuthToken.size() == 30) { AZStd::size_t found = oAuthToken.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyz"); if (found == AZStd::string::npos) { return true; } } return false; } bool TwitchSystemComponent::IsValidSyncToken(const AZStd::string& syncToken) const { // sync tokens are either empty of a opaque token of a min length of 8, but no longer than 64 return syncToken.empty() ? true : IsValidString(syncToken, 8, 64); } }