<commit_before>/* Libraries */
import {EventEmitter} from 'events';
import fetch from 'node-fetch';
import {differenceInCalendarDays, subDays, addDays} from 'date-fns';

/* Interfaces */
import {Company, DailyData, AccountMonthlyData, MonthlyData, Account, UsageData} from './interfaces/general';
import {GetAllAccountsResponse, LoginResponse, MonthlyDataResponse, DailyDataResponse} from './interfaces/responses';
import {API} from './interfaces/API';

/* Interfaces */
export interface SouthernCompanyConfig{
	username: string;
	password: string;
	account?: string;
	accounts?: string[];
}

export class SouthernCompanyAPI extends EventEmitter{
	private config: SouthernCompanyConfig;
	private jwt?: string;
	private company?: Company;

	constructor(config: SouthernCompanyConfig){
		super();

		/* Saving config */
		this.config = config;

		/* Connecting to Southern Company API */
		this.login().then((accounts)=>{
			/* Emitting connected event */
			this.emit('connected', accounts);
		});
	}

	private async login(){
		/* Request Verification Token */
		const loginToken = await this.getRequestVerificationToken();

		/* ScWebToken */
		const ScWebToken = await this.getScWebToken(loginToken, this.config.username, this.config.password);

		/* Saving JWT */
		this.jwt = await this.getJwt(ScWebToken);

		/* Getting accounts if none are supplied */
		if(this.config.account == undefined && this.config.accounts == undefined){
			const accounts = await this.getAccounts();
			this.config.accounts = accounts.map((account)=> account.number.toString());
		}

		/* Returning */
		return this.getAccountsArray();
	}

	/* Utility Methods */
	private getAccountsArray(){
		/* Calulating which accounts to fetch data from */
		let accounts: string[] = [];
		if(this.config.accounts){
			accounts = this.config.accounts;
		}
		else if(this.config.account){
			accounts.push(this.config.account);
		}

		/* Returning accounts array */
		return accounts;
	}
	private static formatDate(date){
		return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()} 12:00:00 AM`;
	}
	private dataSort(a, b){
		if(a[0] > b[0]) return 1;
		else if(a[0] < b[0]) return -1;
		else return 0;
	}

	/* API methods */
	private async getRequestVerificationToken(){
		/* Grabbing login page */
		const response = await fetch('https://webauth.southernco.com/account/login');

		/* Checking for unsuccessful login */
		if(response.status !== 200){
			throw new Error(`Failed to get request verification token: ${response.statusText} ${await response.text()}`);
		}

		/* Converting login page response to text to search for token */
		const loginPage = await response.text();

		/* Regex to match token on page */
		const regex = /webAuth\.aft = '(\S+)'/i;

		/* Matching page and finding token */
		let token: string;
		const matches = loginPage.match(regex);
		if(matches && matches.length > 1){
			token = matches[1];
		}
		else{
			throw new Error(`Could not find request verification token on login page`);
		}

		/* Returning request verification token */
		return token;
	}
	private async getScWebToken(requestVerificationToken: string, username: string, password: string){
		/* Checking if there is a valid config */
		if(!this.config){
			throw new Error(`Failed to get ScWebToken: Need a valid config`);
		}

		/* Grab a ScwebToken by log in */
		const options = {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json; charset=utf-8',
				RequestVerificationToken: requestVerificationToken,
			},
			body: JSON.stringify({username, password, params: {ReturnUrl: "null"}})
		};
		const response = await fetch('https://webauth.southernco.com/api/login', options);

		/* Checking for unsuccessful login */
		if(response.status !== 200){
			throw new Error(`Failed to get ScWebToken: ${response.statusText} ${await response.text()} ${JSON.stringify(options)}`);
		}

		/* Parsing response as JSON to match search response for token */
		const resData: LoginResponse = await response.json();

		/* Regex to match token in response */
		const regex = /<input type='hidden' name='ScWebToken' value='(\S+)'>/i;

		/* Matching response's form to get ScWebToken */
		let ScWebToken: string;
		const matches = resData.data.html.match(regex);
		if(matches && matches.length > 1){
			ScWebToken = matches[1];
		}
		else{
			throw new Error(`Could not find ScWebToken in response`);
		}

		/* Returning ScWebToken */
		return ScWebToken;

	}
	private async getJwt(ScWebToken: string){
		/* Trading ScWebToken for Jwt */
		const options = {
			headers:{
				Cookie: `ScWebToken=${ScWebToken}`
			}
		};
		const response = await fetch('https://customerservice2.southerncompany.com/Account/LoginValidated/JwtToken', options);

		/* Checking for unsuccessful login */
		if(response.status !== 200){
			throw new Error(`Failed to get JWT: ${response.statusText} ${await response.text()} ${JSON.stringify(options)}`);
		}

		/* Regex to parse JWT out of headers */
		const regex = /ScJwtToken=(\S*);/i;

		/* Parsing response header to get token */
		let token: string;
		let cookies = response.headers.get('set-cookie');
		if(cookies){
			const matches = cookies.match(regex);

			/* Checking for matches */
			if(matches && matches.length > 1){
				token = matches[1];
			}
			else{
				throw new Error(`Failed to get JWT: Could not find any token matches in headers`);
			}
		}
		else{<commit_msg>in-progress fixing api, scwebtoken fetched, not jwt<commit_after>/* Libraries */
import {EventEmitter} from 'events';
import fetch from 'node-fetch';
import {differenceInCalendarDays, subDays, addDays} from 'date-fns';

/* Interfaces */
import {Company, DailyData, AccountMonthlyData, MonthlyData, Account, UsageData} from './interfaces/general';
import {GetAllAccountsResponse, LoginResponse, MonthlyDataResponse, DailyDataResponse} from './interfaces/responses';
import {API} from './interfaces/API';

/* Interfaces */
export interface SouthernCompanyConfig{
	username: string;
	password: string;
	account?: string;
	accounts?: string[];
}

export class SouthernCompanyAPI extends EventEmitter{
	private config: SouthernCompanyConfig;
	private jwt?: string;
	private company?: Company;

	constructor(config: SouthernCompanyConfig){
		super();

		/* Saving config */
		this.config = config;

		/* Connecting to Southern Company API */
		this.login().then((accounts)=>{
			/* Emitting connected event */
			this.emit('connected', accounts);
		});
	}

	private async login(){
		/* Request Verification Token */
		const loginToken = await this.getRequestVerificationToken();

		/* ScWebToken */
		const ScWebToken = await this.getScWebToken(loginToken, this.config.username, this.config.password);

		/* Saving JWT */
		this.jwt = await this.getJwt(ScWebToken);

		/* Getting accounts if none are supplied */
		if(this.config.account == undefined && this.config.accounts == undefined){
			const accounts = await this.getAccounts();
			this.config.accounts = accounts.map((account)=> account.number.toString());
		}

		/* Returning */
		return this.getAccountsArray();
	}

	/* Utility Methods */
	private getAccountsArray(){
		/* Calulating which accounts to fetch data from */
		let accounts: string[] = [];
		if(this.config.accounts){
			accounts = this.config.accounts;
		}
		else if(this.config.account){
			accounts.push(this.config.account);
		}

		/* Returning accounts array */
		return accounts;
	}
	private static formatDate(date){
		return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()} 12:00:00 AM`;
	}
	private dataSort(a, b){
		if(a[0] > b[0]) return 1;
		else if(a[0] < b[0]) return -1;
		else return 0;
	}

	/* API methods */
	private async getRequestVerificationToken(){
		/* Grabbing login page */
		const response = await fetch('https://webauth.southernco.com/account/login');

		/* Checking for unsuccessful login */
		if(response.status !== 200){
			throw new Error(`Failed to get request verification token: ${response.statusText} ${await response.text()}`);
		}

		/* Converting login page response to text to search for token */
		const loginPage = await response.text();

		/* Regex to match token on page */
		const regex = /data-aft="(\S+)"/i;

		/* Matching page and finding token */
		let token: string;
		const matches = loginPage.match(regex);
		if(matches && matches.length > 1){
			token = matches[1];
		}
		else{
			throw new Error(`Could not find request verification token on login page`);
		}

		/* Returning request verification token */
		return token;
	}
	private async getScWebToken(requestVerificationToken: string, username: string, password: string){
		/* Checking if there is a valid config */
		if(!this.config){
			throw new Error(`Failed to get ScWebToken: Need a valid config`);
		}

		/* Grab a ScWebToken by log in */
		const options = {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json; charset=utf-8',
				'RequestVerificationToken': requestVerificationToken,
			},
			body: JSON.stringify({username, password, params: {ReturnUrl: "null"}})
		};
		const response = await fetch('https://webauth.southernco.com/api/login', options);

		/* Checking for unsuccessful login */
		if(response.status !== 200){
			throw new Error(`Failed to get ScWebToken: ${response.statusText} ${await response.text()} ${JSON.stringify(options)}`);
		}

		/* Parsing response as JSON to match search response for token */
		const resData: LoginResponse = await response.json();
		//console.log(response.headers);
		/* Regex to match token in response */
		const regex = /<input type='hidden' name='ScWebToken' value='(\S+)'>/i;

		/* Matching response's form to get ScWebToken */
		let ScWebToken: string;
		const matches = resData.data.html.match(regex);
		if(matches && matches.length > 1){
			ScWebToken = matches[1];
		}
		else{
			throw new Error(`Could not find ScWebToken in response`);
		}

		/* Returning ScWebToken */
		//console.log(await response.text());
		return ScWebToken;

	}
	private async getJwt(ScWebToken: string, supplauthtoken: string=''){
		/* Trading ScWebToken for Jwt */
		const options = {
			headers:{
				Cookie: `ScWebToken=${ScWebToken}`,
				'f5avrbbbbbbbbbbbbbbbb': supplauthtoken
			}
		};
		const response = await fetch('https://customerservice2.southerncompany.com/Account/LoginValidated/JwtToken', options);

		/* Checking for unsuccessful login */
		if(response.status !== 200){
			throw new Error(`Failed to get JWT: ${response.statusText} ${await response.text()} ${JSON.stringify(options)}`);
		}

		/* Regex to parse JWT out of headers */
		const regex = /ScJwtToken=(\S*);/i;
		//console.log(response);

		/* Parsing response header to get token */
		let token: string;
		let cookies = response.headers.get('set-cookie');
		if(cookies){
			const matches = cookies.match(regex);

			/* Checking for matches */
			if(matches && matches.length > 1){
				token = matches[1];
			}
			else{
				if (supplauthtoken=='') {
					const regex2 = /f5avrbbbbbbbbbbbbbbbb=(\S*);/i;
					const matches2 = cookies.match(regex2);
					if(matches2 && matches2.length > 1){
						supplauthtoken = matches2[1];
					}
					console.log(`Trying with supplauthtoken ${supplauthtoken}`);
					const jwt = await this.getJwt(ScWebToken,supplauthtoken);
					return jwt;
				}
				throw new Error(`Failed to get JWT: Could not find any token matches in headers`);
			}
		}
		else{
{'fraction_non_alphanumeric': 0.13930961606199366, 'fraction_numerical': 0.0063402606551602675, 'mean_word_length': 3.893149504523912, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 18, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6145159', 'n_tokens_mistral': 3892, 'n_tokens_neox': 3432, 'n_words': 971}