-
Notifications
You must be signed in to change notification settings - Fork 3
/
content.js
273 lines (241 loc) · 7.78 KB
/
content.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
const SUBJECTMAP = {};
/**
* @description Injects the analytics script into the page.
* @returns {Promise<void>}
*/
async function injectAnalytics() {
if (!localStorage.getItem("mixpanel_distinct_id")) {
const distinctId = Date.now().toString();
localStorage.setItem("mixpanel_distinct_id", distinctId);
}
const PAYLOAD = JSON.stringify({
properties: {
token: "3eec01e18d86ddd2a94b043de5658718",
distinct_id: localStorage.getItem("mixpanel_distinct_id"),
vendor: navigator.vendor,
},
event: "Times student portal made better",
});
const res = await fetch("https://api.mixpanel.com/track", {
mode: "cors",
credentials: "omit",
cache: "no-store",
redirect: "follow",
method: "POST",
body: new URLSearchParams({ data: PAYLOAD }).toString(),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
if (!res.ok) {
console.error("Failed to send analytics data to Mixpanel.");
return;
}
console.log("Successfully sent analytics data to Mixpanel.");
}
/**
* @param {number} totalConducted - The total number of hours conducted.
* @param {number} absent - The number of hours absent.
* @returns {number} - The margin required to maintain 75% attendance.
* @description Calculates the margin required to maintain 75% attendance.
* @example
* const margin = calculateMargin(50, 10);
* console.log(margin); // 1
*/
function calculateMargin(totalConducted, absent) {
let margin = 0;
let present = totalConducted - absent;
let current = (present / totalConducted) * 100;
let conducted = totalConducted;
if (current > 75) {
while (current >= 75) {
conducted++;
margin++;
current = (present / conducted) * 100;
}
margin--;
} else {
while (current < 75) {
conducted++;
margin--;
present++;
current = (present / conducted) * 100;
}
}
return margin;
}
/**
* @param {string} selector - The CSS selector for the element to wait for.
* @returns {Promise<Element>} - A promise that resolves to the element matching the selector.
* @description Waits for an element to appear in the DOM and resolves the promise with the element.
*/
async function waitForElement(selector) {
if (typeof selector !== "string") {
throw new Error("The selector must be a string.");
}
return new Promise((resolve) => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
});
}
/**
* @returns {Promise<void>}
* @description Applies the magic to the student portal.
*/
async function applyMagicToStudentPortal() {
const attendanceTable = await waitForElement(
"#divMainDetails > div.container.mt-4 > div.card.mb-4 > div.card-body.p-0 > div > table",
);
const rows = attendanceTable.querySelectorAll(".card-body table tbody tr");
const head = attendanceTable.querySelector(
"#divMainDetails > div.container.mt-4 > div.card.mb-4 > div.card-body.p-0 > div > table > thead > tr",
);
const headCell = document.createElement("td");
if (head.cells[2].innerHTML === "Max. hours") {
headCell.innerHTML = "<strong>Margin</strong>";
head.append(headCell);
}
for (const row of rows) {
SUBJECTMAP[
`${row.cells[0].innerHTML.substring(
0,
row.cells[0].innerHTML.indexOf("<br>"),
)}`
] = row.cells[1].textContent;
const hoursConductedS = row.cells[2].textContent;
const absentS = row.cells[4].textContent;
const cell = document.createElement("td");
const margin = calculateMargin(hoursConductedS, absentS);
cell.textContent = `${margin}`;
if (margin < 0) {
cell.style.color = "red";
}
row.appendChild(cell);
}
}
/**
* @returns {Promise<void>}
* @description Applies the magic to the Academia portal.
*/
async function applyMagicToAcademiaPortal() {
if (!document.getElementById("margin-head")) {
const attendanceTable = await waitForElement("table[bgcolor='#FAFAD2']");
const marksTable = document.querySelector("p + table");
const rows = attendanceTable.querySelectorAll("tbody tr:not(:first-child)");
const head = attendanceTable.querySelector("tbody tr:first-child");
const attendanceHeading = head.children[7];
const headCell = document.createElement("td");
headCell.id = "margin-head"
headCell.innerHTML = "<strong>Margin</strong>";
head.insertBefore(headCell, attendanceHeading.nextSibling);
for (const row of rows) {
SUBJECTMAP[
`${row.cells[0].innerHTML.substring(
0,
row.cells[0].innerHTML.indexOf("<br>"),
)}`
] = row.cells[1].textContent;
const hoursConductedS = row.cells[5].textContent;
const absentS = row.cells[6].textContent;
const cell = document.createElement("td");
const margin = calculateMargin(hoursConductedS, absentS);
cell.textContent = `${margin}`;
if (margin < 0) {
cell.style.color = "red";
}
cell.style.backgroundColor = "#E6E6FA";
row.appendChild(cell);
row.insertBefore(cell, row.children[7].nextSibling);
}
if (!marksTable) return;
const marksRows = marksTable.querySelectorAll("tbody tr:not(:first-child)");
for (const row of marksRows) {
const nestedTable = row.querySelector("table table");
row.cells[0].textContent += ` ${SUBJECTMAP[row.cells[0].textContent]}`;
row.cells[0].style.textAlign = "left";
if (!nestedTable) return;
nestedTable.style.width = "100%";
const cells = nestedTable.querySelectorAll('td font[size="1.5"]');
let sum = 0;
let totalMarks = 0;
for (const cell of cells) {
const number = Number.parseFloat(
cell.innerHTML.substring(cell.innerHTML.lastIndexOf("<br>") + 4),
);
const max = Number.parseFloat(
cell.innerHTML.substring(cell.innerHTML.indexOf("/") + 1),
cell.innerHTML.indexOf("</strong>"),
);
if (!Number.isNaN(number)) {
sum += number;
}
if (!Number.isNaN(max)) {
totalMarks += max;
}
}
const totalCell = document.createElement("td");
totalCell.innerHTML = `<strong>${sum.toFixed(
2,
)}</strong> / ${totalMarks.toFixed(2)}`;
totalCell.setAttribute("colspan", cells.length);
totalCell.style.textAlign = "center";
nestedTable.appendChild(totalCell);
}
}
}
/**
* @returns {Promise<void>}
* @description The main function that runs when the content script is loaded.
*/
async function main() {
const hostname = window.location.hostname;
if (hostname === "academia.srmist.edu.in") {
await applyMagicToAcademiaPortal();
let currentUrl = window.location.href;
// Function to check if the URL has changed
async function checkUrlChange() {
const newUrl = window.location.href;
if (newUrl !== currentUrl) {
currentUrl = newUrl;
if (currentUrl == "https://academia.srmist.edu.in/#Page:My_Attendance")
await applyMagicToAcademiaPortal(); // Reapply main function when URL changes
}
}
window.addEventListener("popstate", checkUrlChange);
console.log(
"%cMaking Academia Better Now",
"color: #bada55; font-size: 20px; font-weight: bold; font-family: 'Comic Sans MS', 'Comic Sans', cursive;"
);
}
if (hostname === "sp.srmist.edu.in") {
await applyMagicToStudentPortal();
console.log(
"%cMaking Student Portal Better Now",
"color: #bada55; font-size: 20px; font-weight: bold; font-family: 'Comic Sans MS', 'Comic Sans', cursive;"
);
}
console.log(
"%cIf you are seeing this message, you are awesome!",
"color: #bada55; font-size: 20px; font-weight: bold; font-family: 'Comic Sans MS', 'Comic Sans', cursive;"
);
console.log(
"%cRaise any issues or contribute to the project at https://github.com/SukhOberoi/SRM-Margin-Check",
"color: #bada55; font-size: 20px; font-weight: bold; font-family: 'Comic Sans MS', 'Comic Sans', cursive;"
);
await injectAnalytics();
}
main();