|
| 1 | +import { type Data, type DataItem, type Route, ViewType } from '@/types'; |
| 2 | + |
| 3 | +import { art } from '@/utils/render'; |
| 4 | +import cache from '@/utils/cache'; |
| 5 | +import ofetch from '@/utils/ofetch'; |
| 6 | +import { parseDate } from '@/utils/parse-date'; |
| 7 | +import timezone from '@/utils/timezone'; |
| 8 | + |
| 9 | +import { type CheerioAPI, type Cheerio, load } from 'cheerio'; |
| 10 | +import type { Element } from 'domhandler'; |
| 11 | +import { type Context } from 'hono'; |
| 12 | +import path from 'node:path'; |
| 13 | + |
| 14 | +export const handler = async (ctx: Context): Promise<Data> => { |
| 15 | + const { id = '9' } = ctx.req.param(); |
| 16 | + const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); |
| 17 | + |
| 18 | + const baseUrl: string = 'https://dbaplus.cn'; |
| 19 | + const targetUrl: string = new URL(`news-${id}-1.html`, baseUrl).href; |
| 20 | + |
| 21 | + const response = await ofetch(targetUrl); |
| 22 | + const $: CheerioAPI = load(response); |
| 23 | + const language = $('html').attr('lang') ?? 'zh'; |
| 24 | + |
| 25 | + let items: DataItem[] = []; |
| 26 | + |
| 27 | + items = $('ul.media-list li.media') |
| 28 | + .slice(0, limit) |
| 29 | + .toArray() |
| 30 | + .map((el): Element => { |
| 31 | + const $el: Cheerio<Element> = $(el); |
| 32 | + const $aEl: Cheerio<Element> = $el.find('h3.media-heading a'); |
| 33 | + |
| 34 | + const title: string = $aEl.text(); |
| 35 | + const image: string | undefined = $el.find('img.media-object').attr('src'); |
| 36 | + const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { |
| 37 | + images: image |
| 38 | + ? [ |
| 39 | + { |
| 40 | + src: image, |
| 41 | + alt: title, |
| 42 | + }, |
| 43 | + ] |
| 44 | + : undefined, |
| 45 | + intro: $el.find('div.mt10').html(), |
| 46 | + }); |
| 47 | + const pubDateStr: string | undefined = $el |
| 48 | + .find('span.time') |
| 49 | + .text() |
| 50 | + .replaceAll(/(年|月)/g, '-') |
| 51 | + .replace('日', ''); |
| 52 | + const linkUrl: string | undefined = $aEl.attr('href'); |
| 53 | + const authorEls: Element[] = $el.find('span.user').toArray(); |
| 54 | + const authors: DataItem['author'] = authorEls.map((authorEl) => { |
| 55 | + const $authorEl: Cheerio<Element> = $(authorEl); |
| 56 | + |
| 57 | + return { |
| 58 | + name: $authorEl.text(), |
| 59 | + url: undefined, |
| 60 | + avatar: undefined, |
| 61 | + }; |
| 62 | + }); |
| 63 | + const upDatedStr: string | undefined = pubDateStr; |
| 64 | + |
| 65 | + const processedItem: DataItem = { |
| 66 | + title, |
| 67 | + description, |
| 68 | + pubDate: pubDateStr ? parseDate(pubDateStr) : undefined, |
| 69 | + link: linkUrl, |
| 70 | + author: authors, |
| 71 | + content: { |
| 72 | + html: description, |
| 73 | + text: description, |
| 74 | + }, |
| 75 | + image, |
| 76 | + banner: image, |
| 77 | + updated: upDatedStr ? parseDate(upDatedStr) : undefined, |
| 78 | + language, |
| 79 | + }; |
| 80 | + |
| 81 | + return processedItem; |
| 82 | + }); |
| 83 | + |
| 84 | + items = await Promise.all( |
| 85 | + items.map((item) => { |
| 86 | + if (!item.link) { |
| 87 | + return item; |
| 88 | + } |
| 89 | + |
| 90 | + return cache.tryGet(item.link, async (): Promise<DataItem> => { |
| 91 | + const detailResponse = await ofetch(item.link); |
| 92 | + const $$: CheerioAPI = load(detailResponse); |
| 93 | + |
| 94 | + const title: string = $$('h2.title').text(); |
| 95 | + const description: string | undefined = |
| 96 | + item.description + |
| 97 | + art(path.join(__dirname, 'templates/description.art'), { |
| 98 | + description: $$('div.new-detailed').html(), |
| 99 | + }); |
| 100 | + const pubDateStr: string | undefined = $$('span.time').first().text(); |
| 101 | + const categories: string[] = $$('meta[name="keywords"]').attr('content')?.split(',') ?? []; |
| 102 | + const authorEls: Element[] = $$('span.user').toArray(); |
| 103 | + const authors: DataItem['author'] = authorEls.map((authorEl) => { |
| 104 | + const $$authorEl: Cheerio<Element> = $$(authorEl); |
| 105 | + |
| 106 | + return { |
| 107 | + name: $$authorEl.text(), |
| 108 | + url: undefined, |
| 109 | + avatar: undefined, |
| 110 | + }; |
| 111 | + }); |
| 112 | + const upDatedStr: string | undefined = pubDateStr; |
| 113 | + |
| 114 | + const processedItem: DataItem = { |
| 115 | + title, |
| 116 | + description, |
| 117 | + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, |
| 118 | + category: categories, |
| 119 | + author: authors, |
| 120 | + content: { |
| 121 | + html: description, |
| 122 | + text: description, |
| 123 | + }, |
| 124 | + updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, |
| 125 | + language, |
| 126 | + }; |
| 127 | + |
| 128 | + return { |
| 129 | + ...item, |
| 130 | + ...processedItem, |
| 131 | + }; |
| 132 | + }); |
| 133 | + }) |
| 134 | + ); |
| 135 | + |
| 136 | + const description: string = $('meta[name="description"]').attr('content') ?? ''; |
| 137 | + |
| 138 | + return { |
| 139 | + title: $('title').text().split(/:/)[0], |
| 140 | + description, |
| 141 | + link: targetUrl, |
| 142 | + item: items, |
| 143 | + allowEmpty: true, |
| 144 | + image: $('div.navbar-header img').attr('src'), |
| 145 | + author: description.split(/:/)[0], |
| 146 | + language, |
| 147 | + id: targetUrl, |
| 148 | + }; |
| 149 | +}; |
| 150 | + |
| 151 | +export const route: Route = { |
| 152 | + path: '/news/:id?', |
| 153 | + name: '资讯', |
| 154 | + url: 'dbaplus.cn', |
| 155 | + maintainers: ['nczitzk'], |
| 156 | + handler, |
| 157 | + example: '/dbaplus/news/9', |
| 158 | + parameters: { |
| 159 | + category: { |
| 160 | + description: '分类,默认为 `9`,即全部,可在对应分类页 URL 中找到', |
| 161 | + options: [ |
| 162 | + { |
| 163 | + label: '全部', |
| 164 | + value: '9', |
| 165 | + }, |
| 166 | + { |
| 167 | + label: '数据库', |
| 168 | + value: '153', |
| 169 | + }, |
| 170 | + { |
| 171 | + label: '国产数据库', |
| 172 | + value: '217', |
| 173 | + }, |
| 174 | + { |
| 175 | + label: 'ORACLE', |
| 176 | + value: '10', |
| 177 | + }, |
| 178 | + { |
| 179 | + label: 'MySQL', |
| 180 | + value: '11', |
| 181 | + }, |
| 182 | + { |
| 183 | + label: 'SQL优化', |
| 184 | + value: '155', |
| 185 | + }, |
| 186 | + { |
| 187 | + label: 'Newsletter', |
| 188 | + value: '156', |
| 189 | + }, |
| 190 | + { |
| 191 | + label: '其它', |
| 192 | + value: '154', |
| 193 | + }, |
| 194 | + { |
| 195 | + label: '运维', |
| 196 | + value: '134', |
| 197 | + }, |
| 198 | + { |
| 199 | + label: '大数据', |
| 200 | + value: '73', |
| 201 | + }, |
| 202 | + { |
| 203 | + label: '架构', |
| 204 | + value: '141', |
| 205 | + }, |
| 206 | + { |
| 207 | + label: 'PaaS云', |
| 208 | + value: '72', |
| 209 | + }, |
| 210 | + { |
| 211 | + label: '职场生涯', |
| 212 | + value: '149', |
| 213 | + }, |
| 214 | + { |
| 215 | + label: '标准评估', |
| 216 | + value: '248', |
| 217 | + }, |
| 218 | + { |
| 219 | + label: '这里有毒', |
| 220 | + value: '21', |
| 221 | + }, |
| 222 | + { |
| 223 | + label: '最新活动', |
| 224 | + value: '152', |
| 225 | + }, |
| 226 | + { |
| 227 | + label: '往期干货', |
| 228 | + value: '148', |
| 229 | + }, |
| 230 | + { |
| 231 | + label: '特别策划', |
| 232 | + value: '150', |
| 233 | + }, |
| 234 | + { |
| 235 | + label: '荐书', |
| 236 | + value: '151', |
| 237 | + }, |
| 238 | + ], |
| 239 | + }, |
| 240 | + }, |
| 241 | + description: `:::tip |
| 242 | +订阅 [资讯](https://dbaplus.cn/news-9-1.html),其源网址为 \`https://dbaplus.cn/news-9-1.html\`,请参考该 URL 指定部分构成参数,此时路由为 [\`/dbaplus/news/9\`](https://rsshub.app/dbaplus/news/9)。 |
| 243 | +
|
| 244 | +::: |
| 245 | +
|
| 246 | +<details> |
| 247 | + <summary>更多分类</summary> |
| 248 | +
|
| 249 | + | [全部](https://dbaplus.cn/news-9-1.html) | [数据库](https://dbaplus.cn/news-153-1.html) | [运维](https://dbaplus.cn/news-134-1.html) | [大数据](https://dbaplus.cn/news-73-1.html) | [架构](https://dbaplus.cn/news-141-1.html) | |
| 250 | + | ---------------------------------------- | -------------------------------------------- | ------------------------------------------ | ------------------------------------------- | ------------------------------------------ | |
| 251 | + | [9](https://rsshub.app/dbaplus/news/9) | [153](https://rsshub.app/dbaplus/news/153) | [134](https://rsshub.app/dbaplus/news/134) | [73](https://rsshub.app/dbaplus/news/73) | [141](https://rsshub.app/dbaplus/news/141) | |
| 252 | +
|
| 253 | + | [PaaS云](https://dbaplus.cn/news-72-1.html) | [职场生涯](https://dbaplus.cn/news-149-1.html) | [标准评估](https://dbaplus.cn/news-248-1.html) | [这里有毒](https://dbaplus.cn/news-21-1.html) | |
| 254 | + | ------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- | --------------------------------------------- | |
| 255 | + | [72](https://rsshub.app/dbaplus/news/72) | [149](https://rsshub.app/dbaplus/news/149) | [248](https://rsshub.app/dbaplus/news/248) | [21](https://rsshub.app/dbaplus/news/21) | |
| 256 | +
|
| 257 | + #### [数据库](https://dbaplus.cn/news-153-1.html) |
| 258 | +
|
| 259 | + | [国产数据库](https://dbaplus.cn/news-217-1.html) | [ORACLE](https://dbaplus.cn/news-10-1.html) | [MySQL](https://dbaplus.cn/news-11-1.html) | [SQL优化](https://dbaplus.cn/news-155-1.html) | [Newsletter](https://dbaplus.cn/news-156-1.html) | |
| 260 | + | ------------------------------------------------ | ------------------------------------------- | ------------------------------------------ | --------------------------------------------- | ------------------------------------------------ | |
| 261 | + | [217](https://rsshub.app/dbaplus/news/217) | [10](https://rsshub.app/dbaplus/news/10) | [11](https://rsshub.app/dbaplus/news/11) | [155](https://rsshub.app/dbaplus/news/155) | [156](https://rsshub.app/dbaplus/news/156) | |
| 262 | +
|
| 263 | + | [其它](https://dbaplus.cn/news-154-1.html) | |
| 264 | + | ------------------------------------------ | |
| 265 | + | [154](https://rsshub.app/dbaplus/news/154) | |
| 266 | +
|
| 267 | + #### [这里有毒](https://dbaplus.cn/news-21-1.html) |
| 268 | +
|
| 269 | + | [最新活动](https://dbaplus.cn/news-152-1.html) | [往期干货](https://dbaplus.cn/news-148-1.html) | [特别策划](https://dbaplus.cn/news-150-1.html) | [荐书](https://dbaplus.cn/news-151-1.html) | |
| 270 | + | ---------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- | ------------------------------------------ | |
| 271 | + | [152](https://rsshub.app/dbaplus/news/152) | [148](https://rsshub.app/dbaplus/news/148) | [150](https://rsshub.app/dbaplus/news/150) | [151](https://rsshub.app/dbaplus/news/151) | |
| 272 | +
|
| 273 | +</details> |
| 274 | +`, |
| 275 | + categories: ['programming'], |
| 276 | + features: { |
| 277 | + requireConfig: false, |
| 278 | + requirePuppeteer: false, |
| 279 | + antiCrawler: false, |
| 280 | + supportRadar: true, |
| 281 | + supportBT: false, |
| 282 | + supportPodcast: false, |
| 283 | + supportScihub: false, |
| 284 | + }, |
| 285 | + radar: [ |
| 286 | + { |
| 287 | + source: ['dbaplus.cn/news*'], |
| 288 | + target: (_, url) => { |
| 289 | + const urlObj: URL = new URL(url); |
| 290 | + const href: string = urlObj.href; |
| 291 | + const id: string | undefined = href.match(/-(\d+)-\.html/)?.[1]; |
| 292 | + |
| 293 | + return `/dbaplus/news${id ? `/${id}` : ''}`; |
| 294 | + }, |
| 295 | + }, |
| 296 | + { |
| 297 | + title: '全部', |
| 298 | + source: ['dbaplus.cn/news-9-1.html'], |
| 299 | + target: '/news/9', |
| 300 | + }, |
| 301 | + { |
| 302 | + title: '数据库', |
| 303 | + source: ['dbaplus.cn/news-153-1.html'], |
| 304 | + target: '/news/153', |
| 305 | + }, |
| 306 | + { |
| 307 | + title: '国产数据库', |
| 308 | + source: ['dbaplus.cn/news-217-1.html'], |
| 309 | + target: '/news/217', |
| 310 | + }, |
| 311 | + { |
| 312 | + title: 'ORACLE', |
| 313 | + source: ['dbaplus.cn/news-10-1.html'], |
| 314 | + target: '/news/10', |
| 315 | + }, |
| 316 | + { |
| 317 | + title: 'MySQL', |
| 318 | + source: ['dbaplus.cn/news-11-1.html'], |
| 319 | + target: '/news/11', |
| 320 | + }, |
| 321 | + { |
| 322 | + title: 'SQL优化', |
| 323 | + source: ['dbaplus.cn/news-155-1.html'], |
| 324 | + target: '/news/155', |
| 325 | + }, |
| 326 | + { |
| 327 | + title: 'Newsletter', |
| 328 | + source: ['dbaplus.cn/news-156-1.html'], |
| 329 | + target: '/news/156', |
| 330 | + }, |
| 331 | + { |
| 332 | + title: '其它', |
| 333 | + source: ['dbaplus.cn/news-154-1.html'], |
| 334 | + target: '/news/154', |
| 335 | + }, |
| 336 | + { |
| 337 | + title: '运维', |
| 338 | + source: ['dbaplus.cn/news-134-1.html'], |
| 339 | + target: '/news/134', |
| 340 | + }, |
| 341 | + { |
| 342 | + title: '大数据', |
| 343 | + source: ['dbaplus.cn/news-73-1.html'], |
| 344 | + target: '/news/73', |
| 345 | + }, |
| 346 | + { |
| 347 | + title: '架构', |
| 348 | + source: ['dbaplus.cn/news-141-1.html'], |
| 349 | + target: '/news/141', |
| 350 | + }, |
| 351 | + { |
| 352 | + title: 'PaaS云', |
| 353 | + source: ['dbaplus.cn/news-72-1.html'], |
| 354 | + target: '/news/72', |
| 355 | + }, |
| 356 | + { |
| 357 | + title: '职场生涯', |
| 358 | + source: ['dbaplus.cn/news-149-1.html'], |
| 359 | + target: '/news/149', |
| 360 | + }, |
| 361 | + { |
| 362 | + title: '标准评估', |
| 363 | + source: ['dbaplus.cn/news-248-1.html'], |
| 364 | + target: '/news/248', |
| 365 | + }, |
| 366 | + { |
| 367 | + title: '这里有毒', |
| 368 | + source: ['dbaplus.cn/news-21-1.html'], |
| 369 | + target: '/news/21', |
| 370 | + }, |
| 371 | + { |
| 372 | + title: '最新活动', |
| 373 | + source: ['dbaplus.cn/news-152-1.html'], |
| 374 | + target: '/news/152', |
| 375 | + }, |
| 376 | + { |
| 377 | + title: '往期干货', |
| 378 | + source: ['dbaplus.cn/news-148-1.html'], |
| 379 | + target: '/news/148', |
| 380 | + }, |
| 381 | + { |
| 382 | + title: '特别策划', |
| 383 | + source: ['dbaplus.cn/news-150-1.html'], |
| 384 | + target: '/news/150', |
| 385 | + }, |
| 386 | + { |
| 387 | + title: '荐书', |
| 388 | + source: ['dbaplus.cn/news-151-1.html'], |
| 389 | + target: '/news/151', |
| 390 | + }, |
| 391 | + ], |
| 392 | + view: ViewType.Articles, |
| 393 | +}; |
0 commit comments