mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-11 15:47:48 +08:00
feat: leboncoin.fr (#2749)
* feat: add leboncoin.fr ad feed * docs: add leboncoin/ad/:query route
This commit is contained in:
@@ -520,6 +520,18 @@ GitHub provides some official RSS feeds:
|
||||
|
||||
<RouteEn author="HenryQW" example="/parcel/hermesuk/[tracking number]" path="/parcel/hermesuk/:tracking" :paramsDesc="['Tracking number']"/>
|
||||
|
||||
## E-commerce
|
||||
|
||||
### leboncoin
|
||||
|
||||
Transform any search into a feed.
|
||||
|
||||
<RouteEn author="Platane" example="/leboncoin/ad/category=10&locations=Paris_75015" path="/leboncoin/ad/:query" :paramsDesc="['search page querystring']">
|
||||
|
||||
For instance, in https://www.leboncoin.fr/recherche/?**category=10&locations=Paris_75015**, the query is **category=10&locations=Paris_75015**
|
||||
|
||||
</RouteEn>
|
||||
|
||||
## Uncategorized
|
||||
|
||||
### EZTV
|
||||
|
||||
@@ -1597,6 +1597,9 @@ router.get('/aliyun/database_month', require('./routes/aliyun/database_month'));
|
||||
// 礼物说
|
||||
router.get('/liwushuo/index', require('./routes/liwushuo/index.js'));
|
||||
|
||||
// leboncoin
|
||||
router.get('/leboncoin/ad/:query', require('./routes/leboncoin/ad.js'));
|
||||
|
||||
// DHL
|
||||
router.get('/dhl/:id', require('./routes/dhl/shipment-tracking'));
|
||||
|
||||
|
||||
131
lib/routes/leboncoin/ad.js
Normal file
131
lib/routes/leboncoin/ad.js
Normal file
@@ -0,0 +1,131 @@
|
||||
const querystring = require('querystring');
|
||||
const got = require('@/utils/got');
|
||||
|
||||
const API_KEY = 'ba0c2dad52b3ec';
|
||||
const SEARCH_ENDPOINT = 'https://api.leboncoin.fr/finder/search';
|
||||
|
||||
// convert the querystring read from the url to the object expected by the rest API
|
||||
const convertQueryToFilters = (query) => {
|
||||
const queryObject = querystring.parse(query);
|
||||
|
||||
const filters = { keywords: {}, location: {}, ranges: {}, enums: {} };
|
||||
|
||||
queryObject.ad_type = queryObject.ad_type || 'offer';
|
||||
|
||||
for (const [key, value] of Object.entries(queryObject)) {
|
||||
// category
|
||||
if (key === 'category') {
|
||||
filters.category = { id: queryObject.category };
|
||||
continue;
|
||||
}
|
||||
|
||||
// location.locations
|
||||
if (key === 'locations') {
|
||||
filters.location.locations = value
|
||||
.split(',')
|
||||
.map((l) => {
|
||||
if (l.startsWith('r_')) {
|
||||
return { locationType: 'region', region_id: l.slice(2) };
|
||||
}
|
||||
if (l.startsWith('dn_')) {
|
||||
return { locationType: 'department_near', department_id: l.slice(3) };
|
||||
}
|
||||
|
||||
{
|
||||
if (l.match(/.+_.+/)) {
|
||||
const [city, zipcode] = l.split('_');
|
||||
return { locationType: 'city', city, zipcode };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// keywords.text
|
||||
if (key === 'text') {
|
||||
filters.keywords.text = value;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// location.area
|
||||
if (key === 'lat' || key === 'lng' || key === 'radius') {
|
||||
filters.location.area = {
|
||||
lat: parseFloat(queryObject.lat),
|
||||
lng: parseFloat(queryObject.lng),
|
||||
radius: parseFloat(queryObject.radius),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
// range type
|
||||
{
|
||||
const m = value.match(/^(\d+|min)-(\d+|max)$/);
|
||||
|
||||
if (m) {
|
||||
const [, min, max] = m;
|
||||
|
||||
filters.ranges = filters.ranges || {};
|
||||
filters.ranges[key] = {};
|
||||
if (min !== 'min') {
|
||||
filters.ranges[key].min = parseInt(min);
|
||||
}
|
||||
if (max !== 'max') {
|
||||
filters.ranges[key].max = parseInt(max);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// enum type
|
||||
filters.enums[key] = value.split(',');
|
||||
}
|
||||
|
||||
return filters;
|
||||
};
|
||||
|
||||
const buildDescription = (ad) =>
|
||||
// the description is made of
|
||||
// - first the thumbnail
|
||||
// - the ad description
|
||||
// - all the images in thumbnail version, and link to large version
|
||||
[
|
||||
(ad.images && ad.images.thumb_url && `<img referrerpolicy="no-referrer" src="${ad.images.thumb_url}">`) || '',
|
||||
'',
|
||||
...ad.body.split(/\n/),
|
||||
'',
|
||||
((ad.images && ad.images.urls_thumb && ad.images.urls_thumb.map((url, i) => `<a href=${ad.images.urls_large[i]}><img referrerpolicy="no-referrer" src="${url}"></a>`)) || []).join(''),
|
||||
].join('<br>');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const query = ctx.params.query;
|
||||
|
||||
const response = await got({
|
||||
headers: {
|
||||
api_key: API_KEY,
|
||||
origin: 'https://www.leboncoin.fr',
|
||||
},
|
||||
method: 'post',
|
||||
url: SEARCH_ENDPOINT,
|
||||
body: JSON.stringify({ filters: convertQueryToFilters(query), limit: 50, limit_alu: 3, sort_by: 'time', sort_order: 'desc' }),
|
||||
});
|
||||
|
||||
ctx.state.data = {
|
||||
title: `ads for ${query.replace(/&/g, ' ')}`,
|
||||
description: `ads for ${query.replace(/&/g, ' ')}`,
|
||||
link: `https://www.leboncoin.fr/recherche/?${query}`,
|
||||
language: 'fr',
|
||||
item: (response.data.ads || [])
|
||||
.filter((ad) => ad.status === 'active')
|
||||
.map((ad) => ({
|
||||
title: (ad.price && ad.price[0] ? `${ad.price[0]}€ - ` : '') + ad.subject,
|
||||
description: buildDescription(ad),
|
||||
pubDate: new Date(ad.first_publication_date).toUTCString(),
|
||||
link: ad.url,
|
||||
})),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user