/*
This file is part of datajams-evictions.
datajams-evictions is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
datajams-evictions is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with datajams-evictions. If not, see <https://www.gnu.org/licenses/>.
Copyright 2020 Luigi Bai
*/
const https = require("https");
const httpOpts = require("./httpOptions");
const HTTPConversation = require("./HTTPConversation.class");
const util = require("util");
const hp = require("htmlparser2");
const DomHandler = require("domhandler").DomHandler;
const du = require("domutils");
const DAO = require("./sqlDAO");
const OdysseyInfo = require("./OdysseyInfo.class");
// ---------------------------------------------------
// Some helper functions:
function checkPreviousSiblings(node, test) {
let ret = false;
if (null !== node) {
if (null !== node.prev) {
let prev = node.prev;
ret = test(prev) || checkPreviousSiblings(prev, test);
}
}
return ret;
}
function findAndSet(label, member, nodeSet, obj) {
// Should work for any label and member:
let cnNode = du.filter((node) => {
return checkPreviousSiblings(node, (testNode) => {
if ("span" === testNode.name && "label" === testNode.attribs.class) {
return ("text" === testNode.children[0].type && label === testNode.children[0].data);
} else {
return false;
}
});
}, nodeSet);
if (0 === cnNode.length) {
// ERROR
console.error("Warning, no value found: ", label, member, obj);
} else {
if (0 === cnNode[0].children.length) {
// ERROR
console.error("Warning, no value found: ", label, member, obj);
} else {
obj[member] = cnNode[0].children[0].data.trim();
}
}
}
function getCaseInfo(nodes, obj) {
let ci = du.filter((node) => {
return ("div" === node.name && "caseInfo" === node.attribs.id);
}, nodes);
// get all the labels:
let labels = du.filter((node) => {
return ("span" === node.name && "value" === node.attribs.class);
}, ci);
findAndSet("Case Number:", "caseNumber", labels, obj);
findAndSet("Filed Date:", "filedDate", labels, obj);
findAndSet("Case Status:", "caseStatus", labels, obj);
};
function getCivilInfo(nodes, obj) {
let ci = du.filter((node) => {
return ("div" === node.name && "civilInfo" === node.attribs.id);
}, nodes);
if (0 === ci.length) {
// Odd. This should be a civil claim, and yet there's no civil info.
// May have found a criminal offense?
} else {
// get all the labels:
let labels = du.filter((node) => {
return ("span" === node.name && "value" === node.attribs.class);
}, ci);
// findAndSet("Nature of Claim:", "natureOfClaim", labels, obj);
findAndSet("Claim Amount:", "claimAmount", labels, obj);
}
};
function getHearingInfo(nodes, obj) {
let ci = du.filter((node) => {
return ("div" === node.name && "eventInfo" === node.attribs.id);
}, nodes);
// get all the hearings:
let hearings = du.filter((node) => {
return ("div" === node.name && ("odd" === node.attribs.class || "even" === node.attribs.class));
}, ci);
let hearingList = [];
hearings.forEach((hearingDiv) => {
let hearing = {};
// get all the labels:
let labels = du.filter((node) => {
return ("span" === node.name && "value" === node.attribs.class);
}, hearingDiv);
findAndSet("Hearing Description:", "hearingDescription", labels, hearing);
findAndSet("Hearing Date/Time:", "hearingDateTime", labels, hearing);
findAndSet("Hearing Result/Cancellation:", "hearingResult", labels, hearing);
findAndSet("Hearing Result/Cancellation Date:", "hearingResultDateTime", labels, hearing);
hearingList.push(hearing);
});
obj.hearings = hearingList;
};
function getPartyInfo(nodes, obj) {
let ci = du.filter((node) => {
return ("div" === node.name && "partyInfo" === node.attribs.id);
}, nodes);
// get all the parties:
let parties = du.filter((node) => {
return ("div" === node.name && ("odd" === node.attribs.class || "even" === node.attribs.class));
}, ci);
let partyList = [];
// For each party:
parties.forEach((partyDiv) => {
let party = {};
// get all the labels:
let labels = du.filter((node) => {
return ("span" === node.name && "value" === node.attribs.class);
}, partyDiv);
findAndSet("Party Name:", "name", labels, party);
findAndSet("Party Type:", "role", labels, party);
partyList.push(party);
});
obj.parties = partyList;
};
function getEventInfo(nodes, obj) {
let ci = du.filter((node) => {
return ("div" === node.name && "filingInfo" === node.attribs.id);
}, nodes);
// get all the parties:
let events = du.filter((node) => {
return ("div" === node.name && ("odd" === node.attribs.class || "even" === node.attribs.class));
}, ci);
let eventList = [];
// For each party:
events.forEach((partyDiv) => {
let party = {};
// get all the labels:
let labels = du.filter((node) => {
return ("span" === node.name && "value" === node.attribs.class);
}, partyDiv);
findAndSet("Event Description:", "description", labels, party);
findAndSet("Date Added:", "dateAdded", labels, party);
eventList.push(party);
});
obj.events = eventList;
};
// ---------------------------------------------------
// Expose just the class:
class Case {
constructor(docket, url) {
this.docket = docket;
if (!url) {
throw new Error("Must include a URL.");
}
this.URL = url;
};
static sqlAllCases(opts) {
if (! opts.database) { throw "Must pass in database."; }
opts.database.each(
(opts.query) ? opts.query : "SELECT case_URL from cases",
(err, row) => {
if (err) {
console.error("Case.sqlAllCases", err);
} else {
opts.rowCallback && "function" === typeof opts.rowCallback && opts.rowCallback(new Case(null, row["case_URL"]));
}
},
opts.completionCallback
);
};
static findCasesByBusiness(name, cb) {
let hc = new HTTPConversation({
debug: false
});
let host = "jpwebsite.harriscountytx.gov";
let handler = new DomHandler((err, dom) => {
if (err) {
console.error("Cases.findCasesByBusiness", err);
} else {
if (cb && "function" === typeof cb) {
// Find the containing node:
let caseTable = du.filter((node) => {
return ("table" === node.name && "cases" === node.attribs.id);
}, dom);
// Now get the table rows:
let caseRows = du.filter((node) => {
return ("tr" === node.name);
}, caseTable);
// For each of the rows, grab the case URL:
let caseHrefs = du.filter((node) => {
return ("a" === node.name && "td" === node.parent.name && 'text' === node.parent.prev.type && null === node.parent.prev.prev);
}, caseRows);
let cases = caseHrefs.map((aNode) => { return "https://"+host+aNode.attribs.href; });
cb(cases);
}
};
}, {
normalizeWhitespace: true
});
// First, get "https://jpwebsite.harriscountytx.gov/FindMyCase/search.jsp"
hc.getURL({
reqPath: "/FindMyCase/search.jsp",
reqHost: host,
callback: (doc) => {
// Next, post the search:
hc.postURLData({
reqPath: "/FindMyCase/SearchForMyCases",
reqHost: host,
data: {
"case": "",
"dlNumber": "",
"lastName": "",
"firstName": "",
"dob": "",
"businessName": name,
"court": "00",
"criteria": "businessNameSearch"
},
parser: new hp.Parser(handler)
});
}
});
};
loadCaseFromURL() {
return new Promise((resFN, rejFN) => {
let opts = new httpOpts();
// With the data all set up, now connect and parse:
let req = https.request(this.URL, opts, (res) => {
let handler = new DomHandler((err, dom) => {
if (err) {
rejFN({ url: this.URL, err: err });
} else {
// Find the containing node:
let dataDiv = du.filter((node) => {
return ("div" === node.name && "contentWithNavBar" === node.attribs.class);
}, dom);
// Get the civil info:
getCaseInfo(dataDiv, this);
getCivilInfo(dataDiv, this);
getPartyInfo(dataDiv, this);
getHearingInfo(dataDiv, this);
getEventInfo(dataDiv, this);
resFN && "function" === typeof resFN && resFN(this);
};
}, {
normalizeWhitespace: true
});
let parser = new hp.Parser(handler);
// What to do with incoming data:
res.on("data", (d) => {
parser.parseChunk(d);
}).on("end", () => {
parser.done();
});
});
req.on('error', e => {
rejFN({ msg: "Load Case from URL HTTP", url: this.URL, err: e });
});
// Ok GO:
req.end();
});
};
storeSQL(database) {
let caseObj = this;
if (caseObj.docket) {
let ctlDocket = new DAO.DocketCtlr(database);
ctlDocket.insert({
$pct: caseObj.docket.court.precinct,
$plc: caseObj.docket.court.place,
$dt: caseObj.docket.date.substr(6)+"-"+
caseObj.docket.date.substr(0, 2)+"-"+
caseObj.docket.date.substr(3, 2)+" "+
caseObj.docket.time,
$url: caseObj.docket.URL.href
});
ctlDocket.finalize();
let ctlDC = new DAO.DocketCaseCtlr(database);
ctlDC.insert({
$pct: caseObj.docket.court.precinct,
$plc: caseObj.docket.court.place,
$dt: caseObj.docket.date.substr(6)+"-"+
caseObj.docket.date.substr(0, 2)+"-"+
caseObj.docket.date.substr(3, 2)+" "+
caseObj.docket.time,
$cnum: caseObj.caseNumber,
$claim: caseObj.claimAmount
});
ctlDC.finalize();
}
let ctlCase = new DAO.CaseCtlr(database);
ctlCase.insert({
$cnum: caseObj.caseNumber,
$curl: caseObj.URL
});
ctlCase.finalize();
let ctlParty = new DAO.PartyCtlr(database);
caseObj.parties.forEach((party) => {
ctlParty.insert({
$cnum: caseObj.caseNumber,
$role: party.role,
$name: party.name
});
});
ctlParty.finalize();
let ctlEvent = new DAO.EventCtlr(database);
caseObj.events.forEach((event) => {
ctlEvent.insert({
$cnum: caseObj.caseNumber,
$desc: event.description,
$date: event.dateAdded
});
});
ctlEvent.finalize();
};
};
module.exports = Case;