cel.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. #!/usr/bin/env python3
  2. import json
  3. import re
  4. from datetime import datetime
  5. from datetime import timedelta as td
  6. fieldsFilter = ('id', 'linkedid', 'appdata', 'lastdata')
  7. class CdrChannel:
  8. def __init__(self, chanData):
  9. self._str = chanData
  10. (self.tech,
  11. self.peer,
  12. self.context,
  13. self.id,
  14. self.leg) = re.match(r'(\w+)/(\w+)(?:@([\w-]+))?-([0-9a-f]{8})(?:;([0-9]+))?', chanData).groups()
  15. def __repr__(self):
  16. return self._str
  17. class CdrEvent:
  18. def __init__(self, eventData):
  19. eventData = dict(eventData)
  20. for key, value in eventData.items():
  21. if ((key not in fieldsFilter) and (value is not None) and (str(value) != '')):
  22. if key == 'extra':
  23. setattr(self, key, json.loads(value))
  24. elif key in ('channame','channel','dstchannel'):
  25. setattr(self, key, CdrChannel(value))
  26. else:
  27. setattr(self, key, value)
  28. def __getattr__(self, attr):
  29. return None
  30. class CdrEvents:
  31. def __init__(self):
  32. self._events = []
  33. def add(self, event):
  34. if isinstance(event, CdrEvent):
  35. self._events.append(event)
  36. else:
  37. self._events.append(CdrEvent(event))
  38. @property
  39. def all(self):
  40. return self._events
  41. @property
  42. def first(self):
  43. return self._events[0] if len(self._events) > 0 else None
  44. @property
  45. def second(self):
  46. return self._events[1] if len(self._events) > 1 else None
  47. @property
  48. def last(self):
  49. return self._events[-1] if len(self._events) > 0 else None
  50. def filter(self, key, value, func='__eq__'):
  51. result = CdrEvents()
  52. for event in self._events:
  53. if getattr(str(getattr(event, key)), func)(value):
  54. result.add(event)
  55. return result
  56. def has(self, value, key='disposition'):
  57. for event in self._events:
  58. if getattr(event, key) == value:
  59. return True
  60. return False
  61. def __len__(self):
  62. return len(self._events)
  63. class CdrUserEvents(CdrEvents):
  64. def __init__(self, user):
  65. self._user = user
  66. self._events = []
  67. self.start = 0
  68. self.end = 0
  69. def add(self, event):
  70. if not isinstance(event, CdrEvent):
  71. event = CdrEvent(event)
  72. if self._user in (event.src, event.dst, event.cnum):
  73. if len(self._events) == 0:
  74. self.start = event.calldate
  75. self.end = event.calldate + td(seconds=event.duration)
  76. self._events.append(event)
  77. else:
  78. if (event.calldate + td(seconds=event.duration)) > self.end:
  79. self.end = event.calldate + td(seconds=event.duration)
  80. if event.calldate < self.start:
  81. self.start = event.calldate
  82. lo = 0
  83. hi = len(self._events)
  84. while lo < hi:
  85. mid = (lo+hi)//2
  86. if event.calldate < self._events[mid].calldate:
  87. hi = mid
  88. else:
  89. lo = mid+1
  90. self._events.insert(lo, event)
  91. def simple(self):
  92. res = []
  93. for event in self._events:
  94. simple_event = {'start': event.calldate,
  95. 'answered': event.disposition=='ANSWERED',
  96. 'duration': event.duration,
  97. #'waiting': event.waiting,
  98. 'application': event.lastapp,
  99. 'dst': event.dst,
  100. 'uniqueid': event.uniqueid,
  101. 'file': event.recordingfile}
  102. res.append(simple_event)
  103. return res
  104. class CelEvents(CdrEvents):
  105. def has(self, value, key='eventtype'):
  106. return super().has(value, key)
  107. class CdrCall:
  108. def __init__(self, event=None):
  109. self.events = CdrEvents()
  110. if event is not None:
  111. self.events.add(event)
  112. self.linkedid = event['linkedid']
  113. else:
  114. self.linkedid = None
  115. @property
  116. def start(self):
  117. return self.events.first.calldate
  118. @property
  119. def disposition(self):
  120. return self.events.first.disposition
  121. @property
  122. def file(self):
  123. return self.events.first.recordingfile
  124. @property
  125. def src(self):
  126. return self.events.first.cnum
  127. @property
  128. def direction(self):
  129. if self.events.first.dcontext == 'from-internal':
  130. if self.events.first.outbound_cnum is not None:
  131. return 'outbound'
  132. else:
  133. return 'local'
  134. else:
  135. return 'inbound'
  136. @property
  137. def dst(self):
  138. # placeholder
  139. # TODO: determine last dst based on disposition
  140. if self.direction == 'outbound':
  141. return self.events.first.dst
  142. elif self.direction == 'local':
  143. return self.events.first.dst
  144. else:
  145. if self.events.first.dstchannel is not None:
  146. return self.events.first.dstchannel.peer
  147. else:
  148. return self.events.first.dst
  149. @property
  150. def did(self):
  151. if self.direction == 'inbound':
  152. return self.events.first.did
  153. else:
  154. return None
  155. @property
  156. def duration(self):
  157. if not self.isAnswered:
  158. return 0
  159. else:
  160. if len(self.events) > 1:
  161. return self.events.second.billsec
  162. else:
  163. return self.events.first.billsec
  164. @property
  165. def waiting(self):
  166. if not self.isAnswered:
  167. return self.events.first.duration
  168. else:
  169. # TODO: exclude time on ivr
  170. return self.events.first.duration - self.duration
  171. @property
  172. def isAnswered(self):
  173. return self.disposition == 'ANSWERED';
  174. class CdrUserCall(CdrCall):
  175. def __init__(self, user, event=None):
  176. self.user = user
  177. self.events = CdrUserEvents(user)
  178. if event is not None:
  179. self.events.add(event)
  180. self.linkedid = event['linkedid']
  181. else:
  182. self.linkedid = None
  183. @property
  184. def file(self):
  185. return self.events.first.recordingfile
  186. @property
  187. def src(self):
  188. return self.events.first.cnum
  189. @property
  190. def direction(self):
  191. if self.user in (self.events.first.src, self.events.first.cnum):
  192. return 'outbound'
  193. else:
  194. return 'inbound'
  195. @property
  196. def dst(self):
  197. return self.events.first.dst
  198. @property
  199. def did(self):
  200. if self.direction == 'inbound':
  201. return self.events.first.did
  202. else:
  203. return None
  204. @property
  205. def duration(self):
  206. if not self.isAnswered:
  207. return 0
  208. else:
  209. return self.events.first.billsec
  210. @property
  211. def waiting(self):
  212. if not self.isAnswered:
  213. return self.events.first.duration
  214. else:
  215. # TODO: exclude time on ivr
  216. return self.events.first.duration - self.duration
  217. @property
  218. def isAnswered(self):
  219. return self.disposition == 'ANSWERED';
  220. @property
  221. def simple(self):
  222. return {'start': self.start,
  223. 'direction': self.direction,
  224. 'answered': self.isAnswered,
  225. 'duration': self.duration,
  226. 'waiting': self.waiting,
  227. 'src': self.src,
  228. 'dst': self.dst,
  229. 'did': self.did,
  230. 'linkedid': self.linkedid,
  231. 'file': self.file,
  232. 'events': self.events.simple()}
  233. class CelCall:
  234. def __init__(self, event=None):
  235. self.events = CelEvents()
  236. if event is not None:
  237. self.events.add(event)
  238. self.linkedid = event['linkedid']
  239. else:
  240. self.linkedid = None
  241. @property
  242. def start(self):
  243. return self.events.first.eventtime