Skip to content

Commit

Permalink
Added automatic API errors resolution mechanism (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
loumadev committed Jan 23, 2022
1 parent 1267916 commit 25ce9dc
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 83 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ class Edupage extends RawData {
// any of the "refresh" methods, if `_update` is set to `true`)

async uploadAttachment(filepath: string): Promise<Attachment>;
async api(options: APIOptions): Promise<RawDataObject | string>;
async api(options: APIOptions, _count: number = 0): Promise<RawDataObject | string, Error | {retry: true, count: number}>;

scheduleSessionPing(): void;
async pingSession(): Promise<boolean>;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "edupage-api",
"version": "0.8.0",
"version": "0.8.1",
"description": "Simple node.js package to manage your EduPage account.",
"main": "index.js",
"scripts": {
Expand Down
45 changes: 29 additions & 16 deletions src/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,23 +128,36 @@ class Application extends RawData {
* @memberof Application
*/
async post(parameters = {}, draftId = null) {
const res = await this.edupage.api({
url: ENDPOINT.DASHBOARD_GCALL,
method: "POST",
type: "text",
data: new URLSearchParams({
gpid: draftId || await this.createDraft(),
gsh: this.edupage.ASC.gsecHash,
action: "create",
_LJSL: "2052", //Seems like some -static- (it might vary) parameter (Loaded JS Libraries), keep it for a safety
...parameters
}).toString(),
encodeBody: false
return new Promise((resolve, reject) => {
const tryFetch = async _count => {
this.edupage.api({
url: ENDPOINT.DASHBOARD_GCALL,
method: "POST",
type: "text",
data: new URLSearchParams({
gpid: draftId || await this.createDraft(),
gsh: this.edupage.ASC.gsecHash,
action: "create",
_LJSL: "2052", //Seems like some -static- (it might vary) parameter (Loaded JS Libraries), keep it for a safety
...parameters
}).toString(),
encodeBody: false
}).then(res => {
const success = /"border_error":\s*false/.test(res) && !/"border_error":\s*true/.test(res);

resolve(success);
}).catch(err => {
if(err.retry) {
debug(`[Application] Got retry signal, retrying...`);
tryFetch(err.count + 1);
} else {
error(`[Application] Could not post application`, err);
reject(new EdupageError("Failed to post application: " + err.message));
}
});
};
tryFetch(-1);
});

const success = /"border_error":\s*false/.test(res) && !/"border_error":\s*true/.test(res);

return success;
}

/**
Expand Down
111 changes: 68 additions & 43 deletions src/Edupage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const debug = require("debug")("edupage:log");
const warn = require("debug")("edupage:warn");
const error = require("debug")("edupage:error");
const fs = require("fs");
const stream = require("stream");
Expand Down Expand Up @@ -240,7 +241,6 @@ class Edupage extends RawData {
* @memberof Edupage
*/
async refreshTimeline(_update = true) {

//Fetch timeline data
const _timeline = await this.api({
url: ENDPOINT.TIMELINE_GET_DATA,
Expand Down Expand Up @@ -442,51 +442,65 @@ class Edupage extends RawData {
* @memberof Edupage
*/
async fetchTimetablesForDates(fromDate, toDate) {
//Get 'gpid' if it doesn't exist yet
if(!this.ASC.gpid) {
debug(`[Timetable] 'gpid' property does not exists, trying to fetch it...`);
try {
const _html = await this.api({url: ENDPOINT.DASHBOARD_GET_CLASSBOOK, method: "GET", type: "text"});
const ids = [..._html.matchAll(/gpid="?(\d+)"?/gi)].map(e => e[1]);
return new Promise((resolve, reject) => {
const tryFetch = async _count => {
//Get 'gpid' if it doesn't exist yet
if(!this.ASC.gpid) {
debug(`[Timetable] 'gpid' property does not exists, trying to fetch it...`);
try {
const _html = await this.api({url: ENDPOINT.DASHBOARD_GET_CLASSBOOK, method: "GET", type: "text"});
const ids = [..._html.matchAll(/gpid="?(\d+)"?/gi)].map(e => e[1]);

if(ids.length) {
this.ASC.gpids = ids;
this.ASC.gpid = ids[ids.length - 1];
}
else throw new Error("Cannot find gpid value");
} catch(err) {
debug(`[Timetable] Could not get 'gpid' property`, err);
throw new EdupageError("Could not get 'gpid' property: " + err.message);
}
debug(`[Timetable] 'gpid' property fetched!`);
}

//Load and parse data
const _html = await this.api({
url: ENDPOINT.DASHBOARD_GCALL,
method: "POST",
type: "text",
data: new URLSearchParams({
gpid: this.ASC.gpid,
gsh: this.ASC.gsecHash,
action: "loadData",
datefrom: Edupage.dateToString(fromDate),
dateto: Edupage.dateToString(toDate),
}).toString(),
encodeBody: false
});
const _json = Timetable.parse(_html);
const timetables = iterate(_json.dates).map(([i, date, data]) => new Timetable(data, date, this));

//Update timetables
timetables.forEach(e => {
const i = this.timetables.findIndex(t => e.date.getTime() == t.date.getTime());
else throw new Error("Cannot find gpid value");
} catch(err) {
debug(`[Timetable] Could not get 'gpid' property`, err);
return reject(new EdupageError("Could not get 'gpid' property: " + err.message));
}
debug(`[Timetable] 'gpid' property fetched!`);
}

if(i > -1) this.timetables[i] = e;
else this.timetables.push(e);
//Load and parse data
this.api({
url: ENDPOINT.DASHBOARD_GCALL,
method: "POST",
type: "text",
data: new URLSearchParams({
gpid: this.ASC.gpid,
gsh: this.ASC.gsecHash,
action: "loadData",
datefrom: Edupage.dateToString(fromDate),
dateto: Edupage.dateToString(toDate),
}).toString(),
encodeBody: false
}, _count).then(_html => {
const _json = Timetable.parse(_html);
const timetables = iterate(_json.dates).map(([i, date, data]) => new Timetable(data, date, this));

//Update timetables
timetables.forEach(e => {
const i = this.timetables.findIndex(t => e.date.getTime() == t.date.getTime());

if(i > -1) this.timetables[i] = e;
else this.timetables.push(e);
});

resolve(timetables);
}).catch(err => {
if(err.retry) {
debug(`[Timetable] Got retry signal, retrying...`);
tryFetch(err.count + 1);
} else {
error(`[Timetable] Could not fetch timetables`, err);
reject(new EdupageError("Failed to fetch timetables: " + err.message));
}
});
};
tryFetch(-1);
});

return timetables;
}

/**
Expand Down Expand Up @@ -535,10 +549,11 @@ class Edupage extends RawData {
*
* @static
* @param {APIOptions} options
* @return {Promise<any>}
* @param {number} [_count=0]
* @return {Promise<any, Error | {retry: true, count: number}>} Resolves: Response body, Rejects: Error or retry object in case of successful invalid gsecHash error resolution
* @memberof Edupage
*/
async api(options) {
async api(options, _count = 0) {
const {
headers = {},
data = {},
Expand All @@ -551,7 +566,7 @@ class Edupage extends RawData {
let url = options.url;

return new Promise((resolve, reject) => {
const tryFetch = (tryCount = 0) => {
const tryFetch = (tryCount = _count || 0) => {
debug(`[API] Trying to send request...`);

const tryLogIn = async () => {
Expand Down Expand Up @@ -579,7 +594,6 @@ class Edupage extends RawData {

//If url is APIEndpoint, convert it to url
if(typeof url === "number") {
debug(`[API] Translating API endpoint into URL...`);
url = this.buildRequestUrl(url);
}

Expand Down Expand Up @@ -615,6 +629,17 @@ class Edupage extends RawData {
//Try to log in
debug(`[API] Server responded with login page`);
tryLogIn();
} else if(text.includes("Error6511024354099")) {
//Invalid gsecHash
error(`[API] Invalid gsecHash, refreshing edupage...`);
this.refreshEdupage().then(() => {
debug(`[API] Edupage refreshed, trying again (${tryCount + 1})...`);
reject({retry: true, count: ++tryCount});
//tryFetch(++tryCount);
}).catch(err => {
error(`[API] Failed to refresh edupage:`, err);
reject(new APIError("Failed to refresh edupage while resolving Invalid gsecHash error", err));
});
} else {
if(type == "json") {
try {
Expand Down
59 changes: 37 additions & 22 deletions src/Lesson.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const {FatalError} = require("./exceptions");
const debug = require("debug")("edupage:log");
const error = require("debug")("edupage:error");
const {FatalError, EdupageError} = require("./exceptions");
const RawData = require("../lib/RawData");
const Assignment = require("./Assignment");
const Class = require("./Class");
Expand Down Expand Up @@ -160,28 +162,41 @@ class Lesson extends RawData {
* @memberof Lesson
*/
async signIntoLesson() {
if(!this.isOnlineLesson) throw new Error(`Cannot sign into this lesson`);

const payload = {
"__args": [
null,
{
"click": true,
"date": this.date.toISOString().slice(0, 10),
"ol_url": this.onlineLessonURL,
"subjectid": this.subject.id
}
],
"__gsh": this.edupage.ASC.gsecHash
};

const res = await this.edupage.api({
url: ENDPOINT.DASHBOARD_SIGN_ONLINE_LESSON,
data: payload,
encodeBody: false
return new Promise((resolve, reject) => {
if(!this.isOnlineLesson) return reject(new Error(`Cannot sign into this lesson`));
const tryFetch = async _count => {

const payload = {
"__args": [
null,
{
"click": true,
"date": this.date.toISOString().slice(0, 10),
"ol_url": this.onlineLessonURL,
"subjectid": this.subject.id
}
],
"__gsh": this.edupage.ASC.gsecHash
};

this.edupage.api({
url: ENDPOINT.DASHBOARD_SIGN_ONLINE_LESSON,
data: payload,
encodeBody: false
}).then(res => {
resolve(res.reload != true);
}).catch(err => {
if(err.retry) {
debug(`[Timetable] Got retry signal, retrying...`);
tryFetch(err.count + 1);
} else {
error(`[Timetable] Could not fetch timetables`, err);
reject(new EdupageError("Failed to fetch timetables: " + err.message));
}
});
};
tryFetch(-1);
});

return res.reload != true;
}
}

Expand Down

0 comments on commit 25ce9dc

Please sign in to comment.