rsshub 1.0.0-master.ff3a9ce → 1.0.0-master.ff3ea5a
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.
Potentially problematic release.
This version of rsshub might be problematic. Click here for more details.
- package/lib/config.ts +38 -0
 - package/lib/errors/index.tsx +3 -2
 - package/lib/middleware/debug.ts +6 -4
 - package/lib/middleware/header.ts +4 -2
 - package/lib/registry.test.ts +24 -1
 - package/lib/registry.ts +40 -11
 - package/lib/router.js +1 -1
 - package/lib/routes/121/namespace.ts +9 -0
 - package/lib/routes/121/templates/description.art +17 -0
 - package/lib/routes/121/weather-live.ts +104 -0
 - package/lib/routes/141jav/index.ts +3 -0
 - package/lib/routes/141ppv/index.ts +3 -0
 - package/lib/routes/163/news/rank.ts +1 -1
 - package/lib/routes/18comic/album.ts +59 -64
 - package/lib/routes/18comic/index.ts +1 -0
 - package/lib/routes/18comic/search.ts +53 -4
 - package/lib/routes/18comic/utils.ts +70 -1
 - package/lib/routes/2048/index.ts +1 -0
 - package/lib/routes/30secondsofcode/utils.ts +0 -1
 - package/lib/routes/6park/index.ts +1 -1
 - package/lib/routes/7mmtv/index.ts +1 -0
 - package/lib/routes/91porn/index.ts +1 -0
 - package/lib/routes/95mm/tab.ts +1 -0
 - package/lib/routes/agefans/detail.ts +1 -1
 - package/lib/routes/aliyun/database-month.ts +1 -1
 - package/lib/routes/anthropic/research.ts +112 -0
 - package/lib/routes/apnews/topics.ts +2 -1
 - package/lib/routes/apnews/utils.ts +1 -1
 - package/lib/routes/apple/design.ts +54 -0
 - package/lib/routes/apple/namespace.ts +1 -1
 - package/lib/routes/aqara/post.ts +1 -1
 - package/lib/routes/augmentcode/blog.ts +161 -0
 - package/lib/routes/augmentcode/namespace.ts +9 -0
 - package/lib/routes/augmentcode/templates/description.art +17 -0
 - package/lib/routes/azul/namespace.ts +9 -0
 - package/lib/routes/azul/packages.ts +105 -0
 - package/lib/routes/banshujiang/index.ts +763 -0
 - package/lib/routes/banshujiang/namespace.ts +9 -0
 - package/lib/routes/banshujiang/templates/description.art +17 -0
 - package/lib/routes/bfl/announcements.ts +1 -1
 - package/lib/routes/bilibili/cache.ts +78 -0
 - package/lib/routes/bilibili/danmaku.ts +1 -1
 - package/lib/routes/bilibili/dynamic.ts +8 -2
 - package/lib/routes/bilibili/video.ts +31 -73
 - package/lib/routes/biquge/index.ts +1 -1
 - package/lib/routes/bsky/keyword.ts +15 -10
 - package/lib/routes/capitalmind/insights.ts +40 -0
 - package/lib/routes/capitalmind/namespace.ts +8 -0
 - package/lib/routes/capitalmind/podcasts.ts +41 -0
 - package/lib/routes/capitalmind/utils.ts +122 -0
 - package/lib/routes/cartoonmad/comic.ts +1 -1
 - package/lib/routes/chikubi/index.ts +1 -0
 - package/lib/routes/chikubi/tag.ts +1 -0
 - package/lib/routes/chocolatey/namespace.ts +9 -0
 - package/lib/routes/chocolatey/packages.ts +122 -0
 - package/lib/routes/cmde/index.ts +1 -1
 - package/lib/routes/cockroachlabs/blog.ts +113 -0
 - package/lib/routes/cockroachlabs/namespace.ts +7 -0
 - package/lib/routes/cool18/index.ts +4 -1
 - package/lib/routes/coomer/index.ts +12 -6
 - package/lib/routes/coomer/namespace.ts +1 -1
 - package/lib/routes/copymanga/comic.ts +7 -9
 - package/lib/routes/css-tricks/articles.ts +48 -0
 - package/lib/routes/css-tricks/collections.ts +62 -0
 - package/lib/routes/css-tricks/namespace.ts +8 -0
 - package/lib/routes/css-tricks/popular.ts +39 -0
 - package/lib/routes/css-tricks/utils.ts +103 -0
 - package/lib/routes/cursor/changelog.ts +26 -17
 - package/lib/routes/cursor/namespace.ts +1 -1
 - package/lib/routes/cw/utils.ts +6 -13
 - package/lib/routes/dealstreetasia/home.ts +1 -1
 - package/lib/routes/dedao/index.ts +3 -3
 - package/lib/routes/deepl/blog.ts +422 -0
 - package/lib/routes/deepl/namespace.ts +9 -0
 - package/lib/routes/deepl/templates/description.art +21 -0
 - package/lib/routes/deeplearning/the-batch.ts +1 -1
 - package/lib/routes/dev.to/guides.ts +100 -0
 - package/lib/routes/dev.to/namespace.ts +8 -0
 - package/lib/routes/dev.to/top.ts +105 -0
 - package/lib/routes/dgut/jwb.ts +73 -0
 - package/lib/routes/dgut/namespace.ts +9 -0
 - package/lib/routes/digitalpolicyalert/activity-tracker.ts +135 -0
 - package/lib/routes/digitalpolicyalert/namespace.ts +9 -0
 - package/lib/routes/dnaindia/common.ts +26 -8
 - package/lib/routes/douban/other/list.ts +3 -3
 - package/lib/routes/e-hentai/index.ts +4 -1
 - package/lib/routes/ehentai/tag.ts +1 -0
 - package/lib/routes/englishhome/index.ts +55 -0
 - package/lib/routes/englishhome/namespace.ts +6 -0
 - package/lib/routes/epicgames/index.ts +2 -2
 - package/lib/routes/espn/news.ts +1 -1
 - package/lib/routes/expats/czech-news.ts +272 -0
 - package/lib/routes/expats/namespace.ts +9 -0
 - package/lib/routes/fanbox/index.ts +1 -0
 - package/lib/routes/fanbox/utils.ts +1 -1
 - package/lib/routes/fansly/tag.ts +1 -0
 - package/lib/routes/fantia/search.ts +1 -1
 - package/lib/routes/fantube/creator.ts +69 -0
 - package/lib/routes/fantube/namespace.ts +7 -0
 - package/lib/routes/fantube/templates/post.art +17 -0
 - package/lib/routes/fantube/types.ts +100 -0
 - package/lib/routes/fantube/utils.ts +268 -0
 - package/lib/routes/follow/profile.ts +1 -1
 - package/lib/routes/followin/news.ts +1 -1
 - package/lib/routes/foresightnews/util.ts +1 -1
 - package/lib/routes/freexcomic/book.ts +4 -1
 - package/lib/routes/gamer/hot.ts +5 -5
 - package/lib/routes/gaoyu/blog.ts +145 -0
 - package/lib/routes/gaoyu/namespace.ts +9 -0
 - package/lib/routes/gaoyu/templates/description.art +7 -0
 - package/lib/routes/geocaching/blogs.ts +2 -2
 - package/lib/routes/github/namespace.ts +1 -1
 - package/lib/routes/github/private-feed.ts +229 -0
 - package/lib/routes/github/star.ts +1 -1
 - package/lib/routes/google/extension.ts +9 -10
 - package/lib/routes/gov/beijing/bphc/index.ts +1 -1
 - package/lib/routes/gov/ccdi/index.ts +1 -1
 - package/lib/routes/gov/chongqing/sydwgkzp.ts +22 -6
 - package/lib/routes/gov/customs/list.ts +1 -1
 - package/lib/routes/gov/general/general.ts +1 -1
 - package/lib/routes/gov/hangzhou/zwfw.ts +1 -1
 - package/lib/routes/gov/pbc/goutongjiaoliu.ts +1 -1
 - package/lib/routes/gov/shenzhen/szlh/index.ts +77 -0
 - package/lib/routes/gov/shenzhen/szlh/namespace.ts +8 -0
 - package/lib/routes/grist/utils.ts +5 -4
 - package/lib/routes/hanime1/search.ts +4 -2
 - package/lib/routes/hellogithub/report.ts +1 -1
 - package/lib/routes/hit/hitgs.ts +240 -54
 - package/lib/routes/hit/templates/description.art +7 -0
 - package/lib/routes/hlju/namespace.ts +8 -0
 - package/lib/routes/hlju/news.ts +141 -0
 - package/lib/routes/hostmonit/cloudflareyes.ts +1 -1
 - package/lib/routes/hotukdeals/index.ts +1 -1
 - package/lib/routes/hyperdash/namespace.ts +7 -0
 - package/lib/routes/hyperdash/templates/description.art +34 -0
 - package/lib/routes/hyperdash/top-traders.ts +84 -0
 - package/lib/routes/hyperdash/utils.ts +49 -0
 - package/lib/routes/hypergryph/arknights/announce.ts +1 -1
 - package/lib/routes/ielts/index.ts +1 -1
 - package/lib/routes/infoq/topic.ts +16 -13
 - package/lib/routes/instagram/web-api/index.ts +17 -13
 - package/lib/routes/instagram/web-api/utils.ts +46 -63
 - package/lib/routes/jamesclear/book-summaries.ts +40 -0
 - package/lib/routes/jamesclear/great-speeches.ts +40 -0
 - package/lib/routes/jamesclear/namespace.ts +8 -0
 - package/lib/routes/jamesclear/quotes.ts +40 -0
 - package/lib/routes/jamesclear/three-two-one.ts +41 -0
 - package/lib/routes/jamesclear/utils.ts +22 -0
 - package/lib/routes/japanpost/utils.ts +2 -2
 - package/lib/routes/javbus/index.ts +3 -0
 - package/lib/routes/javdb/tags.ts +1 -0
 - package/lib/routes/javlibrary/star.ts +1 -0
 - package/lib/routes/javtiful/actress.ts +3 -0
 - package/lib/routes/javtiful/channel.ts +3 -0
 - package/lib/routes/javtrailers/casts.ts +3 -0
 - package/lib/routes/jetbrains/comments.ts +99 -0
 - package/lib/routes/jetbrains/namespace.ts +8 -0
 - package/lib/routes/jimmyspa/books.ts +1 -1
 - package/lib/routes/jimmyspa/news.ts +1 -1
 - package/lib/routes/jpxgmn/tab.ts +3 -0
 - package/lib/routes/kakuyomu/works.ts +3 -0
 - package/lib/routes/kaopu/news.ts +1 -1
 - package/lib/routes/kemono/const.ts +1 -1
 - package/lib/routes/kemono/index.ts +12 -11
 - package/lib/routes/kemono/namespace.ts +1 -1
 - package/lib/routes/kiro/blog.ts +131 -0
 - package/lib/routes/kiro/changelog.ts +128 -0
 - package/lib/routes/kiro/namespace.ts +9 -0
 - package/lib/routes/konachan/post.ts +3 -0
 - package/lib/routes/kovidgoyal/kitty/changelog.ts +83 -0
 - package/lib/routes/kovidgoyal/namespace.ts +7 -0
 - package/lib/routes/kunchengblog/essay.ts +1 -1
 - package/lib/routes/landiannews/category.ts +1 -1
 - package/lib/routes/landiannews/index.ts +1 -1
 - package/lib/routes/landiannews/tag.ts +1 -1
 - package/lib/routes/line/utils.ts +1 -1
 - package/lib/routes/lineageos/changes.ts +79 -0
 - package/lib/routes/lineageos/namespace.ts +9 -0
 - package/lib/routes/linkedin/cn/renderer.ts +1 -1
 - package/lib/routes/liquipedia/cs-matches.ts +42 -14
 - package/lib/routes/literotica/new.ts +1 -0
 - package/lib/routes/lofter/tag.ts +27 -3
 - package/lib/routes/makerworld/contest.ts +51 -0
 - package/lib/routes/makerworld/namespace.ts +7 -0
 - package/lib/routes/makerworld/trending.ts +45 -0
 - package/lib/routes/makerworld/user-upload.ts +54 -0
 - package/lib/routes/makerworld/utils.ts +18 -0
 - package/lib/routes/manhuagui/comic.ts +1 -1
 - package/lib/routes/manus/blog.ts +77 -0
 - package/lib/routes/manus/namespace.ts +7 -0
 - package/lib/routes/mastodon/tag.ts +48 -0
 - package/lib/routes/mathpix/blog.ts +155 -0
 - package/lib/routes/mathpix/namespace.ts +9 -0
 - package/lib/routes/mathpix/templates/description.art +21 -0
 - package/lib/routes/mckinsey/cn/category-map.ts +9 -15
 - package/lib/routes/mckinsey/cn/index.ts +77 -40
 - package/lib/routes/melonbooks/search.ts +1 -0
 - package/lib/routes/metacritic/index.ts +2 -2
 - package/lib/routes/meteoblue/namespace.ts +8 -0
 - package/lib/routes/meteoblue/weathernews.ts +75 -0
 - package/lib/routes/meteor/index.ts +1 -1
 - package/lib/routes/microsoft/addon.ts +7 -9
 - package/lib/routes/mihoyo/zzz/news.ts +106 -0
 - package/lib/routes/missav/new.ts +2 -1
 - package/lib/routes/mit/hanlab.ts +61 -0
 - package/lib/routes/mit/namespace.ts +6 -0
 - package/lib/routes/mittrchina/index.ts +3 -3
 - package/lib/routes/mixcloud/config.ts +129 -0
 - package/lib/routes/mixcloud/index.ts +163 -107
 - package/lib/routes/mixcloud/user-playlist.ts +31 -0
 - package/lib/routes/mixi2/community.ts +72 -0
 - package/lib/routes/mixi2/discovery.ts +52 -0
 - package/lib/routes/mixi2/home.ts +51 -0
 - package/lib/routes/mixi2/namespace.ts +8 -0
 - package/lib/routes/mixi2/user.ts +77 -0
 - package/lib/routes/mixi2/utils.ts +56 -0
 - package/lib/routes/modelscope/learn.ts +94 -0
 - package/lib/routes/moodysmismicrosite/report.ts +1 -1
 - package/lib/routes/musify/index.ts +130 -0
 - package/lib/routes/musify/namespace.ts +9 -0
 - package/lib/routes/musikguru/namespace.ts +9 -0
 - package/lib/routes/musikguru/news.ts +146 -0
 - package/lib/routes/musikguru/templates/description.art +21 -0
 - package/lib/routes/myfans/post.ts +1 -0
 - package/lib/routes/nankai/cc-notice.ts +114 -0
 - package/lib/routes/nankai/jwc.ts +131 -0
 - package/lib/routes/nankai/namespace.ts +7 -0
 - package/lib/routes/nankai/notice.ts +84 -0
 - package/lib/routes/nankai/yzb.ts +97 -0
 - package/lib/routes/neu/yz.ts +172 -0
 - package/lib/routes/newslaundry/explainer.ts +30 -0
 - package/lib/routes/newslaundry/namespace.ts +8 -0
 - package/lib/routes/newslaundry/nl-cheatsheet.ts +30 -0
 - package/lib/routes/newslaundry/nl-collaborations.ts +30 -0
 - package/lib/routes/newslaundry/podcast.ts +61 -0
 - package/lib/routes/newslaundry/reports.ts +30 -0
 - package/lib/routes/newslaundry/shot.ts +30 -0
 - package/lib/routes/newslaundry/subscriber-only.ts +30 -0
 - package/lib/routes/newslaundry/templates/description.art +28 -0
 - package/lib/routes/newslaundry/utils.ts +108 -0
 - package/lib/routes/newswav/latest.ts +100 -0
 - package/lib/routes/newswav/namespace.ts +7 -0
 - package/lib/routes/nhentai/index.ts +1 -0
 - package/lib/routes/nifd/research.ts +16 -0
 - package/lib/routes/nio/namespace.ts +9 -0
 - package/lib/routes/nio/nioradio.ts +75 -0
 - package/lib/routes/njust/cs.ts +58 -0
 - package/lib/routes/njust/utils.ts +1 -1
 - package/lib/routes/ntdm/video.ts +1 -1
 - package/lib/routes/nuaa/utils/pypasswaf.ts +1 -1
 - package/lib/routes/oilchem/index.ts +1 -1
 - package/lib/routes/oncc/money18.ts +1 -1
 - package/lib/routes/openai/cookbook.ts +1 -1
 - package/lib/routes/paulgraham/article.ts +2 -2
 - package/lib/routes/picuki/profile.ts +4 -0
 - package/lib/routes/pixiv/bookmarks.ts +1 -1
 - package/lib/routes/pixiv/novel-api/series/sfw.ts +1 -1
 - package/lib/routes/pixiv/novels.ts +2 -2
 - package/lib/routes/pixiv/user.ts +1 -1
 - package/lib/routes/playno1/av.ts +1 -0
 - package/lib/routes/pornhub/model.ts +3 -7
 - package/lib/routes/pornhub/pornstar.ts +2 -7
 - package/lib/routes/pornhub/users.ts +2 -7
 - package/lib/routes/pornhub/utils.ts +12 -1
 - package/lib/routes/qingting/podcast.ts +2 -1
 - package/lib/routes/qipamaijia/index.ts +1 -0
 - package/lib/routes/questmobile/report.ts +1 -1
 - package/lib/routes/railway/index.ts +73 -0
 - package/lib/routes/railway/namespace.ts +8 -0
 - package/lib/routes/reuters/common.ts +0 -3
 - package/lib/routes/rockthejvm/articles.ts +167 -0
 - package/lib/routes/rockthejvm/namespace.ts +9 -0
 - package/lib/routes/rockthejvm/templates/description.art +7 -0
 - package/lib/routes/samrdprc/namespace.ts +7 -0
 - package/lib/routes/samrdprc/news.ts +79 -0
 - package/lib/routes/sankei/namespace.ts +7 -0
 - package/lib/routes/sankei/news.ts +68 -0
 - package/lib/routes/sankei/topics.ts +71 -0
 - package/lib/routes/sciencenet/user.ts +1 -1
 - package/lib/routes/scoop/apps.ts +188 -0
 - package/lib/routes/scoop/namespace.ts +9 -0
 - package/lib/routes/scoop/templates/description.art +56 -0
 - package/lib/routes/scpta/namespace.ts +8 -0
 - package/lib/routes/scpta/news.ts +101 -0
 - package/lib/routes/seekingalpha/index.ts +1 -1
 - package/lib/routes/sehuatang/index.ts +29 -31
 - package/lib/routes/setn/index.ts +11 -2
 - package/lib/routes/seu/cyber/index.ts +78 -0
 - package/lib/routes/sicau/jiaowu.ts +42 -34
 - package/lib/routes/sis001/forum.ts +1 -0
 - package/lib/routes/sjtu/seiee/icisee.ts +67 -0
 - package/lib/routes/sketis/isabelle-dev/blog/index.ts +1 -1
 - package/lib/routes/smartlink/index.ts +13 -3
 - package/lib/routes/sohu/mp.ts +17 -6
 - package/lib/routes/sotwe/namespace.ts +1 -0
 - package/lib/routes/sotwe/user.ts +98 -0
 - package/lib/routes/spankbang/new-videos.ts +1 -0
 - package/lib/routes/spglobal/ratings.ts +1 -1
 - package/lib/routes/stanford/blog.ts +77 -0
 - package/lib/routes/stanford/namespace.ts +7 -0
 - package/lib/routes/syosetu/index.ts +1 -1
 - package/lib/routes/t66y/index.ts +1 -0
 - package/lib/routes/taobao/mysql.ts +157 -0
 - package/lib/routes/taobao/namespace.ts +2 -2
 - package/lib/routes/techcrunch/category.ts +48 -0
 - package/lib/routes/telegram/channel.ts +2 -2
 - package/lib/routes/tesla/cx.ts +1 -1
 - package/lib/routes/test/index.ts +1 -1
 - package/lib/routes/themoviedb/episodes.ts +1 -1
 - package/lib/routes/thewirehindi/category.ts +78 -0
 - package/lib/routes/thewirehindi/index.ts +43 -0
 - package/lib/routes/thewirehindi/namespace.ts +7 -0
 - package/lib/routes/thewirehindi/templates/description.art +9 -0
 - package/lib/routes/thewirehindi/utils.ts +33 -0
 - package/lib/routes/threads/utils.ts +2 -2
 - package/lib/routes/tidb/blog.ts +314 -0
 - package/lib/routes/tidb/namespace.ts +9 -0
 - package/lib/routes/tumblr/namespace.ts +17 -0
 - package/lib/routes/tumblr/posts.ts +74 -0
 - package/lib/routes/tumblr/utils.ts +110 -0
 - package/lib/routes/tver/namespace.ts +7 -0
 - package/lib/routes/tver/series.ts +98 -0
 - package/lib/routes/twitter/api/web-api/login.ts +1 -3
 - package/lib/routes/twitter/api/web-api/utils.ts +23 -1
 - package/lib/routes/twitter/utils.ts +21 -21
 - package/lib/routes/udn/breaking-news.ts +2 -2
 - package/lib/routes/uestc/auto.ts +1 -1
 - package/lib/routes/uestc/cqe.ts +1 -1
 - package/lib/routes/uestc/scse.ts +1 -1
 - package/lib/routes/uestc/sice.ts +1 -1
 - package/lib/routes/uestc/sise.ts +1 -1
 - package/lib/routes/upc/jwc.ts +32 -46
 - package/lib/routes/uptimerobot/rss.ts +1 -1
 - package/lib/routes/visionias/daily-news-summary.ts +65 -0
 - package/lib/routes/visionias/monthly-magazine.ts +68 -0
 - package/lib/routes/visionias/news-today.ts +3 -13
 - package/lib/routes/visionias/utils.ts +5 -5
 - package/lib/routes/visionias/weekly-focus.ts +2 -2
 - package/lib/routes/wdfxw/bookfree.ts +528 -0
 - package/lib/routes/wdfxw/namespace.ts +8 -0
 - package/lib/routes/wdfxw/templates/description.art +13 -0
 - package/lib/routes/whu/swrh.ts +1 -1
 - package/lib/routes/windsurf/blog.ts +118 -0
 - package/lib/routes/windsurf/changelog.ts +100 -0
 - package/lib/routes/windsurf/namespace.ts +9 -0
 - package/lib/routes/windsurf/templates/description.art +21 -0
 - package/lib/routes/x6d/index.ts +1 -1
 - package/lib/routes/xbookcn/blog.ts +1 -0
 - package/lib/routes/xiaohongshu/util.ts +1 -3
 - package/lib/routes/xiaoyuzhou/pickup.ts +1 -1
 - package/lib/routes/ximalaya/album.ts +32 -70
 - package/lib/routes/ximalaya/types.ts +48 -0
 - package/lib/routes/ximalaya/utils.ts +9 -7
 - package/lib/routes/xmanhua/index.ts +1 -0
 - package/lib/routes/xueqiu/cookies.ts +1 -1
 - package/lib/routes/xueqiu/user.ts +1 -1
 - package/lib/routes/xwenming/index.ts +184 -0
 - package/lib/routes/xwenming/namespace.ts +9 -0
 - package/lib/routes/yahoo/news/utils.ts +1 -1
 - package/lib/routes/yande/post.ts +3 -0
 - package/lib/routes/ymgal/game.ts +1 -0
 - package/lib/routes/youtube/api/google.ts +27 -0
 - package/lib/routes/youtube/api/youtubei.ts +70 -20
 - package/lib/routes/youtube/community.ts +3 -1
 - package/lib/routes/youtube/utils.ts +16 -14
 - package/lib/routes/zaobao/util.ts +8 -5
 - package/lib/routes/zhihu/activities.ts +3 -0
 - package/lib/routes/zhihu/execlib/x-zse-96-v3.ts +5 -5
 - package/lib/routes/zhizhuan100/namespace.ts +7 -0
 - package/lib/routes/zhizhuan100/report.ts +79 -0
 - package/lib/routes/zimuxia/portfolio.ts +1 -1
 - package/lib/routes/zju/sis/index.ts +161 -0
 - package/lib/server.ts +5 -0
 - package/lib/types.ts +58 -54
 - package/lib/utils/helpers.ts +1 -1
 - package/lib/utils/logger.ts +1 -1
 - package/lib/utils/proxy/index.ts +102 -18
 - package/lib/utils/proxy/multi-proxy.ts +139 -0
 - package/lib/utils/proxy/unify-proxy.ts +3 -1
 - package/lib/utils/puppeteer-utils.test.ts +1 -1
 - package/lib/utils/puppeteer.test.ts +14 -27
 - package/lib/utils/puppeteer.ts +51 -37
 - package/lib/utils/readable-social.test.ts +65 -0
 - package/lib/utils/readable-social.ts +15 -1
 - package/lib/utils/request-rewriter/fetch.ts +42 -4
 - package/package.json +54 -56
 - package/lib/routes/mixcloud/queries.ts +0 -2274
 - package/lib/routes-deprecated/dev.to/top.js +0 -40
 
| 
         @@ -0,0 +1,161 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import { DataItem, Route } from '@/types';
         
     | 
| 
      
 2 
     | 
    
         
            +
            import cache from '@/utils/cache';
         
     | 
| 
      
 3 
     | 
    
         
            +
            import got from '@/utils/got';
         
     | 
| 
      
 4 
     | 
    
         
            +
            import { load } from 'cheerio';
         
     | 
| 
      
 5 
     | 
    
         
            +
            import { parseDate } from '@/utils/parse-date';
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            const base = 'http://www.sis.zju.edu.cn/sischinese/';
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            /**
         
     | 
| 
      
 10 
     | 
    
         
            +
             * Map of category types to their corresponding IDs and titles
         
     | 
| 
      
 11 
     | 
    
         
            +
             * Used to generate URLs and titles for different news categories
         
     | 
| 
      
 12 
     | 
    
         
            +
             */
         
     | 
| 
      
 13 
     | 
    
         
            +
            const categoryMap = new Map([
         
     | 
| 
      
 14 
     | 
    
         
            +
                [0, { id: '12614/list.htm', title: '浙江大学外国语学院-重要公告' }],
         
     | 
| 
      
 15 
     | 
    
         
            +
                [1, { id: '12616/list.htm', title: '浙江大学外国语学院-最新通知' }],
         
     | 
| 
      
 16 
     | 
    
         
            +
                [2, { id: '12617/list.htm', title: '浙江大学外国语学院-教育教学' }],
         
     | 
| 
      
 17 
     | 
    
         
            +
                [3, { id: '12618/list.htm', title: '浙江大学外国语学院-科学研究' }],
         
     | 
| 
      
 18 
     | 
    
         
            +
                [4, { id: '12619/list.htm', title: '浙江大学外国语学院-新闻动态' }],
         
     | 
| 
      
 19 
     | 
    
         
            +
                [5, { id: '12620/list.htm', title: '浙江大学外国语学院-联系我们' }],
         
     | 
| 
      
 20 
     | 
    
         
            +
                [6, { id: '12554/list.htm', title: '浙江大学外国语学院-党政管理' }],
         
     | 
| 
      
 21 
     | 
    
         
            +
                [7, { id: '12563/list.htm', title: '浙江大学外国语学院-组织人事' }],
         
     | 
| 
      
 22 
     | 
    
         
            +
                [8, { id: '12572/list.htm', title: '浙江大学外国语学院-科学研究' }],
         
     | 
| 
      
 23 
     | 
    
         
            +
                [9, { id: '12577/list.htm', title: '浙江大学外国语学院-本科教育' }],
         
     | 
| 
      
 24 
     | 
    
         
            +
                [10, { id: '12541/list.htm', title: '浙江大学外国语学院-研究生教育' }],
         
     | 
| 
      
 25 
     | 
    
         
            +
                [11, { id: '12542/list.htm', title: '浙江大学外国语学院-学生思政' }],
         
     | 
| 
      
 26 
     | 
    
         
            +
                [12, { id: 'xyll/list.htm', title: '浙江大学外国语学院-校友联络' }],
         
     | 
| 
      
 27 
     | 
    
         
            +
                [13, { id: '12609/list.htm', title: '浙江大学外国语学院-对外交流' }],
         
     | 
| 
      
 28 
     | 
    
         
            +
            ]);
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            /**
         
     | 
| 
      
 31 
     | 
    
         
            +
             * Fetches and parses news items from a specific category page
         
     | 
| 
      
 32 
     | 
    
         
            +
             * @param categoryId - The category ID to fetch news from
         
     | 
| 
      
 33 
     | 
    
         
            +
             * @returns Promise<DataItem[]> - Array of news items with basic information
         
     | 
| 
      
 34 
     | 
    
         
            +
             */
         
     | 
| 
      
 35 
     | 
    
         
            +
            async function fetchNewsItemsByCategory(categoryId: string): Promise<DataItem[]> {
         
     | 
| 
      
 36 
     | 
    
         
            +
                const response = await got({
         
     | 
| 
      
 37 
     | 
    
         
            +
                    method: 'get',
         
     | 
| 
      
 38 
     | 
    
         
            +
                    url: `${base}${categoryId}`,
         
     | 
| 
      
 39 
     | 
    
         
            +
                });
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                const $ = load(response.data);
         
     | 
| 
      
 42 
     | 
    
         
            +
                const newsItems = $('.news_list').find('li');
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                return newsItems.toArray().map((item) => {
         
     | 
| 
      
 45 
     | 
    
         
            +
                    const element = $(item);
         
     | 
| 
      
 46 
     | 
    
         
            +
                    const href = element.find('a').attr('href');
         
     | 
| 
      
 47 
     | 
    
         
            +
                    let title = element.find('a').attr('title');
         
     | 
| 
      
 48 
     | 
    
         
            +
                    if (!title) {
         
     | 
| 
      
 49 
     | 
    
         
            +
                        // If the title is not found, try to extract it from the link text
         
     | 
| 
      
 50 
     | 
    
         
            +
                        title = element.find('a').text().trim();
         
     | 
| 
      
 51 
     | 
    
         
            +
                    }
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    return {
         
     | 
| 
      
 54 
     | 
    
         
            +
                        title,
         
     | 
| 
      
 55 
     | 
    
         
            +
                        pubDate: parseDate(element.find('.news_meta').text()),
         
     | 
| 
      
 56 
     | 
    
         
            +
                        link: href ? new URL(href, base).href : undefined,
         
     | 
| 
      
 57 
     | 
    
         
            +
                    };
         
     | 
| 
      
 58 
     | 
    
         
            +
                });
         
     | 
| 
      
 59 
     | 
    
         
            +
            }
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            /**
         
     | 
| 
      
 62 
     | 
    
         
            +
             * Enriches a news item with detailed content by fetching its full page
         
     | 
| 
      
 63 
     | 
    
         
            +
             * @param item - The basic news item to enrich
         
     | 
| 
      
 64 
     | 
    
         
            +
             * @param refererUrl - The referer URL to use when fetching the item details
         
     | 
| 
      
 65 
     | 
    
         
            +
             * @returns Promise<DataItem> - The enriched news item with description, author, and full title
         
     | 
| 
      
 66 
     | 
    
         
            +
             */
         
     | 
| 
      
 67 
     | 
    
         
            +
            async function enrichNewsItemWithDetails(item: DataItem, refererUrl: string): Promise<DataItem> {
         
     | 
| 
      
 68 
     | 
    
         
            +
                // If the item doesn't have a link, return the item as-is
         
     | 
| 
      
 69 
     | 
    
         
            +
                if (!item.link) {
         
     | 
| 
      
 70 
     | 
    
         
            +
                    return item;
         
     | 
| 
      
 71 
     | 
    
         
            +
                }
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                return await cache.tryGet(item.link, async () => {
         
     | 
| 
      
 74 
     | 
    
         
            +
                    try {
         
     | 
| 
      
 75 
     | 
    
         
            +
                        // Some news items may link to pages that require ZJU private network access
         
     | 
| 
      
 76 
     | 
    
         
            +
                        // or university account authentication. We'll handle these gracefully.
         
     | 
| 
      
 77 
     | 
    
         
            +
                        const response = await got({
         
     | 
| 
      
 78 
     | 
    
         
            +
                            method: 'get',
         
     | 
| 
      
 79 
     | 
    
         
            +
                            url: item.link,
         
     | 
| 
      
 80 
     | 
    
         
            +
                            headers: {
         
     | 
| 
      
 81 
     | 
    
         
            +
                                Referer: refererUrl,
         
     | 
| 
      
 82 
     | 
    
         
            +
                            },
         
     | 
| 
      
 83 
     | 
    
         
            +
                        });
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                        const $ = load(response.data);
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                        // Extract and set the full article content as description
         
     | 
| 
      
 88 
     | 
    
         
            +
                        const description = $('.wp_articlecontent').html();
         
     | 
| 
      
 89 
     | 
    
         
            +
                        if (description) {
         
     | 
| 
      
 90 
     | 
    
         
            +
                            item.description = description;
         
     | 
| 
      
 91 
     | 
    
         
            +
                        }
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                        // Extract and clean the author information
         
     | 
| 
      
 94 
     | 
    
         
            +
                        let author = $('.arti_metas').find('.arti_publisher').text();
         
     | 
| 
      
 95 
     | 
    
         
            +
                        // Remove the '发布者:' (Publisher:) prefix and trim whitespace
         
     | 
| 
      
 96 
     | 
    
         
            +
                        author = author.replace('发布者:', '').trim();
         
     | 
| 
      
 97 
     | 
    
         
            +
                        if (author) {
         
     | 
| 
      
 98 
     | 
    
         
            +
                            item.author = author;
         
     | 
| 
      
 99 
     | 
    
         
            +
                        }
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                        return item;
         
     | 
| 
      
 102 
     | 
    
         
            +
                    } catch {
         
     | 
| 
      
 103 
     | 
    
         
            +
                        // If there's an error accessing the page (network issues, authentication required, etc.),
         
     | 
| 
      
 104 
     | 
    
         
            +
                        // return the item with only the basic information we already have
         
     | 
| 
      
 105 
     | 
    
         
            +
                        return item;
         
     | 
| 
      
 106 
     | 
    
         
            +
                    }
         
     | 
| 
      
 107 
     | 
    
         
            +
                });
         
     | 
| 
      
 108 
     | 
    
         
            +
            }
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
            export const route: Route = {
         
     | 
| 
      
 111 
     | 
    
         
            +
                path: '/sis/:type',
         
     | 
| 
      
 112 
     | 
    
         
            +
                categories: ['university'],
         
     | 
| 
      
 113 
     | 
    
         
            +
                example: '/zju/sis/0',
         
     | 
| 
      
 114 
     | 
    
         
            +
                parameters: { type: '分类,见下表' },
         
     | 
| 
      
 115 
     | 
    
         
            +
                features: {
         
     | 
| 
      
 116 
     | 
    
         
            +
                    requireConfig: false,
         
     | 
| 
      
 117 
     | 
    
         
            +
                    requirePuppeteer: false,
         
     | 
| 
      
 118 
     | 
    
         
            +
                    antiCrawler: false,
         
     | 
| 
      
 119 
     | 
    
         
            +
                    supportBT: false,
         
     | 
| 
      
 120 
     | 
    
         
            +
                    supportPodcast: false,
         
     | 
| 
      
 121 
     | 
    
         
            +
                    supportScihub: false,
         
     | 
| 
      
 122 
     | 
    
         
            +
                },
         
     | 
| 
      
 123 
     | 
    
         
            +
                name: '外国语学院',
         
     | 
| 
      
 124 
     | 
    
         
            +
                description: `| 重要公告 | 最新通知 | 教育教学 | 科学研究 | 新闻动态 | 联系我们 | 党政管理 | 组织人事 | 科学研究 | 本科教育 | 研究生教育 | 学生思政 | 校友联络 | 对外交流 |
         
     | 
| 
      
 125 
     | 
    
         
            +
            | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
         
     | 
| 
      
 126 
     | 
    
         
            +
            | 0        | 1        | 2        | 3        | 4        | 5        | 6        | 7            | 8            | 9       | 10       | 11       | 12       | 13       |
         
     | 
| 
      
 127 
     | 
    
         
            +
            `,
         
     | 
| 
      
 128 
     | 
    
         
            +
                maintainers: ['Alex222222222222'],
         
     | 
| 
      
 129 
     | 
    
         
            +
                handler: handleSisRequest,
         
     | 
| 
      
 130 
     | 
    
         
            +
                url: 'www.sis.zju.edu.cn',
         
     | 
| 
      
 131 
     | 
    
         
            +
            };
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
            /**
         
     | 
| 
      
 134 
     | 
    
         
            +
             * Main handler function for processing SIS (School of International Studies) news requests
         
     | 
| 
      
 135 
     | 
    
         
            +
             * @param ctx - The request context containing route parameters
         
     | 
| 
      
 136 
     | 
    
         
            +
             * @returns Promise with RSS feed data including title, link, and news items
         
     | 
| 
      
 137 
     | 
    
         
            +
             */
         
     | 
| 
      
 138 
     | 
    
         
            +
            async function handleSisRequest(ctx: { req: { param: (arg0: string) => string } }) {
         
     | 
| 
      
 139 
     | 
    
         
            +
                const requestedType = Number.parseInt(ctx.req.param('type'));
         
     | 
| 
      
 140 
     | 
    
         
            +
                const categoryInfo = categoryMap.get(requestedType);
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                // Validate the requested category type
         
     | 
| 
      
 143 
     | 
    
         
            +
                if (!categoryInfo) {
         
     | 
| 
      
 144 
     | 
    
         
            +
                    const validTypes = [...categoryMap.keys()].join(', ');
         
     | 
| 
      
 145 
     | 
    
         
            +
                    throw new Error(`Invalid type: ${requestedType}. Valid types are: ${validTypes}`);
         
     | 
| 
      
 146 
     | 
    
         
            +
                }
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                const categoryUrl = `${base}${categoryInfo.id}`;
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                // Fetch news items from all relevant categories
         
     | 
| 
      
 151 
     | 
    
         
            +
                const allNewsItems = await fetchNewsItemsByCategory(categoryInfo.id);
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                // Enrich each news item with detailed content
         
     | 
| 
      
 154 
     | 
    
         
            +
                const enrichedItems = await Promise.all(allNewsItems.map((item) => enrichNewsItemWithDetails(item, categoryUrl)));
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
                return {
         
     | 
| 
      
 157 
     | 
    
         
            +
                    title: categoryInfo.title,
         
     | 
| 
      
 158 
     | 
    
         
            +
                    link: categoryUrl,
         
     | 
| 
      
 159 
     | 
    
         
            +
                    item: enrichedItems,
         
     | 
| 
      
 160 
     | 
    
         
            +
                };
         
     | 
| 
      
 161 
     | 
    
         
            +
            }
         
     | 
    
        package/lib/server.ts
    ADDED
    
    
    
        package/lib/types.ts
    CHANGED
    
    | 
         @@ -2,7 +2,7 @@ import type { Context } from 'hono'; 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            // Make sure it's synchronise with scripts/workflow/data.ts
         
     | 
| 
       4 
4 
     | 
    
         
             
            // and lib/routes/rsshub/routes.ts
         
     | 
| 
       5 
     | 
    
         
            -
            type Category =
         
     | 
| 
      
 5 
     | 
    
         
            +
            export type Category =
         
     | 
| 
       6 
6 
     | 
    
         
             
                | 'popular'
         
     | 
| 
       7 
7 
     | 
    
         
             
                | 'social-media'
         
     | 
| 
       8 
8 
     | 
    
         
             
                | 'new-media'
         
     | 
| 
         @@ -98,71 +98,89 @@ export type Data = { 
     | 
|
| 
       98 
98 
     | 
    
         
             
                ttl?: number;
         
     | 
| 
       99 
99 
     | 
    
         
             
            };
         
     | 
| 
       100 
100 
     | 
    
         | 
| 
       101 
     | 
    
         
            -
            type Language =
         
     | 
| 
      
 101 
     | 
    
         
            +
            export type Language =
         
     | 
| 
       102 
102 
     | 
    
         
             
                | 'af'
         
     | 
| 
       103 
     | 
    
         
            -
                | 'sq'
         
     | 
| 
       104 
     | 
    
         
            -
                | 'eu'
         
     | 
| 
       105 
     | 
    
         
            -
                | 'be'
         
     | 
| 
       106 
     | 
    
         
            -
                | 'bg'
         
     | 
| 
       107 
     | 
    
         
            -
                | 'ca'
         
     | 
| 
       108 
     | 
    
         
            -
                | 'zh-CN'
         
     | 
| 
       109 
     | 
    
         
            -
                | 'zh-TW'
         
     | 
| 
       110 
     | 
    
         
            -
                | 'zh-HK'
         
     | 
| 
       111 
     | 
    
         
            -
                | 'hr'
         
     | 
| 
       112 
     | 
    
         
            -
                | 'cs'
         
     | 
| 
       113 
103 
     | 
    
         
             
                | 'ar-DZ'
         
     | 
| 
       114 
     | 
    
         
            -
                | 'ar-SA'
         
     | 
| 
       115 
     | 
    
         
            -
                | 'ar-MA'
         
     | 
| 
       116 
104 
     | 
    
         
             
                | 'ar-IQ'
         
     | 
| 
       117 
105 
     | 
    
         
             
                | 'ar-KW'
         
     | 
| 
      
 106 
     | 
    
         
            +
                | 'ar-MA'
         
     | 
| 
      
 107 
     | 
    
         
            +
                | 'ar-SA'
         
     | 
| 
       118 
108 
     | 
    
         
             
                | 'ar-TN'
         
     | 
| 
      
 109 
     | 
    
         
            +
                | 'be'
         
     | 
| 
      
 110 
     | 
    
         
            +
                | 'bg'
         
     | 
| 
      
 111 
     | 
    
         
            +
                | 'ca'
         
     | 
| 
      
 112 
     | 
    
         
            +
                | 'cs'
         
     | 
| 
       119 
113 
     | 
    
         
             
                | 'da'
         
     | 
| 
       120 
     | 
    
         
            -
                | ' 
     | 
| 
       121 
     | 
    
         
            -
                | ' 
     | 
| 
       122 
     | 
    
         
            -
                | ' 
     | 
| 
      
 114 
     | 
    
         
            +
                | 'de'
         
     | 
| 
      
 115 
     | 
    
         
            +
                | 'de-at'
         
     | 
| 
      
 116 
     | 
    
         
            +
                | 'de-ch'
         
     | 
| 
      
 117 
     | 
    
         
            +
                | 'de-de'
         
     | 
| 
      
 118 
     | 
    
         
            +
                | 'de-li'
         
     | 
| 
      
 119 
     | 
    
         
            +
                | 'de-lu'
         
     | 
| 
      
 120 
     | 
    
         
            +
                | 'el'
         
     | 
| 
       123 
121 
     | 
    
         
             
                | 'en'
         
     | 
| 
       124 
122 
     | 
    
         
             
                | 'en-au'
         
     | 
| 
       125 
123 
     | 
    
         
             
                | 'en-bz'
         
     | 
| 
       126 
124 
     | 
    
         
             
                | 'en-ca'
         
     | 
| 
      
 125 
     | 
    
         
            +
                | 'en-gb'
         
     | 
| 
       127 
126 
     | 
    
         
             
                | 'en-ie'
         
     | 
| 
       128 
127 
     | 
    
         
             
                | 'en-jm'
         
     | 
| 
       129 
128 
     | 
    
         
             
                | 'en-nz'
         
     | 
| 
       130 
129 
     | 
    
         
             
                | 'en-ph'
         
     | 
| 
       131 
     | 
    
         
            -
                | 'en-za'
         
     | 
| 
       132 
130 
     | 
    
         
             
                | 'en-tt'
         
     | 
| 
       133 
     | 
    
         
            -
                | 'en-gb'
         
     | 
| 
       134 
131 
     | 
    
         
             
                | 'en-us'
         
     | 
| 
      
 132 
     | 
    
         
            +
                | 'en-za'
         
     | 
| 
       135 
133 
     | 
    
         
             
                | 'en-zw'
         
     | 
| 
      
 134 
     | 
    
         
            +
                | 'es'
         
     | 
| 
      
 135 
     | 
    
         
            +
                | 'es-ar'
         
     | 
| 
      
 136 
     | 
    
         
            +
                | 'es-bo'
         
     | 
| 
      
 137 
     | 
    
         
            +
                | 'es-cl'
         
     | 
| 
      
 138 
     | 
    
         
            +
                | 'es-co'
         
     | 
| 
      
 139 
     | 
    
         
            +
                | 'es-cr'
         
     | 
| 
      
 140 
     | 
    
         
            +
                | 'es-do'
         
     | 
| 
      
 141 
     | 
    
         
            +
                | 'es-ec'
         
     | 
| 
      
 142 
     | 
    
         
            +
                | 'es-es'
         
     | 
| 
      
 143 
     | 
    
         
            +
                | 'es-gt'
         
     | 
| 
      
 144 
     | 
    
         
            +
                | 'es-hn'
         
     | 
| 
      
 145 
     | 
    
         
            +
                | 'es-mx'
         
     | 
| 
      
 146 
     | 
    
         
            +
                | 'es-ni'
         
     | 
| 
      
 147 
     | 
    
         
            +
                | 'es-pa'
         
     | 
| 
      
 148 
     | 
    
         
            +
                | 'es-pe'
         
     | 
| 
      
 149 
     | 
    
         
            +
                | 'es-pr'
         
     | 
| 
      
 150 
     | 
    
         
            +
                | 'es-py'
         
     | 
| 
      
 151 
     | 
    
         
            +
                | 'es-sv'
         
     | 
| 
      
 152 
     | 
    
         
            +
                | 'es-uy'
         
     | 
| 
      
 153 
     | 
    
         
            +
                | 'es-ve'
         
     | 
| 
       136 
154 
     | 
    
         
             
                | 'et'
         
     | 
| 
       137 
     | 
    
         
            -
                | ' 
     | 
| 
      
 155 
     | 
    
         
            +
                | 'eu'
         
     | 
| 
       138 
156 
     | 
    
         
             
                | 'fi'
         
     | 
| 
      
 157 
     | 
    
         
            +
                | 'fo'
         
     | 
| 
       139 
158 
     | 
    
         
             
                | 'fr'
         
     | 
| 
       140 
159 
     | 
    
         
             
                | 'fr-be'
         
     | 
| 
       141 
160 
     | 
    
         
             
                | 'fr-ca'
         
     | 
| 
      
 161 
     | 
    
         
            +
                | 'fr-ch'
         
     | 
| 
       142 
162 
     | 
    
         
             
                | 'fr-fr'
         
     | 
| 
       143 
163 
     | 
    
         
             
                | 'fr-lu'
         
     | 
| 
       144 
164 
     | 
    
         
             
                | 'fr-mc'
         
     | 
| 
       145 
     | 
    
         
            -
                | ' 
     | 
| 
       146 
     | 
    
         
            -
                | 'gl'
         
     | 
| 
      
 165 
     | 
    
         
            +
                | 'ga'
         
     | 
| 
       147 
166 
     | 
    
         
             
                | 'gd'
         
     | 
| 
       148 
     | 
    
         
            -
                | ' 
     | 
| 
       149 
     | 
    
         
            -
                | 'de-at'
         
     | 
| 
       150 
     | 
    
         
            -
                | 'de-de'
         
     | 
| 
       151 
     | 
    
         
            -
                | 'de-li'
         
     | 
| 
       152 
     | 
    
         
            -
                | 'de-lu'
         
     | 
| 
       153 
     | 
    
         
            -
                | 'de-ch'
         
     | 
| 
       154 
     | 
    
         
            -
                | 'el'
         
     | 
| 
      
 167 
     | 
    
         
            +
                | 'gl'
         
     | 
| 
       155 
168 
     | 
    
         
             
                | 'haw'
         
     | 
| 
      
 169 
     | 
    
         
            +
                | 'hi'
         
     | 
| 
      
 170 
     | 
    
         
            +
                | 'hr'
         
     | 
| 
       156 
171 
     | 
    
         
             
                | 'hu'
         
     | 
| 
       157 
     | 
    
         
            -
                | 'is'
         
     | 
| 
       158 
172 
     | 
    
         
             
                | 'in'
         
     | 
| 
       159 
     | 
    
         
            -
                | ' 
     | 
| 
      
 173 
     | 
    
         
            +
                | 'is'
         
     | 
| 
       160 
174 
     | 
    
         
             
                | 'it'
         
     | 
| 
       161 
     | 
    
         
            -
                | 'it-it'
         
     | 
| 
       162 
175 
     | 
    
         
             
                | 'it-ch'
         
     | 
| 
      
 176 
     | 
    
         
            +
                | 'it-it'
         
     | 
| 
       163 
177 
     | 
    
         
             
                | 'ja'
         
     | 
| 
       164 
178 
     | 
    
         
             
                | 'ko'
         
     | 
| 
       165 
179 
     | 
    
         
             
                | 'mk'
         
     | 
| 
      
 180 
     | 
    
         
            +
                | 'ne'
         
     | 
| 
      
 181 
     | 
    
         
            +
                | 'nl'
         
     | 
| 
      
 182 
     | 
    
         
            +
                | 'nl-be'
         
     | 
| 
      
 183 
     | 
    
         
            +
                | 'nl-nl'
         
     | 
| 
       166 
184 
     | 
    
         
             
                | 'no'
         
     | 
| 
       167 
185 
     | 
    
         
             
                | 'pl'
         
     | 
| 
       168 
186 
     | 
    
         
             
                | 'pt'
         
     | 
| 
         @@ -174,35 +192,18 @@ type Language = 
     | 
|
| 
       174 
192 
     | 
    
         
             
                | 'ru'
         
     | 
| 
       175 
193 
     | 
    
         
             
                | 'ru-mo'
         
     | 
| 
       176 
194 
     | 
    
         
             
                | 'ru-ru'
         
     | 
| 
       177 
     | 
    
         
            -
                | 'sr'
         
     | 
| 
       178 
195 
     | 
    
         
             
                | 'sk'
         
     | 
| 
       179 
196 
     | 
    
         
             
                | 'sl'
         
     | 
| 
       180 
     | 
    
         
            -
                | ' 
     | 
| 
       181 
     | 
    
         
            -
                | ' 
     | 
| 
       182 
     | 
    
         
            -
                | 'es-bo'
         
     | 
| 
       183 
     | 
    
         
            -
                | 'es-cl'
         
     | 
| 
       184 
     | 
    
         
            -
                | 'es-co'
         
     | 
| 
       185 
     | 
    
         
            -
                | 'es-cr'
         
     | 
| 
       186 
     | 
    
         
            -
                | 'es-do'
         
     | 
| 
       187 
     | 
    
         
            -
                | 'es-ec'
         
     | 
| 
       188 
     | 
    
         
            -
                | 'es-sv'
         
     | 
| 
       189 
     | 
    
         
            -
                | 'es-gt'
         
     | 
| 
       190 
     | 
    
         
            -
                | 'es-hn'
         
     | 
| 
       191 
     | 
    
         
            -
                | 'es-mx'
         
     | 
| 
       192 
     | 
    
         
            -
                | 'es-ni'
         
     | 
| 
       193 
     | 
    
         
            -
                | 'es-pa'
         
     | 
| 
       194 
     | 
    
         
            -
                | 'es-py'
         
     | 
| 
       195 
     | 
    
         
            -
                | 'es-pe'
         
     | 
| 
       196 
     | 
    
         
            -
                | 'es-pr'
         
     | 
| 
       197 
     | 
    
         
            -
                | 'es-es'
         
     | 
| 
       198 
     | 
    
         
            -
                | 'es-uy'
         
     | 
| 
       199 
     | 
    
         
            -
                | 'es-ve'
         
     | 
| 
      
 197 
     | 
    
         
            +
                | 'sq'
         
     | 
| 
      
 198 
     | 
    
         
            +
                | 'sr'
         
     | 
| 
       200 
199 
     | 
    
         
             
                | 'sv'
         
     | 
| 
       201 
200 
     | 
    
         
             
                | 'sv-fi'
         
     | 
| 
       202 
201 
     | 
    
         
             
                | 'sv-se'
         
     | 
| 
       203 
202 
     | 
    
         
             
                | 'tr'
         
     | 
| 
       204 
203 
     | 
    
         
             
                | 'uk'
         
     | 
| 
       205 
     | 
    
         
            -
                | ' 
     | 
| 
      
 204 
     | 
    
         
            +
                | 'zh-CN'
         
     | 
| 
      
 205 
     | 
    
         
            +
                | 'zh-HK'
         
     | 
| 
      
 206 
     | 
    
         
            +
                | 'zh-TW'
         
     | 
| 
       206 
207 
     | 
    
         
             
                | 'other';
         
     | 
| 
       207 
208 
     | 
    
         | 
| 
       208 
209 
     | 
    
         
             
            // namespace
         
     | 
| 
         @@ -346,6 +347,9 @@ interface RouteItem { 
     | 
|
| 
       346 
347 
     | 
    
         | 
| 
       347 
348 
     | 
    
         
             
                    /** Set to `true` if the feed supports Sci-Hub */
         
     | 
| 
       348 
349 
     | 
    
         
             
                    supportScihub?: boolean;
         
     | 
| 
      
 350 
     | 
    
         
            +
             
     | 
| 
      
 351 
     | 
    
         
            +
                    /** Set to `true` if this feed is not safe for work */
         
     | 
| 
      
 352 
     | 
    
         
            +
                    nsfw?: boolean;
         
     | 
| 
       349 
353 
     | 
    
         
             
                };
         
     | 
| 
       350 
354 
     | 
    
         | 
| 
       351 
355 
     | 
    
         
             
                /**
         
     | 
    
        package/lib/utils/helpers.ts
    CHANGED
    
    
    
        package/lib/utils/logger.ts
    CHANGED
    
    | 
         @@ -3,7 +3,7 @@ import winston from 'winston'; 
     | 
|
| 
       3 
3 
     | 
    
         
             
            import { config } from '@/config';
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            let transports: (typeof winston.transports.File)[] = [];
         
     | 
| 
       6 
     | 
    
         
            -
            if (!config.noLogfiles) {
         
     | 
| 
      
 6 
     | 
    
         
            +
            if (!config.noLogfiles && !process.env.VERCEL) {
         
     | 
| 
       7 
7 
     | 
    
         
             
                transports = [
         
     | 
| 
       8 
8 
     | 
    
         
             
                    new winston.transports.File({
         
     | 
| 
       9 
9 
     | 
    
         
             
                        filename: path.resolve('logs/error.log'),
         
     | 
    
        package/lib/utils/proxy/index.ts
    CHANGED
    
    | 
         @@ -3,20 +3,72 @@ import { PacProxyAgent } from 'pac-proxy-agent'; 
     | 
|
| 
       3 
3 
     | 
    
         
             
            import { HttpsProxyAgent } from 'https-proxy-agent';
         
     | 
| 
       4 
4 
     | 
    
         
             
            import { SocksProxyAgent } from 'socks-proxy-agent';
         
     | 
| 
       5 
5 
     | 
    
         
             
            import { ProxyAgent } from 'undici';
         
     | 
| 
      
 6 
     | 
    
         
            +
            import logger from '@/utils/logger';
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
8 
     | 
    
         
             
            const proxyIsPAC = config.pacUri || config.pacScript;
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
       9 
10 
     | 
    
         
             
            import pacProxy from './pac-proxy';
         
     | 
| 
       10 
11 
     | 
    
         
             
            import unifyProxy from './unify-proxy';
         
     | 
| 
      
 12 
     | 
    
         
            +
            import createMultiProxy, { type MultiProxyResult, type ProxyState } from './multi-proxy';
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            interface ProxyExport {
         
     | 
| 
      
 15 
     | 
    
         
            +
                agent: PacProxyAgent<string> | HttpsProxyAgent<string> | SocksProxyAgent | null;
         
     | 
| 
      
 16 
     | 
    
         
            +
                dispatcher: ProxyAgent | null;
         
     | 
| 
      
 17 
     | 
    
         
            +
                proxyUri?: string;
         
     | 
| 
      
 18 
     | 
    
         
            +
                proxyObj: Record<string, any>;
         
     | 
| 
      
 19 
     | 
    
         
            +
                proxyUrlHandler?: URL | null;
         
     | 
| 
      
 20 
     | 
    
         
            +
                multiProxy?: MultiProxyResult;
         
     | 
| 
      
 21 
     | 
    
         
            +
                getCurrentProxy: () => ProxyState | null;
         
     | 
| 
      
 22 
     | 
    
         
            +
                markProxyFailed: (proxyUri: string) => void;
         
     | 
| 
      
 23 
     | 
    
         
            +
                getAgentForProxy: (proxyState: ProxyState) => any;
         
     | 
| 
      
 24 
     | 
    
         
            +
                getDispatcherForProxy: (proxyState: ProxyState) => ProxyAgent | null;
         
     | 
| 
      
 25 
     | 
    
         
            +
            }
         
     | 
| 
       11 
26 
     | 
    
         | 
| 
       12 
27 
     | 
    
         
             
            let proxyUri: string | undefined;
         
     | 
| 
       13 
     | 
    
         
            -
            let proxyObj: Record<string, any>  
     | 
| 
      
 28 
     | 
    
         
            +
            let proxyObj: Record<string, any> = {};
         
     | 
| 
       14 
29 
     | 
    
         
             
            let proxyUrlHandler: URL | null = null;
         
     | 
| 
      
 30 
     | 
    
         
            +
            let multiProxy: MultiProxyResult | undefined;
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            const createAgentForProxy = (uri: string, proxyObj: Record<string, any>): any => {
         
     | 
| 
      
 33 
     | 
    
         
            +
                if (uri.startsWith('http')) {
         
     | 
| 
      
 34 
     | 
    
         
            +
                    return new HttpsProxyAgent(uri, {
         
     | 
| 
      
 35 
     | 
    
         
            +
                        headers: {
         
     | 
| 
      
 36 
     | 
    
         
            +
                            'proxy-authorization': proxyObj?.auth ? `Basic ${proxyObj.auth}` : undefined,
         
     | 
| 
      
 37 
     | 
    
         
            +
                        },
         
     | 
| 
      
 38 
     | 
    
         
            +
                    });
         
     | 
| 
      
 39 
     | 
    
         
            +
                } else if (uri.startsWith('socks')) {
         
     | 
| 
      
 40 
     | 
    
         
            +
                    return new SocksProxyAgent(uri);
         
     | 
| 
      
 41 
     | 
    
         
            +
                }
         
     | 
| 
      
 42 
     | 
    
         
            +
                return null;
         
     | 
| 
      
 43 
     | 
    
         
            +
            };
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            const createDispatcherForProxy = (uri: string, proxyObj: Record<string, any>): ProxyAgent | null => {
         
     | 
| 
      
 46 
     | 
    
         
            +
                if (uri.startsWith('http')) {
         
     | 
| 
      
 47 
     | 
    
         
            +
                    return new ProxyAgent({
         
     | 
| 
      
 48 
     | 
    
         
            +
                        uri,
         
     | 
| 
      
 49 
     | 
    
         
            +
                        token: proxyObj?.auth ? `Basic ${proxyObj.auth}` : undefined,
         
     | 
| 
      
 50 
     | 
    
         
            +
                        requestTls: {
         
     | 
| 
      
 51 
     | 
    
         
            +
                            rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0',
         
     | 
| 
      
 52 
     | 
    
         
            +
                        },
         
     | 
| 
      
 53 
     | 
    
         
            +
                    });
         
     | 
| 
      
 54 
     | 
    
         
            +
                }
         
     | 
| 
      
 55 
     | 
    
         
            +
                return null;
         
     | 
| 
      
 56 
     | 
    
         
            +
            };
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
       15 
58 
     | 
    
         
             
            if (proxyIsPAC) {
         
     | 
| 
       16 
59 
     | 
    
         
             
                const proxy = pacProxy(config.pacUri, config.pacScript, config.proxy);
         
     | 
| 
       17 
60 
     | 
    
         
             
                proxyUri = proxy.proxyUri;
         
     | 
| 
       18 
61 
     | 
    
         
             
                proxyObj = proxy.proxyObj;
         
     | 
| 
       19 
62 
     | 
    
         
             
                proxyUrlHandler = proxy.proxyUrlHandler;
         
     | 
| 
      
 63 
     | 
    
         
            +
            } else if (config.proxyUris && config.proxyUris.length > 0) {
         
     | 
| 
      
 64 
     | 
    
         
            +
                multiProxy = createMultiProxy(config.proxyUris, config.proxy);
         
     | 
| 
      
 65 
     | 
    
         
            +
                proxyObj = multiProxy.proxyObj;
         
     | 
| 
      
 66 
     | 
    
         
            +
                const currentProxy = multiProxy.getNextProxy();
         
     | 
| 
      
 67 
     | 
    
         
            +
                if (currentProxy) {
         
     | 
| 
      
 68 
     | 
    
         
            +
                    proxyUri = currentProxy.uri;
         
     | 
| 
      
 69 
     | 
    
         
            +
                    proxyUrlHandler = currentProxy.urlHandler;
         
     | 
| 
      
 70 
     | 
    
         
            +
                }
         
     | 
| 
      
 71 
     | 
    
         
            +
                logger.info(`Multi-proxy initialized with ${config.proxyUris.length} proxies`);
         
     | 
| 
       20 
72 
     | 
    
         
             
            } else {
         
     | 
| 
       21 
73 
     | 
    
         
             
                const proxy = unifyProxy(config.proxyUri, config.proxy);
         
     | 
| 
       22 
74 
     | 
    
         
             
                proxyUri = proxy.proxyUri;
         
     | 
| 
         @@ -26,31 +78,63 @@ if (proxyIsPAC) { 
     | 
|
| 
       26 
78 
     | 
    
         | 
| 
       27 
79 
     | 
    
         
             
            let agent: PacProxyAgent<string> | HttpsProxyAgent<string> | SocksProxyAgent | null = null;
         
     | 
| 
       28 
80 
     | 
    
         
             
            let dispatcher: ProxyAgent | null = null;
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
            if (proxyIsPAC && proxyUri) {
         
     | 
| 
       30 
83 
     | 
    
         
             
                agent = new PacProxyAgent(`pac+${proxyUri}`);
         
     | 
| 
       31 
84 
     | 
    
         
             
            } else if (proxyUri) {
         
     | 
| 
       32 
     | 
    
         
            -
                 
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
                     
     | 
| 
      
 85 
     | 
    
         
            +
                agent = createAgentForProxy(proxyUri, proxyObj);
         
     | 
| 
      
 86 
     | 
    
         
            +
                dispatcher = createDispatcherForProxy(proxyUri, proxyObj);
         
     | 
| 
      
 87 
     | 
    
         
            +
            }
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            const getCurrentProxy = (): ProxyState | null => {
         
     | 
| 
      
 90 
     | 
    
         
            +
                if (multiProxy) {
         
     | 
| 
      
 91 
     | 
    
         
            +
                    return multiProxy.getNextProxy();
         
     | 
| 
      
 92 
     | 
    
         
            +
                }
         
     | 
| 
      
 93 
     | 
    
         
            +
                if (proxyUri) {
         
     | 
| 
      
 94 
     | 
    
         
            +
                    return {
         
     | 
| 
       39 
95 
     | 
    
         
             
                        uri: proxyUri,
         
     | 
| 
       40 
     | 
    
         
            -
                         
     | 
| 
       41 
     | 
    
         
            -
                         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                    });
         
     | 
| 
       45 
     | 
    
         
            -
                } else if (proxyUri.startsWith('socks')) {
         
     | 
| 
       46 
     | 
    
         
            -
                    agent = new SocksProxyAgent(proxyUri);
         
     | 
| 
      
 96 
     | 
    
         
            +
                        isActive: true,
         
     | 
| 
      
 97 
     | 
    
         
            +
                        failureCount: 0,
         
     | 
| 
      
 98 
     | 
    
         
            +
                        urlHandler: proxyUrlHandler,
         
     | 
| 
      
 99 
     | 
    
         
            +
                    };
         
     | 
| 
       47 
100 
     | 
    
         
             
                }
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
      
 101 
     | 
    
         
            +
                return null;
         
     | 
| 
      
 102 
     | 
    
         
            +
            };
         
     | 
| 
       49 
103 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
      
 104 
     | 
    
         
            +
            const markProxyFailed = (failedProxyUri: string) => {
         
     | 
| 
      
 105 
     | 
    
         
            +
                if (multiProxy) {
         
     | 
| 
      
 106 
     | 
    
         
            +
                    multiProxy.markProxyFailed(failedProxyUri);
         
     | 
| 
      
 107 
     | 
    
         
            +
                    const nextProxy = multiProxy.getNextProxy();
         
     | 
| 
      
 108 
     | 
    
         
            +
                    if (nextProxy) {
         
     | 
| 
      
 109 
     | 
    
         
            +
                        proxyUri = nextProxy.uri;
         
     | 
| 
      
 110 
     | 
    
         
            +
                        proxyUrlHandler = nextProxy.urlHandler || null;
         
     | 
| 
      
 111 
     | 
    
         
            +
                        agent = createAgentForProxy(nextProxy.uri, proxyObj);
         
     | 
| 
      
 112 
     | 
    
         
            +
                        dispatcher = createDispatcherForProxy(nextProxy.uri, proxyObj);
         
     | 
| 
      
 113 
     | 
    
         
            +
                        logger.info(`Switched to proxy: ${nextProxy.uri}`);
         
     | 
| 
      
 114 
     | 
    
         
            +
                    } else {
         
     | 
| 
      
 115 
     | 
    
         
            +
                        logger.warn('No available proxies remaining');
         
     | 
| 
      
 116 
     | 
    
         
            +
                        agent = null;
         
     | 
| 
      
 117 
     | 
    
         
            +
                        dispatcher = null;
         
     | 
| 
      
 118 
     | 
    
         
            +
                        proxyUri = undefined;
         
     | 
| 
      
 119 
     | 
    
         
            +
                    }
         
     | 
| 
      
 120 
     | 
    
         
            +
                }
         
     | 
| 
      
 121 
     | 
    
         
            +
            };
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            const getAgentForProxy = (proxyState: ProxyState) => createAgentForProxy(proxyState.uri, proxyObj);
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            const getDispatcherForProxy = (proxyState: ProxyState) => createDispatcherForProxy(proxyState.uri, proxyObj);
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
            const proxyExport: ProxyExport = {
         
     | 
| 
       51 
128 
     | 
    
         
             
                agent,
         
     | 
| 
       52 
129 
     | 
    
         
             
                dispatcher,
         
     | 
| 
       53 
130 
     | 
    
         
             
                proxyUri,
         
     | 
| 
       54 
131 
     | 
    
         
             
                proxyObj,
         
     | 
| 
       55 
132 
     | 
    
         
             
                proxyUrlHandler,
         
     | 
| 
      
 133 
     | 
    
         
            +
                multiProxy,
         
     | 
| 
      
 134 
     | 
    
         
            +
                getCurrentProxy,
         
     | 
| 
      
 135 
     | 
    
         
            +
                markProxyFailed,
         
     | 
| 
      
 136 
     | 
    
         
            +
                getAgentForProxy,
         
     | 
| 
      
 137 
     | 
    
         
            +
                getDispatcherForProxy,
         
     | 
| 
       56 
138 
     | 
    
         
             
            };
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
            export default proxyExport;
         
     | 
| 
         @@ -0,0 +1,139 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import { type Config } from '@/config';
         
     | 
| 
      
 2 
     | 
    
         
            +
            import logger from '@/utils/logger';
         
     | 
| 
      
 3 
     | 
    
         
            +
            import unifyProxy from './unify-proxy';
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            export interface ProxyState {
         
     | 
| 
      
 6 
     | 
    
         
            +
                uri: string;
         
     | 
| 
      
 7 
     | 
    
         
            +
                isActive: boolean;
         
     | 
| 
      
 8 
     | 
    
         
            +
                failureCount: number;
         
     | 
| 
      
 9 
     | 
    
         
            +
                lastFailureTime?: number;
         
     | 
| 
      
 10 
     | 
    
         
            +
                agent?: any;
         
     | 
| 
      
 11 
     | 
    
         
            +
                dispatcher?: any;
         
     | 
| 
      
 12 
     | 
    
         
            +
                urlHandler?: URL | null;
         
     | 
| 
      
 13 
     | 
    
         
            +
            }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            export interface MultiProxyResult {
         
     | 
| 
      
 16 
     | 
    
         
            +
                currentProxy?: ProxyState | null;
         
     | 
| 
      
 17 
     | 
    
         
            +
                allProxies: ProxyState[];
         
     | 
| 
      
 18 
     | 
    
         
            +
                proxyObj: Config['proxy'];
         
     | 
| 
      
 19 
     | 
    
         
            +
                getNextProxy: () => ProxyState | null;
         
     | 
| 
      
 20 
     | 
    
         
            +
                markProxyFailed: (proxyUri: string) => void;
         
     | 
| 
      
 21 
     | 
    
         
            +
                resetProxy: (proxyUri: string) => void;
         
     | 
| 
      
 22 
     | 
    
         
            +
            }
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            const createMultiProxy = (proxyUris: string[], proxyObj: Config['proxy']): MultiProxyResult => {
         
     | 
| 
      
 25 
     | 
    
         
            +
                const proxies: ProxyState[] = [];
         
     | 
| 
      
 26 
     | 
    
         
            +
                let currentProxyIndex = 0;
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                for (const uri of proxyUris) {
         
     | 
| 
      
 29 
     | 
    
         
            +
                    const unifiedProxy = unifyProxy(uri, proxyObj);
         
     | 
| 
      
 30 
     | 
    
         
            +
                    if (unifiedProxy.proxyUri) {
         
     | 
| 
      
 31 
     | 
    
         
            +
                        proxies.push({
         
     | 
| 
      
 32 
     | 
    
         
            +
                            uri: unifiedProxy.proxyUri,
         
     | 
| 
      
 33 
     | 
    
         
            +
                            isActive: true,
         
     | 
| 
      
 34 
     | 
    
         
            +
                            failureCount: 0,
         
     | 
| 
      
 35 
     | 
    
         
            +
                            urlHandler: unifiedProxy.proxyUrlHandler,
         
     | 
| 
      
 36 
     | 
    
         
            +
                        });
         
     | 
| 
      
 37 
     | 
    
         
            +
                    }
         
     | 
| 
      
 38 
     | 
    
         
            +
                }
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                if (proxies.length === 0) {
         
     | 
| 
      
 41 
     | 
    
         
            +
                    logger.warn('No valid proxies found in the provided list');
         
     | 
| 
      
 42 
     | 
    
         
            +
                    return {
         
     | 
| 
      
 43 
     | 
    
         
            +
                        allProxies: [],
         
     | 
| 
      
 44 
     | 
    
         
            +
                        proxyObj: proxyObj || {},
         
     | 
| 
      
 45 
     | 
    
         
            +
                        getNextProxy: () => null,
         
     | 
| 
      
 46 
     | 
    
         
            +
                        markProxyFailed: () => {},
         
     | 
| 
      
 47 
     | 
    
         
            +
                        resetProxy: () => {},
         
     | 
| 
      
 48 
     | 
    
         
            +
                    };
         
     | 
| 
      
 49 
     | 
    
         
            +
                }
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                const healthCheckInterval = proxyObj?.healthCheckInterval || 60000;
         
     | 
| 
      
 52 
     | 
    
         
            +
                const maxFailures = 3;
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                const healthCheck = () => {
         
     | 
| 
      
 55 
     | 
    
         
            +
                    const now = Date.now();
         
     | 
| 
      
 56 
     | 
    
         
            +
                    for (const proxy of proxies) {
         
     | 
| 
      
 57 
     | 
    
         
            +
                        if (!proxy.isActive && proxy.lastFailureTime && now - proxy.lastFailureTime > healthCheckInterval) {
         
     | 
| 
      
 58 
     | 
    
         
            +
                            proxy.isActive = true;
         
     | 
| 
      
 59 
     | 
    
         
            +
                            proxy.failureCount = 0;
         
     | 
| 
      
 60 
     | 
    
         
            +
                            delete proxy.lastFailureTime;
         
     | 
| 
      
 61 
     | 
    
         
            +
                            logger.info(`Proxy ${proxy.uri} marked as active again after health check`);
         
     | 
| 
      
 62 
     | 
    
         
            +
                        }
         
     | 
| 
      
 63 
     | 
    
         
            +
                    }
         
     | 
| 
      
 64 
     | 
    
         
            +
                };
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                setInterval(healthCheck, healthCheckInterval);
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                const getNextProxy = (): ProxyState | null => {
         
     | 
| 
      
 69 
     | 
    
         
            +
                    const activeProxies = proxies.filter((p) => p.isActive);
         
     | 
| 
      
 70 
     | 
    
         
            +
                    if (activeProxies.length === 0) {
         
     | 
| 
      
 71 
     | 
    
         
            +
                        logger.warn('No active proxies available');
         
     | 
| 
      
 72 
     | 
    
         
            +
                        return null;
         
     | 
| 
      
 73 
     | 
    
         
            +
                    }
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                    let nextProxy = activeProxies[currentProxyIndex % activeProxies.length];
         
     | 
| 
      
 76 
     | 
    
         
            +
                    let attempts = 0;
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    while (!nextProxy.isActive && attempts < activeProxies.length) {
         
     | 
| 
      
 79 
     | 
    
         
            +
                        currentProxyIndex = (currentProxyIndex + 1) % activeProxies.length;
         
     | 
| 
      
 80 
     | 
    
         
            +
                        nextProxy = activeProxies[currentProxyIndex];
         
     | 
| 
      
 81 
     | 
    
         
            +
                        attempts++;
         
     | 
| 
      
 82 
     | 
    
         
            +
                    }
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                    if (!nextProxy.isActive) {
         
     | 
| 
      
 85 
     | 
    
         
            +
                        return null;
         
     | 
| 
      
 86 
     | 
    
         
            +
                    }
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                    return nextProxy;
         
     | 
| 
      
 89 
     | 
    
         
            +
                };
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                const markProxyFailed = (proxyUri: string) => {
         
     | 
| 
      
 92 
     | 
    
         
            +
                    const proxy = proxies.find((p) => p.uri === proxyUri);
         
     | 
| 
      
 93 
     | 
    
         
            +
                    if (proxy) {
         
     | 
| 
      
 94 
     | 
    
         
            +
                        proxy.failureCount++;
         
     | 
| 
      
 95 
     | 
    
         
            +
                        proxy.lastFailureTime = Date.now();
         
     | 
| 
      
 96 
     | 
    
         
            +
                        if (proxy.failureCount >= maxFailures) {
         
     | 
| 
      
 97 
     | 
    
         
            +
                            proxy.isActive = false;
         
     | 
| 
      
 98 
     | 
    
         
            +
                            logger.warn(`Proxy ${proxyUri} marked as inactive after ${maxFailures} failures`);
         
     | 
| 
      
 99 
     | 
    
         
            +
                        } else {
         
     | 
| 
      
 100 
     | 
    
         
            +
                            logger.warn(`Proxy ${proxyUri} failed (${proxy.failureCount}/${maxFailures})`);
         
     | 
| 
      
 101 
     | 
    
         
            +
                        }
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                        const activeProxies = proxies.filter((p) => p.isActive);
         
     | 
| 
      
 104 
     | 
    
         
            +
                        if (activeProxies.length > 0) {
         
     | 
| 
      
 105 
     | 
    
         
            +
                            currentProxyIndex = (currentProxyIndex + 1) % activeProxies.length;
         
     | 
| 
      
 106 
     | 
    
         
            +
                            const nextProxy = getNextProxy();
         
     | 
| 
      
 107 
     | 
    
         
            +
                            if (nextProxy) {
         
     | 
| 
      
 108 
     | 
    
         
            +
                                logger.info(`Switching to proxy: ${nextProxy.uri}`);
         
     | 
| 
      
 109 
     | 
    
         
            +
                            }
         
     | 
| 
      
 110 
     | 
    
         
            +
                        }
         
     | 
| 
      
 111 
     | 
    
         
            +
                    }
         
     | 
| 
      
 112 
     | 
    
         
            +
                };
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                const resetProxy = (proxyUri: string) => {
         
     | 
| 
      
 115 
     | 
    
         
            +
                    const proxy = proxies.find((p) => p.uri === proxyUri);
         
     | 
| 
      
 116 
     | 
    
         
            +
                    if (proxy) {
         
     | 
| 
      
 117 
     | 
    
         
            +
                        proxy.isActive = true;
         
     | 
| 
      
 118 
     | 
    
         
            +
                        proxy.failureCount = 0;
         
     | 
| 
      
 119 
     | 
    
         
            +
                        delete proxy.lastFailureTime;
         
     | 
| 
      
 120 
     | 
    
         
            +
                        logger.info(`Proxy ${proxyUri} manually reset`);
         
     | 
| 
      
 121 
     | 
    
         
            +
                    }
         
     | 
| 
      
 122 
     | 
    
         
            +
                };
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                const currentProxy = getNextProxy();
         
     | 
| 
      
 125 
     | 
    
         
            +
                if (currentProxy) {
         
     | 
| 
      
 126 
     | 
    
         
            +
                    logger.info(`Initial proxy selected: ${currentProxy.uri}`);
         
     | 
| 
      
 127 
     | 
    
         
            +
                }
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                return {
         
     | 
| 
      
 130 
     | 
    
         
            +
                    currentProxy,
         
     | 
| 
      
 131 
     | 
    
         
            +
                    allProxies: proxies,
         
     | 
| 
      
 132 
     | 
    
         
            +
                    proxyObj: proxyObj || {},
         
     | 
| 
      
 133 
     | 
    
         
            +
                    getNextProxy,
         
     | 
| 
      
 134 
     | 
    
         
            +
                    markProxyFailed,
         
     | 
| 
      
 135 
     | 
    
         
            +
                    resetProxy,
         
     | 
| 
      
 136 
     | 
    
         
            +
                };
         
     | 
| 
      
 137 
     | 
    
         
            +
            };
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
            export default createMultiProxy;
         
     | 
| 
         @@ -4,7 +4,7 @@ import logger from '@/utils/logger'; 
     | 
|
| 
       4 
4 
     | 
    
         
             
            const defaultProtocol = 'http';
         
     | 
| 
       5 
5 
     | 
    
         
             
            const possibleProtocol = ['http', 'https', 'socks', 'socks4', 'socks4a', 'socks5', 'socks5h'];
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
            const unifyProxy = (proxyUri: Config['proxyUri'], proxyObj: Config['proxy']) => {
         
     | 
| 
      
 7 
     | 
    
         
            +
            const unifyProxy = (proxyUri: Config['proxyUri'] | string, proxyObj: Config['proxy']) => {
         
     | 
| 
       8 
8 
     | 
    
         
             
                proxyObj = proxyObj || {};
         
     | 
| 
       9 
9 
     | 
    
         
             
                const [oriProxyUri, oriProxyObj] = [proxyUri, proxyObj];
         
     | 
| 
       10 
10 
     | 
    
         
             
                proxyObj = { ...proxyObj };
         
     | 
| 
         @@ -110,4 +110,6 @@ const unifyProxy = (proxyUri: Config['proxyUri'], proxyObj: Config['proxy']) => 
     | 
|
| 
       110 
110 
     | 
    
         
             
                return { proxyUri, proxyObj, proxyUrlHandler };
         
     | 
| 
       111 
111 
     | 
    
         
             
            };
         
     | 
| 
       112 
112 
     | 
    
         | 
| 
      
 113 
     | 
    
         
            +
            export const unifyProxies = (proxyUris: string[], proxyObj: Config['proxy']) => proxyUris.map((uri) => unifyProxy(uri, proxyObj)).filter((result) => result.proxyUri);
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
       113 
115 
     | 
    
         
             
            export default unifyProxy;
         
     | 
| 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import { describe, expect, it, vi, afterEach } from 'vitest';
         
     | 
| 
       2 
2 
     | 
    
         
             
            import { parseCookieArray, constructCookieArray, setCookies, getCookies } from '@/utils/puppeteer-utils';
         
     | 
| 
       3 
3 
     | 
    
         
             
            import puppeteer from '@/utils/puppeteer';
         
     | 
| 
       4 
     | 
    
         
            -
            import type { Browser } from 'puppeteer';
         
     | 
| 
      
 4 
     | 
    
         
            +
            import type { Browser } from 'rebrowser-puppeteer';
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            let browser: Browser | null = null;
         
     | 
| 
       7 
7 
     | 
    
         |