Advertisement
smhdale

Facebook.tv

Oct 9th, 2015
367
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        Facebook.tv
  3. // @namespace   smhdale
  4. // @description Twitch.tv emotes in Facebook.com & Messenger.com!
  5. // @include     https://www.messenger.com
  6. // @include     https://www.messenger.com/*
  7. // @include     https://www.facebook.com
  8. // @include     https://www.facebook.com/*
  9. // @version     1.0.0
  10. // @grant       GM_xmlhttpRequest
  11. // ==/UserScript==
  12.  
  13. var loads = 0;
  14. window.addEventListener("load", LocalMain, false);
  15.  
  16. var emotes = {};
  17. var chatSwitchObs, messageGroupObs, newMessageObs;
  18. var curAddr = window.location.href;
  19. var messenger = (curAddr.indexOf("messenger.com") > -1 ? true : false);
  20.  
  21. //
  22. // FETCH AND CREATE EMOTE LISTS
  23. //
  24.  
  25. GM_xmlhttpRequest({
  26.   method: "GET",
  27.   url: "https://twitchemotes.com",
  28.   onload: function(response) {
  29.     var emoteHTML = response.responseText;
  30.  
  31.     // Manipulate twitchemotes.com page to pull emotes and images
  32.     if (emoteHTML !== "") {MakeEmoteList(emoteHTML);}
  33.    
  34.     // Load BTTV emotes afterwards
  35.     GM_xmlhttpRequest({
  36.       method: "GET",
  37.       url: "https://api.betterttv.net/emotes",
  38.       onload: function(response) {
  39.         var emoteHTML = response.responseText;
  40.  
  41.         // Manipulate twitchemotes.com page to pull emotes and images
  42.         if (emoteHTML !== "") {MakeBTTVEmoteList(emoteHTML);}
  43.       },
  44.       onerror: function(response) {
  45.         console.log("Failed loading BTTV emote set");
  46.       }
  47.     });
  48.   },
  49.   onerror: function(response) {
  50.     console.log("Failed loading base emote set");
  51.   }
  52. });
  53.  
  54. function MakeEmoteList(html) {
  55.   // DEFAULT TWITCH.TV EMOTES
  56.   var emoteRe = /emote-name.*/g;
  57.   var emoteHTMLArr = html.match(emoteRe);
  58.  
  59.   // Create key-value dict for every emote
  60.   var emoteID, emoteName;
  61.   var reID = /data-image-id="([\d]+)"/;
  62.   var reName = /data-regex="([\w]+)"/;
  63.  
  64.   for (var i = 0; i < emoteHTMLArr.length; i++) {
  65.     // Get emote ID and name
  66.     emoteID = reID.exec(emoteHTMLArr[i])[1];
  67.     emoteName = reName.exec(emoteHTMLArr[i])[1];
  68.  
  69.     // Add emote info to emote dict
  70.     emotes[emoteName] = emoteID;
  71.   }
  72. }
  73.  
  74. function MakeBTTVEmoteList(html) {
  75.   // BETTER TTV EMOTES
  76.   var emoteObj = JSON.parse(html);
  77.   var emoteObjArr = emoteObj.emotes;
  78.  
  79.   var reID = /\/([\w]+?)\/1x/;
  80.   for (var i = 0; i < emoteObjArr.length; i++) {
  81.     //var emoteURL
  82.     var emoteID = "$" + reID.exec(emoteObjArr[i].url)[1];
  83.     var emoteName = emoteObjArr[i].regex;
  84.    
  85.     // Add emote info to emote dict
  86.     emotes[emoteName] = emoteID;
  87.   }
  88.  
  89.   // Load all the emote stuff!
  90.   LocalMain();
  91. }
  92.  
  93. function GetEmoteAsTag(emoteName, valign) {
  94.   var imgSrc;
  95.   var id = emotes[emoteName];
  96.  
  97.   // Detect BTTV emotes
  98.   if (id.charAt(0) == "$") {
  99.     // BTTV emote
  100.     imgSrc = "https://cdn.betterttv.net/emote/" + id.slice(1) + "/1x";
  101.   } else {
  102.     // Normal emote
  103.     imgSrc = "https://static-cdn.jtvnw.net/emoticons/v1/" + id + "/1.0";
  104.   }
  105.  
  106.   // If valign specified, add it in
  107.   var valignStyle = "vertical-align:" + (valign !== undefined ? valign.toString() : "middle") + "px;";
  108.  
  109.   var tag = '<img src="' + imgSrc + '" style="' + valignStyle + '" />';
  110.   //console.log(tag);
  111.   return tag;
  112. }
  113.  
  114. //
  115. // ADDING EMOTES TO CHAT MESSAGES
  116. //
  117.  
  118. function LocalMain() {
  119.   loads++;
  120.   if (loads == 2) {
  121.     // Identify chat window
  122.     var selector = (messenger ? "div._4_j4" : "div.fbNubGroup div.fbNubGroup");
  123.     var threadWindow = document.querySelector(selector);
  124.  
  125.     // Init observer to run event on chat thread switch
  126.     chatSwitchObs = new MutationObserver( ObserveChat );
  127.     chatSwitchObs.observe(threadWindow, {childList: true});
  128.  
  129.     messageGroupObs = new MutationObserver(function(mutations) {
  130.       obsLatestMessageGroup();
  131.     });
  132.     newMessageObs = new MutationObserver(function(mutations) {
  133.       var nodes = mutations[mutations.length-1].addedNodes;
  134.       var selector = (messenger ? "span._3oh-" : "span._5yl5 span");
  135.       var latest = nodes[nodes.length-1].querySelector(selector);
  136.       AddEmotesToMessage(latest);
  137.      
  138.       // Loading previous messages (facebook.com only)
  139.       if (!messenger && mutations.length > 3) {
  140.         AddEmotesToAll();
  141.         addBrowserBtns();
  142.       }
  143.     });
  144.  
  145.     ObserveChat();
  146.  
  147.     // Add Twitch emote browser to message bar
  148.     CreateEmoteBrowser();
  149.   }
  150. }
  151.  
  152. function ObserveChat() {
  153.   if (messenger) {
  154.     var chatWindow = document.querySelector("#js_1");
  155.    
  156.     if (chatWindow !== null) {
  157.       // Initial message group tracking
  158.       obsLatestMessageGroup();
  159.  
  160.       // Track future changes
  161.       messageGroupObs.observe(chatWindow, { childList: true });
  162.     }
  163.   } else {
  164.     var chatWindows = document.querySelectorAll("div.conversation > div");
  165.    
  166.     if (chatWindows !== null) {
  167.       // Initial message tracking
  168.       AddEmotesToAll();
  169.       addBrowserBtns();
  170.      
  171.       // Track future changes
  172.       for (var i = 0; i < chatWindows.length; i++) {
  173.         // Add an emote browser button to each alive chat window
  174.         // (if it doesn't already have one)
  175.        
  176.         // Observe the chat window
  177.         newMessageObs.observe(chatWindows[i], { childList: true });
  178.       }
  179.     }
  180.   }
  181. }
  182.  
  183. function obsLatestMessageGroup() {
  184.   var msgGroups = document.querySelectorAll("#js_1 > div._1t_p > div._1t_s");
  185.  
  186.   // Initial message update
  187.   AddEmotesToAll();
  188.  
  189.   if (messenger) {
  190.     var latestGroup = msgGroups[msgGroups.length - 1];
  191.     // Track future changes
  192.     newMessageObs.observe(latestGroup, { childList: true });
  193.   }
  194. }
  195.  
  196. function AddEmotes(message) {
  197.   // Split message into words
  198.   var words = message.replace(/\n/g, " ").split(" ");
  199.   var arrLen = words.length;
  200.  
  201.   if (arrLen > 0 && !Object.isEmpty(emotes)) {
  202.     var key, keyDec, emoteImg;
  203.     // Check each word in message for emote phrase
  204.     for (var i = 0; i < arrLen; i++) {
  205.       key = words[i];
  206.       keyDec = DecodeHTML(key);
  207.       if (emotes[keyDec] !== undefined && emotes.hasOwnProperty(keyDec)) {
  208.         // Replace text with emote
  209.         emoteImg = GetEmoteAsTag(keyDec, -7);
  210.         message = message.replace(key, emoteImg);
  211.       }
  212.     }
  213.   }
  214.   // Return message with emote images added
  215.   return message;
  216. }
  217.  
  218. function AddEmotesToMessage(node) {
  219.   node.innerHTML = AddEmotes(node.innerHTML);
  220. }
  221.  
  222. function AddEmotesToAll() {
  223.   var selector = (messenger ? "div > span._3oh-" : "span._5yl5 span");
  224.   var messages = document.querySelectorAll(selector);
  225.   for (var i = 0; i < messages.length; i++) {
  226.     AddEmotesToMessage(messages[i]);
  227.   }
  228. }
  229.  
  230. //
  231. // MISC FUNCTIONS
  232. //
  233.  
  234. Object.isEmpty = function(obj) {
  235.   var empty = true, key;
  236.   for (key in obj) {
  237.     if (obj.hasOwnProperty(key)) {
  238.       empty = false;
  239.       break;
  240.     }
  241.   }
  242.   return empty;
  243. };
  244.  
  245. function DecodeHTML(text) {
  246.   var txtBox = document.createElement("TEXTAREA");
  247.   txtBox.innerHTML = text;
  248.   return txtBox.value;
  249. }
  250.  
  251. function CopyTextToClipboard(text) {
  252.   var textArea = document.createElement("textarea");
  253.  
  254.   // Place off-screen
  255.   textArea.style.position = 'fixed';
  256.   textArea.style.top = -100;
  257.   textArea.style.left = -100;
  258.  
  259.   // Add textarea to document and fill with text to copy
  260.   textArea.value = text;
  261.   document.body.appendChild(textArea);
  262.   textArea.select();
  263.  
  264.   try {
  265.     document.execCommand('copy');
  266.   } catch (err) {
  267.     alert("The emote couldn't be copied.");
  268.   }
  269.  
  270.   // Delete textarea
  271.   document.body.removeChild(textArea);
  272. }
  273.  
  274. //
  275. // INTERACTIVE EMOTE BROWSER
  276. //
  277.  
  278. // SPOILERS ON THIS LINE. DON'T BE A BISH AND LOOK AT THEM.
  279. var headers = ["Click an emote to copy it to your clipboard!","If you don't see any emotes, refresh the page.","Twitch memes get!","If your message is over 50% emotes, you're doing it wrong","Don't use them all at once!","Why are you reading this? Just use the emotes!","A nice fresh box of Twitch emote memes","Your memes, sir.","Click one to get cummies from daddy","GIVE ME THE MEMEmotes","Fuck I'm running out of things to put here","Fuck tha police comin' str8 from the underground","Ey b0ss, cooked up some spicy emotes","SOMETIMES I LIKE TO PLAY WITH MY BALL-","Oooh yes, you know this - I have a big emote collection, like Moses","If you borrow my emotes, then you owe me","Disregard uni assignments, acquire Twitch emotes","Brought to you by Dr. F. Frank, professor in internet retardation.","These emotes are sponsored by my student debt!","Please meme responsibly. Don't emote and drive!","I'm not sure why I wrote so many of these","Try not to meme all over the message you're writing pls","There's a secret string with a 0.001% chance of appearing. This isn't it.","The secret title string doesn't exist. Stop looking."];
  280.  
  281. function GetHeader() {
  282.   var min, max, chance = Math.random();
  283.   if (chance < 0.25) {
  284.     // Group 1 (Main title)
  285.     min = 0;
  286.     max = 1;
  287.   } else if (chance < 0.55) {
  288.     // Group 2 (describes what's in the box)
  289.     min = 2;
  290.     max = 5;
  291.   } else if (chance < 0.99) {
  292.     // Group 3 (maximum shitpost tier)
  293.     min = 6;
  294.     max = headers.length - 3;
  295.   } else {
  296.     // Group 4 (conspiracy theory tier)
  297.     min = headers.length - 2;
  298.     max = headers.length - 1;
  299.   }
  300.  
  301.   return headers[Math.floor(Math.random()*(max-min+1)+min)];
  302. }
  303.  
  304. function ToggleEmoteBrowser(chatName) {
  305.   var emoteHeader = document.querySelector("#emote_header");
  306.   var emoteBrowser = document.querySelector("#emote_browser");
  307.  
  308.   if (emoteBrowser.style.opacity == "0") {
  309.     // Choose a random header
  310.     emoteHeader.innerHTML = GetHeader();
  311.     // Show emote browser
  312.     emoteBrowser.style.display = "block";
  313.     emoteBrowser.style.opacity = "1";
  314.     emoteBrowser.style.transform = "translate3D(-50%, -50%, 0)";
  315.     emoteBrowser.style.pointerEvents = "auto";
  316.     // Remember who opened the browser
  317.     if (chatName !== undefined) {
  318.       emoteBrowser.setAttribute("data-sender", chatName);
  319.     }
  320.   } else {
  321.     // Hide emote browser
  322.     emoteBrowser.style.opacity = "0";
  323.     emoteBrowser.style.transform = "translate3D(-50%, -43%, 0)";
  324.     emoteBrowser.style.pointerEvents = "none";
  325.     // Clear sender attribute and focus input
  326.    
  327.     emoteBrowser.setAttribute("data-sender", "");
  328.   }
  329. }
  330.  
  331. function FindCurrentChatBox() {
  332.   // Get chat that opened emote browser
  333.   var emoteBrowser = document.querySelector("#emote_browser");
  334.   var chatName = emoteBrowser.getAttribute("data-sender");
  335.  
  336.   // Get correct textarea to paste into
  337.   var parents = document.querySelectorAll("div.fbNub._50mz");
  338.   for (var i = 0; i < parents.length; i++) {
  339.     var header = parents[i].querySelector("h4.titlebarTextWrapper a");
  340.     if (chatName == header.getAttribute("href")) {
  341.       // Correct parent, paste here
  342.       return parents[i].querySelector("textarea");
  343.     }
  344.   }
  345.   return null;
  346. }
  347.  
  348. function AddEmoteClickEvent(node, emoteName) {
  349.   node.addEventListener("click", function() {
  350.     // Click event
  351.     var clickEvent = new MouseEvent("click", { bubbles: true, cancelable: false });
  352.  
  353.     // Set focus to input area (messenger)
  354.     if (messenger) {
  355.       // Copy emote name to clipboard
  356.       CopyTextToClipboard(emoteName + " ");
  357.      
  358.       // Send click event to input area
  359.       var inputArea = document.querySelector("div._kmc");
  360.       inputArea.dispatchEvent(clickEvent);
  361.  
  362.       // Hide emote browser
  363.       ToggleEmoteBrowser();
  364.     } else {
  365.       // Arrow key event
  366.       var arrowEvent = new KeyboardEvent("keydown", { key: "ArrowDown" });
  367.  
  368.       var textArea = FindCurrentChatBox();
  369.       if (textArea !== null) {
  370.         textArea.value += emoteName + " ";
  371.         textArea.dispatchEvent(clickEvent);
  372.         textArea.dispatchEvent(arrowEvent);
  373.       }
  374.     }
  375.   });
  376. }
  377.  
  378. function CreateEmoteBrowser() {
  379.   // Emote browser header
  380.   var emoteHeader = document.createElement("DIV");
  381.   emoteHeader.id = "emote_header";
  382.   emoteHeader.style.width = "calc(100% - 38px)";
  383.   emoteHeader.style.backgroundColor = "#6441A5";
  384.   emoteHeader.style.color = "#FFF";
  385.   emoteHeader.style.padding = "4px 0px 4px 0px";
  386.   emoteHeader.style.fontSize = "22px";
  387.   emoteHeader.style.textAlign = "center";
  388.   emoteHeader.style.height = "30px";
  389.   emoteHeader.style.position = "relative";
  390.   emoteHeader.style.float = "left";
  391.  
  392.   var closeBtn = document.createElement("DIV");
  393.   closeBtn.style.backgroundColor = "#6441A5";
  394.   closeBtn.style.fontSize = "22px";
  395.   closeBtn.style.color = "#FFF";
  396.   closeBtn.innerHTML = "[x]";
  397.   closeBtn.style.width = "38px";
  398.   closeBtn.style.height = "30px";
  399.   closeBtn.style.textAlign = "left";
  400.   closeBtn.style.padding = "4px 0px 4px 0px";
  401.   closeBtn.style.position = "relative";
  402.   closeBtn.style.float = "right";
  403.   closeBtn.style.cursor = "pointer";
  404.   closeBtn.addEventListener("click", function() {
  405.     if (!messenger) {
  406.       // Set focus
  407.       var textArea = FindCurrentChatBox();
  408.       if (textArea !== null) {
  409.         var clickEvent = new MouseEvent("click", { bubbles: true, cancelable: false });
  410.         textArea.dispatchEvent(clickEvent);
  411.       }
  412.     }
  413.    
  414.     ToggleEmoteBrowser();
  415.   });
  416.  
  417.   // Emote browser emote area
  418.   var emoteArea = document.createElement("DIV");
  419.   emoteArea.style.padding = "4px";
  420.   var emoteSections = ["Default Twitch.tv set", "Better TTV set"];
  421.   var emoteSection = 0;
  422.   var breakDiv = document.createElement("DIV");
  423.   breakDiv.className = "section_header";
  424.   breakDiv.style.width = "100%";
  425.   breakDiv.style.fontSize = "18px";
  426.   breakDiv.style.textAlign = "center";
  427.   breakDiv.style.clear = "both";
  428.  
  429.   // Populate emote area
  430.   emoteArea.appendChild(breakDiv.cloneNode(false));
  431.   for (var key in emotes) {
  432.     if (emotes.hasOwnProperty(key)) {
  433.       // Check if in BTTV section yet
  434.       if (emotes[key].slice(0, 1) == "$" && emoteSection === 0) {
  435.         // Add break div
  436.         emoteArea.appendChild(breakDiv.cloneNode(false));
  437.         emoteSection++;
  438.       }
  439.      
  440.       // Create clickable emote div
  441.       var emoteImg = document.createElement("DIV");
  442.       emoteImg.setAttribute("title", key);
  443.       // Make it clickable
  444.       AddEmoteClickEvent(emoteImg, key);
  445.       // Style it
  446.       emoteImg.style.cursor = "pointer";
  447.       emoteImg.style.padding = "4px";
  448.       emoteImg.style.display = "inline-block";
  449.       emoteImg.style.textAlign = "center";
  450.       // Add the image
  451.       emoteImg.innerHTML = GetEmoteAsTag(key, -1);
  452.  
  453.       // Append it to the emote area
  454.       emoteArea.appendChild(emoteImg);
  455.     }
  456.   }
  457.  
  458.   // Add break div title sections
  459.   var breakDivs = emoteArea.querySelectorAll("div.section_header");
  460.   for (var i = 0; i < breakDivs.length; i++) {
  461.     breakDivs[i].innerHTML = emoteSections[i];
  462.   }
  463.  
  464.   // Create the emote browser div
  465.   var emoteBrowser = document.createElement("DIV");
  466.   emoteBrowser.id = "emote_browser";
  467.   emoteBrowser.style.width = "60%";
  468.   //emoteBrowser.style.height = "50%";
  469.   emoteBrowser.style.top = "50%";
  470.   emoteBrowser.style.left = "50%";
  471.   emoteBrowser.style.backgroundColor = "#fff";
  472.   emoteBrowser.style.position = "fixed";
  473.   emoteBrowser.style.zIndex = "1000";
  474.   emoteBrowser.style.borderRadius = "10px";
  475.   emoteBrowser.style.boxShadow = "0px 0px 10px #888888";
  476.   emoteBrowser.style.transform = "translate3D(-50%, -43%, 0)";
  477.   emoteBrowser.style.overflow = "hidden";
  478.   emoteBrowser.style.display = "block";
  479.   emoteBrowser.style.opacity = "0";
  480.   emoteBrowser.style.pointerEvents = "none";
  481.   emoteBrowser.style.transition = "opacity 0.25s, transform 0.25s";
  482.  
  483.   emoteBrowser.appendChild(emoteHeader);
  484.   emoteBrowser.appendChild(closeBtn);
  485.   emoteBrowser.appendChild(emoteArea);
  486.   document.body.appendChild(emoteBrowser);
  487.  
  488.   if (messenger) {
  489.     // Create a button to open the emote browser
  490.     var iconArea = document.querySelector("ul._4rv4");
  491.  
  492.     // Create button to open browser
  493.     var openBrowser = document.createElement("LI");
  494.     openBrowser.setAttribute("title", "Twitch emote browser");
  495.     // Add image
  496.     openBrowser.innerHTML = GetEmoteAsTag("Kappa", 2);
  497.     // Add onclick event
  498.     openBrowser.addEventListener("click", ToggleEmoteBrowser);
  499.     // Style it
  500.     openBrowser.style.cursor = "pointer";
  501.     openBrowser.style.paddingRight = "9px";
  502.  
  503.     // Add to button area
  504.     iconArea.appendChild(openBrowser);
  505.   }
  506. }
  507.  
  508. // Add emote browser button to every chat window
  509. function addBrowserBtns() {
  510.   // Create button
  511.   var openBrowser = document.createElement("DIV");
  512.   openBrowser.setAttribute("title", "Twitch emote browser");
  513.   openBrowser.className = "_5g2o open_browser";
  514.   openBrowser.style.width = "18px";
  515.   openBrowser.style.height = "18px";
  516.   openBrowser.style.padding = "5px 6px 5px 0px";
  517.   openBrowser.style.cursor = "pointer";
  518.   openBrowser.style.transition = "opacity 0.2s";
  519.   openBrowser.innerHTML = GetEmoteAsTag("Kappa", 2);
  520.  
  521.   var chats = document.querySelectorAll("div._552n");
  522.   var re = /<img/, btn;
  523.   for (var i = 0; i < chats.length; i++) {
  524.     if (chats[i].querySelector("div.open_browser") === null) {
  525.       chats[i].appendChild(openBrowser.cloneNode(true));
  526.       chats[i].innerHTML = chats[i].innerHTML.replace(re, '<img style="max-height:100%;"');
  527.      
  528.       // Add event listener to each button
  529.       btn = chats[i].querySelector("div.open_browser");
  530.       AddEventListeners(btn);
  531.     }
  532.   }
  533.  
  534.   // Fix padding on input areas
  535.   var inputs = document.querySelectorAll("div._552h._n4k");
  536.   for (i = 0; i < inputs.length; i++) {
  537.     inputs[i].style.paddingRight = "100px";
  538.   }
  539. }
  540.  
  541. function AddEventListeners(node) {
  542.   // Get parent node
  543.   var parents = document.querySelectorAll("div.fbNub._50mz");
  544.   var chatName;
  545.   for (var i = 0; i < parents.length; i++) {
  546.     if (parents[i].contains(node)) {
  547.       var header = parents[i].querySelector("h4.titlebarTextWrapper a");
  548.       chatName = header.getAttribute("href");
  549.       break;
  550.     }
  551.   }
  552.  
  553.   node.addEventListener("click", function() {
  554.     ToggleEmoteBrowser(chatName);
  555.   });
  556.   node.addEventListener("mouseenter", function(){
  557.     node.style.opacity = "0.6";
  558.   });
  559.   node.addEventListener("mouseleave", function(){
  560.     node.style.opacity = "1";
  561.   });
  562. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement