-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathbuilderApps.js
More file actions
365 lines (302 loc) · 8.9 KB
/
Copy pathbuilderApps.js
File metadata and controls
365 lines (302 loc) · 8.9 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/**
* builderApps.js contains the apps that will be used in the 80appBuilder on the new 80legs Portal.
* These apps are programatically added to the base EightyApp (EightyApp.js) and can be called from it.
* While these apps can be called by users as well, we should not advertise this as they are far from being
* 100% polished.
*
* Tests for these apps reside in test/builderApps.test.js
*/
const urlLib = require('url')
const $ = require('cheerio')
/**
* getExternal for use in the portals 80appBuilder
* Gets all of the links for domains outside of the domain we
* are currently crawling.
*/
const getExternal = function (links, $html, url) {
if (!url) {
return links;
}
// Use a set so we don't duplicate any links
let externalLinks = new Set()
// Regex to eliminate any subdomains
const subDomainRegex = new RegExp(/^\w+./g)
// Get the host, remove any Subdomain
let host;
try {
host = urlLib.parse(url).host
} catch (e) {
return links;
}
if (host.split('.').length > 2) {
host = host.replace(subDomainRegex, '')
}
// Get each link on the page
$html.find('a').each((i, link) => {
const href = $(link).attr('href')
let linkHost
try {
linkHost = urlLib.parse(href).host
} catch (e) {
return; // Do nothing with this href
}
// Remove any subdomain
if (typeof linkHost === 'string' && linkHost && linkHost.split('.').length > 2) {
linkHost = linkHost.replace(subDomainRegex, '')
}
// If the host doesn't match the host of the url we are crawling,
// we add it to the externalLinks array
if (linkHost && (linkHost !== host)) {
externalLinks.add(href)
}
})
// Convert the set of links to an array so it's easier to work with
externalLinks = Array.from(externalLinks)
if (externalLinks.length > 0) {
links.push(...externalLinks)
}
return links
}
/*
* getFullTextContent for use in the 80appBuilder
* Returns the full text content of a webpage, if any.
*/
const getFullTextContent = function (resultsObject, $html) {
let fullTextContent = $html.find('body').text()
if (fullTextContent === null) fullTextContent = $html.filter('body').text()
if (fullTextContent) {
resultsObject['fullTextContent'] = fullTextContent
}
return fullTextContent
}
/*
* getPageTitle for use in the 80appBuilder
* Gets the title of the webpage (if any)
*/
const getPageTitle = function (resultsObject, $html) {
let pageTitle = $html.find('title').text()
if (pageTitle === null) pageTitle = $html.filter('title').text()
if (pageTitle) {
resultsObject['pageTitle'] = pageTitle
}
return pageTitle
}
/*
* getMetaDescription for use in the 80appBuilder
* Gets the content of meta description tag
*/
const getMetaDescription = function (resultsObject, $html) {
let metaDescription = $html.find('meta[name="description"]').attr('content')
if (metaDescription === null) metaDescription = $html.filter('meta[name="description"]').attr('content')
if (metaDescription) {
resultsObject['metaDescription'] = metaDescription
}
return metaDescription
}
/**
* fullPageContent for use in the 80legs Portal App Builder
* Adds the full page content to the results object
*/
const fullPageContent = function (resultsObject, $html) {
const content = new String($html).toString()
if (content) {
resultsObject['fullPageContent'] = content
}
return content
}
/**
* crawlInternalLinks for use in the 80legs Portal App Builder
* Grabs all 'a' tags, and checks the host for each url. If the host
* matches the host of the url we are crawling, we collect it
*/
const crawlInternal = function (links, $html, url) {
if (!url) {
return links;
}
// Use a set so we don't duplicate any links
let internalLinks = new Set()
// Regex to eliminate any subdomains
const subDomainRegex = new RegExp(/^\w+./g)
// Get the host, remove any Subdomain
let host;
try {
host = urlLib.parse(url).host
} catch (e) {
return links;
}
if (host.split('.').length > 2) {
host = host.replace(subDomainRegex, '')
}
// Get each link on the page
$html.find('a').each((i, link) => {
const href = $(link).attr('href')
let linkHost
try {
linkHost = urlLib.parse(href).host
} catch (e) {
return; // Do nothing with this href
}
// Remove any subdomain
if (typeof linkHost === 'string' && linkHost && linkHost.split('.').length > 2) {
linkHost = linkHost.replace(subDomainRegex, '')
}
// If the host doesn't match the host of the url we are crawling,
// we add it to the internalLinks array
if (linkHost && (linkHost === host)) {
internalLinks.add(href)
}
})
// Convert the set of links to an array so it's easier to work with
internalLinks = Array.from(internalLinks)
if (internalLinks.length > 0) {
links.push(...internalLinks)
}
return links
}
/**
* keywordCount for use in the 80legs Portal App Builder
* Gets the entire text of a page, ignoring <script>, <style>, and <link> tags.
* Splits the text on whitespace, and adds up how many time any given words appear.
*/
const keywordCount = function (resultsObject, $html) {
const keywordCount = {}
const content = $('script, link, style', $html).remove().end().text()
content.split(/\s+/).forEach(keyword => {
if (keywordCount[keyword]) {
keywordCount[keyword] = keywordCount[keyword] + 1
} else {
keywordCount[keyword] = 1
}
})
resultsObject['keywordCount'] = keywordCount
return keywordCount
}
/**
* @param {Object} resultsObject
* @param {jQueryObject} $html
* @param {string} url
* Collects all the urls inside all img (img80)tags
*/
const collectImages = (resultsObject, $html, url) => {
let images = []
$html.find('img80').each((i, obj) => {
let url = $(obj).attr('src')
images.push(url)
})
resultsObject.images = images
return images
}
/**
* @param {Object} object
* @param {jQueryObject} $html
* @param {string} url
* Collects all the pdfs inside the main document
*/
const collectPDFs = (object, $html, url) => {
let pdfs = []
$html.find('a[href$=".pdf"]').each((i, obj) => {
let url = $(obj).attr('href')
pdfs.push(url)
})
if (pdfs.length > 1) {
object.PDFs = pdfs
}
return pdfs
}
/**
* @param {Object} object
* @param {jQueryObject} $html
* @param {string} url
* Collects all the documents inside the main document
*/
const collectDocs = (object, $html, url) => {
const extensions = ['doc', 'docx', 'csv', 'xls', 'xlsx', 'ppt', 'pdf', 'pptx', 'md', 'txt', 'odf', 'tex', 'dvi']
let documents = []
extensions.forEach(e => {
$html.find(`a[href$=".${e}"]`).each((i, obj) => {
let url = $(obj).attr('href')
documents.push(url)
})
})
if (documents.length > 1) {
object.documents = documents
}
return documents
}
/**
* @param {Object} resultsObject
* @param {jQueryObject} $html
* @param {string} url
* Gets all the emails on the page by a regular expression
* if the returned array is not null and has at least 1 result
* it gets inserted on the resultObject
*/
const getEmailAddresses = (resultsObject, $html, url) => {
let emails = $html.text().match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi)
if (emails && emails.length > 0) {
resultsObject.emails = emails
}
return emails
}
/**
* @param {Object} resultsObject
* @param {jQueryObject} $html
* @param {string} url
* Get all the telephone numbers
* Additional formats can be added
*/
const getPhoneNumbers = (object, $html, url) => {
let formats = '(999)999-9999|999-999-9999|9999999999|9999-9999'
let regex = RegExp('(' +
formats
.replace(/([\(\)])/g, '\\$1')
.replace(/9/g, '\\d') +
')', 'g')
let phones = $html.text().match(regex)
if (phones && phones.length > 0) {
object.phoneNumbers = phones
}
return phones
}
/**
*
* @param {Object} resultsObject
* @param {jQueryObject} $html
* @param {string} url
* Looks for tracking information software
* currently only checks for Google Analytics
*/
const getTrackingInfo = (object, $html, url) => {
const trackingDictionary = {
'ga_action': 'Google Analytics',
'googleanalytics': 'Google Analytics',
'googleanalytics_get_script': 'Google Analytics'
}
const presentTrackingInfo = new Set()
Object.keys(trackingDictionary).forEach(e => {
$html.find('script').each((i, obj) => {
if ($(obj).text().match(e)) {
presentTrackingInfo.add(trackingDictionary[e])
}
})
})
if (presentTrackingInfo.size > 1) {
object.trackingInfo = [...presentTrackingInfo]
}
return [...presentTrackingInfo]
}
module.exports = {
getFullTextContent,
getMetaDescription,
getEmailAddresses,
getPhoneNumbers,
getTrackingInfo,
fullPageContent,
crawlInternal,
collectImages,
getPageTitle,
collectPDFs,
collectDocs,
getExternal,
keywordCount
}