diff --git a/src/server/models/myscrapingbook.js b/src/server/models/myscrapingbook.js new file mode 100755 index 0000000..d9934cf --- /dev/null +++ b/src/server/models/myscrapingbook.js @@ -0,0 +1,99 @@ +const mongoose = require('mongoose').set('debug', false); +const Schema = mongoose.Schema; + +const tools = require('../tools/general'); + +mongoose.Promise = global.Promise; +mongoose.level = 'F'; + +// Resolving error Unknown modifier: $pushAll +mongoose.plugin((schema) => { + schema.options.usePushEach = true; +}); + +const MyScrapingBookSchema = new Schema({ + isbn: { + type: String, + index: true, + }, + isbn10: { + type: String, + }, + fonte: { + type: String, + }, + titolo: { + type: String, + }, + titoloOriginale: { + type: String, + }, + sottotitolo: { + type: String, + }, + autore: { + type: String, + }, + pagine: { + type: String, + }, + misure: { + type: String, + }, + edizione: { + type: String, + }, + editore: { + type: String, + }, + date_pub: { + type: Date, + }, + url: { + type: String, + }, + stelline: { + type: Number, + }, + prezzo: { + type: String, + }, + date_extraction: { + type: Date, + }, + descrizione_lunga: { + type: String, + }, +}); + +MyScrapingBookSchema.statics.getFieldsForSearch = function () { + return [{ field: 'titolo', type: tools.FieldType.string }]; +}; + +MyScrapingBookSchema.statics.executeQueryTable = function (idapp, params, user) { + params.fieldsearch = this.getFieldsForSearch(); + return tools.executeQueryTable(this, idapp, params, user); +}; + +MyScrapingBookSchema.statics.findAllIdApp = async function (idapp) { + const MyScrapingBook = this; + + const myfind = { idapp }; + + try { + return await MyScrapingBook.find(myfind).sort({ titolo: 1 }).lean(); + } catch (err) { + console.error('Errore in MyScrapingBook:', err, model); + return null; + } +}; + +const MyScrapingBook = mongoose.model('MyScrapingBook', MyScrapingBookSchema); + +MyScrapingBook.createIndexes() + .then(() => {}) + .catch((err) => { + throw err; + }); + +module.exports = { MyScrapingBook }; diff --git a/src/server/models/product.js b/src/server/models/product.js index 1247e9a..9f26da2 100755 --- a/src/server/models/product.js +++ b/src/server/models/product.js @@ -494,7 +494,7 @@ module.exports.findAllIdApp = async function (idapp, code, id, all, isbn) { myfind = { ...myfind, code }; } if (isbn) { - myfind = { ...myfind, sbn }; + myfind = { ...myfind, isbn }; } if (id) { myqueryadd = { diff --git a/src/server/modules/CronMod.js b/src/server/modules/CronMod.js index 1e5e8cc..6c97fd0 100644 --- a/src/server/modules/CronMod.js +++ b/src/server/modules/CronMod.js @@ -57,7 +57,10 @@ class CronMod { // } else if (mydata.dbop === 'rigeneraTutto') { // await ListaIngresso.Esegui_CronTab(idapp, mydata); } else if (mydata.dbop === "ScraperMultipleDataAmazon") { - mystr = await AmazonBookScraper.ScraperMultipleDataAmazon(idapp, mydata.options); + mystr = await AmazonBookScraper.ScraperMultipleDataAmazon(idapp, {update: true, aggiornasoloSeVuoti: true, forzaricarica: false}); + ris = { mystr }; + } else if (mydata.dbop === "ScraperMultipleDataDBStored") { + mystr = await AmazonBookScraper.ScraperMultipleDataDBStored(idapp, {update: true, aggiornasoloSeVuoti: true, forzaricarica: false, dbstored: true}); ris = { mystr }; } else if (mydata.dbop === "ScraperGeneraCSV") { mystr = await AmazonBookScraper.ScraperGeneraCSV(idapp, mydata.options, res); diff --git a/src/server/modules/Scraping.js b/src/server/modules/Scraping.js index 4a906c5..5628c39 100644 --- a/src/server/modules/Scraping.js +++ b/src/server/modules/Scraping.js @@ -9,32 +9,76 @@ const shared_consts = require('../tools/shared_nodejs'); const fs = require('fs').promises; // 👈 Usa il modulo promises +const { MyScrapingBook } = require('../models/myscrapingbook'); + class AmazonBookScraper { constructor() { this.baseUrl = 'https://www.amazon.it/dp/'; } - async fetchPageISBN10(isbn10) { + getUserAgentRandom() { + // Lista di User-Agent comuni per i vari browser + const userAgents = [ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/91.0.864.59 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0', + 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; AS; rv:11.0) like Gecko', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.1; rv:56.0) Gecko/20100101 Firefox/56.0', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36', + ]; + + // Seleziona un User-Agent casuale dalla lista + const randomIndex = Math.floor(Math.random() * userAgents.length); + return userAgents[randomIndex]; + } + + async fetchPageISBN10(isbn10, isbn) { + if (!isbn10) return false; + const url = `${this.baseUrl}${isbn10}`; - try { - const { data } = await axios.get(url, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + - 'AppleWebKit/537.36 (KHTML, like Gecko) ' + - 'Chrome/113.0.0.0 Safari/537.36', - // altri header se necessario - }, - }); - return { html: data, url }; - } catch (err) { - console.error(`Errore fetching ISBN ${isbn10}:`, err.message); - return null; + const retryLimit = 2; // Numero massimo di tentativi + const timeout = 7000; // Timeout di 5 secondi per la richiesta + const delay = 10000; // Ritardo tra i tentativi (10 secondi) + + for (let attempt = 1; attempt <= retryLimit; attempt++) { + try { + const { data } = await axios.get(url, { + headers: { + 'User-Agent': this.getUserAgentRandom(), + // Aggiungi altri header se necessario + }, + timeout, // Timeout di 5 secondi per la richiesta + }); + return { html: data, url }; + } catch (err) { + console.error(`Errore fetching isbn10 ${isbn10} (ISBN:${isbn}) (tentativo ${attempt}):`, err.message); + + if (attempt < retryLimit) { + console.log(`Riprovo tra ${delay / 1000} secondi...`); + await new Promise((resolve) => setTimeout(resolve, delay)); // Ritardo prima di riprovare + } else { + console.error( + `Impossibile recuperare la pagina per ISBN10 ${isbn10} (ISBN:${isbn}) dopo ${retryLimit} tentativi.` + ); + await Product.findOneAndUpdate( + { isbn: isbn }, + { $set: { scraped: true, scraped_error: true } }, + { upsert: true, new: true, includeResultMetadata: true } + ).lean(); + + return null; + } + } } } + isbn13to10(isbn13) { try { - if (!isbn13.startsWith('978') || isbn13.length !== 13) return null; + if (!(isbn13.startsWith('978') || isbn13.startsWith('979')) || isbn13.length !== 13) return null; + const core = isbn13.slice(3, 12); // i 9 numeri centrali let sum = 0; @@ -60,7 +104,7 @@ class AmazonBookScraper { } } - async extractData(myproduct, html, url) { + async extractData(myproduct, html, url, isbn10) { const $ = cheerio.load(html); const productInfo = await ProductInfo.findOne({ _id: myproduct.idProductInfo }).lean(); @@ -94,11 +138,19 @@ class AmazonBookScraper { let edizione = null; let publisher = null; let data_pubblicazione = null; + let descrizione_lunga = null; + let prezzo = null; + let stelline = null; + let autore = null; numpagine = this.extractNumeroDiPagine($); const dim = this.extractDimensions($); misure = this.convertDimensionsToMisureMacro(dim); const data_pubb = this.extractDataPubblicazione($); + descrizione_lunga = this.extractDescrizioneLunga($); + autore = this.extractAutore($); + stelline = this.extractStelline($); + prezzo = this.extractPrezzo($); data_pubblicazione = this.parseItalianDate(data_pubb); publisher = this.extractEditore($); @@ -107,39 +159,121 @@ class AmazonBookScraper { edizione = this.extractMonthYear(data_pubb); } - return { + const fonte = 'AMAZON'; + + const data = { + isbn: myproduct.isbn, + isbn10, titolo: title, + fonte, ...(titoloOriginale ? { titoloOriginale } : {}), ...(sottotitolo ? { sottotitolo } : { sottotitolo: '' }), + ...(autore ? { autore } : { autore: '' }), + ...(stelline ? { stelline } : {}), ...(numpagine ? { numpagine } : {}), ...(misure ? { misure } : {}), ...(edizione ? { edizione } : {}), - ...(data_pubblicazione ? { data_pubblicazione } : {}), + ...(descrizione_lunga ? { descrizione_lunga } : {}), ...(publisher ? { editore: publisher } : {}), - url: `URL`, + url: `VAI AL SITO ${fonte}`, + ...(data_pubblicazione ? { data_pubblicazione } : {}), + ...(prezzo ? { prezzo } : {}), }; + + // Aggiungilo al record + await this.addOrUpdateMyScrapingBook(data); + + return data; + } + + async findRecordMyScrapingBookByIsbn(isbn) { + const record = await MyScrapingBook.findOne({ isbn }).lean(); + return record; + } + + async addOrUpdateMyScrapingBook(data) { + try { + const { + isbn, + isbn10, + titolo, + titoloOriginale, + fonte, + url, + sottotitolo, + autore, + numpagine, + misure, + edizione, + editore, + data_pubblicazione, + stelline, + prezzo, + descrizione_lunga, + } = data; + + // Cerca un record esistente per ISBN + let record = await MyScrapingBook.findOne({ isbn }).lean(); + + // Se il record esiste, aggiorna i campi + await MyScrapingBook.updateOne( + { isbn }, + { + $set: { + isbn10, + titolo, + fonte, + titoloOriginale, + sottotitolo, + autore, + pagine: numpagine, + misure, + edizione, + editore, + date_pub: data_pubblicazione, + url, + stelline, + prezzo, + descrizione_lunga, + date_extraction: new Date(), + }, + }, + { upsert: true } + ); + } catch (err) { + console.error('Errore gestendo MyScrapingBook:', err); + } } async scrapeISBN(myproduct, isbn, options) { try { - const isbn10 = this.isbn13to10(isbn); - const res = await this.fetchPageISBN10(isbn10); - if (!res) { - await Product.findOneAndUpdate( - { _id: myproduct._id }, - { $set: { scraped: true, scraped_error: true } }, - { upsert: true, new: true, includeResultMetadata: true } - ).lean(); + const datastored = await this.findRecordMyScrapingBookByIsbn(isbn); - return null; - } - const html = res.html; - if (!html) return null; + let data = null; let updated = null; let risupdate = null; - const data = await this.extractData(myproduct, html, res.url); + // prima cerca sulla tabella che ho scaricato + if (options.forzaricarica || !datastored) { + const isbn10 = this.isbn13to10(isbn); + const res = await this.fetchPageISBN10(isbn10, isbn); + if (!res) { + await Product.findOneAndUpdate( + { _id: myproduct._id }, + { $set: { scraped: true, scraped_error: true } }, + { upsert: true, new: true, includeResultMetadata: true } + ).lean(); + + return null; + } + const html = res.html; + if (!html) return null; + + data = await this.extractData(myproduct, html, res.url, isbn10); + } else { + data = { ...datastored }; + } if (!options?.update) return data; @@ -233,7 +367,7 @@ class AmazonBookScraper { { $set: { scraped: true, scraped_date: new Date() } }, { upsert: true, new: true, returnDocument: 'after' } ); - console.log('upd', upd); + // console.log('upd', upd); } if (aggiornaProductInfo) { @@ -293,13 +427,15 @@ class AmazonBookScraper { if (!arrvar.misure) { datimancanti = true; } - if (!arrvar.edizione) { + /*if (!arrvar.edizione) { datimancanti = true; - } + }*/ } if (product.idProductInfo) { - if (!tools.isDateValid(product.idProductInfo.date_pub)) datimancanti = true; + if (!tools.isDateValid(product.idProductInfo.date_pub)) { + datimancanti = true; + } } return datimancanti; @@ -333,7 +469,7 @@ class AmazonBookScraper { console.log(`scrapeMultiple INIZIATO...`); let dataorainizio = new Date(); - for (let i = 0; i < 100 && i < products.length; i++) { + for (let i = 0; i < products.length; i++) { const product = products[i]; let isbn = product.isbn; @@ -348,7 +484,7 @@ class AmazonBookScraper { } if (i % 1 === 0) { - const percentuale = ((quanti / products.length) * 100).toFixed(2); + const percentuale = ((i / products.length) * 100).toFixed(2); console.log( `Scraping: ${product.isbn} - ${product.idProductInfo.name} - ${quanti} su ${i + 1} / ${ products.length @@ -357,10 +493,11 @@ class AmazonBookScraper { } // Per evitare blocchi, metti una pausa (es. 2 secondi) - await new Promise((r) => setTimeout(r, 3000)); + await new Promise((r) => setTimeout(r, 2000)); } } } + mylog += `RECORD AGGIORNATI: ${results.length - 1} su ${quanti}`; return results; } @@ -389,6 +526,12 @@ class AmazonBookScraper { await Product.updateMany({ idapp, scraped_updated: true }, { $set: { scraped_updated: false } }); } + static async ScraperAzzeraFlagErrori(idapp, options) { + // aggiorna tutti i record di Product (con idapp) scraped: false + + await Product.updateMany({ idapp, scraped_error: true }, { $set: { scraped: false } }); + } + static async removeDuplicateVariations(idapp, options) { let mylog = 'removeDuplicateVariations...\n'; @@ -478,7 +621,6 @@ class AmazonBookScraper { .populate({ path: 'idProductInfo', select: 'date_pub name sottotitolo' }) .lean(); - // Funzione per "appiattire" i dati const flattenData = (data) => { return data.map((item) => { @@ -565,8 +707,9 @@ class AmazonBookScraper { idapp, isbn: { $exists: true, $ne: '' }, scraped: { $ne: true }, // Escludi direttamente i record con scraped = true - $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }], $or: [{ scraped_error: { $exists: false } }, { scraped_error: { $exists: true, $eq: false } }], + /*$or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }], + */ }, }, // Popoliamo il campo idProductInfo @@ -622,31 +765,131 @@ class AmazonBookScraper { } extractDataPubblicazione($) { - // Seleziona il div con id specifico per la data di pubblicazione - const publicationDate = $('#rpi-attribute-book_details-publication_date .rpi-attribute-value span').text().trim(); + try { + // Seleziona il div con id specifico per la data di pubblicazione + const publicationDate = $('#rpi-attribute-book_details-publication_date .rpi-attribute-value span').text().trim(); - // Se non trova la data, ritorna null - return publicationDate || null; + // Se non trova la data, ritorna null + return publicationDate || null; + } catch (error) { + console.error('Error extracting publication date:', error); + return null; + } + } + + extractDescrizioneLunga($) { + try { + // Cerca l'elemento che contiene la descrizione lunga + const descriptionElement = $('#bookDescription_feature_div .a-expander-content span'); + + // Estrai il testo e rimuovi eventuali spazi extra o tag HTML + const description = descriptionElement.text().trim(); + + // Se la descrizione è vuota, restituisci null + return description || null; + } catch (error) { + console.error('Error extracting long description:', error); + return null; + } + } + + extractAutore($) { + try { + // Seleziona tutti gli elementi che contengono gli autori + const authorsElements = $('#bylineInfo .author a'); + + // Estrai il testo per ogni autore e rimuovi spazi extra + const authors = authorsElements.map((i, el) => $(el).text().trim()).get(); + + // Se ci sono autori, restituiscili come stringa separata da virgole + return authors.length > 0 ? authors.join(', ') : null; + } catch (error) { + console.error('Error extracting authors:', error); + return null; + } + } + + extractStelline($) { + try { + // Cerca l'elemento che contiene la descrizione lunga + // Seleziona l'elemento che contiene il voto medio (5,0 su 5 stelle) + const ratingText = $('#acrPopover').attr('title'); + + if (ratingText) { + // Estrai il numero di stelle dal testo "5,0 su 5 stelle" + const stars = ratingText.split(' su ')[0].replace(',', '.'); // Cambia la virgola in punto + return stars; // Restituisce il numero di stelle + } + } catch (error) { + console.error('Error extracting stars:', error); + return null; + } + } + + extractPrezzo($) { + try { + // Seleziona l'elemento che contiene il prezzo + const priceElement = $( + '.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-whole' + ); + + // Estrai il prezzo principale e la parte decimale + const wholePrice = priceElement.text().trim(); + const decimalPrice = $( + '.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-fraction' + ) + .text() + .trim(); + + // Estrai il simbolo della valuta + const currencySymbol = $( + '.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-symbol' + ) + .text() + .trim(); + + // Se il prezzo è stato trovato, formattalo come prezzo completo + if (wholePrice && decimalPrice && currencySymbol) { + const fullPrice = `${wholePrice}${decimalPrice} ${currencySymbol}`; + return fullPrice; + } + + // Se non trova il prezzo, restituisce null + return null; + } catch (error) { + console.error('Error extracting price:', error); + return null; + } } extractNumeroDiPagine($) { - // Seleziona il div con id specifico per pagine - const pagesText = $('#rpi-attribute-book_details-fiona_pages .rpi-attribute-value span').text().trim(); + try { + // Seleziona il div con id specifico per pagine + const pagesText = $('#rpi-attribute-book_details-fiona_pages .rpi-attribute-value span').text().trim(); - // pagesText dovrebbe essere tipo "184 pagine" - if (!pagesText) return null; + // pagesText dovrebbe essere tipo "184 pagine" + if (!pagesText) return null; - // Estrai solo il numero (facoltativo) - const match = pagesText.match(/(\d+)/); - return match ? match[1] : pagesText; // ritorna solo il numero o il testo intero + // Estrai solo il numero (facoltativo) + const match = pagesText.match(/(\d+)/); + return match ? match[1] : pagesText; // ritorna solo il numero o il testo intero + } catch (error) { + console.error('Error extracting number of pages:', error); + return null; + } } extractDimensions($) { - // Seleziona il div con id specifico per dimensioni - const dimText = $('#rpi-attribute-book_details-dimensions .rpi-attribute-value span').text().trim(); + try { + // Seleziona il div con id specifico per dimensioni + const dimText = $('#rpi-attribute-book_details-dimensions .rpi-attribute-value span').text().trim(); - // Se non trova niente ritorna null - return dimText || null; + // Se non trova niente ritorna null + return dimText || null; + } catch (error) { + console.error('Error extracting dimensions:', error); + return null; + } } convertDimensionsToMisureMacro(dimString) { diff --git a/src/server/router/admin_router.js b/src/server/router/admin_router.js index 6da839f..266a1bc 100755 --- a/src/server/router/admin_router.js +++ b/src/server/router/admin_router.js @@ -27,7 +27,7 @@ const Gasordine = require('../models/gasordine'); const { User } = require('../models/user'); -const AmazonBookScraper = require('../modules/scraping'); +const AmazonBookScraper = require('../modules/Scraping'); const { Catalog } = require('../models/catalog'); const { RaccoltaCataloghi } = require('../models/raccoltacataloghi'); diff --git a/src/server/router/catalogs_router.js b/src/server/router/catalogs_router.js index 14fd3c5..2524b4b 100755 --- a/src/server/router/catalogs_router.js +++ b/src/server/router/catalogs_router.js @@ -22,7 +22,7 @@ router.post('/', auth_default, async function (req, res, next) { let ismanager = await tools.isManagerByReq(req); - let catalogs = await Catalog.findAllIdApp(idapp, '', undefined, ismanager); + let catalogs = await Catalog.findAllIdApp(idapp); let orders = null; if (catalogs) return res.send({ code: server_constants.RIS_CODE_OK, catalogs, orders }); diff --git a/src/server/router/myscraping_router.js b/src/server/router/myscraping_router.js new file mode 100755 index 0000000..daf87e4 --- /dev/null +++ b/src/server/router/myscraping_router.js @@ -0,0 +1,57 @@ +const express = require('express'); +const router = express.Router(); + +const tools = require('../tools/general'); + +var server_constants = require('../tools/server_constants'); + +const { User } = require('../models/user'); + +var { authenticate, auth_default } = require('../middleware/authenticate'); + +const _ = require('lodash'); + +const { MyScrapingBook } = require('../models/myscrapingbook'); +const Product = require('../models/product'); + +const AmazonBookScraper = require('../modules/Scraping'); + +//GET /products +router.post('/', auth_default, async function (req, res, next) { + const idapp = req.body.idapp; + const isbn = req.body.isbn; + const forzacaricamento = req.body.forzacaricamento; + + try { + let myscraping = null; + if (isbn) { + myscraping = await MyScrapingBook.findOne({ isbn }).lean(); + + if (!myscraping && forzacaricamento) { + const scraper = new AmazonBookScraper(); + + const options = { + update: false, + forzaricarica: false, + }; + + const myproduct = await Product.getProductByIsbn(idapp, isbn); + if (myproduct && myproduct.length > 0) { + myscraping = await scraper.scrapeISBN(myproduct[0], isbn, options); + // console.log(myscraping); + } + } + } + + if (myscraping) { + return res.send({ code: server_constants.RIS_CODE_OK, myscraping }); + } else { + return res.send({ code: server_constants.RIS_CODE_OK, myscraping: null }); + } + } catch (e) { + console.error(e); + return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e.message }); + } +}); + +module.exports = router; diff --git a/src/server/server.js b/src/server/server.js index 86fca86..d8f49d7 100755 --- a/src/server/server.js +++ b/src/server/server.js @@ -128,6 +128,7 @@ connectToDatabase(connectionUrl, options) const site_router = require('./router/site_router'); const admin_router = require('./router/admin_router'); const products_router = require('./router/products_router'); + const myscraping_router = require('./router/myscraping_router'); const catalogs_router = require('./router/catalogs_router'); const cart_router = require('./router/cart_router'); const orders_router = require('./router/orders_router'); @@ -238,6 +239,7 @@ connectToDatabase(connectionUrl, options) app.use('/site', site_router); app.use('/admin', admin_router); app.use('/products', products_router); + app.use('/myscraping', myscraping_router); app.use('/catalogs', catalogs_router); app.use('/cart', cart_router); app.use('/orders', orders_router); diff --git a/src/server/tools/globalTables.js b/src/server/tools/globalTables.js index fe659d6..336a90e 100755 --- a/src/server/tools/globalTables.js +++ b/src/server/tools/globalTables.js @@ -48,6 +48,7 @@ const Pickup = require('../models/pickup'); const { Newstosent } = require('../models/newstosent'); const { MyPage } = require('../models/mypage'); const { MyElem } = require('../models/myelem'); +const { MyScrapingBook } = require('../models/myscrapingbook'); const { Cron } = require('../models/cron'); const { MyScheda } = require('../models/myscheda'); const { MyBot } = require('../models/bot'); @@ -200,6 +201,8 @@ module.exports = { mytable = MyPage; else if (tablename === 'myelems') mytable = MyElem; + else if (tablename === 'myscrapingbooks') + mytable = MyScrapingBook; else if (tablename === 'crons') mytable = Cron; else if (tablename === 'myschedas') diff --git a/src/server/version.txt b/src/server/version.txt index c3df286..4d668e7 100644 --- a/src/server/version.txt +++ b/src/server/version.txt @@ -1 +1 @@ -1.2.47 \ No newline at end of file +1.2.48 \ No newline at end of file