carddav2asterisk.py 2.93 KB
Newer Older
Citronalco's avatar
Citronalco committed
1
#!/usr/bin/env python3
Citronalco's avatar
Citronalco committed
2
#-*- coding: utf-8 -*-
Citronalco's avatar
Citronalco committed
3
4
5
6
7

import sys
import requests
import vobject
import re
Citronalco's avatar
Citronalco committed
8
9
import asyncio
from panoramisk import Manager
Citronalco's avatar
Citronalco committed
10
11
from requests.auth import HTTPBasicAuth
from lxml import etree
Citronalco's avatar
Citronalco committed
12
13
14
from urllib.parse import urlparse
import argparse
import configparser
Citronalco's avatar
Citronalco committed
15
16
17


# get list with links to all available vcards
Citronalco's avatar
Citronalco committed
18
19
20
21
22
23
24
25
26
27
28
def getAllVcardLinks(url, auth):
  baseurl = urlparse(url).scheme+'://' + urlparse(url).netloc

  r = requests.request('PROPFIND', url, auth = auth)
  if r.status_code != 207:
    raise RuntimeError('error in response from %s: %r' % (url, r))

  root = etree.XML(r.text)
  vcardUrlList = []
  for record in root.xpath(".//d:response", namespaces = {"d" : "DAV:"}):
    type = record.xpath(".//d:getcontenttype", namespaces = {"d" : "DAV:"})
Bernhard Geier's avatar
Bernhard Geier committed
29
    if (type) and type[0].text.startswith("text/vcard"):
Citronalco's avatar
Citronalco committed
30
      vcardlinks = record.xpath(".//d:href", namespaces = {"d" : "DAV:"})
Bernhard Geier's avatar
Bernhard Geier committed
31
      for link in vcardlinks:
Citronalco's avatar
Citronalco committed
32
        vcardUrlList.append(baseurl + link.text)
Bernhard Geier's avatar
Bernhard Geier committed
33
  return vcardUrlList
Citronalco's avatar
Citronalco committed
34
35


Citronalco's avatar
Citronalco committed
36
37
38
39
40
41
42
43
def tidyPhoneNumber(config, num):
  num = re.sub("^\+", "00", num)    # +39 -> 0039
  num = re.sub("\D", "", num)       # remove all non-digits
  if 'phone' in config:
    if 'nationalprefix' in config['phone']:
      num = re.sub("^" + config['phone']['nationalprefix'] + "0*", "0", num)    # strip own national prefix
    if 'domesticprefix' in config['phone']:
      num = re.sub("^[^0]", "0" + config['phone']['domesticprefix'], num)       # add domestic prefix, if missing
Bernhard Geier's avatar
Bernhard Geier committed
44
  return num
Citronalco's avatar
Citronalco committed
45

Citronalco's avatar
Citronalco committed
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

def main():
  parser = argparse.ArgumentParser()
  parser.add_argument('ini_file')
  parser.add_argument("--no-update", help="Don't write to asterisk", action='store_true', default=False)
  args = parser.parse_args()

  config = configparser.RawConfigParser()
  config.read(args.ini_file)

  loop = asyncio.get_event_loop()
  loop.run_until_complete(putCids(loop, args, config))
  loop.close()


def putCids(lp, args, config):
  auth = HTTPBasicAuth(config['carddav']['user'], config['carddav']['pass'])
  url = config['carddav']['url']
Citronalco's avatar
Citronalco committed
64

Bernhard Geier's avatar
Bernhard Geier committed
65
  # connect to asterisk
Citronalco's avatar
Citronalco committed
66
67
68
69
70
  ami = Manager(host = config['ami']['host'],
                  port = config['ami']['port'],
                  username = config['ami']['user'],
                  secret = config['ami']['pass'])
  yield from ami.connect()
Citronalco's avatar
Citronalco committed
71

Bernhard Geier's avatar
Bernhard Geier committed
72
  # get phone numbers from vcard
Citronalco's avatar
Citronalco committed
73
74
75
  for vurl in getAllVcardLinks(url, auth):
    r = requests.request("GET", vurl, auth=auth)
    vcard = vobject.readOne(r.text)
Bernhard Geier's avatar
Bernhard Geier committed
76
77
    if "tel" in vcard.contents:
      for telno in vcard.contents['tel']:
Citronalco's avatar
Citronalco committed
78
        num = tidyPhoneNumber(config, telno.value)
Bernhard Geier's avatar
Bernhard Geier committed
79
80
        if "fn" in vcard.contents:
          name = vcard.fn.value
Citronalco's avatar
Citronalco committed
81
82
83
84
85
86
          print("Adding/updating Number: %s Name: %s" % (num, name), end="... ")
          if not args.no_update:
            ami_result = yield from ami.send_action({"Action": "DBPut", "Family": "cidname", "Key": num, "Val": name})
            print(ami_result.Response)
          else:
            print("no-update")
Bernhard Geier's avatar
Bernhard Geier committed
87
  ami.close()
Citronalco's avatar
Citronalco committed
88
89

if __name__ == "__main__":
Citronalco's avatar
Citronalco committed
90
  main()