first commit
This commit is contained in:
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal 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
32
.npmignore
Normal 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
75
Jenkinsfile
vendored
Normal 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
88
Readme.md
Normal 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
13
config/index.js
Normal 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;
|
||||
45
lib/index.js
Normal file
45
lib/index.js
Normal 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
94
lib/module.js
Normal 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
11
mocha-report-config.json
Normal 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
35
package.json
Normal 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
57
test/index.js
Normal 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user