├── proxies.txt ├── limiteds.txt ├── .gitignore ├── requirements.txt ├── .gitattributes ├── guide-images ├── CodeBtn.png └── DownloadZIP.png ├── scripts ├── startcheck.py ├── update.py ├── proxytest.py └── config.py ├── run.bat ├── run.sh ├── LICENSE ├── README.md └── main.py /proxies.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /limiteds.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*json 2 | 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | httpx 2 | discord-webhook[async] 3 | asyncio -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /guide-images/CodeBtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahrepublic/Better-UGC-Sniper/HEAD/guide-images/CodeBtn.png -------------------------------------------------------------------------------- /guide-images/DownloadZIP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahrepublic/Better-UGC-Sniper/HEAD/guide-images/DownloadZIP.png -------------------------------------------------------------------------------- /scripts/startcheck.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | if not os.path.exists("./config.json"): 4 | print("Ensuring pip is installed") 5 | os.system("python -m ensurepip --upgrade") 6 | 7 | print("Installing required packages") 8 | os.system("python -m pip install -r ./requirements.txt") 9 | exit(0) -------------------------------------------------------------------------------- /scripts/update.py: -------------------------------------------------------------------------------- 1 | import httpx as r 2 | 3 | print("Checking for updates...") 4 | script = r.get("https://raw.githubusercontent.com/noahrepublic/Better-UGC-Sniper/main/main.py").text 5 | with open("main.py", "r") as f: 6 | if f.read() != script: 7 | print("Updating...") 8 | with open("main.py", "w") as f: 9 | f.write(script) 10 | exit(0) 11 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | python ./scripts/startcheck.py 4 | 5 | :ui 6 | cls 7 | title UGC-Sniper Config Editor by noahrepublic#4323 8 | echo THE NEW DISCORD: https://discord.gg/hw2ttCnmdz 9 | echo option select 10 | echo. 11 | echo [1] - Configure Sniper 12 | echo [2] - Start Sniper 13 | echo [3] - Test Proxies (REQUIRES PROXIES IN PROXIES.TXT) 14 | 15 | set /p o= 16 | if %o%==1 goto config 17 | if %o%==2 goto start 18 | if %o%==3 goto test 19 | 20 | pause 21 | 22 | :config 23 | cls 24 | python ./scripts/config.py 25 | goto ui 26 | 27 | 28 | :start 29 | cls 30 | python ./scripts/update.py 31 | python ./main.py 32 | pause 33 | goto ui 34 | 35 | :test 36 | cls 37 | python ./scripts/proxytest.py 38 | pause 39 | goto ui -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python ./scripts/startcheck.py 4 | 5 | while true; do 6 | clear 7 | echo "UGC-Sniper Config Editor by noahrepublic#4323" 8 | echo "THE NEW DISCORD: https://discord.gg/hw2ttCnmdz" 9 | echo "option select" 10 | echo 11 | echo "[1] - Configure Sniper" 12 | echo "[2] - Start Sniper" 13 | echo "[3] - Test Proxies (REQUIRES PROXIES IN PROXIES.TXT)" 14 | 15 | read -p "Enter your choice: " o 16 | 17 | case "$o" in 18 | 1) 19 | clear 20 | python ./scripts/config.py 21 | read -p "Press enter to continue" 22 | ;; 23 | 2) 24 | clear 25 | python ./scripts/update.py 26 | python ./main.py 27 | read -p "Press enter to continue" 28 | ;; 29 | 3) 30 | clear 31 | python ./scripts/proxytest.py 32 | read -p "Press enter to continue" 33 | ;; 34 | *) 35 | read -p "Invalid option. Press enter to continue" 36 | ;; 37 | esac 38 | done 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Noah Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Downloads](https://img.shields.io/github/downloads/noahrepublic/Better-UGC-Sniper/total?) 2 | # [Better-UGC-Sniper](https://discord.gg/Kk8n2QpFCb) 3 | 4 | Open sourced UGC Sniper 5 | 6 | # How do I use this? 7 | 8 | ## 1. Download the repository 9 | 10 | ### Download from the releases your operating systems download zip 11 | 12 | https://github.com/noahrepublic/Better-UGC-Sniper/releases 13 | 14 | ### Extract the zip file to your desired directory 15 | 16 | ## 2. Install Python & Requirements 17 | 18 | A. Download Python from THEIR WEBSITE: https://www.python.org/ (Newest version recommended 3.11.3) 19 | 20 | B. Go to the bot's directory 21 | 22 | C. Run the bat file named "run" 23 | 24 | 25 | ## 3. Configure your cookie 26 | 27 | Once you start the bat file, type 1 and press enter 28 | 29 | It should bring you to the configuration, fill that out. 30 | 31 | ### How to get your cookie: https://youtu.be/hd6xWydE1K0 32 | ## Run 33 | 34 | Open the batch file again, and this time press 2 35 | 36 | ## Proxies? 37 | 38 | Free proxies suck so only really recommended with paid ones. If you are using proxies they _MUST_ be the same country, otherwise it invalids your cookie 39 | -------------------------------------------------------------------------------- /scripts/proxytest.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import httpx as r 3 | from threading import Thread 4 | from concurrent.futures.thread import ThreadPoolExecutor 5 | import os 6 | 7 | proxies = queue.Queue() 8 | 9 | if not os.path.exists("./proxies.txt"): 10 | print("proxies.txt does not exist.") 11 | exit(0) 12 | 13 | with open("./proxies.txt", "r") as f: 14 | for proxy in f.read().replace(" ", "").split("\n"): 15 | if proxy == "": 16 | continue 17 | proxies.put(proxy) 18 | f.close() 19 | 20 | if proxies.empty(): 21 | print("No proxies found in proxies.txt") 22 | exit(0) 23 | 24 | f = open("./proxies.txt", "w") 25 | 26 | startSize = proxies.qsize() 27 | print("Starting proxy validation with", startSize, "proxies.") 28 | runs = 0 29 | 30 | session = r.Session() 31 | 32 | def validateProxy(): 33 | global proxies 34 | global runs 35 | while not proxies.empty(): 36 | proxy = proxies.get() 37 | try: 38 | response = session.get("https://ipinfo.io/json", proxies={"https": proxy, "http": proxy}, timeout=5) 39 | except: 40 | runs += 1 41 | print(f"{runs}/{startSize}") 42 | continue 43 | 44 | 45 | if response.status_code == 200: 46 | f.write(f"\n{proxy}") 47 | runs += 1 48 | print(f"{runs}/{startSize}") 49 | 50 | 51 | 52 | for _ in range(10): 53 | Thread(target=validateProxy).start() 54 | -------------------------------------------------------------------------------- /scripts/config.py: -------------------------------------------------------------------------------- 1 | import requests as r 2 | import json 3 | import os 4 | 5 | if not os.path.exists("./config.json"): 6 | print("Welcome to first time setup") 7 | config = { 8 | "Accounts": [], 9 | "Items": [], 10 | "cooldownPerCheck": 1, 11 | "discordWebhook": "", 12 | } 13 | 14 | roblosecurity = str(input("Enter Primary ROBLOSECURITY: ")) 15 | try: 16 | userId = r.get("https://users.roblox.com/v1/users/authenticated", 17 | cookies={".ROBLOSECURITY": roblosecurity}).json()["id"] 18 | except: 19 | print("ROBLOSECURITY is invalid. Restart the script and try again.") 20 | exit(0) 21 | 22 | 23 | friendlyName = str(input("Enter friendly name: ")) 24 | 25 | config["Accounts"].append({ 26 | "ROBLOSECURITY": roblosecurity, 27 | "userId": userId, 28 | "nickName": friendlyName 29 | }) 30 | 31 | cooldownPerLimited = input("Enter cooldown per limited (Recommended: 0.75): ") 32 | config["cooldownPerLimited"] = float(cooldownPerLimited) 33 | 34 | discordWebhook = str(input("Enter Discord webhook (Leave blank for none): ")) 35 | config["discordWebhook"] = discordWebhook 36 | 37 | 38 | jsonObject = json.dumps(config, indent=4) 39 | 40 | with open("./config.json", "w") as f: 41 | f.write(jsonObject) 42 | f.close() 43 | 44 | print("Config file created. Restart the script.") 45 | exit(0) 46 | 47 | with open("./config.json", "r") as f: 48 | print("Loading config...") 49 | config = json.load(f) 50 | f.close() 51 | 52 | jsonObject = None 53 | print("What would you like to edit? \n" 54 | "1. Edit limited IDs \n" 55 | "2. Edit accounts \n" 56 | "3. Add account \n" 57 | "4. Remove account \n" 58 | "5. Cooldown \n" 59 | "6. Discord webhook \n" 60 | "7. Budget \n" 61 | "8. Exit") 62 | 63 | choice = int(input("")) 64 | if choice == 1: 65 | print("What would you like to do? \n") 66 | 67 | print("1. Add limited ID") 68 | print("2. Remove limited ID") 69 | print("3. Clear limited IDs") 70 | print("4. Exit") 71 | 72 | choice = int(input("")) 73 | if choice == 1: 74 | limitedId = int(input("Enter limited ID: ")) 75 | config["Items"].append({"id": limitedId}) 76 | jsonObject = json.dumps(config, indent=4) 77 | elif choice == 2: 78 | print("What limited ID would you like to remove? \n") 79 | for i, item in enumerate(config["Items"]): 80 | print(f"{i + 1}. {item['id']}") 81 | choice = int(input("")) 82 | if choice > len(config["Items"]) + 1: 83 | print("Invalid choice.") 84 | exit(0) 85 | config["Items"].pop(choice - 1) 86 | jsonObject = json.dumps(config, indent=4) 87 | elif choice == 3: 88 | config["Items"] = [] 89 | jsonObject = json.dumps(config, indent=4) 90 | else: 91 | exit(0) 92 | 93 | elif choice == 2: 94 | print("What account would you like to edit? \n") 95 | for i, account in enumerate(config["Accounts"]): 96 | if i == 0: 97 | print(f"{i + 1}. {account['userId']} {account['nickName']} (Primary)") 98 | else: 99 | print(f"{i + 1}. {account['userId']} {account['nickName']}") 100 | choice = int(input("")) 101 | 102 | if choice > len(config["Accounts"]) + 1: 103 | print("Invalid choice.") 104 | exit(0) 105 | 106 | print("What would you like to edit? \n") 107 | print("1. ROBLOSECURITY") 108 | print("2. Friendly name") 109 | editChoice = int(input("")) 110 | 111 | if editChoice == 1: 112 | roblosecurity = str(input("Enter ROBLOSECURITY: ")) 113 | try: 114 | userId = r.get("https://users.roblox.com/v1/users/authenticated", 115 | cookies={".ROBLOSECURITY": roblosecurity}).json()["id"] 116 | except: 117 | print("ROBLOSECURITY is invalid.") 118 | exit(0) 119 | 120 | config["Accounts"][choice - 1]["ROBLOSECURITY"] = roblosecurity 121 | elif editChoice == 3: 122 | friendlyName = str(input("Enter friendly name: ")) 123 | config["Accounts"][choice - 1]["nickName"] = friendlyName 124 | 125 | print("ROBLOSECURITY updated.") 126 | jsonObject = json.dumps(config, indent=4) 127 | elif choice == 3: 128 | newRoblosecurity = str(input("Enter ROBLOSECURITY: ")) 129 | 130 | try: 131 | userId = r.get("https://users.roblox.com/v1/users/authenticated", 132 | cookies={".ROBLOSECURITY": newRoblosecurity}).json()["id"] 133 | except: 134 | print("ROBLOSECURITY is invalid.") 135 | exit(0) 136 | 137 | 138 | for i, account in enumerate(config["Accounts"]): 139 | if account["userId"] == userId: 140 | print("Account already exists.") 141 | exit(0) 142 | 143 | 144 | friendlyName = str(input("Enter friendly name: ")) 145 | config["Accounts"].append({ 146 | "ROBLOSECURITY": newRoblosecurity, 147 | "userId": userId, 148 | "nickName": friendlyName 149 | }) 150 | 151 | jsonObject = json.dumps(config, indent=4) 152 | elif choice == 4: 153 | print("What account would you like to remove? \n") 154 | for i, account in enumerate(config["Accounts"]): 155 | if i == 0: 156 | print(f"{i + 1}. {account['userId']} {account['nickName']} (Primary)") 157 | else: 158 | print(f"{i + 1}. {account['userId']} {account['nickName']}") 159 | 160 | choice = int(input("")) 161 | if choice > len(config["Accounts"]): 162 | print("Invalid choice.") 163 | exit(0) 164 | 165 | config["Accounts"].pop(choice - 1) 166 | jsonObject = json.dumps(config, indent=4) 167 | 168 | 169 | elif choice == 5: 170 | cooldownPerCheck = input("Enter cooldown per check: ") 171 | config["cooldownPerCheck"] = float(cooldownPerCheck) 172 | jsonObject = json.dumps(config, indent=4) 173 | elif choice == 6: 174 | discordWebhook = str(input("Enter Discord webhook (Leave blank for none): ")) 175 | config["discordWebhook"] = discordWebhook 176 | jsonObject = json.dumps(config, indent=4) 177 | elif choice == 7: 178 | budget = int(input("Enter budget: ")) 179 | 180 | config["budget"] = budget 181 | jsonObject = json.dumps(config, indent=4) 182 | else: 183 | exit(0) 184 | 185 | 186 | if jsonObject: 187 | with open("./config.json", "w") as f: 188 | f.write(jsonObject) 189 | f.close() 190 | 191 | print("Config file updated.") 192 | exit(0) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import uuid 3 | import os 4 | import time 5 | import datetime 6 | import asyncio 7 | import httpx 8 | 9 | from discord_webhook import AsyncDiscordWebhook, DiscordEmbed 10 | 11 | 12 | os.system('cls' if os.name == 'nt' else 'clear') 13 | 14 | print("Better UGC Sniper by noahrepublic#4323, support server: https://discord.com/invite/Kk8n2QpFCb") 15 | print("https://github.com/noahrepublic/Better-UGC-Sniper") 16 | 17 | 18 | global start 19 | 20 | rateTokens = 60 21 | rateLastRefil = asyncio.get_event_loop().time() 22 | 23 | def clear(): 24 | return 'cls' if os.name == 'nt' else 'clear' 25 | 26 | class Sniper: 27 | def __init__(self): 28 | print("Starting sniper") 29 | 30 | self.buyThreadPurchases = 0 # Used to track how many purchases have been made in the buy thread 31 | 32 | self.discordWebhook = None 33 | self.items = [] # Items scanning 34 | 35 | self.cooldown = 1 # Cooldown between checks 36 | 37 | self.proxiesEnabled = False 38 | 39 | self.maxPrice = 0 # Temporary, might add per item 40 | 41 | 42 | # Information prints 43 | 44 | self.checks = 0 45 | self.purchases = 0 46 | 47 | def printAll(self): 48 | # add theme print in the future 49 | print(f"------- Noah's UGC Sniper -------") 50 | print(f"Checks: {self.checks}") 51 | print(f"Purchases: {self.purchases}") 52 | 53 | 54 | class Account: 55 | def __init__(self, accounts): 56 | self.accounts = accounts 57 | self.accountIndex = 0 58 | self.accountLength = len(accounts) 59 | 60 | def getPrimaryAccount(self): # Reset, only used this outside of the buy function 61 | self.accountIndex = 0 62 | return self.getCurrentAccount() 63 | 64 | def getCurrentAccount(self): 65 | return self.accounts[self.accountIndex] 66 | 67 | def nextAccount(self): 68 | self.accountIndex += 1 69 | 70 | if self.accountIndex > self.accountLength: 71 | self.accountIndex = 0 72 | 73 | return self.accounts[self.accountIndex] 74 | 75 | class ProxyHandler: 76 | class TokenBucket: 77 | def __init__(self, capacity, rate): 78 | self.capacity = capacity 79 | self.rate = rate 80 | self.tokens = capacity 81 | self.last = time.monotonic() 82 | 83 | async def consume(self) -> bool: 84 | await self._fillBucket() 85 | if self.tokens >= 1: 86 | self.tokens -= 1 87 | return True 88 | return False 89 | 90 | async def _fillBucket(self) -> None: 91 | now = time.monotonic() 92 | dt = now - self.last 93 | new = dt * self.rate 94 | self.tokens = min(self.tokens + new, self.capacity) 95 | 96 | def __init__(self, proxies, requestsPerMinute): 97 | self.proxies = proxies 98 | self.buckets = {proxy: self.TokenBucket(requestsPerMinute, requestsPerMinute / 60) for proxy in proxies} 99 | self.proxyIndex = 0 100 | self.proxyLength = len(proxies) # save from having to call len() every time 101 | 102 | def nextProxy(self) -> str: 103 | self.currentProxyIndex = (self.currentProxyIndex + 1) % self.proxyLength 104 | return self.proxies[self.currentProxyIndex] 105 | 106 | def currentProxy(self) -> str: 107 | return self.proxies[self.proxyIndex] 108 | 109 | async def rotate(self) -> str: 110 | while True: 111 | proxy = self.currentProxy() 112 | if self.buckets[proxy].consume(): 113 | return proxy 114 | 115 | self.proxyIndex = self.nextProxy() # rotate to next proxy 116 | 117 | 118 | async def getXToken(self, cookie: str) -> dict: 119 | async with httpx.AsyncClient(cookies={".ROBLOSECURITY": cookie}, verify=False) as client: 120 | response = await client.post("https://auth.roblox.com/v2/logout") 121 | xToken = response.headers.get("x-csrf-token") 122 | if not xToken: 123 | raise Exception("Failed to get x-csrf-token, invalid cookie?") 124 | return {"x-csrf-token": xToken, "created": datetime.datetime.now()} 125 | 126 | async def autoXToken(self) -> None: 127 | while True: 128 | await asyncio.sleep(10) 129 | 130 | for account in self.accountHandler.accounts: 131 | 132 | if not account.get("x-csrf-token") or datetime.datetime.now() > account["created"] + datetime.timedelta(minutes=10): 133 | print("Updating x-csrf-tokens") 134 | try: 135 | response = await self.getXToken(account[".ROBLOSECURITY"]) 136 | account["x-csrf-token"] = response["x-csrf-token"] 137 | account["created"] = response["created"] 138 | continue 139 | except Exception as e: 140 | print(f"Failed to update x-csrf-token for {account['nickName']}: {e}") 141 | continue 142 | 143 | async def getProxies(self) -> None: 144 | try: 145 | with open("proxies.txt") as f: 146 | proxies = f.read().replace(" ", "").split("\n") 147 | for proxy in proxies: 148 | if proxy == "": 149 | proxies.remove(proxy) 150 | 151 | self.proxiesLength = len(proxies) 152 | 153 | if self.proxiesLength > 0: 154 | self.proxiesEnabled = True 155 | 156 | print("Proxies enabled.") 157 | 158 | self.proxyHandler = self.ProxyHandler(proxies=proxies, requestsPerMinute=60) 159 | self.proxiesEnabled = True 160 | else: 161 | print("Proxies disabled.") 162 | except Exception as e: 163 | print(f"Failed to read proxies.txt: {e}") 164 | print("proxies.txt not found, Proxies disabled.") 165 | 166 | async def readConfig(self) -> None: 167 | try: 168 | with open("config.json", "r") as f: 169 | config = json.load(f) 170 | configAccounts = config["Accounts"] 171 | accounts = [] 172 | 173 | print("Reading config") 174 | for account in configAccounts: 175 | cookie = account["ROBLOSECURITY"] 176 | userId = account["userId"] 177 | nickName = account["nickName"] 178 | 179 | xToken = await self.getXToken(cookie) 180 | 181 | accounts.append({".ROBLOSECURITY": cookie, "x-csrf-token": xToken["x-csrf-token"], "created": xToken["created"], "userId": userId, "nickName": nickName}) 182 | 183 | self.accountHandler = self.Account(accounts=accounts) 184 | 185 | if config["discordWebhook"] != "" and config["discordWebhook"] != None: 186 | self.discordWebhook = config["discordWebhook"] 187 | 188 | self.maxPrice = account["budget"] 189 | for item in config["Items"]: 190 | if not item["id"]: 191 | continue 192 | self.items.append(str(item["id"])) 193 | 194 | self.cooldown = config["cooldownPerCheck"] 195 | 196 | 197 | except Exception as e: 198 | print(f"Could not read config.json {e}") 199 | exit(0) 200 | 201 | async def buy(self, info: dict, limitedId: int, accountData: dict): # start here tomorrow 202 | collectibleItemId = info["collectibleItemId"] 203 | data = { 204 | "collectibleProductId": info["productId"], 205 | "collectibleItemId": collectibleItemId, 206 | "expectedCurrency": 1, 207 | "expectedPrice": 0, 208 | "expectedPurchaserId": accountData["userId"], 209 | "expectedPurchaserType": "User", 210 | "expectedSellerId": info["creatorTargetId"], 211 | "expectedSellerType": "User" 212 | } 213 | 214 | totalErrors = 0 215 | 216 | print(f"Attempting to buy {info['name']}") 217 | async with httpx.AsyncClient(headers={"x-csrf-token": accountData["x-csrf-token"]}, cookies={".ROBLOSECURITY": accountData[".ROBLOSECURITY"]}) as client: 218 | while True: 219 | if totalErrors >= 5: 220 | print("Failed to buy, too many errors.") 221 | break 222 | 223 | data["idempotencyKey"] = str(uuid.uuid4()) 224 | 225 | try: 226 | response = await client.post(f"https://apis.roblox.com/marketplace-sales/v1/item/{collectibleItemId}/purchase-item", json=data) 227 | except httpx.ConnectError: 228 | print("Failed to connect to Roblox API, retrying...") 229 | totalErrors += 1 230 | continue 231 | except httpx.TimeoutException: 232 | print(f"Connection timed out") 233 | 234 | if response.status_code == 429: 235 | print("Rate limited, retrying in 0.5 seconds") 236 | await asyncio.sleep(0.5) 237 | continue 238 | elif response.status_code == 500: 239 | print("Invalid parameters? (Roblox)") 240 | totalErrors += 1 241 | continue 242 | 243 | try: 244 | responseJSON = response.json() 245 | except: 246 | print("Failed to decode JSON") 247 | totalErrors += 1 248 | continue 249 | 250 | if responseJSON.get("errorMessage") and responseJSON.get("errorMessage") == "QuantityExhausted": 251 | if totalErrors < 3: 252 | print("Quantity exhausted, double checking incase of false positive...") 253 | totalErrors += 1 254 | continue 255 | else: 256 | print("Out of stock! Better luck next time.") 257 | return 258 | 259 | if responseJSON.get("errorMessage") and responseJSON.get("errorMessage") == "Flooded: purchase requests exceeds limit": 260 | print("Roblox: Item purchase request exceeds limit") 261 | 262 | if responseJSON["purchased"]: 263 | print(f"Successfully bought {info['name']}!") 264 | self.buyThreadPurchases += 1 265 | self.purchases += 1 266 | 267 | tasks = [] 268 | embed = DiscordEmbed(title="Noah's UGC Sniper") 269 | embed.set_footer(text="discord.gg/hw2ttCnmdz") 270 | embed.set_timestamp() 271 | embed.set_url(f"https://www.roblox.com/catalog/{limitedId}/noahw") 272 | if self.discordWebhook: 273 | webhook = AsyncDiscordWebhook(url=self.discordWebhook, content=f"Successfully bought {info['name']}!") 274 | 275 | webhook.add_embed(embed) 276 | tasks.append(webhook.execute()) 277 | 278 | await asyncio.gather(*tasks) 279 | 280 | else: 281 | print(f"Failed to buy {info['name']}, {responseJSON['errorMessage']}") 282 | totalErrors += 1 283 | continue 284 | async def waitRatelimit(self): 285 | global rateLastRefil 286 | global rateTokens 287 | while True: 288 | if asyncio.get_event_loop().time() - rateLastRefil >= 60: 289 | rateTokens = 60 290 | rateLastRefil = asyncio.get_event_loop().time() 291 | 292 | if rateTokens <= 0: 293 | await asyncio.sleep(0.01) 294 | else: 295 | rateTokens -= 1 296 | return True 297 | 298 | async def search(self): 299 | while True: 300 | if self.proxiesEnabled and self.proxiesLength > 0: 301 | proxy = f"http://{await self.proxyHandler.rotate()}" 302 | else: 303 | proxy = None 304 | 305 | if not self.proxiesEnabled: 306 | await self.waitRatelimit() 307 | 308 | start = asyncio.get_event_loop().time() 309 | 310 | currentAccount = self.accountHandler.getCurrentAccount() 311 | 312 | 313 | async with httpx.AsyncClient(proxies=proxy, headers={"x-csrf-token": currentAccount["x-csrf-token"], "Accept": "application/json"}, cookies={".ROBLOSECURITY": currentAccount[".ROBLOSECURITY"]}) as session: 314 | 315 | try: 316 | response = await session.post("https://catalog.roblox.com/v1/catalog/items/details", json={"items": [{"itemType": "Asset", "id": int(id)} for id in self.items]}) 317 | limitedsInfo = response.json()["data"] 318 | except httpx.ConnectTimeout: 319 | if self.proxiesEnabled: 320 | print("Proxy timed out") 321 | else: 322 | print("Connection timed out") 323 | continue 324 | except KeyError: 325 | if self.proxiesEnabled: 326 | print("Proxy failed to connect") 327 | else: 328 | print(response.text) 329 | 330 | ##logging.debug(f"Failed to get limiteds info: {response.text}") 331 | if response.status_code == 403: 332 | response = await self.getXToken(currentAccount[".ROBLOSECURITY"]) 333 | currentAccount["x-csrf-token"] = response["x-csrf-token"] 334 | currentAccount["created"] = response["created"] 335 | continue 336 | except Exception as e: 337 | ##logging.debug(f"Failed to get limiteds info: {e}") 338 | if response.status_code == 403: 339 | response = await self.getXToken(currentAccount[".ROBLOSECURITY"]) 340 | currentAccount["x-csrf-token"] = response["x-csrf-token"] 341 | currentAccount["created"] = response["created"] 342 | continue 343 | 344 | 345 | 346 | 347 | if response.status_code == 429: 348 | if self.proxiesEnabled: 349 | continue 350 | 351 | print("Rate limited, waiting to end") 352 | continue # it will start rate limit check 353 | 354 | 355 | validForProductIds = [] 356 | 357 | for limited in limitedsInfo: 358 | if limited.get("price") is None: 359 | continue 360 | 361 | if limited.get("unitsAvailableForConsumption", 0): 362 | print("Out of stock") 363 | continue 364 | 365 | if limited.get("price") > self.maxPrice: 366 | self.items.remove(str(limited["id"])) 367 | print("Item is over budget, removing from list") 368 | continue 369 | 370 | if limited.get("priceStatus") != "Off Sale" and limited.get("unitsAvailableForConsumption", 0) > 0 and limited.get("collectibleItemId"): 371 | validForProductIds.append(limited) 372 | 373 | validForProductIdsLength = len(validForProductIds) 374 | if validForProductIdsLength > 0: 375 | if not self.proxiesEnabled: 376 | await self.waitRatelimit() 377 | productIds = await session.post("https://apis.roblox.com/marketplace-items/v1/items/details", json={"itemIds": [id["collectibleItemId"] for id in validForProductIds]}) 378 | productIds = productIds.json() 379 | 380 | tasks = [] 381 | 382 | for i in range(validForProductIdsLength): 383 | info = validForProductIds[i] 384 | 385 | info["productId"] = productIds[i]["collectibleProductId"] 386 | 387 | for account in self.accountHandler.accounts: 388 | tasks.append(self.buy(info, info["id"], account)) 389 | 390 | 391 | await asyncio.gather(*tasks) 392 | 393 | self.checks += len(self.items) 394 | 395 | end = asyncio.get_event_loop().time() 396 | 397 | print("Checked", round(end - start, 2), "seconds") 398 | await asyncio.sleep(self.cooldown) 399 | 400 | 401 | async def autoPrint(self): 402 | while True: 403 | os.system(clear()) 404 | self.printAll() 405 | 406 | await asyncio.sleep(0.15) 407 | 408 | async def main(self): 409 | print("Started sniper") 410 | coroutines = [] 411 | 412 | coroutines.append(self.autoXToken()) 413 | coroutines.append(self.search()) 414 | coroutines.append(self.autoPrint()) 415 | 416 | await asyncio.gather(*coroutines) 417 | 418 | 419 | async def start(): 420 | tasks = [] 421 | 422 | tasks.append(asyncio.create_task(sniper.readConfig())) 423 | tasks.append(asyncio.create_task(sniper.getProxies())) 424 | 425 | await asyncio.gather(*tasks) 426 | 427 | await sniper.main() 428 | 429 | if __name__ == "__main__": 430 | sniper = Sniper() 431 | 432 | asyncio.run(start()) 433 | --------------------------------------------------------------------------------