Completed
Push — master ( dc345d...e5d792 )
by Dan
02:10
created

Yabot::onTeamJoin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
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 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->getSlack()->disconnect();
103
        $this->getLoop()->stop();
104
    }
105
106
    public function connected()
107
    {
108
        $slack = $this->getSlack();
109
110
        $slack->update(function(User $authedUser) {
111
            $this->pluginManager->setAuthedUser($authedUser);
112
        });
113
114
        $slack->onEvent('message', [$this, 'onMessage']);
115
        $slack->onEvent('team_join', [$this, 'onTeamJoin']);
116
117
        $this->monitor = $this->startConnectionMonitor();
118
    }
119
120
    public function onMessage(Payload $payload)
121
    {
122
        $data = $payload->getData();
123
124
        $this->debug('Received message', $data);
125
126
        try {
127
            $this->logMessage($data);
128
            $message = $this->messageFactory->create($data);
129
        } catch (Throwable $throwable) {
130
            $errmsg = $throwable->getMessage()."\n"
131
                .$throwable->getTraceAsString()."\n"
132
                ."Payload data: ".json_encode($data);
133
            $this->warning($errmsg);
134
            return;
135
        }
136
137
        if ($message->isSelf()) {
138
            return;
139
        }
140
141
        $this->pluginManager->dispatchMessage($message);
142
    }
143
144
    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...
145
    {
146
        $this->getSlack()->updateUsers();
147
    }
148
149
    public function getHelp() : string
150
    {
151
        return implode("\n", $this->pluginManager->getHelp());
152
    }
153
154
    public function getStatus() : string
155
    {
156
        $statuses = $this->pluginManager->getStatuses();
157
158
        array_unshift($statuses, $this->getFormattedMemoryUsage());
159
160
        return implode("\n", $statuses);
161
    }
162
163
    protected function addMemoryReporting()
164
    {
165
        $now = new DateTime();
166
        $then = new DateTime('+1 hour');
167
        $then->setTime($then->format('H'), 0, 0);
168
        $delay = $then->getTimestamp() - $now->getTimestamp();
169
170
        $this->addTimer($delay, function() {
171
            $this->info($this->getFormattedMemoryUsage());
172
            $this->addPeriodicTimer(3600, function() {
173
                $this->info($this->getFormattedMemoryUsage());
174
            });
175
        });
176
    }
177
178
    protected function getFormattedMemoryUsage() : string
179
    {
180
        $memory = memory_get_usage() / 1024;
181
        $formatted = number_format($memory, 3).'K';
182
        return "Current memory usage: {$formatted}";
183
    }
184
185
    protected function logMessage($data)
186
    {
187
        if ($this->messageLog !== null) {
188
            file_put_contents($this->messageLog, json_encode($data) . "\n", FILE_APPEND);
189
        }
190
    }
191
192
    /**
193
     * @return TimerInterface|null
194
     */
195
    protected function startConnectionMonitor()
196
    {
197
        if ($interval = $this->get('connection_monitor.interval')) {
198
199
            $this->getLog()->info("Monitoring websocket connection every $interval seconds.");
200
            $this->notify("Monitoring websocket connection every $interval seconds.");
201
202
            $this->ping();
203
204
            return $this->loop->addPeriodicTimer($interval, function () {
205
206
                if (!$this->pong) {
207
                    $this->getLog()->error('No pong: reconnecting...');
208
                    $this->reconnect();
209
                }
210
211
                $this->ping();
212
            });
213
        }
214
    }
215
216
    protected function ping()
217
    {
218
        $this->pong = false;
219
220
        $this->getSlack()->ping()
221
            ->then(
222
                function (Payload $payload) {
223
                    $this->getLog()->info($payload->toJson());
224
                    $this->pong = true;
225
                }
226
            );
227
    }
228
229
    protected function reconnect()
230
    {
231
        if ($this->monitor) {
232
            $this->loop->cancelTimer($this->monitor);
233
        }
234
235
        $this->getSlack()->reconnect()->then(
236
            function () {
237
                $this->getLog()->info('Reconnected');
238
                $this->monitor = $this->startConnectionMonitor();
239
            },
240
            function () {
241
                $this->getLog()->error('Reconnect failed, shutting down.');
242
                $this->shutDown();
243
            }
244
        );
245
    }
246
247
    protected function notify(string $message)
248
    {
249
        if ($user = $this->get('notify.user')) {
250
            $this->getSlack()->directMessage($message, $user);
251
        }
252
    }
253
}
254