nlobby-mcp
Version:
MCP server for N Lobby school portal
78 lines (68 loc) • 103 kB
JavaScript
#!/usr/bin/env node
import{Server as Y}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as K}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as X,ErrorCode as T,ListResourcesRequestSchema as V,ListToolsRequestSchema as Z,McpError as x,ReadResourceRequestSchema as Q,ListPromptsRequestSchema as ee,GetPromptRequestSchema as te}from"@modelcontextprotocol/sdk/types.js";import z from"axios";import*as j from"cheerio";import P from"node:fs/promises";import J from"node:os";import L from"node:path";import F from"puppeteer";import H from"puppeteer";import{config as M}from"dotenv";M();function G(){switch(process.platform){case"darwin":return"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36";case"win32":return"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36";case"linux":default:return"Mozilla/5.0 (X11; CrOS x86_64 10066.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"}}var h={nlobby:{baseUrl:process.env.NLOBBY_BASE_URL||"https://nlobby.nnn.ed.jp"},mcp:{serverName:process.env.MCP_SERVER_NAME||"nlobby-mcp",serverVersion:process.env.MCP_SERVER_VERSION||"1.3.0"},userAgent:process.env.USER_AGENT||G()};var I=(r=>(r[r.DEBUG=0]="DEBUG",r[r.INFO=1]="INFO",r[r.WARN=2]="WARN",r[r.ERROR=3]="ERROR",r))(I||{}),_=class w{static instance;logLevel=1;isProduction=!1;constructor(){this.isProduction=process.env.NODE_ENV==="production"||process.env.MCP_PRODUCTION==="true"||!process.stdout.isTTY}static getInstance(){return w.instance||(w.instance=new w),w.instance}setLogLevel(e){this.logLevel=e}log(e,n,...s){if(e<this.logLevel||this.isProduction&&e<2)return;let r=new Date().toISOString(),o=I[e],i=`[${r}] [${o}]`;switch(e){case 0:console.debug(i,n,...s);break;case 1:console.info(i,n,...s);break;case 2:console.warn(i,n,...s);break;case 3:console.error(i,n,...s);break}}debug(e,...n){this.log(0,e,...n)}info(e,...n){this.log(1,e,...n)}warn(e,...n){this.log(2,e,...n)}error(e,...n){this.log(3,e,...n)}},t=_.getInstance();var A=class{browser=null;page=null;async initializeBrowser(){try{if(t.info("Initializing browser for authentication..."),this.browser){try{await this.browser.close()}catch(e){t.warn("Error closing existing browser:",e)}this.browser=null,this.page=null}this.browser=await H.launch({headless:!1,defaultViewport:{width:1280,height:800},ignoreDefaultArgs:["--disable-extensions","--disable-default-apps"],args:["--no-sandbox","--disable-setuid-sandbox","--disable-dev-shm-usage","--disable-gpu","--no-first-run","--no-default-browser-check","--no-pings","--password-store=basic","--use-mock-keychain","--memory-pressure-off","--max_old_space_size=4096",'--js-flags="--max-old-space-size=4096"',"--disable-background-timer-throttling","--disable-renderer-backgrounding","--disable-backgrounding-occluded-windows","--disable-background-mode","--disable-default-apps","--disable-sync","--disable-translate","--disable-infobars","--disable-notifications","--disable-popup-blocking","--enable-async-dns","--enable-simple-cache-backend","--enable-tcp-fast-open","--prerender-from-omnibox=disabled","--disable-features=VizDisplayCompositor,TranslateUI","--disable-search-engine-choice-screen","--disable-component-update","--allow-running-insecure-content","--disable-hang-monitor","--disable-prompt-on-repost","--disable-client-side-phishing-detection","--disable-domain-reliability","--disable-logging","--disable-login-animations","--disable-modal-animations","--disable-motion-blur","--disable-smooth-scrolling","--disable-threaded-animation","--disable-threaded-scrolling","--disable-checker-imaging","--disable-new-profile-management","--disable-new-avatar-menu","--disable-new-bookmark-apps"],timeout:6e4,protocolTimeout:6e4,slowMo:250}),this.page=await this.browser.newPage(),await this.page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"),await this.page.setDefaultNavigationTimeout(6e4),await this.page.setDefaultTimeout(3e4),this.page.on("error",e=>{t.error("Page error:",e)}),this.page.on("pageerror",e=>{t.error("Page JavaScript error:",e)}),this.page.on("console",e=>{e.type()==="error"&&t.error("Browser console error:",e.text())}),this.page.on("framedetached",e=>{t.warn("Frame detached:",e.url())}),this.browser.on("disconnected",()=>{t.error("Browser disconnected unexpectedly"),this.browser=null,this.page=null}),this.browser.on("targetcreated",e=>{t.info("New browser target created:",e.url())}),this.browser.on("targetdestroyed",e=>{t.info("Browser target destroyed:",e.url())}),await this.page.setJavaScriptEnabled(!0),await this.page.setCacheEnabled(!1),await this.page.setRequestInterception(!0),this.page.on("request",e=>{let n=e.resourceType();["image","media","font","stylesheet"].includes(n)?n==="image"&&(e.url().includes("accounts.google.com")||e.url().includes("gstatic.com")||e.url().includes("googleapis.com"))?e.continue():e.abort():e.continue()}),t.info("Browser initialized successfully")}catch(e){if(t.error("Failed to initialize browser:",e),this.browser){try{await this.browser.close()}catch(n){t.warn("Error closing browser after initialization failure:",n)}this.browser=null,this.page=null}throw new Error("Failed to initialize browser for authentication")}}async setupPageConfiguration(){if(!this.page)throw new Error("Page not initialized");await this.page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"),await this.page.setDefaultNavigationTimeout(6e4),await this.page.setDefaultTimeout(3e4),this.page.on("error",e=>{t.error("Page error:",e)}),this.page.on("pageerror",e=>{t.error("Page JavaScript error:",e)}),this.page.on("console",e=>{e.type()==="error"&&t.error("Browser console error:",e.text())}),await this.page.setJavaScriptEnabled(!0),await this.page.setCacheEnabled(!1),await this.page.setRequestInterception(!0),this.page.on("request",e=>{let n=e.resourceType();["image","media","font","stylesheet"].includes(n)?n==="image"&&(e.url().includes("accounts.google.com")||e.url().includes("gstatic.com")||e.url().includes("googleapis.com"))?e.continue():e.abort():e.continue()})}async checkBrowserHealth(){try{return!this.browser||!this.browser.isConnected()?(t.warn("Browser is not connected"),!1):!this.page||this.page.isClosed()?(t.warn("Page is closed"),!1):(await this.page.evaluate(()=>document.readyState),!0)}catch(e){return t.warn("Browser health check failed:",e),!1}}async extractCookies(){if(!this.page)throw new Error("Page not initialized");try{t.info("Extracting cookies from N Lobby session...");let e=await this.page.cookies(),n,s,r,o=[];for(let c of e){let a=`${c.name}=${c.value}`;o.push(a),c.name==="__Secure-next-auth.session-token"?n=c.value:c.name==="__Host-next-auth.csrf-token"?s=c.value:c.name==="__Secure-next-auth.callback-url"&&(r=decodeURIComponent(c.value))}let i=o.join("; ");return t.info(`Extracted ${e.length} cookies from N Lobby session`),t.info(`Session token: ${n?"present":"missing"}`),t.info(`CSRF token: ${s?"present":"missing"}`),{sessionToken:n,csrfToken:s,callbackUrl:r,allCookies:i}}catch(e){throw t.error("Failed to extract cookies:",e),new Error("Failed to extract authentication cookies")}}async takeScreenshot(e="nlobby-auth-screenshot.png"){if(!this.page)throw new Error("Page not initialized");let n=`/tmp/${e}`;return await this.page.screenshot({path:n,fullPage:!0}),t.info(`Screenshot saved to ${n}`),n}async getCurrentUrl(){if(!this.page)throw new Error("Page not initialized");return this.page.url()}async getPageTitle(){if(!this.page)throw new Error("Page not initialized");return this.page.title()}async waitForRedirectWithRetry(e,n){let o=e.replace("https://","").replace("http://","");for(let i=1;i<=3;i++)try{(!this.browser||!this.browser.isConnected())&&(t.error("Browser crashed or disconnected, reinitializing..."),await this.initializeBrowser()),t.info(`Waiting for redirect back to N Lobby (attempt ${i}/3)...`),await this.page.waitForFunction(c=>window.location.href.includes(c),{timeout:n/3},o),t.info("Successfully redirected back to N Lobby");return}catch(c){if(i===3)throw t.error("All redirect attempts failed:",c),new Error(`Failed to detect redirect after 3 attempts: ${c instanceof Error?c.message:"Unknown error"}`);t.warn(`Redirect attempt ${i} failed, retrying in 2000ms...`),await new Promise(a=>setTimeout(a,2e3));try{!this.browser||!this.browser.isConnected()?(t.error("Browser crashed, reinitializing..."),await this.initializeBrowser(),await this.page.goto(e,{waitUntil:"networkidle2",timeout:3e4})):await this.page.evaluate(()=>document.readyState)}catch{t.warn("Page became inaccessible, creating new page..."),this.browser&&this.browser.isConnected()&&(this.page=await this.browser.newPage(),await this.setupPageConfiguration())}}}async waitForLoginCompletionWithRetry(e){for(let r=1;r<=3;r++)try{(!this.browser||!this.browser.isConnected())&&(t.error("Browser crashed during login detection, reinitializing..."),await this.initializeBrowser(),await this.page.goto(h.nlobby.baseUrl,{waitUntil:"networkidle2",timeout:3e4})),t.info(`Waiting for login completion (attempt ${r}/3)...`),await this.page.waitForFunction(()=>document.querySelector('[data-testid="user-menu"], .user-profile, .logout-btn')!==null||document.cookie.includes("next-auth.session-token")||window.location.pathname.includes("/home")||window.location.pathname.includes("/dashboard"),{timeout:e/3}),t.info("Login completion detected");return}catch(o){if(r===3)throw t.error("All login detection attempts failed:",o),new Error(`Failed to detect login completion after 3 attempts: ${o instanceof Error?o.message:"Unknown error"}`);t.warn(`Login detection attempt ${r} failed, retrying in 2000ms...`),await new Promise(i=>setTimeout(i,2e3));try{!this.browser||!this.browser.isConnected()?(t.error("Browser crashed during login detection, reinitializing..."),await this.initializeBrowser(),await this.page.goto(h.nlobby.baseUrl,{waitUntil:"networkidle2",timeout:3e4})):await this.page.evaluate(()=>document.readyState)}catch{t.warn("Page became inaccessible during login detection, creating new page..."),this.browser&&this.browser.isConnected()&&(this.page=await this.browser.newPage(),await this.setupPageConfiguration(),await this.page.goto(h.nlobby.baseUrl,{waitUntil:"networkidle2",timeout:3e4}))}}}async close(){try{this.page&&(await this.page.close(),this.page=null),this.browser&&(await this.browser.close(),this.browser=null),t.info("Browser closed successfully")}catch(e){t.error("Error closing browser:",e)}}async interactiveLogin(){if(await this.checkBrowserHealth()||(t.warn("Browser unhealthy, reinitializing..."),await this.initializeBrowser()),!this.browser||!this.page)throw new Error("Browser not initialized. Call initializeBrowser() first.");try{return t.info("Starting interactive login process..."),await this.page.goto(h.nlobby.baseUrl,{waitUntil:"networkidle2",timeout:3e4}),t.info("N Lobby page loaded. Please complete the login process in the browser window."),t.info("The browser will remain open for you to login manually."),await this.waitForLoginCompletionWithRetry(3e5),t.info("Login detected! Extracting cookies..."),await this.extractCookies()}catch(n){if(t.error("Interactive login failed:",n),this.page)try{let s=await this.page.url(),r=await this.page.title();t.error(`Current URL: ${s}`),t.error(`Page title: ${r}`),await this.takeScreenshot("interactive-login-failure-debug.png")}catch(s){t.error("Failed to capture debug information:",s)}throw new Error(`Interactive login failed: ${n instanceof Error?n.message:"Unknown error"}`)}}};var v=class{cookies={};constructor(){}parseCookies(e){let n={},s=e.split(";");for(let r of s){let[o,i]=r.trim().split("=");o&&i&&(o==="__Secure-next-auth.session-token"?n.sessionToken=decodeURIComponent(i):o==="__Host-next-auth.csrf-token"?n.csrfToken=decodeURIComponent(i):o==="__Secure-next-auth.callback-url"&&(n.callbackUrl=decodeURIComponent(i)))}return n}setCookies(e){this.cookies=this.parseCookies(e),t.debug("NextAuth cookies parsed:",{hasSessionToken:!!this.cookies.sessionToken,hasCsrfToken:!!this.cookies.csrfToken,hasCallbackUrl:!!this.cookies.callbackUrl})}getCookies(){return this.cookies}getCookieHeader(){let e=[];return this.cookies.sessionToken&&e.push(`__Secure-next-auth.session-token=${encodeURIComponent(this.cookies.sessionToken)}`),this.cookies.csrfToken&&e.push(`__Host-next-auth.csrf-token=${encodeURIComponent(this.cookies.csrfToken)}`),this.cookies.callbackUrl&&e.push(`__Secure-next-auth.callback-url=${encodeURIComponent(this.cookies.callbackUrl)}`),e.join("; ")}isAuthenticated(){return!!this.cookies.sessionToken}getSessionToken(){return this.cookies.sessionToken||null}async decodeSessionToken(){if(!this.cookies.sessionToken)return null;try{return{token:this.cookies.sessionToken}}catch(e){return t.error("Error decoding session token:",e),null}}clear(){this.cookies={}}};import B from"axios";var $=class{httpClient;nextAuth;requestId=1;allCookies="";constructor(e){this.nextAuth=e,this.httpClient=B.create({baseURL:`${h.nlobby.baseUrl}/api/trpc`,timeout:15e3,headers:{"Content-Type":"application/json","User-Agent":h.userAgent,Accept:"application/json","Accept-Language":"ja,en-US;q=0.7,en;q=0.3","Accept-Encoding":"gzip, deflate, br",Connection:"keep-alive","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"same-origin"},withCredentials:!0}),this.setupInterceptors()}setAllCookies(e){if(!e||e.trim()===""){t.warn("[WARNING] Empty cookies provided to tRPC client"),this.allCookies="";return}this.allCookies=e,t.info("[SUCCESS] tRPC client cookies updated"),t.info(`[SIZE] tRPC cookie string length: ${e.length}`),this.allCookies===e?t.info("[SUCCESS] tRPC cookie verification successful"):t.error("[ERROR] tRPC cookie verification failed")}setupInterceptors(){this.httpClient.interceptors.request.use(e=>{let n;if(this.allCookies&&this.allCookies.trim()!=="")n=this.allCookies,t.debug("[COOKIE] Using all cookies for tRPC request");else{let o=this.nextAuth.getCookieHeader();o&&o.trim()!==""?(n=o,t.debug("[COOKIE] Using NextAuth cookies for tRPC request")):t.warn("[WARNING] No cookies available for tRPC request")}n&&(e.headers.Cookie=n);let s=this.nextAuth.getSessionToken();s?(e.headers.Authorization=`Bearer ${s}`,t.debug("Added Authorization header with session token")):t.warn("[WARNING] No session token available for Authorization header");let r=this.nextAuth.getCookies().csrfToken;return r&&(e.headers["X-CSRF-Token"]=r,t.debug("Added CSRF token to tRPC request")),t.debug("[REQUEST] tRPC request details:",{url:e.url,method:e.method?.toUpperCase(),hasCookies:!!n,hasCSRF:!!r,hasAuth:!!s,cookieSource:this.allCookies?"allCookies":"nextAuth"}),e}),this.httpClient.interceptors.response.use(e=>(t.debug("[SUCCESS] tRPC response received:",{status:e.status,statusText:e.statusText,hasData:!!e.data}),e),async e=>{if(t.error("[ERROR] tRPC request failed:",{status:e.response?.status,statusText:e.response?.statusText,message:e.message,url:e.config?.url}),e.response?.status===401)throw t.error("[BLOCKED] Authentication failed - NextAuth session may be expired"),new Error("Authentication expired. Please re-authenticate with NextAuth cookies.");if(e.response?.status===403)throw t.error("[BLOCKED] Access forbidden - insufficient permissions"),new Error("Access forbidden. Check your permissions or re-authenticate.");if(e.response?.status===404)throw t.error("[BLOCKED] tRPC endpoint not found"),new Error("tRPC endpoint not found. The API may have changed.");return Promise.reject(e)})}getNextRequestId(){return this.requestId++}async call(e,n){let s={id:this.getNextRequestId(),method:e,params:n};try{t.info(`[REQUEST] tRPC call: ${e}`,n?`with params: ${JSON.stringify(n)}`:"without params");let r=this.allCookies||this.nextAuth.getCookieHeader();t.debug(`[COOKIE] Request cookies: ${r?"present":"missing"}`),t.debug("Trying GET approach...");try{let o=this.buildTRPCUrl(e,n);t.debug(`[URL] tRPC GET URL: ${o}`);let i=await this.httpClient.get(o);if(t.debug(`[SUCCESS] tRPC ${e} GET response status: ${i.status}`),i.data.error)throw t.error(`[ERROR] tRPC ${e} GET returned error:`,i.data.error),new Error(`tRPC Error [${i.data.error.code}]: ${i.data.error.message}`);return t.debug(`[SUCCESS] tRPC ${e} GET succeeded`),i.data.result}catch(o){t.debug("[WARNING] GET approach failed, trying POST approach..."),t.debug("[DEBUG] GET error details:",o instanceof Error?o.message:"Unknown error");let i=e;t.debug(`[URL] tRPC POST URL: ${i}`);let c=await this.httpClient.post(i,s,{headers:{"Content-Type":"application/json"}});if(t.debug(`[SUCCESS] tRPC ${e} POST response status: ${c.status}`),c.data.error)throw t.error(`[ERROR] tRPC ${e} POST returned error:`,c.data.error),new Error(`tRPC Error [${c.data.error.code}]: ${c.data.error.message}`);return t.debug(`[SUCCESS] tRPC ${e} POST succeeded`),c.data.result}}catch(r){if(t.error(`[ERROR] tRPC call failed for ${e}:`,{message:r instanceof Error?r.message:"Unknown error",stack:r instanceof Error?r.stack:void 0}),r&&typeof r=="object"&&"response"in r){let o=r;t.error(`[DEBUG] tRPC ${e} Axios error details:`,{status:o.response?.status,statusText:o.response?.statusText,headers:o.response?.headers,data:o.response?.data,url:o.config?.url,method:o.config?.method,timeout:o.config?.timeout,cookies:o.config?.headers?.Cookie?"present":"missing"}),o.response?.status===401?t.error("[BLOCKED] tRPC 401 Unauthorized - session may be expired or invalid"):o.response?.status===403?t.error("[BLOCKED] tRPC 403 Forbidden - insufficient permissions"):o.response?.status===404?t.error("[BLOCKED] tRPC 404 Not Found - endpoint may not exist"):o.response?.status&&o.response.status>=500&&t.error("[BLOCKED] tRPC Server Error - N Lobby backend issue")}else if(r&&typeof r=="object"&&"code"in r){let o=r;o.code==="ECONNREFUSED"?t.error("[NETWORK] Network Error: Connection refused - N Lobby may be down"):o.code==="ETIMEDOUT"?t.error("[TIMEOUT] Network Error: Request timeout - slow network or server overload"):o.code==="ENOTFOUND"&&t.error("[NETWORK] Network Error: DNS lookup failed - check internet connection")}throw r}}buildTRPCUrl(e,n){let s=e,r=JSON.stringify(n||{}),o=new URLSearchParams({input:r}).toString();return`${s}?${o}`}async getUnreadNewsCount(){return this.call("news.getUnreadNewsCount")}async getNotificationMessages(){return this.call("notification.getMessages")}async updateLastAccess(){return this.call("user.updateLastAccess")}async findMainNavigations(){return this.call("menu.findMainNavigations",{})}async readInterestsWithIcon(){return this.call("interest.readInterestsWithIcon")}async readInterests(){return this.call("interest.readInterests")}async readWeights(){return this.call("interest.readWeights")}async getLobbyCalendarEvents(e,n){return this.call("calendar.getLobbyCalendarEvents",{from:e,to:n})}async healthCheck(){t.info("Running tRPC health check...");let e=[{name:"updateLastAccess",method:()=>this.updateLastAccess()},{name:"getUnreadNewsCount",method:()=>this.getUnreadNewsCount()},{name:"findMainNavigations",method:()=>this.findMainNavigations()}];for(let{name:n,method:s}of e)try{return t.debug(`Trying tRPC method: ${n}`),await s(),t.info(`[SUCCESS] tRPC health check passed with method: ${n}`),!0}catch(r){t.debug(`[ERROR] tRPC method ${n} failed:`,r instanceof Error?r.message:"Unknown error");continue}return t.error("[ERROR] All tRPC health check methods failed"),!1}};import{createHash as q}from"crypto";var N=class{credentialStore=new Map;SESSION_TIMEOUT=7200*1e3;hashEmail(e){return q("sha256").update(e).digest("hex")}validateEmail(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))return{valid:!1,userType:"unknown",message:"Invalid email format"};let s=e.split("@")[1].toLowerCase();return s==="nnn.ed.jp"?{valid:!0,userType:"student"}:s==="nnn.ac.jp"?{valid:!0,userType:"staff"}:s==="gmail.com"||s==="yahoo.com"||s==="outlook.com"||s==="hotmail.com"?{valid:!0,userType:"parent"}:{valid:!0,userType:"parent"}}storeSession(e){let n=this.hashEmail(e),s={emailHash:n,timestamp:Date.now(),sessionValid:!0};this.credentialStore.set(n,s),t.info(`Session stored for user: ${e.split("@")[0]}@***`)}hasValidSession(e){let n=this.hashEmail(e),s=this.credentialStore.get(n);return s?Date.now()-s.timestamp>this.SESSION_TIMEOUT?(this.credentialStore.delete(n),!1):s.sessionValid:!1}invalidateSession(e){let n=this.hashEmail(e);this.credentialStore.delete(n),t.info(`Session invalidated for user: ${e.split("@")[0]}@***`)}getLoginGuidance(e){switch(e){case"student":return`
[STUDENT] Student Login Guide:
- Use your @nnn.ed.jp email address
- Use your N High School password
- If you have 2FA enabled, you'll need to complete it during login
- Contact your homeroom teacher if you've forgotten your password`;case"staff":return`
[STAFF] Staff Login Guide:
- Use your @nnn.ac.jp email address
- Use your N High School staff password
- If you have 2FA enabled, you'll need to complete it during login
- Contact IT support if you're having trouble accessing your account`;case"parent":return`
[PARENT] Parent Login Guide:
- Use the email address registered with your child's school account
- Use the password you set when creating your parent account
- If you haven't created a parent account yet, contact your child's school
- If you've forgotten your password, use the password reset option`;default:return`
[LOGIN] General Login Guide:
- Use your registered email address
- Use your N Lobby password
- If you have 2FA enabled, you'll need to complete it during login
- Contact support if you're having trouble`}}getTroubleshootingTips(){return`
[TIPS] Common Login Issues & Solutions:
1. **Wrong Email/Password**
- Double-check your email address and password
- Make sure Caps Lock is off
- Try typing your password in a text editor first
2. **2FA Issues**
- Make sure your authenticator app is synced
- Try using backup codes if available
- Wait for the next code if the current one doesn't work
3. **Browser Issues**
- Clear your browser cache and cookies
- Try using incognito/private mode
- Disable browser extensions temporarily
4. **Account Locked**
- Wait 15-30 minutes before trying again
- Contact support if your account is suspended
5. **Network Issues**
- Check your internet connection
- Try using a different network or VPN
- Make sure N Lobby isn't blocked by your firewall
[PRO-TIP] Pro Tips:
- Use 'interactive_login' if automated login fails
- The browser window will stay open for manual completion
- You can close the browser once login is complete`}cleanupExpiredSessions(){let e=Date.now(),n=0;for(let[s,r]of this.credentialStore.entries())e-r.timestamp>this.SESSION_TIMEOUT&&(this.credentialStore.delete(s),n++);n>0&&t.info(`Cleaned up ${n} expired sessions`)}getSessionStats(){let e=Date.now(),n=0;for(let s of this.credentialStore.values())e-s.timestamp>this.SESSION_TIMEOUT&&n++;return{total:this.credentialStore.size,expired:n}}};var U=class{httpClient;session=null;nextAuth;trpcClient;browserAuth;credentialManager;constructor(){this.nextAuth=new v,this.trpcClient=new $(this.nextAuth),this.browserAuth=new A,this.credentialManager=new N,this.httpClient=z.create({baseURL:h.nlobby.baseUrl,timeout:1e4,headers:{"Content-Type":"application/json","User-Agent":h.userAgent}}),this.setupInterceptors()}setupInterceptors(){this.httpClient.interceptors.request.use(e=>(this.session&&(e.headers.Authorization=`Bearer ${this.session.accessToken}`),e)),this.httpClient.interceptors.response.use(e=>e,async e=>{if(e.response?.status===401&&this.session)throw new Error("Authentication expired. Please re-authenticate.");return Promise.reject(e)})}setSession(e){this.session=e}async getNews(){t.info("[INFO] Starting getNews with HTTP client..."),t.info("[STATUS] Current authentication status:",this.getCookieStatus());try{t.info("[INFO] Fetching news via HTTP client (same method as test_page_content)...");let e=await this.fetchRenderedHtml("/news"),n=this.parseNewsFromHtml(e);if(n&&n.length>0)return t.info(`[SUCCESS] Retrieved ${n.length} news items from HTML`),n;{t.info("[WARNING] HTML scraping returned no data");let s=`HTML scraping returned no data. Debug info:
- Authentication status: ${this.nextAuth.isAuthenticated()?"authenticated":"not authenticated"}
- HTTP cookies: ${this.httpClient.defaults.headers.Cookie?"present":"missing"}
- HTML length: ${e.length} characters
- Contains data grid: ${e.includes('role="row"')}
- Contains Next.js data: ${e.includes("__NEXT_DATA__")}
- Contains self.__next_f.push: ${e.includes("self.__next_f.push")}
Troubleshooting steps:
1. Run 'health_check' to verify connection
2. Run 'test_page_content /news' to check page content
3. Ensure you are properly authenticated using 'set_cookies'
4. Check if the site structure has changed`;throw new Error(s)}}catch(e){throw t.error("[ERROR] getNews failed:",e),e instanceof Error?e:new Error(`Failed to fetch news with HTTP client: ${e instanceof Error?e.message:"Unknown error"}`)}}async getNewsDetail(e){t.info(`[INFO] Fetching news detail for ID: ${e}`),t.info("[STATUS] Current authentication status:",this.getCookieStatus());try{let n=`/news/${e}`;t.info(`[INFO] Fetching news detail from: ${n}`);let s=await this.fetchRenderedHtml(n);t.info(`[SUCCESS] Retrieved HTML for news ${e}: ${s.length} characters`);let r=this.parseNewsDetailFromHtml(s,e);if(r)return t.info(`[SUCCESS] Parsed news detail: ${r.title}`),r;throw new Error(`Failed to parse news detail from HTML for news ID: ${e}`)}catch(n){throw t.error(`[ERROR] getNewsDetail failed for ID ${e}:`,n),n instanceof Error?n:new Error(`Failed to fetch news detail for ID ${e}: ${n instanceof Error?n.message:"Unknown error"}`)}}parseNewsDetailFromHtml(e,n){try{t.info("[INFO] Starting news detail HTML parsing..."),t.debug(`[DATA] HTML length: ${e.length} characters`),t.info("[STEP1] Extracting data from Next.js self.__next_f.push() calls...");let s=e.match(/self\.__next_f\.push\((\[.*?\])\)/g);if(!s||s.length===0)return t.info("[ERROR] No self.__next_f.push() calls found in HTML"),null;t.info(`[SUCCESS] Found ${s.length} self.__next_f.push() calls`);let r=null,o="",i=new Map;for(let a=0;a<s.length;a++){let l=s[a];try{let u=l.match(/self\.__next_f\.push\((\[.*?\])\)/);if(!u)continue;let d=JSON.parse(u[1]);if(d.length>=2&&typeof d[1]=="string"&&d[1].match(/^\d+:T\d+,?$/)){let p=d[1].replace(/,$/,"");if(t.info(`[INFO] Found content reference: ${p}`),a+1<s.length){let C=s[a+1].match(/self\.__next_f\.push\((\[.*?\])\)/);if(C){let g=JSON.parse(C[1]);g.length>=2&&typeof g[1]=="string"&&(i.set(p,g[1]),t.info(`[SUCCESS] Found content for reference ${p}: ${g[1].length} characters`))}}continue}if(d.length>=2&&typeof d[1]=="string"){let f=d[1].match(/^(\d+):(.*)/);if(f)try{let C=f[2],g=JSON.parse(C),m=this.searchForNewsDataInObject(g);m&&(t.info(`[SUCCESS] Found news data in push call ${a+1}`),r=m)}catch{}}}catch{}}if(!r)return t.info("[ERROR] No news data found in any push call"),null;if(r.description){for(let[a,l]of i)if(r.description.includes(a)){o=l,t.info(`[SUCCESS] Found content data using reference ${a}`);break}}!o&&i.size>0&&(o=Array.from(i.values())[0],t.info("[INFO] Using first available content as fallback"));let c={id:r.id||n,microCmsId:r.microCmsId,title:r.title||"No Title",content:this.decodeHtmlContent(o)||r.description||"",description:r.description,publishedAt:r.publishedAt?new Date(r.publishedAt):new Date,menuName:r.menuName||[],isImportant:r.isImportant||!1,isByMentor:r.isByMentor||!1,attachments:r.attachments||[],relatedEvents:r.relatedEvents||[],targetUserQueryId:r.targetUserQueryId,url:`${h.nlobby.baseUrl}/news/${n}`};return t.info(`[TARGET] Successfully parsed news detail: ${c.title}`),c}catch(s){return t.error("[ERROR] Error parsing news detail from HTML:",s),null}}searchForNewsDataInObject(e,n=""){if(!e||typeof e!="object")return null;let s=e;if(s.id&&s.title&&(s.publishedAt||s.description||s.menuName))return t.info(`[INFO] Found news object at path: ${n}`),e;if(s.news&&typeof s.news=="object")return t.info(`[INFO] Found news property at path: ${n}.news`),s.news;for(let[r,o]of Object.entries(s))if(o&&typeof o=="object"){let i=n?`${n}.${r}`:r,c=this.searchForNewsDataInObject(o,i);if(c)return c}return null}decodeHtmlContent(e){if(!e)return"";try{return e.replace(/\\u003c/g,"<").replace(/\\u003e/g,">").replace(/\\u0026/g,"&").replace(/\\"/g,'"').replace(/\\\\/g,"\\")}catch(n){return t.warn("[WARNING] Failed to decode HTML content:",n),e}}async getSchedule(e="personal",n){try{t.info(`[INFO] Fetching ${e} calendar events...`);let s=await this.getGoogleCalendarEvents(e,n),r=this.convertGoogleCalendarEventsToScheduleItems(s);return t.info(`[SUCCESS] Retrieved ${r.length} schedule items`),r}catch(s){throw t.error("[ERROR] Error fetching schedule:",s),new Error(`Failed to fetch ${e} calendar: ${s instanceof Error?s.message:"Unknown error"}`)}}async getGoogleCalendarEvents(e="personal",n){try{t.info(`[INFO] Fetching Google Calendar events for ${e}...`);let s=this.getDefaultDateRange(),r=n||s;t.info(`[INFO] Date range: ${r.from.toISOString()} to ${r.to.toISOString()}`);let o=e==="personal"?"/api/trpc/calendar.getGoogleCalendarEvents":"/api/trpc/calendar.getLobbyCalendarEvents";t.debug(`[URL] Using endpoint: ${o}`);let i={from:r.from.toISOString(),to:r.to.toISOString()};t.debug("[STATUS] Request input:",i),t.debug("[COOKIE] Authentication status:",this.getCookieStatus());let c=await this.httpClient.get(o,{params:{input:JSON.stringify(i)},headers:{Accept:"application/json","Content-Type":"application/json"},withCredentials:!0});t.info(`[SUCCESS] Calendar API response: ${c.status} ${c.statusText}`),t.info("[INFO] Response data analysis:"),t.info(` - Response type: ${typeof c.data}`),t.info(` - Response keys: ${c.data?Object.keys(c.data):"none"}`),t.info(` - Has result: ${c.data?.result?"yes":"no"}`),t.info(` - Has result.data: ${c.data?.result?.data?"yes":"no"}`),t.info(` - Has result.data.gcal: ${c.data?.result?.data?.gcal?"yes":"no"}`),t.info(" - Full response structure:",JSON.stringify(c.data,null,2));let a=[],l=c.data;if(l?.result?.data?.gcal)a=l.result.data.gcal,t.info(`[SUCCESS] Found events in standard gcal format: ${a.length} events`);else if(l?.result?.data?.lcal)a=l.result.data.lcal,t.info(`[SUCCESS] Found events in school lcal format: ${a.length} events`);else if(l?.result?.data&&Array.isArray(l.result.data))a=l.result.data,t.info(`[SUCCESS] Found events in alternative format (direct array): ${a.length} events`);else if(l?.data?.gcal)a=l.data.gcal,t.info(`[SUCCESS] Found events in simplified format: ${a.length} events`);else if(l?.data&&Array.isArray(l.data))a=l.data,t.info(`[SUCCESS] Found events in direct data array format: ${a.length} events`);else if(l?.gcal)a=l.gcal,t.info(`[SUCCESS] Found events in direct gcal format: ${a.length} events`);else if(Array.isArray(l))a=l,t.info(`[SUCCESS] Found events in direct array format: ${a.length} events`);else throw t.info("[WARNING] No calendar data found in any expected format"),t.info("[DATA] Available response keys:",l?Object.keys(l):"none"),t.info("[DATA] Response sample:",JSON.stringify(l).substring(0,300)),new Error(`Invalid calendar response format for ${e} calendar.
Endpoint: ${o}
Response type: ${typeof l}
Response keys: ${l?Object.keys(l).join(", "):"none"}
Expected format: { result: { data: { gcal: [...] } } }
Please check if the ${e} calendar endpoint is correct and returns valid data.
Response preview: ${JSON.stringify(l).substring(0,300)}...`);if(!Array.isArray(a))throw new Error(`Calendar events is not an array: ${typeof a}`);let u=a;if(t.info(`[TARGET] Retrieved ${u.length} calendar events`),u.length>0){let d=u[0];t.info("[LOG] Sample event:",{id:d.id,summary:d.summary,start:d.start,end:d.end})}return u}catch(s){if(t.error("[ERROR] Error fetching Google Calendar events:",s),s&&typeof s=="object"&&"response"in s){let r=s;if(t.debug("[DEBUG] Calendar API error details:",{status:r.response?.status,statusText:r.response?.statusText,data:r.response?.data}),r.response?.status===401)throw new Error("Authentication required. Please use the set_cookies tool to provide valid NextAuth.js session cookies from N Lobby.")}throw s}}getDefaultDateRange(){let e=new Date,n=new Date(e);n.setHours(0,0,0,0);let s=new Date(e);return s.setDate(s.getDate()+7),s.setHours(23,59,59,999),{from:n,to:s}}convertGoogleCalendarEventsToScheduleItems(e){return e.map(n=>{let s=n,r,o;s.startDateTime?(r=new Date(s.startDateTime),o=s.endDateTime?new Date(s.endDateTime):new Date(r.getTime()+3600*1e3)):s.start?(s.start.dateTime?r=new Date(s.start.dateTime):s.start.date?r=new Date(s.start.date+"T00:00:00"):r=new Date,s.end&&s.end.dateTime?o=new Date(s.end.dateTime):s.end&&s.end.date?(o=new Date(s.end.date+"T23:59:59"),o.setDate(o.getDate()-1)):o=new Date(r.getTime()+3600*1e3)):(r=new Date,o=new Date(r.getTime()+3600*1e3));let i="event",c=(s.summary||"").toLowerCase();c.includes("\u6388\u696D")||c.includes("class")?i="class":c.includes("mtg")||c.includes("\u30DF\u30FC\u30C6\u30A3\u30F3\u30B0")||c.includes("meeting")||c.includes("\u9762\u8AC7")?i="meeting":(c.includes("\u8A66\u9A13")||c.includes("exam")||c.includes("\u30C6\u30B9\u30C8"))&&(i="exam");let a=[];return s.attendees&&Array.isArray(s.attendees)&&(a=s.attendees.map(u=>u.email).filter(Boolean)),{id:s.id||s.microCmsId||Math.random().toString(),title:s.summary||s.title||"No Title",description:s.description||"",startTime:r,endTime:o,location:s.location||"",type:i,participants:a}})}createDateRange(e,n){let s=typeof e=="string"?new Date(e):e,r=typeof n=="string"?new Date(n):n;if(isNaN(s.getTime())||isNaN(r.getTime()))throw new Error("Invalid date format provided");if((r.getTime()-s.getTime())/(1e3*60*60*24)<1)throw new Error('To date must be at least 1 day after from date. For single day queries, use period="today" or single from_date parameter.');return{from:s,to:r}}createSingleDayRange(e){let n=typeof e=="string"?new Date(e):e;if(isNaN(n.getTime()))throw new Error("Invalid date format provided");let s=new Date(n);s.setHours(0,0,0,0);let r=new Date(n);return r.setHours(23,59,59,999),{from:s,to:r}}createWeekDateRange(e){let n=e?typeof e=="string"?new Date(e):e:new Date;n.setHours(0,0,0,0);let s=new Date(n);return s.setDate(s.getDate()+6),s.setHours(23,59,59,999),{from:n,to:s}}createMonthDateRange(e,n){let s=new Date,r=e||s.getFullYear(),o=n!==void 0?n:s.getMonth(),i=new Date(r,o,1,0,0,0,0),c=new Date(r,o+1,0,23,59,59,999);return{from:i,to:c}}async getScheduleByDate(e){t.info(`Using backward compatibility method for date: ${e||"today"}`);let n;if(e){let s=new Date(e);if(isNaN(s.getTime()))throw new Error(`Invalid date format: ${e}`);let r=new Date(s);r.setHours(0,0,0,0);let o=new Date(s);o.setHours(23,59,59,999),n={from:r,to:o}}else n=this.getDefaultDateRange();return this.getSchedule("personal",n)}async testCalendarEndpoints(e){t.info("[TEST] Testing both calendar endpoints...");let n=e||this.getDefaultDateRange(),s={personal:{success:!1,count:0},school:{success:!1,count:0}};try{let r=await this.getGoogleCalendarEvents("personal",n);s.personal.success=!0,s.personal.count=r.length,t.info(`[SUCCESS] Personal calendar: ${r.length} events`)}catch(r){s.personal.error=r instanceof Error?r.message:"Unknown error",t.error("[ERROR] Personal calendar failed:",s.personal.error)}try{let r=await this.getGoogleCalendarEvents("school",n);s.school.success=!0,s.school.count=r.length,t.info(`[SUCCESS] School calendar: ${r.length} events`)}catch(r){s.school.error=r instanceof Error?r.message:"Unknown error",t.error("[ERROR] School calendar failed:",s.school.error)}return t.info("[TARGET] Calendar endpoint test summary:",s),s}async getLearningResources(e){try{let n=e?{subject:e}:{},s=await this.httpClient.get("/api/learning-resources",{params:n});if(!s.data.success)throw new Error(s.data.error||"Failed to fetch learning resources");return s.data.data||[]}catch(n){throw t.error("Error fetching learning resources:",n),new Error("Authentication required. Please use the set_cookies tool to provide valid NextAuth.js session cookies from N Lobby.")}}async getUserInfo(){try{let e=await this.httpClient.get("/api/user");if(!e.data.success)throw new Error(e.data.error||"Failed to fetch user info");return e.data.data}catch(e){throw t.error("Error fetching user info:",e),new Error("Authentication required. Please use the set_cookies tool to provide valid NextAuth.js session cookies from N Lobby.")}}async getAccountInfoFromScript(e="/"){t.info(`[INFO] Extracting account information from Next.js script at ${e}`);try{let n=await this.fetchRenderedHtml(e),s=this.extractSessionFromNextJs(n);if(!s)throw new Error("Could not locate session data in Next.js flight scripts. Authentication might be required or the page structure may have changed.");let r=this.buildAccountInfoFromSession(s);return t.info("[SUCCESS] Account information extracted successfully"),r}catch(n){throw t.error("Error extracting account info from script:",n),new Error(`Failed to extract account information: ${n instanceof Error?n.message:"Unknown error"}
Troubleshooting steps:
1. Ensure you are authenticated (use set_cookies)
2. Verify session with health_check`)}}async getStudentCardScreenshot(){let n=(await this.getAccountInfoFromScript("/")).studentNo;if(!n||n.length<3)throw new Error("Student number is missing from account information. Ensure you are authenticated and try again.");let s=this.resolveSecureHostFromStudentNo(n),r=`https://${s}/mypage/student_card/index`,o=`https://nlobby.nnn.ed.jp/mypage/v1/callback?redirect_uri=${encodeURIComponent(r)}`,i=this.httpClient.defaults.headers.Cookie,c=typeof i=="string"?i:i==null?void 0:String(i);if(!c)throw new Error("Authentication cookies are not set. Use the set_cookies tool or interactive_login first.");let a=this.buildPuppeteerCookies(c,"nlobby.nnn.ed.jp");if(a.length===0)throw new Error("Failed to parse authentication cookies for browser session. Please refresh your session and try again.");let l=await this.captureElementScreenshot({startUrl:o,waitForSelector:"#main",screenshotName:`student-card-${Date.now()}.png`,cookies:a});return{base64:l.base64,path:l.path,studentNo:n,secureHost:s,callbackUrl:o,finalUrl:l.finalUrl,elementSize:l.elementSize}}resolveSecureHostFromStudentNo(e){let n=e.charAt(2)?.toUpperCase();return n?n==="N"?"secure.nnn.ed.jp":/[A-Z]/.test(n)?`${n.toLowerCase()}-secure.nnn.ed.jp`:(t.warn(`[STUDENT_CARD] Student number ${e} third character "${n}" is not a letter; defaulting to s-secure.`),"secure.nnn.ed.jp"):(t.warn(`[STUDENT_CARD] Student number ${e} does not contain a third character; defaulting to s-secure.`),"secure.nnn.ed.jp")}buildPuppeteerCookies(e,n){let s=[];for(let r of e.split(";")){let o=r.trim();if(!o)continue;let i=o.indexOf("=");if(i<=0)continue;let c=o.slice(0,i),a=o.slice(i+1);s.push({name:c,value:a,domain:n,path:"/",secure:!0,httpOnly:c.startsWith("__Secure-")||c.startsWith("__Host-")||c.toLowerCase().includes("session"),sameSite:"Lax"})}return s}async captureElementScreenshot(e){t.info(`[STUDENT_CARD] Launching headless browser for student card capture (${e.startUrl})`);let n=await this.launchBrowser();try{let s=await n.newPage();await s.setViewport({width:1280,height:720}),await s.setUserAgent(h.userAgent),e.cookies.length>0?(t.info(`[STUDENT_CARD] Setting ${e.cookies.length} authentication cookies`),await s.setCookie(...e.cookies)):t.warn("[STUDENT_CARD] No cookies provided to browser session");let r=await s.goto(e.startUrl,{waitUntil:"networkidle2",timeout:6e4});r?t.info(`[STUDENT_CARD] Initial navigation status: ${r.status()} ${r.statusText()}`):t.warn("[STUDENT_CARD] Navigation returned no response object; continuing"),await s.waitForSelector(e.waitForSelector,{timeout:6e4}),await new Promise(d=>setTimeout(d,1e3));let o=await s.$(e.waitForSelector);if(!o)throw new Error(`Failed to locate element ${e.waitForSelector} for screenshot`);let i=await o.screenshot({type:"png"}),c=L.join(J.tmpdir(),"nlobby-student-card");await P.mkdir(c,{recursive:!0});let a=L.join(c,e.screenshotName);await P.writeFile(a,i);let l=await o.boundingBox(),u=l?{width:Math.round(l.width),height:Math.round(l.height)}:void 0;return t.info(`[STUDENT_CARD] Screenshot captured at ${a} (final URL: ${s.url()})`),{base64:i.toString("base64"),path:a,finalUrl:s.url(),elementSize:u}}finally{await n.close()}}async launchBrowser(){let e=["--no-sandbox","--disable-setuid-sandbox"],n=[process.env.PUPPETEER_EXECUTABLE_PATH,process.env.CHROME_PATH].filter(a=>!!a&&a.trim().length>0);for(let a of n)try{return t.info(`[STUDENT_CARD] Attempting to launch browser with executablePath=${a}`),await F.launch({headless:!0,executablePath:a,args:e})}catch(l){t.warn(`[STUDENT_CARD] Failed to launch browser with executablePath=${a}:`,l)}let s=[],r=async(a,l)=>{try{return t.info(`[STUDENT_CARD] Trying browser launch via ${l}`),await F.launch(a)}catch(u){return u instanceof Error?(s.push(u),t.warn(`[STUDENT_CARD] Browser launch failed via ${l}: ${u.message}`)):t.warn(`[STUDENT_CARD] Browser launch failed via ${l}:`,u),null}},o=await r({headless:!0,args:e},"default Puppeteer bundle");if(o)return o;let i=await r({headless:!0,channel:"chrome",args:e},"system Chrome channel");if(i)return i;let c=s.map(a=>a.message).join(`
`);throw new Error(`Failed to launch a browser instance for screenshot capture. Tried Puppeteer's managed Chrome and the system Chrome channel. Please install a compatible Chrome build via "npx puppeteer browsers install chrome" or set CHROME_PATH / PUPPETEER_EXECUTABLE_PATH. Original errors:
${c}`)}extractSessionFromNextJs(e){let n=/self\.__next_f\.push\((\[[\s\S]*?\])\)/g,s;for(;(s=n.exec(e))!==null;){let o=s[1];if(o)try{let i=JSON.parse(o),c=this.findSessionInData(i,new WeakSet);if(c)return t.info("[SUCCESS] Found session data in Next.js flight payload"),c}catch(i){t.debug("[DEBUG] Failed to parse self.__next_f.push payload:",i instanceof Error?i.message:"Unknown error")}}let r=[/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/,/window\.__NEXT_DATA__\s*=\s*({[\s\S]*?})(?:;|\s*<\/script>)/];for(let o of r){let i=e.match(o);if(!(!i||!i[1]))try{let c=JSON.parse(i[1]),a=this.findSessionInData(c,new WeakSet);if(a)return t.info("[SUCCESS] Found session data in __NEXT_DATA__ payload"),a}catch(c){t.debug("[DEBUG] Failed to parse __NEXT_DATA__ payload:",c instanceof Error?c.message:"Unknown error")}}return t.info("[WARNING] Session data not found in Next.js scripts"),null}parseNextJsFlightPayload(e){if(typeof e!="string"||e.length===0)return null;let n=e.trim(),s=n.indexOf(":");if(s>0&&s<20){let o=n.slice(0,s);/^[a-z0-9]+$/i.test(o)&&(n=n.slice(s+1))}n=n.trim();let r=n.replace(/"/g,'"').replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&");try{return JSON.parse(r)}catch(o){return t.debug("[DEBUG] Failed to parse Next.js flight string:",o instanceof Error?o.message:"Unknown error"),null}}findSessionInData(e,n){if(e==null)return null;if(typeof e=="string"){let s=this.parseNextJsFlightPayload(e);return s?this.findSessionInData(s,n):null}if(Array.isArray(e)){let s=e;if(n.has(s))return null;n.add(s);for(let r of e){let o=this.findSessionInData(r,n);if(o)return o}return null}if(typeof e=="object"){let s=e;if(n.has(s))return null;if(n.add(s),Object.prototype.hasOwnProperty.call(s,"session")&&s.session&&typeof s.session=="object")return s.session;for(let r of Object.values(s)){let o=this.findSessionInData(r,n);if(o)return o}}return null}buildAccountInfoFromSession(e){let n=e.user??{},s=n.kmsLogin??{},r=s.content??{},o;return typeof n.image=="string"?o=n.image!=="$undefined"?n.image:null:n.image===null&&(o=null),{name:typeof n.name=="string"?n.name:null,email:typeof n.email=="string"?n.email:null,role:typeof n.role=="string"?n.role:null,image:o,userId:typeof r.userId=="string"?r.userId:void 0,studentNo:typeof r.studentNo=="string"?r.studentNo:void 0,schoolCorporationType:typeof r.schoolCorporationType=="number"?r.schoolCorporationType:void 0,grade:typeof r.grade=="number"?r.grade:void 0,term:typeof r.term=="number"?r.term:void 0,isLobbyAdmin:typeof r.isLobbyAdmin=="boolean"?r.isLobbyAdmin:void 0,firstLoginFlg:typeof r.firstLoginFlg=="number"?r.firstLoginFlg:void 0,kmsLoginSuccess:typeof s.success=="boolean"?s.success:void 0,staffDepartments:Array.isArray(r.staffDepartments)?r.staffDepartments:void 0,studentOrganizations:Array.isArray(r.studentOrganizations)?r.studentOrganizations:void 0,rawSession:e}}searchForNewsInData(e,n=""){if(!e||typeof e!="object")return[];if(Array.isArray(e)){if(e.length>0){let r=e[0];if(r&&typeof r=="object"&&["title","name","content","publishedAt","menuName","createdAt","updatedAt","id"].some(c=>c in r))return t.info(`[INFO] Found potential news array at path: ${n}, length: ${e.length}`),t.info("[INFO] Sample item properties:",Object.keys(r)),e}return[]}let s=[];for(let[r,o]of Object.entries(e)){let i=["news","announcements","data","items","list","content","notifications","posts","feed","results"],c=n?`${n}.${r}`:r;i.includes(r.toLowerCase())&&t.info(`[INFO] Searching priority key: ${c}`);let a=this.searchForNewsInData(o,c);s.push(...a)}return s}parseAnnouncementsWithCheerio(e){try{t.info("[TARGET] Starting Cheerio-based DOM parsing...");let n=j.load(e),s=n('div[role="presentation"]');if(t.info(`[INFO] Found ${s.length} div[role="presentation"] elements`),s.length<2)return t.info('[WARNING] Less than 2 div[role="presentation"] elements found'),[];let r=n(s[1]);t.info('[SUCCESS] Located second div[role="presentation"] element');let o=r.find('div[role="row"]');t.info(`[INFO] Found ${o.length} DataGrid rows`);let i=[];return o.each((c,a)=>{try{let l=n(a),u=l.attr("data-id");if(!u){t.info(`[WARNING] Row ${c} has no data-id attribute, skipping`);return}let d=l.find('div[role="gridcell"]');t.info(`[STATUS] Row ${u}: Found ${d.length} grid cells`);let p="",f="",C=new Date,g=!1,m=!1,S="";if(d.each((R,b)=>{let y=n(b);switch(y.attr("data-field")){case"title":{let k=y.find("a");if(k.length>0){let E=k.attr("href");E&&E.startsWith("/news/")?S=`${h.nlobby.baseUrl}${E}`:S=`${h.nlobby.baseUrl}/news/${u}`;let O=k.find("span");p=O.length>0?O.text().trim():k.text().trim()}else p=y.text().trim(),S=`${h.nlobby.baseUrl}/news/${u}`;break}case"menuName":f=y.text().trim();break;case"isImportant":{g=y.text().trim().length>0||y.find("*").length>0;break}case"isUnread":{let k=y.text().trim();m=k.includes("\u672A\u8AAD")||k.length>0;break}case"publishedAt":{let k=y.text().trim();if(k){let E=new Date(k.replace(/\//g,"-"));isNaN(E.getTime())||(C=E)}break}}}),p){let R=S||`${h.nlobby.baseUrl}/news/${u}`,b={id:u,title:p,content:"",publishedAt:C,category:f||"General",priority:g?"high":"medium",targetAudience:["student"],url:R,menuName:f,isImportant:g,isUnread:m};i.push(b),t.info(`[SUCCESS] Added announcement: ${p.substring(0,50)}...`)}else t.info(`[WARNING] Row ${u}: No title found, skipping`)}catch(l){t.error(`[ERROR] Error parsing row ${c}:`,l instanceof Error?l.message:"Unknown error")}}),t.info(`[TARGET] Cheerio parsing completed: ${i.length} news items extracted`),i}catch(n){return t.error("[ERROR] Cheerio parsing failed:",n instanceof Error?n.message:"Unknown error"),[]}}parseNewsFromHtml(e){let n=[];try{t.info("[INFO] Starting HTML parsing..."),t.debug(`[DATA] HTML length: ${e.length} characters`),t.info("[STEP1] Extracting data from Next.js self.__next_f.push() calls...");let s=e.match(/self\.__next_f\.push\((\[.*?\])\)/g);if(s&&s.length>0){t.info(`[SUCCESS] Found ${s.length} self.__next_f.push() calls`);for(let a=0;a<s.length;a++){let l=s[a];try{let u=l.match(/self\.__next_f\.push\((\[.*?\])\)/);if(!u)continue;let d=JSON.parse(u[1]);if(t.info(`[INFO] Push call ${a+1}: Array length ${d.length}, types: [${d.map(f=>typeof f).join(", ")}]`),d.length>=2&&typeof d[1]=="string"){let f=d[1],C=f.match(/^(\d+):(.*)/);if(C){t.info(`[INFO] Push call ${a+1}: Found prefixed data with prefix "${C[1]}"`);try{let g=C[2],m=JSON.parse(g);if(t.info(`[INFO] Push call ${a+1}: Parsed content type: ${typeof m}, isArray: ${Array.isArray(m)}`),Array.isArray(m))for(let S=0;S<m.length;S++){let R=m[S];if(Array.isArray(R)&&R.length>=4&&R[3]&&typeof R[3]=="object"){let b=R[3];if(t.info(`[INFO] Push call ${a+1}, item ${S}: Component data keys: [${Object.keys(b).join(", ")}]`),b.news&&Array.isArray(b.news)&&(t.info(`[SUCCESS] Found news array in push call ${a+1}, item ${S}: ${b.news.length} items`),b.news.length>0)){let y=b.news[0];if(y&&typeof y=="object"&&(y.id||y.title||y.microCmsId))return t.info(`[TARGET] Validated news data structure in push call ${a+1}`),this.transformNewsToAnnouncements(b.news)}}}}catch(g){t.info(`[WARNING] Failed to parse prefixed JSON in push call ${a+1}:`,g instanceof Error?g.message:"Unknown error")}}else try{let g=JSON.parse(f),m=this.searchForNewsInData(g,`push_call_${a+1}_fallback`);if(m&&m.length>0)return t.info(`[SUCCESS] Found ${m.length} news items in push call ${a+1} (fallback)`),this.transformNewsToAnnouncements(m)}catch(g){t.info(`[WARNING] Failed to parse inner JSON in push call ${a+1}:`,g instanceof Error?g.message:"Unknown error")}}let p=this.searchForNewsInData(d,`push_call_${a+1}_direct`);if(p&&p.length>0)return t.info(`[SUCCESS] Found ${p.length} news items directly in push call ${a+1}`),this.transformNewsToAnnouncements(p)}catch(u){t.info(`[WARNING] Failed to parse push call ${a+1}:`,u instanceof Error?u.message:"Unknown error")}}}else t.info("[WARNING] No self.__next_f.push() calls found in HTML");t.info("[STEP2] Attempting DOM parsing with Cheerio as fallback...");let r=this.parseAnnouncementsWithCheerio(e);if(r&&r.length>0)return t.info(`[SUCCESS] Cheerio DOM parsing successful: ${r.length} news items found`),r;t.info("[WARNING] Cheerio DOM parsing returned no results"),t.info("[STEP2] Trying to extract data from __NEXT_DATA__...");let o=[e.match(/window\.__NEXT_DATA__\s*=\s*({.*?})\s*(?:;|<\/script>)/s),e.match(/<script id="__NEXT