Created
July 11, 2025 10:28
-
-
Save BarakChamo/5f4de267a7b6372a37003f714e6ff92c to your computer and use it in GitHub Desktop.
Bounce Play Network Activity Tracker - v 1.5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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