cel.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. class CelEvents(CdrEvents):
  92. def has(self, value, key='eventtype'):
  93. return super().has(value, key)
  94. class CdrCall:
  95. def __init__(self, event=None):
  96. self.events = CdrEvents()
  97. if event is not None:
  98. self.events.add(event)
  99. self.linkedid = event['linkedid']
  100. else:
  101. self.linkedid = None
  102. @property
  103. def start(self):
  104. return self.events.first.calldate
  105. @property
  106. def disposition(self):
  107. return self.events.first.disposition
  108. @property
  109. def file(self):
  110. return self.events.first.recordingfile
  111. @property
  112. def src(self):
  113. return self.events.first.cnum
  114. @property
  115. def direction(self):
  116. if self.events.first.dcontext == 'from-internal':
  117. if self.events.first.outbound_cnum is not None:
  118. return 'outbound'
  119. else:
  120. return 'local'
  121. else:
  122. return 'inbound'
  123. @property
  124. def dst(self):
  125. # placeholder
  126. # TODO: determine last dst based on disposition
  127. if self.direction == 'outbound':
  128. return self.events.first.dst
  129. elif self.direction == 'local':
  130. return self.events.first.dst
  131. else:
  132. if self.events.first.dstchannel is not None:
  133. return self.events.first.dstchannel.peer
  134. else:
  135. return self.events.first.dst
  136. @property
  137. def did(self):
  138. if self.direction == 'inbound':
  139. return self.events.first.did
  140. else:
  141. return None
  142. @property
  143. def duration(self):
  144. if not self.isAnswered:
  145. return 0
  146. else:
  147. if len(self.events) > 1:
  148. return self.events.second.billsec
  149. else:
  150. return self.events.first.billsec
  151. @property
  152. def waiting(self):
  153. if not self.isAnswered:
  154. return self.events.first.duration
  155. else:
  156. # TODO: exclude time on ivr
  157. return self.events.first.duration - self.duration
  158. @property
  159. def isAnswered(self):
  160. return self.disposition == 'ANSWERED';
  161. class CdrUserCall(CdrCall):
  162. def __init__(self, user, event=None):
  163. self.user = user
  164. self.events = CdrUserEvents(user)
  165. if event is not None:
  166. self.events.add(event)
  167. self.linkedid = event['linkedid']
  168. else:
  169. self.linkedid = None
  170. @property
  171. def file(self):
  172. return self.events.first.recordingfile
  173. @property
  174. def src(self):
  175. return self.events.first.cnum
  176. @property
  177. def direction(self):
  178. if self.user in (self.events.first.src, self.events.first.cnum):
  179. return 'outbound'
  180. else:
  181. return 'inbound'
  182. @property
  183. def dst(self):
  184. return self.events.first.dst
  185. @property
  186. def did(self):
  187. if self.direction == 'inbound':
  188. return self.events.first.did
  189. else:
  190. return None
  191. @property
  192. def duration(self):
  193. if not self.isAnswered:
  194. return 0
  195. else:
  196. return self.events.first.billsec
  197. @property
  198. def waiting(self):
  199. if not self.isAnswered:
  200. return self.events.first.duration
  201. else:
  202. # TODO: exclude time on ivr
  203. return self.events.first.duration - self.duration
  204. @property
  205. def isAnswered(self):
  206. return self.disposition == 'ANSWERED';
  207. @property
  208. def simple(self):
  209. return {'start': self.start,
  210. 'direction': self.direction,
  211. 'answered': self.isAnswered,
  212. 'duration': self.duration,
  213. 'waiting': self.waiting,
  214. 'src': self.src,
  215. 'dst': self.dst,
  216. 'did': self.did,
  217. 'linkedid': self.linkedid,
  218. 'file': self.file}
  219. class CelCall:
  220. def __init__(self, event=None):
  221. self.events = CelEvents()
  222. if event is not None:
  223. self.events.add(event)
  224. self.linkedid = event['linkedid']
  225. else:
  226. self.linkedid = None
  227. @property
  228. def start(self):
  229. return self.events.first.eventtime