├── src
├── Parser.php
├── Commands
│ ├── AmiUssd.php
│ ├── OutputStyle.php
│ ├── AmiListen.php
│ ├── AmiCli.php
│ ├── AmiAbstract.php
│ ├── AmiSms.php
│ ├── AmiAction.php
│ └── Command.php
├── Factory.php
└── Providers
│ └── AmiServiceProvider.php
├── bin
└── ami
├── tests
├── Factory.php
├── AmiServiceProvider.php
├── ActionTest.php
├── TestCase.php
└── EventTest.php
├── LICENSE.md
├── composer.json
└── config
└── ami.php
/src/Parser.php:
--------------------------------------------------------------------------------
1 | instance('config', new Repository());
15 | (new EventServiceProvider($container))->register();
16 | (new AmiServiceProvider($container))->register();
17 |
18 | $app = new Application($container, $container['events'], null);
19 | $app->run();
20 |
--------------------------------------------------------------------------------
/tests/Factory.php:
--------------------------------------------------------------------------------
1 | stream = $stream;
23 | }
24 |
25 | /**
26 | * Create client.
27 | *
28 | * @param array $options
29 | *
30 | * @return \React\Promise\Promise
31 | */
32 | public function create(array $options = [])
33 | {
34 | return new FulfilledPromise(new Client($this->stream, new Parser()));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Evgeni Razumov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Commands/AmiUssd.php:
--------------------------------------------------------------------------------
1 | call('ami:action', [
31 | 'action' => 'DongleSendUSSD',
32 | '--arguments' => [
33 | "Device:{$this->argument('device')}",
34 | "USSD:{$this->argument('ussd')}",
35 | ],
36 | ]);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/AmiServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerStream();
19 | parent::register();
20 | }
21 |
22 | /**
23 | * Register stream.
24 | */
25 | protected function registerStream()
26 | {
27 | $this->app->singleton(Stream::class, function ($app) {
28 | return new Stream(fopen('php://memory', 'r+'), $app[LoopInterface::class]);
29 | });
30 | $this->app->alias(Stream::class, 'ami.stream');
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | protected function registerFactory()
37 | {
38 | $this->app->singleton(Factory::class, function ($app) {
39 | return new TestFactory($app[LoopInterface::class], $app[ConnectorInterface::class], $app[Stream::class]);
40 | });
41 | $this->app->alias(Factory::class, 'ami.factory');
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/ActionTest.php:
--------------------------------------------------------------------------------
1 | 'Success',
14 | 'ActionID' => '1',
15 | 'Message' => 'Channel status will follow',
16 | ],
17 | ];
18 | $this->events->listen('ami.action.sended', function () use ($messages) {
19 | $this->assertTrue(true);
20 | $this->stream->emit('data', ["Asterisk Call Manager/1.3\r\n"]);
21 | foreach ($messages as $lines) {
22 | $message = '';
23 | foreach ($lines as $key => $value) {
24 | $message .= "{$key}: {$value}\r\n";
25 | }
26 | $this->stream->emit('data', ["{$message}\r\n"]);
27 | }
28 | });
29 | $this->events->listen('ami.action.responsed', function ($console, $action, Response $response) {
30 | $this->assertEquals('Status', $action);
31 | $this->assertEquals($response->getFields(), [
32 | 'Response' => 'Success',
33 | 'ActionID' => '1',
34 | 'Message' => 'Channel status will follow',
35 | ]);
36 | });
37 | $this->console('ami:action', [
38 | 'action' => 'Status',
39 | ]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | instance('config', new Repository());
41 | (new EventServiceProvider($app))->register();
42 | (new AmiServiceProvider($app))->register();
43 | $this->loop = $app[LoopInterface::class];
44 | $this->loop->nextTick(function () {
45 | if (!$this->running) {
46 | $this->loop->stop();
47 | }
48 | });
49 | $this->stream = $app[Stream::class];
50 | $this->events = $app['events'];
51 | $this->app = $app;
52 | }
53 |
54 | /**
55 | * Call console command.
56 | *
57 | * @param string $command
58 | * @param array $options
59 | */
60 | protected function console($command, array $options = [])
61 | {
62 | return (new Console($this->app, $this->events, '5.3'))->call($command, $options);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Commands/OutputStyle.php:
--------------------------------------------------------------------------------
1 | output = $output;
27 |
28 | parent::__construct($input, $output);
29 | }
30 |
31 | /**
32 | * Returns whether verbosity is quiet (-q).
33 | *
34 | * @return bool
35 | */
36 | public function isQuiet()
37 | {
38 | return $this->output->isQuiet();
39 | }
40 |
41 | /**
42 | * Returns whether verbosity is verbose (-v).
43 | *
44 | * @return bool
45 | */
46 | public function isVerbose()
47 | {
48 | return $this->output->isVerbose();
49 | }
50 |
51 | /**
52 | * Returns whether verbosity is very verbose (-vv).
53 | *
54 | * @return bool
55 | */
56 | public function isVeryVerbose()
57 | {
58 | return $this->output->isVeryVerbose();
59 | }
60 |
61 | /**
62 | * Returns whether verbosity is debug (-vvv).
63 | *
64 | * @return bool
65 | */
66 | public function isDebug()
67 | {
68 | return $this->output->isDebug();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "enniel/ami",
3 | "type": "library",
4 | "description": "Provide asterisk ami to laravel",
5 | "keywords": [
6 | "Enniel",
7 | "Ami",
8 | "Asterisk"
9 | ],
10 | "homepage": "https://github.com/Enniel/Ami",
11 | "license": "MIT",
12 | "authors": [
13 | {
14 | "name": "Enniel"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=5.6.0",
19 | "ext-mbstring": "*",
20 | "illuminate/support": "~5.1",
21 | "illuminate/console": "~5.1",
22 | "illuminate/events": "~5.1",
23 | "illuminate/contracts": "~5.1",
24 | "react/dns": "~0.4.3",
25 | "react/stream": "~0.4.6",
26 | "react/event-loop": "~0.4.2",
27 | "react/socket-client": "~0.4.6",
28 | "clue/ami-react": "~0.3.1",
29 | "jackkum/phppdu": "~1.2.10"
30 | },
31 | "require-dev": {
32 | "friendsofphp/php-cs-fixer": "~2.0.0",
33 | "phpunit/phpunit": "~4.5|~5.0",
34 | "illuminate/config": "~5.1",
35 | "illuminate/container": "~5.1"
36 | },
37 | "autoload": {
38 | "psr-4": {
39 | "Enniel\\Ami\\": "src"
40 | }
41 | },
42 | "autoload-dev": {
43 | "psr-4": {
44 | "Enniel\\Ami\\Tests\\": "tests"
45 | }
46 | },
47 | "scripts": {
48 | "test": [
49 | "@phpunit",
50 | "@phpcs"
51 | ],
52 | "phpunit": "php vendor/bin/phpunit",
53 | "phpcs": "php vendor/bin/php-cs-fixer --diff --dry-run -v fix"
54 | },
55 | "bin": [
56 | "bin/ami"
57 | ],
58 | "extra": {
59 | "branch-alias": {
60 | "dev-master": "2.0-dev"
61 | }
62 | },
63 | "minimum-stability": "dev",
64 | "prefer-stable": true
65 | }
66 |
--------------------------------------------------------------------------------
/src/Factory.php:
--------------------------------------------------------------------------------
1 | connector = $connector;
31 | $this->loop = $loop;
32 | }
33 |
34 | /**
35 | * Create client.
36 | *
37 | * @param array $options
38 | *
39 | * @return \React\Promise\Promise
40 | */
41 | public function create(array $options = [])
42 | {
43 | foreach (['host', 'port', 'username', 'secret'] as $key) {
44 | $options[$key] = Arr::get($options, $key, null);
45 | }
46 | $promise = $this->connector->create($options['host'], $options['port'])->then(function (Stream $stream) {
47 | return new Client($stream, new Parser());
48 | });
49 | if (!is_null($options['username'])) {
50 | $promise = $promise->then(function (Client $client) use ($options) {
51 | $sender = new ActionSender($client);
52 |
53 | return $sender->login($options['username'], $options['secret'])->then(
54 | function () use ($client) {
55 | return $client;
56 | },
57 | function ($error) use ($client) {
58 | $client->close();
59 | throw $error;
60 | }
61 | );
62 | }, function ($error) {
63 | throw $error;
64 | });
65 | }
66 |
67 | return $promise;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Commands/AmiListen.php:
--------------------------------------------------------------------------------
1 | info($event->getName());
39 | $fields = [];
40 | foreach ($event->getFields() as $key => $value) {
41 | $fields[] = [
42 | $key,
43 | $value,
44 | ];
45 | }
46 | $this->table($this->headers, $fields);
47 | }
48 |
49 | public function eventEmitter(Event $event)
50 | {
51 | $name = $event->getName();
52 | $options = Arr::get($this->events, $name, []);
53 | $params = [$event, $options];
54 | $this->dispatcher->fire('ami.events.*', $params);
55 | $this->dispatcher->fire('ami.events.'.$name, $params);
56 | }
57 |
58 | public function client(Client $client)
59 | {
60 | parent::client($client);
61 | $this->info('starting listen ami');
62 | if ($this->option('monitor') && $this->runningInConsole()) {
63 | $client->on('event', [$this, 'eventMonitor']);
64 | }
65 | $client->on('close', function () {
66 | // the connection to the AMI just closed
67 | $this->info('closed listen ami');
68 | });
69 | $client->on('event', [$this, 'eventEmitter']);
70 | $this->dispatcher->fire('ami.listen.started', [$this, $client]);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Commands/AmiCli.php:
--------------------------------------------------------------------------------
1 | info('starting ami cli interface');
40 | $command = $this->argument('cli');
41 | if (!empty($command)) {
42 | $this->sendCommand($command);
43 | } else {
44 | $this->writeInterface();
45 | }
46 | $client->on('close', function () {
47 | // the connection to the AMI just closed
48 | $this->info('closed ami cli');
49 | });
50 | }
51 |
52 | public function sendCommand($command)
53 | {
54 | $this->request('Command', [
55 | 'Command' => $command,
56 | ])->then([$this, 'writeResponse'], [$this, 'writeException']);
57 | }
58 |
59 | public function writeInterface()
60 | {
61 | $command = $this->ask('Write command');
62 | if (in_array(mb_strtolower($command), $this->exit)) {
63 | $this->stop();
64 | }
65 | $this->sendCommand($command);
66 | }
67 |
68 | public function writeResponse(Response $response)
69 | {
70 | $this->line($response->getCommandOutput());
71 | $autoclose = $this->option('autoclose');
72 | if ($autoclose) {
73 | $this->stop();
74 | }
75 | $this->writeInterface();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Commands/AmiAbstract.php:
--------------------------------------------------------------------------------
1 | loop = $loop;
31 | $this->connector = $connector;
32 | $this->events = Arr::get($config, 'events', []);
33 | $this->config = $config;
34 | $this->dispatcher = $dispatcher;
35 | }
36 |
37 | /**
38 | * Execute the console command.
39 | *
40 | * @return mixed
41 | */
42 | public function handle()
43 | {
44 | $options = $this->options();
45 | foreach (['host', 'port', 'username', 'secret'] as $key) {
46 | $value = Arr::get($options, $key, null);
47 | $value = is_null($value) ? Arr::get($this->config, $key, null) : $value;
48 | $options[$key] = $value;
49 | }
50 | $client = $this->connector->create($options);
51 | $client->then([$this, 'client'], [$this, 'writeException']);
52 | $this->loop->run();
53 | }
54 |
55 | public function client(Client $client)
56 | {
57 | $this->client = $client;
58 | $this->client->on('error', [$this, 'writeException']);
59 | }
60 |
61 | public function writeException(Exception $e)
62 | {
63 | $this->warn($e->getMessage());
64 | $this->stop();
65 | }
66 |
67 | public function writeResponse(Response $response)
68 | {
69 | $message = Arr::get($response->getFields(), 'Message', null);
70 | $this->line($message);
71 | $this->stop();
72 | }
73 |
74 | public function request($action, array $options = [])
75 | {
76 | return $this->client->request($this->client->createAction($action, $options));
77 | }
78 |
79 | public function stop()
80 | {
81 | $this->loop->stop();
82 |
83 | return false;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Commands/AmiSms.php:
--------------------------------------------------------------------------------
1 | argument('device');
39 |
40 | return $device ? $device : array_get($this->config, 'dongle.sms.device');
41 | }
42 |
43 | public function sendSms()
44 | {
45 | $this->request('DongleSendSms', [
46 | 'Device' => $this->getDevice(),
47 | 'Number' => $this->argument('number'),
48 | 'Message' => $this->argument('message'),
49 | ])->then([$this, 'writeResponse'], [$this, 'writeException']);
50 | }
51 |
52 | public function sendPdu()
53 | {
54 | $pdu = new Submit();
55 | $pdu->setAddress($this->argument('number'));
56 | $pdu->setData($this->argument('message'));
57 | $promises = [];
58 | foreach ($pdu->getParts() as $part) {
59 | $promises[] = $this->request('DongleSendPdu', [
60 | 'Device' => $this->getDevice(),
61 | 'PDU' => (string) $part,
62 | ]);
63 | }
64 | $promise = \React\Promise\map($promises, function (Response $response) {
65 | Event::fire('ami.dongle.sms.sended', [$this, $response]);
66 | $message = Arr::get($response->getFields(), 'Message', null);
67 | $this->line($message);
68 | });
69 | $promise->then([$this, 'stop'], [$this, 'writeException']);
70 | }
71 |
72 | public function client(Client $client)
73 | {
74 | parent::client($client);
75 | $func = 'sendSms';
76 | if ($this->option('pdu')) {
77 | $func = 'sendPdu';
78 | }
79 | call_user_func([$this, $func]);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Commands/AmiAction.php:
--------------------------------------------------------------------------------
1 | option('arguments');
43 | $arguments = is_array($arguments) ? $arguments : [];
44 | $options = [];
45 | $isAssoc = Arr::isAssoc($arguments);
46 | foreach ($arguments as $key => $value) {
47 | if (Str::contains($value, ':') && !$isAssoc) {
48 | $array = explode(':', $value);
49 | if ($key = Arr::get($array, 0)) {
50 | $value = Arr::get($array, 1, '');
51 | $options[$key] = $value;
52 | }
53 | } else {
54 | $options[$key] = $value;
55 | }
56 | }
57 |
58 | $action = $this->argument('action');
59 | $request = $this->request($action, $options);
60 |
61 | $this->dispatcher->fire('ami.action.sended', [$this, $action, $request]);
62 |
63 | $request->then(
64 | function (Response $response) use ($action) {
65 | $this->dispatcher->fire('ami.action.responsed', [$this, $action, $response]);
66 | if ($this->runningInConsole()) {
67 | $this->responseMonitor($response);
68 | }
69 | $this->stop();
70 | },
71 | function (Exception $exception) {
72 | throw $exception;
73 | });
74 | }
75 |
76 | public function responseMonitor(Response $response)
77 | {
78 | $fields = [];
79 | foreach ($response->getFields() as $key => $value) {
80 | $fields[] = [
81 | $key,
82 | $value,
83 | ];
84 | }
85 | $this->table($this->headers, $fields);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/config/ami.php:
--------------------------------------------------------------------------------
1 | '127.0.0.1',
5 | 'port' => 5038,
6 | 'username' => null,
7 | 'secret' => null,
8 | 'dongle' => [
9 | 'sms' => [
10 | 'device' => null,
11 | ],
12 | ],
13 | 'events' => [
14 | 'AGIExec' => [
15 | ],
16 | 'AgentConnect' => [
17 | ],
18 | 'AgentComplete' => [
19 | ],
20 | 'Agentlogin' => [
21 | ],
22 | 'Agentlogoff' => [
23 | ],
24 | 'Agents' => [
25 | ],
26 | 'AsyncAGI' => [
27 | ],
28 | 'Bridge' => [
29 | ],
30 | 'CDR' => [
31 | ],
32 | 'CEL' => [
33 | ],
34 | 'ChannelUpdate' => [
35 | ],
36 | 'CoreShowChannel' => [
37 | ],
38 | 'CoreShowChannelsComplete' => [
39 | ],
40 | 'DAHDIShowChannelsComplete' => [
41 | ],
42 | 'DAHDIShowChannels' => [
43 | ],
44 | 'DBGetResponse' => [
45 | ],
46 | 'DTMF' => [
47 | ],
48 | 'Dial' => [
49 | ],
50 | 'DongleDeviceEntry' => [
51 | ],
52 | 'DongleNewCUSD' => [
53 | ],
54 | 'DongleNewUSSDBase64' => [
55 | ],
56 | 'DongleNewUSSD' => [
57 | ],
58 | 'DongleSMSStatus' => [
59 | ],
60 | 'DongleShowDevicesComplete' => [
61 | ],
62 | 'DongleStatus' => [
63 | ],
64 | 'DongleUSSDStatus' => [
65 | ],
66 | 'DonglePortFail' => [
67 | ],
68 | 'ExtensionStatus' => [
69 | ],
70 | 'FullyBooted' => [
71 | ],
72 | 'Hangup' => [
73 | ],
74 | 'Hold' => [
75 | ],
76 | 'JabberEvent' => [
77 | ],
78 | 'Join' => [
79 | ],
80 | 'Leave' => [
81 | ],
82 | 'Link' => [
83 | ],
84 | 'ListDialPlan' => [
85 | ],
86 | 'Masquerade' => [
87 | ],
88 | 'MessageWaiting' => [
89 | ],
90 | 'MusicOnHold' => [
91 | ],
92 | 'NewAccountCode' => [
93 | ],
94 | 'NewCallerid' => [
95 | ],
96 | 'Newchannel' => [
97 | ],
98 | 'Newexten' => [
99 | ],
100 | 'Newstate' => [
101 | ],
102 | 'OriginateResponse' => [
103 | ],
104 | 'ParkedCall' => [
105 | ],
106 | 'ParkedCallsComplete' => [
107 | ],
108 | 'PeerEntry' => [
109 | ],
110 | 'PeerStatus' => [
111 | ],
112 | 'PeerlistComplete' => [
113 | ],
114 | 'QueueMemberAdded' => [
115 | ],
116 | 'QueueMember' => [
117 | ],
118 | 'QueueMemberPaused' => [
119 | ],
120 | 'QueueMemberRemoved' => [
121 | ],
122 | 'QueueMemberStatus' => [
123 | ],
124 | 'QueueParams' => [
125 | ],
126 | 'QueueStatusComplete' => [
127 | ],
128 | 'QueueSummaryComplete' => [
129 | ],
130 | 'QueueSummary' => [
131 | ],
132 | 'RTCPReceived' => [
133 | ],
134 | 'RTCPReceiverStat' => [
135 | ],
136 | 'RTCPSent' => [
137 | ],
138 | 'RTPReceiverStat' => [
139 | ],
140 | 'RTPSenderStat' => [
141 | ],
142 | 'RegistrationsComplete' => [
143 | ],
144 | 'Registry' => [
145 | ],
146 | 'Rename' => [
147 | ],
148 | 'ShowDialPlanComplete' => [
149 | ],
150 | 'StatusComplete' => [
151 | ],
152 | 'Status' => [
153 | ],
154 | 'Transfer' => [
155 | ],
156 | 'UnParkedCall' => [
157 | ],
158 | 'Unlink' => [
159 | ],
160 | 'UserEvent' => [
161 | ],
162 | 'VarSet' => [
163 | ],
164 | ],
165 | ];
166 |
--------------------------------------------------------------------------------
/src/Providers/AmiServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
26 | realpath(__DIR__.'/../../config/ami.php') => config_path('ami.php'),
27 | ], 'ami');
28 | }
29 |
30 | /**
31 | * Register any package services.
32 | */
33 | public function register()
34 | {
35 | $this->registerConfig();
36 | $this->registerEventLoop();
37 | $this->registerConnector();
38 | $this->registerFactory();
39 | $this->registerDongleUssd();
40 | $this->registerAmiListen();
41 | $this->registerAmiAction();
42 | $this->registerDongleSms();
43 | $this->registerAmiCli();
44 | $this->commands([
45 | 'command.ami.dongle.ussd',
46 | 'command.ami.dongle.sms',
47 | 'command.ami.listen',
48 | 'command.ami.action',
49 | 'command.ami.cli',
50 | ]);
51 | }
52 |
53 | /**
54 | * Register the configuration.
55 | */
56 | protected function registerConfig()
57 | {
58 | $this->mergeConfigFrom(realpath(__DIR__.'/../../config/ami.php'), 'ami');
59 | }
60 |
61 | /**
62 | * Register the ami listen command.
63 | */
64 | protected function registerAmiListen()
65 | {
66 | $this->app->singleton(AmiListen::class, function ($app) {
67 | return new AmiListen($app['events'], $app['ami.eventloop'], $app['ami.factory'], $app['config']['ami']);
68 | });
69 | $this->app->alias(AmiListen::class, 'command.ami.listen');
70 | }
71 |
72 | /**
73 | * Register the ami listen command.
74 | */
75 | protected function registerAmiCli()
76 | {
77 | $this->app->singleton(AmiCli::class, function ($app) {
78 | return new AmiCli($app['events'], $app['ami.eventloop'], $app['ami.factory'], $app['config']['ami']);
79 | });
80 | $this->app->alias(AmiCli::class, 'command.ami.cli');
81 | }
82 |
83 | /**
84 | * Register the ami action sender.
85 | */
86 | protected function registerAmiAction()
87 | {
88 | $this->app->singleton(AmiAction::class, function ($app) {
89 | return new AmiAction($app['events'], $app['ami.eventloop'], $app['ami.factory'], $app['config']['ami']);
90 | });
91 | $this->app->alias(AmiAction::class, 'command.ami.action');
92 | }
93 |
94 | /**
95 | * Register the dongle sms.
96 | */
97 | protected function registerDongleSms()
98 | {
99 | $this->app->singleton(AmiSms::class, function ($app) {
100 | return new AmiSms($app['events'], $app['ami.eventloop'], $app['ami.factory'], $app['config']['ami']);
101 | });
102 | $this->app->alias(AmiSms::class, 'command.ami.dongle.sms');
103 | }
104 |
105 | /**
106 | * Register the dongle ussd.
107 | */
108 | protected function registerDongleUssd()
109 | {
110 | $this->app->singleton(AmiUssd::class, function ($app) {
111 | return new AmiUssd();
112 | });
113 | $this->app->alias(AmiUssd::class, 'command.ami.dongle.ussd');
114 | }
115 |
116 | /**
117 | * Register event loop.
118 | */
119 | protected function registerEventLoop()
120 | {
121 | $this->app->singleton(LoopInterface::class, function () {
122 | return new StreamSelectLoop();
123 | });
124 | $this->app->alias(LoopInterface::class, 'ami.eventloop');
125 | }
126 |
127 | /**
128 | * Register connector.
129 | */
130 | protected function registerConnector()
131 | {
132 | $this->app->singleton(ConnectorInterface::class, function ($app) {
133 | $loop = $app[LoopInterface::class];
134 |
135 | return new Connector($loop, (new DnsResolver())->create('8.8.8.8', $loop));
136 | });
137 | $this->app->alias(ConnectorInterface::class, 'ami.connector');
138 | }
139 |
140 | /**
141 | * Register factory.
142 | */
143 | protected function registerFactory()
144 | {
145 | $this->app->singleton(Factory::class, function ($app) {
146 | return new Factory($app[LoopInterface::class], $app[ConnectorInterface::class]);
147 | });
148 | $this->app->alias(Factory::class, 'ami.factory');
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/tests/EventTest.php:
--------------------------------------------------------------------------------
1 | 'AgentConnect',
14 | 'Privilege' => 'agent,all',
15 | 'Queue' => 'taxi-operators',
16 | 'Uniqueid' => '1321511811.113',
17 | 'Channel' => 'SIP/100-00000072',
18 | 'Member' => 'SIP/100',
19 | 'MemberName' => 'SIP/100',
20 | 'Holdtime' => '10',
21 | 'BridgedChannel' => '1321511815.114',
22 | 'Ringtime' => '9',
23 | ],
24 | [
25 | 'Event' => 'AgentComplete',
26 | 'Privilege' => 'agent,all',
27 | 'Queue' => 'taxi-operators',
28 | 'Uniqueid' => '1321511811.113',
29 | 'Channel' => 'SIP/100-00000072',
30 | 'Member' => 'SIP/100',
31 | 'MemberName' => 'SIP/100',
32 | 'HoldTime' => '10',
33 | 'TalkTime' => '7',
34 | 'Reason' => 'caller',
35 | ],
36 | [
37 | 'Event' => 'Bridge',
38 | 'Privilege' => 'call,all',
39 | 'Bridgestate' => 'Link',
40 | 'Bridgetype' => 'core',
41 | 'Channel1' => 'SIP/mangotrunk-0000016c',
42 | 'Channel2' => 'SIP/261-0000016d',
43 | 'Uniqueid1' => '1324068645.605',
44 | 'Uniqueid2' => '1324068650.606',
45 | 'Callerid1' => '74997623634',
46 | 'Callerid2' => '261',
47 | ],
48 | [
49 | 'Event' => 'Dial',
50 | 'Privilege' => 'call,all',
51 | 'Subevent' => 'Begin',
52 | 'Channel' => 'SIP/mangotrunk-0000016c',
53 | 'Destination' => 'SIP/261-0000016d',
54 | 'Calleridnum' => '74997623634',
55 | 'Calleridname' => '74997623634',
56 | 'Uniqueid' => '1324068645.605',
57 | 'Destuniqueid' => '1324068650.606',
58 | 'Dialstring' => '261',
59 | ],
60 | [
61 | 'Event' => 'FullyBooted',
62 | 'Privilege' => 'system,all',
63 | 'Status' => 'Fully Booted',
64 | ],
65 | [
66 | 'Event' => 'Join',
67 | 'Privilege' => 'call,all',
68 | 'Channel' => 'SIP/multifon-out-00000071',
69 | 'CallerIDNum' => '79265224173',
70 | 'CallerIDName' => 'unknown',
71 | 'ConnectedLineNum' => 'unknown',
72 | 'ConnectedLineName' => 'unknown',
73 | 'Queue' => 'taxi-operators',
74 | 'Position' => '1',
75 | 'Count' => '1',
76 | 'Uniqueid' => '1321511811.113',
77 | ],
78 | [
79 | 'Event' => 'Link',
80 | 'Channel1' => 'SIP/101-3f3f',
81 | 'Channel2' => 'Zap/2-1',
82 | 'Uniqueid1' => '1094154427.10',
83 | 'Uniqueid2' => '1094154427.11',
84 | ],
85 | [
86 | 'Event' => 'DonglePortFail',
87 | 'Privilege' => 'call,all',
88 | 'Device' => '/dev/ttyUSB8',
89 | 'Message' => 'Response Failed',
90 | ],
91 | ];
92 | $this->events->listen('ami.listen.started', function () use ($messages) {
93 | $this->assertTrue(true);
94 | $this->stream->emit('data', ["Asterisk Call Manager/1.3\r\n"]);
95 | foreach ($messages as $lines) {
96 | $message = '';
97 | foreach ($lines as $key => $value) {
98 | $message .= "{$key}: {$value}\r\n";
99 | }
100 | $this->stream->emit('data', ["{$message}\r\n"]);
101 | }
102 | });
103 | $this->events->listen('ami.events.AgentConnect', function (Event $event) {
104 | $this->assertEquals($event->getFields(), [
105 | 'Event' => 'AgentConnect',
106 | 'Privilege' => 'agent,all',
107 | 'Queue' => 'taxi-operators',
108 | 'Uniqueid' => '1321511811.113',
109 | 'Channel' => 'SIP/100-00000072',
110 | 'Member' => 'SIP/100',
111 | 'MemberName' => 'SIP/100',
112 | 'Holdtime' => '10',
113 | 'BridgedChannel' => '1321511815.114',
114 | 'Ringtime' => '9',
115 | ]);
116 | $this->assertEquals($event->getName(), 'AgentConnect');
117 | });
118 | $this->events->listen('ami.events.AgentComplete', function (Event $event) {
119 | $this->assertEquals($event->getFields(), [
120 | 'Event' => 'AgentComplete',
121 | 'Privilege' => 'agent,all',
122 | 'Queue' => 'taxi-operators',
123 | 'Uniqueid' => '1321511811.113',
124 | 'Channel' => 'SIP/100-00000072',
125 | 'Member' => 'SIP/100',
126 | 'MemberName' => 'SIP/100',
127 | 'HoldTime' => '10',
128 | 'TalkTime' => '7',
129 | 'Reason' => 'caller',
130 | ]);
131 | $this->assertEquals($event->getName(), 'AgentComplete');
132 | });
133 | $this->events->listen('ami.events.Bridge', function (Event $event) {
134 | $this->assertEquals($event->getFields(), [
135 | 'Event' => 'Bridge',
136 | 'Privilege' => 'call,all',
137 | 'Bridgestate' => 'Link',
138 | 'Bridgetype' => 'core',
139 | 'Channel1' => 'SIP/mangotrunk-0000016c',
140 | 'Channel2' => 'SIP/261-0000016d',
141 | 'Uniqueid1' => '1324068645.605',
142 | 'Uniqueid2' => '1324068650.606',
143 | 'Callerid1' => '74997623634',
144 | 'Callerid2' => '261',
145 | ]);
146 | $this->assertEquals($event->getName(), 'Bridge');
147 | });
148 | $this->events->listen('ami.events.Dial', function (Event $event) {
149 | $this->assertEquals($event->getFields(), [
150 | 'Event' => 'Dial',
151 | 'Privilege' => 'call,all',
152 | 'Subevent' => 'Begin',
153 | 'Channel' => 'SIP/mangotrunk-0000016c',
154 | 'Destination' => 'SIP/261-0000016d',
155 | 'Calleridnum' => '74997623634',
156 | 'Calleridname' => '74997623634',
157 | 'Uniqueid' => '1324068645.605',
158 | 'Destuniqueid' => '1324068650.606',
159 | 'Dialstring' => '261',
160 | ]);
161 | $this->assertEquals($event->getName(), 'Dial');
162 | });
163 | $this->events->listen('ami.events.FullyBooted', function (Event $event) {
164 | $this->assertEquals($event->getFields(), [
165 | 'Event' => 'FullyBooted',
166 | 'Privilege' => 'system,all',
167 | 'Status' => 'Fully Booted',
168 | ]);
169 | $this->assertEquals($event->getName(), 'FullyBooted');
170 | });
171 | $this->events->listen('ami.events.Join', function (Event $event) {
172 | $this->assertEquals($event->getFields(), [
173 | 'Event' => 'Join',
174 | 'Privilege' => 'call,all',
175 | 'Channel' => 'SIP/multifon-out-00000071',
176 | 'CallerIDNum' => '79265224173',
177 | 'CallerIDName' => 'unknown',
178 | 'ConnectedLineNum' => 'unknown',
179 | 'ConnectedLineName' => 'unknown',
180 | 'Queue' => 'taxi-operators',
181 | 'Position' => '1',
182 | 'Count' => '1',
183 | 'Uniqueid' => '1321511811.113',
184 | ]);
185 | $this->assertEquals($event->getName(), 'Join');
186 | });
187 | $this->events->listen('ami.events.Link', function (Event $event) {
188 | $this->assertEquals($event->getFields(), [
189 | 'Event' => 'Link',
190 | 'Channel1' => 'SIP/101-3f3f',
191 | 'Channel2' => 'Zap/2-1',
192 | 'Uniqueid1' => '1094154427.10',
193 | 'Uniqueid2' => '1094154427.11',
194 | ]);
195 | $this->assertEquals($event->getName(), 'Link');
196 | });
197 | $this->events->listen('ami.events.DonglePortFail', function (Event $event) {
198 | $this->assertEquals($event->getFields(), [
199 | 'Event' => 'DonglePortFail',
200 | 'Privilege' => 'call,all',
201 | 'Device' => '/dev/ttyUSB8',
202 | 'Message' => 'Response Failed',
203 | ]);
204 | $this->assertEquals($event->getName(), 'DonglePortFail');
205 | $this->running = false;
206 | });
207 | $this->running = true;
208 | $this->console('ami:listen');
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/Commands/Command.php:
--------------------------------------------------------------------------------
1 | OutputInterface::VERBOSITY_VERBOSE,
75 | 'vv' => OutputInterface::VERBOSITY_VERY_VERBOSE,
76 | 'vvv' => OutputInterface::VERBOSITY_DEBUG,
77 | 'quiet' => OutputInterface::VERBOSITY_QUIET,
78 | 'normal' => OutputInterface::VERBOSITY_NORMAL,
79 | ];
80 |
81 | /**
82 | * Create a new console command instance.
83 | */
84 | public function __construct()
85 | {
86 | // We will go ahead and set the name, description, and parameters on console
87 | // commands just to make things a little easier on the developer. This is
88 | // so they don't have to all be manually specified in the constructors.
89 | if (isset($this->signature)) {
90 | $this->configureUsingFluentDefinition();
91 | } else {
92 | parent::__construct($this->name);
93 | }
94 |
95 | $this->setDescription($this->description);
96 |
97 | if (!isset($this->signature)) {
98 | $this->specifyParameters();
99 | }
100 | }
101 |
102 | /**
103 | * Determine if we are running in the console.
104 | *
105 | * @return bool
106 | */
107 | public function runningInConsole()
108 | {
109 | return php_sapi_name() == 'cli';
110 | }
111 |
112 | /**
113 | * Configure the console command using a fluent definition.
114 | */
115 | protected function configureUsingFluentDefinition()
116 | {
117 | list($name, $arguments, $options) = Parser::parse($this->signature);
118 |
119 | parent::__construct($name);
120 |
121 | foreach ($arguments as $argument) {
122 | $this->getDefinition()->addArgument($argument);
123 | }
124 |
125 | foreach ($options as $option) {
126 | $this->getDefinition()->addOption($option);
127 | }
128 | }
129 |
130 | /**
131 | * Specify the arguments and options on the command.
132 | */
133 | protected function specifyParameters()
134 | {
135 | // We will loop through all of the arguments and options for the command and
136 | // set them all on the base command instance. This specifies what can get
137 | // passed into these commands as "parameters" to control the execution.
138 | foreach ($this->getArguments() as $arguments) {
139 | call_user_func_array([$this, 'addArgument'], $arguments);
140 | }
141 |
142 | foreach ($this->getOptions() as $options) {
143 | call_user_func_array([$this, 'addOption'], $options);
144 | }
145 | }
146 |
147 | /**
148 | * Run the console command.
149 | *
150 | * @param \Symfony\Component\Console\Input\InputInterface $input
151 | * @param \Symfony\Component\Console\Output\OutputInterface $output
152 | *
153 | * @return int
154 | */
155 | public function run(InputInterface $input, OutputInterface $output)
156 | {
157 | $this->input = $input;
158 |
159 | $this->output = new OutputStyle($input, $output);
160 |
161 | return parent::run($input, $output);
162 | }
163 |
164 | /**
165 | * Execute the console command.
166 | *
167 | * @param \Symfony\Component\Console\Input\InputInterface $input
168 | * @param \Symfony\Component\Console\Output\OutputInterface $output
169 | *
170 | * @return mixed
171 | */
172 | protected function execute(InputInterface $input, OutputInterface $output)
173 | {
174 | $method = method_exists($this, 'handle') ? 'handle' : 'fire';
175 |
176 | return $this->$method();
177 | }
178 |
179 | /**
180 | * Call another console command.
181 | *
182 | * @param string $command
183 | * @param array $arguments
184 | *
185 | * @return int
186 | */
187 | public function call($command, array $arguments = [])
188 | {
189 | $instance = $this->getApplication()->find($command);
190 |
191 | $arguments['command'] = $command;
192 |
193 | return $instance->run(new ArrayInput($arguments), $this->output);
194 | }
195 |
196 | /**
197 | * Call another console command silently.
198 | *
199 | * @param string $command
200 | * @param array $arguments
201 | *
202 | * @return int
203 | */
204 | public function callSilent($command, array $arguments = [])
205 | {
206 | $instance = $this->getApplication()->find($command);
207 |
208 | $arguments['command'] = $command;
209 |
210 | return $instance->run(new ArrayInput($arguments), new NullOutput());
211 | }
212 |
213 | /**
214 | * Determine if the given argument is present.
215 | *
216 | * @param string|int $name
217 | *
218 | * @return bool
219 | */
220 | public function hasArgument($name)
221 | {
222 | return $this->input->hasArgument($name);
223 | }
224 |
225 | /**
226 | * Get the value of a command argument.
227 | *
228 | * @param string $key
229 | *
230 | * @return string|array
231 | */
232 | public function argument($key = null)
233 | {
234 | if (is_null($key)) {
235 | return $this->input->getArguments();
236 | }
237 |
238 | return $this->input->getArgument($key);
239 | }
240 |
241 | /**
242 | * Get all of the arguments passed to the command.
243 | *
244 | * @return array
245 | */
246 | public function arguments()
247 | {
248 | return $this->argument();
249 | }
250 |
251 | /**
252 | * Determine if the given option is present.
253 | *
254 | * @param string $name
255 | *
256 | * @return bool
257 | */
258 | public function hasOption($name)
259 | {
260 | return $this->input->hasOption($name);
261 | }
262 |
263 | /**
264 | * Get the value of a command option.
265 | *
266 | * @param string $key
267 | *
268 | * @return string|array
269 | */
270 | public function option($key = null)
271 | {
272 | if (is_null($key)) {
273 | return $this->input->getOptions();
274 | }
275 |
276 | return $this->input->getOption($key);
277 | }
278 |
279 | /**
280 | * Get all of the options passed to the command.
281 | *
282 | * @return array
283 | */
284 | public function options()
285 | {
286 | return $this->option();
287 | }
288 |
289 | /**
290 | * Confirm a question with the user.
291 | *
292 | * @param string $question
293 | * @param bool $default
294 | *
295 | * @return bool
296 | */
297 | public function confirm($question, $default = false)
298 | {
299 | return $this->output->confirm($question, $default);
300 | }
301 |
302 | /**
303 | * Prompt the user for input.
304 | *
305 | * @param string $question
306 | * @param string $default
307 | *
308 | * @return string
309 | */
310 | public function ask($question, $default = null)
311 | {
312 | return $this->output->ask($question, $default);
313 | }
314 |
315 | /**
316 | * Prompt the user for input with auto completion.
317 | *
318 | * @param string $question
319 | * @param array $choices
320 | * @param string $default
321 | *
322 | * @return string
323 | */
324 | public function anticipate($question, array $choices, $default = null)
325 | {
326 | return $this->askWithCompletion($question, $choices, $default);
327 | }
328 |
329 | /**
330 | * Prompt the user for input with auto completion.
331 | *
332 | * @param string $question
333 | * @param array $choices
334 | * @param string $default
335 | *
336 | * @return string
337 | */
338 | public function askWithCompletion($question, array $choices, $default = null)
339 | {
340 | $question = new Question($question, $default);
341 |
342 | $question->setAutocompleterValues($choices);
343 |
344 | return $this->output->askQuestion($question);
345 | }
346 |
347 | /**
348 | * Prompt the user for input but hide the answer from the console.
349 | *
350 | * @param string $question
351 | * @param bool $fallback
352 | *
353 | * @return string
354 | */
355 | public function secret($question, $fallback = true)
356 | {
357 | $question = new Question($question);
358 |
359 | $question->setHidden(true)->setHiddenFallback($fallback);
360 |
361 | return $this->output->askQuestion($question);
362 | }
363 |
364 | /**
365 | * Give the user a single choice from an array of answers.
366 | *
367 | * @param string $question
368 | * @param array $choices
369 | * @param string $default
370 | * @param mixed $attempts
371 | * @param bool $multiple
372 | *
373 | * @return string
374 | */
375 | public function choice($question, array $choices, $default = null, $attempts = null, $multiple = null)
376 | {
377 | $question = new ChoiceQuestion($question, $choices, $default);
378 |
379 | $question->setMaxAttempts($attempts)->setMultiselect($multiple);
380 |
381 | return $this->output->askQuestion($question);
382 | }
383 |
384 | /**
385 | * Format input to textual table.
386 | *
387 | * @param array $headers
388 | * @param \Illuminate\Contracts\Support\Arrayable|array $rows
389 | * @param string $style
390 | */
391 | public function table(array $headers, $rows, $style = 'default')
392 | {
393 | $table = new Table($this->output);
394 |
395 | if ($rows instanceof Arrayable) {
396 | $rows = $rows->toArray();
397 | }
398 |
399 | $table->setHeaders($headers)->setRows($rows)->setStyle($style)->render();
400 | }
401 |
402 | /**
403 | * Write a string as information output.
404 | *
405 | * @param string $string
406 | * @param null|int|string $verbosity
407 | */
408 | public function info($string, $verbosity = null)
409 | {
410 | $this->output->writeln("$string");
411 | }
412 |
413 | /**
414 | * Write a string as standard output.
415 | *
416 | * @param string $string
417 | * @param string $style
418 | * @param null|int|string $verbosity
419 | */
420 | public function line($string, $style = null, $verbosity = null)
421 | {
422 | $this->output->writeln($string);
423 | }
424 |
425 | /**
426 | * Write a string as comment output.
427 | *
428 | * @param string $string
429 | * @param null|int|string $verbosity
430 | */
431 | public function comment($string, $verbosity = null)
432 | {
433 | $this->output->writeln("$string");
434 | }
435 |
436 | /**
437 | * Write a string as question output.
438 | *
439 | * @param string $string
440 | * @param null|int|string $verbosity
441 | */
442 | public function question($string, $verbosity = null)
443 | {
444 | $this->output->writeln("$string");
445 | }
446 |
447 | /**
448 | * Write a string as error output.
449 | *
450 | * @param string $string
451 | * @param null|int|string $verbosity
452 | */
453 | public function error($string, $verbosity = null)
454 | {
455 | $this->output->writeln("$string");
456 | }
457 |
458 | /**
459 | * Write a string as warning output.
460 | *
461 | * @param string $string
462 | * @param null|int|string $verbosity
463 | */
464 | public function warn($string, $verbosity = null)
465 | {
466 | if (!$this->output->getFormatter()->hasStyle('warning')) {
467 | $style = new OutputFormatterStyle('yellow');
468 |
469 | $this->output->getFormatter()->setStyle('warning', $style);
470 | }
471 |
472 | $this->output->writeln("$string");
473 | }
474 |
475 | /**
476 | * Get the verbosity level in terms of Symfony's OutputInterface level.
477 | *
478 | * @param string|int $level
479 | *
480 | * @return int
481 | */
482 | protected function parseVerbosity($level = null)
483 | {
484 | if (isset($this->verbosityMap[$level])) {
485 | $level = $this->verbosityMap[$level];
486 | } elseif (!is_int($level)) {
487 | $level = $this->verbosity;
488 | }
489 |
490 | return $level;
491 | }
492 |
493 | /**
494 | * Set the verbosity level.
495 | *
496 | * @param string|int $level
497 | */
498 | protected function setVerbosity($level)
499 | {
500 | $this->verbosity = $this->parseVerbosity($level);
501 | }
502 |
503 | /**
504 | * Get the console command arguments.
505 | *
506 | * @return array
507 | */
508 | protected function getArguments()
509 | {
510 | return [];
511 | }
512 |
513 | /**
514 | * Get the console command options.
515 | *
516 | * @return array
517 | */
518 | protected function getOptions()
519 | {
520 | return [];
521 | }
522 |
523 | /**
524 | * Get the output implementation.
525 | *
526 | * @return \Symfony\Component\Console\Output\OutputInterface
527 | */
528 | public function getOutput()
529 | {
530 | return $this->output;
531 | }
532 |
533 | /**
534 | * Get the Laravel application instance.
535 | *
536 | * @return \Illuminate\Contracts\Container\Container
537 | */
538 | public function getLaravel()
539 | {
540 | return $this->laravel;
541 | }
542 |
543 | /**
544 | * Set the Laravel application instance.
545 | *
546 | * @param \Illuminate\Contracts\Container\Container $laravel
547 | */
548 | public function setLaravel($laravel)
549 | {
550 | $this->laravel = $laravel;
551 | }
552 | }
553 |
--------------------------------------------------------------------------------