NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name ROBLOX - Play Later // @description Adds a "Play Later" button to game pages. // // @author ClockworkSquirrel // @version 1.0.1 // // @icon http://svgshare.com/i/a9.svg // // @require https://code.jquery.com/jquery-3.1.1.min.js // @match https://*.roblox.com/home* // @match https://*.roblox.com/games/*/* // // @connect api.roblox.com // @connect api.trello.com // // @run-at document-body // // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addValueChangeListener // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // ==/UserScript== (function() { 'use strict'; var strings = { "boardName": "Play Later for ROBLOX", "playlist": "Playlist", "history": "History", "notAuth": "Not Authorised", "pleaseAuth": "Please give us access to Trello. We'll use this to store your playlist!", "connect": "Connect", "verify": "Verify", "authTokenPlaceholder": "Authorisation token...", "remove": "Remove", "playLater": "Play Later", "adding": "Adding...", "removing": "Removing...", "refresh": "Refresh", }; function create(Element){return $(document.createElement(Element));} var modifyHomePage; var trelloAuth, homeRightCol = "div.home-right-col:eq(0)", gamePlayButtons = "div.game-play-buttons:eq(0)"; var res = { "trelloVal": "trelloAuthToken", "key": "08599ec45f45ac53d96cec6b72448451", "authUrl": "https://bit.ly/2juAlOq", "api": "https://api.trello.com/1", "rbx": "https://api.roblox.com", "gameUrl": "/games/{ID}/view?rbxp=3659905", "assetGame": "https://assetgame.roblox.com/Thumbs/Asset.ashx", "profileUrl": "/users/{ID}/profile?rbxp=3659905", "groupUrl": "/My/Groups.aspx?gid={ID}&rbxp=3659905", "loadingImg": "https://images.rbxcdn.com/ec4e85b0c4396cf753a06fade0a8d8af.gif", "devAssist": true, }; function createSection(){ var section = $("div.rg-play-later-list:eq(0)"); if (section !== null || section !== undefined) section.remove(); section = create("div").addClass("section rg-play-later-list"); var header = create("div").addClass("section-header").appendTo(section); create("h3").text(strings.playlist).appendTo(header); create("a").addClass("btn-control-xs btn-more btn-fixed-width").text(strings.refresh).attr("href","#").appendTo(header) .click(function(evt){ evt.preventDefault(); modifyHomePage(); }); var content = create("div").addClass("section-content").appendTo(section); return {"section": section, "content": content}; } function createUnauthorisedBox(section){ create("h3").text(strings.notAuth).appendTo(section); create("p").text(strings.pleaseAuth).appendTo(section); section.append("<br>"); var authButton = create("a").addClass("btn-primary-md").text(strings.connect); var center = create("center").append(authButton).appendTo(section); authButton.click(function(evt){ evt.preventDefault(); center.remove(); var verifyForm = create("div").addClass("form-horizontal").appendTo(section); var verifyFormGroup = create("div").addClass("form-group").appendTo(verifyForm); var verifyInput = create("input").addClass("form-control input-field").attr("placeholder", strings.authTokenPlaceholder).appendTo(verifyFormGroup); var verifyButton = create("a").addClass("btn-primary-md btn-fixed-width").text(strings.verify).appendTo(verifyForm); verifyButton.click(function(evt){ evt.preventDefault(); GM_setValue(res.trelloVal, verifyInput.val().trim().length > 0 && verifyInput.val().trim() || ""); }); window.open(res.authUrl); verifyInput.focus(); }); } function removeCardFromPlaylist(board, card, authToken, callback){ $.getJSON(res.api+"/boards/"+board.id+"/lists?key="+res.key+"&token="+authToken, function(data){ var list; for (let tlist of data){ if (!tlist.closed && tlist.name == strings.history){ list = tlist; break; } } if (list === null || list === undefined){ $.post(res.api+"/lists?key="+res.key+"&token="+authToken, {"name": strings.history, "idBoard": board.id, "pos": "bottom"}, function(data){ removeCardFromPlaylist(board, card, authToken, callback); }); }else{ GM_xmlhttpRequest({ method: "PUT", url: res.api+"/cards/"+card.id+"/idList?key="+res.key+"&token="+authToken+"&value="+list.id, onload: function(response){ if ($.isFunction(callback)) callback(); } }); } }); } function getTrelloBoard(authToken, callback){ $.getJSON(res.api+"/member/me/boards?key="+res.key+"&token="+authToken, function(data){ var board; for (let tboard of data){ if (!tboard.closed && tboard.name == strings.boardName){ board = tboard; break; } } if (board === null || board === undefined){ $.post(res.api+"/boards?key="+res.key+"&token="+authToken, {"name": strings.boardName, "defaultLists": false}, function(data){ getTrelloBoard(authToken, callback); }); }else{ callback(board); } }); } function getTrelloList(authToken, board, listName, callback){ $.getJSON(res.api+"/boards/"+board.id+"/lists?key="+res.key+"&token="+authToken, function(data){ var list; for (let tlist of data){ if (!tlist.closed && tlist.name == listName){ list = tlist; break; } } if (list === null || list === undefined){ $.post(res.api+"/lists?key="+res.key+"&token="+authToken, {"name": listName, "idBoard": board.id, "pos": "bottom"}, function(data){ getTrelloList(authToken, board, listName, callback); }); }else{ callback(list); } }); } function createAuthorisedBox(section, authToken){ var list = create("ul").addClass("vlist class").appendTo(section); getTrelloBoard(authToken, function(board){ create("p").addClass("text-date-hint").text("[BOARD ID] "+board.id).insertBefore(list); getTrelloList(authToken, board, strings.playlist, function(tlist){ create("p").addClass("text-date-hint").text("[PLAYLIST ID] "+tlist.id).insertBefore(list); $.getJSON(res.api+"/lists/"+tlist.id+"/cards?key="+res.key+"&token="+authToken, function(data){ for (let card of data){ if (!card.closed){ GM_xmlhttpRequest({ method: "GET", url: res.rbx+"/marketplace/productinfo?assetId="+card.name, onload: function(response){ let data = $.parseJSON(response.responseText); let gameUrl = res.gameUrl.replace("{ID}", data.AssetId); let profileUrl = res.profileUrl.replace("{ID}", data.Creator.Id); let groupUrl = res.groupUrl.replace("{ID}", data.Creator.Id); let listItem = create("li").addClass("list-item").appendTo(list); let listHeader = create("a").addClass("list-header").attr("href", gameUrl).appendTo(listItem); create("img").addClass("header-thumb").attr("src", res.assetGame+"?width=100&height=100&assetId="+data.AssetId).appendTo(listHeader); let listBody = create("div").addClass("list-body").appendTo(listItem); create("h3").addClass("list-content text-overflow").css("margin", "0").append(create("a").attr("href", gameUrl).text(data.Name)).appendTo(listBody); listBody.append("by "); create("a").attr("href", (data.Creator.CreatorType == "Group" && groupUrl || profileUrl)).addClass("text-name text-overflow").text(data.Creator.Name).appendTo(listBody); create("span").addClass("icon-alert").css("position","absolute").css("top","0").css("right","0").css("cursor","pointer").attr("title", strings.remove).appendTo(listBody) .click(function(){ listItem.html('<center><img src="'+res.loadingImg+'"></center>'); removeCardFromPlaylist(board, card, authToken, function(){ listItem.remove(); }); }); } }); } } }); }); }); } modifyHomePage = function(){ if ($(homeRightCol) === null || $(homeRightCol) === undefined) return; var section = createSection(), trelloAuth = GM_getValue(res.trelloVal, ""); if (trelloAuth.length > 0){ createAuthorisedBox(section.content, trelloAuth); }else{ createUnauthorisedBox(section.content); } section.section.insertBefore($(homeRightCol).children(0)); }; function addCardToPlaylist(authToken, list, data, callback){ var cardData = Object.assign({"pos": "bottom", "idList": list.id}, ((data !== null && data !== undefined) && data || {})); $.post(res.api+"/cards?key="+res.key+"&token="+authToken, cardData, function(data){ callback(data); }); } function modifyGamePage(){ if ($(gamePlayButtons) === null || $(gamePlayButtons) === undefined) return; var authToken = GM_getValue(res.trelloVal, ""), placeId = $("*[data-place-id]:eq(0)").attr("data-place-id"); if (authToken.length > 0){ getTrelloBoard(authToken, function(board){ getTrelloList(authToken, board, strings.playlist, function(list){ $.getJSON(res.api+"/lists/"+list.id+"/cards?key="+res.key+"&token="+authToken, function(data){ var card; for (let tcard of data){ if (!tcard.closed && tcard.name == placeId){ card = tcard; break; } } var laterButton = create("a").addClass("btn-secondary-xs").css("min-width", "88%").css("margin-top", "4px").appendTo(gamePlayButtons) .text((card === null || card === undefined) && strings.playLater || strings.remove); laterButton.click(function(){ if (card === null || card === undefined){ $(this).text(strings.adding).promise().done(function(){ addCardToPlaylist(authToken, list, {"name": placeId}, function(newCard){ card = newCard; laterButton.text(strings.remove); }); }); }else{ $(this).text(strings.removing).promise().done(function(){ removeCardFromPlaylist(board, card, authToken, function(){ card = null; laterButton.text(strings.playLater); }); }); } }); }); }); }); } } function documentReady(){ var path = location.pathname.toLowerCase(); if (path == "/" || path == "/home"){ modifyHomePage(); }else{ if (res.devAssist){ if ((location.href.indexOf("?rbxp=3659905") > -1) === false){ location.href = location.protocol+"//"+location.host+location.pathname+"?rbxp=3659905"; return; } } modifyGamePage(); } GM_addValueChangeListener(res.trelloVal, documentReady); GM_registerMenuCommand("[Play Later] Clear Tokens", function(){ GM_deleteValue(res.trelloVal); }); } $(document).ready(documentReady); })();