@@ -34,7 +34,7 @@ import { cacheInstance } from "@App/app/cache";
3434import { formatBytes } from "@App/pkg/utils/utils" ;
3535import { ScriptIcons } from "../options/routes/utils" ;
3636import { bytesDecode , detectEncoding } from "@App/pkg/utils/encoding" ;
37- import { toEncodedURL , prettyUrl } from "@App/pkg/utils/url-utils" ;
37+ import { prettyUrl } from "@App/pkg/utils/url-utils" ;
3838
3939const backgroundPromptShownKey = "background_prompt_shown" ;
4040
@@ -57,48 +57,6 @@ const closeWindow = (shouldGoBack: boolean) => {
5757 }
5858} ;
5959
60- const getCandidateUrls = ( targetUrlHref : string ) => {
61- const encodedUrl = toEncodedURL ( targetUrlHref ) ;
62- const inputU = new URL ( encodedUrl ) ;
63- const extraCandidateUrls = new Set < string > ( ) ;
64- extraCandidateUrls . add ( inputU . href ) ;
65-
66- const hostname = inputU . hostname ;
67- // 兼容 .greasyfork.org, cn-greasyfork.org
68- const hostText = `.${ hostname } ` . replace ( / \W / g, "." ) ;
69- const isGreasyFork = hostText . endsWith ( ".greasyfork.org" ) ;
70- const isSleazyFork = hostText . endsWith ( ".sleazyfork.org" ) ;
71-
72- if ( isGreasyFork || isSleazyFork ) {
73- // example:
74- // CASE 1
75- // raw 'https://update.greasyfork.org/scripts/550295/100%解锁CSDN文库vip文章阅读限制.user.js'
76- // encoded 'https://update.greasyfork.org/scripts/550295/100%25%E8%A7%A3%E9%94%81CSDN%E6%96%87%E5%BA%93vip%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%99%90%E5%88%B6.user.js'
77- // correct 'https://update.greasyfork.org/scripts/550295/100%25%E8%A7%A3%E9%94%81CSDN%E6%96%87%E5%BA%93vip%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%99%90%E5%88%B6.user.js'
78- // CASE 2
79- // raw 'https://update.greasyfork.org/scripts/519037/Nexus No Wait ++.user.js'
80- // encoded 'https://update.greasyfork.org/scripts/519037/Nexus%20No%20Wait%20++.user.js'
81- // correct 'https://update.greasyfork.org/scripts/519037/Nexus%20No%20Wait%20%2B%2B.user.js'
82- try {
83- const encodedPathname = inputU . pathname ;
84- const lastSlashIndex = encodedPathname . lastIndexOf ( "/" ) ;
85- const basePath = encodedPathname . substring ( 0 , lastSlashIndex ) ;
86- const fileName = encodedPathname . substring ( lastSlashIndex + 1 ) ;
87- const reEncodedFileName = encodeURIComponent ( decodeURI ( fileName ) ) ;
88- if ( reEncodedFileName !== fileName ) {
89- const reEncodedPathName = `${ basePath } /${ reEncodedFileName } ` ;
90- const reEncodedUrl = `${ inputU . origin } ${ reEncodedPathName } ${ inputU . search } ${ inputU . hash } ` ;
91- extraCandidateUrls . add ( reEncodedUrl ) ;
92- }
93- } catch ( e ) {
94- // can skip if it cannot be converted using decodeURI
95- console . warn ( e ) ; // just a warning for debug purpose.
96- }
97- }
98-
99- return [ ...extraCandidateUrls ] ;
100- } ;
101-
10260const fetchScriptBody = async ( url : string , { onProgress } : { [ key : string ] : any } ) => {
10361 let origin ;
10462 try {
@@ -702,44 +660,24 @@ function App() {
702660
703661 const targetUrlHref = useMemo ( ( ) => {
704662 if ( ! hasValidSourceParam ) {
705- /**
706- * 逻辑说明:
707- * 在 chrome.declarativeNetRequest 规则中,我们使用 `<,\1,>` 作为占位符引导 API 进行参数填充。
708- * 由于不同浏览器版本或配置对 URL 参数的自动编码(Auto-encoding)策略不一致,
709- * 我们通过检测该占位符的“被编码状态”来逆推浏览器采用了哪种编码方式。
710- */
711- let m ;
712663 let url ;
713664 try {
714- // 场景 1:URL 完全未编码。直接匹配原始特征符号 "<", ">" 和 ","
715- if ( ( m = / \b u r l = ( < , .+ , > ) ( & | $ ) / . exec ( location . search ) ?. [ 1 ] ) ) {
716- url = m ; // 未被编码,取原始值。
717- }
718- // 场景 2:URL 经过了部分编码(类似 encodeURI)。逗号 "," 未被编码,但尖括号被转义为 %3C, %3E
719- else if ( ( m = / \b u r l = ( % 3 C , .+ , % 3 E ) ( & | $ ) / . exec ( location . search ) ?. [ 1 ] ) ) {
720- url = decodeURI ( m ) ;
721- }
722- // 场景 3:URL 经过了完全编码(类似 encodeURIComponent)。逗号也被转义为 %2C
723- else if ( ( m = / \b u r l = ( % 3 C % 2 C .+ % 2 C % 3 E ) ( & | $ ) / . exec ( location . search ) ?. [ 1 ] ) ) {
724- url = decodeURIComponent ( m ) ;
725- }
665+ // 取url=之后的所有内容
666+ url = location . search . match ( / \? u r l = ( [ ^ & ] + ) / ) ?. [ 1 ] || "" ;
726667 } catch {
727668 // ignored
728669 }
729- // 如果正则匹配/标准解码失败,回退到标准的 searchParams 获取方式 (浏览器会自行理解和解码不规范的编码)
730- if ( ! url ) url = searchParams . get ( "url" ) || "" ; // fallback
731- // 移除人工注入的特征锚点 <, ,>,提取真实的 URL 内容
732- url = url . replace ( / ^ < , ( .+ ) , > $ / , "$1" ) ; // 去掉 <, ,>
733- if ( url ) {
734- try {
735- const urlObject = new URL ( url ) ;
736- // 验证解析后的 URL 是否具备核心要素,确保安全性与合法性
737- if ( urlObject . protocol && urlObject . hostname && urlObject . pathname ) {
738- return url ;
739- }
740- } catch {
741- // ignored
670+ if ( ! url ) {
671+ return "" ;
672+ }
673+ try {
674+ const urlObject = new URL ( url ) ;
675+ // 验证解析后的 URL 是否具备核心要素,确保安全性与合法性
676+ if ( urlObject . protocol && urlObject . hostname && urlObject . pathname ) {
677+ return url ;
742678 }
679+ } catch {
680+ // ignored
743681 }
744682 }
745683 return "" ;
@@ -751,39 +689,21 @@ function App() {
751689 errorStatusText : "" ,
752690 } ) ;
753691
754- const loadURLAsync = async ( candidateUrls : string [ ] ) => {
755- // 1. 定义获取单个脚本的内部逻辑,负责处理进度条与单次错误
756- const fetchValidScript = async ( ) => {
757- let firstError : unknown ;
758- for ( const url of candidateUrls ) {
759- try {
760- const result = await fetchScriptBody ( url , {
761- onProgress : ( info : { receivedLength : number } ) => {
762- setFetchingState ( ( prev ) => ( {
763- ...prev ,
764- loadingStatusText : t ( "downloading_status_text" , { bytes : formatBytes ( info . receivedLength ) } ) ,
765- } ) ) ;
766- } ,
767- } ) ;
768- if ( result . code && result . metadata ) {
769- return { result, url } ; // 找到有效的立即返回
770- }
771- } catch ( e ) {
772- if ( ! firstError ) firstError = e ;
773- }
774- }
775- // 如果循环结束都没成功,抛出第一个捕获到的错误或预设错误
776- throw firstError || new Error ( t ( "install_page_load_failed" ) ) ;
777- } ;
778-
692+ const loadURLAsync = async ( urlHref : string ) => {
779693 try {
780694 // 2. 执行获取
781- const { result, url } = await fetchValidScript ( ) ;
782- const { code, metadata } = result ;
695+ const { code, metadata } = await fetchScriptBody ( urlHref , {
696+ onProgress : ( info : { receivedLength : number } ) => {
697+ setFetchingState ( ( prev ) => ( {
698+ ...prev ,
699+ loadingStatusText : t ( "downloading_status_text" , { bytes : `${ formatBytes ( info . receivedLength ) } ` } ) ,
700+ } ) ) ;
701+ } ,
702+ } ) ;
783703
784704 // 3. 处理数据与缓存
785705 const uuid = uuidv4 ( ) ;
786- const scriptData = [ false , createScriptInfo ( uuid , code , url , "user" , metadata ) ] ;
706+ const scriptData = [ false , createScriptInfo ( uuid , code , urlHref , "user" , metadata ) ] ;
787707
788708 await cacheInstance . set ( `${ CACHE_KEY_SCRIPT_INFO } ${ uuid } ` , scriptData ) ;
789709
@@ -806,19 +726,10 @@ function App() {
806726 }
807727 } ;
808728
809- const handleUrlChangeAndFetch = ( targetUrlHref : string ) => {
810- setFetchingState ( ( prev ) => ( {
811- ...prev ,
812- loadingStatusText : t ( "install_page_please_wait" ) ,
813- } ) ) ;
814- const candidateUrls = getCandidateUrls ( targetUrlHref ) ;
815- loadURLAsync ( candidateUrls ) ;
816- } ;
817-
818729 // 有 url 的话下载内容
819730 useEffect ( ( ) => {
820- if ( targetUrlHref ) handleUrlChangeAndFetch ( targetUrlHref ) ;
821- // eslint-disable-next-line react-hooks/exhaustive-deps
731+ if ( ! targetUrlHref ) return ;
732+ loadURLAsync ( targetUrlHref ) ;
822733 } , [ targetUrlHref ] ) ;
823734
824735 if ( ! hasValidSourceParam ) {
0 commit comments