- // ==UserScript==
- // @name allen's block
- // @namespace https://eolstudy.com
- // @version 3.0.1
- // @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/axios@0.25.0/dist/axios.min.js
- // @require https://cdn.jsdelivr.net/npm/qs@6.10.3/dist/qs.min.js
- // @require https://cdn.jsdelivr.net/npm/jquery@3.5.1/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()
- })()