Skip to content

Instantly share code, notes, and snippets.

@BarakChamo
Created July 11, 2025 10:28
Show Gist options
  • Save BarakChamo/5f4de267a7b6372a37003f714e6ff92c to your computer and use it in GitHub Desktop.
Save BarakChamo/5f4de267a7b6372a37003f714e6ff92c to your computer and use it in GitHub Desktop.
Bounce Play Network Activity Tracker - v 1.5
--[[
Bounce Network Measurement Script
Version: 1.5.0
Sends events to the Boune Network when players join or leave the experience (live game only for join/leave events).
Sends a verification event when run in Studio.
Sets a global variable _G.BOUNCE_TRACKER_VERSION.
Setup:
1. Place this script inside ServerScriptService in your Roblox experience.
2. Enable HttpService requests for your experience:
- In Roblox Studio, go to Game Settings (Home tab).
- Navigate to the Security section.
- Ensure "Allow HTTP Requests" is toggled ON. (Script now checks for this and halts if disabled)
3. Configure the `API_KEY` variable below with the key provided by your DSP network.
Notes on Data:
- This data is collected for diagnostic purposes only, to match qualifying experiences with opportunities, and is never shared or resold.
- Player join/leave events are only active in a live game, not in Studio.
--]]
-- Services
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local RunService = game:GetService("RunService")
local LocalizationService = game:GetService("LocalizationService")
local PolicyService = game:GetService("PolicyService")
-- Configuration
local ENDPOINT_URL = "https://api.letsbounce.gg/v1/track"
local API_KEY = "YOUR_API_KEY_HERE" -- <<< IMPORTANT: Replace with your actual API key
-- Script Identity
local SCRIPT_NAME = "BounceNetworkTracker"
local SCRIPT_VERSION = "1.5.0"
_G.BOUNCE_TRACKER_VERSION = SCRIPT_VERSION -- Set global variable for script version (used by Bounce Roblox Studio Plugin)
-- Session ID Management
local userSessionMap = {} -- Maps UserId to SessionId
--[[
Initial Checks - CRITICAL
--]]
if not RunService:IsServer() then
warn(SCRIPT_NAME .. " v" .. SCRIPT_VERSION .. ": This script must run on the server. Halting execution. Context: " .. tostring(RunService:GetCurrentContext()))
return
end
--print(SCRIPT_NAME .. " v" .. SCRIPT_VERSION .. ": Initializing on the server. Global _G.BOUNCE_TRACKER_VERSION set to " .. _G.BOUNCE_TRACKER_VERSION)
if not HttpService.HttpEnabled then
warn(SCRIPT_NAME .. " v" .. SCRIPT_VERSION .. ": HttpService.HttpEnabled is FALSE. Events cannot be sent. Please enable 'Allow HTTP Requests' in Game Settings > Security. Script will now terminate.")
return
end
if API_KEY == "YOUR_API_KEY_HERE" or API_KEY == "" then
warn(SCRIPT_NAME .. " v" .. SCRIPT_VERSION .. ": API_KEY is not configured. Requests might be unauthorized.")
-- Script continues, but sendEvent will fail if API_KEY is still default/empty.
end
--[[
Helper function to generate a random session ID
--]]
local function generateSessionId()
-- Generate a random GUID-like string
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
local sessionId = template:gsub("[xy]", function(c)
local v = (c == "x") and math.random(0, 0xf) or math.random(8, 0xb)
return string.format("%x", v)
end)
return sessionId
end
--[[
Helper function to get or create a session ID for a user
--]]
local function getSessionId(userId)
if not userSessionMap[userId] then
userSessionMap[userId] = generateSessionId()
end
return userSessionMap[userId]
end
--[[
Helper function to remove a user from the session map
--]]
local function removeUserSession(userId)
if userSessionMap[userId] then
userSessionMap[userId] = nil
end
end
--[[
Helper function to send events via HTTP POST
--]]
local function sendEvent(eventData)
if API_KEY == "YOUR_API_KEY_HERE" or API_KEY == "" then
warn(SCRIPT_NAME .. ": Cannot send event, API_KEY is not configured. Event Type: ", eventData.eventType or "N/A")
return
end
local encodedData
local success, result = pcall(function()
encodedData = HttpService:JSONEncode(eventData)
end)
if not success then
warn(SCRIPT_NAME .. ": Failed to JSON encode event data - Error: ", result, "Data: ", eventData)
return
end
local headers = {
["X-API-Key"] = API_KEY
}
-- Perform the HTTP request in a new thread to avoid yielding
task.spawn(function()
local httpSuccess, httpResult = pcall(function()
return HttpService:PostAsync(ENDPOINT_URL, encodedData, Enum.HttpContentType.ApplicationJson, false, headers)
end)
if not httpSuccess then
warn(SCRIPT_NAME .. ": HTTP POST request failed - Error: ", httpResult, "Event Type: ", eventData.eventType or "N/A")
elseif RunService:IsStudio() and eventData.eventType == "trackerVerified" then
-- Log successful verification event send in Studio
print(SCRIPT_NAME .. ": Event '" .. eventData.eventType .. "' sent successfully from Studio.")
end
end)
end
--[[
Handles player joining (Live Game Only)
--]]
local function onPlayerAdded(player)
task.wait(0.5) -- Brief delay for data replication
local playerId = player.UserId
local sessionId = getSessionId(playerId) -- Get or create session ID
local serverInstanceId = game.JobId
local gameId = game.GameId
local placeId = game.PlaceId
local playerCountryRegion = "Unknown"
local regionSuccess, regionResult = pcall(function()
return player:GetCountryRegionForPlayerAsync()
end)
if regionSuccess and regionResult and type(regionResult) == "string" and regionResult ~= "" then
playerCountryRegion = regionResult
else
local getCountryRegionError = regionResult
local fallbackLocale = player.LocaleId
if fallbackLocale and type(fallbackLocale) == "string" and fallbackLocale ~= "" then
playerCountryRegion = fallbackLocale
warn(SCRIPT_NAME .. ": Could not get player country region for " .. playerId .. ". Error: " .. tostring(getCountryRegionError) .. ". Falling back to player's LocaleId: " .. fallbackLocale)
else
playerCountryRegion = LocalizationService.RobloxLocaleId
warn(SCRIPT_NAME .. ": Could not get player country region for " .. playerId .. " (Error: " .. tostring(getCountryRegionError) .. ") AND player.LocaleId was invalid (Value: " .. tostring(fallbackLocale) .. "). Falling back to server's RobloxLocaleId: " .. playerCountryRegion)
end
end
local isLikelyOver13 = false
local policySuccess, policyInfo = pcall(function()
return PolicyService:GetPolicyInfoForPlayerAsync(player)
end)
if policySuccess and policyInfo then
isLikelyOver13 = policyInfo.AreAdsAllowed
else
warn(SCRIPT_NAME .. ": Failed to get PolicyInfo for player " .. playerId .. ". Error: " .. tostring(policyInfo))
end
local joinEventData = {
eventType = "playerJoined",
sessionId = sessionId, -- Use session ID instead of hashed user ID
serverInstanceId = serverInstanceId,
gameId = gameId,
placeId = placeId,
playerCountryRegion = playerCountryRegion,
isLikelyOver13 = isLikelyOver13,
timestamp = DateTime.now():ToIsoDate()
}
sendEvent(joinEventData)
end
--[[
Handles player leaving (Live Game Only)
--]]
local function onPlayerRemoving(player)
local playerId = player.UserId
local sessionId = getSessionId(playerId) -- Get the existing session ID
local serverInstanceId = game.JobId
local gameId = game.GameId
local placeId = game.PlaceId
local leaveEventData = {
eventType = "playerLeft",
sessionId = sessionId, -- Use session ID instead of hashed user ID
serverInstanceId = serverInstanceId,
gameId = gameId,
placeId = placeId,
timestamp = DateTime.now():ToIsoDate()
}
sendEvent(leaveEventData)
-- Remove the user from the session map to keep table size manageable
removeUserSession(playerId)
end
--[[
Environment-Specific Logic: Studio Verification OR Live Game Event Tracking
--]]
if RunService:IsStudio() then
-- Studio-Only Verification Event
--print(SCRIPT_NAME .. " v" .. SCRIPT_VERSION .. ": Studio detected. Sending verification event.")
local verificationEventData = {
eventType = "trackerVerified",
serverInstanceId = game.JobId,
gameId = game.GameId,
placeId = game.PlaceId,
scriptVersion = SCRIPT_VERSION,
timestamp = DateTime.now():ToIsoDate(),
message = SCRIPT_NAME .. " script initialized successfully in Studio."
}
sendEvent(verificationEventData)
else
-- Live Game Event Connections and Existing Player Handling
print(SCRIPT_NAME .. " v" .. SCRIPT_VERSION .. ": Live game detected. Connecting player events.")
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)
-- Handle players already in the game when the script starts
for _, playerInGame in ipairs(Players:GetPlayers()) do
print(SCRIPT_NAME .. ": Processing existing player in live game: " .. playerInGame.Name .. " (UserId: " .. playerInGame.UserId .. ")")
task.spawn(onPlayerAdded, playerInGame)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment