From 86bd22633bc0e0466d5fbc7668c21641c979731c Mon Sep 17 00:00:00 2001 From: Brycey92 Date: Fri, 28 Apr 2023 19:36:01 -0400 Subject: [PATCH] First attempt at multi-user notification sending --- index.ts | 44 ++++++++++++++-- src/interfaces/generic.ts | 7 +++ src/scrapers/authenticate.ts | 97 ++++++++++++++++++++++++++++++++++++ src/util/config.ts | 20 +++++++- src/util/notifier.ts | 11 ++-- 5 files changed, 171 insertions(+), 8 deletions(-) diff --git a/index.ts b/index.ts index daa4357..029f71b 100644 --- a/index.ts +++ b/index.ts @@ -1,10 +1,12 @@ #! /usr/bin/env node +import fs from 'fs'; +import path from 'path'; import packageJson from './package.json'; import { getConfig } from './src/util/config'; import { deleteFiles, ensureDirectory } from './src/util/files'; import { databaseConnection, initializeTables, insertRow, isModuleScraped, queryModuleIDs } from './src/util/database'; -import { authenticateAPI, authenticateSite, getSiteID, isSiteAdmin } from './src/scrapers/authenticate'; +import { authenticateAPI, authenticateAPINotifier, authenticateSite, authenticateSiteNotifier, getSiteID, isSiteAdmin } from './src/scrapers/authenticate'; import { getForums } from './src/scrapers/forums'; import { getNews } from './src/scrapers/news'; import { getAllTickets } from './src/scrapers/tickets'; @@ -18,6 +20,7 @@ import { getGalleries } from './src/scrapers/galleries'; import { MessageType, statusMessage } from './src/util/console'; import { getHTMLModules } from './src/scrapers/html'; import { startNotifier } from './src/util/notifier'; +import { SiteAuth } from './src/interfaces/generic'; async function main(): Promise { // Needed for exit handler @@ -36,6 +39,41 @@ async function main(): Promise { // Login to site and get PHPSESSID and csrf_token const siteAuth = config.siteAuth ? config.siteAuth : await authenticateSite(config.domain, config.email, config.password); + var siteAuths:SiteAuth[] = []; + if (config.notifier) { + if (config.notifier.accounts) { + for (let i = 0; i < config.notifier.accounts.length; i++) { + if (config.notifier.accounts[i].email === config.email) { + config.notifier.accounts[i].sessionID = sessionID; + if (siteAuth !== null && typeof siteAuth !== 'undefined') { + config.notifier.accounts[i].siteAuth = siteAuth; + siteAuths.push(siteAuth); + } + fs.writeFileSync(path.join(process.cwd(), './config.json'), JSON.stringify(config, null, 4)); + } else { + if (!config.notifier.accounts[i].sessionID) { + await authenticateAPINotifier(config.domain, config.notifier.accounts[i].email, config.notifier.accounts[i].password); + } + let curSiteAuth:SiteAuth|undefined|null = config.notifier.accounts[i].siteAuth + if (curSiteAuth !== null && typeof curSiteAuth !== 'undefined') { + siteAuths.push(curSiteAuth); + } else { + curSiteAuth = await authenticateSiteNotifier(config.domain, config.notifier.accounts[i].email, config.notifier.accounts[i].password); + if (curSiteAuth !== null && typeof curSiteAuth !== 'undefined') { + siteAuths.push(curSiteAuth); + } + } + } + } + } else { + let email = config.email; + let password = config.password; + let siteAuthOrUndefined = siteAuth != null ? siteAuth : undefined; + let account = { email, password, sessionID, siteAuthOrUndefined }; + config.notifier.accounts = [ account ]; + } + } + // Check if we are admin let adminMode: boolean = true; if (siteAuth !== null && config.adminMode !== false) { @@ -57,8 +95,8 @@ async function main(): Promise { await initializeTables(database); // Notifier Mode - if (siteAuth != null && config.notifier && config.notifier.enabled === true && config.apiKey) { - await startNotifier(database, config.domain, config.apiKey, siteAuth, config.notifier.messageSubject, config.notifier.messageBody); + if (siteAuth != null && config.notifier && config.notifier.enabled === true && config.apiKey && siteAuths.length != 0) { + await startNotifier(database, config.domain, config.apiKey, siteAuths, config.notifier.messageSubject, config.notifier.messageBody); deleteFiles(['./target/recovery/notifier_progress.json']); } diff --git a/src/interfaces/generic.ts b/src/interfaces/generic.ts index b609d15..a5f070f 100644 --- a/src/interfaces/generic.ts +++ b/src/interfaces/generic.ts @@ -49,4 +49,11 @@ export interface Pagination { export interface SiteAuth { phpSessID: string; csrfToken: string; +} + +export interface Account { + email: string; + password: string; + sessionID?: string; + siteAuth?: SiteAuth; } \ No newline at end of file diff --git a/src/scrapers/authenticate.ts b/src/scrapers/authenticate.ts index eb25fda..d14c46b 100644 --- a/src/scrapers/authenticate.ts +++ b/src/scrapers/authenticate.ts @@ -29,6 +29,41 @@ export async function authenticateAPI(domain: string, email: string, password: s return data.result.session_id; } +export async function authenticateAPINotifier(domain: string, email: string, password: string): Promise { + const params = { + email, + password, + } + + const data = await enjinRequest(params, 'User.login', domain); + + if (data.error) { + throw new Error(`Error authenticating: ${data.error.code} ${data.error.message}`); + } + + const config = JSON.parse(fs.readFileSync(path.join(process.cwd(), './config.json')).toString()); + let foundAccount = false; + let sessionID = data.result.session_id; + if (!config.notifier.accounts || config.notifier.accounts.length == 0) { + let account = { email, password, sessionID }; + config.notifier.accounts = [ account ]; + } else{ + for (let i = 0; !foundAccount && i < config.notifier.accounts.length; i++) { + if (email === config.notifier.accounts[i].email) { + config.notifier.accounts[i].sessionID = sessionID; + foundAccount = true; + } + } + if (!foundAccount) { + let account = { email, password, sessionID }; + config.notifier.accounts.push(account); + } + } + fs.writeFileSync(path.join(process.cwd(), './config.json'), JSON.stringify(config, null, 4)); + + statusMessage(MessageType.Completion, `Authenticated with session ID ${data.result.session_id}`) + return data.result.session_id; +} function booleanPromptUser(question: string): Promise { const rl = readline.createInterface({ @@ -97,6 +132,68 @@ export async function authenticateSite(domain: string, email: string, password: } } +export async function authenticateSiteNotifier(domain: string, email: string, password: string): Promise { + try { + const loginResponse = await getRequest(domain, '/login', {}, '/authenticateSite/loginResponse'); + const setCookie = loginResponse.headers['set-cookie']; + const cf_bm_token = setCookie!.find((cookie: string) => cookie.includes('__cf_bm'))!.split(';')[0]; + const lastviewed = setCookie!.find((cookie: string) => cookie.includes('lastviewed'))!.split(';')[0]; + + const $ = cheerio.load(loginResponse.data); + const formName = $('div.input input[type="password"]').attr('name'); + + const formData = new URLSearchParams({ + m: '0', + do: '', + username: email, + [formName!]: password + }); + + const postLoginResponse = await postRequest(domain, '/login', formData, { + Cookie: `${lastviewed}; enjin_browsertype=web; ${cf_bm_token}`, + }, '/authenticateSite'); + + const phpSessID = postLoginResponse.headers['set-cookie']!.find((cookie: string) => cookie.includes('PHPSESSID'))!.split(';')[0]; + + const homeResponse = await getRequest(domain, '/', { + Cookie: `${lastviewed}; ${phpSessID}; enjin_browsertype=web; ${cf_bm_token}; login_temp=1`, + }, '/authenticateSite/homeResponse'); + + const csrfToken = homeResponse.headers['set-cookie']!.find((cookie: string) => cookie.includes('csrf_token'))!.split(';')[0]; + + const config = JSON.parse(fs.readFileSync(path.join(process.cwd(), './config.json')).toString()); + let siteAuth = { phpSessID, csrfToken }; + let foundAccount = false; + if (!config.notifier.accounts || config.notifier.accounts.length == 0) { + let account = { email, password, siteAuth }; + config.notifier.accounts = [ account ]; + } + else { + for (let i = 0; !foundAccount && i < config.notifier.accounts.length; i++) { + if (email === config.notifier.accounts[i].email) { + config.notifier.accounts[i].siteAuth = siteAuth; + foundAccount = true; + } + } + if (!foundAccount) { + let account = { email, password, siteAuth }; + config.notifier.accounts.push(account); + } + } + fs.writeFileSync(path.join(process.cwd(), './config.json'), JSON.stringify(config, null, 4)); + + statusMessage(MessageType.Completion, `Authenticated with PHPSESSID and CSRF token`); + return { phpSessID, csrfToken }; + } catch (error) { + statusMessage(MessageType.Error, `Error authenticating: ${getErrorMessage(error)}`); + statusMessage(MessageType.Info, 'This seriously limit the info we can scrape from the site.'); + statusMessage(MessageType.Info, 'If you have 2FA enabled, you should disable it and try again later.'); + await booleanPromptUser('Do you still want to continue?') + + return null; + } +} + export async function getSiteID(domain: string): Promise { const data = await enjinRequest({}, 'Site.getStats', domain); diff --git a/src/util/config.ts b/src/util/config.ts index e63bae6..43fadc7 100644 --- a/src/util/config.ts +++ b/src/util/config.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import readline from 'readline'; -import { SiteAuth } from '../interfaces/generic'; +import { SiteAuth, Account } from '../interfaces/generic'; import { MessageType, statusMessage } from './console'; export interface Config { @@ -56,6 +56,7 @@ export interface Config { enabled: boolean; messageSubject: string; messageBody: string; + accounts: Account[]; }; } @@ -107,7 +108,22 @@ const defaultConfig: Config = { retrySeconds: 5, retryTimes: 5, debug: true, - disableSSL: false + disableSSL: false, + notifier: { + enabled: false, + messageSubject: "", + messageBody: "", + accounts: [ + { + email: "someemail@email.com", + password: "somepassword" + }, + { + email: "someemail2@email.com", + password: "somepassword2" + } + ] + } }; let cachedConfig: Config | null = null; diff --git a/src/util/notifier.ts b/src/util/notifier.ts index da8baf6..4ef5a3d 100644 --- a/src/util/notifier.ts +++ b/src/util/notifier.ts @@ -7,6 +7,7 @@ import { fileExists, parseJsonFile } from "./files"; import { addExitListeners, removeExitListeners } from "./exit"; import { enjinRequest } from "./request"; import { Messages } from "../interfaces/messages"; +import { writeJsonFile } from "./files"; export async function startNotifier(database: Database, domain: string, apiKey: string, siteAuths: SiteAuth[], messageSubject: string, messageBody: string) { statusMessage(MessageType.Info, 'Running in notifier mode...'); @@ -74,13 +75,17 @@ export async function startNotifier(database: Database, domain: string, apiKey: if (delay > 0) { statusMessage(MessageType.Process, `Waiting ${delay / 1000} seconds before sending next message...`); await new Promise(resolve => setTimeout(() => resolve(), delay)); + } else if (i == 0) { + statusMessage(MessageType.Process, `Waiting 21 seconds before sending next message...`); + await new Promise(resolve => setTimeout(() => resolve(), 21000)); } // Select the site auth with this auth id const siteAuth = siteAuths[authId % siteAuths.length]; - userCount = [i] - const pmRequest = await enjinRequest ({ + userCount = [i]; + writeJsonFile('./target/recovery/notifier_progress.json', [userCount]); + /*const pmRequest = await enjinRequest ({ recipients: [users[i].user_id], message_subject: messageSubject, message_body: messageBody.replace('{USERNAME}', users[i].username), @@ -127,7 +132,7 @@ export async function startNotifier(database: Database, domain: string, apiKey: // Set the rate-limit to 21 seconds for this auth as to avoid spamming the API rateLimits[authId] = new Date(Date.now() + 21000); continue; - } + }*/ // Set the rate-limit to 21 seconds for auth as to avoid spamming the API rateLimits[authId] = new Date(Date.now() + 21000);