--[[ ================================================================================ BloxList — In-game like reward bridge (server-only script) ================================================================================ Copyright --------- © BloxList. All rights reserved. This script is sample/reference code for connecting a Roblox experience to the BloxList website you use or operate. You may modify and ship it with your game. BloxList is not affiliated with Roblox Corporation. -------------------------------------------------------------------------------- What this script does -------------------------------------------------------------------------------- 1) When someone likes your game on the BloxList website, BloxList’s servers call Roblox Open Cloud to publish a MessagingService message to your universe. 2) This script subscribes to that channel using CONFIG.TOPIC (must match the topic configured on your BloxList listing). The payload is JSON with userId, likeId, nickname, UTC day, timestamps, and an optional HMAC signature. 3) If that player is in this server right now, grantBloxListLikeReward runs immediately. 4) If they are offline, the event is queued in DataStore and delivered the next time they join. Duplicate likeId values are not queued twice; UpdateAsync helps when many servers receive the same broadcast. -------------------------------------------------------------------------------- Creator checklist (BloxList + Roblox) -------------------------------------------------------------------------------- [ ] Your game is listed and approved on BloxList. [ ] You created a Roblox Open Cloud API key with scope: universe-messaging-service:publish for this experience’s universe (Creator Dashboard → Credentials → API keys). [ ] On BloxList, open your game’s detail page (owner-only), enable in-game integration, and paste the API key. [ ] CONFIG.TOPIC here matches the topic on BloxList exactly (default: BloxListLike). [ ] Optional: set a signing secret on BloxList and verify data.sig in your game. -------------------------------------------------------------------------------- Install (Roblox Studio) -------------------------------------------------------------------------------- • Create a Script under ServerScriptService (not a LocalScript). • Paste this entire file into that Script. • Tune CONFIG (topic, DataStore name, demo reward). • Replace or extend grantBloxListLikeReward for your economy. Studio DataStore testing: Home → Game Settings → Security → enable “Enable Studio Access to API Services”. -------------------------------------------------------------------------------- Payload fields (message.Data → JSON) -------------------------------------------------------------------------------- v number Schema version (currently 1) type string Always "bloxlist_like" userId string Roblox user id as a string placeId string Start place id registered on BloxList likeId string Unique BloxList like id (dedupe key) claimedDay string UTC date "YYYY-MM-DD" nickname string Display name entered on BloxList for that day ts number Unix time (seconds) sig string? Optional HMAC-SHA256 hex if signing secret is set HMAC canonical string (must match BloxList server): "v1|bloxlist_like|{userId}|{placeId}|{likeId}|{claimedDay}|{ts}" -------------------------------------------------------------------------------- Notes -------------------------------------------------------------------------------- • MessagingService messages are size-limited; payloads stay small. • Respect DataStore limits at very large scale (consider MemoryStore patterns). • If grantBloxListLikeReward errors, that queue entry is retried on next join. ================================================================================ ]] -------------------------------------------------------------------------------- -- CONFIG — edit for your game (TOPIC must match BloxList listing settings) -------------------------------------------------------------------------------- local CONFIG = { TOPIC = "BloxListLike", PENDING_DATASTORE_NAME = "BloxList_PendingLikes_v1", --[[ Demo reward: adds to an IntValue under leaderstats (common tutorial pattern). • Set ENABLE_DEMO_REWARD = false to only print and skip currency. • Rename DEMO_STAT_NAME to match your game (Cash, Money, Coins, etc.). • DEMO_REWARD_AMOUNT is in your in-game currency units (e.g. 10000). ]] ENABLE_DEMO_REWARD = true, DEMO_STAT_NAME = "Cash", DEMO_REWARD_AMOUNT = 10000, } -------------------------------------------------------------------------------- -- Services -------------------------------------------------------------------------------- local MessagingService = game:GetService("MessagingService") local HttpService = game:GetService("HttpService") local Players = game:GetService("Players") local DataStoreService = game:GetService("DataStoreService") local pendingStore = DataStoreService:GetDataStore(CONFIG.PENDING_DATASTORE_NAME) -------------------------------------------------------------------------------- -- DataStore queue helpers -------------------------------------------------------------------------------- local function pendingKeyForUser(userId) return "u_" .. tostring(userId) end local function normalizeDoc(val) if type(val) ~= "table" then return { items = {} } end if type(val.items) == "table" then return { items = val.items } end if val[1] ~= nil then return { items = val } end return { items = {} } end local function hasLikeId(items, likeId) for _, e in ipairs(items) do if e.likeId == likeId then return true end end return false end local function sortByTs(items) table.sort(items, function(a, b) return (a.ts or 0) < (b.ts or 0) end) end local function enqueuePending(userId, payload) local likeId = tostring(payload.likeId or "") if likeId == "" then return end local entry = { likeId = likeId, claimedDay = tostring(payload.claimedDay or ""), ts = tonumber(payload.ts) or 0, nickname = tostring(payload.nickname or ""), placeId = tostring(payload.placeId or ""), } local key = pendingKeyForUser(userId) local ok, err = pcall(function() pendingStore:UpdateAsync(key, function(old) local doc = normalizeDoc(old) local items = doc.items if hasLikeId(items, likeId) then return old end table.insert(items, entry) sortByTs(items) return { items = items, v = 1 } end) end) if not ok then warn("[BloxList] enqueue UpdateAsync failed:", err) end end -------------------------------------------------------------------------------- -- Demo: leaderstats IntValue (remove or replace in production) -------------------------------------------------------------------------------- local function getOrCreateLeaderstatInt(player, statName, initialIfCreated) local ls = player:FindFirstChild("leaderstats") if not ls then ls = Instance.new("Folder") ls.Name = "leaderstats" ls.Parent = player end local stat = ls:FindFirstChild(statName) if not stat then stat = Instance.new("IntValue") stat.Name = statName stat.Value = initialIfCreated or 0 stat.Parent = ls end if not stat:IsA("IntValue") then warn("[BloxList] leaderstats.", statName, " is not IntValue; skip demo reward.") return nil end return stat end --[[ Main hook — `player` is always in this server. `payload` includes likeId, nickname, claimedDay, placeId, ts, type, etc. Called immediately on like if online, or on join after an offline like. ]] local function grantBloxListLikeReward(player, payload) if CONFIG.ENABLE_DEMO_REWARD then local stat = getOrCreateLeaderstatInt(player, CONFIG.DEMO_STAT_NAME, 0) if stat then stat.Value += CONFIG.DEMO_REWARD_AMOUNT end print( ("[BloxList] Demo reward: %s | +%d %s | likeId=%s | nickname=%s"):format( player.Name, CONFIG.DEMO_REWARD_AMOUNT, CONFIG.DEMO_STAT_NAME, tostring(payload.likeId), tostring(payload.nickname) ) ) else print( ("[BloxList] Like event (demo off): %s | likeId=%s — add your reward code here."):format( player.Name, tostring(payload.likeId) ) ) end end -------------------------------------------------------------------------------- -- Routing: online vs offline queue -------------------------------------------------------------------------------- local function onBloxListMessage(userId, data) local player = Players:GetPlayerByUserId(userId) if player then grantBloxListLikeReward(player, data) else enqueuePending(userId, data) end end local function flushPendingForPlayer(player) local uid = player.UserId local key = pendingKeyForUser(uid) local doc = nil local okGet, getErr = pcall(function() doc = pendingStore:GetAsync(key) end) if not okGet then warn("[BloxList] pending GetAsync failed:", getErr) return end local norm = normalizeDoc(doc) local items = norm.items if #items == 0 then return end local remaining = {} for _, entry in ipairs(items) do local payload = { type = "bloxlist_like", likeId = entry.likeId, claimedDay = entry.claimedDay, ts = entry.ts, nickname = entry.nickname, placeId = entry.placeId, } local okGrant = pcall(function() grantBloxListLikeReward(player, payload) end) if not okGrant then table.insert(remaining, entry) end end local okSet, setErr = pcall(function() if #remaining == 0 then pendingStore:RemoveAsync(key) else sortByTs(remaining) pendingStore:SetAsync(key, { items = remaining, v = 1 }) end end) if not okSet then warn("[BloxList] pending clear SetAsync failed:", setErr) end end Players.PlayerAdded:Connect(function(player) task.defer(function() flushPendingForPlayer(player) end) end) local subscribeOk, subscribeErr = pcall(function() MessagingService:SubscribeAsync(CONFIG.TOPIC, function(message) local decodeOk, data = pcall(function() return HttpService:JSONDecode(message.Data) end) if not decodeOk or type(data) ~= "table" then return end if data.type ~= "bloxlist_like" then return end local uid = tonumber(data.userId) if not uid then return end onBloxListMessage(uid, data) end) end) if not subscribeOk then warn("[BloxList] MessagingService SubscribeAsync failed:", subscribeErr) end