Asterisk und Jajah!
Wirklich schade, dass es peterzahlt.de nicht für Österreich gibt! So eine Einbindung in Asterisk wär ja wirklich spannend zu machen. Aber dafür hab ich etwas von Jajah gehört (ursprünglich offenbar ein Konkurrenzprodukt zu Skype). Auf der Homepage gibt man seine eigene Nummer an, die Zielnummer und man wird zurück gerufen. Wär einfach interessant, das in Asterisk zu implementieren. Und so hab ich zu hacken begonnen. Ein AGI-Script loggt sich über LWP::UserAgent in Jajah ein, und fordert den Rückruf an.
Und genau da kommt das große Problem: Der Rückruf kommt sehr schnell! Im Regelfall geht sich nicht einmal ein Hangup() aus. So ist für den eingehenden “Callback”-Anruf die Nebenstelle blockiert und er landet im Anrufbeantworter. Nicht schön. Gesucht wäre also eine Möglichkeit, einen eingehenden Anruf mit einem geantworteten Channel zu verbinden. Dazu hab ich aber leider nichts gefunden :-(. Aber über einen kleinen Hack gehts trotzdem…
Um ehrlich zu sein, hab ich dieses Problem am Anfang gar nicht bedacht und einfach einmal angefangen, das Parser-Script zu hacken. Erst beim Testen kam ich drauf, dass ich da was vergessen hatte.
Da es offenbar nicht möglich ist, zwei angenommene Channels zu verbinden, kam mir eine Idee: MeetMe() brauch ich ja sonst zu nix, aber da könnte es sich als nützlich erweisen! Bingo!
Vorgangsweise: Man will jemand anrufen. Asterisk überprüft, ob Jajah für den Anruf die günstigste Methode ist und ruft das AGI-Script auf. Dieses loggt sich ein und fordert den Callback an. Während des AGI-Scripts wird das ring-Signal eingespielt. Sofort danach wird ein Konferenzraum mit MeetMe() geöffnet, der solange MoH (Music On Hold) spielt, bis der “markierte” Anruf den Raum betritt. Dann sind die beiden Gespräche verbunden.
Glücklicherweise (?) spooft Jajah die CallerID, sodass der Callback-Anruf die CallerID meiner Wunschdestination hat. Also speichere ich kurz nach dem AGI-Script einen Unix-Timestamp mit der passenden CallerID in die Datenbank (AstDB).
Kommt nun ein Anruf herein, wird überprüft, ob ein Eintrag in der Datenbank existiert. Falls ja, wird er auf alle Fälle gelöscht. Ist dieser in einem Toleranzzeitraum (z.B. 1 Minute, es wird ja der Timestamp gespeichert), wird ebenfalls als markierte Person MeeMe() aufgerufen mit dem Timestamp als Konferenzraum-ID. Die Anrufer sind verbunden und alles ist normal. Ist das Timeout abgelaufen, so wird ganz einfach der Anruf weitergegeben.
Zusätzlich setzt das AGI-Script eine Variable JAJAH_STATUS. Damit ist es möglich zu prüfen, ob der Anruf ergolgreich war. Falls nein, kann als Fallback ein anderes Netz gewählt werden.
Anfangs hatte ich das ganze als komplizierten Dialplan drinnen und das AGI-Script übernahm nur das Parsing. Mittlerweile hab ich alles in das AGI-Script verpackt und es reichen wenige Funktionen im Dialplan
Seit neustem verwende ich zum Glück (!) AEL, sodass ich die Fragmente hier nur mehr als AEL-Code angib. Im Endeffekt muss folgendes Makro hinzugefügt werden:
macro isJajahCallback() {
AGI(jajah.agi,in);
};
Man beachte den Parameter “in” für den eingehenden Rückruf. In den Wählplan wird das im Input-Trunk einfach eingebunden:
context capi-in {
s => {
&hangupWhenChanBusy();
&isJajahCallback();
[...]
};
};
Für das Wählen legt man sich am besten einen eigenen Context an, zu dem man bei Bedarf springt:
context jajah {
_9X. => {
NUM=${EXTEN:1};
JAJAH_USER=jajah_username;
JAJAH_PASS=jajah_password;
JAJAH_NUMBER=registered_jajah_number;
AGI(jajah.agi,out,${NUM});
if(${JAJAH_STATUS} != 0) {
// Hier gehört stattdessen eine Fallbackbehandlung her!
Answer();
SayNumber(${JAJAH_STATUS});
Playtones(info);
Wait(60);
StopPlaytones();
Hangup();
};
t => {
Playtones(info);
Wait(60);
StopPlaytones();
Hangup();
};
};
Wiederum beachte man den zweiten Parameter “out” für “Herauswahl”. Das ist schon das einzige, was man im Dialplan machen muss. Den Rest erledigt das AGI-Script! Es müdden halt die Module für die Datenbank und MeetMe vorhanden und geladen sein.
Der Hauptcode des AGI-Scripts sieht so aus:
- !/usr/bin/perl
- Asterisk --> Jajah Gateway
- copyright 2006 nikolaus hammler
- licensed under GNU GPL
use strict; use LWP::UserAgent;
- use LWP::Debug qw(+);
use HTML::Parser; use HTTP::Cookies; use HTTP::Request::Common qw(POST); use Asterisk::AGI;
my $AGI = new Asterisk::AGI;
my %input = $AGI->ReadParse();
if($ARGV[0] eq "in") { print STDERR "Dialin mode\n";
my $exten = $input{'callerid'};
my $id = $AGI->get_variable("DB(jajah/$exten)"); if($id) { print STDERR "Yes, our callback. exten=$exten, id=$id\n";
$AGI->exec('DBDel', "jajah/$exten");
if(time() - $id < 120) { $AGI->exec(’Answer’); $AGI->exec(’MeetMe’, “$id|Aqd”); $AGI->exec(’Hangup’); } else { print STDERR “Warning: time’s up!\n”; exit 0; } } else { print STDERR “Maybe not our callback, ignoring\n”; exit 0; } } elsif($ARGV[0] ne “out”) { print STDERR “First argument (mode) must either be ‘in’ or ‘out’\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=8′); exit 0; }
print STDERR “Dialout mode\n”;
my $UserAgent = ‘Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rev:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1′;
my $ua = LWP::UserAgent->new();
$ua->agent($UserAgent);
$ua->cookie_jar(HTTP::Cookies->new);
my $username = $AGI->get_variable(’JAJAH_USER’); my $password = $AGI->get_variable(’JAJAH_PASS’); my $jajahnum = $AGI->get_variable(’JAJAH_NUMBER’);
print STDERR “Jajah Username: $username\n”; print STDERR “Jajah Password: $password\n”; print STDERR “Jajah Number: $jajahnum\n”;
if(!$username) { print STDERR “No username given. Please set variable JAJAH_USER\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=1′); exit 0; } if(!$password) { print STDERR “No password given. Please set variable JAJAH_PASS\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=2′); exit 0; } if(!$jajahnum) { print STDERR “No jajah number given. Please set JAJAH_NUMBER\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=6′); exit 0; }
my $exten = $input{’agi_extension’};
if($ARGV[1] ne “”) { $exten = $ARGV[1]; }
print STDERR “ToDial: $exten\n”;
if(!$exten) { print STDERR “Don’t know what to dial, sorry\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=3′); exit 0; }
my $area = ‘43′; my $number = substr($exten, 1);
if(substr($exten, 0, 2) eq ‘00′) {
- fixxme: Was is wenn vorwahl 3-stellig?
$area = substr($exten, 2, 2); $number = substr($exten, 4); } elsif(substr($exten, 0, 1) ne ‘0′) { print STDERR “Wrong format. Pleas use prefix 0 for national and prefix 00 for international calls\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=5′); exit 0; }
$AGI->exec(’Answer’); $AGI->exec(’Playtones’, ‘ring’);
my $html = &PostFormInHTML(’http://www.jajah.com/mini/login.aspx’, ‘theForm’, { ‘ctl00$MainContent$Email’ => $username, ‘ctl00$MainContent$Password’ => $password});
if($html =~ /Successfully logged in/) { print STDERR “Login success. Trying to dial…\n”;
$html = &PostFormInHTML(’http://www.jajah.com/mini/member.aspx?’,
- $html = &PostFormInHTML(’http://www.google.com’,
‘theForm’, {’mynumber’ => $jajahnum, ‘ctl00$MainContent$CallTo$minidialcode’ => $area, ‘ctl00$MainContent$CallTo$mininumber’ => $number});
if($html =~ /Active call/) { print STDERR “Everything seems fine, callback should come shortly\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=0′);
my $id = time();
$AGI->exec(’Set’, “DB(jajah/$exten)=$id”); $AGI->exec(’StopPlaytones’); $AGI->exec(’MeetMe’, “$id|1Mdqwx”);
$AGI->exec(’Hangup’);
exit 0; } else { if(open(DEBUG, “>/tmp/asterisk_jajah_debug.html”)) { print DEBUG $html; close(DEBUG); } print STDERR “Call failed. Maybe you can find debug info in /tmp/asterisk_jajah_debug.html\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=7′); $AGI->exec(’StopPlaytones’); } } else { print STDERR “Login failed\n”; $AGI->exec(’Set’, ‘JAJAH_STATUS=4′); $AGI->exec(’StopPlaytones’); exit 0; }
Den ganzen Code hab ich als Attachment hinzugefügt.
Conclusio: Das ganze war lustig zum programmieren, aber verwendet hab ichs nie weil ich erst später draufgekommen bin, das Jajah nur so unwesentlich billiger ist, dass es sich gar nicht auszahlt. Media:jajahagi.txt