nlobby-cli 1.3.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 +358 -0
- package/SKILLS.md +66 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +128 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Minagishl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# N Lobby CLI & MCP Server
|
|
2
|
+
|
|
3
|
+
> **Note:** The developer assumes no responsibility for any damages that may occur from using this tool. This software was developed for educational purposes and its operation is not guaranteed.
|
|
4
|
+
|
|
5
|
+
A dual-mode CLI and Model Context Protocol (MCP) server for accessing N Lobby school portal data. Use it interactively from the terminal with `nlobby`, or connect it to an AI assistant as an MCP server with `nlobby serve`.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **CLI Mode**: Access N Lobby data directly from the terminal — news, schedule, courses, profile, and more
|
|
10
|
+
- **MCP Mode**: Full MCP server compatible with Claude, Cursor, and other MCP-enabled AI assistants
|
|
11
|
+
- **Browser-based Authentication**: Interactive login via automated Puppeteer browser window
|
|
12
|
+
- **Session Persistence**: CLI mode saves cookies to `~/.nlobby/session` for seamless subsequent use
|
|
13
|
+
- **School Information Access**: Retrieve announcements, schedules, and learning resources
|
|
14
|
+
- **Required Courses Management**: Access required course information and academic data
|
|
15
|
+
- **Multiple Calendar Types**: Support for both personal and school calendars
|
|
16
|
+
- **User Role Support**: Different access levels for students, parents, and staff
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Option 1: Install from npm (Recommended)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g nlobby-cli
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Option 2: Development Installation
|
|
27
|
+
|
|
28
|
+
1. Clone the repository:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/minagishl/nlobby-mcp.git
|
|
32
|
+
cd nlobby-mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. Install dependencies:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
3. Build the project:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm run build
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
Create a `.env` file if you need to override defaults (optional):
|
|
50
|
+
|
|
51
|
+
```env
|
|
52
|
+
NLOBBY_BASE_URL=https://nlobby.nnn.ed.jp
|
|
53
|
+
MCP_SERVER_NAME=nlobby-mcp
|
|
54
|
+
MCP_SERVER_VERSION=1.0.0
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## CLI Usage
|
|
60
|
+
|
|
61
|
+
### Authentication
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Interactive browser login (recommended)
|
|
65
|
+
nlobby login
|
|
66
|
+
|
|
67
|
+
# Set cookies manually
|
|
68
|
+
nlobby cookies set "__Secure-next-auth.session-token=ey...;"
|
|
69
|
+
|
|
70
|
+
# Check current authentication status
|
|
71
|
+
nlobby cookies check
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### News
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# List latest news (default: 10, newest first)
|
|
78
|
+
nlobby news
|
|
79
|
+
|
|
80
|
+
# Filter and sort
|
|
81
|
+
nlobby news --limit 20 --category お知らせ --sort oldest --unread
|
|
82
|
+
|
|
83
|
+
# Show full article
|
|
84
|
+
nlobby news show 980
|
|
85
|
+
|
|
86
|
+
# Mark as read
|
|
87
|
+
nlobby news read 980
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Schedule & Calendar
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Today's schedule
|
|
94
|
+
nlobby schedule
|
|
95
|
+
|
|
96
|
+
# Specific date
|
|
97
|
+
nlobby schedule 2026-04-01
|
|
98
|
+
|
|
99
|
+
# This week's personal calendar
|
|
100
|
+
nlobby calendar
|
|
101
|
+
|
|
102
|
+
# School calendar for a date range
|
|
103
|
+
nlobby calendar --type school --from 2026-04-01 --to 2026-04-07
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Courses
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# All required courses
|
|
110
|
+
nlobby courses
|
|
111
|
+
|
|
112
|
+
# Filter by grade / semester
|
|
113
|
+
nlobby courses --grade 2 --semester 2025
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Profile & Health
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
nlobby profile
|
|
120
|
+
nlobby health
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### MCP Server
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Start MCP server (stdio transport)
|
|
127
|
+
nlobby serve
|
|
128
|
+
# or
|
|
129
|
+
nlobby mcp
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> All commands support `--json` to output raw JSON instead of formatted text.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## MCP Usage
|
|
137
|
+
|
|
138
|
+
<details>
|
|
139
|
+
<summary>Setup with Cursor and Other MCP Clients</summary>
|
|
140
|
+
|
|
141
|
+
### Cursor IDE Setup
|
|
142
|
+
|
|
143
|
+
[](cursor://anysphere.cursor-deeplink/mcp/install?name=nlobby&config=eyJlbnYiOnsiTkxPQkJZX0JBU0VfVVJMIjoiaHR0cHM6Ly9ubG9iYnkubm5uLmVkLmpwIn0sImNvbW1hbmQiOiJucHggLXkgbmxvYmJ5LWNsaSBzZXJ2ZSJ9)
|
|
144
|
+
|
|
145
|
+
Add the following to your Cursor settings (`~/.cursor/config.json`):
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"mcpServers": {
|
|
150
|
+
"nlobby": {
|
|
151
|
+
"command": "npx",
|
|
152
|
+
"args": ["-y", "nlobby-cli", "serve"],
|
|
153
|
+
"env": {
|
|
154
|
+
"NLOBBY_BASE_URL": "https://nlobby.nnn.ed.jp"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Claude Desktop Setup
|
|
162
|
+
|
|
163
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"mcpServers": {
|
|
168
|
+
"nlobby": {
|
|
169
|
+
"command": "npx",
|
|
170
|
+
"args": ["-y", "nlobby-cli", "serve"],
|
|
171
|
+
"env": {
|
|
172
|
+
"NLOBBY_BASE_URL": "https://nlobby.nnn.ed.jp"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Other MCP Clients
|
|
180
|
+
|
|
181
|
+
For any MCP-compatible client, use:
|
|
182
|
+
|
|
183
|
+
- **Command**: `nlobby serve` (if installed globally) or `node /path/to/dist/index.js serve`
|
|
184
|
+
- **Protocol**: stdio
|
|
185
|
+
- **Environment**: Optional environment variables as listed in the Configuration section
|
|
186
|
+
|
|
187
|
+
</details>
|
|
188
|
+
|
|
189
|
+
### MCP Resources
|
|
190
|
+
|
|
191
|
+
| URI | Description |
|
|
192
|
+
| --------------------------- | ----------------------------------------- |
|
|
193
|
+
| `nlobby://news` | School news and notices |
|
|
194
|
+
| `nlobby://schedule` | Daily class schedule and events |
|
|
195
|
+
| `nlobby://required-courses` | Required courses and academic information |
|
|
196
|
+
| `nlobby://user-profile` | Current user information |
|
|
197
|
+
|
|
198
|
+
### MCP Tools
|
|
199
|
+
|
|
200
|
+
#### Authentication
|
|
201
|
+
|
|
202
|
+
| Tool | Description |
|
|
203
|
+
| ----------------------- | ----------------------------------------------- |
|
|
204
|
+
| `interactive_login` | Open browser for manual login (recommended) |
|
|
205
|
+
| `login_help` | Personalized login help and troubleshooting |
|
|
206
|
+
| `set_cookies` | Manually set authentication cookies |
|
|
207
|
+
| `check_cookies` | Check authentication cookie status |
|
|
208
|
+
| `verify_authentication` | Verify authentication status across all clients |
|
|
209
|
+
|
|
210
|
+
#### News
|
|
211
|
+
|
|
212
|
+
| Tool | Key Parameters | Description |
|
|
213
|
+
| ---------------------- | ---------------------------- | ------------------------------------- |
|
|
214
|
+
| `get_news` | `category?` `limit?` `sort?` | Retrieve school news with filtering |
|
|
215
|
+
| `get_news_detail` | `newsId` `markAsRead?` | Full detail for a specific article |
|
|
216
|
+
| `mark_news_as_read` | `ids` (array) | Mark articles as read |
|
|
217
|
+
| `get_unread_news_info` | | Unread count and important-news flags |
|
|
218
|
+
|
|
219
|
+
#### Schedule & Calendar
|
|
220
|
+
|
|
221
|
+
| Tool | Key Parameters | Description |
|
|
222
|
+
| ------------------------- | -------------------------------------------------- | --------------------------------- |
|
|
223
|
+
| `get_schedule` | `date?` | Schedule for a date (YYYY-MM-DD) |
|
|
224
|
+
| `get_calendar_events` | `calendar_type?` `from_date?` `to_date?` `period?` | Calendar events (personal/school) |
|
|
225
|
+
| `test_calendar_endpoints` | `from_date?` `to_date?` | Test both calendar endpoints |
|
|
226
|
+
| `get_calendar_filters` | | Lobby calendar filter definitions |
|
|
227
|
+
|
|
228
|
+
#### Courses & Exams
|
|
229
|
+
|
|
230
|
+
| Tool | Key Parameters | Description |
|
|
231
|
+
| ---------------------- | -------------------------------- | --------------------------------------- |
|
|
232
|
+
| `get_required_courses` | `grade?` `semester?` `category?` | Required courses with progress tracking |
|
|
233
|
+
| `check_exam_day` | `date?` | Check if a date is an exam day |
|
|
234
|
+
| `finish_exam_day_mode` | | End exam day mode |
|
|
235
|
+
| `get_exam_otp` | | Get one-time password for exam |
|
|
236
|
+
|
|
237
|
+
#### Account & Navigation
|
|
238
|
+
|
|
239
|
+
| Tool | Description |
|
|
240
|
+
| ----------------------------- | -------------------------------------------- |
|
|
241
|
+
| `get_account_info` | Extract account info from Next.js page |
|
|
242
|
+
| `get_student_card_screenshot` | Capture student ID card screenshot |
|
|
243
|
+
| `update_last_access` | Update last access timestamp |
|
|
244
|
+
| `get_navigation_menus` | Main navigation menu list |
|
|
245
|
+
| `get_notifications` | Notification messages |
|
|
246
|
+
| `get_user_interests` | User interest tags (with optional icon data) |
|
|
247
|
+
| `get_interest_weights` | Interest weight scale definitions |
|
|
248
|
+
|
|
249
|
+
#### Debugging
|
|
250
|
+
|
|
251
|
+
| Tool | Key Parameters | Description |
|
|
252
|
+
| -------------------- | --------------------- | ------------------------------ |
|
|
253
|
+
| `health_check` | | Test N Lobby API connection |
|
|
254
|
+
| `debug_connection` | `endpoint?` | Detailed connection debugging |
|
|
255
|
+
| `test_page_content` | `endpoint?` `length?` | Page content retrieval testing |
|
|
256
|
+
| `test_trpc_endpoint` | `method` `params?` | Test a specific tRPC endpoint |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Authentication Flow
|
|
261
|
+
|
|
262
|
+
### Method 1: Interactive Browser Login (Recommended)
|
|
263
|
+
|
|
264
|
+
**CLI:**
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
nlobby login
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**MCP tool:** `interactive_login`
|
|
271
|
+
|
|
272
|
+
A browser window opens automatically. Complete the N Lobby login, and cookies are extracted and saved.
|
|
273
|
+
|
|
274
|
+
### Method 2: Manual Cookie Setup
|
|
275
|
+
|
|
276
|
+
1. Log in to N Lobby in your web browser
|
|
277
|
+
2. Open DevTools → Application / Storage → Cookies
|
|
278
|
+
3. Copy all cookies as a string
|
|
279
|
+
|
|
280
|
+
**CLI:**
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
nlobby cookies set "__Secure-next-auth.session-token=ey...;"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**MCP tool:** `set_cookies cookies="__Secure-next-auth.session-token=ey...;"`
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## User Types
|
|
291
|
+
|
|
292
|
+
The server supports three user types based on email domain:
|
|
293
|
+
|
|
294
|
+
| Type | Email Domain |
|
|
295
|
+
| ------------ | -------------------------- |
|
|
296
|
+
| **Students** | `@nnn.ed.jp` |
|
|
297
|
+
| **Staff** | `@nnn.ac.jp` |
|
|
298
|
+
| **Parents** | Any other registered email |
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Project Structure
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
src/
|
|
306
|
+
├── index.ts # Entry point — CLI vs MCP mode detection
|
|
307
|
+
├── config.ts # Configuration management
|
|
308
|
+
├── logger.ts # Logging utilities
|
|
309
|
+
├── trpc-client.ts # tRPC client for API calls
|
|
310
|
+
├── types.ts # TypeScript type definitions
|
|
311
|
+
├── api/
|
|
312
|
+
│ ├── index.ts # NLobbyApi facade + session persistence
|
|
313
|
+
│ ├── context.ts # ApiContext interface
|
|
314
|
+
│ ├── shared.ts # Shared utilities (fetchRenderedHtml, …)
|
|
315
|
+
│ ├── news.ts # News functions
|
|
316
|
+
│ ├── schedule.ts # Schedule / calendar functions
|
|
317
|
+
│ ├── courses.ts # Course / exam functions
|
|
318
|
+
│ ├── account.ts # Account info / student card functions
|
|
319
|
+
│ ├── navigation.ts # Navigation / notification / interest functions
|
|
320
|
+
│ └── health.ts # Health check / debug functions
|
|
321
|
+
├── auth/
|
|
322
|
+
│ ├── browser.ts # Puppeteer browser authentication
|
|
323
|
+
│ ├── nextauth.ts # NextAuth.js session handling
|
|
324
|
+
│ └── credentials.ts # Credential validation and guidance
|
|
325
|
+
├── cli/
|
|
326
|
+
│ ├── index.ts # Commander program wiring
|
|
327
|
+
│ ├── commands/ # login, news, schedule, courses, profile, health, serve
|
|
328
|
+
│ └── formatters/ # Human-readable output formatters
|
|
329
|
+
└── mcp/
|
|
330
|
+
└── server.ts # MCP server (28 tools, 4 resources)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Development
|
|
336
|
+
|
|
337
|
+
### Scripts
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
pnpm run build # Build (esbuild bundle + tsc type declarations)
|
|
341
|
+
pnpm run dev # Watch mode
|
|
342
|
+
pnpm run start # Start MCP server
|
|
343
|
+
pnpm run lint # Lint
|
|
344
|
+
pnpm run format # Format
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Security Notes
|
|
348
|
+
|
|
349
|
+
- CLI cookies are stored in `~/.nlobby/session` (plain text — protect accordingly)
|
|
350
|
+
- MCP mode keeps all authentication tokens in memory only
|
|
351
|
+
- Browser automation is used only for authentication, not data scraping
|
|
352
|
+
- No sensitive data is logged
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
|
package/SKILLS.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# N Lobby CLI & MCP Server — Skills Reference
|
|
2
|
+
|
|
3
|
+
## CLI Commands
|
|
4
|
+
|
|
5
|
+
| Command | Options / Arguments | Description |
|
|
6
|
+
| ------------------------------ | --------------------------------------------------------------------------------------------------- | ------------------------------------------ |
|
|
7
|
+
| `nlobby login` | | Open browser for interactive login |
|
|
8
|
+
| `nlobby cookies set <cookies>` | | Set cookies manually |
|
|
9
|
+
| `nlobby cookies check` | | Show current authentication status |
|
|
10
|
+
| `nlobby news` | `--limit <n>` `--category <cat>` `--sort newest\|oldest\|title-asc\|title-desc` `--unread` `--json` | List news (default: 10, newest first) |
|
|
11
|
+
| `nlobby news show <id>` | `--json` | Show news article detail |
|
|
12
|
+
| `nlobby news read <id>` | | Mark news article as read |
|
|
13
|
+
| `nlobby schedule [date]` | `--json` | Show schedule (YYYY-MM-DD, default: today) |
|
|
14
|
+
| `nlobby calendar` | `--from <date>` `--to <date>` `--type personal\|school` `--json` | Show calendar events (default: this week) |
|
|
15
|
+
| `nlobby courses` | `--grade <n>` `--semester <n>` `--json` | Show required courses |
|
|
16
|
+
| `nlobby profile` | `--json` | Show user profile / account info |
|
|
17
|
+
| `nlobby health` | `--json` | Check API connectivity and authentication |
|
|
18
|
+
| `nlobby serve` / `nlobby mcp` | | Start MCP server (stdio transport) |
|
|
19
|
+
|
|
20
|
+
> All commands support `--json` to output raw JSON instead of formatted text.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## MCP Tools
|
|
25
|
+
|
|
26
|
+
| Tool | Parameters | Description |
|
|
27
|
+
| ----------------------------- | -------------------------------------------------- | --------------------------------------- |
|
|
28
|
+
| `get_news` | `category?` `limit?` `sort?` | Retrieve school news |
|
|
29
|
+
| `get_news_detail` | `newsId` `markAsRead?` | Get full detail of a news article |
|
|
30
|
+
| `mark_news_as_read` | `ids` (array) | Mark news articles as read |
|
|
31
|
+
| `get_unread_news_info` | | Unread news count and important flags |
|
|
32
|
+
| `get_schedule` | `date?` | School schedule for a date (YYYY-MM-DD) |
|
|
33
|
+
| `get_calendar_events` | `calendar_type?` `from_date?` `to_date?` `period?` | Calendar events (personal or school) |
|
|
34
|
+
| `test_calendar_endpoints` | `from_date?` `to_date?` | Test both calendar endpoints |
|
|
35
|
+
| `get_calendar_filters` | | Lobby calendar filter list |
|
|
36
|
+
| `get_required_courses` | `grade?` `semester?` `category?` | Required courses with progress tracking |
|
|
37
|
+
| `check_exam_day` | `date?` | Check if date is an exam day |
|
|
38
|
+
| `finish_exam_day_mode` | | Finish exam day mode |
|
|
39
|
+
| `get_exam_otp` | | Get one-time password for exam |
|
|
40
|
+
| `get_account_info` | | Extract account info from Next.js page |
|
|
41
|
+
| `get_student_card_screenshot` | | Capture student ID card screenshot |
|
|
42
|
+
| `update_last_access` | | Update last access timestamp |
|
|
43
|
+
| `get_navigation_menus` | | Main navigation menu list |
|
|
44
|
+
| `get_notifications` | | Notification messages |
|
|
45
|
+
| `get_user_interests` | `with_icon?` | User interest tags |
|
|
46
|
+
| `get_interest_weights` | | Interest weight scale definitions |
|
|
47
|
+
| `set_cookies` | `cookies` | Set authentication cookies |
|
|
48
|
+
| `check_cookies` | | Check authentication cookie status |
|
|
49
|
+
| `health_check` | | Check API connectivity |
|
|
50
|
+
| `debug_connection` | `endpoint?` | Debug connection with detailed info |
|
|
51
|
+
| `test_page_content` | `endpoint?` `length?` | Retrieve and sample page content |
|
|
52
|
+
| `test_trpc_endpoint` | `method` `params?` | Test a tRPC endpoint directly |
|
|
53
|
+
| `verify_authentication` | | Verify cookie sync across all clients |
|
|
54
|
+
| `interactive_login` | | Open browser for manual login |
|
|
55
|
+
| `login_help` | `email?` | Login help and troubleshooting |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## MCP Resources
|
|
60
|
+
|
|
61
|
+
| URI | Name | Description |
|
|
62
|
+
| --------------------------- | ---------------- | ----------------------------------------- |
|
|
63
|
+
| `nlobby://news` | School News | Latest school news and notices |
|
|
64
|
+
| `nlobby://schedule` | School Schedule | Daily class schedule and events |
|
|
65
|
+
| `nlobby://user-profile` | User Profile | Current user information and preferences |
|
|
66
|
+
| `nlobby://required-courses` | Required Courses | Required courses and academic information |
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var Dt=Object.defineProperty;var p=(o,e)=>()=>(o&&(e=o(o=0)),e);var P=(o,e)=>{for(var t in e)Dt(o,t,{get:e[t],enumerable:!0})};var J={};P(J,{LogLevel:()=>z,Logger:()=>$,logger:()=>i});var z,$,i,m=p(()=>{"use strict";z=(r=>(r[r.DEBUG=0]="DEBUG",r[r.INFO=1]="INFO",r[r.WARN=2]="WARN",r[r.ERROR=3]="ERROR",r))(z||{}),$=class o{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 o.instance||(o.instance=new o),o.instance}setLogLevel(e){this.logLevel=e}forceProductionMode(){this.isProduction=!0}log(e,t,...n){if(e<this.logLevel||this.isProduction&&e<2)return;let r=new Date().toISOString(),s=z[e],a=`[${r}] [${s}]`;switch(e){case 0:console.debug(a,t,...n);break;case 1:console.info(a,t,...n);break;case 2:console.warn(a,t,...n);break;case 3:console.error(a,t,...n);break}}debug(e,...t){this.log(0,e,...t)}info(e,...t){this.log(1,e,...t)}warn(e,...t){this.log(2,e,...t)}error(e,...t){this.log(3,e,...t)}},i=$.getInstance()});import{config as It}from"dotenv";function Lt(){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 g,S=p(()=>{"use strict";It();g={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||Lt()}});var O,ae=p(()=>{"use strict";m();O=class{cookies={};constructor(){}parseCookies(e){let t={},n=e.split(";");for(let r of n){let[s,a]=r.trim().split("=");s&&a&&(s==="__Secure-next-auth.session-token"?t.sessionToken=decodeURIComponent(a):s==="__Host-next-auth.csrf-token"?t.csrfToken=decodeURIComponent(a):s==="__Secure-next-auth.callback-url"&&(t.callbackUrl=decodeURIComponent(a)))}return t}setCookies(e){this.cookies=this.parseCookies(e),i.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 i.error("Error decoding session token:",e),null}}clear(){this.cookies={}}}});import Ut from"axios";var _,ce=p(()=>{"use strict";S();m();_=class{httpClient;nextAuth;requestId=1;allCookies="";constructor(e){this.nextAuth=e,this.httpClient=Ut.create({baseURL:`${g.nlobby.baseUrl}/api/trpc`,timeout:15e3,headers:{"Content-Type":"application/json","User-Agent":g.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()===""){i.warn("[WARNING] Empty cookies provided to tRPC client"),this.allCookies="";return}this.allCookies=e,i.info("[SUCCESS] tRPC client cookies updated"),i.info(`[SIZE] tRPC cookie string length: ${e.length}`),this.allCookies===e?i.info("[SUCCESS] tRPC cookie verification successful"):i.error("[ERROR] tRPC cookie verification failed")}setupInterceptors(){this.httpClient.interceptors.request.use(e=>{let t;if(this.allCookies&&this.allCookies.trim()!=="")t=this.allCookies,i.debug("[COOKIE] Using all cookies for tRPC request");else{let s=this.nextAuth.getCookieHeader();s&&s.trim()!==""?(t=s,i.debug("[COOKIE] Using NextAuth cookies for tRPC request")):i.warn("[WARNING] No cookies available for tRPC request")}t&&(e.headers.Cookie=t);let n=this.nextAuth.getSessionToken();n?(e.headers.Authorization=`Bearer ${n}`,i.debug("Added Authorization header with session token")):i.warn("[WARNING] No session token available for Authorization header");let r=this.nextAuth.getCookies().csrfToken;return r&&(e.headers["X-CSRF-Token"]=r,i.debug("Added CSRF token to tRPC request")),i.debug("[REQUEST] tRPC request details:",{url:e.url,method:e.method?.toUpperCase(),hasCookies:!!t,hasCSRF:!!r,hasAuth:!!n,cookieSource:this.allCookies?"allCookies":"nextAuth"}),e}),this.httpClient.interceptors.response.use(e=>(i.debug("[SUCCESS] tRPC response received:",{status:e.status,statusText:e.statusText,hasData:!!e.data}),e),async e=>{if(i.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 i.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 i.error("[BLOCKED] Access forbidden - insufficient permissions"),new Error("Access forbidden. Check your permissions or re-authenticate.");if(e.response?.status===404)throw i.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,t){let n={id:this.getNextRequestId(),method:e,params:t};try{i.info(`[REQUEST] tRPC call: ${e}`,t?`with params: ${JSON.stringify(t)}`:"without params");let r=this.allCookies||this.nextAuth.getCookieHeader();i.debug(`[COOKIE] Request cookies: ${r?"present":"missing"}`),i.debug("Trying GET approach...");try{let s=this.buildTRPCUrl(e,t);i.debug(`[URL] tRPC GET URL: ${s}`);let a=await this.httpClient.get(s);if(i.debug(`[SUCCESS] tRPC ${e} GET response status: ${a.status}`),a.data.error)throw i.error(`[ERROR] tRPC ${e} GET returned error:`,a.data.error),new Error(`tRPC Error [${a.data.error.code}]: ${a.data.error.message}`);return i.debug(`[SUCCESS] tRPC ${e} GET succeeded`),a.data.result}catch(s){i.debug("[WARNING] GET approach failed, trying POST approach..."),i.debug("[DEBUG] GET error details:",s instanceof Error?s.message:"Unknown error");let a=e;i.debug(`[URL] tRPC POST URL: ${a}`);let c=await this.httpClient.post(a,n,{headers:{"Content-Type":"application/json"}});if(i.debug(`[SUCCESS] tRPC ${e} POST response status: ${c.status}`),c.data.error)throw i.error(`[ERROR] tRPC ${e} POST returned error:`,c.data.error),new Error(`tRPC Error [${c.data.error.code}]: ${c.data.error.message}`);return i.debug(`[SUCCESS] tRPC ${e} POST succeeded`),c.data.result}}catch(r){if(i.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 s=r;i.error(`[DEBUG] tRPC ${e} Axios error details:`,{status:s.response?.status,statusText:s.response?.statusText,headers:s.response?.headers,data:s.response?.data,url:s.config?.url,method:s.config?.method,timeout:s.config?.timeout,cookies:s.config?.headers?.Cookie?"present":"missing"}),s.response?.status===401?i.error("[BLOCKED] tRPC 401 Unauthorized - session may be expired or invalid"):s.response?.status===403?i.error("[BLOCKED] tRPC 403 Forbidden - insufficient permissions"):s.response?.status===404?i.error("[BLOCKED] tRPC 404 Not Found - endpoint may not exist"):s.response?.status&&s.response.status>=500&&i.error("[BLOCKED] tRPC Server Error - N Lobby backend issue")}else if(r&&typeof r=="object"&&"code"in r){let s=r;s.code==="ECONNREFUSED"?i.error("[NETWORK] Network Error: Connection refused - N Lobby may be down"):s.code==="ETIMEDOUT"?i.error("[TIMEOUT] Network Error: Request timeout - slow network or server overload"):s.code==="ENOTFOUND"&&i.error("[NETWORK] Network Error: DNS lookup failed - check internet connection")}throw r}}buildTRPCUrl(e,t){let n=e,r=JSON.stringify(t||{}),s=new URLSearchParams({input:r}).toString();return`${n}?${s}`}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,t){return this.call("calendar.getLobbyCalendarEvents",{from:e,to:t})}async healthCheck(){i.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:t,method:n}of e)try{return i.debug(`Trying tRPC method: ${t}`),await n(),i.info(`[SUCCESS] tRPC health check passed with method: ${t}`),!0}catch(r){i.debug(`[ERROR] tRPC method ${t} failed:`,r instanceof Error?r.message:"Unknown error");continue}return i.error("[ERROR] All tRPC health check methods failed"),!1}}});async function v(o,e){try{i.info("[NETWORK] Fetching HTML using HTTP client (proven method)..."),i.debug(`[URL] URL: ${g.nlobby.baseUrl+e}`),i.info("[COOKIE] Cookies:",o.httpClient.defaults.headers.Cookie?"present":"missing");let t=await o.httpClient.get(e,{headers:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","Accept-Language":"ja,en-US;q=0.7,en;q=0.3","Accept-Encoding":"gzip, deflate, br",Connection:"keep-alive","Upgrade-Insecure-Requests":"1","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"none","Cache-Control":"max-age=0","User-Agent":g.userAgent},withCredentials:!0});if(i.info(`[SUCCESS] HTTP response: ${t.status} ${t.statusText}`),i.info(`[DATA] Content length: ${t.data?.length||"unknown"}`),i.info(`[DATA] Content type: ${t.headers["content-type"]||"unknown"}`),typeof t.data=="string"){let n=t.data,r=n.toLowerCase();if(r.includes("\u30ED\u30B0\u30A4\u30F3")||r.includes("login")?i.warn("[WARNING] WARNING: Page contains login keywords - authentication may have failed"):(r.includes("news")||r.includes("\u304A\u77E5\u3089\u305B"))&&i.info("[SUCCESS] Page appears to contain news content"),r.includes("unauthorized")||r.includes("access denied"))throw new Error("Access denied - authentication failed");return i.info(`[TARGET] HTML retrieved successfully: ${n.length} characters`),n}else throw new Error(`Non-string response received: ${typeof t.data}`)}catch(t){if(i.error("[ERROR] HTTP fetch error:",t instanceof Error?t.message:"Unknown error"),t&&typeof t=="object"&&"response"in t){let n=t;i.debug("[DEBUG] HTTP Error Details:",{status:n.response?.status,statusText:n.response?.statusText,url:n.config?.url,hasData:!!n.response?.data})}throw t}}var Y=p(()=>{"use strict";S();m()});import*as le from"cheerio";function Pt(o){if(!o)return"";try{return o.replace(/\\u003c/g,"<").replace(/\\u003e/g,">").replace(/\\u0026/g,"&").replace(/\\"/g,'"').replace(/\\\\/g,"\\")}catch{return o}}function ue(o,e=""){if(!o||typeof o!="object")return null;let t=o;if(t.id&&t.title&&(t.publishedAt||t.description||t.menuName))return i.info(`[INFO] Found news object at path: ${e}`),o;if(t.news&&typeof t.news=="object")return i.info(`[INFO] Found news property at path: ${e}.news`),t.news;for(let[n,r]of Object.entries(t))if(r&&typeof r=="object"){let s=e?`${e}.${n}`:n,a=ue(r,s);if(a)return a}return null}function j(o,e=""){if(!o||typeof o!="object")return[];if(Array.isArray(o)){if(o.length>0){let n=o[0];if(n&&typeof n=="object"&&["title","name","content","publishedAt","menuName","createdAt","updatedAt","id"].some(a=>a in n))return i.info(`[INFO] Found potential news array at path: ${e}, length: ${o.length}`),o}return[]}let t=[];for(let[n,r]of Object.entries(o)){let s=["news","announcements","data","items","list","content","notifications","posts","feed","results"],a=e?`${e}.${n}`:n;s.includes(n.toLowerCase())&&i.info(`[INFO] Searching priority key: ${a}`);let c=j(r,a);t.push(...c)}return t}function F(o){return o.map((e,t)=>{let n=e,r=new Date;n.publishedAt?r=new Date(n.publishedAt):n.createdAt?r=new Date(n.createdAt):n.updatedAt?r=new Date(n.updatedAt):n.date&&(r=new Date(n.date));let s=n.title||n.name||n.subject||n.heading||`News Item ${t+1}`,a=n.content||n.description||n.body||n.text||n.summary||"",c=n.category||n.menuName||n.type||n.classification||"General",l="medium";n.isImportant===!0||n.important===!0||n.priority==="high"||n.urgent===!0?l="high":(n.priority==="low"||n.minor===!0)&&(l="low");let u=n.id||t,d=`${g.nlobby.baseUrl}/news/${u}`;return{id:n.id?.toString()||t.toString(),title:s,content:a,publishedAt:r,category:c,priority:l,targetAudience:n.targetAudience||["student"],url:d,menuName:n.menuName,isImportant:!!n.isImportant,isUnread:!!n.isUnread,...Object.fromEntries(Object.entries(n).filter(([y])=>!["id","title","content","publishedAt","category","priority","url"].includes(y)))}})}function $t(o){try{i.info("[TARGET] Starting Cheerio-based DOM parsing...");let e=le.load(o),t=e('div[role="presentation"]');if(i.info(`[INFO] Found ${t.length} div[role="presentation"] elements`),t.length<2)return i.info('[WARNING] Less than 2 div[role="presentation"] elements found'),[];let n=e(t[1]);i.info('[SUCCESS] Located second div[role="presentation"] element');let r=n.find('div[role="row"]');i.info(`[INFO] Found ${r.length} DataGrid rows`);let s=[];return r.each((a,c)=>{try{let l=e(c),u=l.attr("data-id");if(!u)return;let d=l.find('div[role="gridcell"]'),h="",y="",f=new Date,w=!1,b=!1,C="";if(d.each((x,W)=>{let N=e(W);switch(N.attr("data-field")){case"title":{let k=N.find("a");if(k.length>0){let E=k.attr("href");E&&E.startsWith("/news/")?C=`${g.nlobby.baseUrl}${E}`:C=`${g.nlobby.baseUrl}/news/${u}`;let ie=k.find("span");h=ie.length>0?ie.text().trim():k.text().trim()}else h=N.text().trim(),C=`${g.nlobby.baseUrl}/news/${u}`;break}case"menuName":y=N.text().trim();break;case"isImportant":{w=N.text().trim().length>0||N.find("*").length>0;break}case"isUnread":{let k=N.text().trim();b=k.includes("\u672A\u8AAD")||k.length>0;break}case"publishedAt":{let k=N.text().trim();if(k){let E=new Date(k.replace(/\//g,"-"));isNaN(E.getTime())||(f=E)}break}}}),h){let x=C||`${g.nlobby.baseUrl}/news/${u}`,W={id:u,title:h,content:"",publishedAt:f,category:y||"General",priority:w?"high":"medium",targetAudience:["student"],url:x,menuName:y,isImportant:w,isUnread:b};s.push(W)}}catch(l){i.error(`[ERROR] Error parsing row ${a}:`,l instanceof Error?l.message:"Unknown error")}}),i.info(`[TARGET] Cheerio parsing completed: ${s.length} news items extracted`),s}catch(e){return i.error("[ERROR] Cheerio parsing failed:",e instanceof Error?e.message:"Unknown error"),[]}}function Ot(o){let e=[];try{i.info("[INFO] Starting HTML parsing...");let t=o.match(/self\.__next_f\.push\((\[.*?\])\)/g);if(t&&t.length>0){i.info(`[SUCCESS] Found ${t.length} self.__next_f.push() calls`);for(let s=0;s<t.length;s++){let a=t[s];try{let c=a.match(/self\.__next_f\.push\((\[.*?\])\)/);if(!c)continue;let l=JSON.parse(c[1]);if(l.length>=2&&typeof l[1]=="string"){let d=l[1],h=d.match(/^(\d+):(.*)/);if(h)try{let y=h[2],f=JSON.parse(y);if(Array.isArray(f))for(let w=0;w<f.length;w++){let b=f[w];if(Array.isArray(b)&&b.length>=4&&b[3]&&typeof b[3]=="object"){let C=b[3];if(C.news&&Array.isArray(C.news)&&C.news.length>0){let x=C.news[0];if(x&&typeof x=="object"&&(x.id||x.title||x.microCmsId))return F(C.news)}}}}catch{}else try{let y=JSON.parse(d),f=j(y,`push_call_${s+1}_fallback`);if(f&&f.length>0)return F(f)}catch{}}let u=j(l,`push_call_${s+1}_direct`);if(u&&u.length>0)return F(u)}catch{}}}let n=$t(o);if(n&&n.length>0)return n;let r=[o.match(/window\.__NEXT_DATA__\s*=\s*({.*?})\s*(?:;|<\/script>)/s),o.match(/<script id="__NEXT_DATA__"[^>]*>([^<]*)<\/script>/s)];for(let s of r)if(s)try{let a=s[1]||s[0],c=JSON.parse(a),l=j(c,"__NEXT_DATA__");if(l&&l.length>0)return F(l)}catch{}}catch(t){i.error("[ERROR] Error parsing news from HTML:",t)}return e}function _t(o,e){try{let t=o.match(/self\.__next_f\.push\((\[.*?\])\)/g);if(!t||t.length===0)return null;let n=null,r="",s=new Map;for(let c=0;c<t.length;c++){let l=t[c];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 h=d[1].replace(/,$/,"");if(c+1<t.length){let f=t[c+1].match(/self\.__next_f\.push\((\[.*?\])\)/);if(f){let w=JSON.parse(f[1]);w.length>=2&&typeof w[1]=="string"&&s.set(h,w[1])}}continue}if(d.length>=2&&typeof d[1]=="string"){let y=d[1].match(/^(\d+):(.*)/);if(y)try{let f=y[2],w=JSON.parse(f),b=ue(w);b&&(n=b)}catch{}}}catch{}}if(!n)return null;if(n.description){for(let[c,l]of s)if(n.description.includes(c)){r=l;break}}return!r&&s.size>0&&(r=Array.from(s.values())[0]),{id:n.id||e,microCmsId:n.microCmsId,title:n.title||"No Title",content:Pt(r)||n.description||"",description:n.description,publishedAt:n.publishedAt?new Date(n.publishedAt):new Date,menuName:n.menuName||[],isImportant:n.isImportant||!1,isByMentor:n.isByMentor||!1,attachments:n.attachments||[],relatedEvents:n.relatedEvents||[],targetUserQueryId:n.targetUserQueryId,url:`${g.nlobby.baseUrl}/news/${e}`}}catch(t){return i.error("[ERROR] Error parsing news detail from HTML:",t),null}}async function de(o){i.info("[INFO] Starting getNews with HTTP client..."),i.info("[STATUS] Current authentication status:",o.getCookieStatus());try{let e=await v(o,"/news"),t=Ot(e);if(t&&t.length>0)return i.info(`[SUCCESS] Retrieved ${t.length} news items from HTML`),t;{let n=`HTML scraping returned no data. Debug info:
|
|
3
|
+
- Authentication status: ${o.nextAuth.isAuthenticated()?"authenticated":"not authenticated"}
|
|
4
|
+
- HTTP cookies: ${o.httpClient.defaults.headers.Cookie?"present":"missing"}
|
|
5
|
+
- HTML length: ${e.length} characters
|
|
6
|
+
|
|
7
|
+
Troubleshooting steps:
|
|
8
|
+
1. Run 'health_check' to verify connection
|
|
9
|
+
2. Ensure you are properly authenticated using 'set_cookies'`;throw new Error(n)}}catch(e){throw i.error("[ERROR] getNews failed:",e),e instanceof Error?e:new Error(`Failed to fetch news: ${e instanceof Error?e.message:"Unknown error"}`)}}async function pe(o,e){i.info(`[INFO] Fetching news detail for ID: ${e}`);try{let t=`/news/${e}`,n=await v(o,t),r=_t(n,e);if(r)return r;throw new Error(`Failed to parse news detail from HTML for news ID: ${e}`)}catch(t){throw i.error(`[ERROR] getNewsDetail failed for ID ${e}:`,t),t instanceof Error?t:new Error(`Failed to fetch news detail for ID ${e}: ${t instanceof Error?t.message:"Unknown error"}`)}}async function ge(o,e){i.info(`[INFO] Marking news article ${e} as read`);try{let t=await o.httpClient.post("/api/trpc/news.upsertBrowsingHistory",`"${e}"`,{headers:{"Content-Type":"application/json",Authorization:o.nextAuth.getCookieHeader(),Referer:`https://nlobby.nnn.ed.jp/news/${e}`}});return i.info(`[SUCCESS] News article ${e} marked as read`),t.data}catch(t){throw i.error(`[ERROR] Failed to mark news ${e} as read:`,t),t}}async function me(o){i.info("[INFO] Fetching unread news info...");try{let e=await o.trpcClient.call("news.getUnreadNewsInfo");return i.info("[SUCCESS] getUnreadNewsInfo succeeded"),e}catch(e){throw i.error("[ERROR] getUnreadNewsInfo failed:",e),e}}var he=p(()=>{"use strict";Y();S();m()});var G={};P(G,{CalendarType:()=>M});var M,R=p(()=>{"use strict";M=(t=>(t.PERSONAL="personal",t.SCHOOL="school",t))(M||{})});function K(){let o=new Date,e=new Date(o);e.setHours(0,0,0,0);let t=new Date(o);return t.setDate(t.getDate()+7),t.setHours(23,59,59,999),{from:e,to:t}}function Ft(o){return o.map(e=>{let t=e,n,r;t.startDateTime?(n=new Date(t.startDateTime),r=t.endDateTime?new Date(t.endDateTime):new Date(n.getTime()+3600*1e3)):t.start?(t.start.dateTime?n=new Date(t.start.dateTime):t.start.date?n=new Date(t.start.date+"T00:00:00"):n=new Date,t.end&&t.end.dateTime?r=new Date(t.end.dateTime):t.end&&t.end.date?(r=new Date(t.end.date+"T23:59:59"),r.setDate(r.getDate()-1)):r=new Date(n.getTime()+3600*1e3)):(n=new Date,r=new Date(n.getTime()+3600*1e3));let s="event",a=(t.summary||"").toLowerCase();a.includes("\u6388\u696D")||a.includes("class")?s="class":a.includes("mtg")||a.includes("\u30DF\u30FC\u30C6\u30A3\u30F3\u30B0")||a.includes("meeting")||a.includes("\u9762\u8AC7")?s="meeting":(a.includes("\u8A66\u9A13")||a.includes("exam")||a.includes("\u30C6\u30B9\u30C8"))&&(s="exam");let c=[];return t.attendees&&Array.isArray(t.attendees)&&(c=t.attendees.map(u=>u.email).filter(Boolean)),{id:t.id||t.microCmsId||Math.random().toString(),title:t.summary||t.title||"No Title",description:t.description||"",startTime:n,endTime:r,location:t.location||"",type:s,participants:c}})}async function D(o,e,t){let{CalendarType:n}=await Promise.resolve().then(()=>(R(),G));try{i.info(`[INFO] Fetching Google Calendar events for ${e}...`);let r=K(),s=t||r;i.info(`[INFO] Date range: ${s.from.toISOString()} to ${s.to.toISOString()}`);let a=e===n.PERSONAL?"/api/trpc/calendar.getGoogleCalendarEvents":"/api/trpc/calendar.getLobbyCalendarEvents",c={from:s.from.toISOString(),to:s.to.toISOString()},l=await o.httpClient.get(a,{params:{input:JSON.stringify(c)},headers:{Accept:"application/json","Content-Type":"application/json"},withCredentials:!0}),u=[],d=l.data;if(d?.result?.data?.gcal)u=d.result.data.gcal;else if(d?.result?.data?.lcal)u=d.result.data.lcal;else if(d?.result?.data&&Array.isArray(d.result.data))u=d.result.data;else if(d?.data?.gcal)u=d.data.gcal;else if(d?.data&&Array.isArray(d.data))u=d.data;else if(d?.gcal)u=d.gcal;else if(Array.isArray(d))u=d;else throw new Error(`Invalid calendar response format for ${e} calendar.`);if(!Array.isArray(u))throw new Error(`Calendar events is not an array: ${typeof u}`);return u}catch(r){throw i.error("[ERROR] Error fetching Google Calendar events:",r),r&&typeof r=="object"&&"response"in r&&r.response?.status===401?new Error("Authentication required. Please use the set_cookies tool to provide valid NextAuth.js session cookies from N Lobby."):r}}async function V(o,e,t){try{i.info(`[INFO] Fetching ${e} calendar events...`);let n=await D(o,e,t),r=Ft(n);return i.info(`[SUCCESS] Retrieved ${r.length} schedule items`),r}catch(n){throw i.error("[ERROR] Error fetching schedule:",n),new Error(`Failed to fetch ${e} calendar: ${n instanceof Error?n.message:"Unknown error"}`)}}async function fe(o,e){let{CalendarType:t}=await Promise.resolve().then(()=>(R(),G)),n;if(e){let r=new Date(e);if(isNaN(r.getTime()))throw new Error(`Invalid date format: ${e}`);let s=new Date(r);s.setHours(0,0,0,0);let a=new Date(r);a.setHours(23,59,59,999),n={from:s,to:a}}else n=K();return V(o,t.PERSONAL,n)}async function ye(o,e){let{CalendarType:t}=await Promise.resolve().then(()=>(R(),G)),n=e||K(),r={personal:{success:!1,count:0},school:{success:!1,count:0}};try{let s=await D(o,t.PERSONAL,n);r.personal.success=!0,r.personal.count=s.length}catch(s){r.personal.error=s instanceof Error?s.message:"Unknown error"}try{let s=await D(o,t.SCHOOL,n);r.school.success=!0,r.school.count=s.length}catch(s){r.school.error=s instanceof Error?s.message:"Unknown error"}return r}async function we(o){i.info("[INFO] Fetching lobby calendar filters...");try{let e=await o.trpcClient.call("calendar.getLobbyCalendarFilters",{});return i.info("[SUCCESS] getLobbyCalendarFilters succeeded"),e&&typeof e=="object"&&"result"in e?e.result?.filter||[]:Array.isArray(e)?e:[]}catch(e){throw i.error("[ERROR] getLobbyCalendarFilters failed:",e),e}}function be(o,e){let t=typeof o=="string"?new Date(o):o,n=typeof e=="string"?new Date(e):e;if(isNaN(t.getTime())||isNaN(n.getTime()))throw new Error("Invalid date format provided");if((n.getTime()-t.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:t,to:n}}function Ce(o){let e=typeof o=="string"?new Date(o):o;if(isNaN(e.getTime()))throw new Error("Invalid date format provided");let t=new Date(e);t.setHours(0,0,0,0);let n=new Date(e);return n.setHours(23,59,59,999),{from:t,to:n}}function ke(o){let e=o?typeof o=="string"?new Date(o):o:new Date;e.setHours(0,0,0,0);let t=new Date(e);return t.setDate(t.getDate()+6),t.setHours(23,59,59,999),{from:e,to:t}}function Se(o,e){let t=new Date,n=o||t.getFullYear(),r=e!==void 0?e:t.getMonth(),s=new Date(n,r,1,0,0,0,0),a=new Date(n,r+1,0,23,59,59,999);return{from:s,to:a}}var xe=p(()=>{"use strict";m()});function Ne(o,e=""){if(!o||typeof o!="object")return null;if(o.educationProcessName&&o.termYears&&Array.isArray(o.termYears))return i.info(`[INFO] Found education data at path: ${e}`),o;for(let[t,n]of Object.entries(o))if(n&&typeof n=="object"){let r=e?`${e}.${t}`:t,s=Ne(n,r);if(s)return s}return null}function jt(o){return o.allCount===0?0:Math.round(o.count/o.allCount*100)}function Mt(o){let e=o.filter(n=>n.score!==null&&n.progress===100);if(e.length===0)return null;let t=e.reduce((n,r)=>n+(r.score||0),0);return Math.round(t/e.length)}function Gt(o){let e=[];for(let t of o.termYears)for(let n of t.courses){let r=jt(n.report),s=Mt(n.reportDetails),a=n.acquired.acquisitionStatus===1,c=n.subjectStatus===1||n.subjectStatus===2,l={...n,termYear:t.termYear,grade:t.grade,term:t.term,progressPercentage:r,averageScore:s,isCompleted:a,isInProgress:c};e.push(l)}return e}async function Ee(o){i.info("[INFO] Starting getRequiredCourses...");try{let e=await o.trpcClient.call("requiredCourse.getRequiredCourses"),t=null,n=e;if(n&&n.result&&n.result.data)t=n.result.data;else if(n&&n.data)t=n.data;else if(n&&n.educationProcessName&&n.termYears)t=n;else{if(n&&Array.isArray(n))return n;if(n){let r=Ne(n);r&&(t=r)}}if(t){let r=Gt(t);return i.info(`[SUCCESS] Total courses extracted: ${r.length}`),r}else throw new Error("Unexpected response format from required courses endpoint.")}catch(e){throw i.error("[ERROR] getRequiredCourses failed:",e),e instanceof Error?e:new Error(`Failed to fetch required courses: ${e instanceof Error?e.message:"Unknown error"}`)}}async function Re(o,e){try{let t=e?{subject:e}:{},n=await o.httpClient.get("/api/learning-resources",{params:t});if(!n.data.success)throw new Error(n.data.error||"Failed to fetch learning resources");return n.data.data||[]}catch(t){throw i.error("Error fetching learning resources:",t),new Error("Authentication required. Please use the set_cookies tool to provide valid NextAuth.js session cookies from N Lobby.")}}async function Ae(o,e){i.info("[INFO] Checking if date is exam day...");let n=(e||new Date).toISOString();try{let r=await o.trpcClient.call("exam.isExamDay",n);return i.info(`[SUCCESS] isExamDay result: ${r}`),r===!0}catch(r){throw i.error("[ERROR] isExamDay failed:",r),r}}async function Te(o){i.info("[INFO] Finishing exam day mode...");try{let e=await o.httpClient.post("/api/trpc/exam.finishExamDayMode","{}",{headers:{"Content-Type":"application/json",Cookie:o.nextAuth.getCookieHeader()}});return i.info("[SUCCESS] finishExamDayMode succeeded"),e.data?.result?.data===!0}catch(e){throw i.error("[ERROR] finishExamDayMode failed:",e),e}}async function ve(o){i.info("[INFO] Fetching exam one-time password...");try{let e=await o.trpcClient.call("auth.student.examOneTimePasswordDisplay");return i.info("[SUCCESS] getExamOneTimePassword succeeded"),e}catch(e){throw i.error("[ERROR] getExamOneTimePassword failed:",e),e}}var De=p(()=>{"use strict";m()});import Ie from"node:fs/promises";import Ht from"node:os";import Le from"node:path";import Ue from"puppeteer";function qt(o){if(typeof o!="string"||o.length===0)return null;let e=o.trim(),t=e.indexOf(":");if(t>0&&t<20){let r=e.slice(0,t);/^[a-z0-9]+$/i.test(r)&&(e=e.slice(t+1))}e=e.trim();let n=e.replace(/"/g,'"').replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&");try{return JSON.parse(n)}catch{return null}}function I(o,e){if(o==null)return null;if(typeof o=="string"){let t=qt(o);return t?I(t,e):null}if(Array.isArray(o)){let t=o;if(e.has(t))return null;e.add(t);for(let n of o){let r=I(n,e);if(r)return r}return null}if(typeof o=="object"){let t=o;if(e.has(t))return null;if(e.add(t),Object.prototype.hasOwnProperty.call(t,"session")&&t.session&&typeof t.session=="object")return t.session;for(let n of Object.values(t)){let r=I(n,e);if(r)return r}}return null}function Bt(o){let e=/self\.__next_f\.push\((\[[\s\S]*?\])\)/g,t;for(;(t=e.exec(o))!==null;){let r=t[1];if(r)try{let s=JSON.parse(r),a=I(s,new WeakSet);if(a)return i.info("[SUCCESS] Found session data in Next.js flight payload"),a}catch{}}let n=[/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/,/window\.__NEXT_DATA__\s*=\s*({[\s\S]*?})(?:;|\s*<\/script>)/];for(let r of n){let s=o.match(r);if(!(!s||!s[1]))try{let a=JSON.parse(s[1]),c=I(a,new WeakSet);if(c)return i.info("[SUCCESS] Found session data in __NEXT_DATA__ payload"),c}catch{}}return null}function Wt(o){let e=o.user??{},t=e.kmsLogin??{},n=t.content??{},r;return typeof e.image=="string"?r=e.image!=="$undefined"?e.image:null:e.image===null&&(r=null),{name:typeof e.name=="string"?e.name:null,email:typeof e.email=="string"?e.email:null,role:typeof e.role=="string"?e.role:null,image:r,userId:typeof n.userId=="string"?n.userId:void 0,studentNo:typeof n.studentNo=="string"?n.studentNo:void 0,schoolCorporationType:typeof n.schoolCorporationType=="number"?n.schoolCorporationType:void 0,grade:typeof n.grade=="number"?n.grade:void 0,term:typeof n.term=="number"?n.term:void 0,isLobbyAdmin:typeof n.isLobbyAdmin=="boolean"?n.isLobbyAdmin:void 0,firstLoginFlg:typeof n.firstLoginFlg=="number"?n.firstLoginFlg:void 0,kmsLoginSuccess:typeof t.success=="boolean"?t.success:void 0,staffDepartments:Array.isArray(n.staffDepartments)?n.staffDepartments:void 0,studentOrganizations:Array.isArray(n.studentOrganizations)?n.studentOrganizations:void 0,rawSession:o}}function zt(o){let e=o.charAt(2)?.toUpperCase();return!e||e==="N"||!/[A-Z]/.test(e)?"secure.nnn.ed.jp":`${e.toLowerCase()}-secure.nnn.ed.jp`}function Jt(o,e){let t=[];for(let n of o.split(";")){let r=n.trim();if(!r)continue;let s=r.indexOf("=");if(s<=0)continue;let a=r.slice(0,s),c=r.slice(s+1);t.push({name:a,value:c,domain:e,path:"/",secure:!0,httpOnly:a.startsWith("__Secure-")||a.startsWith("__Host-")||a.toLowerCase().includes("session"),sameSite:"Lax"})}return t}async function Yt(){let o=["--no-sandbox","--disable-setuid-sandbox"],e=[process.env.PUPPETEER_EXECUTABLE_PATH,process.env.CHROME_PATH].filter(c=>!!c&&c.trim().length>0);for(let c of e)try{return await Ue.launch({headless:!0,executablePath:c,args:o})}catch{}let t=[],n=async(c,l)=>{try{return i.info(`[STUDENT_CARD] Trying browser launch via ${l}`),await Ue.launch(c)}catch(u){return u instanceof Error&&t.push(u),null}},r=await n({headless:!0,args:o},"default Puppeteer bundle");if(r)return r;let s=await n({headless:!0,channel:"chrome",args:o},"system Chrome channel");if(s)return s;let a=t.map(c=>c.message).join(`
|
|
10
|
+
`);throw new Error(`Failed to launch a browser instance for screenshot capture. Please install Chrome via "npx puppeteer browsers install chrome".
|
|
11
|
+
${a}`)}async function Kt(o){i.info("[STUDENT_CARD] Launching headless browser for student card capture");let e=await Yt();try{let t=await e.newPage();await t.setViewport({width:1280,height:720}),await t.setUserAgent(g.userAgent),o.cookies.length>0&&await t.setCookie(...o.cookies),await t.goto(o.startUrl,{waitUntil:"networkidle2",timeout:6e4}),await t.waitForSelector(o.waitForSelector,{timeout:6e4}),await new Promise(u=>setTimeout(u,1e3));let n=await t.$(o.waitForSelector);if(!n)throw new Error(`Failed to locate element ${o.waitForSelector} for screenshot`);let r=await n.screenshot({type:"png"}),s=Le.join(Ht.tmpdir(),"nlobby-student-card");await Ie.mkdir(s,{recursive:!0});let a=Le.join(s,o.screenshotName);await Ie.writeFile(a,r);let c=await n.boundingBox(),l=c?{width:Math.round(c.width),height:Math.round(c.height)}:void 0;return{base64:r.toString("base64"),path:a,finalUrl:t.url(),elementSize:l}}finally{await e.close()}}async function Pe(o){try{let e=await o.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 i.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 function X(o,e="/"){i.info(`[INFO] Extracting account information from Next.js script at ${e}`);try{let t=await v(o,e),n=Bt(t);if(!n)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=Wt(n);return i.info("[SUCCESS] Account information extracted successfully"),r}catch(t){throw i.error("Error extracting account info from script:",t),new Error(`Failed to extract account information: ${t instanceof Error?t.message:"Unknown error"}`)}}async function $e(o){let t=(await X(o,"/")).studentNo;if(!t||t.length<3)throw new Error("Student number is missing from account information. Ensure you are authenticated and try again.");let n=zt(t),r=`https://${n}/mypage/student_card/index`,s=`https://nlobby.nnn.ed.jp/mypage/v1/callback?redirect_uri=${encodeURIComponent(r)}`,a=o.httpClient.defaults.headers.Cookie,c=typeof a=="string"?a:a==null?void 0:String(a);if(!c)throw new Error("Authentication cookies are not set. Use the set_cookies tool or interactive_login first.");let l=Jt(c,"nlobby.nnn.ed.jp");if(l.length===0)throw new Error("Failed to parse authentication cookies for browser session.");let u=await Kt({startUrl:s,waitForSelector:"#main",screenshotName:`student-card-${Date.now()}.png`,cookies:l});return{base64:u.base64,path:u.path,studentNo:t,secureHost:n,callbackUrl:s,finalUrl:u.finalUrl,elementSize:u.elementSize}}async function Oe(o){i.info("[INFO] Updating last access...");try{return await o.httpClient.post("/api/trpc/user.updateLastAccess","{}",{headers:{"Content-Type":"application/json",Cookie:o.nextAuth.getCookieHeader()}}),i.info("[SUCCESS] updateLastAccess succeeded"),!0}catch(e){throw i.error("[ERROR] updateLastAccess failed:",e),e}}var _e=p(()=>{"use strict";Y();S();m()});async function Fe(o){i.info("[INFO] Fetching main navigations...");try{let e=await o.trpcClient.call("menu.findMainNavigations",{});return i.info("[SUCCESS] getMainNavigations succeeded"),e&&typeof e=="object"&&"menus"in e?e.menus||[]:Array.isArray(e)?e:[]}catch(e){throw i.error("[ERROR] getMainNavigations failed:",e),e}}async function je(o){i.info("[INFO] Fetching notification messages...");try{let e=await o.trpcClient.call("notification.getMessages");return i.info("[SUCCESS] getNotificationMessages succeeded"),Array.isArray(e)?e:[]}catch(e){throw i.error("[ERROR] getNotificationMessages failed:",e),e}}async function Me(o,e=!1){let t=e?"interest.readInterestsWithIcon":"interest.readInterests";i.info(`[INFO] Fetching user interests (${t})...`);try{let n=await o.trpcClient.call(t);return i.info("[SUCCESS] getUserInterests succeeded"),n&&typeof n=="object"&&"interests"in n?n.interests||[]:Array.isArray(n)?n:[]}catch(n){throw i.error("[ERROR] getUserInterests failed:",n),n}}async function Ge(o){i.info("[INFO] Fetching interest weights...");try{let e=await o.trpcClient.call("interest.readWeights");return i.info("[SUCCESS] getInterestWeights succeeded"),e&&typeof e=="object"&&"weights"in e?e.weights||[]:Array.isArray(e)?e:[]}catch(e){throw i.error("[ERROR] getInterestWeights failed:",e),e}}var He=p(()=>{"use strict";m()});async function qe(o){i.info("[INFO] Running N Lobby API health check...");let e=[{name:"tRPC lightweight endpoint",test:async()=>{try{let t=await o.trpcClient.getUnreadNewsCount();return typeof t=="number"&&t>=0}catch{return!1}}},{name:"tRPC batch health check",test:async()=>{try{return await o.trpcClient.healthCheck()}catch{return!1}}},{name:"HTML news page access",test:async()=>{try{let t=await o.httpClient.get("/news",{headers:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},withCredentials:!0,timeout:8e3});if(t.status===200&&typeof t.data=="string"){let n=t.data.toLowerCase(),r=n.includes("\u30ED\u30B0\u30A4\u30F3")||n.includes("login")||n.includes("sign-in"),s=n.includes("news")||n.includes("\u304A\u77E5\u3089\u305B")||n.includes("nlobby");return!r&&s}return!1}catch{return!1}}},{name:"Basic server connectivity",test:async()=>{try{return(await o.httpClient.get("/",{timeout:5e3,withCredentials:!0})).status===200}catch{return!1}}}];for(let t=0;t<e.length;t++){let n=e[t];i.info(`[STEP${t+1}] Testing ${n.name}...`);try{if(await n.test())return i.info(`[SUCCESS] ${n.name} passed`),t<3?(i.info("[SUCCESS] Health check passed - authentication and connectivity verified"),!0):(i.info("[WARNING] Health check passed with limited functionality"),!0);i.info(`[ERROR] ${n.name} failed`)}catch(r){i.info(`[ERROR] ${n.name} failed with error:`,r instanceof Error?r.message:"Unknown error")}}return i.info("[ERROR] All health check methods failed"),!1}async function Be(o,e="/news"){let t=[];t.push("[INFO] N Lobby Connection Debug Report"),t.push("=".repeat(50)),t.push(""),t.push("[STATUS] Authentication Status:"),t.push(o.getCookieStatus()),t.push(""),t.push(`[NETWORK] Testing Basic Connectivity to ${e}:`);try{let s=Date.now(),a=await o.httpClient.get(e,{headers:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","User-Agent":g.userAgent},withCredentials:!0,timeout:1e4}),c=Date.now();if(t.push(`[SUCCESS] Response received (${c-s}ms)`),t.push(`[STATUS] Status: ${a.status} ${a.statusText}`),t.push(`[SIZE] Content Length: ${a.data?.length||"unknown"}`),typeof a.data=="string"){let l=a.data.toLowerCase();t.push(""),t.push("[INFO] Content Analysis:"),(l.includes("\u30ED\u30B0\u30A4\u30F3")||l.includes("login")||l.includes("sign-in"))&&t.push("[WARNING] Contains login keywords - may need authentication"),(l.includes("unauthorized")||l.includes("access denied"))&&t.push("[BLOCKED] Access denied detected"),(l.includes("news")||l.includes("announcement")||l.includes("\u304A\u77E5\u3089\u305B"))&&t.push("[SUCCESS] Contains news/announcement content")}}catch(s){if(t.push("[ERROR] Basic connectivity failed"),s&&typeof s=="object"&&"response"in s){let a=s;t.push(`[STATUS] Error Status: ${a.response?.status||"unknown"}`)}else t.push(`[DATA] Error: ${s instanceof Error?s.message:"Unknown error"}`)}t.push(""),t.push("[NETWORK] Network Information:"),t.push(`[URL] Base URL: ${o.httpClient.defaults.baseURL}`),t.push(`[TIMEOUT] Timeout: ${o.httpClient.defaults.timeout}ms`),t.push(""),t.push("=".repeat(50)),t.push("[TARGET] Recommendations:");let n=!!o.httpClient.defaults.headers.Cookie,r=o.nextAuth.isAuthenticated();return!n&&!r?t.push("1. Run interactive_login to authenticate"):n&&r&&(t.push("1. Authentication looks good - issue may be server-side"),t.push("2. Try different endpoints or wait and retry")),t.join(`
|
|
12
|
+
`)}async function We(o,e="/news",t=1e3){try{i.info(`[INFO] Testing page content for ${e}...`);let n=await o.httpClient.get(e,{headers:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language":"ja,en-US;q=0.7,en;q=0.3","User-Agent":g.userAgent},withCredentials:!0,timeout:1e4});if(typeof n.data=="string"){let r=n.data,s=r.substring(0,t),a=[];a.push(`Status: ${n.status} ${n.statusText}`),a.push(`Content Length: ${r.length} characters`),a.push(`Content Type: ${n.headers["content-type"]||"unknown"}`),a.push("");let c=r.toLowerCase();c.includes("\u30ED\u30B0\u30A4\u30F3")||c.includes("login")?a.push("[WARNING] Page contains login keywords - may not be authenticated"):(c.includes("news")||c.includes("\u304A\u77E5\u3089\u305B"))&&a.push("[SUCCESS] Page appears to contain news content"),a.push(""),a.push("[DATA] Content Sample:"),a.push("-".repeat(50));let l=a.join(`
|
|
13
|
+
`)+`
|
|
14
|
+
`+s;return r.length>t?l+`
|
|
15
|
+
|
|
16
|
+
... (${r.length-t} more characters)`:l}else return`Non-string response received: ${typeof n.data}`}catch(n){if(i.error(`[ERROR] Failed to test page content for ${e}:`,n),n&&typeof n=="object"&&"response"in n){let r=n;return`Error ${r.response?.status||"unknown"}: ${r.message||"Unknown error"}`}return`Error: ${n instanceof Error?n.message:"Unknown error"}`}}async function ze(o,e,t){try{i.info(`[INFO] Testing tRPC endpoint: ${e}`);let n=await o.trpcClient.call(e,t);return i.info(`[SUCCESS] tRPC endpoint ${e} succeeded`),{success:!0,method:e,params:t,result:n,timestamp:new Date().toISOString()}}catch(n){i.error(`[ERROR] tRPC endpoint ${e} failed:`,n);let r={success:!1,method:e,params:t,error:n instanceof Error?n.message:"Unknown error",timestamp:new Date().toISOString()};if(n&&typeof n=="object"&&"response"in n){let s=n;r.status=s.response?.status,r.statusText=s.response?.statusText,r.responseData=s.response?.data}return r}}var Je=p(()=>{"use strict";S();m()});import Vt from"axios";import Q from"node:fs";import Xt from"node:os";import Ye from"node:path";function Qt(){try{return Q.readFileSync(Z,"utf8")}catch{return null}}function ee(o){try{let e=Ye.dirname(Z);Q.mkdirSync(e,{recursive:!0}),Q.writeFileSync(Z,o,"utf8")}catch(e){i.warn("[WARNING] Failed to save session to disk:",e)}}var Z,A,H=p(()=>{"use strict";S();m();ae();ce();he();xe();De();_e();He();Je();Z=Ye.join(Xt.homedir(),".nlobby","session");A=class{httpClient;nextAuth;trpcClient;session=null;constructor(){this.nextAuth=new O,this.trpcClient=new _(this.nextAuth),this.httpClient=Vt.create({baseURL:g.nlobby.baseUrl,timeout:1e4,headers:{"Content-Type":"application/json","User-Agent":g.userAgent}}),this.setupInterceptors();let e=Qt();e&&(i.info("[INFO] Loaded session from disk"),this.setCookiesInternal(e))}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)})}setCookiesInternal(e){!e||e.trim()===""||(this.httpClient.defaults.headers.Cookie=e,this.nextAuth.setCookies(e),this.trpcClient.setAllCookies(e))}setSession(e){this.session=e}setCookies(e){if(!e||e.trim()===""){i.warn("[WARNING] Empty cookies provided to setCookies");return}i.debug("[COOKIE] Setting cookies for all clients..."),this.setCookiesInternal(e),i.info("[SUCCESS] HTTP client cookies set"),i.info("[SUCCESS] NextAuth cookies set"),i.info("[SUCCESS] tRPC client cookies set")}getCookieStatus(){let e=!!this.httpClient.defaults.headers.Cookie,t=this.nextAuth.isAuthenticated(),n=this.nextAuth.getCookies(),r=!!this.trpcClient.allCookies,s=this.httpClient.defaults.headers.Cookie,a=typeof s=="string"?s.length:0,c=this.trpcClient.allCookies?.length||0,l=this.nextAuth.getCookieHeader()?.length||0,u=a===c&&c>0;return`[INFO] Authentication Status:
|
|
17
|
+
[HTTP] HTTP client: ${e?"[SUCCESS] cookies set":"[ERROR] no cookies"} (${a} chars)
|
|
18
|
+
[DEBUG] tRPC client: ${r?"[SUCCESS] cookies set":"[ERROR] no cookies"} (${c} chars)
|
|
19
|
+
[AUTH] NextAuth: ${t?"[SUCCESS] authenticated":"[ERROR] not authenticated"} (${l} chars)
|
|
20
|
+
- Session token: ${n.sessionToken?"[SUCCESS] present":"[ERROR] missing"}
|
|
21
|
+
- CSRF token: ${n.csrfToken?"[SUCCESS] present":"[ERROR] missing"}
|
|
22
|
+
- Callback URL: ${n.callbackUrl?"[SUCCESS] present":"[ERROR] missing"}
|
|
23
|
+
|
|
24
|
+
Cookie Synchronization: ${u?"[SUCCESS] synchronized":"[ERROR] not synchronized"}
|
|
25
|
+
${!u&&e?"[WARNING] Cookie length mismatch detected - may cause authentication issues":""}`}async getNews(){return de(this)}async getNewsDetail(e){return pe(this,e)}async markNewsAsRead(e){return ge(this,e)}async getUnreadNewsInfo(){return me(this)}async getSchedule(e,t){return V(this,e,t)}async getGoogleCalendarEvents(e,t){return D(this,e,t)}async getScheduleByDate(e){return fe(this,e)}async testCalendarEndpoints(e){return ye(this,e)}async getLobbyCalendarFilters(){return we(this)}createDateRange(e,t){return be(e,t)}createSingleDayRange(e){return Ce(e)}createWeekDateRange(e){return ke(e)}createMonthDateRange(e,t){return Se(e,t)}async getRequiredCourses(){return Ee(this)}async getLearningResources(e){return Re(this,e)}async isExamDay(e){return Ae(this,e)}async finishExamDayMode(){return Te(this)}async getExamOneTimePassword(){return ve(this)}async getUserInfo(){return Pe(this)}async getAccountInfoFromScript(e="/"){return X(this,e)}async getStudentCardScreenshot(){return $e(this)}async updateLastAccess(){return Oe(this)}async getMainNavigations(){return Fe(this)}async getNotificationMessages(){return je(this)}async getUserInterests(e=!1){return Me(this,e)}async getInterestWeights(){return Ge(this)}async healthCheck(){return qe(this)}async debugConnection(e="/news"){return Be(this,e)}async testPageContent(e="/news",t=1e3){return We(this,e,t)}async testTrpcEndpoint(e,t){return ze(this,e,t)}}});import Zt from"puppeteer";var T,te=p(()=>{"use strict";S();m();T=class{browser=null;page=null;async initializeBrowser(){try{if(i.info("Initializing browser for authentication..."),this.browser){try{await this.browser.close()}catch(e){i.warn("Error closing existing browser:",e)}this.browser=null,this.page=null}this.browser=await Zt.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=>{i.error("Page error:",e)}),this.page.on("pageerror",e=>{i.error("Page JavaScript error:",e)}),this.page.on("console",e=>{e.type()==="error"&&i.error("Browser console error:",e.text())}),this.page.on("framedetached",e=>{i.warn("Frame detached:",e.url())}),this.browser.on("disconnected",()=>{i.error("Browser disconnected unexpectedly"),this.browser=null,this.page=null}),this.browser.on("targetcreated",e=>{i.info("New browser target created:",e.url())}),this.browser.on("targetdestroyed",e=>{i.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 t=e.resourceType();["image","media","font","stylesheet"].includes(t)?t==="image"&&(e.url().includes("accounts.google.com")||e.url().includes("gstatic.com")||e.url().includes("googleapis.com"))?e.continue():e.abort():e.continue()}),i.info("Browser initialized successfully")}catch(e){if(i.error("Failed to initialize browser:",e),this.browser){try{await this.browser.close()}catch(t){i.warn("Error closing browser after initialization failure:",t)}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=>{i.error("Page error:",e)}),this.page.on("pageerror",e=>{i.error("Page JavaScript error:",e)}),this.page.on("console",e=>{e.type()==="error"&&i.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 t=e.resourceType();["image","media","font","stylesheet"].includes(t)?t==="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()?(i.warn("Browser is not connected"),!1):!this.page||this.page.isClosed()?(i.warn("Page is closed"),!1):(await this.page.evaluate(()=>document.readyState),!0)}catch(e){return i.warn("Browser health check failed:",e),!1}}async extractCookies(){if(!this.page)throw new Error("Page not initialized");try{i.info("Extracting cookies from N Lobby session...");let e=await this.page.cookies(),t,n,r,s=[];for(let c of e){let l=`${c.name}=${c.value}`;s.push(l),c.name==="__Secure-next-auth.session-token"?t=c.value:c.name==="__Host-next-auth.csrf-token"?n=c.value:c.name==="__Secure-next-auth.callback-url"&&(r=decodeURIComponent(c.value))}let a=s.join("; ");return i.info(`Extracted ${e.length} cookies from N Lobby session`),i.info(`Session token: ${t?"present":"missing"}`),i.info(`CSRF token: ${n?"present":"missing"}`),{sessionToken:t,csrfToken:n,callbackUrl:r,allCookies:a}}catch(e){throw i.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 t=`/tmp/${e}`;return await this.page.screenshot({path:t,fullPage:!0}),i.info(`Screenshot saved to ${t}`),t}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,t){let s=e.replace("https://","").replace("http://","");for(let a=1;a<=3;a++)try{(!this.browser||!this.browser.isConnected())&&(i.error("Browser crashed or disconnected, reinitializing..."),await this.initializeBrowser()),i.info(`Waiting for redirect back to N Lobby (attempt ${a}/3)...`),await this.page.waitForFunction(c=>window.location.href.includes(c),{timeout:t/3},s),i.info("Successfully redirected back to N Lobby");return}catch(c){if(a===3)throw i.error("All redirect attempts failed:",c),new Error(`Failed to detect redirect after 3 attempts: ${c instanceof Error?c.message:"Unknown error"}`);i.warn(`Redirect attempt ${a} failed, retrying in 2000ms...`),await new Promise(l=>setTimeout(l,2e3));try{!this.browser||!this.browser.isConnected()?(i.error("Browser crashed, reinitializing..."),await this.initializeBrowser(),await this.page.goto(e,{waitUntil:"networkidle2",timeout:3e4})):await this.page.evaluate(()=>document.readyState)}catch{i.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())&&(i.error("Browser crashed during login detection, reinitializing..."),await this.initializeBrowser(),await this.page.goto(g.nlobby.baseUrl,{waitUntil:"networkidle2",timeout:3e4})),i.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}),i.info("Login completion detected");return}catch(s){if(r===3)throw i.error("All login detection attempts failed:",s),new Error(`Failed to detect login completion after 3 attempts: ${s instanceof Error?s.message:"Unknown error"}`);i.warn(`Login detection attempt ${r} failed, retrying in 2000ms...`),await new Promise(a=>setTimeout(a,2e3));try{!this.browser||!this.browser.isConnected()?(i.error("Browser crashed during login detection, reinitializing..."),await this.initializeBrowser(),await this.page.goto(g.nlobby.baseUrl,{waitUntil:"networkidle2",timeout:3e4})):await this.page.evaluate(()=>document.readyState)}catch{i.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(g.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),i.info("Browser closed successfully")}catch(e){i.error("Error closing browser:",e)}}async interactiveLogin(){if(await this.checkBrowserHealth()||(i.warn("Browser unhealthy, reinitializing..."),await this.initializeBrowser()),!this.browser||!this.page)throw new Error("Browser not initialized. Call initializeBrowser() first.");try{return i.info("Starting interactive login process..."),await this.page.goto(g.nlobby.baseUrl,{waitUntil:"networkidle2",timeout:3e4}),i.info("N Lobby page loaded. Please complete the login process in the browser window."),i.info("The browser will remain open for you to login manually."),await this.waitForLoginCompletionWithRetry(3e5),i.info("Login detected! Extracting cookies..."),await this.extractCookies()}catch(t){if(i.error("Interactive login failed:",t),this.page)try{let n=await this.page.url(),r=await this.page.title();i.error(`Current URL: ${n}`),i.error(`Page title: ${r}`),await this.takeScreenshot("interactive-login-failure-debug.png")}catch(n){i.error("Failed to capture debug information:",n)}throw new Error(`Interactive login failed: ${t instanceof Error?t.message:"Unknown error"}`)}}}});import{createHash as en}from"crypto";var q,Ke=p(()=>{"use strict";m();q=class{credentialStore=new Map;SESSION_TIMEOUT=7200*1e3;hashEmail(e){return en("sha256").update(e).digest("hex")}validateEmail(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))return{valid:!1,userType:"unknown",message:"Invalid email format"};let n=e.split("@")[1].toLowerCase();return n==="nnn.ed.jp"?{valid:!0,userType:"student"}:n==="nnn.ac.jp"?{valid:!0,userType:"staff"}:n==="gmail.com"||n==="yahoo.com"||n==="outlook.com"||n==="hotmail.com"?{valid:!0,userType:"parent"}:{valid:!0,userType:"parent"}}storeSession(e){let t=this.hashEmail(e),n={emailHash:t,timestamp:Date.now(),sessionValid:!0};this.credentialStore.set(t,n),i.info(`Session stored for user: ${e.split("@")[0]}@***`)}hasValidSession(e){let t=this.hashEmail(e),n=this.credentialStore.get(t);return n?Date.now()-n.timestamp>this.SESSION_TIMEOUT?(this.credentialStore.delete(t),!1):n.sessionValid:!1}invalidateSession(e){let t=this.hashEmail(e);this.credentialStore.delete(t),i.info(`Session invalidated for user: ${e.split("@")[0]}@***`)}getLoginGuidance(e){switch(e){case"student":return`
|
|
26
|
+
[STUDENT] Student Login Guide:
|
|
27
|
+
- Use your @nnn.ed.jp email address
|
|
28
|
+
- Use your N High School password
|
|
29
|
+
- If you have 2FA enabled, you'll need to complete it during login
|
|
30
|
+
- Contact your homeroom teacher if you've forgotten your password`;case"staff":return`
|
|
31
|
+
[STAFF] Staff Login Guide:
|
|
32
|
+
- Use your @nnn.ac.jp email address
|
|
33
|
+
- Use your N High School staff password
|
|
34
|
+
- If you have 2FA enabled, you'll need to complete it during login
|
|
35
|
+
- Contact IT support if you're having trouble accessing your account`;case"parent":return`
|
|
36
|
+
[PARENT] Parent Login Guide:
|
|
37
|
+
- Use the email address registered with your child's school account
|
|
38
|
+
- Use the password you set when creating your parent account
|
|
39
|
+
- If you haven't created a parent account yet, contact your child's school
|
|
40
|
+
- If you've forgotten your password, use the password reset option`;default:return`
|
|
41
|
+
[LOGIN] General Login Guide:
|
|
42
|
+
- Use your registered email address
|
|
43
|
+
- Use your N Lobby password
|
|
44
|
+
- If you have 2FA enabled, you'll need to complete it during login
|
|
45
|
+
- Contact support if you're having trouble`}}getTroubleshootingTips(){return`
|
|
46
|
+
[TIPS] Common Login Issues & Solutions:
|
|
47
|
+
|
|
48
|
+
1. **Wrong Email/Password**
|
|
49
|
+
- Double-check your email address and password
|
|
50
|
+
- Make sure Caps Lock is off
|
|
51
|
+
- Try typing your password in a text editor first
|
|
52
|
+
|
|
53
|
+
2. **2FA Issues**
|
|
54
|
+
- Make sure your authenticator app is synced
|
|
55
|
+
- Try using backup codes if available
|
|
56
|
+
- Wait for the next code if the current one doesn't work
|
|
57
|
+
|
|
58
|
+
3. **Browser Issues**
|
|
59
|
+
- Clear your browser cache and cookies
|
|
60
|
+
- Try using incognito/private mode
|
|
61
|
+
- Disable browser extensions temporarily
|
|
62
|
+
|
|
63
|
+
4. **Account Locked**
|
|
64
|
+
- Wait 15-30 minutes before trying again
|
|
65
|
+
- Contact support if your account is suspended
|
|
66
|
+
|
|
67
|
+
5. **Network Issues**
|
|
68
|
+
- Check your internet connection
|
|
69
|
+
- Try using a different network or VPN
|
|
70
|
+
- Make sure N Lobby isn't blocked by your firewall
|
|
71
|
+
|
|
72
|
+
[PRO-TIP] Pro Tips:
|
|
73
|
+
- Use 'interactive_login' if automated login fails
|
|
74
|
+
- The browser window will stay open for manual completion
|
|
75
|
+
- You can close the browser once login is complete`}cleanupExpiredSessions(){let e=Date.now(),t=0;for(let[n,r]of this.credentialStore.entries())e-r.timestamp>this.SESSION_TIMEOUT&&(this.credentialStore.delete(n),t++);t>0&&i.info(`Cleaned up ${t} expired sessions`)}getSessionStats(){let e=Date.now(),t=0;for(let n of this.credentialStore.values())e-n.timestamp>this.SESSION_TIMEOUT&&t++;return{total:this.credentialStore.size,expired:t}}}});var re={};P(re,{NLobbyMCPServer:()=>ne});import{Server as tn}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as nn}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as rn,ErrorCode as L,ListResourcesRequestSchema as on,ListToolsRequestSchema as sn,McpError as U,ReadResourceRequestSchema as an,ListPromptsRequestSchema as cn,GetPromptRequestSchema as ln}from"@modelcontextprotocol/sdk/types.js";var ne,oe=p(()=>{"use strict";H();S();te();Ke();R();m();ne=class{server;api;browserAuth;credentialManager;constructor(){this.server=new tn({name:g.mcp.serverName,version:g.mcp.serverVersion},{capabilities:{resources:{},tools:{},prompts:{}}}),this.api=new A,this.browserAuth=new T,this.credentialManager=new q,this.setupHandlers()}setupHandlers(){this.server.setRequestHandler(on,async()=>({resources:[{uri:"nlobby://news",name:"School News",description:"Latest school news and notices",mimeType:"application/json"},{uri:"nlobby://schedule",name:"School Schedule",description:"Daily class schedule and events",mimeType:"application/json"},{uri:"nlobby://user-profile",name:"User Profile",description:"Current user information and preferences",mimeType:"application/json"},{uri:"nlobby://required-courses",name:"Required Courses",description:"Required courses and academic information",mimeType:"application/json"}]})),this.server.setRequestHandler(an,async e=>{let{uri:t}=e.params;try{switch(t){case"nlobby://news":{let n=await this.api.getNews();return{contents:[{uri:t,mimeType:"application/json",text:JSON.stringify(n,null,2)}]}}case"nlobby://schedule":{let n=await this.api.getSchedule("personal");return{contents:[{uri:t,mimeType:"application/json",text:JSON.stringify(n,null,2)}]}}case"nlobby://user-profile":{let n=await this.api.getUserInfo();return{contents:[{uri:t,mimeType:"application/json",text:JSON.stringify(n,null,2)}]}}case"nlobby://required-courses":{let n=await this.api.getRequiredCourses();return{contents:[{uri:t,mimeType:"application/json",text:JSON.stringify(n,null,2)}]}}default:throw new U(L.InvalidRequest,`Unknown resource: ${t}`)}}catch(n){throw new U(L.InternalError,`Failed to read resource: ${n instanceof Error?n.message:"Unknown error"}`)}}),this.server.setRequestHandler(sn,async()=>({tools:[{name:"get_news",description:"Retrieve school news",inputSchema:{type:"object",properties:{category:{type:"string",description:"Filter by category (optional)"},limit:{type:"number",description:"Maximum number of news items to retrieve (optional, default: 10)",minimum:1,default:10},sort:{type:"string",description:"Sort order: 'newest' (default), 'oldest', 'title-asc', 'title-desc'",enum:["newest","oldest","title-asc","title-desc"]}}}},{name:"get_news_detail",description:"Retrieve detailed information for a specific news article",inputSchema:{type:"object",properties:{newsId:{type:"string",description:"The ID of the news article to retrieve"},markAsRead:{type:"boolean",description:"Mark the news article as read (optional, default: false)",default:!1}},required:["newsId"]}},{name:"get_account_info",description:"Extract account information by parsing Next.js flight data from a rendered page",inputSchema:{type:"object",properties:{}}},{name:"get_student_card_screenshot",description:"Capture a screenshot of the student ID card by following the secure portal redirect flow",inputSchema:{type:"object",properties:{}}},{name:"get_required_courses",description:"Retrieve required courses information with detailed progress tracking",inputSchema:{type:"object",properties:{grade:{type:"number",description:"Filter by grade level (1, 2, or 3) (optional)"},semester:{type:"string",description:'Filter by term year (e.g., "2024", "2025") (optional)'},category:{type:"string",description:'Filter by curriculum category (e.g., "\u56FD\u8A9E", "\u6570\u5B66", "\u82F1\u8A9E") (optional)'}}}},{name:"get_schedule",description:"Get school schedule for a specific date (backward compatibility)",inputSchema:{type:"object",properties:{date:{type:"string",description:"Date in YYYY-MM-DD format (optional, defaults to today)"}}}},{name:"get_calendar_events",description:"Get calendar events with advanced options",inputSchema:{type:"object",properties:{calendar_type:{type:"string",enum:["personal","school"],description:"Type of calendar to retrieve (personal or school)",default:"personal"},from_date:{type:"string",description:"Start date in YYYY-MM-DD format (optional). If only from_date is provided, it will be treated as a single day."},to_date:{type:"string",description:"End date in YYYY-MM-DD format (optional). Must be at least 1 day after from_date when both are provided."},period:{type:"string",enum:["today","week","month"],description:'Predefined period (optional, overrides from/to dates). Use "today" for single day queries.'}}}},{name:"test_calendar_endpoints",description:"Test both personal and school calendar endpoints",inputSchema:{type:"object",properties:{from_date:{type:"string",description:"Start date in YYYY-MM-DD format (optional). If only from_date is provided, it will be treated as a single day."},to_date:{type:"string",description:"End date in YYYY-MM-DD format (optional). Must be at least 1 day after from_date when both are provided."}}}},{name:"set_cookies",description:"Set authentication cookies for N Lobby access",inputSchema:{type:"object",properties:{cookies:{type:"string",description:"Cookie string from authenticated N Lobby session"}},required:["cookies"]}},{name:"check_cookies",description:"Check if authentication cookies are set",inputSchema:{type:"object",properties:{}}},{name:"health_check",description:"Check if N Lobby API connection is working",inputSchema:{type:"object",properties:{}}},{name:"debug_connection",description:"Debug N Lobby connection with detailed information",inputSchema:{type:"object",properties:{endpoint:{type:"string",description:"Endpoint to test (default: /news)",default:"/news"}}}},{name:"test_page_content",description:"Test page content retrieval and show sample content",inputSchema:{type:"object",properties:{endpoint:{type:"string",description:"Endpoint to test (default: /news)",default:"/news"},length:{type:"number",description:"Number of characters to show (default: 1000)",default:1e3}}}},{name:"test_trpc_endpoint",description:"Test specific tRPC endpoint with detailed response",inputSchema:{type:"object",properties:{method:{type:"string",description:"tRPC method to test (e.g., news.getUnreadNewsCount, user.updateLastAccess)",default:"user.updateLastAccess"},params:{type:"string",description:"JSON string of parameters (optional)"}}}},{name:"verify_authentication",description:"Verify authentication status and cookie synchronization across all clients",inputSchema:{type:"object",properties:{}}},{name:"interactive_login",description:"Open browser for manual login to N Lobby (no credentials required)",inputSchema:{type:"object",properties:{}}},{name:"login_help",description:"Get help and troubleshooting tips for N Lobby login",inputSchema:{type:"object",properties:{email:{type:"string",description:"Your email address (optional, for personalized help)"}}}},{name:"mark_news_as_read",description:"Mark news articles as read",inputSchema:{type:"object",properties:{ids:{type:"array",items:{type:"string"},description:"Array of news article IDs to mark as read"}},required:["ids"]}},{name:"check_exam_day",description:"Check if the specified date (or today if omitted) is an exam day",inputSchema:{type:"object",properties:{date:{type:"string",description:"Date in YYYY-MM-DD format (optional, defaults to today)"}}}},{name:"finish_exam_day_mode",description:"Finish exam day mode",inputSchema:{type:"object",properties:{}}},{name:"get_exam_otp",description:"Get one-time password for exam",inputSchema:{type:"object",properties:{}}},{name:"update_last_access",description:"Update the last access timestamp for the current user",inputSchema:{type:"object",properties:{}}},{name:"get_navigation_menus",description:"Get main navigation menu list",inputSchema:{type:"object",properties:{}}},{name:"get_unread_news_info",description:"Get unread news information including count and important news flags",inputSchema:{type:"object",properties:{}}},{name:"get_notifications",description:"Get notification messages",inputSchema:{type:"object",properties:{}}},{name:"get_user_interests",description:"Get user interest tags (optionally with icon information)",inputSchema:{type:"object",properties:{with_icon:{type:"boolean",description:"Whether to include icon information (optional, default: false)",default:!1}}}},{name:"get_interest_weights",description:"Get interest weight scale definitions",inputSchema:{type:"object",properties:{}}},{name:"get_calendar_filters",description:"Get lobby calendar filter list",inputSchema:{type:"object",properties:{}}}]})),this.server.setRequestHandler(rn,async e=>{let{name:t,arguments:n}=e.params;try{switch(t){case"get_news":try{let{category:r,limit:s=10,sort:a="newest"}=n,c=await this.api.getNews(),l=r?c.filter(u=>u.category===r):c;switch(a){case"oldest":l.sort((u,d)=>new Date(u.publishedAt||0).getTime()-new Date(d.publishedAt||0).getTime());break;case"title-asc":l.sort((u,d)=>(u.title||"").localeCompare(d.title||""));break;case"title-desc":l.sort((u,d)=>(d.title||"").localeCompare(u.title||""));break;case"newest":default:l.sort((u,d)=>new Date(d.publishedAt||0).getTime()-new Date(u.publishedAt||0).getTime());break}return s>0&&(l=l.slice(0,s)),{content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}
|
|
76
|
+
|
|
77
|
+
To authenticate:
|
|
78
|
+
1. Login to N Lobby in your browser
|
|
79
|
+
2. Open Developer Tools (F12)
|
|
80
|
+
3. Go to Application/Storage tab
|
|
81
|
+
4. Copy cookies and use the set_cookies tool
|
|
82
|
+
5. Use health_check to verify connection`}]}}case"get_news_detail":try{let{newsId:r,markAsRead:s=!1}=n,a=await this.api.getNewsDetail(r);if(s)try{await this.api.markNewsAsRead(r)}catch(c){i.error(`Failed to mark news ${r} as read:`,c)}return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_account_info":try{let r=await this.api.getAccountInfoFromScript();return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_student_card_screenshot":try{let r=await this.api.getStudentCardScreenshot();return{content:[{type:"text",text:JSON.stringify({message:"Student card screenshot captured successfully. Image data attached as base64.",filePath:r.path,studentNo:r.studentNo,secureHost:r.secureHost,callbackUrl:r.callbackUrl,finalUrl:r.finalUrl,elementSize:r.elementSize},null,2)},{type:"image",mimeType:"image/png",data:r.base64}]}}catch(r){return{content:[{type:"text",text:`Error capturing student card screenshot: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_required_courses":try{let{grade:r,semester:s,category:a}=n,l=await this.api.getRequiredCourses();if(r!==void 0){let d=r===1?"1\u5E74\u6B21":r===2?"2\u5E74\u6B21":r===3?"3\u5E74\u6B21":`${r}\u5E74\u6B21`;l=l.filter(h=>h.grade===d)}s&&(l=l.filter(d=>d.termYear&&d.termYear.toString().includes(s))),a&&(l=l.filter(d=>d.curriculumName&&d.curriculumName.toLowerCase().includes(a.toLowerCase())));let u={totalCourses:l.length,filters:{grade:r,semester:s,category:a},coursesByGrade:this.groupCoursesByGrade(l),coursesByCurriculum:this.groupCoursesByCurriculum(l),completedCourses:l.filter(d=>d.isCompleted).length,inProgressCourses:l.filter(d=>d.isInProgress).length,courses:l};return{content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_schedule":try{let{date:r}=n,s=await this.api.getScheduleByDate(r);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_calendar_events":try{let{calendar_type:r,from_date:s,to_date:a,period:c}=n,l=r==="school"?"school":"personal",u;if(c)switch(c){case"today":{let h=new Date;u=this.api.createSingleDayRange(h);break}case"week":u=this.api.createWeekDateRange();break;case"month":u=this.api.createMonthDateRange();break;default:throw new Error(`Invalid period: ${c}`)}else s&&a?u=this.api.createDateRange(s,a):s&&(u=this.api.createSingleDayRange(s));let d=await this.api.getSchedule(l,u);return{content:[{type:"text",text:`[DATE] Calendar Events (${r||"personal"})${u?` from ${u.from.toDateString()} to ${u.to.toDateString()}`:" (current week)"}
|
|
83
|
+
|
|
84
|
+
${JSON.stringify(d,null,2)}`}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"test_calendar_endpoints":try{let{from_date:r,to_date:s}=n,a;r&&s?a=this.api.createDateRange(r,s):r&&(a=this.api.createSingleDayRange(r));let c=await this.api.testCalendarEndpoints(a);return{content:[{type:"text",text:["[TEST] Calendar Endpoints Test Results","=".repeat(40),"",`[DATE] Test Period: ${a?`${a.from.toDateString()} to ${a.to.toDateString()}`:"Current week (default)"}`,"","[PERSONAL] Personal Calendar:",` Status: ${c.personal.success?"[SUCCESS] Success":"[ERROR] Failed"}`,` Events: ${c.personal.count}`,c.personal.error?` Error: ${c.personal.error}`:"","","[SCHOOL] School Calendar:",` Status: ${c.school.success?"[SUCCESS] Success":"[ERROR] Failed"}`,` Events: ${c.school.count}`,c.school.error?` Error: ${c.school.error}`:"","","[STATUS] Summary:"," Total Endpoints: 2",` Successful: ${(c.personal.success?1:0)+(c.school.success?1:0)}`,` Failed: ${(c.personal.success?0:1)+(c.school.success?0:1)}`,` Total Events: ${c.personal.count+c.school.count}`].filter(Boolean).join(`
|
|
85
|
+
`)}]}}catch(r){return{content:[{type:"text",text:`Error testing calendar endpoints: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"set_cookies":{let{cookies:r}=n;return this.api.setCookies(r),{content:[{type:"text",text:"Authentication cookies have been set. You can now access real N Lobby data."}]}}case"check_cookies":return{content:[{type:"text",text:`Cookie status: ${this.api.getCookieStatus()}`}]};case"health_check":return{content:[{type:"text",text:`N Lobby API connection: ${await this.api.healthCheck()?"healthy":"failed"}`}]};case"debug_connection":{let{endpoint:r}=n;return{content:[{type:"text",text:await this.api.debugConnection(r||"/news")}]}}case"test_page_content":{let{endpoint:r,length:s}=n,a=await this.api.testPageContent(r||"/news",s||1e3);return{content:[{type:"text",text:`Sample content from ${r||"/news"}:
|
|
86
|
+
|
|
87
|
+
${a}`}]}}case"test_trpc_endpoint":{let{method:r,params:s}=n;try{let a=s?JSON.parse(s):{},c=await this.api.testTrpcEndpoint(r,a);return{content:[{type:"text",text:`Result of ${r} with params ${JSON.stringify(a)}:
|
|
88
|
+
|
|
89
|
+
${JSON.stringify(c,null,2)}`}]}}catch(a){return{content:[{type:"text",text:`Error testing tRPC endpoint ${r}: ${a instanceof Error?a.message:"Unknown error"}`}]}}}case"verify_authentication":return{content:[{type:"text",text:`[INFO] Authentication Verification Report
|
|
90
|
+
|
|
91
|
+
${this.api.getCookieStatus()}
|
|
92
|
+
|
|
93
|
+
[LOG] Recommendations:
|
|
94
|
+
${this.getAuthenticationRecommendations()}`}]};case"interactive_login":try{await this.browserAuth.initializeBrowser();let r=await this.browserAuth.interactiveLogin();return this.api.setCookies(r.allCookies),await this.browserAuth.close(),{content:[{type:"text",text:`[SUCCESS] Successfully logged in to N Lobby!
|
|
95
|
+
|
|
96
|
+
Extracted cookies:
|
|
97
|
+
- Session Token: ${r.sessionToken?"present":"missing"}
|
|
98
|
+
- CSRF Token: ${r.csrfToken?"present":"missing"}
|
|
99
|
+
- Callback URL: ${r.callbackUrl||"not set"}
|
|
100
|
+
|
|
101
|
+
You can now access real N Lobby data using other tools.`}]}}catch(r){return await this.browserAuth.close(),{content:[{type:"text",text:`[ERROR] Interactive login failed: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"login_help":{let{email:r}=n,s=`[LOGIN] N Lobby Login Help
|
|
102
|
+
|
|
103
|
+
`;if(r){let c=this.credentialManager.validateEmail(r);s+=`[EMAIL] Email: ${r}
|
|
104
|
+
`,s+=`[USER] User Type: ${c.userType}
|
|
105
|
+
`,s+=`[SUCCESS] Valid: ${c.valid?"Yes":"No"}
|
|
106
|
+
|
|
107
|
+
`,c.valid||(s+=`[ERROR] Issue: ${c.message}
|
|
108
|
+
|
|
109
|
+
`),s+=this.credentialManager.getLoginGuidance(c.userType)}else s+=this.credentialManager.getLoginGuidance("unknown");s+=`
|
|
110
|
+
|
|
111
|
+
${this.credentialManager.getTroubleshootingTips()}`;let a=this.credentialManager.getSessionStats();return s+=`
|
|
112
|
+
|
|
113
|
+
[STATUS] Session Stats:
|
|
114
|
+
- Active sessions: ${a.total-a.expired}
|
|
115
|
+
- Expired sessions: ${a.expired}`,{content:[{type:"text",text:s}]}}case"mark_news_as_read":try{let{ids:r}=n;if(!r||r.length===0)return{content:[{type:"text",text:"Error: No news article IDs provided."}]};let s=[],a=[];for(let l of r)try{await this.api.markNewsAsRead(l),s.push(l)}catch(u){a.push({id:l,error:u instanceof Error?u.message:"Unknown error"})}let c="";return s.length>0&&(c+=`Successfully marked ${s.length} news article(s) as read: ${s.join(", ")}
|
|
116
|
+
`),a.length>0&&(c+=`
|
|
117
|
+
Failed to mark ${a.length} news article(s) as read:
|
|
118
|
+
`,a.forEach(({id:l,error:u})=>{c+=`- ${l}: ${u}
|
|
119
|
+
`})),{content:[{type:"text",text:c.trim()}]}}catch(r){return{content:[{type:"text",text:`Error marking news as read: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"check_exam_day":try{let{date:r}=n,s=r?new Date(r):void 0,a=await this.api.isExamDay(s);return{content:[{type:"text",text:JSON.stringify({date:s?s.toISOString().split("T")[0]:new Date().toISOString().split("T")[0],isExamDay:a},null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"finish_exam_day_mode":try{let r=await this.api.finishExamDayMode();return{content:[{type:"text",text:JSON.stringify({success:r},null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_exam_otp":try{let r=await this.api.getExamOneTimePassword();return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"update_last_access":try{let r=await this.api.updateLastAccess();return{content:[{type:"text",text:JSON.stringify({success:r},null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_navigation_menus":try{let r=await this.api.getMainNavigations();return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_unread_news_info":try{let r=await this.api.getUnreadNewsInfo();return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_notifications":try{let r=await this.api.getNotificationMessages();return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_user_interests":try{let{with_icon:r=!1}=n,s=await this.api.getUserInterests(r);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_interest_weights":try{let r=await this.api.getInterestWeights();return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}case"get_calendar_filters":try{let r=await this.api.getLobbyCalendarFilters();return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}]}}default:throw new U(L.MethodNotFound,`Unknown tool: ${t}`)}}catch(r){throw new U(L.InternalError,`Tool execution failed: ${r instanceof Error?r.message:"Unknown error"}`)}}),this.server.setRequestHandler(cn,async()=>({prompts:[]})),this.server.setRequestHandler(ln,async e=>{let{name:t}=e.params;throw new U(L.InvalidRequest,`Unknown prompt: ${t}`)})}async start(){try{let e=new nn;await this.server.connect(e),i.info("N Lobby MCP Server started successfully")}catch(e){i.error("Failed to start server:",e),process.exit(1)}}getAuthenticationRecommendations(){let e=this.api.getCookieStatus(),t=[];return e.includes("[ERROR] no cookies")&&e.includes("[ERROR] not authenticated")?(t.push("1. Run interactive_login to authenticate with N Lobby"),t.push("2. Make sure to complete the login process in the browser window"),t.push('3. Wait for the "Login successful" message before proceeding')):e.includes("[ERROR] not synchronized")?(t.push("1. Cookie synchronization issue detected"),t.push("2. Try running interactive_login again to refresh all cookies")):e.includes("[SUCCESS] authenticated")&&e.includes("[SUCCESS] synchronized")?(t.push("1. Authentication appears to be working correctly"),t.push("2. If endpoints are still failing, the issue may be server-side"),t.push("3. Try running health_check to verify connectivity")):(t.push("1. Check the authentication status above for specific issues"),t.push("2. Run health_check to verify overall system health")),t.join(`
|
|
120
|
+
`)}groupCoursesByGrade(e){let t={};for(let n of e){let r=n.grade||"Unknown";t[r]=(t[r]||0)+1}return t}groupCoursesByCurriculum(e){let t={};for(let n of e){let r=n.curriculumName||"Unknown";t[r]=(t[r]||0)+1}return t}}});import{Command as Ve}from"commander";function Xe(o){return new Ve("login").description("Authenticate with N Lobby via browser").action(async()=>{console.log("Opening browser for N Lobby authentication...");try{let t=new T;await t.initializeBrowser();let n=await t.interactiveLogin();n&&n.allCookies?(o.setCookies(n.allCookies),ee(n.allCookies),console.log("[OK] Login successful. Session saved.")):(console.error("[FAIL] Login failed or was cancelled."),process.exit(1))}catch(t){console.error("[FAIL]",t instanceof Error?t.message:t),process.exit(1)}})}function Qe(o){let e=new Ve("cookies").description("Manage authentication cookies");return e.command("set <cookies>").description("Set cookies manually").action(t=>{o.setCookies(t),ee(t),console.log("[OK] Cookies saved.")}),e.command("check").description("Show current cookie/authentication status").action(()=>{console.log(o.getCookieStatus())}),e}var Ze=p(()=>{"use strict";H();te()});function et(o){if(o.length===0)return"No news found.";let e=[];for(let t of o){let n=new Date(t.publishedAt).toLocaleDateString("ja-JP"),r=t.isUnread?" [UNREAD]":"",s=t.isImportant?" [!]":"";e.push(`[${t.id}] ${n}${s}${r}`),e.push(` ${t.title}`),t.menuName&&e.push(` Category: ${t.menuName}`),e.push("")}return e.join(`
|
|
121
|
+
`).trimEnd()}function tt(o){let e=[],t=new Date(o.publishedAt).toLocaleDateString("ja-JP");if(e.push(`Title: ${o.title}`),e.push(`Date: ${t}`),e.push(`Category: ${o.menuName.join(", ")}`),o.isImportant&&e.push("Important: Yes"),e.push(`URL: ${o.url}`),e.push(""),e.push("\u2500".repeat(50)),e.push(""),o.description)e.push(o.description);else{let n=o.content.replace(/<[^>]+>/g,"").replace(/ /g," ").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/\n{3,}/g,`
|
|
122
|
+
|
|
123
|
+
`).trim();e.push(n)}if(o.attachments&&o.attachments.length>0){e.push(""),e.push("Attachments:");for(let n of o.attachments)e.push(` - ${n.fileName}: ${n.href}`)}return e.join(`
|
|
124
|
+
`)}var nt=p(()=>{"use strict"});import{Command as un}from"commander";function rt(o){let e=new un("news").description("N Lobby news");return e.command("list",{isDefault:!0}).description("List news").option("--limit <n>","Number of items to show","10").option("--category <cat>","Filter by category").option("--sort <order>","Sort order: newest|oldest|title-asc|title-desc","newest").option("--unread","Show only unread items").option("--json","Output raw JSON").action(async t=>{try{let n=await o.getNews();if(t.unread&&(n=n.filter(s=>s.isUnread)),t.category){let s=t.category.toLowerCase();n=n.filter(a=>a.category.toLowerCase().includes(s)||(a.menuName??"").toLowerCase().includes(s))}switch(t.sort){case"oldest":n.sort((s,a)=>new Date(s.publishedAt).getTime()-new Date(a.publishedAt).getTime());break;case"title-asc":n.sort((s,a)=>s.title.localeCompare(a.title));break;case"title-desc":n.sort((s,a)=>a.title.localeCompare(s.title));break;default:n.sort((s,a)=>new Date(a.publishedAt).getTime()-new Date(s.publishedAt).getTime())}let r=parseInt(t.limit,10);n=n.slice(0,r),t.json?console.log(JSON.stringify(n,null,2)):console.log(et(n))}catch(n){console.error("[FAIL]",n instanceof Error?n.message:n),process.exit(1)}}),e.command("show <id>").description("Show news detail").option("--json","Output raw JSON").action(async(t,n)=>{try{let r=await o.getNewsDetail(t);n.json?console.log(JSON.stringify(r,null,2)):console.log(tt(r))}catch(r){console.error("[FAIL]",r instanceof Error?r.message:r),process.exit(1)}}),e.command("read <id>").description("Mark news as read").action(async t=>{try{await o.markNewsAsRead(t),console.log(`[OK] Marked ${t} as read.`)}catch(n){console.error("[FAIL]",n instanceof Error?n.message:n),process.exit(1)}}),e}var ot=p(()=>{"use strict";nt()});function B(o){return o.toLocaleTimeString("ja-JP",{hour:"2-digit",minute:"2-digit"})}function st(o){return o.toLocaleDateString("ja-JP",{year:"numeric",month:"2-digit",day:"2-digit",weekday:"short"})}function it(o){if(o.length===0)return"No schedule items found.";let e=new Map;for(let n of o){let r=st(new Date(n.startTime)),s=e.get(r)??[];s.push(n),e.set(r,s)}let t=[];for(let[n,r]of e){t.push(`\u2500\u2500 ${n} \u2500\u2500`);for(let s of r){let a=B(new Date(s.startTime)),c=B(new Date(s.endTime));t.push(` ${a}\u2013${c} ${s.title}`),s.location&&t.push(` Location: ${s.location}`),s.description&&t.push(` ${s.description}`)}t.push("")}return t.join(`
|
|
125
|
+
`).trimEnd()}function at(o){if(o.length===0)return"No calendar events found.";let e=[];for(let t of o){let n=t.start.dateTime??t.start.date??"",r=t.end.dateTime??t.end.date??"",s=!t.start.dateTime,a;if(s)a=n;else{let c=new Date(n),l=new Date(r);a=`${st(c)} ${B(c)}\u2013${B(l)}`}if(e.push(`[${t.id}] ${t.summary}`),e.push(` ${a}`),t.location&&e.push(` Location: ${t.location}`),t.description){let c=t.description.replace(/<[^>]+>/g,"").trim();c&&e.push(` ${c}`)}e.push("")}return e.join(`
|
|
126
|
+
`).trimEnd()}var ct=p(()=>{"use strict"});import{Command as lt}from"commander";function ut(o){return new lt("schedule").description("Show schedule for a date (default: today)").argument("[date]","Date in YYYY-MM-DD format (default: today)").option("--json","Output raw JSON").action(async(t,n)=>{try{let r=await o.getScheduleByDate(t);n.json?console.log(JSON.stringify(r,null,2)):console.log(it(r))}catch(r){console.error("[FAIL]",r instanceof Error?r.message:r),process.exit(1)}})}function dt(o){return new lt("calendar").description("Show calendar events").option("--from <date>","Start date (YYYY-MM-DD)").option("--to <date>","End date (YYYY-MM-DD)").option("--type <type>","Calendar type: personal|school (default: personal)","personal").option("--json","Output raw JSON").action(async t=>{try{let n=t.type==="school"?"school":"personal",r=t.from&&t.to?o.createDateRange(t.from,t.to):o.createWeekDateRange(),s=await o.getGoogleCalendarEvents(n,r);t.json?console.log(JSON.stringify(s,null,2)):console.log(at(s))}catch(n){console.error("[FAIL]",n instanceof Error?n.message:n),process.exit(1)}})}var pt=p(()=>{"use strict";R();ct()});function dn(o){switch(o){case 0:return"Not Started";case 1:return"In Progress";case 2:return"Completed";default:return`Status ${o}`}}function gt(o){if(o.length===0)return"No courses found.";let e=[],t=new Map;for(let n of o){let r=n.grade?`Grade ${n.grade}${n.term!=null?` / Term ${n.term}`:""}`:"Courses",s=t.get(r)??[];s.push(n),t.set(r,s)}for(let[n,r]of t){e.push(`\u2500\u2500 ${n} \u2500\u2500`);for(let s of r){let a=dn(s.subjectStatus),c=s.progressPercentage!=null?` (${s.progressPercentage.toFixed(0)}%)`:"";e.push(` [${s.subjectCode}] ${s.subjectName}`),e.push(` Curriculum: ${s.curriculumName}`),e.push(` Status: ${a}${c}`),e.push(` Reports: ${s.report.count}/${s.report.allCount}`),s.schooling.necessaryCount>0&&e.push(` Attendance: ${s.schooling.attendanceCount}/${s.schooling.necessaryCount}`),s.averageScore!=null&&e.push(` Avg Score: ${s.averageScore.toFixed(1)}`);let l=s.acquired.approvedCredit;l>0&&e.push(` Credits: ${l}`),e.push("")}}return e.join(`
|
|
127
|
+
`).trimEnd()}var mt=p(()=>{"use strict"});import{Command as pn}from"commander";function ht(o){return new pn("courses").description("Show required courses").option("--grade <n>","Filter by grade").option("--semester <n>","Filter by semester/term").option("--json","Output raw JSON").action(async t=>{try{let n=await o.getRequiredCourses();t.grade&&(n=n.filter(r=>String(r.grade)===t.grade)),t.semester&&(n=n.filter(r=>String(r.term)===t.semester)),t.json?console.log(JSON.stringify(n,null,2)):console.log(gt(n))}catch(n){console.error("[FAIL]",n instanceof Error?n.message:n),process.exit(1)}})}var ft=p(()=>{"use strict";mt()});function yt(o){let e=[];return e.push("\u2500\u2500 Account Information \u2500\u2500"),e.push(`Name: ${o.name??"(unknown)"}`),e.push(`Email: ${o.email??"(unknown)"}`),e.push(`Role: ${o.role??"(unknown)"}`),o.studentNo&&e.push(`Student No: ${o.studentNo}`),o.userId&&e.push(`User ID: ${o.userId}`),o.grade!=null&&e.push(`Grade: ${o.grade}`),o.term!=null&&e.push(`Term: ${o.term}`),o.isLobbyAdmin&&e.push("Admin: Yes"),typeof o.kmsLoginSuccess=="boolean"&&e.push(`KMS Login: ${o.kmsLoginSuccess?"OK":"Failed"}`),Array.isArray(o.studentOrganizations)&&o.studentOrganizations.length>0&&e.push(`Organizations: ${o.studentOrganizations.length}`),Array.isArray(o.staffDepartments)&&o.staffDepartments.length>0&&e.push(`Departments: ${o.staffDepartments.length}`),e.join(`
|
|
128
|
+
`)}var wt=p(()=>{"use strict"});import{Command as gn}from"commander";function bt(o){return new gn("profile").description("Show user profile and account information").option("--json","Output raw JSON").action(async t=>{try{let n=await o.getAccountInfoFromScript("/");t.json?console.log(JSON.stringify(n,null,2)):console.log(yt(n))}catch(n){console.error("[FAIL]",n instanceof Error?n.message:n),process.exit(1)}})}var Ct=p(()=>{"use strict";wt()});function kt(o){return o?"[OK] N Lobby API is reachable and authenticated.":"[FAIL] N Lobby API health check failed. Run `nlobby login` or `nlobby cookies set <cookies>` to authenticate."}var St=p(()=>{"use strict"});import{Command as mn}from"commander";function xt(o){return new mn("health").description("Check N Lobby API connectivity and authentication").option("--json","Output raw JSON").action(async t=>{try{let n=await o.healthCheck();t.json?console.log(JSON.stringify({ok:n},null,2)):console.log(kt(n)),n||process.exit(1)}catch(n){console.error("[FAIL]",n instanceof Error?n.message:n),process.exit(1)}})}var Nt=p(()=>{"use strict";St()});import{Command as hn}from"commander";function Et(){return new hn("serve").alias("mcp").description("Start the MCP server (stdio transport)").action(async()=>{let{logger:e}=await Promise.resolve().then(()=>(m(),J));e.forceProductionMode();let{NLobbyMCPServer:t}=await Promise.resolve().then(()=>(oe(),re));await new t().start()})}var Rt=p(()=>{"use strict"});var Tt={};P(Tt,{buildProgram:()=>At,runCli:()=>yn});import{Command as fn}from"commander";function At(){let o=new A,e=new fn().name("nlobby").description("N Lobby CLI \u2014 access N Lobby from the command line").version("1.4.0");return e.addCommand(Xe(o)),e.addCommand(Qe(o)),e.addCommand(rt(o)),e.addCommand(ut(o)),e.addCommand(dt(o)),e.addCommand(ht(o)),e.addCommand(bt(o)),e.addCommand(xt(o)),e.addCommand(Et()),e}async function yn(){await At().parseAsync(process.argv)}var vt=p(()=>{"use strict";H();Ze();ot();pt();ft();Ct();Nt();Rt()});var se=process.argv.slice(2),wn=se[0]==="serve"||se[0]==="mcp",bn=se.length===0&&!process.stdin.isTTY;async function Cn(){if(wn||bn){let{logger:o}=await Promise.resolve().then(()=>(m(),J));o.forceProductionMode();let{NLobbyMCPServer:e}=await Promise.resolve().then(()=>(oe(),re));await new e().start()}else{let{runCli:o}=await Promise.resolve().then(()=>(vt(),Tt));await o()}}Cn().catch(o=>{console.error("Fatal error:",o instanceof Error?o.message:o),process.exit(1)});
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nlobby-cli",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "CLI and MCP server for N Lobby school portal",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"cli",
|
|
9
|
+
"mcp",
|
|
10
|
+
"mcp-server",
|
|
11
|
+
"nlobby",
|
|
12
|
+
"school",
|
|
13
|
+
"portal"
|
|
14
|
+
],
|
|
15
|
+
"author": "minagishl",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/minagishl/nlobby-mcp.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/minagishl/nlobby-mcp#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/minagishl/nlobby-mcp/issues"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^0.6.0",
|
|
27
|
+
"axios": "^1.6.0",
|
|
28
|
+
"cheerio": "^1.0.0",
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"dotenv": "^16.3.1",
|
|
31
|
+
"playwright": "^1.54.1",
|
|
32
|
+
"puppeteer": "^24.12.1",
|
|
33
|
+
"zod": "^3.22.4"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@eslint/js": "^9.35.0",
|
|
37
|
+
"@types/node": "^20.10.0",
|
|
38
|
+
"@typescript-eslint/eslint-plugin": "^8.42.0",
|
|
39
|
+
"@typescript-eslint/parser": "^8.42.0",
|
|
40
|
+
"esbuild": "^0.25.6",
|
|
41
|
+
"eslint": "^9.35.0",
|
|
42
|
+
"globals": "^16.3.0",
|
|
43
|
+
"jest": "^29.7.0",
|
|
44
|
+
"jiti": "^2.5.1",
|
|
45
|
+
"prettier": "^3.6.2",
|
|
46
|
+
"typescript": "^5.3.0",
|
|
47
|
+
"typescript-eslint": "^8.42.0"
|
|
48
|
+
},
|
|
49
|
+
"bin": {
|
|
50
|
+
"nlobby": "dist/index.js",
|
|
51
|
+
"nlobby-mcp": "dist/index.js"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist/index.js",
|
|
55
|
+
"dist/index.d.ts",
|
|
56
|
+
"README.md",
|
|
57
|
+
"LICENSE",
|
|
58
|
+
"SKILLS.md"
|
|
59
|
+
],
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "node scripts/build.mjs",
|
|
62
|
+
"dev": "tsc --watch",
|
|
63
|
+
"start": "node dist/index.js",
|
|
64
|
+
"test": "jest",
|
|
65
|
+
"lint": "eslint src/**/*.ts",
|
|
66
|
+
"format": "prettier --write ."
|
|
67
|
+
}
|
|
68
|
+
}
|