#!/usr/bin/env python3 import json import re from datetime import datetime fieldsFilter = ('id', 'linkedid', 'appdata', 'lastdata') class CdrChannel: def __init__(self, chanData): self._str = chanData (self.tech, self.peer, self.context, self.id, self.leg) = re.match(r'(\w+)/(\w+)(?:@([\w-]+))?-([0-9a-f]{8})(?:;([0-9]+))?', chanData).groups() def __repr__(self): return self._str class CdrEvent: def __init__(self, eventData): for key, value in eventData.items(): if ((key not in fieldsFilter) and (value is not None) and (str(value) != '')): if key == 'extra': setattr(self, key, json.loads(value)) elif key in ('channame','channel','dstchannel'): setattr(self, key, CdrChannel(value)) else: setattr(self, key, value) def __getattr__(self, attr): return None class CdrEvents: def __init__(self): self._events = [] def add(self, event): if isinstance(event, CdrEvent): self._events.append(event) else: self._events.append(CdrEvent(event)) @property def all(self): return self._events @property def first(self): return self._events[0] if len(self._events) > 0 else None @property def second(self): return self._events[1] if len(self._events) > 1 else None @property def last(self): return self._events[-1] if len(self._events) > 0 else None def filter(self, key, value, func='__eq__'): result = CdrEvents() for event in self._events: if getattr(str(getattr(event, key)), func)(value): result.add(event) return result def has(self, value, key='disposition'): for event in self._events: if getattr(event, key) == value: return True return False def __len__(self): return len(self._events) class CelEvents(CdrEvents): def has(self, value, key='eventtype'): return super().has(value, key) class CdrCall: def __init__(self, event=None): self.events = CdrEvents() if event is not None: self.events.add(event) self.linkedid = event['linkedid'] else: self.linkedid = None @property def start(self): return self.events.first.calldate @property def disposition(self): return self.events.first.disposition @property def file(self): return self.events.first.recordingfile @property def src(self): return self.events.first.cnum @property def direction(self): if self.events.first.dcontext == 'from-internal': if self.events.first.outbound_cnum is not None: return 'outbound' else: return 'local' else: return 'inbound' @property def dst(self): # placeholder # TODO: determine last dst based on disposition if self.direction == 'inbound': return self.events.first.dstchannel.peer else: return self.events.first.dst @property def did(self): if self.direction == 'inbound': return self.events.first.did else: return None @property def duration(self): if not self.isAnswered: return 0 else: if len(self.events) > 1: return self.events.second.billsec else: return self.events.first.billsec @property def waiting(self): if not self.isAnswered: return self.events.first.duration else: # TODO: exclude time on ivr return self.events.first.duration - self.duration @property def isAnswered(self): return self.disposition == 'ANSWERED'; class CelCall: def __init__(self, event=None): self.events = CelEvents() if event is not None: self.events.add(event) self.linkedid = event['linkedid'] else: self.linkedid = None @property def start(self): return self.events.first.eventtime