Skip to content

Commit

Permalink
Merge pull request #15 from bodhivb/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
bodhivb authored Jul 7, 2023
2 parents ad95b1e + 3a51323 commit 713d488
Show file tree
Hide file tree
Showing 36 changed files with 663 additions and 151 deletions.
38 changes: 31 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,43 @@

Pixel Schedule is an interactive gamification platform for school schedule. Here you can see where each teacher is and which lesson is being taught in the classroom. It is linked to the Magister app and keeps us informed of changes through fun interactive effects and notifications.

# Installation
# Access schedule data

Due to sensitive data, you need to install the API on your device and log in to allow the webapp to access your rooster.
To handle the school schedule data, we've created a small temporary workaround that connects our webapp directly to the Xedule API.
For a temporarily bypass of the standard cross-origin security, use the following command in CMD:

Here is the link where you can install:
https://github.com/bodhivb/pixel-schedule-api/ (WIP - Coming soon)
start chrome.exe --user-data-dir="c:/temp/chrome" --disable-web-security https://bodhivb.github.io/pixel-schedule/ --kiosk

After installation, you can view the Pixel Schedule at:
https://bodhivb.github.io/pixel-schedule/
Caution: don't disable web security on other sites without understanding their source code, as it can expose you to security risks.

# For developers

The webapp is written in TypeScript using [PixiJS](https://github.com/pixijs/pixijs) v7. When building the project, TypeScript files are compiles to JavaScript and all code is bundled into one minified file using [Webpack](https://github.com/webpack/webpack).

Feel free to view the source code and add something fun as a pull request to make the SintLucas Pixel-Schedule even cooler!

If you have any questions? Feel free to contact me via Discord [Bodhi#0001](https://discord.com/users/151423248020537345).
Your feedbacks are welcome! :)

# Desired outcomes

### Real-time Schedule

That teachers stay in the classroom where their lesson is, is not quite finished yet, when they are finished then the basic concept is finished!

### Interactive

However, it still lacks interactive feature, including clickable teacher profiles and random speech bubbles corresponding to their current lesson and other fun interactives. Feel free to suggest any other additions!

### Pixel Schedule API

Build an API that users can connect to via [SurfConext](https://wiki.surfnet.nl/display/surfconextdev/Documentation+for+Service+Providers) to access data on teachers presence, skills, and important notifications/changes.

### Display date

Display the date and time in the top left corner, resembling a lock screen or Wallpaper Engine. This allows for convenient access to useful default information while maintaining a visually pleasing background.

### Weather

Add weather integration, based on school location. Show corresponding weather conditions within the Pixel Schedule, such as rain when it's raining.

Furthermore, the app already includes real-time background changes for daytime and evening. Thanks to [@Martijndewin](https://github.com/Martijndewin) for his contribution to the development of this feature.
1 change: 1 addition & 0 deletions assets/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ body {
border: 0;
padding: 0;
overflow: hidden;
font-family: Arial, Helvetica, sans-serif;
}

/* OVERLAY CSS */
Expand Down
26 changes: 0 additions & 26 deletions assets/examples/teachers.json

This file was deleted.

12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions source/api/appointmentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { IXeduleAppointment } from "../interfaces/xedule/xeduleAppointment";
import { XeduleApi } from "./xeduleApi";

export class AppointmentApi extends XeduleApi<IXeduleAppointment> {
constructor() {
super("Appointment/Date/");
}

/**
* Get the appointment for the current week with selected attendee
* @returns
*/
public async getAppointment(...attendeeIds: number[]): Promise<any[]> {
// Week date
const today = new Date();
const nextWeek = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 7
);

return super.get(
this.dateToPath(today) +
"/" +
this.dateToPath(nextWeek) +
"/Attendee?id=" +
attendeeIds.join("&id=")
);
}

/**
* Convert date to xedule path format
* @param date
* @returns
*/
public dateToPath(date: Date) {
const day = date.getDate();
const month = date.getMonth() + 1; // January is 0
const year = date.getFullYear();

return `${year}-${month}-${day}`;
}
}
163 changes: 163 additions & 0 deletions source/api/authenticationApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";

interface ForwardResponse {
errorText: string;
forwardCount: number;
lastResponse: AxiosResponse;
}

interface LoginData {
username: string;
password: string;
}

interface AuthenticationResponse {
isSuccess: boolean;
errorMessage?: string;
token?: string;
}

export class AuthenticationApi {
private httpService: AxiosInstance;
private readonly authenticationUrl: string =
"https://myx-silu.xedule.nl/Authentication/sso/SSOLogin.aspx?ngsw-bypass=true";

constructor() {
this.httpService = axios.create({ withCredentials: true });
}

public async checkAuthStatus() {
return this.httpService.get(this.authenticationUrl);
}

/**
* Authenticated via SURFConext authentication and return token
* @param body login data
* @returns response result status with token
*/
public async authentication(
body: LoginData
): Promise<AuthenticationResponse> {
// Setup first request
const firstRequest: AxiosRequestConfig = {
method: "get",
url: this.authenticationUrl,
};

// Forward the request
const { errorText, lastResponse } = await this.createForwardRequest(
this.httpService,
firstRequest,
5,
body
);

// Error handling
if (errorText) {
return { isSuccess: false, errorMessage: errorText };
}
// Error handling
if (!lastResponse.config.url?.includes("sso.xedule.nl")) {
return { isSuccess: false, errorMessage: "Result is different" };
}

// Get the token
let token: string = (
lastResponse.request.path ?? lastResponse.request.responseURL
).toString();

if (token) {
if (token.includes("token=")) {
token = token.substring(token.indexOf("token=") + "token=".length);
}

if (token.includes("&ngsw-bypass=")) {
token = token.substring(0, token.indexOf("&ngsw-bypass="));
}

// Return token
return { isSuccess: true, token };
}

return {
isSuccess: false,
errorMessage: "This application is out of date",
};
}

/**
* createForwardRequest will be forwarded automatically if there is submit form in response
* @param axiosInstance
* @param firstRequest
* @param maxForwards
* @param loginData
* @returns The last response
*/
async createForwardRequest(
axiosInstance: AxiosInstance,
firstRequest: AxiosRequestConfig,
maxForwards: number = 5,
loginData: { username: string; password: string }
): Promise<ForwardResponse> {
let errorText = "";
let forwardCount = 0;
let lastResponse = await axiosInstance.request(firstRequest);
const dom = new DOMParser();

for (; forwardCount < maxForwards; forwardCount++) {
const document = dom.parseFromString(lastResponse.data, "text/html");
const forwardForm = document.querySelector("form");

// Stop forward if response has no form
if (!forwardForm) {
break;
}

// Stop forward if there are errors
const errorElement = document.getElementById("errorText");
if (errorElement && errorElement.innerHTML) {
errorText = errorElement.innerHTML;
break;
}

// Create a new config request
const config: AxiosRequestConfig<any> = {};
config.url = forwardForm.action;
config.method = forwardForm.method ?? "post";
config.data = {};

// Copy the form data into a new request
const inputs = forwardForm.querySelectorAll("input");
inputs.forEach((input) => (config.data[input.name] = input.value));

// Login page
if (lastResponse.data.includes("FormsAuthentication")) {
// Fill the username and password in
config.data["UserName"] = loginData.username;
config.data["Password"] = loginData.password;

if (config.url.startsWith(window.location.origin)) {
// Remove the origin from the url
config.url = config.url.substring(window.location.origin.length);
}

if (!config.url.startsWith("http")) {
// Fix the url with domain name
config.url = "https://adfsproxy.sintlucas.nl:443" + config.url;
}
}

// Stop forward if a new request is same as previous
if (lastResponse.config.url == config.url) {
errorText = "The request has been black holed.";
break;
}

// Convert json object to http query string
config.data = new URLSearchParams(config.data);
lastResponse = await axiosInstance.request(config);
}

return { errorText, forwardCount, lastResponse };
}
}
8 changes: 8 additions & 0 deletions source/api/classroomApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IXeduleClassroom } from "../interfaces/xedule/xeduleClassroom";
import { XeduleApi } from "./xeduleApi";

export class ClassroomApi extends XeduleApi<IXeduleClassroom> {
constructor() {
super("Attendee/Type/Classroom");
}
}
8 changes: 8 additions & 0 deletions source/api/teacherApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IXeduleTeacher } from "../interfaces/xedule/xeduleTeacher";
import { XeduleApi } from "./xeduleApi";

export class TeacherApi extends XeduleApi<IXeduleTeacher> {
constructor() {
super("Attendee/Type/Teacher");
}
}
35 changes: 35 additions & 0 deletions source/api/xeduleApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import axios from "axios";

export class XeduleApi<T = any> {
private baseUrl: string = "https://myx-silu.xedule.nl/api/";
private basePath: string;

public data: T[] = [];

/**
* Abstract class
* @param basePath The path to the api endpoint
*/
constructor(basePath: string) {
this.basePath = basePath;
}

/**
* Get the data from the xedule api
* @param path URL parameters to add to the base path
* @returns
*/
public async get(path: string = ""): Promise<T[]> {
const res = await axios.get(this.baseUrl + this.basePath + path, {
headers: { Authorization: "Bearer " + localStorage.getItem("token") },
});

if (res.status == 200 && res.data.result && res.data.result.length > 0) {
this.data = res.data.result;
} else {
//TODO error handling
}

return this.data;
}
}
13 changes: 11 additions & 2 deletions source/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ export default class App extends Application<HTMLCanvasElement> {

// Initialize GameManager
GameManager.init(this, overlay);

this.ticker.maxFPS = 90;

this.ticker.add((dt) => this.Update(dt));

// call updateMinute once every minute
setInterval(function(self) {
self.ticker.addOnce((dt) => self.UpdateMinute(dt));
}, 60 * 1000, this)
}

// Trigger when the browser window is resized.
Expand All @@ -32,4 +36,9 @@ export default class App extends Application<HTMLCanvasElement> {
private Update(dt: number) {
GameManager.instance.Update(dt);
}

private UpdateMinute(dt: number) {
GameManager.instance.UpdateMinute(dt);
}

}
Loading

0 comments on commit 713d488

Please sign in to comment.