diff --git a/package.json b/package.json index c036c93..76154a9 100644 --- a/package.json +++ b/package.json @@ -5,21 +5,17 @@ "type": "module", "license": "MIT", "dependencies": { - "cookie": "^0.5.0", - "get-video-duration": "^4.1.0", - "got": "^13.0.0", - "jsdom": "^22.1.0", - "mongodb": "^6.2.0", - "neste": "^3.0.0", - "telegraf": "^4.15.0", - "turndown": "^7.1.2" + "cookie": "^0.6.0", + "got": "^14.0.0", + "jsdom": "^23.0.1", + "mongodb": "^6.3.0", + "neste": "^3.0.0" }, "devDependencies": { - "@types/cookie": "^0.5.3", - "@types/jsdom": "^21.1.4", - "@types/node": "^20.8.10", - "@types/turndown": "^5.0.3", + "@types/cookie": "^0.6.0", + "@types/jsdom": "^21.1.6", + "@types/node": "^20.10.3", "ts-node": "^10.9.1", - "typescript": "^5.2.2" + "typescript": "^5.3.2" } } diff --git a/src/atividades.ts b/src/atividades.ts deleted file mode 100644 index 48a87ee..0000000 --- a/src/atividades.ts +++ /dev/null @@ -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"); - -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>((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((acc, id) => { - const atividade = fn.detalheBoletimDto.detalheDisciplinarDtoList[id]; - let ActType: Exclude = "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 - }; -} \ No newline at end of file diff --git a/src/boletim.ts b/src/boletim.ts new file mode 100644 index 0000000..874909f --- /dev/null +++ b/src/boletim.ts @@ -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; + detalheDisciplinarDtoList: Record; + } +}[]; + +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(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((pontos, info) => pontos + info.pontuacao, 0), + } + }).reduce>((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, + }; +} \ No newline at end of file diff --git a/src/dashboard.ts b/src/dashboard.ts new file mode 100644 index 0000000..f35766e --- /dev/null +++ b/src/dashboard.ts @@ -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(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; + 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(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)); + } +}; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 75f26dd..6131347 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,261 +1,72 @@ -import neste, { parseBody } from "neste"; -import crypto from "node:crypto"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { Boletim, activity } from "./atividades.js"; -import { remoteRequest } from "./lib/colabora_req.js"; -import { decrypt, encrypt } from "./lib/password.js"; -import "./telegram.js"; -import { User, syncUser, user } from "./user.js"; +import neste from "neste"; +import boletim from "./boletim.js"; +import Dashboard, { Atividades, Aulas } from "./dashboard.js"; +import request, { ColaborareadRequest } from "./lib/colabora_req.js"; +import "./lib/db_connect.js"; +import { encrypt } from "./lib/password.js"; const app = neste(); +// Listen HTTP Server +app.listen(process.env.PORT||"3000", () => console.log(app.address())); + declare module "neste" { export interface Request { - User: User; + Got?: ColaborareadRequest; } } -app.post("/login", parseBody(), async (req, res) => { - const { username, password } = req.body; - const dbUser = await user.findOne({ "colaborar.user": username }); - 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 login - await remoteRequest(username, await encrypt(password)); - - // UUID unique - let userID: string; - while (!userID) { - userID = crypto.randomUUID(); - if (await user.findOne({ userID })) userID = undefined; - } - - let sessionid: string; - 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) { - return res.status(401).json({ - errors: [ - { - type: "auth", - message: "Não foi possivel autenticar no site do colaboraread, cheque seu login" - }, - { - type: "generic", - message: err.message||String(err) +app.use(async (req, res, next): Promise => { + if (typeof req.headers.authorization === "string" && req.headers.authorization.length > 8) { + if (req.headers.authorization.toLowerCase().startsWith("basic ")) { + try { + let password: string, username: string; + const decode = Buffer.from(req.headers.authorization.slice(6).trim(), "base64").toString("utf8"); + if (decode.indexOf(":") > 0) { + const i = decode.indexOf(":"); + username = decode.slice(0, i); + password = decode.slice(i+1); } - ] - }); - } -}); - -app.use(async (req, res, next) => { - 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; + req.Got = await request(username, await encrypt(password)); + return next(); + } catch (err) { + return res.status(400).json({ + errors: [ + { + type: "Auth", + message: "Usuario ou senha Errados" + }, + { + type: "colaborareadAuth", + message: err?.message||String(err) + } + ] + }); + } + } else if (req.headers.authorization.toLowerCase().startsWith("bearer ")) { + } else if (req.headers.authorization.toLowerCase().startsWith("token ")) { + req.headers.authorization.slice(6).trim(); } - if (req.User) return next(); } return res.status(401).json({ errors: [ { - type: "auth", - message: "Require Cookie sessionid or Authorization header!" + type: "Auth", + 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 -app.post("/", async (req, res) => { - const ids = await syncUser(req.User.userID); - return res.status(201).json(ids.aulas); -}); +// Semestres +app.get("/", async (req, res) => res.json(await Dashboard(req.Got))); -// Check -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!" } ] })); +// Boletim +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 -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})))); - -// 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); }) \ No newline at end of file +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))); \ No newline at end of file diff --git a/src/lib/colabora_req.ts b/src/lib/colabora_req.ts index 4884209..b5c3585 100644 --- a/src/lib/colabora_req.ts +++ b/src/lib/colabora_req.ts @@ -1,15 +1,29 @@ -import { decrypt } from "./password.js"; 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>; + reloadAuth: () => Promise; + got: Got; + gotDom: (url: string | URL, options?: OptionsOfTextResponseBody | OptionsOfBufferResponseBody) => Promise<{ + url: string; + headers: IncomingHttpHeaders; + jsdoc: JSDOM; + }>; +} /** * Create extends to got * @param username - Username 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 { password = await decrypt(password); const localCookie = new Map>() const defaultGot = got.extend({ @@ -17,27 +31,31 @@ export default async function remoteRequest(username: string, password: string) throwHttpErrors: true, encoding: "utf8", 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", - "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-platform": "\"Windows\"", - "sec-fetch-site": "none", - "sec-fetch-mode": "navigate", - "sec-fetch-user": "?1", "sec-fetch-dest": "document", - // "accept-encoding": "gzip, deflate, br", - "accept-language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7" + "sec-fetch-mode": "navigate", + "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: { - async getCookieString(url: string) { const data = localCookie.get((new URL(url)).hostname)||{}; return Object.keys(data).reduce((acc, k) => acc.concat(";", k, "=", data[k]), ""); }, + async getCookieString(url: string) { + const data = localCookie.get((new URL(url)).hostname)||{}; + return Object.keys(data).reduce((acc, k) => acc.concat(";", k, "=", data[k]), ""); + }, async setCookie(rawCookie: string, url: string) { const host = (new URL(url)).hostname if (!(localCookie.has(host))) localCookie.set(host, {}); const cookieData = cookie.parse(rawCookie); - ([ "Expires", "Max-Age", "Domain", "Path", "Secure", "HttpOnly", "SameSite" ]).forEach(k => delete cookieData[k]); 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]; } } @@ -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"); 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(); + 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 { - got: defaultGot.extend({ followRedirect: true }), cookies: localCookie, - reloadAuth + reloadAuth, + got: goto, + gotDom, }; } \ No newline at end of file diff --git a/src/lib/password.ts b/src/lib/password.ts index 90cfa52..99a2554 100644 --- a/src/lib/password.ts +++ b/src/lib/password.ts @@ -2,6 +2,7 @@ import crypto from "node:crypto"; import util from "node:util"; const scrypt = util.promisify(crypto.scrypt) as (password: crypto.BinaryLike, salt: crypto.BinaryLike, keylen: number) => Promise; + export async function encrypt(text: string): Promise { const iv = crypto.randomBytes(16), secret = crypto.randomBytes(24); const key = await scrypt(secret, "salt", 24); diff --git a/src/lib/sync.ts b/src/lib/sync.ts deleted file mode 100644 index acaea7f..0000000 --- a/src/lib/sync.ts +++ /dev/null @@ -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(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 => ({ - 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; - 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>((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((acc, id) => { - const atividade = fn.detalheBoletimDto.detalheDisciplinarDtoList[id]; - let ActType: Exclude = "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((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((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((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((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((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((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((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((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(); -} \ No newline at end of file diff --git a/src/telegram.ts b/src/telegram.ts deleted file mode 100644 index cba7109..0000000 --- a/src/telegram.ts +++ /dev/null @@ -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(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 ", - "/logout: Fazer logout do bot", - "/list: Lista todas as aulas", - "/boletim : Pegue o boletim, junto com uma media final", - "/av : 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 "); - 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 \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")) \ No newline at end of file diff --git a/src/user.ts b/src/user.ts deleted file mode 100644 index 2062bd3..0000000 --- a/src/user.ts +++ /dev/null @@ -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>; -}; - -export const user = database.collection("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; -} \ No newline at end of file