Last active
January 28, 2025 23:03
-
-
Save beall49/9b83d67fdfdd816ff5404c20d175baa5 to your computer and use it in GitHub Desktop.
Delete Reddit Comments
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
/* 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