Skip to content

Instantly share code, notes, and snippets.

@FlafyDev
Created August 9, 2022 09:27
Show Gist options
  • Save FlafyDev/9f5f03903addfdb94e60ea167e345fb8 to your computer and use it in GitHub Desktop.
Save FlafyDev/9f5f03903addfdb94e60ea167e345fb8 to your computer and use it in GitHub Desktop.
ZLibrary metadata and downloader for my book reader.
import 'package:epub_reader/providers/book_downloader/zlibrary_book_downloader.dart';
import 'package:http/http.dart';
import '../../models/book.dart';
abstract class BookDownloader {
Client httpClient;
BookDownloader({required this.httpClient});
Future<Uri?> getEpubDownload(BookIdentifier bookIdentifier);
}
enum BookDownloaderEnum {
none,
zlibrary,
}
extension BookDownloaderEnumExtension on BookDownloaderEnum {
String get name {
switch (this) {
case BookDownloaderEnum.none:
return 'None';
case BookDownloaderEnum.zlibrary:
return 'ZLibrary';
}
}
}
Future<BookDownloader?> createBookDownloader(
BookDownloaderEnum bookDownloader) async {
switch (bookDownloader) {
case BookDownloaderEnum.none:
return null;
case BookDownloaderEnum.zlibrary:
return ZLibraryBookDownloader(httpClient: Client());
}
}
import 'package:epub_reader/models/book.dart';
import 'package:epub_reader/providers/book_metadata/zlibrary_book_metadata.dart';
import 'package:http/http.dart';
abstract class BookMetadata {
Client httpClient;
BookMetadata({required this.httpClient});
Future<List<Book>> searchBooks(String query);
Future<Book?> getBookData(BookIdentifier bookIdentifier);
}
enum BookMetadataEnum {
none,
zlibrary,
}
extension BookMetadataEnumExtension on BookMetadataEnum {
String get name {
switch (this) {
case BookMetadataEnum.none:
return 'None';
case BookMetadataEnum.zlibrary:
return 'ZLibrary';
}
}
}
Future<BookMetadata?> createBookMetadata(BookMetadataEnum bookMetadata) async {
switch (bookMetadata) {
case BookMetadataEnum.none:
return null;
case BookMetadataEnum.zlibrary:
return ZLibraryBookMetadata(httpClient: Client());
}
}
import 'package:epub_reader/models/book.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import '../book_metadata/zlibrary_book_metadata.dart';
import 'book_downloader.dart';
class ZLibraryBookDownloader extends BookDownloader {
ZLibraryBookMetadata zLibraryBookMetadata;
ZLibraryBookDownloader({required Client httpClient})
: zLibraryBookMetadata = ZLibraryBookMetadata(httpClient: httpClient),
super(httpClient: httpClient);
@override
Future<Uri?> getEpubDownload(BookIdentifier bookIdentifier) async {
String? id = bookIdentifier.other["zlibrary"];
if (id == null) {
final isbn = bookIdentifier.getAnyIsbn();
if (isbn == null) {
return null;
}
final books = await zLibraryBookMetadata.searchBooks(isbn);
if (books.isEmpty) {
return null;
}
id = books.first.bookIdentifier.other["zlibrary"];
}
final rawHtml = (await httpClient
.get(zLibraryBookMetadata.baseUri.replace(path: "$id")))
.body;
final document = parse(rawHtml);
final downloadLinks = document.body!
.querySelectorAll('[href]')
.where((elem) =>
elem.attributes["href"]!.contains("/dl/") &&
elem.text.toLowerCase().contains("epub"))
.map((elem) => zLibraryBookMetadata.baseUri
.replace(path: elem.attributes["href"]!));
if (downloadLinks.isEmpty) {
return null;
}
return downloadLinks.first;
// String rawHtml =
// (await httpClient.get(baseUri.replace(path: "s/$isbn"))).body;
}
}
import 'package:epub_reader/models/book.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'book_metadata.dart';
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as dom;
class ZLibraryBookMetadata extends BookMetadata {
Uri baseUri = Uri.https("b-ok.asia", "");
ZLibraryBookMetadata({required Client httpClient})
: super(httpClient: httpClient);
@override
Future<Book?> getBookData(BookIdentifier bookIdentifier) async {
String rawHtml = (await httpClient
.get(baseUri.replace(path: "${bookIdentifier.other["zlibrary"]}")))
.body;
final document = parse(rawHtml);
final body = document.body!;
final isbns = body.getElementsByClassName("property_isbn");
return Book(
name: body.querySelector('[itemprop="name"]')?.text.trim() ?? "No title",
authors: body
.querySelectorAll('[itemprop="author"]')
.map((authorElem) => authorElem.text)
.toList(),
description:
body.querySelector('[id="bookDescriptionBox"]')?.text.trim() ?? "",
coverProvider: NetworkImage(body
.getElementsByClassName("details-book-cover")[0]
.attributes["href"]!),
bookIdentifier: BookIdentifier(
isbn10: _findIsbnFromList(isbns, 10),
isbn13: _findIsbnFromList(isbns, 13),
other: {
"zlibrary": bookIdentifier.other["zlibrary"],
},
),
);
}
String? _findIsbnFromList(List<dom.Element> list, int length) {
return list
.map<String?>(
(elem) => elem.children[1].text,
)
.firstWhere(
(isbn) => isbn?.length == length,
orElse: () => null,
);
}
// Uri(
// scheme: "https",
// host: "b-ok.asia",
// )
@override
Future<List<Book>> searchBooks(String query) async {
String rawHtml =
(await httpClient.get(baseUri.replace(path: "s/$query"))).body;
final document = parse(rawHtml);
final books = document.getElementsByClassName("bookRow");
return books.map((book) {
final coverUrl = book.querySelector('img')?.attributes["data-src"];
return Book(
name:
book.querySelector('[itemprop="name"]')?.text.trim() ?? "No title",
authors: book
.querySelectorAll('[itemprop="author"]')
.map((authorElem) => authorElem.text)
.toList(),
description: "",
coverProvider:
coverUrl != null && !coverUrl.contains("cover-not-exists")
? NetworkImage(coverUrl)
: null,
pages: 1,
bookIdentifier: BookIdentifier(
isbn13: book
.querySelector(".checkBookDownloaded")
?.attributes["data-isbn"],
other: {
"zlibrary":
book.querySelector('[itemprop="name"] > a')?.attributes["href"],
},
),
);
}).toList();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment