Skip to content

Instantly share code, notes, and snippets.

@mykdavies
Last active July 14, 2023 17:46
Show Gist options
  • Save mykdavies/05510e2ff25b43ffb0e75ad7cb1a132b to your computer and use it in GitHub Desktop.
Save mykdavies/05510e2ff25b43ffb0e75ad7cb1a132b to your computer and use it in GitHub Desktop.
Destroy your Reddit comment history
// 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