Update
This commit is contained in:
1 parent
b780bff850
commit
7b3ea37127
1752 files changed
+1341
-166
No files matched your search
+72
-1
@@ -10,7 +10,7 @@ export class BrowserClient {
|
||||
constructor(
|
||||
private config: Config,
|
||||
private logger: Logger
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async initialize(): Promise<BrowserContext> {
|
||||
const sessionDir = path.resolve(process.cwd(), this.config.browser.sessionDir);
|
||||
@@ -82,4 +82,75 @@ export class BrowserClient {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async login(): Promise<boolean> {
|
||||
if (!this.config.reddit.username || !this.config.reddit.password) {
|
||||
this.logger.error('Login failed: Missing credentials in config/env');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.info(`Attempting to log in as ${this.config.reddit.username}...`);
|
||||
const page = await this.getPage();
|
||||
|
||||
// Navigate to login page
|
||||
await page.goto('https://www.reddit.com/login', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// Wait for inputs
|
||||
// Reddit uses various IDs but name="username" and name="password" are reliable
|
||||
this.logger.debug('Waiting for login form...');
|
||||
|
||||
// Handle the new Reddit login UI (sometimes it's split into two steps or inside a shadow DOM/iframe,
|
||||
// but usually the specific login page is standard)
|
||||
await page.waitForSelector('input[name="username"]', { timeout: 10000 });
|
||||
await page.waitForSelector('input[name="password"]', { timeout: 10000 });
|
||||
|
||||
// Fill credentials
|
||||
await page.fill('input[name="username"]', this.config.reddit.username);
|
||||
await page.fill('input[name="password"]', this.config.reddit.password);
|
||||
|
||||
// Click login button
|
||||
// Look for a button with type="submit" or text containing "Log In"
|
||||
await page.click('button[type="submit"]'); // Standard form submit
|
||||
|
||||
this.logger.info('Credentials submitted. Waiting for navigation...');
|
||||
|
||||
// Wait for successful login indicator
|
||||
// We can reuse the checks from isSessionValid logic, but with a longer timeout
|
||||
try {
|
||||
await page.waitForSelector('[data-testid="user-drawer-button"]', { timeout: 20000 });
|
||||
this.logger.info('Login successful!');
|
||||
return true;
|
||||
} catch (e) {
|
||||
// Double check if we are redirected to home
|
||||
const url = page.url();
|
||||
if (url === 'https://www.reddit.com/' || url.match(/reddit\.com\/?$/)) {
|
||||
// Verify one more time
|
||||
const isValid = await this.isSessionValid();
|
||||
if (isValid) {
|
||||
this.logger.info('Login successful (verified by session check)');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for error messages
|
||||
const errorContent = await page.evaluate(() => {
|
||||
const err = document.querySelector('[role="alert"], .AnimatedForm__errorMessage');
|
||||
return err ? err.textContent : null;
|
||||
});
|
||||
|
||||
if (errorContent) {
|
||||
this.logger.error(`Login failed with error: ${errorContent}`);
|
||||
} else {
|
||||
this.logger.error('Login flow finished but session is invalid.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Error during automated login:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-19
@@ -7,7 +7,7 @@ export class RedditMessagePage {
|
||||
constructor(
|
||||
private page: Page,
|
||||
private logger: Logger
|
||||
) {}
|
||||
) { }
|
||||
|
||||
setDryRun(enabled: boolean): void {
|
||||
this.dryRun = enabled;
|
||||
@@ -60,7 +60,7 @@ export class RedditMessagePage {
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw new Error('Could not find recipient input field');
|
||||
throw new Error('Could not find recipient input field');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export class RedditMessagePage {
|
||||
['faceplate-text-input[name="message-title"]', 'input[name="subject"]', '#subject'],
|
||||
subject
|
||||
);
|
||||
|
||||
|
||||
if (!subjectFilled) {
|
||||
throw new Error('Could not find subject input field');
|
||||
}
|
||||
@@ -98,8 +98,11 @@ export class RedditMessagePage {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find and click submit button
|
||||
// Find and click submit button - prioritized by specificity to user provided snippet
|
||||
const submitButton =
|
||||
(await this.page.$('button[type="submit"]:has-text("Send")')) ||
|
||||
(await this.page.$('button.button-primary:has-text("Send")')) ||
|
||||
(await this.page.$('button:has(span:has-text("Send"))')) ||
|
||||
(await this.page.$('button[type="submit"]')) ||
|
||||
(await this.page.$('button:has-text("Send")')) ||
|
||||
(await this.page.$('input[type="submit"]'));
|
||||
@@ -114,7 +117,7 @@ export class RedditMessagePage {
|
||||
await submitButton.waitForElementState('enabled', { timeout: 5000 });
|
||||
} catch (e) {
|
||||
this.logger.warn('Submit button still disabled after filling fields. Attempting to trigger validation...');
|
||||
|
||||
|
||||
// Try to trigger validation by blurring/focusing the last field
|
||||
await this.page.keyboard.press('Tab');
|
||||
await this.page.waitForTimeout(500);
|
||||
@@ -124,15 +127,15 @@ export class RedditMessagePage {
|
||||
// One last check
|
||||
const isEnabled = await submitButton.isEnabled();
|
||||
if (!isEnabled) {
|
||||
this.logger.warn('Button still disabled. Attempting force click...');
|
||||
await submitButton.click({ force: true });
|
||||
// If force click fails/doesn't work, we proceed to success check anyway
|
||||
// (sometimes it works even if reported disabled in shadow dom scenarios)
|
||||
this.logger.warn('Button still disabled. Attempting force click...');
|
||||
await submitButton.click({ force: true });
|
||||
// If force click fails/doesn't work, we proceed to success check anyway
|
||||
// (sometimes it works even if reported disabled in shadow dom scenarios)
|
||||
} else {
|
||||
await submitButton.click();
|
||||
await submitButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If we didn't force click above (normal path)
|
||||
// Actually, the try-catch block above handles all clicking scenarios.
|
||||
// We don't need a second click here.
|
||||
@@ -144,8 +147,8 @@ export class RedditMessagePage {
|
||||
this.page.waitForSelector('.success, [class*="success"], [class*="Success"]', { timeout: 15000 }),
|
||||
// Check for the specific success message in the new UI
|
||||
this.page.waitForFunction(() => {
|
||||
const confirmation = document.querySelector('.message-sent-confirmation-text');
|
||||
return confirmation && !confirmation.hasAttribute('hidden') && confirmation.textContent?.trim().length > 0;
|
||||
const confirmation = document.querySelector('.message-sent-confirmation-text');
|
||||
return confirmation && !confirmation.hasAttribute('hidden') && confirmation.textContent?.trim().length > 0;
|
||||
}, null, { timeout: 15000 })
|
||||
]);
|
||||
|
||||
@@ -162,8 +165,8 @@ export class RedditMessagePage {
|
||||
// Also check if the success message is visible now (in case race failed slightly early)
|
||||
const successMsg = await this.page.$('.message-sent-confirmation-text:not([hidden])');
|
||||
if (successMsg && (await successMsg.isVisible())) {
|
||||
this.logger.info(`Message sent successfully to u/${recipient}`);
|
||||
return true;
|
||||
this.logger.info(`Message sent successfully to u/${recipient}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for error messages
|
||||
@@ -172,7 +175,7 @@ export class RedditMessagePage {
|
||||
if (errorElement && await errorElement.isVisible()) {
|
||||
const errorText = await errorElement.textContent();
|
||||
if (errorText && errorText.trim().length > 0) {
|
||||
throw new Error(`Reddit error: ${errorText}`);
|
||||
throw new Error(`Reddit error: ${errorText}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +184,7 @@ export class RedditMessagePage {
|
||||
// Or just log warning.
|
||||
// For now, let's trust the user report that it works and be less aggressive with throwing.
|
||||
this.logger.warn('Message send status unclear (no success indicator found, but no error either). Assuming success if no error alert appeared.');
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to send message to u/${recipient}:`, error);
|
||||
@@ -198,7 +201,7 @@ export class RedditMessagePage {
|
||||
// Clear potentially existing content if it's an input/textarea
|
||||
// Note: .fill() usually clears, but for custom elements or div-inputs we might need to select all + delete
|
||||
// For now, we assume click focuses it.
|
||||
|
||||
|
||||
if (useDelay) {
|
||||
await this.typeWithDelay(value);
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user