Slupik98

[FB][Script] Heart reaction

Nov 21st, 2019
388
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Heart reaction
  3. // @namespace    http://tampermonkey.net/
  4. // @version      1.0
  5. // @description  try to take over the world!
  6. // @author       You
  7. // @match        https://www.facebook.com/*
  8. // @grant        none
  9. // ==/UserScript==
  10.  
  11. injectScript(() => {
  12.     const USER_ID = getCookie("c_user") // Your identification number
  13.     const DTSG = getDTSG() // A token generated by Facebook that's unique to each user
  14.  
  15.     // An ID that allows you to send POST requests to change message reactions
  16.     const REACT_DOC_ID = "1491398900900362"
  17.  
  18.     const oldOpen = XMLHttpRequest.prototype.open // The default XHR open function
  19.  
  20.     /*
  21.     An object containing key-value pairs, where the key is a thread ID and
  22.     the value is an array of the messages in that thread
  23.     */
  24.     const threads = {}
  25.  
  26.     let currentThreadID // The ID of the thread that's currently open
  27.  
  28.     XMLHttpRequest.prototype.open = updateThreads // Modify the default XHR open function
  29.  
  30.     /*
  31.     Each time an XHR request is initialized, update the threads object to contain
  32.     the most recent messages and reactions
  33.     */
  34.     function updateThreads(method, url) {
  35.       // New messages are being loaded
  36.       if (url === "/api/graphqlbatch/") {
  37.         this.addEventListener("load", () => {
  38.           // Get the thread that has just been loaded
  39.           const thread = JSON.parse(this.responseText.slice(0, this.responseText.lastIndexOf("{"))).o0.data.message_thread
  40.  
  41.           if (!thread)
  42.             return
  43.  
  44.           currentThreadID = thread.id // Store the current thread's ID
  45.  
  46.           // If this is the first time loading this thread
  47.           if (!threads[currentThreadID]) {
  48.             // If any messages were loaded, store those messages
  49.             if (thread.messages)
  50.               threads[currentThreadID] = thread.messages.nodes
  51.  
  52.             // If no messages were loaded, initialize the messages of the current thread to an empty array
  53.             else
  54.               threads[currentThreadID] = []
  55.           }
  56.  
  57.           // If this thread has been loaded before, and new messages were loaded
  58.           else if (thread.messages)
  59.             // Add the new messages to the current thread's array of messages
  60.             threads[currentThreadID] = thread.messages.nodes.concat(threads[currentThreadID])
  61.         })
  62.       }
  63.  
  64.       // A reaction has been changed
  65.       else if (url.includes("REACTION")) {
  66.         url = decodeURIComponent(url)
  67.  
  68.         // Get the variables contained in the URL
  69.         const searchValue = "variables="
  70.         const searchValueIndex = url.indexOf(searchValue)
  71.         const data = JSON.parse(url.slice(searchValueIndex + searchValue.length)).data
  72.  
  73.         // Make sure the reactions stored in the threads object are up-to-date
  74.         updateReactions(data.message_id, data.reaction, data.action)
  75.       }
  76.  
  77.       // Call the default XHR function
  78.       oldOpen.apply(this, arguments)
  79.     }
  80.  
  81.     // Update the reactions stored in the threads object
  82.     function updateReactions(messageID, reaction, action) {
  83.       // Get an array of reactions of the message with the specified ID
  84.       const messageReactions = threads[currentThreadID].find(message => {
  85.         return message.message_id === messageID
  86.       }).message_reactions
  87.  
  88.       // Find the reaction, if any, that the current user made on the specified message
  89.       const currentReactionIndex = messageReactions.findIndex(msgReaction => msgReaction.user.id === USER_ID)
  90.       const currentReaction = (currentReactionIndex >= 0) ? messageReactions[currentReactionIndex] : null
  91.  
  92.       // If the user added a reaction
  93.       if (action === "ADD_REACTION") {
  94.         // If the user previously reacted to this message, change the reaction
  95.         if (currentReaction)
  96.           currentReaction.reaction = reaction
  97.  
  98.         // If this is the first time the user has reacted to this message, push a new reaction
  99.         else
  100.           messageReactions.push({
  101.             user: {
  102.               id: USER_ID
  103.             },
  104.             reaction: reaction
  105.           })
  106.       }
  107.  
  108.       // If the user removed a reaction, remove that reaction from the array
  109.       else if (currentReaction)
  110.         messageReactions.splice(currentReactionIndex, 1)
  111.     }
  112.  
  113.     document.body.addEventListener("click", evt => {
  114.       // A button that the user presses to show that they want to react to a message
  115.  
  116.       const reactButtonSelector = "._1z8r"
  117.  
  118.       // A list box containing all of the possible reactions the user can make
  119.       const reactListBoxSelector = "._1z8q._fy2"
  120.  
  121.       // A dot inside the react list box indicating which reaction, if any, that the user has selected
  122.       const reactIndicatorClass = "_5zvq"
  123.  
  124.       // If the user has clicked on a react button
  125.       if(evt.target.closest("._5zvq") !== null) {
  126.           console.log(evt.target)
  127.           setTimeout(() => {
  128.             const reactListBox = document.querySelector(reactListBoxSelector)
  129.  
  130.             if (!reactListBox)
  131.               return
  132.  
  133.             // Generate a button that allows the user to give the message a heart reaction
  134.             const heartReactButton = htmlToElement(
  135.               `<span class="_1z8r" aria-label="❤" id="❤" role="option" aria-selected="false" tabindex="0">
  136.                 <span>
  137.                   <img alt="❤" class="_1ift _5m3a img" src="https://static.xx.fbcdn.net/images/emoji.php/v9/t31/2/128/2764.png">
  138.                 </span>
  139.               </span>`
  140.             )
  141.  
  142.             const heartEyesReactButton = reactListBox.children[0] // The button for the heart eyes reaction
  143.  
  144.             // Add the heart react button to the react list box
  145.             reactListBox.insertBefore(heartReactButton, heartEyesReactButton)
  146.  
  147.             // Update the position of the react list box to be centered
  148.             reactListBox.dispatchEvent(new MouseEvent("mouseout", {bubbles: true}))
  149.  
  150.             // Use the heart eyes react button to get the message ID
  151.             getMessageID(heartEyesReactButton).then(messageID => {
  152.               /*
  153.               If the user already reacted to this message with a heart, add a dot
  154.               to indicate the user's current reaction
  155.               */
  156.               if (reactionExists(messageID, "❤"))
  157.                 heartReactButton.classList.add(reactIndicatorClass)
  158.  
  159.               // When the heart react button is clicked
  160.               heartReactButton.addEventListener("click", () => {
  161.                 setMessageReaction(messageID, "❤") // Set the reaction for the message
  162.                 reactListBox.parentElement.removeChild(reactListBox) // Remove the react list box
  163.               })
  164.             })
  165.           }, 50)
  166.       }
  167.     })
  168.  
  169.     /*
  170.     Given a pre-exisiting react button, return a promise of the ID of the message
  171.     associated with this button
  172.     */
  173.     function getMessageID(reactButton) {
  174.       const messageIDPromise = new Promise(resolve => {
  175.         // Intercept the XHR initialization caused by clicking the react button
  176.         XMLHttpRequest.prototype.open = (method, url) => {
  177.             console.log(url)
  178.           url = decodeURIComponent(url)
  179.             console.log(url)
  180.  
  181.           const searchValue = "variables="
  182.           const searchValueIndex = url.indexOf(searchValue)
  183.             console.log(searchValueIndex)
  184.  
  185.           if (searchValueIndex === -1)
  186.             return
  187.  
  188.           // Parse the URL to get the message ID
  189.           const messageID = JSON.parse(url.slice(searchValueIndex + searchValue.length)).data.message_id
  190.             console.log(messageID)
  191.  
  192.           XMLHttpRequest.prototype.open = updateThreads // Reset the XHR open function
  193.           resolve(messageID)
  194.         }
  195.       })
  196.  
  197.       reactButton.click() // Click the specified react button
  198.  
  199.       return messageIDPromise
  200.     }
  201.  
  202.     // Check if the user already gave a certain reaction on the specified message
  203.     function reactionExists(messageID, reaction) {
  204.       if (!threads[currentThreadID])
  205.         return false
  206.  
  207.       // Find the message with the specified ID
  208.       const message = threads[currentThreadID].find(msg => msg.message_id === messageID)
  209.  
  210.       /*
  211.       Loop through the reactions of this message to see if the user
  212.       has given a certain reaction
  213.       */
  214.       return message.message_reactions.findIndex(msgReaction => {
  215.         return msgReaction.user.id === USER_ID && msgReaction.reaction === reaction
  216.       }) >= 0
  217.     }
  218.  
  219.     // Create a POST request to set the reaction of a particular message
  220.     function setMessageReaction(messageID, reaction) {
  221.       const variables = {
  222.         data: {
  223.           client_mutation_id: 0,
  224.           actor_id: USER_ID,
  225.           action: reactionExists(messageID, reaction) ? "REMOVE_REACTION" : "ADD_REACTION",
  226.           message_id: messageID,
  227.           reaction: reaction
  228.         }
  229.       }
  230.  
  231.       // Make sure the reactions stored in the threads object are up-to-date
  232.       updateReactions(messageID, reaction, variables.data.action)
  233.  
  234.       const xhr = new XMLHttpRequest()
  235.       xhr.open("POST", `/webgraphql/mutation/?doc_id=${REACT_DOC_ID}&variables=${JSON.stringify(variables)}`, true)
  236.  
  237.       const formData = new FormData()
  238.       formData.set("fb_dtsg", DTSG)
  239.  
  240.       xhr.send(formData)
  241.     }
  242.  
  243.     // Returns the value of a cookie with the given name
  244.     function getCookie(name) {
  245.       match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"))
  246.  
  247.       if (match)
  248.         return match[2]
  249.     }
  250.  
  251.     // Parse through the script tags in the document to get the Facebook DTSG token
  252.     function getDTSG() {
  253.       // An array of strings containing the code of each script tag
  254.       const scripts = Array.from(document.querySelectorAll("script")).map(script => script.innerText)
  255.  
  256.       // The DTSG token comes directly after this string
  257.       const searchValue = `"DTSGInitialData",[],{"token":"`
  258.  
  259.      let DTSG = ""
  260.  
  261.      // Loop through each script
  262.      for (let i = 0; i < scripts.length; i++) {
  263.        const script = scripts[i]
  264.        const searchValueIndex = script.indexOf(searchValue)
  265.  
  266.        if (searchValueIndex !== -1) {
  267.          /*
  268.          Loop from the character right after the search value to the character right
  269.          before the next double quotes
  270.          */
  271.          for (let j = searchValueIndex + searchValue.length; script[j] !== `"`; j++)
  272.             DTSG += script[j]
  273.  
  274.           break
  275.         }
  276.       }
  277.  
  278.       return DTSG
  279.     }
  280.  
  281.     // Create a DOM element with the specified HTML
  282.     function htmlToElement(html) {
  283.       const template = document.createElement("template")
  284.       html = html.trim()
  285.       template.innerHTML = html
  286.       return template.content.firstChild
  287.     }
  288.   })
  289.  
  290.   /*
  291.   Injects a specified function into the DOM using a script tag,
  292.   and runs that function. This allows the content script to access
  293.   the website's page variables.
  294.   */
  295.   function injectScript(func) {
  296.     const script = document.createElement("script")
  297.     script.appendChild(document.createTextNode("(" + func + ")()"))
  298.     document.body.appendChild(script)
  299.     document.body.removeChild(script)
  300.   }
Advertisement
Add Comment
Please, Sign In to add comment