Completed
Push — master ( e135cc...194ff8 )
by Dan
07:03 queued 04:54
created

Yabot::checkPong()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 0
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 React\EventLoop\Timer\TimerInterface;
18
use Slack\Payload;
19
use Slack\User;
20
use Throwable;
21
22
class Yabot
23
{
24
    use LogTrait;
25
    use LoopTrait;
26
    use SlackTrait;
27
    use ConfigTrait;
28
29
    /** @var MessageFactory */
30
    private $messageFactory;
31
32
    /** @var PluginManager */
33
    private $pluginManager;
34
35
    /** @var string */
36
    private $messageLog;
37
38
    /** @var TimerInterface */
39
    private $monitor;
40
41
    /** @var bool */
42
    private $pong;
43
44
    public function __construct(
45
        LoggerInterface $logger,
46
        LoopInterface $eventLoop,
47
        Client $slackClient,
48
        MessageFactory $messageFactory,
49
        PluginManager $pluginManager,
50
        array $config = []
51
    ) {
52
        $this->setLog($logger);
53
        $this->setLoop($eventLoop);
54
        $this->setSlack($slackClient);
55
        $this->setConfig($config);
56
        $this->messageFactory = $messageFactory;
57
        $this->pluginManager = $pluginManager;
58
        $this->messageLog = null;
59
    }
60
61
    public function getMessageLog()
62
    {
63
        return $this->messageLog;
64
    }
65
66
    public function setMessageLog(string $messageLog = null)
67
    {
68
        $this->messageLog = $messageLog ?? null;
69
    }
70
71
    public function init(array $plugins)
72
    {
73
        foreach ($plugins as $pluginId => $plugin) {
74
            /** @var PluginInterface $plugin */
75
76
            $this->info("loading $pluginId");
77
78
            try {
79
                $this->pluginManager->loadPlugin($pluginId, $plugin);
80
            } catch (Exception $e) {
81
                $this->warning("Unhandled Exception while loading $pluginId: ".$e->getMessage());
82
                $this->warning($e->getTraceAsString());
83
            }
84
        }
85
    }
86
87
    public function run()
88
    {
89
        $slack = $this->getSlack();
90
91
        $slack->init();
92
93
        $slack->connect()->then([$this, 'connected']);
94
95
        $this->addMemoryReporting();
96
97
        $this->getLoop()->run();
98
    }
99
100
    public function shutDown()
101
    {
102
        $this->getLog()->error('Shutting down...');
103
104
        $this->getSlack()->disconnect();
105
        $this->getLoop()->stop();
106
    }
107
108
    public function reconnect()
109
    {
110
        $this->getLog()->error('Reconnecting...');
111
112
        if ($this->monitor) {
113
            $this->loop->cancelTimer($this->monitor);
114
        }
115
116
        $this->getSlack()->reconnect()->then(
117
            function () {
118
                $this->getLog()->info('Reconnected');
119
                $this->monitor = $this->startConnectionMonitor();
120
            },
121
            function () {
122
                $this->getLog()->error('Reconnect failed, shutting down.');
123
                $this->shutDown();
124
            }
125
        );
126
    }
127
128
    public function connected()
129
    {
130
        $slack = $this->getSlack();
131
132
        $slack->update(function(User $authedUser) {
133
            $this->pluginManager->setAuthedUser($authedUser);
134
        });
135
136
        $slack->onEvent('message', [$this, 'onMessage']);
137
        $slack->onEvent('team_join', [$this, 'onTeamJoin']);
138
139
        $this->monitor = $this->startConnectionMonitor();
140
    }
141
142
    public function onMessage(Payload $payload)
143
    {
144
        $data = $payload->getData();
145
146
        $this->debug('Received message', $data);
147
148
        try {
149
            $this->logMessage($data);
150
            $message = $this->messageFactory->create($data);
151
        } catch (Throwable $throwable) {
152
            $errmsg = $throwable->getMessage()."\n"
153
                .$throwable->getTraceAsString()."\n"
154
                ."Payload data: ".json_encode($data);
155
            $this->warning($errmsg);
156
            return;
157
        }
158
159
        if ($message->isSelf()) {
160
            return;
161
        }
162
163
        $this->pluginManager->dispatchMessage($message);
164
    }
165
166
    public function onTeamJoin(Payload $payload)
0 ignored issues
show
Unused Code introduced by
The parameter $payload is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
167
    {
168
        $this->getSlack()->updateUsers();
169
    }
170
171
    public function getHelp() : string
172
    {
173
        return implode("\n", $this->pluginManager->getHelp());
174
    }
175
176
    public function getStatus() : string
177
    {
178
        $statuses = $this->pluginManager->getStatuses();
179
180
        array_unshift($statuses, $this->getFormattedMemoryUsage());
181
182
        return implode("\n", $statuses);
183
    }
184
185
    protected function addMemoryReporting()
186
    {
187
        $now = new DateTime();
188
        $then = new DateTime('+1 hour');
189
        $then->setTime($then->format('H'), 0, 0);
190
        $delay = $then->getTimestamp() - $now->getTimestamp();
191
192
        $this->addTimer($delay, function() {
193
            $this->info($this->getFormattedMemoryUsage());
194
            $this->addPeriodicTimer(3600, function() {
195
                $this->info($this->getFormattedMemoryUsage());
196
            });
197
        });
198
    }
199
200
    protected function getFormattedMemoryUsage() : string
201
    {
202
        $memory = memory_get_usage() / 1024;
203
        $formatted = number_format($memory, 3).'K';
204
        return "Current memory usage: {$formatted}";
205
    }
206
207
    protected function logMessage($data)
208
    {
209
        if ($this->messageLog !== null) {
210
            file_put_contents($this->messageLog, json_encode($data) . "\n", FILE_APPEND);
211
        }
212
    }
213
214
    /**
215
     * @return TimerInterface|null
216
     */
217
    protected function startConnectionMonitor()
218
    {
219
        if ($interval = $this->get('connection_monitor.interval')) {
220
221
            $this->getLog()->info("Monitoring websocket connection every $interval seconds.");
222
            $this->notify("Monitoring websocket connection every $interval seconds.");
223
224
            $this->ping();
225
226
            return $this->loop->addPeriodicTimer($interval, function () {
227
                $this->checkPong();
228
                $this->ping();
229
            });
230
        }
231
    }
232
233
    protected function checkPong()
234
    {
235
        if (!$this->pong) {
236
            $this->getLog()->error('No pong.');
237
238
            $failureStrategy = $this->get('connection_monitor.failure_strategy', 'reconnect');
239
240
            if ($failureStrategy === 'reconnect') {
241
                $this->reconnect();
242
                return;
243
            }
244
245
            if ($failureStrategy !== 'shutdown') {
246
                $this->getLog()->error("Unknown connection_monitor.failure_strategy '$failureStrategy'");
247
            }
248
249
            $this->shutDown();
250
        }
251
    }
252
253
    protected function ping()
254
    {
255
        $this->pong = false;
256
257
        $this->getSlack()->ping()
258
            ->then(
259
                function (Payload $payload) {
260
                    $this->getLog()->info($payload->toJson());
261
                    $this->pong = true;
262
                }
263
            );
264
    }
265
266
    protected function notify(string $message)
267
    {
268
        if ($user = $this->get('notify.user')) {
269
            $this->getSlack()->directMessage($message, $user);
270
        }
271
    }
272
}
273