Clients/Client2018/content/internal/AppShell/Modules/Shell/FriendsData.lua

445 lines
14 KiB
Lua

--[[
// FriendsData.lua
// Caches the current friends pagination to used by anyone in the app
// TODO:
Need polling to update friends. How are we going to handle all the cases
like the person you're selecting going offline, etc..
]]
local CoreGui = game:GetService("CoreGui")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local FriendService = nil
pcall(function() FriendService = game:GetService('FriendService') end)
local ThirdPartyUserService = nil
pcall(function() ThirdPartyUserService = game:GetService('ThirdPartyUserService') end)
local isNotConsole = UserSettings().GameSettings:InStudioMode() or game:GetService('UserInputService'):GetPlatform() == Enum.Platform.Windows
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ShellModules = Modules:FindFirstChild("Shell")
local Http = require(ShellModules:FindFirstChild('Http'))
local Utility = require(ShellModules:FindFirstChild('Utility'))
local Analytics = require(ShellModules:FindFirstChild('Analytics'))
local EventHub = require(ShellModules:FindFirstChild('EventHub'))
local TableUtilities = require(Modules.LuaApp.TableUtilities)
local ReloaderManager = require(ShellModules:FindFirstChild('ReloaderManager'))
local MakeSafeAsync = require(ShellModules:FindFirstChild('SafeAsync'))
local AppState = require(ShellModules.AppState)
local ResetUserThumbnails = require(ShellModules.Actions.ResetUserThumbnails)
local SetFriendsData = require(ShellModules.Actions.SetFriendsData)
local SetRenderedFriendsData = require(ShellModules.Actions.SetRenderedFriendsData)
-- NOTE: This is just required for fixing Usernames in auto-generatd games
local GameData = require(ShellModules:FindFirstChild('GameData'))
local XboxRecommendedPeople = settings():GetFFlag("XboxRecommendedPeople2")
local ConvertMyPlaceNameInXboxAppFlag = settings():GetFFlag("ConvertMyPlaceNameInXboxApp")
local FriendsData = {}
local function OnUserAccountChanged()
FriendsData.Setup()
end
EventHub:addEventListener(EventHub.Notifications["AuthenticationSuccess"], "FriendsData", OnUserAccountChanged)
if ThirdPartyUserService then
ThirdPartyUserService.ActiveUserSignedOut:connect(function()
FriendsData.Reset()
end)
end
local isOnlineFriendsPolling = false
local isFriendEventsConnected = false
local renderedFriendsUpdateSuspended = false
local cachedFriendsData = nil
local cachedFriendsDataMap = {}
local friendsDataConns = {}
local function filterFriends(friendsData)
if isNotConsole or XboxRecommendedPeople then
for i = 1, #friendsData do
local data = friendsData[i]
if data.gamertag == "" then
data.gamertag = nil
end
if data.robloxuid <= 0 then
data.robloxuid = nil
end
if data.xuid == "" then
data.xuid = nil
end
if data.placeId == 0 then
data.placeId = nil
end
if data.lastLocation == "" then
data.lastLocation = nil
end
if data.robloxName == "" then
data.robloxName = nil
end
local placeId = data.placeId
local lastLocation = data.lastLocation
-- If the lastLocation for a user is some user place with a GeneratedUsername in it
-- then replace it with the actual creator name!
if ConvertMyPlaceNameInXboxAppFlag and placeId and lastLocation and GameData:ExtractGeneratedUsername(lastLocation) then
local gameCreator = GameData:GetGameCreatorAsync(placeId)
if gameCreator then
lastLocation = GameData:GetFilteredGameName(lastLocation, gameCreator)
end
end
data.placeId = placeId
data.lastLocation = lastLocation
end
else
for i = 1, #friendsData do
local data = friendsData[i]
if data["PlaceId"] == 0 then
data["PlaceId"] = nil
end
if data["LastLocation"] == "" then
data["LastLocation"] = nil
end
if data["RobloxName"] == "" then
data["RobloxName"] = nil
end
local placeId = data["PlaceId"]
local lastLocation = data["LastLocation"]
-- If the lastLocation for a user is some user place with a GeneratedUsername in it
-- then replace it with the actual creator name!
if ConvertMyPlaceNameInXboxAppFlag and placeId and lastLocation and GameData:ExtractGeneratedUsername(lastLocation) then
local gameCreator = GameData:GetGameCreatorAsync(placeId)
if gameCreator then
lastLocation = GameData:GetFilteredGameName(lastLocation, gameCreator)
end
end
data["PlaceId"] = placeId
data["LastLocation"] = lastLocation
end
end
return friendsData
end
--TODO Remove when remove FFlag XboxRecommendedPeople2
local function sortFriendsData(tempFriendsData)
table.sort(tempFriendsData, function(a, b)
if a["PlaceId"] and b["PlaceId"] then
return a["display"] < b["display"]
end
if a["PlaceId"] then
return true
end
if b["PlaceId"] then
return false
end
return a["display"] < b["display"]
end)
end
--TODO Remove when remove FFlag XboxRecommendedPeople2
local function uploadFriendsAnalytics(friendsData)
if friendsData then
local numPlaying = 0
for i, data in pairs(friendsData) do
if data["PlaceId"] then
numPlaying = numPlaying + 1
end
end
Analytics.UpdateHeartbeatObject({
FriendsPlaying = numPlaying;
FriendsOnline = #friendsData;
});
end
end
-- Combine roblox uid and xbox id into a unique id that exists for all roblox and/or xbox friends
local function uniqueId(friendEntry)
return tostring(friendEntry.robloxuid) .. "#" .. tostring(friendEntry.xuid)
end
-- Marks which friends have been updated or added in the newFriendsData table
local function diffFriends(oldFriendsData, newFriendsData)
-- Build lookup table of old friends using unique id
local oldFriendLookup = {}
if oldFriendsData and type(oldFriendsData) == "table" then
for _, friendEntry in pairs(oldFriendsData) do
oldFriendLookup[uniqueId(friendEntry)] = friendEntry
end
end
-- Check if any friends in new table have been updated
if newFriendsData and type(newFriendsData) == "table" then
for _, newFriendEntry in pairs(newFriendsData) do
local oldFriendEntry = oldFriendLookup[uniqueId(newFriendEntry)]
if oldFriendEntry then
local isUpdated = false
for friendFieldKey, friendFieldValue in pairs(newFriendEntry) do
if friendFieldValue ~= oldFriendEntry[friendFieldKey] then
isUpdated = true
break
end
end
newFriendEntry.isUpdated = isUpdated
else
newFriendEntry.isUpdated = true
end
end
end
end
--[[ Public API ]]--
FriendsData.OnFriendsDataUpdated = Utility.Signal()
--TODO Remove when remove FFlag XboxRecommendedPeople2
local function processNewFriendsData(newFriendsData)
local myOnlineFriends = {}
if newFriendsData then
myOnlineFriends = filterFriends(newFriendsData)
sortFriendsData(myOnlineFriends)
end
return myOnlineFriends
end
function FriendsData.GetOnlineFriendsAsync()
if not cachedFriendsData then
--Wait until we get cachedFriendsData from FriendService/FriendEvents disconnect(user sign out)
while isFriendEventsConnected and not cachedFriendsData do
wait()
end
end
return cachedFriendsData or {}
end
-- we make connections through this function so we can clean them all up upon
-- clearing the friends data
function FriendsData.ConnectUpdateEvent(cbFunc)
local cn = FriendsData.OnFriendsDataUpdated:connect(cbFunc)
table.insert(friendsDataConns, cn)
end
function FriendsData.Reset()
isOnlineFriendsPolling = false
for index,cn in pairs(friendsDataConns) do
cn = Utility.DisconnectEvent(cn)
friendsDataConns[index] = nil
end
isFriendEventsConnected = false
cachedFriendsData = nil
cachedFriendsDataMap = {}
if XboxRecommendedPeople then
AppState.store:dispatch(ResetUserThumbnails())
AppState.store:dispatch(SetFriendsData())
AppState.store:dispatch(SetRenderedFriendsData())
renderedFriendsUpdateSuspended = false
end
if isNotConsole then
ReloaderManager:removeReloader("FriendsData")
FriendsData.ReloaderFuncId = nil
end
end
local function CheckEntryUpdate(newFriendsData)
local validEntries = {}
for i = 1, #newFriendsData do
local data = newFriendsData[i]
local xuid = data.xuid or ""
local robloxuid = data.robloxuid or ""
local idStr = tostring(xuid.."#"..robloxuid)
validEntries[idStr] = true
if cachedFriendsDataMap[idStr] then --check whether entry changed
local differentAttributes = TableUtilities.TableDifference(cachedFriendsDataMap[idStr], data)
if next(differentAttributes) ~= nil then
data.isUpdated = true
end
end
cachedFriendsDataMap[idStr] = data
end
for idStr in pairs(cachedFriendsDataMap) do
if not validEntries[idStr] then
cachedFriendsDataMap[idStr] = nil
end
end
end
local UpdateRenderedFriendsData;
if XboxRecommendedPeople then
UpdateRenderedFriendsData = function(newFriendsData)
AppState.store:dispatch(SetRenderedFriendsData(newFriendsData))
--data for HomePane friend scroller, also from store, but need the CheckEntryUpdate
CheckEntryUpdate(newFriendsData)
cachedFriendsData = newFriendsData
FriendsData.OnFriendsDataUpdated:fire(newFriendsData)
end
function FriendsData:SuspendUpdate()
renderedFriendsUpdateSuspended = true
end
function FriendsData:ResumeUpdate()
--Get latest data from store when ResumeUpdate(), don't dispatch if the friends data hasn't been fetched yet
if AppState.store:getState().Friends.initialized then
UpdateRenderedFriendsData(AppState.store:getState().Friends.data)
end
renderedFriendsUpdateSuspended = false
end
end
function FriendsData.Setup()
FriendsData.Reset()
--We make the conns once user logged in, and once we get the cachedFriendsData from FriendService
--this func becomes sync call
if PlatformService and FriendService then
--Connect FriendsUpdated event to get newFriendsData at intervals
table.insert(friendsDataConns, FriendService.FriendsUpdated:connect(function(newFriendsData)
if not XboxRecommendedPeople then
cachedFriendsData = processNewFriendsData(newFriendsData)
uploadFriendsAnalytics(cachedFriendsData)
FriendsData.OnFriendsDataUpdated:fire(cachedFriendsData)
else
newFriendsData = filterFriends(newFriendsData)
AppState.store:dispatch(SetFriendsData(newFriendsData))
if not AppState.store:getState().RenderedFriends.initialized or not renderedFriendsUpdateSuspended then
UpdateRenderedFriendsData(newFriendsData)
end
end
end))
isFriendEventsConnected = true
--Try to get the cachedFriendsData, check if the friends data has been fetched on Friend Service
local success, result = pcall(function()
return FriendService:GetPlatformFriends()
end)
if success then
if not XboxRecommendedPeople then
cachedFriendsData = processNewFriendsData(result)
else
result = filterFriends(result)
AppState.store:dispatch(SetFriendsData(result))
if not AppState.store:getState().RenderedFriends.initialized or not renderedFriendsUpdateSuspended then
UpdateRenderedFriendsData(result)
end
end
end
else
if isNotConsole then
local POLL_DELAY = 30
local GetRecommendPeopleInStudio = MakeSafeAsync({
asyncFunc = function()
local finalRecommendedUsers = {}
local result = Http.GetRecommendedUsersndsAsync()
if result and result["recommendedUsers"] then
local recommendedUsersMap = {}
local recommendedUsers = result["recommendedUsers"]
local userIds = {}
for i = 1, #recommendedUsers do
local data = recommendedUsers[i]
local robloxuid = data.userId
if robloxuid then
table.insert(userIds, robloxuid)
recommendedUsersMap[robloxuid] =
{
robloxuid = robloxuid,
robloxName = data.userName
}
end
end
local presenceInfo = Http.GetUsersPresenceAsync(userIds)
if presenceInfo and presenceInfo["userPresences"] then
for _, presence in ipairs(presenceInfo["userPresences"]) do
local robloxuid = presence.userId
if robloxuid and recommendedUsersMap[robloxuid] then
recommendedUsersMap[robloxuid].placeId = presence.rootPlaceId
recommendedUsersMap[robloxuid].lastLocation = presence.lastLocation
recommendedUsersMap[robloxuid].friendsSource = "Roblox"
local robloxStatus = "Offline"
local rank = 4
if presence.userPresenceType == 1 then
robloxStatus = "Online"
rank = 3
elseif presence.userPresenceType == 2 then
if presence.rootPlaceId and presence.rootPlaceId > 0 then
robloxStatus = "InGame"
rank = 1
else
robloxStatus = "Online"
rank = 3
end
elseif presence.userPresenceType == 3 then
robloxStatus = "InStudio"
rank = 2
end
recommendedUsersMap[robloxuid].robloxStatus = robloxStatus
recommendedUsersMap[robloxuid].rank = rank
table.insert(finalRecommendedUsers, recommendedUsersMap[robloxuid])
end
end
end
local function sortFunc(a, b)
if a.rank == b.rank then
return a.robloxName:lower() < b.robloxName:lower()
end
return a.rank < b.rank
end
table.sort(finalRecommendedUsers, sortFunc)
end
return finalRecommendedUsers
end,
callback = function(finalRecommendedUsers)
finalRecommendedUsers = filterFriends(finalRecommendedUsers)
if not XboxRecommendedPeople then
CheckEntryUpdate(finalRecommendedUsers)
cachedFriendsData = finalRecommendedUsers
FriendsData.OnFriendsDataUpdated:fire(cachedFriendsData)
else
finalRecommendedUsers = filterFriends(finalRecommendedUsers)
AppState.store:dispatch(SetFriendsData(finalRecommendedUsers))
if not AppState.store:getState().RenderedFriends.initialized or not renderedFriendsUpdateSuspended then
UpdateRenderedFriendsData(finalRecommendedUsers)
end
end
end,
userRelated = true
})
if not isOnlineFriendsPolling then
isOnlineFriendsPolling = true
isFriendEventsConnected = true
spawn(function()
ReloaderManager:removeReloader("FriendsData")
FriendsData.ReloaderFuncId = ReloaderManager:addReloaderFunc("FriendsData", function() GetRecommendPeopleInStudio() end, POLL_DELAY, true)
ReloaderManager:callReloaderFunc("FriendsData", FriendsData.ReloaderFuncId)
end)
end
end
end
end
return FriendsData