Completed
Push — master ( 6daf81...e8654c )
by Dan
02:19
created

Yabot::startConnectionMonitor()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
cc 3
eloc 14
nc 2
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 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
            if ($this->messageLog !== null) {
120
                $this->logMessage($data);
121
            }
122
            $message = $this->messageFactory->create($data);
123
        } catch (Throwable $throwable) {
124
            $errmsg = $throwable->getMessage()."\n"
125
                .$throwable->getTraceAsString()."\n"
126
                ."Payload data: ".json_encode($data);
127
            $this->warning($errmsg);
128
            return;
129
        }
130
131
        if ($message->isSelf()) {
132
            return;
133
        }
134
135
        $this->pluginManager->dispatchMessage($message);
136
    }
137
138
    public function getHelp() : string
139
    {
140
        return implode("\n", $this->pluginManager->getHelp());
141
    }
142
143
    public function getStatus() : string
144
    {
145
        $statuses = $this->pluginManager->getStatuses();
146
147
        array_unshift($statuses, $this->getFormattedMemoryUsage());
148
149
        return implode("\n", $statuses);
150
    }
151
152
    protected function addMemoryReporting()
153
    {
154
        $now = new DateTime();
155
        $then = new DateTime('+1 hour');
156
        $then->setTime($then->format('H'), 0, 0);
157
        $delay = $then->getTimestamp() - $now->getTimestamp();
158
159
        $this->addTimer($delay, function() {
160
            $this->info($this->getFormattedMemoryUsage());
161
            $this->addPeriodicTimer(3600, function() {
162
                $this->info($this->getFormattedMemoryUsage());
163
            });
164
        });
165
    }
166
167
    protected function getFormattedMemoryUsage() : string
168
    {
169
        $memory = memory_get_usage() / 1024;
170
        $formatted = number_format($memory, 3).'K';
171
        return "Current memory usage: {$formatted}";
172
    }
173
174
    protected function logMessage($data)
175
    {
176
        file_put_contents($this->messageLog, json_encode($data)."\n", FILE_APPEND);
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