#!/usr/bin/env python3 import json import re from datetime import datetime from datetime import timedelta as td 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): eventData = dict(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 CdrUserEvents(CdrEvents): def __init__(self, user): self._user = user self._events = [] self.start = 0 self.end = 0 def add(self, event): if not isinstance(event, CdrEvent): event = CdrEvent(event) if self._user in (event.src, event.dst, event.cnum): if len(self._events) == 0: self.start = event.calldate self.end = event.calldate + td(seconds=event.duration) self._events.append(event) else: if (event.calldate + td(seconds=event.duration)) > self.end: self.end = event.calldate + td(seconds=event.duration) if event.calldate < self.start: self.start = event.calldate lo = 0 hi = len(self._events) while lo < hi: mid = (lo+hi)//2 if event.calldate < self._events[mid].calldate: hi = mid else: lo = mid+1 self._events.insert(lo, event) def simple(self): res = [] for event in self._events: simple_event = {'start': event.calldate, 'answered': event.disposition=='ANSWERED', 'duration': event.duration, #'waiting': event.waiting, 'application': event.lastapp 'dst': event.dst, 'uniqueid': self.uniqueid, 'file': self.recordingfile} res.append(simple_event) return res 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 == 'outbound': return self.events.first.dst elif self.direction == 'local': return self.events.first.dst else: if self.events.first.dstchannel is not None: 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 CdrUserCall(CdrCall): def __init__(self, user, event=None): self.user = user self.events = CdrUserEvents(user) if event is not None: self.events.add(event) self.linkedid = event['linkedid'] else: self.linkedid = None @property def file(self): return self.events.first.recordingfile @property def src(self): return self.events.first.cnum @property def direction(self): if self.user in (self.events.first.src, self.events.first.cnum): return 'outbound' else: return 'inbound' @property def dst(self): 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: 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'; @property def simple(self): return {'start': self.start, 'direction': self.direction, 'answered': self.isAnswered, 'duration': self.duration, 'waiting': self.waiting, 'src': self.src, 'dst': self.dst, 'did': self.did, 'linkedid': self.linkedid, 'file': self.file, 'events': self.events.simple()} 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