Greasy Fork is available in English.
リツイートした人に通知を飛ばさずいいねするボタンを追加する。
// ==UserScript== // @name [Twitter]こっそりいいね // @namespace https://greasyfork.org/ja/users/1023652 // @version ######1919810.0.2 // @description リツイートした人に通知を飛ばさずいいねするボタンを追加する。 // @author ゆにてぃー // @match https://twitter.com/* // @connect twitter.com // @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (function() { 'use strict'; const desktop_env = {'tweet_field': 'article[data-testid="tweet"]','retweeted': '[data-testid="socialContext"]','liked_color': 'r-vkub15','liked':'M20.884 13.19c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z'}; const mobile_env = {'tweet_field': 'article[data-testid="tweet"]','retweeted': '[data-testid="socialContext"]','liked_color': 'r-vkub15','liked':'M20.884 13.19c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z'}; var env_selector; function isMobileDevice(){ const userAgent = navigator.userAgent || navigator.vendor || window.opera; return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent); } if(isMobileDevice()){ env_selector = mobile_env; }else{ env_selector = desktop_env; } let updating = false; window.addEventListener("scroll", update); init(); async function main(tweets){ tweets.forEach(function(element){ if(element.querySelector(".sneakilyLike") || ! element.querySelector(env_selector.retweeted) || !element.querySelector('[data-testid="like"]')) return; let tweet_link = Array.from(element.querySelectorAll("a[aria-label]")).filter(function(tmp){return tmp.href.match(/\/status\/[0-9]*(\/analytics)?$/)})[0].href.replace(/\/analytics$/,''); let button = document.createElement('button'); let fotter = element.querySelector('div[id][role="group"]'); let like_element = fotter.querySelector('[data-testid="like"]'); button.textContent = "いいね!"; button.style.fontSize = '0.7em'; button.style.whiteSpace = 'nowrap'; button.classList.add('sneakilyLike'); button.addEventListener('click',async function(event){ this.disabled = true; const status = await request(new requestObject(tweet_link)); if(status.response.data.favorite_tweet == "Done"){ like_element.querySelector('div[dir="ltr"]').classList.add(env_selector.liked_color); like_element.querySelector('div[dir="ltr"]').style.color = "rgb(249, 24, 128)"; like_element.querySelector("path").setAttribute('d',env_selector.liked); like_element.setAttribute('data-testid', 'unlike'); } this.remove(); }); fotter.insertBefore(button, like_element.parentElement.nextSibling); }); } function init() { main(document.querySelectorAll(env_selector.tweet_field)); } function update() { if(updating) return; updating = true; init(); setTimeout(() => {updating = false;}, 1000); } function getCookieArray() { var arr = []; if(document.cookie != '') { var tmp = document.cookie.split('; '); for(var i = 0; i < tmp.length; i++) { var data = tmp[i].split('='); arr[data[0]] = decodeURIComponent(data[1]); } } return arr; } const x_csrf_token = getCookieArray().ct0; class requestObject{ constructor(URL){ this.method = 'POST'; this.respType = 'json'; this.url = 'https://twitter.com/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet'; this.body = `{"variables":{"tweet_id":"${URL.split('/').pop()}"},"queryId":"lI07N6Otwv1PhnEgXILM7A"}`; this.headers = { 'Content-Type': 'application/json', 'User-agent': window.navigator.userAgent, 'accept': '*/*', 'Referer': URL, 'Origin': 'https://twitter.com', 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA', "x-csrf-token": x_csrf_token, 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'navigate', }; this.package = null; this.anonymous = false; } } async function request(object, timeout = 60000) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: object.method, url: object.url, headers: object.headers, responseType: object.respType, data: object.body, anonymous: object.anonymous, timeout: timeout, onload: function(responseDetails) { return resolve(responseDetails); }, ontimeout: function(responseDetails) { return reject(`[request]time out:\nresponse ${responseDetails}`); }, onerror: function(responseDetails) { return reject(`[request]error:\nresponse ${responseDetails}`); } }); }); } })();