@padrocha/uam-scraping
Version:
Scraping of teachers
387 lines (327 loc) • 13.4 kB
text/typescript
import readline from 'readline';
import puppeteer from 'puppeteer';
import { config } from './config';
declare global {
interface String {
capitalize(): string;
}
}
String.prototype.capitalize = function (this: string) {
return this.toLowerCase().replace(/(?:^|\s|["'([{])+\S/g, match => match.toUpperCase());
};
export const week = new Array<day>('monday', 'tuesday', 'wednesday', 'thursday', 'friday');
export function log(param: string, pad = config.CONSOLESIZE): void {
param = ` ${param} `;
const start = (pad - param.length) / 2 + param.length;
console.log(param.padStart(Math.round(start), '-').padEnd(pad, '-'));
}
export function confirm(message: string) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise<boolean>(response => {
rl.question(`\x1b[34m?\x1b[0m ${message} (Y/N)? `, (answer) => {
process.stdout.moveCursor(0, -1);
process.stdout.clearScreenDown();
response(new Array('y', 'Y', 's', 'S', '').includes(answer.trim()));
rl.close();
});
});
}
export function askUser() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise<string>(resolve => {
rl.question('User: ', user => {
resolve(user);
rl.close();
})
});
}
export function password() {
const rl: any = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const stdoutMuted = true;
const query = "Password : ";
rl._writeToOutput = function _writeToOutput(stringToWrite: string) {
const animation = (rl.line.length % 2 == 1) ? "=-" : "-=";
if (stdoutMuted)
process.stdout.write(`\x1B[2K\x1B[200D${query}[${animation}]`);
else
process.stdout.write(stringToWrite);
};
return new Promise<string>(resolve => {
rl.question(query, function (password: string) {
process.stdout.write('\n');
resolve(password);
rl.close();
});
});
}
export function progressBar(length = 0, count = 0): void {
const percent = count / length;
const percent_handle = isNaN(percent) ? 0 : percent;
const percent_fixed = Math.ceil(percent_handle * 100);
const size = config.CONSOLESIZE - 4 - percent_fixed.toString().length;
const bar = '.'.repeat(size * percent_handle).padEnd(size, " ");
if (percent_handle === 1) {
process.stdout.write(`\r[${bar}] ${percent_fixed}%`);
process.stdout.write(`\r`);
} else {
process.stdout.write(`\r[${bar}] ${percent_fixed}%`);
}
}
export function selectUEAS(ueas_options: uea[]) {
const ueas = ueas_options.map(option => {
return { ...option, choosed: false } as uea & { choosed?: boolean };
});
let index = 0;
let toogle = false;
const menu = () => {
if (!!toogle) {
process.stdout.moveCursor(0, -(ueas.length - 1));
process.stdout.cursorTo(0);
process.stdout.clearScreenDown();
} else {
toogle = true;
}
ueas.forEach(({ name, choosed }, i) => {
let option = (i === index)
? `\x1b[35m> ${name}`
: name;
if (!!choosed) {
option = `\x1b[4m${option}`;
}
process.stdout.write(`${option}\x1b[0m${i !== ueas.length - 1 ? '\n' : ''}`);
});
}
return new Promise<uea[]>((resolve, rejected) => {
console.log('\x1b[34m?\x1b[0m %s', 'Choose what subjects are taken');
console.log('\x1b[31m!\x1b[0m %s', 'Press [Esc] to resume process');
readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('keypress', (_, { name, ctrl }) => {
if (name === 'down' && index < ueas.length - 1) {
++index;
} else if (name === 'up' && index > 0) {
--index;
}
if (name === 'return') {
ueas[index].choosed = !ueas[index].choosed;
}
if (name === 'escape' || (name === 'c' && ctrl)) {
process.stdin.setRawMode(false);
process.stdin.pause();
if (name === 'c' && ctrl)
process.exit(0);
process.stdout.moveCursor(0, -(ueas.length + 1));
process.stdout.cursorTo(0);
process.stdout.clearScreenDown();
resolve(ueas.filter(option => {
const choosed = option.choosed;
delete option.choosed;
return choosed;
}));
process.stdin.removeAllListeners('keypress');
} else {
menu();
}
});
menu();
} else {
rejected('Idk man, you r ugly');
}
});
}
export async function tryDOM<T>(callback: () => Promise<T extends void ? never : T | null | undefined>, DOM: puppeteer.Page) {
let response: T | null | undefined;
let response_tries = 0;
do {
response = await callback();
if (response_tries === config.TRIES) {
throw new Error("Element from DOM couldn´t load property");
} else if (!response) {
await DOM.waitForTimeout(config.TIMEOUT);
++response_tries;
}
if (response instanceof Array && response.length < 1) {
response = undefined;
}
} while (!response);
return response;
}
export function timeParse(time: string): time | null {
const [starts, ends] = time.slice(0, 13).trim().split(' - ');
return time.trim().length > 1
? { starts, ends }
: null;
}
export function timeStringify(time: number): string {
const timeDate = new Date(time);
const hours = timeDate.getHours().toString();
const minutes = timeDate.getMinutes().toString();
return (hours.length < 2 ? 0 + hours : hours) + ':' + (minutes.length < 2 ? 0 + minutes : minutes);
}
export function selectTeachers(teachers_options: Set<string>) {
let teachers = Array.from(teachers_options).map(name => {
return { name, choosed: false } as { name: string; choosed?: boolean; };
}).sort(({ name: a }, { name: b }) => (a < b) ? -1 : ((a > b) ? 1 : 0));
let index = 0;
let toogle = false;
const menu = () => {
if (!!toogle) {
process.stdout.moveCursor(0, -(teachers.length - 1));
process.stdout.cursorTo(0);
process.stdout.clearScreenDown();
} else {
toogle = true;
}
teachers.forEach(({ name, choosed }, i) => {
let option = (i === index)
? `\x1b[35m> ${name.capitalize()}`
: name.capitalize();
if (!!choosed) {
option = `\x1b[4m${option}`;
}
process.stdout.write(`${option}\x1b[0m${i !== teachers.length - 1 ? '\n' : ''}`);
});
}
return new Promise<string[]>((resolve, rejected) => {
console.log('\x1b[34m?\x1b[0m %s', 'Choose what subjects are taken');
console.log('\x1b[31m!\x1b[0m %s', 'Press [Esc] to resume process');
readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('keypress', (_, { name, ctrl }) => {
if (name === 'down' && index < teachers.length - 1) {
++index;
} else if (name === 'up' && index > 0) {
--index;
}
if (name === 'return') {
teachers[index].choosed = !teachers[index].choosed;
}
if (name === 'escape' || (name === 'c' && ctrl)) {
process.stdin.setRawMode(false);
process.stdin.pause();
if (name === 'c' && ctrl)
process.exit(0);
process.stdout.moveCursor(0, -(teachers.length + 1));
process.stdout.cursorTo(0);
process.stdout.clearScreenDown();
resolve(teachers.filter(({ choosed }) => choosed).map(({ name }) => name));
process.stdin.removeAllListeners('keypress');
} else {
menu();
}
});
menu();
} else {
rejected('Idk man, you r ugly');
}
});
}
export function selectSchedule(schedules: [any, Map<string, ueaData>][]) {
let index = 0;
let toogle = false;
let clear_area = 4;
let byDay: weekByDay;
let schedule_subjects: schedule['subjects_info'];
const display = () => {
if (!!toogle) {
process.stdout.moveCursor(0, -clear_area);
process.stdout.cursorTo(0);
process.stdout.clearScreenDown();
clear_area = 4;
} else {
toogle = true;
}
const [, schedule] = schedules[index];
const min_date = Math.min.apply(Math, week.map(day => Math.min.apply(Math, Array.from(schedule)
.filter(([, { [day]: _ }]) => _)
.map(([, { [day]: _ }]) => Date.parse('01/01/1970 ' + _?.starts)))));
const max_date = Math.max.apply(Math, week.map(day => Math.max.apply(Math, Array.from(schedule)
.filter(([, { [day]: _ }]) => _)
.map(([, { [day]: _ }]) => Date.parse('01/01/1970 ' + _?.ends)))));
clear_area += schedule.size;
schedule_subjects = Array.from(schedule).map(([subject, schedule_info]) => {
console.log(`${schedule_info.key} | ${schedule_info.teacher.name.capitalize()} | ${subject}`);
return {
key: schedule_info.key,
teacher: schedule_info.teacher.name.capitalize(),
subject
};
});
byDay = {} as weekByDay;
for (let i = min_date; i < max_date; i += 1_800_000) {
const curr_time = timeStringify(i);
const week_time: perDay = {
monday: null,
tuesday: null,
wednesday: null,
thursday: null,
friday: null,
};
for (const day of week) {
for (const [, { key, [day]: _ }] of schedule) {
if (_) {
if (curr_time >= _.starts && curr_time < _.ends) {
week_time[day] = !week_time[day]
? key
: key + '/' + week_time[day];
}
}
}
}
byDay[curr_time] = week_time
++clear_area;
}
console.table(byDay);
const left = index !== 0 ? '<' : '';
const right = index !== schedules.length - 1 ? '>' : '';
process.stdout.write(`\x1b[34m?\x1b[0m Index ${left}[${index + 1}/${schedules.length}]${right}`);
}
return new Promise<schedule>((resolve, reject) => {
console.log('\x1b[34m?\x1b[0m %s', 'Browse the best schedules posible');
console.log('\x1b[31m!\x1b[0m %s', 'Press [Enter] to choose schedule');
readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('keypress', (_, { name, ctrl }) => {
if (name === 'right' && index < schedules.length - 1) {
++index;
} else if (name === 'left' && index > 0) {
--index;
}
if (name === 'return' || (name === 'c' && ctrl)) {
process.stdin.setRawMode(false);
process.stdin.pause();
if (name === 'c' && ctrl)
process.exit(0);
process.stdout.moveCursor(0, -(clear_area + 2));
process.stdout.cursorTo(0);
process.stdout.clearScreenDown();
resolve({
subjects_info: schedule_subjects,
hours: byDay
});
process.stdin.removeAllListeners('keypress');
} else {
display();
}
});
display();
} else {
reject('I still dunno man, fkg creep');
}
});
}