Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Heart reaction
- // @namespace http://tampermonkey.net/
- // @version 1.0
- // @description try to take over the world!
- // @author You
- // @match https://www.facebook.com/*
- // @grant none
- // ==/UserScript==
- injectScript(() => {
- const USER_ID = getCookie("c_user") // Your identification number
- const DTSG = getDTSG() // A token generated by Facebook that's unique to each user
- // An ID that allows you to send POST requests to change message reactions
- const REACT_DOC_ID = "1491398900900362"
- const oldOpen = XMLHttpRequest.prototype.open // The default XHR open function
- /*
- An object containing key-value pairs, where the key is a thread ID and
- the value is an array of the messages in that thread
- */
- const threads = {}
- let currentThreadID // The ID of the thread that's currently open
- XMLHttpRequest.prototype.open = updateThreads // Modify the default XHR open function
- /*
- Each time an XHR request is initialized, update the threads object to contain
- the most recent messages and reactions
- */
- function updateThreads(method, url) {
- // New messages are being loaded
- if (url === "/api/graphqlbatch/") {
- this.addEventListener("load", () => {
- // Get the thread that has just been loaded
- const thread = JSON.parse(this.responseText.slice(0, this.responseText.lastIndexOf("{"))).o0.data.message_thread
- if (!thread)
- return
- currentThreadID = thread.id // Store the current thread's ID
- // If this is the first time loading this thread
- if (!threads[currentThreadID]) {
- // If any messages were loaded, store those messages
- if (thread.messages)
- threads[currentThreadID] = thread.messages.nodes
- // If no messages were loaded, initialize the messages of the current thread to an empty array
- else
- threads[currentThreadID] = []
- }
- // If this thread has been loaded before, and new messages were loaded
- else if (thread.messages)
- // Add the new messages to the current thread's array of messages
- threads[currentThreadID] = thread.messages.nodes.concat(threads[currentThreadID])
- })
- }
- // A reaction has been changed
- else if (url.includes("REACTION")) {
- url = decodeURIComponent(url)
- // Get the variables contained in the URL
- const searchValue = "variables="
- const searchValueIndex = url.indexOf(searchValue)
- const data = JSON.parse(url.slice(searchValueIndex + searchValue.length)).data
- // Make sure the reactions stored in the threads object are up-to-date
- updateReactions(data.message_id, data.reaction, data.action)
- }
- // Call the default XHR function
- oldOpen.apply(this, arguments)
- }
- // Update the reactions stored in the threads object
- function updateReactions(messageID, reaction, action) {
- // Get an array of reactions of the message with the specified ID
- const messageReactions = threads[currentThreadID].find(message => {
- return message.message_id === messageID
- }).message_reactions
- // Find the reaction, if any, that the current user made on the specified message
- const currentReactionIndex = messageReactions.findIndex(msgReaction => msgReaction.user.id === USER_ID)
- const currentReaction = (currentReactionIndex >= 0) ? messageReactions[currentReactionIndex] : null
- // If the user added a reaction
- if (action === "ADD_REACTION") {
- // If the user previously reacted to this message, change the reaction
- if (currentReaction)
- currentReaction.reaction = reaction
- // If this is the first time the user has reacted to this message, push a new reaction
- else
- messageReactions.push({
- user: {
- id: USER_ID
- },
- reaction: reaction
- })
- }
- // If the user removed a reaction, remove that reaction from the array
- else if (currentReaction)
- messageReactions.splice(currentReactionIndex, 1)
- }
- document.body.addEventListener("click", evt => {
- // A button that the user presses to show that they want to react to a message
- const reactButtonSelector = "._1z8r"
- // A list box containing all of the possible reactions the user can make
- const reactListBoxSelector = "._1z8q._fy2"
- // A dot inside the react list box indicating which reaction, if any, that the user has selected
- const reactIndicatorClass = "_5zvq"
- // If the user has clicked on a react button
- if(evt.target.closest("._5zvq") !== null) {
- console.log(evt.target)
- setTimeout(() => {
- const reactListBox = document.querySelector(reactListBoxSelector)
- if (!reactListBox)
- return
- // Generate a button that allows the user to give the message a heart reaction
- const heartReactButton = htmlToElement(
- `<span class="_1z8r" aria-label="❤" id="❤" role="option" aria-selected="false" tabindex="0">
- <span>
- <img alt="❤" class="_1ift _5m3a img" src="https://static.xx.fbcdn.net/images/emoji.php/v9/t31/2/128/2764.png">
- </span>
- </span>`
- )
- const heartEyesReactButton = reactListBox.children[0] // The button for the heart eyes reaction
- // Add the heart react button to the react list box
- reactListBox.insertBefore(heartReactButton, heartEyesReactButton)
- // Update the position of the react list box to be centered
- reactListBox.dispatchEvent(new MouseEvent("mouseout", {bubbles: true}))
- // Use the heart eyes react button to get the message ID
- getMessageID(heartEyesReactButton).then(messageID => {
- /*
- If the user already reacted to this message with a heart, add a dot
- to indicate the user's current reaction
- */
- if (reactionExists(messageID, "❤"))
- heartReactButton.classList.add(reactIndicatorClass)
- // When the heart react button is clicked
- heartReactButton.addEventListener("click", () => {
- setMessageReaction(messageID, "❤") // Set the reaction for the message
- reactListBox.parentElement.removeChild(reactListBox) // Remove the react list box
- })
- })
- }, 50)
- }
- })
- /*
- Given a pre-exisiting react button, return a promise of the ID of the message
- associated with this button
- */
- function getMessageID(reactButton) {
- const messageIDPromise = new Promise(resolve => {
- // Intercept the XHR initialization caused by clicking the react button
- XMLHttpRequest.prototype.open = (method, url) => {
- console.log(url)
- url = decodeURIComponent(url)
- console.log(url)
- const searchValue = "variables="
- const searchValueIndex = url.indexOf(searchValue)
- console.log(searchValueIndex)
- if (searchValueIndex === -1)
- return
- // Parse the URL to get the message ID
- const messageID = JSON.parse(url.slice(searchValueIndex + searchValue.length)).data.message_id
- console.log(messageID)
- XMLHttpRequest.prototype.open = updateThreads // Reset the XHR open function
- resolve(messageID)
- }
- })
- reactButton.click() // Click the specified react button
- return messageIDPromise
- }
- // Check if the user already gave a certain reaction on the specified message
- function reactionExists(messageID, reaction) {
- if (!threads[currentThreadID])
- return false
- // Find the message with the specified ID
- const message = threads[currentThreadID].find(msg => msg.message_id === messageID)
- /*
- Loop through the reactions of this message to see if the user
- has given a certain reaction
- */
- return message.message_reactions.findIndex(msgReaction => {
- return msgReaction.user.id === USER_ID && msgReaction.reaction === reaction
- }) >= 0
- }
- // Create a POST request to set the reaction of a particular message
- function setMessageReaction(messageID, reaction) {
- const variables = {
- data: {
- client_mutation_id: 0,
- actor_id: USER_ID,
- action: reactionExists(messageID, reaction) ? "REMOVE_REACTION" : "ADD_REACTION",
- message_id: messageID,
- reaction: reaction
- }
- }
- // Make sure the reactions stored in the threads object are up-to-date
- updateReactions(messageID, reaction, variables.data.action)
- const xhr = new XMLHttpRequest()
- xhr.open("POST", `/webgraphql/mutation/?doc_id=${REACT_DOC_ID}&variables=${JSON.stringify(variables)}`, true)
- const formData = new FormData()
- formData.set("fb_dtsg", DTSG)
- xhr.send(formData)
- }
- // Returns the value of a cookie with the given name
- function getCookie(name) {
- match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"))
- if (match)
- return match[2]
- }
- // Parse through the script tags in the document to get the Facebook DTSG token
- function getDTSG() {
- // An array of strings containing the code of each script tag
- const scripts = Array.from(document.querySelectorAll("script")).map(script => script.innerText)
- // The DTSG token comes directly after this string
- const searchValue = `"DTSGInitialData",[],{"token":"`
- let DTSG = ""
- // Loop through each script
- for (let i = 0; i < scripts.length; i++) {
- const script = scripts[i]
- const searchValueIndex = script.indexOf(searchValue)
- if (searchValueIndex !== -1) {
- /*
- Loop from the character right after the search value to the character right
- before the next double quotes
- */
- for (let j = searchValueIndex + searchValue.length; script[j] !== `"`; j++)
- DTSG += script[j]
- break
- }
- }
- return DTSG
- }
- // Create a DOM element with the specified HTML
- function htmlToElement(html) {
- const template = document.createElement("template")
- html = html.trim()
- template.innerHTML = html
- return template.content.firstChild
- }
- })
- /*
- Injects a specified function into the DOM using a script tag,
- and runs that function. This allows the content script to access
- the website's page variables.
- */
- function injectScript(func) {
- const script = document.createElement("script")
- script.appendChild(document.createTextNode("(" + func + ")()"))
- document.body.appendChild(script)
- document.body.removeChild(script)
- }
Advertisement
Add Comment
Please, Sign In to add comment