Skip to content

Commit 823731f

Browse files
sioaekoclaude
andcommitted
Add script storage folder setting
- New setting to specify a folder for saving/loading scripts - EroScripts downloads save to the configured folder - Video playback falls back to script folder when no local script found - Supports drag & drop fallback too Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 14dab89 commit 823731f

File tree

12 files changed

+67
-20
lines changed

12 files changed

+67
-20
lines changed

electron/main.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,28 @@ ipcMain.handle('fs:readDir', async (_event, dirPath: string) => {
124124
}
125125
})
126126

127-
ipcMain.handle('fs:readFunscript', async (_event, videoPath: string) => {
127+
ipcMain.handle('fs:readFunscript', async (_event, videoPath: string, scriptFolder?: string) => {
128128
const ext = path.extname(videoPath)
129+
const baseName = path.basename(videoPath, ext)
130+
131+
// 1. Check next to video file
129132
const scriptPath = videoPath.replace(ext, '.funscript')
130133
try {
131134
const content = fs.readFileSync(scriptPath, 'utf-8')
132135
return JSON.parse(content)
133-
} catch {
134-
return null
136+
} catch {}
137+
138+
// 2. Fallback: check script storage folder
139+
if (scriptFolder) {
140+
try {
141+
const fallbackPath = path.join(scriptFolder, baseName + '.funscript')
142+
const content = fs.readFileSync(fallbackPath, 'utf-8')
143+
console.log('[Script] Found in script folder:', fallbackPath)
144+
return JSON.parse(content)
145+
} catch {}
135146
}
147+
148+
return null
136149
})
137150

138151
ipcMain.handle('fs:saveFunscript', async (_event, videoPath: string, data: string) => {
@@ -641,13 +654,15 @@ ipcMain.handle('eroscripts:fetch', async (_event, url: string) => {
641654
}
642655
})
643656

644-
ipcMain.handle('eroscripts:download', async (_event, url: string) => {
657+
ipcMain.handle('eroscripts:download', async (_event, url: string, scriptFolder?: string, saveName?: string) => {
645658
try {
646-
const tempDir = path.join(app.getPath('temp'), 'scriptplayerplus-ero')
647-
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true })
659+
// Use script folder if set, otherwise temp
660+
const saveDir = scriptFolder || path.join(app.getPath('temp'), 'scriptplayerplus-ero')
661+
if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir, { recursive: true })
648662

649-
const fileName = decodeURIComponent(url.split('/').pop() || 'script.funscript')
650-
const localPath = path.join(tempDir, fileName)
663+
const fileName = saveName || decodeURIComponent(url.split('/').pop() || 'script.funscript')
664+
const localPath = path.join(saveDir, fileName)
665+
console.log('[EroScripts] Downloading to:', localPath)
651666

652667
await new Promise<void>((resolve, reject) => {
653668
const parsed = new URL(url)

electron/preload.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
1414

1515
// File system
1616
readDir: (path: string) => ipcRenderer.invoke('fs:readDir', path),
17-
readFunscript: (videoPath: string) => ipcRenderer.invoke('fs:readFunscript', videoPath),
17+
readFunscript: (videoPath: string, scriptFolder?: string) => ipcRenderer.invoke('fs:readFunscript', videoPath, scriptFolder),
1818
saveFunscript: (videoPath: string, data: string) => ipcRenderer.invoke('fs:saveFunscript', videoPath, data),
1919
getVideoUrl: (filePath: string) => ipcRenderer.invoke('fs:getVideoUrl', filePath),
2020

@@ -39,6 +39,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
3939
eroscriptsLogin: () => ipcRenderer.invoke('eroscripts:login'),
4040
eroscriptsLogout: () => ipcRenderer.invoke('eroscripts:logout'),
4141
eroscriptsFetch: (url: string) => ipcRenderer.invoke('eroscripts:fetch', url),
42-
eroscriptsDownload: (url: string) => ipcRenderer.invoke('eroscripts:download', url),
42+
eroscriptsDownload: (url: string, scriptFolder?: string, saveName?: string) => ipcRenderer.invoke('eroscripts:download', url, scriptFolder, saveName),
4343
eroscriptsGetCookies: () => ipcRenderer.invoke('eroscripts:getCookies'),
4444
})

src/App.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ export default function App() {
5858
const url = await window.electronAPI.getVideoUrl(file.path)
5959
setVideoUrl(url)
6060

61-
const script = await window.electronAPI.readFunscript(file.path)
61+
const script = await window.electronAPI.readFunscript(file.path, settings.scriptFolder)
6262
const parsed = script ? parseFunscript(script) : null
6363
setFunscript(parsed)
6464
setScriptUploadUrl(null)
6565

6666
if (parsed && handyService.isConnected) {
6767
uploadToHandy(parsed.actions)
6868
}
69-
}, [])
69+
}, [settings.scriptFolder])
7070

7171
const uploadToHandy = async (scriptActions: FunscriptAction[]) => {
7272
const url = await handyService.uploadAndSetup(scriptActions)
@@ -156,7 +156,7 @@ export default function App() {
156156
const url = await window.electronAPI.getVideoUrl(path)
157157
setVideoUrl(url)
158158

159-
const script = await window.electronAPI.readFunscript(path)
159+
const script = await window.electronAPI.readFunscript(path, settings.scriptFolder)
160160
const parsed = script ? parseFunscript(script) : null
161161
setFunscript(parsed)
162162
setScriptUploadUrl(null)
@@ -199,6 +199,7 @@ export default function App() {
199199
handyConnected={handyConnected}
200200
onHandyConnect={handleHandyConnect}
201201
onHandyDisconnect={handleHandyDisconnect}
202+
scriptFolder={settings.scriptFolder}
202203
/>
203204
<VideoPlayer
204205
videoUrl={videoUrl}

src/components/EroScriptsPanel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ interface EroScriptSearchResult {
2525

2626
interface EroScriptsPanelProps {
2727
currentVideoName: string | null
28+
scriptFolder?: string
2829
}
2930

30-
export default function EroScriptsPanel({ currentVideoName }: EroScriptsPanelProps) {
31+
export default function EroScriptsPanel({ currentVideoName, scriptFolder }: EroScriptsPanelProps) {
3132
const { t } = useTranslation()
3233
const [query, setQuery] = useState('')
3334
const [results, setResults] = useState<EroScriptSearchResult[]>([])
@@ -172,7 +173,7 @@ export default function EroScriptsPanel({ currentVideoName }: EroScriptsPanelPro
172173
return
173174
}
174175
setDownloading(url)
175-
const result = await window.electronAPI.eroscriptsDownload(url)
176+
const result = await window.electronAPI.eroscriptsDownload(url, scriptFolder, filename)
176177
if (result.ok) {
177178
setDownloaded(prev => new Set(prev).add(url))
178179
}

src/components/Settings.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,14 @@ function GeneralSection({
106106
try {
107107
const folderPath = await (window as any).electronAPI?.openFolder?.()
108108
if (folderPath) update('defaultVideoFolder', folderPath)
109-
} catch {
110-
// not available
111-
}
109+
} catch {}
110+
}
111+
112+
const handleBrowseScriptFolder = async () => {
113+
try {
114+
const folderPath = await (window as any).electronAPI?.openFolder?.()
115+
if (folderPath) update('scriptFolder', folderPath)
116+
} catch {}
112117
}
113118

114119
return (
@@ -143,6 +148,21 @@ function GeneralSection({
143148
</button>
144149
</FieldRow>
145150

151+
<Divider />
152+
153+
<FieldRow
154+
label={t('settings.scriptFolder')}
155+
description={settings.scriptFolder || t('settings.noFolderSelected')}
156+
>
157+
<button
158+
onClick={handleBrowseScriptFolder}
159+
className="flex items-center gap-1.5 px-3 py-1.5 bg-accent/10 hover:bg-accent/20 text-accent rounded text-xs transition-colors"
160+
>
161+
<FolderOpen size={12} />
162+
{t('settings.browse')}
163+
</button>
164+
</FieldRow>
165+
146166
</div>
147167
)
148168
}

src/components/Sidebar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ interface SidebarProps {
4949
handyConnected: boolean
5050
onHandyConnect: (key: string) => void
5151
onHandyDisconnect: () => void
52+
scriptFolder?: string
5253
}
5354

5455
interface FolderGroup {
@@ -81,6 +82,7 @@ export default function Sidebar({
8182
handyConnected,
8283
onHandyConnect,
8384
onHandyDisconnect,
85+
scriptFolder,
8486
}: SidebarProps) {
8587
const { t } = useTranslation()
8688
const [tab, setTab] = useState<'files' | 'search' | 'device'>('files')
@@ -244,6 +246,7 @@ export default function Sidebar({
244246
{tab === 'search' && (
245247
<EroScriptsPanel
246248
currentVideoName={currentFile ? getFileName(currentFile) : null}
249+
scriptFolder={scriptFolder}
247250
/>
248251
)}
249252

src/i18n/locales/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ const en: Record<string, string> = {
7373
'settings.timeline': 'Timeline',
7474
'settings.device': 'Device',
7575
'settings.eroscripts': 'EroScripts',
76+
'settings.scriptFolder': 'Script Storage Folder',
77+
'settings.scriptFolderDesc': 'Downloaded scripts are saved here and used as fallback when no local script is found',
7678
'settings.noFolderSelected': 'No folder selected',
7779
'settings.browse': 'Browse',
7880
'settings.autoLoadScriptDesc': 'Automatically load matching .funscript files when opening a video',

src/i18n/locales/ja.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const ja: Record<string, string> = {
6161
'settings.about': '\u60C5\u5831',
6262
'settings.version': '\u30D0\u30FC\u30B8\u30E7\u30F3',
6363
'settings.videoFolder': '\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u52D5\u753B\u30D5\u30A9\u30EB\u30C0\u30FC',
64+
'settings.scriptFolder': '\u30B9\u30AF\u30EA\u30D7\u30C8\u4FDD\u5B58\u30D5\u30A9\u30EB\u30C0\u30FC',
6465
'settings.chooseFolder': '\u30D5\u30A9\u30EB\u30C0\u30FC\u3092\u9078\u629E',
6566
'settings.keyboardShortcuts': '\u30AD\u30FC\u30DC\u30FC\u30C9\u30B7\u30E7\u30FC\u30C8\u30AB\u30C3\u30C8',
6667
'settings.playPause': '\u518D\u751F / \u4E00\u6642\u505C\u6B62',

src/i18n/locales/ko.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const ko: Record<string, string> = {
6161
'settings.about': '\uC815\uBCF4',
6262
'settings.version': '\uBC84\uC804',
6363
'settings.videoFolder': '\uAE30\uBCF8 \uBE44\uB514\uC624 \uD3F4\uB354',
64+
'settings.scriptFolder': '스크립트 저장 폴더',
6465
'settings.chooseFolder': '\uD3F4\uB354 \uC120\uD0DD',
6566
'settings.keyboardShortcuts': '\uD0A4\uBCF4\uB4DC \uB2E8\uCD95\uD0A4',
6667
'settings.playPause': '\uC7AC\uC0DD / \uC77C\uC2DC\uC815\uC9C0',

src/i18n/locales/zh.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const zh: Record<string, string> = {
6161
'settings.about': '\u5173\u4E8E',
6262
'settings.version': '\u7248\u672C',
6363
'settings.videoFolder': '\u9ED8\u8BA4\u89C6\u9891\u6587\u4EF6\u5939',
64+
'settings.scriptFolder': '\u811A\u672C\u5B58\u50A8\u6587\u4EF6\u5939',
6465
'settings.chooseFolder': '\u9009\u62E9\u6587\u4EF6\u5939',
6566
'settings.keyboardShortcuts': '\u952E\u76D8\u5FEB\u6377\u952E',
6667
'settings.playPause': '\u64AD\u653E / \u6682\u505C',

0 commit comments

Comments
 (0)