🎉 Mostly working matrix bot
(timetable group handling missing)
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
.env
|
||||
data.json
|
280
index.js
Normal file
280
index.js
Normal file
@ -0,0 +1,280 @@
|
||||
import * as sdk from "matrix-js-sdk";
|
||||
import fetch from "node-fetch";
|
||||
import "dotenv/config";
|
||||
import { JsonDB, Config } from "node-json-db";
|
||||
import { TimetableClient } from "./timetable.js";
|
||||
|
||||
const db = new JsonDB(new Config("data", true, true, "/"));
|
||||
const timetable = new TimetableClient(
|
||||
process.env.TIMETABLE_ENDPOINT,
|
||||
process.env.TIMETABLE_TOKEN
|
||||
);
|
||||
|
||||
await db.push("/rooms", {}, false);
|
||||
|
||||
// Login
|
||||
const userId = process.env.MATRIX_USER;
|
||||
const client = sdk.createClient({
|
||||
baseUrl: process.env.MATRIX_URL,
|
||||
accessToken: process.env.MATRIX_TOKEN,
|
||||
userId: userId,
|
||||
fetchFn: fetch,
|
||||
});
|
||||
|
||||
client.once("sync", function (state) {
|
||||
if (state === "PREPARED") {
|
||||
console.log("sync finished");
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-join invited rooms
|
||||
client.on("RoomMember.membership", function (_, member) {
|
||||
if (member.membership === "invite" && member.userId === userId) {
|
||||
client
|
||||
.joinRoom(member.roomId)
|
||||
.then(function () {
|
||||
console.log("Auto-joined %s", member.roomId);
|
||||
})
|
||||
.catch(() => {
|
||||
client.leave(member.roomId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function formatUpdateMessage(update) {
|
||||
let message = "";
|
||||
if (update.type == "addition") message += "🆕";
|
||||
else if (update.type == "deletion") message += "🗑";
|
||||
else if (update.type == "change") message += "✏";
|
||||
|
||||
if (update.type == "addition" || update.type == "deletion") {
|
||||
message += " ";
|
||||
if (update.change.type == "cancellation") message += "🚫";
|
||||
else message += "📑";
|
||||
}
|
||||
message += ` <b>${update.lesson}th Lesson</b><br><ul>\n`;
|
||||
|
||||
if (update.type == "addition" || update.type == "deletion") {
|
||||
message += `\n<li><b>Subject:</b> ${
|
||||
update.change.change.subject || "Unknown"
|
||||
}`;
|
||||
if (update.change.change.teacher) {
|
||||
message += `</li>\n<li><b>Teacher:</b> <del>${
|
||||
update.change.teacher || "Unknown"
|
||||
}</del> ${update.change.change.teacher}`;
|
||||
} else {
|
||||
message += `</li>\n<li><b>Teacher:</b> ${
|
||||
update.change.teacher || "Unknown"
|
||||
}`;
|
||||
}
|
||||
message += `</li>\n<li><b>Room:</b> ${
|
||||
update.change.change.room || "Unknown"
|
||||
}`;
|
||||
if (update.change.notes) {
|
||||
message += `</li>\n<li><b>Notes:</b></li>\n${update.change.notes}`;
|
||||
}
|
||||
} else {
|
||||
for (const change of Object.keys(update.change)) {
|
||||
const changeValue = update.change[change];
|
||||
message += `<br>\n${
|
||||
change.charAt(0).toUpperCase() + change.slice(1)
|
||||
}: <del>${changeValue.before}</del> ${changeValue.after}`;
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
// Check for timetable updates
|
||||
setTimeout(async () => {
|
||||
const history = await timetable.getUpdates();
|
||||
const lastCheck = await db.getObjectDefault("/lastTimetableUpdate", 0);
|
||||
const updates = history.filter((entry) => entry.updatedAt > lastCheck);
|
||||
|
||||
await db.push("/lastTimetableUpdate", new Date().getTime());
|
||||
|
||||
const rooms = await db.getData("/rooms");
|
||||
for (const update of updates) {
|
||||
for (const roomId of Object.keys(rooms)) {
|
||||
const room = rooms[roomId];
|
||||
if (!update.class.includes(room.filter)) continue;
|
||||
console.log(update);
|
||||
|
||||
let message = formatUpdateMessage(update);
|
||||
client.sendHtmlMessage(roomId, plainText(message), message);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Handle events
|
||||
client.on("Room.timeline", async function (event, room) {
|
||||
if (event.getLocalAge() > 5000) return;
|
||||
if (event.event.sender == userId) return;
|
||||
try {
|
||||
if (event.getType() === "m.room.message") {
|
||||
await handleMessage(event, room.roomId);
|
||||
} else if (
|
||||
event.getType() === "m.reaction" ||
|
||||
event.getType() === "m.room.redaction"
|
||||
) {
|
||||
await handleReaction(event, room.roomId);
|
||||
}
|
||||
} catch (e) {
|
||||
client.sendTextMessage(
|
||||
room.roomId,
|
||||
"⛔ Your command failed executing! Please try again"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function plainText(message) {
|
||||
return message.replace("<li>", "- ").replace(/<[^>]+>/g, "");
|
||||
}
|
||||
|
||||
function sendReaction(room, toEvent, text) {
|
||||
client.sendEvent(room, "m.reaction", {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.annotation",
|
||||
event_id: toEvent,
|
||||
key: text,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleMessage(event, room) {
|
||||
const body = event.event.content.body;
|
||||
const sender = event.event.sender;
|
||||
|
||||
const userState = await db.getObjectDefault(`/rooms/${room}/state`);
|
||||
|
||||
if (!userState) {
|
||||
if (body == "help") {
|
||||
// Send help message
|
||||
const helpMessage = `<b>Available commands</b>:<ul>
|
||||
<li> <code>help</code>: List available commands </li>
|
||||
<li> <code>info</code>: Get information about the current room's configuration </li>
|
||||
<li> <code>filter [class (eg. 10c)]</code>: Set the class to filter for. Use only filter available in the timetable v2 app.</li>
|
||||
<li> <code>timetable</code>: Set your timetable</li>
|
||||
<li> <code>groups</code>: Configure your timetable groups</li>
|
||||
<li> <code>reset</code>: Reset the configuration for this room</li>
|
||||
`;
|
||||
client.sendHtmlMessage(room, plainText(helpMessage), helpMessage);
|
||||
} else if (body == "info") {
|
||||
// Send room information
|
||||
const roomData = await db.getObjectDefault(`/rooms/${room}`);
|
||||
if (!roomData) {
|
||||
client.sendTextMessage(
|
||||
room,
|
||||
"⛔ This room has no configuration data yet"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const response = `<b>Current Room Configuration</b>:
|
||||
<br>Filter: <code>${roomData.filter || "None"}</code>
|
||||
<br>Timetable ID: <code>${roomData.timetable || "Not set"}</code>
|
||||
<br>Timetable Groups: <code>${(roomData.groups || []).join(", ")}</code>`;
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
} else if (body.startsWith("filter ")) {
|
||||
// Set filtering
|
||||
const filter = body.split(" ")[1].toLowerCase();
|
||||
const response = `👍 Now filtering for <code>${filter}</code>`;
|
||||
await db.push(`/rooms/${room}/filter`, filter);
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
} else if (body == "timetable") {
|
||||
// Timetable
|
||||
if (!(await db.getObjectDefault(`/rooms/${room}/filter`))) {
|
||||
client.sendTextMessage(room, "⛔ You need to configure a filter first");
|
||||
return;
|
||||
}
|
||||
// Query timetables
|
||||
const timetables = await timetable.getTimetables(
|
||||
await db.getData(`/rooms/${room}/filter`)
|
||||
);
|
||||
await db.push(`/rooms/${room}/state`, "timetable");
|
||||
let response = `🛠 Which timetable do you want to use?<ul>\n`;
|
||||
// Build response message
|
||||
for (const timetable of timetables.timetables) {
|
||||
response += `<li><code>${timetable.id}</code>: ${timetable.title}</li>\n`;
|
||||
}
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
} else if (body == "groups") {
|
||||
if (!(await db.getObjectDefault(`/rooms/${room}/timetable`))) {
|
||||
client.sendTextMessage(room, "⛔ You need to select a timetable first");
|
||||
return;
|
||||
}
|
||||
// Timetable groups
|
||||
await db.push(`/rooms/${room}/state`, "groups");
|
||||
await db.push(`/rooms/${room}/stateData/sender`, event.sender.userId);
|
||||
const groups = await timetable.getGroups(
|
||||
await db.getData(`/rooms/${room}/filter`),
|
||||
await db.getData(`/rooms/${room}/timetable`)
|
||||
);
|
||||
let response = `🛠 Set your timetable groups by reacting with the correct group. React with ✅ to confirm your selection.<ul>`;
|
||||
const message = await client.sendHtmlMessage(
|
||||
room,
|
||||
plainText(response),
|
||||
response
|
||||
);
|
||||
for (const group of groups) {
|
||||
sendReaction(room, message.event_id, group);
|
||||
}
|
||||
sendReaction(room, message.event_id, "✅");
|
||||
} else if (body == "reset") {
|
||||
// Reset room
|
||||
await db.delete(`/rooms/${room}`);
|
||||
const response = "⚠ The configuration for this room was reset!";
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
} else {
|
||||
const response =
|
||||
"Unknown command! Type <code>help</code> for a list of valid commands";
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
}
|
||||
} else {
|
||||
// If user is in setup mode
|
||||
if (body == "abort") {
|
||||
await db.delete(`/rooms/${room}/state`);
|
||||
const response = `❌ Aborted current action`;
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
} else if (userState == "timetable") {
|
||||
const timetable = parseInt(body);
|
||||
if (!timetable) {
|
||||
client.sendTextMessage(room, "Please enter the timetable id!");
|
||||
return;
|
||||
}
|
||||
await db.push(`/rooms/${room}/timetable`, timetable);
|
||||
await db.delete(`/rooms/${room}/state`);
|
||||
const response = `✅ Now using timetable <code>${timetable}</code>`;
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
} else {
|
||||
const response =
|
||||
"⚠ You are currently in setup mode. Complete the setup or type <code>abort</code> to execute other commands.";
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReaction(event, room) {
|
||||
const state = await db.getObjectDefault(`/rooms/${room}/state`);
|
||||
if (state == "groups") {
|
||||
const stateData = await db.getData(`/rooms/${room}/stateData`);
|
||||
if (event.sender.userId != stateData.sender) return;
|
||||
if (event.event.content["m.relates_to"].key == "✅") {
|
||||
await db.push(`/rooms/${room}/groups`, Object.values(stateData.groups));
|
||||
await db.delete(`/rooms/${room}/stateData`);
|
||||
await db.delete(`/rooms/${room}/state`);
|
||||
const response = `✅ Now using timetable groups <code>${Object.values(
|
||||
stateData.groups
|
||||
).join(", ")}</code>`;
|
||||
client.sendHtmlMessage(room, plainText(response), response);
|
||||
} else if (event.getType() === "m.reaction") {
|
||||
await db.push(
|
||||
`/rooms/${room}/stateData/groups/${event.event.event_id}`,
|
||||
event.event.content["m.relates_to"].key
|
||||
);
|
||||
} else {
|
||||
await db.delete(`/rooms/${room}/stateData/groups/${event.event.redacts}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.startClient(0);
|
469
package-lock.json
generated
Normal file
469
package-lock.json
generated
Normal file
@ -0,0 +1,469 @@
|
||||
{
|
||||
"name": "timetable-v2-notify",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "timetable-v2-notify",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.3.1",
|
||||
"matrix-js-sdk": "^26.1.0",
|
||||
"node-fetch": "^3.3.1",
|
||||
"node-json-db": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
|
||||
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@matrix-org/matrix-sdk-crypto-js": {
|
||||
"version": "0.1.0-alpha.11",
|
||||
"resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.11.tgz",
|
||||
"integrity": "sha512-HD3rskPkqrUUSaKzGLg97k/bN+OZrkcX7ODB/pNBs/jqq+/A0wDKqsszJotzFwsQcDPpWn78BmMyvBo4tLxKjw==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/events": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
||||
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
|
||||
},
|
||||
"node_modules/@types/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
|
||||
},
|
||||
"node_modules/another-json": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz",
|
||||
"integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg=="
|
||||
},
|
||||
"node_modules/base-x": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
|
||||
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
|
||||
},
|
||||
"node_modules/bs58": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
|
||||
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
|
||||
"dependencies": {
|
||||
"base-x": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/motdotla/dotenv?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/loglevel": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
|
||||
"integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/loglevel"
|
||||
}
|
||||
},
|
||||
"node_modules/matrix-events-sdk": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz",
|
||||
"integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="
|
||||
},
|
||||
"node_modules/matrix-js-sdk": {
|
||||
"version": "26.1.0",
|
||||
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-26.1.0.tgz",
|
||||
"integrity": "sha512-2isNpMFCR0hV8wNub4GJ/+8lIbz/jag12gf1P8rU++PfLFpeObooIDI3nnffBGLV5biZ3iLkaLOKAl33dSxchA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.10",
|
||||
"another-json": "^0.2.0",
|
||||
"bs58": "^5.0.0",
|
||||
"content-type": "^1.0.4",
|
||||
"loglevel": "^1.7.1",
|
||||
"matrix-events-sdk": "0.0.1",
|
||||
"matrix-widget-api": "^1.3.1",
|
||||
"p-retry": "4",
|
||||
"sdp-transform": "^2.14.1",
|
||||
"unhomoglyph": "^1.0.6",
|
||||
"uuid": "9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/matrix-widget-api": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.4.0.tgz",
|
||||
"integrity": "sha512-dw0dRylGQzDUoiaY/g5xx1tBbS7aoov31PRtFMAvG58/4uerYllV9Gfou7w+I1aglwB6hihTREzKltVjARWV6A==",
|
||||
"dependencies": {
|
||||
"@types/events": "^3.0.0",
|
||||
"events": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
|
||||
"integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/node-json-db": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-2.2.0.tgz",
|
||||
"integrity": "sha512-GBuXPjItZ+XivMxR7bhSjVeXKiHZKRN/HbvqrwVemV6V5atcnIIZbg2J2vJHE6RYHydtPCJoUy05YaK4wQO0TA==",
|
||||
"dependencies": {
|
||||
"rwlock": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p-retry": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||
"dependencies": {
|
||||
"@types/retry": "0.12.0",
|
||||
"retry": "^0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"node_modules/retry": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/rwlock": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rwlock/-/rwlock-5.0.0.tgz",
|
||||
"integrity": "sha512-XgzRqLMfCcm9QfZuPav9cV3Xin5TRcIlp4X/SH3CvB+x5D2AakdlEepfJKDd8ByncvfpcxNWdRZVUl38PS6ZJg=="
|
||||
},
|
||||
"node_modules/sdp-transform": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz",
|
||||
"integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==",
|
||||
"bin": {
|
||||
"sdp-verify": "checker.js"
|
||||
}
|
||||
},
|
||||
"node_modules/unhomoglyph": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz",
|
||||
"integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
|
||||
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"@matrix-org/matrix-sdk-crypto-js": {
|
||||
"version": "0.1.0-alpha.11",
|
||||
"resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.11.tgz",
|
||||
"integrity": "sha512-HD3rskPkqrUUSaKzGLg97k/bN+OZrkcX7ODB/pNBs/jqq+/A0wDKqsszJotzFwsQcDPpWn78BmMyvBo4tLxKjw=="
|
||||
},
|
||||
"@types/events": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
||||
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
|
||||
},
|
||||
"@types/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
|
||||
},
|
||||
"another-json": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz",
|
||||
"integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg=="
|
||||
},
|
||||
"base-x": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
|
||||
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
|
||||
},
|
||||
"bs58": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
|
||||
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
|
||||
"requires": {
|
||||
"base-x": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
|
||||
},
|
||||
"data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ=="
|
||||
},
|
||||
"events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
|
||||
},
|
||||
"fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"requires": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"requires": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"loglevel": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
|
||||
"integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg=="
|
||||
},
|
||||
"matrix-events-sdk": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz",
|
||||
"integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="
|
||||
},
|
||||
"matrix-js-sdk": {
|
||||
"version": "26.1.0",
|
||||
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-26.1.0.tgz",
|
||||
"integrity": "sha512-2isNpMFCR0hV8wNub4GJ/+8lIbz/jag12gf1P8rU++PfLFpeObooIDI3nnffBGLV5biZ3iLkaLOKAl33dSxchA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.10",
|
||||
"another-json": "^0.2.0",
|
||||
"bs58": "^5.0.0",
|
||||
"content-type": "^1.0.4",
|
||||
"loglevel": "^1.7.1",
|
||||
"matrix-events-sdk": "0.0.1",
|
||||
"matrix-widget-api": "^1.3.1",
|
||||
"p-retry": "4",
|
||||
"sdp-transform": "^2.14.1",
|
||||
"unhomoglyph": "^1.0.6",
|
||||
"uuid": "9"
|
||||
}
|
||||
},
|
||||
"matrix-widget-api": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.4.0.tgz",
|
||||
"integrity": "sha512-dw0dRylGQzDUoiaY/g5xx1tBbS7aoov31PRtFMAvG58/4uerYllV9Gfou7w+I1aglwB6hihTREzKltVjARWV6A==",
|
||||
"requires": {
|
||||
"@types/events": "^3.0.0",
|
||||
"events": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
|
||||
"integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
|
||||
"requires": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
}
|
||||
},
|
||||
"node-json-db": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-2.2.0.tgz",
|
||||
"integrity": "sha512-GBuXPjItZ+XivMxR7bhSjVeXKiHZKRN/HbvqrwVemV6V5atcnIIZbg2J2vJHE6RYHydtPCJoUy05YaK4wQO0TA==",
|
||||
"requires": {
|
||||
"rwlock": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"p-retry": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||
"requires": {
|
||||
"@types/retry": "0.12.0",
|
||||
"retry": "^0.13.1"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"retry": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="
|
||||
},
|
||||
"rwlock": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rwlock/-/rwlock-5.0.0.tgz",
|
||||
"integrity": "sha512-XgzRqLMfCcm9QfZuPav9cV3Xin5TRcIlp4X/SH3CvB+x5D2AakdlEepfJKDd8ByncvfpcxNWdRZVUl38PS6ZJg=="
|
||||
},
|
||||
"sdp-transform": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz",
|
||||
"integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw=="
|
||||
},
|
||||
"unhomoglyph": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz",
|
||||
"integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||
},
|
||||
"web-streams-polyfill": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
|
||||
}
|
||||
}
|
||||
}
|
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "timetable-v2-notify",
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "minie4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.3.1",
|
||||
"matrix-js-sdk": "^26.1.0",
|
||||
"node-fetch": "^3.3.1",
|
||||
"node-json-db": "^2.2.0"
|
||||
}
|
||||
}
|
74
timetable.js
Normal file
74
timetable.js
Normal file
@ -0,0 +1,74 @@
|
||||
import fetch from "node-fetch";
|
||||
|
||||
export class TimetableClient {
|
||||
constructor(apiEndpoint, token) {
|
||||
this.apiEndpoint = apiEndpoint;
|
||||
this.token = token;
|
||||
}
|
||||
login(password) {
|
||||
fetch(
|
||||
this.apiEndpoint + `/token?password=${encodeURIComponent(password)}`,
|
||||
{ method: "post" }
|
||||
)
|
||||
.then((e) => {
|
||||
if (e.status == 401) {
|
||||
console.warn("Invalid Timetable V2 password!");
|
||||
return;
|
||||
} else if (e.status != 200) {
|
||||
console.log("Failed requesting Timetable V2 API-Token!");
|
||||
return;
|
||||
}
|
||||
return e.json();
|
||||
})
|
||||
.then((token) => (this.token = token.token));
|
||||
}
|
||||
getTimetables(forClass) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(
|
||||
this.apiEndpoint +
|
||||
`/timetable?token=${encodeURIComponent(
|
||||
this.token
|
||||
)}&class=${encodeURIComponent(forClass)}`
|
||||
)
|
||||
.then((e) => {
|
||||
if (e.status != 200) reject();
|
||||
else return e.json();
|
||||
})
|
||||
.then((timetables) => resolve(timetables));
|
||||
});
|
||||
}
|
||||
async getGroups(timetableClass, timetableId) {
|
||||
const timetables = await fetch(
|
||||
this.apiEndpoint +
|
||||
`/timetable?token=${encodeURIComponent(
|
||||
this.token
|
||||
)}&class=${encodeURIComponent(timetableClass)}`
|
||||
);
|
||||
const timetable = (await timetables.json()).timetables.find(
|
||||
(e) => e.id == timetableId
|
||||
);
|
||||
|
||||
const foundTimetableGroups = [];
|
||||
if (!timetable.data) return [];
|
||||
// Make a list of all possible timetable groups
|
||||
// found in the current timetable
|
||||
for (const day of timetable.data) {
|
||||
for (const lesson of day) {
|
||||
if (Array.isArray(lesson) && lesson.length > 1) {
|
||||
for (const group of lesson) {
|
||||
if (group.group && !foundTimetableGroups.includes(group.group)) {
|
||||
foundTimetableGroups.push(group.group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundTimetableGroups;
|
||||
}
|
||||
async getUpdates() {
|
||||
const history = await fetch(
|
||||
this.apiEndpoint + "/history" + `?token=${encodeURIComponent(this.token)}`
|
||||
);
|
||||
return await history.json();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user