Skip to content

Commit 976c6c8

Browse files
authored
Merge branch 'master' into master
2 parents dbce373 + 5ae7432 commit 976c6c8

File tree

6 files changed

+429
-358
lines changed

6 files changed

+429
-358
lines changed

lib/routes/2048/index.tsx

Lines changed: 34 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { load } from 'cheerio';
2-
import { renderToString } from 'hono/jsx/dom/server';
32

43
import type { Route } from '@/types';
54
import cache from '@/utils/cache';
@@ -81,33 +80,12 @@ async function handler(ctx) {
8180
async () => {
8281
const captchaPage = await ofetch(redirectResponse.url);
8382
const $captcha = load(captchaPage);
84-
85-
const cookieResponse = await ofetch.raw(redirectResponse.url, {
86-
method: 'POST',
87-
headers: {
88-
'Content-Type': 'application/x-www-form-urlencoded',
89-
Cookie: `safe18_tok=${$captcha('form#s18f input[name="tok"]').attr('value') || ''}`,
90-
},
91-
body: new URLSearchParams(
92-
Object.fromEntries(
93-
$captcha('form#s18f input')
94-
.toArray()
95-
.map((el) => [el.attribs.name, el.attribs.value])
96-
.filter(([name, value]) => name !== null && value !== null)
97-
)
98-
).toString(),
99-
redirect: 'manual',
100-
});
101-
102-
const safe18Pass = cookieResponse.headers
103-
.getSetCookie()
104-
?.find((cookie) => cookie.startsWith('safe18_pass='))
105-
?.split(';')[0]
106-
.split('=')[1];
107-
83+
// Extract the value of safeid from the $captcha HTML content
84+
const safeidMatch = $captcha.html()?.match(/var\s+safeid\s*=\s*'([^']+)'/);
85+
const safeid = safeidMatch ? safeidMatch[1] : undefined;
10886
return {
10987
url: redirectResponse.url,
110-
safe18Pass,
88+
safeid,
11189
};
11290
},
11391
86400, // fixed cookie duration: 24 hours
@@ -117,7 +95,7 @@ async function handler(ctx) {
11795

11896
const response = await ofetch.raw(currentUrl, {
11997
headers: {
120-
cookie: `safe18_pass=${redirected.safe18Pass}`,
98+
cookie: `_safe=${redirected.safeid}`,
12199
},
122100
});
123101

@@ -147,7 +125,7 @@ async function handler(ctx) {
147125
cache.tryGet(item.guid, async () => {
148126
const detailResponse = await ofetch(item.link, {
149127
headers: {
150-
cookie: `safe18_pass=${redirected.safe18Pass}`,
128+
cookie: `_safe=${redirected.safeid}`,
151129
},
152130
});
153131

@@ -167,33 +145,40 @@ async function handler(ctx) {
167145
item.author = content('.fl.black').first().text();
168146
item.pubDate = timezone(parseDate(content('span.fl.gray').first().attr('title')), +8);
169147

170-
const downloadLink = content('#read_tpc').first().find('a').last();
148+
const readTpc = content('#read_tpc').first();
171149
const copyLink = content('#copytext')?.first()?.text();
172-
if (new URL(downloadLink.text()).hostname === 'bt.azvmw.com') {
173-
const torrentResponse = await ofetch(downloadLink.text());
174-
175-
const torrent = load(torrentResponse);
176-
177-
item.enclosure_type = 'application/x-bittorrent';
178-
const ahref = torrent('.uk-button').last().attr('href');
179-
item.enclosure_url = ahref?.startsWith('http') ? ahref : `https://bt.azvmw.com/${ahref}`;
180-
181-
const magnet = torrent('.uk-button').first().attr('href');
182-
183-
downloadLink.replaceWith(renderToString(<DownloadLinks magnet={magnet} torrent={item.enclosure_url} />));
184-
} else if (copyLink?.startsWith('magnet')) {
185-
// copy link
186-
item.enclosure_url = copyLink;
187-
item.enclosure_type = 'x-scheme-handler/magnet';
150+
const readTpcHtml = readTpc.html() ?? '';
151+
const magnetText = readTpc.find('.magnet-text').first().text().trim();
152+
153+
// Extract enclosure: rmdown.com (fetch page for magnet) | magnet from 哈希校验 | copyLink
154+
const rmdownLink = readTpc.find('a[href*="rmdown.com/link.php"]').first().attr('href');
155+
const enclosureHref = rmdownLink?.startsWith('http') ? rmdownLink : rmdownLink ? `https://www.rmdown.com/${rmdownLink}` : null;
156+
157+
if (enclosureHref) {
158+
const rmdownPage = await cache.tryGet(`2048:rmdown:${enclosureHref}`, () => ofetch(enclosureHref));
159+
const btihMatch = rmdownPage.match(/Code:\s*([a-fA-F0-9]{40})/);
160+
const magnetUrl = btihMatch ? `magnet:?xt=urn:btih:${btihMatch[1]}` : null;
161+
if (magnetUrl) {
162+
item.enclosure_url = magnetUrl;
163+
item.enclosure_type = 'x-scheme-handler/magnet';
164+
}
165+
}
166+
if (!item.enclosure_url) {
167+
const hashMatch = readTpcHtml.match(/[^;]*;\s*([a-fA-F0-9]{40})\s*[;]/);
168+
const magnetFromHash = hashMatch ? `magnet:?xt=urn:btih:${hashMatch[1]}` : null;
169+
const magnetFromText = magnetText.match(/magnet:\?xt=urn:btih:[^\s"'<>]+/)?.[0];
170+
const magnetLink = magnetFromText ?? readTpcHtml.match(/magnet:\?xt=urn:btih:[^\s"'<>]+/)?.[0] ?? magnetFromHash ?? copyLink;
171+
if (magnetLink?.startsWith('magnet')) {
172+
item.enclosure_url = magnetLink;
173+
item.enclosure_type = 'x-scheme-handler/magnet';
174+
}
188175
}
189-
190-
const desp = content('#read_tpc').first();
191176

192177
content('.showhide img').each(function () {
193-
desp.append(`<br><img style="max-width: 100%;" src="${content(this).attr('src')}">`);
178+
readTpc.append(`<br><img style="max-width: 100%;" src="${content(this).attr('src')}">`);
194179
});
195180

196-
item.description = desp.html();
181+
item.description = readTpc.html();
197182

198183
return item;
199184
})
@@ -206,9 +191,3 @@ async function handler(ctx) {
206191
item: items,
207192
};
208193
}
209-
210-
const DownloadLinks = ({ magnet, torrent }: { magnet?: string; torrent?: string }) => (
211-
<>
212-
<a href={magnet}>磁力連結</a> | <a href={torrent}>下載檔案</a>
213-
</>
214-
);

lib/routes/xupt/jyc.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { load } from 'cheerio';
2+
3+
import type { Route } from '@/types';
4+
import ofetch from '@/utils/ofetch';
5+
6+
export const route: Route = {
7+
path: '/jyc/:type?',
8+
categories: ['university'],
9+
example: '/xupt/jyc',
10+
parameters: { type: '分类,默认为 tzgg(通知公告)' },
11+
features: {
12+
requireConfig: false,
13+
requirePuppeteer: false,
14+
antiCrawler: false,
15+
supportBT: false,
16+
supportPodcast: false,
17+
supportScihub: false,
18+
},
19+
radar: [
20+
{
21+
source: ['jyc.xupt.edu.cn/index/tzgg.htm'],
22+
target: '/jyc/tzgg',
23+
},
24+
],
25+
name: '教务处通知公告',
26+
maintainers: ['StudyingLover'],
27+
handler,
28+
description: `| 分类 | 参数 |
29+
| ---- | ---- |
30+
| 通知公告 | tzgg |`,
31+
};
32+
33+
async function handler(ctx) {
34+
const type = ctx.req.param('type') || 'tzgg';
35+
const typeDict = {
36+
tzgg: ['通知公告', 'https://jyc.xupt.edu.cn/index/tzgg.htm'],
37+
};
38+
39+
const [typeName, url] = typeDict[type as keyof typeof typeDict] || typeDict.tzgg;
40+
41+
const response = await ofetch(url);
42+
const $ = load(response);
43+
44+
const list = $('.ej_list li')
45+
.toArray()
46+
.map((item) => {
47+
const $item = $(item);
48+
const $link = $item.find('a').first();
49+
50+
// 处理相对链接,转换为绝对链接
51+
const linkHref = $link.attr('href') || '';
52+
const absoluteLink = new URL(linkHref, 'https://jyc.xupt.edu.cn').href;
53+
54+
// 日期格式:<span><i>18</i>/03</span> - 只有月日,无年份
55+
// 根据规范,网站不提供完整日期时不添加 pubDate
56+
57+
return {
58+
title: $link.text().trim(),
59+
link: absoluteLink,
60+
};
61+
})
62+
.filter((item) => item.title && item.link);
63+
64+
return {
65+
title: `西安邮电大学教务处 - ${typeName}`,
66+
link: url,
67+
item: list,
68+
};
69+
}

lib/routes/xupt/namespace.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { Namespace } from '@/types';
2+
3+
export const namespace: Namespace = {
4+
name: '西安邮电大学',
5+
url: 'xupt.edu.cn',
6+
description: '西安邮电大学教务处通知公告',
7+
zh: {
8+
name: '西安邮电大学',
9+
},
10+
};

lib/utils/ofetch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ declare module 'ofetch' {
1313

1414
config.enableRemoteDebugging && process.env.NODE_ENV === 'dev' && register();
1515

16-
const rofetch = createFetch().create({
16+
const rofetch = createFetch({ fetch: (...args: Parameters<typeof fetch>) => globalThis.fetch(...args) }).create({
1717
retryStatusCodes: [400, 408, 409, 425, 429, 500, 502, 503, 504],
1818
retry: config.requestRetry,
1919
retryDelay: 1000,

package.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,16 @@
6363
"@hono/zod-openapi": "1.2.3",
6464
"@jocmp/mercury-parser": "3.0.7",
6565
"@notionhq/client": "5.15.0",
66-
"@opentelemetry/api": "1.9.0",
67-
"@opentelemetry/exporter-prometheus": "0.213.0",
68-
"@opentelemetry/exporter-trace-otlp-http": "0.213.0",
69-
"@opentelemetry/resources": "2.6.0",
70-
"@opentelemetry/sdk-metrics": "2.6.0",
71-
"@opentelemetry/sdk-trace-base": "2.6.0",
66+
"@opentelemetry/api": "1.9.1",
67+
"@opentelemetry/exporter-prometheus": "0.214.0",
68+
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
69+
"@opentelemetry/resources": "2.6.1",
70+
"@opentelemetry/sdk-metrics": "2.6.1",
71+
"@opentelemetry/sdk-trace-base": "2.6.1",
7272
"@opentelemetry/semantic-conventions": "1.40.0",
7373
"@rss3/sdk": "0.0.25",
7474
"@scalar/hono-api-reference": "0.10.5",
75-
"@sentry/node": "10.45.0",
75+
"@sentry/node": "10.46.0",
7676
"aes-js": "3.1.2",
7777
"cheerio": "1.2.0",
7878
"city-timezones": "1.3.3",
@@ -94,7 +94,7 @@
9494
"http-cookie-agent": "7.0.3",
9595
"https-proxy-agent": "8.0.0",
9696
"iconv-lite": "0.7.2",
97-
"imapflow": "1.2.17",
97+
"imapflow": "1.2.18",
9898
"instagram-private-api": "1.46.1",
9999
"ioredis": "5.10.1",
100100
"ip-regex": "5.0.0",
@@ -134,7 +134,7 @@
134134
"tsx": "4.21.0",
135135
"twitter-api-v2": "1.29.0",
136136
"ufo": "1.6.3",
137-
"undici": "7.24.5",
137+
"undici": "7.24.6",
138138
"uuid": "13.0.0",
139139
"winston": "3.19.0",
140140
"xxhash-wasm": "1.1.0",
@@ -194,7 +194,7 @@
194194
"node-network-devtools": "1.0.29",
195195
"oxfmt": "0.42.0",
196196
"oxlint": "1.57.0",
197-
"oxlint-tsgolint": "0.17.3",
197+
"oxlint-tsgolint": "0.17.4",
198198
"remark-parse": "11.0.0",
199199
"tsdown": "0.21.5",
200200
"typescript": "5.9.3",

0 commit comments

Comments
 (0)