@@ -55,6 +55,53 @@ const closeWindow = (doBackwards: boolean) => {
5555 window . close ( ) ;
5656 }
5757} ;
58+ const resolveReachableUrl = async ( inputUrl : URL , fallbackUrl : string ) => {
59+ const candidateUrls = new Set < string > ( ) ;
60+ candidateUrls . add ( inputUrl . href ) ;
61+
62+ const hostname = inputUrl . hostname ;
63+ const isGreasyFork = `.${ hostname } ` . endsWith ( ".greasyfork.org" ) || `.${ hostname } ` . endsWith ( "cn-greasyfork.org" ) ;
64+
65+ if ( isGreasyFork ) {
66+ // example:
67+ // 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
68+ let decodedHref : string ;
69+ try {
70+ decodedHref = decodeURI ( inputUrl . href ) ;
71+ } catch {
72+ decodedHref = fallbackUrl ;
73+ }
74+ const lastSlashIndex = decodedHref . lastIndexOf ( "/" ) ;
75+ const basePath = decodedHref . substring ( 0 , lastSlashIndex ) ;
76+ const fileName = decodedHref . substring ( lastSlashIndex + 1 ) ;
77+ const reEncodedUrl = `${ basePath } /${ encodeURIComponent ( fileName ) } ` ;
78+ candidateUrls . add ( reEncodedUrl ) ;
79+ }
80+ candidateUrls . add ( fallbackUrl ) ;
81+ const requestOrigin = inputUrl . origin ;
82+ for ( const candidateUrl of candidateUrls ) {
83+ let response : Response | undefined ;
84+ try {
85+ response = await fetch ( candidateUrl , {
86+ method : "HEAD" ,
87+ headers : {
88+ "Cache-Control" : "no-cache" ,
89+ Accept :
90+ "text/javascript,application/javascript,text/plain,application/octet-stream,application/force-download" ,
91+ "Accept-Encoding" : "br;q=1.0, gzip;q=0.8, *;q=0.1" ,
92+ Origin : requestOrigin ,
93+ } ,
94+ referrer : requestOrigin + "/" ,
95+ } ) ;
96+ } catch {
97+ // ignored
98+ }
99+ if ( response ?. ok ) {
100+ return candidateUrl ;
101+ }
102+ }
103+ return "" ;
104+ } ;
58105
59106const fetchScriptBody = async ( url : string , { onProgress } : { [ key : string ] : any } ) => {
60107 let origin ;
@@ -652,21 +699,24 @@ function App() {
652699 return ! ! ( searchParams . get ( "uuid" ) || searchParams . get ( "file" ) ) ;
653700 } , [ searchParams ] ) ;
654701
655- const urlHref = useMemo ( ( ) => {
702+ const [ urlHref , setUrlHref ] = useState < string > ( "" ) ;
703+
704+ useEffect ( ( ) => {
705+ let resultAsync : PromiseLike < string > | string = "" ;
656706 try {
657707 if ( ! hasUUIDorFile ) {
658708 const url = searchParams . get ( "url" ) ;
659709 if ( url ) {
660710 const urlObject = new URL ( url ) ;
661711 if ( urlObject . protocol && urlObject . hostname && urlObject . pathname ) {
662- return urlObject . href ;
712+ resultAsync = resolveReachableUrl ( urlObject , url ) ;
663713 }
664714 }
665715 }
666716 } catch {
667717 // ignored
668718 }
669- return "" ;
719+ Promise . resolve ( resultAsync ) . then ( setUrlHref ) ;
670720 } , [ hasUUIDorFile , searchParams ] ) ;
671721
672722 const [ fetchingState , setFetchingState ] = useState ( {
0 commit comments