Last active
July 14, 2023 17:46
-
-
Save mykdavies/05510e2ff25b43ffb0e75ad7cb1a132b to your computer and use it in GitHub Desktop.
Destroy your Reddit comment history
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
// Use the contents of your GDPR comments.csv file to edit all | |
// of your Reddit history. You can request this history at: | |
// https://www.reddit.com/settings/data-request | |
// They don't check whether you're actually an EU citizen :-) | |
// It uses puppeteer to remotely control a (headless) Chrome browser | |
// If you have a massive history or you posted in many subs that | |
// are now private you may want to refine the script's error | |
// handling which is currently just to dump failed edits to stdout. | |
// The delays may be excessive, but do mean that this job should be | |
// left to run in the background | |
import 'dart:convert'; | |
import 'dart:io'; | |
import 'dart:math'; | |
import 'package:csv/csv.dart'; | |
import 'package:puppeteer/puppeteer.dart'; | |
const un = 'username'; | |
const pw = 'hunter2'; | |
// Useful if you're running a few times with different criteria. | |
const stopWords = ['OLDKEYWORD', 'API ERROR', 'EZEKIEL']; | |
const newStopWord = 'EZEKIEL'; | |
const newPhrases = [ | |
"The path of the righteous man is beset on all sides by the " | |
"inequities of the selfish and the tyranny of evil men.", | |
"Blessed is he who, in the name of charity and good will, shepherds " | |
"the weak through the valley of the darkness. For he is truly his " | |
"brother's keeper and the finder of lost children.", | |
"And I will strike down upon thee with great vengeance and furious " | |
"anger those who attempt to poison and destroy my brothers. " | |
"And you will know I am the Lord when I lay my vengeance upon you.", | |
]; | |
final _random = Random(); | |
void main() async { | |
final input = File('comments.csv').openRead(); | |
final fields = await input | |
.transform(utf8.decoder) | |
.transform(CsvToListConverter()) | |
.toList(); | |
// Download the Chrome binaries, launch it and connect to the "DevTools" | |
var browser = await puppeteer.launch(headless: true); | |
// Open a new tab | |
var page = await browser.newPage(); | |
// Load old reddit of course. | |
await page.goto('https://old.reddit.com', wait: Until.load); | |
await page.type('input[name=user]', un); | |
await page.type('input[name=passwd]', pw); | |
await Future.wait([ | |
page.waitForNavigation(), | |
page.click('#login_login-main button[type=submit]') | |
]); | |
// You're now logged on to this browser instance. | |
File outputFile = File('output.json'); | |
for (var row in fields.skip(1)) { | |
var url = (row[1] as String).replaceFirst('www', 'old'); | |
// Catch any errors and report them to the console for the | |
// user to act on or ignore as they wish. | |
try { | |
await page.goto(url, wait: Until.load); | |
// Look for the highlighted area showing the comment. | |
var el = await page.waitForSelector( | |
'.commentarea div.usertext-body div.md', | |
timeout: Duration(seconds: 5)); | |
String myComment = await el!.propertyValue('textContent'); | |
// Don't do anything if we've already processed this comment. | |
if (stopWords.any((e) => myComment.contains(e))) { | |
// Apply delay just in case of rate limiting. | |
sleep(Duration(seconds: 2)); | |
continue; | |
} | |
// Build the save record. | |
var pid = url.split('/')[6]; | |
var cid = url.split('/')[8]; | |
var archive = { | |
"link_title": await (await page.$("a.title.may-blank.loggedin")) | |
.propertyValue('textContent'), | |
"link_id": cid, | |
"subreddit": await (await page.$("div#header span.redditname a")) | |
.propertyValue('textContent'), | |
"comment_id": cid, | |
"parent_id": pid, | |
"score": (await (await page.$("span.score.likes")) | |
.propertyValue('textContent')) | |
.split(' ') | |
.first, | |
"body": myComment, | |
"created": await page | |
.$eval("p.tagline time", r'''e=>e.getAttribute('datetime')'''), | |
// await (await page.$("p.tagline time")).propertyValue('datetime') | |
}; | |
// Good point at which to decide if you don't want to edit this comment | |
// in this pass based on score, length or whatever: | |
// if () continue; | |
// Press the edit link. | |
await page.click('ul.flat-list li a.edit-usertext'); | |
// Wait for this comment's textarea to be brought forward. | |
var editBoxSelector = 'div#thing_t1_$cid .usertext-edit div.md textarea'; | |
var watchEdit = page.waitForSelector(editBoxSelector, | |
visible: true, timeout: Duration(seconds: 5)); | |
// We can assume it's not null because the `waitForSelector` call | |
// would have thrown an exception. | |
var editBox = (await watchEdit)!; | |
// Delete and replace existing content. | |
// You could do an eval call to edit the DOM directly instead. | |
await editBox.click(clickCount: 3); // select all (ish) | |
await editBox.press(Key.backspace); // delete selection | |
// Look for any missed text and delete it. | |
var spareText = await page.$eval(editBoxSelector, r'''el => el.value'''); | |
await editBox.click(); | |
for (var i = 0; i < spareText.length; i++) { | |
await page.keyboard.press(Key.backspace); | |
} | |
// Includes the comment id as a random element to evade oversensitive | |
// spam traps. You could just use any old random string instead. | |
await editBox.type('${newPhrases[_random.nextInt(newPhrases.length)]} ' | |
'[$newStopWord: $cid]\n'); | |
await page | |
.click('div#thing_t1_$cid .usertext-buttons button[type=submit]'); | |
outputFile.writeAsStringSync('${json.encode(archive)}\n', | |
mode: FileMode.append); | |
// print('edited $url'); | |
sleep(Duration(seconds: 10)); | |
} catch (e) { | |
print("Couldn't edit $url"); | |
// Just in case there is some serious rate limiting happening. | |
sleep(Duration(seconds: 20)); | |
continue; | |
} | |
} | |
// Gracefully close the browser's process | |
await browser.close(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment