support multiple users

multi-user
Jan 2021-03-26 10:51:30 +01:00
parent 4b31c83320
commit a9c3f42f17
8 changed files with 183 additions and 64 deletions

BIN
assets/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
assets/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,22 @@
export default class NotificationMessage {
constructor() {
this.title = '';
this.body = '';
this.footer = null;
this.isError = false;
this.isImportant = false;
this.color = 0xFF9A00;
}
setTitle(title: string) { this.title = title; return this; }
setBody(body: string) { this.body = body; return this; }
setFooter(footer: string | null) { this.footer = footer; return this; }
setColor(color?: string | number | null) { this.color = color == null ? 0xFF9A00 : color; return this; }
error(isError?: boolean | null) { this.isError = isError == null ? true : isError; return this; }
important(isImportant?: boolean | null) { this.isImportant = isImportant == null ? true : isImportant; return this; }
public title: string;
public body: string;
public footer: string | null;
public isError: boolean;
public isImportant: boolean;
public color: string | number;
}

14
src/class/User.ts Normal file
View File

@ -0,0 +1,14 @@
export default class User {
public creds: {
schoolName: string,
username: string,
password: string,
baseURL: string,
};
public connections: Array<{
type: string,
userID: string,
channelID: string,
}>;
public ID: string;
}

View File

@ -5,13 +5,8 @@ import Discord from 'discord.js';
import Enmap from 'enmap';
import fs from 'fs';
import * as WebUntis from 'webuntis';
const untis = new WebUntis.default(
process.env.SCHOOLNAME,
process.env.USERNAME,
process.env.PASSWORD,
process.env.BASEURL
);
import User from './class/User';
import NotificationMessage from './class/NotificationMessage';
console.log('Discord Bot: Logging in');
const client = new Discord.Client();
@ -21,56 +16,99 @@ client.on('ready', () => console.log(`Discord Bot: Logged in as ${client.user.ta
const seenMessages = new Enmap({ name: "seenMessages" });
const knownLessons = new Enmap({ name: "knownLessons" });
const defaultEmbedColor = 0xFF9A00;
const usersDB = new Enmap({ name: "users" }) as Enmap<string|number, User>;
const untisInstances = [] as Array<{ untis: WebUntis.default, user: User }>;
usersDB.forEach(user => untisInstances.push({
untis: new WebUntis.default(
user.creds.schoolName,
user.creds.username,
user.creds.password,
user.creds.baseURL
),
user: user
}));
// Periodically attempt to connect to untis and fetch timetable updates
let fetchUpdates = () => {
console.log('Untis: Logging in');
try {
untis.login()
.then(() => {
console.log(`Untis: Logged in`)
fs.readdirSync(`${__dirname}/modules`).filter(file => file.endsWith('.js')).forEach(file => {
require(`${__dirname}/modules/${file}`).run();
});
setTimeout(() => untis.logout().then(() => console.log('Untis: Logged out')), 10000);
});
} catch(e) {
console.error(e);
console.log(`Fetching ${untisInstances.length} users.`);
untisInstances.forEach(({ untis, user }) => {
try {
let embed = new Discord.MessageEmbed()
.setTitle('An error has occurred.')
.setDescription(`\`\`\`js\n${e}\`\`\``)
.setColor(0xff0000);
sendEmbed(embed);
// Create database entries
if (!knownLessons.get(user.ID)) knownLessons.set(user.ID, {});
if (!seenMessages.get(user.ID)) seenMessages.set(user.ID, {});
untis.login()
.then(() => {
fs.readdirSync(`${__dirname}/modules`).filter(file => file.endsWith('.js')).forEach(file => {
require(`${__dirname}/modules/${file}`).run(untis, user);
});
setTimeout(() => untis.logout().then(() => console.log('Untis: Logged out')), 10000);
});
} catch(e) {
console.error(e);
try {
sendMessage(new NotificationMessage()
.setTitle('An error has occurred.')
.setBody(`\`\`\`js\n${e}\`\`\``)
.error(),
user);
} catch(e) {
console.error(e);
}
}
}
});
}
fetchUpdates();
setInterval(fetchUpdates, 60000);
async function sendEmbed(embed: Discord.MessageEmbed) {
const channel = await client.channels.fetch(process.env.CHANNEL) as Discord.DMChannel;
if (!channel) throw `ERROR: Could not find channel`;
if (!embed.timestamp) embed.setTimestamp();
channel.send(embed).catch(console.error);
async function sendMessage(message: NotificationMessage, user: User) {
user.connections.forEach(async connection => {
switch(connection.type) {
case 'discord':
const channel = await client.channels.fetch(connection.channelID).catch(()=>{}) as Discord.DMChannel;
if (!channel) return console.log(`Could not find channel: ${channel?.id}`);
let embed = new Discord.MessageEmbed()
.setAuthor(message.title)
.setDescription(message.body)
.setColor(message.color);
if (message.footer) embed.setFooter(message.footer);
// Couldn't figure out how to attach the images in the assets folder, so I use hardcoded URLs instead
if (message.isImportant) {
embed.setAuthor(embed.author.name, 'https://cdn.discordapp.com/attachments/637695358373199879/824676476405940284/info.png');
}
if (message.isError) {
embed.color = 0xff0000;
embed.setAuthor(embed.author.name, 'https://cdn.discordapp.com/attachments/637695358373199879/824676465051828334/error.png' );
}
if (!embed.timestamp) embed.setTimestamp();
channel.send(embed)
.catch(console.error);
break;
default: console.log(`Unknown connection type: ${connection.type}`);
}
});
}
export default {
untis: untis,
untisInstances,
usersDB,
bot: client,
defaultEmbedColor: defaultEmbedColor,
sendEmbed: sendEmbed,
sendEmbed: sendMessage,
db: {
seenMessages: seenMessages,
knownLessons: knownLessons
}
};
};
// Run tasks in dist/tasks/*
fs.readdirSync(`${__dirname}/tasks`).filter(file => file.endsWith('.js')).forEach(file =>
require(`${__dirname}/tasks/${file}`));

View File

@ -1,23 +1,23 @@
import * as WebUntis from 'webuntis';
import main from '../index';
import Discord from 'discord.js';
const { bot, defaultEmbedColor, untis, db, sendEmbed } = main;
import User from '../class/User';
import NotificationMessage from '../class/NotificationMessage';
const { bot, untisInstances, db, sendEmbed } = main;
export async function run() {
export async function run(untis: WebUntis.default, user: User) {
let news = await untis.getNewsWidget(new Date(), true);
if (!news) return;
news.messagesOfDay.forEach(message => {
if (db.seenMessages.get(`${message.id}`)) return;
if (db.seenMessages.get(user.ID, `${message.id}`)) return;
console.log('New message found: ' + message.text);
let embed = new Discord.MessageEmbed()
.setAuthor('Notification')
.setColor(defaultEmbedColor)
let notif = new NotificationMessage()
.setTitle('Notification')
.setTitle(message.subject)
.setDescription(message.text.replace(/\<br\>/g, "\n"))
.setBody(message.text.replace(/\<br\>/g, "\n"))
.setFooter(`Notification ID: ${message.id}`);
sendEmbed(embed);
db.seenMessages.set(`${message.id}`, true);
sendEmbed(notif, user);
db.seenMessages.set(user.ID, true, `${message.id}`);
});
}

View File

@ -1,15 +1,16 @@
import * as WebUntis from 'webuntis';
import main from '../index';
import Discord from 'discord.js';
const { bot, defaultEmbedColor, untis, db, sendEmbed } = main;
import User from '../class/User';
import NotificationMessage from '../class/NotificationMessage';
const { bot, untisInstances, db, sendEmbed } = main;
export async function run() {
export async function run(untis: WebUntis.default, user: User) {
let timetable = await untis.getOwnTimetableForRange(new Date(Date.now() - 86400000), new Date(Date.now() + (86400000 * 7)), true);
let sentClasses = {};
timetable.forEach(lesson => {
let kLesson = db.knownLessons.get(`${lesson.id}`);
let kLesson = db.knownLessons.get(user.ID, `${lesson.id}`);
if (kLesson && hasChanged(lesson, kLesson)) {
if (sentClasses['' + (lesson.sg || lesson.su?.[0]?.id) + ' -- ' + lesson.date]) return;
let dateInfo = {
@ -24,10 +25,8 @@ export async function run() {
let weekDay = ['Sunday', 'Monday', 'Thursday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
let embed = new Discord.MessageEmbed()
.setTitle(`Lesson updated`)
.setColor(defaultEmbedColor)
.setAuthor(`${weekDay}, ${date.getDate()}. ${date.getMonth() + 1}.: ${lesson.sg || '(Unnamed lesson)'} - ${lesson.te[0].longname || 'No teacher'}`);
let notif = new NotificationMessage()
.setTitle(`${weekDay}, ${date.getDate()}. ${date.getMonth() + 1}.: ${lesson.sg || '(Unnamed lesson)'} - ${lesson.te[0].longname || 'No teacher'}`);
let desc = ``;
@ -69,15 +68,15 @@ export async function run() {
// Change the embed color when teacher ID is 0.
// Teacher ID 0 means that the class is canelled (at least on my school),
// although I don't know if this is always the case.
if (lesson.code == 'irregular' || lesson.te[0].id == 0) embed.setColor('A781B4');
if (lesson.code == 'irregular' || lesson.te[0].id == 0) notif.setColor('A781B4');
embed.setDescription(desc);
notif.setBody(desc);
sentClasses['' + (lesson.sg || lesson.su?.[0]?.id) + ' -- ' + lesson.date] = true;
sendEmbed(embed);
sendEmbed(notif, user);
console.log(`Sent timetable update`);
db.knownLessons.set(`${lesson.id}`, lesson);
db.knownLessons.set(user.ID, lesson, `${lesson.id}`);
}
else db.knownLessons.set(`${lesson.id}`, lesson);
else db.knownLessons.set(user.ID, lesson, `${lesson.id}`);
});
}

46
src/tasks/cleanDB.ts Normal file
View File

@ -0,0 +1,46 @@
import main from '../index';
const { db } = main;
/**
* To-Do:
* - Remove invalid entries from usersDB
* - Clean up seenMessages DB
*/
// Deletes old, unneeded entries from the database
const run = async () => {
// Known lessons DB
try {
let deleted = 0;
db.knownLessons.forEach(userDB => {
Object.entries(userDB).forEach(data => {
let key: string | number = data[0];
let entry: any = data[1];
if (!entry.date || unfuckDate(entry.date).getTime() < Date.now() - (86400000 * 2)) {
deleted++;
db.knownLessons.delete(key);
}
});
});
if (deleted > 0) console.log(`Cleared ${deleted} entries from knownLessons`);
} catch(e) {
console.error(e);
}
}
let unfuckDate = (date: number): Date => {
let dateInfo = {
year: Number(`${date}`.substr(0, 4)),
month: Number(`${date}`.substr(4, 2)),
day: Number(`${date}`.substr(6, 2))
}
let newDate = new Date()
newDate.setFullYear(dateInfo.year);
newDate.setMonth(dateInfo.month - 1);
newDate.setDate(dateInfo.day);
return newDate;
}
run();
setInterval(run, 1000 * 60 * 60 * 12); // Run every 12 hours