Skip to content

Commit 5a29923

Browse files
committed
Add mangalib.org domain and auto splitting large files
1 parent f670dff commit 5a29923

10 files changed

Lines changed: 318 additions & 62 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#### Браузерное расширение для загрузки манги | v1.0.1
44

55
[![Test Extension](https://github.com/ivanvit100/DownloadLib/actions/workflows/test.yaml/badge.svg)](https://github.com/ivanvit/DownloadLib/actions/workflows/test.yaml)
6-
![Code Coverage](https://img.shields.io/badge/Coverage-86.09%25-brightgreen)
6+
![Code Coverage](https://img.shields.io/badge/Coverage-87.65%25-brightgreen)
77

88
**DownloadLib** — расширение для браузера, позволяющее скачивать мангу с порталов [*MangaLib*](https://mangalib.me/) и [*RanobeLib*](https://ranobelib.me/) в форматах *FB2*, *EPUB* и *PDF*. Поддерживает автоматическую обработку изображений и текста, а также гибкие настройки скорости загрузки.
99

background/Background.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function detectServiceByReferer(details) {
4040

4141
if (referer.includes('ranobelib.me'))
4242
return 'ranobelib';
43-
if (referer.includes('mangalib.me'))
43+
if (referer.includes('mangalib.me') || referer.includes('mangalib.org'))
4444
return 'mangalib';
4545

4646
if (isImageRequest(details.url)) {
@@ -283,6 +283,7 @@ if (browserAPI && browserAPI.webRequest && browserAPI.webRequest.onBeforeRequest
283283
const tabUrl = details.documentUrl || details.initiator || details.originUrl || '';
284284
if (
285285
tabUrl.includes('mangalib.me') ||
286+
tabUrl.includes('mangalib.org') ||
286287
tabUrl.includes('ranobelib.me')
287288
) {
288289
isService = true;

core/DownloadManager.js

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @module core/DownloadManager
55
* @license MIT
66
* @author ivanvit
7-
* @version 1.0.0
7+
* @version 1.0.1
88
*/
99

1010
'use strict';
@@ -118,23 +118,66 @@
118118

119119
downloadState.chapters = chapters;
120120

121-
const chapterContents = await this.downloadChapters(
122-
service,
123-
downloadState,
124-
chapters,
125-
onProgress
126-
);
121+
const MAX_CHAPTERS_PER_FILE = serviceKey === 'mangalib' ? 80 : Infinity;
122+
123+
if (chapters.length > MAX_CHAPTERS_PER_FILE) {
124+
const totalParts = Math.ceil(chapters.length / MAX_CHAPTERS_PER_FILE);
125+
126+
for (let partIndex = 0; partIndex < totalParts; partIndex++) {
127+
await downloadState.controller.waitIfPaused();
128+
if (downloadState.controller.shouldStop()) break;
129+
130+
const startIdx = partIndex * MAX_CHAPTERS_PER_FILE;
131+
const endIdx = Math.min((partIndex + 1) * MAX_CHAPTERS_PER_FILE, chapters.length);
132+
const chaptersForPart = chapters.slice(startIdx, endIdx);
133+
134+
this.updateStatus(downloadId, `Часть ${partIndex + 1}/${totalParts}: Загрузка глав ${startIdx + 1}-${endIdx}...`, 10);
135+
136+
const chapterContents = await this.downloadChapters(
137+
service,
138+
downloadState,
139+
chaptersForPart,
140+
onProgress,
141+
startIdx,
142+
chapters.length
143+
);
144+
145+
this.updateStatus(downloadId, `Часть ${partIndex + 1}/${totalParts}: Создание ${format.toUpperCase()}...`, 90);
146+
const exporter = global.ExporterFactory.create(format);
147+
148+
const partSuffix = ` (Часть ${partIndex + 1} из ${totalParts})`
149+
const mangaWithSuffix = { ...manga, rus_name: (manga.rus_name || manga.name) + partSuffix };
150+
151+
const file = await exporter.export(mangaWithSuffix, chapterContents, coverBase64);
152+
153+
await this.saveFile(file.blob, file.filename);
154+
155+
downloadState.chapterContents = [];
156+
}
157+
158+
this.updateStatus(downloadId, 'Готово!', 100);
159+
this.eventBus.emit('download:completed', downloadState);
160+
161+
return { success: true, downloadId };
162+
} else {
163+
const chapterContents = await this.downloadChapters(
164+
service,
165+
downloadState,
166+
chapters,
167+
onProgress
168+
);
127169

128-
this.updateStatus(downloadId, `Создание ${format.toUpperCase()}...`, 95);
129-
const exporter = global.ExporterFactory.create(format);
130-
const file = await exporter.export(manga, chapterContents, coverBase64);
170+
this.updateStatus(downloadId, `Создание ${format.toUpperCase()}...`, 95);
171+
const exporter = global.ExporterFactory.create(format);
172+
const file = await exporter.export(manga, chapterContents, coverBase64);
131173

132-
await this.saveFile(file.blob, file.filename);
174+
await this.saveFile(file.blob, file.filename);
133175

134-
this.updateStatus(downloadId, 'Готово!', 100);
135-
this.eventBus.emit('download:completed', downloadState);
136-
137-
return { success: true, downloadId };
176+
this.updateStatus(downloadId, 'Готово!', 100);
177+
this.eventBus.emit('download:completed', downloadState);
178+
179+
return { success: true, downloadId };
180+
}
138181

139182
} catch (error) {
140183
console.error('[DownloadManager] Error:', error);
@@ -420,22 +463,23 @@
420463
};
421464
}
422465

423-
async downloadChapters(service, downloadState, chapters, onProgress) {
466+
async downloadChapters(service, downloadState, chapters, onProgress, startIndex = 0, totalChapters = null) {
424467
const results = [];
425-
const total = chapters.length;
468+
const total = totalChapters || chapters.length;
426469

427-
for (let i = 0; i < total; i++) {
470+
for (let i = 0; i < chapters.length; i++) {
428471
await downloadState.controller.waitIfPaused();
429472
if (downloadState.controller.shouldStop()) break;
430473

431-
downloadState.currentChapterIndex = i;
474+
downloadState.currentChapterIndex = startIndex + i;
432475

433476
const chapter = chapters[i];
434-
const progress = Math.floor((i / total) * 80) + 10;
477+
const globalIndex = startIndex + i;
478+
const progress = Math.floor((globalIndex / total) * 80) + 10;
435479

436480
this.updateStatus(
437481
downloadState.id,
438-
`Глава ${i + 1}/${total}: ${chapter.name || chapter.number}`,
482+
`Глава ${globalIndex + 1}/${total}: ${chapter.name || chapter.number}`,
439483
progress
440484
);
441485

core/ServiceRegistry.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @module core/ServiceRegistry
55
* @license MIT
66
* @author ivanvit
7-
* @version 1.0.0
7+
* @version 1.0.1
88
*/
99

1010
'use strict';
@@ -34,6 +34,7 @@
3434

3535
getServiceByUrl(url) {
3636
for (const [name, { instance, matcher }] of this.services) {
37+
console.log(name, matcher);
3738
try {
3839
if (matcher(url)) return instance;
3940
} catch (e) {

manifest.chrome.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "DownloadLib",
4-
"version": "1.0.1-beta",
4+
"version": "1.0.1",
55
"description": "Скачивает мангу с порталов MangaLib в удобном для чтения формате.",
66
"author": "https://ivanvit.ru",
77
"homepage_url": "https://github.com/ivanvit100/DownloadLib",
@@ -31,6 +31,7 @@
3131

3232
"host_permissions": [
3333
"https://mangalib.me/*",
34+
"https://mangalib.org/*",
3435
"https://ranobelib.me/*",
3536
"https://api.cdnlibs.org/*",
3637
"https://img3.mixlib.me/*",
@@ -61,6 +62,7 @@
6162
{
6263
"matches": [
6364
"https://mangalib.me/*",
65+
"https://mangalib.org/*",
6466
"https://ranobelib.me/*"
6567
],
6668
"js": ["background/RemoveAds.js"],
@@ -69,6 +71,7 @@
6971
{
7072
"matches": [
7173
"*://*.mangalib.me/*",
74+
"*://*.mangalib.org/*",
7275
"*://*.ranobelib.me/*",
7376
"*://*.imgslib.link/*"
7477
],

manifest.firefox.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
"host_permissions": [
5555
"https://mangalib.me/*",
56+
"https://mangalib.org/*",
5657
"https://ranobelib.me/*",
5758
"https://api.cdnlibs.org/*",
5859
"https://img3.mixlib.me/*",
@@ -74,6 +75,7 @@
7475
{
7576
"matches": [
7677
"https://mangalib.me/*",
78+
"https://mangalib.org/*",
7779
"https://ranobelib.me/*"
7880
],
7981
"js": ["background/RemoveAds.js"],
@@ -82,6 +84,7 @@
8284
{
8385
"matches": [
8486
"*://*.mangalib.me/*",
87+
"*://*.mangalib.org/*",
8588
"*://*.ranobelib.me/*",
8689
"*://*.imgslib.link/*"
8790
],

services/mangalib/MangaLibService.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @module services/mangalib/MangaLibService
55
* @license MIT
66
* @author ivanvit
7-
* @version 1.0.0
7+
* @version 1.0.1
88
*/
99

1010
'use strict';
@@ -22,7 +22,7 @@
2222
static matches(url) {
2323
try {
2424
const hostname = new URL(url).hostname;
25-
return /mangalib\.me$/i.test(hostname) || /imgslib\.link$/i.test(hostname);
25+
return /mangalib\.me$/i.test(hostname) || /imgslib\.link$/i.test(hostname) || /mangalib\.org$/i.test(hostname);
2626
} catch {
2727
return false;
2828
}

tests/core/DownloadManager.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,4 +731,41 @@ describe('DownloadManager', () => {
731731
logSpy.mockRestore();
732732
});
733733

734+
it('Calculates total parts using Math.ceil when chapters exceed max per file for mangalib', async () => {
735+
const dm = new DownloadManager();
736+
const chapters = [];
737+
for (let i = 1; i <= 181; i++) chapters.push({ volume: '1', number: String(i) });
738+
serviceMock.fetchChaptersList = vi.fn(async () => ({ data: chapters }));
739+
serviceMock.fetchChapter = vi.fn(async () => ({ data: { content: [{ type: 'text', text: 'ok' }] } }));
740+
const mathCeilSpy = vi.spyOn(Math, 'ceil');
741+
const saveFileSpy = vi.spyOn(dm, 'saveFile').mockResolvedValue();
742+
const delaySpy = vi.spyOn(dm, 'delay').mockResolvedValue();
743+
await dm.startDownload({ serviceKey: 'mangalib', url: 'https://site/manga/slug' });
744+
expect(mathCeilSpy).toHaveBeenCalledWith(181 / 80);
745+
expect(saveFileSpy).toHaveBeenCalledTimes(3);
746+
mathCeilSpy.mockRestore();
747+
saveFileSpy.mockRestore();
748+
delaySpy.mockRestore();
749+
}, 30000);
750+
751+
it('Breaks loop early when controller should stop', async () => {
752+
const dm = new DownloadManager();
753+
const chapters = [];
754+
for (let i = 1; i <= 181; i++) chapters.push({ volume: '1', number: String(i) });
755+
serviceMock.fetchChaptersList = vi.fn(async () => ({ data: chapters }));
756+
serviceMock.fetchChapter = vi.fn(async () => ({ data: { content: [{ type: 'text', text: 'ok' }] } }));
757+
const saveFileSpy = vi.spyOn(dm, 'saveFile').mockResolvedValue();
758+
const delaySpy = vi.spyOn(dm, 'delay').mockResolvedValue();
759+
const ctrl = dm.createController();
760+
let stopCalled = false;
761+
ctrl.shouldStop = () => {
762+
stopCalled = true;
763+
return true;
764+
};
765+
await dm.startDownload({ serviceKey: 'mangalib', url: 'https://site/manga/slug', controller: ctrl });
766+
expect(stopCalled).toBe(true);
767+
expect(saveFileSpy).toHaveBeenCalledTimes(0);
768+
saveFileSpy.mockRestore();
769+
delaySpy.mockRestore();
770+
});
734771
});

0 commit comments

Comments
 (0)