Reewrite #1
22
package.json
22
package.json
@ -5,21 +5,17 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.6.0",
|
||||||
"get-video-duration": "^4.1.0",
|
"got": "^14.0.0",
|
||||||
"got": "^13.0.0",
|
"jsdom": "^23.0.1",
|
||||||
"jsdom": "^22.1.0",
|
"mongodb": "^6.3.0",
|
||||||
"mongodb": "^6.2.0",
|
"neste": "^3.0.0"
|
||||||
"neste": "^3.0.0",
|
|
||||||
"telegraf": "^4.15.0",
|
|
||||||
"turndown": "^7.1.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cookie": "^0.5.3",
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/jsdom": "^21.1.4",
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/node": "^20.8.10",
|
"@types/node": "^20.10.3",
|
||||||
"@types/turndown": "^5.0.3",
|
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,328 +0,0 @@
|
|||||||
import { randomBytes } from "crypto";
|
|
||||||
import { createReadStream, createWriteStream } from "fs";
|
|
||||||
import { mkdir, rm, writeFile } from "fs/promises";
|
|
||||||
import getVideoDuration from "get-video-duration";
|
|
||||||
import { Got, HTTPError } from "got";
|
|
||||||
import { JSDOM } from "jsdom";
|
|
||||||
import { tmpdir } from "os";
|
|
||||||
import path from "path";
|
|
||||||
import { pipeline } from "stream/promises";
|
|
||||||
import TurndownService from "turndown";
|
|
||||||
import { database } from "./lib/db_connect.js";
|
|
||||||
import { ActivityType, AtividadeRoot, BoletimColaborar } from "./lib/sync.js";
|
|
||||||
|
|
||||||
export type Activity = {
|
|
||||||
/** ID da atividade */
|
|
||||||
id: string;
|
|
||||||
description: string;
|
|
||||||
} & ({
|
|
||||||
type: "Tele Aula";
|
|
||||||
file: { name: string; url: string; }[];
|
|
||||||
videoIDs: {
|
|
||||||
id: string;
|
|
||||||
exemplarId: string;
|
|
||||||
location: string;
|
|
||||||
videoDuration: number;
|
|
||||||
}[];
|
|
||||||
}|{
|
|
||||||
type: "Conteúdo Web";
|
|
||||||
files: {
|
|
||||||
title: string;
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
}[];
|
|
||||||
}|{
|
|
||||||
type: "Web Aula";
|
|
||||||
}|{
|
|
||||||
type: "Referências Digitais";
|
|
||||||
}|{
|
|
||||||
type: "Prova Presencial";
|
|
||||||
}|{
|
|
||||||
type: "Avaliação Virtual";
|
|
||||||
asks: (string[])|{
|
|
||||||
ask: string;
|
|
||||||
select: {
|
|
||||||
title: string;
|
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
}[];
|
|
||||||
}|{
|
|
||||||
type: "Fórum";
|
|
||||||
}|{
|
|
||||||
type: "Portfólio";
|
|
||||||
files: { name: string; url: string; }[]
|
|
||||||
}|{
|
|
||||||
type: "Desafio Nota Máxima";
|
|
||||||
}|{
|
|
||||||
type: "Acelere Sua Carreira";
|
|
||||||
}|{
|
|
||||||
type: "Avaliação de Proficiência";
|
|
||||||
}|{
|
|
||||||
type: "Prova Prática Presencial";
|
|
||||||
}|{
|
|
||||||
type: "Atividade Diagnóstica";
|
|
||||||
}|{
|
|
||||||
type: "Atividade de Aprendizagem";
|
|
||||||
}|{
|
|
||||||
type: "Leitura";
|
|
||||||
files: { title: string; url: string }[];
|
|
||||||
externalFiles: { title: string; url: string }[];
|
|
||||||
});
|
|
||||||
|
|
||||||
export const activity = database.collection<Activity>("activity");
|
|
||||||
|
|
||||||
export async function insertActivitysToCollection(got: Got, semestre: string, id: string) {
|
|
||||||
// Mount URL Request's
|
|
||||||
const dashboard = new URL(path.posix.join("/aluno/timeline/index", semestre), "https://www.colaboraread.com.br"); dashboard.searchParams.set("ofertaDisciplinaId", id);
|
|
||||||
const index = await got(dashboard);
|
|
||||||
|
|
||||||
// Parse HTML Page
|
|
||||||
const doc = new JSDOM(index.body, { url: index.url });
|
|
||||||
const { window: { document } } = doc;
|
|
||||||
|
|
||||||
await mkdir("./tmp_data/", { recursive: true }).catch(() => {});
|
|
||||||
await writeFile(("./tmp_data/").concat(id, ".html"), index.body);
|
|
||||||
|
|
||||||
let filesIDs: string[] = [];
|
|
||||||
|
|
||||||
for (const e of document.querySelectorAll("#js-activities-container > li")) {
|
|
||||||
if (!(e.querySelector("div:nth-child(2) > div > h4 > span > span"))) continue;
|
|
||||||
const typeElement: HTMLSpanElement = e.querySelector("div:nth-child(2) > div > h4 > span > span");
|
|
||||||
Array.from(typeElement.querySelectorAll("*")).forEach(s => s.remove()); // Remove icon
|
|
||||||
const type = (typeElement.innerText || typeElement.innerHTML).trim().toLowerCase();
|
|
||||||
const titleElement: HTMLElement = e.querySelector("div.timeline-panel > div > h4 > small"), title = (titleElement.innerText || titleElement.innerHTML).trim();
|
|
||||||
|
|
||||||
if (type === "teleaula") {
|
|
||||||
const id = e.querySelector("div.timeline-panel > div > p:nth-child(4) > small > em").textContent;
|
|
||||||
filesIDs.push(id);
|
|
||||||
if (await activity.findOne({ id })) continue;
|
|
||||||
|
|
||||||
const teleIndex = await got(new URL(e.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), index.url));
|
|
||||||
const doc = new JSDOM(teleIndex.body, { url: teleIndex.url });
|
|
||||||
const { window: { document } } = doc;
|
|
||||||
|
|
||||||
await activity.insertOne({
|
|
||||||
type: "Tele Aula",
|
|
||||||
id,
|
|
||||||
description: title,
|
|
||||||
file: Array.from(e.querySelectorAll("div.timeline-panel > div > div.timeline-body > ul > li")).filter(a => !!(a.querySelector("small > a"))).map(a => a.querySelector("small > a")).map(s => ({ name: s.getAttribute("title"), url: (new URL(s.getAttribute("href"), index.url)).toString() })),
|
|
||||||
videoIDs: (await Promise.all(Array.from(document.querySelectorAll("#conteudo > div > div > div.col-md-8.panel.mb-30 > div:nth-child(2) > div > div > div")).map(s => s.querySelector("div > div > a").getAttribute("onclick")).map(async (click) => {
|
|
||||||
click = click.split("\n").map(s => s.trim()).join("").trim();
|
|
||||||
click = click.slice(click.indexOf("(")+1, click.indexOf(")"));
|
|
||||||
const params = click.split(",").map(s => s.trim()).map(s => s.slice(s.startsWith("\"") ? 1 : 0, s.endsWith("\"") ? -1 : undefined).trim());
|
|
||||||
const [ codigoMidia, , , atividadeOfertaId, exemplarId ] = params;
|
|
||||||
const fileUrl = (new URL(path.posix.join("/video", codigoMidia), "https://mdstrm.com")).toString().concat(".mp4");
|
|
||||||
|
|
||||||
const videoDurationURL = new URL("https://colaboraread.com.br/aluno/playerVideo/getVideoDuration");
|
|
||||||
videoDurationURL.searchParams.set("codigoMidia", codigoMidia);
|
|
||||||
const videoDuration: number = await got(videoDurationURL).then(s => JSON.parse(s.body).videoDuration, async (err: HTTPError) => {
|
|
||||||
if (err.response?.statusCode === 500) {
|
|
||||||
const file = path.join(tmpdir(), randomBytes(8).toString("hex").concat(".mp4"));
|
|
||||||
return pipeline(got(fileUrl, { isStream: true }), createWriteStream(file)).then(() => getVideoDuration.getVideoDurationInSeconds(createReadStream(file)), (err: HTTPError) => {
|
|
||||||
if (err.response && err.response.statusCode === 404) return undefined;
|
|
||||||
throw err;
|
|
||||||
}).finally(() => rm(file, { force: true }));
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!videoDuration) return null;
|
|
||||||
return {
|
|
||||||
id: atividadeOfertaId,
|
|
||||||
exemplarId,
|
|
||||||
location: fileUrl,
|
|
||||||
videoDuration
|
|
||||||
};
|
|
||||||
}))).filter(Boolean),
|
|
||||||
});
|
|
||||||
} else if (type === "portfólio") {
|
|
||||||
const id = e.querySelector("div.timeline-panel > div > p:nth-child(5) > small > em").textContent;
|
|
||||||
filesIDs.push(id);
|
|
||||||
if (await activity.findOne({ id })) continue;
|
|
||||||
await activity.insertOne({
|
|
||||||
type: "Portfólio",
|
|
||||||
id,
|
|
||||||
description: title,
|
|
||||||
files: Array.from(e.querySelectorAll("div.timeline-panel > div > div.timeline-body > ul > li")).map(e => e.querySelector("small > a")).filter(Boolean).map(s => ({ name: s.getAttribute("title"), url: (new URL(s.getAttribute("href"), index.url)).toString() }))
|
|
||||||
});
|
|
||||||
} else if (type === "leitura") {
|
|
||||||
const id = e.querySelector("div.timeline-panel > div > p:nth-child(4) > small > em").textContent;
|
|
||||||
filesIDs.push(id);
|
|
||||||
if (await activity.findOne({ id })) continue;
|
|
||||||
await activity.insertOne({
|
|
||||||
type: "Leitura",
|
|
||||||
id, description: title,
|
|
||||||
files: Array.from(e.querySelectorAll("div.timeline-panel > div > div > ul:nth-child(3) > li")).map(s => s.querySelector("small > a")).filter(Boolean).map(s => ({title: s.getAttribute("title"), url: (new URL(s.getAttribute("href"), index.url)).toString() })),
|
|
||||||
externalFiles: Array.from(e.querySelectorAll("div.timeline-panel > div > div > ul:nth-child(7) > li")||[]).map(s => s.querySelector("small > a")).filter(Boolean).map(s => ({ title: s.getAttribute("title")||s.innerHTML.trim(), url: (new URL(s.getAttribute("href"), index.url)).toString() })),
|
|
||||||
});
|
|
||||||
} else if (type === "conteúdo web") {
|
|
||||||
const id = e.querySelector("div.timeline-panel > div > p:nth-child(4) > small > em").textContent;
|
|
||||||
filesIDs.push(id);
|
|
||||||
if (await activity.findOne({ id })) continue;
|
|
||||||
const webExtIndex = await got(new URL(e.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), index.url));
|
|
||||||
const doc = new JSDOM(webExtIndex.body, { url: webExtIndex.url });
|
|
||||||
const { window: { document } } = doc;
|
|
||||||
if (document.querySelector("#detalhe > summary > p > b")) {
|
|
||||||
await activity.insertOne({
|
|
||||||
type: "Conteúdo Web",
|
|
||||||
id, description: title,
|
|
||||||
files: Array.from(document.querySelectorAll("#detalhe > a")).map(s => {
|
|
||||||
const page = s.getAttribute("href");
|
|
||||||
const atividadeOfertaId = ((s.getAttribute("onclick")||"").trim().split("\n").map(s => s.trim()).join("")).slice((s.getAttribute("onclick")||"").indexOf("(")+1, (s.getAttribute("onclick")||"").indexOf(")")).split(",").map(s => ((s = s.trim()).startsWith("\"")||s.startsWith("'")) ? s.slice(1, -1) : s)[0];
|
|
||||||
return {
|
|
||||||
title: s.querySelector("p").textContent.trim(),
|
|
||||||
id: atividadeOfertaId,
|
|
||||||
url: (new URL(page, webExtIndex.url)).toString(),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} else throw new Error(doc.serialize());
|
|
||||||
} else if (type === "avaliação virtual") {
|
|
||||||
const id = e.querySelector("div.timeline-panel > div > p:nth-child(4) > small > em").textContent;
|
|
||||||
filesIDs.push(id);
|
|
||||||
if (await activity.findOne({ id })) continue;
|
|
||||||
const avaIndex = await got(new URL(e.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), index.url));
|
|
||||||
const doc = new JSDOM(avaIndex.body, { url: avaIndex.url });
|
|
||||||
const { window: { document } } = doc;
|
|
||||||
|
|
||||||
// Atividade não liberada!
|
|
||||||
if (!(document.querySelector("#conteudo > div.container-fluid > div:nth-child(2) > div.col-md-9 > div"))) continue;
|
|
||||||
|
|
||||||
await activity.insertOne({
|
|
||||||
type: "Avaliação Virtual",
|
|
||||||
id, description: title,
|
|
||||||
asks: Array.from(document.querySelectorAll("#conteudo > div.container-fluid > div:nth-child(2) > div.col-md-9 > div")).map(s => s.querySelector("div")).map(s => {
|
|
||||||
Array.from(s.querySelectorAll("*")).filter(s => s.getAttribute("href")||s.getAttribute("src")).forEach(s => {
|
|
||||||
if (s.getAttribute("href")) s.setAttribute("href", (new URL(s.getAttribute("href"), avaIndex.url)).toString());
|
|
||||||
else s.setAttribute("src", (new URL(s.getAttribute("src"), avaIndex.url)).toString());
|
|
||||||
});
|
|
||||||
return (new TurndownService()).turndown(s.innerHTML.trim());
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else if (type === "live") {
|
|
||||||
// const id = e.querySelector("div.timeline-panel > div > p:nth-child(4) > small > em").textContent;
|
|
||||||
} else if (type === "engajamento ava") {
|
|
||||||
// const id = e.querySelector("div.timeline-panel > div > p:nth-child(4) > small > em").textContent;
|
|
||||||
} else {
|
|
||||||
const id = String(Array.from(e.querySelectorAll("* > p > small")).filter(s => s.textContent.toLowerCase().includes("cod")||s.textContent.toLowerCase().includes("atividade:")).map(s => (s.querySelector("em").textContent)).at(-1)||"").trim();
|
|
||||||
if (!id) continue;
|
|
||||||
console.dir({ type, title, id }, {depth: null});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filesIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function Boletim(got: Got, matricula: string, disciplina: string) {
|
|
||||||
const botURL = new URL(path.posix.join("/aluno/boletim/getListaDetalheBoletimDto"), "https://colaboraread.com.br/");
|
|
||||||
botURL.searchParams.set("matriculaId", matricula);
|
|
||||||
botURL.searchParams.set("ofertaDisciplinaId", disciplina); // botURL.searchParams.set("tipoDisciplinaId", "13");
|
|
||||||
const boletimRequest = await got(botURL);
|
|
||||||
|
|
||||||
// Pretty boletim
|
|
||||||
const boletim = (JSON.parse(boletimRequest.body) as BoletimColaborar[]).map(fn => {
|
|
||||||
return {
|
|
||||||
ava: fn.detalheBoletimDto.detalheEngajamentoDtoList.reduce<Record<string, number>>((acc, { descricaoAtividade: name, pontuacao }) => { acc[name] = pontuacao; return acc; }, {}),
|
|
||||||
frenquencia: Object.keys(fn.detalheBoletimDto.detalheFrequenciaDtoList).reduce<{type: ActivityType; descricaoAtividade: string; situacao?: string; cargaHoraria: { cargaHorariaDisciplina: number; cargaHoraria: number; }; datas: { lancamento: string; atividade: string; inicio: string; fim: string; }; tipo: { id: number; atividade: string; };}[]>((acc, id) => {
|
|
||||||
const atividade = fn.detalheBoletimDto.detalheFrequenciaDtoList[id];
|
|
||||||
let ActType: ActivityType = "Não definido";
|
|
||||||
if (id === "2") ActType = "Tele Aula";
|
|
||||||
else if (id === "4") ActType = "Web Aula";
|
|
||||||
else if (id === "17") ActType = "Conteúdo Web";
|
|
||||||
else if (id === "14") ActType = "Referências Digitais";
|
|
||||||
else if (id === "13") ActType = "Prova Presencial";
|
|
||||||
else if (id === "6") ActType = "Avaliação Virtual";
|
|
||||||
else if (id === "16") ActType = "Fórum";
|
|
||||||
else if (id === "1") ActType = "Portfólio";
|
|
||||||
else if (id === "28") ActType = "Desafio Nota Máxima";
|
|
||||||
else if (id === "31") ActType = "Acelere Sua Carreira";
|
|
||||||
else if (id === "22") ActType = "Avaliação de Proficiência";
|
|
||||||
else if (id === "26") ActType = "Prova Prática Presencial";
|
|
||||||
else if (id === "23") ActType = "Atividade Diagnóstica";
|
|
||||||
else if (id === "24") ActType = "Atividade de Aprendizagem";
|
|
||||||
return acc.concat(atividade.map((s): (typeof acc)[number] => ({
|
|
||||||
type: ActType,
|
|
||||||
descricaoAtividade: s.descricaoAtividade,
|
|
||||||
situacao: s.situacao,
|
|
||||||
cargaHoraria: {
|
|
||||||
cargaHorariaDisciplina: s.cargaHorariaDisciplina,
|
|
||||||
cargaHoraria: s.cargaHoraria,
|
|
||||||
},
|
|
||||||
datas: {
|
|
||||||
lancamento: s.dataLancamento,
|
|
||||||
atividade: s.dataAtividade,
|
|
||||||
inicio: s.dataInicioAtividade,
|
|
||||||
fim: s.dataFimAtividade,
|
|
||||||
},
|
|
||||||
tipo: {
|
|
||||||
id: s.tipoOfertaId,
|
|
||||||
atividade: s.tipoAtividade.name,
|
|
||||||
}
|
|
||||||
})));
|
|
||||||
}, []),
|
|
||||||
atividades: Object.keys(fn.detalheBoletimDto.detalheDisciplinarDtoList).reduce<AtividadeRoot[]>((acc, id) => {
|
|
||||||
const atividade = fn.detalheBoletimDto.detalheDisciplinarDtoList[id];
|
|
||||||
let ActType: Exclude<ActivityType, "Portfólio"|"Prova Presencial"> = "Não definido";
|
|
||||||
if (id === "1") return acc.concat(atividade.map(({ descricaoAtividade, atividadeId, atividadeOfertaId, pontuacaoMinina, pontuacao }): (typeof acc)[number] => ({ type: "Portfólio", atividadeId, atividadeOfertaId, descricaoAtividade, pontuacaoMinina, pontuacao })));
|
|
||||||
else if (id === "2") ActType = "Tele Aula";
|
|
||||||
else if (id === "4") ActType = "Web Aula";
|
|
||||||
else if (id === "6") ActType = "Avaliação Virtual";
|
|
||||||
else if (id === "13") return acc.concat(atividade.map(({ descricaoAtividade, atividadeId, atividadeOfertaId, pontuacaoMinina, pontuacao, acertos, sequenciaAvaliacao }): (typeof acc)[number] => ({ type: "Prova Presencial", atividadeId, atividadeOfertaId, descricaoAtividade, acertos, sequenciaAvaliacao, pontuacaoMinina, pontuacao })));
|
|
||||||
else if (id === "14") ActType = "Referências Digitais";
|
|
||||||
else if (id === "16") ActType = "Fórum";
|
|
||||||
else if (id === "17") ActType = "Conteúdo Web";
|
|
||||||
else if (id === "22") ActType = "Avaliação de Proficiência";
|
|
||||||
else if (id === "23") ActType = "Atividade Diagnóstica";
|
|
||||||
else if (id === "24") ActType = "Atividade de Aprendizagem";
|
|
||||||
else if (id === "26") ActType = "Prova Prática Presencial";
|
|
||||||
else if (id === "28") ActType = "Desafio Nota Máxima";
|
|
||||||
else if (id === "31") ActType = "Acelere Sua Carreira";
|
|
||||||
|
|
||||||
// Generic
|
|
||||||
return acc.concat(atividade.map((s): (typeof acc)[number] => {
|
|
||||||
const { descricaoAtividade, atividadeAvaliacaoId, atividadeOfertaId, atividadeId, sequenciaAvaliacao, pontuacaoMinina, pontuacao } = s;
|
|
||||||
return {
|
|
||||||
type: ActType,
|
|
||||||
descricaoAtividade,
|
|
||||||
atividadeAvaliacaoId, atividadeOfertaId, atividadeId,
|
|
||||||
pontos: {
|
|
||||||
sequenciaAvaliacao,
|
|
||||||
pontuacaoMinina,
|
|
||||||
pontuacao,
|
|
||||||
},
|
|
||||||
raw: s,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
// atividadesRaw: fn.detalheBoletimDto.detalheDisciplinarDtoList,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Media final calc
|
|
||||||
const Pontos: number[] = []
|
|
||||||
|
|
||||||
// Provas
|
|
||||||
const provas = boletim[0].atividades.reduce((acc, i) => i.type === "Prova Presencial" ? (acc + i.pontuacao) : acc, 0);
|
|
||||||
Pontos.push(provas);
|
|
||||||
|
|
||||||
let avaPoints = Object.keys(boletim[0].ava).reduce((acc, k) => acc + boletim[0].ava[k], 0);
|
|
||||||
let avaliacao = boletim[0].atividades.reduce((acc, info) => {
|
|
||||||
if (info.type !== "Avaliação Virtual") return acc;
|
|
||||||
info.raw.sequenciaAvaliacao === 1 && acc.g < info.raw.pontuacao && (acc.g = info.raw.pontuacao);
|
|
||||||
info.raw.sequenciaAvaliacao === 2 && acc.a < info.raw.pontuacao && (acc.a = info.raw.pontuacao);
|
|
||||||
acc.k = acc.g + acc.a;
|
|
||||||
return acc;
|
|
||||||
}, { k: 0, g: 0, a: 0 }).k;
|
|
||||||
|
|
||||||
if (provas >= 3000 && avaliacao >= 1500) Pontos.push(avaliacao, avaPoints);
|
|
||||||
|
|
||||||
const media = parseFloat(((Pontos.reduce((acc, p) => acc+p, 0) / 100) / 10).toPrecision(2));
|
|
||||||
return {
|
|
||||||
/** Media da aula, nota não final */
|
|
||||||
media: {
|
|
||||||
nota: media,
|
|
||||||
aprovado: media >= 6,
|
|
||||||
},
|
|
||||||
boletim
|
|
||||||
};
|
|
||||||
}
|
|
137
src/boletim.ts
Normal file
137
src/boletim.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { Got } from "./lib/colabora_req.js";
|
||||||
|
|
||||||
|
export enum AulasType {
|
||||||
|
"Portfólio" = "1",
|
||||||
|
"Tele Aula" = "2",
|
||||||
|
"Leitura" = "3",
|
||||||
|
"Web Aula" = "4",
|
||||||
|
"Avaliação Virtual" = "6",
|
||||||
|
"Prova Presencial" = "13",
|
||||||
|
"Referências Digitais" = "14",
|
||||||
|
"Fórum" = "16",
|
||||||
|
"Conteúdo Web" = "17",
|
||||||
|
"Avaliação de Proficiência" = "22",
|
||||||
|
"Atividade Diagnóstica" = "23",
|
||||||
|
"Atividade de Aprendizagem" = "24",
|
||||||
|
"Prova Prática Presencial" = "26",
|
||||||
|
"Engajamento AVA" = "27",
|
||||||
|
"Desafio Nota Máxima" = "28",
|
||||||
|
"Acelere Sua Carreira" = "31",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AulasTypeIDs: readonly (AulasType[number])[] = Object.freeze([
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"4",
|
||||||
|
"6",
|
||||||
|
"13",
|
||||||
|
"14",
|
||||||
|
"16",
|
||||||
|
"17",
|
||||||
|
"22",
|
||||||
|
"23",
|
||||||
|
"24",
|
||||||
|
"26",
|
||||||
|
"28",
|
||||||
|
"31"
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type Boletim = {
|
||||||
|
detalheBoletimDto: {
|
||||||
|
detalheEngajamentoDtoList: {
|
||||||
|
descricaoAtividade: string;
|
||||||
|
pontuacao: number;
|
||||||
|
tipoAtividade: {
|
||||||
|
enumType: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
}[];
|
||||||
|
detalheFrequenciaDtoList: Record<string, {
|
||||||
|
cargaHoraria: number;
|
||||||
|
cargaHorariaDisciplina: number;
|
||||||
|
dataAtividade: string;
|
||||||
|
dataFimAtividade: string;
|
||||||
|
dataInicioAtividade: string;
|
||||||
|
dataLancamento: string;
|
||||||
|
descricaoAtividade: string;
|
||||||
|
situacao: string;
|
||||||
|
tipoOfertaId: number;
|
||||||
|
tipoAtividade: {
|
||||||
|
enumType: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}[]>;
|
||||||
|
detalheDisciplinarDtoList: Record<AulasType, {
|
||||||
|
acertos: number|null;
|
||||||
|
apresentacao: boolean;
|
||||||
|
atividadeAvaliacaoId: number;
|
||||||
|
atividadeId: number;
|
||||||
|
atividadeLancamentoId?: number;
|
||||||
|
atividadeOfertaId: number;
|
||||||
|
caminhoAtividade1: any;
|
||||||
|
caminhoAtividade2?: any;
|
||||||
|
dataAtividade1: string;
|
||||||
|
dataAtividade2: string;
|
||||||
|
dataFimGabaritoAluno: string;
|
||||||
|
dataInicioGabaritoAluno: string;
|
||||||
|
descricaoAtividade: string;
|
||||||
|
gabarito: any;
|
||||||
|
interdisciplinar: boolean;
|
||||||
|
likes: number;
|
||||||
|
objetivas?: string;
|
||||||
|
pontuacao: number;
|
||||||
|
pontuacaoApresentacao: number;
|
||||||
|
pontuacaoMinina: number;
|
||||||
|
pontuacaoTotalApresentacao: number;
|
||||||
|
sequenciaAvaliacao?: number;
|
||||||
|
tipoAtividadeId: number;
|
||||||
|
tipoOfertaId: number;
|
||||||
|
tipoAtividade: {
|
||||||
|
enumType: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}[]>;
|
||||||
|
}
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export default Get;
|
||||||
|
export async function Get(got: Got, matricula: string, disciplina: string) {
|
||||||
|
const boletimUrl = new URL("https://www.colaboraread.com.br/aluno/boletim/getListaDetalheBoletimDto");
|
||||||
|
boletimUrl.searchParams.set("matriculaId", matricula);
|
||||||
|
boletimUrl.searchParams.set("ofertaDisciplinaId", disciplina);
|
||||||
|
const body: Boletim = (await got<Boletim>(boletimUrl, { responseType: "json" })).body;
|
||||||
|
|
||||||
|
const media = body.map(body => {
|
||||||
|
const Acts = Object.keys(body.detalheBoletimDto.detalheDisciplinarDtoList).map<{type: AulasType, pontos: number}>((type: AulasType) => {
|
||||||
|
if (type === AulasType["Avaliação Virtual"]) {
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
pontos: body.detalheBoletimDto.detalheDisciplinarDtoList[AulasType["Avaliação Virtual"]].reduce((acc, info) => {
|
||||||
|
info.sequenciaAvaliacao === 1 && acc.g < info.pontuacao && (acc.g = info.pontuacao);
|
||||||
|
info.sequenciaAvaliacao === 2 && acc.a < info.pontuacao && (acc.a = info.pontuacao);
|
||||||
|
acc.k = acc.g + acc.a;
|
||||||
|
return acc;
|
||||||
|
}, { k: 0, g: 0, a: 0 }).k,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
pontos: body.detalheBoletimDto.detalheDisciplinarDtoList[type].reduce<number>((pontos, info) => pontos + info.pontuacao, 0),
|
||||||
|
}
|
||||||
|
}).reduce<Record<AulasType, number>>((acc, k) => Object.assign(acc, { [k.type]: k.pontos }), Object.create({}));
|
||||||
|
|
||||||
|
const ava = body.detalheBoletimDto.detalheEngajamentoDtoList.reduce((acc, k) => acc + k.pontuacao, 0);
|
||||||
|
const Notas: (number|undefined)[] = [ Acts[AulasType["Prova Presencial"]], Acts[AulasType["Portfólio"]], ava ];
|
||||||
|
if (Acts[AulasType["Prova Presencial"]] >= 3000) Notas.push(ava, Acts[AulasType["Avaliação Virtual"]]);
|
||||||
|
|
||||||
|
const media = parseFloat(Math.min(10, Math.max(0, ((Notas.reduce((acc, p) => acc + (p||0), 0) / 100) / 10))).toPrecision(2));
|
||||||
|
return {
|
||||||
|
media,
|
||||||
|
aprovado: media >= 6,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
media,
|
||||||
|
boletim: body,
|
||||||
|
};
|
||||||
|
}
|
173
src/dashboard.ts
Normal file
173
src/dashboard.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import path from "path";
|
||||||
|
import util from "node:util"
|
||||||
|
import { ColaborareadRequest } from "./lib/colabora_req.js";
|
||||||
|
import { AulasType, AulasTypeIDs } from "./boletim.js";
|
||||||
|
import { writeFile } from "fs/promises";
|
||||||
|
|
||||||
|
export default Get;
|
||||||
|
export async function Get(colareq: ColaborareadRequest) {
|
||||||
|
const { window: { document } } = (await colareq.gotDom("https://www.colaboraread.com.br/index/index")).jsdoc;
|
||||||
|
document.querySelector("#navbar-content-aluno-cursos > div > div:nth-child(1) > div")
|
||||||
|
return Array.from(document.querySelectorAll("#navbar-content-aluno-cursos > div > div")).reduce<{ name: string; semestres: {semestreID: string; title: string;}[] }[]>((acc, e) => {
|
||||||
|
if (!(e.querySelector("div > div:nth-child(2) > h3"))) return acc;
|
||||||
|
else if (!(e.querySelector("div > div:nth-child(1) > div:nth-child(3) > form > div:nth-child(1) > select > option"))) return acc;
|
||||||
|
|
||||||
|
// Title
|
||||||
|
const title = (e.querySelector("div > div:nth-child(2) > h3") as HTMLHeadingElement).getAttribute("title");
|
||||||
|
|
||||||
|
// retornar os cursos
|
||||||
|
return acc.concat({
|
||||||
|
name: title,
|
||||||
|
semestres: Array.from<HTMLOptionElement>(e.querySelectorAll("div > div:nth-child(1) > div:nth-child(3) > form > div:nth-child(1) > select > option")).map(({ value, innerText, innerHTML }) => ({ semestreID: value, title: (innerText||innerHTML).trim() })).reverse()
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Aulas {
|
||||||
|
export async function Get(colareq: ColaborareadRequest, semestre: string) {
|
||||||
|
const dashboard = new URL("https://www.colaboraread.com.br/aluno/dashboard/index"); dashboard.searchParams.set("matriculaId", semestre);
|
||||||
|
await colareq.gotDom(dashboard);
|
||||||
|
const { url: pageUrl, jsdoc } = (await colareq.gotDom(dashboard));
|
||||||
|
const { window: { document } } = jsdoc;
|
||||||
|
console.log(pageUrl.toString());
|
||||||
|
await writeFile(String().concat("./tmp_data/aulas_", semestre, ".html"), jsdoc.serialize())
|
||||||
|
|
||||||
|
return Array.from(document.querySelectorAll("#navbar-content-aluno-pda > ul > li")).reduce<{ title: string; id: string }[]>((acc, e) => {
|
||||||
|
if (!(e.querySelector("table > tbody > tr > td:nth-child(1) > a"))) return acc;
|
||||||
|
const a = e.querySelector("table > tbody > tr > td:nth-child(1) > a");
|
||||||
|
const title = a.getAttribute("title").trim(), pageHref = (new URL(a.getAttribute("href"), pageUrl)).searchParams.get("ofertaDisciplinaId")
|
||||||
|
if (pageHref) return acc.concat({ title, id: pageHref });
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export namespace Atividades {
|
||||||
|
export interface Atividade {
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Teleaula extends Atividade {
|
||||||
|
type: "Teleaula";
|
||||||
|
files: {
|
||||||
|
title: string;
|
||||||
|
url: URL;
|
||||||
|
}[];
|
||||||
|
videos: {
|
||||||
|
codigoMidia: string;
|
||||||
|
mediaType: string;
|
||||||
|
atividadeOfertaId: number;
|
||||||
|
exemplarId?: string;
|
||||||
|
mdsUrl?: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Portfolio extends Atividade {
|
||||||
|
type: "Portfólio";
|
||||||
|
Dates: Record<string, Date[]>;
|
||||||
|
files: {
|
||||||
|
title: string;
|
||||||
|
url: URL;
|
||||||
|
}[];
|
||||||
|
historico?: {
|
||||||
|
type: string;
|
||||||
|
text: string;
|
||||||
|
description?: string;
|
||||||
|
date?: Date;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function Get(colareq: ColaborareadRequest, semestre: string, id: string): Promise<(Atividade|Teleaula|Portfolio)[]> {
|
||||||
|
const indexURL = new URL(path.posix.join("/aluno/timeline/index", semestre).concat("?ofertaDisciplinaId=", id), "https://www.colaboraread.com.br");
|
||||||
|
const { jsdoc, url: pageUrl } = (await colareq.gotDom(indexURL));
|
||||||
|
const { window: { document } } = jsdoc;
|
||||||
|
|
||||||
|
return Promise.all(Array.from(document.querySelectorAll("#js-activities-container > li")).map(async act => {
|
||||||
|
console.log(Array.from(act.classList.values()));
|
||||||
|
if (AulasTypeIDs.some(s => act.classList.contains(String().concat("tipo-", s)))) {
|
||||||
|
const type = AulasTypeIDs.find(s => act.classList.contains(String().concat("tipo-", s)));
|
||||||
|
if (type === AulasType["Portfólio"]) {
|
||||||
|
const portUrl = new URL(act.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), pageUrl);
|
||||||
|
const { jsdoc } = await colareq.gotDom(portUrl);
|
||||||
|
const { window: { document } } = jsdoc;
|
||||||
|
|
||||||
|
const port: Portfolio = Object.create({});
|
||||||
|
port.type = "Portfólio";
|
||||||
|
port.id = portUrl.searchParams.get("atividadeDisciplinaId");
|
||||||
|
port.title = act.querySelector("div.timeline-panel > div > h4 > small").textContent.trim();
|
||||||
|
|
||||||
|
port.files = Array.from(document.querySelectorAll("#conteudo > div > div:nth-child(2) > div.col-md-4 > div > div.panel-body > ul:nth-child(4) > li")).map(s => s.querySelector("a")).filter(Boolean).map(s => ({
|
||||||
|
title: s.getAttribute("name")||s.getAttribute("title"),
|
||||||
|
url: new URL(s.getAttribute("href"), portUrl)
|
||||||
|
}));
|
||||||
|
|
||||||
|
const daySplit = (s: string) => { const [day, month, yeat] = s.split("/"); return String().concat(month, "/", day, "/", yeat); }
|
||||||
|
port.Dates = {};
|
||||||
|
Array.from(document.querySelectorAll("#conteudo > div > div:nth-child(2) > div.col-md-4 > div:nth-child(1) > div.panel-body > ul:nth-child(1) > li")).forEach((doc): any => {
|
||||||
|
const text = doc.textContent.trim();
|
||||||
|
let index: number;
|
||||||
|
if ((index = text.indexOf(":")) >= 0) {
|
||||||
|
const key = text.slice(0, index).trim();
|
||||||
|
const [ day1, hor1,, day2, hor2 ] = text.slice(index+1).trim().split(/\s+/);
|
||||||
|
if (!day2) return port.Dates[key] = [ new Date(String().concat(daySplit(day1), " ", hor1, " GMT-3")) ];
|
||||||
|
port.Dates[key] = [ new Date(String().concat(daySplit(day1), " ", hor1, " GMT-3")), new Date(String().concat(daySplit(day2), " ", hor2, " GMT-3")) ];
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (document.querySelector("#conteudo > div > div:nth-child(2) > div.col-md-8 > div")) {
|
||||||
|
port.historico = [];
|
||||||
|
Array.from(document.querySelectorAll("#conteudo > div > div:nth-child(2) > div.col-md-8 > div")).forEach(a => {
|
||||||
|
if (a.querySelector("div:nth-child(1) > h5")) {
|
||||||
|
const dd = new Date(daySplit(a.querySelector("div:nth-child(1) > h5 > abbr").getAttribute("title")));
|
||||||
|
const status = a.querySelector("div:nth-child(1) > h5 > span");
|
||||||
|
|
||||||
|
port.historico.push({
|
||||||
|
type: status.textContent.trim(),
|
||||||
|
date: dd,
|
||||||
|
description: status.getAttribute("title"),
|
||||||
|
text: a.querySelector("div:nth-child(2) > p").textContent.trim(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
port.historico.push({
|
||||||
|
type: String(a.querySelector("div:nth-child(1) > p:nth-child(1) > span")?.textContent).trim(),
|
||||||
|
date: new Date(daySplit(a.querySelector("div:nth-child(1) > p:nth-child(2) > abbr").getAttribute("title").trim())),
|
||||||
|
text: String(a.querySelector("div:nth-child(2) > p")?.textContent).trim(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return port;
|
||||||
|
} else if (type === AulasType["Leitura"]) {
|
||||||
|
await writeFile(String().concat("./tmp_data/", type, ".html"), act.innerHTML);
|
||||||
|
} else if (type === AulasType["Conteúdo Web"]) {
|
||||||
|
} else if (type === AulasType["Tele Aula"]) {
|
||||||
|
const teleUrl = new URL(act.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), pageUrl);
|
||||||
|
const { jsdoc } = await colareq.gotDom(teleUrl);
|
||||||
|
const { window: { document } } = jsdoc;
|
||||||
|
const tele: Teleaula = Object.create({});
|
||||||
|
tele.type = "Teleaula";
|
||||||
|
tele.id = teleUrl.searchParams.get("atividadeDisciplinaId");
|
||||||
|
tele.title = act.querySelector("div.timeline-panel > div > h4 > small").textContent.trim();
|
||||||
|
tele.files = Array.from(act.querySelectorAll("div.timeline-panel > div > div.timeline-body > ul > li")).map<HTMLAnchorElement>(s => s.querySelector("small > a")).filter(Boolean).map(s => ({ title: s.getAttribute("title")||s.getAttribute("name"), url: new URL(s.href, pageUrl) }));
|
||||||
|
|
||||||
|
const ids = Array.from(document.querySelectorAll("#conteudo > div > div > div.col-md-8.panel.mb-30 > div:nth-child(2) > div > div > div")).map(div => {
|
||||||
|
/** codigoMidia, mediaType, matriculaId, atividadeOfertaId, exemplarId */
|
||||||
|
let media: { codigoMidia: string, mediaType: string, atividadeOfertaId: number, exemplarId?: string };
|
||||||
|
// @ts-ignore
|
||||||
|
function playVideosMensagem(codigoMidia, mediaType, _matriculaId, atividadeOfertaId, exemplarId) { media = {codigoMidia, mediaType, atividadeOfertaId, exemplarId}; }
|
||||||
|
eval(div.querySelector("div > div > a").getAttribute("onclick"));
|
||||||
|
return media;
|
||||||
|
});
|
||||||
|
|
||||||
|
tele.videos = ids.map(s => ({ ...s, mdsUrl: util.format("https://mdstrm.com/video/%s.json", s.codigoMidia), }));
|
||||||
|
return tele;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})).then(s => s.filter(Boolean));
|
||||||
|
}
|
||||||
|
};
|
269
src/index.ts
269
src/index.ts
@ -1,261 +1,72 @@
|
|||||||
import neste, { parseBody } from "neste";
|
import neste from "neste";
|
||||||
import crypto from "node:crypto";
|
import boletim from "./boletim.js";
|
||||||
import { tmpdir } from "node:os";
|
import Dashboard, { Atividades, Aulas } from "./dashboard.js";
|
||||||
import path from "node:path";
|
import request, { ColaborareadRequest } from "./lib/colabora_req.js";
|
||||||
import { Boletim, activity } from "./atividades.js";
|
import "./lib/db_connect.js";
|
||||||
import { remoteRequest } from "./lib/colabora_req.js";
|
import { encrypt } from "./lib/password.js";
|
||||||
import { decrypt, encrypt } from "./lib/password.js";
|
|
||||||
import "./telegram.js";
|
|
||||||
import { User, syncUser, user } from "./user.js";
|
|
||||||
|
|
||||||
const app = neste();
|
const app = neste();
|
||||||
|
// Listen HTTP Server
|
||||||
|
app.listen(process.env.PORT||"3000", () => console.log(app.address()));
|
||||||
|
|
||||||
declare module "neste" {
|
declare module "neste" {
|
||||||
export interface Request {
|
export interface Request {
|
||||||
User: User;
|
Got?: ColaborareadRequest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.post("/login", parseBody(), async (req, res) => {
|
app.use(async (req, res, next): Promise<any> => {
|
||||||
const { username, password } = req.body;
|
if (typeof req.headers.authorization === "string" && req.headers.authorization.length > 8) {
|
||||||
const dbUser = await user.findOne({ "colaborar.user": username });
|
if (req.headers.authorization.toLowerCase().startsWith("basic ")) {
|
||||||
if (!dbUser) return res.status(400).json({ errors: [ { type: "user", message: "Usuario não foi cadastrado no nosso sistema" } ] });
|
|
||||||
else if (password !== await decrypt(dbUser.colaborar.password)) return res.status(400).json({ errors: [ { type: "passwd", message: "Senha invalida!" } ] });
|
|
||||||
let sessionid: string;
|
|
||||||
while (!sessionid) {
|
|
||||||
sessionid = crypto.randomBytes(16).toString("hex");
|
|
||||||
if (await user.findOne({ sessions: sessionid })) sessionid = undefined;
|
|
||||||
}
|
|
||||||
await user.findOneAndUpdate({ userID: dbUser.userID }, { $push: { sessions: sessionid } });
|
|
||||||
req.Cookies.set("sessionid", sessionid);
|
|
||||||
return res.status(200).json({ sessionid });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/register", parseBody(), async (req, res) => {
|
|
||||||
if (!req.body) return res.status(400).json({ errors: [ { type: "body", message: "require body!" } ] });
|
|
||||||
const { username, password } = req.body;
|
|
||||||
if (!(typeof username === "string" && typeof password === "string")) return res.status(400).json({ errors: [ { type: "body", message: "Invalid body!" }, { type: "username", message: "require string username" }, { type: "password", message: "require string password" } ] });
|
|
||||||
else if (await user.findOne({ "colaborar.user": username })) return res.status(400).json({ errors: [ { type: "user", message: "Usuario jã foi cadastrado no nosso sistema" } ] });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try login
|
let password: string, username: string;
|
||||||
await remoteRequest(username, await encrypt(password));
|
const decode = Buffer.from(req.headers.authorization.slice(6).trim(), "base64").toString("utf8");
|
||||||
|
if (decode.indexOf(":") > 0) {
|
||||||
// UUID unique
|
const i = decode.indexOf(":");
|
||||||
let userID: string;
|
username = decode.slice(0, i);
|
||||||
while (!userID) {
|
password = decode.slice(i+1);
|
||||||
userID = crypto.randomUUID();
|
|
||||||
if (await user.findOne({ userID })) userID = undefined;
|
|
||||||
}
|
}
|
||||||
|
req.Got = await request(username, await encrypt(password));
|
||||||
let sessionid: string;
|
return next();
|
||||||
while (!sessionid) {
|
|
||||||
sessionid = crypto.randomBytes(16).toString("hex");
|
|
||||||
if (await user.findOne({ sessions: sessionid })) sessionid = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert in db
|
|
||||||
await user.insertOne({
|
|
||||||
userID,
|
|
||||||
colaborar: {
|
|
||||||
user: username,
|
|
||||||
password: await encrypt(password)
|
|
||||||
},
|
|
||||||
telegramChats: [],
|
|
||||||
sessions: [ sessionid ],
|
|
||||||
aulas: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
req.Cookies.set("sessionid", sessionid);
|
|
||||||
return res.status(201).json({ userID, sessionid });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return res.status(401).json({
|
return res.status(400).json({
|
||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
type: "auth",
|
type: "Auth",
|
||||||
message: "Não foi possivel autenticar no site do colaboraread, cheque seu login"
|
message: "Usuario ou senha Errados"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "generic",
|
type: "colaborareadAuth",
|
||||||
message: err.message||String(err)
|
message: err?.message||String(err)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
} else if (req.headers.authorization.toLowerCase().startsWith("bearer ")) {
|
||||||
|
} else if (req.headers.authorization.toLowerCase().startsWith("token ")) {
|
||||||
app.use(async (req, res, next) => {
|
req.headers.authorization.slice(6).trim();
|
||||||
let { authorization } = req.headers;
|
|
||||||
if (req.Cookies.has("sessionid")) {
|
|
||||||
req.User = await user.findOne({ sessions: req.Cookies.get("sessionid")[0] });
|
|
||||||
if (req.User) return next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof authorization === "string") {
|
|
||||||
if (authorization.startsWith("token ")||authorization.startsWith("Token ")) {
|
|
||||||
authorization = authorization.substring(5).trim();
|
|
||||||
req.User = await user.findOne({ sessions: authorization });
|
|
||||||
} else if (authorization.startsWith("basic ")||authorization.startsWith("Basic ")) {
|
|
||||||
const decodeAuth = Buffer.from(authorization.substring(5).trim(), "base64").toString("utf8");
|
|
||||||
const findex = decodeAuth.indexOf(":"), username = decodeAuth.substring(0, findex-1), password = decodeAuth.substring(findex);
|
|
||||||
req.User = await user.findOne({ "colaborar.user": username });
|
|
||||||
if (req.User && (await decrypt(req.User.colaborar.password) !== password)) req.User = undefined;
|
|
||||||
}
|
|
||||||
if (req.User) return next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
type: "auth",
|
type: "Auth",
|
||||||
message: "Require Cookie sessionid or Authorization header!"
|
message: "Usuario ou token necessario"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}, parseBody({ formEncoded: { limit: -1, limitResponse: false }, formData: { tmpFolder: path.join(tmpdir(), String().concat("colaborar_data")) } }));
|
|
||||||
|
|
||||||
// Cursos
|
|
||||||
app.get("/", async (req, res) => {
|
|
||||||
return res.json(Object.keys(req.User.aulas).reduce((acc, semestre) => acc.concat({ semestre, activity: Object.keys(req.User.aulas[semestre]).map(id => ({ name: req.User.aulas[semestre][id].name, id })) }), []));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sincronizar as aulas com o banco de dados
|
// Semestres
|
||||||
app.post("/", async (req, res) => {
|
app.get("/", async (req, res) => res.json(await Dashboard(req.Got)));
|
||||||
const ids = await syncUser(req.User.userID);
|
|
||||||
return res.status(201).json(ids.aulas);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check
|
// Boletim
|
||||||
app.use("/:matricula", async (req, res, next) => req.User.aulas[req.params.matricula] ? next() : res.status(404).json({ errors: [ { type: "Matricula", message: "Matricula não registrada!" } ] }));
|
app.get("/boletim/:semestre/:displina", async (req, res) => {
|
||||||
|
const bot = await boletim(req.Got.got, req.params.semestre, req.params.displina);
|
||||||
|
return res.json(bot);
|
||||||
|
});
|
||||||
|
|
||||||
// Aulas
|
// Aulas
|
||||||
app.get("/:matricula", async (req, res) => res.json(Object.keys(req.User.aulas[req.params.matricula]).map(id => ({name: req.User.aulas[req.params.matricula][id].name, id}))));
|
app.get("/aula/:semestre", async (req, res) => res.json(await Aulas.Get(req.Got, req.params.semestre)));
|
||||||
|
app.get("/aula/:semestre/:displina", async (req, res) => res.json(await Atividades.Get(req.Got, req.params.semestre, req.params.displina)));
|
||||||
// Sync semestre
|
|
||||||
app.post("/:matricula/sync", async (req, res) => {
|
|
||||||
const ids = await syncUser(req.User.userID, req.params.matricula);
|
|
||||||
return res.status(201).json(ids.aulas[req.params.matricula]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get activity's
|
|
||||||
app.get("/:matricula/:disciplina", async (req, res, next) => {
|
|
||||||
if (!(req.User.aulas[req.params.matricula][req.params.disciplina])) return next();
|
|
||||||
const { name, ids } = req.User.aulas[req.params.matricula][req.params.disciplina];
|
|
||||||
return res.json({
|
|
||||||
title: name,
|
|
||||||
activity: await activity.find({ id: { $in: ids } }).toArray(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/:matricula/:disciplina/book", async (req, res, next) => {
|
|
||||||
if (!(req.User.aulas[req.params.matricula][req.params.disciplina])) return next();
|
|
||||||
const { ids } = req.User.aulas[req.params.matricula][req.params.disciplina];
|
|
||||||
const act = await activity.find({ id: { $in: ids } }).toArray();
|
|
||||||
const book = act.filter(s => s.type === "Leitura" && (s.files.concat(s.externalFiles).some(x => x.title.toLowerCase().includes("livro")||x.title.toLowerCase().includes("didático")))).at(0);
|
|
||||||
if (!book || book.type !== "Leitura") return res.status(400).json({ erros: [ { type: "file", message: "File not found!" } ] });
|
|
||||||
const fileUrl = new URL(book.files.concat(book.externalFiles).find(x => x.title.toLowerCase().includes("livro")||x.title.toLowerCase().includes("didático")).url);
|
|
||||||
const { got } = await remoteRequest(req.User.colaborar.user, req.User.colaborar.password);
|
|
||||||
if (fileUrl.searchParams.has("urlOrigem")) return got(fileUrl.searchParams.get("urlOrigem"), { isStream: true }).pipe(res);
|
|
||||||
return got(fileUrl, { isStream: true }).pipe(res);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/:matricula/:disciplina/teleaula/:id", async (req, res) => {
|
|
||||||
const act = await activity.findOne({ id: req.params.id });
|
|
||||||
if (!act || act.type !== "Tele Aula") return res.status(400).json({ error: [ { type: "activity", message: "Aula não existe" } ].concat(act.type !== "Tele Aula" ? { type: "activity", message: "ID invalido" } : undefined) });
|
|
||||||
const { got } = await remoteRequest(req.User.colaborar.user, req.User.colaborar.password);
|
|
||||||
|
|
||||||
const videos = await Promise.all(act.videoIDs.map(async d => {
|
|
||||||
const currentTime: number = JSON.parse((await got("https://www.colaboraread.com.br/aluno/videoAnotacao/getStatusExemplar", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"accept": "application/json",
|
|
||||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
||||||
},
|
|
||||||
body: (new URLSearchParams({
|
|
||||||
matriculaId: req.params.matricula,
|
|
||||||
exemplarId: d.exemplarId
|
|
||||||
})).toString()
|
|
||||||
})).body).currentTime;
|
|
||||||
|
|
||||||
return Object.assign(d, { currentTime });
|
|
||||||
}))
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
title: act.description,
|
|
||||||
videos,
|
|
||||||
files: act.file.map(s => ({
|
|
||||||
title: s.name,
|
|
||||||
download: path.posix.join(req.reqPath, Buffer.from(s.url, "utf8").toString("hex")),
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/:matricula/:disciplina/teleaula/:id", async (req, res) => {
|
|
||||||
if (!req.body) return res.status(400).json({ errors: [ { type: "body", message: "require body!" } ] });
|
|
||||||
const { videoID, time, end = false } = req.body;
|
|
||||||
if (!(typeof videoID === "string" && time > 0)) return res.status(400).json({ errors: [ { type: "body", message: "require body!" } ] });
|
|
||||||
const act = await activity.findOne({ id: req.params.id });
|
|
||||||
if (!act || act.type !== "Tele Aula") return res.status(400).json({ error: [ { type: "activity", message: "Aula não existe" } ].concat(act.type !== "Tele Aula" ? { type: "activity", message: "ID invalido" } : undefined) });
|
|
||||||
const video = act.videoIDs.find(s => s.id === videoID);
|
|
||||||
if (!video) return res.status(400).json({ errors: [ { type: "videoID", message: "Video ID not found!" } ] });
|
|
||||||
const { got } = await remoteRequest(req.User.colaborar.user, req.User.colaborar.password);
|
|
||||||
|
|
||||||
if (end) {
|
|
||||||
await got("https://www.colaboraread.com.br/aluno/videoAnotacao/updateEndTimeStatusExemplarVideo", {
|
|
||||||
headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" },
|
|
||||||
body: (new URLSearchParams({
|
|
||||||
currentTime: "0", end: "true", // Valores fixos
|
|
||||||
matriculaId: req.params.matricula,
|
|
||||||
exemplarId: video.exemplarId,
|
|
||||||
})).toString(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await got("https://www.colaboraread.com.br/aluno/playerVideo/updateProgressoAluno", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" },
|
|
||||||
body: (new URLSearchParams({
|
|
||||||
matriculaId: req.params.matricula,
|
|
||||||
atividadeOfertaId: video.id,
|
|
||||||
totalTime: String(video.videoDuration),
|
|
||||||
currentTime: String(time),
|
|
||||||
})).toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(201).json({
|
|
||||||
atividadeOfertaId: video.id,
|
|
||||||
totalTime: video.videoDuration,
|
|
||||||
currentTime: time,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
app.get("/:matricula/:disciplina/teleaula/:id/:filehash", async (req, res) => {
|
|
||||||
const act = await activity.findOne({ id: req.params.id });
|
|
||||||
if (!act || act.type !== "Tele Aula") return res.status(400).json({ error: [ { type: "activity", message: "Aula não existe" } ].concat(act.type !== "Tele Aula" ? { type: "activity", message: "ID invalido" } : undefined) });
|
|
||||||
const fileUrl = Buffer.from(req.params.filehash, "hex").toString("utf8");
|
|
||||||
const is = act.file.find(s => s.url === fileUrl);
|
|
||||||
if (!is) return res.status(404).json({ errors: [ { type: "file", message: "File not found!" } ] });
|
|
||||||
|
|
||||||
const { got } = await remoteRequest(req.User.colaborar.user, req.User.colaborar.password);
|
|
||||||
return got(fileUrl, { isStream: true }).pipe(res);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/:matricula/:disciplina/boletim", async (req, res) => {
|
|
||||||
const { got } = await remoteRequest(req.User.colaborar.user, req.User.colaborar.password);
|
|
||||||
return res.json(await Boletim(got, req.params.matricula, req.params.disciplina));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/:matricula/:disciplina/:id", async (req, res) => {
|
|
||||||
const { matricula, disciplina, id } = req.params;
|
|
||||||
if (!(req.User.aulas[matricula][disciplina].ids.includes(id))) return res.status(404).json({ errors: [ { type: "material", message: "Conteudo não liberado" } ] });
|
|
||||||
const act = await activity.findOne({ id });
|
|
||||||
return res.json(act);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use((req, res) => {
|
|
||||||
console.log(req.path);
|
|
||||||
res.sendStatus(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen
|
|
||||||
app.listen(process.env.PORT||3000, () => { const addr = app.address(); console.log("HTTP Listen on %s", typeof addr === "object" ? addr.port : addr); })
|
|
@ -1,15 +1,29 @@
|
|||||||
import { decrypt } from "./password.js";
|
|
||||||
import cookie from "cookie";
|
import cookie from "cookie";
|
||||||
import got from "got";
|
import got, { Got, OptionsOfBufferResponseBody, OptionsOfTextResponseBody } from "got";
|
||||||
|
import { IncomingHttpHeaders } from "http";
|
||||||
|
import { JSDOM } from "jsdom";
|
||||||
|
import { decrypt } from "./password.js";
|
||||||
|
|
||||||
export {remoteRequest};
|
export { remoteRequest };
|
||||||
|
export type { Got };
|
||||||
|
|
||||||
|
export interface ColaborareadRequest {
|
||||||
|
cookies: Map<string, Record<string, string>>;
|
||||||
|
reloadAuth: () => Promise<void>;
|
||||||
|
got: Got;
|
||||||
|
gotDom: (url: string | URL, options?: OptionsOfTextResponseBody | OptionsOfBufferResponseBody) => Promise<{
|
||||||
|
url: string;
|
||||||
|
headers: IncomingHttpHeaders;
|
||||||
|
jsdoc: JSDOM;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create extends to got
|
* Create extends to got
|
||||||
* @param username - Username to auth
|
* @param username - Username to auth
|
||||||
* @param password - Password to auth
|
* @param password - Password to auth
|
||||||
*/
|
*/
|
||||||
export default async function remoteRequest(username: string, password: string) {
|
export default async function remoteRequest(username: string, password: string): Promise<ColaborareadRequest> {
|
||||||
password = await decrypt(password);
|
password = await decrypt(password);
|
||||||
const localCookie = new Map<string, Record<string, string>>()
|
const localCookie = new Map<string, Record<string, string>>()
|
||||||
const defaultGot = got.extend({
|
const defaultGot = got.extend({
|
||||||
@ -17,27 +31,31 @@ export default async function remoteRequest(username: string, password: string)
|
|||||||
throwHttpErrors: true,
|
throwHttpErrors: true,
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
headers: {
|
headers: {
|
||||||
"upgrade-insecure-requests": "1",
|
|
||||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
|
|
||||||
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||||
"sec-ch-ua": "\"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"114\", \"Google Chrome\";v=\"114\"",
|
"accept-language": "pt-BR,pt;q=0.9,en;q=0.8,en-US;q=0.7",
|
||||||
|
"cache-control": "no-cache",
|
||||||
|
"pragma": "no-cache",
|
||||||
|
"sec-ch-ua": "\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\"",
|
||||||
"sec-ch-ua-mobile": "?0",
|
"sec-ch-ua-mobile": "?0",
|
||||||
"sec-ch-ua-platform": "\"Windows\"",
|
"sec-ch-ua-platform": "\"Windows\"",
|
||||||
"sec-fetch-site": "none",
|
|
||||||
"sec-fetch-mode": "navigate",
|
|
||||||
"sec-fetch-user": "?1",
|
|
||||||
"sec-fetch-dest": "document",
|
"sec-fetch-dest": "document",
|
||||||
// "accept-encoding": "gzip, deflate, br",
|
"sec-fetch-mode": "navigate",
|
||||||
"accept-language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
|
"sec-fetch-site": "same-origin",
|
||||||
|
"sec-fetch-user": "?1",
|
||||||
|
"upgrade-insecure-requests": "1",
|
||||||
|
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537."
|
||||||
},
|
},
|
||||||
cookieJar: {
|
cookieJar: {
|
||||||
async getCookieString(url: string) { const data = localCookie.get((new URL(url)).hostname)||{}; return Object.keys(data).reduce<string>((acc, k) => acc.concat(";", k, "=", data[k]), ""); },
|
async getCookieString(url: string) {
|
||||||
|
const data = localCookie.get((new URL(url)).hostname)||{};
|
||||||
|
return Object.keys(data).reduce<string>((acc, k) => acc.concat(";", k, "=", data[k]), "");
|
||||||
|
},
|
||||||
async setCookie(rawCookie: string, url: string) {
|
async setCookie(rawCookie: string, url: string) {
|
||||||
const host = (new URL(url)).hostname
|
const host = (new URL(url)).hostname
|
||||||
if (!(localCookie.has(host))) localCookie.set(host, {});
|
if (!(localCookie.has(host))) localCookie.set(host, {});
|
||||||
const cookieData = cookie.parse(rawCookie);
|
const cookieData = cookie.parse(rawCookie);
|
||||||
([ "Expires", "Max-Age", "Domain", "Path", "Secure", "HttpOnly", "SameSite" ]).forEach(k => delete cookieData[k]);
|
|
||||||
for (const k in cookieData) {
|
for (const k in cookieData) {
|
||||||
|
if (([ "expires", "max-age", "domain", "path", "secure", "httponly", "samesite" ]).includes(k.toLowerCase())) continue;
|
||||||
if (cookieData[k]) localCookie.get(host)[k] = cookieData[k];
|
if (cookieData[k]) localCookie.get(host)[k] = cookieData[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,11 +78,22 @@ export default async function remoteRequest(username: string, password: string)
|
|||||||
const localURL = new URL((auth.headers.location || "/"), "https://www.colaboraread.com.br");
|
const localURL = new URL((auth.headers.location || "/"), "https://www.colaboraread.com.br");
|
||||||
if (auth.statusCode === 302 && (localURL.pathname === "/login/authfail" || localURL.pathname === "/login/auth" || localURL.searchParams.has("login_error"))) throw new Error("Invalid username or password!");
|
if (auth.statusCode === 302 && (localURL.pathname === "/login/authfail" || localURL.pathname === "/login/auth" || localURL.searchParams.has("login_error"))) throw new Error("Invalid username or password!");
|
||||||
}
|
}
|
||||||
|
|
||||||
await reloadAuth();
|
await reloadAuth();
|
||||||
|
const goto = defaultGot.extend({ followRedirect: true });
|
||||||
|
|
||||||
|
async function gotDom(url: string|URL, options?: OptionsOfTextResponseBody|OptionsOfBufferResponseBody) {
|
||||||
|
const req = await goto(url, options);
|
||||||
|
return {
|
||||||
|
url: req.url,
|
||||||
|
headers: req.headers,
|
||||||
|
jsdoc: new JSDOM(req.body as string|Buffer, { url: req.url, }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
got: defaultGot.extend({ followRedirect: true }),
|
|
||||||
cookies: localCookie,
|
cookies: localCookie,
|
||||||
reloadAuth
|
reloadAuth,
|
||||||
|
got: goto,
|
||||||
|
gotDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|||||||
import util from "node:util";
|
import util from "node:util";
|
||||||
|
|
||||||
const scrypt = util.promisify(crypto.scrypt) as (password: crypto.BinaryLike, salt: crypto.BinaryLike, keylen: number) => Promise<Buffer>;
|
const scrypt = util.promisify(crypto.scrypt) as (password: crypto.BinaryLike, salt: crypto.BinaryLike, keylen: number) => Promise<Buffer>;
|
||||||
|
|
||||||
export async function encrypt(text: string): Promise<string> {
|
export async function encrypt(text: string): Promise<string> {
|
||||||
const iv = crypto.randomBytes(16), secret = crypto.randomBytes(24);
|
const iv = crypto.randomBytes(16), secret = crypto.randomBytes(24);
|
||||||
const key = await scrypt(secret, "salt", 24);
|
const key = await scrypt(secret, "salt", 24);
|
||||||
|
540
src/lib/sync.ts
540
src/lib/sync.ts
@ -1,540 +0,0 @@
|
|||||||
import { Got } from "got";
|
|
||||||
import { JSDOM } from "jsdom";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
export async function Cursos(got: Got) {
|
|
||||||
const index = await got("https://www.colaboraread.com.br/index/index");
|
|
||||||
const doc = new JSDOM(index.body, { url: index.url });
|
|
||||||
const { window: { document } } = doc;
|
|
||||||
document.querySelector("#navbar-content-aluno-cursos > div > div:nth-child(1) > div")
|
|
||||||
return Array.from(document.querySelectorAll("#navbar-content-aluno-cursos > div > div")).reduce<{ name: string; semestres: {semestreID: string; title: string;}[] }[]>((acc, e) => {
|
|
||||||
if (!(e.querySelector("div > div:nth-child(2) > h3"))) return acc;
|
|
||||||
else if (!(e.querySelector("div > div:nth-child(1) > div:nth-child(3) > form > div:nth-child(1) > select > option"))) return acc;
|
|
||||||
|
|
||||||
// Title
|
|
||||||
const title = (e.querySelector("div > div:nth-child(2) > h3") as HTMLHeadingElement).getAttribute("title");
|
|
||||||
|
|
||||||
// Push to array
|
|
||||||
acc.push({
|
|
||||||
name: title,
|
|
||||||
semestres: Array.from<HTMLOptionElement>(e.querySelectorAll("div > div:nth-child(1) > div:nth-child(3) > form > div:nth-child(1) > select > option")).map(({ value, innerText, innerHTML }) => ({ semestreID: value, title: (innerText||innerHTML).trim() })).reverse()
|
|
||||||
});
|
|
||||||
|
|
||||||
// retornar os cursos
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Prova = {
|
|
||||||
semestre: string;
|
|
||||||
ano: string;
|
|
||||||
unidade: {
|
|
||||||
nome: string;
|
|
||||||
unidadeCode: string;
|
|
||||||
};
|
|
||||||
exames: {
|
|
||||||
nome: string;
|
|
||||||
code: string;
|
|
||||||
cancelado: boolean;
|
|
||||||
status: number;
|
|
||||||
disciplina: {
|
|
||||||
nome: string;
|
|
||||||
codigo: string;
|
|
||||||
};
|
|
||||||
dates: {
|
|
||||||
start: Date;
|
|
||||||
end: Date;
|
|
||||||
schedule?: {
|
|
||||||
start: Date;
|
|
||||||
end: Date;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
gabarito?: any;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function Aulas(got: Got, semestre: string) {
|
|
||||||
const dashboard = new URL("https://www.colaboraread.com.br/aluno/dashboard/index"); dashboard.searchParams.set("matriculaId", semestre);
|
|
||||||
const index = await got(dashboard);
|
|
||||||
const doc = new JSDOM(index.body, { url: index.url });
|
|
||||||
const { window: { document } } = doc;
|
|
||||||
const provaURL = Array.from(document.querySelectorAll("a")).filter(s => s.href.includes("/redirectAgendamentoProvaFrontend"))[0]?.href;
|
|
||||||
let prova: undefined|Prova;
|
|
||||||
if (provaURL) {
|
|
||||||
try {
|
|
||||||
const redirectAgen = new URL(provaURL, index.url);
|
|
||||||
const { headers: { location: tokenAgendamento } } = await got(redirectAgen, { followRedirect: false });
|
|
||||||
const token = (new URL(tokenAgendamento)).searchParams.get("token");
|
|
||||||
const provasBody = (await got("https://x47kn51jsc.execute-api.us-east-1.amazonaws.com/application/student/exams/", { headers: { authorization: ("Bearer ").concat(token), }, responseType: "json"})).body as any;
|
|
||||||
prova = {
|
|
||||||
ano: redirectAgen.searchParams.get("ano"),
|
|
||||||
semestre: provasBody.semester,
|
|
||||||
unidade: provasBody.unit,
|
|
||||||
exames: await Promise.all((provasBody.exams as any[]).map(async (s): Promise<Prova["exames"][number]> => ({
|
|
||||||
nome: s.discipline,
|
|
||||||
code: s.locator,
|
|
||||||
status: s.status,
|
|
||||||
cancelado: s.canceled,
|
|
||||||
disciplina: {
|
|
||||||
codigo: s.disciplineCod,
|
|
||||||
nome: s.title,
|
|
||||||
},
|
|
||||||
dates: {
|
|
||||||
start: new Date(s.beginDate),
|
|
||||||
end: new Date(s.endDate),
|
|
||||||
...((s.beginScheduler && s.endScheduler) && {
|
|
||||||
schedule: {
|
|
||||||
start: new Date(s.beginScheduler),
|
|
||||||
end: new Date(s.endScheduler)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
gabarito: ((new Date(s.feedback?.endDate||Date.now() + 1000)).getTime() >= Date.now()) ? (await got(("https://yvlfaxjs8l.execute-api.us-east-1.amazonaws.com/prd/feedback?locator=").concat(s.locator), { headers: { authorization: ("Bearer ").concat(token), }, responseType: "json"}).then(s => s.body, () => null)) : null
|
|
||||||
})))
|
|
||||||
};
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
prova,
|
|
||||||
aulas: Array.from(document.querySelectorAll("#navbar-content-aluno-pda > ul > li")).reduce<{ title: string; id: string }[]>((acc, e) => {
|
|
||||||
if (!(e.querySelector("table > tbody > tr > td:nth-child(1) > a"))) return acc;
|
|
||||||
const a = e.querySelector("table > tbody > tr > td:nth-child(1) > a");
|
|
||||||
const title = a.getAttribute("title").trim(), pageHref = (new URL(a.getAttribute("href"), index.url)).searchParams.get("ofertaDisciplinaId")
|
|
||||||
if (pageHref) acc.push({ title, id: pageHref });
|
|
||||||
return acc;
|
|
||||||
}, []),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BoletimColaborar = {
|
|
||||||
detalheBoletimDto: {
|
|
||||||
detalheDisciplinarDtoList: {
|
|
||||||
[key: string]: {
|
|
||||||
acertos: number|null;
|
|
||||||
apresentacao: boolean;
|
|
||||||
atividadeAvaliacaoId: number;
|
|
||||||
atividadeId: number;
|
|
||||||
atividadeLancamentoId?: number;
|
|
||||||
atividadeOfertaId: number;
|
|
||||||
caminhoAtividade1: any;
|
|
||||||
caminhoAtividade2?: any;
|
|
||||||
dataAtividade1: string;
|
|
||||||
dataAtividade2: string;
|
|
||||||
dataFimGabaritoAluno: string;
|
|
||||||
dataInicioGabaritoAluno: string;
|
|
||||||
descricaoAtividade: string;
|
|
||||||
gabarito: any;
|
|
||||||
interdisciplinar: boolean;
|
|
||||||
likes: number;
|
|
||||||
objetivas?: string;
|
|
||||||
pontuacao: number;
|
|
||||||
pontuacaoApresentacao: number;
|
|
||||||
pontuacaoMinina: number;
|
|
||||||
pontuacaoTotalApresentacao: number;
|
|
||||||
sequenciaAvaliacao?: number;
|
|
||||||
tipoAtividadeId: number;
|
|
||||||
tipoOfertaId: number;
|
|
||||||
tipoAtividade: {
|
|
||||||
enumType: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
detalheEngajamentoDtoList: {
|
|
||||||
descricaoAtividade: string;
|
|
||||||
pontuacao: number;
|
|
||||||
tipoAtividade: {
|
|
||||||
enumType: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
}[];
|
|
||||||
detalheFrequenciaDtoList: {
|
|
||||||
[type: string]: {
|
|
||||||
cargaHoraria: number;
|
|
||||||
cargaHorariaDisciplina: number;
|
|
||||||
dataAtividade: string;
|
|
||||||
dataFimAtividade: string;
|
|
||||||
dataInicioAtividade: string;
|
|
||||||
dataLancamento: string;
|
|
||||||
descricaoAtividade: string;
|
|
||||||
situacao: string;
|
|
||||||
tipoOfertaId: number;
|
|
||||||
tipoAtividade: {
|
|
||||||
enumType: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ActivityType = "Não definido" | "Tele Aula" | "Web Aula" | "Conteúdo Web" | "Referências Digitais" | "Prova Presencial" | "Avaliação Virtual" | "Fórum" | "Portfólio" | "Desafio Nota Máxima" | "Acelere Sua Carreira" | "Avaliação de Proficiência" | "Prova Prática Presencial" | "Atividade Diagnóstica" | "Atividade de Aprendizagem";
|
|
||||||
|
|
||||||
export type AtividadeRoot = {
|
|
||||||
type: Exclude<ActivityType, "Portfólio"|"Prova Presencial">;
|
|
||||||
descricaoAtividade: string;
|
|
||||||
atividadeAvaliacaoId: number;
|
|
||||||
atividadeOfertaId: number;
|
|
||||||
atividadeId: number;
|
|
||||||
pontos: {
|
|
||||||
sequenciaAvaliacao: number;
|
|
||||||
pontuacaoMinina: number;
|
|
||||||
pontuacao?: number;
|
|
||||||
};
|
|
||||||
raw: BoletimColaborar["detalheBoletimDto"]["detalheDisciplinarDtoList"][string][number];
|
|
||||||
}|{
|
|
||||||
type: "Portfólio";
|
|
||||||
descricaoAtividade: string;
|
|
||||||
atividadeId: number;
|
|
||||||
atividadeOfertaId: number;
|
|
||||||
pontuacaoMinina: number;
|
|
||||||
pontuacao:number
|
|
||||||
}|{
|
|
||||||
type: "Prova Presencial"
|
|
||||||
descricaoAtividade: string;
|
|
||||||
atividadeId: number;
|
|
||||||
atividadeOfertaId: number;
|
|
||||||
acertos: number;
|
|
||||||
sequenciaAvaliacao: number;
|
|
||||||
pontuacaoMinina: number;
|
|
||||||
pontuacao: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Aula = {
|
|
||||||
title: string;
|
|
||||||
date: {
|
|
||||||
begin: Date;
|
|
||||||
end: Date;
|
|
||||||
recovery?: {
|
|
||||||
begin: Date;
|
|
||||||
end: Date;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
} & ({
|
|
||||||
type: "portfólio";
|
|
||||||
id: string;
|
|
||||||
points: { hits: number; max: number; };
|
|
||||||
files: {title: string; url: string;}[];
|
|
||||||
} | {
|
|
||||||
type: "teleaula";
|
|
||||||
id: string;
|
|
||||||
files: {title: string; url: string;}[];
|
|
||||||
} | {
|
|
||||||
type: "avaliação virtual";
|
|
||||||
id: string;
|
|
||||||
points: { hits: number; max: number; };
|
|
||||||
} | {
|
|
||||||
type: "conteúdo web";
|
|
||||||
id: string;
|
|
||||||
} | {
|
|
||||||
type: "leitura";
|
|
||||||
files: {title: string; url: string;}[];
|
|
||||||
externFiles: {title: string; url: string;}[];
|
|
||||||
} | {
|
|
||||||
type: "prova presencial da disciplina";
|
|
||||||
points: {
|
|
||||||
hits: number;
|
|
||||||
max: number;
|
|
||||||
}
|
|
||||||
}|{
|
|
||||||
type: string;
|
|
||||||
[k: string]: any;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liste as atividade da aula e o boletim.
|
|
||||||
*
|
|
||||||
* @param got - got com os cookies
|
|
||||||
* @param semestre - ID do semetre normalmente nesse formato `{RA}{SEMESTRE}`, exp: {100000}{01},{100000}{02}, {100000}{04}...
|
|
||||||
* @param id - ID da Aula
|
|
||||||
*/
|
|
||||||
export async function Atividades(got: Got, semestre: string, id: string) {
|
|
||||||
// Mount URL Request's
|
|
||||||
const dashboard = new URL(path.posix.join("/aluno/timeline/index", semestre), "https://www.colaboraread.com.br"); dashboard.searchParams.set("ofertaDisciplinaId", id);
|
|
||||||
const botURL = new URL(path.posix.join("/aluno/boletim/getListaDetalheBoletimDto"), "https://colaboraread.com.br/"); botURL.searchParams.set("matriculaId", semestre); botURL.searchParams.set("ofertaDisciplinaId", id); // botURL.searchParams.set("tipoDisciplinaId", "13");
|
|
||||||
|
|
||||||
// Request
|
|
||||||
const index = await got(dashboard), boletimRequest = await got(botURL);
|
|
||||||
|
|
||||||
// Parse HTML Page
|
|
||||||
const doc = new JSDOM(index.body, { url: index.url });
|
|
||||||
const { window: { document } } = doc;
|
|
||||||
|
|
||||||
// Pretty boletim
|
|
||||||
const boletim = (JSON.parse(boletimRequest.body) as BoletimColaborar[]).map(fn => {
|
|
||||||
return {
|
|
||||||
ava: fn.detalheBoletimDto.detalheEngajamentoDtoList.reduce<Record<string, number>>((acc, { descricaoAtividade: name, pontuacao }) => { acc[name] = pontuacao; return acc; }, {}),
|
|
||||||
frenquencia: Object.keys(fn.detalheBoletimDto.detalheFrequenciaDtoList).reduce<{type: ActivityType; descricaoAtividade: string; situacao?: string; cargaHoraria: { cargaHorariaDisciplina: number; cargaHoraria: number; }; datas: { lancamento: string; atividade: string; inicio: string; fim: string; }; tipo: { id: number; atividade: string; };}[]>((acc, id) => {
|
|
||||||
const atividade = fn.detalheBoletimDto.detalheFrequenciaDtoList[id];
|
|
||||||
let ActType: ActivityType = "Não definido";
|
|
||||||
if (id === "2") ActType = "Tele Aula";
|
|
||||||
else if (id === "4") ActType = "Web Aula";
|
|
||||||
else if (id === "17") ActType = "Conteúdo Web";
|
|
||||||
else if (id === "14") ActType = "Referências Digitais";
|
|
||||||
else if (id === "13") ActType = "Prova Presencial";
|
|
||||||
else if (id === "6") ActType = "Avaliação Virtual";
|
|
||||||
else if (id === "16") ActType = "Fórum";
|
|
||||||
else if (id === "1") ActType = "Portfólio";
|
|
||||||
else if (id === "28") ActType = "Desafio Nota Máxima";
|
|
||||||
else if (id === "31") ActType = "Acelere Sua Carreira";
|
|
||||||
else if (id === "22") ActType = "Avaliação de Proficiência";
|
|
||||||
else if (id === "26") ActType = "Prova Prática Presencial";
|
|
||||||
else if (id === "23") ActType = "Atividade Diagnóstica";
|
|
||||||
else if (id === "24") ActType = "Atividade de Aprendizagem";
|
|
||||||
return acc.concat(atividade.map((s): (typeof acc)[number] => ({
|
|
||||||
type: ActType,
|
|
||||||
descricaoAtividade: s.descricaoAtividade,
|
|
||||||
situacao: s.situacao,
|
|
||||||
cargaHoraria: {
|
|
||||||
cargaHorariaDisciplina: s.cargaHorariaDisciplina,
|
|
||||||
cargaHoraria: s.cargaHoraria,
|
|
||||||
},
|
|
||||||
datas: {
|
|
||||||
lancamento: s.dataLancamento,
|
|
||||||
atividade: s.dataAtividade,
|
|
||||||
inicio: s.dataInicioAtividade,
|
|
||||||
fim: s.dataFimAtividade,
|
|
||||||
},
|
|
||||||
tipo: {
|
|
||||||
id: s.tipoOfertaId,
|
|
||||||
atividade: s.tipoAtividade.name,
|
|
||||||
}
|
|
||||||
})));
|
|
||||||
}, []),
|
|
||||||
atividades: Object.keys(fn.detalheBoletimDto.detalheDisciplinarDtoList).reduce<AtividadeRoot[]>((acc, id) => {
|
|
||||||
const atividade = fn.detalheBoletimDto.detalheDisciplinarDtoList[id];
|
|
||||||
let ActType: Exclude<ActivityType, "Portfólio"|"Prova Presencial"> = "Não definido";
|
|
||||||
if (id === "1") return acc.concat(atividade.map(({ descricaoAtividade, atividadeId, atividadeOfertaId, pontuacaoMinina, pontuacao }): (typeof acc)[number] => ({ type: "Portfólio", atividadeId, atividadeOfertaId, descricaoAtividade, pontuacaoMinina, pontuacao })));
|
|
||||||
else if (id === "2") ActType = "Tele Aula";
|
|
||||||
else if (id === "4") ActType = "Web Aula";
|
|
||||||
else if (id === "6") ActType = "Avaliação Virtual";
|
|
||||||
else if (id === "13") return acc.concat(atividade.map(({ descricaoAtividade, atividadeId, atividadeOfertaId, pontuacaoMinina, pontuacao, acertos, sequenciaAvaliacao }): (typeof acc)[number] => ({ type: "Prova Presencial", atividadeId, atividadeOfertaId, descricaoAtividade, acertos, sequenciaAvaliacao, pontuacaoMinina, pontuacao })));
|
|
||||||
else if (id === "14") ActType = "Referências Digitais";
|
|
||||||
else if (id === "16") ActType = "Fórum";
|
|
||||||
else if (id === "17") ActType = "Conteúdo Web";
|
|
||||||
else if (id === "22") ActType = "Avaliação de Proficiência";
|
|
||||||
else if (id === "23") ActType = "Atividade Diagnóstica";
|
|
||||||
else if (id === "24") ActType = "Atividade de Aprendizagem";
|
|
||||||
else if (id === "26") ActType = "Prova Prática Presencial";
|
|
||||||
else if (id === "28") ActType = "Desafio Nota Máxima";
|
|
||||||
else if (id === "31") ActType = "Acelere Sua Carreira";
|
|
||||||
|
|
||||||
// Generic
|
|
||||||
return acc.concat(atividade.map((s): (typeof acc)[number] => {
|
|
||||||
const { descricaoAtividade, atividadeAvaliacaoId, atividadeOfertaId, atividadeId, sequenciaAvaliacao, pontuacaoMinina, pontuacao } = s;
|
|
||||||
return {
|
|
||||||
type: ActType,
|
|
||||||
descricaoAtividade,
|
|
||||||
atividadeAvaliacaoId, atividadeOfertaId, atividadeId,
|
|
||||||
pontos: {
|
|
||||||
sequenciaAvaliacao,
|
|
||||||
pontuacaoMinina,
|
|
||||||
pontuacao,
|
|
||||||
},
|
|
||||||
raw: s,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
// atividadesRaw: fn.detalheBoletimDto.detalheDisciplinarDtoList,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extração da aulas
|
|
||||||
const aulas = Array.from(document.querySelectorAll("#js-activities-container > li")).reduce<Aula[]>((acc, e) => {
|
|
||||||
if (!(e.querySelector("div:nth-child(2) > div > h4 > span > span"))) return acc;
|
|
||||||
const typeElement: HTMLSpanElement = e.querySelector("div:nth-child(2) > div > h4 > span > span");
|
|
||||||
Array.from(typeElement.querySelectorAll("*")).forEach(s => s.remove()); // Remove icon
|
|
||||||
const type = (typeElement.innerText || typeElement.innerHTML).trim().toLowerCase();
|
|
||||||
const titleElement: HTMLElement = e.querySelector("div.timeline-panel > div > h4 > small"), title = (titleElement.innerText || titleElement.innerHTML).trim();
|
|
||||||
|
|
||||||
if (type === "teleaula") {
|
|
||||||
const hour = e.querySelector("div.timeline-panel > div > p:nth-child(3) > small > em").innerHTML.trim().split("-"),
|
|
||||||
date = e.querySelector("div.timeline-panel > div > p:nth-child(2) > small > em").innerHTML.trim().split("-").reduce<Date[]>((acc, date, index) => {
|
|
||||||
const [ day, month, year ] = date.trim().split("/"), hor = hour[index];
|
|
||||||
return acc.concat(new Date(String().concat(month, "/", day, "/", year, " ", hor, " GMT-3")));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const files = Array.from(e.querySelectorAll("div.timeline-panel > div > div.timeline-body > ul > li")).reduce<{title: string, url: string}[]>((acc, a) => {
|
|
||||||
if (!(a.querySelector("small > a"))) return acc;
|
|
||||||
const fileA = a.querySelector("small > a");
|
|
||||||
return acc.concat({
|
|
||||||
title: fileA.getAttribute("title"),
|
|
||||||
url: new URL(fileA.getAttribute("href"), index.url).toString(),
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const videoPage = new URL(e.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), index.url);
|
|
||||||
return acc.concat({
|
|
||||||
type: "teleaula",
|
|
||||||
title,
|
|
||||||
id: videoPage.searchParams.get("atividadeDisciplinaId"),
|
|
||||||
date: {
|
|
||||||
begin: date[0],
|
|
||||||
end: date[1]
|
|
||||||
},
|
|
||||||
files,
|
|
||||||
});
|
|
||||||
} else if (type === "portfólio") {
|
|
||||||
const hour = e.querySelector("div.timeline-panel > div > p:nth-child(3) > small > em").innerHTML.trim().split("-");
|
|
||||||
let dateS = e.querySelector("div.timeline-panel > div > p:nth-child(2) > small > em").innerHTML.trim().split("-");
|
|
||||||
if (e.querySelector("div.timeline-panel > div > p:nth-child(4) > small > em")) dateS = dateS.concat(e.querySelector("div.timeline-panel > div > p:nth-child(4) > small > em").innerHTML.split("-"));
|
|
||||||
const dates = dateS.reduce<Date[]>((acc, date, index, l) => {
|
|
||||||
if (!hour[index]) index = (~index) + l.length;
|
|
||||||
const [ day, month, year ] = date.trim().split("/"), hor = hour.at(index);
|
|
||||||
return acc.concat(new Date(String().concat(month, "/", day, "/", year, " ", hor, " GMT-3")));
|
|
||||||
}, []);
|
|
||||||
const files = Array.from(e.querySelectorAll("div.timeline-panel > div > div.timeline-body > ul > li")).map(e => e.querySelector("small > a")).filter(Boolean).map(s => ({ title: s.getAttribute("title"), url: (new URL(s.getAttribute("href"), index.url)).toString() }))
|
|
||||||
const portUrl = new URL(e.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), index.url);
|
|
||||||
const [ ppo, pmax ] = e.querySelector("div.timeline-panel > div > div:nth-child(6) > div > div > small").textContent.split("de");
|
|
||||||
return acc.concat({
|
|
||||||
type,
|
|
||||||
title,
|
|
||||||
id: portUrl.searchParams.get("atividadeDisciplinaId"),
|
|
||||||
date: {
|
|
||||||
begin: dates[0],
|
|
||||||
end: dates[1],
|
|
||||||
...(dates[2] ? { recovery: { begin: dates[2], end: dates[3] } } : {})
|
|
||||||
},
|
|
||||||
points: {
|
|
||||||
hits: parseFloat(ppo.trim()),
|
|
||||||
max: parseFloat(pmax.trim())
|
|
||||||
},
|
|
||||||
files,
|
|
||||||
});
|
|
||||||
} else if (type === "avaliação virtual") {
|
|
||||||
const hour = e.querySelector("div.timeline-panel > div > p:nth-child(3) > small > em").innerHTML.trim().split("-"),
|
|
||||||
date = e.querySelector("div.timeline-panel > div > p:nth-child(2) > small > em").innerHTML.trim().split("-").reduce<Date[]>((acc, date, index) => {
|
|
||||||
const [ day, month, year ] = date.trim().split("/"), hor = hour[index];
|
|
||||||
return acc.concat(new Date(String().concat(month, "/", day, "/", year, " ", hor, " GMT-3")));
|
|
||||||
}, []);
|
|
||||||
const portUrl = new URL(e.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), index.url);
|
|
||||||
const [ ppo, pmax ] = (e.querySelector("div.timeline-panel > div > div:nth-child(5) > div > div > small")||e.querySelector("div.timeline-panel > div > div:nth-child(6) > div > div > small")).textContent.split("de");
|
|
||||||
|
|
||||||
return acc.concat({
|
|
||||||
type, title,
|
|
||||||
id: portUrl.searchParams.get("atividadeDisciplinaId"),
|
|
||||||
date: {
|
|
||||||
begin: date[0],
|
|
||||||
end: date[1],
|
|
||||||
},
|
|
||||||
points: {
|
|
||||||
hits: parseFloat((ppo||"").trim()),
|
|
||||||
max: parseFloat((pmax||"").trim())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === "conteúdo web") {
|
|
||||||
const hour = e.querySelector("div.timeline-panel > div > p:nth-child(3) > small > em").innerHTML.trim().split("-"),
|
|
||||||
date = e.querySelector("div.timeline-panel > div > p:nth-child(2) > small > em").innerHTML.trim().split("-").reduce<Date[]>((acc, date, index) => {
|
|
||||||
const [ day, month, year ] = date.trim().split("/"), hor = hour[index];
|
|
||||||
return acc.concat(new Date(String().concat(month, "/", day, "/", year, " ", hor, " GMT-3")));
|
|
||||||
}, []);
|
|
||||||
const portUrl = new URL(e.querySelector("div.timeline-panel > div > div.timeline-body > div.row.form-group > div > a").getAttribute("href"), index.url);
|
|
||||||
return acc.concat({
|
|
||||||
type, title,
|
|
||||||
id: portUrl.searchParams.get("atividadeDisciplinaId"),
|
|
||||||
date: { begin: date[0], end: date[1] },
|
|
||||||
});
|
|
||||||
} else if (type === "leitura") {
|
|
||||||
const hour = e.querySelector("div.timeline-panel > div > p:nth-child(3) > small > em").innerHTML.trim().split("-"),
|
|
||||||
date = e.querySelector("div.timeline-panel > div > p:nth-child(2) > small > em").innerHTML.trim().split("-").reduce<Date[]>((acc, date, index) => {
|
|
||||||
const [ day, month, year ] = date.trim().split("/"), hor = hour[index];
|
|
||||||
return acc.concat(new Date(String().concat(month, "/", day, "/", year, " ", hor, " GMT-3")));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return acc.concat({
|
|
||||||
type, title,
|
|
||||||
date: { begin: date[0], end: date[1] },
|
|
||||||
files: Array.from(e.querySelectorAll("div.timeline-panel > div > div > ul:nth-child(3) > li")).map(s => s.querySelector("small > a")).filter(Boolean).map(s => ({name: s.getAttribute("title"), url: (new URL(s.getAttribute("href"), index.url)).toString() })),
|
|
||||||
externFiles: Array.from(e.querySelectorAll("div.timeline-panel > div > div > ul:nth-child(7) > li")||[]).map(s => s.querySelector("small > a")).filter(Boolean).map(s => ({name: s.getAttribute("title")||s.innerHTML.trim(), url: (new URL(s.getAttribute("href"), index.url)).toString() })),
|
|
||||||
});
|
|
||||||
} else if (type === "prova presencial da disciplina") {
|
|
||||||
const hour = e.querySelector("div.timeline-panel > div > p:nth-child(3) > small > em").innerHTML.trim().split("-"),
|
|
||||||
date = e.querySelector("div.timeline-panel > div > p:nth-child(2) > small > em").innerHTML.trim().split("-").reduce<Date[]>((acc, date, index) => {
|
|
||||||
const [ day, month, year ] = date.trim().split("/"), hor = hour[index];
|
|
||||||
return acc.concat(new Date(String().concat(month, "/", day, "/", year, " ", hor, " GMT-3")));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [ ppo, pmax ] = e.querySelector("div.timeline-panel > div > div:nth-child(4) > div > div > small").textContent.split("de");
|
|
||||||
return acc.concat({
|
|
||||||
type, title,
|
|
||||||
date: { begin: date[0], end: date[1] },
|
|
||||||
points: {
|
|
||||||
hits: parseFloat(ppo.trim()),
|
|
||||||
max: parseFloat(pmax.trim())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const hour = e.querySelector("div.timeline-panel > div > p:nth-child(3) > small > em").innerHTML.trim().split("-"),
|
|
||||||
date = e.querySelector("div.timeline-panel > div > p:nth-child(2) > small > em").innerHTML.trim().split("-").reduce<Date[]>((acc, date, index) => {
|
|
||||||
const [ day, month, year ] = date.trim().split("/"), hor = hour[index];
|
|
||||||
return acc.concat(new Date(String().concat(month, "/", day, "/", year, " ", hor, " GMT-3")));
|
|
||||||
}, []);
|
|
||||||
return acc.concat({
|
|
||||||
type, title, date: { begin: date[0], end: date[1] }
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Media final calc
|
|
||||||
|
|
||||||
let avaPoints = Object.keys(boletim[0].ava).reduce((acc, k) => acc + boletim[0].ava[k], 0);
|
|
||||||
let avaliacao = boletim[0].atividades.reduce((acc, info) => { if (info.type !== "Avaliação Virtual") return acc; info.raw.sequenciaAvaliacao === 1 && acc.g < info.raw.pontuacao && (acc.g = info.raw.pontuacao); info.raw.sequenciaAvaliacao === 2 && acc.a < info.raw.pontuacao && (acc.a = info.raw.pontuacao); acc.k = acc.g + acc.a; return acc; }, { k: 0, g: 0, a: 0 }).k;
|
|
||||||
const provas = boletim[0].atividades.reduce((acc, i) => i.type === "Prova Presencial" ? (acc + i.pontuacao) : acc, 0);
|
|
||||||
if (provas <= 3000) avaPoints = avaliacao = 0;
|
|
||||||
else if (avaliacao <= 1500) avaPoints = avaliacao = 0;
|
|
||||||
let Pontos: number[] = [ avaPoints, provas ];
|
|
||||||
|
|
||||||
// Retorno
|
|
||||||
return {
|
|
||||||
/** Boletim */
|
|
||||||
boletim,
|
|
||||||
/** Aulas */
|
|
||||||
aulas,
|
|
||||||
/** Media da aula, nota não final */
|
|
||||||
media: parseFloat(((Pontos.reduce((acc, p) => acc+p, 0) / 100) / 10).toPrecision(2)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function VideoAulaProgress(got: Got, videoID: string, matriculaId: string, atividadeOfertaId: string) {
|
|
||||||
const videoDurationURL = new URL("https://colaboraread.com.br/aluno/playerVideo/getVideoDuration");
|
|
||||||
videoDurationURL.searchParams.set("codigoMidia", videoID);
|
|
||||||
const videoDuration: number = JSON.parse((await got(videoDurationURL)).body).videoDuration;
|
|
||||||
return {
|
|
||||||
url: (new URL(path.posix.join("/video", videoID), "https://mdstrm.com")).toString().concat(".mp4"),
|
|
||||||
videoDuration,
|
|
||||||
async updateProgress(currentTime: number) {
|
|
||||||
if (currentTime > videoDuration) throw new Error("Invalid currentTime!");
|
|
||||||
await got("https://colaboraread.com.br/aluno/playerVideo/updateProgressoAluno", {
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
totalTime: videoDuration,
|
|
||||||
currentTime,
|
|
||||||
matriculaId,
|
|
||||||
atividadeOfertaId
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveProgressoEngajamento(got: Got, matriculaId: string, atividadeOfertaId: string, progresso: number) {
|
|
||||||
await got(new URL("https://colaboraread.com.br/aluno/timeline/saveProgressoEngajamento"), {
|
|
||||||
method: "post",
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
matriculaId,
|
|
||||||
atividadeOfertaId: atividadeOfertaId,
|
|
||||||
progresso: progresso,
|
|
||||||
isAluno: true
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function syncActivity(got: Got, semestreID: string, atividadeDisciplinaId: string) {
|
|
||||||
// https://www.colaboraread.com.br/aluno/avaliacao/form/{semestre}?atividadeDisciplinaId={actID}
|
|
||||||
const actUrl = new URL(path.posix.join("/aluno/avaliacao/form", semestreID), "https://www.colaboraread.com.br");
|
|
||||||
actUrl.searchParams.set("atividadeDisciplinaId", atividadeDisciplinaId);
|
|
||||||
|
|
||||||
return actUrl.toString();
|
|
||||||
}
|
|
140
src/telegram.ts
140
src/telegram.ts
@ -1,140 +0,0 @@
|
|||||||
import { Context, Telegraf } from "telegraf";
|
|
||||||
import { Boletim } from "./atividades.js";
|
|
||||||
import remoteRequest from "./lib/colabora_req.js";
|
|
||||||
import { decrypt } from "./lib/password.js";
|
|
||||||
import { User, user } from "./user.js";
|
|
||||||
const { TELEGRAM_TOKEN = "6705260087:AAHwZBhRPGxle4OuVButQZ5PgsfuonKH4AU" } = process.env;
|
|
||||||
|
|
||||||
export interface Middle extends Context {
|
|
||||||
User?: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start bot
|
|
||||||
const bot = new Telegraf<Middle>(TELEGRAM_TOKEN);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Telegram markdown:
|
|
||||||
|
|
||||||
*bold \*text*
|
|
||||||
_italic \*text_
|
|
||||||
__underline__
|
|
||||||
~strikethrough~
|
|
||||||
||spoiler||
|
|
||||||
*bold _italic bold ~italic bold strikethrough ||italic bold strikethrough spoiler||~ __underline italic bold___ bold*
|
|
||||||
[inline URL](http://www.example.com/)
|
|
||||||
[inline mention of a user](tg://user?id=123456789)
|
|
||||||
![👍](tg://emoji?id=5368324170671202286)
|
|
||||||
`inline fixed-width code`
|
|
||||||
```block fixed-width code```
|
|
||||||
```python
|
|
||||||
pre-formatted fixed-width code block written in the Python programming language```
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const helpStartMessage = [
|
|
||||||
"Olá seja bem-vindo ao colaborar remix!",
|
|
||||||
"",
|
|
||||||
"Estou em desenvolvimento por enquanto, então estou limitado aos comandos!",
|
|
||||||
"",
|
|
||||||
"/start ou /help: Está mensagem",
|
|
||||||
"/login: Fazer login no colaborar remix, exemplo: /login <RA/Semestre> <senha>",
|
|
||||||
"/logout: Fazer logout do bot",
|
|
||||||
"/list: Lista todas as aulas",
|
|
||||||
"/boletim <ID>: Pegue o boletim, junto com uma media final",
|
|
||||||
"/av <ID>: Fazer avaliação do aula pelo telegram (Limitado a algumas avaliações)",
|
|
||||||
];
|
|
||||||
bot.start(fn => fn.reply(helpStartMessage.join("\n"))).help(fn => fn.reply(helpStartMessage.join("\n")));
|
|
||||||
|
|
||||||
bot.command("login", async (ctx) => {
|
|
||||||
await ctx.deleteMessage(ctx.message.message_id);
|
|
||||||
if (await user.findOne({ telegramChats: ctx.message.chat.id })) return ctx.reply("Você já esta logado");
|
|
||||||
else if (ctx.args.length < 2) return ctx.reply("Login invalido, /login <RA/Semestre> <senha>");
|
|
||||||
const [ username, pass ] = ctx.args;
|
|
||||||
const userDb = await user.findOne({ "colaborar.user": username });
|
|
||||||
if (!userDb) return ctx.reply("Usuario não existe ou não está cadastrado em nosso banco de dados!");
|
|
||||||
else if (await decrypt(userDb.colaborar.password) !== pass) return ctx.reply("Senha invalida!");
|
|
||||||
(userDb.telegramChats = (userDb.telegramChats || [])).push(ctx.message.chat.id);
|
|
||||||
await user.findOneAndUpdate({ _id: userDb._id }, { $set: { telegramChats: userDb.telegramChats } });
|
|
||||||
ctx.reply(`Olá novamente ${ctx.from.username}`);
|
|
||||||
return ctx;
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.command("logout", async (ctx) => {
|
|
||||||
if (await user.findOne({ telegramChats: ctx.message.chat.id })) {
|
|
||||||
await user.findOneAndUpdate({ telegramChats: ctx.message.chat.id }, { $pull: { telegramChats: ctx.message.chat.id } })
|
|
||||||
ctx.reply(`🖐️ tchau ${ctx.from.username}`);
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
ctx.reply("Você não está logado");
|
|
||||||
return ctx;
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.use(async (ctx, next) => {
|
|
||||||
ctx.User = await user.findOne({ telegramChats: ctx.chat.id });
|
|
||||||
if (!ctx.User) {
|
|
||||||
ctx.reply("Faça o login!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.command("list", async (ctx) => {
|
|
||||||
for (const semestreId in ctx.User.aulas) {
|
|
||||||
const message = [ `RA/Semestre: ${semestreId}` ];
|
|
||||||
for (const aulaId in ctx.User.aulas[semestreId]) message.push("", `ID: ${aulaId}\nName: ${ctx.User.aulas[semestreId][aulaId].name}`);
|
|
||||||
ctx.reply(message.join("\n"))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.command("boletim", async (ctx) => {
|
|
||||||
if (ctx.args.length === 0 || ctx.args[0] === "help") return ctx.reply("/boletim <ID da aula>\n\nVocê consegue pegar o ID da aula com o /list");
|
|
||||||
const semestreId = Object.keys(ctx.User.aulas).find(semestre => Object.keys(ctx.User.aulas[semestre]).some(s => s === ctx.args[0]));
|
|
||||||
if (!semestreId) return ctx.reply("Aula não está registrada!");
|
|
||||||
const { got } = await remoteRequest(ctx.User.colaborar.user, ctx.User.colaborar.password);
|
|
||||||
const bol = await Boletim(got, semestreId, ctx.args[0]);
|
|
||||||
|
|
||||||
// Media
|
|
||||||
await ctx.reply(`Sua media: ${bol.media.nota}`);
|
|
||||||
if (!bol.media.aprovado) {
|
|
||||||
const men = [ "Precisa melhorar essa media", "Vamos la ver alguns conteudos, para melhorar essa media sua" ];
|
|
||||||
await ctx.reply(men.at(men.length * Math.random())||men.at(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
const boletim = bol.boletim[0];
|
|
||||||
await ctx.reply(([ "Nota AVA:" ]).concat(Object.keys(boletim.ava).map(s => String().concat(" ", s, ": ", String(boletim.ava[s])))).join("\n"));
|
|
||||||
|
|
||||||
ctx.reply(boletim.atividades.map(s => {
|
|
||||||
let base = String().concat(s.type, " (", String(s.atividadeId) ,")", ":\n");
|
|
||||||
if (s.type === "Prova Presencial") {
|
|
||||||
base = base.concat(
|
|
||||||
" Acertou: ", String(s.acertos), "\n",
|
|
||||||
" Ponto: ", String(s.pontuacao), "\n",
|
|
||||||
" Minimo: ", String(s.pontuacaoMinina)
|
|
||||||
);
|
|
||||||
} else if (s.type === "Avaliação Virtual") {
|
|
||||||
base = base.concat(
|
|
||||||
" Pontos: ", String(s.pontos.pontuacao),
|
|
||||||
);
|
|
||||||
} else if (s.type === "Portfólio") {
|
|
||||||
base = base.concat(
|
|
||||||
" Pontos: ", String(s.pontuacao), "\n",
|
|
||||||
" Minimo: ", String(s.pontuacaoMinina),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base;
|
|
||||||
}).join("\n\n"));
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.command("av", async (ctx) => {
|
|
||||||
ctx.reply("ainda não funciona, estamos trabalhando nessa parte ainda!");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start
|
|
||||||
bot.launch();
|
|
||||||
console.log("Telegram bot started");
|
|
||||||
process.once("SIGINT", () => bot.stop("SIGINT"))
|
|
||||||
process.once("SIGTERM", () => bot.stop("SIGTERM"))
|
|
53
src/user.ts
53
src/user.ts
@ -1,53 +0,0 @@
|
|||||||
import { insertActivitysToCollection } from "./atividades.js";
|
|
||||||
import colab from "./lib/colabora_req.js";
|
|
||||||
import { database } from "./lib/db_connect.js";
|
|
||||||
import { Aulas, Cursos } from "./lib/sync.js";
|
|
||||||
|
|
||||||
export type User = {
|
|
||||||
/** User ID */
|
|
||||||
userID: string;
|
|
||||||
|
|
||||||
/** Cookie token */
|
|
||||||
sessions: string[];
|
|
||||||
|
|
||||||
/** Telegram chat id */
|
|
||||||
telegramChats: number[];
|
|
||||||
|
|
||||||
/** Ananhanguera user auth */
|
|
||||||
colaborar: {
|
|
||||||
user: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Semestre -> Matricula -> Aulas[] */
|
|
||||||
aulas: Record<string, Record<string, { name: string; ids: string[] }>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const user = database.collection<User>("User");
|
|
||||||
|
|
||||||
export async function syncUser(userID: string, semestre?: string) {
|
|
||||||
const userData = await user.findOne({ userID });
|
|
||||||
if (!userData) throw new Error("");
|
|
||||||
const { got } = await colab(userData.colaborar.user, userData.colaborar.password);
|
|
||||||
const cursos = await Cursos(got)
|
|
||||||
if (typeof semestre === "string" && semestre.length > 0) {
|
|
||||||
userData.aulas[semestre] = {};
|
|
||||||
for (const aula of (await Aulas(got, semestre)).aulas) {
|
|
||||||
const ids = await insertActivitysToCollection(got, semestre, aula.id);
|
|
||||||
userData.aulas[semestre][aula.id] = { name: aula.title, ids };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const curso of cursos) {
|
|
||||||
for (const { semestreID } of curso.semestres) {
|
|
||||||
userData.aulas[semestreID] = {};
|
|
||||||
for (const aula of (await Aulas(got, semestreID)).aulas) {
|
|
||||||
const ids = await insertActivitysToCollection(got, semestreID, aula.id);
|
|
||||||
userData.aulas[semestreID][aula.id] = { name: aula.title, ids };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await user.findOneAndReplace({ _id: userData._id }, userData);
|
|
||||||
return userData;
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user