]>
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 | ||
13 | loop = asyncio.get_event_loop() | |
14 | ||
15 | class Asterisk(misc.Object): | |
16 | def init(self): | |
17 | self.__manager = None | |
18 | ||
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 | ||
55 | async def ping(self): | |
56 | manager = await self.manager() | |
57 | ||
58 | result = manager.ping() | |
59 | print(result) | |
60 | ||
61 | async def get_sip_channels(self, filter=None): | |
62 | channels = [] | |
63 | ||
64 | for data in await self.manager.send_action({"Action" : "CoreShowChannels"}): | |
65 | # Skip header and trailer | |
66 | if data.eventlist: | |
67 | continue | |
68 | ||
69 | # Parse channel | |
70 | channel = Channel(self.backend, data) | |
71 | ||
72 | # Apply filter | |
73 | if filter and not channel.matches(filter): | |
74 | continue | |
75 | ||
76 | channels.append(channel) | |
77 | ||
78 | return channels | |
79 | ||
4235ba55 MT |
80 | async def get_registrations(self, filter=None): |
81 | registrations = [] | |
82 | ||
83 | for data in await self.manager.send_action({"Action" : "PJSIPShowContacts"}): | |
84 | # Skip header and trailer | |
85 | if data.eventlist: | |
86 | continue | |
87 | ||
88 | # Parse registration | |
89 | registration = Registration(self.backend, data) | |
90 | ||
91 | # Apply filter | |
92 | if filter and not registration.matches(filter): | |
93 | continue | |
94 | ||
95 | registrations.append(registration) | |
96 | ||
97 | return registrations | |
98 | ||
00465786 MT |
99 | async def get_outbound_registrations(self): |
100 | registrations = [] | |
101 | ||
102 | for data in await self.manager.send_action({"Action" : "PJSIPShowRegistrationsOutbound"}): | |
103 | if not data.Event == "OutboundRegistrationDetail": | |
104 | continue | |
105 | ||
106 | registration = OutboundRegistration(self.backend, data) | |
107 | registrations.append(registration) | |
108 | ||
109 | return registrations | |
110 | ||
8e93325b MT |
111 | async def get_queues(self): |
112 | queues = {} | |
113 | ||
114 | # Fetch all queues | |
115 | for data in await self.manager.send_action({"Action" : "QueueSummary"}): | |
116 | if not data.Event == "QueueSummary": | |
117 | continue | |
118 | ||
119 | queue = Queue(self.backend, data) | |
120 | queues[queue.name] = queue | |
121 | ||
122 | # Fetch all members | |
123 | for data in await self.manager.send_action({"Action" : "QueueStatus"}): | |
124 | print(data) | |
125 | ||
126 | if not data.Event == "QueueMember": | |
127 | continue | |
128 | ||
129 | member = QueueMember(self.backend, data) | |
130 | ||
131 | # Append to the matching queue | |
132 | try: | |
133 | queues[member.queue].members.append(member) | |
134 | except KeyError: | |
135 | pass | |
136 | ||
137 | return queues.values() | |
138 | ||
d6c41da2 MT |
139 | |
140 | class Channel(misc.Object): | |
141 | def init(self, data): | |
142 | self.data = data | |
143 | ||
144 | def __str__(self): | |
145 | return self.connected_line | |
146 | ||
147 | @property | |
148 | def account_code(self): | |
149 | return self.data.AccountCode | |
150 | ||
151 | @property | |
152 | def connected_line(self): | |
153 | return self.data.ConnectedLineName or self.data.ConnectedLineNum | |
154 | ||
155 | def matches(self, filter): | |
156 | return filter in ( | |
157 | self.data.CallerIDNum, | |
158 | ) | |
159 | ||
160 | @property | |
161 | def duration(self): | |
162 | h, m, s = self.data.Duration.split(":") | |
163 | ||
164 | try: | |
165 | h, m, s = int(h), int(m), int(s) | |
166 | except TypeError: | |
167 | return 0 | |
168 | ||
169 | return datetime.timedelta(hours=h, minutes=m, seconds=s) | |
170 | ||
171 | def is_connected(self): | |
172 | return self.data.ChannelStateDesc == "Up" | |
173 | ||
174 | def is_ringing(self): | |
175 | return self.data.ChannelStateDesc == "Ringing" | |
4235ba55 MT |
176 | |
177 | ||
178 | class Registration(misc.Object): | |
179 | def init(self, data): | |
180 | self.data = data | |
181 | ||
182 | def __lt__(self, other): | |
183 | if isinstance(other, self.__class__): | |
184 | if isinstance(self.user, accounts.Account): | |
185 | if isinstance(other.user, accounts.Account): | |
186 | return self.user < other.user | |
187 | else: | |
188 | return self.user.name < other.user | |
189 | else: | |
190 | if isinstance(other.user, accounts.Account): | |
191 | return self.user < other.user.name | |
192 | else: | |
193 | return self.user < other.user | |
194 | ||
195 | return NotImplemented | |
196 | ||
197 | def __str__(self): | |
198 | return self.user_agent | |
199 | ||
200 | def matches(self, filter): | |
201 | return self.data.Endpoint == filter | |
202 | ||
203 | @lazy_property | |
204 | def uri(self): | |
205 | return urllib.parse.urlparse(self.data.Uri) | |
206 | ||
207 | @lazy_property | |
208 | def uri_params(self): | |
209 | params = {} | |
210 | ||
211 | for param in self.uri.params.split(";"): | |
212 | key, _, value = param.partition("=") | |
213 | ||
214 | params[key] = value | |
215 | ||
216 | return params | |
217 | ||
218 | @property | |
219 | def transport(self): | |
220 | return self.uri_params.get("transport") | |
221 | ||
222 | @lazy_property | |
223 | def user(self): | |
224 | return self.backend.accounts.get_by_sip_id(self.data.Endpoint) or self.data.Endpoint | |
225 | ||
226 | @property | |
227 | def address(self): | |
228 | # Remove the user | |
229 | user, _, address = self.uri.path.partition("@") | |
230 | ||
231 | # Remove the port | |
232 | address, _, port = address.rpartition(":") | |
233 | ||
234 | return address | |
235 | ||
236 | @property | |
237 | def user_agent(self): | |
238 | return self.data.UserAgent.replace("_", " ") | |
239 | ||
240 | @property | |
241 | def roundtrip(self): | |
242 | return int(self.data.RoundtripUsec) / 1000 | |
00465786 MT |
243 | |
244 | ||
245 | class OutboundRegistration(misc.Object): | |
246 | def init(self, data): | |
247 | self.data = data | |
248 | ||
249 | def __lt__(self, other): | |
250 | if isinstance(other, self.__class__): | |
251 | return self.server < other.server or self.username < other.username | |
252 | ||
253 | return NotImplemented | |
254 | ||
255 | @lazy_property | |
256 | def uri(self): | |
257 | return urllib.parse.urlparse(self.data.ClientUri) | |
258 | ||
259 | @property | |
260 | def server(self): | |
261 | username, _, server = self.uri.path.partition("@") | |
262 | ||
263 | return server | |
264 | ||
265 | @property | |
266 | def username(self): | |
267 | username, _, server = self.uri.path.partition("@") | |
268 | ||
269 | return username | |
270 | ||
271 | @property | |
272 | def status(self): | |
273 | return self.data.Status | |
8e93325b MT |
274 | |
275 | ||
276 | class Queue(misc.Object): | |
277 | def init(self, data): | |
278 | self.data = data | |
279 | ||
280 | self.members = [] | |
281 | ||
282 | def __str__(self): | |
283 | return self.name | |
284 | ||
285 | @property | |
286 | def name(self): | |
287 | return self.data.Queue | |
288 | ||
289 | def is_available(self): | |
290 | return self.data.Available == "1" | |
291 | ||
292 | @property | |
293 | def callers(self): | |
294 | return int(self.data.Callers) | |
295 | ||
296 | ||
297 | class QueueMember(misc.Object): | |
298 | def init(self, data): | |
299 | self.data = data | |
300 | ||
301 | def __str__(self): | |
302 | return self.name | |
303 | ||
304 | @property | |
305 | def name(self): | |
306 | return self.data.Name | |
307 | ||
308 | @property | |
309 | def queue(self): | |
310 | return self.data.Queue | |
311 | ||
312 | @property | |
313 | def calls_taken(self): | |
314 | return int(self.data.CallsTaken) | |
315 | ||
316 | def is_in_call(self): | |
317 | return self.data.InCall == "1" | |
318 | ||
319 | @property | |
320 | def last_call_at(self): | |
321 | return datetime.datetime.fromtimestamp(int(self.data.LastCall)) | |
322 | ||
323 | @property | |
324 | def logged_in_at(self): | |
325 | return datetime.datetime.fromtimestamp(int(self.data.LoginTime)) | |
326 | ||
327 | # XXX status? |