cel.py 6.9 KB

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