open-vtop 1.0.0 → 1.0.3
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/dist/index.js +41 -16
- package/dist/session-manager.js +20 -8
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -6,9 +6,25 @@ import { serveStatic } from "@hono/node-server/serve-static";
|
|
|
6
6
|
import { Home } from "./views/Home.js";
|
|
7
7
|
import { SuccessMessage } from "./views/partials.js";
|
|
8
8
|
import { sessionManager } from "./session-manager.js";
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
9
12
|
const app = new Hono();
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
// Serve htmx from node_modules - resolve absolute path
|
|
15
|
+
const htmxPath = join(__dirname, "../node_modules/htmx.org/dist/htmx.min.js");
|
|
16
|
+
app.get("/static/htmx.js", async (c) => {
|
|
17
|
+
try {
|
|
18
|
+
const content = await readFile(htmxPath);
|
|
19
|
+
return c.body(content, 200, {
|
|
20
|
+
"Content-Type": "application/javascript",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
console.error("Failed to serve HTMX:", e);
|
|
25
|
+
return c.text("Failed to load HTMX", 500);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
12
28
|
// Main page
|
|
13
29
|
app.get("/", (c) => {
|
|
14
30
|
return c.html(_jsx(Home, {}));
|
|
@@ -24,10 +40,11 @@ app.get("/api/session/status", (c) => {
|
|
|
24
40
|
const lastInit = state.lastInitialized
|
|
25
41
|
? new Date(state.lastInitialized).toLocaleString()
|
|
26
42
|
: "Never";
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
43
|
+
// Redact sensitive values for security
|
|
44
|
+
const hasCsrf = !!state.csrf;
|
|
45
|
+
const hasJsession = state.cookies.has("JSESSIONID");
|
|
46
|
+
const hasServerId = state.cookies.has("SERVERID");
|
|
47
|
+
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:" }), " ", hasCsrf ? "✅ Present" : "❌ Missing"] }), _jsxs("div", { style: "margin-bottom: 0.5rem;", children: [_jsx("strong", { children: "JSESSIONID:" }), " ", hasJsession ? "✅ Present" : "❌ Missing"] }), _jsxs("div", { children: [_jsx("strong", { children: "SERVERID:" }), " ", hasServerId ? "✅ Present" : "❌ Missing"] })] }));
|
|
31
48
|
});
|
|
32
49
|
// Manual session refresh endpoint
|
|
33
50
|
app.post("/api/session/refresh", async (c) => {
|
|
@@ -92,13 +109,16 @@ app.get("/api/login/status/html", (c) => {
|
|
|
92
109
|
// Debug logs storage
|
|
93
110
|
const debugLogs = [];
|
|
94
111
|
function addDebugLog(level, message) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
// Only log if specifically enabled
|
|
113
|
+
if (process.env.DEBUG_LOGS === "true") {
|
|
114
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
115
|
+
debugLogs.push({ timestamp, level, message });
|
|
116
|
+
// Keep only last 100 logs
|
|
117
|
+
if (debugLogs.length > 100) {
|
|
118
|
+
debugLogs.shift();
|
|
119
|
+
}
|
|
120
|
+
console.log(`[${level}] ${message}`);
|
|
100
121
|
}
|
|
101
|
-
console.log(`[${level}] ${message}`);
|
|
102
122
|
}
|
|
103
123
|
// Form-based login endpoint (for HTMX)
|
|
104
124
|
app.post("/api/login/form", async (c) => {
|
|
@@ -111,18 +131,19 @@ app.post("/api/login/form", async (c) => {
|
|
|
111
131
|
addDebugLog("ERROR", "Login attempt with missing credentials");
|
|
112
132
|
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
133
|
}
|
|
114
|
-
|
|
134
|
+
// Don't log credentials even in debug
|
|
135
|
+
addDebugLog("INFO", "Login requested");
|
|
115
136
|
// Show that login is in progress
|
|
116
137
|
const startTime = Date.now();
|
|
117
138
|
addDebugLog("INFO", "Starting login process - polling for text CAPTCHA...");
|
|
118
139
|
const success = await sessionManager.login(username, password, regNo);
|
|
119
140
|
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
120
141
|
if (success) {
|
|
121
|
-
addDebugLog("SUCCESS", `Login successful
|
|
142
|
+
addDebugLog("SUCCESS", `Login successful (took ${duration}s)`);
|
|
122
143
|
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
144
|
}
|
|
124
145
|
else {
|
|
125
|
-
addDebugLog("ERROR", `Login failed
|
|
146
|
+
addDebugLog("ERROR", `Login failed (took ${duration}s)`);
|
|
126
147
|
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
148
|
}
|
|
128
149
|
}
|
|
@@ -133,6 +154,9 @@ app.post("/api/login/form", async (c) => {
|
|
|
133
154
|
});
|
|
134
155
|
// Debug logs endpoint
|
|
135
156
|
app.get("/api/debug/logs", (c) => {
|
|
157
|
+
if (process.env.DEBUG_LOGS !== "true") {
|
|
158
|
+
return c.html(_jsx("div", { style: "color: #888;", children: "Debug logs are disabled. Set DEBUG_LOGS=true to enable." }));
|
|
159
|
+
}
|
|
136
160
|
if (debugLogs.length === 0) {
|
|
137
161
|
return c.html(_jsx("div", { style: "color: #888;", children: "No logs yet. Try logging in to see debug output." }));
|
|
138
162
|
}
|
|
@@ -196,9 +220,10 @@ app.get("/api/assignments/html", async (c) => {
|
|
|
196
220
|
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
221
|
}
|
|
198
222
|
});
|
|
223
|
+
const port = Number(process.env.PORT) || 6767;
|
|
199
224
|
serve({
|
|
200
225
|
fetch: app.fetch,
|
|
201
|
-
port
|
|
226
|
+
port,
|
|
202
227
|
}, (info) => {
|
|
203
228
|
console.log(`🚀 Server is running on http://localhost:${info.port}`);
|
|
204
229
|
console.log(`📊 Session status: http://localhost:${info.port}/api/session/status`);
|
package/dist/session-manager.js
CHANGED
|
@@ -276,7 +276,7 @@ class VTOPSessionManager {
|
|
|
276
276
|
solvedCaptcha = await solve(cleanDataUri);
|
|
277
277
|
console.log("✅ Solved CAPTCHA:", solvedCaptcha);
|
|
278
278
|
// Save captcha image for debugging
|
|
279
|
-
if (parts?.base64) {
|
|
279
|
+
if (parts?.base64 && process.env.DEBUG_CAPTCHA === "true") {
|
|
280
280
|
const out = path.resolve(process.cwd(), "captcha.jpg");
|
|
281
281
|
await saveCaptchaImage(parts.base64, out);
|
|
282
282
|
// console.log(`📷 Saved CAPTCHA image to ${out}`);
|
|
@@ -349,13 +349,25 @@ class VTOPSessionManager {
|
|
|
349
349
|
: new URL(location, LOGIN_PAGE).toString();
|
|
350
350
|
await this.fetchWithCookies(redirectUrl);
|
|
351
351
|
}
|
|
352
|
-
// After successfully submitting the captcha,
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
352
|
+
// After successfully submitting the captcha, we MUST verify login success
|
|
353
|
+
console.log("POST successful, verifying login status...");
|
|
354
|
+
// Try to navigate to post-login pages to confirm we are actually logged in
|
|
355
|
+
// VTOP is tricky: a 200 OK on login POST doesn't guarantee success (could be wrong password)
|
|
356
|
+
const navSuccess = await this.navigatePostLogin();
|
|
357
|
+
if (navSuccess) {
|
|
358
|
+
console.log("🎉 Login verified successfully!");
|
|
359
|
+
this.state.loggedIn = true;
|
|
360
|
+
this.state.username = username;
|
|
361
|
+
this.state.regNo = regNo;
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
console.error("❌ Login verification failed - cleaning up session");
|
|
366
|
+
this.state.loggedIn = false;
|
|
367
|
+
this.state.username = null;
|
|
368
|
+
this.state.regNo = null;
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
359
371
|
}
|
|
360
372
|
catch (e) {
|
|
361
373
|
console.error("Login POST failed:", e);
|
package/package.json
CHANGED
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
"open-vtop": "dist/index.js"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
|
-
"dist"
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
20
22
|
],
|
|
21
23
|
"devDependencies": {
|
|
22
24
|
"@types/node": "^20.11.17",
|
|
23
25
|
"tsx": "^4.7.1",
|
|
24
26
|
"typescript": "^5.8.3"
|
|
25
27
|
},
|
|
26
|
-
"version": "1.0.
|
|
28
|
+
"version": "1.0.3"
|
|
27
29
|
}
|