open-vtop 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/bitmaps.js +9081 -0
- package/dist/captcha-solver.js +213 -0
- package/dist/index.js +205 -0
- package/dist/session-manager.js +588 -0
- package/dist/views/Home.js +5 -0
- package/dist/views/components/Container.js +4 -0
- package/dist/views/components/Item.js +4 -0
- package/dist/views/layouts/Base.js +121 -0
- package/dist/views/partials.js +4 -0
- package/package.json +27 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CAPTCHA Solver for VTOP
|
|
3
|
+
* Uses canvas for image processing and bitmap matching / neural network for character recognition
|
|
4
|
+
*/
|
|
5
|
+
import { createCanvas, loadImage } from "canvas";
|
|
6
|
+
import { bitmaps } from "./bitmaps.js";
|
|
7
|
+
// ============ Bitmap-based solver (original method) ============
|
|
8
|
+
function captchaParse(imgarr) {
|
|
9
|
+
let captcha = "";
|
|
10
|
+
// Noise removal
|
|
11
|
+
for (let x = 1; x < 44; x++) {
|
|
12
|
+
for (let y = 1; y < 179; y++) {
|
|
13
|
+
const condition1 = imgarr[x][y - 1] === 255 &&
|
|
14
|
+
imgarr[x][y] === 0 &&
|
|
15
|
+
imgarr[x][y + 1] === 255;
|
|
16
|
+
const condition2 = imgarr[x - 1][y] === 255 &&
|
|
17
|
+
imgarr[x][y] === 0 &&
|
|
18
|
+
imgarr[x + 1][y] === 255;
|
|
19
|
+
const condition3 = imgarr[x][y] !== 255 && imgarr[x][y] !== 0;
|
|
20
|
+
if (condition1 || condition2 || condition3) {
|
|
21
|
+
imgarr[x][y] = 255;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Character matching
|
|
26
|
+
const chars = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ";
|
|
27
|
+
for (let j = 30; j < 181; j += 30) {
|
|
28
|
+
const matches = [];
|
|
29
|
+
for (let i = 0; i < chars.length; i++) {
|
|
30
|
+
let match = 0;
|
|
31
|
+
let black = 0;
|
|
32
|
+
const ch = chars.charAt(i);
|
|
33
|
+
const mask = bitmaps[ch];
|
|
34
|
+
if (!mask)
|
|
35
|
+
continue;
|
|
36
|
+
for (let x = 0; x < 32; x++) {
|
|
37
|
+
for (let y = 0; y < 30; y++) {
|
|
38
|
+
const y1 = y + j - 30;
|
|
39
|
+
const x1 = x + 12;
|
|
40
|
+
if (imgarr[x1]?.[y1] === mask[x]?.[y] && mask[x]?.[y] === 0) {
|
|
41
|
+
match += 1;
|
|
42
|
+
}
|
|
43
|
+
if (mask[x]?.[y] === 0) {
|
|
44
|
+
black += 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const perc = black > 0 ? match / black : 0;
|
|
49
|
+
matches.push([perc, ch]);
|
|
50
|
+
}
|
|
51
|
+
captcha += matches.reduce((a, b) => (a[0] > b[0] ? a : b), [0, ""])[1];
|
|
52
|
+
}
|
|
53
|
+
return captcha;
|
|
54
|
+
}
|
|
55
|
+
// ============ Saturation-based solver (neural network method) ============
|
|
56
|
+
function preImg(img) {
|
|
57
|
+
let avg = 0;
|
|
58
|
+
img.forEach((e) => e.forEach((f) => (avg += f)));
|
|
59
|
+
avg /= img.length * img[0].length;
|
|
60
|
+
const bits = new Array(img.length);
|
|
61
|
+
for (let i = 0; i < img.length; i++) {
|
|
62
|
+
bits[i] = new Array(img[0].length);
|
|
63
|
+
for (let j = 0; j < img[0].length; j++) {
|
|
64
|
+
bits[i][j] = img[i][j] > avg ? 1 : 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return bits;
|
|
68
|
+
}
|
|
69
|
+
function saturation(d) {
|
|
70
|
+
const saturate = new Array(d.length / 4);
|
|
71
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
72
|
+
const min = Math.min(d[i], d[i + 1], d[i + 2]);
|
|
73
|
+
const max = Math.max(d[i], d[i + 1], d[i + 2]);
|
|
74
|
+
saturate[i / 4] = max > 0 ? Math.round(((max - min) * 255) / max) : 0;
|
|
75
|
+
}
|
|
76
|
+
const img = new Array(40);
|
|
77
|
+
for (let i = 0; i < 40; i++) {
|
|
78
|
+
img[i] = new Array(200);
|
|
79
|
+
for (let j = 0; j < 200; j++) {
|
|
80
|
+
img[i][j] = saturate[i * 200 + j];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const bls = new Array(6);
|
|
84
|
+
for (let i = 0; i < 6; i++) {
|
|
85
|
+
const x1 = (i + 1) * 25 + 2;
|
|
86
|
+
const y1 = 7 + 5 * (i % 2) + 1;
|
|
87
|
+
const x2 = (i + 2) * 25 + 1;
|
|
88
|
+
const y2 = 35 - 5 * ((i + 1) % 2);
|
|
89
|
+
bls[i] = img.slice(y1, y2).map((row) => row.slice(x1, x2));
|
|
90
|
+
}
|
|
91
|
+
return bls;
|
|
92
|
+
}
|
|
93
|
+
function flatten(arr) {
|
|
94
|
+
const bits = new Array(arr.length * arr[0].length);
|
|
95
|
+
for (let i = 0; i < arr.length; i++) {
|
|
96
|
+
for (let j = 0; j < arr[0].length; j++) {
|
|
97
|
+
bits[i * arr[0].length + j] = arr[i][j];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return bits;
|
|
101
|
+
}
|
|
102
|
+
function matMul(a, b) {
|
|
103
|
+
const x = a.length;
|
|
104
|
+
const z = a[0].length;
|
|
105
|
+
const y = b[0].length;
|
|
106
|
+
const product = new Array(x);
|
|
107
|
+
for (let p = 0; p < x; p++) {
|
|
108
|
+
product[p] = new Array(y).fill(0);
|
|
109
|
+
}
|
|
110
|
+
for (let i = 0; i < x; i++) {
|
|
111
|
+
for (let j = 0; j < y; j++) {
|
|
112
|
+
for (let k = 0; k < z; k++) {
|
|
113
|
+
product[i][j] += a[i][k] * b[k][j];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return product;
|
|
118
|
+
}
|
|
119
|
+
function matAdd(a, b) {
|
|
120
|
+
const c = new Array(a.length);
|
|
121
|
+
for (let i = 0; i < a.length; i++) {
|
|
122
|
+
c[i] = a[i] + b[i];
|
|
123
|
+
}
|
|
124
|
+
return c;
|
|
125
|
+
}
|
|
126
|
+
function maxSoft(a) {
|
|
127
|
+
const n = [...a];
|
|
128
|
+
let s = 0;
|
|
129
|
+
n.forEach((f) => {
|
|
130
|
+
s += Math.exp(f);
|
|
131
|
+
});
|
|
132
|
+
for (let i = 0; i < a.length; i++) {
|
|
133
|
+
n[i] = Math.exp(a[i]) / s;
|
|
134
|
+
}
|
|
135
|
+
return n;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Solve a CAPTCHA image using the saturation-based neural network method
|
|
139
|
+
* @param imgDataUri - Base64 data URI of the captcha image
|
|
140
|
+
* @returns Solved captcha string (6 characters)
|
|
141
|
+
*/
|
|
142
|
+
export async function solve(imgDataUri) {
|
|
143
|
+
const weights = bitmaps.weights;
|
|
144
|
+
const biases = bitmaps.biases;
|
|
145
|
+
if (!weights?.length || !biases?.length) {
|
|
146
|
+
console.warn("⚠️ Neural network weights/biases not loaded, falling back to bitmap method");
|
|
147
|
+
return solveBitmap(imgDataUri);
|
|
148
|
+
}
|
|
149
|
+
const labelTxt = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
150
|
+
const canvas = createCanvas(200, 40);
|
|
151
|
+
const ctx = canvas.getContext("2d");
|
|
152
|
+
const image = await loadImage(imgDataUri);
|
|
153
|
+
ctx.drawImage(image, 0, 0, 200, 40);
|
|
154
|
+
const pd = ctx.getImageData(0, 0, 200, 40);
|
|
155
|
+
let bls = saturation(pd.data);
|
|
156
|
+
let out = "";
|
|
157
|
+
for (let i = 0; i < 6; i++) {
|
|
158
|
+
let block = preImg(bls[i]);
|
|
159
|
+
const flatBlock = [flatten(block)];
|
|
160
|
+
const mulResult = matMul(flatBlock, weights);
|
|
161
|
+
const addResult = matAdd(mulResult[0], biases);
|
|
162
|
+
const softResult = maxSoft(addResult);
|
|
163
|
+
const maxIdx = softResult.indexOf(Math.max(...softResult));
|
|
164
|
+
out += labelTxt[maxIdx];
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Solve a CAPTCHA using bitmap matching (fallback method)
|
|
170
|
+
* @param imgDataUri - Base64 data URI of the captcha image
|
|
171
|
+
* @returns Solved captcha string
|
|
172
|
+
*/
|
|
173
|
+
export async function solveBitmap(imgDataUri) {
|
|
174
|
+
const canvas = createCanvas(180, 45);
|
|
175
|
+
const ctx = canvas.getContext("2d");
|
|
176
|
+
const image = await loadImage(imgDataUri);
|
|
177
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
178
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
179
|
+
const data = imageData.data;
|
|
180
|
+
// Convert to grayscale 2D array
|
|
181
|
+
const arr = [];
|
|
182
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
183
|
+
const gval = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
|
|
184
|
+
arr.push(Math.round(gval));
|
|
185
|
+
}
|
|
186
|
+
const newArr = [];
|
|
187
|
+
const width = 180;
|
|
188
|
+
while (arr.length) {
|
|
189
|
+
newArr.push(arr.splice(0, width));
|
|
190
|
+
}
|
|
191
|
+
return captchaParse(newArr);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Extract base64 data from a data URI
|
|
195
|
+
* @param dataUri - Full data URI string
|
|
196
|
+
* @returns Object with mimeType and base64 data, or null if invalid
|
|
197
|
+
*/
|
|
198
|
+
export function extractDataUriParts(dataUri) {
|
|
199
|
+
const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
|
|
200
|
+
if (!match)
|
|
201
|
+
return null;
|
|
202
|
+
return { mimeType: match[1], base64: match[2] };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Save captcha image to a file (for debugging)
|
|
206
|
+
* @param base64 - Base64 encoded image data
|
|
207
|
+
* @param filePath - Output file path
|
|
208
|
+
*/
|
|
209
|
+
export async function saveCaptchaImage(base64, filePath) {
|
|
210
|
+
const fs = await import("fs/promises");
|
|
211
|
+
const buffer = Buffer.from(base64, "base64");
|
|
212
|
+
await fs.writeFile(filePath, buffer);
|
|
213
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
3
|
+
import { serve } from "@hono/node-server";
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
6
|
+
import { Home } from "./views/Home.js";
|
|
7
|
+
import { SuccessMessage } from "./views/partials.js";
|
|
8
|
+
import { sessionManager } from "./session-manager.js";
|
|
9
|
+
const app = new Hono();
|
|
10
|
+
// Serve htmx from node_modules
|
|
11
|
+
app.use("/static/htmx.js", serveStatic({ path: "./node_modules/htmx.org/dist/htmx.min.js" }));
|
|
12
|
+
// Main page
|
|
13
|
+
app.get("/", (c) => {
|
|
14
|
+
return c.html(_jsx(Home, {}));
|
|
15
|
+
});
|
|
16
|
+
// API Endpoints
|
|
17
|
+
app.get("/api/hello", (c) => {
|
|
18
|
+
return c.html(_jsx(SuccessMessage, { message: "Hello from HTMX!" }));
|
|
19
|
+
});
|
|
20
|
+
// Session status endpoint
|
|
21
|
+
app.get("/api/session/status", (c) => {
|
|
22
|
+
const state = sessionManager.getState();
|
|
23
|
+
const status = state.initialized ? "✅ Initialized" : "❌ Not Initialized";
|
|
24
|
+
const lastInit = state.lastInitialized
|
|
25
|
+
? new Date(state.lastInitialized).toLocaleString()
|
|
26
|
+
: "Never";
|
|
27
|
+
const csrfToken = state.csrf || "None";
|
|
28
|
+
const jsessionid = state.cookies.get("JSESSIONID") || "None";
|
|
29
|
+
const serverid = state.cookies.get("SERVERID") || "None";
|
|
30
|
+
return c.html(_jsxs("div", { style: "font-family: monospace; padding: 1rem; background: #f5f5f5; border-radius: 4px; color: #000;", children: [_jsxs("div", { style: "margin-bottom: 0.5rem;", children: [_jsx("strong", { children: "Status:" }), " ", status] }), _jsxs("div", { style: "margin-bottom: 0.5rem;", children: [_jsx("strong", { children: "Last Initialized:" }), " ", lastInit] }), _jsxs("div", { style: "margin-bottom: 0.5rem;", children: [_jsx("strong", { children: "Total Cookies:" }), " ", state.cookies.size] }), _jsxs("div", { style: "margin-bottom: 0.5rem;", children: [_jsx("strong", { children: "CSRF Token:" }), " ", csrfToken] }), _jsxs("div", { style: "margin-bottom: 0.5rem;", children: [_jsx("strong", { children: "JSESSIONID:" }), " ", jsessionid] }), _jsxs("div", { children: [_jsx("strong", { children: "SERVERID:" }), " ", serverid] })] }));
|
|
31
|
+
});
|
|
32
|
+
// Manual session refresh endpoint
|
|
33
|
+
app.post("/api/session/refresh", async (c) => {
|
|
34
|
+
try {
|
|
35
|
+
await sessionManager.refresh();
|
|
36
|
+
return c.json({ success: true, message: "Session refreshed successfully" });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return c.json({ success: false, error: String(error) }, 500);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// Login endpoint
|
|
43
|
+
app.post("/api/login", async (c) => {
|
|
44
|
+
try {
|
|
45
|
+
const body = await c.req.json();
|
|
46
|
+
const { username, password, regNo } = body;
|
|
47
|
+
if (!username || !password || !regNo) {
|
|
48
|
+
return c.json({
|
|
49
|
+
success: false,
|
|
50
|
+
error: "Username, password, and registration number are required",
|
|
51
|
+
}, 400);
|
|
52
|
+
}
|
|
53
|
+
console.log(`🔐 Login requested for user: ${username} (${regNo})`);
|
|
54
|
+
const success = await sessionManager.login(username, password, regNo);
|
|
55
|
+
if (success) {
|
|
56
|
+
return c.json({
|
|
57
|
+
success: true,
|
|
58
|
+
message: "Login successful",
|
|
59
|
+
username: sessionManager.getUsername(),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
return c.json({
|
|
64
|
+
success: false,
|
|
65
|
+
error: "Login failed - check credentials or try again",
|
|
66
|
+
}, 401);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error("Login error:", error);
|
|
71
|
+
return c.json({ success: false, error: String(error) }, 500);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// Login status endpoint
|
|
75
|
+
app.get("/api/login/status", (c) => {
|
|
76
|
+
return c.json({
|
|
77
|
+
loggedIn: sessionManager.isLoggedIn(),
|
|
78
|
+
username: sessionManager.getUsername(),
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// Login status HTML endpoint
|
|
82
|
+
app.get("/api/login/status/html", (c) => {
|
|
83
|
+
const isLoggedIn = sessionManager.isLoggedIn();
|
|
84
|
+
const username = sessionManager.getUsername();
|
|
85
|
+
if (isLoggedIn) {
|
|
86
|
+
return c.html(_jsxs("div", { style: "padding: 1rem; background: #d4edda; border: 1px solid #c3e6cb; border-radius: 4px; color: #155724;", children: [_jsx("strong", { children: "\u2705 Logged In" }), _jsxs("div", { style: "margin-top: 0.5rem;", children: ["Username: ", _jsx("code", { children: username })] })] }));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return c.html(_jsxs("div", { style: "padding: 1rem; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; color: #721c24;", children: [_jsx("strong", { children: "\u274C Not Logged In" }), _jsx("div", { style: "margin-top: 0.5rem;", children: "Please use the login form above." })] }));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// Debug logs storage
|
|
93
|
+
const debugLogs = [];
|
|
94
|
+
function addDebugLog(level, message) {
|
|
95
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
96
|
+
debugLogs.push({ timestamp, level, message });
|
|
97
|
+
// Keep only last 100 logs
|
|
98
|
+
if (debugLogs.length > 100) {
|
|
99
|
+
debugLogs.shift();
|
|
100
|
+
}
|
|
101
|
+
console.log(`[${level}] ${message}`);
|
|
102
|
+
}
|
|
103
|
+
// Form-based login endpoint (for HTMX)
|
|
104
|
+
app.post("/api/login/form", async (c) => {
|
|
105
|
+
try {
|
|
106
|
+
const formData = await c.req.parseBody();
|
|
107
|
+
const username = formData["username"];
|
|
108
|
+
const password = formData["password"];
|
|
109
|
+
const regNo = formData["regNo"];
|
|
110
|
+
if (!username || !password || !regNo) {
|
|
111
|
+
addDebugLog("ERROR", "Login attempt with missing credentials");
|
|
112
|
+
return c.html(_jsxs("div", { style: "padding: 1rem; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; color: #721c24;", children: [_jsx("strong", { children: "\u274C Error:" }), " Username, password, and registration number are required."] }));
|
|
113
|
+
}
|
|
114
|
+
addDebugLog("INFO", `Login requested for user: ${username} (${regNo})`);
|
|
115
|
+
// Show that login is in progress
|
|
116
|
+
const startTime = Date.now();
|
|
117
|
+
addDebugLog("INFO", "Starting login process - polling for text CAPTCHA...");
|
|
118
|
+
const success = await sessionManager.login(username, password, regNo);
|
|
119
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
120
|
+
if (success) {
|
|
121
|
+
addDebugLog("SUCCESS", `Login successful for ${username} (took ${duration}s)`);
|
|
122
|
+
return c.html(_jsxs("div", { style: "padding: 1rem; background: #d4edda; border: 1px solid #c3e6cb; border-radius: 4px; color: #155724;", children: [_jsx("strong", { children: "\uD83C\uDF89 Login Successful!" }), _jsxs("div", { style: "margin-top: 0.5rem;", children: ["Welcome, ", _jsx("strong", { children: username })] }), _jsxs("div", { style: "margin-top: 0.25rem; font-size: 0.85rem;", children: ["Login took ", duration, " seconds"] })] }));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
addDebugLog("ERROR", `Login failed for ${username} (took ${duration}s)`);
|
|
126
|
+
return c.html(_jsxs("div", { style: "padding: 1rem; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; color: #721c24;", children: [_jsx("strong", { children: "\u274C Login Failed" }), _jsx("div", { style: "margin-top: 0.5rem;", children: "Check your credentials or try again. VTOP might be showing reCAPTCHA." }), _jsxs("div", { style: "margin-top: 0.25rem; font-size: 0.85rem;", children: ["Attempt took ", duration, " seconds"] })] }));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
addDebugLog("ERROR", `Login exception: ${String(error)}`);
|
|
131
|
+
return c.html(_jsxs("div", { style: "padding: 1rem; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; color: #721c24;", children: [_jsx("strong", { children: "\u274C Error:" }), " ", String(error)] }));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
// Debug logs endpoint
|
|
135
|
+
app.get("/api/debug/logs", (c) => {
|
|
136
|
+
if (debugLogs.length === 0) {
|
|
137
|
+
return c.html(_jsx("div", { style: "color: #888;", children: "No logs yet. Try logging in to see debug output." }));
|
|
138
|
+
}
|
|
139
|
+
return c.html(_jsx("div", { children: debugLogs.map((log, i) => (_jsxs("div", { style: {
|
|
140
|
+
color: log.level === "ERROR"
|
|
141
|
+
? "#ff6b6b"
|
|
142
|
+
: log.level === "SUCCESS"
|
|
143
|
+
? "#51cf66"
|
|
144
|
+
: log.level === "WARN"
|
|
145
|
+
? "#fcc419"
|
|
146
|
+
: "#0f0",
|
|
147
|
+
marginBottom: "0.25rem",
|
|
148
|
+
}, children: [_jsxs("span", { style: "color: #888;", children: ["[", log.timestamp, "]"] }), " ", _jsxs("span", { style: "font-weight: bold;", children: ["[", log.level, "]"] }), " ", log.message] }, i))) }));
|
|
149
|
+
});
|
|
150
|
+
// Clear debug logs endpoint
|
|
151
|
+
app.post("/api/debug/logs/clear", (c) => {
|
|
152
|
+
debugLogs.length = 0;
|
|
153
|
+
return c.html(_jsx("div", { style: "color: #888;", children: "Logs cleared." }));
|
|
154
|
+
});
|
|
155
|
+
// Assignments JSON endpoint
|
|
156
|
+
app.get("/api/assignments", async (c) => {
|
|
157
|
+
if (!sessionManager.isLoggedIn()) {
|
|
158
|
+
return c.json({ success: false, error: "Not logged in" }, 401);
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const assignments = await sessionManager.fetchUpcomingAssignments();
|
|
162
|
+
return c.json({ success: true, assignments });
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return c.json({ success: false, error: String(error) }, 500);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// Assignments HTML endpoint (for HTMX)
|
|
169
|
+
app.get("/api/assignments/html", async (c) => {
|
|
170
|
+
if (!sessionManager.isLoggedIn()) {
|
|
171
|
+
return c.html(_jsxs("div", { style: "padding: 1rem; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; color: #721c24;", children: [_jsx("strong", { children: "\u274C Not Logged In" }), _jsx("div", { style: "margin-top: 0.5rem;", children: "Please log in first to see your assignments." })] }));
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const assignments = await sessionManager.fetchUpcomingAssignments();
|
|
175
|
+
if (assignments.length === 0) {
|
|
176
|
+
return c.html(_jsxs("div", { style: "padding: 1.5rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white; text-align: center;", children: [_jsx("div", { style: "font-size: 3rem; margin-bottom: 0.5rem;", children: "\uD83C\uDF89" }), _jsx("strong", { style: "font-size: 1.25rem;", children: "No Upcoming Assignments!" }), _jsx("div", { style: "margin-top: 0.5rem; opacity: 0.9;", children: "You're all caught up. Time to relax!" })] }));
|
|
177
|
+
}
|
|
178
|
+
return c.html(_jsxs("div", { style: "display: flex; flex-direction: column; gap: 1rem;", children: [_jsxs("div", { style: "padding: 0.75rem 1rem; background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); border-radius: 8px; color: white; font-weight: bold;", children: ["\uD83D\uDCDA ", assignments.length, " Upcoming Assignment", assignments.length > 1 ? "s" : ""] }), assignments.map((ass, i) => (_jsxs("div", { style: {
|
|
179
|
+
padding: "1rem",
|
|
180
|
+
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)",
|
|
181
|
+
borderRadius: "12px",
|
|
182
|
+
border: "1px solid rgba(255,255,255,0.1)",
|
|
183
|
+
color: "#fff",
|
|
184
|
+
boxShadow: "0 4px 15px rgba(0,0,0,0.2)",
|
|
185
|
+
}, children: [_jsxs("div", { style: "display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.75rem;", children: [_jsxs("div", { children: [_jsx("div", { style: "font-size: 0.85rem; color: #64ffda; font-weight: 600;", children: ass.courseCode }), _jsx("div", { style: "font-size: 0.8rem; color: #aaa;", children: ass.courseName })] }), _jsx("div", { style: {
|
|
186
|
+
padding: "0.25rem 0.75rem",
|
|
187
|
+
background: ass.status?.toLowerCase().includes("pending")
|
|
188
|
+
? "#ff6b6b"
|
|
189
|
+
: "#4CAF50",
|
|
190
|
+
borderRadius: "20px",
|
|
191
|
+
fontSize: "0.75rem",
|
|
192
|
+
fontWeight: "bold",
|
|
193
|
+
}, children: ass.status || "Pending" })] }), _jsx("div", { style: "font-weight: bold; font-size: 1rem; margin-bottom: 0.5rem;", children: ass.assignmentTitle }), _jsxs("div", { style: "display: flex; justify-content: space-between; font-size: 0.85rem; color: #aaa;", children: [_jsxs("span", { children: ["\uD83D\uDCC5 Due: ", ass.dueDate || "N/A"] }), _jsxs("span", { children: ["\uD83D\uDCCA Max: ", ass.maxMarks || "N/A", " marks"] })] })] }, i)))] }));
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return c.html(_jsxs("div", { style: "padding: 1rem; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; color: #721c24;", children: [_jsx("strong", { children: "\u274C Error:" }), " ", String(error)] }));
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
serve({
|
|
200
|
+
fetch: app.fetch,
|
|
201
|
+
port: 6767,
|
|
202
|
+
}, (info) => {
|
|
203
|
+
console.log(`🚀 Server is running on http://localhost:${info.port}`);
|
|
204
|
+
console.log(`📊 Session status: http://localhost:${info.port}/api/session/status`);
|
|
205
|
+
});
|