import _ from 'lodash'; import log from 'electron-log'; //tslint:disable-line:match-default-export-name import throat from 'throat'; import { NoteChecker } from './note-checker'; import request from 'request-promise'; //tslint:disable-line:match-default-export-name /* tslint:disable:no-unsafe-any */ export interface SiteSessionInterface { start(): Promise; stop(): Promise; } export interface SiteSessionInterfaceCollection extends Record { notes: NoteChecker; } export class SiteSession { private readonly sessionThroat = throat(1); readonly interfaces: SiteSessionInterfaceCollection = { notes: new NoteChecker(this) }; private state: 'active' | 'inactive' = 'inactive'; private account = ''; private password = ''; private request: request.RequestPromiseAPI = request.defaults({ jar: request.jar() }); private csrf = ''; setCredentials(account: string, password: string): void { this.account = account; this.password = password; } async start(): Promise { try { await this.stop(); await this.init(); await this.login(); this.state = 'active'; await Promise.all( _.map(this.interfaces, (i) => i.start()) ); } catch(err) { this.state = 'inactive'; log.error('sitesession.start.error', err); } } async stop(): Promise { try { await Promise.all( _.map(this.interfaces, (i) => i.stop()) ); } catch(err) { log.error('sitesession.stop.error', err); } this.csrf = ''; this.state = 'inactive'; } private async init(): Promise { log.debug('sitesession.init'); this.request = request.defaults({ jar: request.jar() }); this.csrf = ''; const res = await this.get('/'); if (res.statusCode !== 200) { throw new Error(`SiteSession.init: Invalid status code: ${res.status}`); } const input = res.body.match(//); if ((!input) || (input.length < 1)) { throw new Error('SiteSession.init: Missing csrf token'); } const csrf = input[0].match(/value="([a-zA-Z0-9]+)"/); if ((!csrf) || (csrf.length < 2)) { throw new Error('SiteSession.init: Missing csrf token value'); } this.csrf = csrf[1]; } private async login(): Promise { log.debug('sitesession.login'); if ((this.password === '') || (this.account === '')) { throw new Error('User credentials not set'); } const res = await this.post( '/action/script_login.php', { username: this.account, password: this.password, csrf_token: this.csrf }, false, { followRedirect: false, simple: false } ); if (res.statusCode !== 302) { throw new Error('Invalid status code'); } // console.log('RES RES RES', res); log.debug('sitesession.login.success'); } // tslint:disable-next-line:prefer-function-over-method private async ensureLogin(): Promise { if (this.state !== 'active') { throw new Error('Site session not active'); } } private async prepareRequest( method: string, uri: string, mustBeLoggedIn: boolean, config: Partial ): Promise { if (mustBeLoggedIn) { await this.ensureLogin(); } return _.merge( { method, uri: `https://www.f-list.net${uri}`, resolveWithFullResponse: true }, config ); } async get(uri: string, mustBeLoggedIn: boolean = false, config: Partial = {}): Promise { return this.sessionThroat( async() => { const finalConfig = await this.prepareRequest('get', uri, mustBeLoggedIn, config); return this.request(finalConfig); } ); } async post( uri: string, data: Record, mustBeLoggedIn: boolean = false, config: Partial = {} ): Promise { return this.sessionThroat( async() => { const finalConfig = await this.prepareRequest('post', uri, mustBeLoggedIn, _.merge({ form: data }, config)); return this.request(finalConfig); } ); } async onConnectionClosed(): Promise { await this.stop(); } async onConnectionEstablished(): Promise { await this.start(); } }