ClockworkSquirrel / ROBLOX - Pinned Users

// ==UserScript==
// @name         ROBLOX - Pinned Users
// @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
// @require      https://pastebin.com/raw/nEq3ZVCM
// @match        https://*.roblox.com/home*
// @match        https://*.roblox.com/users/*/profile
//
// @connect      api.roblox.com
//
// @run-at       document-body
//
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_addValueChangeListener
// ==/UserScript==

(function() {
    'use strict';
	
	var res = {
		"auto-update-on": true,
		
		"notification-stream-data": "https://www.roblox.com/notification-stream/notification-stream-data",
		"presence": "https://www.roblox.com/presence/users?userIds=",
		"user": "https://api.roblox.com/users/",
		"avatar-img": "https://assetgame.roblox.com/Thumbs/Avatar.ashx?width=100&height=100&userId=",
	};
	
	function create(Element){return $(document.createElement(Element));}
	var friendsDiv = "div.section.home-friends", profileFooter = "div.profile-about-footer:eq(0)";
	var isHome = (location.pathname == "/home"), users = [], userId = 0;
	
	function addToPinnedUsers(addUserId){
		users = GM_getValue(userId+"_PINNED", users);
		users.push(addUserId); GM_setValue(userId+"_PINNED", users);
	}
	
	function removeFromPinnedUsers(removeUserId){
		var newUsers = [];
		
		users = GM_getValue(userId+"_PINNED", users);
		for (var thisUserId of users){
			if (thisUserId !== removeUserId){
				newUsers.push(thisUserId);
			}
		}
		
		users = newUsers;
		GM_setValue(userId+"_PINNED", users);
		
		return users;
	}
	
	function userIsPinned(checkUserId){
		for (var thisUserId of users){
			if (thisUserId === checkUserId){
				return true;
			}
		}
		
		return false;
	}
	
	function modifyHomePage(){
		var mainDiv = $("div.home-pinned-users:eq(0)");
		if (mainDiv !== null && mainDiv !== undefined){
			mainDiv.remove();
		}
		
		mainDiv = create("div").addClass("col-xs-12").addClass("section").addClass("home-friends")
		.addClass("home-pinned-users").insertBefore(friendsDiv);
			
		create("div").addClass("container-header").append("<h3>Pinned Users ("+users.length+")</h3>").appendTo(mainDiv);
		
		var contentDiv = create("div").addClass("section-content").appendTo(mainDiv);
		$.getJSON(res.presence+users.join("&userIds="), function(data){
			var userList = create("ul").addClass("hlist").appendTo(contentDiv);
			
			$.each(data, function(){
				var presence = this;

				GM_xmlhttpRequest({
					method: "GET",
					url: res.user+presence.UserId,
					onload: function(response){
						var data = $.parseJSON(response.responseText);
						
						var userMain = create("li").addClass("list-item friend").appendTo(userList);
						var avatarContainer = create("div").addClass("avatar-container").appendTo(userMain);

						var profileLink = create("a").addClass("avatar avatar-card-fullbody friend-link")
						.attr("title", data.Username).attr("href", "/users/"+data.Id+"/profile")
						.appendTo(avatarContainer);

						var imgWrapper = create("span").addClass("avatar-card-link").addClass("friend-avatar").appendTo(profileLink)
						.append('<img src="'+res["avatar-img"]+data.Id+'" class="avatar-card-image" alt="'+data.Username+'">');

						profileLink.append('<span class="text-overflow friend-name">'+data.Username+'</span>');

						if (presence.UserPresenceType > 0){
							var statusIcon = create("span").addClass("avatar-status").addClass("friend-status")
							.attr("title", presence.LastLocation);

							if (presence.UserPresenceType === 1){
								statusIcon.addClass("icon-online").appendTo(avatarContainer);
							}else if(presence.UserPresenceType === 2){
								var placeLink = create("a").addClass("place-link").attr("href", "/users/"+data.Id+"/profile")
								.appendTo(avatarContainer);

								statusIcon.addClass("icon-game").appendTo(placeLink);
							}else{
								statusIcon.addClass("icon-studio").appendTo(avatarContainer);
							}
						}
					},
				});
			});
		});
	}
	
	function modifyProfilePage(){
		var thisUser = $("div[data-profileuserid]:eq(0)").attr("data-profileuserid");
		
		var pinButton = create("a").addClass("btn-secondary-xs").appendTo(create("div").appendTo($(profileFooter).parent()))
		.text(userIsPinned(thisUser) && "Unpin from Home" || "Pin to Home");
		
		pinButton.click(function(evt){
			evt.preventDefault();
			
			if (userIsPinned(thisUser)){
				removeFromPinnedUsers(thisUser);
			}else{
				addToPinnedUsers(thisUser);
			}
			
			pinButton.text(userIsPinned(thisUser) && "Unpin from Home" || "Pin to Home");
		});
	}
	
	function documentLoaded(){
		$.getJSON(res["notification-stream-data"], function(data){
			userId = data.CurrentUserId;
			users = GM_getValue(userId+"_PINNED", []);
			
			if (isHome){
				modifyHomePage();
				
				GM_addValueChangeListener(userId+"_PINNED", function(){
					users = GM_getValue(userId+"_PINNED", users);
					modifyHomePage();
				});
				
				if (res["auto-update-on"]){
					setInterval(modifyHomePage, 15000);
				}
			}else{
				modifyProfilePage();
			}
		});
	}
	
	$(document).ready(documentLoaded);
})();