Modules for a Donation Game

for getting the gamepasses and catalog assets that a player has created.

local https = game:GetService("HttpService")

return function(plr)
	local ALL = {}
	local plrname = plr.Name
	local userid = plr.UserId
	local tshirturl = "https://catalog.roproxy.com/v1/search/items/details?Category=3&Subcategory=55&CreatorName="..plrname.."&salesTypeFilter=1&SortType=3"
	local shirturl =  "https://catalog.roproxy.com/v1/search/items/details?Category=3&Subcategory=56&CreatorName="..plrname.."&salesTypeFilter=1&SortType=3"
	local panturl =   "https://catalog.roproxy.com/v1/search/items/details?Category=3&Subcategory=57&CreatorName="..plrname.."&salesTypeFilter=1&SortType=3"


	local loaded = 0

	spawn(function()
		print("Get")
		local response = https:GetAsync(tshirturl)
		warn(tshirturl)

		local data = https:JSONDecode(response)
		local items = data["data"]


		for i,v in ipairs(items) do
			if v["price"] then
				if v["price"] > 0 then
					table.insert(ALL, {v["id"],v["price"],false})
				end
			end
		end
		loaded += 1
	end)

	spawn(function()
		print("Get")
		local response = https:GetAsync(shirturl)
		warn(shirturl)

		local data = https:JSONDecode(response)
		local items = data["data"]

		for i,v in ipairs(items) do
			if v["price"] then
				if v["price"] > 0 then
					table.insert(ALL, {v["id"],v["price"],false})
				end
			end
		end
		loaded += 1
	end)

	spawn(function()
		print("Get")
		local response = https:GetAsync(panturl)
		warn(panturl)

		local data = https:JSONDecode(response)
		local items = data["data"]

		for i,v in ipairs(items) do
			if v["price"] then
				if v["price"] > 0 then
					table.insert(ALL, {v["id"],v["price"],false})
				end
			end
		end

		loaded += 1
	end)


	spawn(function() --gamepasses

		local games = {}

		local gameurl = "https://games.roproxy.com/v2/users/"..userid.."/games?accessFilter=2&limit=50&sortOrder=Desc"
		local cur = ""



		while true do
			print("Get")
			local response = https:GetAsync(gameurl.."&cursor="..cur)
			warn(gameurl.."&cursor="..cur)
			local data = https:JSONDecode(response)
			local items = data["data"]
			local cur = data["nextPageCursor"]

			for i,v in ipairs(items) do
				table.insert(games,v["id"])
			end
			if not cur then
				break
			end
		end
		
		local w = 0

		for i,v in ipairs(games) do
			spawn(function()
				local url = "https://games.roproxy.com/v1/games/"..v.."/game-passes?limit=100&sortOrder=Asc"
				print("Get")
				local response = https:GetAsync(url)
				warn(url)
				local data = https:JSONDecode(response)

				local items = data["data"]

				for i,v in ipairs(items) do

					if v["price"] then
						table.insert(ALL, {v["id"],v["price"],true})
					end

				end
				w += 1
			end)
		end
		while w ~= #games do
			task.wait()
		end
		loaded += 1
	end)
	
	while loaded ~= 4 do
		task.wait()
	end
	
	return ALL
end

For handling purchases and preventing exploits where people change the price right before buying to exploit there donation amounts

local module = {}

local c = {}

-- gamepasses
function module.PromptGamepassPurchase(plr,id) --returns the robux spent when the purchase has finsihed, if the player didnt purchase it returns nil
	if game:GetService("RunService"):IsClient() then
		error("you must use DonationGameService from the server!")
	end
	
	if table.find(c,plr) then
		warn("already open")
		return
	end

	table.insert(c,plr)
	local prices = {}
	local processing = true
	local toreturn = nil
	
	
	spawn(function()
		while processing do
			local price = game:GetService("MarketplaceService"):GetProductInfo(id,Enum.InfoType.GamePass)["PriceInRobux"]
			table.insert(prices,price)
			--print(price)
			task.wait(0.5)
		end
	end)
	
	
	game:GetService("MarketplaceService"):PromptGamePassPurchase(plr,id)


	local a = game:GetService("MarketplaceService").PromptGamePassPurchaseFinished:Connect(function(plr1,finalid,wasbought)
		if plr1 ~= plr then
			return
		end
		if not wasbought then
			processing = false
			table.remove(c,table.find(c,plr))
			return
		end
		if finalid ~= id then
			return
		end
		
		local didchange = false
		local pricetheyspent

		for i,v in ipairs(prices) do
			for i,v1 in ipairs(prices) do
				if v ~= v1 then
					didchange = true
				end
			end
		end

		if didchange then
			local lowest = math.huge
			for i,v in ipairs(prices) do
				if lowest > v then
					lowest = v
				end
			end
			pricetheyspent = lowest
		else
			pricetheyspent = prices[1]
		end
		
		
		processing = false
		
		toreturn = pricetheyspent
	end)


	while processing do
		wait()
	end
	table.remove(c,table.find(c,plr))
	a:Disconnect()
	
	return toreturn
end

--eg shirts, pants, tshirts
function module.PromptAssetPurchase(plr,id) --returns the robux spent when the purchase has finsihed, if the player didnt purchase it returns nil
	if game:GetService("RunService"):IsClient() then
		error("you must use DonationGameService from the server!")
	end
	
	if table.find(c,plr) then
		warn("already open")
		return
	end

	table.insert(c,plr)
	local prices = {}
	local processing = true
	local toreturn = nil


	spawn(function()
		while processing do
			local price = game:GetService("MarketplaceService"):GetProductInfo(id,Enum.InfoType.Asset)["PriceInRobux"]
			table.insert(prices,price)
			--print(price)
			task.wait(0.5)
		end
	end)

	game:GetService("MarketplaceService"):PromptPurchase(plr,id)


	local a = game:GetService("MarketplaceService").PromptPurchaseFinished:Connect(function(plr1,finalid,wasbought)
		if plr1 ~= plr then
			return
		end
		if not wasbought then
			processing = false
			table.remove(c,table.find(c,plr))
			return
		end
		if finalid ~= id then
			return
		end

		local didchange = false
		local pricetheyspent

		for i,v in ipairs(prices) do
			for i,v1 in ipairs(prices) do
				if v ~= v1 then
					didchange = true
				end
			end
		end

		if didchange then
			local lowest = math.huge
			for i,v in ipairs(prices) do
				if lowest > v then
					lowest = v
				end
			end
			pricetheyspent = lowest
		else
			pricetheyspent = prices[1]
		end



		processing = false

		toreturn = pricetheyspent
	end)


	while processing do
		wait()
	end
	table.remove(c,table.find(c,plr))
	a:Disconnect()

	return toreturn
end

return module
1 Like

ld recommend you make sure your code is OSS worthy before posting it onto the devforums, you are using spawn instead of task.spawn, indexing tables like v["price"] instead of v.price and using wait even though you used task.wait in other places :sob:

it would also be helpful if you did not name your variebles 1 letter like “c”, my dms are open if youd like to message me on discord for some advice

3 Likes

nah its good bro wait just makes it a bit slower

its not. open source code on the devforums should set a standard for scripting on roblox. this does not do that

2 Likes

v[“string”] is perfectly acceptable.

even if no one is uses this, no one should be bashing him

1 Like

Looks like it’s AI generated considering the inconsistent writing

1 Like
  1. Your code inconsistently uses the up-to-date task library, to not using it.
  2. Your code does not use string interpolation.
  3. Your code misuses spawn and does not provide a significant benefit over iteration. Any case where spawn would be handy is also where HTTP Service would throttle, and then you’d be creating race conditions for retries. Speaking of which…
  4. There’s no protection on HttpService::GetAsync. Asynchronous functions should always be protected. There’s also no rate-limiting.
  5. Your code doesn’t give variables clear and concise names. This is a very opinionated thing, but most people would agree that at least your code is not clear or concise.
  6. Unnecessary waiting, especially for code that could yield and has no protection against that happening! This is a way bigger problem than you realize.
  7. Your code does not provide typings. This is a preference but come on, they’ve been out since 2020. There’s no reason to not just adapt.
  8. This is a released module yet isn’t release-ready. You’ve left a lot of debug logging in here for no reason. This should’ve been removed before publishing or at least make a toggleable variable for debug outputs.

Anyway, enough criticism, what should this resource maybe look like as a open-source-ready resource? It’s easy to be told what you did wrong but not be shown what you were supposed to do. When it comes to programming, there’s always going to be multiple different ways of getting your task finished and everyone will always argue about better methods till the end of time. Of course, these are mostly opinionated and it usually doesn’t matter. The reason your module falls under the circumstance of actually mattering is because it goes against the standards of what is expected.

I’m not saying my implementation is better in optimization, or, quite frankly, if it even works. This is untested and I wrote it inside of the Devforum. Presentation, being up-to-date, readability, type-safety, limit safety (if applicable, which it is in your case) are all extremely necessary when it comes to community resources. Even if you don’t need them or use them for yourself, when you publish something here it’s no longer for yourself and not everyone is you. So, here’s a more legible, type-safe, throttle-safe*, up-to-date with standards, and spawnless version of getting Gamepasses (Products) and Catalog Assets of a specified Player which also allows offline players to be checked given a username or userId.

* The HTTPS request throttle is not fool proof if you’re making a lot of requests outside of this module.

local players = game:GetService("Players")
local https = game:GetService("HttpService")

local placeURL = "https://games.roproxy.com/v2/users/%d/games?accessFilter=2&limit=50&sortOrder=Desc"
local gamepassURL = "https://games.roproxy.com/v1/games/%d/game-passes?limit=100&sortORder=Asc"
local catalogURL = "https://catalog.roproxy.com/v1/search/items/details?Category=3&Subcategory=%d&CreatorName=%s&salesTypeFilter=1&SortType=3"

local bindable = Instance.new("BindableEvent")

local totalRequests = 0 
local requestLimit = 60 -- to be safe in case there's other requests in the game
local retryLimit = 3
local function get(url: string, retries: number?): any
    retries = retries or 0
    totalRequests += 1

    if totalRequests >= requestLimit then
        bindable.Event:Wait()
    end

    local success, response = pcall(https.GetAsync, https, url)
    task.delay(60, function()
        totalRequests -= 1
        bindable:Fire()
    end)

    local function retry()
        return get(url)
    end

    if not success then
        if retries < retryLimit then
            return https:JSONDecode(get(url, retries + 1)), retry
        end

        return nil
    end

    return https:JSONDecode(response), retry
end

export type Asset = {
    id: number,
    price: number,
    isGamepass: boolean
}

return function(player: Player | string | number): {Asset}?
    local playerName
    local playerUserId

    if type(player) == "string" then -- assume username, you could do a tonumber check too!
        playerName = player

        local success, userId = pcall(players.GetUserIdFromNameAsync, players, playerName)
        if not success then
            return nil
        end

        playerUserId = userId
    elseif type(player) == "number" then -- assume user ID
        playerUserId = player

        local success, username = pcall(players.GetNameFromUserIdAsync, players, playerUserId)
        if not success then
            return nil
        end

        playerName = username
    elseif typeof(player) == "Player" then
        playerName = player.Name
        playerUserId = player.UserId
    else
        return nil
    end

    local tshirtURL = catalogURL:format(55, playerName)
    local shirtURL = catalogURL:format(56, playerName)
    local pantsURL = catalogURL:format(57, playerName)
    local placesURL = placeURL:format(playerUserId)

    local all = {}

    for _, url in { tshirtURL, shirtURL, pantsURL } do
        local response, retry

        repeat response, retry = get(url)
        until response and response.data or not retry

        if response then
            for _, item in response.data do
                if item.price and item.price > 0 then
                    table.insert(all, {
                        id = item.id,
                        price = item.price,
                        isGamepass = false
                    })
                end
            end
        end
    end

    local places = {}
    local cursor = ""
    local retries = 0

    while retries < retryLimit do
        local response, retry

        repeat
            response, retry = get(`{placesURL}&cursor={cursor}`)
            retries += 1
        until response and response.data or not retry

        if response then
            cursor = response.nextPageCursor
            retries = 0

            for _, place in response.data do
                table.insert(places, place.id)
            end
        end
    end

    for _, placeId in places do
        local gamepassesURL = gamepassURL:format(placeId)
        local response, retry

        repeat response, retry = get(gamepassesURL)
        until response and response.data or not retry

        if response then
            for _, gamepass in response.data do
                if gamepass.price then
                    table.insert(all, {
                        id = gamepass.id,
                        price = gamepass.price,
                        isGamepass = true
                    })
                end
            end
        end
    end

    return all
end
3 Likes

I made another version some time ago. Maybe it’s better (?)