Add MySQL contacts table + mysql backend for vm_contacts
- CREATE TABLE contacts (number_e164 UNIQUE, name, email) in asterisk DB - new lookup_mysql() backend, registered as first in _BACKENDS - vm_import_contacts.py: import VCF into MySQL (527 added) - contacts.conf: backends=mysql,file - live: 16 of 130 messages resolved via MySQL contacts
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
Caller-ID -> contact name resolution for Asterisk voicemail notifications.
|
||||
|
||||
Backends (tried in the order given by contacts.conf 'backends='):
|
||||
mysql - MySQL contacts table on the asterisk DB (primary, fast, offline).
|
||||
file - local vCard (.vcf) or CSV export; no auth, offline, fast.
|
||||
google - Google People API via the Hermes OAuth token.
|
||||
carddav - generic CardDAV with username + app password.
|
||||
@ -21,6 +22,7 @@ import configparser
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
@ -311,6 +313,41 @@ def lookup_google(cfg, num_digits, n, log):
|
||||
return None
|
||||
|
||||
|
||||
# ----------------------------------------------------------- mysql backend
|
||||
def lookup_mysql(cfg, num_digits, n, log):
|
||||
"""Look up a contact in the asterisk MySQL DB contacts table.
|
||||
|
||||
Uses vm_store.connect() so it shares the same asterisk DB config and
|
||||
PyMySQL dependency already required by the portal. If the table does
|
||||
not not exist yet, run vm_import_contacts.py to create and populate it.
|
||||
"""
|
||||
try:
|
||||
sys_path = os.path.dirname(os.path.abspath(__file__))
|
||||
if sys_path not in sys.path:
|
||||
sys.path.insert(0, sys_path)
|
||||
import vm_store # noqa: E402 (local import to avoid hard dep at import time)
|
||||
except Exception as e:
|
||||
log("contacts mysql backend: vm_store unavailable (%s)" % e)
|
||||
return None
|
||||
|
||||
num = extract_number(num_digits)
|
||||
if not num:
|
||||
return None
|
||||
try:
|
||||
con = vm_store.connect()
|
||||
row = con.execute(
|
||||
"SELECT name, email FROM contacts "
|
||||
"WHERE number_e164 = ? AND name IS NOT NULL AND name != '' "
|
||||
"ORDER BY id LIMIT 1",
|
||||
(num,)).fetchone()
|
||||
con.close()
|
||||
if row:
|
||||
return (row["name"], row.get("email") or None)
|
||||
except Exception as e:
|
||||
log("contacts mysql backend error: %s" % e)
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------- carddav backend
|
||||
def lookup_carddav(cfg, num_digits, n, log):
|
||||
import ssl
|
||||
@ -356,7 +393,7 @@ def lookup_carddav(cfg, num_digits, n, log):
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- entrypoint
|
||||
_BACKENDS = {"file": lookup_file, "google": lookup_google, "carddav": lookup_carddav}
|
||||
_BACKENDS = {"mysql": lookup_mysql, "file": lookup_file, "google": lookup_google, "carddav": lookup_carddav}
|
||||
|
||||
|
||||
def resolve(caller_id, log=print):
|
||||
|
||||
84
src/vm_import_contacts.py
Normal file
84
src/vm_import_contacts.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Import /var/lib/vm-transcribe/contacts.vcf into the MySQL contacts table.
|
||||
|
||||
Idempotent: re-runs skip rows whose number_e164 already exists (UNIQUE key).
|
||||
Caller-visible numbers are stored as the canonical E.164 form produced by
|
||||
vm_contacts.normalize_uk, matching what Asterisk stores in callerid.
|
||||
|
||||
Run as the asterisk user:
|
||||
sudo -u asterisk /opt/vm-transcribe/venv/bin/python3 /opt/vm-transcribe/vm_import_contacts.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import vm_contacts
|
||||
import vm_store
|
||||
|
||||
|
||||
VCF_PATH = "/var/lib/vm-transcribe/contacts.vcf"
|
||||
DB_TABLE = "contacts"
|
||||
|
||||
|
||||
def main():
|
||||
total = added = skipped = 0
|
||||
con = vm_store.connect()
|
||||
|
||||
# Ensure table exists.
|
||||
con.execute(
|
||||
"CREATE TABLE IF NOT EXISTS %s ("
|
||||
"id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, "
|
||||
"number_e164 VARCHAR(20) NOT NULL UNIQUE, "
|
||||
"name VARCHAR(255) NOT NULL, "
|
||||
"email VARCHAR(255), "
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "
|
||||
"updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP "
|
||||
"ON UPDATE CURRENT_TIMESTAMP, "
|
||||
"INDEX idx_number (number_e164), "
|
||||
"INDEX idx_name (name)"
|
||||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" % DB_TABLE)
|
||||
con.commit()
|
||||
|
||||
if not os.path.exists(VCF_PATH):
|
||||
print("VCF not found:", VCF_PATH)
|
||||
return
|
||||
|
||||
with open(VCF_PATH, encoding="utf-8", errors="replace") as fh:
|
||||
entries = vm_contacts._parse_vcf(fh.read())
|
||||
|
||||
# Load existing numbers so we can skip idempotently without relying on
|
||||
# INSERT IGNORE swallowing other constraint failures.
|
||||
existing = {
|
||||
r["number_e164"] for r in con.execute(
|
||||
"SELECT number_e164 FROM %s" % DB_TABLE).fetchall()
|
||||
}
|
||||
|
||||
for name, nums, emails in entries:
|
||||
if vm_contacts._is_junk_name(name):
|
||||
continue
|
||||
email = emails[0] if emails else None
|
||||
for raw_num in nums:
|
||||
e164 = vm_contacts.normalize_uk(vm_contacts.digits_of(raw_num))
|
||||
if not e164:
|
||||
continue
|
||||
total += 1
|
||||
if e164 in existing:
|
||||
skipped += 1
|
||||
continue
|
||||
try:
|
||||
con.execute(
|
||||
"INSERT INTO %s (number_e164, name, email) VALUES (?, ?, ?)"
|
||||
% DB_TABLE, (e164, name, email))
|
||||
existing.add(e164)
|
||||
added += 1
|
||||
except Exception as e:
|
||||
print("insert error for %s (%s): %s" % (e164, name, e))
|
||||
|
||||
con.commit()
|
||||
con.close()
|
||||
print("vcf entries: %d, added: %d, skipped (already known): %d" % (
|
||||
total, added, skipped))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user