/lib/Case.class.js (e95ef48547f2ba4017f1cdd34c17e32def52f127) (10564 bytes) (mode 100644) (type blob)

/*
  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;


Mode Type Size Ref File
100644 blob 31 ce60eb947708d4530bee8739c111f9c0ab750eaa .gitignore
100644 blob 33877 e37e32e4e836c2394b62c0a848c2094f687eb530 LICENSE
100644 blob 1171 354a297411cdddbe0355507766510a182aba0874 Promise.shim.js
100644 blob 384 4f72a1e5d3c830cb6a5f962a320da2d22567f82d README.md
100644 blob 364 c97984072c2cd4c8daa74c3e6ff6eb0434dc5d72 creds.example.js
040000 tree - 2f905348757f0e27d93465c3a1a1100dce0f80bf lib
100644 blob 3385 a0eb075e7aa659c0f83bbfc988ced15555227e9e loadDockets.js
100644 blob 49738 d6ac529ef6f7718e7bb1770f5544f35238738473 package-lock.json
100644 blob 627 1dbf524b42fda0760f06490e6c8dd142cab2308c package.json
100644 blob 2130 69eac4aecd2ae8d90fb05de3e27156fd8a0d590d schema.mssql.sql
100644 blob 1032 dd314aae1344ab599bf5ad4a79b64e3f129a6abb schema.sqlite3.sql
100644 blob 1608 1086eccf9183298d0fdedf88b8bc19c9a6159278 searchNames.js
100755 blob 162 7c25a482064c9580388f09c4d91683f3858ff7ca sqdump
100644 blob 2181 66cb76343f7b83906ea940e9a5ccf90ce7f024e1 updateStatus.js
100644 blob 6576 67a852a1d29d13ecbea36711fea8ba8b3bd18fcf xferData.js
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/datajams-lbai/datajams-evictions

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/datajams-lbai/datajams-evictions

Clone this repository using git:
git clone git://git.rocketgit.com/user/datajams-lbai/datajams-evictions

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main