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 = {}; }