From 5e1e3c674b761864a669d290fd974cb29e03079b Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:29:06 -0400 Subject: [PATCH 01/12] Update README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index c04bfdb..757c224 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,3 @@ Terminal app (run with CMD or press play in an IDE) -C# web driver libray from nuget - -Not affilated with roblox blah blah, dont complain to me if you get rate limted - - -program tells you how to use it +Not affilated with roblox blah blah From 9f277c4dbfbbd331aa0fdd598ce94b9d1768ce16 Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:32:21 -0400 Subject: [PATCH 02/12] Update Program.cs - Replaced Puppeteer/web scraping with Roblox Toolbox API - Improved performance by batching asset requests - Reduced scan types to audio only (removed decals/clothes/models) - Added automatic audio.txt template creation on first run - Cleaner and simpler project structure overall --- RobloxAssetChecker/Program.cs | 342 ++++++++++------------------------ 1 file changed, 97 insertions(+), 245 deletions(-) diff --git a/RobloxAssetChecker/Program.cs b/RobloxAssetChecker/Program.cs index 27fd61d..eb963e3 100644 --- a/RobloxAssetChecker/Program.cs +++ b/RobloxAssetChecker/Program.cs @@ -1,310 +1,162 @@ -using PuppeteerSharp; +using System.Text.Json; namespace RobloxAssetChecker; internal static partial class RobloxAssetChecker { - [Flags] - private enum IdType - { - None = 0, - Audio = 1 << 0, - Decals = 1 << 1, - Clothes = 1 << 2, - Models = 1 << 3 - } - private static IdType _toScan = IdType.None; - private enum ReturnType { Public, PublicArchived, - GroupOrRlyOldUnkown, - Moderated, + Moderated } - private static IBrowser? _browser; + private static readonly HttpClient _http = new HttpClient(); + private const int BatchSize = 100; - private static void CheckFiles() + private static void EnsureFile() { - if (File.Exists("audio.txt")) - _toScan |= IdType.Audio; - - if (File.Exists("clothes.txt")) - _toScan |= IdType.Clothes; - - if (File.Exists("decals.txt")) - _toScan |= IdType.Decals; + const string file = "audio.txt"; - if (File.Exists("models.txt")) - _toScan |= IdType.Models; - } - - private static async Task KillBrowser() - { - if (_browser is null) return; - try { await _browser.CloseAsync(); } - catch + if (!File.Exists(file)) { - // ignored + File.WriteAllText(file, +@"# Audio Scan List +# Format: ID - optional name +# Example: +123456789 - cool song +987654321 - background music +"); + Console.WriteLine("Created audio.txt template"); } - - _browser = null; - } - - private static async Task Main() - { - Console.CancelKeyPress += (_, e) => - { - e.Cancel = true; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("\nInterrupted, closing browser..."); - Console.ResetColor(); - KillBrowser().GetAwaiter().GetResult(); - Environment.Exit(0); - }; - - AppDomain.CurrentDomain.ProcessExit += (_, _) => - { - KillBrowser().GetAwaiter().GetResult(); - }; - - Console.Clear(); - Console.ForegroundColor = ConsoleColor.DarkMagenta; - Console.WriteLine("Graze's Asset Checker"); - Console.WriteLine(new string('-', 50)); - - Console.WriteLine("Downloading Headless Chrome if needed. Please wait..."); - var browserFetcher = new BrowserFetcher(); - await browserFetcher.DownloadAsync(); - - _browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true }); - var page = await _browser.NewPageAsync(); - - Console.WriteLine("Checking what to scan."); - CheckFiles(); - - if (_toScan == IdType.None) - { - Console.WriteLine("Nothing to scan, returning."); - await KillBrowser(); - return; - } - - if ((_toScan & IdType.Audio) != 0) - { - Console.WriteLine("Scanning audio..."); - await ScanTxtFile("audio.txt", page, IdType.Audio); - } - if ((_toScan & IdType.Clothes) != 0) - { - Console.WriteLine("Scanning clothes..."); - await ScanTxtFile("clothes.txt", page, IdType.Clothes); - } - if ((_toScan & IdType.Decals) != 0) - { - Console.WriteLine("Scanning decals..."); - await ScanTxtFile("decals.txt", page, IdType.Decals); - } - if ((_toScan & IdType.Models) != 0) - { - Console.WriteLine("Scanning models..."); - await ScanTxtFile("models.txt", page, IdType.Models); - } - - await KillBrowser(); } - private record AssetEntry(string Id, string? Label); + private record AssetEntry(long Id, string? Label); - private static List ParseAssetFile(IEnumerable lines) + private static List ParseFile(IEnumerable lines) { - var seen = new HashSet(); + var seen = new HashSet(); var results = new List(); foreach (var raw in lines) { var line = raw.Trim(); - if (string.IsNullOrEmpty(line) || line.StartsWith('#')) + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) continue; - var m = AssetIdRegex().Match(line); - if (!m.Success) + var parts = line.Split(['-', ':'], 2); + + if (!long.TryParse(parts[0].Trim(), out var id)) continue; - var id = m.Value; if (!seen.Add(id)) continue; - var after = line[(m.Index + m.Length)..]; - var label = LabelSeparatorRegex().Replace(after, "").Trim(); + string? label = parts.Length > 1 ? parts[1].Trim() : null; + if (string.IsNullOrWhiteSpace(label)) + label = null; - results.Add(new AssetEntry(id, string.IsNullOrWhiteSpace(label) ? null : label)); + results.Add(new AssetEntry(id, label)); } return results; } - private static string CensorId(string id) => - id.Length <= 5 ? new string('#', id.Length) - : id[..3] + new string('#', id.Length - 5) + id[^2..]; - - private static async Task ScanTxtFile(string toScan, IPage page, IdType type) + private static async Task Main() { - var rawLines = await File.ReadAllLinesAsync(toScan); - var entries = ParseAssetFile(rawLines); + Console.Clear(); + Console.ForegroundColor = ConsoleColor.DarkMagenta; + Console.WriteLine("Audio Asset Checker (API Version)"); + Console.WriteLine(new string('-', 50)); - Console.WriteLine($"Found {entries.Count}/{rawLines.Length} IDs for checking (duplicates/headers removed)\n"); + EnsureFile(); - var ids = new List(); - var publicArchived = new List(); - var groupOrOld = new List(); - var moderated = 0; - var total = entries.Count; - var statusLine = Console.CursorTop; + var rawLines = await File.ReadAllLinesAsync("audio.txt"); + var entries = ParseFile(rawLines); - void DrawCounter() - { - Console.SetCursorPosition(0, statusLine + 1); - Console.ForegroundColor = ConsoleColor.Green; - Console.Write($" Public: {ids.Count}"); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write($" PublicArchived: {publicArchived.Count}"); - Console.ForegroundColor = ConsoleColor.Blue; - Console.Write($" Group/Old/Unknown(playable*): {groupOrOld.Count}"); - Console.ForegroundColor = ConsoleColor.Red; - Console.Write($" Moderated: {moderated}"); - Console.ForegroundColor = ConsoleColor.DarkMagenta; - Console.Write($" Total: {ids.Count + publicArchived.Count + groupOrOld.Count + moderated}/{total}"); - Console.Write(string.Empty.PadRight(Console.WindowWidth - Console.CursorLeft)); - Console.ResetColor(); - } + Console.WriteLine($"Loaded {entries.Count} audio IDs\n"); - for (var i = 0; i < total; i++) - { - var entry = entries[i]; - var id = entry.Id; + var publicList = new List(); + var archivedList = new List(); + var missingList = new List(); - Console.SetCursorPosition(0, statusLine); - Console.ForegroundColor = ConsoleColor.DarkMagenta; - Console.Write($"Checking {CensorId(id)}: Checking... ({i + 1}/{total})".PadRight(Console.WindowWidth)); - DrawCounter(); - - var url = $"https://create.roblox.com/store/asset/{id}"; - await page.GoToAsync(url, new NavigationOptions - { - WaitUntil = [WaitUntilNavigation.Networkidle2], - Timeout = 30_000 - }); + for (int i = 0; i < entries.Count; i += BatchSize) + { + var batch = entries.Skip(i).Take(BatchSize).ToList(); + var idList = string.Join(",", batch.Select(x => x.Id)); - await Task.Delay(1_200); + var url = + $"https://apis.roblox.com/toolbox-service/v1/items/details?assetIds={idList}"; - var content = await page.GetContentAsync(); + string json; - var returnedStatus = type switch + try { - IdType.Audio => ScanAudio(content), - IdType.Decals => ScanDecals(content), - IdType.Clothes => ScanClothes(content), - IdType.Models => ScanModels(content), - _ => ReturnType.Moderated - }; - - switch (returnedStatus) + json = await _http.GetStringAsync(url); + } + catch { - case ReturnType.Public: - ids.Add(entry); - Console.ForegroundColor = ConsoleColor.Green; - break; - - case ReturnType.PublicArchived: - publicArchived.Add(entry); - Console.ForegroundColor = ConsoleColor.Cyan; - break; - - case ReturnType.GroupOrRlyOldUnkown: - groupOrOld.Add(entry); - Console.ForegroundColor = ConsoleColor.Blue; - break; - - case ReturnType.Moderated: - default: - moderated++; - Console.ForegroundColor = ConsoleColor.Red; - break; + Console.WriteLine("Request failed, skipping batch."); + continue; } - var statusLabel = returnedStatus.ToString(); - Console.SetCursorPosition(0, statusLine); - Console.Write($"Checking {CensorId(id)}: {statusLabel} ({i + 1}/{total})".PadRight(Console.WindowWidth)); - DrawCounter(); - Console.ResetColor(); - - await Task.Delay(300); - } - - Console.ForegroundColor = ConsoleColor.DarkMagenta; - var outputFile = Path.GetFileNameWithoutExtension(toScan) + " - Sorted.txt"; + using var doc = JsonDocument.Parse(json); + var returned = new HashSet(); - var output = ids.Aggregate( - " # Assets AutoCheck by Graze # \n # Public IDs # \n", - (current, e) => current + FormatEntry(e)); + if (doc.RootElement.TryGetProperty("data", out var data)) + { + foreach (var item in data.EnumerateArray()) + { + var asset = item.GetProperty("asset"); + var id = asset.GetProperty("id").GetInt64(); + returned.Add(id); - var output2 = publicArchived.Aggregate( - " # Public but Archived IDs # \n" + - " # These play fine but wont show in search # \n", - (current, e) => current + FormatEntry(e)); + var name = asset.GetProperty("name").GetString(); - var output3 = groupOrOld.Aggregate( - " # Group/Arcive (?) IDs or Really Old (?) # \n" + - " # Im not fully sure how to fully split every Archive type but most should play in boombox games #\n" + - " # If they are really old, they probably won't play in new games Tho # \n", - (current, e) => current + FormatEntry(e)); + bool published = false; - var final = output; - if (publicArchived.Count > 0) final += output2; - if (groupOrOld.Count > 0) final += output3; + if (item.TryGetProperty("fiatProduct", out var fiat) && + fiat.TryGetProperty("published", out var pub)) + { + published = pub.GetBoolean(); + } - await File.WriteAllTextAsync(outputFile, final); + var entry = batch.FirstOrDefault(x => x.Id == id); + var label = entry.Label ?? name; - Console.SetCursorPosition(0, statusLine + 3); - Console.WriteLine($"Results saved to {outputFile}"); - return; + if (published) + publicList.Add(new AssetEntry(id, label)); + else + archivedList.Add(new AssetEntry(id, label)); - static string FormatEntry(AssetEntry e) => - e.Label is null ? $"{e.Id}\n" : $"{e.Id} - {e.Label}\n"; - } - - private static ReturnType ScanAudio(string content) - { - if (content.Contains("data-testid=\"PLAYWRIGHT_audioPlayer\"")) - return ReturnType.Public; - - if (content.Contains("data-testid=\"PLAYWRIGHT_getAsset\"") || content.Contains("disabled=\"\"") || content.Contains("Audio preview is not available on your browser.")) - return content.Contains("@DistrokidOfficial") ? ReturnType.PublicArchived : ReturnType.GroupOrRlyOldUnkown; + Console.WriteLine($"{id} -> {(published ? "Public" : "Archived")}"); + } + } - return ReturnType.Moderated; - } + foreach (var item in batch) + { + if (!returned.Contains(item.Id)) + { + missingList.Add(item); + Console.WriteLine($"{item.Id} -> Moderated / Missing"); + } + } + } - private static ReturnType ScanDecals(string content) - { - return content.Contains("Mui-selected") ? ReturnType.Public : ReturnType.Moderated; - } + var output = + " # Public Audio # \n" + + string.Join("\n", publicList.Select(Format)) + + "\n\n # Archived Audio # \n" + + string.Join("\n", archivedList.Select(Format)) + + "\n\n # Moderated / Missing # \n" + + string.Join("\n", missingList.Select(Format)); - private static ReturnType ScanClothes(string content) - { - return content.Contains("shopping-cart-buy-button") ? ReturnType.Public : ReturnType.Moderated; - } + await File.WriteAllTextAsync("audio - Sorted.txt", output); - private static ReturnType ScanModels(string content) - { - return content.Contains("Mui-selected") ? ReturnType.Public : ReturnType.Moderated; + Console.WriteLine("\nDone! Saved to audio - Sorted.txt"); } - [System.Text.RegularExpressions.GeneratedRegex(@"(? e.Label is null ? $"{e.Id}" : $"{e.Id} - {e.Label}"; +} From fb61b415cc21107089016540feb797bc60b593df Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:38:33 -0400 Subject: [PATCH 03/12] Delete .gitignore dont need. --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index add57be..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -bin/ -obj/ -/packages/ -riderModule.iml -/_ReSharper.Caches/ \ No newline at end of file From 06dcf0a8544438a0b9068429c9545b59c5b50b51 Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:38:55 -0400 Subject: [PATCH 04/12] Delete RobloxAssetChecker.sln dont need --- RobloxAssetChecker.sln | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 RobloxAssetChecker.sln diff --git a/RobloxAssetChecker.sln b/RobloxAssetChecker.sln deleted file mode 100644 index 475aeca..0000000 --- a/RobloxAssetChecker.sln +++ /dev/null @@ -1,16 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RobloxAssetChecker", "RobloxAssetChecker\RobloxAssetChecker.csproj", "{7F29E013-6A0E-4609-B4C8-319F918722C9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7F29E013-6A0E-4609-B4C8-319F918722C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F29E013-6A0E-4609-B4C8-319F918722C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F29E013-6A0E-4609-B4C8-319F918722C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F29E013-6A0E-4609-B4C8-319F918722C9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal From 8bb7e4623cb0325433b91674b2470c16d17d068c Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:39:39 -0400 Subject: [PATCH 05/12] Delete RobloxAssetChecker/RobloxAssetChecker.csproj dont need. --- RobloxAssetChecker/RobloxAssetChecker.csproj | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 RobloxAssetChecker/RobloxAssetChecker.csproj diff --git a/RobloxAssetChecker/RobloxAssetChecker.csproj b/RobloxAssetChecker/RobloxAssetChecker.csproj deleted file mode 100644 index c5ba191..0000000 --- a/RobloxAssetChecker/RobloxAssetChecker.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - net10.0 - enable - enable - - - - - - - From 8b71323bf0178588b5f210c02319ca837109c5cd Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:42:45 -0400 Subject: [PATCH 06/12] Update README.md Better README.md --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 757c224..05a4764 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ -Terminal app (run with CMD or press play in an IDE) +# Roblox Asset Checker -Not affilated with roblox blah blah +simple terminal tool that checks roblox audio ids and tells you if they’re public, archived, or gone + +## how to use + +- run it in an IDE (VS / VS Code) or just `dotnet run` +- put audio ids into `audio.txt` +- run the program + +it will scan everything and spit out a sorted file with results + +## what it does + +- public audio +- archived audio +- missing / moderated audio + +## note + +uses roblox's toolbox api + +## disclaimer + +not affiliated with roblox bla bla bla From 71ed68b4fd4ef5f92c5036aa9ff589efa84aeb0d Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:44:54 -0400 Subject: [PATCH 07/12] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 05a4764..14d380c 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,4 @@ uses roblox's toolbox api ## disclaimer not affiliated with roblox bla bla bla +original creator: @The-Graze From 582983499191a872ea818493c1743683ab10db47 Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:46:28 -0400 Subject: [PATCH 08/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14d380c..425d28f 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,4 @@ uses roblox's toolbox api ## disclaimer not affiliated with roblox bla bla bla -original creator: @The-Graze +original creator: https://github.com/The-Graze From 504e5df7cf3fa368860e72c723e4f9cdce347b32 Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 11:48:21 -0400 Subject: [PATCH 09/12] Rename RobloxAssetChecker/Program.cs to Program.cs --- RobloxAssetChecker/Program.cs => Program.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename RobloxAssetChecker/Program.cs => Program.cs (100%) diff --git a/RobloxAssetChecker/Program.cs b/Program.cs similarity index 100% rename from RobloxAssetChecker/Program.cs rename to Program.cs From 74d5e9128137de341052d2a37cd92778e16536fa Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 12:02:54 -0400 Subject: [PATCH 10/12] Create audio.txt --- audio.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 audio.txt diff --git a/audio.txt b/audio.txt new file mode 100644 index 0000000..9dbeb73 --- /dev/null +++ b/audio.txt @@ -0,0 +1,5 @@ +# Audio Scan List +# Format: ID - optional name +# Example: +123456789 - cool song +987654321 - background music From eea19a956841168f1b81f4a26bd773440ffe3779 Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Fri, 1 May 2026 12:04:10 -0400 Subject: [PATCH 11/12] Switch output format to JSON Changed the output from TXT to structured JSON format for easier parsing and future use. --- Program.cs | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/Program.cs b/Program.cs index eb963e3..bd95532 100644 --- a/Program.cs +++ b/Program.cs @@ -144,17 +144,40 @@ private static async Task Main() } } - var output = - " # Public Audio # \n" + - string.Join("\n", publicList.Select(Format)) + - "\n\n # Archived Audio # \n" + - string.Join("\n", archivedList.Select(Format)) + - "\n\n # Moderated / Missing # \n" + - string.Join("\n", missingList.Select(Format)); + // ========================= + // SAFE JSON BUILDER (FIX) + // ========================= - await File.WriteAllTextAsync("audio - Sorted.txt", output); + string Escape(string s) + { + return s + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\n", "\\n") + .Replace("\r", ""); + } + + string BuildJson(List list) + { + return "[\n" + string.Join(",\n", + list.Select(x => + x.Label is null + ? $" {{ \"id\": {x.Id} }}" + : $" {{ \"id\": {x.Id}, \"label\": \"{Escape(x.Label)}\" }}" + ) + ) + "\n]"; + } + + var jsonOutput = + $@"{{ + ""public"": {BuildJson(publicList)}, + ""archived"": {BuildJson(archivedList)}, + ""missing"": {BuildJson(missingList)} +}}"; + + await File.WriteAllTextAsync("audio - Sorted.json", jsonOutput); - Console.WriteLine("\nDone! Saved to audio - Sorted.txt"); + Console.WriteLine("\nDone! Saved to audio - Sorted.json"); } private static string Format(AssetEntry e) From 60a9b17058e5c77c32cd3e40535a5f53cb6a5842 Mon Sep 17 00:00:00 2001 From: Curvn <50517248+Curvn@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:32:57 -0400 Subject: [PATCH 12/12] Update Program.cs --- Program.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Program.cs b/Program.cs index bd95532..171ec7b 100644 --- a/Program.cs +++ b/Program.cs @@ -144,10 +144,6 @@ private static async Task Main() } } - // ========================= - // SAFE JSON BUILDER (FIX) - // ========================= - string Escape(string s) { return s