Skip to content

Instantly share code, notes, and snippets.

@beall49
Last active January 28, 2025 23:03
Show Gist options
  • Save beall49/9b83d67fdfdd816ff5404c20d175baa5 to your computer and use it in GitHub Desktop.
Save beall49/9b83d67fdfdd816ff5404c20d175baa5 to your computer and use it in GitHub Desktop.
Delete Reddit Comments
/* you'll need to install dotenv */
import * as dotenv from 'dotenv';
import fs from 'fs';
/* where package.json lives */
const root = process.cwd();
const EMPTY = '';
const ZERO = 0;
/* Load environment variables from the .env file located at the root directory */
dotenv.config({path: `${root}/.env`});
/* Define the home directory for the script (I put this in a directory called `reddit-delete`) */
const HOME = `${root}/reddit-delete`;
/* Retrieve Reddit API credentials from environment variables */
const clientId = process.env.REDDIT_CLIENT;
const clientSecret = process.env.REDDIT_SECRET;
const username = process.env.REDDIT_USERNAME;
const password = process.env.REDDIT_PASS;
const appName = process.env.REDDIT_APP_NAME
let accessToken = '';
/* Disable TLS certificate validation for local development */
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
/**
* Delays execution for a specified amount of time.
*
* @param {number} time - The time to delay in milliseconds.
* @returns {Promise<any>} A promise that resolves after the specified delay.
*/
const delay = async (time: number): Promise<any> => new Promise(resolve => setTimeout(resolve, time));
/**
* Checks if the current access token is still valid.
*
* @returns {boolean} True if the token is valid, false otherwise.
*/
const isTokenValid = (): boolean => {
/* Check if the token.json file exists */
if (fs.existsSync(`${HOME}/token.json`)) {
/* Read and parse the token.json file */
const jwt: {access_token: string, expires: number} = JSON.parse(fs.readFileSync(`${HOME}/token.json`, 'utf-8'));
/* Check if the token has not expired */
return jwt.expires > Date.now();
}
return false;
};
/**
* Fetches an access token from Reddit's API.
*
* @returns {Promise<void>} A promise that resolves when the access token is fetched and saved.
*/
const getAccessToken = async (): Promise<void> => {
/* Make a POST request to Reddit's access token endpoint */
const response = await fetch('https://www.reddit.com/api/v1/access_token', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(`${clientId}:${clientSecret}`),
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'password',
username: username,
password: password
})
});
/* Parse and save the access token from the response */
const data = await response.json();
accessToken = data.access_token;
fs.writeFileSync(`${HOME}/token.json`, JSON.stringify({...data, expires: Date.now() + 3_600_000}, null, 4));
};
/**
* Retrieves the user's comments from Reddit and saves them to a file.
*
* @param {string} [suffix=EMPTY] - The pagination suffix for fetching comments.
* @returns {Promise<{comments: string[], suffix: string}>} A promise that resolves with the comments and pagination suffix.
*/
const getComments = async (suffix: string = EMPTY): Promise<{comments: string[], suffix: string}> => {
/* Make a GET request to Reddit's API to fetch user comments */
const url = `https://oauth.reddit.com/user/${username}/comments?limit=100${suffix}`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'User-Agent': `${appName}/0.0.1`
}
}
);
/* Extract comment IDs and return them along with the pagination suffix */
const res = await response.json();
const json = res.data;
const comments = json.children.map(ea => ea?.data?.name);
return {comments, suffix: json.after};
};
/**
* Deletes a comment on Reddit by its ID.
*
* @param {string} id - The ID of the comment to delete.
* @returns {Promise<any>} A promise that resolves when the comment is deleted.
*/
const deleteComment = async (id: string): Promise<any> => {
/* Make a POST request to Reddit's API to delete the comment */
return await fetch('https://oauth.reddit.com/api/del', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': `${appName}/0.0.1`
},
body: new URLSearchParams({id}),
});
};
/**
* Deletes all comments listed in the comments.json file.
*
* @returns {Promise<void>} A promise that resolves when all comments are deleted.
*/
const deleteComments = async (): Promise<void> => {
/* Check if the access token is valid, otherwise fetch a new one */
if (!isTokenValid()) {
void await getAccessToken();
}
/* Read comment IDs from the JSON file */
const ids: Set<string> = new Set(JSON.parse(fs.readFileSync(`${HOME}/comments.json`, 'utf-8')));
/* Iterate over each comment ID and delete the corresponding comment */
for (const id of ids) {
const response = await deleteComment(id);
if (response.ok) {
console.log(`deleted=${response.ok}: ${id}`);
/* Delay between deletions to avoid rate limiting */
void await delay(750);
} else {
console.log(`Something went wrong with this one:`, id);
break;
}
}
};
/**
* Runs the process of retrieving and deleting comments in iterations.
*
* @returns {Promise<void>} A promise that resolves when all iterations are complete.
*/
const getAllComments = async (): Promise<void> => {
const track = [];
/* Check if the access token is valid, otherwise fetch a new one */
if (!isTokenValid()) {
void await getAccessToken();
}
const sorts = ['&sort=new', '&sort=hot', '&sort=top', '&sort=controversial'];
for (const sort of sorts) {
const res = await getComments(sort);
track.push(res.comments);
let suffix = res.suffix;
let index = ZERO;
while (!!suffix) {
index++;
const page = !!suffix ? `&after=${suffix}` : EMPTY;
const response = await getComments(`${sort}${page}`);
const comments = response.comments;
suffix = response.suffix;
console.log(`Suffix `, suffix);
if (comments?.length) {
track.push(comments);
}
}
}
/* Save all retrieved comments to a JSON file */
fs.writeFileSync(`${HOME}/comments.json`, JSON.stringify(track.flat(), null, 4));
};
/* Start the process of retrieving and deleting comments */
void await getAllComments();
void await deleteComments();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment