Completed
Push — master ( e9e104...d0006b )
by Dan
02:01
created

Yabot::onMessage()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
cc 3
eloc 15
nc 4
nop 1
1
<?php
2
3
namespace Nopolabs\Yabot;
4
5
use DateTime;
6
use Exception;
7
use Nopolabs\Yabot\Helpers\ConfigTrait;
8
use Nopolabs\Yabot\Helpers\LogTrait;
9
use Nopolabs\Yabot\Helpers\LoopTrait;
10
use Nopolabs\Yabot\Helpers\SlackTrait;
11
use Nopolabs\Yabot\Message\MessageFactory;
12
use Nopolabs\Yabot\Plugin\PluginInterface;
13
use Nopolabs\Yabot\Plugin\PluginManager;
14
use Nopolabs\Yabot\Slack\Client;
15
use Psr\Log\LoggerInterface;
16
use React\EventLoop\LoopInterface;
17
use Slack\Payload;
18
use Slack\User;
19
use Throwable;
20
21
class Yabot
22
{
23
    use LogTrait;
24
    use LoopTrait;
25
    use SlackTrait;
26
    use ConfigTrait;
27
28
    /** @var MessageFactory */
29
    private $messageFactory;
30
31
    /** @var PluginManager */
32
    private $pluginManager;
33
34
    private $messageLog;
35
36
    public function __construct(
37
        LoggerInterface $logger,
38
        LoopInterface $eventLoop,
39
        Client $slackClient,
40
        MessageFactory $messageFactory,
41
        PluginManager $pluginManager,
42
        array $config = []
43
    ) {
44
        $this->setLog($logger);
45
        $this->setLoop($eventLoop);
46
        $this->setSlack($slackClient);
47
        $this->setConfig($config);
48
        $this->messageFactory = $messageFactory;
49
        $this->pluginManager = $pluginManager;
50
        $this->messageLog = null;
51
    }
52
53
    public function getMessageLog()
54
    {
55
        return $this->messageLog;
56
    }
57
58
    public function setMessageLog(string $messageLog = null)
59
    {
60
        $this->messageLog = $messageLog ?? null;
61
    }
62
63
    public function init(array $plugins)
64
    {
65
        foreach ($plugins as $pluginId => $plugin) {
66
            /** @var PluginInterface $plugin */
67
68
            $this->info("loading $pluginId");
69
70
            try {
71
                $this->pluginManager->loadPlugin($pluginId, $plugin);
72
            } catch (Exception $e) {
73
                $this->warning("Unhandled Exception while loading $pluginId: ".$e->getMessage());
74
                $this->warning($e->getTraceAsString());
75
            }
76
        }
77
    }
78
79
    public function run()
80
    {
81
        $slack = $this->getSlack();
82
83
        $slack->init();
84
85
        $slack->connect()->then([$this, 'connected']);
86
87
        $this->addMemoryReporting();
88
89
        $this->getLoop()->run();
90
    }
91
92
93
    public function shutDown()
94
    {
95
        $this->getSlack()->disconnect();
96
        $this->getLoop()->stop();
97
    }
98
99
    public function connected()
100
    {
101
        $slack = $this->getSlack();
102
103
        $slack->update(function(User $authedUser) {
104
            $this->pluginManager->setAuthedUser($authedUser);
105
        });
106
107
        $slack->onEvent('message', [$this, 'onMessage']);
108
109
        $this->startConnectionMonitor();
110
    }
111
112
    public function onMessage(Payload $payload)
113
    {
114
        $data = $payload->getData();
115
116
        $this->debug('Received message', $data);
117
118
        try {
119
            $this->logMessage($data);
120
            $message = $this->messageFactory->create($data);
121
        } catch (Throwable $throwable) {
122
            $errmsg = $throwable->getMessage()."\n"
123
                .$throwable->getTraceAsString()."\n"
124
                ."Payload data: ".json_encode($data);
125
            $this->warning($errmsg);
126
            return;
127
        }
128
129
        if ($message->isSelf()) {
130
            return;
131
        }
132
133
        $this->pluginManager->dispatchMessage($message);
134
    }
135
136
    public function getHelp() : string
137
    {
138
        return implode("\n", $this->pluginManager->getHelp());
139
    }
140
141
    public function getStatus() : string
142
    {
143
        $statuses = $this->pluginManager->getStatuses();
144
145
        array_unshift($statuses, $this->getFormattedMemoryUsage());
146
147
        return implode("\n", $statuses);
148
    }
149
150
    protected function addMemoryReporting()
151
    {
152
        $now = new DateTime();
153
        $then = new DateTime('+1 hour');
154
        $then->setTime($then->format('H'), 0, 0);
155
        $delay = $then->getTimestamp() - $now->getTimestamp();
156
157
        $this->addTimer($delay, function() {
158
            $this->info($this->getFormattedMemoryUsage());
159
            $this->addPeriodicTimer(3600, function() {
160
                $this->info($this->getFormattedMemoryUsage());
161
            });
162
        });
163
    }
164
165
    protected function getFormattedMemoryUsage() : string
166
    {
167
        $memory = memory_get_usage() / 1024;
168
        $formatted = number_format($memory, 3).'K';
169
        return "Current memory usage: {$formatted}";
170
    }
171
172
    protected function logMessage($data)
173
    {
174
        if ($this->messageLog !== null) {
175
            file_put_contents($this->messageLog, json_encode($data) . "\n", FILE_APPEND);
176
        }
177
    }
178
179
    protected function startConnectionMonitor()
180
    {
181
        if ($interval = $this->get('connection_monitor.interval')) {
182
            $this->getLog()->info("Monitoring websocket connection every $interval seconds.");
183
            $this->loop->addPeriodicTimer($interval, function () {
184
                static $pong = true;
185
                if (!$pong) {
186
                    $this->getLog()->error('Connection failed: no pong!');
187
                    $this->shutDown();
188
                }
189
                $this->getSlack()->getRealTimeClient()->ping()
190
                    ->then(
191
                        function (Payload $payload) use (&$pong) {
192
                            $this->getLog()->info($payload->toJson());
193
                            $pong = true;
194
                        }
195
                    );
196
                $pong = false;
197
            });
198
        }
199
    }
200
}
201