rsshub 1.0.0-master.fb6d4f3 → 1.0.0-master.fb7e42c
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/lib/assets/logo.svg +1 -0
- package/lib/config.ts +10 -0
- package/lib/errors/index.test.ts +15 -5
- package/lib/errors/types/captcha.ts +5 -0
- package/lib/middleware/cache.ts +4 -0
- package/lib/middleware/parameter.ts +1 -1
- package/lib/registry.ts +6 -2
- package/lib/routes/10000link/info.ts +1 -1
- package/lib/routes/18comic/album.ts +1 -0
- package/lib/routes/18comic/blogs.ts +1 -0
- package/lib/routes/18comic/search.ts +1 -0
- package/lib/routes/199it/index.ts +1 -1
- package/lib/routes/21caijing/channel.ts +1 -1
- package/lib/routes/51cto/utils.ts +1 -1
- package/lib/routes/6v123/index.ts +487 -0
- package/lib/routes/6v123/namespace.ts +1 -0
- package/lib/routes/8kcos/cat.ts +3 -0
- package/lib/routes/8kcos/latest.ts +1 -0
- package/lib/routes/8kcos/tag.ts +1 -0
- package/lib/routes/91porn/author.ts +1 -0
- package/lib/routes/95mm/category.ts +1 -0
- package/lib/routes/95mm/tag.ts +1 -0
- package/lib/routes/aa1/60s.ts +189 -0
- package/lib/routes/{xinhuanet → aa1}/namespace.ts +3 -3
- package/lib/routes/abc/index.ts +1 -1
- package/lib/routes/abskoop/index.ts +3 -0
- package/lib/routes/abskoop/nsfw.ts +3 -0
- package/lib/routes/acgvinyl/namespace.ts +6 -0
- package/lib/routes/acgvinyl/news.ts +86 -0
- package/lib/routes/ally/rail.ts +1 -1
- package/lib/routes/amazfitwatchfaces/index.ts +1 -2
- package/lib/routes/anthropic/news.ts +13 -11
- package/lib/routes/apnews/mobile-api.ts +1 -1
- package/lib/routes/apnews/sitemap.ts +1 -1
- package/lib/routes/app-sales/index.ts +1 -1
- package/lib/routes/apple/apps.ts +2 -2
- package/lib/routes/apple/podcast.ts +58 -25
- package/lib/routes/apple/security-releases.ts +2 -2
- package/lib/routes/asiafruitchina/categories.ts +1 -1
- package/lib/routes/asiantolick/index.ts +3 -0
- package/lib/routes/asmr-200/index.ts +1 -0
- package/lib/routes/bandisoft/history.ts +2 -2
- package/lib/routes/bangumi.tv/group/reply.ts +1 -1
- package/lib/routes/banshujiang/index.ts +1 -1
- package/lib/routes/banyuetan/index.ts +1 -1
- package/lib/routes/baozimh/index.ts +21 -3
- package/lib/routes/baselang/index.ts +117 -0
- package/lib/routes/baselang/namespace.ts +6 -0
- package/lib/routes/bbc/learningenglish.ts +35 -20
- package/lib/routes/bestofjs/monthly.ts +137 -0
- package/lib/routes/bestofjs/namespace.ts +7 -0
- package/lib/routes/bestofjs/templates/description.art +36 -0
- package/lib/routes/bilibili/cache.ts +17 -1
- package/lib/routes/bilibili/dynamic.ts +34 -8
- package/lib/routes/bilibili/page.ts +1 -1
- package/lib/routes/bilibili/ranking.ts +25 -17
- package/lib/routes/bilibili/wasm-exec.ts +1 -1
- package/lib/routes/bilibili/weekly-recommend.ts +22 -6
- package/lib/routes/bjp/apod.ts +1 -1
- package/lib/routes/bnext/index.ts +58 -0
- package/lib/routes/bnext/namespace.ts +7 -0
- package/lib/routes/bookwalker/namespace.ts +9 -0
- package/lib/routes/bookwalker/search.ts +114 -0
- package/lib/routes/bookwalker/templates/description.art +13 -0
- package/lib/routes/booru/mmda.ts +1 -0
- package/lib/routes/buaa/jiaowu.ts +1 -1
- package/lib/routes/bullionvault/gold-news.ts +4 -4
- package/lib/routes/cast/index.ts +1 -1
- package/lib/routes/cbndata/information.ts +250 -0
- package/lib/routes/cbndata/namespace.ts +9 -0
- package/lib/routes/cbndata/templates/description.art +17 -0
- package/lib/routes/cccmc/index.ts +1 -1
- package/lib/routes/ccg/index.ts +181 -0
- package/lib/routes/ccg/namespace.ts +9 -0
- package/lib/routes/ccg/templates/description.art +21 -0
- package/lib/routes/chikubi/category.ts +1 -0
- package/lib/routes/chikubi/navigation.ts +1 -0
- package/lib/routes/chikubi/nipple-video-category.ts +1 -0
- package/lib/routes/chikubi/nipple-video-maker.ts +1 -0
- package/lib/routes/chikubi/search.ts +1 -0
- package/lib/routes/chinadaily/language.ts +1 -1
- package/lib/routes/chinaratings/credit-research.ts +1 -1
- package/lib/routes/chub/characters.ts +3 -0
- package/lib/routes/civitai/discussions.ts +1 -0
- package/lib/routes/civitai/models.ts +1 -0
- package/lib/routes/civitai/user.ts +3 -0
- package/lib/routes/cline/blog.ts +34 -23
- package/lib/routes/coolapk/utils.ts +5 -4
- package/lib/routes/coolbuy/index.ts +106 -0
- package/lib/routes/coolbuy/namespace.ts +9 -0
- package/lib/routes/coolbuy/templates/description.art +48 -0
- package/lib/routes/coolidge/film-guide.ts +60 -0
- package/lib/routes/coolidge/namespace.ts +7 -0
- package/lib/routes/coolidge/news.ts +65 -0
- package/lib/routes/coolidge/templates/description.art +4 -0
- package/lib/routes/coomer/index.ts +4 -6
- package/lib/routes/copymanga/comic.ts +2 -1
- package/lib/routes/cosplaytele/category.ts +1 -0
- package/lib/routes/cosplaytele/latest.ts +1 -0
- package/lib/routes/cosplaytele/popular.ts +1 -0
- package/lib/routes/cosplaytele/tag.ts +1 -0
- package/lib/routes/cpta/handler.ts +1 -1
- package/lib/routes/creative-comic/book.ts +1 -1
- package/lib/routes/crush/index.ts +77 -0
- package/lib/routes/crush/namespace.ts +7 -0
- package/lib/routes/cursor/blog.ts +84 -0
- package/lib/routes/daum/potplayer.ts +3 -3
- package/lib/routes/dbaplus/new.ts +392 -0
- package/lib/routes/dbaplus/templates/description.art +21 -0
- package/lib/routes/deepl/blog.ts +2 -3
- package/lib/routes/diariofruticola/filtro.ts +1 -1
- package/lib/routes/digitalpolicyalert/activity-tracker.ts +1 -1
- package/lib/routes/dlsite/campaign.ts +1 -0
- package/lib/routes/dlsite/ci-en/article.ts +1 -0
- package/lib/routes/dlsite/new.ts +1 -0
- package/lib/routes/dlsite/z-index/index.ts +3 -0
- package/lib/routes/dockerhub/utils.ts +1 -1
- package/lib/routes/dora-world/article.ts +2 -2
- package/lib/routes/dytt/index.ts +1 -2
- package/lib/routes/eeo/kuaixun.ts +154 -0
- package/lib/routes/eeo/namespace.ts +9 -0
- package/lib/routes/eeo/templates/description.art +7 -0
- package/lib/routes/ehentai/ehapi.ts +3 -3
- package/lib/routes/ehentai/favorites.ts +1 -0
- package/lib/routes/ehentai/search.ts +1 -0
- package/lib/routes/elecfans/article.ts +64 -0
- package/lib/routes/elecfans/namespace.ts +7 -0
- package/lib/routes/elecfans/soft.ts +69 -0
- package/lib/routes/engineering/namespace.ts +8 -0
- package/lib/routes/engineering/tag.ts +55 -0
- package/lib/routes/eventbrite/events.ts +152 -0
- package/lib/routes/eventbrite/namespace.ts +7 -0
- package/lib/routes/eventernote/actors.ts +61 -64
- package/lib/routes/everia/category.ts +1 -0
- package/lib/routes/everia/latest.ts +1 -0
- package/lib/routes/everia/search.ts +1 -0
- package/lib/routes/everia/tag.ts +1 -0
- package/lib/routes/expats/czech-news.ts +1 -1
- package/lib/routes/fangchan/list.ts +1 -1
- package/lib/routes/fansly/post.ts +1 -0
- package/lib/routes/fantia/search.ts +1 -0
- package/lib/routes/fantia/user.ts +1 -0
- package/lib/routes/finology/tag.ts +1 -1
- package/lib/routes/furaffinity/art.ts +1 -0
- package/lib/routes/furaffinity/browse.ts +1 -0
- package/lib/routes/furaffinity/commissions.ts +1 -0
- package/lib/routes/furaffinity/home.ts +1 -0
- package/lib/routes/furaffinity/journal-comments.ts +1 -0
- package/lib/routes/furaffinity/journals.ts +1 -0
- package/lib/routes/furaffinity/search.ts +1 -0
- package/lib/routes/furaffinity/shouts.ts +1 -0
- package/lib/routes/furaffinity/status.ts +1 -0
- package/lib/routes/furaffinity/submission-comments.ts +1 -0
- package/lib/routes/furaffinity/user.ts +1 -0
- package/lib/routes/furaffinity/watchers.ts +1 -0
- package/lib/routes/furaffinity/watching.ts +1 -0
- package/lib/routes/gamebase/news.ts +2 -2
- package/lib/routes/gamer/gnn-index.ts +8 -3
- package/lib/routes/gcores/categories.ts +1 -1
- package/lib/routes/gcores/collections.ts +1 -1
- package/lib/routes/gcores/tags.ts +1 -1
- package/lib/routes/gcores/topics.ts +1 -1
- package/lib/routes/gdufs/xwxy/index.ts +196 -0
- package/lib/routes/gelbooru/post.ts +1 -0
- package/lib/routes/genossenschaften/index.ts +2 -3
- package/lib/routes/github/activity.ts +1 -1
- package/lib/routes/gitstar-ranking/index.ts +1 -1
- package/lib/routes/go/jihs/idwr.ts +3 -4
- package/lib/routes/google/jules.ts +63 -0
- package/lib/routes/gov/mem/namespace.ts +7 -0
- package/lib/routes/gov/mem/zfxxgkpt.ts +96 -0
- package/lib/routes/gov/moa/gjs.ts +1 -1
- package/lib/routes/gov/mot/index.ts +157 -53
- package/lib/routes/grainoil/category.ts +1 -2
- package/lib/routes/gxmzu/lib.ts +2 -2
- package/lib/routes/gxmzu/utils/index.ts +2 -2
- package/lib/routes/hameln/chapter.ts +1 -1
- package/lib/routes/hanime1/previews.ts +1 -0
- package/lib/routes/hanime1/search.ts +1 -0
- package/lib/routes/hex-rays/index.ts +1 -1
- package/lib/routes/hit/hitgs.ts +1 -1
- package/lib/routes/hit/namespace.ts +1 -1
- package/lib/routes/hpoi/banner-item.ts +28 -11
- package/lib/routes/hpoi/info.ts +1 -1
- package/lib/routes/hudsonrivertrading/index.ts +122 -0
- package/lib/routes/hudsonrivertrading/namespace.ts +7 -0
- package/lib/routes/huggingface/daily-papers.ts +45 -9
- package/lib/routes/huijin-inv/namespace.ts +7 -0
- package/lib/routes/huijin-inv/news.ts +83 -0
- package/lib/routes/hupu/index.ts +170 -74
- package/lib/routes/hupu/types.ts +163 -0
- package/lib/routes/hust/gs.ts +1 -1
- package/lib/routes/hust/mse.ts +1 -1
- package/lib/routes/huxiu/util.ts +2 -2
- package/lib/routes/immich/cursed-knowledge.ts +49 -0
- package/lib/routes/immich/namespace.ts +7 -0
- package/lib/routes/infoq/presentations.ts +1 -1
- package/lib/routes/instagram/common-utils.ts +3 -3
- package/lib/routes/investor/index.ts +362 -148
- package/lib/routes/itch/devlog.ts +7 -3
- package/lib/routes/iwara/index.ts +3 -0
- package/lib/routes/iwara/subscriptions.ts +1 -0
- package/lib/routes/javbus/index.ts +1 -1
- package/lib/routes/javdb/actors.ts +1 -0
- package/lib/routes/javdb/index.ts +3 -0
- package/lib/routes/javdb/lists.ts +3 -0
- package/lib/routes/javdb/makers.ts +1 -0
- package/lib/routes/javdb/rankings.ts +1 -0
- package/lib/routes/javdb/search.ts +1 -0
- package/lib/routes/javdb/series.ts +1 -0
- package/lib/routes/javdb/videocodes.ts +1 -0
- package/lib/routes/javlibrary/bestrated.ts +3 -0
- package/lib/routes/javlibrary/bestreviews.ts +1 -0
- package/lib/routes/javlibrary/genre.ts +3 -0
- package/lib/routes/javlibrary/maker.ts +1 -0
- package/lib/routes/javlibrary/mostwanted.ts +3 -0
- package/lib/routes/javlibrary/newentries.ts +3 -0
- package/lib/routes/javlibrary/newrelease.ts +3 -0
- package/lib/routes/javlibrary/update.ts +3 -0
- package/lib/routes/javlibrary/user.ts +3 -0
- package/lib/routes/javlibrary/utils.ts +1 -1
- package/lib/routes/javtrailers/categories.ts +3 -0
- package/lib/routes/javtrailers/studios.ts +3 -0
- package/lib/routes/jbma/namespace.ts +17 -0
- package/lib/routes/jbma/report.ts +471 -0
- package/lib/routes/jetbrains/comments.ts +1 -1
- package/lib/routes/jingzhengu/utils.ts +1 -1
- package/lib/routes/jou/utils/index.ts +2 -2
- package/lib/routes/jpxgmn/search.ts +3 -0
- package/lib/routes/jpxgmn/weekly.ts +3 -0
- package/lib/routes/juejin/aicoding.ts +102 -0
- package/lib/routes/juejin/trending.ts +70 -20
- package/lib/routes/juejin/utils.ts +36 -51
- package/lib/routes/jumeili/home.ts +1 -1
- package/lib/routes/kakuyomu/works.ts +1 -1
- package/lib/routes/kemono/index.ts +11 -3
- package/lib/routes/kemono/templates/discord.art +1 -1
- package/lib/routes/kisskiss/blog.ts +1 -0
- package/lib/routes/komiic/comic.ts +2 -1
- package/lib/routes/koyso/index.ts +339 -0
- package/lib/routes/koyso/namespace.ts +9 -0
- package/lib/routes/koyso/templates/description.art +13 -0
- package/lib/routes/kpopping/kpics.ts +3 -4
- package/lib/routes/kpopping/news.ts +2 -2
- package/lib/routes/laimanhua/index.ts +1 -0
- package/lib/routes/letterboxd/index.ts +65 -0
- package/lib/routes/letterboxd/namespace.ts +8 -0
- package/lib/routes/lhratings/research.ts +1 -1
- package/lib/routes/literotica/category.ts +3 -0
- package/lib/routes/lovelive-anime/topics.ts +5 -6
- package/lib/routes/maccms/index.ts +1 -1
- package/lib/routes/mangadex/index.ts +3 -0
- package/lib/routes/mangadex/mdlist/feed.ts +1 -0
- package/lib/routes/mangadex/user/feed.ts +1 -0
- package/lib/routes/mangadex/user/follows.ts +1 -0
- package/lib/routes/manhuagui/comic.ts +1 -0
- package/lib/routes/manhuagui/subscribe.ts +1 -0
- package/lib/routes/manyvids/video.ts +3 -0
- package/lib/routes/mercari/util.ts +1 -1
- package/lib/routes/mihoyo/sr/news.ts +2 -1
- package/lib/routes/mihoyo/zzz/news.ts +2 -1
- package/lib/routes/mingpao/index.ts +1 -1
- package/lib/routes/musify/index.ts +2 -3
- package/lib/routes/mycard520/news.ts +2 -2
- package/lib/routes/nankai/ai-notice.ts +142 -0
- package/lib/routes/nankai/graduate-notice.ts +162 -0
- package/lib/routes/natgeo/natgeo.ts +3 -2
- package/lib/routes/naturalism/namespace.ts +7 -0
- package/lib/routes/naturalism/new.ts +59 -0
- package/lib/routes/netflav/index.ts +3 -0
- package/lib/routes/nhentai/search.ts +1 -0
- package/lib/routes/nhk/news-web-easy.ts +1 -1
- package/lib/routes/nicovideo/mylist.ts +39 -0
- package/lib/routes/nicovideo/types.ts +27 -0
- package/lib/routes/nicovideo/utils.ts +27 -1
- package/lib/routes/nikkei/cn/index.ts +1 -4
- package/lib/routes/nio/nioradio.ts +1 -1
- package/lib/routes/njxzc/utils/index.ts +2 -2
- package/lib/routes/notion/namespace.ts +2 -2
- package/lib/routes/now/news.ts +1 -1
- package/lib/routes/nytimes/index.ts +1 -1
- package/lib/routes/olevod/vod.ts +3 -0
- package/lib/routes/olevod/vodlist.ts +3 -0
- package/lib/routes/oreno3d/main.ts +1 -0
- package/lib/routes/oschina/column.ts +1 -2
- package/lib/routes/oschina/event.ts +1 -1
- package/lib/routes/osu/beatmaps/latest-ranked.ts +2 -3
- package/lib/routes/papers/category.ts +3 -4
- package/lib/routes/patreon/feed.ts +1 -0
- package/lib/routes/picuki/profile.ts +13 -5
- package/lib/routes/pixiv/bookmarks.ts +1 -0
- package/lib/routes/pixiv/illustfollow.ts +1 -0
- package/lib/routes/pixiv/novel-api/user-novels/sfw.ts +1 -1
- package/lib/routes/pixiv/novel-series.ts +17 -3
- package/lib/routes/pixiv/novels.ts +1 -0
- package/lib/routes/pixiv/ranking.ts +1 -0
- package/lib/routes/pixiv/search.ts +1 -0
- package/lib/routes/pixiv/user.ts +1 -0
- package/lib/routes/pixivision/utils.ts +1 -1
- package/lib/routes/pku/hr.ts +1 -1
- package/lib/routes/pku/scc/recruit.ts +1 -1
- package/lib/routes/pornhub/category-url.ts +1 -0
- package/lib/routes/pornhub/category.ts +1 -0
- package/lib/routes/pornhub/pornstar.ts +1 -0
- package/lib/routes/pornhub/search.ts +1 -0
- package/lib/routes/pornhub/users.ts +1 -0
- package/lib/routes/producthunt/templates/description.art +2 -2
- package/lib/routes/producthunt/today.ts +17 -8
- package/lib/routes/ps/trophy.ts +1 -1
- package/lib/routes/pubscholar/utils.ts +14 -1
- package/lib/routes/qq/lol/news.ts +1 -1
- package/lib/routes/qweather/3days.ts +14 -14
- package/lib/routes/qweather/now.ts +12 -10
- package/lib/routes/qweather/util.tsx +89 -0
- package/lib/routes/qwenlm/blog.ts +75 -0
- package/lib/routes/qwenlm/namespace.ts +6 -0
- package/lib/routes/radio-canada/latest.ts +30 -17
- package/lib/routes/rawkuma/manga.ts +1 -0
- package/lib/routes/ruc/ai.ts +1 -1
- package/lib/routes/ruc/hr.ts +1 -1
- package/lib/routes/samrdprc/index.ts +241 -0
- package/lib/routes/samrdprc/namespace.ts +1 -1
- package/lib/routes/scientificamerican/podcast.ts +2 -2
- package/lib/routes/scitechvista/index.ts +99 -0
- package/lib/routes/scitechvista/namespace.ts +7 -0
- package/lib/routes/scitechvista/templates/description.art +6 -0
- package/lib/routes/scoop/apps.ts +2 -4
- package/lib/routes/sdo/ff14risingstones/api.ts +78 -0
- package/lib/routes/sdo/ff14risingstones/constant.ts +338 -0
- package/lib/routes/sdo/ff14risingstones/posts.ts +80 -0
- package/lib/routes/sdo/ff14risingstones/strats.ts +75 -0
- package/lib/routes/sdo/ff14risingstones/templates/duties-party.art +41 -0
- package/lib/routes/sdo/ff14risingstones/templates/fc-party.art +26 -0
- package/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art +9 -0
- package/lib/routes/sdo/ff14risingstones/templates/rp-party.art +15 -0
- package/lib/routes/sdo/ff14risingstones/timeline.ts +31 -0
- package/lib/routes/sdo/ff14risingstones/types/dynamic.ts +50 -0
- package/lib/routes/sdo/ff14risingstones/types/index.ts +3 -0
- package/lib/routes/sdo/ff14risingstones/types/other.ts +57 -0
- package/lib/routes/sdo/ff14risingstones/types/party.ts +111 -0
- package/lib/routes/sdo/ff14risingstones/user-dynamics.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/user-posts.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/user-resently.ts +38 -0
- package/lib/routes/sdo/ff14risingstones/user-strats.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/utils.ts +215 -0
- package/lib/routes/sdo/namespace.ts +7 -0
- package/lib/routes/secretsanfrancisco/namespace.ts +7 -0
- package/lib/routes/secretsanfrancisco/rss.ts +101 -0
- package/lib/routes/secretsanfrancisco/templates/description.art +9 -0
- package/lib/routes/sehuatang/user.ts +1 -0
- package/lib/routes/showstart/utils.ts +1 -1
- package/lib/routes/shu/global.ts +6 -5
- package/lib/routes/shu/xxgk.ts +107 -0
- package/lib/routes/shuiguopai/index.ts +3 -0
- package/lib/routes/sis001/author.ts +1 -0
- package/lib/routes/sjtu/jwc.ts +1 -1
- package/lib/routes/skeb/following-creators.ts +1 -0
- package/lib/routes/skeb/following-works.ts +1 -0
- package/lib/routes/skeb/friend-works.ts +1 -0
- package/lib/routes/skeb/index.ts +1 -0
- package/lib/routes/skeb/search.ts +1 -0
- package/lib/routes/skeb/works.ts +44 -25
- package/lib/routes/skebetter/illust.ts +1 -0
- package/lib/routes/skebetter/index.ts +1 -0
- package/lib/routes/skebetter/manga.ts +1 -0
- package/lib/routes/sohu/mp.ts +1 -1
- package/lib/routes/sotwe/user.ts +1 -1
- package/lib/routes/sspai/index.ts +5 -1
- package/lib/routes/stcn/index.ts +1 -2
- package/lib/routes/stcn/rank.ts +1 -2
- package/lib/routes/surfshark/blog.ts +273 -77
- package/lib/routes/surfshark/templates/description.art +16 -4
- package/lib/routes/sustainabilitymag/articles.ts +1 -1
- package/lib/routes/syosetu/dev.ts +1 -1
- package/lib/routes/syosetu/ranking-isekai.ts +1 -1
- package/lib/routes/szse/disclosure/listed-notice.ts +44 -6
- package/lib/routes/t66y/post.ts +1 -0
- package/lib/routes/techcrunch/news.ts +1 -0
- package/lib/routes/techpowerup/index.ts +11 -11
- package/lib/routes/techpowerup/namespace.ts +1 -1
- package/lib/routes/techpowerup/review.ts +12 -12
- package/lib/routes/techpowerup/utils.ts +2 -2
- package/lib/routes/telegram/channel-media.ts +249 -0
- package/lib/routes/telegram/channel.ts +5 -4
- package/lib/routes/telegram/stories.ts +130 -0
- package/lib/routes/telegram/tglib/channel.ts +146 -117
- package/lib/routes/telegram/tglib/client.ts +37 -139
- package/lib/routes/tesla/cx.ts +1 -1
- package/lib/routes/test/index.ts +4 -0
- package/lib/routes/thegadgetflow/namespace.ts +7 -0
- package/lib/routes/thegadgetflow/rss.ts +99 -0
- package/lib/routes/thegadgetflow/templates/description.art +9 -0
- package/lib/routes/theverge/index.ts +20 -6
- package/lib/routes/threads/utils.ts +7 -3
- package/lib/routes/tidb/blog.ts +2 -2
- package/lib/routes/tmtpost/column.ts +1 -1
- package/lib/routes/toutiao/user.ts +2 -2
- package/lib/routes/trendforce/namespace.ts +9 -0
- package/lib/routes/trendforce/new.ts +113 -0
- package/lib/routes/tsinghua/news.ts +87 -0
- package/lib/routes/twitter/api/mobile-api/api.ts +1 -1
- package/lib/routes/twitter/api/web-api/api.ts +5 -1
- package/lib/routes/txrjy/fornumtopic.ts +2 -2
- package/lib/routes/typst/universe.ts +1 -1
- package/lib/routes/uber/blog.ts +87 -46
- package/lib/routes/uraaka-joshi/uraaka-joshi-user.ts +1 -0
- package/lib/routes/uraaka-joshi/uraaka-joshi.ts +3 -0
- package/lib/routes/wainao/topics.ts +1 -1
- package/lib/routes/wdfxw/bookfree.ts +1 -1
- package/lib/routes/weibo/utils.ts +17 -9
- package/lib/routes/wikipedia/current-events.ts +411 -0
- package/lib/routes/wikipedia/namespace.ts +7 -0
- package/lib/routes/wnacg/category.ts +3 -0
- package/lib/routes/wnacg/index.ts +3 -0
- package/lib/routes/wnacg/tag.ts +3 -0
- package/lib/routes/wohnnet/index.ts +2 -2
- package/lib/routes/xiaohongshu/user.ts +2 -2
- package/lib/routes/xiaohongshu/util.ts +16 -9
- package/lib/routes/xjtu/ee-jzxx.ts +1 -1
- package/lib/routes/xjtu/zs.ts +1 -1
- package/lib/routes/xsijishe/forum.ts +1 -0
- package/lib/routes/xsijishe/rank.ts +1 -0
- package/lib/routes/xwenming/index.ts +1 -1
- package/lib/routes/yahoo/news/utils.ts +7 -3
- package/lib/routes/ymgal/article.ts +1 -1
- package/lib/routes/yoasobi-music/media.ts +1 -1
- package/lib/routes/youtube/api/google.ts +20 -6
- package/lib/routes/youtube/api/subtitles.ts +86 -0
- package/lib/routes/youtube/api/youtubei.ts +7 -51
- package/lib/routes/youtube/channel.ts +1 -1
- package/lib/routes/youtube/community.ts +4 -4
- package/lib/routes/youtube/user.ts +1 -1
- package/lib/routes/yuanliao/index.ts +1 -1
- package/lib/routes/zaimanhua/comic.ts +1 -0
- package/lib/routes/zaimanhua/update.ts +1 -0
- package/lib/routes/zaker/utils.ts +1 -1
- package/lib/routes/zaobao/interactive.ts +18 -7
- package/lib/routes/zaobao/other.ts +3 -2
- package/lib/routes/zaobao/realtime.ts +3 -2
- package/lib/routes/zaobao/util.tsx +185 -0
- package/lib/routes/zaobao/zaobao.tsx +23 -0
- package/lib/routes/zaobao/znews.ts +3 -2
- package/lib/routes/zhihu/daily-section.ts +19 -24
- package/lib/routes/zhihu/daily.ts +8 -11
- package/lib/routes/zju/cse/index.ts +102 -0
- package/lib/routes/zodgame/forum.ts +1 -0
- package/lib/server.ts +1 -1
- package/lib/types.ts +1 -1
- package/lib/utils/puppeteer-utils.test.ts +5 -5
- package/lib/utils/puppeteer.test.ts +1 -1
- package/lib/views/index.tsx +4 -4
- package/package.json +55 -57
- package/lib/routes/gdufs/xwxy/xwxy-news.ts +0 -90
- package/lib/routes/hit/jwc.ts +0 -86
- package/lib/routes/picuki/utils.ts +0 -19
- package/lib/routes/qweather/templates/3days.art +0 -22
- package/lib/routes/qweather/templates/now.art +0 -16
- package/lib/routes/xinhuanet/app.ts +0 -109
- package/lib/routes/zaobao/templates/zaobao.art +0 -17
- package/lib/routes/zaobao/util.ts +0 -198
|
@@ -10,6 +10,8 @@ import NotFoundError from '@/errors/types/not-found';
|
|
|
10
10
|
import { Data } from '@/types';
|
|
11
11
|
import dayjs from 'dayjs';
|
|
12
12
|
import duration from 'dayjs/plugin/duration.js';
|
|
13
|
+
import { getSrtAttachmentBatch } from './subtitles';
|
|
14
|
+
|
|
13
15
|
dayjs.extend(duration);
|
|
14
16
|
|
|
15
17
|
let count = 0;
|
|
@@ -105,8 +107,9 @@ export const getDataByUsername = async ({ username, embed, filterShorts }: { use
|
|
|
105
107
|
if (!playlistItems) {
|
|
106
108
|
throw new NotFoundError("This channel doesn't have any content.");
|
|
107
109
|
}
|
|
108
|
-
const videoIds = playlistItems.data.items.map((item) => item.snippet.resourceId.videoId)
|
|
109
|
-
const videoDetails = await utils.getVideos(videoIds, 'contentDetails', cache);
|
|
110
|
+
const videoIds = playlistItems.data.items.map((item) => item.snippet.resourceId.videoId);
|
|
111
|
+
const videoDetails = await utils.getVideos(videoIds.join(','), 'contentDetails', cache);
|
|
112
|
+
const subtitlesMap = await getSrtAttachmentBatch(videoIds);
|
|
110
113
|
|
|
111
114
|
return {
|
|
112
115
|
title: `${userHandleData?.channelName || username} - YouTube`,
|
|
@@ -120,6 +123,8 @@ export const getDataByUsername = async ({ username, embed, filterShorts }: { use
|
|
|
120
123
|
const videoId = snippet.resourceId.videoId;
|
|
121
124
|
const img = utils.getThumbnail(snippet.thumbnails);
|
|
122
125
|
const detail = videoDetails?.data.items.find((d) => d.id === videoId);
|
|
126
|
+
const srtAttachments = subtitlesMap[videoId] || [];
|
|
127
|
+
|
|
123
128
|
return {
|
|
124
129
|
title: snippet.title,
|
|
125
130
|
description: utils.renderDescription(embed, videoId, img, utils.formatDescription(snippet.description)),
|
|
@@ -133,6 +138,7 @@ export const getDataByUsername = async ({ username, embed, filterShorts }: { use
|
|
|
133
138
|
mime_type: 'text/html',
|
|
134
139
|
duration_in_seconds: detail?.contentDetails.duration ? dayjs.duration(detail.contentDetails.duration).asSeconds() : undefined,
|
|
135
140
|
},
|
|
141
|
+
...srtAttachments,
|
|
136
142
|
],
|
|
137
143
|
};
|
|
138
144
|
}),
|
|
@@ -147,8 +153,9 @@ export const getDataByChannelId = async ({ channelId, embed, filterShorts }: { c
|
|
|
147
153
|
const playlistId = filterShorts ? utils.getPlaylistWithShortsFilter(channelId) : originalPlaylistId;
|
|
148
154
|
|
|
149
155
|
const data = (await utils.getPlaylistItems(playlistId, 'snippet', cache)).data.items;
|
|
150
|
-
const videoIds = data.map((item) => item.snippet.resourceId.videoId)
|
|
151
|
-
const videoDetails = await utils.getVideos(videoIds, 'contentDetails', cache);
|
|
156
|
+
const videoIds = data.map((item) => item.snippet.resourceId.videoId);
|
|
157
|
+
const videoDetails = await utils.getVideos(videoIds.join(','), 'contentDetails', cache);
|
|
158
|
+
const subtitlesMap = await getSrtAttachmentBatch(videoIds);
|
|
152
159
|
|
|
153
160
|
return {
|
|
154
161
|
title: `${data[0].snippet.channelTitle} - YouTube`,
|
|
@@ -161,6 +168,8 @@ export const getDataByChannelId = async ({ channelId, embed, filterShorts }: { c
|
|
|
161
168
|
const videoId = snippet.resourceId.videoId;
|
|
162
169
|
const img = utils.getThumbnail(snippet.thumbnails);
|
|
163
170
|
const detail = videoDetails?.data.items.find((d) => d.id === videoId);
|
|
171
|
+
const srtAttachments = subtitlesMap[videoId] || [];
|
|
172
|
+
|
|
164
173
|
return {
|
|
165
174
|
title: snippet.title,
|
|
166
175
|
description: utils.renderDescription(embed, videoId, img, utils.formatDescription(snippet.description)),
|
|
@@ -174,6 +183,7 @@ export const getDataByChannelId = async ({ channelId, embed, filterShorts }: { c
|
|
|
174
183
|
mime_type: 'text/html',
|
|
175
184
|
duration_in_seconds: detail?.contentDetails.duration ? dayjs.duration(detail.contentDetails.duration).asSeconds() : undefined,
|
|
176
185
|
},
|
|
186
|
+
...srtAttachments,
|
|
177
187
|
],
|
|
178
188
|
};
|
|
179
189
|
}),
|
|
@@ -184,8 +194,9 @@ export const getDataByPlaylistId = async ({ playlistId, embed }: { playlistId: s
|
|
|
184
194
|
const playlistTitle = (await utils.getPlaylist(playlistId, 'snippet', cache)).data.items[0].snippet.title;
|
|
185
195
|
|
|
186
196
|
const data = (await utils.getPlaylistItems(playlistId, 'snippet', cache)).data.items.filter((d) => d.snippet.title !== 'Private video' && d.snippet.title !== 'Deleted video');
|
|
187
|
-
const videoIds = data.map((item) => item.snippet.resourceId.videoId)
|
|
188
|
-
const videoDetails = await utils.getVideos(videoIds, 'contentDetails', cache);
|
|
197
|
+
const videoIds = data.map((item) => item.snippet.resourceId.videoId);
|
|
198
|
+
const videoDetails = await utils.getVideos(videoIds.join(','), 'contentDetails', cache);
|
|
199
|
+
const subtitlesMap = await getSrtAttachmentBatch(videoIds);
|
|
189
200
|
|
|
190
201
|
return {
|
|
191
202
|
title: `${playlistTitle} by ${data[0].snippet.channelTitle} - YouTube`,
|
|
@@ -196,6 +207,8 @@ export const getDataByPlaylistId = async ({ playlistId, embed }: { playlistId: s
|
|
|
196
207
|
const videoId = snippet.resourceId.videoId;
|
|
197
208
|
const img = utils.getThumbnail(snippet.thumbnails);
|
|
198
209
|
const detail = videoDetails?.data.items.find((d) => d.id === videoId);
|
|
210
|
+
const srtAttachments = subtitlesMap[videoId] || [];
|
|
211
|
+
|
|
199
212
|
return {
|
|
200
213
|
title: snippet.title,
|
|
201
214
|
description: utils.renderDescription(embed, videoId, img, utils.formatDescription(snippet.description)),
|
|
@@ -209,6 +222,7 @@ export const getDataByPlaylistId = async ({ playlistId, embed }: { playlistId: s
|
|
|
209
222
|
mime_type: 'text/html',
|
|
210
223
|
duration_in_seconds: detail?.contentDetails.duration ? dayjs.duration(detail.contentDetails.duration).asSeconds() : undefined,
|
|
211
224
|
},
|
|
225
|
+
...srtAttachments,
|
|
212
226
|
],
|
|
213
227
|
};
|
|
214
228
|
}),
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { getSubtitles } from 'youtube-caption-extractor';
|
|
2
|
+
import pMap from 'p-map';
|
|
3
|
+
import cache from '@/utils/cache';
|
|
4
|
+
|
|
5
|
+
function pad(n: number, width: number = 2) {
|
|
6
|
+
return String(n).padStart(width, '0');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function toSrtTime(seconds: number): string {
|
|
10
|
+
const totalMs = Math.floor(seconds * 1000);
|
|
11
|
+
const hours = Math.floor(totalMs / 3_600_000);
|
|
12
|
+
const minutes = Math.floor((totalMs % 3_600_000) / 60000);
|
|
13
|
+
const secs = Math.floor((totalMs % 60000) / 1000);
|
|
14
|
+
const millis = totalMs % 1000;
|
|
15
|
+
return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${pad(millis, 3)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type Subtitle = {
|
|
19
|
+
start: string;
|
|
20
|
+
dur: string;
|
|
21
|
+
text: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function convertToSrt(segments: Subtitle[]): string {
|
|
25
|
+
return segments
|
|
26
|
+
.map((seg, index) => {
|
|
27
|
+
const start = Number.parseFloat(seg.start);
|
|
28
|
+
const end = start + Number.parseFloat(seg.dur);
|
|
29
|
+
return `${index + 1}
|
|
30
|
+
${toSrtTime(start)} --> ${toSrtTime(end)}
|
|
31
|
+
${seg.text}
|
|
32
|
+
`;
|
|
33
|
+
})
|
|
34
|
+
.join('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const getSubtitlesByVideoId = (videoId: string) =>
|
|
38
|
+
cache.tryGet(`youtube:getSubtitlesByVideoId:${videoId}`, async () => {
|
|
39
|
+
try {
|
|
40
|
+
const subtitles = await getSubtitles({ videoID: videoId });
|
|
41
|
+
const srt = convertToSrt(subtitles);
|
|
42
|
+
return srt;
|
|
43
|
+
} catch {
|
|
44
|
+
// Return empty string if subtitles are not available
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const createSubtitleDataUrl = (srt: string): string => `data:text/plain;charset=utf-8,${encodeURIComponent(srt)}`;
|
|
50
|
+
|
|
51
|
+
function createSrtAttachmentFromSrt(srt: string): Array<{ url: string; mime_type: string; title: string }> {
|
|
52
|
+
if (!srt || srt.trim() === '') {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const dataUrl = createSubtitleDataUrl(srt);
|
|
57
|
+
return [
|
|
58
|
+
{
|
|
59
|
+
url: dataUrl,
|
|
60
|
+
mime_type: 'text/srt',
|
|
61
|
+
title: 'Subtitles',
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const getSrtAttachment = async (videoId: string): Promise<Array<{ url: string; mime_type: string; title: string }>> => {
|
|
67
|
+
try {
|
|
68
|
+
const srt = await getSubtitlesByVideoId(videoId);
|
|
69
|
+
return createSrtAttachmentFromSrt(srt);
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const getSrtAttachmentBatch = async (videoIds: string[]) => {
|
|
76
|
+
const results = await pMap(
|
|
77
|
+
videoIds,
|
|
78
|
+
async (videoId) => {
|
|
79
|
+
const srt = await getSubtitlesByVideoId(videoId);
|
|
80
|
+
return { videoId, srt: createSrtAttachmentFromSrt(srt) };
|
|
81
|
+
},
|
|
82
|
+
{ concurrency: 5 }
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return Object.fromEntries(results.map(({ videoId, srt }) => [videoId, srt]));
|
|
86
|
+
};
|
|
@@ -1,44 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Data } from '@/types';
|
|
2
2
|
import cache from '@/utils/cache';
|
|
3
|
+
import { parseRelativeDate } from '@/utils/parse-date';
|
|
3
4
|
import { Innertube } from 'youtubei.js';
|
|
4
5
|
import utils, { getVideoUrl } from '../utils';
|
|
5
|
-
import {
|
|
6
|
-
import { parseRelativeDate } from '@/utils/parse-date';
|
|
6
|
+
import { getSrtAttachmentBatch } from './subtitles';
|
|
7
7
|
|
|
8
8
|
const innertubePromise = Innertube.create();
|
|
9
9
|
|
|
10
|
-
function pad(n: number, width: number = 2) {
|
|
11
|
-
return String(n).padStart(width, '0');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function toSrtTime(seconds: number): string {
|
|
15
|
-
const totalMs = Math.floor(seconds * 1000);
|
|
16
|
-
const hours = Math.floor(totalMs / 3_600_000);
|
|
17
|
-
const minutes = Math.floor((totalMs % 3_600_000) / 60000);
|
|
18
|
-
const secs = Math.floor((totalMs % 60000) / 1000);
|
|
19
|
-
const millis = totalMs % 1000;
|
|
20
|
-
return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${pad(millis, 3)}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type Subtitle = {
|
|
24
|
-
start: string;
|
|
25
|
-
dur: string;
|
|
26
|
-
text: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function convertToSrt(segments: Subtitle[]): string {
|
|
30
|
-
return segments
|
|
31
|
-
.map((seg, index) => {
|
|
32
|
-
const start = Number.parseFloat(seg.start);
|
|
33
|
-
const end = start + Number.parseFloat(seg.dur);
|
|
34
|
-
return `${index + 1}
|
|
35
|
-
${toSrtTime(start)} --> ${toSrtTime(end)}
|
|
36
|
-
${seg.text}
|
|
37
|
-
`;
|
|
38
|
-
})
|
|
39
|
-
.join('\n');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
10
|
export const getChannelIdByUsername = (username: string) =>
|
|
43
11
|
cache.tryGet(`youtube:getChannelIdByUsername:${username}`, async () => {
|
|
44
12
|
const innertube = await innertubePromise;
|
|
@@ -51,17 +19,11 @@ export const getDataByUsername = async ({ username, embed, filterShorts }: { use
|
|
|
51
19
|
return getDataByChannelId({ channelId, embed, filterShorts });
|
|
52
20
|
};
|
|
53
21
|
|
|
54
|
-
const getSubtitlesByVideoId = (videoId: string) =>
|
|
55
|
-
cache.tryGet(`youtube:getSubtitlesByVideoId:${videoId}`, async () => {
|
|
56
|
-
const subtitles = await getSubtitles({ videoID: videoId });
|
|
57
|
-
const srt = convertToSrt(subtitles);
|
|
58
|
-
return srt;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
22
|
export const getDataByChannelId = async ({ channelId, embed }: { channelId: string; embed: boolean; filterShorts: boolean }): Promise<Data> => {
|
|
62
23
|
const innertube = await innertubePromise;
|
|
63
24
|
const channel = await innertube.getChannel(channelId);
|
|
64
25
|
const videos = await channel.getVideos();
|
|
26
|
+
const videoSubtitles = await getSrtAttachmentBatch(videos.videos.filter((video) => 'video_id' in video).map((video) => video.video_id));
|
|
65
27
|
|
|
66
28
|
return {
|
|
67
29
|
title: `${channel.metadata.title || channelId} - YouTube`,
|
|
@@ -72,10 +34,8 @@ export const getDataByChannelId = async ({ channelId, embed }: { channelId: stri
|
|
|
72
34
|
item: await Promise.all(
|
|
73
35
|
videos.videos
|
|
74
36
|
.filter((video) => 'video_id' in video)
|
|
75
|
-
.map(
|
|
76
|
-
const
|
|
77
|
-
const dataUrl = `data:text/plain;charset=utf-8,${encodeURIComponent(srt)}`;
|
|
78
|
-
|
|
37
|
+
.map((video) => {
|
|
38
|
+
const srtAttachments = videoSubtitles[video.video_id] || [];
|
|
79
39
|
const img = 'best_thumbnail' in video ? video.best_thumbnail?.url : 'thumbnails' in video ? video.thumbnails?.[0]?.url : undefined;
|
|
80
40
|
|
|
81
41
|
return {
|
|
@@ -91,11 +51,7 @@ export const getDataByChannelId = async ({ channelId, embed }: { channelId: stri
|
|
|
91
51
|
mime_type: 'text/html',
|
|
92
52
|
duration_in_seconds: video.duration && 'seconds' in video.duration ? video.duration.seconds : undefined,
|
|
93
53
|
},
|
|
94
|
-
|
|
95
|
-
url: dataUrl,
|
|
96
|
-
mime_type: 'text/srt',
|
|
97
|
-
title: 'Subtitles',
|
|
98
|
-
},
|
|
54
|
+
...srtAttachments,
|
|
99
55
|
],
|
|
100
56
|
};
|
|
101
57
|
})
|
|
@@ -21,7 +21,7 @@ export const route: Route = {
|
|
|
21
21
|
name: 'Channel with id',
|
|
22
22
|
maintainers: ['DIYgod', 'pseudoyu'],
|
|
23
23
|
handler,
|
|
24
|
-
description: `:::tip Parameter
|
|
24
|
+
description: `::: tip Parameter
|
|
25
25
|
| Name | Description | Default |
|
|
26
26
|
| ---------- | ----------------------------------------------------------------------------------- | ------- |
|
|
27
27
|
| embed | Whether to embed the video, fill in any value to disable embedding | embed |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Route } from '@/types';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import ofetch from '@/utils/ofetch';
|
|
4
4
|
import { load } from 'cheerio';
|
|
5
5
|
import { parseRelativeDate } from '@/utils/parse-date';
|
|
6
6
|
import { art } from '@/utils/render';
|
|
@@ -12,7 +12,7 @@ export const route: Route = {
|
|
|
12
12
|
categories: ['social-media'],
|
|
13
13
|
example: '/youtube/community/@JFlaMusic',
|
|
14
14
|
parameters: { handle: 'YouTube handles or channel id' },
|
|
15
|
-
name: 'Community',
|
|
15
|
+
name: 'Community Posts',
|
|
16
16
|
maintainers: ['TonyRL'],
|
|
17
17
|
handler,
|
|
18
18
|
};
|
|
@@ -25,7 +25,7 @@ async function handler(ctx) {
|
|
|
25
25
|
urlPath = `channel/${handle}`;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const response = await ofetch(`https://www.youtube.com/${urlPath}/posts`);
|
|
29
29
|
const $ = load(response);
|
|
30
30
|
const ytInitialData = JSON.parse(
|
|
31
31
|
$('script')
|
|
@@ -62,7 +62,7 @@ async function handler(ctx) {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
return {
|
|
65
|
-
title: `${username} - Community - YouTube`,
|
|
65
|
+
title: `${username} - Community Posts- YouTube`,
|
|
66
66
|
link: channelMetadata.channelUrl,
|
|
67
67
|
description: channelMetadata.description,
|
|
68
68
|
item: items,
|
|
@@ -12,7 +12,7 @@ export const route: Route = {
|
|
|
12
12
|
username: 'YouTuber handle with @',
|
|
13
13
|
routeParams: 'Extra parameters, see the table below',
|
|
14
14
|
},
|
|
15
|
-
description: `:::tip Parameter
|
|
15
|
+
description: `::: tip Parameter
|
|
16
16
|
| Name | Description | Default |
|
|
17
17
|
| ---------- | ----------------------------------------------------------------------------------- | ------- |
|
|
18
18
|
| embed | Whether to embed the video, fill in any value to disable embedding | embed |
|
|
@@ -139,7 +139,7 @@ export const route: Route = {
|
|
|
139
139
|
],
|
|
140
140
|
},
|
|
141
141
|
},
|
|
142
|
-
description: `:::tip
|
|
142
|
+
description: `::: tip
|
|
143
143
|
订阅 [问题反馈](https://yuanliao.info/t/bug-report),其源网址为 \`https://yuanliao.info/t/bug-report\`,请参考该 URL 指定部分构成参数,此时路由为 [\`/yuanliao/bug-report\`](https://rsshub.app/yuanliao/bug-report)。
|
|
144
144
|
:::
|
|
145
145
|
|
|
@@ -84,7 +84,7 @@ export const getSafeLineCookieWithData = async (link): Promise<{ cookie: string;
|
|
|
84
84
|
query: {
|
|
85
85
|
once_id: onceId,
|
|
86
86
|
v: '1.0.0',
|
|
87
|
-
hints: hints.
|
|
87
|
+
hints: hints.toSorted(() => Math.random() - 0.5).join(','),
|
|
88
88
|
},
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Route } from '@/types';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { logo } from './util';
|
|
3
|
+
import ofetch from '@/utils/ofetch';
|
|
4
|
+
import { parseDate } from '@/utils/parse-date';
|
|
4
5
|
|
|
5
6
|
export const route: Route = {
|
|
6
7
|
path: '/interactive-graphics',
|
|
@@ -11,15 +12,25 @@ export const route: Route = {
|
|
|
11
12
|
handler,
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
async function handler(
|
|
15
|
+
async function handler() {
|
|
16
|
+
const baseUrl = 'https://www.zaobao.com.sg'; // SG Only
|
|
15
17
|
const sectionLink = '/interactive-graphics';
|
|
16
18
|
|
|
17
|
-
const
|
|
19
|
+
const response = await ofetch(`${baseUrl}/_plat/api/v2/page-content/interactive-graphics`);
|
|
20
|
+
|
|
21
|
+
const items = response.response.articles.map((i) => ({
|
|
22
|
+
title: i.title,
|
|
23
|
+
description: i.summary,
|
|
24
|
+
link: new URL(i.href, baseUrl).href,
|
|
25
|
+
pubDate: parseDate(i.timestamp, 'X'),
|
|
26
|
+
image: i.thumbnail,
|
|
27
|
+
}));
|
|
18
28
|
|
|
19
29
|
return {
|
|
20
|
-
title:
|
|
30
|
+
title: response.seoMetaInfo.seoTitle,
|
|
21
31
|
link: baseUrl + sectionLink,
|
|
22
|
-
description:
|
|
23
|
-
|
|
32
|
+
description: response.seoMetaInfo.seoDescription,
|
|
33
|
+
image: logo,
|
|
34
|
+
item: items,
|
|
24
35
|
};
|
|
25
36
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Route } from '@/types';
|
|
2
|
-
import { parseList } from './util';
|
|
2
|
+
import { logo, parseList } from './util';
|
|
3
3
|
const baseUrl = 'https://www.zaobao.com';
|
|
4
4
|
|
|
5
5
|
export const route: Route = {
|
|
@@ -18,12 +18,13 @@ async function handler(ctx) {
|
|
|
18
18
|
const section = ctx.req.param('section') ?? 'china';
|
|
19
19
|
const sectionLink = `/${type}/${section}`;
|
|
20
20
|
|
|
21
|
-
const { title, resultList } = await parseList(
|
|
21
|
+
const { title, resultList } = await parseList(sectionLink);
|
|
22
22
|
|
|
23
23
|
return {
|
|
24
24
|
title: `《联合早报》${title}`,
|
|
25
25
|
link: baseUrl + sectionLink,
|
|
26
26
|
description: '新加坡、中国、亚洲和国际的即时、评论、商业、体育、生活、科技与多媒体新闻,尽在联合早报。',
|
|
27
|
+
image: logo,
|
|
27
28
|
item: resultList,
|
|
28
29
|
};
|
|
29
30
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Route } from '@/types';
|
|
2
|
-
import { parseList } from './util';
|
|
2
|
+
import { logo, parseList } from './util';
|
|
3
3
|
const baseUrl = 'https://www.zaobao.com';
|
|
4
4
|
|
|
5
5
|
export const route: Route = {
|
|
@@ -48,12 +48,13 @@ async function handler(ctx) {
|
|
|
48
48
|
break;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const { resultList } = await parseList(
|
|
51
|
+
const { resultList } = await parseList(sectionLink);
|
|
52
52
|
|
|
53
53
|
return {
|
|
54
54
|
title: `《联合早报》-${name}-即时`,
|
|
55
55
|
link: baseUrl + sectionLink,
|
|
56
56
|
description: '新加坡、中国、亚洲和国际的即时、评论、商业、体育、生活、科技与多媒体新闻,尽在联合早报。',
|
|
57
|
+
image: logo,
|
|
57
58
|
item: resultList,
|
|
58
59
|
};
|
|
59
60
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import cache from '@/utils/cache';
|
|
2
|
+
import ofetch from '@/utils/ofetch';
|
|
3
|
+
import { load } from 'cheerio';
|
|
4
|
+
import { parseDate } from '@/utils/parse-date';
|
|
5
|
+
import { renderToString } from 'hono/jsx/dom/server';
|
|
6
|
+
import { base32 } from 'rfc4648';
|
|
7
|
+
import Zaobao from './zaobao';
|
|
8
|
+
|
|
9
|
+
const baseUrl = 'https://www.zaobao.com';
|
|
10
|
+
export const logo = 'https://www.zaobao.com.sg/favicon.ico';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 通用解析页面类似 https://www.zaobao.com/realtime/china 的网站
|
|
14
|
+
*
|
|
15
|
+
* @param {string} sectionUrl 形如 /realtime/china 的字符串
|
|
16
|
+
* @returns 新闻标题以及新闻列表
|
|
17
|
+
*/
|
|
18
|
+
export const parseList = async (
|
|
19
|
+
sectionUrl: string
|
|
20
|
+
): Promise<{
|
|
21
|
+
title: string;
|
|
22
|
+
resultList: {
|
|
23
|
+
title: string;
|
|
24
|
+
description: string;
|
|
25
|
+
pubDate: Date;
|
|
26
|
+
link: string;
|
|
27
|
+
category: string[];
|
|
28
|
+
}[];
|
|
29
|
+
}> => {
|
|
30
|
+
const pageResponse = await ofetch.raw(baseUrl + sectionUrl);
|
|
31
|
+
const $ = load(pageResponse._data);
|
|
32
|
+
let data = $('.card-listing .card .content-header a');
|
|
33
|
+
if (data.length === 0) {
|
|
34
|
+
// for HK version
|
|
35
|
+
data = $('[data-testid="article-list"] article div div a.article-link');
|
|
36
|
+
}
|
|
37
|
+
const origin = new URL(pageResponse.url).origin;
|
|
38
|
+
|
|
39
|
+
const title = $('meta[property="og:title"]').attr('content');
|
|
40
|
+
|
|
41
|
+
const resultList = await Promise.all(
|
|
42
|
+
data.toArray().map((item) => {
|
|
43
|
+
const $item = $(item);
|
|
44
|
+
const link = baseUrl + $item.attr('href');
|
|
45
|
+
|
|
46
|
+
return cache.tryGet(link, async () => {
|
|
47
|
+
const response = await ofetch.raw(origin + $item.attr('href'));
|
|
48
|
+
let $1 = load(response._data);
|
|
49
|
+
|
|
50
|
+
let title, pubDate, category, images;
|
|
51
|
+
const jsonText = $1('head script[type="application/ld+json"]')
|
|
52
|
+
.text()
|
|
53
|
+
.replaceAll(/[\u0000-\u001F\u007F-\u009F]/g, '');
|
|
54
|
+
const ldJson = JSON.parse(jsonText);
|
|
55
|
+
|
|
56
|
+
const isSingapore = response.url.startsWith('https://www.zaobao.com.sg/');
|
|
57
|
+
if (isSingapore) {
|
|
58
|
+
const hydrationData = JSON.parse(
|
|
59
|
+
JSON.parse(
|
|
60
|
+
`"${
|
|
61
|
+
$1('script:contains("__staticRouterHydrationData")')
|
|
62
|
+
.text()
|
|
63
|
+
.match(/__staticRouterHydrationData = JSON.parse\("(.*)"\);/)?.[1]
|
|
64
|
+
}"`
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
const article = hydrationData.loaderData['0-0'].context.payload.article;
|
|
68
|
+
title = article.headline;
|
|
69
|
+
pubDate = parseDate(article.create_time, 'X');
|
|
70
|
+
category = article.tags.map((t) => t.name);
|
|
71
|
+
$1 = load(article.body_cn, null, false);
|
|
72
|
+
images = article.images;
|
|
73
|
+
} else {
|
|
74
|
+
title = ldJson.headline;
|
|
75
|
+
pubDate = parseDate(ldJson.datePublished);
|
|
76
|
+
category = ldJson.keywords.split(',');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// $1('.overlay-microtransaction').remove();
|
|
80
|
+
// $1('#video-freemium-player').remove();
|
|
81
|
+
$1('.bff-google-ad, .bff-recommend-article').remove(); // SG
|
|
82
|
+
$1('button.cursor-pointer').remove(); // SG
|
|
83
|
+
$1('.bff-inline-image-expand-icon').remove(); // SG
|
|
84
|
+
$1('img[alt="expend icon"]').remove(); // HK
|
|
85
|
+
|
|
86
|
+
let articleBodyNode;
|
|
87
|
+
if (isSingapore) {
|
|
88
|
+
articleBodyNode = $1;
|
|
89
|
+
} else {
|
|
90
|
+
// for HK version
|
|
91
|
+
$1('astro-island, .further-reading, .read-on-app-cover').remove();
|
|
92
|
+
articleBodyNode = $1('.article-body');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const articleBody = articleBodyNode.html();
|
|
96
|
+
const imageDataArray = processImageData(isSingapore, images, $1);
|
|
97
|
+
|
|
98
|
+
// use JSX as template
|
|
99
|
+
const dom = <Zaobao imageDataArray={imageDataArray} articleBody={articleBody}></Zaobao>;
|
|
100
|
+
const description = renderToString(dom);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
title,
|
|
104
|
+
description,
|
|
105
|
+
pubDate,
|
|
106
|
+
link,
|
|
107
|
+
category,
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
return {
|
|
113
|
+
title,
|
|
114
|
+
resultList,
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const orderContent = (parent) => {
|
|
119
|
+
for (const [i, e] of parent
|
|
120
|
+
.children()
|
|
121
|
+
.toArray()
|
|
122
|
+
.toSorted((a, b) => {
|
|
123
|
+
const index = Buffer.from(base32.parse('GM======')).toString(); // substring(3)
|
|
124
|
+
a = Buffer.from(
|
|
125
|
+
base32.parse(
|
|
126
|
+
parent
|
|
127
|
+
.find((element) => a(element))
|
|
128
|
+
.data('s')
|
|
129
|
+
.slice(index)
|
|
130
|
+
)
|
|
131
|
+
).toString();
|
|
132
|
+
b = Buffer.from(
|
|
133
|
+
base32.parse(
|
|
134
|
+
parent
|
|
135
|
+
.find((element) => b(element))
|
|
136
|
+
.data('s')
|
|
137
|
+
.slice(index)
|
|
138
|
+
)
|
|
139
|
+
).toString();
|
|
140
|
+
return a - b;
|
|
141
|
+
})
|
|
142
|
+
.entries()) {
|
|
143
|
+
parent.find((element) => e(element)).attr('s', i);
|
|
144
|
+
parent.append(e);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export interface ImageData {
|
|
149
|
+
type: string;
|
|
150
|
+
html: string;
|
|
151
|
+
src?: string;
|
|
152
|
+
title?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const processImageData = (isSg, images, $1) => {
|
|
156
|
+
if (isSg && images) {
|
|
157
|
+
return images.map((img) => ({
|
|
158
|
+
type: 'data',
|
|
159
|
+
html: '',
|
|
160
|
+
src: img.url
|
|
161
|
+
.replaceAll(/\/\/.*\.com\/s3fs-public/g, '//static.zaobao.com/s3fs-public')
|
|
162
|
+
.replaceAll('s3/files', 's3fs-public')
|
|
163
|
+
.split('?')[0],
|
|
164
|
+
title: img.caption,
|
|
165
|
+
})) as ImageData[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const hkImg = $1('[data-testid="article-banner"] img');
|
|
169
|
+
if (hkImg.length) {
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
type: 'data',
|
|
173
|
+
html: hkImg.prop('outerHTML'),
|
|
174
|
+
src: hkImg
|
|
175
|
+
.attr('src')
|
|
176
|
+
.replaceAll(/\/\/.*\.com\/s3fs-public/g, '//static.zaobao.com/s3fs-public')
|
|
177
|
+
.replaceAll('s3/files', 's3fs-public')
|
|
178
|
+
.split('?')[0],
|
|
179
|
+
title: hkImg.attr('title'),
|
|
180
|
+
},
|
|
181
|
+
] as ImageData[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return [];
|
|
185
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ImageData } from './util';
|
|
2
|
+
|
|
3
|
+
function zaobao({ imageDataArray, articleBody }) {
|
|
4
|
+
return (
|
|
5
|
+
<>
|
|
6
|
+
{imageDataArray.map((imageData: ImageData) =>
|
|
7
|
+
imageData.type === 'normalHTML' ? (
|
|
8
|
+
<div dangerouslySetInnerHTML={{ __html: imageData.html }} />
|
|
9
|
+
) : (
|
|
10
|
+
imageData.type === 'data' && (
|
|
11
|
+
<figure>
|
|
12
|
+
<img src={imageData.src} />
|
|
13
|
+
<figcaption>{imageData.title}</figcaption>
|
|
14
|
+
</figure>
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
)}
|
|
18
|
+
{articleBody && <div dangerouslySetInnerHTML={{ __html: articleBody }} />}
|
|
19
|
+
</>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default zaobao;
|