);
};
diff --git a/components/global/Footer.js b/components/global/Footer.js
index 13f2682..5f4484d 100644
--- a/components/global/Footer.js
+++ b/components/global/Footer.js
@@ -2,8 +2,8 @@ import React from "react";
import Link from "next/link";
const Footer = () => {
- const version = "v2.14.2";
- const versionSlug = "2142---jun-30-2023";
+ const version = "v2.15.0";
+ const versionSlug = "2150---jul-15-2023";
console.log(`%c${version} (Oreki)`, `color:green`);
diff --git a/components/global/ThemeToggle.js b/components/global/ThemeToggle.js
index 0694e65..c1d179f 100644
--- a/components/global/ThemeToggle.js
+++ b/components/global/ThemeToggle.js
@@ -22,55 +22,54 @@ const ThemeToggle = () => {
return (
// Change the button icon based on the preferred theme
- <>
-
);
};
diff --git a/components/listpage/BookList.js b/components/listpage/BookList.js
index aa6e0a8..8003523 100644
--- a/components/listpage/BookList.js
+++ b/components/listpage/BookList.js
@@ -7,82 +7,108 @@ const BookList = (props) => {
return (
- {props.books.map((data, i) => (
-
);
};
diff --git a/package-lock.json b/package-lock.json
index 4f2568a..ff1b3bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "biblioreads",
- "version": "2.14.2",
+ "version": "2.15.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "biblioreads",
- "version": "2.14.2",
+ "version": "2.15.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"babel-loader": "^9.1.0",
diff --git a/package.json b/package.json
index d1850c8..4a15e21 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "biblioreads",
- "version": "2.14.2",
+ "version": "2.15.0",
"description": "An Alternative Goodreads Front-End",
"private": true,
"author": "Nesaku",
diff --git a/pages/api/author/books.js b/pages/api/author/books.js
new file mode 100644
index 0000000..a35b73a
--- /dev/null
+++ b/pages/api/author/books.js
@@ -0,0 +1,92 @@
+const cheerio = require("cheerio");
+
+const BooksScraper = async (req, res) => {
+ if (req.method === "POST") {
+ // The default sort is by popularity
+ // Use the URL parameter "per_page" to get 100 instead of the default 30 books
+ const scrapeURL =
+ req.body.queryURL.split("&")[0] + `?page=${req.body.page}&per_page=100`;
+ try {
+ const response = await fetch(`${scrapeURL}`, {
+ method: "GET",
+ headers: new Headers({
+ "User-Agent":
+ process.env.NEXT_PUBLIC_USER_AGENT ||
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36",
+ }),
+ });
+
+ const htmlString = await response.text();
+ const $ = cheerio.load(htmlString);
+ const title = $("div.mainContentFloat > h1").text();
+ /*
+ const author = $("div.leftContainer > div > a.authorName").text();
+ const authorURL = $("div.leftContainer > div > a.authorName").attr(
+ "href"
+ );
+ const authorIMG = $("div.leftContainer > a > img").attr("src");
+ */
+ const desc = $("div.leftContainer > div:nth-child(2)").text();
+ const books = $("tbody > tr")
+ .map((i, el) => {
+ const $el = $(el);
+ const cover = $el.find("td > a > img.bookCover").attr("src");
+ const title = $el.find("td > a > span").text();
+ const bookURL = $el.find("td > a").attr("href");
+ const author = $el
+ .find("td > span[itemprop = 'author'] > div > a > span")
+ .text();
+ const authorURL = $el
+ .find("td > span[itemprop = 'author'] > div > a")
+ .attr("href");
+ const rating = $el
+ .find("td > div > span.greyText.smallText.uitext > span")
+ .text();
+ const id = i + 1;
+ return {
+ id: id,
+ cover: cover,
+ title: title,
+ bookURL: bookURL,
+ author: author,
+ authorURL: authorURL,
+ rating: rating,
+ };
+ })
+ .toArray();
+ const previousPage = $(
+ "div.leftContainer > div[style='float: right'] > div > a.previous_page"
+ ).attr("href");
+ const nextPage = $(
+ "div.leftContainer > div[style='float: right'] > div > a.next_page"
+ ).attr("href");
+ const lastScraped = new Date().toISOString();
+ res.statusCode = 200;
+ return res.json({
+ status: "Received",
+ source: "https://github.com/nesaku/biblioreads",
+ scrapeURL: scrapeURL,
+ title: title,
+ desc: desc,
+ books: books,
+ previousPage: previousPage,
+ nextPage: nextPage,
+ lastScraped: lastScraped,
+ });
+ } catch (error) {
+ res.statusCode = 404;
+ console.error("An Error Has Occurred");
+ return res.json({
+ status: "Error - Invalid Query",
+ scrapeURL: scrapeURL,
+ });
+ }
+ } else {
+ res.statusCode = 405;
+ return res.json({
+ status: "Error 405 - Method Not Allowed",
+ });
+ }
+};
+
+export default BooksScraper;
diff --git a/pages/api/author-scraper.js b/pages/api/author/info.js
similarity index 100%
rename from pages/api/author-scraper.js
rename to pages/api/author/info.js
diff --git a/pages/author/list/[...slug].js b/pages/author/list/[...slug].js
new file mode 100644
index 0000000..3c173b8
--- /dev/null
+++ b/pages/author/list/[...slug].js
@@ -0,0 +1,74 @@
+import { useEffect, useState } from "react";
+import { useRouter } from "next/router";
+
+import Header from "../../../components/global/Header";
+import Footer from "../../../components/global/Footer";
+import Loader from "../../../components/global/Loader";
+import ErrorMessage from "../../../components/global/ErrorMessage";
+import AuthorBookList from "../../../components/authorpage/AuthorBookList";
+
+const Slug = () => {
+ const router = useRouter();
+ const { slug } = router.query;
+ const [scrapedData, setScrapedData] = useState({});
+ const [error, setError] = useState(false);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const res = await fetch(`/api/author/books`, {
+ method: "POST",
+ headers: {
+ "content-type": "application/json",
+ },
+ body: JSON.stringify({
+ page: 1,
+ queryURL: `https://www.goodreads.com/author/list/${slug}`,
+ }),
+ });
+ if (res.ok) {
+ const data = await res.json();
+ setScrapedData(data);
+ } else {
+ setError(true);
+ }
+ };
+ if (slug) {
+ fetchData();
+ }
+ }, [slug]);
+
+ return (
+
+
+
+ {error && (
+
+ )}
+ {!error && (
+ <>
+ {scrapedData.title === undefined &&
}
+ {scrapedData.error && (
+
+ )}
+ {scrapedData.title === "" && (
+
+ )}
+ {scrapedData &&
}
+ >
+ )}
+
+
+
+ );
+};
+
+export default Slug;
diff --git a/pages/author/show/[...slug].js b/pages/author/show/[...slug].js
index 32f0246..bcebf99 100644
--- a/pages/author/show/[...slug].js
+++ b/pages/author/show/[...slug].js
@@ -15,7 +15,7 @@ const Slug = () => {
useEffect(() => {
const fetchData = async () => {
- const res = await fetch(`/api/author-scraper`, {
+ const res = await fetch(`/api/author/info`, {
method: "POST",
headers: {
"content-type": "application/json",
diff --git a/pages/list/show/[...slug].js b/pages/list/show/[...slug].js
index 8b25141..4ba3273 100644
--- a/pages/list/show/[...slug].js
+++ b/pages/list/show/[...slug].js
@@ -44,7 +44,7 @@ const Slug = () => {
{error && (
)}
{!error && (
@@ -53,13 +53,13 @@ const Slug = () => {
{scrapedData.error && (
)}
{scrapedData.title === "" && (
)}
{scrapedData &&
}