├── .gitignore ├── 0.1.0 ├── README.md ├── config.js ├── index.js ├── package-lock.json └── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 0.1.0/node_modules 2 | -------------------------------------------------------------------------------- /0.1.0/README.md: -------------------------------------------------------------------------------- 1 | # Project FCF (Flush Conceivable Fakers - 清洗明显的假账号) 2 | ## 介绍 3 | 今天上午玩砍口垒之余,在昨天晚上的0.01-demo版本的经验上,改进了API的使用方式,制作出了有实用价值的FCF工具。\ 4 | 代码可读性也提升到了正常人的水平。 5 | ## 原理 6 | ### 检测 7 | 该工具利用follower的数目检测一位你未关注的关注者是不是视奸账号或bot。\ 8 | 逻辑如下: 9 | 1. 若该用户的follower数目大于100,则判断其不是视奸号。 10 | 2. 否则,若该用户的follower数目小于5,则判断其为视奸号。 11 | 3. 否则,若该用户的follower数目小于50,且follower/following数目之比小于0.05,则判断其为视奸号。 12 | 4. 否则,说明没有根据判断该用户的类型,暂时判断该用户不是视奸号。 13 | ### 移除 14 | 软件将视奸号从你的粉丝中移除的方法如下:先block,再解block,过五秒(以免请求过于频繁被twitter官方橄榄)后开始处理下一个视奸号。\ 15 | 解block的操作只是为了不伤及无辜的善良twitter用户;如果您没有锁推,建议将解block的操作去掉,以防被移除的视奸号卷土重来。 16 | ## 使用 17 | 需要安装node.js,同时需要获得twitter developer帐号。\ 18 | 获得twitter developer帐号后,将config.js中的“待填写”改为你的twitter帐号的相应信息。\ 19 | 然后在空文件夹下,依次运行npm init,npm install twit,npm install readline,再把index.js与config.js复制进去。\ 20 | 最后运行node index.js即可。 21 | ## 待填坑的部分 22 | 其实有了Twitter API,我们可以写一些更多的内容,比如用户名里有\*旗emoji或“孙笑川”等字符串、地点为\*国、简介中有\*国等关键词的用户全部屏蔽(笑),或者ID为乱码的屏蔽。但是我懒,不想写了。 23 | -------------------------------------------------------------------------------- /0.1.0/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | consumer_key: '待填写', 3 | consumer_secret: '待填写', 4 | access_token: '待填写', 5 | access_token_secret: '待填写', 6 | timeout_ms: 60*1000, // optional HTTP request timeout to apply to all requests. 7 | strictSSL: true, // optional - requires SSL certificates to be valid. 8 | } 9 | -------------------------------------------------------------------------------- /0.1.0/index.js: -------------------------------------------------------------------------------- 1 | //Project F. C. F. (Fuck Chinese Followers) first work, version 1.0.0. 2 | //Copyright LabMikazu (毅航实验室), Shizuki Kagurazaka (Ziyue Ji), 2020.8.6. 3 | 4 | console.log("the tool is running"); 5 | 6 | const readline = require('readline').createInterface({ 7 | input: process.stdin, 8 | output: process.stdout 9 | }); 10 | 11 | //function getFollowers 12 | //arguments: the authentification T, the screenName, time interval, already listed followers, cursor 13 | //return: a Promise either to be resolve(list) or reject(err), where list is the listed followers. 14 | function getFollowers (T, screenName, interval = 5 * 62 * 1000, followers = [], cursor = -1) { 15 | return new Promise((resolve, reject) => { 16 | T.get('followers/ids', { screen_name: screenName, stringify_ids: true, cursor: cursor, count: 5000 }, (err, data, response) => { 17 | if (err) { 18 | if (err.message === 'Rate limit exceeded') { 19 | console.log("Rate limit reached. Wait for 300 seconds. No.1") 20 | setTimeout(() => { 21 | return resolve(getFollowers(T, screenName, interval, followers, cursor)) 22 | }, interval) 23 | } else { 24 | cursor = -1 25 | reject(err) 26 | } 27 | } else { 28 | cursor = data.next_cursor_str 29 | followers.push(data.ids) 30 | if (cursor != '0') { 31 | return resolve(getFollowers(T, screenName, interval, followers, cursor)) 32 | } else { 33 | return resolve([].concat(...followers)) 34 | } 35 | } 36 | }) 37 | }) 38 | } 39 | 40 | //function getFriends 41 | //arguments: the authentification T, the screenName, time interval, already listed friends, cursor 42 | //return: a Promise either to be resolve(list) or reject(err), where list is the listed friends. 43 | function getFriends (T, screenName, interval = 5 * 62 * 1000, friends = [], cursor = -1) { 44 | return new Promise((resolve, reject) => { 45 | T.get('friends/ids', { screen_name: screenName, stringify_ids: true, cursor: cursor, count: 5000 }, (err, data, response) => { 46 | if (err) { 47 | if (err.message === 'Rate limit exceeded') { 48 | console.log("Rate limit reached. Wait for 300 seconds. No.2") 49 | setTimeout(() => { 50 | return resolve(getFriends(T, screenName, interval, friends, cursor)) 51 | }, interval) 52 | } else { 53 | cursor = -1 54 | reject(err) 55 | } 56 | } else { 57 | cursor = data.next_cursor_str 58 | friends.push(data.ids) 59 | if (cursor != '0') { 60 | return resolve(getFriends(T, screenName, interval, friends, cursor)) 61 | } else { 62 | return resolve([].concat(...friends)) 63 | } 64 | } 65 | }) 66 | }) 67 | } 68 | 69 | //function listtruefollowers 70 | //arguments: idlist, my_friendslist 71 | //return: a new list contains all that appeared in idlist but not in my_friendslist 72 | function listtruefollowers(idlist, my_friendslist){ 73 | var newlist = []; 74 | idlist.forEach((item) => { 75 | if (!(my_friendslist.includes(item))){ 76 | newlist.push(item); 77 | } 78 | }); 79 | return newlist; 80 | } 81 | 82 | //function judge 83 | //arguments: a user object 84 | //return: 1 if judged Chinese bot and 0 if not so. 85 | function judge_A(_, _){ 86 | return 1; 87 | } 88 | function judge_A2(user, minimum_common_friends_count, my_friendslist){ 89 | if (user.protected){ 90 | return 0; 91 | } console.log("Reading followers of account %s", user.screen_name); 92 | getFollowers(T, user.screen_name).then(idlist => { 93 | result = (idlist.filter(value => my_friendslist.includes(value)).length < minimum_common_friends_count); 94 | }); 95 | return result; 96 | } 97 | function judge_B(user, _){ 98 | if (user.protected){ 99 | return 1; 100 | } return 0; 101 | } 102 | function judge_C(user, ratio){ 103 | if (parseFloat(user.followers_count) / parseFloat(user.friends_count) <= parseFloat(ratio)){ 104 | return 1; 105 | } return 0; 106 | } 107 | function judge_D(user, lowest){ 108 | if (user.followers_count <= parseFloat(lowest)){ 109 | return 1; 110 | } return 0; 111 | } 112 | 113 | //function listprocess 114 | //arguments: an id list, the authentication T, my_friendslist, time interval, already listed blockers, cursor 115 | //return: a Promise either to be reject(err) or resolve(list), where list is the list of blockers. 116 | function listprocess (list, mode, param, T, my_friendslist, interval = 5 * 62 * 1000, blockers = [], cursor = 0) { 117 | if (mode == "A") { 118 | var judge = judge_A; 119 | } else if (mode == "B") { 120 | var judge = judge_B; 121 | } else if (mode == "C") { 122 | var judge = judge_C; 123 | } else if (mode == "D") { 124 | var judge = judge_D; 125 | } else { 126 | var judge = (item, param) => judge_A2(item, param, my_friendslist); 127 | } 128 | return new Promise((resolve, reject) => { 129 | T.get('users/lookup', { user_id: list.slice(cursor, cursor + 100 >= list.length ? list.length : cursor + 100) }, (err, data, response) => { 130 | if (err) { 131 | if (err.message === 'Rate limit exceeded') { 132 | console.log("Rate limit reached. Wait for 300 seconds. No.3") 133 | setTimeout(() => { 134 | return resolve(listprocess(list, mode, param, T, interval, blockers, cursor)) 135 | }, interval) 136 | } else { 137 | reject(err) 138 | } 139 | } else { 140 | cursor = cursor + 100 141 | data.forEach((item) => { 142 | if(judge(item, param)){ 143 | blockers.push(item.screen_name); 144 | } 145 | }) 146 | console.log("Scanned: %d, Detected Chinese bots: %d", cursor >= list.length ? list.length : cursor , blockers.length); 147 | if (cursor < list.length) { 148 | return resolve(listprocess(list, mode, param, T, interval, blockers, cursor)) 149 | } else { 150 | return resolve([].concat(...blockers)) 151 | } 152 | } 153 | }) 154 | }) 155 | } 156 | 157 | //function blocksingle 158 | //arguments: screen name, the authentication T, cursor 159 | //return: a Promise either to be resolve(1), resolve("RATE LIMIT EXCEEDED") or reject(err). 160 | function blocksingle(name, T, cursor){ 161 | return new Promise((resolve, reject) => { 162 | T.post('blocks/create', { screen_name: name }, function (err, data, response) { 163 | if(err){ 164 | if (err.message === 'Rate limit exceeded') { 165 | console.log("Rate limit reached. Wait for 300 seconds. No.4") 166 | resolve("RATE LIMIT EXCEEDED") 167 | } else { 168 | reject("ERROR!") 169 | } 170 | }else{ 171 | console.log("Successfully blocked the %d Chinese bot: %s", cursor + 1, data.screen_name); 172 | resolve(1); 173 | } 174 | }); 175 | }) 176 | } 177 | 178 | //function unblocksingle 179 | //arguments: screen name, the authentication T, cursor 180 | //return: a Promise either to be resolve(1), resolve("RATE LIMIT EXCEEDED") or reject(err). 181 | function unblocksingle(name, T, cursor){ 182 | return new Promise((resolve, reject) => { 183 | T.post('blocks/destroy', { screen_name: name }, function (err, data, response) { 184 | if(err){ 185 | if (err.message === 'Rate limit exceeded') { 186 | console.log("Rate limit reached. Wait for 300 seconds. No.5") 187 | resolve("RATE LIMIT EXCEEDED") 188 | } else { 189 | reject("ERROR!") 190 | } 191 | }else{ 192 | console.log("Successfully unblocked the %d Chinese bot: %s", cursor + 1, data.screen_name); 193 | resolve(1); 194 | } 195 | }); 196 | }) 197 | } 198 | 199 | //function blockprocess 200 | //arguments: a list, the authentication T, time interval, cursor 201 | //return: a Promise either to be resolve(1) or reject(err). 202 | function blockprocess(list, T, interval = 5 * 62 * 1000, cursor_b = 0, cursor_u = 0){ 203 | return new Promise((resolve, reject) => { 204 | var blockinterval = setInterval(() => { 205 | blocksingle(list[cursor_b], T, cursor_b).then((result) => { 206 | if(result == 1){ 207 | cursor_b += 1; 208 | unblocksingle(list[cursor_u], T, cursor_u).then((result) => { 209 | if(result == 1){ 210 | cursor_u += 1; 211 | if (cursor_u >= list.length){ 212 | clearInterval(blockinterval); 213 | } 214 | }else if(result === "RATE LIMIT EXCEEDED"){ 215 | clearInterval(blockinterval); 216 | }else{ 217 | reject("ERROR!") 218 | } 219 | }) 220 | }else if(result === "RATE LIMIT EXCEEDED"){ 221 | clearInterval(blockinterval); 222 | }else{ 223 | reject("ERROR!") 224 | } 225 | }) 226 | }, 4000); 227 | if (cursor_u >= list.length){ 228 | return resolve(1); 229 | } else { 230 | setTimeout(() => { 231 | return resolve(blockprocess(list, T, interval, cursor_b, cursor_u)); 232 | }, interval); 233 | } 234 | }) 235 | } 236 | 237 | 238 | //function main 239 | //arguments: scrid 240 | //return: none 241 | function main(scrid, mode, param){ 242 | var Twit = require('twit'); 243 | var config = require('./config'); 244 | var T = new Twit(config); 245 | var blocklist = []; 246 | getFollowers(T, scrid).then(idlist => { 247 | console.log("Your followers number is: %d", idlist.length); 248 | getFriends(T, scrid).then(my_friendslist => { 249 | console.log("Your friends number is: %d", my_friendslist.length); 250 | var truelist = listtruefollowers(idlist, my_friendslist); 251 | console.log("Your non-friend followers count: %d", truelist.length); 252 | listprocess(truelist, mode, param, T, my_friendslist).then(blocklist => { 253 | console.log("The total number of Chinese bots detected: %d", blocklist.length); 254 | console.log("This is a list of the first 100 in them.") 255 | console.log(blocklist.slice(0, 100)); 256 | blockprocess(blocklist, T).then(result =>{ 257 | if(result == 1){ 258 | console.log("The tool succeeded in fucking Chinese followers."); 259 | }else{ 260 | throw new Error("ERROR!") 261 | } 262 | }) 263 | }) 264 | }) 265 | }) 266 | } 267 | 268 | readline.question("Input your screen ID\n", (scrid) => { 269 | readline.question("Please input your mode (input A/A2/B/C, default is A2).\n Mode A: \ 270 | Block all non-friend followers;\n Mode A2:\ 271 | Block all non-friend followers without enough common friends, except locked ones;\n Mode B: \ 272 | Block all locked accounts in non-friend followers;\n Mode C: \ 273 | Block all followers with a particular follower/following ratio;\n Mode D: \ 274 | Block all followers with a particular follower number.\n", (mode) => { 275 | readline.question("Please input the parameter.\n For mode A or B:\ 276 | This is non-necessary.\n For mode A2:\ 277 | Please input a minimum common friends number. For mode C: \ 278 | Please input a minimum follower/following ratio.\n For mode D: \ 279 | Please input a minimum follower number.\n", (param) => { 280 | main(scrid, mode, param) 281 | }); 282 | }); 283 | }); 284 | -------------------------------------------------------------------------------- /0.1.0/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project_fcf", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "6.12.6", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 10 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 11 | "requires": { 12 | "fast-deep-equal": "^3.1.1", 13 | "fast-json-stable-stringify": "^2.0.0", 14 | "json-schema-traverse": "^0.4.1", 15 | "uri-js": "^4.2.2" 16 | } 17 | }, 18 | "asn1": { 19 | "version": "0.2.4", 20 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 21 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 22 | "requires": { 23 | "safer-buffer": "~2.1.0" 24 | } 25 | }, 26 | "assert-plus": { 27 | "version": "1.0.0", 28 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 29 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 30 | }, 31 | "asynckit": { 32 | "version": "0.4.0", 33 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 34 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 35 | }, 36 | "aws-sign2": { 37 | "version": "0.7.0", 38 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 39 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 40 | }, 41 | "aws4": { 42 | "version": "1.11.0", 43 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", 44 | "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" 45 | }, 46 | "bcrypt-pbkdf": { 47 | "version": "1.0.2", 48 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 49 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 50 | "requires": { 51 | "tweetnacl": "^0.14.3" 52 | } 53 | }, 54 | "bluebird": { 55 | "version": "3.7.2", 56 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 57 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" 58 | }, 59 | "caseless": { 60 | "version": "0.12.0", 61 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 62 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 63 | }, 64 | "combined-stream": { 65 | "version": "1.0.8", 66 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 67 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 68 | "requires": { 69 | "delayed-stream": "~1.0.0" 70 | } 71 | }, 72 | "core-util-is": { 73 | "version": "1.0.2", 74 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 75 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 76 | }, 77 | "dashdash": { 78 | "version": "1.14.1", 79 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 80 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 81 | "requires": { 82 | "assert-plus": "^1.0.0" 83 | } 84 | }, 85 | "delayed-stream": { 86 | "version": "1.0.0", 87 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 88 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 89 | }, 90 | "ecc-jsbn": { 91 | "version": "0.1.2", 92 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 93 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 94 | "requires": { 95 | "jsbn": "~0.1.0", 96 | "safer-buffer": "^2.1.0" 97 | } 98 | }, 99 | "extend": { 100 | "version": "3.0.2", 101 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 102 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 103 | }, 104 | "extsprintf": { 105 | "version": "1.3.0", 106 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 107 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 108 | }, 109 | "fast-deep-equal": { 110 | "version": "3.1.3", 111 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 112 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 113 | }, 114 | "fast-json-stable-stringify": { 115 | "version": "2.1.0", 116 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 117 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 118 | }, 119 | "forever-agent": { 120 | "version": "0.6.1", 121 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 122 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 123 | }, 124 | "form-data": { 125 | "version": "2.3.3", 126 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 127 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 128 | "requires": { 129 | "asynckit": "^0.4.0", 130 | "combined-stream": "^1.0.6", 131 | "mime-types": "^2.1.12" 132 | } 133 | }, 134 | "getpass": { 135 | "version": "0.1.7", 136 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 137 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 138 | "requires": { 139 | "assert-plus": "^1.0.0" 140 | } 141 | }, 142 | "har-schema": { 143 | "version": "2.0.0", 144 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 145 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 146 | }, 147 | "har-validator": { 148 | "version": "5.1.5", 149 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 150 | "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 151 | "requires": { 152 | "ajv": "^6.12.3", 153 | "har-schema": "^2.0.0" 154 | } 155 | }, 156 | "http-signature": { 157 | "version": "1.2.0", 158 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 159 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 160 | "requires": { 161 | "assert-plus": "^1.0.0", 162 | "jsprim": "^1.2.2", 163 | "sshpk": "^1.7.0" 164 | } 165 | }, 166 | "is-typedarray": { 167 | "version": "1.0.0", 168 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 169 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 170 | }, 171 | "isstream": { 172 | "version": "0.1.2", 173 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 174 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 175 | }, 176 | "jsbn": { 177 | "version": "0.1.1", 178 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 179 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 180 | }, 181 | "json-schema": { 182 | "version": "0.2.3", 183 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 184 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 185 | }, 186 | "json-schema-traverse": { 187 | "version": "0.4.1", 188 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 189 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 190 | }, 191 | "json-stringify-safe": { 192 | "version": "5.0.1", 193 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 194 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 195 | }, 196 | "jsprim": { 197 | "version": "1.4.1", 198 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 199 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 200 | "requires": { 201 | "assert-plus": "1.0.0", 202 | "extsprintf": "1.3.0", 203 | "json-schema": "0.2.3", 204 | "verror": "1.10.0" 205 | } 206 | }, 207 | "mime": { 208 | "version": "1.6.0", 209 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 210 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 211 | }, 212 | "mime-db": { 213 | "version": "1.49.0", 214 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", 215 | "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" 216 | }, 217 | "mime-types": { 218 | "version": "2.1.32", 219 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", 220 | "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", 221 | "requires": { 222 | "mime-db": "1.49.0" 223 | } 224 | }, 225 | "oauth-sign": { 226 | "version": "0.9.0", 227 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 228 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 229 | }, 230 | "performance-now": { 231 | "version": "2.1.0", 232 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 233 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 234 | }, 235 | "psl": { 236 | "version": "1.8.0", 237 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 238 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" 239 | }, 240 | "punycode": { 241 | "version": "2.1.1", 242 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 243 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 244 | }, 245 | "qs": { 246 | "version": "6.5.2", 247 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 248 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 249 | }, 250 | "readline": { 251 | "version": "1.3.0", 252 | "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", 253 | "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" 254 | }, 255 | "request": { 256 | "version": "2.88.2", 257 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 258 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 259 | "requires": { 260 | "aws-sign2": "~0.7.0", 261 | "aws4": "^1.8.0", 262 | "caseless": "~0.12.0", 263 | "combined-stream": "~1.0.6", 264 | "extend": "~3.0.2", 265 | "forever-agent": "~0.6.1", 266 | "form-data": "~2.3.2", 267 | "har-validator": "~5.1.3", 268 | "http-signature": "~1.2.0", 269 | "is-typedarray": "~1.0.0", 270 | "isstream": "~0.1.2", 271 | "json-stringify-safe": "~5.0.1", 272 | "mime-types": "~2.1.19", 273 | "oauth-sign": "~0.9.0", 274 | "performance-now": "^2.1.0", 275 | "qs": "~6.5.2", 276 | "safe-buffer": "^5.1.2", 277 | "tough-cookie": "~2.5.0", 278 | "tunnel-agent": "^0.6.0", 279 | "uuid": "^3.3.2" 280 | } 281 | }, 282 | "safe-buffer": { 283 | "version": "5.2.1", 284 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 285 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 286 | }, 287 | "safer-buffer": { 288 | "version": "2.1.2", 289 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 290 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 291 | }, 292 | "sshpk": { 293 | "version": "1.16.1", 294 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 295 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 296 | "requires": { 297 | "asn1": "~0.2.3", 298 | "assert-plus": "^1.0.0", 299 | "bcrypt-pbkdf": "^1.0.0", 300 | "dashdash": "^1.12.0", 301 | "ecc-jsbn": "~0.1.1", 302 | "getpass": "^0.1.1", 303 | "jsbn": "~0.1.0", 304 | "safer-buffer": "^2.0.2", 305 | "tweetnacl": "~0.14.0" 306 | } 307 | }, 308 | "tough-cookie": { 309 | "version": "2.5.0", 310 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 311 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 312 | "requires": { 313 | "psl": "^1.1.28", 314 | "punycode": "^2.1.1" 315 | } 316 | }, 317 | "tunnel-agent": { 318 | "version": "0.6.0", 319 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 320 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 321 | "requires": { 322 | "safe-buffer": "^5.0.1" 323 | } 324 | }, 325 | "tweetnacl": { 326 | "version": "0.14.5", 327 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 328 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 329 | }, 330 | "twit": { 331 | "version": "2.2.11", 332 | "resolved": "https://registry.npmjs.org/twit/-/twit-2.2.11.tgz", 333 | "integrity": "sha512-BkdwvZGRVoUTcEBp0zuocuqfih4LB+kEFUWkWJOVBg6pAE9Ebv9vmsYTTrfXleZGf45Bj5H3A1/O9YhF2uSYNg==", 334 | "requires": { 335 | "bluebird": "^3.1.5", 336 | "mime": "^1.3.4", 337 | "request": "^2.68.0" 338 | } 339 | }, 340 | "uri-js": { 341 | "version": "4.4.1", 342 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 343 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 344 | "requires": { 345 | "punycode": "^2.1.0" 346 | } 347 | }, 348 | "uuid": { 349 | "version": "3.4.0", 350 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 351 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 352 | }, 353 | "verror": { 354 | "version": "1.10.0", 355 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 356 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 357 | "requires": { 358 | "assert-plus": "^1.0.0", 359 | "core-util-is": "1.0.2", 360 | "extsprintf": "^1.2.0" 361 | } 362 | } 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /0.1.0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project_fcf", 3 | "version": "1.0.0", 4 | "description": "今天上午玩砍口垒之余,在昨天晚上的0.01-demo版本的经验上,改进了API的使用方式,制作出了有实用价值的FCF工具。\\ 代码可读性也提升到了正常人的水平。 ## 原理 ### 检测 该工具利用follower的数目检测一位你未关注的关注者是不是视奸账号或bot。\\ 逻辑如下: 1. 若该用户的follower数目大于100,则判断其不是视奸号。 2. 否则,若该用户的follower数目小于5,则判断其为视奸号。 3. 否则,若该用户的follower数目小于50,且follower/following数目之比小于0.05,则判断其为视奸号。 4. 否则,说明没有根据判断该用户的类型,暂时判断该用户不是视奸号。 ### 移除 软件将视奸号从你的粉丝中移除的方法如下:先block,再解block,过五秒(以免请求过于频繁被twitter官方橄榄)后开始处理下一个视奸号。\\ 解block的操作只是为了不伤及无辜的善良twitter用户;如果您没有锁推,建议将解block的操作去掉,以防被移除的视奸号卷土重来。 ## 使用 需要安装node.js,同时需要获得twitter developer帐号。\\ 获得twitter developer帐号后,将config.js中的“待填写”改为你的twitter帐号的相应信息。\\ 然后在空文件夹下,依次运行npm init,npm install twit,npm install readline,再把index.js与config.js复制进去。\\ 最后运行node index.js即可。 ## 待填坑的部分 其实有了Twitter API,我们可以写一些更多的内容,比如用户名里有\\*旗emoji或“孙笑川”等字符串、地点为\\*国、简介中有\\*国等关键词的用户全部屏蔽(笑),或者ID为乱码的屏蔽。但是我懒,不想写了。", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/opengl106/project_fcf.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/opengl106/project_fcf/issues" 17 | }, 18 | "homepage": "https://github.com/opengl106/project_fcf#readme", 19 | "dependencies": { 20 | "readline": "^1.3.0", 21 | "twit": "^2.2.11" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project FCF (Flush Conceivable Fakers) 2 | 3 | ## Introduction 4 | This is a shit-like simple project for blocking twitter bots (presumably from Chinese powers) that follows you, to improve the use experience. 5 | 6 | Update 10/2023: As shitty Elon Musk disabled Twitter API for free users, I can't use this personally now, but I strongly suggest against donating money to the shitty X platform (debris of former Twitter). 7 | 8 | ## Usage 9 | 5 modes are available. 10 | 11 | ### Mode A 12 | Block all non-friend followers. 13 | 14 | Used when you feel anxiety about your social media privacy issue 15 | 16 | ### Mode A2 17 | Block all non-friend followers without enough common friends, except locked (protected) ones. 18 | 19 | Used when you want to clear weirdoes following you, but not want to hurt normal followers with common friends. 20 | 21 | ### Mode B 22 | Block all locked accounts in non-friend followers. 23 | 24 | Be cautious to use since this would hurt friendly normal protected followers. 25 | 26 | ### Mode C 27 | Block all followers with a particular follower/following ratio. 28 | 29 | Sorta practical in judging conceivable fakers, huh. 30 | 31 | ### Mode D 32 | Block all followers with a particular follower number. 33 | 34 | Kinda practical but not really. 35 | 36 | ## Trivia 37 | "FCF" initially means something far more aggro and derogatory, but was reported by Chinese users as "racial" and "hateful" and thus was forced to censored to the currenty meaning by Github. 38 | 39 | One can easily figure out or speculate what it was meant to be initially, given that they personally knows the author, Lena Shizuki, and her relentless agony & ressentment (<- a French word borrowed by F. Nietzsche). --------------------------------------------------------------------------------