@@ -10,6 +10,8 @@ import {
1010 Check ,
1111 ChevronDown ,
1212 ChevronUp ,
13+ Film ,
14+ FileText ,
1315} from 'lucide-react'
1416import { useTranslation } from '../i18n'
1517
@@ -38,7 +40,7 @@ export default function EroScriptsPanel({ currentVideoName, scriptFolder }: EroS
3840 const [ username , setUsername ] = useState ( '' )
3941 const [ loggingIn , setLoggingIn ] = useState ( false )
4042 const [ expandedTopic , setExpandedTopic ] = useState < number | null > ( null )
41- const [ downloadLinks , setDownloadLinks ] = useState < Record < number , Array < { filename : string ; url : string } > > > ( { } )
43+ const [ downloadLinks , setDownloadLinks ] = useState < Record < number , Array < { filename : string ; url : string ; type : 'script' | 'video' } > > > ( { } )
4244 const [ downloading , setDownloading ] = useState < string | null > ( null )
4345 const [ downloaded , setDownloaded ] = useState < Set < string > > ( new Set ( ) )
4446
@@ -120,7 +122,7 @@ export default function EroScriptsPanel({ currentVideoName, scriptFolder }: EroS
120122 const resp = await window . electronAPI . eroscriptsFetch ( `${ BASE_URL } /t/${ topicId } .json` )
121123 if ( resp . ok && resp . data ) {
122124 const posts = resp . data . post_stream ?. posts || [ ]
123- const links : Array < { filename : string ; url : string } > = [ ]
125+ const links : Array < { filename : string ; url : string ; type : 'script' | 'video' } > = [ ]
124126
125127 for ( const post of posts ) {
126128 const cooked = post . cooked || ''
@@ -134,7 +136,7 @@ export default function EroScriptsPanel({ currentVideoName, scriptFolder }: EroS
134136 const fullUrl = url . startsWith ( 'http' ) ? url : `${ BASE_URL } ${ url } `
135137 const filename = safeDecodeURI ( linkText )
136138 if ( ! links . some ( l => l . url === fullUrl ) ) {
137- links . push ( { filename, url : fullUrl } )
139+ links . push ( { filename, url : fullUrl , type : 'script' } )
138140 }
139141 }
140142
@@ -145,7 +147,7 @@ export default function EroScriptsPanel({ currentVideoName, scriptFolder }: EroS
145147 const fullUrl = url . startsWith ( 'http' ) ? url : `${ BASE_URL } ${ url } `
146148 if ( ! links . some ( l => l . url === fullUrl ) ) {
147149 const filename = safeDecodeURI ( fullUrl . split ( '/' ) . pop ( ) || 'script' )
148- links . push ( { filename, url : fullUrl } )
150+ links . push ( { filename, url : fullUrl , type : 'script' } )
149151 }
150152 }
151153
@@ -156,12 +158,36 @@ export default function EroScriptsPanel({ currentVideoName, scriptFolder }: EroS
156158 const fullUrl = link . url . startsWith ( 'http' ) ? link . url : `${ BASE_URL } ${ link . url } `
157159 if ( ! links . some ( l => l . url === fullUrl ) ) {
158160 const rawName = link . title || link . url . split ( '/' ) . pop ( ) || 'script.funscript'
159- links . push ( { filename : safeDecodeURI ( rawName ) , url : fullUrl } )
161+ links . push ( { filename : safeDecodeURI ( rawName ) , url : fullUrl , type : 'script' } )
162+ }
163+ }
164+ }
165+ }
166+
167+ // Detect video/cloud links from cooked HTML and link_counts
168+ const allHrefRegex = / < a [ ^ > ] + h r e f = " ( [ ^ " ] * ) " [ ^ > ] * > / gi
169+ while ( ( match = allHrefRegex . exec ( cooked ) ) !== null ) {
170+ const url = match [ 1 ]
171+ const cloud = getCloudService ( url )
172+ if ( cloud && ! links . some ( l => l . url === url ) ) {
173+ links . push ( { filename : cloud . label , url, type : 'video' } )
174+ }
175+ }
176+
177+ if ( post . link_counts ) {
178+ for ( const link of post . link_counts ) {
179+ if ( link . url ) {
180+ const cloud = getCloudService ( link . url )
181+ if ( cloud && ! links . some ( l => l . url === link . url ) ) {
182+ links . push ( { filename : cloud . label , url : link . url , type : 'video' } )
160183 }
161184 }
162185 }
163186 }
164187 }
188+
189+ // Sort: scripts first, then video links
190+ links . sort ( ( a , b ) => ( a . type === b . type ? 0 : a . type === 'script' ? - 1 : 1 ) )
165191 setDownloadLinks ( prev => ( { ...prev , [ topicId ] : links } ) )
166192 }
167193 }
@@ -294,18 +320,29 @@ export default function EroScriptsPanel({ currentVideoName, scriptFolder }: EroS
294320 downloadLinks [ result . topicId ] . map ( link => (
295321 < button
296322 key = { link . url }
297- onClick = { ( ) => handleDownload ( link . url , link . filename ) }
298- disabled = { downloading === link . url }
299- className = "w-full flex items-center gap-2 px-2 py-1.5 bg-surface-300/50 hover:bg-surface-100/30 rounded text-[10px] transition-colors disabled:opacity-50"
323+ onClick = { ( ) => link . type === 'video' ? window . open ( link . url , '_blank' ) : handleDownload ( link . url , link . filename ) }
324+ disabled = { link . type === 'script' && downloading === link . url }
325+ className = { `w-full flex items-center gap-2 px-2 py-1.5 rounded text-[10px] transition-colors disabled:opacity-50 ${
326+ link . type === 'video'
327+ ? 'bg-blue-500/10 hover:bg-blue-500/20'
328+ : 'bg-surface-300/50 hover:bg-surface-100/30'
329+ } `}
300330 >
301- { downloaded . has ( link . url ) ? (
331+ { link . type === 'video' ? (
332+ < Film size = { 11 } className = "text-blue-400 flex-shrink-0" />
333+ ) : downloaded . has ( link . url ) ? (
302334 < Check size = { 11 } className = "text-green-400 flex-shrink-0" />
303335 ) : downloading === link . url ? (
304336 < RefreshCw size = { 11 } className = "animate-spin text-accent flex-shrink-0" />
305337 ) : (
306- < Download size = { 11 } className = "text-accent flex-shrink-0" />
338+ < FileText size = { 11 } className = "text-accent flex-shrink-0" />
339+ ) }
340+ < span className = { `truncate ${ link . type === 'video' ? 'text-blue-300' : 'text-text-secondary' } ` } >
341+ { link . filename }
342+ </ span >
343+ { link . type === 'video' && (
344+ < ExternalLink size = { 9 } className = "text-blue-400/50 flex-shrink-0 ml-auto" />
307345 ) }
308- < span className = "text-text-secondary truncate" > { link . filename } </ span >
309346 </ button >
310347 ) )
311348 ) : (
@@ -352,6 +389,39 @@ function safeDecodeURI(str: string): string {
352389 try { return decodeURIComponent ( str ) } catch { return str }
353390}
354391
392+ const CLOUD_SERVICES : Array < { pattern : RegExp ; label : string } > = [
393+ { pattern : / m e g a \. n z | m e g a \. c o \. n z / i, label : 'MEGA' } ,
394+ { pattern : / d r i v e \. g o o g l e \. c o m / i, label : 'Google Drive' } ,
395+ { pattern : / p i x e l d r a i n \. c o m / i, label : 'Pixeldrain' } ,
396+ { pattern : / d r o p b o x \. c o m / i, label : 'Dropbox' } ,
397+ { pattern : / m e d i a f i r e \. c o m / i, label : 'MediaFire' } ,
398+ { pattern : / g o f i l e \. ( i o | m e ) / i, label : 'GoFile' } ,
399+ { pattern : / w o r k u p l o a d \. c o m / i, label : 'WorkUpload' } ,
400+ { pattern : / a n o n f i l e s \. c o m / i, label : 'AnonFiles' } ,
401+ { pattern : / 1 f i c h i e r \. c o m / i, label : '1Fichier' } ,
402+ { pattern : / s e n d s p a c e \. c o m / i, label : 'SendSpace' } ,
403+ { pattern : / r a p i d g a t o r \. ( n e t | a s i a ) / i, label : 'Rapidgator' } ,
404+ { pattern : / u p l o a d e d \. ( n e t | t o ) / i, label : 'Uploaded' } ,
405+ { pattern : / k a t f i l e \. c o m / i, label : 'Katfile' } ,
406+ { pattern : / s p a n k b a n g \. c o m / i, label : 'SpankBang' } ,
407+ { pattern : / p o r n h u b \. c o m / i, label : 'Pornhub' } ,
408+ { pattern : / x h a m s t e r \. c o m / i, label : 'xHamster' } ,
409+ { pattern : / x v i d e o s \. c o m / i, label : 'XVideos' } ,
410+ { pattern : / e r o m e \. c o m / i, label : 'Erome' } ,
411+ { pattern : / r e d g i f s \. c o m / i, label : 'RedGIFs' } ,
412+ { pattern : / o n e d r i v e \. l i v e \. c o m | 1 d r v \. m s / i, label : 'OneDrive' } ,
413+ ]
414+
415+ function getCloudService ( url : string ) : { label : string } | null {
416+ // Skip EroScripts internal links
417+ if ( url . includes ( 'eroscripts.com' ) ) return null
418+ if ( url . includes ( '/uploads/' ) ) return null
419+ for ( const svc of CLOUD_SERVICES ) {
420+ if ( svc . pattern . test ( url ) ) return { label : svc . label }
421+ }
422+ return null
423+ }
424+
355425function isFunscriptUrl ( url : string ) : boolean {
356426 const lower = url . toLowerCase ( )
357427 return lower . includes ( '.funscript' ) || lower . includes ( '.zip' ) || lower . includes ( '.7z' ) || lower . includes ( '.rar' ) || lower . includes ( '/uploads/' )
0 commit comments