-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
128 lines (112 loc) · 2.78 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
const cheerio = require('cheerio')
const superagent = require('superagent')
const BASE_URL =
'https://www.faa.gov/air_traffic/flight_info/aeronav/digital_products/dtpp/search/'
// For some reason the server takes forever to respond without this request header
const ACCEPT = 'text/html'
/**
* A shortcut to the list() method
*/
const terminalProcedures = (module.exports = (icaos, options = {}) => {
return terminalProcedures.list(icaos, options)
})
/**
* Main fetching method; accepts one or more ICAO codes
*/
terminalProcedures.list = (icaos, options = {}) => {
if (Array.isArray(icaos)) {
return Promise.all(icaos.map(listOne))
}
return listOne(icaos)
}
/**
* Fetch the current diagrams distribution cycle numbers (.e.g, 1813)
*/
const fetchCurrentCycle = (terminalProcedures.fetchCurrentCycle = async () => {
const response = await superagent
.get(BASE_URL)
.set('Accept', ACCEPT)
const $ = cheerio.load(response.text)
return $('select#cycle > option:contains(Current)').val()
})
/**
* Using the current cycle, fetch the terminal procedures for a single ICAO code
*/
const listOne = async icao => {
const searchCycle = await fetchCurrentCycle()
let procedures = []
let lastPageFetched = 0
let lastNumFetched = 1
while (lastNumFetched > 0) {
const page = await superagent
.get(
`${BASE_URL}/results/?cycle=${searchCycle}&ident=${icao}&sort=type&dir=asc&page=${lastPageFetched +
1}`
)
.set('Accept', ACCEPT)
.then(res => parse(res.text))
if (page) {
lastNumFetched = page.length
lastPageFetched += 1
procedures = procedures.concat(page)
} else {
break
}
}
return procedures
}
/**
* Parsing helper methods
*/
const text = ($row, columnIndex) =>
$row
.find(`td:nth-child(${columnIndex})`)
.text()
.trim()
const link = ($row, columnIndex) =>
$row
.find(`td:nth-child(${columnIndex})`)
.find('a')
.attr('href')
const extractRow = $row => {
const type = text($row, 7)
if (!type) {
return null
}
return {
state: text($row, 1),
city: text($row, 2),
airport: text($row, 3),
ident: text($row, 4),
vol: text($row, 5),
flag: text($row, 6),
type,
procedure: {
name: text($row, 8),
url: link($row, 8)
},
compare: {
name: text($row, 9),
url: link($row, 9)
}
}
}
/**
* Parse the response HTML into JSON
*/
const parse = html => {
const $ = cheerio.load(html)
const $resultsTable = $('#resultsTable')
if (!$resultsTable.html()) {
console.error('Unable to parse the #resultsTable page element')
return null
}
const results = $resultsTable
.find('tr')
.toArray()
.map(row => extractRow($(row)))
.filter(x => !!x)
if (results.length > 0) {
return results
}
}