您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Block with love.
当前为
// ==UserScript== // @name block CCP propaganda tweets likers // @namespace https://eolstudy.com // @version 4.3.6 // @description Block with love. // @author amormaid // @run-at document-end // @grant GM_registerMenuCommand // @match https://twitter.com/* // @match https://mobile.twitter.com/* // @match https://tweetdeck.twitter.com/* // @exclude https://twitter.com/account/* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js // @license MIT // ==/UserScript== /* Inspired by Twitter-Block-With-Love https://greasyfork.org/en/scripts/398540-twitter-block-with-love */ (_ => { function stringify(obj, sep, eq) { sep = sep || '&'; eq = eq || '='; let str = ""; for (var k in obj) { str += k + eq + decodeURIComponent(obj[k]) + sep } return str.slice(0, -1) }; function parse(str) { var obj = new Object(); strs = str.split("&"); for (var i = 0; i < strs.length; i++) { let index = strs[i].indexOf("=") obj[strs[i].slice(0, index)] = decodeURIComponent(strs[i].slice(index + 1)); } return obj; } //解析url地址 function getRequest() { var url = location.search; //获取url中"?"符后的字串 var theRequest = new Object(); if (url.indexOf("?") != -1) { var str = url.substr(1); return parse(str) } } const translations = { // Please submit a feedback on Greasyfork.com if your language is not in the list bellow 'en': { lang_name: 'English', like_title: 'Liked by', like_list_identifier: 'Timeline: Liked by', retweet_title: 'Retweeted by', retweet_list_identifier: 'Timeline: Retweeted by', block_btn: 'Block Pinker', block_success: 'All users blocked!', mute_btn: 'Mute all', mute_success: 'All users muted!', include_original_tweeter: 'Include the original Tweeter', logs: 'Logs', list_members: 'List members', list_members_identifier: 'Timeline: List members', block_retweets_notice: 'TBWL has only blocked users that retweeted without comments.\n Please block users retweeting with comments manually.' } } let i18n = translations.en function get_theme_color (){ const close_icon = $('div[aria-label] > div[dir="auto"] > svg[viewBox="0 0 24 24"]')[0] return window.getComputedStyle(close_icon).color } async function sleep (ms = 50) { return new Promise(resolve => setTimeout(resolve, ms)) } function component_to_hex (c) { if (typeof(c) === 'string') c = Number(c) const hex = c.toString(16); return hex.length === 1 ? ("0" + hex) : hex; } function rgb_to_hex (r, g, b) { return "#" + component_to_hex(r) + component_to_hex(g) + component_to_hex(b); } function get_cookie (cname) { let name = cname + '=' let ca = document.cookie.split(';') for (let i = 0; i < ca.length; ++i) { let c = ca[i].trim() if (c.indexOf(name) === 0) { return c.substring(name.length, c.length) } } return '' } function getStorage (name) { try { return window.JSON.parse(sessionStorage.getItem(name) || '[]') } catch (err) { sessionStorage.setItem(name, '[]') return [] } } function setStorage (name, val) { sessionStorage.setItem(name, window.JSON.stringify(val)) } function get_ancestor (dom, level) { for (let i = 0; i < level; ++i) { dom = dom.parent() } return dom } const ajax = axios.create({ baseURL: 'https://api.twitter.com', withCredentials: true, headers: { // 'User-Agent': '', 'Authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA', 'X-Twitter-Auth-Type': 'OAuth2Session', 'X-Twitter-Active-User': 'yes', 'X-Csrf-Token': get_cookie('ct0') } }) // https://developer.twitter.com/en/docs/twitter-api/v1 // users this user is following async function get_friends (userId) { const cachedFriends = getStorage('friends') if (cachedFriends && cachedFriends.length) { return cachedFriends } const my_id = get_cookie('twid').replace('u%3D', '') const users = await ajax.get(`/1.1/friends/ids.json?user_id=${userId || my_id}&count=5000&stringify_ids=true`).then( res => res.data.ids ) || [] // console.log('get_friends', users) setStorage('friends', window.JSON.stringify(users)) return users } // users follow this user async function get_followers (userId) { const cachedUsers = getStorage('followers') if (cachedUsers && cachedUsers.length) { return cachedUsers } const my_id = get_cookie('twid').replace('u%3D', '') const users = await ajax.get(`/1.1/followers/ids.json?user_id=${userId || my_id}&count=5000&stringify_ids=true`).then( res => res.data.ids ) || [] // console.log('get_followers', users) setStorage('followers', window.JSON.stringify(users)) return users } async function get_list_menber (listId) { const cachedUsers = getStorage('ccpmember') if (cachedUsers && cachedUsers.length) { return cachedUsers } const users = await ajax.get(`/1.1/lists/members.json?list_id=${listId}&count=5000`).then( res => res.data.users ) // console.log('get_list_menber', users) const newUsers = (users || []).map(({ id_str }) => id_str) setStorage('ccpmember', window.JSON.stringify(newUsers)) return newUsers } // https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/create-manage-lists/overview async function get_list_tweets(listId, tweets_number = 100, last_id) { const tweets_list_res = await ajax.get(`/1.1/lists/statuses.json?list_id=${listId}&include_rts=false&count=${tweets_number}${last_id ? `&max_id=${last_id}` : ''}`) // console.log('get_list_tweets res before ', last_id, ' is ', tweets_list_res.data) return (tweets_list_res || {}).data || [] } function get_tweet_id () { // https://twitter.com/any/thing/status/1234567/anything => 1234567/anything => 1234567 return location.href.split('status/')[1].split('/')[0] } // function get_list_id () { // // https://twitter.com/any/thing/lists/1234567/anything => 1234567/anything => 1234567 // return location.href.split('lists/')[1].split('/')[0] // } // fetch_likers and fetch_no_comment_retweeters need to be merged into one function async function fetch_likers (tweetId) { const users = await ajax.get(`/2/timeline/liked_by.json?tweet_id=${tweetId}`).then( res => res.data.globalObjects.users ) let likers = [] Object.keys(users).forEach(user => likers.push(user)) // keys of users are id strings return likers } // async function fetch_no_comment_retweeters (tweetId) { // const users = (await ajax.get(`/2/timeline/retweeted_by.json?tweet_id=${tweetId}`)).data.globalObjects.users // let targets = [] // Object.keys(users).forEach(user => targets.push(user)) // return targets // } // async function fetch_list_members (listId) { // const users = (await ajax.get(`/1.1/lists/members.json?list_id=${listId}`)).data.users // let members = [] // members = users.map(u => u.id_str) // return members // } function block_user (id) { // ajax.post('/1.1/blocks/create.json', Qs.stringify({ ajax.post('/1.1/blocks/create.json', stringify({ user_id: id }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }) } // function mute_user (id) { // ajax.post('/1.1/mutes/users/create.json', Qs.stringify({ // user_id: id // }), { // headers: { // 'Content-Type': 'application/x-www-form-urlencoded' // } // }) // } async function get_list_likers_a (tweets_list = [], pre_id, tweets_number_max = 5,count = 0) { const max_tweets_per_request = 2 // let tweets_list = [] let last_id = pre_id if (tweets_list.length >= tweets_number_max) { return tweets_list } if (count >= 5) { return tweets_list } let new_list new_list = await get_list_tweets('1661581723006803968', max_tweets_per_request, last_id) // it is so wired, code has TDZ con not access new_list before initialize // console.log('pre_id is ', pre_id, new_list) // console.log('new_list', new_list) (new_list || []).forEach(list_item => { const {id} = list_item || {} last_id = id tweets_list.push(list_item) }) return await get_list_likers(tweets_list, last_id, tweets_number_max, count + 1) } async function get_list_likers (tweets_number_max = 1000,) { const max_tweets_per_request = Math.min(85, tweets_number_max) let tweets_list = [] let last_id const query_times = Math.ceil(tweets_number_max/max_tweets_per_request) const promise_sequence = Array.from(new Array(query_times + 1)).reduce(async (acc, cur) => { // const pre_res = await acc || [] const [, pre_res = []] = await Promise.all([sleep(Math.ceil(Math.random() * 100)), acc]) const last_tweet = (pre_res.slice(-1) || [])[0] || {} const {id} = last_tweet // console.log('pre_res ', pre_res) last_id = id tweets_list = [...tweets_list, ...pre_res] if (tweets_list.length >= tweets_number_max) { return Promise.resolve() } else { const new_request = get_list_tweets('1661581723006803968', max_tweets_per_request, last_id) return new_request } }, Promise.resolve()) await promise_sequence console.log('get_list_likers end') return tweets_list } let block_status // block_all_liker and block_no_comment_retweeters need to be merged async function block_all_pinker () { if (block_status) { return success_notice('', 'API limit, please wait') } block_status = 'running' // const tweetId = get_tweet_id() const [my_followers, my_friends, listMember] = await Promise.all([ get_followers(), get_friends(), get_list_menber('1497432788634841089') // ccp propaganda list ]) // console.log('my_followers', my_followers) // console.log('my_friends', my_friends) // console.log('listMember', listMember) // https://twitter.com/harry_shosta/status/1669140306481278977/likes // const likers = await fetch_likers(tweetId) // console.log('likers', likers) // console.log('newLikers', newLikers) // console.log('will not block ', likers.filter(id => !newLikers.includes(id))) // likers.forEach(id => block_user(id)) // success_notice(i18n.like_list_identifier, i18n.block_success) try { let likers = [] if (window.location.href.match(/\d+\/likes$/)) { const tweetId = get_tweet_id() likers = await fetch_likers(tweetId) // likers.forEach(id => block_user(id)) // success_notice(i18n.like_list_identifier, i18n.block_success) } if (!window.location.href.match(/\d+\/likes$/)) { const block_like_threshlod = 99 const query_tweets_number = 100 const twetts_aa = await get_list_likers(query_tweets_number) console.log('twetts_aa ', twetts_aa) // twetts_aa.forEach(async (item, index) => { // index < 1 && console.log('twetts_aa item ', item) // const {id_str, favorite_count} = item // if (favorite_count > 99) { // const _likers = await fetch_likers(id_str) // _likers.forEach(likerId => likers.push(likerId)) // } // }) const promise_sequence = ([...twetts_aa, {}]).reduce(async (acc, cur) => { const pre_res = await acc || [] likers = [...likers, ...pre_res] // const last_tweet = (pre_res.slice(-1) || [])[0] || {} const {id_str, favorite_count} = cur if (favorite_count > block_like_threshlod && id_str) { const new_request = await fetch_likers(id_str) // console.log(cur, new_request, '\r\n\r\n\r\n') return new_request } else { return Promise.resolve() } }, Promise.resolve()) await promise_sequence } const newLikers = likers.filter(id => { const flag_a = !my_followers.includes(id) const flag_b = !my_friends.includes(id) const flag_c = !listMember.includes(id) return flag_a && flag_b && flag_c }) const pure_new_likers = Array.from(new Set(newLikers || [])) console.log('pure_new_likers ', pure_new_likers) const block_sequence = ([...pure_new_likers, '']).reduce(async (acc, cur, index) => { await Promise.all([sleep(Math.ceil(Math.random() * 100)), acc]) if (index > 0 && index % 250 === 0) { // limit 300 per minute await sleep(15 * 60 * 1000) } let id = cur if (id) { block_notice(i18n.like_list_identifier, index, pure_new_likers.length) const new_request = block_user(id) return new_request } else { return Promise.resolve() } }, Promise.resolve()) await block_sequence // likers.forEach(id => block_user(id)) // success_notice(i18n.like_list_identifier, i18n.block_success) block_status = null } catch (err) { console.error(err) block_status = null success_notice('', 'block failed') } } function success_notice (identifier, success_msg) { const btnNode = document.createElement('div') btnNode.innerText = success_msg btnNode.style.cssText = ` position: absolute; left: calc(50% - 25vw); top: 40vw; width: 50vw; height: 10vw; background-color: rgba(255, 255, 255, 0.5); border: 1px solid #eaeaea; text-align: center; line-height: 10vw; ` document.body.append(btnNode) setTimeout( () => { btnNode.parentNode.removeChild(btnNode) }, 3000) } let timer_id function block_notice (identifier, current, total) { let btnNode = document.getElementById('block_root') if (!btnNode) { btnNode = document.createElement('div') btnNode.setAttribute('id', 'block_root') } btnNode.innerText = `bocked ${current + 1} / ${total}` btnNode.style.cssText = ` position: absolute; left: calc(50% - 25vw); top: 40vw; width: 50vw; height: 10vw; background-color: rgba(255, 255, 255, 0.5); border: 1px solid #eaeaea; text-align: center; line-height: 10vw; ` document.body.append(btnNode) clearTimeout(timer_id) timer_id = setTimeout( () => { btnNode.parentNode.removeChild(btnNode) }, 5000) } function mount_button (parentDom, name, executer) { const btnNode = document.createElement('button') btnNode.innerText = name btnNode.style.cssText = ` position: absolute; right: 0px; top: 5px; ` btnNode.addEventListener('click', executer) parentDom.append(btnNode) } function use_MutationObserver () { const targetNode = document.getElementById('react-root'); // Options for the observer (which mutations to observe) const config = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed const callback = (mutationList, observer) => { for (const mutation of mutationList) { if (mutation.type === 'childList' && mutation.target?.innerText?.includes(i18n.like_title)) { // console.log('mutation', mutation) const domList = Array.from(mutation.target.getElementsByTagName('h2')) const domTarget = domList.find(i => i.innerText === i18n.like_title) if (domTarget) { mount_button(domTarget, i18n.block_btn, block_all_pinker) } } // if (mutation.type === 'childList') { // console.log('A child node has been added or removed.'); // } else if (mutation.type === 'attributes') { // console.log(`The ${mutation.attributeName} attribute was modified.`); // } } }; const observer = new MutationObserver(callback) observer.observe(targetNode, config); } const mount_root = document.getElementsByTagName('body')[0] mount_button(mount_root, i18n.block_btn, block_all_pinker) // main() })()