diff --git a/docs/README.md b/docs/README.md index 3095f2e95..aa9baffa6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1825,3 +1825,19 @@ nearby: 可选 0 或 1,默认 0 为不包括,是否包括临近机场 1. 简单模式,例如「data visualization」,[https://rsshub.app/google/scholar/data+visualization](https://rsshub.app/google/scholar/data+visualization)。 2. 高级模式,前往 [Google Scholar](https://scholar.google.com/schhp?hl=zh-cn&as_sdt=0,5),点击左上角,选择高级搜索并提交查询。此时 URL 应为:[https://scholar.google.com/scholar?as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=zh-CN&as_sdt=0%2C5](https://scholar.google.com/scholar?as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=zh-CN&as_sdt=0%2C5),复制`https://scholar.google.com/scholar?`后的所有语句作为本路由的查询参数。例子所对应的完整路由为[https://rsshub.app/google/scholar/as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=zh-CN&as_sdt=0%2C5](https://rsshub.app/google/scholar/as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=zh-CN&as_sdt=0%2C5)。 + +## Hopper Flight Deals + +### Hopper 特价机票 + +举例: 伦敦希思罗 ✈ 北京首都国际 [https://rsshub.app/hopper/LHR/PEK](https://rsshub.app/hopper/LHR/PEK) + +路由: `/hopper/:from/:to?` + +参数: + +from: 始发地,IATA 国际航空运输协会机场代码 + +to: 目的地,IATA 国际航空运输协会机场代码,可选,缺省则目的地为`任意城市` + +IATA 国际航空运输协会机场代码,参见[维基百科 国际航空运输协会机场代码]() diff --git a/docs/en/README.md b/docs/en/README.md index 3839c1a47..24d2c958a 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -308,8 +308,11 @@ Eg: [https://rsshub.app/github/issue/DIYgod/RSSHub](https://rsshub.app/github/is Route: `/github/issue/:user/:repo` -Parameters: user, username -Parameters: repo, repo name +Parameters: + +user, username + +repo, repo name ## EZTV @@ -366,7 +369,7 @@ locations: the departing city, consists of an 「ISO 3166-1 country code」 and 1. Origin's ISO 3166-1 country code + city name, eg. `us+new york`, [https://rsshub.app/atfd/us+new york](https://rsshub.app/atfd/us+new%20york) 2. Multiple origins are support via a comma separated string, eg. `us+new york,gb+london`, [https://rsshub.app/atfd/us+new york,gb+london/](https://rsshub.app/atfd/us+new%20york,gb+london/) -For ISO 3166-1 country codes please refer to [wikipedia ISO_3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) +For ISO 3166-1 country codes please refer to [Wikipedia ISO_3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) nearby: whether includes nearby airports, optional value of 0 or 1, default to 0 (exclude nearby airports) @@ -389,3 +392,19 @@ Parameters: query, query statement which supports「Basic」and「Advanced」mod 1. Basic mode, sample query is the keywords desired, eg.「data visualization」, [https://rsshub.app/google/scholar/data+visualization](https://rsshub.app/google/scholar/data+visualization). 2. Advanced mode, visit [Google Scholar](https://scholar.google.com/schhp?hl=en&as_sdt=0,5), click the top left corner and select「Advanced Search」, fill in your conditions and submit the search. The URL should look like this: [https://scholar.google.com/scholar?as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5](https://scholar.google.com/scholar?as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5), copy everything after `https://scholar.google.com/scholar?` from the URL and use it as the query for this route. The complete URL for the above example should look like this: [https://rsshub.app/google/scholar/as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5](https://rsshub.app/google/scholar/as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5). + +## Hopper + +### Hopper Flight Deals + +Eg: London Heathrow Airport ✈ Beijing Capital International Airport [https://rsshub.app/hopper/LHR/PEK](https://rsshub.app/hopper/LHR/PEK) + +Route: `/hopper/:from/:to?` + +Parameters: + +from, origin airport IATA code + +to, destination airport IATA code, optional, if unset the destination will be set to `anywhere` + +For airport IATA code please refer to [Wikipedia List of airports by IATA code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_code:_A) diff --git a/router.js b/router.js index 0c0c013d9..9a821e86a 100755 --- a/router.js +++ b/router.js @@ -414,4 +414,7 @@ router.get('/fir/update/:id', require('./routes/fir/update')); // Google router.get('/google/scholar/:query', require('./routes/google/scholar')); +// Hopper +router.get('/hopper/:from/:to?', require('./routes/hopper/index')); + module.exports = router; diff --git a/routes/hopper/index.js b/routes/hopper/index.js new file mode 100644 index 000000000..bdd55a14c --- /dev/null +++ b/routes/hopper/index.js @@ -0,0 +1,77 @@ +const axios = require('../../utils/axios'); +const config = require('../../config'); +const cheerio = require('cheerio'); + +module.exports = async (ctx) => { + const from = ctx.params.from; + const to = ctx.params.to || 'Anywhere'; + + const url = `https://www.hopper.com/deals/best/from/${from}/to/${to}`; + const title = `Hopper - Flights From ${from} to ${to}`; + + const response = await axios({ + method: 'get', + url, + headers: { + 'User-Agent': config.ua, + }, + params: { + pid: 'website', + c: 'flexweb', + }, + }); + + const $ = cheerio.load(response.data); + const list = $('div.prices li a'); + + 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(), + }; +}; + +function formatDate(v) { + return new Intl.DateTimeFormat('en-gb', { + year: 'numeric', + weekday: 'short', + month: 'short', + day: 'numeric', + }).format(new Date(v)); +} + +function getMonthYear(v) { + const date = new Date(v); + return date.toLocaleString('en-gb', { + month: 'long', + year: 'numeric', + }); +}