Compare commits

...

7 Commits

Author SHA1 Message Date
orangecoding
ec47137b89 upgrading dependencies 2026-05-21 21:40:35 +02:00
orangecoding
33161de087 upgrading dependencies 2026-05-19 09:15:12 +02:00
Stephan
acab23207e fix: kleinanzige listing might have a different structure when it was with no image created (#308) 2026-05-16 14:16:31 +02:00
orangecoding
2896d531e4 upgrading pois 2026-05-13 08:14:57 +02:00
orangecoding
0cbfa25062 upgrading pois 2026-05-12 19:28:58 +02:00
orangecoding
bcd3042026 fixing error messages not being shown properly in user table 2026-05-12 13:24:13 +02:00
orangecoding
0ce93acaf6 more demo fixes 2026-05-12 13:12:26 +02:00
15 changed files with 315 additions and 520 deletions

View File

@@ -227,7 +227,7 @@ class FredyPipelineExecutioner {
const extractor = new Extractor({ ...this._providerConfig.puppeteerOptions, browser: this._browser }); const extractor = new Extractor({ ...this._providerConfig.puppeteerOptions, browser: this._browser });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
extractor extractor
.execute(url, this._providerConfig.waitForSelector) .execute(url, this._providerConfig.waitForSelector, this._providerId)
.then(() => { .then(() => {
const listings = extractor.parseResponseText( const listings = extractor.parseResponseText(
this._providerConfig.crawlContainer, this._providerConfig.crawlContainer,

View File

@@ -76,13 +76,13 @@ fastify.register(async (app) => {
app.register(dashboardPlugin, { prefix: '/api/dashboard' }); app.register(dashboardPlugin, { prefix: '/api/dashboard' });
app.register(userSettingsPlugin, { prefix: '/api/user/settings' }); app.register(userSettingsPlugin, { prefix: '/api/user/settings' });
app.register(trackingPlugin, { prefix: '/api/tracking' }); app.register(trackingPlugin, { prefix: '/api/tracking' });
app.register(generalSettingsPlugin, { prefix: '/api/admin/generalSettings' });
}); });
// Admin-only routes // Admin-only routes
fastify.register(async (app) => { fastify.register(async (app) => {
app.addHook('preHandler', authHook); app.addHook('preHandler', authHook);
app.addHook('preHandler', adminHook); app.addHook('preHandler', adminHook);
app.register(generalSettingsPlugin, { prefix: '/api/admin/generalSettings' });
app.register(backupPlugin, { prefix: '/api/admin/backup' }); app.register(backupPlugin, { prefix: '/api/admin/backup' });
app.register(userPlugin, { prefix: '/api/admin/users' }); app.register(userPlugin, { prefix: '/api/admin/users' });
}); });

View File

@@ -27,8 +27,11 @@ export default async function generalSettingsPlugin(fastify) {
} }
const localSettings = await getSettings(); const localSettings = await getSettings();
if (localSettings.demoMode && !isAdmin(request)) { if (!isAdmin(request)) {
return reply.code(403).send({ error: 'In demo mode, it is not allowed to change these settings.' }); const reason = localSettings.demoMode
? 'In demo mode, it is not allowed to change these settings.'
: 'Only admins can change these settings.';
return reply.code(403).send({ error: reason });
} }
try { try {

View File

@@ -26,7 +26,7 @@ function parseId(shortenedLink) {
async function fetchDetails(listing, browser) { async function fetchDetails(listing, browser) {
try { try {
const html = await puppeteerExtractor(listing.link, null, { browser }); const html = await puppeteerExtractor(listing.link, null, { browser, name: 'immobilienDe_details' });
if (!html) return listing; if (!html) return listing;
const $ = cheerio.load(html); const $ = cheerio.load(html);

View File

@@ -16,7 +16,7 @@ let appliedBlackList = [];
async function fetchDetails(listing, browser) { async function fetchDetails(listing, browser) {
try { try {
const html = await puppeteerExtractor(listing.link, null, { browser }); const html = await puppeteerExtractor(listing.link, null, { browser, name: 'immowelt_details' });
if (!html) return listing; if (!html) return listing;
const $ = cheerio.load(html); const $ = cheerio.load(html);

View File

@@ -128,7 +128,7 @@ async function enrichListingFromDetails(listing, browser) {
if (!absoluteLink) return listing; if (!absoluteLink) return listing;
try { try {
const html = await puppeteerExtractor(absoluteLink, null, { browser }); const html = await puppeteerExtractor(absoluteLink, null, { browser, name: 'kleinanzeigen_details' });
if (!html) return { ...listing, link: absoluteLink }; if (!html) return { ...listing, link: absoluteLink };
const { detailAddress, detailDescription } = extractDetailFromHtml(html); const { detailAddress, detailDescription } = extractDetailFromHtml(html);
@@ -196,8 +196,8 @@ const config = {
id: '.aditem@data-adid', id: '.aditem@data-adid',
price: '.aditem-main--middle--price-shipping--price | removeNewline | trim', price: '.aditem-main--middle--price-shipping--price | removeNewline | trim',
tags: '.aditem-main--middle--tags | removeNewline | trim', tags: '.aditem-main--middle--tags | removeNewline | trim',
title: '.aditem-main .text-module-begin a | removeNewline | trim', title: '.aditem-main .text-module-begin | removeNewline | trim',
link: '.aditem-main .text-module-begin a@href | removeNewline | trim', link: '.aditem@data-href',
description: '.aditem-main .aditem-main--middle--description | removeNewline | trim', description: '.aditem-main .aditem-main--middle--description | removeNewline | trim',
address: '.aditem-main--top--left | trim | removeNewline', address: '.aditem-main--top--left | trim | removeNewline',
image: 'img@src', image: 'img@src',

View File

@@ -16,7 +16,7 @@ let appliedBlackList = [];
async function fetchDetails(listing, browser) { async function fetchDetails(listing, browser) {
try { try {
const html = await puppeteerExtractor(listing.link, 'body', { browser }); const html = await puppeteerExtractor(listing.link, 'body', { browser, name: 'sparkasse_details' });
const $ = cheerio.load(html); const $ = cheerio.load(html);
const nextDataRaw = $('#__NEXT_DATA__').text; const nextDataRaw = $('#__NEXT_DATA__').text;

View File

@@ -16,7 +16,7 @@ let appliedBlackList = [];
async function fetchDetails(listing, browser) { async function fetchDetails(listing, browser) {
try { try {
const html = await puppeteerExtractor(listing.link, null, { browser }); const html = await puppeteerExtractor(listing.link, null, { browser, name: 'wgGesucht_details' });
if (!html) return listing; if (!html) return listing;
const $ = cheerio.load(html); const $ = cheerio.load(html);

View File

@@ -29,11 +29,12 @@ export default class Extractor {
* your response will never contain what you are really looking for * your response will never contain what you are really looking for
* @param url * @param url
* @param waitForSelector * @param waitForSelector
* @param jobKey
*/ */
execute = async (url, waitForSelector = null) => { execute = async (url, waitForSelector = null, jobKey = null) => {
this.responseText = null; this.responseText = null;
try { try {
this.responseText = await puppeteerExtractor(url, waitForSelector, this.options); this.responseText = await puppeteerExtractor(url, waitForSelector, { ...this.options, name: jobKey });
if (this.responseText != null) { if (this.responseText != null) {
loadParser(this.responseText); loadParser(this.responseText);
} }

View File

@@ -4,7 +4,7 @@
*/ */
import { launch } from 'cloakbrowser/puppeteer'; import { launch } from 'cloakbrowser/puppeteer';
import { debug, botDetected } from './utils.js'; import { botDetected, debug } from './utils.js';
import { getPreLaunchConfig } from './botPrevention.js'; import { getPreLaunchConfig } from './botPrevention.js';
import logger from '../logger.js'; import logger from '../logger.js';
import { trackPoi } from '../tracking/Tracker.js'; import { trackPoi } from '../tracking/Tracker.js';
@@ -50,7 +50,7 @@ export async function launchBrowser(url, options) {
preCfg.windowSizeArg, preCfg.windowSizeArg,
]; ];
const browser = await launch({ return await launch({
headless: options?.puppeteerHeadless ?? true, headless: options?.puppeteerHeadless ?? true,
humanize: true, humanize: true,
args, args,
@@ -59,8 +59,6 @@ export async function launchBrowser(url, options) {
...(options?.proxyUrl ? { proxy: options.proxyUrl } : {}), ...(options?.proxyUrl ? { proxy: options.proxyUrl } : {}),
...(preCfg.timezone ? { timezone: preCfg.timezone } : {}), ...(preCfg.timezone ? { timezone: preCfg.timezone } : {}),
}); });
return browser;
} }
/** /**
@@ -148,7 +146,11 @@ export default async function execute(url, waitForSelector, options) {
if (botDetected(pageSource, statusCode)) { if (botDetected(pageSource, statusCode)) {
logger.warn('We have been detected as a bot :-/ Tried url: => ', url); logger.warn('We have been detected as a bot :-/ Tried url: => ', url);
await trackPoi(TRACKING_POIS.DETECTED_AS_BOT); if (options != null && options.name != null) {
await trackPoi(TRACKING_POIS.DETECTED_AS_BOT + '_' + options.name);
} else {
await trackPoi(TRACKING_POIS.DETECTED_AS_BOT);
}
result = null; result = null;
} else { } else {

View File

@@ -1,6 +1,6 @@
{ {
"name": "fredy", "name": "fredy",
"version": "22.0.4", "version": "22.0.9",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].", "description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": { "scripts": {
"prepare": "husky", "prepare": "husky",
@@ -62,9 +62,9 @@
"Firefox ESR" "Firefox ESR"
], ],
"dependencies": { "dependencies": {
"@douyinfe/semi-icons": "^2.97.0", "@douyinfe/semi-icons": "^2.99.0",
"@douyinfe/semi-ui": "2.97.0", "@douyinfe/semi-ui": "2.99.0",
"@douyinfe/semi-ui-19": "^2.97.0", "@douyinfe/semi-ui-19": "^2.99.0",
"@fastify/cookie": "^11.0.2", "@fastify/cookie": "^11.0.2",
"@fastify/helmet": "^13.0.2", "@fastify/helmet": "^13.0.2",
"@fastify/session": "^11.1.1", "@fastify/session": "^11.1.1",
@@ -73,12 +73,12 @@
"@modelcontextprotocol/sdk": "^1.29.0", "@modelcontextprotocol/sdk": "^1.29.0",
"@sendgrid/mail": "8.1.6", "@sendgrid/mail": "8.1.6",
"@turf/boolean-point-in-polygon": "^7.3.5", "@turf/boolean-point-in-polygon": "^7.3.5",
"@vitejs/plugin-react": "6.0.1", "@vitejs/plugin-react": "6.0.2",
"adm-zip": "^0.5.17", "adm-zip": "^0.5.17",
"better-sqlite3": "^12.9.0", "better-sqlite3": "^12.10.0",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"cheerio": "^1.2.0", "cheerio": "^1.2.0",
"cloakbrowser": "^0.3.28", "cloakbrowser": "^0.3.30",
"fastify": "^5.8.5", "fastify": "^5.8.5",
"handlebars": "4.7.9", "handlebars": "4.7.9",
"maplibre-gl": "^5.24.0", "maplibre-gl": "^5.24.0",
@@ -89,18 +89,18 @@
"nodemailer": "^8.0.7", "nodemailer": "^8.0.7",
"p-throttle": "^8.1.0", "p-throttle": "^8.1.0",
"package-up": "^5.0.0", "package-up": "^5.0.0",
"puppeteer-core": "^24.43.1", "puppeteer-core": "^25.0.4",
"query-string": "9.3.1", "query-string": "9.3.1",
"react": "19.2.6", "react": "19.2.6",
"react-chartjs-2": "^5.3.1", "react-chartjs-2": "^5.3.1",
"react-dom": "19.2.6", "react-dom": "19.2.6",
"react-range-slider-input": "^3.3.5", "react-range-slider-input": "^3.3.5",
"react-router": "7.15.0", "react-router": "7.15.1",
"react-router-dom": "7.15.0", "react-router-dom": "7.15.1",
"resend": "^6.12.3", "resend": "^6.12.3",
"semver": "^7.8.0", "semver": "^7.8.1",
"slack": "11.0.2", "slack": "11.0.2",
"vite": "8.0.12", "vite": "8.0.14",
"x-var": "^3.0.1", "x-var": "^3.0.1",
"zustand": "^5.0.13" "zustand": "^5.0.13"
}, },
@@ -111,16 +111,16 @@
"@babel/preset-react": "7.28.5", "@babel/preset-react": "7.28.5",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"eslint": "10.3.0", "eslint": "10.4.0",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-react": "7.37.5", "eslint-plugin-react": "7.37.5",
"globals": "^17.6.0", "globals": "^17.6.0",
"history": "5.3.0", "history": "5.3.0",
"husky": "9.1.7", "husky": "9.1.7",
"less": "4.6.4", "less": "4.6.4",
"lint-staged": "17.0.4", "lint-staged": "17.0.5",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
"prettier": "3.8.3", "prettier": "3.8.3",
"vitest": "^4.1.6" "vitest": "^4.1.7"
} }
} }

View File

@@ -95,7 +95,10 @@ async function downloadHtmlProvider(name, providerConfig, launchBrowser, closeBr
const browser = await launchBrowser(providerConfig.url, {}); const browser = await launchBrowser(providerConfig.url, {});
try { try {
const html = await puppeteerExtractor(providerConfig.url, providerConfig.waitForSelector, { browser }); const html = await puppeteerExtractor(providerConfig.url, providerConfig.waitForSelector, {
browser,
name: 'dowload_fixtures',
});
if (!html) { if (!html) {
console.warn(` Failed to download ${name}`); console.warn(` Failed to download ${name}`);

View File

@@ -174,7 +174,7 @@ const JobGrid = () => {
Toast.success('Job status successfully changed'); Toast.success('Job status successfully changed');
loadData(); loadData();
} catch (error) { } catch (error) {
Toast.error(error); Toast.error(error.error);
} }
}; };

View File

@@ -37,7 +37,7 @@ const Users = function Users() {
await actions.jobsData.getJobs(); await actions.jobsData.getJobs();
await actions.user.getUsers(); await actions.user.getUsers();
} catch (error) { } catch (error) {
Toast.error(error); Toast.error(error.error);
setUserIdToBeRemoved(null); setUserIdToBeRemoved(null);
} }
}; };

754
yarn.lock

File diff suppressed because it is too large Load Diff