- Use Chrome to login to twitter - do any 2FA you need in browser.
- Edit the
TWITTER_PROFILE_USERNAME
to your Twitter user name (what's in your profile URL). - Edit the
CHROME_PROFILE_PATH
to your current system OS user's google chrome userdata path. - Run multiple times until all the tweets are removed. (Edit
$replies =
to false and repeat).
Last active
November 16, 2023 23:02
-
-
Save mallardduck/fe9a7aed38a156e821980815b27ec5d2 to your computer and use it in GitHub Desktop.
Symfony Panther Tweets Deleter
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
placeholder to control gist title name |
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
{ | |
"require": { | |
"symfony/panther": "^2.0", | |
"dbrekelmans/bdi": "^1.0" | |
} | |
} |
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 | |
use Facebook\WebDriver\Remote\RemoteWebElement; | |
use Facebook\WebDriver\WebDriverBy; | |
use Facebook\WebDriver\WebDriverElement; | |
use Symfony\Component\Panther\Client; | |
require __DIR__.'/vendor/autoload.php'; // Composer's autoloader | |
global $client; | |
CONST TWITTER_PROFILE_USERNAME='dan_in_tweets'; | |
const CHROME_PROFILE_PATH='/home/dan/.config/google-chrome'; | |
$client = Client::createChromeClient(null, [ | |
'--no-sandbox', | |
'--window-size=1200,1500', | |
'--user-data-dir=' . CHROME_PROFILE_PATH, | |
]); | |
$client->request('GET', 'https://twitter.com/' . TWITTER_PROFILE_USERNAME); // Yes, this website is 100% written in JavaScript | |
$client->waitFor('//span[ text() = "Tweets" ]'); | |
echo "found menu..." . PHP_EOL; | |
$client->waitForVisibility('//*[@id="react-root"]/div/div/div[2]/main/div/div/div/div[1]/div/div[3]/div/div/section/div'); | |
echo "found timeline" . PHP_EOL; | |
$replies = true; | |
if ($replies === false) { | |
sleep(1); | |
while ( | |
$element = $client->findElement(WebDriverBy::cssSelector('section article:first-of-type')) | |
) { | |
$element->getLocationOnScreenOnceScrolledIntoView(); | |
echo "found element" . PHP_EOL; | |
sleep(1); | |
delete_tweet_or_unretweet($element); | |
sleep(1); | |
} | |
} else { | |
$repliesReload = false; | |
replies_reloaded: | |
if ($repliesReload) { | |
$client->waitFor('//span[ text() = "Tweets" ]'); | |
echo "found menu..." . PHP_EOL; | |
$client->waitForVisibility('//*[@id="react-root"]/div/div/div[2]/main/div/div/div/div[1]/div/div[3]/div/div/section/div'); | |
echo "found timeline" . PHP_EOL; | |
$repliesReload = false; | |
} | |
$client->findElement(WebDriverBy::xpath('//span[ text() = "Tweets & replies" ]'))->click(); | |
$client->waitForVisibility('//*[@id="react-root"]/div/div/div[2]/main/div/div/div/div[1]/div/div[3]/div/div/section/div'); | |
try { | |
while ($articles = $client->findElements(WebDriverBy::cssSelector('[aria-label^="Timeline"] article'))) { | |
$articles = array_values( | |
array_filter( | |
$articles, | |
function (RemoteWebElement $val) { | |
try { | |
$val->findElement(WebDriverBy::cssSelector('*[data-testid="UserAvatar-Container-dan_in_tweets"]')); | |
return true; | |
} catch (\Exception $e) { | |
return false; | |
} | |
}) | |
); | |
foreach ($articles as $article) { | |
$article->getLocationOnScreenOnceScrolledIntoView(); | |
sleep(1); | |
delete_tweet($article); | |
} | |
} | |
} catch (\Exception $e) { | |
$client->reload(); | |
$repliesReload = true; | |
goto replies_reloaded; | |
} | |
} | |
function delete_tweet_or_unretweet(WebDriverElement $element) | |
{ | |
try { | |
$unretweet = $element->findElement(WebDriverBy::xpath('//div[@data-testid="unretweet"]')); | |
} catch (\Exception $e) { | |
$unretweet = null; | |
} | |
if ($unretweet !== null) { | |
confirm_unretweet($unretweet); | |
return; | |
} | |
delete_tweet($element); | |
} | |
function confirm_unretweet(WebDriverElement $unretweet) | |
{ | |
global $client; | |
try { | |
$unretweet->click(); | |
} catch (\Exception $e) { | |
dd( | |
$unretweet->getLocation() | |
); | |
} | |
try { | |
$unretweetConfirm = $client->findElement(WebDriverBy::xpath('//div[@data-testid="unretweetConfirm"]')); | |
} catch (\Exception $e) { | |
echo "Couldn't find the unretweet confirm button..." . PHP_EOL; | |
return false; | |
} | |
$unretweetConfirm->click(); | |
return true; | |
} | |
function delete_tweet(WebDriverElement $element) | |
{ | |
global $client; | |
$caretMenu = null; | |
try { | |
$caretMenu = $element->findElement(WebDriverBy::cssSelector('div[data-testid="caret"]')); | |
echo "found caret" . PHP_EOL; | |
} catch (\Exception $e) { | |
echo "CANNOT find caret" . PHP_EOL; | |
sleep(30); | |
// TODO: add exception for can't find caret... | |
} | |
$caretMenu->click(); | |
$deleteButton = null; | |
try { | |
$deleteButton = $client->findElement(WebDriverBy::cssSelector('div[role="menuitem"][tabindex="0"]:first-of-type')); | |
echo "found delete" . PHP_EOL; | |
} catch (\Exception $e) { | |
echo "CANNOT find delete" . PHP_EOL; | |
sleep(30); | |
// TODO: add exception for can't find delete... | |
} | |
var_dump($deleteButton->getText()); | |
if ($deleteButton->getText() === "Delete") $deleteButton->click(); | |
try { | |
$deleteConfirm = $client->findElement(WebDriverBy::xpath('//div[@data-testid="confirmationSheetConfirm"]')); | |
} catch (\Exception $e) { | |
echo "Couldn't find the delete confirm button..." . PHP_EOL; | |
return false; | |
} | |
$deleteConfirm->click(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment