initial
This commit is contained in:
commit
94a1962043
1 changed files with 227 additions and 0 deletions
227
transfer_back.py
Executable file
227
transfer_back.py
Executable file
|
@ -0,0 +1,227 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from panoramisk import Manager
|
||||
from panoramisk.actions import Action
|
||||
|
||||
LOOP=asyncio.get_event_loop()
|
||||
|
||||
log = logging.getLogger('transferback')
|
||||
|
||||
async def on_tutto(manager, msg):
|
||||
for prefix in ['Var', 'RTC']:
|
||||
if msg.event.startswith(prefix):
|
||||
return
|
||||
log.debug('... Event %s', msg.event)
|
||||
|
||||
class BaseApp:
|
||||
def __init__(self, manager, msg):
|
||||
self.manager = manager
|
||||
self.initial_msg = msg
|
||||
self.local_channel = msg.channel
|
||||
self.events = []
|
||||
self.log = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
def send_agi_command(self, *args, **kwargs):
|
||||
return self.manager.send_agi_command(self.local_channel, *args, **kwargs)
|
||||
|
||||
def register_event(self, event, callback):
|
||||
self.events.append(
|
||||
(event, self.manager.register_event(event, callback)))
|
||||
|
||||
def unregister_event(self, event_name, callback=None):
|
||||
if callback is None:
|
||||
for ev, callback in self.events:
|
||||
if ev == event_name:
|
||||
self.unregister_event(event_name, callback)
|
||||
return
|
||||
else:
|
||||
self.events.remove((event_name, callback))
|
||||
self.manager.callbacks[event_name].remove(callback)
|
||||
|
||||
def self_destroy(self):
|
||||
for ev, callback in self.events:
|
||||
self.manager.callbacks[ev].remove(callback)
|
||||
|
||||
def filter_channel(channel):
|
||||
def create_func(f):
|
||||
def new_func(manager, event):
|
||||
if event.channel != channel:
|
||||
return
|
||||
return f(manager, event)
|
||||
return new_func
|
||||
return create_func
|
||||
|
||||
|
||||
class RefusedError(Exception):
|
||||
pass
|
||||
|
||||
class TransferAndTakeBack(BaseApp):
|
||||
'''
|
||||
The idea is to provide a method to implement a transfer, and then be able to "revoke" the transfer whenever we want.
|
||||
|
||||
In our scenario, there is a local user and a remote user.
|
||||
The remote user must have no power.
|
||||
The local user triggers it all.
|
||||
|
||||
When the local user activates a feature, which calls AGI(agi:sync), this is called.
|
||||
|
||||
This is the behavior we want:
|
||||
- the remote user is trasferred to a well-known place
|
||||
- the local user gets music
|
||||
- when the local user presses "#", we
|
||||
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.is_context_auth(self.initial_msg.context):
|
||||
raise RefusedError("Invalid context")
|
||||
self.log.info('%s init: %s', self.__class__.__name__, self.initial_msg)
|
||||
|
||||
|
||||
# meant to be overridden by children {{{
|
||||
def get_destination(self):
|
||||
return 'private,9438,1'
|
||||
|
||||
def get_backroom(self):
|
||||
return 'private,9401,1'
|
||||
|
||||
def get_waiting_room(self):
|
||||
return 'private,9401,1'
|
||||
|
||||
def is_context_auth(self, context):
|
||||
return context == 'from-regia'
|
||||
|
||||
# }}}
|
||||
|
||||
async def run(self):
|
||||
self.channels = []
|
||||
self.register_event('CoreShowChannel', self.on_channel)
|
||||
self.register_event('CoreShowChannelsComplete', self.on_channels)
|
||||
self.manager.send_action(Action({'Action': 'CoreShowChannels'}), as_list=False)
|
||||
# the response will actually be a series of CoreShowChannel, which will trigger self.on_channel
|
||||
|
||||
def on_channel(self, _, msg):
|
||||
self.channels.append(msg)
|
||||
|
||||
async def on_channels(self, _, msg):
|
||||
self.unregister_event('CoreShowChannel')
|
||||
self.unregister_event('CoreShowChannelsComplete')
|
||||
# we've got everything in many "on_channel" invocation, now let's check better
|
||||
our_channel = [m for m in self.channels if m.channel == self.local_channel]
|
||||
assert len(our_channel) == 1
|
||||
our_channel = our_channel[0]
|
||||
bridgeid = our_channel.bridgeid
|
||||
|
||||
channels_in_bridge = [m for m in self.channels if m.bridgeid == bridgeid and m.channel != self.local_channel]
|
||||
self.remote_channel = channels_in_bridge[0].channel.split(';')[0]
|
||||
|
||||
self.log.info('FOUND: %s', self.remote_channel)
|
||||
await self.on_remote_channel_found()
|
||||
|
||||
async def on_remote_channel_found(self):
|
||||
# now we know what is the other channel; this means we can move them wherever we want
|
||||
cmd = 'channel redirect {other} {dest}'.format(
|
||||
other=self.remote_channel,
|
||||
dest=self.get_destination()
|
||||
)
|
||||
self.log.debug('CMD = %s', cmd)
|
||||
self.manager.send_command(cmd)
|
||||
|
||||
# let's wait them somewhere
|
||||
cmd = 'channel redirect {channel} {waitingroom}'.format(
|
||||
channel=self.local_channel,
|
||||
waitingroom=self.get_waiting_room(),
|
||||
)
|
||||
self.log.debug('CMD = %s', cmd)
|
||||
self.manager.send_command(cmd)
|
||||
|
||||
# eventi possibili:
|
||||
# - l'utente locale preme dei tasti
|
||||
# - l'utente locale riaggancia
|
||||
self.register_event('DTMFEnd', filter_channel(self.local_channel)(self.on_dtmf))
|
||||
self.register_event('Hangup', filter_channel(self.local_channel)(self.on_hangup))
|
||||
self.register_event('Hangup', filter_channel(self.remote_channel)(self.on_hangup))
|
||||
|
||||
|
||||
def on_dtmf(self, _, msg):
|
||||
'''
|
||||
when the local user uses dtmf, they can talk back to the outside user
|
||||
'''
|
||||
if msg.digit != '9':
|
||||
return
|
||||
self.unregister_event('DTMFEnd')
|
||||
# a questo punto possiamo procedere a spostare il canale iniziale
|
||||
self.log.info("Local user requested to talk back with the remote user")
|
||||
|
||||
to_move = [self.remote_channel]
|
||||
if self.get_backroom() != self.get_waiting_room():
|
||||
to_move.append(self.local_channel)
|
||||
for channel in to_move:
|
||||
|
||||
cmd = 'channel redirect {chan} {newroom}'.format(
|
||||
chan=channel,
|
||||
newroom=self.get_backroom(),
|
||||
)
|
||||
self.log.debug('CMD = %s', cmd)
|
||||
self.manager.send_command(cmd)
|
||||
|
||||
# we must not self_destroy, or we'll never notice the Hangup
|
||||
assert set(e[0] for e in self.events) == {'Hangup'}
|
||||
|
||||
def on_hangup(self, _, msg):
|
||||
if msg.channel == self.remote_channel:
|
||||
self.log.info('Remote user hanged up')
|
||||
channel = self.local_channel
|
||||
elif msg.channel == self.local_channel:
|
||||
self.log.info('Local user hanged up')
|
||||
channel = self.remote_channel
|
||||
else:
|
||||
self.log.error('This should never happen! someone else hanged??? %s', msg.channel)
|
||||
return
|
||||
cmd = 'channel request hangup {channel}'.format(channel=channel)
|
||||
self.log.debug('CMD = %s', cmd)
|
||||
self.manager.send_command(cmd)
|
||||
|
||||
self.self_destroy()
|
||||
|
||||
def run_app(Cls):
|
||||
async def real_run(manager, msg):
|
||||
try:
|
||||
instance = Cls(manager, msg)
|
||||
except RefusedError:
|
||||
return
|
||||
await instance.run()
|
||||
return real_run
|
||||
|
||||
|
||||
async def init(manager):
|
||||
# manager.send_command('sip show peers')
|
||||
|
||||
manager.register_event('*', on_tutto)
|
||||
manager.register_event('AsyncAGIStart', run_app(TransferAndTakeBack))
|
||||
|
||||
async def on_shutdown(m):
|
||||
pass
|
||||
|
||||
def main():
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
manager = Manager(
|
||||
loop=LOOP,
|
||||
host='127.0.0.1',
|
||||
port=5038,
|
||||
ssl=False,
|
||||
encoding='utf8',
|
||||
username='admin',
|
||||
secret='secret123password',
|
||||
ping_delay=10, # Delay after start
|
||||
ping_interval=10, # Periodically ping AMI (dead or alive)
|
||||
reconnect_timeout=2, # Timeout reconnect if connection lost
|
||||
)
|
||||
manager.connect(run_forever=True, on_startup=init, on_shutdown=on_shutdown)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in a new issue