Skip to content

Commit

Permalink
Merge pull request #5 from MikeZeDev/Experiments
Browse files Browse the repository at this point in the history
Experiments
  • Loading branch information
MikeZeDev authored Nov 24, 2024
2 parents 55ae7ed + c344fb7 commit 256cae9
Show file tree
Hide file tree
Showing 15 changed files with 1,206 additions and 10 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ cache
/redist/*.zip
/test/*.log
junit.xml
.idea/
.idea/
.vs
Binary file added src/web/img/connectors/beautymanga
Binary file not shown.
53 changes: 53 additions & 0 deletions src/web/mjs/connectors/BeautyManga.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import WordPressMadara from './templates/WordPressMadara.mjs';
export default class BeautyManga extends WordPressMadara {
constructor() {
super();
super.id = 'beautymanga';
super.label = 'BeautyManga';
this.tags = [ 'manga', 'webtoon', 'english' ];
this.url = 'https://mangadex.today';
this.path = '/popular-manga';
this.queryMangas = 'div.item-thumb.hover-details.c-image-hover a';
this.queryMangasPageCount = 'ul.pagination li:nth-last-of-type(2) a';
this.pathMangas = '?page=%PAGE%';
this.queryPages = 'p#arraydata';
this.mangaTitleFilter = '';
}
async _getMangasFromPage(page) {
let uri = new URL(this.path + this.pathMangas.replace('%PAGE%', page), this.url);
uri.pathname = uri.pathname.replace(/\/+/g, '/');
let request = new Request(uri, this.requestOptions);
let data = await this.fetchDOM(request, this.queryMangas);
return data.map(element => {
return {
id: new URL(element.href, request.url).pathname,
title: element.title.replace(this.mangaTitleFilter, '').trim()
};
});
}
async _getChaptersAjaxOld(mangaID) {
const uri = new URL('/ajax-list-chapter?mangaID='+mangaID, this.url);
const request = new Request(uri, {
method: 'GET'
});
const data = await this.fetchDOM(request, this.queryChapters);
if (data.length) {
return data;
} else {
throw new Error('No chapters found (new ajax endpoint)!');
}
}
async _getPages(chapter) {
let uri = new URL(chapter.id, this.url);
let request = new Request(uri, this.requestOptions);
let data = await this.fetchDOM(request, this.queryPages);
let el = data[0].innerText.split(',');
return el.map(element => {
const uri = new URL(this.getAbsolutePath(element, request.url));
return this.createConnectorURI({
url: uri.href,
referer: uri.origin
});
});
}
}
154 changes: 154 additions & 0 deletions src/web/mjs/connectors/BookWalker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import Connector from '../engine/Connector.mjs';
import Manga from '../engine/Manga.mjs';

export default class BookWalker extends Connector {

constructor() {
super();
super.id = 'bookwalker';
super.label = 'BookWalker';
this.tags = [ 'manga', 'japanese' ];
this.url = 'https://global.bookwalker.jp';
}

async _getMangaFromURI(uri) {
let id = uri.pathname;
let request = new Request(uri, this.requestOptions);
let data = await this.fetchDOM(request, 'div.detail-book-title h1[itemprop="name"]');
let title = data[0].textContent.trim();
return new Manga(this, id, title);
}

async _getChapters(manga) {
const url = new URL(manga.id, this.url);

Check failure on line 23 in src/web/mjs/connectors/BookWalker.mjs

View workflow job for this annotation

GitHub Actions / Ubuntu

Mixed spaces and tabs
url.searchParams.set('sample', '2');

Check failure on line 24 in src/web/mjs/connectors/BookWalker.mjs

View workflow job for this annotation

GitHub Actions / Ubuntu

Mixed spaces and tabs
return [{id : url.pathname+url.search, title : 'Free Sample',}];

Check failure on line 25 in src/web/mjs/connectors/BookWalker.mjs

View workflow job for this annotation

GitHub Actions / Ubuntu

Mixed spaces and tabs
}

async _getPages(chapter) {
const script = `
new Promise((resolve, reject) => {
setTimeout(async () => {
const a2f = NFBR.a2F ? new NFBR.a2F() : new NFBR.a2f();
const params = new URL(window.location).searchParams;
const parameters = await a2f.a5W({
contentId: params.get(NFBR.a5q.Key.CONTENT_ID), // Content ID => 'cid'
a6m: params.get(NFBR.a5q.Key.LICENSE_TOKEN), // License Token => 'lit'
preview: params.get(NFBR.a5q.Key.LOOK_INSIDE) === '1', // Look Inside => 'lin'
contentType: params.get(NFBR.a5q.Key.CONTENT_TYPE) || 1, // Content Type => 'cty'
title: params.get(NFBR.a5q.Key.CONTENT_TITLE), // Content Title => 'cti'
winWidth: 3840,
winHeight: 2160
});
//Create a model
const model = new NFBR.a6G.Model({
'settings': new self.NFBR.Settings('NFBR.SettingData'),
'viewerFontSize': self.NFBR.a0X.a3k,
'viewerFontFace': self.NFBR.a0X.a3k,
'viewerSpreadDouble': true,
'viewerb5c': null,
'viewerSpread': {},
'queryParamForContentUrl' : parameters.contentAppendParam,
});
//Create the bookloader
const bl = new NFBR.a5n();
bl.B0a = "normal_default";
//Create a "Content" that will be filled using the bookloader as5 async function
let data = new NFBR.a6i.Content(parameters.url);
let v_a6L = new NFBR.a6G.a6L(model);
await bl.a5s(data, "configuration", v_a6L);
//console.log(data);
//data is now our JSON with all the infos
const pages = data.configuration.contents.map((page, index) => {
let mode = 'raw';
let extension = '.jpeg';
//*****************/
//GETTING PAGE URL
//*****************/
//Create a Page
const fPage = new NFBR.a6i.Page(index, page.file, "0", extension, "");
const realURL = v_a6L.getPageContentUrl_(data, fPage);
//*****************************/
//GETTING IMAGE SCRAMBLE DATA
//*****************************/
//Fill infos in the page for a7b to work
const fileinfos = data.files[index].FileLinkInfo.PageLinkInfoList[0].Page;
fPage.width = fileinfos.Size.Width;
fPage.height = fileinfos.Size.Height;
fPage.info = fileinfos;
//Fill more infos needed for unscrambling
fPage.A3j(data);
let blocks = [];
if (fileinfos.BlockHeight) //if we have a block size for the page, its a puzzle !
{
mode = 'puzzle';
blocks = window.NFBR.a6G.a5x.prototype.R8x(fPage, fPage.width, fPage.height)
}
return {
mode: mode,
imageUrl: realURL,
encryption: {
blocks: JSON.stringify(blocks),//stringify the array greatly speed up createConnectorURI
}
};
});
resolve(pages);
}, 1000);
});
`;
const uri = new URL( chapter.id, this.url );
const request = new Request( uri.href, this.requestOptions );
const data = await Engine.Request.fetchUI(request, script);
return data.map(page => page.mode == 'raw' ? page.imageUrl : this.createConnectorURI(page));
}

async _handleConnectorURI(payload) {
const uri = new URL(payload.imageUrl, this.url);
const request = new Request(uri, this.requestOptions);
const response = await fetch(request);
switch (payload.mode) {
case 'puzzle': {
let data = await response.blob();
data = await this._descrambleImage(data, payload.encryption.blocks);
return this._blobToBuffer(data);
}
default: {
let data = await response.blob();
return this._blobToBuffer(data);
}
}
}

async _descrambleImage(scrambled, blocks) {
let bitmap = await createImageBitmap(scrambled);
return new Promise(resolve => {
let canvas = document.createElement('canvas');
canvas.width = bitmap.width;
canvas.height = bitmap.height;
var ctx = canvas.getContext('2d');
const blockz = JSON.parse(blocks);
for (let q of blockz) {
ctx.drawImage(bitmap, q.destX, q.destY, q.width, q.height, q.srcX, q.srcY, q.width, q.height);
}
canvas.toBlob(data => {
resolve(data);
}, Engine.Settings.recompressionFormat.value, parseFloat(Engine.Settings.recompressionQuality.value)/100);
} );
}

}
135 changes: 135 additions & 0 deletions src/web/mjs/connectors/Cuutruyen.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import Connector from '../engine/Connector.mjs';
import Manga from '../engine/Manga.mjs';

export default class CuuTruyen extends Connector {
constructor() {
super();
super.id = 'cuutruyen';
super.label = 'Cứu Truyện';
this.tags = ['manga', 'vietnamese'];
this.url = 'https://cuutruyen.net';
}

async _getMangaFromURI(uri) {
const mangaid = uri.href.match(/\/mangas\/([0-9]+)/)[1];
const req = new URL(`/api/v2/mangas/${mangaid}`, this.url);
const request = new Request(req, this.requestOptions);
const { data: { name } } = await this.fetchJSON(request);
return new Manga(this, mangaid, name.trim());
}

async _getMangas() {
const uri = new URL('/api/v2/mangas/recently_updated?page=1&per_page=30', this.url);
const request = new Request(uri, this.requestOptions);
const data = await this.fetchJSON(request);
const pages = data._metadata.total_pages;

const mangaList = this._getMangasFromPage(data);

for (let page = 2; page <= pages; page++) {
const uri = new URL(`/api/v2/mangas/recently_updated?page=${page}&per_page=30`, this.url);
const request = new Request(uri, this.requestOptions);
const data = await this.fetchJSON(request);
const mangas = this._getMangasFromPage(data);
mangaList.push(...mangas);
}
return mangaList;
}

_getMangasFromPage(data) {
return data.data.map((c) => ({
id: c.id,
title: c.name.trim(),
}));
}

async _getChapters(manga) {
const uri = new URL(`/api/v2/mangas/${manga.id}/chapters`, this.url);
const request = new Request(uri, this.requestOptions);
const { data } = await this.fetchJSON(request);
return data
.filter((chapter) => chapter.status === 'processed')
.map((chapter) => {
let title = `Chapter ${chapter.number}`;

if (chapter.name) {
title += `: ${chapter.name}`;
}

return { id: chapter.id, title };
});
}

async _getPages(chapter) {
const uri = new URL('/api/v2/chapters/' + chapter.id, this.url);
const request = new Request(uri, this.requestOptions);
const { data: { pages } } = await this.fetchJSON(request);

if (pages.some((image) => image.status !== 'processed')) {
throw new Error('This chapter is still processing, please try again later.');
}

return pages.map((image) => {
return this.createConnectorURI({
url: image.image_url,
drmData: image.drm_data,
});
});
}

async _handleConnectorURI(payload) {
const response = await fetch(payload.url, {
cache: 'no-cache',
referrer: `${this.url}/`,
headers: {
'accept': 'image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5',
},
});

if (!payload.drmData) {
return this._blobToBuffer(await response.blob());
}

const decryptedDrmData = this.decodeXorCipher(atob(payload.drmData), '3141592653589793');

if (!decryptedDrmData.startsWith('#v4|')) {
throw new Error(`Invalid DRM data (does not start with magic bytes): ${decryptedDrmData}`);
}

const image = await createImageBitmap(await response.blob());
const canvas = document.createElement('canvas');

canvas.width = image.width;
canvas.height = image.height;

const ctx = canvas.getContext('2d');
let sy = 0;

for (const t of decryptedDrmData.split('|').slice(1)) {
const [dy, height] = t.split('-', 2).map(Number);

ctx.drawImage(image, 0, sy, image.width, height, 0, dy, image.width, height);
sy += height;
}

return this._blobToBuffer(await this._canvasToBlob(canvas));
}

_canvasToBlob(canvas) {
return new Promise(resolve => {
canvas.toBlob(data => {
resolve(data);
}, Engine.Settings.recompressionFormat.value, parseFloat(Engine.Settings.recompressionQuality.value) / 100);
});
}

decodeXorCipher(data, key) {
let output = "";

for (let i = 0; i < data.length; i++) {
output += String.fromCharCode(data.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}

return output;
}
}
Loading

0 comments on commit 256cae9

Please sign in to comment.