Skip to content

Commit 853fcce

Browse files
committed
use commander for command line arguments
1 parent 69895a2 commit 853fcce

File tree

3 files changed

+170
-209
lines changed

3 files changed

+170
-209
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"author": "",
1010
"license": "ISC",
1111
"dependencies": {
12+
"commander": "^11.1.0",
1213
"puppeteer": "^20.0.0",
1314
"ts-node": "^10.9.1",
1415
"typescript": "^5.1.6"

src/index.ts

Lines changed: 164 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -1,221 +1,176 @@
1-
import puppeteer from "puppeteer";
2-
import { prompt } from "./utils/terminal";
1+
import { program } from "commander";
2+
import fs from "fs";
33
import path from "path";
4+
import puppeteer from "puppeteer";
45
import { loadData } from "./load-data";
6+
import { prompt } from "./utils/terminal";
57

6-
const jsonFileInput = path.resolve(process.cwd(), process.argv[2]);
7-
8-
//only required to write a log
9-
const fs = require('fs');
10-
const log_name = Date.now() + '_log.txt';
11-
12-
//debug functions
13-
var log = false;
14-
var extended_error = true;
15-
var skipTo = 0;
16-
17-
//basic delays and timeout
18-
var delay_amount = 5000;
19-
var timeout_amount = 5000;
20-
21-
//index of args
22-
var i = 0;
23-
for (const arg of process.argv)
24-
{
25-
//i had some errors so this is here to just catch and print them
26-
try{
27-
switch(arg)
28-
{
29-
case "-debug" : {log = true; break;}
30-
case "-log" : {extended_error = true; break;}
31-
case "-exlog" :
32-
{
33-
log = true;
34-
extended_error = true;
35-
break;
36-
}
37-
case "-nolog" : {extended_error = false; log = false; break;}
38-
case "-skip" : {skipTo = Number(process.argv[i+1]); break;}
39-
case "-delay" : {
40-
var arg_delay = Number(process.argv[i+1]);
41-
if(arg_delay < 5000)
42-
console.log("Lower then 5000ms delay could cause rate limiting!");
43-
delay_amount = arg_delay;
44-
break;
45-
}
46-
case "-timeout" : {
47-
var arg_timeout = Number(process.argv[i+1]);
48-
if(arg_timeout < 5000)
49-
console.log("Lower then 5000ms could cause false positives when checking for page elements!");
50-
timeout_amount = arg_timeout;
51-
break;
52-
}
53-
}
54-
}
55-
catch(error)
56-
{
57-
console.log(error);
58-
}
59-
i++;
60-
}
8+
program
9+
.argument("<file>", "the tweets file to load")
10+
.option("-d, --debug", "writes debug information to log file")
11+
.option("-l, --log", "writes log information to log file", false)
12+
.option("-e, --exlog", "writes all information from log and debug to log file", true)
13+
.option("-n, --nolog", "forces app to run without making a log file, could be helpful if removing large amounts of tweets/retweets/likes")
14+
.option(
15+
"-s, --skip <number>",
16+
"skips up to the index given, good if you had to close the app or it crashed and don't have time to rerun the entire file",
17+
"0"
18+
)
19+
.option(
20+
"-t, --timeout <number>",
21+
"the timeout amount used after tweet is loaded (helpful on low bandwidth connections), try not to use below 5000ms as this could cause rate limiting",
22+
"5000"
23+
)
24+
.option("-w, --wait <number>", "the delay used between actions, try not to use below 5000ms as this could cause rate limiting", "5000");
6125

26+
program.parse();
27+
const options = program.opts();
28+
const log = options.log;
29+
const extended_error = options.exlog;
30+
const skipTo = parseInt(options.skip);
31+
const timeout_amount = parseInt(options.timeout);
32+
const delay_amount = parseInt(options.wait);
6233

63-
34+
const jsonFileInput = path.resolve(process.cwd(), options.file);
35+
const log_name = Date.now() + "_log.txt";
6436

37+
(async () => {
38+
var tweets;
39+
var isLikes;
6540

41+
//checks for .js files
42+
try {
43+
tweets = await loadData(jsonFileInput);
44+
console.log(`Found ${tweets.length} tweets`);
45+
isLikes = !(typeof tweets[0].tweetId === "undefined");
46+
} catch (e) {
47+
console.log("No tweet.js, twitter-circle-tweet.js or like.js, Exiting Program");
48+
process.exit(0);
49+
}
6650

67-
(async () => {
68-
69-
var tweets;
70-
var isLikes;
71-
72-
//checks for .js files
73-
try{
74-
tweets = await loadData(jsonFileInput);
75-
console.log(`Found ${tweets.length} tweets`);
76-
isLikes = !(typeof tweets[0].tweetId === 'undefined');
77-
}catch(e){
78-
console.log("No tweet.js, twitter-circle-tweet.js or like.js, Exiting Program");
79-
process.exit(0);
80-
}
81-
82-
//browser instance
83-
const browser = await puppeteer.launch({ headless: false });
84-
const page = await browser.newPage();
85-
await page.goto("https://twitter.com/");
86-
87-
//create new log file
88-
if(log || extended_error)
89-
fs.writeFileSync(log_name, 'Process Started');
90-
91-
92-
//wait for interaction
93-
await prompt("Login and press enter to continue...");
94-
95-
//check if user is logged in by clicking on the profile button
96-
try{
97-
await page.click('a[data-testid="AppTabBar_Profile_Link"');
98-
page.waitForNavigation({ timeout: timeout_amount })
99-
}catch(error){
100-
//might not always be a not logged in issue but in the case it's not log the error to see if we can fix it
101-
if(extended_error)
102-
fs.appendFileSync(log_name, '\n' + error);
103-
104-
//print to screen and exit log
105-
console.log("Not logged in, Exiting Program");
106-
console.log(error);
107-
108-
//close browser and the process
109-
await browser.close();
110-
process.exit(0);
111-
}
112-
113-
//only require to see where you are in the list
114-
var tweet_index = 0;
115-
116-
for (const tweet of tweets) {
117-
tweet_index++;
118-
if(tweet_index < skipTo)
119-
continue;
120-
if(isLikes){
121-
await page.goto(`https://twitter.com/x/status/${tweet.tweetId}`);
122-
try{
123-
//check for options menu, if it times out we log the error and continue to next instance
124-
const options = await page.waitForSelector('article[data-testid="tweet"][tabindex="-1"] div[aria-label=More]', { visible: true, timeout: timeout_amount });
125-
await delay(delay_amount);
126-
127-
try{
128-
//check if its a liked tweet if it is un-like it
129-
await page.click('div[data-testid="unlike"]');
130-
await delay(delay_amount * 2);
131-
132-
//log it
133-
console.log("unliked, " + tweet_index);
134-
if(log)
135-
fs.appendFileSync(log_name, '\n' + 'un-retweeted: #' + tweet_index + ' ID: ' + tweet.tweetId);
136-
}
137-
catch(error)
138-
{
139-
//log error and continue on
140-
console.log('Error: probably already unliked');
141-
if(log)
142-
fs.appendFileSync(log_name, '\n' + 'Errored: #' + tweet_index + ' ID: ' + tweet.tweetId);
143-
if(extended_error)
144-
fs.appendFileSync(log_name, '\n' + error);
145-
console.log(error);
146-
}
147-
}
148-
catch(error)
149-
{
150-
// log error and continue on
151-
console.log('Error: tweet unavalible');
152-
if(log)
153-
fs.appendFileSync(log_name, '\n' + 'Errored: #' + tweet_index + ' ID: ' + tweet.tweetId);
154-
if(extended_error)
155-
fs.appendFileSync(log_name, '\n' + error);
156-
console.log(error);
157-
}
158-
}
159-
else{
160-
await page.goto(`https://twitter.com/x/status/${tweet.id}`);
161-
try{
162-
//check for options menu, if it times out we log the error and continue to next instance
163-
const options = await page.waitForSelector('article[data-testid="tweet"][tabindex="-1"] div[aria-label=More]', { visible: true, timeout: timeout_amount });
164-
await delay(delay_amount);
165-
try{
166-
//check if its a retweet if it is un-retweet it
167-
await page.click('div[data-testid="unretweet"]');
168-
await delay(delay_amount);
169-
170-
//confirm un-retweet
171-
await page.click('div[data-testid="unretweetConfirm"]');
172-
await delay(delay_amount);
173-
174-
//log it
175-
console.log("Unretweeted, " + tweet_index);
176-
if(log)
177-
fs.appendFileSync(log_name, '\n' + 'un-retweeted: #' + tweet_index + ' ID: ' + tweet.id);
178-
await delay(delay_amount);
179-
180-
}catch(e) //if its not a retweet continue to tweet delete
181-
{
182-
// click on the first found selector
183-
await options?.click();
184-
await delay(delay_amount);
185-
186-
// select delete
187-
await page.click('div[data-testid="Dropdown"] > div[role="menuitem"]');
188-
await delay(delay_amount);
189-
190-
// confirm delete
191-
await page.click('div[data-testid="confirmationSheetConfirm"]');
192-
await delay(delay_amount);
193-
194-
//log it
195-
console.log("Deleted, " + tweet_index);
196-
if(log)
197-
fs.appendFileSync(log_name, '\n' + 'Deleted: #' + tweet_index + ' ID: ' + tweet.id);
198-
}
199-
}catch(error)
200-
{
201-
// log error and continue on
202-
console.log('Error: probably already deleted');
203-
if(log)
204-
fs.appendFileSync(log_name, '\n' + 'Errored: #' + tweet_index + ' ID: ' + tweet.id);
205-
if(extended_error)
206-
fs.appendFileSync(log_name, '\n' + error);
207-
console.log(error);
208-
}
209-
}
210-
}
211-
// close browser
212-
await browser.close();
51+
//browser instance
52+
const browser = await puppeteer.launch({ headless: false });
53+
const page = await browser.newPage();
54+
await page.goto("https://twitter.com/");
55+
56+
//create new log file
57+
if (log || extended_error) fs.writeFileSync(log_name, "Process Started");
58+
59+
//wait for interaction
60+
await prompt("Login and press enter to continue...");
61+
62+
//check if user is logged in by clicking on the profile button
63+
try {
64+
await page.click('a[data-testid="AppTabBar_Profile_Link"');
65+
page.waitForNavigation({ timeout: timeout_amount });
66+
} catch (error) {
67+
//might not always be a not logged in issue but in the case it's not log the error to see if we can fix it
68+
if (extended_error) fs.appendFileSync(log_name, "\n" + error);
69+
70+
//print to screen and exit log
71+
console.log("Not logged in, Exiting Program");
72+
console.log(error);
73+
74+
//close browser and the process
75+
await browser.close();
76+
process.exit(0);
77+
}
78+
79+
//only require to see where you are in the list
80+
var tweet_index = 0;
81+
82+
for (const tweet of tweets) {
83+
tweet_index++;
84+
if (tweet_index < skipTo) continue;
85+
if (isLikes) {
86+
await page.goto(`https://twitter.com/x/status/${tweet.tweetId}`);
87+
try {
88+
//check for options menu, if it times out we log the error and continue to next instance
89+
const options = await page.waitForSelector('article[data-testid="tweet"][tabindex="-1"] div[aria-label=More]', {
90+
visible: true,
91+
timeout: timeout_amount,
92+
});
93+
await delay(delay_amount);
94+
95+
try {
96+
//check if its a liked tweet if it is un-like it
97+
await page.click('div[data-testid="unlike"]');
98+
await delay(delay_amount * 2);
99+
100+
//log it
101+
console.log("unliked, " + tweet_index);
102+
if (log) fs.appendFileSync(log_name, "\n" + "un-retweeted: #" + tweet_index + " ID: " + tweet.tweetId);
103+
} catch (error) {
104+
//log error and continue on
105+
console.log("Error: probably already unliked");
106+
if (log) fs.appendFileSync(log_name, "\n" + "Errored: #" + tweet_index + " ID: " + tweet.tweetId);
107+
if (extended_error) fs.appendFileSync(log_name, "\n" + error);
108+
console.log(error);
109+
}
110+
} catch (error) {
111+
// log error and continue on
112+
console.log("Error: tweet unavalible");
113+
if (log) fs.appendFileSync(log_name, "\n" + "Errored: #" + tweet_index + " ID: " + tweet.tweetId);
114+
if (extended_error) fs.appendFileSync(log_name, "\n" + error);
115+
console.log(error);
116+
}
117+
} else {
118+
await page.goto(`https://twitter.com/x/status/${tweet.id}`);
119+
try {
120+
//check for options menu, if it times out we log the error and continue to next instance
121+
const options = await page.waitForSelector('article[data-testid="tweet"][tabindex="-1"] div[aria-label=More]', {
122+
visible: true,
123+
timeout: timeout_amount,
124+
});
125+
await delay(delay_amount);
126+
try {
127+
//check if its a retweet if it is un-retweet it
128+
await page.click('div[data-testid="unretweet"]');
129+
await delay(delay_amount);
130+
131+
//confirm un-retweet
132+
await page.click('div[data-testid="unretweetConfirm"]');
133+
await delay(delay_amount);
134+
135+
//log it
136+
console.log("Unretweeted, " + tweet_index);
137+
if (log) fs.appendFileSync(log_name, "\n" + "un-retweeted: #" + tweet_index + " ID: " + tweet.id);
138+
await delay(delay_amount);
139+
} catch (
140+
e //if its not a retweet continue to tweet delete
141+
) {
142+
// click on the first found selector
143+
await options?.click();
144+
await delay(delay_amount);
145+
146+
// select delete
147+
await page.click('div[data-testid="Dropdown"] > div[role="menuitem"]');
148+
await delay(delay_amount);
149+
150+
// confirm delete
151+
await page.click('div[data-testid="confirmationSheetConfirm"]');
152+
await delay(delay_amount);
153+
154+
//log it
155+
console.log("Deleted, " + tweet_index);
156+
if (log) fs.appendFileSync(log_name, "\n" + "Deleted: #" + tweet_index + " ID: " + tweet.id);
157+
}
158+
} catch (error) {
159+
// log error and continue on
160+
console.log("Error: probably already deleted");
161+
if (log) fs.appendFileSync(log_name, "\n" + "Errored: #" + tweet_index + " ID: " + tweet.id);
162+
if (extended_error) fs.appendFileSync(log_name, "\n" + error);
163+
console.log(error);
164+
}
165+
}
166+
}
167+
// close browser
168+
await browser.close();
213169
})();
214170

215171
// delay function to help avoid any rate limiting or slow connection issues
216172
function delay(ms: number) {
217-
//only here just to do a quick skip
218-
if(ms ==0)
219-
return;
220-
return new Promise( resolve => setTimeout(resolve, ms) );
221-
}
173+
//only here just to do a quick skip
174+
if (ms == 0) return;
175+
return new Promise((resolve) => setTimeout(resolve, ms));
176+
}

0 commit comments

Comments
 (0)