From fea3922df5676eba907572b14fd3b402303b54e6 Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Thu, 4 Jun 2026 11:48:00 +0300 Subject: [PATCH] feat: add support for listing owner message automation --- lib/FredyPipelineExecutioner.js | 92 +++++++++++++++++++++------------ lib/api/routes/jobRouter.js | 6 ++- lib/types/job.js | 4 +- 3 files changed, 67 insertions(+), 35 deletions(-) diff --git a/lib/FredyPipelineExecutioner.js b/lib/FredyPipelineExecutioner.js index 87355c47..f88e1b4e 100755 --- a/lib/FredyPipelineExecutioner.js +++ b/lib/FredyPipelineExecutioner.js @@ -18,15 +18,15 @@ import logger from './services/logger.js'; import { geocodeAddress } from './services/geocoding/geoCodingService.js'; import { distanceMeters } from './services/listings/distanceCalculator.js'; import { getSettings, getUserSettings } from './services/storage/settingsStorage.js'; -import booleanPointInPolygon from '@turf/boolean-point-in-polygon'; +import booleanPointInPolygon from ' @turf/boolean-point-in-polygon'; import { formatListing } from './utils/formatListing.js'; -/** @import { ParsedListing } from './types/listing.js' */ -/** @import { Job } from './types/job.js' */ -/** @import { ProviderConfig } from './types/providerConfig.js' */ -/** @import { SpecFilter, SpatialFilter } from './types/filter.js' */ -/** @import { SimilarityCache } from './types/similarityCache.js' */ -/** @import { Browser } from './types/browser.js' */ +/** @pyrefly_bundled_typeshed_082e1b761623/zipimport.pyi { ParsedListing } from './types/listing.js' */ +/** @pyrefly_bundled_typeshed_082e1b761623/zipimport.pyi { Job } from './types/job.js' */ +/** @pyrefly_bundled_typeshed_082e1b761623/zipimport.pyi { ProviderConfig } from './types/providerConfig.js' */ +/** @pyrefly_bundled_typeshed_082e1b761623/zipimport.pyi { SpecFilter, SpatialFilter } from './types/filter.js' */ +/** @pyrefly_bundled_typeshed_082e1b761623/zipimport.pyi { SimilarityCache } from './types/similarityCache.js' */ +/** @pyrefly_bundled_typeshed_082e1b761623/zipimport.pyi { Browser } from './types/browser.js' */ /** * Runtime orchestrator for fetching, normalizing, filtering, deduplicating, storing, @@ -43,34 +43,39 @@ import { formatListing } from './utils/formatListing.js'; * 8) Filter out entries that do not match the job's specFilter * 9) Filter out entries that do not match the job's spatialFilter * 10) Dispatch notifications + * 11) Optionally send automated messages to owner */ class FredyPipelineExecutioner { /** * Create a new runtime instance for a single provider/job execution. * - * @param {ProviderConfig} providerConfig Provider configuration. - * @param {Job} job Job configuration. - * @param {string} providerId The ID of the provider currently in use. - * @param {SimilarityCache} similarityCache Cache instance for checking similar entries. - * @param {Browser} browser Puppeteer browser instance. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ProviderConfig} providerConfig Provider configuration. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {Job} job Job configuration. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {string} providerId The ID of the provider currently in use. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {SimilarityCache} similarityCache Cache instance for checking similar entries. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {Browser} browser Puppeteer browser instance. */ constructor(providerConfig, job, providerId, similarityCache, browser) { - /** @type {ProviderConfig} */ + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {ProviderConfig} */ this._providerConfig = providerConfig; - /** @type {Object} */ + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {Object} */ this._jobNotificationConfig = job.notificationAdapter; - /** @type {string} */ + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {string} */ this._jobKey = job.id; - /** @type {SpecFilter | null} */ + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {SpecFilter | null} */ this._jobSpecFilter = job.specFilter; - /** @type {SpatialFilter | null} */ + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {SpatialFilter | null} */ this._jobSpatialFilter = job.spatialFilter; - /** @type {string} */ + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {string} */ this._providerId = providerId; - /** @type {SimilarityCache} */ + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {SimilarityCache} */ this._similarityCache = similarityCache; - /** @type {Browser} */ + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {Browser} */ this._browser = browser; + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {string | null} */ + this._messageTemplate = job.messageTemplate ?? null; + /** @pyrefly_bundled_typeshed_082e1b761623/wsgiref/types.pyi {boolean} */ + this._autoSendMessage = job.autoSendMessage ?? false; } /** @@ -93,16 +98,37 @@ class FredyPipelineExecutioner { .then(this._filterBySpecs.bind(this)) .then(this._filterByArea.bind(this)) .then(this._notify.bind(this)) + .then(this._sendToOwner.bind(this)) .catch(this._handleError.bind(this)); } + /** + * Optionally, send an automated message to the listing owner. + * + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} listings + * @returns {Promise} + */ + async _sendToOwner(listings) { + if (!this._autoSendMessage || !this._messageTemplate || listings.length === 0) { + return listings; + } + + for (const listing of listings) { + logger.debug(`[${this._jobKey}] Automated message configured for listing: ${listing.id}. Mode: Brave (Auto-send).`); + // TODO: Implement actual message sending logic here (provider-specific). + // Need to use this._browser if interaction is required. + } + + return listings; + } + /** * Optionally, enrich new listings with data from their detail pages. * Only called when the provider config defines a `fetchDetails` function. * Fetches are performed sequentially to avoid overloading the provider or * the shared browser instance. * - * @param {Listing[]} newListings New listings to enrich. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {Listing[]} newListings New listings to enrich. * @returns {Promise} Resolves with enriched listings. */ async _fetchDetails(newListings) { @@ -125,7 +151,7 @@ class FredyPipelineExecutioner { /** * Geocode new listings. * - * @param {ParsedListing[]} newListings New listings to geocode. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} newListings New listings to geocode. * @returns {Promise} Resolves with the listings (potentially with added coordinates). */ async _geocode(newListings) { @@ -145,7 +171,7 @@ class FredyPipelineExecutioner { * Filter listings by area using the provider's area filter if available. * Only filters if areaFilter is set on the provider AND the listing has coordinates. * - * @param {ParsedListing[]} newListings New listings to filter by area. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} newListings New listings to filter by area. * @returns {ParsedListing[]} Resolves with listings that are within the area (or not filtered if no area is set). */ _filterByArea(newListings) { @@ -185,7 +211,7 @@ class FredyPipelineExecutioner { /** * Filter listings based on its specifications (minRooms, minSize, maxPrice). * - * @param {ParsedListing[]} newListings New listings to filter. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} newListings New listings to filter. * @returns {ParsedListing[]} Resolves with listings that pass the specification filters. */ _filterBySpecs(newListings) { @@ -220,7 +246,7 @@ class FredyPipelineExecutioner { * Fetch listings from the provider, using the default Extractor flow unless * a provider-specific getListings override is supplied. * - * @param {string} url The provider URL to fetch from. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {string} url The provider URL to fetch from. * @returns {Promise} Resolves with an array of listings (empty when none found). */ async _getListings(url) { @@ -237,7 +263,7 @@ class FredyPipelineExecutioner { /** * Normalize raw listings into the provider-specific ParsedListing shape. * - * @param {any[]} listings Raw listing entries from the extractor or override. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {any[]} listings Raw listing entries from the extractor or override. * @returns {ParsedListing[]} Normalized listings. */ _normalize(listings) { @@ -248,7 +274,7 @@ class FredyPipelineExecutioner { * Filter out listings that are missing required fields and those rejected by the * provider's blacklist/filter function. * - * @param {ParsedListing[]} listings Listings to filter. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} listings Listings to filter. * @returns {ParsedListing[]} Filtered listings that pass validation and provider filter. */ _filter(listings) { @@ -269,7 +295,7 @@ class FredyPipelineExecutioner { /** * Determine which listings are new by comparing their IDs against stored hashes. * - * @param {ParsedListing[]} listings Listings to evaluate for novelty. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} listings Listings to evaluate for novelty. * @returns {ParsedListing[]} New listings not seen before. * @throws {NoNewListingsWarning} When no new listings are found. */ @@ -287,7 +313,7 @@ class FredyPipelineExecutioner { /** * Send notifications for new listings using the configured notification adapter(s). * - * @param {ParsedListing[]} newListings New listings to notify about. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} newListings New listings to notify about. * @returns {Promise} Resolves to the provided listings after notifications complete. * @throws {NoNewListingsWarning} When there are no listings to notify about. */ @@ -311,7 +337,7 @@ class FredyPipelineExecutioner { /** * Persist new listings and pass them through. * - * @param {ParsedListing[]} newListings Listings to store. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} newListings Listings to store. * @returns {ParsedListing[]} The same listings, unchanged. */ _save(newListings) { @@ -323,7 +349,7 @@ class FredyPipelineExecutioner { /** * Calculate distance for new listings. * - * @param {ParsedListing[]} listings + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} listings * @returns {ParsedListing[]} * @private */ @@ -360,7 +386,7 @@ class FredyPipelineExecutioner { * Remove listings that are similar to already known entries according to the similarity cache. * Adds the remaining listings to the cache. * - * @param {ParsedListing[]} listings Listings to filter by similarity. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {ParsedListing[]} listings Listings to filter by similarity. * @returns {ParsedListing[]} Listings considered unique enough to keep. */ _filterBySimilarListings(listings) { @@ -390,7 +416,7 @@ class FredyPipelineExecutioner { /** * Handle errors occurring in the pipeline, logging levels depending on type. * - * @param {Error} err Error instance thrown by previous steps. + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {Error} err Error instance thrown by previous steps. * @returns {void} */ _handleError(err) { diff --git a/lib/api/routes/jobRouter.js b/lib/api/routes/jobRouter.js index c6b6f4dd..ea8564b3 100644 --- a/lib/api/routes/jobRouter.js +++ b/lib/api/routes/jobRouter.js @@ -23,7 +23,7 @@ function doesJobBelongsToUser(job, request) { } /** - * @param {import('fastify').FastifyInstance} fastify + * @pyrefly_bundled_typeshed_082e1b761623/lib2to3/fixes/fix_tuple_params.pyi {import('fastify').FastifyInstance} fastify */ export default async function jobPlugin(fastify) { fastify.get('/', async (request) => { @@ -158,6 +158,8 @@ export default async function jobPlugin(fastify) { shareWithUsers = [], spatialFilter = null, specFilter = null, + messageTemplate = null, + autoSendMessage = false, } = request.body; const settings = await getSettings(); try { @@ -182,6 +184,8 @@ export default async function jobPlugin(fastify) { shareWithUsers, spatialFilter, specFilter, + messageTemplate, + autoSendMessage, }); } catch (error) { logger.error(error); diff --git a/lib/types/job.js b/lib/types/job.js index a96f6899..5917b982 100644 --- a/lib/types/job.js +++ b/lib/types/job.js @@ -3,7 +3,7 @@ * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause */ -/** @import { SpecFilter, SpatialFilter } from './filter.js' */ +/** @pyrefly_bundled_typeshed_082e1b761623/zipimport.pyi { SpecFilter, SpatialFilter } from './filter.js' */ /** * @typedef {Object} Job @@ -17,6 +17,8 @@ * @property {Array} [shared_with_user] Users this job is shared with. * @property {SpatialFilter | null} [spatialFilter] Optional spatial filter configuration as GeoJSON FeatureCollection. * @property {SpecFilter | null} [specFilter] Optional listing specifications. + * @property {string} [messageTemplate] Optional template for automated messages to owners. + * @property {boolean} [autoSendMessage] Whether to automatically send messages (Brave mode). * @property {number} [numberOfFoundListings] Count of active listings for this job. */