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

Commit 440820d

Browse files
committed
Add DankeMoe and MahouShoujoBu
1 parent 2217de4 commit 440820d

File tree

8 files changed

+419
-0
lines changed

8 files changed

+419
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ Misc. sources for scanlators and aggregators.
33

44
# Websites
55
* https://catmanga.org/
6+
* https://danke.moe/
67
* http://dridesp.ooo/n/
78
* https://glitchycomics.com/
9+
* https://mahoushoujobu.com/
810
* https://www.novelcool.com/
911
* https://rainofsnow.com/
1012
* https://raw.senmanga.com/

src/DankeFurs/DankeFurs.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {SourceInfo} from "paperback-extensions-common"
2+
import {GuyaTemplate} from "../GuyaTemplate";
3+
4+
const BASE = "https://danke.moe"
5+
6+
export const DankeFursInfo: SourceInfo = {
7+
icon: "icon.png",
8+
version: "1.0.0",
9+
name: "DankeFurs",
10+
author: "PythonCoderAS",
11+
authorWebsite: "https://github.com/PythonCoderAS",
12+
description: "Extension that pulls manga from DankeFurs",
13+
language: "en",
14+
hentaiSource: false,
15+
websiteBaseURL: BASE
16+
}
17+
18+
export class DankeFurs extends GuyaTemplate {
19+
readonly baseUrl: string = BASE;
20+
}

src/DankeFurs/includes/icon.png

6.3 KB
Loading

src/GuyaTemplate.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/**
2+
* This is <b>not</b> a dedicated Guya.moe template repo. However, I feel that it is pointless to introduce yet another
3+
* repository for what is essentially 2-3 sources.
4+
*/
5+
6+
import {
7+
Source,
8+
Manga,
9+
Chapter,
10+
ChapterDetails,
11+
HomeSection,
12+
SearchRequest,
13+
LanguageCode,
14+
MangaStatus,
15+
MangaUpdates,
16+
PagedResults,
17+
} from "paperback-extensions-common"
18+
19+
const SPLIT_VAR = "|"
20+
21+
export abstract class GuyaTemplate extends Source {
22+
readonly abstract baseUrl: string;
23+
async getMangaDetails(mangaId: string): Promise<Manga> {
24+
25+
let request = createRequestObject({
26+
metadata: { mangaId },
27+
url: `${this.baseUrl}/api/get_all_series/`,
28+
method: "GET",
29+
})
30+
31+
let response = await this.requestManager.schedule(request, 1)
32+
33+
let result = typeof response.data === "string" ? JSON.parse(response.data) : response.data
34+
35+
let mangas = []
36+
for (let series in result) {
37+
let seriesDetails = result[series]
38+
if (mangaId.includes(seriesDetails["slug"])) {
39+
mangas.push(
40+
createManga({
41+
id: seriesDetails["slug"],
42+
titles: [series],
43+
image: `${this.baseUrl}/${seriesDetails["cover"]}`,
44+
rating: 5,
45+
status: MangaStatus.ONGOING,
46+
artist: seriesDetails["artist"],
47+
author: seriesDetails["author"],
48+
desc: seriesDetails["description"],
49+
})
50+
)
51+
}
52+
}
53+
54+
return mangas[0]
55+
}
56+
57+
58+
async getChapters(mangaId: string): Promise<Chapter[]> {
59+
let request = createRequestObject({
60+
metadata: { mangaId },
61+
url: `${this.baseUrl}/api/series/${mangaId}/`,
62+
method: "GET",
63+
})
64+
65+
let response = await this.requestManager.schedule(request, 1)
66+
67+
let result = typeof response.data === "string" ? JSON.parse(response.data) : response.data
68+
let rawChapters = result["chapters"]
69+
let groupMap = result["groups"]
70+
71+
let chapters = []
72+
for (let chapter in rawChapters) {
73+
let chapterMetadata = rawChapters[chapter]
74+
for (let group in chapterMetadata["groups"]) {
75+
chapters.push(
76+
createChapter({
77+
id: `${chapter}${SPLIT_VAR}${group}`,
78+
mangaId: mangaId,
79+
chapNum: Number(chapter),
80+
langCode: LanguageCode.ENGLISH,
81+
name: chapterMetadata["title"],
82+
volume: chapterMetadata["volume"],
83+
group: groupMap[group],
84+
time: new Date(
85+
Number(chapterMetadata["release_date"][group]) * 1000
86+
),
87+
})
88+
)
89+
}
90+
}
91+
return chapters
92+
}
93+
94+
async getChapterDetails(mangaId: string, chapterId: string): Promise<ChapterDetails> {
95+
96+
const request = createRequestObject({
97+
url: `${this.baseUrl}/api/series/${mangaId}/`,
98+
method: "GET",
99+
})
100+
101+
const data = await this.requestManager.schedule(request, 1)
102+
103+
let result = typeof data.data === "string" ? JSON.parse(data.data) : data.data
104+
let rawChapters = result["chapters"]
105+
let [chapter, group] = chapterId.split(SPLIT_VAR)
106+
return createChapterDetails({
107+
id: chapterId,
108+
longStrip: false,
109+
mangaId: mangaId,
110+
pages: rawChapters[chapter]["groups"][group].map(
111+
(page: string) =>
112+
`${this.baseUrl}/media/manga/${mangaId}/chapters/${rawChapters[chapter]["folder"]}/${group}/${page}`
113+
),
114+
})
115+
}
116+
117+
async searchRequest(searchQuery: SearchRequest, metadata: any): Promise<PagedResults> {
118+
119+
const request = createRequestObject({
120+
url: `${this.baseUrl}/api/get_all_series/`,
121+
method: "GET",
122+
})
123+
124+
const data = await this.requestManager.schedule(request, 1)
125+
126+
let result = typeof data.data === "string" ? JSON.parse(data.data) : data.data
127+
let query = searchQuery.title ?? ''
128+
129+
let filteredResults = Object.keys(result).filter((e) =>
130+
e.toLowerCase().includes(query.toLowerCase())
131+
)
132+
133+
let tiles = filteredResults.map((series) => {
134+
let seriesMetadata = result[series]
135+
return createMangaTile({
136+
id: seriesMetadata["slug"],
137+
image: `${this.baseUrl}/${seriesMetadata["cover"]}`,
138+
title: createIconText({ text: series }),
139+
})
140+
})
141+
142+
return createPagedResults({
143+
results: tiles
144+
})
145+
}
146+
147+
async getHomePageSections(sectionCallback: (section: HomeSection) => void): Promise<void> {
148+
149+
// Send the empty homesection back so the app can preload the section
150+
var homeSection = createHomeSection({ id: "all_guya", title: "ALL GUYA" })
151+
sectionCallback(homeSection)
152+
153+
const request = createRequestObject({
154+
url: `${this.baseUrl}/api/get_all_series/`,
155+
method: "GET"
156+
})
157+
158+
const data = await this.requestManager.schedule(request, 1)
159+
160+
let result = typeof data.data === "string" ? JSON.parse(data.data) : data.data
161+
162+
let mangas = []
163+
for (let series in result) {
164+
let seriesDetails = result[series]
165+
mangas.push(
166+
createMangaTile({
167+
id: seriesDetails["slug"],
168+
image: `${this.baseUrl}/${seriesDetails["cover"]}`,
169+
title: createIconText({ text: series }),
170+
})
171+
)
172+
}
173+
homeSection.items = mangas
174+
175+
sectionCallback(homeSection)
176+
}
177+
178+
async filterUpdatedManga(mangaUpdatesFoundCallback: (updates: MangaUpdates) => void, time: Date, ids: string[]): Promise<void> {
179+
180+
const request = createRequestObject({
181+
url: `${this.baseUrl}/api/get_all_series/`,
182+
method: "GET"
183+
})
184+
185+
const data = await this.requestManager.schedule(request, 1)
186+
187+
let result = typeof data.data === "string" ? JSON.parse(data.data) : data.data
188+
189+
let foundIds: string[] = []
190+
191+
for (let series in result) {
192+
const seriesDetails = result[series]
193+
const seriesUpdated = new Date(seriesDetails["last_updated"] * 1000)
194+
const id = seriesDetails["slug"];
195+
if (seriesUpdated >= time && ids.includes(id)) {
196+
foundIds.push(id);
197+
}
198+
}
199+
mangaUpdatesFoundCallback(createMangaUpdates({ ids: foundIds }))
200+
}
201+
202+
getMangaShareUrl(mangaId: string) {
203+
return `${this.baseUrl}/read/manga/${mangaId}/`
204+
}
205+
}

src/MahouShoujoBu/MahouShoujoBu.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {SourceInfo} from "paperback-extensions-common"
2+
import {GuyaTemplate} from "../GuyaTemplate";
3+
4+
const BASE = "https://mahoushoujobu.com/"
5+
6+
export const MahouShoujoBuInfo: SourceInfo = {
7+
icon: "icon.png",
8+
version: "1.0.0",
9+
name: "MahouShoujoBu",
10+
author: "PythonCoderAS",
11+
authorWebsite: "https://github.com/PythonCoderAS",
12+
description: "Extension that pulls manga from MahouShoujoBu",
13+
language: "en",
14+
hentaiSource: false,
15+
websiteBaseURL: BASE
16+
}
17+
18+
export class MahouShoujoBu extends GuyaTemplate {
19+
readonly baseUrl: string = BASE;
20+
}
23.1 KB
Loading

src/tests/DankeFurs.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import cheerio from "cheerio";
2+
import { DankeFurs } from "../DankeFurs/DankeFurs";
3+
import { APIWrapper, Source } from "paperback-extensions-common";
4+
5+
describe("DankeFurs Tests", function () {
6+
var wrapper: APIWrapper = new APIWrapper();
7+
var source: Source = new DankeFurs(cheerio);
8+
var chai = require("chai"),
9+
expect = chai.expect;
10+
var chaiAsPromised = require("chai-as-promised");
11+
chai.use(chaiAsPromised);
12+
13+
var mangaId = "kazu-megu";
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.be.not.empty;
26+
expect(data.status, "Missing Status").to.exist;
27+
expect(data.author, "Missing Author").to.be.not.empty;
28+
expect(data.desc, "Missing Description").to.be.not.empty;
29+
expect(data.titles, "Missing Titles").to.be.not.empty;
30+
expect(data.rating, "Missing Rating").to.exist;
31+
});
32+
33+
it("Get Chapters", async () => {
34+
let data = await wrapper.getChapters(source, mangaId);
35+
36+
expect(data, "No chapters present for: [" + mangaId + "]").to.not.be.empty;
37+
38+
let entry = data[0];
39+
expect(entry.id, "No ID present").to.not.be.empty;
40+
expect(entry.time, "No date present").to.exist;
41+
expect(entry.name, "No title available").to.not.be.empty;
42+
expect(entry.chapNum, "No chapter number present").to.exist;
43+
expect(entry.volume, "No volume data available").to.not.be.empty;
44+
});
45+
46+
it("Get Chapter Details", async () => {
47+
let chapters = await wrapper.getChapters(source, mangaId);
48+
let data = await wrapper.getChapterDetails(source, mangaId, chapters[0].id);
49+
50+
expect(data, "No server response").to.exist;
51+
expect(data, "Empty server response").to.not.be.empty;
52+
53+
expect(data.id, "Missing ID").to.be.not.empty;
54+
expect(data.mangaId, "Missing MangaID").to.be.not.empty;
55+
expect(data.pages, "No pages present").to.be.not.empty;
56+
});
57+
58+
it("Testing search", async () => {
59+
let testSearch = createSearchRequest({
60+
title: "kazu",
61+
});
62+
63+
let search = await wrapper.searchRequest(source, testSearch);
64+
let result = search.results[0];
65+
66+
expect(result, "No response from server").to.exist;
67+
68+
expect(result.id, "No ID found for search query").to.be.not.empty;
69+
expect(result.image, "No image found for search").to.be.not.empty;
70+
expect(result.title, "No title").to.be.not.null;
71+
expect(result.subtitleText, "No subtitle text").to.be.not.null;
72+
});
73+
74+
it("Testing Home-Page aquisition", async () => {
75+
let homePages = await wrapper.getHomePageSections(source);
76+
expect(homePages, "No response from server").to.exist;
77+
});
78+
79+
it("Testing Notifications", async () => {
80+
const updates = await wrapper.filterUpdatedManga(source, new Date("2021-03-26"), [mangaId]);
81+
82+
expect(updates, "No server response").to.exist;
83+
expect(updates, "Empty server response").to.not.be.empty;
84+
expect(updates[0].ids, "No updates").to.not.be.empty;
85+
});
86+
});

0 commit comments

Comments
 (0)