-
Notifications
You must be signed in to change notification settings - Fork 9.1k
feat(route/sjtu): Add route for Public OA #20967
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a new route for SJTU (Shanghai Jiao Tong University) Public OA system, allowing users to fetch official documents through RSS feeds.
Changes:
- Adds a new authenticated route
/sjtu/publicoathat fetches documents from the university's public OA system - Implements cookie-based authentication using JAAuthCookie configuration
- Adds configuration support for JAAuthCookie in the config file
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| lib/routes/sjtu/publicoa.ts | Implements the Public OA route with authentication and document fetching logic |
| lib/config.ts | Adds JAAuthCookie configuration support for SJTU routes |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| async function handler() { | ||
| if (!config.sjtu?.JAAuthCookie) { | ||
| throw 'JAAuthCookie needs to be set to use this route.'; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message should use new Error() instead of throwing a plain string. According to JavaScript best practices and conventions in this codebase, errors should be thrown as Error objects.
| throw 'JAAuthCookie needs to be set to use this route.'; | |
| throw new Error('JAAuthCookie needs to be set to use this route.'); |
| const cookieJar = new CookieJar(); | ||
|
|
||
| async function handler() { |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The global cookieJar instance may cause issues in concurrent requests since it's shared across all handler invocations. Consider creating a new CookieJar instance inside the handler function, similar to the pattern used in lib/routes/gdut/oa-news.ts (line 98) where new CookieJar() is created within the handler function scope.
| const cookieJar = new CookieJar(); | |
| async function handler() { | |
| async function handler() { | |
| const cookieJar = new CookieJar(); |
| while (true) { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| const res = await ofetch.raw(requestUrl, { | ||
| headers: { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| cookie: (await cookieJar.getCookieString(requestUrl)) as string, | ||
| }, | ||
| redirect: 'manual', | ||
| }); | ||
| const setCookies = res.headers.getSetCookie(); | ||
| for (const c of setCookies) { | ||
| cookieJar.setCookieSync(c, requestUrl); | ||
| } | ||
|
|
||
| if (res.status >= 300 && res.status < 400) { | ||
| const location = res.headers.get('location'); | ||
| if (typeof location === 'string') { | ||
| requestUrl = new URL(location, requestUrl).href; | ||
| } | ||
| } else { | ||
| break; | ||
| } | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error response handling in this redirect loop doesn't handle cases where the authentication flow fails completely or where redirects might be malicious. The loop should include a maximum iteration count to prevent infinite loops and should validate redirect URLs before following them.
| } | ||
| } | ||
| return await getPublicOAList(); | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the initial API request at line 44 doesn't return a 401 error but fails in some other way (network error, timeout, 500 error, etc.), the promise will resolve with undefined. This will cause a runtime error at line 86 when trying to access list.entities. The error handling should be expanded to handle all failure cases, not just 401 authentication errors.
| } | |
| } | |
| throw error; |
| import { CookieJar } from 'tough-cookie'; | ||
|
|
||
| import { config } from '@/config'; | ||
| import type { Route } from '@/types'; | ||
| import ofetch from '@/utils/ofetch'; | ||
| import { parseDate } from '@/utils/parse-date'; | ||
| import timezone from '@/utils/timezone'; | ||
|
|
||
| const urlRoot = 'https://publicoa.sjtu.edu.cn'; | ||
|
|
||
| export const route: Route = { | ||
| path: '/publicoa', | ||
| categories: ['university'], | ||
| example: '/sjtu/publicoa', | ||
| parameters: {}, | ||
| features: { | ||
| requireConfig: [ | ||
| { | ||
| name: 'JAAuthCookie', | ||
| description: 'JAAuthCookie, 登陆后提取自jaccount.sjtu.edu.cn', | ||
| }, | ||
| ], | ||
| requirePuppeteer: false, | ||
| antiCrawler: false, | ||
| supportBT: false, | ||
| supportPodcast: false, | ||
| supportScihub: false, | ||
| }, | ||
| name: '上海交通大学公文系统', | ||
| maintainers: [''], | ||
| handler, | ||
| description: `需要用户认证`, | ||
| }; | ||
|
|
||
| const cookieJar = new CookieJar(); | ||
|
|
||
| async function handler() { | ||
| if (!config.sjtu?.JAAuthCookie) { | ||
| throw 'JAAuthCookie needs to be set to use this route.'; | ||
| } | ||
|
|
||
| cookieJar.setCookieSync(`JAAuthCookie=${config.sjtu.JAAuthCookie}; Domain=.jaccount.sjtu.edu.cn; Path=/`, 'https://jaccount.sjtu.edu.cn'); | ||
| async function getPublicOAList() { | ||
| return await ofetch(`${urlRoot}/api/doc/list`, { | ||
| headers: { | ||
| cookie: (await cookieJar.getCookieString(urlRoot)) as string, | ||
| }, | ||
| }); | ||
| } | ||
| const list: any = await new Promise((resolve) => { | ||
| resolve( | ||
| getPublicOAList().catch(async (error) => { | ||
| if (error.response?.status === 401) { | ||
| let requestUrl = urlRoot; | ||
| while (true) { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| const res = await ofetch.raw(requestUrl, { | ||
| headers: { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| cookie: (await cookieJar.getCookieString(requestUrl)) as string, | ||
| }, | ||
| redirect: 'manual', | ||
| }); | ||
| const setCookies = res.headers.getSetCookie(); | ||
| for (const c of setCookies) { | ||
| cookieJar.setCookieSync(c, requestUrl); | ||
| } | ||
|
|
||
| if (res.status >= 300 && res.status < 400) { | ||
| const location = res.headers.get('location'); | ||
| if (typeof location === 'string') { | ||
| requestUrl = new URL(location, requestUrl).href; | ||
| } | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| return await getPublicOAList(); | ||
| } | ||
| }) | ||
| ); | ||
| }); | ||
|
|
||
| return { | ||
| title: '上海交通大学公文系统', | ||
| item: list.entities.map((item) => ({ | ||
| title: item.title, | ||
| author: item.doccode, | ||
| pubDate: timezone(parseDate(item.qfdate), +8), | ||
| link: item.pdfpath, | ||
| })), | ||
| link: urlRoot, | ||
| }; | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description's example routes section is empty. According to the PR template, this section should contain the complete route path(s) starting with /, for example: /sjtu/publicoa. This is a requirement stated in the template.
Co-authored-by: Copilot <[email protected]>
|
Successfully generated as following: http://localhost:1200/sjtu/publicoa - Failed ❌ |
| const res = await ofetch.raw(requestUrl, { | ||
| headers: { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| cookie: (await cookieJar.getCookieString(requestUrl)) as string, | ||
| }, | ||
| redirect: 'manual', | ||
| }); |
Check failure
Code scanning / ESLint
Disallow `await` inside of loops Error
| const res = await ofetch.raw(requestUrl, { | ||
| headers: { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| cookie: (await cookieJar.getCookieString(requestUrl)) as string, |
Check failure
Code scanning / ESLint
Disallow `await` inside of loops Error
Involved Issue / 该 PR 相关 Issue
Close #
Example for the Proposed Route(s) / 路由地址示例
New RSS Route Checklist / 新 RSS 路由检查表
PuppeteerNote / 说明