├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── -------.md │ └── bug---.md └── workflows │ └── build.yml ├── .gitignore ├── .npmignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.ts ├── package.json ├── renovate.json ├── rome.json ├── src ├── app.ts ├── provider.ts ├── sites │ ├── 51.ruyo.net.ts │ ├── addons.mozilla.org.ts │ ├── afadian.net.ts │ ├── app.yinxiang.com.ts │ ├── blog.51cto.com.ts │ ├── blog.csdn.net.ts │ ├── daily.zhihu.com.ts │ ├── docs.google.com.ts │ ├── getpocket.com.ts │ ├── gitee.com.ts │ ├── gmail.google.com.ts │ ├── infoq.cn.ts │ ├── juejin.com.ts │ ├── mail.qq.com.ts │ ├── mijisou.com.ts │ ├── oschina.com.ts │ ├── play.google.com.ts │ ├── sspai.com.ts │ ├── steamcommunity.com.ts │ ├── tieba.baidu.com.ts │ ├── twitter.com.ts │ ├── video.baidu.com.ts │ ├── weibo.com.ts │ ├── www.baidu.com.ts │ ├── www.dogedoge.com.ts │ ├── www.douban.com.ts │ ├── www.google.com.ts │ ├── www.jianshu.com.ts │ ├── www.logonews.cn.ts │ ├── www.so.com.ts │ ├── www.sogou.com.ts │ ├── www.youtube.com.ts │ ├── www.zhihu.com.ts │ ├── xueshu.baidu.com.ts │ └── zhuanlan.zhihu.com.ts └── utils.ts ├── tsconfig.json └── webpack.config.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://github.com/axetroy/buy-me-a-cup-of-tea 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 请求支持新站点 3 | about: 请说出你想要支持的站点 4 | title: "支持网站: xxx" 5 | labels: enhancement 6 | assignees: "" 7 | --- 8 | 9 | ### 新的网站支持 10 | 11 | > 请输入你想要支持的网站 12 | 13 | ### 有重定向链接的网页地址 14 | 15 | > 请填写有重定向链接的网页地址,方便我定位支持 16 | > 17 | > 个人精力有限,我不太可能会在网站中一个一个的去找,感谢你的理解 18 | > 19 | > 否则我大概率不会支持 20 | 21 | ### 其他说明 22 | 23 | > 可选 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: BUG 反馈 3 | about: 请带上出现 BUG 的页面地址 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | ### BUG 描述 10 | 11 | > 请对这个 BUG 进行描述 12 | 13 | ### 相关的连接 14 | 15 | > 请填写有重定向链接的网页地址,方便我定位排查 16 | > 17 | > 个人精力有限,我不太可能会在网站中一个一个的去找,感谢你的理解 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ["12.14.1"] 11 | os: [ubuntu-latest, macOS-latest, windows-latest] 12 | name: node.js ${{ matrix.node }} test in ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Environment 17 | run: | 18 | node -v 19 | npm -v 20 | yarn --version 21 | 22 | - name: Install 23 | run: yarn 24 | 25 | - name: Linter 26 | run: yarn run lint 27 | 28 | - name: Formater 29 | run: yarn run format 30 | 31 | - name: Compile 32 | run: yarn run build 33 | 34 | - uses: actions/upload-artifact@v3 35 | if: runner.os == 'linux' 36 | with: 37 | name: dist 38 | path: ./dist 39 | 40 | - name: Deploy 41 | if: runner.os == 'linux' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') 42 | uses: JamesIves/github-pages-deploy-action@v4.4.3 43 | with: 44 | branch: gh-pages # The branch the action should deploy to. 45 | folder: dist # The folder the action should deploy. 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | 86 | dist 87 | .idea 88 | yarn.lock 89 | package-lock.json 90 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional eslint cache 40 | .eslintcache 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | 45 | # Output of 'npm pack' 46 | *.tgz 47 | 48 | # Yarn Integrity file 49 | .yarn-integrity 50 | 51 | # IDEA 52 | .idea 53 | 54 | # dist 55 | dist 56 | 57 | .travis.yml 58 | yarn.lock 59 | package-lock.json 60 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["rome.rome"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "rome.rome" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "rome.rome" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.21.1 (2023-07-21) 2 | 3 | ### Bugs fixed: 4 | 5 | - youtube([`ea804d1`](https://github.com/axetroy/anti-redirect/commit/ea804d125cb9de9fa3fccf290d4a87d1b8fb0905)) (by Axetroy) 6 | 7 | ## v2.21.0 (2023-05-29) 8 | 9 | ### New feature: 10 | 11 | - 增加少数派的支持, close #620([`27a53ed`](https://github.com/axetroy/anti-redirect/commit/27a53ed5f4f03985638fa0bff419e1b8aa4b6b7d)) (by Axetroy) 12 | - 支持 csdn 自定义域名, close #623([`30ae45f`](https://github.com/axetroy/anti-redirect/commit/30ae45fec6c3d7a4d91c05d61ae0152fe77f510e)) (by Axetroy) 13 | - 支持 gitee.com, close #641([`0dacbf2`](https://github.com/axetroy/anti-redirect/commit/0dacbf2883ddd5bed3f4b1bd11304df343bb34a6)) (by Axetroy) 14 | - add xie.infoq.cn([`d4df9b2`](https://github.com/axetroy/anti-redirect/commit/d4df9b24bdbef29d17eaa20e7ebf555f0d75771a)) (by Axetroy) 15 | 16 | ### Bugs fixed: 17 | 18 | - format([`bf5fb2a`](https://github.com/axetroy/anti-redirect/commit/bf5fb2a4be1ed2694eb87a7158086d6b163ff346)) (by Axetroy) 19 | - lint([`a059c15`](https://github.com/axetroy/anti-redirect/commit/a059c15f54a3d1f33a736388943bd350c132ff32)) (by Axetroy) 20 | - lint([`eaf94e8`](https://github.com/axetroy/anti-redirect/commit/eaf94e81adf8997aa29863c708c4a21644f66704)) (by Axetroy) 21 | 22 | ## v2.20.0 (2022-10-03) 23 | 24 | ### New feature: 25 | 26 | - 支持 51 CTO. close #566([`ddfa551`](https://github.com/axetroy/anti-redirect/commit/ddfa5510e01841d94a3fb0f20f687fb743a5100c)) (by Axetroy) 27 | - 支持爱发电. close #598([`35d0b1b`](https://github.com/axetroy/anti-redirect/commit/35d0b1bd392ceb6505cf5a60721d7c8627098449)) (by Axetroy) 28 | 29 | ### Bugs fixed: 30 | 31 | - 修复 Google 的其他地方站点. close #599([`2a89351`](https://github.com/axetroy/anti-redirect/commit/2a8935123243f89951ad40545444f8741eea33ee)) (by Axetroy) 32 | 33 | ## v2.19.6 (2022-05-02) 34 | 35 | ### Bugs fixed: 36 | 37 | - google([`c64381e`](https://github.com/axetroy/anti-redirect/commit/c64381e9f6bc7fee108e3daf3af5743ca5b0278c)) (by Axetroy) 38 | - google([`ddfcb55`](https://github.com/axetroy/anti-redirect/commit/ddfcb55ad05df07561ff477a454b3f7e7bc951af)) (by Axetroy) 39 | 40 | ## v2.19.5 (2022-04-10) 41 | 42 | ### Bugs fixed: 43 | 44 | - 提升百度的稳定性([`0efa80f`](https://github.com/axetroy/anti-redirect/commit/0efa80fde242eeaf092cc9d69b547dd3a8e78397)) (by Axetroy) 45 | 46 | ## v2.19.4 (2022-04-10) 47 | 48 | ### New feature: 49 | 50 | - 支持标志情报局. close #481([`84e7231`](https://github.com/axetroy/anti-redirect/commit/84e72315d65fbf75e4b84d56718fb8f81621bdba)) (by Axetroy) 51 | 52 | ## v2.19.3 (2022-04-10) 53 | 54 | ## v2.19.2 (2022-04-10) 55 | 56 | ### New feature: 57 | 58 | - 支持 juejin.cn([`e26c6a2`](https://github.com/axetroy/anti-redirect/commit/e26c6a25834c7638dffe0db3fc27cb2e51ee7880)) (by Axetroy) 59 | 60 | ### Bugs fixed: 61 | 62 | - twitter. close #565([`b43b527`](https://github.com/axetroy/anti-redirect/commit/b43b52715b64107bd71799d344e2ead2296c396a)) (by Axetroy) 63 | 64 | ## v2.19.0 (2021-03-22) 65 | 66 | ### New feature: 67 | 68 | - 支持 oschina([`e2e15c7`](https://github.com/axetroy/anti-redirect/commit/e2e15c77a90011b8dd301f506a2765e3260b9c90)) (by axetroy) 69 | 70 | ## v2.18.7 (2021-03-09) 71 | 72 | ## v2.18.6 (2021-03-09) 73 | 74 | ### Bugs fixed: 75 | 76 | - 修复 CSDN 的 TOC 重定向会把 hash 地址的跳转错误([`c1a2c2e`](https://github.com/axetroy/anti-redirect/commit/c1a2c2e8377d2a486ef0146d6e8c4a0f3d84c198)) (by axetroy) 77 | 78 | ## v2.18.5 (2021-01-20) 79 | 80 | ### Bugs fixed: 81 | 82 | - 修复简书有些链接没有去除重定向 close #366([`b5cb3c7`](https://github.com/axetroy/anti-redirect/commit/b5cb3c7eca7ad8fb79ec9c678710e8a205860db4)) (by axetroy) 83 | 84 | ## v2.18.4 (2021-01-02) 85 | 86 | ## v2.18.3 (2020-12-31) 87 | 88 | ## v2.18.2 (2020-12-31) 89 | 90 | ## v2.18.0 (2020-12-25) 91 | 92 | ### New feature: 93 | 94 | - 支持印象笔记. close #344([`ef4a5e2`](https://github.com/axetroy/anti-redirect/commit/ef4a5e2ebb251f37cfb02c17b92e5ff5f109930e)) (by axetroy) 95 | 96 | ## v2.17.0 (2020-12-25) 97 | 98 | ### New feature: 99 | 100 | - 支持 CSDN. close #350([`814b162`](https://github.com/axetroy/anti-redirect/commit/814b16297f3a4c8588c13644da06cd2eb1b6d4fd)) (by axetroy) 101 | 102 | ## v2.16.2 (2020-10-25) 103 | 104 | ### Bugs fixed: 105 | 106 | - #277([`4c0908d`](https://github.com/axetroy/anti-redirect/commit/4c0908d0dcd2254af5f18585e86ba9088a3f3bbe)) (by axetroy) 107 | 108 | ## v2.16.0 (2020-09-23) 109 | 110 | ### New feature: 111 | 112 | - 添加秘迹的支持 close #251([`6a10c3c`](https://github.com/axetroy/anti-redirect/commit/6a10c3cb40e351534d333cbc051f79887eb422da)) (by axetroy) 113 | 114 | ### Bugs fixed: 115 | 116 | - 修复在搜索结果中打不开的百度文库 close #212([`f845d24`](https://github.com/axetroy/anti-redirect/commit/f845d24d006c9db92829c9938976a52ee10ab8f8)) (by axetroy) 117 | 118 | ## v2.15.2 (2020-06-07) 119 | 120 | ### New feature: 121 | 122 | - 支持 youtube channel 页 (#243)([`5bfd7e9`](https://github.com/axetroy/anti-redirect/commit/5bfd7e942214cc34b3660995a9a057146666ac54)) (by 人造电子小猫咪) 123 | 124 | ## v2.15.1 (2020-04-10) 125 | 126 | ### Bugs fixed: 127 | 128 | - 修复 360 搜索. close #204([`f16b81c`](https://github.com/axetroy/anti-redirect/commit/f16b81cdd451527e69859aa65917214154737611)) (by axetroy) 129 | 130 | ## v2.15.0 (2020-03-24) 131 | 132 | ### New feature: 133 | 134 | - 支持 steam. close #48([`0256a46`](https://github.com/axetroy/anti-redirect/commit/0256a4667ccbdf38f3fcb4cf10cb70500a09d73e)) (by axetroy) 135 | 136 | ### Bugs fixed: 137 | 138 | - 修复简书部分页面没有去除重定向 close #199([`3d8c78b`](https://github.com/axetroy/anti-redirect/commit/3d8c78b7d95d6d69c78277ef2dea6e6253e57e39)) (by axetroy) 139 | 140 | ## v2.14.5 (2020-02-26) 141 | 142 | ### Bugs fixed: 143 | 144 | - 适配 Google 的新追踪策略. close #176([`5cb1da0`](https://github.com/axetroy/anti-redirect/commit/5cb1da06d1d6337c922363a539c50984ed17b556)) (by axetroy) 145 | 146 | ## v2.14.4 (2020-02-11) 147 | 148 | ## v2.14.3 (2020-02-11) 149 | 150 | ### New feature: 151 | 152 | - 部分网站的去除重定向请求,不再携带 cookie([`279d80c`](https://github.com/axetroy/anti-redirect/commit/279d80c39fe1859b6d9a61e1eb09f6c6e9f9ca7f)) (by axetroy) 153 | 154 | ## v2.14.2 (2019-12-11) 155 | 156 | ### Bugs fixed: 157 | 158 | - 简书([`56503b4`](https://github.com/axetroy/anti-redirect/commit/56503b45aac07708258a289595adf6248e7f31a1)) (by axetroy) 159 | 160 | ## v2.14.1 (2019-11-26) 161 | 162 | ### Bugs fixed: 163 | 164 | - update([`ea15955`](https://github.com/axetroy/anti-redirect/commit/ea15955d55445e57c905c74fb98815aac48e53cb)) (by axetroy) 165 | 166 | ## v2.14.0 (2019-11-26) 167 | 168 | ### New feature: 169 | 170 | - support ruyo. close #155([`4f3bded`](https://github.com/axetroy/anti-redirect/commit/4f3bdeda6a3f7f4da0c02c383f72372f63c8b812)) (by axetroy) 171 | 172 | ## v2.13.1 (2019-09-28) 173 | 174 | ## v2.13.0 (2019-09-23) 175 | 176 | ### New feature: 177 | 178 | - support dogedoge.com. close #127([`dba98e0`](https://github.com/axetroy/anti-redirect/commit/dba98e0cd6af30d8e5fd6222e15a677bb4264ca4)) (by Axetroy) 179 | 180 | ## v2.12.0 (2019-06-24) 181 | 182 | ## v2.11.3 (2019-05-21) 183 | 184 | ## v2.11.2 (2019-04-10) 185 | 186 | ### Bugs fixed: 187 | 188 | - 更精准的重定向去除. close #49([`0d0bb46`](https://github.com/axetroy/anti-redirect/commit/0d0bb466e81dfdb92671a848c59d9d7065e2fc30)) (by axetroy) 189 | 190 | ## v2.11.1 (2019-04-10) 191 | 192 | ## v2.11.0 (2019-01-17) 193 | 194 | ### New feature: 195 | 196 | - 支持去除 pocket 重定向, close #47([`a259e6c`](https://github.com/axetroy/anti-redirect/commit/a259e6c7789b6916823975f6ec4df222a6d04350)) (by axetroy) 197 | 198 | ## v2.10.1 (2019-01-10) 199 | 200 | ### Bugs fixed: 201 | 202 | - 修复 QQ 邮箱失效的问题 close #40([`ce9a6c0`](https://github.com/axetroy/anti-redirect/commit/ce9a6c0c67fc354966d52ff93e33f97fb7ebaecc)) (by axetroy) 203 | - 由于豆瓣的问题,暂时只支持部分页面生效去重定向([`9b7eaed`](https://github.com/axetroy/anti-redirect/commit/9b7eaed6652e558ff5b144ef27e8cf93b6b8a473)) (by axetroy) 204 | 205 | ## v2.10.0 (2018-12-20) 206 | 207 | ### New feature: 208 | 209 | - 支持豆瓣评分([`b11073f`](https://github.com/axetroy/anti-redirect/commit/b11073f8055a5e98994a5ceebe74e5c27b34deb3)) (by axetroy) 210 | 211 | ## v2.9.0 (2018-04-28) 212 | 213 | ### New feature: 214 | 215 | - 支持使用 boolean 和 function 来定义一个 provider 是否应该启动([`61dccf1`](https://github.com/axetroy/anti-redirect/commit/61dccf12532afa7be6ca7ad914520050026b8887)) (by axetroy) 216 | - support google play([`ae822b8`](https://github.com/axetroy/anti-redirect/commit/ae822b80c7ade51921b55f743a593d97a40d0cfa)) (by axetroy) 217 | - 支持 gmail([`6f21f91`](https://github.com/axetroy/anti-redirect/commit/6f21f9128adf3569fb57033f42d3497f56951a05)) (by axetroy) 218 | 219 | ## v2.8.0 (2018-04-27) 220 | 221 | ## v2.7.0 (2018-04-25) 222 | 223 | ### New feature: 224 | 225 | - 支持简书([`df87bf8`](https://github.com/axetroy/anti-redirect/commit/df87bf8c46d2fdbf881bd7850e7d52c521447d11)) (by axetroy) 226 | 227 | ## v2.6.1 (2018-03-15) 228 | 229 | ### New feature: 230 | 231 | - 添加支持火狐浏览器扩展页支持([`b9f9d4d`](https://github.com/axetroy/anti-redirect/commit/b9f9d4d2dd49c5219ef3be61a18755353d98eb84)) (by axetroy) 232 | 233 | ### Bugs fixed: 234 | 235 | - 修复 so.com 再翻页中有问题的 bug([`7ae798f`](https://github.com/axetroy/anti-redirect/commit/7ae798f4a0a8b8b8d2448b790fbd030bbfb55f6c)) (by axetroy) 236 | 237 | ## v2.6.0 (2018-02-21) 238 | 239 | ### New feature: 240 | 241 | - 添加支持火狐浏览器扩展页面的重定向([`51c91f7`](https://github.com/axetroy/anti-redirect/commit/51c91f7df1c42de5fd975e3ca7338890db0e8539)) (by axetroy) 242 | 243 | ## v2.5.0 (2018-02-21) 244 | 245 | ### New feature: 246 | 247 | - 支持去除 QQ 邮箱重定向([`34d0d66`](https://github.com/axetroy/anti-redirect/commit/34d0d66ad333ea5c1c395f8528ef9bf8045a7223)) (by axetroy) 248 | 249 | ## v2.4.1 (2018-01-04) 250 | 251 | ### Bugs fixed: 252 | 253 | - 修复百度学术有部分跳转链接没有去除的问题([`d10755a`](https://github.com/axetroy/anti-redirect/commit/d10755a516fd8024bc3514469782941661516f6e)) (by axetroy) 254 | 255 | ## v2.4.0 (2018-01-04) 256 | 257 | ### New feature: 258 | 259 | - 支持百度学术([`446f5b9`](https://github.com/axetroy/anti-redirect/commit/446f5b995cc2117f8f0c1f283d8b31bd7b0ae394)) (by axetroy) 260 | 261 | ## v2.3.1 (2017-09-19) 262 | 263 | ### Bugs fixed: 264 | 265 | - **sogou**: 修复搜狗搜索下, 和翻页脚本不兼容的问题([`6ebf396`](https://github.com/axetroy/anti-redirect/commit/6ebf396a1f932805e4027a39468cd2e83ded207c)) (by axetroy) 266 | 267 | ## v2.3.0 (2017-09-04) 268 | 269 | ### New feature: 270 | 271 | - 如果页面处于初始的状态,没有滚动过,则出发一次 onScroll 事件([`5f112d3`](https://github.com/axetroy/anti-redirect/commit/5f112d3ccb657679ae5209fc0dea89cf532c6241)) (by axetroy) 272 | - 百度贴吧去除重定向成功之后,添加成功的标记([`35c952a`](https://github.com/axetroy/anti-redirect/commit/35c952a6f0b45ade3e68f9ceff24a337875b65e3)) (by axetroy) 273 | 274 | ## v2.2.0 (2017-09-04) 275 | 276 | ### New feature: 277 | 278 | - 支持掘金去除重定向([`ad55ca9`](https://github.com/axetroy/anti-redirect/commit/ad55ca9d3e02ad66f65706ad72f2e2e4763c61d1)) (by axetroy) 279 | - provider 继承自事件发生器,监听页面滚动,会自动去除重定向([`168e4d8`](https://github.com/axetroy/anti-redirect/commit/168e4d861f6df0c53399f7ffd30581214ceaf28d)) (by axetroy) 280 | - **changelog**: support generate CHANGELOG([`bcb24ea`](https://github.com/axetroy/anti-redirect/commit/bcb24ea337ecf4e3f7fcb4033d7c6dbddfaa59cb)) (by axetroy) 281 | 282 | ## v2.1.0 (2017-09-04) 283 | 284 | ### New feature: 285 | 286 | - 支持 google docs 站点([`11eafae`](https://github.com/axetroy/anti-redirect/commit/11eafaee95e744ce7e6f57cd770d83aea3e99ac2)) (by axetroy) 287 | - add es6-promise polyfill([`46d95c6`](https://github.com/axetroy/anti-redirect/commit/46d95c6516b28b798f95beea06cc5a438d42e269)) (by axetroy) 288 | 289 | ## v2.0.0 (2017-09-02) 290 | 291 | ### New feature: 292 | 293 | - add meta info([`61dfab9`](https://github.com/axetroy/anti-redirect/commit/61dfab96348177bc1ee8ce6102e6cdbefd79e30f)) (by Axetroy) 294 | 295 | ### Bugs fixed: 296 | 297 | - 修复贴吧部分重定向无法去处的问题([`a8b92cb`](https://github.com/axetroy/anti-redirect/commit/a8b92cbf5fe57d55d703885f9be41d41f32d92c2)) (by axetroy) 298 | - typo([`56912cd`](https://github.com/axetroy/anti-redirect/commit/56912cd3dcce92b2c75500962f8298ecc83eb78e)) (by axetroy) 299 | - fix about class Query([`d3ca05d`](https://github.com/axetroy/anti-redirect/commit/d3ca05ddf572fd2f3d254eefc6da2bbed222589e)) (by axetroy) 300 | - fix a bug about class Query([`bd1505d`](https://github.com/axetroy/anti-redirect/commit/bd1505d0b87716ea7fec788c6f5a28fb6db35d56)) (by axetroy) 301 | 302 | ## 1.2.0 (2016-12-29) 303 | 304 | ### New feature: 305 | 306 | - 支持 sogou 搜索([`1316e26`](https://github.com/axetroy/anti-redirect/commit/1316e26352f8a05fd31ee16f9cf1d16b7d5f869a)) (by axetroy) 307 | 308 | ## 1.1.0 (2016-12-29) 309 | 310 | ### New feature: 311 | 312 | - 支持知乎专栏([`eed370d`](https://github.com/axetroy/anti-redirect/commit/eed370d8caf0908fea359f5644be5f1a88a468a1)) (by axetroy) 313 | 314 | ## 1.0.1 (2016-12-23) 315 | 316 | ### New feature: 317 | 318 | - add baidu-video support([`b421c6e`](https://github.com/axetroy/anti-redirect/commit/b421c6e39bb32af67d2ddce98a0074ae6f5f8def)) (by axetroy) 319 | - **tieba**: 添加对百度贴吧反重定向的支持([`516ef7e`](https://github.com/axetroy/anti-redirect/commit/516ef7e7b17c9558b0821c34bfbef8033fe7a055)) (by axetroy) 320 | - **new**: 新增 weibo/twitter 的去除跳转,以及重构一些代码([`cb07843`](https://github.com/axetroy/anti-redirect/commit/cb0784331c56c649bbc4b20df690ada50ae72300)) (by axetroy) 321 | - **\***: rebuild([`e8030dd`](https://github.com/axetroy/anti-redirect/commit/e8030dd0d39dbded7b298169932f488e576827cf)) (by axetroy) 322 | - **more**: 重构脚本,支持更多站点([`c366c51`](https://github.com/axetroy/anti-redirect/commit/c366c51d8596ce1aa2c40a67e619f7fed77d8763)) (by axetroy) 323 | 324 | ### Bugs fixed: 325 | 326 | - 修复 360so 解析地址不正确的问题(锅是 360 的,不关我事)([`859ab94`](https://github.com/axetroy/anti-redirect/commit/859ab9483769382f26d31eceb52ca51d218188ea)) (by axetroy) 327 | - **google**: 修复谷歌搜索反重定向不正确的问题,以及重构部分代码([`edc68d4`](https://github.com/axetroy/anti-redirect/commit/edc68d4e7f3017bd07336673c14b4ad6c7ec4f67)) (by axetroy) 328 | - **proto**: 修复获取下,获取协议的兼容性,以及应用启动的部分参数([`d2bb7ef`](https://github.com/axetroy/anti-redirect/commit/d2bb7ef9ffb9a5fe76fcfaf967a72d5c0e9a936b)) (by axetroy) 329 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Axetroy 2 | 3 | Anti 996 License Version 1.0 (Draft) 4 | 5 | Permission is hereby granted to any individual or legal entity 6 | obtaining a copy of this licensed work (including the source code, 7 | documentation and/or related items, hereinafter collectively referred 8 | to as the "licensed work"), free of charge, to deal with the licensed 9 | work for any purpose, including without limitation, the rights to use, 10 | reproduce, modify, prepare derivative works of, distribute, publish 11 | and sublicense the licensed work, subject to the following conditions: 12 | 13 | 1. The individual or the legal entity must conspicuously display, 14 | without modification, this License and the notice on each redistributed 15 | or derivative copy of the Licensed Work. 16 | 17 | 2. The individual or the legal entity must strictly comply with all 18 | applicable laws, regulations, rules and standards of the jurisdiction 19 | relating to labor and employment where the individual is physically 20 | located or where the individual was born or naturalized; or where the 21 | legal entity is registered or is operating (whichever is stricter). In 22 | case that the jurisdiction has no such laws, regulations, rules and 23 | standards or its laws, regulations, rules and standards are 24 | unenforceable, the individual or the legal entity are required to 25 | comply with Core International Labor Standards. 26 | 27 | 3. The individual or the legal entity shall not induce or force its 28 | employee(s), whether full-time or part-time, or its independent 29 | contractor(s), in any methods, to agree in oral or written form, to 30 | directly or indirectly restrict, weaken or relinquish his or her 31 | rights or remedies under such laws, regulations, rules and standards 32 | relating to labor and employment as mentioned above, no matter whether 33 | such written or oral agreement are enforceable under the laws of the 34 | said jurisdiction, nor shall such individual or the legal entity 35 | limit, in any methods, the rights of its employee(s) or independent 36 | contractor(s) from reporting or complaining to the copyright holder or 37 | relevant authorities monitoring the compliance of the license about 38 | its violation(s) of the said license. 39 | 40 | THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 41 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 42 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 43 | IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, 44 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 45 | OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION WITH THE 46 | LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/axetroy/anti-redirect/workflows/build/badge.svg)](https://github.com/axetroy/anti-redirect/actions) 2 | [![DeepScan grade](https://deepscan.io/api/teams/5773/projects/7595/branches/79869/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=5773&pid=7595&bid=79869) 3 | [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) 4 | [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) 5 | 6 | ### GM 脚本,反重定向 7 | 8 | 去除各搜索引擎/常用网站的重定向 9 | 10 | > 注意事项: 11 | > 12 | > 重定向一般有两种目的 13 | > 14 | > 1. 追踪用户打开了哪些 URL 15 | > 2. 在用户跳转到站外之前进行确认地址,防止打开不明的页面 16 | 17 | ### 反馈地址 18 | 19 | > 反馈最好能带上出问题的网页地址 20 | 21 | - https://github.com/axetroy/anti-redirect/issues/new/choose 22 | - https://github.com/axetroy/anti-redirect/issues/new/choose 23 | - https://github.com/axetroy/anti-redirect/issues/new/choose 24 | 25 | ### 如果这能够帮助到你, 不妨点个 star, 你的支持就是我更新的动力 26 | 27 | [点击从 Github 安装](https://github.com/axetroy/anti-redirect/raw/gh-pages/anti-redirect.user.js) 28 | 29 | [点击从 GreasyFork 安装](https://greasyfork.org/scripts/11915-anti-redirect-typescript/code/anti-redirect.user.js) 30 | 31 | [点击从 CDN 安装(国内用户)](https://cdn.jsdelivr.net/gh/axetroy/anti-redirect@gh-pages/anti-redirect.user.js) 32 | 33 | ### 工作原理 34 | 35 | 1. 根据 URL 上暴露出来的跳转链接,正则匹配提取真实的地址,例如知乎,Google 36 | 2. 如果 A 标签的内容为真实的地址,则替换,例如百度贴吧 37 | 3. 逐一发送请求,获取真实的地址,例如百度搜索 38 | 4. 根据请求特殊页面,这个特殊页面没有重定向地址,然后覆盖当前页,例如百度搜索,搜狗搜索 39 | 5. 覆盖原本的链接点击事件,比如 qq 邮箱 40 | 41 | ### 更新日志 42 | 43 | [https://github.com/axetroy/anti-redirect/blob/master/CHANGELOG.md](https://github.com/axetroy/anti-redirect/blob/master/CHANGELOG.md) 44 | 45 | ### 支持的站点 46 | 47 | - [x] 知乎 48 | - [x] 知乎专栏 49 | - [x] 知乎日报 50 | - [x] Google 搜索 51 | - [x] Google 文档 52 | - [x] Google Play 53 | - [x] Google Gmail 54 | - [x] Google Youtube 55 | - [x] Steam 56 | - [x] 360 搜索 57 | - [x] 新浪微博 58 | - [x] Twitter 59 | - [x] 搜狗搜索 60 | - [x] 百度搜索 61 | - [x] 百度视频 62 | - [x] 百度学术 63 | - [x] 百度贴吧 64 | - [x] 掘金 65 | - [x] QQ 邮箱 66 | - [x] Mozilla 67 | - [x] 简书 68 | - [x] 豆瓣 69 | - [x] Pocket 70 | - [x] DogeDoge 71 | - [x] 秘迹 72 | - [x] CSDN 73 | - [x] 开源中国 74 | - [x] 印象笔记 75 | - [x] 标志情报局 76 | - [x] 爱发电 77 | - [x] 51 CTO 78 | - [x] InfoQ 79 | - [x] Gitee 80 | - [x] 少数派 81 | 82 |
更多 83 | 84 | - [x] 51.ruyo.net 85 | 86 |
87 | 88 | ### 我想支持更多的站点 89 | 90 | 点击这个[链接](https://github.com/axetroy/anti-redirect/issues/new),提交 issues,说出你想要支持的站点 91 | 92 | ### 贡献代码 93 | 94 | 需要通过 NodeJs 把 TypeScript 编译成 javascript 95 | 96 | ```bash 97 | git clone https://github.com/axetroy/anti-redirect.git 98 | 99 | cd ./anti-redirect 100 | 101 | npm install 102 | npm run watch 103 | ``` 104 | 105 | ### 开源许可 106 | 107 | The [Anti 996 License](https://github.com/axetroy/anti-redirect/blob/master/LICENSE) 108 | 109 | 请仔细阅读开源许可。 110 | 111 | 简而言之: 如果你正在 996,或者你的公司/单位正在 996 ,那么请不要安装这个脚本! 112 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { App } from "@/app"; 2 | import { RuyoProvider } from "@/sites/51.ruyo.net"; 3 | import { MozillaProvider } from "@/sites/addons.mozilla.org"; 4 | import { YinXiangProvider } from "@/sites/app.yinxiang.com"; 5 | import { CSDNProvider } from "@/sites/blog.csdn.net"; 6 | import { OSChinaProvider } from "@/sites/oschina.com"; 7 | import { ZhihuDailyProvider } from "@/sites/daily.zhihu.com"; 8 | import { GoogleDocsProvider } from "@/sites/docs.google.com"; 9 | import { PocketProvider } from "@/sites/getpocket.com"; 10 | import { GmailProvider } from "@/sites/gmail.google.com"; 11 | import { JuejinProvider } from "@/sites/juejin.com"; 12 | import { QQMailProvider } from "@/sites/mail.qq.com"; 13 | import { MiJiProvider } from "@/sites/mijisou.com"; 14 | import { GooglePlayProvider } from "@/sites/play.google.com"; 15 | import { SteamProvider } from "@/sites/steamcommunity.com"; 16 | import { TiebaProvider } from "@/sites/tieba.baidu.com"; 17 | import { TwitterProvider } from "@/sites/twitter.com"; 18 | import { BaiduVideoProvider } from "@/sites/video.baidu.com"; 19 | import { WeboProvider } from "@/sites/weibo.com"; 20 | import { BaiduProvider } from "@/sites/www.baidu.com"; 21 | import { DogeDogeProvider } from "@/sites/www.dogedoge.com"; 22 | import { DouBanProvider } from "@/sites/www.douban.com"; 23 | import { GoogleProvider } from "@/sites/www.google.com"; 24 | import { JianShuProvider } from "@/sites/www.jianshu.com"; 25 | import { SoProvider } from "@/sites/www.so.com"; 26 | import { SoGouProvider } from "@/sites/www.sogou.com"; 27 | import { YoutubeProvider } from "@/sites/www.youtube.com"; 28 | import { ZhihuProvider } from "@/sites/www.zhihu.com"; 29 | import { BaiduXueshuProvider } from "@/sites/xueshu.baidu.com"; 30 | import { ZhihuZhuanlanProvider } from "@/sites/zhuanlan.zhihu.com"; 31 | import { LogonewsProvider } from "@/sites/www.logonews.cn"; 32 | import { AfDianNetProvider } from "@/sites/afadian.net"; 33 | import { Blog51CTO } from "@/sites/blog.51cto.com"; 34 | import { InfoQProvider } from "@/sites/infoq.cn"; 35 | import { GiteeProvider } from "@/sites/gitee.com"; 36 | import { SSPaiProvider } from "@/sites/sspai.com"; 37 | import http from "gm-http"; 38 | 39 | const app = new App(); 40 | const isDebug: boolean = process.env.NODE_ENV !== "production"; 41 | 42 | http.setConfig({ debug: isDebug }); 43 | 44 | app 45 | .setConfig({ isDebug }) 46 | .registerProvider([ 47 | { 48 | // 测试地址: https://www.zhihu.com/question/25258775 49 | name: "知乎", 50 | test: /www\.zhihu\.com/, 51 | provider: ZhihuProvider, 52 | }, 53 | { 54 | // 测试地址: https://zhuanlan.zhihu.com/p/20549978 55 | name: "知乎专栏", 56 | test: /zhuanlan\.zhihu\.com/, 57 | provider: ZhihuZhuanlanProvider, 58 | }, 59 | { 60 | // 测试地址: 61 | name: "知乎日报", 62 | test: /daily\.zhihu\.com/, 63 | provider: ZhihuDailyProvider, 64 | }, 65 | { 66 | name: "Google搜索", 67 | test: /\w+\.google\./, 68 | provider: GoogleProvider, 69 | }, 70 | { 71 | // 测试地址: https://docs.google.com/spreadsheets/d/1TFcEXMcKrwoIAECIVyBU0GPoSmRqZ7A0VBvqeKYVSww/htmlview 72 | name: "Google Docs", 73 | test: /docs\.google\.com/, 74 | provider: GoogleDocsProvider, 75 | }, 76 | { 77 | name: "Gmail", 78 | test: /mail\.google\.com/, 79 | provider: GmailProvider, 80 | }, 81 | { 82 | // 测试地址: https://play.google.com/store/movies/details/%E7%A7%BB%E5%8B%95%E8%BF%B7%E5%AE%AE_%E6%AD%BB%E4%BA%A1%E8%A7%A3%E8%97%A5?id=YNy7gRqwtMk 83 | name: "Google Play", 84 | test: /play\.google\.com/, 85 | provider: GooglePlayProvider, 86 | }, 87 | { 88 | // 测试地址: https://www.youtube.com/watch?v=XTXSRRSv1bY 89 | name: "Google Youtube", 90 | test: /www\.youtube\.com/, 91 | provider: YoutubeProvider, 92 | }, 93 | { 94 | // 测试地址: https://www.so.com/s?ie=utf-8&fr=none&src=360sou_newhome&q=chrome 95 | name: "360搜索", 96 | test: /www\.so\.com/, 97 | provider: SoProvider, 98 | }, 99 | { 100 | name: "新浪微博", 101 | test: /\.weibo\.com/, 102 | provider: WeboProvider, 103 | }, 104 | // 测试: https://twitter.com/ftium4/status/1512815116810522631 105 | { 106 | name: "Twitter", 107 | test: /twitter\.com/, 108 | provider: TwitterProvider, 109 | }, 110 | { 111 | // 测试: http://www.sogou.com/web?query=chrome&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=1527&sst0=1504347367611&lkt=0%2C0%2C0&sugsuv=00091651B48CA45F593B61A29B131405&sugtime=1504347367611 112 | name: "搜狗搜索", 113 | test: /www\.sogou\.com/, 114 | provider: SoGouProvider, 115 | }, 116 | { 117 | // 测试: https://www.baidu.com/s?wd=chrome&rsv_spt=1&rsv_iqid=0xcb136237000ed40e&issp=1&f=8&rsv_bp=0&rsv_idx=2&ie=utf-8&tn=baidulocal&rsv_enter=1&rsv_sug3=7&rsv_sug1=7&rsv_sug7=101&rsv_sug2=0&inputT=813&rsv_sug4=989×tamp=1504349229266&rn=50&vf_bl=1 118 | name: "百度搜索", 119 | test: /www\.baidu\.com/, 120 | provider: BaiduProvider, 121 | }, 122 | { 123 | // 测试: https://www.baidu.com/s?wd=chrome&pn=20&oq=chrome&tn=baiduhome_pg&ie=utf-8&usm=3&rsv_idx=2&rsv_pq=e043900d0000752d&rsv_t=6bb0UqEwp2Tle6TAMBDlU3Wg%2BSxoqvvOhZKyQgM%2BVQP8Gc54QZLhcDcj62eGfNG75aq5&rsv_page=1 124 | name: "百度视频", 125 | test: /v\.baidu\.com/, 126 | provider: BaiduVideoProvider, 127 | }, 128 | { 129 | // 测试: http://xueshu.baidu.com/s?wd=paperuri%3A%28ae4d6b5da05eca552dab05aeefb966e6%29&ie=utf-8&filter=sc_long_sign&sc_ks_para=q%3D%E2%80%9C%E4%BA%92%E8%81%94%E7%BD%91%2B%E5%81%A5%E5%BA%B7%E7%AE%A1%E7%90%86%E2%80%9D%E6%A8%A1%E5%BC%8F%E6%8E%A2%E8%AE%A8%E5%8F%8A%E5%85%B6%E5%BA%94%E7%94%A8&tn=SE_baiduxueshu_c1gjeupa 130 | name: "百度学术", 131 | test: /xueshu\.baidu\.com/, 132 | provider: BaiduXueshuProvider, 133 | }, 134 | { 135 | // 测试地址: http://tieba.baidu.com/p/5300844180 136 | name: "百度贴吧", 137 | test: /tieba\.baidu\.com/, 138 | provider: TiebaProvider, 139 | }, 140 | { 141 | // 测试地址: https://juejin.im/entry/59ac8fa551882524241a8802?utm_source=gold_browser_extension 142 | name: "掘金", 143 | test: /juejin\.(im|cn)/, 144 | provider: JuejinProvider, 145 | }, 146 | { 147 | name: "QQ邮箱", 148 | test: /mail\.qq\.com/, 149 | provider: QQMailProvider, 150 | }, 151 | { 152 | // 测试地址: https://addons.mozilla.org/zh-CN/firefox/addon/evernote-web-clipper/ 153 | name: "Mozilla", 154 | test: /addons\.mozilla\.org/, 155 | provider: MozillaProvider, 156 | }, 157 | { 158 | // 测试地址: https://www.jianshu.com/p/979776ca44b8 159 | // https://www.jianshu.com/p/fc8abc65bbb2 160 | name: "简书", 161 | test: /www\.jianshu\.com/, 162 | provider: JianShuProvider, 163 | }, 164 | { 165 | // 测试地址: https://www.douban.com/doulist/240962/ 166 | // 测试地址: https://www.douban.com/search?cat=1002&q=%E9%BB%91%E9%95%9C 167 | name: "豆瓣", 168 | test: /douban\.com/, 169 | provider: DouBanProvider, 170 | }, 171 | { 172 | // 测试地址: https://getpocket.com/a/recommended/ 173 | // 需要登陆 174 | name: "Pocket", 175 | test: /getpocket\.com/, 176 | provider: PocketProvider, 177 | }, 178 | { 179 | // 测试地址: https://www.dogedoge.com/results?q=chrome 180 | name: "DogeDoge", 181 | test: /www\.dogedoge\.com/, 182 | provider: DogeDogeProvider, 183 | }, 184 | { 185 | // 测试地址: https://51.ruyo.net/15053.html 186 | name: "Ruyo", 187 | test: /51\.ruyo\.net/, 188 | provider: RuyoProvider, 189 | }, 190 | { 191 | // 测试地址: https://steamcommunity.com/sharedfiles/filedetails/?id=1311535531 192 | name: "Steam", 193 | test: /steamcommunity\.com/, 194 | provider: SteamProvider, 195 | }, 196 | { 197 | // 测试地址: https://mijisou.com/?q=chrome&category_general=on&time_range=&language=zh-CN&pageno=1 198 | name: "秘迹", 199 | test: /mijisou\.com/, 200 | provider: MiJiProvider, 201 | }, 202 | { 203 | // 测试地址: https://github.com/axetroy/anti-redirect/issues/350 204 | name: "CSDN", 205 | test: /blog\.csdn\.net/, 206 | provider: CSDNProvider, 207 | }, 208 | { 209 | // 测试地址:https://my.oschina.net/chipo/blog/3067672 210 | name: "OS China", 211 | test: /oschina\.net/, 212 | provider: OSChinaProvider, 213 | }, 214 | { 215 | // 测试地址: https://github.com/axetroy/anti-redirect/issues/350 216 | name: "印象笔记", 217 | test: /app\.yinxiang\.com/, 218 | provider: YinXiangProvider, 219 | }, 220 | { 221 | // 测试地址: https://www.logonews.cn/2021073002420141.html 222 | name: "标志情报局", 223 | test: /www\.logonews\.cn/, 224 | provider: LogonewsProvider, 225 | }, 226 | { 227 | // 测试地址: https://afdian.net/a/xiaofanEric 228 | name: "爱发电", 229 | test: /afdian\.net/, 230 | provider: AfDianNetProvider, 231 | }, 232 | { 233 | // 测试地址: https://blog.51cto.com/u_11512826/2068421 234 | name: "51CTO博客", 235 | test: /blog\.51cto\.com/, 236 | provider: Blog51CTO, 237 | }, 238 | { 239 | // 测试地址: https://xie.infoq.cn/link?target=https%3A%2F%2Fwww.finclip.com%2F%3Fchannel%3Dinfoqseo 240 | name: 'InfoQ', 241 | test: /infoq\.cn/, 242 | provider: InfoQProvider 243 | }, 244 | { 245 | // 测试地址: https://gitee.com/Tencent/ncnn 246 | name: 'Gitee', 247 | test: /gitee.com/, 248 | provider: GiteeProvider 249 | }, 250 | { 251 | // 测试地址: https://sspai.com/post/77499 252 | name: '少数派', 253 | test: /sspai\.com/, 254 | provider: SSPaiProvider 255 | } 256 | ]) 257 | .bootstrap(); 258 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anti-redirect", 3 | "version": "2.21.7", 4 | "description": "去除重定向, 支持谷歌/百度/搜狗/360/知乎/贴吧/简书/豆瓣/微博...", 5 | "main": "./dist/anti-redirect.user.js", 6 | "scripts": { 7 | "test": "npm run build", 8 | "build": "cross-env NODE_ENV=production webpack --progress", 9 | "watch": "cross-env NODE_ENV=development webpack --progress --watch", 10 | "changelog": "npx conventional-changelog-cli -p angular -i CHANGELOG.md -s -r 0", 11 | "deploy": "npm run build && npx gh-pages -d ./dist", 12 | "lint": "rome check --apply-unsafe --formatter-enabled true --organize-imports-enabled true --linter-enabled true --verbose ./src", 13 | "format": "rome format --write ./src" 14 | }, 15 | "author": "Axetroy", 16 | "license": "MIT", 17 | "keywords": [ 18 | "greasy", 19 | "script", 20 | "javascript", 21 | "redirect" 22 | ], 23 | "devDependencies": { 24 | "@types/node": "18.16.10", 25 | "@types/webpack": "5.28.1", 26 | "cross-env": "7.0.3", 27 | "date-fns": "2.29.3", 28 | "rome": "^12.1.3", 29 | "ts-loader": "9.4.1", 30 | "ts-node": "10.9.1", 31 | "tsconfig-paths-webpack-plugin": "^4.1.0", 32 | "typescript": "^5.2.2", 33 | "webpack": "^5.88.2", 34 | "webpack-cli": "^5.1.4" 35 | }, 36 | "dependencies": { 37 | "gm-http": "^0.2.1", 38 | "lodash.debounce": "^4.0.8", 39 | "lodash.throttle": "^4.1.1", 40 | "p-retry": "^6.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /rome.json: -------------------------------------------------------------------------------- 1 | { 2 | "linter": { 3 | "enabled": true, 4 | "rules": { 5 | "recommended": true 6 | } 7 | }, 8 | "formatter": { 9 | "indentStyle": "space", 10 | "lineWidth": 120 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { IProvider, IProviderConstructor } from "./provider"; 2 | import { Marker, debounceDecorator, getRedirect, isInView, throttleDecorator } from "./utils"; 3 | 4 | type tester = () => boolean; 5 | 6 | interface IProviderConfig { 7 | name: string; 8 | test: RegExp | boolean | tester; 9 | provider: IProviderConstructor; 10 | } 11 | 12 | export interface IAppConfig { 13 | isDebug: boolean; 14 | } 15 | 16 | export class App { 17 | private config: IAppConfig; 18 | private provides: IProvider[] = []; 19 | constructor() { 20 | console.log( 21 | "%c Anti-Redirect %c Copyright \xa9 2015-%s %s", 22 | 'font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size:64px;color:#00bbee;-webkit-text-fill-color:#00bbee;-webkit-text-stroke: 1px #00bbee;', 23 | "font-size:12px;color:#999999;", 24 | new Date().getFullYear(), 25 | "\n" + "Author @Axetroy", 26 | ); 27 | console.log("[Anti Redirect]: 如果发现页面重定向未去除,欢迎反馈!"); 28 | console.log( 29 | `%c[Anti Redirect]: 支付宝搜索 "%c511118132%c" 领取红包支持作者!`, 30 | "font-size: 12px;", 31 | "font-size: 16px;color: red", 32 | "font-size: 12px;", 33 | ); 34 | } 35 | /** 36 | * A 标签是否匹配服务提供者 37 | * @param aElement 38 | * @param provider 39 | */ 40 | private isMatchProvider(aElement: HTMLAnchorElement, provider: IProvider): boolean { 41 | if (aElement.getAttribute(Marker.RedirectStatusDone)) { 42 | return false; 43 | } 44 | if (provider.test instanceof RegExp && !provider.test.test(aElement.href)) { 45 | return false; 46 | } 47 | if (typeof provider.test === "function" && !provider.test(aElement)) { 48 | return false; 49 | } 50 | if (provider.test instanceof Boolean) { 51 | return provider.test as boolean; 52 | } 53 | return true; 54 | } 55 | /** 56 | * 当鼠标移动到 A 标签上时 57 | * @param event 58 | */ 59 | @throttleDecorator(50) 60 | private onHover(event: Event) { 61 | const aElement: HTMLAnchorElement = event.target as HTMLAnchorElement; 62 | if (aElement.tagName !== "A") { 63 | return; 64 | } 65 | // trigger on hover handler 66 | for (const provider of this.provides) { 67 | if (this.isMatchProvider(aElement, provider)) { 68 | provider.resolve(aElement); 69 | } 70 | } 71 | } 72 | /** 73 | * 当页面滚动时 74 | */ 75 | @debounceDecorator(300) 76 | private onScroll() { 77 | // 筛选所有在可视区域内的A标签 78 | const visibleElements: HTMLAnchorElement[] = [].slice 79 | .call(document.querySelectorAll("a[href]")) 80 | .filter((aElement: HTMLAnchorElement) => { 81 | return aElement.href.indexOf("http") > -1 && isInView(aElement) && getRedirect(aElement) <= 2; 82 | }); 83 | // trigger scroll handler 84 | for (const provider of this.provides) { 85 | for (const aElement of visibleElements) { 86 | if (this.isMatchProvider(aElement, provider)) { 87 | provider.resolve(aElement); 88 | } 89 | } 90 | } 91 | } 92 | /** 93 | * 当页面准备就绪时,进行初始化动作 94 | */ 95 | private async pageOnReady() { 96 | for (const provider of this.provides) { 97 | if (provider.onInit) { 98 | await provider.onInit(); 99 | } 100 | // 如果页面处于初始的状态,没有滚动过,则出发一次onScroll事件 101 | if (window.scrollY <= 0) { 102 | this.onScroll(); 103 | } 104 | } 105 | } 106 | /** 107 | * 设置配置 108 | * @param config 109 | */ 110 | public setConfig(config: IAppConfig): this { 111 | this.config = config; 112 | return this; 113 | } 114 | /** 115 | * 注册服务提供者 116 | * @param providers 117 | */ 118 | public registerProvider(providers: IProviderConfig[]): this { 119 | for (const provideConfig of providers) { 120 | // test 如果是 boolean 121 | if (provideConfig.test === false) { 122 | continue; 123 | } 124 | // test 如果是正则表达式 125 | if (provideConfig.test instanceof RegExp && !provideConfig.test.test(document.domain)) { 126 | continue; 127 | } 128 | // test 如果是一个function 129 | if (typeof provideConfig.test === "function" && provideConfig.test() === false) { 130 | continue; 131 | } 132 | const provider = new provideConfig.provider(); 133 | provider.isDebug = this.config.isDebug; 134 | this.provides.push(provider); 135 | console.info(`[Anti-redirect]: 加载引擎 ${provideConfig.name}`); 136 | console.info(`当前页面: '${location.href}'`); 137 | } 138 | return this; 139 | } 140 | /** 141 | * 启动应用 142 | */ 143 | public bootstrap() { 144 | addEventListener("scroll", this.onScroll.bind(this)); 145 | addEventListener("mousemove", this.onHover.bind(this)); 146 | addEventListener("DOMContentLoaded", this.pageOnReady.bind(this)); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/provider.ts: -------------------------------------------------------------------------------- 1 | export type IProviderConstructor = new () => IProvider; 2 | 3 | export type tester = (aElement: HTMLAnchorElement) => boolean; 4 | 5 | export interface IProvider { 6 | isDebug?: boolean; 7 | test: RegExp | tester | boolean | null; 8 | onInit?(): Promise; 9 | resolve(aElementList: HTMLAnchorElement): void; 10 | } 11 | -------------------------------------------------------------------------------- /src/sites/51.ruyo.net.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class RuyoProvider implements IProvider { 5 | public test = /\/[^\?]*\?u=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("u")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/addons.mozilla.org.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect, matchLinkFromUrl } from "@/utils"; 3 | 4 | export class MozillaProvider implements IProvider { 5 | public test = /outgoing\.prod\.mozaws\.net\/v\d\/\w+\/(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, matchLinkFromUrl(aElement, this.test)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/afadian.net.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class AfDianNetProvider implements IProvider { 5 | public test = /afdian\.net\/link\?target=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("target")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/app.yinxiang.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class YinXiangProvider implements IProvider { 5 | public test = /^http:\/\//; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | // 编辑器 8 | if (aElement.hasAttribute("data-mce-href")) { 9 | if (!aElement.onclick) { 10 | antiRedirect(aElement, aElement.href, { force: true }); 11 | aElement.onclick = (e) => { 12 | // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向 13 | if (e.stopPropagation) { 14 | e.stopPropagation(); 15 | } 16 | aElement.setAttribute("target", "_blank"); 17 | window.top ? window.top.open(aElement.href) : window.open(aElement.href); 18 | }; 19 | } 20 | } 21 | // 分享页面 22 | else if (/^https:\/\/app\.yinxiang\.com\/OutboundRedirect\.action\?dest=/.test(aElement.href)) { 23 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("dest")); 24 | } 25 | } 26 | public async onInit(): Promise { 27 | const handler = (e) => { 28 | const dom = e.target as HTMLElement; 29 | 30 | const tagName = dom.tagName.toUpperCase(); 31 | 32 | switch (tagName) { 33 | case "A": { 34 | this.resolve(dom as HTMLAnchorElement); 35 | break; 36 | } 37 | case "IFRAME": { 38 | if (dom.hasAttribute("anti-redirect-handled")) { 39 | return; 40 | } 41 | dom.setAttribute("anti-redirect-handled", "1"); 42 | (dom as HTMLIFrameElement).contentWindow.document.addEventListener("mouseover", handler); 43 | break; 44 | } 45 | } 46 | }; 47 | 48 | document.addEventListener("mouseover", handler); 49 | return this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/sites/blog.51cto.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | 3 | export class Blog51CTO implements IProvider { 4 | public test = true; 5 | private container: HTMLElement; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | this.container = document.querySelector(".article-detail"); 8 | if (this.container?.contains(aElement)) { 9 | if (!aElement.onclick && aElement.href) { 10 | aElement.onclick = function antiRedirectOnClickFn(e) { 11 | e.stopPropagation(); 12 | e.preventDefault(); 13 | e.stopImmediatePropagation(); 14 | 15 | const $a = document.createElement("a"); 16 | 17 | $a.href = aElement.href; 18 | $a.target = aElement.target; 19 | 20 | $a.click(); 21 | }; 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/sites/blog.csdn.net.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class CSDNProvider implements IProvider { 5 | public test = /^https?:\/\//; 6 | private container: HTMLElement; 7 | public resolve(aElement: HTMLAnchorElement) { 8 | this.container = document.querySelector("#content_views"); 9 | if (this.container?.contains(aElement)) { 10 | if (!aElement.onclick && aElement.origin !== window.location.origin) { 11 | antiRedirect(aElement, aElement.href, { force: true }); 12 | aElement.onclick = (e) => { 13 | // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向 14 | if (e.stopPropagation) { 15 | e.stopPropagation(); 16 | } 17 | 18 | aElement.setAttribute("target", "_blank"); 19 | }; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/sites/daily.zhihu.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class ZhihuDailyProvider implements IProvider { 5 | public test = /zhihu\.com\/\?target=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("target")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/docs.google.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class GoogleDocsProvider implements IProvider { 5 | public test = /www\.google\.com\/url\?q=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("q")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/getpocket.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class PocketProvider implements IProvider { 5 | public test = /getpocket\.com\/redirect\?url=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("url")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/gitee.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class GiteeProvider implements IProvider { 5 | public test = /gitee\.com\/link\?target=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("target")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/gmail.google.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | export class GmailProvider implements IProvider { 4 | public test = true; 5 | private REDIRECT_PROPERTY = "data-saferedirecturl"; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | // 移除这个属性,那么 a 链接就不会跳转 8 | // FIXME: gmail 是多层 iframe 嵌套 9 | if (aElement.getAttribute(this.REDIRECT_PROPERTY)) { 10 | aElement.removeAttribute(this.REDIRECT_PROPERTY); 11 | antiRedirect(aElement, aElement.href); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/sites/infoq.cn.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class InfoQProvider implements IProvider { 5 | public test = /infoq\.cn\/link\?target=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("target")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/juejin.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class JuejinProvider implements IProvider { 5 | public test = /link\.juejin\.(im|cn)\/\?target=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | const finalURL = new URL(aElement.href).searchParams.get("target"); 8 | antiRedirect(aElement, finalURL); 9 | 10 | if (this.test.test(aElement.title)) { 11 | aElement.title = finalURL; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/sites/mail.qq.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | 3 | export class QQMailProvider implements IProvider { 4 | public test = true; 5 | private container: HTMLElement; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | this.container = document.querySelector("#contentDiv"); 8 | if (this.container?.contains(aElement)) { 9 | if (aElement.onclick) { 10 | aElement.onclick = (e) => { 11 | // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向 12 | if (e.stopPropagation) { 13 | e.stopPropagation(); 14 | } 15 | }; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/sites/mijisou.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class MiJiProvider implements IProvider { 5 | public test = /mijisou\.com\/url_proxy\?proxyurl=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("proxyurl")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/oschina.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class OSChinaProvider implements IProvider { 5 | public test = /oschina\.net\/action\/GoToLink\?url=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("url")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/play.google.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { Marker, antiRedirect } from "@/utils"; 3 | export class GooglePlayProvider implements IProvider { 4 | public test(aElement: HTMLAnchorElement) { 5 | if (/google\.com\/url\?q=(.*)/.test(aElement.href)) { 6 | return true; 7 | } else if (/^\/store\/apps\/details/.test(location.pathname)) { 8 | return true; 9 | } 10 | 11 | return false; 12 | } 13 | public resolve(aElement: HTMLAnchorElement) { 14 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("q")); 15 | 16 | // 移除开发者栏目下的重定向 17 | const eles = [].slice.call(document.querySelectorAll("a.hrTbp")); 18 | 19 | for (const ele of eles) { 20 | if (!ele.href) { 21 | continue; 22 | } 23 | if (ele.getAttribute(Marker.RedirectStatusDone)) { 24 | continue; 25 | } 26 | 27 | ele.setAttribute(Marker.RedirectStatusDone, ele.href); 28 | ele.setAttribute("target", "_blank"); 29 | 30 | ele.addEventListener( 31 | "click", 32 | (event) => { 33 | event.stopPropagation(); 34 | }, 35 | true, 36 | ); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/sites/sspai.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class SSPaiProvider implements IProvider { 5 | public test = /sspai\.com\/link\?target=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("target")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/steamcommunity.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class SteamProvider implements IProvider { 5 | public test = /steamcommunity\.com\/linkfilter\/\?url=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("url")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/tieba.baidu.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | export class TiebaProvider implements IProvider { 4 | public test = /jump\d*\.bdimg\.com/; 5 | public resolve(aElement: HTMLAnchorElement) { 6 | if (!this.test.test(aElement.href)) { 7 | return; 8 | } 9 | let url = ""; 10 | const text: string = aElement.innerText || aElement.textContent || ""; 11 | try { 12 | if (/https?:\/\//.test(text)) { 13 | url = decodeURIComponent(text); 14 | } 15 | } catch (e) { 16 | url = /https?:\/\//.test(text) ? text : ""; 17 | } 18 | if (url) { 19 | antiRedirect(aElement, url); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/sites/twitter.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class TwitterProvider implements IProvider { 5 | public test = /t\.co\/\w+/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | if (!this.test.test(aElement.href)) { 8 | return; 9 | } 10 | 11 | if (/https?:\/\//.test(aElement.title)) { 12 | const url: string = decodeURIComponent(aElement.title); 13 | 14 | antiRedirect(aElement, url); 15 | return; 16 | } 17 | 18 | const innerText = aElement.innerText.replace(/…$/, ""); 19 | 20 | if (/https?:\/\//.test(innerText)) { 21 | antiRedirect(aElement, innerText); 22 | return; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/sites/video.baidu.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | import http from "gm-http"; 4 | 5 | export class BaiduVideoProvider implements IProvider { 6 | public test = /v\.baidu\.com\/link\?url=/; 7 | public resolve(aElement: HTMLAnchorElement) { 8 | http 9 | .request({ 10 | url: aElement.href, 11 | method: "GET", 12 | anonymous: true, 13 | }) 14 | .then((res: Response$) => { 15 | if (res.finalUrl) { 16 | antiRedirect(aElement, res.finalUrl); 17 | } 18 | }) 19 | .catch((err) => { 20 | console.error(err); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/sites/weibo.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class WeboProvider implements IProvider { 5 | public test = /t\.cn\/\w+/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | if (!(this.test.test(aElement.href) && /^https?:\/\//.test(aElement.title))) { 8 | return; 9 | } 10 | 11 | const url: string = decodeURIComponent(aElement.title); 12 | 13 | if (url) { 14 | aElement.href = url; 15 | antiRedirect(aElement, url); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/sites/www.baidu.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect, decreaseRedirect, getRedirect, increaseRedirect } from "@/utils"; 3 | import http from "gm-http"; 4 | import pRetry from "p-retry"; 5 | 6 | export class BaiduProvider implements IProvider { 7 | public test = /www\.baidu\.com\/link\?url=/; 8 | public resolve(aElement: HTMLAnchorElement) { 9 | if (getRedirect(aElement) <= 2 && this.test.test(aElement.href)) { 10 | increaseRedirect(aElement); 11 | 12 | pRetry(() => this.handlerOneElement(aElement), { retries: 3 }) 13 | .then((res) => { 14 | decreaseRedirect(aElement); 15 | }) 16 | .catch((err) => { 17 | decreaseRedirect(aElement); 18 | }); 19 | } 20 | } 21 | 22 | private async handlerOneElement(aElement: HTMLAnchorElement): Promise { 23 | try { 24 | const res = await http.request({ 25 | url: aElement.href, 26 | method: "GET", 27 | anonymous: true, 28 | }); 29 | 30 | if (res.finalUrl) { 31 | antiRedirect(aElement, res.finalUrl); 32 | } 33 | 34 | return res; 35 | } catch (err) { 36 | console.error(err); 37 | return Promise.reject(new Error(`[http]: ${aElement.href} fail`)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/sites/www.dogedoge.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect, decreaseRedirect, getRedirect, increaseRedirect } from "@/utils"; 3 | import http from "gm-http"; 4 | 5 | export class DogeDogeProvider implements IProvider { 6 | public test = /www\.dogedoge\.com\/rd\/.{1,}/; 7 | public resolve(aElement: HTMLAnchorElement) { 8 | if (getRedirect(aElement) <= 2 && this.test.test(aElement.href)) { 9 | increaseRedirect(aElement); 10 | this.handlerOneElement(aElement) 11 | .then((res) => { 12 | decreaseRedirect(aElement); 13 | }) 14 | .catch((err) => { 15 | decreaseRedirect(aElement); 16 | }); 17 | } 18 | } 19 | 20 | private async handlerOneElement(aElement: HTMLAnchorElement): Promise { 21 | try { 22 | const res: Response$ = await http.request({ 23 | url: aElement.href, 24 | method: "GET", 25 | anonymous: true, 26 | }); 27 | if (res.finalUrl) { 28 | antiRedirect(aElement, res.finalUrl); 29 | } 30 | return res; 31 | } catch (err) { 32 | console.error(err); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/sites/www.douban.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class DouBanProvider implements IProvider { 5 | public test = /douban\.com\/link2\/?\?url=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("url")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/www.google.com.ts: -------------------------------------------------------------------------------- 1 | import { antiRedirect } from "../utils"; 2 | import { IProvider } from "@/provider"; 3 | 4 | export class GoogleProvider implements IProvider { 5 | public test = true; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | const traceProperties = ["ping", "data-jsarwt", "data-usg", "data-ved"]; 8 | 9 | // 移除追踪 10 | for (const property of traceProperties) { 11 | if (aElement.getAttribute(property)) { 12 | aElement.removeAttribute(property); 13 | } 14 | } 15 | 16 | // 移除多余的事件 17 | if (aElement.getAttribute("onmousedown")) { 18 | aElement.removeAttribute("onmousedown"); 19 | } 20 | 21 | // 尝试去除重定向 22 | if (aElement.getAttribute("data-href")) { 23 | const realUrl: string = aElement.getAttribute("data-href"); 24 | antiRedirect(aElement, realUrl); 25 | } 26 | 27 | const url = new URL(aElement.href); 28 | 29 | if (url.searchParams.get("url")) { 30 | antiRedirect(aElement, url.searchParams.get("url")); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/sites/www.jianshu.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class JianShuProvider implements IProvider { 5 | public test = (aElement: HTMLAnchorElement) => { 6 | const isLink1 = /links\.jianshu\.com\/go/.test(aElement.href); 7 | const isLink2 = /link\.jianshu\.com(\/)?\?t=/.test(aElement.href); 8 | const isLink3 = /jianshu\.com\/go-wild\/?\?(.*)url=/.test(aElement.href); 9 | 10 | if (isLink1 || isLink2 || isLink3) { 11 | return true; 12 | } 13 | 14 | return false; 15 | }; 16 | public resolve(aElement: HTMLAnchorElement) { 17 | const search = new URL(aElement.href).searchParams; 18 | antiRedirect(aElement, search.get("to") || search.get("t") || search.get("url")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/sites/www.logonews.cn.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class LogonewsProvider implements IProvider { 5 | public test = /link\.logonews\.cn\/\?url=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("url")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/www.so.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class SoProvider implements IProvider { 5 | public test = /so\.com\/link\?(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | const url = aElement.getAttribute("data-mdurl") || aElement.getAttribute("e-landurl"); 8 | 9 | if (url) { 10 | antiRedirect(aElement, url); 11 | } 12 | 13 | // remove track 14 | aElement.removeAttribute("e_href"); 15 | aElement.removeAttribute("data-res"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/sites/www.sogou.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect, decreaseRedirect, getRedirect, getText, increaseRedirect, queryParser } from "@/utils"; 3 | import http from "gm-http"; 4 | 5 | export class SoGouProvider implements IProvider { 6 | public test = /www\.sogou\.com\/link\?url=/; 7 | public async resolve(aElement: HTMLAnchorElement) { 8 | try { 9 | if (getRedirect(aElement) <= 2 && this.test.test(aElement.href)) { 10 | increaseRedirect(aElement); 11 | const res = await http.request({ 12 | url: aElement.href, 13 | method: "GET", 14 | anonymous: true, 15 | }); 16 | decreaseRedirect(aElement); 17 | const finalUrl = res.finalUrl; 18 | if (finalUrl && !this.test.test(finalUrl)) { 19 | antiRedirect(aElement, res.finalUrl); 20 | } else { 21 | const matcher = res.responseText.match(/URL=['"]([^'"]+)['"]/); 22 | if (matcher?.[1]) { 23 | antiRedirect(aElement, res.finalUrl); 24 | } 25 | } 26 | } 27 | } catch (err) { 28 | decreaseRedirect(aElement); 29 | console.error(err); 30 | } 31 | } 32 | private parsePage(res: Response$): void { 33 | const responseText: string = res.responseText.replace(/(src=[^>]*|link=[^>])/g, ""); 34 | const html: HTMLHtmlElement = document.createElement("html"); 35 | html.innerHTML = responseText; 36 | 37 | // let selector = '#main .results div.vrwrap>h3'; 38 | // let selector = '#main .results h3>a'; 39 | const selector = '#main .results a[href*="www.sogou.com/link?url="]'; 40 | const remotes = [].slice.call(html.querySelectorAll("#main .results a[href]")); 41 | const locals = [].slice.call(document.querySelectorAll(selector)); 42 | 43 | for (const localEle of locals) { 44 | for (const remoteEle of remotes) { 45 | let localText = getText(localEle); 46 | let remoteText = getText(remoteEle); 47 | 48 | // 通用按钮,例如【点击下载】 49 | if (localEle.classList.contains("str-public-btn")) { 50 | localText = getText(localEle.parentNode); 51 | remoteText = getText(remoteEle.parentNode); 52 | } else if (localEle.classList.contains("str_img")) { 53 | // 图片 54 | localText = getText(localEle.parentNode.parentNode); 55 | remoteText = getText(remoteEle.parentNode.parentNode); 56 | } 57 | 58 | if (!localText || localText !== remoteText) { 59 | return; 60 | } 61 | antiRedirect(localEle, remoteEle.href); 62 | } 63 | } 64 | } 65 | public async onInit() { 66 | if (!/www\.sogou\.com\/web/.test(window.top.location.href)) { 67 | return; 68 | } 69 | const query = queryParser(window.top.location.search); 70 | 71 | // 搜索使用http搜索,得到的是直接链接 72 | const url: string = `${location.protocol.replace(/:$/, "").replace("s", "")}://${ 73 | location.host + location.pathname + query 74 | }`; 75 | 76 | http 77 | .get(url) 78 | .then((res: Response$) => { 79 | this.parsePage(res); 80 | }) 81 | .catch((err) => { 82 | console.error(err); 83 | }); 84 | return this; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/sites/www.youtube.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class YoutubeProvider implements IProvider { 5 | public test = /www\.youtube\.com\/redirect\?.{1,}/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("q")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/www.zhihu.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class ZhihuProvider implements IProvider { 5 | public test = /zhihu\.com\/\?target=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("target")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/sites/xueshu.baidu.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class BaiduXueshuProvider implements IProvider { 5 | public test = /xueshu\.baidu\.com\/s?\?(.*)/; // 此处无用 6 | public resolve(aElement: HTMLAnchorElement) { 7 | const realHref: string = aElement.getAttribute("data-link") || aElement.getAttribute("data-url"); 8 | if (realHref) { 9 | antiRedirect(aElement, decodeURIComponent(realHref)); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/sites/zhuanlan.zhihu.com.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from "@/provider"; 2 | import { antiRedirect } from "@/utils"; 3 | 4 | export class ZhihuZhuanlanProvider implements IProvider { 5 | public test = /link\.zhihu\.com\/\?target=(.*)/; 6 | public resolve(aElement: HTMLAnchorElement) { 7 | antiRedirect(aElement, new URL(aElement.href).searchParams.get("target")); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as debounce from "lodash.debounce"; 2 | import * as throttle from "lodash.throttle"; 3 | 4 | export enum Marker { 5 | RedirectCount = "redirect-count", 6 | RedirectStatusDone = "anti-redirect-origin-href", 7 | } 8 | 9 | // rome-ignore lint/suspicious/noExplicitAny: allow return anything 10 | type DecoratorMethodFunction = (originMethod: Function, context: ClassMemberDecoratorContext) => any; 11 | 12 | /** 13 | * 根据url上的路径匹配,去除重定向 14 | * @param {HTMLAnchorElement} aElement 15 | * @param {RegExp} tester 16 | * @returns {boolean} 17 | */ 18 | export function matchLinkFromUrl(aElement: HTMLAnchorElement, tester: RegExp): string { 19 | const matcher: string[] = tester.exec(aElement.href); 20 | if (!(matcher?.length && matcher[1])) { 21 | return ""; 22 | } 23 | 24 | let url = ""; 25 | try { 26 | url = decodeURIComponent(matcher[1]); 27 | } catch (e) { 28 | url = /https?:\/\//.test(matcher[1]) ? matcher[1] : ""; 29 | } 30 | return url; 31 | } 32 | 33 | class Query { 34 | private object: Record = {}; 35 | 36 | constructor(public queryStr: string) { 37 | this.object = this.toObject(queryStr.replace(/^\?+/, "")); 38 | } 39 | 40 | private toObject(queryStr: string) { 41 | const obj: Record = {}; 42 | queryStr.split("&").forEach((item) => { 43 | const arr: string[] = item.split("=") || []; 44 | let key: string = arr[0] || ""; 45 | let value: string = arr[1] || ""; 46 | try { 47 | key = decodeURIComponent(arr[0] || ""); 48 | value = decodeURIComponent(arr[1] || ""); 49 | } catch (err) { 50 | // 51 | } 52 | if (key) { 53 | obj[key] = value; 54 | } 55 | }); 56 | return obj; 57 | } 58 | 59 | public toString(): string { 60 | const arr: string[] = []; 61 | for (const key in this.object) { 62 | if (Object.prototype.hasOwnProperty.call(this.object, key)) { 63 | const value = this.object[key]; 64 | arr.push(`${key}=${value}`); 65 | } 66 | } 67 | return arr.length ? `?${arr.join("&")}` : ""; 68 | } 69 | } 70 | 71 | export function queryParser(queryString: string): Query { 72 | return new Query(queryString); 73 | } 74 | 75 | export function getText(htmlElement: HTMLElement): string { 76 | return (htmlElement.innerText || htmlElement.textContent).trim(); 77 | } 78 | 79 | export function throttleDecorator(wait: number, options = {}): DecoratorMethodFunction { 80 | return (originMethod, context: ClassMemberDecoratorContext) => { 81 | return throttle(originMethod, wait, options); 82 | }; 83 | } 84 | 85 | export function debounceDecorator(wait: number, options = {}): DecoratorMethodFunction { 86 | return (originMethod, context: ClassMemberDecoratorContext) => { 87 | return debounce(originMethod, wait, options); 88 | }; 89 | } 90 | 91 | export function isInView(element: HTMLElement): boolean { 92 | const rect = element.getBoundingClientRect(); 93 | 94 | const vWidth = window.innerWidth || document.documentElement.clientWidth; 95 | const vHeight = window.innerHeight || document.documentElement.clientHeight; 96 | 97 | const efp = (x, y) => { 98 | return document.elementFromPoint(x, y); 99 | }; 100 | 101 | // Return false if it's not in the viewport 102 | if (rect.right < 0 || rect.bottom < 0 || rect.left > vWidth || rect.top > vHeight) { 103 | return false; 104 | } 105 | 106 | // Return true if any of its four corners are visible 107 | return ( 108 | element.contains(efp(rect.left, rect.top)) || 109 | element.contains(efp(rect.right, rect.top)) || 110 | element.contains(efp(rect.right, rect.bottom)) || 111 | element.contains(efp(rect.left, rect.bottom)) 112 | ); 113 | } 114 | 115 | export function getRedirect(aElement: HTMLAnchorElement): number { 116 | return +(aElement.getAttribute(Marker.RedirectCount) || 0); 117 | } 118 | 119 | export function increaseRedirect(aElement: HTMLAnchorElement): void { 120 | const num: number = getRedirect(aElement); 121 | aElement.setAttribute(Marker.RedirectCount, `${num}${1}`); 122 | } 123 | 124 | export function decreaseRedirect(aElement: HTMLAnchorElement): void { 125 | const num: number = getRedirect(aElement); 126 | if (num > 0) { 127 | aElement.setAttribute(Marker.RedirectCount, `${num - 1}`); 128 | } 129 | } 130 | 131 | interface IAntiRedirectOption { 132 | debug?: boolean; 133 | force?: boolean; 134 | } 135 | 136 | /** 137 | * 去除重定向 138 | * @param aElement A标签元素 139 | * @param realUrl 真实的地址 140 | * @param options 141 | */ 142 | export function antiRedirect(aElement: HTMLAnchorElement, realUrl: string, options: IAntiRedirectOption = {}) { 143 | options.debug = typeof options.debug === "undefined" ? process.env.NODE_ENV !== "production" : options.debug; 144 | 145 | options.force = options.force; 146 | 147 | if (!options.force && (!realUrl || aElement.href === realUrl)) { 148 | return; 149 | } 150 | if (options.debug) { 151 | aElement.style.backgroundColor = "green"; 152 | } 153 | aElement.setAttribute(Marker.RedirectStatusDone, aElement.href); 154 | aElement.href = realUrl; 155 | } 156 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "none", 5 | "target": "ES2015", 6 | "lib": ["es6", "dom"], 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "typeRoots": ["node_modules/@types"], 10 | "paths": { 11 | "@/*": ["src/*"] 12 | } 13 | }, 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by axetroy on 16-9-15. 3 | */ 4 | 5 | import { format } from "date-fns"; 6 | import * as path from "path"; 7 | import * as webpack from "webpack"; 8 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); 9 | const pkg = require("./package.json"); 10 | 11 | // webpack.config.js 12 | const webpackConfig: webpack.Configuration = { 13 | entry: { 14 | "anti-redirect": path.join(__dirname, "index.ts"), 15 | }, 16 | output: { 17 | path: path.join(__dirname, "/dist"), 18 | filename: "[name].user.js", 19 | }, 20 | resolve: { 21 | modules: ["node_modules"], 22 | extensions: [".js", ".ts"], 23 | plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })], 24 | }, 25 | module: { 26 | rules: [{ test: /\.tsx?$/, loader: "ts-loader" }], 27 | }, 28 | mode: "none", 29 | plugins: [ 30 | new webpack.DefinePlugin( 31 | (() => { 32 | const result = { "process.env.NODE_ENV": '"development"' }; 33 | for (const key in process.env) { 34 | if (process.env.hasOwnProperty(key)) { 35 | result[`process.env.${key}`] = JSON.stringify(process.env[key]); 36 | } 37 | } 38 | return result; 39 | })() 40 | ), 41 | new webpack.LoaderOptionsPlugin({ 42 | minimize: true, 43 | debug: false, 44 | }), 45 | new webpack.BannerPlugin({ 46 | banner: `// ==UserScript== 47 | // @name ${pkg.name} 48 | // @author ${pkg.author} 49 | // @description ${pkg.description} 50 | // @version ${pkg.version} 51 | // @update ${format(new Date(), "yyyy-MM-dd HH:mm:ss")} 52 | // @grant GM_xmlhttpRequest 53 | // @match *://www.baidu.com/* 54 | // @match *://tieba.baidu.com/* 55 | // @match *://v.baidu.com/* 56 | // @match *://xueshu.baidu.com/* 57 | // @include *://www.google* 58 | // @match *://www.google.com/* 59 | // @match *://docs.google.com/* 60 | // @match *://mail.google.com/* 61 | // @match *://play.google.com/* 62 | // @match *://www.youtube.com/* 63 | // @match *://encrypted.google.com/* 64 | // @match *://www.so.com/* 65 | // @match *://www.zhihu.com/* 66 | // @match *://daily.zhihu.com/* 67 | // @match *://zhuanlan.zhihu.com/* 68 | // @match *://weibo.com/* 69 | // @match *://twitter.com/* 70 | // @match *://www.sogou.com/* 71 | // @match *://juejin.im/* 72 | // @match *://juejin.cn/* 73 | // @match *://mail.qq.com/* 74 | // @match *://addons.mozilla.org/* 75 | // @match *://www.jianshu.com/* 76 | // @match *://www.douban.com/* 77 | // @match *://getpocket.com/* 78 | // @match *://www.dogedoge.com/* 79 | // @match *://51.ruyo.net/* 80 | // @match *://steamcommunity.com/* 81 | // @match *://mijisou.com/* 82 | // @match *://blog.csdn.net/* 83 | // @match *://*.blog.csdn.net/* 84 | // @match *://*.oschina.net/* 85 | // @match *://app.yinxiang.com/* 86 | // @match *://www.logonews.cn/* 87 | // @match *://afdian.net/* 88 | // @match *://blog.51cto.com/* 89 | // @match *://xie.infoq.cn/* 90 | // @match *://gitee.com/* 91 | // @match *://sspai.com/* 92 | // @connect www.baidu.com 93 | // @connect * 94 | // @compatible chrome 完美运行 95 | // @compatible firefox 完美运行 96 | // @supportURL https://github.com/axetroy/anti-redirect/issues/new/choose 97 | // @homepage https://github.com/axetroy/anti-redirect 98 | // @run-at document-start 99 | // @contributionURL troy450409405@gmail.com|alipay.com 100 | // @downloadURL https://github.com/axetroy/anti-redirect/raw/gh-pages/anti-redirect.user.js 101 | // @updateURL https://github.com/axetroy/anti-redirect/raw/gh-pages/anti-redirect.user.js 102 | // @namespace https://greasyfork.org/zh-CN/users/3400-axetroy 103 | // @license Anti 996 License; https://github.com/axetroy/anti-redirect/blob/master/LICENSE 104 | // ==/UserScript== 105 | 106 | // Github源码: https://github.com/axetroy/anti-redirect 107 | 108 | // 如果这能帮助到你,欢迎在 Github 上点击 star 和 follow. 109 | 110 | // 或者在支付宝搜索 " 511118132 " 领取红包 111 | 112 | // 你的支持就是我更新的动力 113 | 114 | `, 115 | entryOnly: true, 116 | raw: true, 117 | }), 118 | ], 119 | devtool: 'inline-source-map', 120 | }; 121 | 122 | module.exports = webpackConfig; 123 | --------------------------------------------------------------------------------