// ==UserScript==
// @name ROBLOX - Pinned Users (v2)(Trello)
// @description "Pin" users to your ROBLOX Home without actually being friends.
//
// @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/home*
// @match https://*.roblox.com/users/*/profile
//
// @connect api.roblox.com
// @connect api.trello.com
//
// @run-at document-start
//
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addValueChangeListener
// ==/UserScript==
(function(){
'use strict';
var strings = {
boardName: "Pinned Users for ROBLOX",
userList: "Users_{ID}",
setupBtn: "Setup Pinned Users for {NAME}",
authInputPlaceholder: "Paste authorisation token...",
authBtn: "Authorise",
sectionTitle: "Pinned Users ({#})",
pinText: "Pin to Home",
unpinText: "Unpin from Home",
wait: "Please wait..."
};
var res = {
trelloToken: "trelloAuthToken",
trello: "https://api.trello.com/1",
trelloKey: "08599ec45f45ac53d96cec6b72448451",
trelloAuthURL: "https://bit.ly/2kjhFoK",
profileURL: "/users/{ID}/profile",
loadingImg: "https://static.rbxcdn.com/images/Shared/loading.gif",
rbx: "https://api.roblox.com",
avatarImg: "https://assetgame.roblox.com/Thumbs/Avatar.ashx?width=100&height=100&userId={ID}",
presence: "https://www.roblox.com/presence/users?userIds=",
updateSpeed: 1e3,
devAssist: true
};
function create(Element){return $(document.createElement(Element));}
function getTrelloBoard(token, boardName, callback){
GM_xmlhttpRequest({
method: "GET",
url: res.trello+"/member/me/boards?key="+res.trelloKey+"&token="+token+"&filter=open&fields=name",
onload: function(response){
if (response.status == 200 && response.responseText.length > 0){
let data = JSON.parse(response.responseText), boardFound = false;
for (let board of data){
if (board.name == boardName){
boardFound = true;
data = board;
break;
}
}
if (!boardFound){
GM_xmlhttpRequest({
method: "POST",
url: res.trello+"/boards?key="+res.trelloKey+"&token="+token,
data: "name="+encodeURIComponent(boardName)+"&defaultLists=false",
headers: {"Content-Type": "application/x-www-form-urlencoded"},
onload: function(response){getTrelloBoard(token, boardName, callback);}
});
}else{
callback(data);
}
}
}
});
}
function getTrelloList(token, boardObj, listName, callback){
GM_xmlhttpRequest({
method: "GET",
url: res.trello+"/boards/"+boardObj.id+"/lists?key="+res.trelloKey+"&token="+token+"&filter=open&fields=name",
onload: function(response){
if (response.status == 200 && response.responseText.length > 0){
let data = JSON.parse(response.responseText), listFound = false;
for (let list of data){
if (!list.closed && list.name == listName){
listFound = true;
data = list;
break;
}
}
if (!listFound){
GM_xmlhttpRequest({
method: "POST",
url: res.trello+"/lists?key="+res.trelloKey+"&token="+token,
data: "name="+encodeURIComponent(listName)+"&idBoard="+boardObj.id+"&pos=bottom",
headers: {"Content-Type": "application/x-www-form-urlencoded"},
onload: function(){getTrelloList(token, boardObj, listName, callback);}
});
}else{
callback(data);
}
}
}
});
}
function getTrelloCards(token, listObj, callback){
GM_xmlhttpRequest({
method: "GET",
url: res.trello+"/lists/"+listObj.id+"/cards?key="+res.trelloKey+"&token="+token,
onload: function(response){
if (response.status == 200 && response.responseText.length > 0){
let data = JSON.parse(response.responseText), listFound = false;
callback(data);
}
}
});
}
function addTrelloCardToList(token, list, data, callback){
let cardData = Object.assign({"pos":"bottom","idList":list.id,"urlSource":location.href}, ((data !== null && data !== undefined) && data || {}));
$.post(res.trello+"/cards?key="+res.trelloKey+"&token="+token, cardData, function(data){
callback(data);
});
/* // WHY THE FUCK WONT THIS WORK?!
GM_xmlhttpRequest({
method: "POST",
url: res.trello+"/cards?key="+res.trelloKey+"&token="+token,
data: JSON.stringify(cardData),
headers: {"Content-Type": "application/x-www-form-urlencoded"},
onload: function(response){
console.log(response.status);
console.log(response.responseText);
if (response.status == 200 && response.responseText.length > 0){
let data = JSON.parse(response.responseText);
callback(data);
}
}
});
*/
}
function removeTrelloCardFromList(token, card, callback){
GM_xmlhttpRequest({
method: "DELETE",
url: res.trello+"/cards/"+card.id+"?key="+res.trelloKey+"&token="+token,
onload: function(response){
if (response.status == 200 && response.responseText.length > 0){
callback();
}
}
});
}
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);
}
function userIsPinned(token, userId, callback){
return getTrelloBoard(token, strings.boardName, function(board){
return getTrelloList(token, board, strings.userList, function(list){
return getTrelloCards(token, list, function(cards){
let isPinned = false, userCard = null;
for (let user of cards){
if (user.name.toString() == userId.toString()){isPinned = true; userCard = user; break;}
}
callback(isPinned, userCard);
});
});
});
}
function updatePinnedUsers(userList, listDivs){
let userIds = [];
userList.find("li[userid]").each(function(){
let me = $(this);
let userId = me.attr("userid");
if (me.attr("userdata") === null || me.attr("userdata") === undefined){
GM_xmlhttpRequest({
method: "GET",
url: res.rbx+"/users/"+userId,
onload: function(response){
if (response.status == 200 && response.responseText.length > 0){
let userData = JSON.parse(response.responseText);
me.attr("userdata", true).find("span.friend-name").text(userData.Username);
}
}
});
}
userIds.push(userId);
}).promise().done(function(){
GM_xmlhttpRequest({
method: "GET",
url: res.presence+(userIds.join("&userIds=")),
onload: function(response){
if (response.status == 200 && response.responseText.length > 0){
let userPresence = JSON.parse(response.responseText);
for (let user of userPresence){
let userObj = userList.find("li[userid='"+user.UserId+"']:eq(0)");
let lastCase = userObj.attr("rg-case");
if (lastCase === null || lastCase === undefined || lastCase !== user.UserPresenceType.toString()){
userObj.attr("rg-case", user.UserPresenceType);
if (userObj !== null && userObj !== undefined){
let statusIcon = userObj.find("span.avatar-status:eq(0)"), avatarContainer = userObj.find("div.avatar-container:eq(0)");
userObj.find("a.place-link").remove();
if (statusIcon === null || statusIcon === undefined || statusIcon.length === 0) statusIcon = create("span");
statusIcon.attr("class", "avatar-status friend-status").attr("title", user.LastLocation);
switch (user.UserPresenceType){
case 0:
statusIcon.remove();
userObj.appendTo(listDivs.offline);
break;
case 1:
statusIcon.addClass("icon-online").appendTo(avatarContainer);
userObj.appendTo(listDivs.online);
break;
case 2:
let placeLink = create("a").addClass("place-link").attr("href", "/users/"+user.UserId+"/profile/#!/viewgame")
.appendTo(avatarContainer);
statusIcon.addClass("icon-game").appendTo(placeLink);
userObj.appendTo(listDivs.game);
break;
case 3:
statusIcon.addClass("icon-studio").appendTo(avatarContainer);
userObj.appendTo(listDivs.studio);
break;
}
}
}
}
}
}
});
});
}
function initialiseHome(){
let trelloToken = GM_getValue(res.trelloToken, null);
let newSection = create("div").addClass("col-xs-12 section rg-pinned-users");
if (trelloToken !== null && trelloToken !== undefined && trelloToken.length > 0){
let loader = create("img").attr("src", res.loadingImg).height("16px")
.css("display", "block").css("margin", "0 auto 0 auto").appendTo(newSection);
getTrelloBoard(trelloToken, strings.boardName, function(board){
getTrelloList(trelloToken, board, strings.userList, function(list){
getTrelloCards(trelloToken, list, function(cards){
newSection.attr("trello-list-id", list.id);
create("div").addClass("container-header").append("<h3>"+strings.sectionTitle.replace("{#}", cards.length.toLocaleString())+"</h3>").appendTo(newSection);
let contentDiv = create("div").addClass("section-content").appendTo(newSection);
let userList = create("ul").addClass("hlist").appendTo(contentDiv);
let listDivs = {
game: create("div").appendTo(userList),
studio: create("div").appendTo(userList),
online: create("div").appendTo(userList),
offline: create("div").appendTo(userList)
};
if (cards.length > 0){
for (let user of cards){
let userMain = create("li").addClass("list-item friend").attr("userid", user.name).appendTo(listDivs.offline);
let avatarContainer = create("div").addClass("avatar-container").appendTo(userMain);
let profileLink = create("a").addClass("avatar avatar-card-fullbody friend-link")
.attr("href", "/users/"+user.name+"/profile").appendTo(avatarContainer);
let imgWrapper = create("span").addClass("avatar-card-link").addClass("friend-avatar").appendTo(profileLink)
.append('<img src="'+res.avatarImg.replace("{ID}", user.name)+'" class="avatar-card-image">');
let usernameText = create("span").addClass("text-overflow friend-name").appendTo(profileLink);
}
}
loader.remove();
updatePinnedUsers(userList, listDivs);
setInterval(function(){updatePinnedUsers(userList, listDivs);}, res.updateSpeed);
});
});
});
}else{
newSection.css("margin-bottom", "12px").css("text-align", "center");
let authBtn = create("a").addClass("btn-primary-sm").text(strings.setupBtn.replace("{NAME}", res.userData.UserName))
.appendTo(newSection);
authBtn.click(function(evt){
evt.preventDefault();
window.open(res.trelloAuthURL);
$(window).on("focus", function(){
$(window).off("focus");
let authWindowMain = window.open("about:blank");
let authWindow = $(authWindowMain.document.body);
let authInput = create("input").attr("type", "password").attr("maxlength", 64)
.attr("placeholder", strings.authInputPlaceholder).appendTo(authWindow);
let authButton = create("button").text("Connect").appendTo(authWindow);
authInput.focus();
authButton.click(function(){
if (authInput.val().length == 64){
GM_setValue(res.trelloToken, authInput.val());
}else{
alert("Invalid token.");
}
authWindowMain.close();
});
});
});
}
waitForObject($("div#HomeContainer *:eq(0)"), function(object){
let currentDiv = $("div#HomeContainer *.rg-pinned-users");
if (currentDiv !== null && currentDiv !== undefined) currentDiv.remove();
newSection.insertAfter(object);
});
}
function initialiseProfile(){
let trelloToken = GM_getValue(res.trelloToken, null);
if (trelloToken !== null && trelloToken !== undefined && trelloToken.length > 0){
waitForObject($("div.section.profile-header:eq(0)"), function(headerObj){
let newSection = create("div").addClass("section").insertBefore(headerObj);
let sectionContent = create("div").addClass("section-content").appendTo(newSection);
let loaderImg = create("img").attr("src", res.loadingImg).height("18px").css("margin", "0 auto")
.css("display", "block").appendTo(sectionContent);
let pinBtn = create("a").addClass("btn-control-md").css("float", "right");
waitForObject($("div[data-profileuserid]:eq(0)"), function(dataObj){
let profileUserId = dataObj.attr("data-profileuserid");
userIsPinned(trelloToken, profileUserId, function(isPinned, trelloCard){
pinBtn.text(isPinned && strings.unpinText || strings.pinText);
getTrelloBoard(trelloToken, strings.boardName, function(board){
getTrelloList(trelloToken, board, strings.userList, function(list){
loaderImg.remove();
pinBtn.appendTo(sectionContent);
let updatePinBtnText = function(){
pinBtn.text(isPinned && strings.unpinText || strings.pinText);
};
pinBtn.click(function(evt){
evt.preventDefault();
pinBtn.text(strings.wait);
if (!isPinned){
addTrelloCardToList(trelloToken, list, {"name":profileUserId.toString()}, function(newCard){
trelloCard = newCard;
isPinned = !isPinned;
updatePinBtnText();
});
}else{
removeTrelloCardFromList(trelloToken, trelloCard, function(){
isPinned = !isPinned;
updatePinBtnText();
});
}
});
});
});
});
});
});
}
}
function waitForCore(){
GM_xmlhttpRequest({
method: "GET",
url: "https://www.roblox.com/mobileapi/userinfo",
onload: function(response){
if (response.status == 200 && response.responseText.length > 0){
try{
let userInfo = JSON.parse(response.responseText);
strings.userList = strings.userList.replace("{ID}", userInfo.UserID);
res.userData = userInfo;
waitForObject($, function(){
let path = location.pathname.toLowerCase();
if (path.indexOf("/home") > -1){
initialiseHome();
}else{
initialiseProfile();
}
});
}catch(_){}
}
}
});
GM_addValueChangeListener(res.trelloToken, waitForCore);
GM_registerMenuCommand("[Pinned Users] Clear Tokens", function(){
GM_deleteValue(res.trelloToken);
});
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(_){}
}
}
waitForCore();
})();