diff --git a/docs/README.md b/docs/README.md index c4239aa780..39c9606e39 100644 --- a/docs/README.md +++ b/docs/README.md @@ -172,7 +172,7 @@ id, App Store app id,必选,如 QQ 的链接为 https://itunes.apple.com/cn/ ### App Store/Mac App Store 价格更新(限免) -举例: [https://rsshub.app/appstore/price/cn/mac/id115244347](https://rsshub.app/appstore/price/cn/mac/id115244347) +举例: [https://rsshub.app/appstore/price/cn/mac/id1152443474](https://rsshub.app/appstore/price/cn/mac/id1152443474) 路由: `/appstore/price/:country/:type/:id` @@ -1890,12 +1890,16 @@ type,可选,不填则默认为 `all` ### Hopper 特价机票 -举例: 伦敦希思罗 ✈ 北京首都国际 [https://rsshub.app/hopper/LHR/PEK](https://rsshub.app/hopper/LHR/PEK) +本路由返回由 Hopper 算法给出的现在可购入最便宜的折扣机票,通常包含 6 个结果。出行日期将由 Hopper 算法定义,可能是明天也可能是 10 个月后。 -路由: `/hopper/:from/:to?` +举例: 伦敦希思罗 ✈ 北京首都国际 [https://rsshub.app/hopper/1/LHR/PEK](https://rsshub.app/hopper/1/LHR/PEK) + +路由: `/hopper/:lowestOnly/:from/:to?` 参数: +lowestOnly: 是否只返回最低价机票,`1`:是,其他任意值:否 + from: 始发地,IATA 国际航空运输协会机场代码 to: 目的地,IATA 国际航空运输协会机场代码,可选,缺省则目的地为`任意城市` diff --git a/docs/en/README.md b/docs/en/README.md index cb6b808fc3..6e4e44900e 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -150,7 +150,7 @@ id, App Store app id, obtain from the app URL `https://itunes.apple.com/us/app/r ### App Store/Mac App Store Price Drop Alert -eg: [https://rsshub.app/appstore/price/cn/mac/id115244347](https://rsshub.app/appstore/price/cn/mac/id115244347) +eg: [https://rsshub.app/appstore/price/us/mac/id1152443474](https://rsshub.app/appstore/price/cn/mac/id1152443474) Route: `/appstore/price/:country/:type/:id` @@ -455,12 +455,16 @@ Parameters: ### Hopper Flight Deals -Eg: London Heathrow Airport ✈ Beijing Capital International Airport [https://rsshub.app/hopper/LHR/PEK](https://rsshub.app/hopper/LHR/PEK) +This route returns a list of flight deals (in most cases, 6 flight deals) for a period defined by Hopper's algorithm, which means the travel date will be totally random (could be tomorrow or 10 months from now). -Route: `/hopper/:from/:to?` +Eg: London Heathrow Airport ✈ Beijing Capital International Airport [https://rsshub.app/hopper/1/LHR/PEK](https://rsshub.app/hopper/1/LHR/PEK) + +Route: `/hopper/:lowestOnly/:from/:to?` Parameters: +- lowestOnly, set to `1` will return the cheapest deal only, instead of all deals, so you don't get spammed + - from, origin airport IATA code - to, destination airport IATA code, optional, if unset the destination will be set to `anywhere` diff --git a/router.js b/router.js index 473998afcc..60477da590 100755 --- a/router.js +++ b/router.js @@ -422,6 +422,6 @@ router.get('/appstore/update/:country/:id', require('./routes/appstore/update')) router.get('/appstore/price/:country/:type/:id', require('./routes/appstore/price')); // Hopper -router.get('/hopper/:from/:to?', require('./routes/hopper/index')); +router.get('/hopper/:lowestOnly/:from/:to?', require('./routes/hopper/index')); module.exports = router; diff --git a/routes/hopper/index.js b/routes/hopper/index.js index bdd55a14cf..a28132dfe5 100644 --- a/routes/hopper/index.js +++ b/routes/hopper/index.js @@ -3,8 +3,10 @@ const config = require('../../config'); const cheerio = require('cheerio'); module.exports = async (ctx) => { - const from = ctx.params.from; - const to = ctx.params.to || 'Anywhere'; + const from = ctx.params.from.toUpperCase(); + const to = ctx.params.to !== undefined ? ctx.params.to.toUpperCase() : 'Anywhere'; + + const type = ctx.params.lowestOnly; const url = `https://www.hopper.com/deals/best/from/${from}/to/${to}`; const title = `Hopper - Flights From ${from} to ${to}`; @@ -23,39 +25,38 @@ module.exports = async (ctx) => { const $ = cheerio.load(response.data); const list = $('div.prices li a'); + const items = []; + + if (type === '1') { + let lowest = 99999; + let lowIndex = 0; + + list.map((i, e) => { + const current = parseInt( + $(e) + .find('.price') + .text() + .replace(/\D/g, '') + ); + + if (current < lowest) { + lowest = current; + lowIndex = i; + } + }); + + items.push(formatDesc($(list[lowIndex]))); + } else { + list.map((i, e) => { + items.push(formatDesc($(e))); + }); + } ctx.state.data = { title, link: url, description: title, - item: list - .map((i, e) => { - const item = $(e).attr('href'); - let reg = new RegExp('destination=(.*?)&', 'g'); - const destination = reg.exec(item)[1]; - reg = new RegExp('origin=(.*?)&', 'g'); - const origin = reg.exec(item)[1]; - reg = new RegExp('departureDate=(.*?)&', 'g'); - const departureDate = reg.exec(item)[1]; - reg = new RegExp('returnDate=(.*?)&', 'g'); - const returnDate = reg.exec(item)[1]; - - const price = $(list[i]) - .find('.price') - .text(); - - const title = `${origin} ✈ ${destination} ${getMonthYear(departureDate)} for ${price}`; - - const description = `
FromToPrice
- ${origin}${destination}${price}
${formatDate(departureDate)} ✈ ${formatDate(returnDate)}`; - return { - title, - description, - guid: item, - link: item, - }; - }) - .get(), + item: items, }; }; @@ -75,3 +76,28 @@ function getMonthYear(v) { year: 'numeric', }); } + +function formatDesc(e) { + const item = e.attr('href'); + let reg = new RegExp('destination=(.*?)&', 'g'); + const destination = reg.exec(item)[1]; + reg = new RegExp('origin=(.*?)&', 'g'); + const origin = reg.exec(item)[1]; + reg = new RegExp('departureDate=(.*?)&', 'g'); + const departureDate = reg.exec(item)[1]; + reg = new RegExp('returnDate=(.*?)&', 'g'); + const returnDate = reg.exec(item)[1]; + + const price = e.find('.price').text(); + + const title = `${origin} ✈ ${destination} ${getMonthYear(departureDate)} for ${price}`; + + const description = `
FromToPrice
+ ${origin}${destination}${price}
${formatDate(departureDate)} ✈ ${formatDate(returnDate)}`; + return { + title, + description, + guid: item, + link: item, + }; +}