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 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
- // Serve htmx from node_modules
11
- app.use("/static/htmx.js", serveStatic({ path: "./node_modules/htmx.org/dist/htmx.min.js" }));
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
- 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] })] }));
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
- 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();
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
- addDebugLog("INFO", `Login requested for user: ${username} (${regNo})`);
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 for ${username} (took ${duration}s)`);
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 for ${username} (took ${duration}s)`);
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: 6767,
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`);
@@ -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, assume login worked
353
- // The actual success will be determined when we try to fetch data
354
- console.log("🎉 Login POST submitted successfully!");
355
- this.state.loggedIn = true;
356
- this.state.username = username;
357
- this.state.regNo = regNo;
358
- return true;
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.0"
28
+ "version": "1.0.3"
27
29
  }