Skip to content

Commit 074e373

Browse files
committed
fixed some bugs
1 parent 17f3517 commit 074e373

7 files changed

Lines changed: 291 additions & 69 deletions

File tree

.github/workflows/build.yml

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -63,37 +63,38 @@ jobs:
6363
- name: Build Electron App
6464
env:
6565
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66-
run: npx electron-builder build --publish always ${{ matrix.args }}
66+
# 1. Changed to --publish never to stop the API collision
67+
run: npx electron-builder build --publish never ${{ matrix.args }}
6768

6869
- name: Prepare Release Notes
6970
if: startsWith(github.ref, 'refs/tags/') && matrix.os == 'macos-latest'
7071
shell: bash
7172
run: |
7273
cat <<EOF > release_notes.md
7374
## 📖 Universal Novel Scraper ${{ github.ref_name }}
74-
75-
Download the version specific to your operating system below.
76-
77-
### 💻 Installation & Security (Unsigned Apps)
75+
76+
Download the version specific to your operating system below.
77+
78+
### 💻 Installation & Security (Unsigned Apps)
7879
79-
#### 🍎 macOS (M1/M2/M3/M4 & Intel)
80-
Because this app is not signed with an Apple Developer Certificate, macOS will block it by default.
81-
82-
1. Download the `.dmg`, open it, and drag **UNS** to your **Applications** folder.
83-
2. Try to open the app. You will see a warning: *"UNS can’t be opened because it is from an unidentified developer."* Click **OK**.
84-
3. Open **System Settings** > **Privacy & Security**.
85-
4. Scroll down to the **Security** section. You will see a message: *"UNS was blocked from use because it is not from an identified developer."*
86-
5. Click **Open Anyway**.
87-
6. Enter your Mac password and click **Open** one last time.
80+
#### 🍎 macOS (M1/M2/M3/M4 & Intel)
81+
Because this app is not signed with an Apple Developer Certificate, macOS will block it by default.
82+
83+
1. Download the \`.dmg\`, open it, and drag **UNS** to your **Applications** folder.
84+
2. Try to open the app. You will see a warning: *"UNS can’t be opened because it is from an unidentified developer."* Click **OK**.
85+
3. Open **System Settings** > **Privacy & Security**.
86+
4. Scroll down to the **Security** section. You will see a message: *"UNS was blocked from use because it is not from an identified developer."*
87+
5. Click **Open Anyway**.
88+
6. Enter your Mac password and click **Open** one last time.
8889
89-
#### 🪟 Windows
90-
1. Download the `.exe` installer.
91-
2. If Windows SmartScreen blocks it, click **More Info** > **Run Anyway**.
92-
93-
#### 🐧 Linux
94-
1. Download the `.AppImage`.
95-
2. Make it executable: `chmod +x UNS-*.AppImage`.
96-
3. Run the file.
90+
#### 🪟 Windows
91+
1. Download the \`.exe\` installer.
92+
2. If Windows SmartScreen blocks it, click **More Info** > **Run Anyway**.
93+
94+
#### 🐧 Linux
95+
1. Download the \`.AppImage\`.
96+
2. Make it executable: \`chmod +x UNS-*.AppImage\`.
97+
3. Run the file.
9798
EOF
9899
99100
- name: Create or Update Release
@@ -103,6 +104,8 @@ jobs:
103104
name: Release ${{ github.ref_name }}
104105
body_path: ${{ matrix.os == 'macos-latest' && 'release_notes.md' || '' }}
105106
append_body: false
107+
# 2. Added this so the Linux/Windows runners don't crash looking for Mac files
108+
fail_on_unmatched_files: false
106109
files: |
107110
dist_electron/*.dmg
108111
dist_electron/*.exe

frontend/src/pages/Library.jsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ export default function Library() {
3838

3939
// --- DATA FETCHING & LOCAL STORAGE ---
4040
const fetchBooks = async (retryCount = 0) => {
41-
setIsLoading(true);
41+
// Only set loading to true on the very first attempt to avoid UI flickering
42+
if (retryCount === 0) setIsLoading(true);
43+
4244
try {
4345
const res = await fetch(`${API_BASE}/library`);
4446
if (res.ok) {
@@ -49,8 +51,10 @@ export default function Library() {
4951
throw new Error("Server not ready");
5052
}
5153
} catch (e) {
52-
if (retryCount < 5) {
53-
setTimeout(() => fetchBooks(retryCount + 1), 1000);
54+
// Increased to 15 retries, waiting 1.5 seconds each (up to ~22 seconds of patience)
55+
// This gives the Python engine plenty of time to boot on a cold start.
56+
if (retryCount < 15) {
57+
setTimeout(() => fetchBooks(retryCount + 1), 1500);
5458
} else {
5559
setIsLoading(false);
5660
}
@@ -59,6 +63,19 @@ export default function Library() {
5963

6064
useEffect(() => {
6165
fetchBooks();
66+
67+
if (window.electronAPI?.onEngineReady) {
68+
window.electronAPI.onEngineReady(() => {
69+
fetchBooks();
70+
});
71+
}
72+
73+
// Cleanup listener on unmount
74+
return () => {
75+
if (window.electronAPI?.removeEngineReadyListener) {
76+
window.electronAPI.removeEngineReadyListener();
77+
}
78+
};
6279
}, []);
6380

6481
useEffect(() => {
@@ -613,6 +630,11 @@ export default function Library() {
613630

614631
<div className={`absolute inset-0 bg-gradient-to-t from-zinc-950 via-zinc-950/50 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300 flex flex-col justify-end p-4 z-20 ${isSelectionMode ? "pointer-events-none" : ""}`}>
615632
<div className="flex justify-end gap-2 mb-2 translate-y-4 group-hover:translate-y-0 transition-transform duration-300">
633+
{isEpub && (
634+
<button onClick={(e) => { e.stopPropagation(); window.electronAPI?.openEpub(book.filename); }} className="p-2.5 bg-zinc-900/80 hover:bg-blue-600 text-zinc-300 hover:text-white rounded-xl border border-white/5 transition-colors shadow-xl" title="Open in System App">
635+
<ExternalLink size={18} />
636+
</button>
637+
)}
616638
<a href={`${API_BASE}/library/download/${encodeURIComponent(book.filename)}`} download onClick={(e) => e.stopPropagation()} className="p-2.5 bg-zinc-900/80 hover:bg-blue-600 text-zinc-300 hover:text-white rounded-xl border border-white/5 transition-colors shadow-xl" title="Download File"><Download size={18} /></a>
617639
<button onClick={(e) => { e.stopPropagation(); handleDelete(book.filename); }} className="p-2.5 bg-zinc-900/80 hover:bg-red-600 text-zinc-300 hover:text-white rounded-xl border border-white/5 transition-colors shadow-xl" title="Delete"><Trash2 size={18} /></button>
618640
</div>

frontend/src/pages/Settings.jsx

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
22
import {
33
Info, Package, User, Calendar, Hash, FileText, Github, Heart, Sparkles,
44
Zap, CheckCircle, Settings as SettingsIcon,
5-
ArrowUpCircle, RefreshCw, Download, Globe, Plus, Trash2, Box, ShoppingBag, Loader2
5+
ArrowUpCircle, RefreshCw, Download, Globe, Trash2, ShoppingBag, Loader2
66
} from 'lucide-react';
77

88
export default function Settings() {
@@ -12,6 +12,7 @@ export default function Settings() {
1212
const [latestVersion, setLatestVersion] = useState(null);
1313
const [updateAvailable, setUpdateAvailable] = useState(false);
1414
const [searchTerm, setSearchTerm] = useState('');
15+
const [downloadingUpdate, setDownloadingUpdate] = useState(false);
1516

1617
// Provider State
1718
const [installedProviders, setInstalledProviders] = useState([]);
@@ -22,7 +23,7 @@ export default function Settings() {
2223
name: "novel-scraper-desktop",
2324
productName: "UNS",
2425
description: "Desktop app for scraping web novels into EPUB format",
25-
version: "1.2.0", // Current Version
26+
version: "1.2.1",
2627
author: "Osama",
2728
license: "CC-BY-NC-4.0",
2829
appId: "com.universalnovelscraper.app",
@@ -31,11 +32,49 @@ export default function Settings() {
3132
};
3233

3334
useEffect(() => {
34-
checkForUpdates();
3535
refreshInstalled();
3636
if (activeTab === 'store') fetchStore();
37+
38+
if (window.electronAPI) {
39+
window.electronAPI.onUpdateAvailable((info) => {
40+
setLatestVersion(info.version);
41+
setUpdateAvailable(true);
42+
setUpdateLoading(false);
43+
});
44+
45+
window.electronAPI.onUpdateNotAvailable(() => {
46+
setUpdateAvailable(false);
47+
setUpdateLoading(false);
48+
});
49+
50+
window.electronAPI.onUpdateDownloaded(() => {
51+
setDownloadingUpdate(false);
52+
});
53+
54+
// 👈 NEW: Instantly stop all spinners if the connection fails
55+
window.electronAPI.onUpdateError((errorMessage) => {
56+
console.error("Update failed:", errorMessage);
57+
setUpdateLoading(false);
58+
setDownloadingUpdate(false);
59+
});
60+
}
61+
62+
return () => {
63+
if (window.electronAPI) window.electronAPI.removeUpdateListeners();
64+
};
3765
}, [activeTab]);
3866

67+
// Look how much cleaner this is!
68+
const checkForUpdates = () => {
69+
setUpdateLoading(true);
70+
window.electronAPI?.checkForUpdates();
71+
};
72+
73+
const handleUpdateNow = () => {
74+
setDownloadingUpdate(true);
75+
window.electronAPI?.downloadUpdate();
76+
};
77+
3978
const refreshInstalled = async () => {
4079
const list = await window.electronAPI?.getProviders() || [];
4180
setInstalledProviders(list);
@@ -51,7 +90,7 @@ export default function Settings() {
5190
// 2. Map the manifest data to include the download URL for each script
5291
const scripts = manifest.map(item => ({
5392
...item,
54-
id:item.id,
93+
id: item.id,
5594
name: item.name || item.id.charAt(0).toUpperCase() + item.id.slice(1),
5695
type: item.type,
5796
download_url: `https://raw.githubusercontent.com/${packageInfo.providersRepo}/main/${item.id}.js`
@@ -78,19 +117,6 @@ export default function Settings() {
78117
if (success) refreshInstalled();
79118
};
80119

81-
const checkForUpdates = async () => {
82-
setUpdateLoading(true);
83-
try {
84-
const response = await fetch(`https://api.github.com/repos/${packageInfo.repo}/releases/latest`);
85-
const data = await response.json();
86-
if (data.tag_name) {
87-
const latest = data.tag_name.replace('v', '');
88-
setLatestVersion(latest);
89-
if (latest !== packageInfo.version) setUpdateAvailable(true);
90-
}
91-
} catch (e) { } finally { setUpdateLoading(false); }
92-
};
93-
94120
return (
95121
<div className="max-w-5xl mx-auto px-6 animate-in fade-in duration-700">
96122
{/* Header */}
@@ -130,10 +156,10 @@ export default function Settings() {
130156
</h2>
131157
<button
132158
onClick={checkForUpdates}
133-
disabled={updateLoading}
134-
className="p-2 hover:bg-zinc-800 rounded-lg transition-colors text-zinc-400"
159+
disabled={updateLoading || downloadingUpdate}
160+
className="p-2 hover:bg-zinc-800 rounded-lg transition-colors text-zinc-400 disabled:opacity-50"
135161
>
136-
<RefreshCw size={16} className={updateLoading ? "animate-spin" : ""} />
162+
<RefreshCw size={16} className={(updateLoading || downloadingUpdate) ? "animate-spin" : ""} />
137163
</button>
138164
</div>
139165
<div className="p-6">
@@ -149,11 +175,21 @@ export default function Settings() {
149175
</div>
150176
</div>
151177
<button
152-
onClick={() => window.electronAPI?.openExternal(`https://github.com/${packageInfo.repo}/releases/latest`)}
153-
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-all"
178+
onClick={handleUpdateNow}
179+
disabled={downloadingUpdate}
180+
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-all disabled:opacity-50"
154181
>
155-
<Download size={16} />
156-
Update Now
182+
{downloadingUpdate ? (
183+
<>
184+
<Loader2 size={16} className="animate-spin" />
185+
Downloading...
186+
</>
187+
) : (
188+
<>
189+
<Download size={16} />
190+
Update Now
191+
</>
192+
)}
157193
</button>
158194
</div>
159195
) : (
@@ -299,7 +335,7 @@ export default function Settings() {
299335
// 🔍 Match Case-Insensitive for IDs
300336
const installed = installedProviders.find(p => p.id.toLowerCase() === script.id.toLowerCase());
301337
const needsUpdate = installed && installed.version !== script.version;
302-
338+
303339
return (
304340
<div key={script.id} className="p-5 bg-zinc-950/50 border border-zinc-800 rounded-2xl flex items-center justify-between hover:border-zinc-700 transition-all group">
305341
<div className="flex items-center gap-4">

main.js

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { app, BrowserWindow, ipcMain, shell, dialog } = require('electron');
2+
const { autoUpdater } = require('electron-updater');
23
const path = require('path');
34
const { execFile } = require('child_process');
45
const axios = require('axios');
@@ -16,6 +17,7 @@ let enableCloudflareBypass = false;
1617
let currentJobId = null;
1718
let waitingForHuman = false;
1819
let showBrowserWindow = false;
20+
autoUpdater.autoDownload = false;
1921

2022
// ============ PATHS ============
2123
const userDataPath = app.getPath('userData');
@@ -362,7 +364,7 @@ async function downloadDirectFile(event, jobData) {
362364
try {
363365
// 1. Navigate to the intermediate /slow_download/ page
364366
await scraperWindow.loadURL(jobData.start_url);
365-
367+
366368
// Wait for potential Cloudflare on the intermediate page
367369
const solved = await waitForCloudflareSolve(scraperWindow, jobData.job_id);
368370
if (!solved || scrapeCancelled) return;
@@ -373,7 +375,7 @@ async function downloadDirectFile(event, jobData) {
373375
let finalDownloadUrl = null;
374376
for (let i = 0; i < 30; i++) { // Wait up to 30 seconds
375377
if (scrapeCancelled) return;
376-
378+
377379
finalDownloadUrl = await scraperWindow.webContents.executeJavaScript(`
378380
(() => {
379381
// Check for the raw text URL Anna's Archive sometimes uses
@@ -408,9 +410,9 @@ async function downloadDirectFile(event, jobData) {
408410
await new Promise((resolve, reject) => {
409411
scraperWindow.webContents.session.once('will-download', (e, item) => {
410412
// Get the actual filename from the server (e.g., "book.epub" or "book.pdf")
411-
const fileName = item.getFilename();
413+
const fileName = item.getFilename();
412414
const destPath = path.join(outputDir, 'epubs', fileName);
413-
415+
414416
item.setSavePath(destPath);
415417

416418
item.on('updated', (event, state) => {
@@ -581,10 +583,73 @@ ipcMain.on('toggle-scraper-view', (e, show) => {
581583
else if (!waitingForHuman) win.hide();
582584
});
583585

586+
// ==========================================
587+
// AUTO-UPDATER IPC HANDLERS
588+
// ==========================================
589+
590+
// Tell the updater to check GitHub for a new release
591+
ipcMain.on('check-for-updates', () => {
592+
// If running locally, fake the response so the spinner doesn't get stuck
593+
if (!app.isPackaged) {
594+
console.log("Running in dev mode: Skipping real update check.");
595+
// Fake a 1-second delay so the UI animation looks natural, then stop it
596+
setTimeout(() => {
597+
if (mainWindow) mainWindow.webContents.send('update-not-available');
598+
}, 1000);
599+
return;
600+
}
601+
602+
// If in production, do the real check
603+
autoUpdater.checkForUpdates().catch(err => {
604+
console.error("Failed to check for updates:", err);
605+
if (mainWindow) mainWindow.webContents.send('update-error', err.message);
606+
});
607+
});
608+
609+
// Triggered when the user clicks "Update Now" in React
610+
ipcMain.on('download-update', () => {
611+
autoUpdater.downloadUpdate().catch(err => {
612+
console.error("Failed to download update:", err);
613+
});
614+
});
615+
616+
// Send an alert to React when an update is found on GitHub
617+
autoUpdater.on('update-available', (info) => {
618+
if (mainWindow) {
619+
mainWindow.webContents.send('update-available', info);
620+
}
621+
});
622+
623+
autoUpdater.on('update-not-available', () => {
624+
if (mainWindow) {
625+
mainWindow.webContents.send('update-not-available');
626+
}
627+
});
628+
629+
// Send an alert to React when the download hits 100%
630+
autoUpdater.on('update-downloaded', (info) => {
631+
if (mainWindow) {
632+
mainWindow.webContents.send('update-downloaded', info);
633+
}
634+
});
635+
636+
autoUpdater.on('error', (err) => {
637+
console.error('Auto-updater error:', err);
638+
if (mainWindow) {
639+
mainWindow.webContents.send('update-error', err.message || 'Update failed');
640+
}
641+
});
642+
584643
app.on('ready', () => {
585644
loadExternalProviders(); // Initial load of scripts
586645
startPythonBackend();
587646
createWindow();
647+
648+
if (app.isPackaged) {
649+
autoUpdater.checkForUpdates().catch(err => {
650+
console.error("Startup update check failed:", err);
651+
});
652+
}
588653
setTimeout(() => waitForEngine(mainWindow), 1000);
589654
});
590655

0 commit comments

Comments
 (0)