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 = `
| From | To | Price |
|---|
|
- ${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 = `| From | To | Price |
|---|
|
+ ${origin} | ${destination} | ${price} |
${formatDate(departureDate)} ✈ ${formatDate(returnDate)}`;
+ return {
+ title,
+ description,
+ guid: item,
+ link: item,
+ };
+}