ItsXyron commited on
Commit
8a28ed2
·
verified ·
1 Parent(s): 59038fc

Add complete OSINT suite: UPI 360 Resolver, Vehicle RC, Challan, IFSC, Pincode, IP, PAN, GST

Browse files
Files changed (2) hide show
  1. external_services.py +335 -0
  2. main.py +110 -0
external_services.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import time
3
+ import requests
4
+ import logging
5
+ from typing import Dict, Any, Optional
6
+ from cachetools import TTLCache
7
+
8
+ logger = logging.getLogger("ExternalServices")
9
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
10
+
11
+ # In-memory TTL Cache (50,000 items, 24-hour expiration)
12
+ SERVICE_CACHE = TTLCache(maxsize=50000, ttl=86400)
13
+ OWNER_TAG = "@its_Xyron"
14
+ DEMO_UPSTREAM = "https://dark-api-hub.onrender.com/api"
15
+
16
+ def _clean_response(data: Any) -> Any:
17
+ """Recursively strip third-party promotional keys and inject official owner tag."""
18
+ if isinstance(data, dict):
19
+ cleaned = {}
20
+ for k, v in data.items():
21
+ if str(k).lower() in ("credit", "buy premium api from", "powered by", "dev"):
22
+ continue
23
+ cleaned[k] = _clean_response(v)
24
+ return cleaned
25
+ elif isinstance(data, list):
26
+ return [_clean_response(item) for item in data]
27
+ return data
28
+
29
+ class ServiceGateway:
30
+ @staticmethod
31
+ def get_ifsc(code: str) -> Dict[str, Any]:
32
+ """Ultra-fast IFSC Lookup with Razorpay primary and Dark-API fallback."""
33
+ clean_code = re.sub(r'[^a-zA-Z0-9]', '', str(code)).upper().strip()
34
+ cache_key = f"ifsc:{clean_code}"
35
+ if cache_key in SERVICE_CACHE:
36
+ cached = SERVICE_CACHE[cache_key]
37
+ cached["cached"] = True
38
+ return cached
39
+
40
+ t0 = time.time()
41
+ # 1. Primary: Razorpay Open IFSC
42
+ try:
43
+ r = requests.get(f"https://ifsc.razorpay.com/{clean_code}", timeout=4)
44
+ if r.status_code == 200:
45
+ data = r.json()
46
+ res = {
47
+ "status": "success",
48
+ "owner": OWNER_TAG,
49
+ "ifsc": clean_code,
50
+ "bank": data.get("BANK"),
51
+ "branch": data.get("BRANCH"),
52
+ "address": data.get("ADDRESS"),
53
+ "city": data.get("CITY"),
54
+ "district": data.get("DISTRICT"),
55
+ "state": data.get("STATE"),
56
+ "micr": data.get("MICR"),
57
+ "contact": data.get("CONTACT"),
58
+ "upi": data.get("UPI", True),
59
+ "rtgs": data.get("RTGS", True),
60
+ "neft": data.get("NEFT", True),
61
+ "imps": data.get("IMPS", True),
62
+ "latency_ms": round((time.time() - t0) * 1000, 2),
63
+ "cached": False
64
+ }
65
+ SERVICE_CACHE[cache_key] = res
66
+ return res
67
+ except Exception:
68
+ pass
69
+
70
+ # 2. Fallback
71
+ try:
72
+ r = requests.get(f"{DEMO_UPSTREAM}/ifsc?key=DEMO&ifsc={clean_code}", timeout=5)
73
+ if r.status_code == 200:
74
+ d = _clean_response(r.json())
75
+ res = {
76
+ "status": "success",
77
+ "owner": OWNER_TAG,
78
+ "ifsc": clean_code,
79
+ "bank": d.get("BANK"),
80
+ "branch": d.get("BRANCH"),
81
+ "address": d.get("ADDRESS"),
82
+ "city": d.get("CITY"),
83
+ "state": d.get("STATE"),
84
+ "data": d,
85
+ "latency_ms": round((time.time() - t0) * 1000, 2),
86
+ "cached": False
87
+ }
88
+ SERVICE_CACHE[cache_key] = res
89
+ return res
90
+ except Exception as e:
91
+ logger.error(f"IFSC error: {e}")
92
+
93
+ return {"status": "not_found", "owner": OWNER_TAG, "message": f"IFSC code '{clean_code}' not found."}
94
+
95
+ @staticmethod
96
+ def get_pincode(pin: str) -> Dict[str, Any]:
97
+ """Pincode resolution with multi-provider fallback."""
98
+ clean_pin = re.sub(r'\D', '', str(pin)).strip()
99
+ cache_key = f"pin:{clean_pin}"
100
+ if cache_key in SERVICE_CACHE:
101
+ cached = SERVICE_CACHE[cache_key]
102
+ cached["cached"] = True
103
+ return cached
104
+
105
+ t0 = time.time()
106
+ # Upstream query
107
+ try:
108
+ r = requests.get(f"{DEMO_UPSTREAM}/pincode?key=DEMO&pin={clean_pin}", timeout=5)
109
+ if r.status_code == 200:
110
+ data = _clean_response(r.json())
111
+ res = {
112
+ "status": "success",
113
+ "owner": OWNER_TAG,
114
+ "pincode": clean_pin,
115
+ "result": data.get("result", []),
116
+ "latency_ms": round((time.time() - t0) * 1000, 2),
117
+ "cached": False
118
+ }
119
+ SERVICE_CACHE[cache_key] = res
120
+ return res
121
+ except Exception as e:
122
+ logger.error(f"Pincode error: {e}")
123
+
124
+ return {"status": "not_found", "owner": OWNER_TAG, "message": f"Pincode '{clean_pin}' not found."}
125
+
126
+ @staticmethod
127
+ def get_ip(ip: str) -> Dict[str, Any]:
128
+ """IP Geolocation and ASN intelligence."""
129
+ clean_ip = str(ip).strip()
130
+ cache_key = f"ip:{clean_ip}"
131
+ if cache_key in SERVICE_CACHE:
132
+ cached = SERVICE_CACHE[cache_key]
133
+ cached["cached"] = True
134
+ return cached
135
+
136
+ t0 = time.time()
137
+ # Direct ip-api query
138
+ try:
139
+ r = requests.get(f"http://ip-api.com/json/{clean_ip}?fields=status,message,country,countryCode,region,regionName,city,zip,lat,lon,timezone,isp,org,as,query", timeout=4)
140
+ if r.status_code == 200:
141
+ d = r.json()
142
+ if d.get("status") == "success":
143
+ res = {
144
+ "status": "success",
145
+ "owner": OWNER_TAG,
146
+ "ip": clean_ip,
147
+ "country": d.get("country"),
148
+ "country_code": d.get("countryCode"),
149
+ "region": d.get("regionName"),
150
+ "city": d.get("city"),
151
+ "zip": d.get("zip"),
152
+ "lat": d.get("lat"),
153
+ "lon": d.get("lon"),
154
+ "timezone": d.get("timezone"),
155
+ "isp": d.get("isp"),
156
+ "org": d.get("org"),
157
+ "asn": d.get("as"),
158
+ "latency_ms": round((time.time() - t0) * 1000, 2),
159
+ "cached": False
160
+ }
161
+ SERVICE_CACHE[cache_key] = res
162
+ return res
163
+ except Exception:
164
+ pass
165
+
166
+ # Fallback
167
+ try:
168
+ r = requests.get(f"{DEMO_UPSTREAM}/ip?key=DEMO&ip={clean_ip}", timeout=5)
169
+ if r.status_code == 200:
170
+ d = _clean_response(r.json())
171
+ res = {
172
+ "status": "success",
173
+ "owner": OWNER_TAG,
174
+ "ip": clean_ip,
175
+ "data": d,
176
+ "latency_ms": round((time.time() - t0) * 1000, 2),
177
+ "cached": False
178
+ }
179
+ SERVICE_CACHE[cache_key] = res
180
+ return res
181
+ except Exception as e:
182
+ logger.error(f"IP info error: {e}")
183
+
184
+ return {"status": "not_found", "owner": OWNER_TAG, "message": f"IP '{clean_ip}' not found."}
185
+
186
+ @staticmethod
187
+ def get_vehicle_rc(vehicle_no: str) -> Dict[str, Any]:
188
+ """Vehicle RC Registration Details."""
189
+ clean_v = re.sub(r'[^a-zA-Z0-9]', '', str(vehicle_no)).upper().strip()
190
+ cache_key = f"rc:{clean_v}"
191
+ if cache_key in SERVICE_CACHE:
192
+ cached = SERVICE_CACHE[cache_key]
193
+ cached["cached"] = True
194
+ return cached
195
+
196
+ t0 = time.time()
197
+ try:
198
+ r = requests.get(f"{DEMO_UPSTREAM}/rc?key=DEMO&owner={clean_v}", timeout=7)
199
+ if r.status_code == 200:
200
+ d = _clean_response(r.json())
201
+ res = {
202
+ "status": "success",
203
+ "owner": OWNER_TAG,
204
+ "vehicle_number": clean_v,
205
+ "details": d.get("rc_x", {}).get("data") or d.get("data") or d,
206
+ "latency_ms": round((time.time() - t0) * 1000, 2),
207
+ "cached": False
208
+ }
209
+ SERVICE_CACHE[cache_key] = res
210
+ return res
211
+ except Exception as e:
212
+ logger.error(f"Vehicle RC error: {e}")
213
+
214
+ return {"status": "not_found", "owner": OWNER_TAG, "message": f"Vehicle '{clean_v}' records not found."}
215
+
216
+ @staticmethod
217
+ def get_vehicle_challan(vehicle_no: str) -> Dict[str, Any]:
218
+ """Vehicle Traffic Challan Lookup."""
219
+ clean_v = re.sub(r'[^a-zA-Z0-9]', '', str(vehicle_no)).upper().strip()
220
+ cache_key = f"challan:{clean_v}"
221
+ if cache_key in SERVICE_CACHE:
222
+ cached = SERVICE_CACHE[cache_key]
223
+ cached["cached"] = True
224
+ return cached
225
+
226
+ t0 = time.time()
227
+ try:
228
+ r = requests.get(f"{DEMO_UPSTREAM}/challan?key=DEMO&vehicle={clean_v}", timeout=7)
229
+ if r.status_code == 200:
230
+ d = _clean_response(r.json())
231
+ res = {
232
+ "status": "success",
233
+ "owner": OWNER_TAG,
234
+ "vehicle_number": clean_v,
235
+ "challans": d.get("data", {}).get("challan_details") or d.get("challan_details") or d,
236
+ "latency_ms": round((time.time() - t0) * 1000, 2),
237
+ "cached": False
238
+ }
239
+ SERVICE_CACHE[cache_key] = res
240
+ return res
241
+ except Exception as e:
242
+ logger.error(f"Vehicle Challan error: {e}")
243
+
244
+ return {"status": "not_found", "owner": OWNER_TAG, "message": f"No challans found for '{clean_v}'."}
245
+
246
+ @staticmethod
247
+ def get_pan_details(pan: str) -> Dict[str, Any]:
248
+ """PAN Verification & Entity Name."""
249
+ clean_pan = re.sub(r'[^a-zA-Z0-9]', '', str(pan)).upper().strip()
250
+ cache_key = f"pan:{clean_pan}"
251
+ if cache_key in SERVICE_CACHE:
252
+ cached = SERVICE_CACHE[cache_key]
253
+ cached["cached"] = True
254
+ return cached
255
+
256
+ t0 = time.time()
257
+ try:
258
+ r = requests.get(f"{DEMO_UPSTREAM}/pan?key=DEMO&pan={clean_pan}", timeout=6)
259
+ if r.status_code == 200:
260
+ d = _clean_response(r.json())
261
+ res = {
262
+ "status": "success",
263
+ "owner": OWNER_TAG,
264
+ "pan": clean_pan,
265
+ "details": d.get("data") or d,
266
+ "latency_ms": round((time.time() - t0) * 1000, 2),
267
+ "cached": False
268
+ }
269
+ SERVICE_CACHE[cache_key] = res
270
+ return res
271
+ except Exception as e:
272
+ logger.error(f"PAN error: {e}")
273
+
274
+ return {"status": "not_found", "owner": OWNER_TAG, "message": f"PAN '{clean_pan}' not found."}
275
+
276
+ @staticmethod
277
+ def get_gst_details(gstin: str) -> Dict[str, Any]:
278
+ """GST Registration & Business Details."""
279
+ clean_gst = re.sub(r'[^a-zA-Z0-9]', '', str(gstin)).upper().strip()
280
+ cache_key = f"gst:{clean_gst}"
281
+ if cache_key in SERVICE_CACHE:
282
+ cached = SERVICE_CACHE[cache_key]
283
+ cached["cached"] = True
284
+ return cached
285
+
286
+ t0 = time.time()
287
+ try:
288
+ r = requests.get(f"{DEMO_UPSTREAM}/gst?key=DEMO&gst={clean_gst}", timeout=6)
289
+ if r.status_code == 200:
290
+ d = _clean_response(r.json())
291
+ res = {
292
+ "status": "success",
293
+ "owner": OWNER_TAG,
294
+ "gstin": clean_gst,
295
+ "details": d.get("mastersindia") or d.get("data") or d,
296
+ "latency_ms": round((time.time() - t0) * 1000, 2),
297
+ "cached": False
298
+ }
299
+ SERVICE_CACHE[cache_key] = res
300
+ return res
301
+ except Exception as e:
302
+ logger.error(f"GST error: {e}")
303
+
304
+ return {"status": "not_found", "owner": OWNER_TAG, "message": f"GSTIN '{clean_gst}' not found."}
305
+
306
+ @staticmethod
307
+ def get_pan_to_gst(pan: str) -> Dict[str, Any]:
308
+ """PAN to linked GST numbers."""
309
+ clean_pan = re.sub(r'[^a-zA-Z0-9]', '', str(pan)).upper().strip()
310
+ cache_key = f"pantogst:{clean_pan}"
311
+ if cache_key in SERVICE_CACHE:
312
+ cached = SERVICE_CACHE[cache_key]
313
+ cached["cached"] = True
314
+ return cached
315
+
316
+ t0 = time.time()
317
+ try:
318
+ r = requests.get(f"{DEMO_UPSTREAM}/Pantogst?key=DEMO&pan={clean_pan}", timeout=6)
319
+ if r.status_code == 200:
320
+ d = _clean_response(r.json())
321
+ res = {
322
+ "status": "success",
323
+ "owner": OWNER_TAG,
324
+ "pan": clean_pan,
325
+ "gst_details": d.get("gst_details") or [],
326
+ "count": d.get("count", 0),
327
+ "latency_ms": round((time.time() - t0) * 1000, 2),
328
+ "cached": False
329
+ }
330
+ SERVICE_CACHE[cache_key] = res
331
+ return res
332
+ except Exception as e:
333
+ logger.error(f"PAN to GST error: {e}")
334
+
335
+ return {"status": "not_found", "owner": OWNER_TAG, "message": f"No GST linked to PAN '{clean_pan}'."}
main.py CHANGED
@@ -11,6 +11,7 @@ from fastapi.staticfiles import StaticFiles
11
  from pydantic import BaseModel, Field
12
 
13
  from engine import LookupEngine
 
14
  from auth import KeyManager, ADMIN_MASTER_KEY, INTERNAL_BOT_KEY
15
 
16
  start_time = time.time()
@@ -368,6 +369,115 @@ async def lookup_upi(
368
  return res
369
 
370
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  @app.get("/api/phone/{phone_number}")
372
  async def lookup_by_phone(phone_number: str, auth_meta: Dict[str, Any] = Depends(verify_api_key)):
373
  """Search Telecom 2.5B Database by phone number."""
 
11
  from pydantic import BaseModel, Field
12
 
13
  from engine import LookupEngine
14
+ from external_services import ServiceGateway
15
  from auth import KeyManager, ADMIN_MASTER_KEY, INTERNAL_BOT_KEY
16
 
17
  start_time = time.time()
 
369
  return res
370
 
371
 
372
+ # ---------------------------------------------------------
373
+ # Banking, Business, Vehicle & Network Intelligence APIs
374
+ # ---------------------------------------------------------
375
+ @app.get("/api/ifsc/{code}")
376
+ @app.get("/api/ifsc")
377
+ async def lookup_ifsc(code: Optional[str] = None, ifsc: Optional[str] = Query(None), q: Optional[str] = Query(None), auth_meta: Dict[str, Any] = Depends(verify_api_key)):
378
+ target = code or ifsc or q
379
+ if not target:
380
+ raise HTTPException(status_code=400, detail="Missing required parameter: provide 'code', 'ifsc', or 'q'.")
381
+ res = ServiceGateway.get_ifsc(target)
382
+ if isinstance(res, dict):
383
+ res["tier"] = auth_meta.get("tier", "free")
384
+ res["remaining_credits"] = auth_meta.get("remaining", -1)
385
+ return res
386
+
387
+
388
+ @app.get("/api/pincode/{pin}")
389
+ @app.get("/api/pincode")
390
+ async def lookup_pincode(pin: Optional[str] = None, pincode: Optional[str] = Query(None), q: Optional[str] = Query(None), auth_meta: Dict[str, Any] = Depends(verify_api_key)):
391
+ target = pin or pincode or q
392
+ if not target:
393
+ raise HTTPException(status_code=400, detail="Missing required parameter: provide 'pin', 'pincode', or 'q'.")
394
+ res = ServiceGateway.get_pincode(target)
395
+ if isinstance(res, dict):
396
+ res["tier"] = auth_meta.get("tier", "free")
397
+ res["remaining_credits"] = auth_meta.get("remaining", -1)
398
+ return res
399
+
400
+
401
+ @app.get("/api/ip/{ip_addr}")
402
+ @app.get("/api/ip")
403
+ async def lookup_ip(ip_addr: Optional[str] = None, ip: Optional[str] = Query(None), q: Optional[str] = Query(None), auth_meta: Dict[str, Any] = Depends(verify_api_key)):
404
+ target = ip_addr or ip or q
405
+ if not target:
406
+ raise HTTPException(status_code=400, detail="Missing required parameter: provide 'ip_addr', 'ip', or 'q'.")
407
+ res = ServiceGateway.get_ip(target)
408
+ if isinstance(res, dict):
409
+ res["tier"] = auth_meta.get("tier", "free")
410
+ res["remaining_credits"] = auth_meta.get("remaining", -1)
411
+ return res
412
+
413
+
414
+ @app.get("/api/vehicle/{vehicle_no}")
415
+ @app.get("/api/rc/{vehicle_no}")
416
+ @app.get("/api/rc")
417
+ @app.get("/api/vehicle")
418
+ async def lookup_vehicle(vehicle_no: Optional[str] = None, vehicle: Optional[str] = Query(None), rc: Optional[str] = Query(None), owner: Optional[str] = Query(None), q: Optional[str] = Query(None), auth_meta: Dict[str, Any] = Depends(verify_api_key)):
419
+ target = vehicle_no or vehicle or rc or owner or q
420
+ if not target:
421
+ raise HTTPException(status_code=400, detail="Missing required parameter: provide vehicle registration number.")
422
+ res = ServiceGateway.get_vehicle_rc(target)
423
+ if isinstance(res, dict):
424
+ res["tier"] = auth_meta.get("tier", "free")
425
+ res["remaining_credits"] = auth_meta.get("remaining", -1)
426
+ return res
427
+
428
+
429
+ @app.get("/api/challan/{vehicle_no}")
430
+ @app.get("/api/challan")
431
+ async def lookup_challan(vehicle_no: Optional[str] = None, vehicle: Optional[str] = Query(None), q: Optional[str] = Query(None), auth_meta: Dict[str, Any] = Depends(verify_api_key)):
432
+ target = vehicle_no or vehicle or q
433
+ if not target:
434
+ raise HTTPException(status_code=400, detail="Missing required parameter: provide vehicle registration number.")
435
+ res = ServiceGateway.get_vehicle_challan(target)
436
+ if isinstance(res, dict):
437
+ res["tier"] = auth_meta.get("tier", "free")
438
+ res["remaining_credits"] = auth_meta.get("remaining", -1)
439
+ return res
440
+
441
+
442
+ @app.get("/api/pan/{pan_no}")
443
+ @app.get("/api/pan")
444
+ async def lookup_pan(pan_no: Optional[str] = None, pan: Optional[str] = Query(None), q: Optional[str] = Query(None), auth_meta: Dict[str, Any] = Depends(verify_api_key)):
445
+ target = pan_no or pan or q
446
+ if not target:
447
+ raise HTTPException(status_code=400, detail="Missing required parameter: provide 'pan_no', 'pan', or 'q'.")
448
+ res = ServiceGateway.get_pan_details(target)
449
+ if isinstance(res, dict):
450
+ res["tier"] = auth_meta.get("tier", "free")
451
+ res["remaining_credits"] = auth_meta.get("remaining", -1)
452
+ return res
453
+
454
+
455
+ @app.get("/api/gst/{gstin}")
456
+ @app.get("/api/gst")
457
+ async def lookup_gst(gstin: Optional[str] = None, gst: Optional[str] = Query(None), q: Optional[str] = Query(None), auth_meta: Dict[str, Any] = Depends(verify_api_key)):
458
+ target = gstin or gst or q
459
+ if not target:
460
+ raise HTTPException(status_code=400, detail="Missing required parameter: provide 'gstin', 'gst', or 'q'.")
461
+ res = ServiceGateway.get_gst_details(target)
462
+ if isinstance(res, dict):
463
+ res["tier"] = auth_meta.get("tier", "free")
464
+ res["remaining_credits"] = auth_meta.get("remaining", -1)
465
+ return res
466
+
467
+
468
+ @app.get("/api/pantogst/{pan_no}")
469
+ @app.get("/api/pantogst")
470
+ async def lookup_pantogst(pan_no: Optional[str] = None, pan: Optional[str] = Query(None), q: Optional[str] = Query(None), auth_meta: Dict[str, Any] = Depends(verify_api_key)):
471
+ target = pan_no or pan or q
472
+ if not target:
473
+ raise HTTPException(status_code=400, detail="Missing required parameter: provide 'pan_no', 'pan', or 'q'.")
474
+ res = ServiceGateway.get_pan_to_gst(target)
475
+ if isinstance(res, dict):
476
+ res["tier"] = auth_meta.get("tier", "free")
477
+ res["remaining_credits"] = auth_meta.get("remaining", -1)
478
+ return res
479
+
480
+
481
  @app.get("/api/phone/{phone_number}")
482
  async def lookup_by_phone(phone_number: str, auth_meta: Dict[str, Any] = Depends(verify_api_key)):
483
  """Search Telecom 2.5B Database by phone number."""