mirror of
https://github.com/MiyakoYakota/search.0t.rocks.git
synced 2025-02-05 15:37:21 +00:00
1745 lines
57 KiB
TypeScript
1745 lines
57 KiB
TypeScript
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<string, string> = {
|
||
"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<string, string> = {
|
||
"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<string, number> = {}
|
||
const lastQueryTimes: Record<string, number[]> = {}
|
||
const numLookupsPerIP: Record<string, number> = {}
|
||
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<number> {
|
||
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<string, string | string[]>, 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<string, number> = {}
|
||
|
||
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<string, any> = {}
|
||
|
||
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 <encodedPayload>
|
||
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<string, any> = {}
|
||
|
||
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}`)
|
||
})
|