- // ==UserScript==
- // @name block CCP propaganda tweets likers
- // @namespace https://eolstudy.com
- // @version 4.4.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/*
-
- // @license MIT
- // ==/UserScript==
-
- /* Inspired by Twitter-Block-With-Love https://greasyfork.org/en/scripts/398540-twitter-block-with-love */
-
- (_ => {
- // @require https://cdn.jsdelivr.net/npm/axios@0.25.0/dist/axios.min.js
-
- class HTTPError extends Error {
- constructor(response, ...params) {
- // Pass remaining arguments (including vendor specific ones) to parent constructor
- super(...params);
-
- this.response = response;
-
- // Maintains proper stack trace for where our error was thrown (only available on V8)
- if (Error.captureStackTrace) {
- Error.captureStackTrace(this, HTTPError);
- }
-
- this.name = 'HTTPError';
- }
- }
-
- // window.axios = {};
- const ajax = {};
- ['get','post','put','delete'].forEach(requestType => {
- ajax[requestType] = function (url, data){
- return fetch(`https://api.twitter.com${url}`, {
- headers: {
- // modify Content-Type for /1.1/blocks/create.json
- "Content-Type": requestType.toUpperCase() === 'POST' ? 'application/x-www-form-urlencoded' : "application/json",
- "Accept": "application/json",
- //"X-Requested-With": "XMLHttpRequest",
- // "X-CSRF-Token": token.content
- 'Authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
- 'X-Twitter-Auth-Type': 'OAuth2Session',
- 'X-Twitter-Active-User': 'yes',
- 'X-Csrf-Token': get_cookie('ct0')
-
- },
- method: requestType,
- mode: "cors",
- credentials: "include",
- body: requestType.toUpperCase() === 'POST' ? stringify(data) : JSON.stringify(data),
- })
- .then( async response=>{
- if (!response.ok) {
- let data=await response.json();
- response.data = data;
- console.log(response);
- throw new HTTPError(response, response.statusText);
- }
- return response;
- }).then(response=>{
- return response.json();
- }).then(jsonResponse => {
- return {data:jsonResponse};
- });
- }
- });
-
-
- 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;
- var theRequest = new Object();
- if (url.indexOf("?") != -1) {
- var str = url.substr(1);
- return parse(str)
- }
- }
-
- 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
- async function get_user_timeline (screen_name) {
- // https://api.twitter.com/1.1/statuses/user_timeline.json
- const tweets_list = await ajax.get(`/1.1/statuses/user_timeline.json?screen_name=${screen_name}&count=5000&stringify_ids=true`).then(
- res => res.data
- ) || []
- return tweets_list
- }
- // 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'
- // }
- // })
- ajax.post('/1.1/blocks/create.json', {
- user_id: id
- })
- }
-
- // 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 last_run
- // block_all_liker and block_no_comment_retweeters need to be merged
- async function block_all_pinker () {
- if (last_run && (new Date().getTime() - last_run < 30 * 1000)) {
- return block_notice('API limit, please wait')
- }
- block_notice('blocker running !')
- last_run = new Date().getTime()
- // 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))
- // block_notice('All users blocked!')
- try {
- let likers = []
-
- // at likes page, block likes user
- if (window.location.href.match(/\d+\/likes$/)) {
- const tweetId = get_tweet_id()
- likers = await fetch_likers(tweetId)
-
- // likers.forEach(id => block_user(id))
- // block_notice('All users blocked!')
- }
-
-
-
- if (!window.location.href.match(/\d+\/likes$/)) {
- let twetts_asemble = []
- const block_like_threshlod = 33 // 99
- // at home page, block list tweets likes user
- if (window.location.pathname === '/home') {
-
- const query_tweets_number = 33 // 100
- twetts_asemble = await get_list_likers(query_tweets_number)
- console.log('twetts_asemble ', twetts_asemble)
- } else {
- // at list menber page, block member tweets likes user
- twetts_asemble = await get_user_timeline(window.location.pathname.replace('/', ''))
- console.log('twetts_asemble', twetts_asemble)
- }
-
-
- // twetts_asemble.forEach(async (item, index) => {
- // index < 1 && console.log('twetts_asemble 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_asemble, {}]).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 === 80 || index === 350 || index === 700) {
- // limit 300 per minute
- await sleep(15 * 60 * 1000)
- }
- let id = cur
- if (id) {
- block_notice(`bocked ${index + 1} / ${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))
- // last_run = null
-
- if (window.location.pathname === '/home') {
-
- setTimeout(block_all_pinker, 20 * 60 * 1000)
- }
-
- } catch (err) {
- console.error(err)
- // last_run = null
- block_notice('block failed')
- }
-
-
- }
-
-
- let timer_id
- function block_notice (message) {
- let btnNode = document.getElementById('block_root')
- if (!btnNode) {
- btnNode = document.createElement('div')
- btnNode.setAttribute('id', 'block_root')
- }
-
- btnNode.innerText = message
- btnNode.style.cssText = `
- position: absolute;
- left: calc(50% - 100px);
- top: calc(50vh - 60px);;
- width: 200px;
- height: 120px;
- background-color: rgba(255, 255, 255, 0.5);
- border: 1px solid #eaeaea;
- text-align: center;
- line-height: 120px;
- font-size: 16px;
- `
- 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('liked by')) {
- // console.log('mutation', mutation)
- const domList = Array.from(mutation.target.getElementsByTagName('h2'))
- const domTarget = domList.find(i => i.innerText === 'liked by')
- if (domTarget) {
- mount_button(domTarget, 'Block Pinker', 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, 'Block Pinker', block_all_pinker)
- // use_MutationObserver()
- // main()
- })()