Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions lib/routes/xtu/cyxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import type { Route } from '@/types';

Check failure

Code scanning / oxlint

simple-import-sort(imports) Error

Run autofix to sort these imports!
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';

const rootUrl = 'https://cyxy.xtu.edu.cn/';
const host = 'cyxy.xtu.edu.cn';

const handler: Route['handler'] = async (ctx) => {
const type = ctx.req.param('type') ?? 'dxscxcyxljhxm';
const typeDict = {
dxscxcyxljhxm: ['大学生创新创业训练计划', 'scsj/dxscxcyxljhxm.htm'],
scjs: ['双创竞赛', 'scsj/scjs/zggjdxscxds1.htm'],
};

if (!typeDict[type]) {
throw new Error(`Invalid type: ${type}`);
}

const listUrl = `${rootUrl}${typeDict[type][1]}`;

const response = await ofetch(listUrl);
const $ = load(response);

const list = $('.common-list li')
.toArray()
.map((item) => {
const $item = $(item);
const $a = $item.find('a').first();

let title = $a.attr('title') || $item.find('h2.ellipsis').text() || $a.text() || '';

Check failure

Code scanning / oxlint

eslint(prefer-const) Error

title is never reassigned.
Use const instead.
let link = $a.attr('href') || '';

// 处理相对路径
if (link && !link.startsWith('http')) {
if (link.startsWith('../')) {
link = rootUrl + link.replace(/\.\.\//g, '');

Check failure

Code scanning / oxlint

eslint-plugin-unicorn(prefer-string-replace-all) Error

Prefer String#replaceAll() over String#replace() when using a regex with the global flag.
Replace replace with replaceAll.

Check failure

Code scanning / CodeQL

Incomplete multi-character sanitization High

This string may still contain
../
, which may cause a path injection vulnerability.
} else if (link.startsWith('./')) {
link = rootUrl + link.replace(/\.\//g, '');

Check failure

Code scanning / oxlint

eslint-plugin-unicorn(prefer-string-replace-all) Error

Prefer String#replaceAll() over String#replace() when using a regex with the global flag.
Replace replace with replaceAll.
} else if (link.startsWith('/')) {
link = rootUrl.slice(0, -1) + link;
} else {
link = rootUrl + link;
}
}

// 从 date 结构中提取日期 (格式: <h3>10</h3><p>2026.02</p>)
const $dateH3 = $item.find('.date h3');
const $dateP = $item.find('.date p');
let pubDate: Date | undefined;
if ($dateH3.length && $dateP.length) {
const day = $dateH3.text().trim();
const yearMonth = $dateP.text().trim().replace('.', '-');
const dateText = `${yearMonth}-${day}`;
pubDate = timezone(parseDate(dateText), +8);
}

return {
title: title.trim(),
link: link.trim(),
pubDate,
};
})
.filter((item) => item.title && item.link);

const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const newItem = {
...item,
description: '',
};

try {
const linkHost = new URL(item.link).hostname;
if (host === linkHost) {
const response = await ofetch(item.link);
const $ = load(response);

// 尝试多种可能的内容选择器
const contentSelectors = ['.v_news_content', '.content-detail', '.article-content', '.wp_articlecontent', '#main-content', '.content', '.news-content', '.infodetail'];

for (const selector of contentSelectors) {
const content = $(selector).html();
if (content) {
newItem.description = content;
break;
}
}

// 如果没找到内容,返回链接提示
if (!newItem.description) {
newItem.description = `<p>请访问原网页查看内容:<a href="${item.link}">${item.title}</a></p>`;
}
} else {
// 外部链接
newItem.description = `<p>外部链接:<a href="${item.link}">${item.title}</a></p>`;
}
} catch {
newItem.description = `<p>获取内容失败,请访问原网页:<a href="${item.link}">${item.title}</a></p>`;
}

return newItem;
})
)
);

return {
title: `湘潭大学创新创业学院 - ${typeDict[type][0]}`,
link: listUrl,
item: items,
};
};

export const route: Route = {
name: '创新创业学院',
path: '/cyxy/:type?',
example: '/xtu/cyxy/dxscxcyxljhxm',
url: 'cyxy.xtu.edu.cn',
handler,
categories: ['university'],
maintainers: ['zzy00747'],
parameters: {
type: '栏目类型,见下表,默认为大创项目',
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
description: `| 栏目 | 参数 | 说明 |
| ---- | ---- | ---- |
| 大创项目 | dxscxcyxljhxm | 大学生创新创业训练计划项目 |
| 双创竞赛 | scjs | 双创竞赛(互联网+、挑战杯等) |`,
};
129 changes: 129 additions & 0 deletions lib/routes/xtu/jwc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import type { Route } from '@/types';

Check failure

Code scanning / oxlint

simple-import-sort(imports) Error

Run autofix to sort these imports!
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';

const rootUrl = 'https://jwc.xtu.edu.cn/';
const host = 'jwc.xtu.edu.cn';

const handler: Route['handler'] = async (ctx) => {
const type = ctx.req.param('type') ?? 'tzgg';
const listUrl = type === 'xkjs' ? `${rootUrl}xkjs/tzgg.htm` : `${rootUrl}tzgg.htm`;

const response = await ofetch(listUrl);
const $ = load(response);

const list = $('.col-md-9 li')
.toArray()
.filter((item) => {
const $item = $(item);
// 只保留包含 span 日期和 a 链接的列表项
return $item.find('span').length > 0 && $item.find('a').length > 0;
})
.map((item) => {
const $item = $(item);
const $a = $item.find('a');
const $span = $item.find('span');

let title = $a.attr('title') || $a.text() || '';

Check failure

Code scanning / oxlint

eslint(prefer-const) Error

title is never reassigned.
Use const instead.
let link = $a.attr('href') || '';

// 处理相对路径
if (link && !link.startsWith('http')) {
if (link.startsWith('../')) {
link = rootUrl + link.replace(/\.\.\//g, '');

Check failure

Code scanning / oxlint

eslint-plugin-unicorn(prefer-string-replace-all) Error

Prefer String#replaceAll() over String#replace() when using a regex with the global flag.
Replace replace with replaceAll.

Check failure

Code scanning / CodeQL

Incomplete multi-character sanitization High

This string may still contain
../
, which may cause a path injection vulnerability.
} else if (link.startsWith('./')) {
link = rootUrl + link.replace(/\.\//g, '');

Check failure

Code scanning / oxlint

eslint-plugin-unicorn(prefer-string-replace-all) Error

Prefer String#replaceAll() over String#replace() when using a regex with the global flag.
Replace replace with replaceAll.
} else if (link.startsWith('/')) {
link = rootUrl.slice(0, -1) + link;
} else {
link = rootUrl + link;
}
}

const dateText = $span.text().trim();
const pubDate = dateText ? timezone(parseDate(dateText), +8) : undefined;

return {
title: title.trim(),
link: link.trim(),
pubDate,
};
})
.filter((item) => item.title && item.link);

const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const newItem = {
...item,
description: '',
};

try {
const linkHost = new URL(item.link).hostname;
if (host === linkHost) {
const response = await ofetch(item.link);
const $ = load(response);

// 尝试多种可能的内容选择器
const contentSelectors = ['.v_news_content', '.content-detail', '.article-content', '.wp_articlecontent', '#main-content', '.content'];

for (const selector of contentSelectors) {
const content = $(selector).html();
if (content) {
newItem.description = content;
break;
}
}

// 如果没找到内容,返回链接提示
if (!newItem.description) {
newItem.description = `<p>请访问原网页查看内容:<a href="${item.link}">${item.title}</a></p>`;
}
} else {
// 外部链接
newItem.description = `<p>外部链接:<a href="${item.link}">${item.title}</a></p>`;
}
} catch {
newItem.description = `<p>获取内容失败,请访问原网页:<a href="${item.link}">${item.title}</a></p>`;
}

return newItem;
})
)
);

return {
title: type === 'xkjs' ? '湘潭大学教务处 - 学科竞赛通知公告' : '湘潭大学教务处 - 通知公告',
link: listUrl,
item: items,
};
};

export const route: Route = {
name: '教务处通知公告',
path: '/jwc/:type?',
example: '/xtu/jwc/tzgg',
url: 'jwc.xtu.edu.cn',
handler,
categories: ['university'],
maintainers: ['zzy00747'],
parameters: {
type: '通知类型,见下表,默认为首页通知公告',
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
description: `| 类型 | 参数 | 说明 |
| ---- | ---- | ---- |
| 通知公告 | tzgg | 首页综合通知公告 |
| 学科竞赛 | xkjs | 学科竞赛相关通知公告 |`,
};
Loading
Loading