您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Block with love.
当前为
// ==UserScript== // @name allen's block // @namespace https://eolstudy.com // @version 3.0 // @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 // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/qs.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @license MIT // ==/UserScript== /* global axios $ Qs */ (_ => { /* Begin of Dependencies */ // https://gist.githubusercontent.com/BrockA/2625891/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts, that detects and handles AJAXed content. Usage example: waitForKeyElements ( "div.comments" , commentCallbackFunction ); //--- Page-specific function to do what we want when the node is found. function commentCallbackFunction (jNode) { jNode.text ("This comment changed by waitForKeyElements()."); } IMPORTANT: This function requires your script to have loaded jQuery. */ function waitForKeyElements ( selectorTxt, /* Required: The jQuery selector string that specifies the desired element(s). */ actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */ bWaitOnce, /* Optional: If false, will continue to scan for new elements even after the first match is found. */ iframeSelector /* Optional: If set, identifies the iframe to search. */ ) { var targetNodes, btargetsFound; if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt); else targetNodes = $(iframeSelector).contents () .find (selectorTxt); if (targetNodes && targetNodes.length > 0) { btargetsFound = true; /*--- Found target node(s). Go through each and act if they are new. */ targetNodes.each ( function () { var jThis = $(this); var alreadyFound = jThis.data ('alreadyFound') || false; if (!alreadyFound) { //--- Call the payload function. var cancelFound = actionFunction (jThis); if (cancelFound) btargetsFound = false; else jThis.data ('alreadyFound', true); } } ); } else { btargetsFound = false; } //--- Get the timer-control variable for this selector. var controlObj = waitForKeyElements.controlObj || {}; var controlKey = selectorTxt.replace (/[^\w]/g, "_"); var timeControl = controlObj [controlKey]; //--- Now set or clear the timer as appropriate. if (btargetsFound && bWaitOnce && timeControl) { //--- The only condition where we need to clear the timer. clearInterval (timeControl); delete controlObj [controlKey] } else { //--- Set a timer, if needed. if ( ! timeControl) { timeControl = setInterval ( function () { waitForKeyElements ( selectorTxt, actionFunction, bWaitOnce, iframeSelector ); }, 300 ); controlObj [controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } /* End of Dependencies */ 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 all', 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 } 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 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: { 'Authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA', 'X-Twitter-Auth-Type': 'OAuth2Session', 'X-Twitter-Active-User': 'yes', 'X-Csrf-Token': get_cookie('ct0') } }) // users this user is following async function get_friends (userId) { const cachedFriends = window.JSON.parse(sessionStorage.getItem('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) sessionStorage.setItem('friends', window.JSON.stringify(users)) return users } // users follow this user async function get_followers (userId) { const cachedUsers = window.JSON.parse(sessionStorage.getItem('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) sessionStorage.setItem('followers', window.JSON.stringify(users)) return users } async function get_list_menber (listId) { const cachedUsers = window.JSON.parse(sessionStorage.getItem('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) sessionStorage.setItem('ccpmember', window.JSON.stringify(newUsers)) return newUsers } 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({ 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' // } // }) // } // block_all_liker and block_no_comment_retweeters need to be merged async function block_all_likers () { 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) const likers = await fetch_likers(tweetId) console.log('likers', likers) 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 }) console.log('newLikers', newLikers) console.log('will not block ', likers.filter(id => !newLikers.includes(id))) // likers.forEach(id => block_user(id)) } async function block_no_comment_retweeters () { const tweetId = get_tweet_id() const retweeters = await fetch_no_comment_retweeters(tweetId) retweeters.forEach(id => block_user(id)) const tabName = location.href.split('retweets/')[1] if (tabName === 'with_comments') { if (!block_no_comment_retweeters.hasAlerted) { block_no_comment_retweeters.hasAlerted = true alert(i18n.block_rt_notice) } } } async function block_list_members () { const listId = get_list_id() const members = await fetch_list_members(listId) members.forEach(id => block_user(id)) } function success_notice (identifier, success_msg) { return _ => { const alertColor = 'rgb(224, 36, 94)' const container = $('div[aria-label="' + identifier + '"]') container.children().fadeOut(400, _ => { const notice = $(` <div style=" color: ${alertColor}; text-align: center; margin-top: 3em; font-size: x-large; "> <span>${success_msg}</span> </div> `) container.append(notice) }) } } function mount_button (parentDom, name, executer, success_notifier) { let themeColor = get_theme_color() const hoverColor = themeColor.replace(/rgb/i, "rgba").replace(/\)/, ', 0.1)') const mousedownColor = themeColor.replace(/rgb/i, "rgba").replace(/\)/, ', 0.2)') const btn_mousedown = 'bwl-btn-mousedown' const btn_hover = 'bwl-btn-hover' $('head').append(` <style> .bwl-btn-base { min-height: 30px; padding-left: 1em; padding-right: 1em; border: 1px solid ${themeColor} !important; border-radius: 9999px; } .${btn_mousedown} { background-color: ${mousedownColor}; cursor: pointer; } .${btn_hover} { background-color: ${hoverColor}; cursor: pointer; } .bwl-btn-inner-wrapper { font-weight: bold; -webkit-box-align: center; align-items: center; -webkit-box-flex: 1; flex-grow: 1; color: ${themeColor}; display: flex; } .bwl-text-font { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif; color: ${themeColor}; } </style> `) const button = $(` <div aria-haspopup="true" role="button" data-focusable="true" class="bwl-btn-base" style="margin:3px" > <div class="bwl-btn-inner-wrapper"> <span> <span class="bwl-text-font">${name}</span> </span> </div> </div> `) .addClass(parentDom.prop('classList')[0]) .hover(function () { $(this).addClass(btn_hover) }, function () { $(this).removeClass(btn_hover) $(this).removeClass(btn_mousedown) }) .on('selectstart', function () { return false }) .mousedown(function () { $(this).removeClass(btn_hover) $(this).addClass(btn_mousedown) }) .mouseup(function () { $(this).removeClass(btn_mousedown) if ($(this).is(':hover')) { $(this).addClass(btn_hover) } }) .click(executer) .click(success_notifier) parentDom.append(button) } function main () { waitForKeyElements('h2:has(> span:contains(' + i18n.like_title + '))', dom => { const ancestor = get_ancestor(dom, 3) mount_button(ancestor, i18n.block_btn, block_all_likers, success_notice(i18n.like_list_identifier, i18n.block_success)) }) waitForKeyElements('h2:has(> span:contains(' + i18n.retweet_title + '))', dom => { const ancestor = get_ancestor(dom, 3) mount_button(ancestor, i18n.block_btn, block_no_comment_retweeters, success_notice(i18n.retweet_list_identifier, i18n.block_success)) }) waitForKeyElements('h2:has(> span:contains(' + i18n.list_members + '))', dom => { const ancestor = get_ancestor(dom, 3) mount_button(ancestor, i18n.block_btn, block_list_members, success_notice(i18n.list_members_identifier, i18n.block_success)) }) } main() })()