]>
Commit | Line | Data |
---|---|---|
d6c41da2 MT |
1 | #!/usr/bin/python3 |
2 | ||
3 | import asyncio | |
4 | import datetime | |
5 | import logging | |
6 | import panoramisk | |
4235ba55 | 7 | import urllib.parse |
d6c41da2 | 8 | |
4235ba55 | 9 | from . import accounts |
d6c41da2 MT |
10 | from . import misc |
11 | from .decorators import * | |
12 | ||
d6c41da2 MT |
13 | class Asterisk(misc.Object): |
14 | def init(self): | |
15 | self.__manager = None | |
16 | ||
57476094 MT |
17 | loop = asyncio.get_event_loop() |
18 | ||
d6c41da2 MT |
19 | # Connect as soon as the event loop starts |
20 | loop.create_task(self.connect()) | |
21 | ||
22 | @property | |
23 | def manager(self): | |
24 | if not self.__manager: | |
25 | raise RuntimeError("Asterisk is not connected") | |
26 | ||
27 | return self.__manager | |
28 | ||
29 | async def connect(self): | |
30 | """ | |
31 | Connects to Asterisk | |
32 | """ | |
33 | manager = panoramisk.Manager( | |
34 | host = self.settings.get("asterisk-ami-host"), | |
35 | username = self.settings.get("asterisk-ami-username"), | |
36 | secret = self.settings.get("asterisk-ami-secret"), | |
37 | ||
38 | on_connect=self._on_connect, | |
39 | ) | |
40 | ||
41 | # Connect | |
42 | await manager.connect() | |
43 | ||
44 | return manager | |
45 | ||
46 | def _on_connect(self, manager): | |
47 | logging.debug("Connection to Asterisk established") | |
48 | ||
49 | # Close any existing connections | |
50 | if self.__manager: | |
51 | self.__manager.close() | |
52 | ||
53 | self.__manager = manager | |
54 | ||
214a68a0 | 55 | async def _fetch(self, cls, action, filter=None, data={}): |
ed17ffb1 | 56 | objects = [] |
d6c41da2 | 57 | |
214a68a0 MT |
58 | # Collect arguments |
59 | args = { "Action" : action } | data | |
60 | ||
ed17ffb1 | 61 | # Run the action and parse all messages |
214a68a0 | 62 | for data in await self.manager.send_action(args): |
d8fa7a5a | 63 | if not "Event" in data or not data.Event == cls.event: |
d6c41da2 MT |
64 | continue |
65 | ||
ed17ffb1 MT |
66 | # Create the object and append it to the list |
67 | o = cls(self.backend, data) | |
d6c41da2 | 68 | |
ed17ffb1 MT |
69 | # Filter out anything unwanted |
70 | if filter and not o.matches(filter): | |
d6c41da2 MT |
71 | continue |
72 | ||
ed17ffb1 | 73 | objects.append(o) |
d6c41da2 | 74 | |
ed17ffb1 | 75 | return objects |
4235ba55 | 76 | |
ed17ffb1 MT |
77 | async def get_sip_channels(self, filter=None): |
78 | return await self._fetch(Channel, "CoreShowChannels", filter=filter) | |
4235ba55 | 79 | |
ed17ffb1 MT |
80 | async def get_registrations(self, filter=None): |
81 | return await self._fetch(Registration, "PJSIPShowContacts", filter=filter) | |
4235ba55 | 82 | |
00465786 | 83 | async def get_outbound_registrations(self): |
ed17ffb1 | 84 | return await self._fetch(OutboundRegistration, "PJSIPShowRegistrationsOutbound") |
00465786 | 85 | |
8e93325b | 86 | async def get_queues(self): |
8e93325b | 87 | # Fetch all queues |
ed17ffb1 | 88 | queues = { q.name : q for q in await self._fetch(Queue, "QueueSummary") } |
8e93325b MT |
89 | |
90 | # Fetch all members | |
ed17ffb1 | 91 | for member in await self._fetch(QueueMember, "QueueStatus"): |
8e93325b MT |
92 | # Append to the matching queue |
93 | try: | |
94 | queues[member.queue].members.append(member) | |
95 | except KeyError: | |
96 | pass | |
97 | ||
98 | return queues.values() | |
99 | ||
214a68a0 MT |
100 | async def get_conferences(self): |
101 | conferences = await self._fetch(Conference, "ConfbridgeListRooms") | |
102 | ||
103 | # Fetch everything else | |
104 | async with asyncio.TaskGroup() as tasks: | |
105 | for c in conferences: | |
106 | tasks.create_task(c._fetch()) | |
107 | ||
108 | return conferences | |
109 | ||
d6c41da2 MT |
110 | |
111 | class Channel(misc.Object): | |
ed17ffb1 MT |
112 | event = "CoreShowChannel" |
113 | ||
d6c41da2 MT |
114 | def init(self, data): |
115 | self.data = data | |
116 | ||
117 | def __str__(self): | |
118 | return self.connected_line | |
119 | ||
120 | @property | |
121 | def account_code(self): | |
122 | return self.data.AccountCode | |
123 | ||
124 | @property | |
125 | def connected_line(self): | |
126 | return self.data.ConnectedLineName or self.data.ConnectedLineNum | |
127 | ||
128 | def matches(self, filter): | |
129 | return filter in ( | |
130 | self.data.CallerIDNum, | |
131 | ) | |
132 | ||
133 | @property | |
134 | def duration(self): | |
135 | h, m, s = self.data.Duration.split(":") | |
136 | ||
137 | try: | |
138 | h, m, s = int(h), int(m), int(s) | |
139 | except TypeError: | |
140 | return 0 | |
141 | ||
142 | return datetime.timedelta(hours=h, minutes=m, seconds=s) | |
143 | ||
144 | def is_connected(self): | |
145 | return self.data.ChannelStateDesc == "Up" | |
146 | ||
147 | def is_ringing(self): | |
148 | return self.data.ChannelStateDesc == "Ringing" | |
4235ba55 MT |
149 | |
150 | ||
151 | class Registration(misc.Object): | |
ed17ffb1 MT |
152 | event = "ContactList" |
153 | ||
4235ba55 MT |
154 | def init(self, data): |
155 | self.data = data | |
156 | ||
157 | def __lt__(self, other): | |
158 | if isinstance(other, self.__class__): | |
159 | if isinstance(self.user, accounts.Account): | |
160 | if isinstance(other.user, accounts.Account): | |
161 | return self.user < other.user | |
162 | else: | |
163 | return self.user.name < other.user | |
164 | else: | |
165 | if isinstance(other.user, accounts.Account): | |
166 | return self.user < other.user.name | |
167 | else: | |
168 | return self.user < other.user | |
169 | ||
170 | return NotImplemented | |
171 | ||
172 | def __str__(self): | |
173 | return self.user_agent | |
174 | ||
175 | def matches(self, filter): | |
176 | return self.data.Endpoint == filter | |
177 | ||
178 | @lazy_property | |
179 | def uri(self): | |
180 | return urllib.parse.urlparse(self.data.Uri) | |
181 | ||
182 | @lazy_property | |
183 | def uri_params(self): | |
184 | params = {} | |
185 | ||
186 | for param in self.uri.params.split(";"): | |
187 | key, _, value = param.partition("=") | |
188 | ||
189 | params[key] = value | |
190 | ||
191 | return params | |
192 | ||
193 | @property | |
194 | def transport(self): | |
195 | return self.uri_params.get("transport") | |
196 | ||
197 | @lazy_property | |
198 | def user(self): | |
199 | return self.backend.accounts.get_by_sip_id(self.data.Endpoint) or self.data.Endpoint | |
200 | ||
201 | @property | |
202 | def address(self): | |
203 | # Remove the user | |
204 | user, _, address = self.uri.path.partition("@") | |
205 | ||
206 | # Remove the port | |
207 | address, _, port = address.rpartition(":") | |
208 | ||
209 | return address | |
210 | ||
211 | @property | |
212 | def user_agent(self): | |
213 | return self.data.UserAgent.replace("_", " ") | |
214 | ||
215 | @property | |
216 | def roundtrip(self): | |
df3e4d31 MT |
217 | try: |
218 | return int(self.data.RoundtripUsec) / 1000 | |
219 | except ValueError: | |
220 | pass | |
00465786 MT |
221 | |
222 | ||
223 | class OutboundRegistration(misc.Object): | |
ed17ffb1 MT |
224 | event = "OutboundRegistrationDetail" |
225 | ||
00465786 MT |
226 | def init(self, data): |
227 | self.data = data | |
228 | ||
229 | def __lt__(self, other): | |
230 | if isinstance(other, self.__class__): | |
231 | return self.server < other.server or self.username < other.username | |
232 | ||
233 | return NotImplemented | |
234 | ||
235 | @lazy_property | |
236 | def uri(self): | |
237 | return urllib.parse.urlparse(self.data.ClientUri) | |
238 | ||
239 | @property | |
240 | def server(self): | |
241 | username, _, server = self.uri.path.partition("@") | |
242 | ||
243 | return server | |
244 | ||
245 | @property | |
246 | def username(self): | |
247 | username, _, server = self.uri.path.partition("@") | |
248 | ||
249 | return username | |
250 | ||
251 | @property | |
252 | def status(self): | |
253 | return self.data.Status | |
8e93325b MT |
254 | |
255 | ||
256 | class Queue(misc.Object): | |
ed17ffb1 MT |
257 | event = "QueueSummary" |
258 | ||
8e93325b MT |
259 | def init(self, data): |
260 | self.data = data | |
261 | ||
262 | self.members = [] | |
263 | ||
264 | def __str__(self): | |
265 | return self.name | |
266 | ||
267 | @property | |
268 | def name(self): | |
269 | return self.data.Queue | |
270 | ||
271 | def is_available(self): | |
272 | return self.data.Available == "1" | |
273 | ||
274 | @property | |
275 | def callers(self): | |
276 | return int(self.data.Callers) | |
277 | ||
278 | ||
279 | class QueueMember(misc.Object): | |
ed17ffb1 MT |
280 | event = "QueueMember" |
281 | ||
8e93325b MT |
282 | def init(self, data): |
283 | self.data = data | |
284 | ||
285 | def __str__(self): | |
286 | return self.name | |
287 | ||
288 | @property | |
289 | def name(self): | |
290 | return self.data.Name | |
291 | ||
292 | @property | |
293 | def queue(self): | |
294 | return self.data.Queue | |
295 | ||
296 | @property | |
297 | def calls_taken(self): | |
298 | return int(self.data.CallsTaken) | |
299 | ||
300 | def is_in_call(self): | |
301 | return self.data.InCall == "1" | |
302 | ||
303 | @property | |
304 | def last_call_at(self): | |
305 | return datetime.datetime.fromtimestamp(int(self.data.LastCall)) | |
306 | ||
307 | @property | |
308 | def logged_in_at(self): | |
309 | return datetime.datetime.fromtimestamp(int(self.data.LoginTime)) | |
310 | ||
311 | # XXX status? | |
214a68a0 MT |
312 | |
313 | ||
314 | class Conference(misc.Object): | |
315 | event = "ConfbridgeListRooms" | |
316 | ||
317 | def init(self, data): | |
318 | self.data = data | |
319 | ||
320 | def __str__(self): | |
321 | return self.name | |
322 | ||
323 | @property | |
324 | def name(self): | |
325 | return self.data.Conference | |
326 | ||
327 | async def _fetch(self): | |
328 | # Fetch all members | |
329 | self.members = await self.backend.asterisk._fetch( | |
330 | ConferenceMember, "ConfbridgeList", data={ "Conference" : self.name, }) | |
331 | ||
332 | ||
333 | class ConferenceMember(misc.Object): | |
334 | event = "ConfbridgeList" | |
335 | ||
336 | def init(self, data): | |
337 | self.data = data | |
338 | ||
339 | def __str__(self): | |
340 | return self.name | |
341 | ||
342 | def __lt__(self, other): | |
343 | if isinstance(other, self.__class__): | |
344 | return not self.duration < other.duration | |
345 | ||
346 | return NotImplemented | |
347 | ||
348 | @property | |
349 | def name(self): | |
350 | return "%s <%s>" % (self.data.CallerIDName, self.data.CallerIDNum) | |
351 | ||
352 | def is_admin(self): | |
353 | return self.data.Admin == "Yes" | |
354 | ||
355 | def is_muted(self): | |
356 | return self.data.Muted == "Yes" | |
357 | ||
358 | @property | |
359 | def duration(self): | |
360 | return datetime.timedelta(seconds=int(self.data.AnsweredTime)) |