diff --git a/main.ts b/main.ts
index c99d3080..595dcacd 100644
--- a/main.ts
+++ b/main.ts
@@ -133,10 +133,16 @@ autoUpdater.on('update-not-available', (event) => {
});
autoUpdater.on('download-progress', (progressObj) => {
- let log_message = "Download speed: " + progressObj.bytesPerSecond;
- log_message = log_message + ' - Downloaded ' + progressObj.percent + '%';
- log_message = log_message + ' (' + progressObj.transferred + "/" + progressObj.total + ')';
- win.webContents.send('download_progress', log_message);
+ const downloadSpeed = Math.round((progressObj.bytesPerSecond / 1024) * 100) / 100;
+ const downloadPercentage = progressObj.percent;
+ const downloadTransferred = progressObj.transferred;
+ const downloadTotal = progressObj.total;
+ win.webContents.send('download_progress', {
+ downloadSpeed: downloadSpeed,
+ downloadPercentage: downloadPercentage,
+ downloadTransferred: downloadTransferred,
+ downloadTotal: downloadTotal
+ });
})
// Update has been downloaded
diff --git a/package.json b/package.json
index c55603f4..4239a41c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "novelscraper",
- "version": "2.0.0",
+ "version": "2.0.1",
"description": "App for downloading novels from pirate sites.",
"homepage": "https://github.com/HanaDigital/NovelScraper",
"author": {
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 2515ff88..d86de205 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -81,7 +81,21 @@
display: isUpdating ? 'block' : 'none'
}"
>
- UPDATING
+
{{ updateData.downloadPercentage }}%
+
+
+
+
+ {{ updateData.downloadSpeed }} Kb/s - ({{
+ updateData.downloadTransferred
+ }}
+ / {{ updateData.downloadTotal }})
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index 43ddcd08..4220c12b 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -178,8 +178,15 @@ body {
background: white;
position: relative;
+ .downloadInfo {
+ margin-top: 0.5em;
+ width: 100%;
+ text-align: center;
+ }
+
.promptWrapper {
display: flex;
+ flex-direction: column;
justify-content: center;
text-align: center;
bottom: 0px;
@@ -187,9 +194,15 @@ body {
padding: 10px;
box-shadow: inset 0px 0px 12px -5px rgba(0, 0, 0, 0.25);
transition: all 1s;
+
+ span {
+ margin-top: 1em;
+ }
}
.updating {
+ position: relative;
+ overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
@@ -199,21 +212,37 @@ body {
right: 0;
bottom: 0;
border-radius: 5px;
- padding: 10px;
box-shadow: inset 0px 0px 12px -5px rgba(0, 0, 0, 0.25);
- background: linear-gradient(
- -45deg,
- #74ccbc,
- rgb(140, 255, 240),
- #44beff,
- #00a2ff
- );
- background-size: 400% 400%;
- animation: load 5s ease infinite;
transition: all 1s;
- color: white;
+ color: #505050;
font-weight: bolder;
+
+ h3 {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .updatingBar {
+ height: 40px;
+ background: linear-gradient(
+ -45deg,
+ #74ccbc,
+ #8cfff0,
+ #44beff,
+ #00a2ff
+ );
+ background-size: 400% 400%;
+ animation: load 5s ease infinite;
+ box-shadow: inset 0px 0px 12px -5px rgba(0, 0, 0, 0.25);
+ transition: all 1s;
+ }
}
p {
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 68fc4198..24062d6a 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -14,6 +14,8 @@ export class AppComponent {
menuButtons: HTMLCollectionOf = document.getElementsByClassName("button");
+ updateData: any;
+
constructor(zone: NgZone) {
console.log('Production: ', AppConfig.production);
@@ -36,7 +38,7 @@ export class AppComponent {
});
ipcRenderer.on('download_progress', (event, arg) => {
- console.log(arg);
+ this.updateData = arg;
});
ipcRenderer.on('update_downloaded', () => {
@@ -61,7 +63,7 @@ export class AppComponent {
}
toggleUpdatePrompt(): void {
- this.isUpdate = false;
+ this.isUpdate = !this.isUpdate;
}
// Minimize Window
diff --git a/src/app/pages/novel/novel.component.ts b/src/app/pages/novel/novel.component.ts
index ac6ee5f4..c676f190 100644
--- a/src/app/pages/novel/novel.component.ts
+++ b/src/app/pages/novel/novel.component.ts
@@ -21,13 +21,13 @@ export class NovelComponent implements OnInit {
fromHome: boolean = history.state.fromHome
fromLibrary: boolean = history.state.fromLibrary
- progress: number = 0;
+ progress = 0;
downloadID: number;
checkDownload: NodeJS.Timeout;
- showModal: boolean = false;
- loading: boolean = false;
+ showModal = false;
+ loading = false;
constructor(private router: Router, public database: DatabaseService, public sourceManager: SourceManagerService) {
if (!this.source) this.source = {
@@ -76,6 +76,7 @@ export class NovelComponent implements OnInit {
addToLibrary(): void {
this.novel = this.database.addNovel(this.novel);
+ this.refresh();
}
download(): void {
diff --git a/src/app/pages/source-page/source-page.component.ts b/src/app/pages/source-page/source-page.component.ts
index 76f25ae5..050725b9 100644
--- a/src/app/pages/source-page/source-page.component.ts
+++ b/src/app/pages/source-page/source-page.component.ts
@@ -25,9 +25,9 @@ export class SourcePageComponent implements OnInit {
ngOnInit(): void {
if (!this.source) this.source = {
- name: "BoxNovel",
- link: "https://boxnovel.com",
- icon: "assets/img/sources/boxnovel-logo.png"
+ name: "NovelFull",
+ link: "https://novelfull.com",
+ icon: "assets/img/sources/novelfull-logo.png"
};
this.service = this.sourceManager.getService(this.source.name);
diff --git a/src/app/resources/sourceList.ts b/src/app/resources/sourceList.ts
index 4973fdad..cbbcaf46 100644
--- a/src/app/resources/sourceList.ts
+++ b/src/app/resources/sourceList.ts
@@ -1,6 +1,7 @@
import { sourcesList } from "./types"
export const sources: sourcesList = [
+ { name: "NovelFull", link: "https://novelfull.com", icon: "assets/img/sources/novelfull-logo.png" },
{ name: "BoxNovel", link: "https://boxnovel.com", icon: "assets/img/sources/boxnovel-logo.png" },
{ name: "ReadLightNovel", link: "https://www.readlightnovel.org", icon: "assets/img/sources/readlightnovel-logo.png" },
];
diff --git a/src/app/services/sources/novelfull.service.spec.ts b/src/app/services/sources/novelfull.service.spec.ts
new file mode 100644
index 00000000..251eeec3
--- /dev/null
+++ b/src/app/services/sources/novelfull.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { NovelfullService } from './novelfull.service';
+
+describe('NovelfullService', () => {
+ let service: NovelfullService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(NovelfullService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/services/sources/novelfull.service.ts b/src/app/services/sources/novelfull.service.ts
new file mode 100644
index 00000000..2c1e2d6d
--- /dev/null
+++ b/src/app/services/sources/novelfull.service.ts
@@ -0,0 +1,252 @@
+import { Injectable } from '@angular/core';
+import { chapterObj, novelObj } from "app/resources/types";
+import { DatabaseService } from "../database.service";
+import { NovelFactoryService } from "../novel-factory.service";
+import { sourceService } from "./sourceService";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class NovelfullService extends sourceService {
+
+ constructor(public database: DatabaseService, public novelFactory: NovelFactoryService) {
+ super(database);
+ }
+
+ async searchWIthLink(link: string, source: string, updatingInfo: boolean): Promise {
+ this.error = false;
+ this.searching = true;
+
+ let novel: novelObj = {}; // Declare novel object
+
+ // Check if the novel exists in the database
+ novel = this.database.getNovel(link)
+ if (novel && !updatingInfo) {
+ this.sourceNovels.unshift(novel);
+ return novel;
+ } else if (!updatingInfo) {
+ novel = {};
+ }
+
+ try {
+ const html = await this.getHtml(link); // Get HTML from the link
+
+ // Link
+ if (!updatingInfo) novel.link = link;
+
+ // Source
+ if (!updatingInfo) novel.source = source;
+
+ // InLibrary
+ if (!updatingInfo) novel.inLibrary = false; // Set as false to distinguish between novels already present
+
+ //////////////////////// YOUR CODE STARTS HERE ///////////////////////////////
+
+ // FIXME: Name
+ novel.name = html.getElementsByClassName('title')[0].textContent;
+
+ // FIXME: LatestChapter
+ novel.latestChapter = html.getElementsByClassName('l-chapter')[0].getElementsByTagName('a')[0].title;
+
+ // FIXME: Cover
+ novel.cover = html.getElementsByClassName('book')[0].getElementsByTagName('img')[0].src.replace('localhost:4200', 'novelfull.com');
+
+ // FIXME: TotalChapters
+ const lastPage = parseInt(html.getElementsByClassName('pagination')[0].getElementsByClassName('last')[0].getElementsByTagName('a')[0].getAttribute('data-page'));
+ let totalChapters = lastPage * 50;
+ const lastPageHtml = await this.getHtml(link + "?page=" + (lastPage + 1) + "&per-page=50");
+ totalChapters += lastPageHtml.getElementsByClassName('row')[1].getElementsByTagName('li').length;
+ novel.totalChapters = totalChapters;
+
+ // FIXME: Author(s)
+ novel.author = html.getElementsByClassName('info')[0].getElementsByTagName('a')[0].text;
+
+ // FIXME: Genre(s)
+ const genres = html.getElementsByClassName('info')[0].getElementsByTagName('div')[1].getElementsByTagName('a');
+ let genre = "";
+ for (let i = 0; i < genres.length; i++) {
+ genre += genres[i].innerText + ", ";
+ }
+ novel.genre = genre.slice(0, -2);
+
+ // FIXME: Summary
+ const summaryList = html.getElementsByClassName('desc-text')[0].getElementsByTagName('p');
+ try {
+ let summary = ""
+ for (let i = 0; i < summaryList.length; i++) {
+ summary += summaryList[i].innerText.trim() + "\n";
+ }
+ novel.summary = summary;
+ } catch (error) {
+ novel.summary = "N/A";
+ console.log(error);
+ }
+
+ //////////////////////// YOUR CODE ENDS HERE /////////////////////////////////
+
+ this.pushOrUpdateNovel(novel, updatingInfo);
+ } catch (error) {
+ console.error(error);
+ this.errorMessage = "ERROR FETCHING NOVEL";
+ this.error = true;
+ }
+
+ this.searching = false;
+ return novel;
+ }
+
+ async searchWithName(name: string, source: string): Promise {
+ this.error = false;
+ this.searching = true;
+
+ //////////////////////// [1] YOUR CODE STARTS HERE ///////////////////////////////
+
+ // FIXME: Generate the search link from novel name
+ name = encodeURI(name.replace(/ /g, '+')); // Replace spaces in novel name to a + for creating the search link
+ const searchLink = "https://novelfull.com/search?keyword=" + name; // Search link that will find the novels of this name
+
+ //////////////////////// YOUR CODE ENDS HERE /////////////////////////////////
+
+ const foundNovels: novelObj[] = []; // Will store the novels found from this name
+
+ try {
+ const html = await this.getHtml(searchLink);
+ let novel: novelObj;
+ //////////////////////// [2] YOUR CODE STARTS HERE ///////////////////////////////
+
+ // FIXME: Get the list of all search result elements
+ const novelList = html.getElementsByClassName('list-truyen')[0].getElementsByClassName('row');
+
+ //////////////////////// YOUR CODE ENDS HERE /////////////////////////////////
+
+ for (let i = 0; i < novelList.length; i++) {
+ novel = {};
+
+ // Source
+ novel.source = source;
+
+ //////////////////////// [3] YOUR CODE STARTS HERE ///////////////////////////////
+ console.log(novelList[i])
+ // FIXME: Link
+ novel.link = novelList[i].getElementsByClassName('truyen-title')[0].getElementsByTagName('a')[0].href.replace(/http:\/\/localhost:\d+/g, 'https://novelfull.com');
+
+ // FIXME: Name
+ novel.name = novelList[i].getElementsByClassName('truyen-title')[0].getElementsByTagName('a')[0].innerText;
+
+ // FIXME: LatestChapter
+ novel.latestChapter = novelList[i].getElementsByClassName('chapter-text')[0].textContent;
+
+ // FIXME: Cover
+ novel.cover = novelList[i].getElementsByTagName('img')[0].src.replace('localhost:4200', 'novelfull.com');
+ console.log(novel.cover);
+
+ // FIXME: TotalChapters
+ novel.totalChapters = 0; // If totalChapters is unknown, set it to 0 as it will not accept a string
+
+ // FIXME: Author(s)
+ novel.author = novelList[i].getElementsByClassName("author")[0].textContent;
+
+ // FIXME: Genre(s)
+ novel.genre = "unknown";
+
+ // FIXME: Summary
+ novel.summary = "unknown";
+
+ //////////////////////// YOUR CODE ENDS HERE /////////////////////////////////
+
+ // Check if novel is already in the searched novel list and remove it
+ this.sourceNovels = this.sourceNovels.filter(sourceNovel => sourceNovel.link !== novel.link);
+
+ // Check if the novel exists in the database
+ const libNovel = this.database.getNovel(novel.link);
+ if (libNovel) {
+ foundNovels.push(libNovel);
+ continue;
+ } else {
+ foundNovels.push(novel);
+ }
+
+ // Source
+ novel.source = source;
+ }
+ } catch (error) {
+ console.error(error)
+ this.errorMessage = "ERROR SEARCHING FOR NOVEL";
+ this.error = true;
+ }
+
+ this.searching = false;
+ this.sourceNovels = [...foundNovels, ...this.sourceNovels];
+ }
+
+ async download(novel: novelObj, downloadID: number): Promise {
+ let downloadedChapters: chapterObj[] = []; // List of download chapters
+
+ try {
+ const html = await this.getHtml(novel.link);
+
+ //////////////////////// [1] YOUR CODE STARTS HERE ///////////////////////////////
+
+ let chapterLinks = [];
+ let chapterNames = [];
+ const lastPage = parseInt(html.getElementsByClassName('pagination')[0].getElementsByClassName('last')[0].getElementsByTagName('a')[0].getAttribute('data-page')) + 1;
+ for (let i = 1; i <= lastPage; i++) {
+ const currentPageHtml = await this.getHtml(novel.link + "?page=" + i + "&per-page=50");
+ const chapters = currentPageHtml.getElementsByClassName('row')[1].getElementsByTagName('li');
+ for (let x = 0; x < chapters.length; x++) {
+ chapterLinks.push(chapters[x].getElementsByTagName('a')[0].href.replace(/http:\/\/localhost:\d+/g, 'https://novelfull.com'));
+ chapterNames.push(chapters[x].getElementsByTagName('a')[0].title);
+ }
+ }
+
+ //////////////////////// YOUR CODE ENDS HERE /////////////////////////////////
+
+ const update = this.update(novel, chapterLinks.length);
+ if (update.startIndex === -1) {
+ this.database.cancelDownload(downloadID);
+ this.database.updateDownloading(novel.link, false);
+ return;
+ }
+ else if (update.startIndex !== 0) {
+ downloadedChapters = update.updateChapters;
+ chapterLinks = chapterLinks.slice(update.startIndex);
+ chapterNames = chapterNames.slice(update.startIndex);
+ }
+
+ // Download each chapter at a time
+ for (let i = 0; i < chapterLinks.length; i++) {
+ if (this.database.isCanceled(downloadID)) {
+ this.database.updateDownloading(novel.link, false);
+ console.log('Download canceled!')
+ return;
+ }
+
+ const html = await this.getHtml(chapterLinks[i]);
+
+ //////////////////////// [2] YOUR CODE STARTS HERE ///////////////////////////////
+
+ // FIXME: you have the html of the chapter page
+ // Get the element that wraps all the paragraphs of the chapter
+ const chapterHtml = html.getElementsByClassName('chapter-c')[0];
+
+ //////////////////////// YOUR CODE ENDS HERE /////////////////////////////////
+
+ const chapterTitle = chapterNames[i];
+
+ let chapterBody = "" + chapterTitle + "
";
+ chapterBody += chapterHtml.outerHTML;
+
+
+ const chapter = this.prepChapter(novel, downloadID, chapterTitle, chapterBody, i, chapterLinks.length);
+ downloadedChapters.push(chapter);
+ }
+
+ this.novelFactory.generateEpub(novel, downloadedChapters, downloadID);
+
+ } catch (error) {
+ this.database.cancelDownload(downloadID);
+ this.database.updateDownloading(novel.link, false);
+ console.error(error);
+ }
+ }
+}
diff --git a/src/app/services/sources/readlightnovel-service.service.ts b/src/app/services/sources/readlightnovel-service.service.ts
index 5c63f590..f896396f 100644
--- a/src/app/services/sources/readlightnovel-service.service.ts
+++ b/src/app/services/sources/readlightnovel-service.service.ts
@@ -24,7 +24,7 @@ export class ReadlightnovelService extends sourceService {
if (novel && !updatingInfo) {
this.sourceNovels.unshift(novel);
return novel;
- } else {
+ } else if (!updatingInfo) {
novel = {};
}
diff --git a/src/app/services/sources/source-service-manager.service.ts b/src/app/services/sources/source-service-manager.service.ts
index 637f1d3e..fc4fbef4 100644
--- a/src/app/services/sources/source-service-manager.service.ts
+++ b/src/app/services/sources/source-service-manager.service.ts
@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { novelObj } from 'app/resources/types';
import { BoxnovelService } from './boxnovel.service';
+import { NovelfullService } from './novelfull.service';
import { ReadlightnovelService } from './readlightnovel-service.service';
import { sourceService } from './sourceService';
@@ -11,11 +12,12 @@ export class SourceManagerService {
isChecking = false;
- constructor(public boxnovelService: BoxnovelService, public readlightnovelService: ReadlightnovelService) { }
+ constructor(public boxnovelService: BoxnovelService, public readlightnovelService: ReadlightnovelService, public novelFullService: NovelfullService) { }
getService(sourceName: string): sourceService {
if (sourceName === "BoxNovel") return this.boxnovelService;
else if (sourceName === "ReadLightNovel") return this.readlightnovelService;
+ else if (sourceName === "NovelFull") return this.novelFullService;
else return undefined;
}
diff --git a/src/app/services/sources/sourceService.ts b/src/app/services/sources/sourceService.ts
index 472b987a..c3d9d8db 100644
--- a/src/app/services/sources/sourceService.ts
+++ b/src/app/services/sources/sourceService.ts
@@ -49,6 +49,7 @@ export class sourceService {
}
pushOrUpdateNovel(novel: novelObj, updatingInfo: boolean): void {
+ if (!novel.downloadedChapters) novel.downloadedChapters = 0;
if (!updatingInfo) {
this.sourceNovels = this.sourceNovels.filter(sourceNovel => sourceNovel.link !== novel.link);
this.sourceNovels.unshift(novel);
diff --git a/src/app/services/sources/sourceTemplate.ts b/src/app/services/sources/sourceTemplate.ts
index 612d19b9..a2a26171 100644
--- a/src/app/services/sources/sourceTemplate.ts
+++ b/src/app/services/sources/sourceTemplate.ts
@@ -26,7 +26,7 @@ export class ReadlightnovelService extends sourceService {
if (novel && !updatingInfo) {
this.sourceNovels.unshift(novel);
return novel;
- } else {
+ } else if (!updatingInfo) {
novel = {};
}