import mustache from 'mustache' import express, { response } from 'express' import axios from 'axios' import { SolrRecord } from './types/solrrecord' import _ from 'lodash' import bodyParser from 'body-parser' import { v4 as uuidv4, v4 } from 'uuid' import ss from 'simple-statistics' import ip from 'ip' import crypto from 'crypto' import { spawn } from 'child_process' // import fetch from 'node-fetch'; const stateMapping: Record = { "Alabama": "AL", "Kentucky": "KY", "Ohio": "OH", "Alaska": "AK", "Louisiana": "LA", "Oklahoma": "OK", "Arizona": "AZ", "Maine": "ME", "Oregon": "OR", "Arkansas": "AR", "Maryland": "MD", "Pennsylvania": "PA", "American Samoa": "AS", "Massachusetts": "MA", "Puerto Rico": "PR", "California": "CA", "Michigan": "MI", "Rhode Island": "RI", "Colorado": "CO", "Minnesota": "MN", "South Carolina": "SC", "Connecticut": "CT", "Mississippi": "MS", "South Dakota": "SD", "Delaware": "DE", "Missouri": "MO", "Tennessee": "TN", "District of Columbia": "DC", "Montana": "MT", "Texas": "TX", "Florida": "FL", "Nebraska": "NE", "Trust Territories": "TT", "Georgia": "GA", "Nevada": "NV", "Utah": "UT", "Guam": "GU", "New Hampshire": "NH", "Vermont": "VT", "Hawaii": "HI", "New Jersey": "NJ", "Virginia": "VA", "Idaho": "ID", "New Mexico": "NM", "Virgin Islands": "VI", "Illinois": "IL", "New York": "NY", "Washington": "WA", "Indiana": "IN", "North Carolina": "NC", "West Virginia": "WV", "Iowa": "IA", "North Dakota": "ND", "Wisconsin": "WI", "Kansas": "KS", "Northern Mariana Islands": "MP", "Wyoming": "WY" } const streetSuffixMapping: Record = { "Alley": "aly", "Alleyway": "alyw", "Anex": "anx", "Annex": "anx", "Arcade": "arc", "Avenue": "ave", "Bayou": "byu", "Beach": "bch", "Bend": "bnd", "Bluff": "blf", "Bottom": "btm", "Boulevard": "blvd", "Branch": "br", "Bridge": "brg", "Brook": "brk", "Bypass": "byp", "Camp": "cp", "Canyon": "cyn", "Cape": "cpe", "Causeway": "cswy", "Center": "ctr", "Circle": "cir", "Cliff": "clf", "Club": "clb", "Common": "cmn", "Corner": "cor", "Course": "crse", "Court": "ct", "Courts": "cts", "Cove": "cv", "Creek": "crk", "Crescent": "cres", "Crest": "crst", "Crossing": "xing", "Crossroad": "xrd", "Curve": "curv", "Dale": "dl", "Dam": "dm", "Drive": "dr", "Estate": "est", "Expressway": "expy", "Extension": "ext", "Falls": "fls", "Ferry": "fry", "Field": "fld", "Flat": "flt", "Ford": "frd", "Forest": "frst", "Forge": "frg", "Fork": "frk", "Fort": "ft", "Freeway": "fwy", "Garden": "gdn", "Gateway": "gtwy", "Glen": "gln", "Green": "grn", "Grove": "grv", "Harbor": "hbr", "Haven": "hvn", "Heights": "hts", "Highway": "hwy", "Hill": "hl", "Hollow": "holw", "Inlet": "inlt", "Island": "is", "Junction": "jct", "Key": "ky", "Knoll": "knl", "Lake": "lk", "Landing": "lndg", "Lane": "ln", "Light": "lgt", "Loaf": "lf", "Lock": "lck", "Locks": "lcks", "Lodge": "ldg", "Loop": "loop", "Mall": "mall", "Manor": "mnr", "Meadow": "mdw", "Mews": "mews", "Mill": "ml", "Mission": "msn", "Motorway": "mtwy", "Mount": "mt", "Mountain": "mtn", "Neck": "nck", "Orchard": "orch", "Overpass": "opas", "Park": "park", "Parkway": "pkwy", "Pass": "pass", "Path": "path", "Pike": "pike", "Pine": "pne", "Place": "pl", "Plain": "pln", "Plaza": "plz", "Point": "pt", "Port": "prt", "Prairie": "pr", "Radial": "radl", "Ramp": "ramp", "Ranch": "rnch", "Rapid": "rpd", "Rest": "rst", "Ridge": "rdg", "River": "riv", "Road": "rd", "Route": "rte", "Row": "row", "Rue": "rue", "Run": "run", "Shore": "shr", "Skyway": "skwy", "Spring": "spg", "Springs": "spgs", "Spur": "spur", "Square": "sq", "Station": "sta", "Stravenue": "stra", "Stream": "strm", "Street": "st", "Summit": "smt", "Terrace": "ter", "Throughway": "trwy", "Trace": "trce", "Track": "trak", "Trafficway": "trfy", "Trail": "trl", "Trailer": "trlr", "Tunnel": "tunl", "Turnpike": "tpke", "Underpass": "upas", "Union": "un", "Valley": "vly", "Viaduct": "via", "View": "vw", "Village": "vlg", "Ville": "vl", "Vista": "vis", "Walk": "walk", "Way": "way", "Well": "wl", "Wells": "wls", "Wye": "wye" } const app = express() const port = 3000 const hlrToken = '' const hlrSecret = '' const basic = crypto.createHash('sha256').update(hlrToken + ':' + hlrSecret).digest('hex'); const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/; let handledCharges: string[] = [] // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })) // parse application/json app.use(bodyParser.json()) const fs = require('fs') const donations = JSON.parse(fs.readFileSync('templates/pages/donations.json', 'utf-8')) // const mapTemplate = fs.readFileSync('templates/pages/map.html', 'utf-8') const indexTemplate = fs.readFileSync('templates/pages/home.mustache', 'utf-8') const recordByIdTemplate = fs.readFileSync('templates/pages/recordById.mustache', 'utf-8') const recordsListingTemplate = fs.readFileSync('templates/pages/recordsListing.mustache', 'utf-8') const donationsTemplate = fs.readFileSync('templates/pages/donations.html', 'utf-8') const exportsTemplate = fs.readFileSync('templates/pages/exports.html', 'utf-8') const visualizerTemplate = fs.readFileSync('templates/pages/visualizer.html', 'utf-8') const servers = ["http://solr1:8983/solr/BigData/select"] const blacklistedAutomatedIps: string[] = [] const whitelistedUserAgents: string[] = [ // API Keys ] // Stores that last time a query was made to a server for an IP address const lastQueryTime: Record = {} const lastQueryTimes: Record = {} const numLookupsPerIP: Record = {} let hasRequestedWallet: string[] = [] function getRandServer() { var index = Math.floor(Math.random() * servers.length); return servers[index]; } function getRandWalletsServer() { var index = Math.floor(Math.random() * servers.length); return servers[index].replace('BigData', 'Wallets'); } async function queryForDocsSpatial(latLong: string, distanceKm: number) { const records: SolrRecord[] = [] let numDocs = 0 await axios.get(getRandServer(), { params: { q: "latLong:*", rows: 500, fq: "{!geofilt sfield=latLong}", pt: latLong, d: distanceKm } }).then(({ data }) => { records.push(...data.response.docs) numDocs += data.response.numFound }) console.log(`Spatial query for ${latLong} returned ${numDocs} records.`) return { numDocs: numDocs, records: records } } async function getWalletBalance(wallet: string): Promise { return axios.get(getRandWalletsServer(), { params: { q: `id:"${wallet}"` } }) .then(({ data }) => { console.log(data.response.docs[0]) if (data.response.docs[0]) { return data.response.docs[0].credits } else { return 0 } } ) .catch((err) => { console.log(err); return 0 }) } async function addWalletBalance(wallet: string, credits: number) { if (typeof credits !== 'number') { console.log(`Credits is not a number: ${credits}`) throw new Error('Credits is not a number') } // Get the old balance const oldBalance = await getWalletBalance(wallet) console.log('old balance: ' + oldBalance + ' credits: ' + credits + ' wallet: ' + wallet + ' new balance: ' + (oldBalance + credits)) // Add the new balance credits += oldBalance // Update the wallet return axios.post(getRandWalletsServer().replace('select', 'update'), { "add": { "doc": { // uuid v4 "id": wallet, "credits": credits } } }, { params: { commit: true } }).then(() => { console.log(`Added ${credits} credits to wallet ${wallet}`) return true }) .catch ((err) => { console.log(err) return false }) } async function removeWalletBalance(wallet: string, credits: number) { if (typeof credits !== 'number') { console.log(`Credits is not a number: ${credits}`) throw new Error('Credits is not a number') } // Get the old balance const oldBalance = await getWalletBalance(wallet) // Remove the new balance const newBalance = oldBalance - credits // Update the wallet return axios.post(getRandWalletsServer().replace('select', 'update'), { "add": { "doc": { // uuid v4 "id": wallet, "credits": newBalance } } }, { params: { commit: true } }).then(() => { console.log(`Removed ${credits} credits to wallet ${wallet}`) return true }) } async function queryForDocs(query: string, limit?: number, start?: number, sort?: string) { const records: SolrRecord[] = [] let numDocs = 0 await axios.get(getRandServer(), { params: { q: query, rows: limit || 100, sort: sort, start: start || 0 } }).then(({ data }) => { records.push(...data.response.docs) numDocs += data.response.numFound }) return { numDocs: numDocs, records: records } } async function queryForExportDocs(query: string, limit?: number, start?: number, sort?: string) { const records: SolrRecord[] = [] let numDocs = 0 await axios.get(getRandServer().replace('BigData', 'Exports'), { params: { q: query, rows: limit || 100, sort: sort, start: start || 0 } }).then(({ data }) => { records.push(...data.response.docs) numDocs += data.response.numFound }) return { numDocs: numDocs, records: records } } function buildQuery(requestedQuery: Record, res: express.Response) { let query: string[] = [] let orQuery: string[] = [] let notQuery: string[] = [] let additionalQuery: string[] = [] let doAdditionalQuery = false if (requestedQuery.firstName != undefined) { if (typeof requestedQuery.firstName == 'string') { query.push(`firstName:${requestedQuery.firstName.replace(' ', '?')}`) } } if (requestedQuery.notfirstName != undefined) { if (typeof requestedQuery.notfirstName === 'string') { notQuery.push(`firstName:${requestedQuery.notfirstName.replace(' ', '?')}`) } else if (Array.isArray(requestedQuery.notfirstName)) { requestedQuery.notfirstName.forEach((notItem: any) => { notQuery.push(`firstName:${notItem.replace(' ', '?')}`) }) } } if (requestedQuery.lastName != undefined) { if (typeof requestedQuery.lastName == 'string') { query.push(`lastName:${requestedQuery.lastName.replace(' ', '?')}`) } } if (requestedQuery.notlastName != undefined) { if (typeof requestedQuery.notlastName === 'string') { notQuery.push(`lastName:${requestedQuery.notlastName.replace(' ', '?')}`) } else if (Array.isArray(requestedQuery.notlastName)) { requestedQuery.notlastName.forEach((notItem: any) => { notQuery.push(`lastName:${notItem.replace(' ', '?')}`) }) } } if (requestedQuery.birthYear != undefined) { if (typeof requestedQuery.birthYear == 'string') { query.push(`birthYear:${requestedQuery.birthYear}`) } } if (requestedQuery.notbirthYear != undefined) { if (typeof requestedQuery.notbirthYear === 'string') { notQuery.push(`birthYear:${requestedQuery.notbirthYear.replace(' ', '?')}`) } else if (Array.isArray(requestedQuery.notbirthYear)) { requestedQuery.notbirthYear.forEach((notItem: any) => { notQuery.push(`birthYear:${notItem.replace(' ', '?')}`) }) } } if (requestedQuery.ips != undefined) { if (typeof requestedQuery.ips == 'string') { query.push(`ips:WRfKdFVogXnk82${requestedQuery.ips}WRfKdFVogXnk82`) } } if (requestedQuery.notips != undefined) { if (typeof requestedQuery.notips === 'string') { notQuery.push(`ips:WRfKdFVogXnk82${requestedQuery.notips}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notips)) { requestedQuery.notips.forEach((notItem: any) => { notQuery.push(`ips:WRfKdFVogXnk82${notItem}WRfKdFVogXnk82`) }) } } if (requestedQuery.asn != undefined) { if (typeof requestedQuery.asn == 'string') { query.push(`asn:${requestedQuery.asn}`) } } if (requestedQuery.notasn != undefined) { if (typeof requestedQuery.notasn === 'string') { notQuery.push(`asn:${requestedQuery.notasn.replace(' ', '?')}`) } else if (Array.isArray(requestedQuery.notasn)) { requestedQuery.notasn.forEach((notItem: any) => { notQuery.push(`asn:${notItem.replace(' ', '?')}`) }) } } if (requestedQuery.domain != undefined) { if (requestedQuery.exact) { if (typeof requestedQuery.domain == 'string') { query.push(`domain:WRfKdFVogXnk82${requestedQuery.domain.replace(' ', '?')}WRfKdFVogXnk82`) } } else { if (typeof requestedQuery.domain == 'string') { query.push(`domain:${requestedQuery.domain.replace(' ', '?')}`) } } } if (requestedQuery.notdomain != undefined) { if (typeof requestedQuery.notdomain === 'string') { notQuery.push(`domain:WRfKdFVogXnk82${requestedQuery.notdomain.replace(' ', '?')}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notdomain)) { requestedQuery.notdomain.forEach((notItem: any) => { notQuery.push(`domain:WRfKdFVogXnk82${notItem.replace(' ', '?')}WRfKdFVogXnk82`) }) } } if (requestedQuery.asnOrg != undefined) { if (typeof requestedQuery.asnOrg == 'string') { query.push(`asnOrg:WRfKdFVogXnk82${requestedQuery.asnOrg}WRfKdFVogXnk82`) } } if (requestedQuery.notasnOrg != undefined) { if (typeof requestedQuery.notasnOrg === 'string') { notQuery.push(`asnOrg:${requestedQuery.notasnOrg.replace(' ', '?')}`) } else if (Array.isArray(requestedQuery.notasnOrg)) { requestedQuery.notasnOrg.forEach((notItem: any) => { notQuery.push(`asnOrg:WRfKdFVogXnk82${notItem.replace(' ', '?')}WRfKdFVogXnk82`) }) } } if (requestedQuery.country != undefined) { if (typeof requestedQuery.country == 'string') { query.push(`country:WRfKdFVogXnk82${requestedQuery.country}WRfKdFVogXnk82`) } } if (requestedQuery.notcountry != undefined) { if (typeof requestedQuery.notcountry === 'string') { notQuery.push(`country:WRfKdFVogXnk82${requestedQuery.notcountry.replace(' ', '?')}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notcountry)) { requestedQuery.notcountry.forEach((notItem: any) => { notQuery.push(`country:WRfKdFVogXnk82${notItem.replace(' ', '?')}WRfKdFVogXnk82`) }) } } if (requestedQuery.continent != undefined) { if (typeof requestedQuery.continent == 'string') { query.push(`continent:WRfKdFVogXnk82${requestedQuery.continent}WRfKdFVogXnk82`) } } if (requestedQuery.notcontinent != undefined) { if (typeof requestedQuery.notcontinent === 'string') { notQuery.push(`continent:WRfKdFVogXnk82${requestedQuery.notcontinent.replace(' ', '?')}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notcontinent)) { requestedQuery.notcontinent.forEach((notItem: any) => { notQuery.push(`continent:WRfKdFVogXnk82${notItem.replace(' ', '?')}WRfKdFVogXnk82`) }) } } if (requestedQuery.firstName != undefined && requestedQuery.lastName != undefined && typeof requestedQuery.firstName == 'string' && typeof requestedQuery.lastName == 'string') { const firstName = requestedQuery.firstName.replace(' ', '?') const lastName = requestedQuery.lastName.replace(' ', '?') if (!requestedQuery.exact) { additionalQuery.push(`emails:${firstName}?${lastName}`) doAdditionalQuery = true } } if (requestedQuery.source != undefined) { if (typeof requestedQuery.source == 'string') { query.push(`source:WRfKdFVogXnk82${requestedQuery.source}WRfKdFVogXnk82`) } } if (requestedQuery.notsource != undefined) { if (typeof requestedQuery.notsource === 'string') { notQuery.push(`source:WRfKdFVogXnk82${requestedQuery.notsource.replace(' ', '?')}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notsource)) { requestedQuery.notsource.forEach((notItem: any) => { notQuery.push(`source:WRfKdFVogXnk82${notItem.replace(' ', '?')}WRfKdFVogXnk82`) }) } } if (requestedQuery.emails != undefined) { if (typeof requestedQuery.emails == 'string') { // check if there's a * in the email if (requestedQuery.emails.indexOf('*') != -1) { query.push(`emails:${requestedQuery.emails.replace('@', '?')}`) } else { if (requestedQuery.exact) { query.push(`emails:WRfKdFVogXnk82${requestedQuery.emails.replace('@', '?')}WRfKdFVogXnk82`) } else { query.push(`emails:${requestedQuery.emails.replace('@', '?')}`) } } const mostCommonEmailDomains = [/\@gmail\..*/gi, /\@yahoo\..*/gi, /\@hotmail\..*/gi, /\@outlook\..*/gi, /\@aol\..*/gi, /\@icloud\..*/gi, /\@mail\..*/gi, /\@protonmail\..*/gi, /\@zoho\..*/gi, /\@msn\..*/gi, /\@yandex\..*/gi, /\@gmx\..*/gi, /\@live\..*/gi, /\@mail\.ru\..*/gi, /\@inbox\..*/gi, /\@ymail\..*/gi, /\@comcast\..*/gi, /\@verizon\..*/gi, /\@att\..*/gi, /\@sbcglobal\..*/gi, /\@cox\..*/gi, /\@earthlink\..*/gi, /\@charter\..*/gi, /\@optonline\..*/gi, /\@frontier\..*/gi, /\@windstream\..*/gi, /\@q\.com\..*/gi, /\@btinternet\..*/gi, /\@btconnect\..*/gi, /\@ntlworld\..*/gi, /\@bt\..*/gi, /\@virginmedia\..*/gi, /\@btopenworld\..*/gi, /\@talktalk\..*/gi, /\@sky\..*/gi, /\@orange\..*/gi, /\@bt\..*/gi, /\@virgin\..*/gi, /\@ntl\..*/gi, /\@freeserve\..*/gi, /\@blueyonder\..*/gi, /\@btinternet\..*/gi, /\@tiscali\..*/gi, /\@virgin\..*/gi, /\@tesco\..*/gi, /\@onetel\..*/gi, /\@bt\..*/gi, /\@virgin\..*/gi, /\@ntl\..*/gi, /\@freeserve\..*/gi, /\@blueyonder\..*/gi, /\@btinternet\..*/gi, /\@tiscali\..*/gi, /\@virgin\..*/gi, /\@tesco\..*/gi, /\@onetel\..*/gi, /\@bt\..*/gi, /\@virgin\..*/] const emailSplit = requestedQuery.emails.split('@') if (requestedQuery.emails.indexOf('*') == -1) { additionalQuery.push(`emails:WRfKdFVogXnk82${emailSplit[0]}WRfKdFVogXnk82`) } doAdditionalQuery = true; let isCommonDomain = false; for (const domain of mostCommonEmailDomains) { if (domain.test(requestedQuery.emails as string)) { isCommonDomain = true } } if (!isCommonDomain) { if (emailSplit.length === 2) { const domain = emailSplit[emailSplit.length - 1] additionalQuery.push(`emails:${domain}`) doAdditionalQuery = true } } } } if (requestedQuery.notemails != undefined) { if (typeof requestedQuery.notemails === 'string') { notQuery.push(`emails:WRfKdFVogXnk82${requestedQuery.notemails.replace('@', '?')}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notemails)) { requestedQuery.notemails.forEach((notItem: any) => { notQuery.push(`emails:WRfKdFVogXnk82${notItem.replace('@', '?')}WRfKdFVogXnk82`) }) } } if (requestedQuery.VRN != undefined) { if (typeof requestedQuery.VRN == 'string') { query.push(`VRN:${requestedQuery.VRN.toLowerCase()}`) } } if (requestedQuery.notVRN != undefined) { if (typeof requestedQuery.notVRN === 'string') { notQuery.push(`VRN:${requestedQuery.notVRN.toLowerCase()}`) } else if (Array.isArray(requestedQuery.notVRN)) { requestedQuery.notVRN.forEach((notItem: any) => { notQuery.push(`VRN:${notItem.toLowerCase()}`) }) } } if (requestedQuery.usernames != undefined) { if (requestedQuery.exact) { query.push(`usernames:WRfKdFVogXnk82${requestedQuery.usernames}WRfKdFVogXnk82`) } else { query.push(`usernames:${requestedQuery.usernames}`) } } if (requestedQuery.notusernames != undefined) { if (typeof requestedQuery.notusernames === 'string') { notQuery.push(`usernames:${requestedQuery.notusernames}`) } else if (Array.isArray(requestedQuery.notusernames)) { requestedQuery.notusernames.forEach((notItem: any) => { notQuery.push(`usernames:${notItem.toLowerCase()}`) }) } } if (requestedQuery.address != undefined) { query.push(`address:WRfKdFVogXnk82${requestedQuery.address}WRfKdFVogXnk82`) } if (requestedQuery.notaddress != undefined) { if (typeof requestedQuery.notaddress === 'string') { notQuery.push(`address:WRfKdFVogXnk82${requestedQuery.notaddress}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notaddress)) { requestedQuery.notaddress.forEach((notItem: any) => { notQuery.push(`address:WRfKdFVogXnk82${notItem}WRfKdFVogXnk82`) }) } } if (requestedQuery.city != undefined) { query.push(`city:${requestedQuery.city}`) } if (requestedQuery.notcity != undefined) { if (typeof requestedQuery.notcity === 'string') { notQuery.push(`city:WRfKdFVogXnk82${requestedQuery.notcity}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notcity)) { requestedQuery.notcity.forEach((notItem: any) => { notQuery.push(`city:WRfKdFVogXnk82${notItem}WRfKdFVogXnk82`) }) } } if (requestedQuery.zipCode != undefined) { query.push(`zipCode:${requestedQuery.zipCode}`) } if (requestedQuery.notzipCode != undefined) { if (typeof requestedQuery.notzipCode === 'string') { notQuery.push(`address:WRfKdFVogXnk82${requestedQuery.notzipCode}WRfKdFVogXnk82`) } else if (Array.isArray(requestedQuery.notzipCode)) { requestedQuery.notzipCode.forEach((notItem: any) => { notQuery.push(`address:WRfKdFVogXnk82${notItem}WRfKdFVogXnk82`) }) } } if (requestedQuery.state != undefined) { query.push(`state:${requestedQuery.state}`) } if (requestedQuery.notstate != undefined) { if (typeof requestedQuery.notstate === 'string') { notQuery.push(`state:${requestedQuery.notstate}`) } else if (Array.isArray(requestedQuery.notstate)) { requestedQuery.notstate.forEach((notItem: any) => { notQuery.push(`state:${notItem}`) }) } } if (requestedQuery.phoneNumbers != undefined && typeof requestedQuery.phoneNumbers == 'string') { if (!requestedQuery.exact) { query.push(`phoneNumbers:WRfKdFVogXnk82${requestedQuery.phoneNumbers.replace(/\D+/g, "")}WRfKdFVogXnk82`) } else { // Replace all non-digits or non-question marks with query.push(`phoneNumbers:WRfKdFVogXnk82${requestedQuery.phoneNumbers.replace(/\D+/g, "")}WRfKdFVogXnk82`) additionalQuery.push(`phoneNumbers:1${requestedQuery.phoneNumbers.replace(/\D+/g, "")}`) additionalQuery.push(`phoneNumbers:7${requestedQuery.phoneNumbers.replace(/\D+/g, "")}`) doAdditionalQuery = true; } } if (requestedQuery.notphoneNumbers != undefined) { if (typeof requestedQuery.notphoneNumbers === 'string') { notQuery.push(`phoneNumbers:${requestedQuery.notphoneNumbers}`) } else if (Array.isArray(requestedQuery.notphoneNumbers)) { requestedQuery.notphoneNumbers.forEach((notItem: any) => { notQuery.push(`phoneNumbers:${notItem}`) }) } } if (requestedQuery.passwords != undefined && typeof requestedQuery.passwords === 'string') { query.push(`passwords:${requestedQuery.passwords}`) } else if (requestedQuery.passwords != undefined && Array.isArray(requestedQuery.passwords)) { requestedQuery.passwords.forEach((password) => { orQuery.push(`passwords:WRfKdFVogXnk82${password}WRfKdFVogXnk82`) }) } if (requestedQuery.notpasswords != undefined) { if (typeof requestedQuery.notpasswords === 'string') { notQuery.push(`passwords:${requestedQuery.notpasswords}`) } else if (Array.isArray(requestedQuery.notpasswords)) { requestedQuery.notpasswords.forEach((notItem: any) => { notQuery.push(`passwords:${notItem}`) }) } } if (requestedQuery.vin != undefined && typeof requestedQuery.vin === 'string') { query.push(`vin:${requestedQuery.vin}`) } else if (requestedQuery.vin != undefined && Array.isArray(requestedQuery.vin)) { requestedQuery.vin.forEach((vin) => { orQuery.push(`vin:WRfKdFVogXnk82${vin}WRfKdFVogXnk82`) }) } let queryBuilt = sanitizeQuery(query.join(' AND ')) if (orQuery.length > 0) { if (query.length > 0) { queryBuilt += ' OR ' + sanitizeQuery(orQuery.join(' OR ')) } else { queryBuilt += sanitizeQuery(orQuery.join(' OR ')) } } if (notQuery.length > 0) { if (query.length > 0) { queryBuilt += ' NOT ' + sanitizeQuery(notQuery.join(' NOT ')) } else { return res.status(400).send("Error: you sent an negative query without a regular query! Silly goose.") } } return { query: queryBuilt, additionalQuery: additionalQuery, doAdditionalQuery: doAdditionalQuery } } async function getSimilarRecords(record: SolrRecord) { let responses: any[] = [] const scoresList: Record = {} const relatedDocIDs: string[] = [] await Promise.all([ ...(record.emails || []).map((email) => { return axios.get(getRandServer(), { params: { q: `emails:"${email.replace(/\"/gi, '').replace(/\//gi, '')}"`, rows: 20, fl: 'id' } }).then(({ data }) => { data.response.docs.forEach((doc: SolrRecord) => { if (doc.id === record.id) { return } if (!(doc.id in scoresList)) { scoresList[doc.id] = 15 } else { scoresList[doc.id] += 5 } relatedDocIDs.push(doc.id) }) }) }), ...(record.usernames || []).map((email) => { return axios.get(getRandServer(), { params: { q: `usernames:"${email.replace(/\"/gi, '').replace(/\//gi, '')}"`, rows: 20, fl: 'id' } }).then(({ data }) => { data.response.docs.forEach((doc: SolrRecord) => { if (doc.id === record.id) { return } if (!(doc.id in scoresList)) { scoresList[doc.id] = 15 } else { scoresList[doc.id] += 5 } relatedDocIDs.push(doc.id) }) }) }), axios.get(getRandServer(), { params: { q: `id:${record.id}`, mlt: true, 'mlt.fl': 'emails', 'mlt.mindf': 2, 'mlt.mintf': 2, } }).then(({ data }) => { data.moreLikeThis.forEach((doc: any) => { if (typeof doc == 'object') { doc.docs.forEach((relatedDoc: any) => { if (scoresList[relatedDoc.id]) { scoresList[relatedDoc.id] += relatedDoc.score } else { scoresList[relatedDoc.id] = relatedDoc.score } }) } }) responses.push(...data.moreLikeThis) }), axios.get(getRandServer(), { params: { q: `id:${record.id}`, mlt: true, 'mlt.fl': 'usernames', 'mlt.mindf': 1, 'mlt.mintf': 1, 'mlt.match.include': true, } }).then(({ data }) => { data.moreLikeThis.forEach((doc: any) => { if (typeof doc == 'object') { doc.docs.forEach((relatedDoc: any) => { if (scoresList[relatedDoc.id]) { scoresList[relatedDoc.id] += relatedDoc.score } else { scoresList[relatedDoc.id] = relatedDoc.score } }) } }) responses.push(...data.moreLikeThis) }), axios.get(getRandServer(), { params: { q: `id:${record.id}`, mlt: true, 'mlt.fl': 'phoneNumbers', 'mlt.mindf': 1, 'mlt.mintf': 1, 'mlt.match.include': true, } }).then(({ data }) => { data.moreLikeThis.forEach((doc: any) => { if (typeof doc == 'object') { doc.docs.forEach((relatedDoc: any) => { if (scoresList[relatedDoc.id]) { scoresList[relatedDoc.id] += relatedDoc.score } else { scoresList[relatedDoc.id] = relatedDoc.score } }) } }) responses.push(...data.moreLikeThis) }), axios.get(getRandServer(), { params: { q: `id:${record.id}`, mlt: true, 'mlt.fl': 'address_search', 'mlt.mindf': 1, 'mlt.mintf': 1, 'mlt.match.include': true, } }).then(({ data }) => { data.moreLikeThis.forEach((doc: any) => { if (typeof doc == 'object') { doc.docs.forEach((relatedDoc: any) => { if (scoresList[relatedDoc.id]) { scoresList[relatedDoc.id] += relatedDoc.score } else { scoresList[relatedDoc.id] = relatedDoc.score } }) } }) responses.push(...data.moreLikeThis) }), axios.get(getRandServer(), { params: { q: `id:${record.id}`, mlt: true, 'mlt.fl': 'firstName,lastName', 'mlt.mindf': 1, 'mlt.mintf': 1, 'mlt.match.include': true, } }).then(({ data }) => { data.moreLikeThis.forEach((doc: any) => { if (typeof doc == 'object') { doc.docs.forEach((relatedDoc: any) => { if (scoresList[relatedDoc.id]) { scoresList[relatedDoc.id] += relatedDoc.score } else { scoresList[relatedDoc.id] = relatedDoc.score } }) } }) responses.push(...data.moreLikeThis) }), ]) responses.forEach((response) => { if (typeof response == 'object') { relatedDocIDs.push(...(response.docs.map((doc: any) => doc.id))) } }) let docs: SolrRecord[] = [] // Get unique doc IDs const uniqueDocIDs = [...new Set(relatedDocIDs)] await Promise.all(uniqueDocIDs.map((doc: any) => queryForDocs(`id:${doc}`))).then((results) => { results.forEach((result) => { docs.push(...result.records.map((result) => { return { ...result, 'similarity score': scoresList[result.id] || 0, } })) }) }) // Sort the docs by score docs = docs.sort((a, b) => { if (scoresList[a.id] > scoresList[b.id]) { return -1 } else if (scoresList[a.id] < scoresList[b.id]) { return 1 } else { return 0 } }) return docs } function sanitizeQuery(query: string) { //return query; return query.replace(/[^\w\s\$\:\.\@\-\*\а-яА-ЯёЁ]/gi, '?').replace(/WRfKdFVogXnk82/g, '"') } app.get('/exports', async (req, res) => { const rendered = mustache.render(exportsTemplate, {}) return res.send(rendered) }) app.get('/exports/:walletid', async (req, res) => { try { const walletid = req.params.walletid // Ensure the wallet ID is valid if (!uuidRegex.test(walletid)) { return res.status(400).json({ error: 'Invalid wallet ID.' }) } const records = await queryForExportDocs(`wallet:"${walletid}"`) return res.json(records.records.map((record: any) => { return { ...record, jobid: record.id, exportCount: record.count, } })) } catch (e) { return res.status(500).json([ { id: 'internal error', query: 'sorry bout that... try again?' } ]) } }) app.get('/', async (req, res) => { const rendered = mustache.render(indexTemplate, { count: "14,491,682,918" }) return res.send(rendered) }) app.get('/visualize', async (req, res) => { const rendered = mustache.render(visualizerTemplate, {}) return res.send(rendered) }) app.get('/map', async (req, res) => { res.sendFile(__dirname + '/static/map.html') }) app.get('/favicon.ico', (req, res) => { // Read the favicon file and send it to the client res.sendFile(__dirname + '/static/favicon.ico') }) app.get('/robots.txt', (req, res) => { // Read robots.txt and send it to the client res.sendFile(__dirname + '/static/robots.txt') }) app.get('/dear_peter', (req, res) => { res.sendFile(__dirname + '/static/dear_peter.html') }) app.get('/terms', (req, res) => { res.sendFile(__dirname + '/static/terms.html') }) app.get('/faq', (req, res) => { res.sendFile(__dirname + '/static/faq.html') }) app.get('/canary', (req, res) => { res.sendFile(__dirname + '/static/canary.html') }) app.get('/donations', (req, res) => { // Render out the donations page tempate const rendered = mustache.render(donationsTemplate, { donations: donations.sort((a: any, b: any) => b.amount - a.amount) }) return res.send(rendered) }) app.post('/export', async (req, res) => { try { // Read body let body = req.body // Check if the body is valid if (!body || !body.walletId || !body.exportCount) { return res.status(400).send('Invalid body') } // Make sure export count is > 0 if (typeof body.exportCount !== 'number') { // Set it to be a number body.exportCount = parseInt(body.exportCount, 10) } if (body.exportCount <= 0) { return res.status(400).send('Invalid export count') } // If the wallet ID is invalid uuid4 if (!uuidRegex.test(body.walletId)) { return res.status(400).send('Invalid wallet ID') } // Get credits for the wallet const credits = await getWalletBalance(body.walletId) const cost = (body.exportCount - 100) / 10 if (cost > credits) { return res.status(400).send('Insufficient credits') } // if cost < 0 if (cost < 0) { return res.status(400).send('Invalid export count') } let requestedQuery: Record = {} for (const [key, value] of Object.entries(req.query)) { if (key !== 'wt') { if (typeof value === 'string' || Array.isArray(value)) { requestedQuery[key] = value } else { console.log(`Invalid query: ${key} is not a string or array. ${value}`) } } } const queryBuilt = buildQuery(requestedQuery, res as any) as any const jobid = uuidv4() if (!queryBuilt.query || !queryBuilt.additionalQuery || typeof queryBuilt.doAdditionalQuery !== 'boolean') { return res.status(400).send('Invalid query') } let finalQuery = queryBuilt.query if (queryBuilt.additionalQuery.length > 0) { finalQuery = `(${finalQuery}) OR ${sanitizeQuery(queryBuilt.additionalQuery.join(' OR '))}` } const payload = { "status": "started", "jobid": jobid, "cost": cost, "query": finalQuery, "additionalQuery": queryBuilt.additionalQuery, "doAdditionalQuery": queryBuilt.doAdditionalQuery, "exportCount": body.exportCount, "success": true, "walletId": body.walletId } // Return to client that the job has been started res.json(payload) // End the response res.end() // Deduct clients from wallet await removeWalletBalance(body.walletId, cost) // Post to wallet DB await axios.post(getRandServer().replace('BigData', 'Exports').replace('select', 'update'), { "add": { "doc": { // uuid v4 "id": payload.jobid, "cost": payload.cost, "wallet": payload.walletId, "query": payload.query, "status": payload.status, 'count': payload.exportCount, } } }, { params: { commit: true } }) // Base64 encode payload const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64') // asyncronously run python3 exports/doExport.py const pythonProcess = spawn('python3', ['exports/doExport.py', encodedPayload]) // Listen for data from the python process pythonProcess.stdout.on('data', (data) => { console.log(`Export Job ${jobid}: ${data}`); }) // Listen for errors from the python process pythonProcess.stderr.on('data', (data) => { console.error(`Export Job ${jobid}: ${data}`); }) } catch (e) { console.error(e) res.status(400).send('stoooop') } }) app.post('/exports/callbacks/a-unique-id/exportCb', async (req, res) => { // Read body const body = req.body if (body.status === 'failed') { // Re-credit wallet await addWalletBalance(body.walletId, body.cost) } // Post to db await axios.post(getRandServer().replace('BigData', 'Exports').replace('select', 'update'), { "add": { "doc": { // uuid v4 "id": body.jobid, "cost": body.cost, "wallet": body.walletId, "query": body.query, "status": body.status, 'count': body.exportCount, "link": body.link } } }, { params: { commit: true } }) return res.status(200) }) app.get('/wallet/:walletId', async (req, res) => { try { const walletId = req.params.walletId if (!uuidRegex.test(walletId)) { console.log('bad wallet id regex') return res.status(400).json({ "error": "bad wallet id" }) } if (typeof walletId !== 'string') { return res.status(400).json({ "error": "bad request" }) } if (walletId.length !== 36) { console.log('bad wallet id length') return res.status(400).json({ "error": "bad wallet id" }) } let balance = await getWalletBalance(walletId) return res.json({ "credits": balance }) } catch { return res.status(400).json({ "error": "stooppp" }) } }) function isAutomated(req: any) { // Check if ip is in blacklist const connectingIp = req.headers['cf-connecting-ip'] || req.connection.remoteAddress; if (blacklistedAutomatedIps.includes(connectingIp)) { console.log(`BLACKLISTED IP ${connectingIp}`) return true } // Check if user agent has axios in it const userAgent = req.headers['user-agent'] if (whitelistedUserAgents.some((whitelistedAgent) => userAgent.includes(whitelistedAgent))) { return false } if (userAgent != undefined) { if (typeof userAgent == 'string') { if (Object.keys(req.query).length === 1 && req.query.emails) { // Check if the user has gotten their wallet const connectingIp = req.headers['cf-connecting-ip'] if (typeof connectingIp === 'string') { if (hasRequestedWallet.includes(connectingIp)) { return false } } } } } return false } async function checkIPAutomatedSTDDEV(req: any) { const ip = req.headers['cf-connecting-ip'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent'] if (whitelistedUserAgents.some((whitelistedAgent) => userAgent.includes(whitelistedAgent))) { return false } if (ip === '0.0.0.0' || !ip) { // Hello, KT :3c console.log('wat r u doin') return true } else if (typeof ip === 'string') { if (numLookupsPerIP[ip] === undefined) { numLookupsPerIP[ip] = 0 } // If no last query time if (lastQueryTime[ip] === undefined) { lastQueryTime[ip] = Date.now() return false } else { // Get the time between the last query and now const timeSinceLastQuery = (Date.now() - lastQueryTime[ip]) console.log(`Time since last query for ${ip}: ${timeSinceLastQuery}`) numLookupsPerIP[ip] += 1 console.log(ip + ' has ' + numLookupsPerIP[ip] + ' lookups.') if (numLookupsPerIP[ip] > 200) { blacklistedAutomatedIps.push(ip) return true } // Add current request to lookup table if (lastQueryTimes[ip] === undefined) { lastQueryTimes[ip] = [] } // If there's less than 40 values in the array, add the current time if (lastQueryTimes[ip].length < 40) { lastQueryTimes[ip].push(timeSinceLastQuery) } // If there's more than 40 values in the array, remove the first value and add the current time else if (lastQueryTimes[ip].length >= 40) { lastQueryTimes[ip].shift() lastQueryTimes[ip].push(timeSinceLastQuery) } // If there's more than 20 values in the array, calculate the standard deviation between the values if (lastQueryTimes[ip].length >= 20) { const standardDeviation = ss.standardDeviation(lastQueryTimes[ip]) console.log(`Standard deviation for ${ip}: ${standardDeviation}`) // If the standard deviation is less than 1000, the user is probably a bot if (standardDeviation < 1800) { console.log('erm haht the duence') blacklistedAutomatedIps.push(ip) return true } } // If all of the requests have less than 2000 ms of delay: if (lastQueryTimes[ip].length >= 20) { if (lastQueryTimes[ip].every((request) => request < 5000)) { blacklistedAutomatedIps.push(ip) return true } } lastQueryTime[ip] = Date.now() return false; } } else { console.log('erm haht the duence') return true } return false } async function doAutomatedRes(res: any, json?: boolean) { if (json) { return res.json({ resultCount: 69420, count: 69420, records: [ { id: uuidv4(), firstName: '[[EXTREMELY LOUD INCORRECT BUZZER]]', lastName: 'Automated scraping detected. Please contact miyakoyakota@riseup.com to be whitelisted.', email: 'This data comes from https://search.illicit.services and it is free to use. Do not pay for access to this data.', } ] }) } else { const rendered = mustache.render(recordsListingTemplate, { resultCount: 69420, count: 69420, records: [ { id: uuidv4(), firstName: '[[EXTREMELY LOUD INCORRECT BUZZER]]', fields: [ "firstName: [[EXTREMELY LOUD INCORRECT BUZZER]]", "lastName: Automated scraping detected. Please contact miyakoyakota@riseup.com to be whitelisted. If you would like to use this site as an API, you may add ?wt=json to return JSON.", "email: This data comes from https://search.illicit.services and it is free to use. Do not pay for access to this data.", ] } ] }) return res.send(rendered) } } app.get('/documents/by_id/:id', async (req, res) => { if (isAutomated(req)) { return doAutomatedRes(res, req.query.wt === 'json') } if (await checkIPAutomatedSTDDEV(req)) { return doAutomatedRes(res, req.query.wt === 'json') } const records = await queryForDocs(`id:${req.params.id}`) const record = records.records[0] if (record === undefined) { return res.status(404).send('No record found.') } // If wt=json if (req.query.wt == 'json') { // check moreLikeThis if (req.query.moreLikeThis == 'true') { const similarRecords = await getSimilarRecords(record).catch((err) => { console.log(err) return [] }) return res.json({ record: record, related: similarRecords }) } else { return res.json({ record: record }) } } const similarRecords = await getSimilarRecords(record).catch((err) => { return [] }) const rendered = mustache.render(recordByIdTemplate, { id: record.id, record: Object.entries(_.omit(record, ['id', '_version_'])).map(([key, value]) => { return { key: key, value: value } }), related: similarRecords.map((record) => { return { id: record.id, record: Object.entries(_.omit(record, ['id', '_version_'])).map(([key, value]) => { return { key: key, value: value } }) } }) }) return res.send(rendered) }) function makeid(length: number) { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; let counter = 0; while (counter < length) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); counter += 1; } return result; } app.get('/spatial', async (req, res) => { if (req.query.latLong != undefined && typeof req.query.latLong === 'string') { // Validate latLong using regex const latLongRegex = /^-?\d{1,3}(?:\.\d{1,20})?,-?\d{1,3}(?:\.\d{1,20})?$/; // Validate d within 0.1 and 1000 if (typeof req.query.d !== 'string' && req.query.d !== undefined) { return res.send({ error: true, errorMessage: 'Please provide a valid d between 0.1 and 1000' }) } const d = parseFloat(req.query.d || '0.2') if (d < 0.1 || d > 1000) { return res.send({ error: true, errorMessage: 'Please provide a valid d between 0.1 and 1000' }) } if (latLongRegex.test(req.query.latLong)) { let recordsResponse = await queryForDocsSpatial(req.query.latLong, d).catch((err) => { return { numDocs: 0, records: [] as SolrRecord[] } }) res.json(recordsResponse) } else { // latLong is not valid return res.send({ error: true, errorMessage: 'Please only send coordinates as lat,long' }) } } else { res.json({ error: true, errorMessage: "Provide a latLong! Dumbo." }) } }) app.get('/records', async (req, res) => { try { const userAgent = req.headers['user-agent'] if (!(userAgent === 'yeayeyayaeyayeayeyaeyeayyaeyeyaeyae')) { if (isAutomated(req)) { return doAutomatedRes(res, req.query.wt === 'json') } } if (req.query.wt === 'json') { // Check if the user-agent is in the list of allowed user-agents const apiKey = req.query.apikey || req.headers['user-agent'] if (typeof apiKey === 'string') { if (whitelistedUserAgents.includes(apiKey)) { // User agent is allowed // Continue } else { console.log(`IP ${req.headers['cf-connecting-ip'] || req.connection.remoteAddress} tried to access the API with user-agent ${apiKey}`) // User agent is not allowed res.status(403) return res.json({ error: true, message: 'API Access requires a free API token. Please contact miyakoyakota@riseup.com to get one.' }) } } else { return res.json({ error: true, message: 'API Access requires a free API token. Please contact miyakoyakota@riseup.com to get one.' }) } } let requestedQuery: Record = {} for (const [key, value] of Object.entries(req.query)) { if (key !== 'wt') { if (typeof value === 'string' || Array.isArray(value)) { requestedQuery[key] = value } else { console.log(`Invalid query: ${key} is not a string or array. ${value}`) } } } const queryBuilt = buildQuery(requestedQuery, res as any) as any if (!Boolean(queryBuilt.query)) { return res.status(400).send('no query!!!') } // Check if type is express.Response if (queryBuilt === undefined) { return res.status(400).send('no query!!!') } if (!queryBuilt.query || !queryBuilt.additionalQuery || typeof queryBuilt.doAdditionalQuery !== 'boolean') { return res.status(400).send('no query!!!') } let totalRecordCount = 0 let recordsResponse: any = null recordsResponse = await queryForDocs(queryBuilt.query).catch((err) => { console.log(err) return { numDocs: 0, records: [] as SolrRecord[] } }) totalRecordCount += recordsResponse.numDocs let records = recordsResponse.records if (queryBuilt.doAdditionalQuery && !req.query.exact && req.query.exact != 'true' && req.query.exact != '1' && req.query.exact != 'yes' && req.query.exact != 'y' && req.query.exact != 'on' && req.query.exact != 't' && req.query.exact != 'sure' && req.query.exact != 'please' && req.query.exact != 'True') { const additionalQueryBuilt = sanitizeQuery(queryBuilt.additionalQuery.join(' OR ')) let additionalRecordsResponse: any = null if (typeof req.query.sofreshandsoclean === 'string') { additionalRecordsResponse = await queryForDocs(additionalQueryBuilt, 10000).catch((err) => { return { numDocs: 0, records: [] as SolrRecord[] } }) } else { additionalRecordsResponse = await queryForDocs(additionalQueryBuilt).catch((err) => { return { numDocs: 0, records: [] as SolrRecord[] } }) } totalRecordCount += additionalRecordsResponse.numDocs const additionalRecords = additionalRecordsResponse.records records.push(...additionalRecords) } let recordsFinal = records.map((record: any) => { return { ...record, canMap: Boolean(record.address) || Boolean(record.latLong), fields: Object.keys(_.omit(record, ['id', '_version_'])).map((key) => { return `${key}: ${(record as any)[key]}` }) } }) // Remove duplicates recordsFinal = _.uniqBy(recordsFinal, 'id') // If wt=json is set return as json if (req.query.wt == 'json') { return res.json({ resultCount: totalRecordCount, count: records.length, records: recordsFinal }) } else { const rendered = mustache.render(recordsListingTemplate, { resultCount: totalRecordCount, count: records.length, records: recordsFinal, finalquery: queryBuilt.query }) return res.send(rendered) } } catch (e) { res.status(500) } }) app.listen(port, '0.0.0.0', () => { console.log(`Example app listening on port ${port}`) })