Retweet and Like List Restorer for Twitter.
// ==UserScript== // @name [Twitter]返ってこい!リツイート欄! // @name:ja [Twitter]返ってこい!リツイート欄! // @name:en [Twitter]Engagement Restorer // @namespace https://greasyfork.org/ja/users/1023652 // @version 1145141919810.0.12 // @description リツイートや引用、いいねをした人を表示するリンクを追加します。 // @description:ja リツイートや引用、いいねをした人を表示するリンクを追加します。 // @description:en Retweet and Like List Restorer for Twitter. // @author ゆにてぃー // @match https://twitter.com/* // @match https://x.com/* // @match https://X.com/* // @connect api.twitter.com // @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com // @grant GM_xmlhttpRequest // @license MIT // @run-at document-end // ==/UserScript== (function(){ 'use strict'; let currentUrl = document.location.href; let updating = false; locationChange(); const engagementsTextColor = { "0": {"count": "rgb(15, 20, 25)","text": "rgb(83, 100, 113)"}, "1": {"count": "rgb(247, 249, 249)","text": "rgb(139, 152, 165)"}, "2": {"count": "rgb(231, 233, 234)","text": "rgb(113, 118, 123)"} }; async function main(){ if(!currentUrl.match(/https?\:\/\/twitter\.com\/\w*\/status\/[0-9]*($|\?.*)/) || document.getElementById('restoreEngagements'))return; const tweetLink = currentUrl.match(/https?\:\/\/twitter\.com\/\w*\/status\/[0-9].*/)[0]; const tweetId = tweetLink.match(/\/status\/(\d+)/)[1]; let response = (await request(new requestObject_twitter_graphQL(tweetId))).response.data.threaded_conversation_with_injections_v2.instructions[0]; response = response.entries[response.entries.findIndex((tmp) => tmp.entryId == `tweet-${tweetId}`)].content.itemContent.tweet_results.result.legacy; const engagemants = {"favorite_count": response.favorite_count,"quote_count": response.quote_count,"retweet_count": response.retweet_count}; console.log(engagemants); const target_node = Array.from((await wait_load_Element('article[data-testid="tweet"]'))).find(node => { const timeParents = Array.from(node.querySelectorAll('time')).map(time => time.parentNode); return timeParents.some(parent => parent.href && parent.href.match(tweetId)); }); const engagemants_aria = target_node.querySelector('[role="group"]'); if(!engagemants_aria)return; let envEngagementsTextColor = engagementsTextColor[GetCookie("night_mode") || "0"]; const flexContainer = document.createElement('div'); flexContainer.style.display = 'flex'; flexContainer.style.justifyContent = 'space-between'; flexContainer.style.width = '70%'; flexContainer.id = 'restoreEngagements'; const links = [ { "name": "retweets", "href": tweetLink + "/retweets", "count": round_half_up(engagemants.retweet_count,env_Text.roundingScale,env_Text.decimalPlaces,env_Text.units), "text": env_Text.retweet }, { "name": "quotes", "href": tweetLink + "/quotes", "count": round_half_up(engagemants.quote_count,env_Text.roundingScale,env_Text.decimalPlaces,env_Text.units), "text": env_Text.quoted, }, { "name": "likes", "href": tweetLink + "/likes", "count": round_half_up(engagemants.favorite_count,env_Text.roundingScale,env_Text.decimalPlaces,env_Text.units), "text": env_Text.like, }, ]; links.forEach((a, index) => { const newLink = document.createElement('a'); newLink.style.textDecoration = 'none'; newLink.href = a.href; const countText = document.createElement('span'); countText.textContent = a.count; countText.style.color = envEngagementsTextColor.count; newLink.appendChild(countText); const textPart = document.createElement('span'); textPart.textContent = " " + a.text; textPart.style.color = envEngagementsTextColor.text; newLink.appendChild(textPart); newLink.addEventListener('click', (e) => { e.preventDefault(); clickTab(a.name,target_node); }); flexContainer.appendChild(newLink); }); if(currentUrl.match(/https?\:\/\/twitter\.com\/\w*\/status\/[0-9]*($|\?.*)/))engagemants_aria.parentNode.prepend(flexContainer); } Text.ja = { "retweet": "リツイート", "quoted": "件の引用", "like": "いいね", "units": "万", "roundingScale": 10000, "decimalPlaces": 2, } Text.en = { "retweet": "Retweets", "quoted": "Quotes", "like": "Likes", "units": "K", "roundingScale": 1000, "decimalPlaces": 1, } let env_Text = Text[GetCookie("lang")] || Text.en; function update(){ if(updating) return; updating = true; main(); setTimeout(() => {updating = false;}, 1000); } function locationChange(){ const observer = new MutationObserver(mutations => { mutations.forEach(() => { if(currentUrl !== document.location.href){ currentUrl = document.location.href; main(); } }); }); const target = document.getElementById("react-root"); const config = {childList: true,subtree: true}; observer.observe(target, config); } async function clickTab(name,target_node){ target_node.querySelector('[data-testid="caret"]').click(); document.querySelector('[data-testid="tweetEngagements"]').click(); const engagemants_aria = (await wait_load_Element('nav[aria-live="polite"]'))[0]; const regex = new RegExp(name + '$'); engagemants_aria.querySelectorAll('[role="presentation"] a').forEach((e)=>{ if(e.href.match(regex)) e.click(); }); } function round_half_up(original_value,where_round_off,decimal_place = 0,unit_str = ""){ if(Number(original_value)>=Number(where_round_off)){ var tmp_value; tmp_value = Math.round(Number(original_value) / Number(where_round_off) * Math.pow(10,Number(decimal_place))) / Math.pow(10,Number(decimal_place)); if(unit_str == ""){ return tmp_value; }else{ return `${tmp_value}${unit_str}` } }else{ return original_value; } } function wait_load_Element(Element_Name,interval = 100,retry = 25){ return new Promise((resolve, reject) => { const MAX_RETRY_COUNT = retry; let retry_counter = 0; let set_interval_id = setInterval(find_target_element, interval); function find_target_element(){ retry_counter++; if(retry_counter > MAX_RETRY_COUNT){ clearInterval(set_interval_id); return reject("Max retry count reached"); } let target_elements = document.querySelectorAll(`${Element_Name}`); if(target_elements.length > 0){ clearInterval(set_interval_id); return resolve(target_elements); } } }); } function GetCookie(name){ let arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); if(arr = document.cookie.match(reg)){ return decodeURIComponent(arr[2]); }else{ return null; } } async function request(object, maxRetries = 1, timeout = 60000){ let retryCount = 0; while(retryCount <= maxRetries){ try{ return await 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){ resolve(responseDetails); }, ontimeout: function(responseDetails){ reject(`[request]time out:\nresponse ${responseDetails}`); }, onerror: function(responseDetails){ reject(`[request]error:\nresponse ${responseDetails}`); } }); }); }catch(error){ retryCount++; console.warn(`Retry ${retryCount}: Failed to fetch ${object.url}. Reason: ${error}`); if(retryCount === maxRetries){ throw new Error(`Failed to fetch ${object.url} after ${maxRetries} retries.`); } } } } class requestObject_twitter_1_1{ constructor(ID){ this.method = 'GET'; this.respType = 'json'; this.url = "https://api.twitter.com/1.1/statuses/lookup.json?id=" + ID + "&tweet_mode=extended"; this.body = null; this.headers = { "Content-Type": "application/json", 'Referer': "https://twitter.com/", 'Authorization': `Bearer AAAAAAAAAAAAAAAAAAAAAIK1zgAAAAAA2tUWuhGZ2JceoId5GwYWU5GspY4%3DUq7gzFoCZs1QfwGoVdvSac3IniczZEYXIcDyumCauIXpcAPorE`, 'x-csrf-token': GetCookie("ct0"), }; this.package = null; this.anonymous = false; } } class requestObject_twitter_graphQL{ constructor(ID){ this.method = 'GET'; this.respType = 'json'; this.url = `https://api.twitter.com/graphql/NNiD2K-nEYUfXlMwGCocMQ/TweetDetail?variables=%7B%22focalTweetId%22%3A%22${ID}%22%2C%22with_rux_injections%22%3Afalse%2C%22includePromotedContent%22%3Atrue%2C%22withCommunity%22%3Atrue%2C%22withQuickPromoteEligibilityTweetFields%22%3Atrue%2C%22withBirdwatchNotes%22%3Atrue%2C%22withSuperFollowsUserFields%22%3Atrue%2C%22withDownvotePerspective%22%3Afalse%2C%22withReactionsMetadata%22%3Afalse%2C%22withReactionsPerspective%22%3Afalse%2C%22withSuperFollowsTweetFields%22%3Atrue%2C%22withVoice%22%3Atrue%2C%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Atrue%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Afalse%2C%22verified_phone_label_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22tweetypie_unmention_optimization_enabled%22%3Atrue%2C%22vibe_api_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22view_counts_everywhere_api_enabled%22%3Atrue%2C%22longform_notetweets_consumption_enabled%22%3Atrue%2C%22tweet_awards_web_tipping_enabled%22%3Afalse%2C%22freedom_of_speech_not_reach_fetch_enabled%22%3Afalse%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Afalse%2C%22interactive_text_enabled%22%3Atrue%2C%22responsive_web_text_conversations_enabled%22%3Afalse%2C%22responsive_web_enhance_cards_enabled%22%3Afalse%7D`; this.body = null; this.headers = { "Content-Type": "application/json", 'User-agent': navigator.userAgent || navigator.vendor || window.opera, 'accept': '*/*', 'Referer': "https://twitter.com/", 'Host': 'api.twitter.com', 'authorization': `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`, 'x-csrf-token': GetCookie("ct0"), }; this.package = null; this.anonymous = false; } } main(); })();