diff --git a/README.md b/README.md index 38ba33df..82b98763 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ npm start <-- Runs the app itself. - Open Folder Icon:
Icons made by Freepik from www.flaticon.com
- Error Icon:
Icons made by Vectors Market from www.flaticon.com
- Update Icon:
Icons made by Dave Gandy from www.flaticon.com
+- Browser Icon:
Icons made by Smashicons from www.flaticon.com
### License Creative Commons License
Novel Scraper by dr_nyt is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. diff --git a/attributions.txt b/attributions.txt index b21dfab0..aa0723ec 100644 --- a/attributions.txt +++ b/attributions.txt @@ -9,4 +9,5 @@ Open Folder Icon:
Icons made by Vectors Market from www.flaticon.com
Update Icon:
Icons made by Dave Gandy from www.flaticon.com
+Browser Icon:
Icons made by Smashicons from www.flaticon.com
diff --git a/logo.png b/logo.png new file mode 100644 index 00000000..db846bc6 Binary files /dev/null and b/logo.png differ diff --git a/main.ts b/main.ts index 782d508f..d925d680 100644 --- a/main.ts +++ b/main.ts @@ -4,10 +4,12 @@ import * as url from 'url'; const ipc = require('electron').ipcMain; const { autoUpdater } = require('electron-updater'); +const Splashscreen = require('@trodi/electron-splashscreen'); + var status = 0; let win: BrowserWindow = null; const args = process.argv.slice(1), - serve = args.some(val => val === '--serve'); + serve = args.some(val => val === '--serve'); function createWindow(): BrowserWindow { @@ -15,17 +17,44 @@ function createWindow(): BrowserWindow { const size = electronScreen.getPrimaryDisplay().workAreaSize; // Create the browser window. - win = new BrowserWindow({ - x: 0, - y: 0, + // win = new BrowserWindow({ + // x: 0, + // y: 0, + // width: 1060, + // height: 600, + // 'minWidth': 1060, + // 'minHeight': 500, + // frame: false, + // webPreferences: { + // nodeIntegration: true, + // allowRunningInsecureContent: (serve) ? true : false, + // }, + // }); + + const windowOptions = { width: 1060, height: 600, + center: true, 'minWidth': 1060, 'minHeight': 500, frame: false, webPreferences: { nodeIntegration: true, allowRunningInsecureContent: (serve) ? true : false, + } + }; + + autoUpdater.checkForUpdatesAndNotify(); + + win = Splashscreen.initSplashScreen({ + windowOpts: windowOptions, + templateUrl: path.join(__dirname, "splashScreen.html"), + delay: 0, // force show immediately since example will load fast + minVisible: 2000, // show for 1.5s so example is obvious + splashScreenOpts: { + height: 500, + width: 700, + transparent: true, }, }); @@ -104,7 +133,6 @@ ipc.on('closed', _ => { ipc.on('app_version', (event) => { event.sender.send('app_version', { version: app.getVersion() }); console.log('Checking for updates...'); - autoUpdater.checkForUpdatesAndNotify(); }); autoUpdater.on('update-available', () => { diff --git a/package.json b/package.json index 11f4d983..9ae1410e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "novelscraper", - "version": "1.0.0", + "version": "1.0.3", "description": "Educational app on how to web scrape novels from pirate sites.", "homepage": "https://github.com/dr-nyt/NovelScraper", "author": { @@ -8,8 +8,8 @@ "email": "shehryar.dev@gmail.com" }, "repository": { - "type" : "git", - "url" : "https://github.com/dr-nyt/NovelScraper.git" + "type": "git", + "url": "https://github.com/dr-nyt/NovelScraper.git" }, "keywords": [ "webscraper", @@ -63,7 +63,7 @@ "@typescript-eslint/parser": "2.20.0", "chai": "4.2.0", "codelyzer": "5.1.2", - "conventional-changelog-cli": "2.0.32", + "conventional-changelog-cli": "2.0.34", "core-js": "3.1.4", "cross-env": "7.0.0", "electron": "8.0.1", @@ -94,10 +94,11 @@ }, "dependencies": { "@angular/animations": "9.1.0", + "@trodi/electron-splashscreen": "1.0.0", "cloudscraper": "4.6.0", "electron-updater": "4.2.5", "fs-extra": "9.0.0", - "jquery": "3.4.1", + "jquery": "3.5.1", "mime": "2.4.4", "nconf": "0.10.0", "nodepub": "2.2.0", diff --git a/splashScreen.html b/splashScreen.html new file mode 100644 index 00000000..7135bb76 --- /dev/null +++ b/splashScreen.html @@ -0,0 +1,183 @@ + + + + + + + +
+ +

NovelScraper

+ +
+
+
+
+
+ +

CHECKING FOR UPDATES...

+
+ + diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 743e34e0..18bd1a86 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -5,6 +5,7 @@ import { PageNotFoundComponent } from './shared/components'; // Page routes import { SourcesComponent } from './sources/sources.component'; import { NovelplanetSourceComponent } from './novelplanet-source/novelplanet-source.component'; +import { ReadlightnovelSourceComponent } from './readlightnovel-source/readlightnovel-source.component'; import { BoxnovelSourceComponent } from './boxnovel-source/boxnovel-source.component'; import { LibraryComponent } from './library/library.component'; import { NovelComponent } from './novel/novel.component'; @@ -13,6 +14,7 @@ import { SettingComponent } from './setting/setting.component'; const routes: Routes = [ { path: 'sources', component: SourcesComponent }, { path: 'novelplanetSource', component: NovelplanetSourceComponent }, + { path: 'readlightnovelSource', component: ReadlightnovelSourceComponent }, { path: 'boxnovelSource', component: BoxnovelSourceComponent }, { path: 'novel', component: NovelComponent }, { path: 'library', component: LibraryComponent }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2589724c..0bad837a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -20,6 +20,7 @@ import { AppComponent } from './app.component'; import { SourcesComponent } from './sources/sources.component'; import { LibraryComponent } from './library/library.component'; import { NovelplanetSourceComponent } from './novelplanet-source/novelplanet-source.component'; +import { ReadlightnovelSourceComponent } from './readlightnovel-source/readlightnovel-source.component'; import { BoxnovelSourceComponent } from './boxnovel-source/boxnovel-source.component'; import { NovelComponent } from './novel/novel.component'; import { SettingComponent } from './setting/setting.component'; @@ -30,7 +31,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { } @NgModule({ - declarations: [AppComponent, SourcesComponent, LibraryComponent, NovelplanetSourceComponent, BoxnovelSourceComponent, NovelComponent, SettingComponent], + declarations: [AppComponent, SourcesComponent, LibraryComponent, NovelplanetSourceComponent, BoxnovelSourceComponent, NovelComponent, SettingComponent, ReadlightnovelSourceComponent], imports: [ BrowserModule, FormsModule, diff --git a/src/app/boxnovel-source/boxnovel-source.component.html b/src/app/boxnovel-source/boxnovel-source.component.html index 5fa9d44c..f94a2ae5 100644 --- a/src/app/boxnovel-source/boxnovel-source.component.html +++ b/src/app/boxnovel-source/boxnovel-source.component.html @@ -1,6 +1,7 @@

BoxNovel

+
diff --git a/src/app/boxnovel-source/boxnovel-source.component.ts b/src/app/boxnovel-source/boxnovel-source.component.ts index 48717b6d..1c311f32 100644 --- a/src/app/boxnovel-source/boxnovel-source.component.ts +++ b/src/app/boxnovel-source/boxnovel-source.component.ts @@ -6,6 +6,8 @@ import { LibraryService } from '../library.service'; // Import BoxNovel Service import { BoxnovelService } from '../boxnovel.service'; +const { shell } = require('electron'); + @Component({ selector: 'app-boxnovel-source', templateUrl: './boxnovel-source.component.html', @@ -18,9 +20,10 @@ export class BoxnovelSourceComponent implements OnInit { constructor(private router: Router, private library: LibraryService ,public boxnovelService: BoxnovelService) { } ngOnInit(): void { + document.getElementById("boxnovel-website").addEventListener("click", this.openWebsite); } - // Binded to novelplanet search bar + // Binded to boxnovel search bar search(val) { if(val == undefined || val == "") { return; @@ -34,6 +37,10 @@ export class BoxnovelSourceComponent implements OnInit { } } + openWebsite() { + shell.openExternal('https://boxnovel.com/') + } + // Load novelpage with the information of the novel clicked on loadNovelPage(novel) { this.router.navigateByUrl('/novel', { state: { novel: novel, source: 'boxnovel' } }); diff --git a/src/app/boxnovel.service.ts b/src/app/boxnovel.service.ts index c0239a59..b126464a 100644 --- a/src/app/boxnovel.service.ts +++ b/src/app/boxnovel.service.ts @@ -63,13 +63,19 @@ export class BoxnovelService { let startIndex = 0; + // Update novel if already downloaded let novel = this.library.getNovel(link); - if (novel.state.downloaded) { - console.log('Loading existing chapters...'); - let chapters = fs.readFileSync(novel.info.folderPath + '\\chapters.json'); - let chapterList = JSON.parse(chapters); - downloadedChapters = chapterList.chapters; - startIndex = downloadedChapters.length; + try { + if (novel.state.downloaded) { + console.log('Loading existing chapters...'); + let chapters = fs.readFileSync(novel.info.folderPath + '\\chapters.json'); + let chapterList = JSON.parse(chapters); + downloadedChapters = chapterList.chapters; + startIndex = downloadedChapters.length; + } + } catch(error) { + console.log(error); + console.log("Couldn't load update files. Starting download from start."); } // Download each chapter at a time @@ -81,11 +87,12 @@ export class BoxnovelService { let stringHtml = await this.getHtmlString(chapterLinks[i]); let pageHtml = new DOMParser().parseFromString(stringHtml, 'text/html'); - let html = pageHtml.getElementsByClassName('entry-content')[0]; + let chapterHtml = pageHtml.getElementsByClassName('entry-content')[0]; let chapterTitle = chapterNames[i]; - let chapterBody = html.outerHTML; + let chapterBody = "

" + chapterTitle + "

"; + chapterBody += chapterHtml.outerHTML; chapterBody += "

" chapterBody += "

dr-nyt's NovelScraper scraped this novel from a pirate site.

" chapterBody += "

If you can, please support the author(s) of this novel: " + novel.info.author + "

" @@ -106,6 +113,7 @@ export class BoxnovelService { async fetchFromLink(link) { try { + // Remove any duplicates of the novel for(let novel of this.localNovels) { if(novel.info.link == link) { this.localNovels.splice( this.localNovels.indexOf(novel), 1 ); @@ -118,7 +126,7 @@ export class BoxnovelService { let latestChapter = ""; let cover = ""; let totalChapters = 0; - let source = "novelplanet"; + let source = "boxnovel"; let author = "unknown"; let genre = ""; let summary = "" diff --git a/src/app/novel/novel.component.html b/src/app/novel/novel.component.html index 89c1f799..95c58587 100644 --- a/src/app/novel/novel.component.html +++ b/src/app/novel/novel.component.html @@ -1,21 +1,38 @@

{{pageSource}}

+
-
+
+
+
+

Are you sure you want to delete this novel?

+

Downloaded files will also be deleted!

+ +
+ + +
+
+
+
+
+

NovelPlanet has shutdown. Please switch to another source. PRESSING THE DELETE BUTTON WILL ALSO DELETE THE FILES so be carefull.

+
+
@@ -57,7 +74,7 @@

{{novel.info.name}}

- +
diff --git a/src/app/novel/novel.component.scss b/src/app/novel/novel.component.scss index c5c3b39c..64f18867 100644 --- a/src/app/novel/novel.component.scss +++ b/src/app/novel/novel.component.scss @@ -2,7 +2,7 @@ .page { - #coverChangeDialogue { + .dialogue { position: fixed; width: 100%; height: 100%; @@ -52,6 +52,51 @@ } } + #removeDialogue { + display: none; + color: white; + justify-content: center; + align-items: center; + + .dialogueBox { + width: 30%; + background: $content-background; + padding: 1em; + margin-bottom: 1em; + border-radius: 5px; + text-align: center; + + .warning { + color: rgb(255, 121, 121); + font-weight: bold; + } + + .buttons { + margin-top: 1em; + display: flex; + justify-content: space-evenly; + + input { + cursor: pointer; + width: 30%; + padding: 0.7em; + border-radius: 5px; + border: none; + } + + .yesButton { + background: rgb(117, 39, 39); + color: white; + } + + .noButton { + background: #0065a3; + color: white; + } + } + } + } + .content { .novelNav { @@ -73,6 +118,24 @@ } } + .deprecated { + display: none; + width: 100%; + padding: 5px; + padding-left: 30px; + padding-right: 30px; + margin-bottom: 0.7em; + p { + background: rgb(117, 39, 39); + padding: 1em; + color: white; + + b { + color: yellow; + } + } + } + .wrapper { padding-left: 30px; padding-right: 30px; diff --git a/src/app/novel/novel.component.ts b/src/app/novel/novel.component.ts index cc580cc6..51d46c7f 100644 --- a/src/app/novel/novel.component.ts +++ b/src/app/novel/novel.component.ts @@ -1,13 +1,17 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, NgZone } from '@angular/core'; import { Router } from '@angular/router'; // Import Library Service import { LibraryService } from '../library.service'; // Import Novelplanet Service import { NovelplanetService } from '../novelplanet.service'; +// Import Readlightnovel Service +import { ReadlightnovelService } from '../readlightnovel.service'; // Import BoxNovel Service import { BoxnovelService } from '../boxnovel.service'; +const { shell } = require('electron'); + import { ThrowStmt } from '@angular/compiler'; @Component({ @@ -31,6 +35,7 @@ export class NovelComponent implements OnInit { inLibrary: boolean = false; downloaded: boolean = false; downloading: boolean = false; + showRemoveDialogue: boolean = false; // Index of the downloadTracker in the library downloadID: number = 0; @@ -42,10 +47,13 @@ export class NovelComponent implements OnInit { private router: Router, public library: LibraryService, public novelplanetService: NovelplanetService, - private boxnovelService: BoxnovelService) { } + private boxnovelService: BoxnovelService, + public readlightnovelService: ReadlightnovelService, + private ngZone: NgZone) { } // On init ngOnInit(): void { + // document.getElementById("novel-website").addEventListener("click", this.openWebsite); this.coverChangeDialogue = document.getElementById("coverChangeDialogue"); this.coverChangeDialogue.addEventListener('click', (event) => { if (event.target == this.coverChangeDialogue) { @@ -53,24 +61,23 @@ export class NovelComponent implements OnInit { } }); - // For testing purposes only if (this.novel === undefined) { this.novel = { info: { - "link": "https://novelplanet.com/Novel/Dragon-God-of-Blood", - "name": "Tales Of Demons and Gods", - "latestChapter": "Chapter 1365 - The Mysterious Deskmate (125) (Ongoing)", - "cover": "assets/test/tldg.jpg", - "totalChapters": 2208, - "source": "novelplanet", - "author": "KillerBee", - "genre": "Action, Adventure, Drama, Fantasy, Harem, Martial Arts, Romance, Seinen, Tragedy, Wuxia, Xianxia", - "summary": "Zhou Han, whose family members all died in battles, chose to join the army and try to get more resources for his martial cultivation by undertaking battle achievements. One day, when he opened a scroll put on his bed by an unknown person, he entered a special space-time of an ancient sacrificing spirit. Though he did not understand what a sacrificing spirit was, or how their system functioned, the spirit acknowledged that he had absorbed Zhou Han’s energy for half a year. That was the reason why Zhou Han had became so weak and could not fight for the past several months. But the good news was that the spirit wanted to accept Zhou Han as his master before he could absorb his energy, and so the spirit could help him cultivate. With the help of the spirit, Zhou Han became a martial god in his army, and was chosen by the martial union of the empire to be trained for the more important task of defending the country from the demons." + link: "https://novelplanet.com/Novel/Dragon-God-of-Blood", + name: "Dragon God of Blood", + latestChapter: "Chapter 21: Reo (Ongoing)", + cover: "https://66.media.tumblr.com/1c578a2c3333a213a6d479205a4e9640/3ff18d1780838da9-1c/s400x600/f577954acb9560fbe8f4ff219e9d65f2db382c4b.jpg", + totalChapters: 21, + source: "novelplanet", + author: "3DImmortal", + genre: "Action, Fantasy", + summary: "Rus is treated like a slave in his tribe, he is forced to look for very valuable stones in the mountain of Rushu flowers every day for 12 hours straight. One day, he decides to plan his escape from the tribe, but he never thought his life would change completely that day.", + folderPath: "A:\\Downloads\\NovelScraper-Library\\Dragon God of Blood" }, state: { - "isDownloading": false, - "downloaded": false + "downloaded": true } }; } @@ -95,11 +102,13 @@ export class NovelComponent implements OnInit { // Set name of the page to whatever source loaded this page if (this.source == 'novelplanet') { - this.pageSource = 'NovelPlanet' + this.pageSource = 'NovelPlanet'; + } else if (this.source == 'readlightnovel') { + this.pageSource = 'ReadLightNovel'; } else if (this.source == 'boxnovel') { - this.pageSource = 'BoxNovel' + this.pageSource = 'BoxNovel'; } else if (this.source == 'library') { - this.pageSource = 'Library' + this.pageSource = 'Library'; } else { this.pageSource = 'Error: update novel.component.ts' } @@ -113,10 +122,12 @@ export class NovelComponent implements OnInit { this.downloading = true; this.novelplanetService.downloadNovel(this.novel.info.link, this.downloadID).then((saved) => { if (saved) { - this.novel.info.folderPath = this.library.getNovel(this.novel.info.link).info.folderPath; - this.downloading = false; - this.downloaded = true; - this.novelplanetService.updateDownloaded(this.novel.info.link, true); + this.ngZone.run(() => { + this.novel.info.folderPath = this.library.getNovel(this.novel.info.link).info.folderPath; + this.downloading = false; + this.downloaded = true; + this.novelplanetService.updateDownloaded(this.novel.info.link, true); + }); } }); } else if (this.novel.info.source == 'boxnovel') { @@ -124,10 +135,25 @@ export class NovelComponent implements OnInit { this.downloading = true; this.boxnovelService.downloadNovel(this.novel.info.link, this.downloadID).then((saved) => { if (saved) { - this.novel.info.folderPath = this.library.getNovel(this.novel.info.link).info.folderPath; - this.downloading = false; - this.downloaded = true; - this.boxnovelService.updateDownloaded(this.novel.info.link, true); + this.ngZone.run(() => { + this.novel.info.folderPath = this.library.getNovel(this.novel.info.link).info.folderPath; + this.downloading = false; + this.downloaded = true; + this.boxnovelService.updateDownloaded(this.novel.info.link, true); + }); + } + }); + } else if (this.novel.info.source == 'readlightnovel') { + this.downloadID = this.library.addDownloadTracker(this.novel.info.link); + this.downloading = true; + this.readlightnovelService.downloadNovel(this.novel.info.link, this.downloadID).then((saved) => { + if (saved) { + this.ngZone.run(() => { + this.novel.info.folderPath = this.library.getNovel(this.novel.info.link).info.folderPath; + this.downloading = false; + this.downloaded = true; + this.readlightnovelService.updateDownloaded(this.novel.info.link, true); + }); } }); } @@ -155,6 +181,11 @@ export class NovelComponent implements OnInit { this.downloading = false; } + openWebsite() { + console.log(this.novel); + shell.openExternal(this.novel.info.link); + } + // Binded to the add to library button addToLibrary() { this.library.addNovel( @@ -169,24 +200,37 @@ export class NovelComponent implements OnInit { this.novel.info.summary); this.inLibrary = true; - if(this.novel.info.source == 'novelplanet') { + if (this.novel.info.source == 'novelplanet') { this.novelplanetService.updateInLibrary(this.novel.info.link, true); - } else if(this.novel.info.source == 'boxnovel') { + } else if (this.novel.info.source == 'boxnovel') { this.boxnovelService.updateInLibrary(this.novel.info.link, true); + } else if (this.novel.info.source == 'readlightnovel') { + this.readlightnovelService.updateInLibrary(this.novel.info.link, true); } } // Binded to the remove from library button removeFromLibrary() { + this.showRemoveDialogue = true; + } + + closeRemoveDialogue() { + this.showRemoveDialogue = false; + } + + deleteNovel() { this.library.removeNovel(this.novel.info.link); this.inLibrary = false; this.downloaded = false; - if(this.novel.info.source == 'novelplanet') { + if (this.novel.info.source == 'novelplanet') { this.novelplanetService.updateInLibrary(this.novel.info.link, false); - } else if(this.novel.info.source == 'boxnovel') { + } else if (this.novel.info.source == 'boxnovel') { this.boxnovelService.updateInLibrary(this.novel.info.link, false); + } else if (this.novel.info.source == 'readlightnovel') { + this.readlightnovelService.updateInLibrary(this.novel.info.link, false); } + this.showRemoveDialogue = false; } // Keeps track of page to return to on back button @@ -194,6 +238,8 @@ export class NovelComponent implements OnInit { this.library.loadNovels(); if (this.source == 'novelplanet') { this.router.navigateByUrl('/novelplanetSource'); + } else if (this.source == 'readlightnovel') { + this.router.navigateByUrl('/readlightnovelSource'); } else if (this.source == 'boxnovel') { this.router.navigateByUrl('/boxnovelSource'); } else if (this.source == 'library') { diff --git a/src/app/novelplanet-source/novelplanet-source.component.html b/src/app/novelplanet-source/novelplanet-source.component.html index 464a3fdc..2a8191e0 100644 --- a/src/app/novelplanet-source/novelplanet-source.component.html +++ b/src/app/novelplanet-source/novelplanet-source.component.html @@ -3,25 +3,8 @@

NovelPlanet

-
- - -
- - -

LOADING

-
- -
-
- -
-

{{novel.info.name}}

-
-
-
+
+

NovelPlanet has been shutdown!


+

I am currently working on picking up another source. You can join my discord for source suggestions or more info: https://discord.gg/Wya4Dst

diff --git a/src/app/novelplanet-source/novelplanet-source.component.scss b/src/app/novelplanet-source/novelplanet-source.component.scss index 5cf6dc5c..3d4b4fe0 100644 --- a/src/app/novelplanet-source/novelplanet-source.component.scss +++ b/src/app/novelplanet-source/novelplanet-source.component.scss @@ -1 +1,13 @@ @import "../../assets/scss/global-variables"; + +.page { + color: white; + + .status { + padding: 1em; + + h1 { + color: rgb(255, 116, 116); + } + } +} diff --git a/src/app/novelplanet.service.ts b/src/app/novelplanet.service.ts index b1b10a49..5d1404a2 100644 --- a/src/app/novelplanet.service.ts +++ b/src/app/novelplanet.service.ts @@ -95,7 +95,8 @@ export class NovelplanetService { ads[x].remove(); } - let chapterBody = html.outerHTML; + let chapterBody = "

" + chapterTitle + "

"; + chapterBody += html.outerHTML; chapterBody += "

" chapterBody += "

dr-nyt's NovelScraper scraped this novel from a pirate site.

" chapterBody += "

If you can, please support the author(s) of this novel: " + novel.info.author + "

" diff --git a/src/app/readlightnovel-source/readlightnovel-source.component.html b/src/app/readlightnovel-source/readlightnovel-source.component.html new file mode 100644 index 00000000..f75a6fb0 --- /dev/null +++ b/src/app/readlightnovel-source/readlightnovel-source.component.html @@ -0,0 +1,32 @@ +
+
+

ReadLightNovel

+ +
+ +
+ + +
+ + +

LOADING

+
+

Novel search is not supported on this site.

+

Please paste the link of the novel you want from www.readlightnovel.org

+
+
+ +
+
+ +
+

{{novel.info.name}}

+
+
+
+
+
diff --git a/src/app/readlightnovel-source/readlightnovel-source.component.scss b/src/app/readlightnovel-source/readlightnovel-source.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/readlightnovel-source/readlightnovel-source.component.spec.ts b/src/app/readlightnovel-source/readlightnovel-source.component.spec.ts new file mode 100644 index 00000000..f11cb14f --- /dev/null +++ b/src/app/readlightnovel-source/readlightnovel-source.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ReadlightnovelSourceComponent } from './readlightnovel-source.component'; + +describe('ReadlightnovelSourceComponent', () => { + let component: ReadlightnovelSourceComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ReadlightnovelSourceComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ReadlightnovelSourceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/readlightnovel-source/readlightnovel-source.component.ts b/src/app/readlightnovel-source/readlightnovel-source.component.ts new file mode 100644 index 00000000..9f2b44d9 --- /dev/null +++ b/src/app/readlightnovel-source/readlightnovel-source.component.ts @@ -0,0 +1,47 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +// Import Library Service +import { LibraryService } from '../library.service'; +// Import Readlightnovel Service +import { ReadlightnovelService } from '../readlightnovel.service'; + +const { shell } = require('electron'); + +@Component({ + selector: 'app-readlightnovel-source', + templateUrl: './readlightnovel-source.component.html', + styleUrls: ['./readlightnovel-source.component.scss'] +}) +export class ReadlightnovelSourceComponent implements OnInit { + + val: any; + + constructor(private router: Router, private library: LibraryService, public readlightnovelService: ReadlightnovelService) { } + + ngOnInit(): void { + document.getElementById("readlightnovel-website").addEventListener("click", this.openWebsite); + } + + // Binded to novelfull search bar + search(val) { + if(val == undefined || val == "") { + return; + } else if(val.toLowerCase().includes("readlightnovel.org/")) { + this.readlightnovelService.show('loading'); + this.readlightnovelService.fetchFromLink(val); + } else { + this.readlightnovelService.show('loading'); + this.readlightnovelService.fetchFromSearch(val); + } + } + + openWebsite() { + shell.openExternal('https://www.readlightnovel.org/') + } + + // Load novelpage with the information of the novel clicked on + loadNovelPage(novel) { + this.router.navigateByUrl('/novel', { state: { novel: novel, source: 'readlightnovel' } }); + } +} diff --git a/src/app/readlightnovel.service.spec.ts b/src/app/readlightnovel.service.spec.ts new file mode 100644 index 00000000..4584865f --- /dev/null +++ b/src/app/readlightnovel.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ReadlightnovelService } from './readlightnovel.service'; + +describe('ReadlightnovelService', () => { + let service: ReadlightnovelService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ReadlightnovelService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/readlightnovel.service.ts b/src/app/readlightnovel.service.ts new file mode 100644 index 00000000..6fe01034 --- /dev/null +++ b/src/app/readlightnovel.service.ts @@ -0,0 +1,294 @@ +import { Injectable, NgZone } from '@angular/core'; + +// Import Library Service +import { LibraryService } from './library.service'; +// import cloudscraper to bypass cloudflare +const cloudscraper = (window).require('cloudscraper'); +//Import fs for chapter management +const fs = (window).require('fs'); + +@Injectable({ + providedIn: 'root' +}) +export class ReadlightnovelService { + + // Stores list of novels recently searched for (not in library) + localNovels: any[] = []; + + // UI Controls on what element to show + showEmpty: boolean = true; // Show empty icon + showLoading: boolean = false; // Show loading icon + showContent: boolean = false; // Show localNovels + showError: boolean = false; + showNotify: boolean = false; + + constructor(private ngZone: NgZone, private library: LibraryService) { } + + async downloadNovel(link, downloadID) { + console.log('Building...'); + + let downloadedChapters = []; + try { + let stringHtml = await this.getHtmlString(link); + let html = document.createElement('html'); + html.innerHTML = stringHtml; + + // Get chapter links and names + let chapterLinks = []; + let chapterNames = []; + let chapterTabs = html.getElementsByClassName("tab-content")[0].getElementsByClassName("tab-pane"); + for (let i = 0; i < chapterTabs.length; i++) { + if (chapterTabs[i].getElementsByTagName("li").length !== 0) { + let chapterHolders = chapterTabs[i].getElementsByTagName("li"); + for(let x = 0; x < chapterHolders.length; x++) { + chapterLinks.push(chapterHolders[x].getElementsByTagName('a')[0].getAttribute('href')); + chapterNames.push(chapterHolders[x].innerText); + } + } + } + + // Update totalChapter defind in library + this.library.updateTotalChapters(link, chapterLinks.length); + + let startIndex = 0; + + // Update novel if already downloaded + let novel = this.library.getNovel(link); + try { + if (novel.state.downloaded) { + console.log('Loading existing chapters...'); + let chapters = fs.readFileSync(novel.info.folderPath + '\\chapters.json'); + let chapterList = JSON.parse(chapters); + downloadedChapters = chapterList.chapters; + startIndex = downloadedChapters.length; + } + } catch (error) { + console.log(error); + console.log("Couldn't load update files. Starting download from start."); + } + + // Download each chapter at a time + for (let i = startIndex; i < chapterLinks.length; i++) { + if (this.library.downloadTrackers[downloadID].cancel) { + console.log('Download canceled!') + return false; + } + + let stringHtml = await this.getHtmlString(chapterLinks[i]); + let pageHtml = new DOMParser().parseFromString(stringHtml, 'text/html'); + let chapterHtml = pageHtml.getElementsByClassName('desc')[0].getElementsByClassName("hidden")[0]; + + let chapterTitle = chapterNames[i]; + + let chapterBody = "

" + chapterTitle + "

"; + chapterBody += chapterHtml.outerHTML; + chapterBody += "

" + chapterBody += "

dr-nyt's NovelScraper scraped this novel from a pirate site.

" + chapterBody += "

If you can, please support the author(s) of this novel: " + novel.info.author + "

" + downloadedChapters.push({ title: chapterTitle, data: chapterBody }); + + let percentage = +(((i / chapterLinks.length) * 100).toFixed(2)); + this.library.updateDownloadTracker(downloadID, percentage); + } + + this.library.cancelDownload(downloadID); + await this.library.generateEpub(link, downloadedChapters); + return true; + + } catch (error) { + console.log(error); + } + } + + async fetchFromLink(link) { + try { + // Remove any duplicates of the novel + for (let novel of this.localNovels) { + if (novel.info.link == link) { + this.localNovels.splice(this.localNovels.indexOf(novel), 1); + } + } + + let novel = this.library.getNovel(link); + + let name = ""; + let latestChapter = ""; + let cover = ""; + let totalChapters = 0; + let source = "readlightnovel"; + let author = ""; + let genre = ""; + let summary = "" + let downloaded = false; + let inLibrary = false; + + if (novel === undefined) { + let stringHtml = await this.getHtmlString(link); + let html = document.createElement('html'); + html.innerHTML = stringHtml; + + name = html.getElementsByClassName("block-title")[0].getElementsByTagName("h1")[0].innerText; + try { + latestChapter = html.getElementsByClassName("novel-right")[0].getElementsByClassName("novel-detail-item")[5].getElementsByTagName("a")[0].innerText; + } catch (error) { + console.log(error); + } + cover = html.getElementsByClassName('novel-cover')[0].getElementsByTagName("img")[0].src; + + let chapterTabs = html.getElementsByClassName("tab-content")[0].getElementsByClassName("tab-pane"); + for (let i = 0; i < chapterTabs.length; i++) { + if (chapterTabs[i].getElementsByTagName("li").length !== 0) { + totalChapters += chapterTabs[i].getElementsByTagName("li").length; + } + } + + source = "readlightnovel"; + + // Get list of authors + let authorList = html.getElementsByClassName("novel-left")[0].getElementsByClassName("novel-detail-item")[4].getElementsByTagName("li"); + try { + for (let i = 0; i < authorList.length; i++) { + author += authorList[i].innerText.trim() + ', '; + } + author = author.slice(0, -2); + } catch (error) { + author = "unkown"; + console.log(error); + } + + // Get list of genres + let genreList = html.getElementsByClassName("novel-left")[0].getElementsByClassName("novel-detail-item")[1].getElementsByTagName("a"); + try { + for (let i = 0; i < genreList.length; i++) { + genre += genreList[i].innerText.trim() + ', '; + } + genre = genre.slice(0, -2); + } catch (error) { + genre = "unkown"; + console.log(error); + } + + // Get list of summary + let summaryList = html.getElementsByClassName("novel-right")[0].getElementsByClassName("novel-detail-item")[0].getElementsByTagName("p"); + try { + for (let i = 0; i < summaryList.length; i++) { + summary += summaryList[i].innerText.trim() + "\n"; + } + } catch (error) { + summary = "unkown"; + console.log(error); + } + + } else { + name = novel.info.name; + console.log(name + " in library!"); + latestChapter = novel.info.latestChapter; + cover = novel.info.cover; + totalChapters = novel.info.totalChapters; + source = novel.info.source; + author = novel.info.author; + genre = novel.info.genre; + summary = novel.info.summary; + downloaded = novel.state.downloaded; + inLibrary = true; + } + + this.localNovels.unshift({ + info: { + link: link, + name: name, + latestChapter: latestChapter, + cover: cover, + totalChapters: totalChapters, + source: source, + author: author, + genre: genre, + summary: summary + }, + state: { + downloaded: downloaded, + inLibrary: inLibrary + } + }); + + this.ngZone.run(() => { + this.show('content'); + }); + + } catch (error) { + this.ngZone.run(() => { + console.log(error); + this.show('error'); + return; + }); + } + } + + async fetchFromSearch(val) { + this.show("notify"); + } + + // Utility function to get html from a link using selenium + async getHtmlString(link) { + var options = { + method: 'GET', + url: link, + } + + let stringHtml = await cloudscraper(options, (error, response, novelHtmlString) => { + return novelHtmlString; + }); + return stringHtml; + } + + updateInLibrary(link, update) { + for (let novel of this.localNovels) { + if (novel.info.link == link) { + novel.state.inLibrary = update; + } + } + } + + updateDownloaded(link, update) { + for (let novel of this.localNovels) { + if (novel.info.link == link) { + novel.state.downloaded = update; + } + } + } + + // Utility function turns off other elements when one is shown + show(section: string) { + if (section == 'empty') { + this.showEmpty = true; + this.showLoading = false; + this.showContent = false; + this.showError = false; + this.showNotify = false; + } else if (section == 'loading') { + this.showEmpty = false; + this.showLoading = true; + this.showContent = false; + this.showError = false; + this.showNotify = false; + } else if (section == 'content') { + this.showEmpty = false; + this.showLoading = false; + this.showContent = true; + this.showError = false; + this.showNotify = false; + } else if (section == 'error') { + this.showEmpty = false; + this.showLoading = false; + this.showContent = false; + this.showError = true; + this.showNotify = false; + } else if (section == 'notify') { + this.showEmpty = false; + this.showLoading = false; + this.showContent = false; + this.showError = false; + this.showNotify = true; + } + } +} diff --git a/src/app/setting/setting.component.html b/src/app/setting/setting.component.html index f7d4eb45..ad11f65a 100644 --- a/src/app/setting/setting.component.html +++ b/src/app/setting/setting.component.html @@ -5,7 +5,7 @@

SETTINGS

- +
diff --git a/src/app/setting/setting.component.scss b/src/app/setting/setting.component.scss index 4c319660..5d7272a8 100644 --- a/src/app/setting/setting.component.scss +++ b/src/app/setting/setting.component.scss @@ -9,6 +9,8 @@ label { color: white; + margin-bottom: 5px; + width: 100%; } .settingInput { diff --git a/src/app/sources/sources.component.html b/src/app/sources/sources.component.html index 18ba7bdb..3901a8c1 100644 --- a/src/app/sources/sources.component.html +++ b/src/app/sources/sources.component.html @@ -1,23 +1,31 @@
-

Sources

+

Sources

    -
  • -
    -
    - -

    NovelPlanet

    -
    -
  • +
  • +
    +
    + +

    NovelPlanet

    +
    +
  • -
  • -
    -
    - -

    BoxNovel

    -
    -
  • +
  • +
    +
    + +

    ReadLightNovel

    +
    +
  • + +
  • +
    +
    + +

    BoxNovel

    +
    +
diff --git a/src/app/sources/sources.component.scss b/src/app/sources/sources.component.scss index 9b4580c4..3ece34a7 100644 --- a/src/app/sources/sources.component.scss +++ b/src/app/sources/sources.component.scss @@ -13,8 +13,8 @@ cursor: pointer; float: left; margin-right: 30px; - width: 150px; - height: 150px; + width: 175px; + height: 175px; text-align: center; border-radius: 12px; overflow: hidden; @@ -27,7 +27,7 @@ position: absolute; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.315); + background: rgba(0, 0, 0, 0.6); opacity: 0; transition: .2s ease-in-out opacity; } @@ -70,6 +70,14 @@ .sourceHolder:focus { outline: none; } + + #novelplanetSource { + border: 5px solid rgba(255, 0, 0, 0.644); + + .img-gradient { + background: rgba(255, 0, 0, 0.644); + } + } } } } diff --git a/src/assets/rsc/boxnovel-logo.png b/src/assets/rsc/boxnovel-logo.png index b772a278..18a06de9 100644 Binary files a/src/assets/rsc/boxnovel-logo.png and b/src/assets/rsc/boxnovel-logo.png differ diff --git a/src/assets/rsc/internet.svg b/src/assets/rsc/internet.svg new file mode 100644 index 00000000..82e8bde6 --- /dev/null +++ b/src/assets/rsc/internet.svg @@ -0,0 +1,2 @@ + + diff --git a/src/assets/rsc/logo5.png b/src/assets/rsc/logo5.png new file mode 100644 index 00000000..a8229e1b Binary files /dev/null and b/src/assets/rsc/logo5.png differ diff --git a/src/assets/rsc/novelfull-logo.png b/src/assets/rsc/novelfull-logo.png new file mode 100644 index 00000000..87ed0654 Binary files /dev/null and b/src/assets/rsc/novelfull-logo.png differ diff --git a/src/assets/rsc/readlightnovel-logo.png b/src/assets/rsc/readlightnovel-logo.png new file mode 100644 index 00000000..96009ba1 Binary files /dev/null and b/src/assets/rsc/readlightnovel-logo.png differ diff --git a/src/styles.scss b/src/styles.scss index 1f16437d..d5ee146b 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -34,6 +34,14 @@ html, body { font-size: 38px; color: white; } + + .website { + cursor: pointer; + position: absolute; + right: 40px; + top: 45px; + width: 35px; + } } .content { @@ -83,6 +91,7 @@ html, body { display: flex; align-items: center; justify-content: center; + text-align: center; img { width: 200px; @@ -102,6 +111,10 @@ html, body { -webkit-animation: text-flicker-out-glow 2.5s ease-in-out infinite both; animation: text-flicker-out-glow 2.5s ease-in-out infinite both; } + + .notify { + color: white; + } } .novelList {