Compare commits

...

6 Commits

Author SHA1 Message Date
weakmap@gmail.com
c6bb3c44d4 upgrade dependencies, fixing tests 2025-02-23 17:14:39 +01:00
weakmap@gmail.com
a3471a091a upgrade dependencies, fixing tests 2025-02-23 17:13:08 +01:00
Christian Kellner
b5a96afcc8 upgrading dependencies 2025-01-17 22:08:04 +01:00
Stefan
3903ab59cf fix normalized wggesucht link (#123) 2025-01-17 22:05:34 +01:00
weakmap@gmail.com
8fe7cec2a1 improve pushover notification service 2025-01-10 19:51:14 +01:00
Christian Kellner
97deea6f5b Update README.md 2025-01-09 17:31:46 +01:00
7 changed files with 493 additions and 407 deletions

View File

@@ -11,7 +11,7 @@ If _Fredy_ finds matching results, it will send them to you via Slack, Email, Te
# Sponsorship [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/orangecoding) # Sponsorship [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/orangecoding)
If you like my work, consider becoming a sponsor. I'm not expecting anybody to pay for _Fredy_ or any other Open Source Project I'm maintaining, however keep in mind, I'm doing all of this in my spare time :) Thanks. If you like my work, consider becoming a sponsor. I'm not expecting anybody to pay for _Fredy_ or any other Open Source Project I'm maintaining, however keep in mind, I'm doing all of this in my spare time :) Thanks.
<img src="https://github.com/orangecoding/fredy/blob/master/doc/jetbrains.png" width="200"> [![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSourceSupport)
_Fredy_ is supported by JetBrains under Open Source Support Program _Fredy_ is supported by JetBrains under Open Source Support Program

View File

@@ -7,9 +7,11 @@ export const send = ({ serviceName, newListings, notificationConfig, jobKey }) =
const job = getJob(jobKey); const job = getJob(jobKey);
const jobName = job == null ? jobKey : job.name; const jobName = job == null ? jobKey : job.name;
const promises = newListings.map((newListing) => { const promises = newListings.map((newListing) => {
const message = `Address: ${newListing.address} Size: ${newListing.size.replace(/2m/g, '$m^2$')} Price: ${ const message = `
newListing.price Address: ${newListing.address}
}`; Size: ${newListing.size.replace(/2m/g, '$m^2$')}
Price: ${newListing.price}
Link: ${newListing.link}`;
return fetch(server, { return fetch(server, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({

View File

@@ -1,50 +1,73 @@
import { markdown2Html } from '../../services/markdown.js'; import {markdown2Html} from '../../services/markdown.js';
import { getJob } from '../../services/storage/jobStorage.js'; import {getJob} from '../../services/storage/jobStorage.js';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
export const send = ({ serviceName, newListings, notificationConfig, jobKey }) => { export const send = ({serviceName, newListings, notificationConfig, jobKey}) => {
const { token, user, device } = notificationConfig.find((adapter) => adapter.id === config.id).fields; const {token, user, device} = notificationConfig.find((adapter) => adapter.id === config.id).fields;
const job = getJob(jobKey); const job = getJob(jobKey);
const jobName = job == null ? jobKey : job.name; const jobName = job == null ? jobKey : job.name;
const promises = newListings.map((newListing) => { const promises = newListings.map((newListing) => {
const title = `${jobName} at ${serviceName}: ${newListing.title}`; const title = `${jobName} at ${serviceName}: ${newListing.title}`;
const message = `Address: ${newListing.address}\nSize: ${newListing.size}\nPrice: ${newListing.price}\nLink: ${newListing.link}`; const message = `Address: ${newListing.address}\nSize: ${newListing.size}\nPrice: ${newListing.price}\nLink: ${newListing.link}`;
return fetch('https://api.pushover.net/1/messages.json', { return fetch('https://api.pushover.net/1/messages.json', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
token: token, token: token,
user: user, user: user,
message: message, message: message,
device: device, device: device,
title: title, title: title,
}), }),
});
}); });
});
return Promise.all(promises); return Promise.all(promises)
.then((responses) => {
// Convert all responses to JSON
return Promise.all(responses.map((response) => response.json()));
})
.then((data) => {
// Check for errors in the data
const error = data
.map((item) => (item.errors != null && item.errors.length > 0 ? item.errors.join(', ') : null))
.filter((err) => err !== null);
if (error.length > 0) {
// Reject with the combined error messages
return Promise.reject(error.join('; '));
}
return data;
})
.then(() => {
return Promise.resolve();
})
.catch((error) => {
return Promise.reject(error);
});
}; };
export const config = { export const config = {
id: 'pushover', id: 'pushover',
name: 'Pushover', name: 'Pushover',
readme: markdown2Html('lib/notification/adapter/pushover.md'), readme: markdown2Html('lib/notification/adapter/pushover.md'),
description: 'Fredy will send new listings to your mobile using Pushover.', description: 'Fredy will send new listings to your mobile using Pushover.',
fields: { fields: {
token: { token: {
type: 'text', type: 'text',
label: 'API token', label: 'API token',
description: 'Your application\'s API token.', description: 'Your application\'s API token.',
},
user: {
type: 'text',
label: 'User key',
description: 'Your user/group key.',
},
device: {
type: 'text',
label: 'Device name',
description: 'The device name to send your notification to. Messages may be addressed to multiple specific devices by joining them with a comma.',
},
}, },
user: {
type: 'text',
label: 'User key',
description: 'Your user/group key.',
},
device: {
type: 'text',
label: 'Device name',
description: 'The device name to send your notification to. Messages may be addressed to multiple specific devices by joining them with a comma.',
},
},
}; };

View File

@@ -4,7 +4,8 @@ let appliedBlackList = [];
function normalize(o) { function normalize(o) {
const id = buildHash(o.id, o.price); const id = buildHash(o.id, o.price);
return Object.assign(o, {id}); const link = `https://www.wg-gesucht.de${o.link}`;
return Object.assign(o, { id, link });
} }
function applyBlacklist(o) { function applyBlacklist(o) {

View File

@@ -1,6 +1,6 @@
{ {
"name": "fredy", "name": "fredy",
"version": "11.0.1", "version": "11.0.4",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].", "description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": { "scripts": {
"start": "node prod.js", "start": "node prod.js",
@@ -50,12 +50,12 @@
"Firefox ESR" "Firefox ESR"
], ],
"dependencies": { "dependencies": {
"@douyinfe/semi-ui": "2.72.3", "@douyinfe/semi-ui": "2.75.0",
"@rematch/core": "2.2.0", "@rematch/core": "2.2.0",
"@rematch/loading": "2.1.2", "@rematch/loading": "2.1.2",
"@sendgrid/mail": "8.1.4", "@sendgrid/mail": "8.1.4",
"@vitejs/plugin-react": "4.3.4", "@vitejs/plugin-react": "4.3.4",
"better-sqlite3": "^11.7.2", "better-sqlite3": "^11.8.1",
"body-parser": "1.20.3", "body-parser": "1.20.3",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
"cookie-session": "2.1.0", "cookie-session": "2.1.0",
@@ -66,11 +66,11 @@
"lowdb": "6.0.1", "lowdb": "6.0.1",
"markdown": "^0.5.0", "markdown": "^0.5.0",
"mixpanel": "^0.18.0", "mixpanel": "^0.18.0",
"nanoid": "5.0.9", "nanoid": "5.1.2",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"node-mailjet": "6.0.6", "node-mailjet": "6.0.6",
"package-up": "^5.0.0", "package-up": "^5.0.0",
"puppeteer": "^23.11.1", "puppeteer": "^24.2.1",
"puppeteer-extra": "^3.3.6", "puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-stealth": "^2.11.2", "puppeteer-extra-plugin-stealth": "^2.11.2",
"query-string": "9.1.1", "query-string": "9.1.1",
@@ -88,21 +88,21 @@
"vite": "5.4.11" "vite": "5.4.11"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.26.0", "@babel/core": "7.26.9",
"@babel/eslint-parser": "7.25.9", "@babel/eslint-parser": "7.26.8",
"@babel/preset-env": "7.26.0", "@babel/preset-env": "7.26.9",
"@babel/preset-react": "7.26.3", "@babel/preset-react": "7.26.3",
"chai": "5.1.2", "chai": "5.2.0",
"eslint": "8.56.0", "eslint": "8.56.0",
"eslint-config-prettier": "8.8.0", "eslint-config-prettier": "8.8.0",
"eslint-plugin-react": "7.37.3", "eslint-plugin-react": "7.37.4",
"esmock": "2.6.9", "esmock": "2.7.0",
"history": "5.3.0", "history": "5.3.0",
"husky": "9.1.7", "husky": "9.1.7",
"less": "4.2.1", "less": "4.2.2",
"lint-staged": "15.3.0", "lint-staged": "15.4.3",
"mocha": "10.8.2", "mocha": "10.8.2",
"prettier": "3.4.2", "prettier": "3.5.2",
"redux-logger": "3.0.6" "redux-logger": "3.0.6"
} }
} }

View File

@@ -1,40 +1,38 @@
import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js';
import { get } from '../mocks/mockNotification.js'; import {get} from '../mocks/mockNotification.js';
import { mockFredy, providerConfig } from '../utils.js'; import {mockFredy, providerConfig} from '../utils.js';
import { expect } from 'chai'; import {expect} from 'chai';
import * as provider from '../../lib/provider/immonet.js'; import * as provider from '../../lib/provider/immonet.js';
describe('#immonet testsuite()', () => { describe('#immonet testsuite()', () => {
after(() => { after(() => {
similarityCache.stopCacheCleanup(); similarityCache.stopCacheCleanup();
}); });
provider.init(providerConfig.immonet, [], []); provider.init(providerConfig.immonet, [], []);
it('should test immonet provider', async () => { it('should test immonet provider', async () => {
const Fredy = await mockFredy(); const Fredy = await mockFredy();
return await new Promise((resolve) => { return await new Promise((resolve) => {
const fredy = new Fredy(provider.config, null, provider.metaInformation.id, 'immonet', similarityCache); const fredy = new Fredy(provider.config, null, provider.metaInformation.id, 'immonet', similarityCache);
fredy.execute().then((listing) => { fredy.execute().then((listing) => {
expect(listing).to.be.a('array'); expect(listing).to.be.a('array');
const notificationObj = get(); const notificationObj = get();
expect(notificationObj).to.be.a('object'); expect(notificationObj).to.be.a('object');
expect(notificationObj.serviceName).to.equal('immonet'); expect(notificationObj.serviceName).to.equal('immonet');
notificationObj.payload.forEach((notify) => { notificationObj.payload.forEach((notify) => {
/** check the actual structure **/ /** check the actual structure **/
expect(notify.id).to.be.a('string'); expect(notify.id).to.be.a('string');
expect(notify.price).to.be.a('string'); expect(notify.price).to.be.a('string');
expect(notify.size).to.be.a('string'); expect(notify.size).to.be.a('string');
expect(notify.title).to.be.a('string'); expect(notify.title).to.be.a('string');
expect(notify.link).to.be.a('string'); expect(notify.link).to.be.a('string');
expect(notify.address).to.be.a('string'); expect(notify.address).to.be.a('string');
/** check the values if possible **/ expect(notify.size).that.does.include('m²');
expect(notify.price).that.does.include('€'); expect(notify.title).to.be.not.empty;
expect(notify.size).that.does.include('m²'); expect(notify.address).to.be.not.empty;
expect(notify.title).to.be.not.empty; });
expect(notify.address).to.be.not.empty; resolve();
}); });
resolve(); });
});
}); });
});
}); });

688
yarn.lock

File diff suppressed because it is too large Load Diff