Skip to content
This repository was archived by the owner on Nov 20, 2021. It is now read-only.

Commit 1445fee

Browse files
committed
Add dridespoon and fix voidscans
1 parent 4038bef commit 1445fee

File tree

6 files changed

+336
-3
lines changed

6 files changed

+336
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Misc. sources for scanlators and aggregators.
33

44
# Websites
55
* https://catmanga.org/
6+
* http://dridesp.ooo/n/
67
* https://glitchycomics.com/
78
* https://www.novelcool.com/
89
* https://rainofsnow.com/

src/Dridespoon/Dridespoon.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import {
2+
Chapter,
3+
ChapterDetails,
4+
HomeSection,
5+
LanguageCode,
6+
Manga,
7+
MangaStatus,
8+
MangaTile,
9+
MangaUpdates,
10+
PagedResults,
11+
Request,
12+
RequestManager,
13+
SearchRequest,
14+
Source,
15+
SourceInfo,
16+
TagType,
17+
} from "paperback-extensions-common"
18+
import {DridespoonParser} from "./DridespoonParser";
19+
20+
const BASE = "http://dridesp.ooo"
21+
22+
export const DridespoonInfo: SourceInfo = {
23+
icon: "icon.png",
24+
version: "1.0.0",
25+
name: "Dridespoon",
26+
author: "PythonCoderAS",
27+
authorWebsite: "https://github.com/PythonCoderAS",
28+
description: "Extension that pulls manga from Dridespoon",
29+
language: "en",
30+
hentaiSource: false,
31+
websiteBaseURL: BASE,
32+
sourceTags: [
33+
{
34+
text: "Notifications",
35+
type: TagType.GREEN
36+
}
37+
]
38+
}
39+
40+
export class Dridespoon extends Source {
41+
42+
private readonly parser: DridespoonParser = new DridespoonParser();
43+
44+
45+
readonly requestManager: RequestManager = createRequestManager({
46+
requestsPerSecond: 5,
47+
requestTimeout: 10000
48+
});
49+
50+
getMangaShareUrl(mangaId: string): string {
51+
return `${BASE}/n/${mangaId}`;
52+
}
53+
54+
async getHomePageSections(sectionCallback: (section: HomeSection) => void): Promise<void> {
55+
const options: Request = createRequestObject({
56+
url: `${BASE}/n/`,
57+
method: 'GET'
58+
});
59+
let response = await this.requestManager.schedule(options, 1);
60+
let $ = this.cheerio.load(response.data);
61+
sectionCallback(createHomeSection({
62+
id: "1",
63+
items: this.parser.parseMangaList($, BASE, "sfw"),
64+
title: "Involvements"
65+
}));
66+
sectionCallback(createHomeSection({
67+
id: "2",
68+
items: this.parser.parseMangaList($, BASE, "collapseOne"),
69+
title: "Past Involvements"
70+
}));
71+
sectionCallback(createHomeSection({
72+
id: "3",
73+
items: this.parser.parseMangaList($, BASE, "collapseThree"),
74+
title: "Finished Projects"
75+
}));
76+
}
77+
78+
79+
async getWebsiteMangaDirectory(metadata: any): Promise<PagedResults> {
80+
const options: Request = createRequestObject({
81+
url: `${BASE}/n/`,
82+
method: 'GET'
83+
});
84+
let response = await this.requestManager.schedule(options, 1);
85+
let $ = this.cheerio.load(response.data);
86+
return createPagedResults({
87+
results: this.parser.parseMangaList($, BASE, "main")
88+
})
89+
}
90+
91+
async getChapterDetails(mangaId: string, chapterId: string): Promise<ChapterDetails> {
92+
let url = this.getMangaShareUrl(mangaId);
93+
if (chapterId !== "Paperback-iOS-sentinel-id"){
94+
url += "/" + chapterId
95+
}
96+
const options: Request = createRequestObject({
97+
url: url,
98+
method: 'GET'
99+
});
100+
let response = await this.requestManager.schedule(options, 1);
101+
let $ = this.cheerio.load(response.data);
102+
const pages = this.parser.parsePages($, BASE)
103+
return createChapterDetails({
104+
id: chapterId,
105+
longStrip: false,
106+
mangaId: mangaId,
107+
pages: pages
108+
})
109+
}
110+
111+
async getChapters(mangaId: string): Promise<Chapter[]> {
112+
const options: Request = createRequestObject({
113+
url: this.getMangaShareUrl(mangaId),
114+
method: 'GET'
115+
});
116+
let response = await this.requestManager.schedule(options, 1);
117+
let $ = this.cheerio.load(response.data);
118+
let parsedPages = this.parser.parsePages($, BASE);
119+
if (parsedPages.length !== 0) {
120+
return [createChapter({
121+
chapNum: 1,
122+
id: "Paperback-iOS-sentinel-id",
123+
langCode: LanguageCode.ENGLISH,
124+
mangaId: mangaId
125+
})]
126+
} else {
127+
return this.parser.parseChapterList($, mangaId);
128+
}
129+
}
130+
131+
async getMangaDetails(mangaId: string): Promise<Manga> {
132+
const options: Request = createRequestObject({
133+
url: this.getMangaShareUrl(mangaId),
134+
method: 'GET'
135+
});
136+
let response = await this.requestManager.schedule(options, 1);
137+
let $ = this.cheerio.load(response.data);
138+
let parsedPages = this.parser.parsePages($, BASE);
139+
if (parsedPages.length !== 0){
140+
return createManga({
141+
id: mangaId,
142+
image: parsedPages[0],
143+
rating: 0,
144+
status: MangaStatus.COMPLETED,
145+
titles: [mangaId]
146+
})
147+
} else {
148+
return this.parser.parseManga($, mangaId, BASE);
149+
}
150+
}
151+
152+
async searchRequest(query: SearchRequest, metadata: any): Promise<PagedResults> {
153+
// TODO: Wait for search to be implemented on the website.
154+
const results = (await this.getWebsiteMangaDirectory(null)).results;
155+
const data: MangaTile[] = [];
156+
for (let i = 0; i < results.length; i++) {
157+
const key = results[i];
158+
if (query.title) {
159+
if ((key.title.text || "").toLowerCase().includes((query.title.toLowerCase()))) {
160+
data.push(key);
161+
}
162+
}
163+
}
164+
return createPagedResults({
165+
results: data
166+
});
167+
}
168+
169+
170+
async filterUpdatedManga(mangaUpdatesFoundCallback: (updates: MangaUpdates) => void, time: Date, ids: string[]): Promise<void> {
171+
mangaUpdatesFoundCallback(createMangaUpdates({ids: ids}));
172+
}
173+
}

src/Dridespoon/DridespoonParser.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {Chapter, LanguageCode, Manga, MangaStatus, MangaTile} from "paperback-extensions-common";
2+
3+
export class DridespoonParser {
4+
5+
pageRegex = /src:"(https:\/\/beta\.voidscans\.net\/[^\s"']+)"/g
6+
7+
parseMangaList($: CheerioStatic, base: string, id: string) {
8+
const mangaTiles: MangaTile[] = [];
9+
$(`#${id} div.col-md-4.picture-item:not(.manga-card)`).map((index, element) => {
10+
const link = $("a", $("li.list-group-item", element).last());
11+
let linkId = link.attr("href");
12+
if (!linkId){
13+
const link2 = $("a", element).first().attr("href")
14+
if (link2 && link2.match(/\/$/)){
15+
// Exception for manga where there are no chapters but the manga itself is the chapter.
16+
linkId = link2;
17+
}
18+
}
19+
if (linkId){
20+
mangaTiles.push(createMangaTile({
21+
id: linkId.replace(`/n/`, "").replace("/", ""),
22+
title: createIconText({
23+
text: $("p", element).first().text().trim().replaceAll(/\s{2,}/g, "")
24+
}),
25+
image: `${base}/${($("img", element).first().attr("src") || "").replace(/^\//, "")}`
26+
}))
27+
}
28+
})
29+
return mangaTiles;
30+
}
31+
32+
parsePages($: CheerioStatic, base: string): string[] {
33+
const pages: string[] = [];
34+
$("img[data-src]").map((index, element) => {
35+
pages.push(element.attribs["data-src"].replace("/../..", base))
36+
})
37+
return pages;
38+
}
39+
40+
parseChapterList($: CheerioStatic, mangaId: string) {
41+
const chapters: Chapter[] = [];
42+
$("table[data-sort-name] tbody tr").map((index, element) => {
43+
const chapNum = Number($("th", element).first().text());
44+
const data: Chapter = {
45+
chapNum: chapNum,
46+
id: String(chapNum),
47+
langCode: LanguageCode.ENGLISH,
48+
mangaId: mangaId,
49+
time: new Date($("td", element).last().text()),
50+
name: $("td", element).first().text()
51+
}
52+
chapters.push(createChapter(data))
53+
})
54+
return chapters
55+
}
56+
57+
parseManga($: CheerioStatic, mangaId: string, base: string) {
58+
const mangaObj: Manga = {
59+
desc: $("p#series_desc").first().text().trim(),
60+
id: mangaId,
61+
image: base + ($("img").attr("src") || ""),
62+
rating: 0,
63+
status: MangaStatus.ONGOING,
64+
titles: [$("h1").first().text()],
65+
}
66+
return createManga(mangaObj)
67+
}
68+
69+
}

src/Dridespoon/includes/icon.png

47 KB
Loading

src/VoidScans/VoidScans.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const BASE = "https://voidscans.net"
1515

1616
export const VoidScansInfo: SourceInfo = {
1717
icon: "icon.svg",
18-
version: "1.3.2",
18+
version: "1.4.0",
1919
name: "VoidScans",
2020
author: "PythonCoderAS",
2121
authorWebsite: "https://github.com/PythonCoderAS",
@@ -69,7 +69,7 @@ export class VoidScans extends Source {
6969
const pages = this.parser.parsePages($)
7070
return createChapterDetails({
7171
id: chapterId,
72-
longStrip: true,
72+
longStrip: false,
7373
mangaId: mangaId,
7474
pages: pages
7575
})
@@ -102,7 +102,7 @@ export class VoidScans extends Source {
102102
for (let i = 0; i < results.length; i++) {
103103
const key = results[i];
104104
if (query.title) {
105-
if ((key.primaryText?.text || "").toLowerCase().includes((query.title.toLowerCase()))) {
105+
if ((key.title.text || "").toLowerCase().includes((query.title.toLowerCase()))) {
106106
data.push(key);
107107
}
108108
}
@@ -114,6 +114,8 @@ export class VoidScans extends Source {
114114

115115

116116
async filterUpdatedManga(mangaUpdatesFoundCallback: (updates: MangaUpdates) => void, time: Date, ids: string[]): Promise<void> {
117+
// TODO: Wait for times to be implemented.
118+
/*
117119
const options: Request = createRequestObject({
118120
url: `${BASE}`,
119121
method: 'GET'
@@ -123,5 +125,6 @@ export class VoidScans extends Source {
123125
mangaUpdatesFoundCallback(createMangaUpdates({
124126
ids: this.parser.parseUpdatedManga($, BASE, time)
125127
}));
128+
*/
126129
}
127130
}

src/tests/Dridespoon.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import cheerio from "cheerio";
2+
import {Dridespoon} from "../Dridespoon/Dridespoon";
3+
import {APIWrapper, Source} from "paperback-extensions-common";
4+
5+
describe("Dridespoon Tests", function () {
6+
let wrapper: APIWrapper = new APIWrapper();
7+
let source: Source = new Dridespoon(cheerio);
8+
let chai = require("chai"),
9+
expect = chai.expect;
10+
let chaiAsPromised = require("chai-as-promised");
11+
chai.use(chaiAsPromised);
12+
13+
let mangaId = "GT-giRl";
14+
15+
it("Retrieve Manga Details", async () => {
16+
let details = await wrapper.getMangaDetails(source, mangaId);
17+
expect(
18+
details,
19+
"No results found with test-defined ID [" + mangaId + "]"
20+
).to.exist;
21+
22+
// Validate that the fields are filled
23+
let data = details;
24+
expect(data.id, "Missing ID").to.be.not.empty;
25+
expect(data.image, "Missing Image").to.exist;
26+
expect(data.status, "Missing Status").to.exist;
27+
expect(data.titles, "Missing Titles").to.be.not.empty;
28+
expect(data.rating, "Missing Rating").to.exist;
29+
expect(data.desc, "Missing Description").to.be.not.empty;
30+
});
31+
32+
it("Get Chapters", async () => {
33+
let data = await wrapper.getChapters(source, mangaId);
34+
35+
expect(data, "No chapters present for: [" + mangaId + "]").to.not.be.empty;
36+
37+
let entry = data[0];
38+
expect(entry.id, "No ID present").to.not.be.empty;
39+
expect(entry.chapNum, "No chapter number present").to.exist;
40+
});
41+
42+
it("Get Chapter Details", async () => {
43+
let chapters = await wrapper.getChapters(source, mangaId);
44+
let data = await wrapper.getChapterDetails(source, mangaId, chapters[0].id);
45+
46+
expect(data, "Empty server response").to.not.be.empty;
47+
48+
expect(data.id, "Missing ID").to.be.not.empty;
49+
expect(data.mangaId, "Missing MangaID").to.be.not.empty;
50+
expect(data.pages, "No pages present").to.be.not.empty;
51+
});
52+
53+
it("Testing search", async () => {
54+
let testSearch = createSearchRequest({
55+
title: "GT",
56+
});
57+
58+
let search = await wrapper.searchRequest(source, testSearch);
59+
let result = search.results[0];
60+
61+
expect(result, "No response from server").to.exist;
62+
63+
expect(result.id, "No ID found for search query").to.be.not.empty;
64+
expect(result.title, "No title").to.be.not.empty;
65+
});
66+
67+
it("Testing Home Page", async () => {
68+
let result = await wrapper.getHomePageSections(source);
69+
expect(result, "No response from server").to.exist;
70+
let item = result[0];
71+
expect(item, "Empty response from server").to.exist;
72+
if (item.items) {
73+
let subitem = item.items[0];
74+
75+
expect(subitem.id, "No ID found for homepage item").to.not.be.empty;
76+
expect(subitem.title, "No Title found for homepage item").to.not.be.empty;
77+
expect(subitem.image, "No Image found for homepage item").to.not.be.empty;
78+
}
79+
})
80+
81+
it("Testing Notifications", async () => {
82+
let updates = await wrapper.filterUpdatedManga(source, new Date("2021-1-27"), [mangaId])
83+
expect(updates, "No server response").to.exist
84+
expect(updates, "Empty server response").to.not.be.empty
85+
expect(updates[0], "No updates").to.not.be.empty;
86+
})
87+
});

0 commit comments

Comments
 (0)