├── LICENSE ├── README.md ├── TODO ├── bin └── whitelist.sh ├── ggautoblocker.pl ├── sourcelist.txt └── whitelist.txt /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Randi Harper 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Good Game Auto Blocker 2 | 3 | Please see http://blog.randi.io/good-game-auto-blocker/ for a more updated version of this README. 4 | 5 | ### history 6 | 7 | A major problem with social media is the lack of flexible filtering controls. Twitter has a block mechanism, but a user has to initiate contact in order to be blocked. For most forms of harassment, this is an effective way of moderating conversations. Unfortunately, as more social campaigns use Twitter as their basis for communications, this approach becomes less effective. While it’s suitable for use against a single harasser, it’s useless against a large number of accounts targeting a single user. These tweets needed to be stopped before they land in the user’s notifications. 8 | 9 | ### how it works 10 | 11 | Good Game Auto Blocker compares the follower lists for a given set of Twitter accounts. If anyone is found to be following more than one of these accounts, they are added to a list and blocked. 12 | 13 | Most discussions of ggautoblocker are referencing the GamerGate-specific block list. The GamerGate block list filters the majority of Twitter interactions by GamerGate supporters. This list is maintained and shared by the author, Randi Harper, as well as a number of volunteers. The previous version of ggautoblocker can be found at [freebsdgirl/ggautoblocker](http://github.com/freebsdgirl/ggautoblocker/), but a complete rewrite is underway. Newer versions will be published on [OAPI's github](http://github.com/oapi/ggautoblocker). 14 | 15 | ### dependencies for running the old code manually 16 | 17 | Requires Net::Twitter which has a huge level of crazy deps. 18 | 19 | --- 20 | 21 | THE REST OF THIS PAGE REFERS TO THE SHARED GAMERGATE BLOCK LIST. 22 | 23 | 24 | ### how to use it 25 | 26 | [Subscribe](https://blocktogether.org/show-blocks/5867111278318bd542293272f75147f8fc5931bea431e7ca16e9242964965d66494a6fb68f3518b82f171bcf0e419ccc) to the blocklist. Subscribers will automatically receive the updates. 27 | 28 | ### are you on it 29 | 30 | The easiest way to check if you’re on the block list is to verify if [@randi_ebooks](http://twitter.com/randi_ebooks) is blocking you. This is the account that maintains and shares the block list. (This account is a markov bot that is not monitored by a human, so please don’t direct any questions at it.) 31 | 32 | The block list is periodically re-generated and updated. Each update is announced with at least 24 hours advance notice by the [@ggautoblocker](http://twitter.com/ggautoblocker) Twitter account. 33 | 34 | ### appealing a block 35 | 36 | After reviewing the [appeals board policy guidelines](https://docs.google.com/document/d/14iu4XVTKw2tSAlv3x8ktxQfz550bB_EtoUjNYIdPCpk/edit), send an [email](mailto:appeals@ggautoblocker.com) with your twitter username as well as any relevant information you believe they should know. [All requests and discussions are public](https://groups.google.com/forum/#!forum/ggautoblocker-appeals). You will be contacted when a decision has been made. 37 | 38 | The maintainer of this block list is not a member of the appeals board and will not remove blocks without appeals board approval. 39 | 40 | ### contacting support 41 | 42 | Please read the README and check out the latest version of [Frequently Yelled Statements](http://blog.randi.io/good-game-auto-blocker/frequently-yelled-statements/). Submit support requests via the [Contact page](http://blog.randi.io/contact/). 43 | 44 | ### what’s next 45 | 46 | While the community version on github has been written in perl, it’s not going to be developed any further. Instead, there’s a new version written in ruby that will soon be open sourced. 47 | 48 | ### how you can help 49 | 50 | After 15 years in engineering, I left to spend my time working on anti-harassment tools, policy, and education. I co-founded a (soon-to-be) non-profit, the [Online Abuse Prevention Initiative](http://onlineabuseprevention.org/). Until OAPI is further off the ground and able to receive funding, donations to my personal [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=donations%40ggautoblocker%2ecom&lc=US&item_name=ggautoblocker&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) or [Patreon](http://www.patreon.com/freebsdgirl) are always appreciated. I am currently 100% crowd-funded. 51 | 52 | There are currently no spots open on the appeals board, but watch this space. 53 | 54 | The code (though not the service) is currently in the process of being transitioned over to being managed by OAPI, so we’re not looking for any new developers yet. 55 | 56 | A step-by-step guide for setting up the ggautoblocker GamerGate blocklist with screenshots would be immensely helpful. We want instructions that a user completely new to Twitter would be able to follow. If you’d like to put that together, email or use the Contact page to submit a draft. 57 | 58 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Check whitelist IDs instead of names 2 | 3 | Add scoring: 4 | - age of account 5 | - follower:following ratio 6 | - number of blacklisted users a sheeple is following 7 | 8 | Split up API requests so after every block of follower ids is retrieved, an API request to retrieve the username immediately follows it. We'll see how this works. It's going to add more requests for users because prior to this, we were only querying users that followed more than 1 account. If it gets to be a problem, can switch back to the old method. 9 | 10 | Update README to reflect how to use third-party tools to implement mass blocks until API issues are worked out with twitter. 11 | -------------------------------------------------------------------------------- /bin/whitelist.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eou pipefail 4 | 5 | readonly USERNAME=$(echo "$1" | tr -d '@') 6 | 7 | function user_exists_warning(){ 8 | echo "$USERNAME is already on whitelist. Exiting..." 9 | exit 1 10 | } 11 | 12 | function add_user_to_whitelist(){ 13 | echo "$USERNAME" >> whitelist.txt 14 | } 15 | 16 | function add_whitelist_to_git(){ 17 | git add whitelist.txt 18 | git commit -m "Add $USERNAME to whitelist based on Appeal Group decision" 19 | } 20 | 21 | function main(){ 22 | if [[ $(grep -i -e "^$USERNAME$" whitelist.txt ) ]];then 23 | user_exists_warning 24 | else 25 | add_user_to_whitelist 26 | add_whitelist_to_git 27 | fi 28 | } 29 | 30 | main 31 | -------------------------------------------------------------------------------- /ggautoblocker.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | 3 | use strict; 4 | 5 | use Net::Twitter; 6 | 7 | # Needs read/write access! 8 | 9 | my $consumer_key = ""; 10 | my $consumer_secret = ""; 11 | 12 | my $access_token = ""; 13 | my $access_secret = ""; 14 | 15 | my $sourcelist_file = "sourcelist.txt"; 16 | my $whitelist_file = "whitelist.txt"; 17 | 18 | my $debug = 1; 19 | 20 | my ( @whitelist, @idiots ); 21 | 22 | my %problem; 23 | 24 | my ( @follower_ids, @myfollower_ids, @sheeple_ids ); 25 | 26 | 27 | # set up our twitter connection 28 | 29 | my $nt = Net::Twitter->new( 30 | traits => [qw/API::RESTv1_1/], 31 | ssl => 1, 32 | consumer_key => $consumer_key, 33 | consumer_secret => $consumer_secret, 34 | access_token => $access_token, 35 | access_token_secret => $access_secret 36 | ); 37 | 38 | 39 | # check our rate limits. 40 | # this would probably be better off stored in a hash whose values are monitored by 41 | # this program so we didn't have to make this call so often, but whatever. 42 | sub get_rate_limit { 43 | my $type = shift; 44 | 45 | my $m = $nt->rate_limit_status; 46 | 47 | if ( $m->{'resources'}->{'application'}->{'/application/rate_limit_status'}->{'remaining'} == 0 ) { 48 | print " -- API limit reached, waiting for ". ( $m->{'resources'}->{'application'}->{'/application/rate_limit_status'}->{'reset'} - time ) . " seconds --\n" if $debug; 49 | sleep ( $m->{'resources'}->{'application'}->{'/application/rate_limit_status'}->{'reset'} - time + 1 ); 50 | } 51 | 52 | if ( $type =~ /followers/ ) { 53 | return { 54 | remaining => $m->{'resources'}->{'followers'}->{'/followers/ids'}->{'remaining'}, 55 | reset => $m->{'resources'}->{'followers'}->{'/followers/ids'}->{'reset'} 56 | }; 57 | } elsif ( $type =~ /lookup_users/ ) { 58 | return { 59 | remaining => $m->{'resources'}->{'users'}->{'/users/lookup'}->{'remaining'}, 60 | reset => $m->{'resources'}->{'users'}->{'/users/lookup'}->{'reset'} 61 | }; 62 | } 63 | } 64 | 65 | 66 | # sleep until the reset is happy again. 67 | sub wait_for_rate_limit { 68 | my $type = shift; 69 | 70 | my $limit = get_rate_limit($type); 71 | 72 | if ( $limit->{'remaining'} == 0 ) { 73 | print " -- API limit reached, waiting for ". ( $limit->{'reset'} - time ) . " seconds --\n" if $debug; 74 | sleep ( $limit->{'reset'} - time + 1 ); 75 | } 76 | } 77 | 78 | 79 | # get a list of followers 80 | sub get_followers { 81 | my $screen_name = shift; 82 | my @followers; 83 | 84 | for ( my $cursor = -1, my $r; $cursor; $cursor = $r->{next_cursor} ) { 85 | wait_for_rate_limit('followers'); 86 | 87 | if ( $screen_name ) { 88 | $r = $nt->followers_ids({ screen_name => $screen_name, cursor => $cursor }); 89 | } else { 90 | $r = $nt->followers_ids({ cursor => $cursor }); 91 | } 92 | 93 | push @followers, @{$r->{ids}}; 94 | } 95 | 96 | return @followers; 97 | } 98 | 99 | 100 | # get screen_names to go along with the account ids. 101 | sub get_screen_names { 102 | my $sheeple = shift; 103 | my @ids = keys %$sheeple; 104 | my @names; 105 | 106 | while ( $#ids > 0 ) { 107 | wait_for_rate_limit('lookup_users'); 108 | 109 | my @subset_ids = splice @ids, 0, 100; 110 | my $users = $nt->lookup_users( { user_id => \@subset_ids } ); 111 | 112 | foreach my $user ( @{$users} ) { 113 | $sheeple->{$user->{'id'}}->{'name'} = $user->{'screen_name'}; 114 | } 115 | } 116 | 117 | } 118 | 119 | 120 | # is username in whitelist? 121 | sub is_whitelisted { 122 | my $sheep = shift; 123 | return 0 unless $sheep; 124 | 125 | if ( @whitelist ) { 126 | foreach my $notasheep ( @whitelist ) { 127 | return 1 if $notasheep =~ $sheep; 128 | } 129 | } 130 | 131 | return 0; 132 | } 133 | 134 | 135 | # get a list of whitelisted users 136 | open W, '<', $whitelist_file or die "Can't open $whitelist_file: $!\n"; 137 | foreach ( ) { 138 | chomp; 139 | push @whitelist, $_; 140 | } 141 | close W; 142 | 143 | 144 | # get a list of idiots 145 | open B, '<', $sourcelist_file or die "Can't open $sourcelist_file: $!\n"; 146 | foreach ( ) { 147 | chomp; 148 | push @idiots, $_; 149 | } 150 | close B; 151 | 152 | 153 | 154 | print "This is going to take a while, because API limits are dumb.\n\n"; 155 | $| = 1; 156 | 157 | 158 | # first, get a list of the IDs of all the problem children. 159 | foreach my $idiot ( @idiots ) { 160 | print "Examining follower list for $idiot.\n"; 161 | push @follower_ids, get_followers( $idiot ); 162 | } 163 | 164 | 165 | # get a list of our personal followers 166 | print "Getting a list of my followers for comparison.\n"; 167 | @myfollower_ids = get_followers; 168 | 169 | 170 | # get a list of unique IDs. 171 | print "Examining follower lists...\n"; 172 | 173 | 174 | # if the sheeple is following us, don't put it in the main array. put it in 175 | # a separate data structure. This is to keep ourselves from doing excessive 176 | # API calls later when we're checking usernames. 177 | foreach my $id (@follower_ids) { 178 | if ( exists $problem{$id} ) { 179 | $problem{$id}->{'count'}++; 180 | } else { 181 | $problem{$id}->{'count'} = 1; 182 | 183 | # does this id exist in our followers? 184 | foreach my $my_id ( @myfollower_ids ) { 185 | if ( $my_id == $id ) { 186 | $problem{$id}->{'stalker'} = 1; 187 | } 188 | } 189 | } 190 | } 191 | 192 | # print out some stats 193 | # BUG! these numbers aren't accurate. plzfix. 194 | @sheeple_ids = keys %problem; 195 | print "> $#follower_ids users following idiots.\n"; 196 | print "> $#sheeple_ids users following multiple accounts.\n"; 197 | 198 | 199 | # save ids to file if we're debugging 200 | if ( $debug ) { 201 | print "Saving list of IDs to block_ids.txt.\n"; 202 | open BL, '>block_ids.txt' or die "Can't open block_ids.txt: $!\n"; 203 | foreach my $monster ( keys %problem ) { 204 | if ( $problem{$monster}->{'count'} == 1 ) { 205 | delete $problem{$monster}; 206 | } else { 207 | print BL "$monster\n"; 208 | } 209 | } 210 | close BL; 211 | } 212 | 213 | # turn IDs into usernames. 214 | print "Getting list of usernames from IDs.\n"; 215 | 216 | get_screen_names(\%problem); 217 | 218 | 219 | # save to a file, but only if they aren't part of the whitelist. 220 | print "Saving list of usernames to block_names.txt & shared_names.txt\n"; 221 | open BL, '>block_names.txt' or die "Can't open block_names.txt: $!\n"; 222 | open SL, '>shared_names.txt' or die "Can't open shared_names.txt: $!\n"; 223 | foreach my $sheep ( keys %problem ) { 224 | next if is_whitelisted( $problem{$sheep}->{'name'} ); 225 | next unless exists $problem{$sheep}->{'name'}; 226 | next unless exists $problem{$sheep}->{'count'}; 227 | 228 | print BL $problem{$sheep}->{'name'}."\n"; 229 | 230 | if ( exists $problem{$sheep}->{'stalker'} ) { 231 | print SL $problem{$sheep}->{'name'}."\n"; 232 | } 233 | } 234 | close BL; 235 | close SL; 236 | 237 | 238 | # TODO: actually block the user! 239 | # $nt->create_block( { user_id => $id } ) 240 | 241 | # TODO: do we want to look at account stats for these users? maybe limit by f:f ratio/acct creation date? 242 | # otherwise users like hootsuite are going to get blocked. 243 | -------------------------------------------------------------------------------- /sourcelist.txt: -------------------------------------------------------------------------------- 1 | Nero 2 | FartToContinue 3 | PlayDangerously 4 | roguestargamez 5 | TheRalphRetort 6 | RealVivianJames 7 | CHOBITCOIN 8 | -------------------------------------------------------------------------------- /whitelist.txt: -------------------------------------------------------------------------------- 1 | gamergatetxt 2 | theFriedEel 3 | Roran_Stehl 4 | AndyMicone 5 | davidpakmanshow 6 | JTDabbagian 7 | Mikebaker733t 8 | 8BitBecca 9 | Boogie2988 10 | Shurmaster 11 | hotelzululima 12 | Dynamite_Derek 13 | DavidFutrelle 14 | AdrianChen 15 | EugeneSZ 16 | gamergatesays 17 | thrillho101 18 | mmogirlkai 19 | MothershipTeam 20 | Trybius 21 | sentreh 22 | MothershipTeam 23 | Hitman_Spike 24 | paulengelhard 25 | theaduskin 26 | maelorin 27 | sanosuke123 28 | JoshHano 29 | MaslabDroid 30 | dantronlesotho 31 | GoodMenProject 32 | lomecas 33 | KrisMactavish 34 | dpakman 35 | metalgearmkiii 36 | ghostlev 37 | PauloBelato 38 | steepinkline 39 | APGNation 40 | SeanWinnett 41 | Asher_Wolf 42 | ezekielfe 43 | mitsureiji 44 | cabermea 45 | KFC 46 | brane_drayn 47 | Filippus_ 48 | go_darki 49 | stupiddog_hnng 50 | MouseGentle 51 | jmarcoli 52 | siaraDarais 53 | playdor 54 | jrblanc1 55 | maiyanee 56 | FaraJosieAngela 57 | lewdlycordelia 58 | BigTastyBurger 59 | kuuuramantoonis 60 | Cernowatch 61 | Flandre5carlet 62 | endartica 63 | me0witude 64 | KarlJamesJones 65 | AmandaShebang 66 | discordia1337 67 | deceptive_games 68 | Jomann 69 | DeathOnFrontier 70 | zenofdesign 71 | Femitheist 72 | _lifestyled 73 | T_K_Hanrahan 74 | bccasteel 75 | Wily_Matt 76 | TeamSJW 77 | dwisms 78 | rhoulette 79 | PolyphonicLemur 80 | sir_roflcopter 81 | palvium 82 | andrew9472 83 | pascallisch 84 | catfreq 85 | ellypriZeMaN 86 | expELLAarmus 87 | SMT_YHVH 88 | thrillhaus_ 89 | nwjerseyliz 90 | noclu_ 91 | KokJan 92 | gnuchris 93 | mokoneko_ 94 | SidneyLehrer 95 | AlternateSteve2 96 | palpatineoppa 97 | Zephyrel 98 | liekness 99 | strafegame 100 | theswweet 101 | AceKenshader 102 | mizanyx 103 | prtgrl 104 | igvacor94 105 | HowToMakeAnRPG 106 | DanielMasonNZ 107 | wnatch 108 | Mr_KNE 109 | Bitter_one13 110 | dfkt 111 | 1iane 112 | sirnightowl 113 | BenDoernberg 114 | messeth 115 | creatrixtiara 116 | Baeocystin 117 | ilifadeili 118 | elissabeth 119 | CalmBit 120 | theboom1 121 | --------------------------------------------------------------------------------