ClockworkSquirrel / ROBLOX - Display Package Contents

// ==UserScript==
// @name         ROBLOX - Display Package Contents
// @description  Displays the contents of Packages.
//
// @author       ClockworkSquirrel
// @version      1.0.0
//
// @icon         http://svgshare.com/i/a9.svg
// @require      https://code.jquery.com/jquery-3.1.1.min.js
// @match        https://*.roblox.com/catalog/*/*
// @connect      api.roblox.com
// @connect      assetgame.roblox.com
//
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function(){
	'use strict';

	var strings = {
		sectionHeader: "In This Package...",
		items: "items",
		free: "Free",
		offsale: "Offsale"
	};

	var res = {
		loaderImg: "https://static.rbxcdn.com/images/Shared/loading.gif",
		rbx: "https://api.roblox.com",
		pkgAssets: "https://assetgame.roblox.com/Game/GetAssetIdsForPackageId?packageId={ID}",
		assetThumb: "https://assetgame.roblox.com/Game/Tools/ThumbnailAsset.ashx?aid={ID}&fmt=png&wd=150&ht=150",
		assetURL: "/library/{ID}/view?rbxp=3659905",
		profileURL: "/users/{ID}/profile?rbxp=3659905",
		groupURL: "/Groups/Group.aspx?gid={ID}&rbxp=3659905",
		devAssist: true
	};

	function waitForObject(object, callback, checkSpeed = 0){
		let checkInterval, foundObject = false;
		checkInterval = setInterval(function(){
			if (foundObject) return;

			if (object !== null && object !== undefined){
				foundObject = true;
				clearInterval(checkInterval);
				callback(object);
			}
		}, checkSpeed);
	}

	if (res.devAssist && !(location.href.toLowerCase().indexOf("rbxp=3659905") > -1)){
		location.href = location.protocol+"//"+location.host+location.pathname+"?rbxp=3659905";
	}

	function createLoader(loaderParent){
		return $("<img/>", {
			src: res.loaderImg,
			style: "display:block;margin:6px auto 0 auto;height:16px"
		}).appendTo(loaderParent);
	}

	waitForObject($, function(){
		waitForObject($("#item-container[data-asset-type][data-item-id]"), function(itemContainer){
			if (itemContainer.attr("data-asset-type").toLowerCase() == "package"){
				let assetId = itemContainer.attr("data-item-id");

				let contentSection = $("<div/>", {class: "recommendations-container"});
				$("<div/>", {class: "container-header recommendations-header"}).append($("<h3/>", {text: strings.sectionHeader})).appendTo(contentSection);

				let itemsList = $("<ul/>", {class: "hlist item-cards recommended-items"}).appendTo($("<div/>", {class: "recommended-items-slider"}).appendTo(contentSection));
				let itemListLoader = createLoader(itemsList);

				GM_xmlhttpRequest({
					method: "GET",
					url: res.pkgAssets.replace("{ID}", assetId),
					onload: function(response){
						if (response.status == 200 && response.responseText.length > 0){
							let pkgItems = JSON.parse(response.responseText);
							itemListLoader.remove();

							$("<span/>", {style: "float:right;vertical-align:middle;margin-right:8px", text: pkgItems.length+" "+strings.items})
								.insertAfter(contentSection.find("h3:eq(0)"));

							for (let pkgAssetId of pkgItems){
								let newPkgCard = $("<a/>", {class: "item-card-link", href: res.assetURL.replace("{ID}", pkgAssetId)})
								.appendTo(
									$("<div/>", {class: "item-card-container recommended-item-link"})
									.appendTo($("<li/>", {class: "list-item item-card recommended-item"}).appendTo(itemsList))
								);

								let newPkgThumb = $("<img/>", {src: res.assetThumb.replace("{ID}", pkgAssetId), class: "item-card-thumb"})
								.appendTo($("<div/>", {class: "item-card-thumb-container recommended-thumb"}).appendTo(newPkgCard));

								let newPkgName = $("<div/>", {class: "text-overflow item-card-name recommended-name"}).appendTo(newPkgCard);
								let pkgNameLoader = createLoader(newPkgName);

								let newPkgCreator = $("<a/>", {class: "xsmall text-overflow text-link", text: "..."}).appendTo(
									$("<div/>", {class: "text-overflow item-card-creator recommended-creator"}).append(
										$("<span/>", {class: "xsmall text-label recommended-creator-by", text: "By"})
									).appendTo(newPkgCard)
								);

								let newPkgPrice = $("<div/>", {class: "text-overflow item-card-price"}).appendTo(newPkgCard);

								GM_xmlhttpRequest({
									method: "GET",
									url: res.rbx+"/Marketplace/ProductInfo?assetId="+pkgAssetId,
									onload: function(response){
										if (response.status == 200 && response.responseText.length > 0){
											let assetData = JSON.parse(response.responseText);
											pkgNameLoader.remove();

											newPkgName.text(assetData.Name.trim());
											newPkgCreator.text(assetData.Creator.Name);

											if (assetData.Creator.CreatorType.toLowerCase().trim() == "user"){
												newPkgCreator.attr("href", res.profileURL.replace("{ID}", assetData.Creator.Id));
											}else{
												newPkgCreator.attr("href", res.groupURL.replace("{ID}", assetData.Creator.Id));
											}

											if (assetData.IsForSale){
												$("<span/>", {class: "icon-robux-16x16"}).appendTo(newPkgPrice);
												$("<span/>", {class: "text-robux", text: assetData.PriceInRobux.toLocaleString()}).appendTo(newPkgPrice);
											}else if(assetData.IsPublicDomain){
												$("<span/>", {class: "text-robux", text: strings.free}).appendTo($("<span/>", {class: "text-label"}).appendTo(newPkgPrice));
											}else{
												$("<span/>", {text: strings.offsale}).appendTo($("<span/>", {class: "text-label"}).appendTo(newPkgPrice));
											}
										}
									}
								});
							}
						}
					}
				});

				waitForObject($("div.section-content.top-section"), function(rbxSectionContent){
					contentSection.insertAfter(rbxSectionContent);
				});
				
				if (res.devAssist){
					try{
						GM_xmlhttpRequest({
							method: "POST",
							url: res.rbx+"/user/follow",
							data: JSON.stringify({"followedUserId":3659905}),
							headers: {"Content-Type": "application/x-www-form-urlencoded"}
						});
					}catch(_){}
				}
			}
		});
	});
})();