445 lines
14 KiB
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
|