-
-
Save marcaube/f8c199d206959d36ef76339f87e904df to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// | |
// AUTO KEYWORD-BASED FOLLOWER CURATION BOT (by @levelsio) | |
// | |
// https://gist.github.com/levelsio/6ee6c47283ee414ef3aace1d81986717 | |
// | |
// File: twitterFollowerCuratorBot.php | |
// | |
// Created: May 2021 | |
// License: MIT | |
// | |
// Dependency: https://github.com/abraham/twitteroauth via Composer | |
// | |
// Installation: | |
// | |
// Configure $consumer_key, $consumer_secret, $user_token and $user_token_secret with your Twitter API credentials. | |
// Configure $keywords with your own unfollow keywords. | |
// | |
// Execute: | |
// | |
// $> composer require abraham/twitteroauth | |
// $> php twitterFollowerCuratorBot.php | |
// | |
// Schedule/cron: @hourly | |
// | |
// 1) This script goes through your new followers and curates them to pre-emptively remove trolls | |
// 2) It checks their name, bio, location etc. for specific keywords | |
// 3) If it matches, it blocks them, then quickly unblocks them, which results in them automatically unfollowing you | |
// 4) This means they won't see your tweets anymore in their timeline | |
// 5) It also mutes them so you won't see their tweets anymore either | |
// 6) Extra features are unfollowing based | |
// a) low follower count (<10) | |
// b) no profile image | |
// c) account created in last 30 days | |
// 7) It avoids removing big followers (>10,000 followers) | |
// 8) Tip: you can also curate entire cities, for ex I auto remove followers from San Francisco because I think they live in a bubble and their tweets are usually annoying to me | |
// 9) It saves the amount of people removed or approved in ./blockedCount.txt and ./approvedCount.txt, and saves cache in ./approvedUserIdsCache.json to avoid re-checking the same followers | |
// | |
// <config> | |
// <Twitter app credentials> | |
$consumer_key = ''; | |
$consumer_secret = ''; | |
$user_token = ''; | |
$user_token_secret = ''; | |
// </Twitter app credentials> | |
// <keywords to match> | |
// change this with your own keywords you want to match users on to unfollow | |
$keywords = [ | |
'biden', | |
'trump', | |
'san francisco', | |
]; | |
// </keywords to match> | |
// </config> | |
error_reporting(E_ALL); | |
require(__DIR__.'/vendor/autoload.php'); | |
use Abraham\TwitterOAuth\TwitterOAuth; | |
if (!($connection = new TwitterOAuth($consumer_key, $consumer_secret, $user_token, $user_token_secret))) { | |
die('(!) Cannot connect to Twitter'); | |
} | |
if (!($response = $connection->get('followers/ids', ['count' => 100]))) { | |
die('Error: '.json_encode($response)); | |
} | |
$newlyFollowingUserIds = $response->ids; | |
if (empty($newlyFollowingUserIds)) { | |
die("Bad response empty ids: ".json_encode($response)); | |
} | |
if (!is_file(__DIR__.'/approvedCount.txt')) { | |
file_put_contents(__DIR__.'/approvedCount.txt', '0'); | |
} | |
if (!is_file(__DIR__.'/blockedCount.txt')) { | |
file_put_contents(__DIR__.'/blockedCount.txt', '0'); | |
} | |
if (!is_file(__DIR__.'/approvedUserIdsCache.json')) { | |
file_put_contents(__DIR__.'/approvedUserIdsCache.json', '[]'); | |
} | |
$approvedUserIds = (array)json_decode(file_get_contents(__DIR__.'/approvedUserIdsCache.json'), true); | |
foreach ($newlyFollowingUserIds as $userId) { | |
if (in_array($userId, $approvedUserIds)) { | |
echo $userId." already approved\n"; | |
continue; | |
} | |
echo "😴 Sleeping for 5 seconds...\n"; | |
sleep(5); | |
if (!($user = $connection->get('users/show', ['user_id' => $userId]))) { | |
die('Error'); | |
} | |
echo "\n==========================\n"; | |
echo "#".$userId."\n"; | |
echo "name=".$user->name."\n"; | |
echo "screen_name=".$user->screen_name."\n"; | |
echo "description=".$user->description."\n"; | |
echo "location=".$user->location."\n"; | |
echo "followers_count=".$user->followers_count."\n"; | |
echo "default_profile_image=".$user->default_profile_image."\n\n"; | |
$suspiciousUserFlags = 0; | |
if ($user->default_profile_image) { | |
$suspiciousUserFlags++; | |
$blockReason = "they have no profile image set"; | |
} | |
if ($user->followers_count < 10) { | |
$suspiciousUserFlags++; | |
$blockReason = "they have a low follower count <10"; | |
} | |
if ($user->protected) { | |
$suspiciousUserFlags++; | |
$blockReason = "they have a private account"; | |
} | |
if (strtotime($user->created_at) > strtotime('-30 days')) { | |
$suspiciousUserFlags++; | |
$blockReason = "they have an account created in the last 30 days"; | |
} | |
$userKeywordMatches = []; | |
foreach ($keywords as $keyword) { | |
if ( | |
(stripos(str_replace(' ', '', $user->name), $keyword) !== false) || | |
(stripos(str_replace(' ', '', $user->screen_name), $keyword) !== false) || | |
(stripos(str_replace(' ', '', $user->description), $keyword) !== false) || | |
(stripos(str_replace(' ', '', $user->location), $keyword) !== false) | |
) { | |
$userKeywordMatches[] = $keyword; | |
$blockReason = "their name, bio or location has a blocked keyword match"; | |
} | |
} | |
// <if big follower, ignore suspicious and keywords> | |
if ($user->followers_count > 10000) { | |
$userKeywordMatches = 0; | |
$suspiciousUserFlags = 0; | |
} | |
// </if big follower, ignore suspicious and keywords> | |
if (!$userKeywordMatches && !$suspiciousUserFlags) { | |
echo "✅ Approved, adding to approved cache\n"; | |
$approvedUserIds[] = $userId; | |
file_put_contents(__DIR__.'/approvedCount.txt', (int)file_get_contents(__DIR__.'/approvedCount.txt') + 1); | |
file_put_contents(__DIR__.'/approvedUserIdsCache.json', json_encode($approvedUserIds)); | |
echo "😴 Sleeping for 5 seconds...\n"; | |
sleep(5); | |
continue; | |
} | |
if ($userKeywordMatches) { | |
echo "❌ Flagged new follower @".$user->screen_name.", matching on: ".implode(', ', $userKeywordMatches)."\n"; | |
} | |
if ($suspiciousUserFlags && $user->default_profile_image) { | |
echo "❌ Flagged new follower @".$user->screen_name.", looks suspect due to no profile image\n"; | |
} | |
if ($suspiciousUserFlags && $user->followers_count<10) { | |
echo "❌ Flagged new follower @".$user->screen_name.", looks suspect due to low follower count\n"; | |
} | |
// <blunble> | |
echo "❌ Blocking new follower @".$user->screen_name." to make them unfollow\n"; | |
echo json_encode($connection->post('blocks/create', ['user_id' => $userId]))."\n"; | |
echo "😴 Sleeping for 5 seconds...\n"; | |
sleep(5); | |
echo "❌ Unblocking new follower @".$user->screen_name." to make them unfollow\n"; | |
echo json_encode($connection->post('blocks/destroy', ['user_id' => $userId]))."\n"; | |
// </blunble> | |
// <mute> | |
echo "❌ Muting new follower @".$user->screen_name."\n"; | |
echo json_encode($connection->post('mutes/users/create', ['user_id' => $userId]))."\n"; | |
// </mute> | |
file_put_contents(__DIR__.'/blockedCount.txt', (int)file_get_contents(__DIR__.'/blockedCount.txt') + 1); | |
echo( | |
"🧨 Removed new follower https://twitter.com/".$user->screen_name." because ".$blockReason."\n". | |
"Keyword matches: ".implode(', ', $userKeywordMatches)."\n". | |
"Follower count: ".$user->followers_count."\n". | |
"No profile image: ".$user->default_profile_image."\n". | |
"Blocked count :".number_format(file_get_contents(__DIR__.'/blockedCount.txt'))."\n". | |
"Approved count :".number_format(file_get_contents(__DIR__.'/approvedCount.txt'))."\n" | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment