first commit

This commit is contained in:
2025-01-18 21:26:13 +03:00
commit 1a01c5fabc
11 changed files with 499 additions and 0 deletions

48
.gitignore vendored Normal file
View File

@@ -0,0 +1,48 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
reports/
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
node_modules/
public/src/thumbs
public/avatars/
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
package-lock.json
.env
.env-mongo
.vscode/
settings.json
Procfile
dump.rdb
error.log
output.log
.env.development
.env.production

32
.npmignore Normal file
View File

@@ -0,0 +1,32 @@
# Jenkins ve test raporlarını dahil et
Jenkinsfile
mocha-report-config.json
reports/
# Geçici dosyalar ve günlükler
*.log
*.tmp
# Node.js bağımlılıkları
node_modules/
# Derleme veya yapım çıktıları
dist/
build/
# Test ve kapsama raporları
coverage/
# Yapılandırma dosyaları (sadece geliştirme için)
.env
.eslintrc.js
.babelrc
# Projeye özel dosyalar
.secret
.vscode/
.idea/
# Diğer geçici dosyalar
*.swp
*.bak

75
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,75 @@
pipeline {
agent any
environment {
BUILD_TIMESTAMP = new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new java.util.Date())
}
tools {
nodejs('22.13.0')
}
triggers {
cron('H */6 * * *') // Her 6 saatte bir çalıştır
}
stages {
stage('List directory - Before removing reports') {
steps {
sh '''
echo "List directory - Before removing reports"
ls -la
'''
}
}
stage('Remove reports') {
steps {
sh 'rm -rf reports'
}
}
stage('List directory - After removing reports') {
steps {
sh 'ls -la'
}
}
stage('Install Dependencies') {
steps {
sh 'npm install'
}
}
stage('Run Tests') {
steps {
sh 'npm test'
}
}
stage('Publish Test Results') {
steps {
junit 'reports/test-results.xml'
}
}
}
post {
always {
slackSend(
channel: '#jenkins',
tokenCredentialId: 'slack-token',
message: """
\n 🧠 Goodreads Book Search Integration Test:
\n - Is the ISBN '9944824453' 🔥
\n - Is the book title 'Dövmeli Adam' 🚀
\n - Is the book's publication date '1 September 2008' ⏰
\n - Is the page count '640' 📋
\n Build Zamanı: ${env.BUILD_TIMESTAMP}
\n Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' tamamlandı.
\n Detaylar: ${env.BUILD_URL}
""",
color: currentBuild.result == 'SUCCESS' ? 'good' : 'danger'
)
}
failure {
slackSend(
channel: '#jenkins',
tokenCredentialId: 'slack-token',
message: "Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' başarısız oldu. Detaylar: ${env.BUILD_URL}",
color: 'danger'
)
}
}
}

88
Readme.md Normal file
View File

@@ -0,0 +1,88 @@
# Unoffical Goodreads Book Search API
This library allows you to retrieve book information from Goodreads using only the ISBN number, without needing an API key. The library analyzes Goodreads' HTML structure and returns book details in JSON format. It works asynchronously (based on Promises).
## ✨ Features
- 📚 Retrieves book information from Goodreads using the ISBN number.
- 🖼️ Returns detailed information such as book cover, title, author, genre, and publication date.
- 🕒 Customizable delay between requests.
- ✅ Tests integrated with Mocha and Chai.
- 🌐 Web data is fetched and parsed using Axios and Cheerio.
## 🎯 Requirements
- Node.js (v14 veya üzeri)
- NPM
- Internet connection
- Curiosity
## 📦 Installation
```bash
npm install wisecolt-goodreads-search-api
```
## 🚀 Usage
The following example shows how to use the API:
```javascript
const GoodreadsBookSearch = require("wisecolt-goodreads-search-api");
(async () => {
try {
const BookSearch = new GoodreadsBookSearch();
const bookDetails = await BookSearch.getBookDetails("9944824453");
console.log(bookDetails);
} catch (error) {
console.error("Hata:", error.message);
}
})();
// Example Output:
{
title: 'Dövmeli Adam',
thumbImage: 'https: //res.cloudinary.com/path/to/image.jpg',
authorName: {
author: { name: 'Peter V. Brett', profileLink: '/author/show/12345'
},
translators: ['Çevirmen Adı 1', 'Çevirmen Adı 2'
]
},
description: 'Kitap açıklaması burada yer alır.',
page: '640',
isbn: '9944824453',
date: '01 Sept 2008',
rate: '4.3',
genres: ['Fantasy', 'Adventure', 'Action'
]
}
```
## 📂 Project Structure
```javascript
wisecolt-goodreads-search-api
config
index.js # Konfigürasyon dosyası
lib
index.js # GoodreadsBookSearch sınıfı
module.js # Veri işleme ve parse işlemleri
test
index.js # Entegrasyon testleri
index.js # Giriş noktası
package.json # Bağımlılıklar ve betikler
README.md # Dokümantasyon
```
## 🧪 Tests
To run the tests, follow these steps:
1. Install test dependencies:
```javascript
npm install
```
2. Run the tests:
```javascript
npm test
```

13
config/index.js Normal file
View File

@@ -0,0 +1,13 @@
require("dotenv").config();
const config = {
goodreadsBaseUrl: "https://www.goodreads.com/search?q=",
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.105 Safari/537.36",
},
// Goodreads'e istek atarken, iki istek arasındaki zaman farkıdır
fetchTimeout: 2000,
};
module.exports = config;

1
index.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./lib');

45
lib/index.js Normal file
View File

@@ -0,0 +1,45 @@
const modules = require("./module");
const config = require("../config");
const axios = require("axios");
class GoodreadsBookSearch {
getBookDetails = async (isbn) => {
const headers = config.headers;
const url = encodeURI(config.goodreadsBaseUrl + isbn);
try {
return new Promise((resolve, reject) => {
setTimeout(async () => {
try {
const response = await axios.get(url, { headers });
if (!response.data) {
throw new Error("Detay bilgisi bulunamadı!");
}
const details = await modules.extractBookDetails(
response.data,
isbn,
);
resolve(details);
} catch (error) {
reject(error);
}
}, config.fetchTimeout);
});
} catch (error) {
throw new Error("Hata: " + error.message);
}
};
}
module.exports = GoodreadsBookSearch;
// (async () => {
// try {
// const BookSearch = new GoodreadsBookSearch();
// const bookDetails = await BookSearch.getBookDetails("6059141005");
// // console.log(bookDetails);
// } catch (error) {
// console.log(error.message);
// }
// })();

94
lib/module.js Normal file
View File

@@ -0,0 +1,94 @@
const cheerio = require("cheerio");
const extractBookPage = ($) => {
// "pagesFormat" test ID'sine sahip p etiketindeki sayıyı çıkar
const pagesText = $('[data-testid="pagesFormat"]').text();
// Metinden yalnızca rakamı almak için regex kullan
const match = pagesText.match(/\d+/); // Sadece sayıları bulur
return match ? match[0] : null; // Sayıyı döndür
};
const extractBookDate = ($) => {
const publicationDateText = $('p[data-testid="publicationInfo"]').text().replace('First published ', '').trim();
const publicationDate = new Date(publicationDateText);
const formattedDate = publicationDate.toLocaleDateString('en-GB', {
day: '2-digit',
month: 'short',
year: 'numeric'
});
return formattedDate;
};
const extractAuthorAndTranslators = ($) => {
// Yazar ve çevirmen isimlerini Set'lere ekle
const authorsSet = new Set();
const translatorsSet = new Set();
const authorLink = $('a.ContributorLink').attr('href');
$('.ContributorLink').each((index, element) => {
const name = $(element).find('.ContributorLink__name').text().trim();
const role = $(element).find('.ContributorLink__role').text().trim();
if (role.toLowerCase().includes('translator')) {
translatorsSet.add(name); // Çevirmenleri ekle
} else {
authorsSet.add({ name, profileLink: authorLink }); // Yazarları ekle
}
});
const authors = Array.from(authorsSet);
const translators = Array.from(translatorsSet);
const result = {};
if (authors.length > 0) {
result.author = authors[0]; // İlk yazar
}
if (translators.length > 0) {
result.translators = translators; // Çevirmenlerin tamamı
}
return result;
}
function extractGenreList($) {
const genres = [];
$('span.BookPageMetadataSection__genreButton').each((index, element) => {
const genre = $(element).find('.Button__labelItem').text().trim();
genres.push(genre);
});
return genres;
}
const extractBookDetails = async (html, isbn, gemini_api_key) => {
const $ = cheerio.load(html);
const title = $('h1[data-testid="bookTitle"]').text(); // OK
const thumbImage = $('meta[property="og:image"]').attr('content'); // OK
const authorName = extractAuthorAndTranslators($); // OK
const description = $('.BookPageMetadataSection__description .Formatted').text().trim(); //OK
const page = extractBookPage($);
const rate = $('.RatingStatistics__rating').first().text().trim();
const date = extractBookDate($);
const genres = extractGenreList($);
return {
title,
thumbImage,
authorName,
description,
page,
isbn,
date,
rate,
genres
};
};
module.exports = { extractBookDetails };

11
mocha-report-config.json Normal file
View File

@@ -0,0 +1,11 @@
{
"reporterEnabled": "spec, mocha-junit-reporter",
"mochaJunitReporterReporterOptions": {
"mochaFile": "./reports/test-results.xml",
"testsuitesTitle": "🧠 Goodreads Book Search Integration Test",
"suiteTitleSeparatedBy": ": ",
"properties": {
"environment": "development"
}
}
}

35
package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "wisecolt-goodreads-search-book",
"version": "1.1.0",
"description": "Allows searching for books on Goodreads. To print the book description with gemini-ai you need an api key.",
"repository": {
"type": "git",
"url": "https://github.com/szbk/goodreads-book-search.git"
},
"keywords": [
"goodreads",
"book",
"isbn",
"search",
"library",
"api"
],
"main": "index.js",
"scripts": {
"test": "mocha --reporter mocha-multi-reporters --reporter-options configFile=mocha-report-config.json"
},
"author": "wisecolt",
"license": "MIT",
"dependencies": {
"@google/generative-ai": "^0.8.0",
"axios": "^1.6.8",
"cheerio": "^1.0.0-rc.12",
"dotenv": "^16.4.5"
},
"devDependencies": {
"chai": "^5.1.2",
"mocha": "^11.0.1",
"mocha-junit-reporter": "^2.2.1",
"mocha-multi-reporters": "^1.5.1"
}
}

57
test/index.js Normal file
View File

@@ -0,0 +1,57 @@
const { expect } = require("chai");
const GoodreadsBookSearch = require("../index");
describe("🧠 Goodreads Book Search Integration Test", () => {
let bookSearch;
const isbn = "9944824453";
const timeoutDuration = 30000;
beforeEach(() => {
bookSearch = new GoodreadsBookSearch();
});
it('Is the ISBN "9944824453" 🔥', function (done) {
this.timeout(timeoutDuration);
bookSearch.getBookDetails(isbn)
.then((bookDetails) => {
expect(bookDetails).to.have.property("isbn");
expect(bookDetails.isbn).to.equal("9944824453");
done();
})
.catch(done);
});
it('Is the book title "Dövmeli Adam" 🚀', function (done) {
this.timeout(timeoutDuration);
bookSearch.getBookDetails(isbn)
.then((bookDetails) => {
expect(bookDetails).to.have.property("title");
expect(bookDetails.title).to.equal("Dövmeli Adam");
done();
})
.catch(done);
});
it('Is the book\'s publication date "1 September 2008" ⏰', function (done) {
this.timeout(timeoutDuration);
bookSearch.getBookDetails(isbn)
.then((bookDetails) => {
expect(bookDetails).to.have.property("date");
expect(bookDetails.date).to.equal("01 Sept 2008");
done();
})
.catch(done);
});
it('Is the page count "640" 📋', function (done) {
this.timeout(timeoutDuration);
bookSearch.getBookDetails(isbn)
.then((bookDetails) => {
expect(bookDetails).to.have.property("page");
expect(bookDetails.page).to.equal("640");
done();
})
.catch(done);
});
});