- // ==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/axios@0.25.0/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()
- })()