File lib/Case.class.js added (mode: 100644) (index 0000000..6f1b438) |
|
1 |
|
/* |
|
2 |
|
This file is part of datajams-evictions. |
|
3 |
|
|
|
4 |
|
datajams-evictions is free software: you can redistribute it and/or modify |
|
5 |
|
it under the terms of the GNU Affero General Public License as published by |
|
6 |
|
the Free Software Foundation, either version 3 of the License, or |
|
7 |
|
(at your option) any later version. |
|
8 |
|
|
|
9 |
|
datajams-evictions is distributed in the hope that it will be useful, |
|
10 |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
|
GNU Affero General Public License for more details. |
|
13 |
|
|
|
14 |
|
You should have received a copy of the GNU Affero General Public License |
|
15 |
|
along with datajams-evictions. If not, see <https://www.gnu.org/licenses/>. |
|
16 |
|
|
|
17 |
|
Copyright 2020 Luigi Bai |
|
18 |
|
*/ |
|
19 |
|
const https = require("https"); |
|
20 |
|
const httpOpts = require("./httpOptions"); |
|
21 |
|
const HTTPConversation = require("./HTTPConversation.class"); |
|
22 |
|
const util = require("util"); |
|
23 |
|
const hp = require("htmlparser2"); |
|
24 |
|
const DomHandler = require("domhandler").DomHandler; |
|
25 |
|
const du = require("domutils"); |
|
26 |
|
const DAO = require("./sqlDAO"); |
|
27 |
|
const OdysseyInfo = require("./OdysseyInfo.class"); |
|
28 |
|
|
|
29 |
|
// --------------------------------------------------- |
|
30 |
|
// Some helper functions: |
|
31 |
|
function checkPreviousSiblings(node, test) { |
|
32 |
|
let ret = false; |
|
33 |
|
|
|
34 |
|
if (null !== node) { |
|
35 |
|
if (null !== node.prev) { |
|
36 |
|
let prev = node.prev; |
|
37 |
|
ret = test(prev) || checkPreviousSiblings(prev, test); |
|
38 |
|
} |
|
39 |
|
} |
|
40 |
|
|
|
41 |
|
return ret; |
|
42 |
|
} |
|
43 |
|
|
|
44 |
|
function findAndSet(label, member, nodeSet, obj) { |
|
45 |
|
// Should work for any label and member: |
|
46 |
|
let cnNode = du.filter((node) => { |
|
47 |
|
return checkPreviousSiblings(node, (testNode) => { |
|
48 |
|
if ("span" === testNode.name && "label" === testNode.attribs.class) { |
|
49 |
|
return ("text" === testNode.children[0].type && label === testNode.children[0].data); |
|
50 |
|
} else { |
|
51 |
|
return false; |
|
52 |
|
} |
|
53 |
|
}); |
|
54 |
|
}, nodeSet); |
|
55 |
|
|
|
56 |
|
if (0 === cnNode.length) { |
|
57 |
|
// ERROR |
|
58 |
|
console.error("Warning, no value found: ", label, member, obj); |
|
59 |
|
} else { |
|
60 |
|
if (0 === cnNode[0].children.length) { |
|
61 |
|
// ERROR |
|
62 |
|
console.error("Warning, no value found: ", label, member, obj); |
|
63 |
|
} else { |
|
64 |
|
obj[member] = cnNode[0].children[0].data.trim(); |
|
65 |
|
} |
|
66 |
|
} |
|
67 |
|
} |
|
68 |
|
|
|
69 |
|
function getCaseInfo(nodes, obj) { |
|
70 |
|
let ci = du.filter((node) => { |
|
71 |
|
return ("div" === node.name && "caseInfo" === node.attribs.id); |
|
72 |
|
}, nodes); |
|
73 |
|
|
|
74 |
|
// get all the labels: |
|
75 |
|
let labels = du.filter((node) => { |
|
76 |
|
return ("span" === node.name && "value" === node.attribs.class); |
|
77 |
|
}, ci); |
|
78 |
|
|
|
79 |
|
findAndSet("Case Number:", "caseNumber", labels, obj); |
|
80 |
|
findAndSet("Filed Date:", "filedDate", labels, obj); |
|
81 |
|
findAndSet("Case Status:", "caseStatus", labels, obj); |
|
82 |
|
}; |
|
83 |
|
|
|
84 |
|
function getCivilInfo(nodes, obj) { |
|
85 |
|
let ci = du.filter((node) => { |
|
86 |
|
return ("div" === node.name && "civilInfo" === node.attribs.id); |
|
87 |
|
}, nodes); |
|
88 |
|
|
|
89 |
|
if (0 === ci.length) { |
|
90 |
|
// Odd. This should be a civil claim, and yet there's no civil info. |
|
91 |
|
// May have found a criminal offense? |
|
92 |
|
} else { |
|
93 |
|
|
|
94 |
|
// get all the labels: |
|
95 |
|
let labels = du.filter((node) => { |
|
96 |
|
return ("span" === node.name && "value" === node.attribs.class); |
|
97 |
|
}, ci); |
|
98 |
|
|
|
99 |
|
// findAndSet("Nature of Claim:", "natureOfClaim", labels, obj); |
|
100 |
|
findAndSet("Claim Amount:", "claimAmount", labels, obj); |
|
101 |
|
} |
|
102 |
|
}; |
|
103 |
|
|
|
104 |
|
function getHearingInfo(nodes, obj) { |
|
105 |
|
let ci = du.filter((node) => { |
|
106 |
|
return ("div" === node.name && "eventInfo" === node.attribs.id); |
|
107 |
|
}, nodes); |
|
108 |
|
|
|
109 |
|
// get all the hearings: |
|
110 |
|
let hearings = du.filter((node) => { |
|
111 |
|
return ("div" === node.name && ("odd" === node.attribs.class || "even" === node.attribs.class)); |
|
112 |
|
}, ci); |
|
113 |
|
|
|
114 |
|
let hearingList = []; |
|
115 |
|
|
|
116 |
|
hearings.forEach((hearingDiv) => { |
|
117 |
|
let hearing = {}; |
|
118 |
|
|
|
119 |
|
// get all the labels: |
|
120 |
|
let labels = du.filter((node) => { |
|
121 |
|
return ("span" === node.name && "value" === node.attribs.class); |
|
122 |
|
}, hearingDiv); |
|
123 |
|
|
|
124 |
|
findAndSet("Hearing Description:", "hearingDescription", labels, hearing); |
|
125 |
|
findAndSet("Hearing Date/Time:", "hearingDateTime", labels, hearing); |
|
126 |
|
findAndSet("Hearing Result/Cancellation:", "hearingResult", labels, hearing); |
|
127 |
|
findAndSet("Hearing Result/Cancellation Date:", "hearingResultDateTime", labels, hearing); |
|
128 |
|
|
|
129 |
|
hearingList.push(hearing); |
|
130 |
|
}); |
|
131 |
|
|
|
132 |
|
obj.hearings = hearingList; |
|
133 |
|
}; |
|
134 |
|
|
|
135 |
|
function getPartyInfo(nodes, obj) { |
|
136 |
|
let ci = du.filter((node) => { |
|
137 |
|
return ("div" === node.name && "partyInfo" === node.attribs.id); |
|
138 |
|
}, nodes); |
|
139 |
|
|
|
140 |
|
// get all the parties: |
|
141 |
|
let parties = du.filter((node) => { |
|
142 |
|
return ("div" === node.name && ("odd" === node.attribs.class || "even" === node.attribs.class)); |
|
143 |
|
}, ci); |
|
144 |
|
|
|
145 |
|
let partyList = []; |
|
146 |
|
|
|
147 |
|
// For each party: |
|
148 |
|
parties.forEach((partyDiv) => { |
|
149 |
|
let party = {}; |
|
150 |
|
|
|
151 |
|
// get all the labels: |
|
152 |
|
let labels = du.filter((node) => { |
|
153 |
|
return ("span" === node.name && "value" === node.attribs.class); |
|
154 |
|
}, partyDiv); |
|
155 |
|
|
|
156 |
|
findAndSet("Party Name:", "name", labels, party); |
|
157 |
|
findAndSet("Party Type:", "role", labels, party); |
|
158 |
|
|
|
159 |
|
partyList.push(party); |
|
160 |
|
}); |
|
161 |
|
|
|
162 |
|
obj.parties = partyList; |
|
163 |
|
}; |
|
164 |
|
|
|
165 |
|
function getEventInfo(nodes, obj) { |
|
166 |
|
let ci = du.filter((node) => { |
|
167 |
|
return ("div" === node.name && "filingInfo" === node.attribs.id); |
|
168 |
|
}, nodes); |
|
169 |
|
|
|
170 |
|
// get all the parties: |
|
171 |
|
let events = du.filter((node) => { |
|
172 |
|
return ("div" === node.name && ("odd" === node.attribs.class || "even" === node.attribs.class)); |
|
173 |
|
}, ci); |
|
174 |
|
|
|
175 |
|
let eventList = []; |
|
176 |
|
|
|
177 |
|
// For each party: |
|
178 |
|
events.forEach((partyDiv) => { |
|
179 |
|
let party = {}; |
|
180 |
|
|
|
181 |
|
// get all the labels: |
|
182 |
|
let labels = du.filter((node) => { |
|
183 |
|
return ("span" === node.name && "value" === node.attribs.class); |
|
184 |
|
}, partyDiv); |
|
185 |
|
|
|
186 |
|
findAndSet("Event Description:", "description", labels, party); |
|
187 |
|
findAndSet("Date Added:", "dateAdded", labels, party); |
|
188 |
|
|
|
189 |
|
eventList.push(party); |
|
190 |
|
}); |
|
191 |
|
|
|
192 |
|
obj.events = eventList; |
|
193 |
|
}; |
|
194 |
|
// --------------------------------------------------- |
|
195 |
|
|
|
196 |
|
// Expose just the class: |
|
197 |
|
class Case { |
|
198 |
|
constructor(docket, url) { |
|
199 |
|
this.docket = docket; |
|
200 |
|
|
|
201 |
|
if (!url) { |
|
202 |
|
throw new Error("Must include a URL."); |
|
203 |
|
} |
|
204 |
|
this.URL = url; |
|
205 |
|
}; |
|
206 |
|
|
|
207 |
|
static sqlAllCases(database, rowCB, completeCB) { |
|
208 |
|
database.each( |
|
209 |
|
"SELECT case_URL from cases", |
|
210 |
|
(err, row) => { |
|
211 |
|
if (err) { |
|
212 |
|
console.error("Case.sqlAllCases", err); |
|
213 |
|
} else { |
|
214 |
|
rowCB && "function" === typeof rowCB && rowCB(new Case(null, row["case_URL"])); |
|
215 |
|
} |
|
216 |
|
}, |
|
217 |
|
completeCB |
|
218 |
|
); |
|
219 |
|
}; |
|
220 |
|
|
|
221 |
|
static findCasesByBusiness(name, cb) { |
|
222 |
|
let hc = new HTTPConversation({ |
|
223 |
|
debug: false |
|
224 |
|
}); |
|
225 |
|
|
|
226 |
|
let host = "jpwebsite.harriscountytx.gov"; |
|
227 |
|
|
|
228 |
|
let handler = new DomHandler((err, dom) => { |
|
229 |
|
if (err) { |
|
230 |
|
console.error("Cases.findCasesByBusiness", err); |
|
231 |
|
} else { |
|
232 |
|
if (cb && "function" === typeof cb) { |
|
233 |
|
// Find the containing node: |
|
234 |
|
let caseTable = du.filter((node) => { |
|
235 |
|
return ("table" === node.name && "cases" === node.attribs.id); |
|
236 |
|
}, dom); |
|
237 |
|
|
|
238 |
|
// Now get the table rows: |
|
239 |
|
let caseRows = du.filter((node) => { |
|
240 |
|
return ("tr" === node.name); |
|
241 |
|
}, caseTable); |
|
242 |
|
|
|
243 |
|
// For each of the rows, grab the case URL: |
|
244 |
|
let caseHrefs = du.filter((node) => { |
|
245 |
|
return ("a" === node.name && "td" === node.parent.name && 'text' === node.parent.prev.type && null === node.parent.prev.prev); |
|
246 |
|
}, caseRows); |
|
247 |
|
|
|
248 |
|
let cases = caseHrefs.map((aNode) => { return "https://"+host+aNode.attribs.href; }); |
|
249 |
|
cb(cases); |
|
250 |
|
} |
|
251 |
|
}; |
|
252 |
|
}, { |
|
253 |
|
normalizeWhitespace: true |
|
254 |
|
}); |
|
255 |
|
|
|
256 |
|
// First, get "https://jpwebsite.harriscountytx.gov/FindMyCase/search.jsp" |
|
257 |
|
hc.getURL({ |
|
258 |
|
reqPath: "/FindMyCase/search.jsp", |
|
259 |
|
reqHost: host, |
|
260 |
|
callback: (doc) => { |
|
261 |
|
// Next, post the search: |
|
262 |
|
hc.postURLData({ |
|
263 |
|
reqPath: "/FindMyCase/SearchForMyCases", |
|
264 |
|
reqHost: host, |
|
265 |
|
data: { |
|
266 |
|
"case": "", |
|
267 |
|
"dlNumber": "", |
|
268 |
|
"lastName": "", |
|
269 |
|
"firstName": "", |
|
270 |
|
"dob": "", |
|
271 |
|
"businessName": name, |
|
272 |
|
"court": "00", |
|
273 |
|
"criteria": "businessNameSearch" |
|
274 |
|
}, |
|
275 |
|
parser: new hp.Parser(handler) |
|
276 |
|
}); |
|
277 |
|
} |
|
278 |
|
}); |
|
279 |
|
|
|
280 |
|
}; |
|
281 |
|
|
|
282 |
|
loadCaseFromURL() { |
|
283 |
|
return new Promise((resFN, rejFN) => { |
|
284 |
|
let opts = new httpOpts(); |
|
285 |
|
|
|
286 |
|
// With the data all set up, now connect and parse: |
|
287 |
|
let req = https.request(this.URL, opts, (res) => { |
|
288 |
|
let handler = new DomHandler((err, dom) => { |
|
289 |
|
if (err) { |
|
290 |
|
rejFN({ url: this.URL, err: err }); |
|
291 |
|
} else { |
|
292 |
|
// Find the containing node: |
|
293 |
|
let dataDiv = du.filter((node) => { |
|
294 |
|
return ("div" === node.name && "contentWithNavBar" === node.attribs.class); |
|
295 |
|
}, dom); |
|
296 |
|
|
|
297 |
|
// Get the civil info: |
|
298 |
|
getCaseInfo(dataDiv, this); |
|
299 |
|
getCivilInfo(dataDiv, this); |
|
300 |
|
getPartyInfo(dataDiv, this); |
|
301 |
|
getHearingInfo(dataDiv, this); |
|
302 |
|
getEventInfo(dataDiv, this); |
|
303 |
|
|
|
304 |
|
resFN && "function" === typeof resFN && resFN(this); |
|
305 |
|
}; |
|
306 |
|
}, { |
|
307 |
|
normalizeWhitespace: true |
|
308 |
|
}); |
|
309 |
|
let parser = new hp.Parser(handler); |
|
310 |
|
|
|
311 |
|
// What to do with incoming data: |
|
312 |
|
res.on("data", (d) => { |
|
313 |
|
parser.parseChunk(d); |
|
314 |
|
}).on("end", () => { |
|
315 |
|
parser.done(); |
|
316 |
|
}); |
|
317 |
|
|
|
318 |
|
}); |
|
319 |
|
|
|
320 |
|
req.on('error', e => { |
|
321 |
|
rejFN({ msg: "Load Case from URL HTTP", err: e }); |
|
322 |
|
}); |
|
323 |
|
|
|
324 |
|
// Ok GO: |
|
325 |
|
req.end(); |
|
326 |
|
}); |
|
327 |
|
}; |
|
328 |
|
|
|
329 |
|
storeSQL(database) { |
|
330 |
|
let caseObj = this; |
|
331 |
|
|
|
332 |
|
if (caseObj.docket) { |
|
333 |
|
let ctlDocket = new DAO.DocketCtlr(database); |
|
334 |
|
ctlDocket.insert({ |
|
335 |
|
$pct: caseObj.docket.court.precinct, |
|
336 |
|
$plc: caseObj.docket.court.place, |
|
337 |
|
$dt: caseObj.docket.date.substr(6)+"-"+ |
|
338 |
|
caseObj.docket.date.substr(0, 2)+"-"+ |
|
339 |
|
caseObj.docket.date.substr(3, 2)+" "+ |
|
340 |
|
caseObj.docket.time, |
|
341 |
|
$url: caseObj.docket.URL.href |
|
342 |
|
}); |
|
343 |
|
ctlDocket.finalize(); |
|
344 |
|
|
|
345 |
|
let ctlDC = new DAO.DocketCaseCtlr(database); |
|
346 |
|
ctlDC.insert({ |
|
347 |
|
$pct: caseObj.docket.court.precinct, |
|
348 |
|
$plc: caseObj.docket.court.place, |
|
349 |
|
$dt: caseObj.docket.date.substr(6)+"-"+ |
|
350 |
|
caseObj.docket.date.substr(0, 2)+"-"+ |
|
351 |
|
caseObj.docket.date.substr(3, 2)+" "+ |
|
352 |
|
caseObj.docket.time, |
|
353 |
|
$cnum: caseObj.caseNumber, |
|
354 |
|
$claim: caseObj.claimAmount |
|
355 |
|
}); |
|
356 |
|
ctlDC.finalize(); |
|
357 |
|
} |
|
358 |
|
|
|
359 |
|
let ctlCase = new DAO.CaseCtlr(database); |
|
360 |
|
ctlCase.insert({ |
|
361 |
|
$cnum: caseObj.caseNumber, |
|
362 |
|
$curl: caseObj.URL |
|
363 |
|
}); |
|
364 |
|
ctlCase.finalize(); |
|
365 |
|
|
|
366 |
|
let ctlParty = new DAO.PartyCtlr(database); |
|
367 |
|
caseObj.parties.forEach((party) => { |
|
368 |
|
ctlParty.insert({ |
|
369 |
|
$cnum: caseObj.caseNumber, |
|
370 |
|
$role: party.role, |
|
371 |
|
$name: party.name |
|
372 |
|
}); |
|
373 |
|
}); |
|
374 |
|
ctlParty.finalize(); |
|
375 |
|
|
|
376 |
|
let ctlEvent = new DAO.EventCtlr(database); |
|
377 |
|
caseObj.events.forEach((event) => { |
|
378 |
|
ctlEvent.insert({ |
|
379 |
|
$cnum: caseObj.caseNumber, |
|
380 |
|
$desc: event.description, |
|
381 |
|
$date: event.dateAdded |
|
382 |
|
}); |
|
383 |
|
}); |
|
384 |
|
ctlEvent.finalize(); |
|
385 |
|
}; |
|
386 |
|
}; |
|
387 |
|
|
|
388 |
|
module.exports = Case; |
File lib/Court.class.js added (mode: 100644) (index 0000000..743336b) |
|
1 |
|
/* |
|
2 |
|
This file is part of datajams-evictions. |
|
3 |
|
|
|
4 |
|
datajams-evictions is free software: you can redistribute it and/or modify |
|
5 |
|
it under the terms of the GNU Affero General Public License as published by |
|
6 |
|
the Free Software Foundation, either version 3 of the License, or |
|
7 |
|
(at your option) any later version. |
|
8 |
|
|
|
9 |
|
datajams-evictions is distributed in the hope that it will be useful, |
|
10 |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
|
GNU Affero General Public License for more details. |
|
13 |
|
|
|
14 |
|
You should have received a copy of the GNU Affero General Public License |
|
15 |
|
along with datajams-evictions. If not, see <https://www.gnu.org/licenses/>. |
|
16 |
|
|
|
17 |
|
Copyright 2020 Luigi Bai |
|
18 |
|
*/ |
|
19 |
|
const https = require("https"); |
|
20 |
|
const httpOpts = require("./httpOptions"); |
|
21 |
|
const util = require("util"); |
|
22 |
|
const hp = require("htmlparser2"); |
|
23 |
|
const DomHandler = require("domhandler").DomHandler; |
|
24 |
|
const du = require("domutils"); |
|
25 |
|
const Docket = require("./Docket.class"); |
|
26 |
|
|
|
27 |
|
module.exports = class Court { |
|
28 |
|
constructor(argv) { |
|
29 |
|
if (! argv) { |
|
30 |
|
throw new Error("Must have arguments."); |
|
31 |
|
} |
|
32 |
|
|
|
33 |
|
if (! argv.precinct) { |
|
34 |
|
throw new Error("Must have a precinct."); |
|
35 |
|
} |
|
36 |
|
this.precinct = argv.precinct; |
|
37 |
|
|
|
38 |
|
if (! argv.place) { |
|
39 |
|
throw new Error("Must have a place."); |
|
40 |
|
} |
|
41 |
|
this.place = argv.place; |
|
42 |
|
}; |
|
43 |
|
|
|
44 |
|
toString() { |
|
45 |
|
return JSON.stringify({ precinct: this.precinct, place: this.place }); |
|
46 |
|
}; |
|
47 |
|
|
|
48 |
|
getDockets(argv) { |
|
49 |
|
if (! argv) { |
|
50 |
|
throw new Error("Must have arguments."); |
|
51 |
|
} |
|
52 |
|
|
|
53 |
|
return new Promise((resFN, rejFN) => { |
|
54 |
|
let startDate = argv.startDate || new Date(); |
|
55 |
|
|
|
56 |
|
let endDate = argv.endDate; |
|
57 |
|
if (! argv.endDate) { |
|
58 |
|
this.endDate = new Date(this.startDate.valueOf()); |
|
59 |
|
this.endDate.setDate(this.startDate.getDate() + 6); |
|
60 |
|
} |
|
61 |
|
|
|
62 |
|
let docketType = argv.docketType || "Eviction Docket"; |
|
63 |
|
|
|
64 |
|
let params = new URLSearchParams({ |
|
65 |
|
"court": (this.precinct * 10 + this.place), |
|
66 |
|
"fdate": (startDate.getMonth() + 1)+"/"+(startDate.getDate())+"/"+(startDate.getFullYear()), |
|
67 |
|
"tdate": (endDate.getMonth() + 1)+"/"+(endDate.getDate())+"/"+(endDate.getFullYear()) |
|
68 |
|
}); |
|
69 |
|
let opts = new httpOpts(); |
|
70 |
|
let reqURL = "https://jpwebsite.harriscountytx.gov/WebDockets/GetDocketSummary?"+params.toString(); |
|
71 |
|
|
|
72 |
|
this.dockets = []; |
|
73 |
|
|
|
74 |
|
// With the data all set up, now connect and parse: |
|
75 |
|
let req = https.request(reqURL, opts, (res) => { |
|
76 |
|
let handler = new DomHandler((err, dom) => { |
|
77 |
|
if (err) { |
|
78 |
|
rejFN({ url: reqURL, err: err }); |
|
79 |
|
} else { |
|
80 |
|
let isEventDescription = (node, type) => { |
|
81 |
|
let ret = ("div" === node.name && "record" === node.attribs.class); |
|
82 |
|
if (ret && undefined !== type) { |
|
83 |
|
ret = false; |
|
84 |
|
// Dig a little deeper. node.children[x].name="span" && node.children[x].attribs.class="eventDescription" |
|
85 |
|
for (let i = 0; i < node.children.length; i++) { |
|
86 |
|
let ci = node.children[i]; |
|
87 |
|
if ("span" === ci.name && "eventDescription" === ci.attribs.class) { |
|
88 |
|
// One of the children has to be "text" and match "type": |
|
89 |
|
for (let j = 0; !ret && j < ci.children.length; j++) { |
|
90 |
|
let cj = ci.children[j]; |
|
91 |
|
ret = ("text" === cj.type && type === cj.data); |
|
92 |
|
} |
|
93 |
|
} |
|
94 |
|
} |
|
95 |
|
} |
|
96 |
|
return ret; |
|
97 |
|
}; |
|
98 |
|
|
|
99 |
|
// Find the containing node: |
|
100 |
|
let events = du.filter((node) => { |
|
101 |
|
return ("div" === node.name && "contentWithNavBar" === node.attribs.class); |
|
102 |
|
}, dom)[0]; |
|
103 |
|
|
|
104 |
|
// Ok. Need to walk the DOM object with the lousy tools I have in javascript. |
|
105 |
|
let docketPromises = []; |
|
106 |
|
for (let di = 0; di < events.children.length; di++) { |
|
107 |
|
// Looking for: node.name = "div", node.attribs.class = "record" |
|
108 |
|
let node = events.children[di]; |
|
109 |
|
if (isEventDescription(node, docketType)) { |
|
110 |
|
// Work backwards to find the div.docketTimeHeader: |
|
111 |
|
let found = false; |
|
112 |
|
for (let dj = di; !found && dj >= 0; dj--) { |
|
113 |
|
let psib = events.children[dj]; |
|
114 |
|
found = ("div" === psib.name && "docketTimeHeader" === psib.attribs.class); |
|
115 |
|
if (found) { |
|
116 |
|
// Dig out the href: |
|
117 |
|
for (let si = 0; si < psib.children.length; si++) { |
|
118 |
|
let snode = psib.children[si]; |
|
119 |
|
if ("a" === snode.name) { |
|
120 |
|
let hURL = new URL(snode.attribs.href, reqURL); |
|
121 |
|
let docket = new Docket({ |
|
122 |
|
court: this, |
|
123 |
|
URL: hURL, |
|
124 |
|
type: docketType |
|
125 |
|
}); |
|
126 |
|
this.dockets.push(docket); |
|
127 |
|
docketPromises.push(docket.getCases(argv.caseCallback)); |
|
128 |
|
} |
|
129 |
|
} |
|
130 |
|
} |
|
131 |
|
} |
|
132 |
|
} |
|
133 |
|
} |
|
134 |
|
// At this point, not much to return. Let the embedded |
|
135 |
|
// promises do their work? |
|
136 |
|
Promise.all(docketPromises) |
|
137 |
|
.then(val => { |
|
138 |
|
resFN({ |
|
139 |
|
court: this, |
|
140 |
|
dockets: val.map(v => { |
|
141 |
|
if ((v && v instanceof Array && v.length > 0) || v) |
|
142 |
|
return v; |
|
143 |
|
}) |
|
144 |
|
}); |
|
145 |
|
}) |
|
146 |
|
.catch(err => { rejFN(err); }) |
|
147 |
|
; |
|
148 |
|
}; |
|
149 |
|
}, { |
|
150 |
|
normalizeWhitespace: true |
|
151 |
|
}); |
|
152 |
|
let parser = new hp.Parser(handler); |
|
153 |
|
|
|
154 |
|
// What to do with incoming data: |
|
155 |
|
res.on("data", (d) => { |
|
156 |
|
parser.parseChunk(d); |
|
157 |
|
}).on("end", () => { |
|
158 |
|
parser.done(); |
|
159 |
|
}); |
|
160 |
|
|
|
161 |
|
}); |
|
162 |
|
|
|
163 |
|
req.on('error', (e) => { |
|
164 |
|
console.error(reqURL, e); |
|
165 |
|
}); |
|
166 |
|
|
|
167 |
|
// Ok GO: |
|
168 |
|
req.end(); |
|
169 |
|
}); |
|
170 |
|
} |
|
171 |
|
}; |
File lib/Docket.class.js added (mode: 100644) (index 0000000..90b150d) |
|
1 |
|
/* |
|
2 |
|
This file is part of datajams-evictions. |
|
3 |
|
|
|
4 |
|
datajams-evictions is free software: you can redistribute it and/or modify |
|
5 |
|
it under the terms of the GNU Affero General Public License as published by |
|
6 |
|
the Free Software Foundation, either version 3 of the License, or |
|
7 |
|
(at your option) any later version. |
|
8 |
|
|
|
9 |
|
datajams-evictions is distributed in the hope that it will be useful, |
|
10 |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
|
GNU Affero General Public License for more details. |
|
13 |
|
|
|
14 |
|
You should have received a copy of the GNU Affero General Public License |
|
15 |
|
along with datajams-evictions. If not, see <https://www.gnu.org/licenses/>. |
|
16 |
|
|
|
17 |
|
Copyright 2020 Luigi Bai |
|
18 |
|
*/ |
|
19 |
|
const https = require("https"); |
|
20 |
|
const httpOpts = require("./httpOptions"); |
|
21 |
|
const util = require("util"); |
|
22 |
|
const hp = require("htmlparser2"); |
|
23 |
|
const DomHandler = require("domhandler").DomHandler; |
|
24 |
|
const du = require("domutils"); |
|
25 |
|
const Case = require("./Case.class"); |
|
26 |
|
|
|
27 |
|
module.exports = class Docket { |
|
28 |
|
constructor(argv) { |
|
29 |
|
if (!argv.court) { |
|
30 |
|
throw new Error("Must have a parent court."); |
|
31 |
|
} |
|
32 |
|
this.court = argv.court; |
|
33 |
|
|
|
34 |
|
this.docketType = argv.docketType || "Eviction Docket"; |
|
35 |
|
|
|
36 |
|
if (!argv.URL) { |
|
37 |
|
throw new Error("Must include a URL."); |
|
38 |
|
} |
|
39 |
|
this.URL = argv.URL; |
|
40 |
|
this.date = this.URL.searchParams.get("date"); |
|
41 |
|
let oTime = this.URL.searchParams.get("time"); |
|
42 |
|
this.time = oTime.substr(0, 2) + ":" + oTime.substr(2, 2) + ":" + oTime.substr(4); |
|
43 |
|
}; |
|
44 |
|
|
|
45 |
|
toString() { |
|
46 |
|
return JSON.stringify({ |
|
47 |
|
court: this.court.toString(), |
|
48 |
|
docketType: this.docketType, |
|
49 |
|
url: this.url.toString(), |
|
50 |
|
cases: this.cases |
|
51 |
|
}); |
|
52 |
|
}; |
|
53 |
|
|
|
54 |
|
getCases(caseCallback) { |
|
55 |
|
return new Promise((resFN, rejFN) => { |
|
56 |
|
let opts = new httpOpts(); |
|
57 |
|
|
|
58 |
|
// With the data all set up, now connect and parse: |
|
59 |
|
let req = https.request(this.URL, opts, (res) => { |
|
60 |
|
let handler = new DomHandler((err, dom) => { |
|
61 |
|
if (err) { |
|
62 |
|
rejFN({ url: this.URL, err: err }); |
|
63 |
|
} else { |
|
64 |
|
// Find the containing node: |
|
65 |
|
let events = du.filter((node) => { |
|
66 |
|
return ("div" === node.name && "contentWithNavBar" === node.attribs.class); |
|
67 |
|
}, dom); |
|
68 |
|
// Then get the div.caseDetails nodes within them: |
|
69 |
|
let cases = du.filter((node) => { |
|
70 |
|
return ("div" === node.name && "caseDetails" === node.attribs.class); |
|
71 |
|
}, events); |
|
72 |
|
// From those, get the span.emphasis nodes: |
|
73 |
|
let urlSpans = du.filter((node) => { |
|
74 |
|
if ("span" === node.name && "emphasis" === node.attribs.class) { |
|
75 |
|
// Check the siblings to make sure they're the event type we want: |
|
76 |
|
let sibList = du.filter((sib) => { |
|
77 |
|
return ("text" === sib.type && sib.data.trim() === this.docketType); |
|
78 |
|
}, du.getSiblings(node)); |
|
79 |
|
return (sibList.length > 0); |
|
80 |
|
} else { |
|
81 |
|
return false; |
|
82 |
|
} |
|
83 |
|
}, cases); |
|
84 |
|
|
|
85 |
|
// In those, grab the "a" nodes: |
|
86 |
|
let urlNodes = du.filter((node) => { |
|
87 |
|
return ("a" === node.name); |
|
88 |
|
}, urlSpans); |
|
89 |
|
|
|
90 |
|
// For each of these, grab the hrefs: |
|
91 |
|
this.cases = urlNodes.map((node) => { |
|
92 |
|
return new Case(this, new URL(node.attribs.href, this.URL).toString()); |
|
93 |
|
}); |
|
94 |
|
|
|
95 |
|
Promise.all( |
|
96 |
|
this.cases.map((caseObj) => { |
|
97 |
|
return caseObj.loadCaseFromURL() |
|
98 |
|
.then(caseO => { |
|
99 |
|
caseCallback && "function" === typeof caseCallback && caseCallback(caseO); |
|
100 |
|
}) |
|
101 |
|
.catch(err => { rejFN(err); }) |
|
102 |
|
; |
|
103 |
|
}) |
|
104 |
|
) |
|
105 |
|
.then(val => { |
|
106 |
|
resFN({ |
|
107 |
|
docket: this, |
|
108 |
|
cases: val.map(v => { return v; }) |
|
109 |
|
}); |
|
110 |
|
}) |
|
111 |
|
.catch(err => { rejFN(err); }) |
|
112 |
|
; |
|
113 |
|
}; |
|
114 |
|
}, { |
|
115 |
|
normalizeWhitespace: true |
|
116 |
|
}); |
|
117 |
|
let parser = new hp.Parser(handler); |
|
118 |
|
|
|
119 |
|
// What to do with incoming data: |
|
120 |
|
res.on("data", (d) => { |
|
121 |
|
parser.parseChunk(d); |
|
122 |
|
}).on("end", () => { |
|
123 |
|
parser.done(); |
|
124 |
|
}); |
|
125 |
|
|
|
126 |
|
}); |
|
127 |
|
|
|
128 |
|
req.on('error', (e) => { |
|
129 |
|
rejFN({ msg: "Load Docket HTTP", err: e }); |
|
130 |
|
}); |
|
131 |
|
|
|
132 |
|
// Ok GO: |
|
133 |
|
req.end(); |
|
134 |
|
}); |
|
135 |
|
}; |
|
136 |
|
}; |
File lib/HTTPConversation.class.js added (mode: 100644) (index 0000000..5c32b16) |
|
1 |
|
/* |
|
2 |
|
This file is part of datajams-evictions. |
|
3 |
|
|
|
4 |
|
datajams-evictions is free software: you can redistribute it and/or modify |
|
5 |
|
it under the terms of the GNU Affero General Public License as published by |
|
6 |
|
the Free Software Foundation, either version 3 of the License, or |
|
7 |
|
(at your option) any later version. |
|
8 |
|
|
|
9 |
|
datajams-evictions is distributed in the hope that it will be useful, |
|
10 |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
|
GNU Affero General Public License for more details. |
|
13 |
|
|
|
14 |
|
You should have received a copy of the GNU Affero General Public License |
|
15 |
|
along with datajams-evictions. If not, see <https://www.gnu.org/licenses/>. |
|
16 |
|
|
|
17 |
|
Copyright 2020 Luigi Bai |
|
18 |
|
*/ |
|
19 |
|
const https = require("https"); |
|
20 |
|
const httpOpts = require("./httpOptions"); |
|
21 |
|
const tough = require("tough-cookie"); |
|
22 |
|
|
|
23 |
|
module.exports = class { |
|
24 |
|
constructor(argv) { |
|
25 |
|
let opts = argv || {}; |
|
26 |
|
this.debug = opts.debug; |
|
27 |
|
this.verbose = opts.verbose; |
|
28 |
|
this.jar = (new tough.CookieJar()); |
|
29 |
|
}; |
|
30 |
|
|
|
31 |
|
postURLData(argv) { |
|
32 |
|
let args = argv || {}; |
|
33 |
|
|
|
34 |
|
if (! args.reqPath) throw "Must have a request path."; |
|
35 |
|
if (! args.reqHost) throw "Must have a request host."; |
|
36 |
|
|
|
37 |
|
let opts = new httpOpts(); |
|
38 |
|
opts.host = args.reqHost; |
|
39 |
|
opts.path = args.reqPath; |
|
40 |
|
opts.method = "POST"; |
|
41 |
|
opts.headers["content-type"] = "application/x-www-form-urlencoded"; |
|
42 |
|
delete opts.headers["Accept-Encoding"]; |
|
43 |
|
this.referURL && (opts.headers["referer"] = this.referURL); |
|
44 |
|
|
|
45 |
|
let reqURL = opts.protocol+"//"+opts.host+opts.path; |
|
46 |
|
opts.headers["cookie"] = this.jar.getSetCookieStringsSync(reqURL); |
|
47 |
|
|
|
48 |
|
this.debug && console.log(opts); |
|
49 |
|
this.debug && console.log(reqURL); |
|
50 |
|
|
|
51 |
|
// With the data all set up, now connect and parse: |
|
52 |
|
let req = https.request(opts, (res) => { |
|
53 |
|
this.debug && console.log(res); |
|
54 |
|
res.headers["set-cookie"] && res.headers["set-cookie"].map((cookie) => { this.jar.setCookieSync(cookie, reqURL); }); |
|
55 |
|
|
|
56 |
|
// What to do with incoming data: |
|
57 |
|
let doc = ""; |
|
58 |
|
res.on("data", (d) => { |
|
59 |
|
this.verbose && console.log("postURLData", d.toString()); |
|
60 |
|
if (args.parser) { |
|
61 |
|
args.parser.parseChunk(d); |
|
62 |
|
} else { |
|
63 |
|
doc = doc + d; |
|
64 |
|
} |
|
65 |
|
}).on("end", () => { |
|
66 |
|
this.referURL = reqURL; |
|
67 |
|
args.parser && args.parser.done(); |
|
68 |
|
args.callback && "function" === typeof args.callback && args.callback(doc); |
|
69 |
|
}); |
|
70 |
|
|
|
71 |
|
}); |
|
72 |
|
|
|
73 |
|
req.on('error', (e) => { |
|
74 |
|
console.error(reqURL, e); |
|
75 |
|
}); |
|
76 |
|
|
|
77 |
|
let us = new URLSearchParams(args.data); |
|
78 |
|
this.debug && console.log("data: ", us.toString()); |
|
79 |
|
req.write(us.toString()); |
|
80 |
|
|
|
81 |
|
// Ok GO: |
|
82 |
|
req.end(); |
|
83 |
|
}; |
|
84 |
|
|
|
85 |
|
getURL(argv) { |
|
86 |
|
let args = argv || {}; |
|
87 |
|
|
|
88 |
|
if (! args.reqPath) throw "Must have a request path."; |
|
89 |
|
if (! args.reqHost) throw "Must have a request host."; |
|
90 |
|
|
|
91 |
|
let opts = new httpOpts(); |
|
92 |
|
opts.protocol = "https:"; |
|
93 |
|
opts.host = args.reqHost; |
|
94 |
|
opts.path = args.reqPath; |
|
95 |
|
opts.method = "GET"; |
|
96 |
|
this.referURL && (opts.headers["referer"] = this.referURL); |
|
97 |
|
|
|
98 |
|
let reqURL = opts.protocol+"//"+opts.host+opts.path; |
|
99 |
|
opts.headers["cookie"] = this.jar.getSetCookieStringsSync(reqURL); |
|
100 |
|
|
|
101 |
|
this.debug && console.log(opts); |
|
102 |
|
this.debug && console.log(reqURL); |
|
103 |
|
|
|
104 |
|
// With the data all set up, now connect and parse: |
|
105 |
|
let req = https.request(opts, (res) => { |
|
106 |
|
this.debug && console.log(res); |
|
107 |
|
res.headers["set-cookie"] && res.headers["set-cookie"].map((cookie) => { this.jar.setCookieSync(cookie, reqURL); }); |
|
108 |
|
|
|
109 |
|
// What to do with incoming data: |
|
110 |
|
let doc = ""; |
|
111 |
|
res.on("data", (d) => { |
|
112 |
|
this.verbose && console.log("getURL", d.toString()); |
|
113 |
|
if (args.parser) { |
|
114 |
|
args.parser.parseChunk(d); |
|
115 |
|
} else { |
|
116 |
|
doc = doc + d; |
|
117 |
|
} |
|
118 |
|
}).on("end", () => { |
|
119 |
|
this.referURL = reqURL; |
|
120 |
|
args.parser && args.parser.done(); |
|
121 |
|
args.callback && "function" === typeof args.callback && args.callback(doc); |
|
122 |
|
}); |
|
123 |
|
|
|
124 |
|
}); |
|
125 |
|
|
|
126 |
|
req.on('error', (e) => { |
|
127 |
|
console.error(reqURL, e); |
|
128 |
|
}); |
|
129 |
|
|
|
130 |
|
// Ok GO: |
|
131 |
|
req.end(); |
|
132 |
|
}; |
|
133 |
|
}; |
File lib/OdysseyInfo.class.js added (mode: 100644) (index 0000000..e021696) |
|
1 |
|
const https = require("https"); |
|
2 |
|
const httpOpts = require("./httpOptions"); |
|
3 |
|
const HTTPConversation = require("./HTTPConversation.class"); |
|
4 |
|
const util = require("util"); |
|
5 |
|
const hp = require("htmlparser2"); |
|
6 |
|
const DomHandler = require("domhandler").DomHandler; |
|
7 |
|
const du = require("domutils"); |
|
8 |
|
|
|
9 |
|
/* |
|
10 |
|
function postURLData(oiObj, reqPath, data, cb) { |
|
11 |
|
let opts = new httpOpts(); |
|
12 |
|
opts.host = "odysseyportal.harriscountytx.gov"; |
|
13 |
|
opts.path = reqPath; |
|
14 |
|
opts.method = "POST"; |
|
15 |
|
opts.headers["content-type"] = "application/x-www-form-urlencoded"; |
|
16 |
|
oiObj.referURL && (opts.headers["referer"] = oiObj.referURL); |
|
17 |
|
|
|
18 |
|
let reqURL = opts.protocol+"//"+opts.host+reqPath; |
|
19 |
|
opts.headers["cookie"] = oiObj.jar.getSetCookieStringsSync(reqURL); |
|
20 |
|
|
|
21 |
|
oiObj.debug && console.log(opts); |
|
22 |
|
oiObj.debug && console.log(reqURL); |
|
23 |
|
|
|
24 |
|
// With the data all set up, now connect and parse: |
|
25 |
|
let req = https.request(opts, (res) => { |
|
26 |
|
oiObj.debug && console.log(res.headers); |
|
27 |
|
res.headers["set-cookie"] && res.headers["set-cookie"].map((cookie) => { oiObj.jar.setCookieSync(cookie, reqURL); }); |
|
28 |
|
|
|
29 |
|
// What to do with incoming data: |
|
30 |
|
let doc = ""; |
|
31 |
|
res.on("data", (d) => { |
|
32 |
|
oiObj.verbose && console.log("postURLData", d.toString()); |
|
33 |
|
doc = doc + d; |
|
34 |
|
}).on("end", () => { |
|
35 |
|
oiObj.referURL = reqURL; |
|
36 |
|
cb(doc); |
|
37 |
|
}); |
|
38 |
|
|
|
39 |
|
}); |
|
40 |
|
|
|
41 |
|
req.on('error', (e) => { |
|
42 |
|
console.error(reqURL, e); |
|
43 |
|
}); |
|
44 |
|
|
|
45 |
|
let us = new URLSearchParams(data); |
|
46 |
|
oiObj.debug && console.log("data: ", us.toString()); |
|
47 |
|
req.write(us.toString()); |
|
48 |
|
|
|
49 |
|
// Ok GO: |
|
50 |
|
req.end(); |
|
51 |
|
}; |
|
52 |
|
|
|
53 |
|
function getURL(oiObj, argv) { |
|
54 |
|
if (!argv.requestPath) { |
|
55 |
|
throw new Error("Must include a request path."); |
|
56 |
|
} |
|
57 |
|
|
|
58 |
|
let opts = new httpOpts(); |
|
59 |
|
opts.protocol = "https:"; |
|
60 |
|
opts.host = "odysseyportal.harriscountytx.gov"; |
|
61 |
|
opts.path = argv.requestPath; |
|
62 |
|
opts.method = "GET"; |
|
63 |
|
oiObj.referURL && (opts.headers["referer"] = oiObj.referURL); |
|
64 |
|
|
|
65 |
|
let reqURL = opts.protocol+"//"+opts.host+"/"+argv.requestPath; |
|
66 |
|
opts.headers["cookie"] = oiObj.jar.getSetCookieStringsSync(reqURL); |
|
67 |
|
|
|
68 |
|
oiObj.debug && console.log(opts); |
|
69 |
|
oiObj.debug && console.log(reqURL); |
|
70 |
|
|
|
71 |
|
// With the data all set up, now connect and parse: |
|
72 |
|
let req = https.request(opts, (res) => { |
|
73 |
|
oiObj.debug && console.log(res.headers); |
|
74 |
|
res.headers["set-cookie"] && res.headers["set-cookie"].map((cookie) => { oiObj.jar.setCookieSync(cookie, argv.requestURL); }); |
|
75 |
|
|
|
76 |
|
// What to do with incoming data: |
|
77 |
|
let doc = ""; |
|
78 |
|
res.on("data", (d) => { |
|
79 |
|
oiObj.verbose && console.log("getURL", d.toString()); |
|
80 |
|
if (argv.parser) { |
|
81 |
|
argv.parser.parseChunk(d); |
|
82 |
|
} else { |
|
83 |
|
doc = doc + d; |
|
84 |
|
} |
|
85 |
|
}).on("end", () => { |
|
86 |
|
oiObj.referURL = argv.requestURL; |
|
87 |
|
argv.parser && argv.parser.done(); |
|
88 |
|
argv.callback && argv.callback(doc); |
|
89 |
|
}); |
|
90 |
|
|
|
91 |
|
}); |
|
92 |
|
|
|
93 |
|
req.on('error', (e) => { |
|
94 |
|
console.error(reqURL, e); |
|
95 |
|
}); |
|
96 |
|
|
|
97 |
|
// Ok GO: |
|
98 |
|
req.end(); |
|
99 |
|
}; |
|
100 |
|
*/ |
|
101 |
|
|
|
102 |
|
function getCaseIDParser(oiObj, callback) { |
|
103 |
|
// <a class="caseLink" data-caseid="jBTdKQrus6pMdAW2SPPMBA2" data-caseid-num="6402180">205100178940</a> |
|
104 |
|
let handler = new DomHandler((err, dom) => { |
|
105 |
|
if (err) { |
|
106 |
|
console.error("caseIDParser", err); |
|
107 |
|
} else { |
|
108 |
|
// Find the containing node: |
|
109 |
|
let idLinks = du.filter((node) => { |
|
110 |
|
return ("a" === node.name && "caseLink" === node.attribs.class); |
|
111 |
|
}, dom); |
|
112 |
|
|
|
113 |
|
if (1 !== idLinks.length) { |
|
114 |
|
throw new Error("Unable to find caseID."); |
|
115 |
|
} |
|
116 |
|
|
|
117 |
|
oiObj.caseID = idLinks[0].attribs["data-caseid"]; |
|
118 |
|
|
|
119 |
|
// Now use the callback to process the hrefs: |
|
120 |
|
(callback && "function" === typeof callback) && callback(); |
|
121 |
|
}; |
|
122 |
|
}, { |
|
123 |
|
normalizeWhitespace: true |
|
124 |
|
}); |
|
125 |
|
|
|
126 |
|
return new hp.Parser(handler); |
|
127 |
|
}; |
|
128 |
|
|
|
129 |
|
function getCaseInfoParser(oiObj, callback) { |
|
130 |
|
// <a class="caseLink" data-caseid="jBTdKQrus6pMdAW2SPPMBA2" data-caseid-num="6402180">205100178940</a> |
|
131 |
|
let handler = new DomHandler((err, dom) => { |
|
132 |
|
if (err) { |
|
133 |
|
console.error("caseIDParser", err); |
|
134 |
|
} else { |
|
135 |
|
// Find the containing node: |
|
136 |
|
let partyInfo = du.filter((node) => { |
|
137 |
|
return ("div" === node.name && "divPartyInformation_body" === node.attribs.id); |
|
138 |
|
}, dom); |
|
139 |
|
|
|
140 |
|
if (1 !== partyInfo.length) { |
|
141 |
|
throw new Error("Unable to find party info."); |
|
142 |
|
} |
|
143 |
|
|
|
144 |
|
// Each party is in a <div class="col-md-8"/> |
|
145 |
|
let partyDivs = du.filter((node) => { |
|
146 |
|
return ("div" === node.name && "col-md-8" === node.attribs.class); |
|
147 |
|
}, partyInfo); |
|
148 |
|
|
|
149 |
|
// Each of those has a <p><span>class</span>name</p> and <p>Address</p>: |
|
150 |
|
this.parties = []; |
|
151 |
|
partyDivs.forEach((partyDiv) => { |
|
152 |
|
let party = {}; |
|
153 |
|
let pDivs = du.filter((node) => { |
|
154 |
|
return ("p" === node.name); |
|
155 |
|
}, partyDiv); |
|
156 |
|
|
|
157 |
|
// pDivs[0]: <p><span>class</span>name</p> |
|
158 |
|
du.filter((node) => { |
|
159 |
|
return ("span" === node.name); |
|
160 |
|
}, pDivs[0]).forEach((span) => { |
|
161 |
|
// SHOULD only be ONE of these: |
|
162 |
|
party.role = du.filter((node) => { |
|
163 |
|
return ("text" === node.type); |
|
164 |
|
}, span).map((val) => { |
|
165 |
|
return val.data; |
|
166 |
|
}).join(" ").trim(); |
|
167 |
|
// Get rid of the span for the next manipulation: |
|
168 |
|
du.removeElement(span); |
|
169 |
|
}); |
|
170 |
|
party.name = du.filter((node) => { |
|
171 |
|
return ("text" === node.type); |
|
172 |
|
}, pDivs[0]).map((val) => { |
|
173 |
|
return val.data; |
|
174 |
|
}).join(" ").trim(); |
|
175 |
|
|
|
176 |
|
// pDivs[1]: Address |
|
177 |
|
party.address = du.filter((node) => { |
|
178 |
|
return ("text" === node.type); |
|
179 |
|
}, pDivs[1]).map((val) => { |
|
180 |
|
return val.data; |
|
181 |
|
}); |
|
182 |
|
|
|
183 |
|
this.parties.push(party); |
|
184 |
|
}); |
|
185 |
|
|
|
186 |
|
// Now do the callback: |
|
187 |
|
(callback && "function" === typeof callback) && callback(oiObj.parties); |
|
188 |
|
}; |
|
189 |
|
}, { |
|
190 |
|
normalizeWhitespace: true |
|
191 |
|
}); |
|
192 |
|
|
|
193 |
|
return new hp.Parser(handler); |
|
194 |
|
}; |
|
195 |
|
|
|
196 |
|
module.exports = class OdysseyInfo { |
|
197 |
|
constructor(caseNumber, argv) { |
|
198 |
|
if (! caseNumber) { |
|
199 |
|
throw new Error("Must define a case number!"); |
|
200 |
|
} |
|
201 |
|
let opts = argv || {}; |
|
202 |
|
this.caseNum = caseNumber; |
|
203 |
|
this.debug = opts.debug; |
|
204 |
|
}; |
|
205 |
|
|
|
206 |
|
findCase(cb) { |
|
207 |
|
let host = "odysseyportal.harriscountytx.gov"; |
|
208 |
|
let hc = new HTTPConversation(); |
|
209 |
|
|
|
210 |
|
hc.getURL({ reqHost: host, reqPath: "/OdysseyPortalJP", callback: () => { |
|
211 |
|
hc.getURL({ reqHost: host, reqPath: "/OdysseyPortalJP/Home/Dashboard/29", callback: () => { |
|
212 |
|
hc.postURLData({ |
|
213 |
|
reqHost: host, |
|
214 |
|
reqPath: "/OdysseyPortalJP/SmartSearch/SmartSearch/SmartSearch", |
|
215 |
|
data: [ |
|
216 |
|
[ "Settings.CaptchaEnabled", "False" ], |
|
217 |
|
[ "Settings.CaptchaDisabledForAuthenticated", "False" ], |
|
218 |
|
[ "caseCriteria.SearchCriteria", this.caseNum ], |
|
219 |
|
[ "caseCriteria.JudicialOfficerSearchBy", "" ], |
|
220 |
|
[ "caseCriteria.NameMiddle", "" ], |
|
221 |
|
[ "caseCriteria.NameSuffix", "" ], |
|
222 |
|
[ "caseCriteria.AdvancedSearchOptionsOpen", "false" ], |
|
223 |
|
[ "caseCriteria.CourtLocation_input", "Harris County JPs Odyssey Portal" ], |
|
224 |
|
[ "caseCriteria.CourtLocation", "Harris County JPs Odyssey Portal" ], |
|
225 |
|
[ "caseCriteria.SearchBy_input", "Smart Search" ], |
|
226 |
|
[ "caseCriteria.SearchBy", "SmartSearch" ], |
|
227 |
|
[ "caseCriteria.SearchCases", "true" ], |
|
228 |
|
[ "caseCriteria.SearchCases", "false" ], |
|
229 |
|
[ "caseCriteria.SearchJudgments", "true" ], |
|
230 |
|
[ "caseCriteria.SearchJudgments", "false" ], |
|
231 |
|
[ "caseCriteria.SearchByPartyName", "true" ], |
|
232 |
|
[ "caseCriteria.SearchByNickName", "false" ], |
|
233 |
|
[ "caseCriteria.SearchByBusinessName", "false" ], |
|
234 |
|
[ "caseCriteria.UseSoundex", "false" ], |
|
235 |
|
[ "caseCriteria.Gender_input", "" ], |
|
236 |
|
[ "caseCriteria.Gender", "" ], |
|
237 |
|
[ "caseCriteria.Race_input", "" ], |
|
238 |
|
[ "caseCriteria.Race", "" ], |
|
239 |
|
[ "caseCriteria.FBINumber", "" ], |
|
240 |
|
[ "caseCriteria.SONumber", "" ], |
|
241 |
|
[ "caseCriteria.BookingNumber", "" ], |
|
242 |
|
[ "caseCriteria.CaseType_input", "" ], |
|
243 |
|
[ "caseCriteria.CaseType", "" ], |
|
244 |
|
[ "caseCriteria.CaseStatus_input", "" ], |
|
245 |
|
[ "caseCriteria.CaseStatus", "" ], |
|
246 |
|
[ "caseCriteria.FileDateStart", "" ], |
|
247 |
|
[ "caseCriteria.FileDateEnd", "" ], |
|
248 |
|
[ "caseCriteria.JudicialOfficer_input", "" ], |
|
249 |
|
[ "caseCriteria.JudicialOfficer", "" ], |
|
250 |
|
[ "caseCriteria.JudgmentType_input", "" ], |
|
251 |
|
[ "caseCriteria.JudgmentType", "" ], |
|
252 |
|
[ "caseCriteria.JudgmentDateFrom", "" ], |
|
253 |
|
[ "caseCriteria.JudgmentDateTo", "" ] |
|
254 |
|
], |
|
255 |
|
callback: () => { |
|
256 |
|
hc.getURL({ reqHost: host, reqPath: "/OdysseyPortalJP/Home/WorkspaceMode?p=0", callback: () => { |
|
257 |
|
setTimeout(() => { |
|
258 |
|
hc.getURL({ |
|
259 |
|
reqHost: host, |
|
260 |
|
reqPath: "/OdysseyPortalJP/SmartSearch/SmartSearchResults?_="+(new Date()).valueOf(), |
|
261 |
|
parser: getCaseIDParser(this, () => { |
|
262 |
|
this.debug && console.log("CaseID: ", this.caseID); |
|
263 |
|
hc.getURL({ |
|
264 |
|
reqHost: host, |
|
265 |
|
reqPath: "/OdysseyPortalJP/Case/CaseDetail?eid="+this.caseID+"&tabIndex=3&_="+(new Date()).valueOf(), |
|
266 |
|
parser: getCaseInfoParser(this, (info) => { |
|
267 |
|
if (cb && "function" === typeof cb) { |
|
268 |
|
cb(this); |
|
269 |
|
} else { |
|
270 |
|
console.log(this); |
|
271 |
|
} |
|
272 |
|
}) |
|
273 |
|
}); |
|
274 |
|
}) |
|
275 |
|
}); |
|
276 |
|
}, 1000); |
|
277 |
|
}}); |
|
278 |
|
}}); |
|
279 |
|
}}); |
|
280 |
|
}}); |
|
281 |
|
}; |
|
282 |
|
}; |
File loadDockets.js added (mode: 100644) (index 0000000..a0eb075) |
|
1 |
|
/* |
|
2 |
|
This file is part of datajams-evictions. |
|
3 |
|
|
|
4 |
|
datajams-evictions is free software: you can redistribute it and/or modify |
|
5 |
|
it under the terms of the GNU Affero General Public License as published by |
|
6 |
|
the Free Software Foundation, either version 3 of the License, or |
|
7 |
|
(at your option) any later version. |
|
8 |
|
|
|
9 |
|
datajams-evictions is distributed in the hope that it will be useful, |
|
10 |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
|
GNU Affero General Public License for more details. |
|
13 |
|
|
|
14 |
|
You should have received a copy of the GNU Affero General Public License |
|
15 |
|
along with datajams-evictions. If not, see <https://www.gnu.org/licenses/>. |
|
16 |
|
|
|
17 |
|
Copyright 2020 Luigi Bai |
|
18 |
|
*/ |
|
19 |
|
const DAO = require("./lib/sqlDAO"); |
|
20 |
|
const Court = require("./lib/Court.class"); |
|
21 |
|
|
|
22 |
|
let today = new Date(); |
|
23 |
|
let future = (new Date(today.valueOf())); |
|
24 |
|
future.setDate(today.getDate() + 6); |
|
25 |
|
|
|
26 |
|
// Emit the CSV headers; we get a line for each case, below: |
|
27 |
|
console.log( |
|
28 |
|
'"Case Number",', |
|
29 |
|
'"Precinct",', |
|
30 |
|
'"Place",', |
|
31 |
|
'"Date",', |
|
32 |
|
'"Time",', |
|
33 |
|
'"DateTime",', |
|
34 |
|
'"Claim",', |
|
35 |
|
'"Plaintiff",', |
|
36 |
|
'"Defendant 1",', |
|
37 |
|
'"Defendant 2",', |
|
38 |
|
'"Defendant 3",', |
|
39 |
|
'"Case URL",', |
|
40 |
|
'"Docket URL"' |
|
41 |
|
); |
|
42 |
|
|
|
43 |
|
let opts = require("./creds")["SQLITE3"]; |
|
44 |
|
|
|
45 |
|
opts.connectCallback = (database) => { |
|
46 |
|
let promises = []; |
|
47 |
|
|
|
48 |
|
// Loop over all 8 precincts, two places per precinct: |
|
49 |
|
for (let pct = 1; pct <= 8; pct ++) { |
|
50 |
|
for (let plc = 1; plc <= 2; plc ++) { |
|
51 |
|
// param is pct * 10 + plc: |
|
52 |
|
let court = new Court({ |
|
53 |
|
precinct: pct, |
|
54 |
|
place: plc |
|
55 |
|
}); |
|
56 |
|
|
|
57 |
|
promises.push(court.getDockets({ |
|
58 |
|
startDate: today, |
|
59 |
|
endDate: future, |
|
60 |
|
docketType: "Eviction Docket", |
|
61 |
|
caseCallback: (caseObj) => { |
|
62 |
|
// First, store the case into the database: |
|
63 |
|
caseObj.storeSQL(database); |
|
64 |
|
|
|
65 |
|
// These functions allow the "pivot" below on each case: |
|
66 |
|
let plaintiffs = caseObj.parties.filter((party) => { |
|
67 |
|
return ("Plaintiff" === party.role); |
|
68 |
|
}); |
|
69 |
|
let defendants = caseObj.parties.filter((party) => { |
|
70 |
|
return ("Defendant" === party.role); |
|
71 |
|
}); |
|
72 |
|
|
|
73 |
|
// See the CSV header above? Emit each case to that format: |
|
74 |
|
console.log( |
|
75 |
|
caseObj.caseNumber + ",", |
|
76 |
|
caseObj.docket.court.precinct + ",", |
|
77 |
|
caseObj.docket.court.place + ",", |
|
78 |
|
caseObj.docket.date + ",", |
|
79 |
|
caseObj.docket.time + ",", |
|
80 |
|
'"' + |
|
81 |
|
caseObj.docket.date.substr(6)+"-"+ |
|
82 |
|
caseObj.docket.date.substr(0, 2)+"-"+ |
|
83 |
|
caseObj.docket.date.substr(3, 2)+" "+ |
|
84 |
|
caseObj.docket.time |
|
85 |
|
+ '",', |
|
86 |
|
'"' + caseObj.claimAmount + '",', |
|
87 |
|
(plaintiffs.length > 0) ? '"' + plaintiffs[0].name + '",' : '"",', |
|
88 |
|
(defendants.length > 0) ? '"' + defendants[0].name + '",' : '"",', |
|
89 |
|
(defendants.length > 1) ? '"' + defendants[1].name + '",' : '"",', |
|
90 |
|
(defendants.length > 2) ? '"' + defendants[2].name + '",' : '"",', |
|
91 |
|
'"' + caseObj.URL + '",', |
|
92 |
|
'"' + caseObj.docket.URL.href + '",' |
|
93 |
|
); |
|
94 |
|
} |
|
95 |
|
})); |
|
96 |
|
} |
|
97 |
|
} |
|
98 |
|
|
|
99 |
|
// Wait for all the dockets to load, then emit their info: |
|
100 |
|
Promise.all(promises) |
|
101 |
|
.then(val => { console.log("All courts: ", val); }) |
|
102 |
|
.catch(err => { console.error("All courts ERR: ", err); }) |
|
103 |
|
.finally(() => { |
|
104 |
|
console.log("Finished loading all dockets from all courts."); |
|
105 |
|
(require("./lib/httpOptions").getGlobalAgent().destroy()); |
|
106 |
|
DAO.disconnect(database); |
|
107 |
|
}); |
|
108 |
|
; |
|
109 |
|
}; |
|
110 |
|
|
|
111 |
|
DAO.connect(opts); |