Move to multi-files

This commit is contained in:
BlipRanger 2024-08-24 23:20:28 -04:00
parent 3c70c1d53d
commit 78ca8f815a
9 changed files with 2930 additions and 170 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

BIN
books.db Normal file

Binary file not shown.

View File

@ -3,6 +3,8 @@ const express = require('express');
const https = require('https'); const https = require('https');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const sqlite3 = require('sqlite3').verbose();
const bodyParser = require('body-parser');
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
@ -13,6 +15,28 @@ const certificate = fs.readFileSync(path.join(__dirname, 'server.cert'), 'utf8')
const credentials = { key: privateKey, cert: certificate }; const credentials = { key: privateKey, cert: certificate };
// Middleware to parse JSON bodies
app.use(express.json()); // Use built-in body-parser for JSON
// Set up the SQLite database
const db = new sqlite3.Database('./books.db', (err) => {
if (err) {
console.error('Could not connect to database', err);
} else {
console.log('Connected to database');
db.run(`
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
isbn TEXT,
title TEXT,
authors TEXT,
publishedDate TEXT,
description TEXT
)
`);
}
});
// Serve static files from the 'public' directory // Serve static files from the 'public' directory
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
@ -27,6 +51,8 @@ app.get('/book/:isbn', async (req, res) => {
const bookData = openLibraryResponse.data[`ISBN:${isbn}`]; const bookData = openLibraryResponse.data[`ISBN:${isbn}`];
if (bookData) { if (bookData) {
console.log('Book data found in Open Library');
console.log(bookData);
res.json(formatOpenLibraryData(bookData)); res.json(formatOpenLibraryData(bookData));
} else { } else {
// Fallback to Internet Archive if no data from Open Library // Fallback to Internet Archive if no data from Open Library
@ -45,23 +71,41 @@ app.get('/book/:isbn', async (req, res) => {
} }
}); });
// Endpoint to store book in the database
app.post('/store-book', (req, res) => {
const { isbn, title, authors, publishedDate, description } = req.body;
const query = `INSERT INTO books (isbn, title, authors, publishedDate, description) VALUES (?, ?, ?, ?, ?)`;
const params = [isbn, title, authors ? authors.join(', ') : '', publishedDate, description || ''];
db.run(query, params, function (err) {
if (err) {
console.error('Error storing book in database:', err);
res.status(500).json({ error: 'Failed to store book in database' });
} else {
res.json({ success: true, message: 'Book stored successfully', bookId: this.lastID });
}
});
});
function formatOpenLibraryData(data) { function formatOpenLibraryData(data) {
return { return {
isbn: data.identifiers.isbn_13 ? data.identifiers.isbn_13[0] : '',
title: data.title, title: data.title,
authors: data.authors ? data.authors.map(author => author.name) : [], authors: data.authors ? data.authors.map(author => author.name) : [],
publishedDate: data.publish_date, publishedDate: data.publish_date,
description: data.excerpts ? data.excerpts[0].text : 'No description available', description: data.excerpts ? data.excerpts[0].text : 'No description available',
cover: data.cover ? data.cover.large : ''
}; };
} }
function formatArchiveData(data) { function formatArchiveData(data) {
return { return {
isbn: data.isbn ? data.isbn[0] : '',
title: data.title, title: data.title,
authors: data.creator, authors: data.creator,
publishedDate: data.date, publishedDate: data.date,
description: data.description ? data.description[0] : 'No description available', description: data.description ? data.description[0] : 'No description available',
cover: data.cover ? `https://archive.org/services/img/${data.identifier}` : ''
}; };
} }
@ -69,4 +113,4 @@ const httpsServer = https.createServer(credentials, app);
httpsServer.listen(PORT, () => { httpsServer.listen(PORT, () => {
console.log(`HTTPS Server running on https://localhost:${PORT}`); console.log(`HTTPS Server running on https://localhost:${PORT}`);
}); });

1363
node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

1366
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^1.7.5", "axios": "^1.7.5",
"express": "^4.19.2" "express": "^4.19.2",
"sqlite3": "^5.1.7"
} }
} }

View File

@ -4,23 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ISBN Scanner</title> <title>ISBN Scanner</title>
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script> <link rel="stylesheet" href="styles.css">
<style>
#interactive.viewport {
position: relative;
width: 100%;
height: 400px;
overflow: hidden;
}
canvas.drawing, canvas.drawingBuffer {
position: absolute;
top: 0;
left: 0;
}
#book-info {
margin-top: 20px;
}
</style>
</head> </head>
<body> <body>
<h1>Scan a Book ISBN</h1> <h1>Scan a Book ISBN</h1>
@ -35,153 +19,17 @@
<input type="file" id="barcode-input" accept="image/*"> <input type="file" id="barcode-input" accept="image/*">
</div> </div>
<div id="book-info"></div> <div id="book-info"></div>
<div id="prompt">
<script> <p id="prompt-message"></p>
let selectedDeviceId; <button id="confirm">Yes</button>
<button id="edit-title">No, I'll add a title</button>
async function testCameraAccess() { </div>
try { <div id="title-input">
const stream = await navigator.mediaDevices.getUserMedia({ video: true }); <label for="manual-title">Title:</label>
console.log('Camera access granted', stream); <input type="text" id="manual-title">
} catch (error) { <button id="save-title">Save</button>
console.error('Error accessing camera:', error); </div>
} <script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
} <script src="script.js"></script>
// Get available video input devices (cameras)
async function getCameras() {
try {
// Ensure the cameraSelect element is defined before accessing it
const cameraSelect = document.getElementById('camera-select');
const stream = await navigator.mediaDevices.getUserMedia({ video: true }); // Prompt for camera access
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
// Update selectedDeviceId when user selects a camera
cameraSelect.addEventListener('change', function() {
selectedDeviceId = this.value;
});
if (videoDevices.length > 0) {
videoDevices.forEach((device, index) => {
const option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label || `Camera ${index + 1}`;
cameraSelect.appendChild(option);
});
} else {
console.log("No video devices found.");
cameraSelect.innerHTML = '<option>No cameras found</option>';
}
} catch (error) {
console.error("Error accessing the camera or enumerating devices:", error);
document.getElementById('camera-select').innerHTML = '<option>'+error+'</option>';
}
}
function startScanner() {
if (!selectedDeviceId) {
alert('No camera selected or available.');
return;
}
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector('#interactive'),
constraints: {
deviceId: selectedDeviceId,
facingMode: "environment", // Default to rear camera
},
},
decoder: {
readers: ["ean_reader"] // EAN is the standard format for ISBN-13 barcodes
}
}, function (err) {
if (err) {
console.log(err);
return;
}
console.log("Initialization finished. Ready to start");
Quagga.start();
});
Quagga.onDetected(async function (data) {
const isbn = data.codeResult.code;
console.log("Detected ISBN:", isbn);
Quagga.stop(); // Stop the scanner once an ISBN is detected
// Fetch book details
fetchBookInfo(isbn);
});
}
function fetchBookInfo(isbn) {
fetch(`/book/${isbn}`)
.then(response => response.json())
.then(data => displayBookInfo(data))
.catch(err => console.error('Error fetching book data:', err));
}
function displayBookInfo(data) {
const bookInfoDiv = document.getElementById('book-info');
if (data.title) {
bookInfoDiv.innerHTML = `
<h2>${data.title}</h2>
<p><strong>Authors:</strong> ${data.authors.join(', ')}</p>
<p><strong>Published Date:</strong> ${data.publishedDate}</p>
<p><strong>Description:</strong> ${data.description}</p>
`;
} else {
bookInfoDiv.innerHTML = '<p>No book found for this ISBN.</p>';
}
}
// Handle image file input
document.getElementById('barcode-input').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const imageSrc = e.target.result;
decodeBarcodeFromImage(imageSrc);
};
reader.readAsDataURL(file);
}
});
function decodeBarcodeFromImage(imageSrc) {
Quagga.decodeSingle({
src: imageSrc,
numOfWorkers: 0, // Needs to be 0 when used with decodeSingle()
inputStream: {
size: 800 // Limit the size for better performance
},
decoder: {
readers: ["ean_reader"] // EAN is the standard format for ISBN-13 barcodes
}
}, function(result) {
if (result && result.codeResult) {
console.log("Detected ISBN from image:", result.codeResult.code);
fetchBookInfo(result.codeResult.code);
} else {
console.log("Barcode not detected in image.");
document.getElementById('book-info').innerHTML = '<p>Barcode not detected in the image.</p>';
}
});
}
// Start the scanner when the start button is clicked
document.getElementById('start-scanner').addEventListener('click', startScanner);
// Get cameras on page load
window.onload = getCameras;
</script>
</body> </body>
</html> </html>

114
public/script.js Normal file
View File

@ -0,0 +1,114 @@
let selectedDeviceId;
let isScanning = false; // Flag to prevent multiple scans at the same time
async function testCameraAccess() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
console.log('Camera access granted', stream);
} catch (error) {
console.error('Error accessing camera:', error);
}
}
// Get available video input devices (cameras)
async function getCameras() {
try {
const cameraSelect = document.getElementById('camera-select');
const stream = await navigator.mediaDevices.getUserMedia({ video: true }); // Prompt for camera access
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
// Update selectedDeviceId when user selects a camera
cameraSelect.addEventListener('change', function() {
selectedDeviceId = this.value;
});
if (videoDevices.length > 0) {
videoDevices.forEach((device, index) => {
const option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label || `Camera ${index + 1}`;
cameraSelect.appendChild(option);
});
} else {
console.log("No video devices found.");
cameraSelect.innerHTML = '<option>No cameras found</option>';
}
} catch (error) {
console.error("Error accessing the camera or enumerating devices:", error);
document.getElementById('camera-select').innerHTML = '<option>' + error + '</option>';
}
}
function startScanner() {
if (!selectedDeviceId) {
alert('No camera selected or available.');
return;
}
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector('#interactive'),
constraints: {
deviceId: selectedDeviceId,
facingMode: "environment", // Default to rear camera
},
},
decoder: {
readers: ["ean_reader"] // EAN is the standard format for ISBN-13 barcodes
}
}, function (err) {
if (err) {
console.log(err);
return;
}
console.log("Initialization finished. Ready to start");
Quagga.start();
});
Quagga.onDetected(async function (data) {
if (isScanning) return; // Prevent further scans while a scan is being processed
isScanning = true; // Set the scanning flag
const isbn = data.codeResult.code;
console.log("Detected ISBN:", isbn);
Quagga.stop(); // Stop the scanner once an ISBN is detected
// Fetch book details
await fetchBookInfo(isbn);
isScanning = false; // Reset the scanning flag once processing is done
});
}
async function fetchBookInfo(isbn) {
try {
const response = await fetch(`/book/${isbn}`);
const bookData = await response.json();
if (bookData.title) {
promptUserWithBook(bookData);
} else {
console.log("No book data found. Restarting scanner...");
startScanner(); // Restart the scanner if no book information is found
}
} catch (error) {
console.error('Error fetching book data:', error);
startScanner(); // Restart the scanner on error as well
}
}
function promptUserWithBook(bookData) {
// Display book information or prompt the user for confirmation
document.getElementById('book-info').textContent = `Title: ${bookData.title}`;
document.getElementById('prompt').style.display = 'block';
// Additional logic for user confirmation can be added here
}
// Start the scanner when the start button is clicked
document.getElementById('start-scanner').addEventListener('click', startScanner);
// Get cameras on page load
window.onload = getCameras;

25
public/styles.css Normal file
View File

@ -0,0 +1,25 @@
#interactive.viewport {
position: relative;
width: 100%;
height: 400px;
overflow: hidden;
}
canvas.drawing, canvas.drawingBuffer {
position: absolute;
top: 0;
left: 0;
}
#book-info {
margin-top: 20px;
}
#prompt {
display: none;
margin-top: 20px;
}
#title-input {
display: none;
}