/* * Copyright (c) 2026 by Christian Kellner. * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause */ /* Rent a flat Web: https://www.immobilienscout24.de/Suche/de/nordrhein-westfalen/duesseldorf/wohnung-mieten?numberofrooms=1.0-10000.0&price=1.0-10000.0&livingspace=10.0-10000.0&pricetype=rentpermonth&enteredFrom=result_list */ /* Rent a flat: Web: https://www.immobilienscout24.de/Suche/de/nordrhein-westfalen/duesseldorf/wohnung-mieten?enteredFrom=one_step_search Mobile: https://api.mobile.immobilienscout24.de/search/list?numberofrooms=1.5-&searchId=d7c127d8-6630-49e8-a1dd-5ae04dad454d&sorting=standard&pagesize=20&livingspace=10-500&pagenumber=1&realestatetype=apartmentrent&priceType=calculatedtotalrent&price=1-10000&publishedafter=2025-05-14T09:11:54&channel=is24&searchType=region&geocodes=/de/nordrhein-westfalen/duesseldorf&features=adKeysAndStringValues,virtualTour,contactDetails,viareporting,nextgen,calculatedTotalRent,listingsInListFirstSummary,xxlListingType,quickfilters,grouping,projectsInAllRealestateTypes,fairPrice */ /* Rent a house: Web: https://www.immobilienscout24.de/Suche/de/nordrhein-westfalen/duesseldorf/haus-mieten?enteredFrom=one_step_search Mobile: https://api.mobile.immobilienscout24.de/search/map/v3?publishedafter=2025-05-14T09:12:49&pagenumber=1&searchType=region&geocodes=/de/nordrhein-westfalen/duesseldorf&realEstateType=houserent&pagesize=300&features=disableNHBGrouping,nextGen,fairPrice,listingsInListFirstSummary,xxlListingType,contactDetails&sorting=standard */ /* buy a flat Web: https://www.immobilienscout24.de/Suche/de/nordrhein-westfalen/duesseldorf/wohnung-kaufen?numberofrooms=1.0-10000.0&price=1.0-10000.0&livingspace=1.0-10000.0&enteredFrom=result_list Mobile: https://api.mobile.immobilienscout24.de/search/map/v3?features=disableNHBGrouping,nextGen,fairPrice,listingsInListFirstSummary,xxlListingType,contactDetails&sorting=standard&realEstateType=apartmentbuy&pagesize=300&pagenumber=1&geocodes=/de/nordrhein-westfalen/duesseldorf&publishedafter=2025-05-14T09:14:43&searchType=region */ /* Buy a house Web: https://www.immobilienscout24.de/Suche/de/nordrhein-westfalen/duesseldorf/haus-kaufen?numberofrooms=1.0-10000.0&price=1.0-10000.0E7&livingspace=1.0-10000.0&enteredFrom=result_list Mobile: https://api.mobile.immobilienscout24.de/search/map/v3?geocodes=/de/nordrhein-westfalen/duesseldorf&features=disableNHBGrouping,nextGen,fairPrice,listingsInListFirstSummary,xxlListingType,contactDetails&searchType=region&realEstateType=housebuy&pagenumber=1&pagesize=300&sorting=standard&publishedafter=2025-05-14T09:16:28 */ /* Buy a house only in parts of a city Web: https://www.immobilienscout24.de/Suche/de/nordrhein-westfalen/haus-kaufen?numberofrooms=1.0-10000.0&price=1.0-10000.0E7&livingspace=1.0-10000.0&geocodes=1276010037,1276010014,1276010012&enteredFrom=result_list Mobile: https://api.mobile.immobilienscout24.de/search/list?pagesize=20&pagenumber=1&features=adKeysAndStringValues,virtualTour,contactDetails,viareporting,grouping,nextgen,listingsInListFirstSummary,xxlListingType,quickfilters,fairPrice&sorting=standard&channel=is24&geocodes=/de/nordrhein-westfalen/duesseldorf/stadtbezirk-1&searchType=region&realestatetype=housebuy&publishedafter=2025-05-14T09:17:23 */ /* Buy a house with radius Web: https://www.immobilienscout24.de/Suche/radius/haus-kaufen?centerofsearchaddress=D%C3%BCsseldorf%3B%3B%3B%3B%3B%3B&numberofrooms=1.0-10000.0&price=1.0-1.0E7&livingspace=1.0-10000.0&geocoordinates=51.22496%3B6.77567%3B5.0&enteredFrom=result_list Mobile: https://api.mobile.immobilienscout24.de/home/search/total?pagenumber=1&pagesize=1&geocoordinates=51.224960;6.775670;4.0&sorting=standard&searchType=radius&features=adKeysAndStringValues,virtualTour,contactDetails,grouping,nextgen,listingsInListFirstSummary,xxlListingType,fairPrice&channel=is24&realestatetype=housebuy&publishedafter=2025-05-14T09:19:43 */ /* Buy a house with shape Web: https://www.immobilienscout24.de/Suche/shape/haus-kaufen?shape=eW1yd0hpZGloQGBJa1NfQWFsQG9Uc1ZvVmlDbHdAZ2BAaEBjfEB5U3NWY2NCa0RvWmpwQG1KYGdCeldqU3Z4QGBAbENvQmJWaGtA&numberofrooms=1.0-100000.0&price=1.0-1.0E7&livingspace=1.0-100000.0&enteredFrom=result_list#/ Mobile: https://api.mobile.immobilienscout24.de/search/map/v3?features=disableNHBGrouping,nextGen,fairPrice,listingsInListFirstSummary,xxlListingType,contactDetails&publishedafter=2025-05-14T09:19:43&sorting=standard&pagesize=300&searchType=shape&realEstateType=housebuy&pagenumber=1&shape=%7D%7BjwHy%7Cqh@jCKdCgAvB_BdB%7DBzAaCjAqCfAqC~@uCt@iCh@eCZkCLyC?_EO%7DEa@%7DEa@iE_@%7BD%5DaDe@gDi@gDo@uCu@kBcB_AeDOiE?iDCgCMuBOkDCkG?yFRgD%60@cB%5C%7BA%60@eBx@aB%7C@kAbAy@rAe@bBUxCAhE?dFh@fGlAzGbBbHlBxGdB%60FrAhDz@xBh@nAf@l@RNNXkCkMJR~B%7CEnCpErCnDtClCvC~ApCh@rCJpC? */ import queryString from 'query-string'; import { nullOrEmpty } from '../../utils.js'; const PARAM_NAME_MAP = { heatingtypes: 'heatingtypes', haspromotion: 'haspromotion', numberofrooms: 'numberofrooms', livingspace: 'livingspace', energyefficiencyclasses: 'energyefficiencyclasses', exclusioncriteria: 'exclusioncriteria', equipment: 'equipment', petsallowedtypes: 'petsallowedtypes', price: 'price', constructionyear: 'constructionyear', apartmenttypes: 'apartmenttypes', pricetype: 'pricetype', floor: 'floor', geocodes: 'geocodes', geocoordinates: 'geocoordinates', shape: 'shape', sorting: 'sorting', newbuilding: 'newbuilding', }; const EQUIPMENT_MAP = { parking: 'parking', cellar: 'cellar', builtinkitchen: 'builtInKitchen', lift: 'lift', garden: 'garden', guesttoilet: 'guestToilet', balcony: 'balcony', handicappedaccessible: 'handicappedAccessible', }; const REAL_ESTATE_TYPE = { 'haus-mieten': 'houserent', 'wohnung-mieten': 'apartmentrent', 'wohnung-kaufen': 'apartmentbuy', 'haus-kaufen': 'housebuy', }; const WEB_PATH_TO_APARTMENT_EQUIPMENT_MAP = { // Category "Balkon/Terrasse" 'wohnung-mit-balkon-mieten': { equipment: ['balcony'] }, 'wohnung-mit-garten-mieten': { equipment: ['garden'] }, // Category "Wohnungstyp" 'souterrainwohnung-mieten': { apartmenttypes: ['halfbasement'] }, 'erdgeschosswohnung-mieten': { apartmenttypes: ['groundfloor'] }, 'hochparterrewohnung-mieten': { apartmenttypes: ['raisedgroundfloor'] }, 'etagenwohnung-mieten': { apartmenttypes: ['apartment'] }, 'loft-mieten': { apartmenttypes: ['loft'] }, 'maisonette-mieten': { apartmenttypes: ['maisonette'] }, 'terrassenwohnung-mieten': { apartmenttypes: ['terracedflat'] }, 'penthouse-mieten': { apartmenttypes: ['penthouse'] }, 'dachgeschosswohnung-mieten': { apartmenttypes: ['roofstorey'] }, // Category "Ausstattung" 'wohnung-mit-garage-mieten': { equipment: ['parking'] }, 'wohnung-mit-einbaukueche-mieten': { equipment: ['builtinkitchen'] }, 'wohnung-mit-keller-mieten': { equipment: ['cellar'] }, // Category "Merkmale" 'neubauwohnung-mieten': { newbuilding: true }, 'barrierefreie-wohnung-mieten': { equipment: ['handicappedaccessible'] }, }; export function convertWebToMobile(webUrl) { let url; try { url = new URL(webUrl); } catch { throw new Error(`Invalid URL: ${webUrl}`); } const segments = url.pathname.split('/'); if (segments[1] !== 'Suche') { throw new Error(`Unexpected path format: ${url.pathname}. We're expecting to see "/Suche" in the path.`); } const realTypeKey = segments.at(-1); let realType = REAL_ESTATE_TYPE[realTypeKey]; let additionalParamsFromWebPath; if (!realType) { // Test for seo optimized apartment path (only used on the ImmoScout web app) if (WEB_PATH_TO_APARTMENT_EQUIPMENT_MAP[realTypeKey]) { additionalParamsFromWebPath = WEB_PATH_TO_APARTMENT_EQUIPMENT_MAP[realTypeKey]; realType = REAL_ESTATE_TYPE['wohnung-mieten']; } else { throw new Error(`Real estate type not found: ${realTypeKey}`); } } if (segments.includes('shape')) { throw new Error('Shape is currently not supported using Immoscout'); } const { query: rawParams } = queryString.parseUrl(webUrl, { arrayFormat: 'comma' }); const webParams = Object.fromEntries( Object.entries(rawParams).filter(([key]) => key !== 'enteredFrom' && PARAM_NAME_MAP[key]), ); const geocodes = `/${segments.slice(2, 5).join('/')}`; const isRadius = segments.includes('radius'); const mobileParams = { searchType: isRadius ? 'radius' : 'region', realestatetype: realType, ...(isRadius ? {} : { geocodes }), ...additionalParamsFromWebPath, }; if (webParams.geocoordinates) { mobileParams.geocoordinates = webParams.geocoordinates; } for (const [key, val] of Object.entries(webParams)) { if (key === 'equipment') { const items = [].concat(val).flatMap((v) => `${v}`.split(',')); const currentEquipmentParams = mobileParams[PARAM_NAME_MAP[key]]; mobileParams[PARAM_NAME_MAP[key]] = [ ...(currentEquipmentParams ?? []), ...items.map((item) => EQUIPMENT_MAP[item.toLowerCase()]).filter(Boolean), ]; } else { mobileParams[PARAM_NAME_MAP[key]] = val; } } const mobileQuery = queryString.stringify(mobileParams, { arrayFormat: 'comma', encode: true, skipEmptyString: true, }); return `https://api.mobile.immobilienscout24.de/search/list?${mobileQuery}`; } export function convertImmoscoutListingToMobileListing(url) { if (nullOrEmpty(url)) { return null; } return url.replace( /^https:\/\/www\.immobilienscout24\.de\/expose\//, 'https://api.mobile.immobilienscout24.de/expose/', ); }