Merge branch 'mail-adapter'

This commit is contained in:
Christian Kellner
2020-07-17 22:22:34 +02:00
8 changed files with 60 additions and 56 deletions

View File

@@ -152,14 +152,14 @@ Districts that are unwanted can be blacklisted here.
This makes sense for provider that only offer limited filter functions like Kalaydo/Ebay. This makes sense for provider that only offer limited filter functions like Kalaydo/Ebay.
# API # API
While Fredy is running, you can make use of the rest api provided on port `9988` to get information about the current status of Fredy. While Fredy is running, you can make use of the rest api provided on port `9998` to get information about the current status of Fredy.
#### http://localhost:9988/ #### http://localhost:9998/
Gives you an overview of running search jobs, their included enabled provider, last execution and the number of listings, found by each provider. Gives you an overview of running search jobs, their included enabled provider, last execution and the number of listings, found by each provider.
#### http://localhost:9988/ping #### http://localhost:9998/ping
Should you ever need some health checks, this returns pong ;) Should you ever need some health checks, this returns pong ;)
#### http://localhost:9988/jobs/:name #### http://localhost:9998/jobs/:name
Returns specific information about the job with the given name or `404` if the job could not be found. Returns specific information about the job with the given name or `404` if the job could not be found.
# Docker # Docker

View File

@@ -3,7 +3,7 @@ const {
setKnownListings, setKnownListings,
getKnownListings, getKnownListings,
setNumberOfTotalFoundProviderListings, setNumberOfTotalFoundProviderListings,
getForTesting getForTesting,
} = require('./services/store'); } = require('./services/store');
const notify = require('./notification/notify'); const notify = require('./notification/notify');
@@ -79,7 +79,7 @@ class FredyRuntime {
} }
_findNew(listings) { _findNew(listings) {
const newListings = listings.filter(o => getKnownListings(this._jobKey, this._providerId).indexOf(o.id) === -1); const newListings = listings.filter((o) => getKnownListings(this._jobKey, this._providerId).indexOf(o.id) === -1);
if (newListings.length === 0) { if (newListings.length === 0) {
throw new NoNewListingsError(); throw new NoNewListingsError();
@@ -89,14 +89,14 @@ class FredyRuntime {
} }
_notify(newListings) { _notify(newListings) {
const sendNotifications = notify.send(this._providerId, newListings, this._notificationConfig); const sendNotifications = notify.send(this._providerId, newListings, this._notificationConfig, this._jobKey);
return Promise.all(sendNotifications).then(() => newListings); return Promise.all(sendNotifications).then(() => newListings);
} }
_save(newListings) { _save(newListings) {
setKnownListings(this._jobKey, this._providerId, [ setKnownListings(this._jobKey, this._providerId, [
...getKnownListings(this._jobKey, this._providerId), ...getKnownListings(this._jobKey, this._providerId),
...newListings.map(l => l.id) ...newListings.map((l) => l.id),
]); ]);
return newListings; return newListings;
} }

View File

@@ -1,24 +1,24 @@
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const config = require('../../conf/config'); const config = require('../../conf/config');
const { getLastJobExecution, getLastProviderExecution, getTotalNumberOfListings } = require('../services/store'); const { getLastJobExecution, getLastProviderExecution, getTotalNumberOfListings } = require('../services/store');
const PORT = 9988; const PORT = 9998;
const service = require('restana')(); const service = require('restana')();
service.use(bodyParser.json()); service.use(bodyParser.json());
service.get('/', async (req, res) => { service.get('/', async (req, res) => {
const result = {}; const result = {};
Object.keys(config.jobs).forEach(job => { Object.keys(config.jobs).forEach((job) => {
result[job] = { result[job] = {
lastExecution: getLastJobExecution(job), lastExecution: getLastJobExecution(job),
enabledProvider: Object.keys(config.jobs[job].provider) enabledProvider: Object.keys(config.jobs[job].provider)
.filter(providerKey => config.jobs[job].provider[providerKey].enabled) .filter((providerKey) => config.jobs[job].provider[providerKey].enabled)
.map(providerKey => { .map((providerKey) => {
return { return {
name: providerKey, name: providerKey,
lastExecution: getLastProviderExecution(job, providerKey), lastExecution: getLastProviderExecution(job, providerKey),
totalFindings: getTotalNumberOfListings(job, providerKey) totalFindings: getTotalNumberOfListings(job, providerKey),
}; };
}) }),
}; };
}); });
res.body = result; res.body = result;
@@ -35,22 +35,22 @@ service.get('/jobs/:name', async (req, res) => {
res.body = { res.body = {
lastExecution: getLastJobExecution(jobKey), lastExecution: getLastJobExecution(jobKey),
enabledProvider: Object.keys(config.jobs[jobKey].provider) enabledProvider: Object.keys(config.jobs[jobKey].provider)
.filter(providerKey => config.jobs[jobKey].provider[providerKey].enabled) .filter((providerKey) => config.jobs[jobKey].provider[providerKey].enabled)
.map(providerKey => { .map((providerKey) => {
return { return {
name: providerKey, name: providerKey,
url: config.jobs[jobKey].provider[providerKey].url, url: config.jobs[jobKey].provider[providerKey].url,
lastExecution: getLastProviderExecution(jobKey, providerKey), lastExecution: getLastProviderExecution(jobKey, providerKey),
totalFindings: getTotalNumberOfListings(jobKey, providerKey) totalFindings: getTotalNumberOfListings(jobKey, providerKey),
}; };
}) }),
}; };
res.send(); res.send();
}); });
service.get('/ping', function(req, res) { service.get('/ping', function (req, res) {
res.body = { res.body = {
pong: 'pong' pong: 'pong',
}; };
res.send(); res.send();
}); });

View File

@@ -3,13 +3,14 @@
* @param serviceName e.g immoscout * @param serviceName e.g immoscout
* @param newListings an array with newly found listings * @param newListings an array with newly found listings
* @param notificationConfig config of this notification adapter * @param notificationConfig config of this notification adapter
* @param jobKey name of the current job that is being executed
*/ */
exports.send = (serviceName, newListings, notificationConfig) => { exports.send = (serviceName, newListings, notificationConfig, jobKey) => {
const { enabled } = notificationConfig.console; const { enabled } = notificationConfig.console;
if (!enabled) { if (!enabled) {
return [Promise.resolve()]; return [Promise.resolve()];
} }
/* eslint-disable no-console */ /* eslint-disable no-console */
return [Promise.resolve(console.info(`Found entry from service ${serviceName}:`, newListings))]; return [Promise.resolve(console.info(`Found entry from service ${serviceName}, Job: ${jobKey}:`, newListings))];
/* eslint-enable no-console */ /* eslint-enable no-console */
}; };

View File

@@ -5,9 +5,10 @@ const sgMail = require('@sendgrid/mail');
* @param serviceName e.g immoscout * @param serviceName e.g immoscout
* @param newListings an array with newly found listings * @param newListings an array with newly found listings
* @param notificationConfig config of this notification adapter * @param notificationConfig config of this notification adapter
* * @param jobKey name of the current job that is being executed
* @returns {Promise<Chat.PostMessage.Response> | void} * @returns {Promise<Chat.PostMessage.Response> | void}
*/ */
exports.send = (serviceName, newListings, notificationConfig) => { exports.send = (serviceName, newListings, notificationConfig, jobKey) => {
const { apiKey, enabled, receiver, from, templateId } = notificationConfig.sendGrid; const { apiKey, enabled, receiver, from, templateId } = notificationConfig.sendGrid;
if (!enabled) { if (!enabled) {
return [Promise.resolve()]; return [Promise.resolve()];
@@ -17,9 +18,9 @@ exports.send = (serviceName, newListings, notificationConfig) => {
templateId, templateId,
to: receiver, to: receiver,
from, from,
subject: `Service ${serviceName} found ${newListings.length} new listing(s)`, subject: `Job ${jobKey} | Service ${serviceName} found ${newListings.length} new listing(s)`,
dynamic_template_data: { dynamic_template_data: {
serviceName, serviceName: `Job: (${jobKey}) | Service: ${serviceName}`,
numberOfListings: newListings.length, numberOfListings: newListings.length,
listings: newListings, listings: newListings,
}, },

View File

@@ -6,18 +6,19 @@ const msg = Slack.chat.postMessage;
* @param serviceName e.g immoscout * @param serviceName e.g immoscout
* @param newListings an array with newly found listings * @param newListings an array with newly found listings
* @param notificationConfig config of this notification adapter * @param notificationConfig config of this notification adapter
* * @param jobKey name of the current job that is being executed
* @returns {Promise<Chat.PostMessage.Response> | void} * @returns {Promise<Chat.PostMessage.Response> | void}
*/ */
exports.send = (serviceName, newListings, notificationConfig) => { exports.send = (serviceName, newListings, notificationConfig, jobKey) => {
const { token, channel, enabled } = notificationConfig.slack; const { token, channel, enabled } = notificationConfig.slack;
if (!enabled) { if (!enabled) {
return [Promise.resolve()]; return [Promise.resolve()];
} }
return newListings.map(payload => return newListings.map((payload) =>
msg({ msg({
token, token,
channel, channel,
text: `*(${serviceName})* - ${payload.title}`, text: `*(${serviceName} - ${jobKey})* - ${payload.title}`,
attachments: [ attachments: [
{ {
fallback: payload.title, fallback: payload.title,
@@ -28,23 +29,23 @@ exports.send = (serviceName, newListings, notificationConfig) => {
{ {
title: 'Price', title: 'Price',
value: payload.price, value: payload.price,
short: false short: false,
}, },
{ {
title: 'Size', title: 'Size',
value: payload.size, value: payload.size,
short: false short: false,
}, },
{ {
title: 'Address', title: 'Address',
value: payload.address, value: payload.address,
short: false short: false,
} },
], ],
footer: 'Powered by Fredy', footer: 'Powered by Fredy',
ts: new Date().getTime() / 1000 ts: new Date().getTime() / 1000,
} },
] ],
}) })
); );
}; };

View File

@@ -1,33 +1,36 @@
const TelegramBot = require('tg-yarl'); const TelegramBot = require('tg-yarl');
const opts = {parse_mode: 'Markdown'}; const opts = { parse_mode: 'Markdown' };
/** /**
* sends new listings to telegram * sends new listings to telegram
* @param serviceName e.g immoscout * @param serviceName e.g immoscout
* @param newListings an array with newly found listings * @param newListings an array with newly found listings
* @param notificationConfig config of this notification adapter * @param notificationConfig config of this notification adapter
* * @param jobKey name of the current job that is being executed
* @returns {Promise<Void> | void} * @returns {Promise<Void> | void}
*/ */
exports.send = (serviceName, newListings, notificationConfig) => { exports.send = (serviceName, newListings, notificationConfig, jobKey) => {
const {enabled, token, chatId} = notificationConfig.telegram; const { enabled, token, chatId } = notificationConfig.telegram;
if (!enabled) { if (!enabled) {
return [Promise.resolve()]; return [Promise.resolve()];
} }
const bot = new TelegramBot(token); const bot = new TelegramBot(token);
let message = `Service _${serviceName}_ found _${newListings.length}_ new listings:\n\n`; let message = `Job: ${jobKey} | Service _${serviceName}_ found _${newListings.length}_ new listings:\n\n`;
message += newListings.map(o => message += newListings.map(
`*${shorten(o.title.replace(/\*/g, ''), 45)}*\n` + (o) =>
[o.address, o.price, o.size].join(' | ') + '\n' + `*${shorten(o.title.replace(/\*/g, ''), 45)}*\n` +
`[LINK](${o.link})\n\n`); [o.address, o.price, o.size].join(' | ') +
'\n' +
`[LINK](${o.link})\n\n`
);
return bot.sendMessage(chatId, message, opts); return bot.sendMessage(chatId, message, opts);
}; };
function shorten(str, len = 30) { function shorten(str, len = 30) {
return str.length > len ? str.substring(0, len) + '...' : str; return str.length > len ? str.substring(0, len) + '...' : str;
} }

View File

@@ -2,15 +2,13 @@ const fs = require('fs');
const path = './adapter'; const path = './adapter';
/** Read every integration existing in ./adapter **/ /** Read every integration existing in ./adapter **/
const adapter = fs const adapter = fs.readdirSync('./lib/notification/adapter').map((integPath) => require(`${path}/${integPath}`));
.readdirSync('./lib/notification/adapter')
.map(integPath => require(`${path}/${integPath}`));
if (adapter.length === 0) { if (adapter.length === 0) {
throw new Error('Please specify at least one notification provider'); throw new Error('Please specify at least one notification provider');
} }
exports.send = (serviceName, payload, notificationConfig) => { exports.send = (serviceName, payload, notificationConfig, jobKey) => {
//this is not being used in tests, therefor adapter are always set //this is not being used in tests, therefor adapter are always set
return adapter.map(a => a.send(serviceName, payload, notificationConfig)); return adapter.map((a) => a.send(serviceName, payload, notificationConfig, jobKey));
}; };