commit db3f0dbca503fe13132362e41adc6118ae3ffdaf Author: Daniel Bulant Date: Wed Oct 6 08:12:14 2021 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4624a2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +config.yml +package-lock.json +pnpm-lock.yaml +settings.sqlite \ No newline at end of file diff --git a/commands/ssps/rozvrh.js b/commands/ssps/rozvrh.js new file mode 100644 index 0000000..dfbcd9d --- /dev/null +++ b/commands/ssps/rozvrh.js @@ -0,0 +1,64 @@ +const commando = require("@iceprod/discord.js-commando"); +const { MessageEmbed } = require("discord.js"); +const api = require("../../utils/api"); + +/** + * @typedef {T extends PromiseLike ? U : T} Depromise + * @template T + */ +/** @type {Record>>} */ +var cache = {}; + +var map = { + "1K": "1Y" +} + +module.exports = class rozvrh extends commando.Command { + constructor(client) { + super(client, { + name: "rozvrh", + memberName: "rozvrh", + group: "ssps", + description: "Zobrazí rozvrh hodin pro danou třídu", + args: [{ + key: "className", + type: "string", + prompt: "Jakou třídu chcete zvolit?", + validate(val) { + return /^[1-4]\.?[ABCKGL]$/i.test(val); + } + }] + }); + } + + async run(msg, { className }) { + className = className.replace(".", "").toUpperCase(); + className = map[className]; + if(!className) return msg.reply("Třída není podporovaná."); + if(!cache[className]) { + cache[className] = await api.getSchedule(className); + } + + const date = new Date; + const dayOfWeek = (date.getDay() > 1 && date.getDay() < 6 ? date.getDay() : 1) - 1; + const schedule = cache[className].schedule[dayOfWeek]; + + const embed = new MessageEmbed(); + embed.setTitle("Rozvrh"); + + for(const cellI in schedule) { + const cell = schedule[cellI]; + if(!cell) { + console.log("Wut?", cellI, cell); + continue; + } + try { + embed.addField(cell.Subject.Abbrev, `${cell.Room.Abbrev} - ${cell.Teacher.Name}`); + } catch(e) { + console.warn(e, cellI, cell); + } + } + + return msg.reply(embed); + } +}; \ No newline at end of file diff --git a/commands/ssps/suplovani.js b/commands/ssps/suplovani.js new file mode 100644 index 0000000..64f5c05 --- /dev/null +++ b/commands/ssps/suplovani.js @@ -0,0 +1,17 @@ +const commando = require("@iceprod/discord.js-commando"); + +module.exports = class suplovani extends commando.Command { + constructor(client) { + super(client, { + name: "suplovani", + memberName: "suplovani", + group: "ssps", + description: "Zobrazí stav suplování", + args: [] + }); + } + + run(msg) { + return msg.reply("TBD"); + } +}; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..036e1da --- /dev/null +++ b/index.js @@ -0,0 +1,38 @@ +const Commando = require('@iceprod/discord.js-commando'); +const sqlite = require('sqlite'); +const sqlite3 = require("sqlite3"); +const yaml = require("js-yaml"); +const fs = require("fs-extra"); +const path = require("path"); + +const config = yaml.load(fs.readFileSync("./config.yml", { encoding: "utf-8" })); + +const client = new Commando.Client({ + owner: '820696421912412191', + commandPrefix: "ssps!" +}); + +client.on("commandError", (c, e) => { + console.error(e); +}); + +client.on("ready", () => { + console.log("Ready"); +}); + +client.registry + .registerGroups([ + ["ssps", "Příkazy pro SSPŠ"], + ["nastaveni", "Nastavení bota"] + ]) + .registerDefaults() + .registerCommandsIn(path.join(__dirname, 'commands')); + +client.setProvider( + sqlite.open({ + driver: sqlite3.Database, + filename: path.join(__dirname, 'settings.sqlite3') + }).then(db => new Commando.SQLiteProvider(db)) +).catch(console.error); + +client.login(config.token); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b66cdf4 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "@iceprod/discord.js-commando": "^0.14.4", + "discord.js": "12", + "fs-extra": "^10.0.0", + "js-yaml": "^4.1.0", + "node-fetch": "2", + "sqlite": "^4.0.23", + "sqlite3": "^5.0.2" + } +} diff --git a/settings.sqlite3 b/settings.sqlite3 new file mode 100644 index 0000000..0ae24e8 Binary files /dev/null and b/settings.sqlite3 differ diff --git a/utils/api.js b/utils/api.js new file mode 100644 index 0000000..991ecf0 --- /dev/null +++ b/utils/api.js @@ -0,0 +1,115 @@ +const fetch = require("node-fetch"); + +async function request(endpoint, body) { + const res = await fetch("https://www.ssps.cz/" + endpoint, { + method: body ? "POST" : "GET", + body, + headers: { + accept: "application/json", + "user-agent": "Discord Bot (https://danbulant.eu Daniel Bulant bulant.da.2021@ssps.cz)", + "x-notes": "Zobrazeni informaci o skole v ramci Discordu" + } + }); + return await res.json(); +} + +/** + * @typedef AbsentClass + * @property {ClassEntity} Entity + * @property {(null | "ODP")[]} Reasons + */ +/** + * @typedef ClassEntity + * @property {string} Id + * @property {string} Abbrev + */ +/** + * @typedef ChangedClass + * @property {ClassEntity} Class + * @property {ChangedLesson[]} ChangedLessons + * @property {ChangedLesson[]} CancelledLessons + * @property {string[]} ChangedGroups + */ +/** + * @typedef ChangedLesson + * @property {"supluje" | "přesun >>" | "přesun <<" | "spojí"} ChgType1 + * @property {string} ChgType2 + * @property {string} Hour Číslo ale ve stringu + * @property {string} Subject 3 letter code in uppercase + * @property {string} Group Empty if all + * @property {string} [Room] Room number + * @property {string} [Teacher] Teacher code (Abbrev) + */ + +class Supplementations { + constructor(data) { + this.data = data; + } + + getByClassName(name) { + /** @type {AbsentClass[]} */ + const absent = this.data.AbsentClasses.filter(t => t.Entity.Abbrev === name); + /** @type {ChangedClass[]} */ + const changed = this.data.ChangesForClasses.filter(t => t.Class.Abbrev === name); + return { absent, changed }; + } + + getByTeacher(abbrev) { + return this.data.ChangesForTeachers.filter(t => t.Teacher.Abbrev === abbrev || t.Teacher.Name === abbrev); + } +} + +/** + * @typedef Atom + * @property {string} Id + * @property {string} Abbrev + * @property {string} Name + */ +/** + * @typedef CellAtom + * @property {Atom} Class + * @property {Atom} Group + * @property {Atom} Subject + * @property {Atom} Teacher + * @property {Atom} Room + * @property {Atom[]} Cycles + * @property {Atom[]} Stamps + */ + +class Schedule { + /** @type {CellAtom[][]} */ + schedule = []; + constructor(data) { + this.data = data; + for(var cell of data.Cells) { + if(!this.schedule[cell.DayIndex]) this.schedule[cell.DayIndex] = []; + this.schedule[cell.DayIndex][cell.HourIndex] = cell.Atoms; + } + } + + get type() { + return this.data.Type; + } + + get id() { + return this.data.TargetId; + } +} + +class API { + request = request; + + async getSupplementations(date = new Date) { + const res = await request(`wp-content/themes/ssps-wordpress-theme/supplementation.php/?date=${date.getFullYear()}${date.getMonth().toString().padStart(2, "0")}${date.getDate().toString().padStart(2, "0")}`); + + return new Supplementations(res); + } + + async getSchedule(className) { + const res = await request(`wp-content/themes/ssps-wordpress-theme/schedule.php/?class=${className}`); + + return new Schedule(res); + } +} + +module.exports = new API; \ No newline at end of file