1
|
|
|
<?php |
2
|
|
|
namespace Sovereign; |
3
|
|
|
|
4
|
|
|
|
5
|
|
|
use Discord\Cache\Cache; |
6
|
|
|
use Discord\Cache\Drivers\ArrayCacheDriver; |
7
|
|
|
use Discord\Discord; |
8
|
|
|
use Discord\Parts\Channel\Message; |
9
|
|
|
use Discord\Parts\Guild\Guild; |
10
|
|
|
use Discord\Parts\User\Game; |
11
|
|
|
use Discord\Parts\WebSockets\PresenceUpdate; |
12
|
|
|
use Discord\WebSockets\Event; |
13
|
|
|
use Discord\WebSockets\WebSocket; |
14
|
|
|
use Monolog\Logger; |
15
|
|
|
use League\Container\Container; |
16
|
|
|
use Sovereign\Lib\Config as globalConfig; |
17
|
|
|
use Sovereign\Lib\cURL; |
18
|
|
|
use Sovereign\Lib\Db; |
19
|
|
|
use Sovereign\Lib\Permissions; |
20
|
|
|
use Sovereign\Lib\Settings; |
21
|
|
|
use Sovereign\Lib\Users; |
22
|
|
|
use Sovereign\Plugins\onMessage\cleverBotMessage; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Class Sovereign |
26
|
|
|
* @package Sovereign |
27
|
|
|
*/ |
28
|
|
|
class Sovereign |
29
|
|
|
{ |
30
|
|
|
/** |
31
|
|
|
* @var WebSocket |
32
|
|
|
*/ |
33
|
|
|
public $websocket; |
34
|
|
|
/** |
35
|
|
|
* @var Discord |
36
|
|
|
*/ |
37
|
|
|
protected $discord; |
38
|
|
|
/** |
39
|
|
|
* @var Container |
40
|
|
|
*/ |
41
|
|
|
protected $container; |
42
|
|
|
/** |
43
|
|
|
* @var Logger |
44
|
|
|
*/ |
45
|
|
|
protected $log; |
46
|
|
|
/** |
47
|
|
|
* @var globalConfig |
48
|
|
|
*/ |
49
|
|
|
protected $globalConfig; |
50
|
|
|
/** |
51
|
|
|
* @var Db |
52
|
|
|
*/ |
53
|
|
|
protected $db; |
54
|
|
|
/** |
55
|
|
|
* @var cURL |
56
|
|
|
*/ |
57
|
|
|
protected $curl; |
58
|
|
|
/** |
59
|
|
|
* @var Settings |
60
|
|
|
*/ |
61
|
|
|
protected $settings; |
62
|
|
|
/** |
63
|
|
|
* @var Permissions |
64
|
|
|
*/ |
65
|
|
|
protected $permissions; |
66
|
|
|
/** |
67
|
|
|
* @var Users |
68
|
|
|
*/ |
69
|
|
|
protected $users; |
70
|
|
|
/** |
71
|
|
|
* @var array |
72
|
|
|
*/ |
73
|
|
|
private $onMessage = []; |
74
|
|
|
/** |
75
|
|
|
* @var array |
76
|
|
|
*/ |
77
|
|
|
private $onVoice = []; |
78
|
|
|
/** |
79
|
|
|
* @var array |
80
|
|
|
*/ |
81
|
|
|
private $onTimer = []; |
82
|
|
|
/** |
83
|
|
|
* @var \Pool |
84
|
|
|
*/ |
85
|
|
|
private $pool; |
86
|
|
|
/** |
87
|
|
|
* @var \Pool |
88
|
|
|
*/ |
89
|
|
|
private $timers; |
90
|
|
|
/** |
91
|
|
|
* @var array |
92
|
|
|
*/ |
93
|
|
|
private $audioStreams; |
94
|
|
|
/** |
95
|
|
|
* @var array |
96
|
|
|
*/ |
97
|
|
|
private $extras = []; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Sovereign constructor. |
101
|
|
|
* @param Container $container |
102
|
|
|
*/ |
103
|
|
|
public function __construct(Container $container) |
104
|
|
|
{ |
105
|
|
|
$this->container = $container; |
106
|
|
|
$this->log = $container->get('log'); |
107
|
|
|
$this->globalConfig = $container->get('config'); |
108
|
|
|
$this->db = $container->get('db'); |
109
|
|
|
$this->curl = $container->get('curl'); |
110
|
|
|
$this->settings = $container->get('settings'); |
111
|
|
|
$this->permissions = $container->get('permissions'); |
112
|
|
|
$this->users = $container->get('users'); |
113
|
|
|
$this->extras['startTime'] = time(); |
114
|
|
|
$this->extras['memberCount'] = 0; |
115
|
|
|
$this->extras['guildCount'] = 0; |
116
|
|
|
$this->pool = new \Pool(count($this->onMessage), \Worker::class); |
117
|
|
|
$this->timers = new \Pool(count($this->onTimer), \Worker::class); |
118
|
|
|
|
119
|
|
|
// Init Discord and Websocket |
120
|
|
|
$this->log->addInfo('Initializing Discord and Websocket connections..'); |
121
|
|
|
$this->discord = Discord::createWithBotToken($this->globalConfig->get('token', 'bot')); |
122
|
|
|
Cache::setCache(new ArrayCacheDriver()); |
123
|
|
|
$this->websocket = new WebSocket($this->discord); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* @param $type |
128
|
|
|
* @param $command |
129
|
|
|
* @param $class |
130
|
|
|
* @param $perms |
131
|
|
|
* @param $description |
132
|
|
|
* @param $usage |
133
|
|
|
* @param $timer |
134
|
|
|
*/ |
135
|
|
|
public function addPlugin($type, $command, $class, $perms, $description, $usage, $timer) |
136
|
|
|
{ |
137
|
|
|
$this->log->addInfo("Adding plugin: {$command}"); |
138
|
|
|
$this->$type[$command] = [ |
139
|
|
|
'permissions' => $perms, |
140
|
|
|
'class' => $class, |
141
|
|
|
'description' => $description, |
142
|
|
|
'usage' => $usage, |
143
|
|
|
'timer' => $timer |
144
|
|
|
]; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* |
149
|
|
|
*/ |
150
|
|
|
public function run() |
151
|
|
|
{ |
152
|
|
|
// Reap the threads! |
153
|
|
|
$this->websocket->loop->addPeriodicTimer(600, function () { |
|
|
|
|
154
|
|
|
$this->log->addInfo('Restarting the threading pool, to clear out old threads..'); |
155
|
|
|
|
156
|
|
|
// Shutdown the pool |
157
|
|
|
$this->pool->shutdown(); |
158
|
|
|
$this->timers->shutdown(); |
159
|
|
|
|
160
|
|
|
// Startup the pool again |
161
|
|
|
$this->pool = new \Pool(count($this->onMessage), \Worker::class); |
162
|
|
|
$this->timers = new \Pool(count($this->onTimer), \Worker::class); |
163
|
|
|
}); |
164
|
|
|
|
165
|
|
|
// Handle the onReady event, and setup some timers and so forth |
166
|
|
|
$this->websocket->on('ready', function (Discord $discord) { |
167
|
|
|
$this->log->addInfo('Websocket connected..'); |
168
|
|
|
|
169
|
|
|
// Update our presence status |
170
|
|
|
$game = new Game(array('name' => $this->globalConfig->get('presence', 'bot', "table flippin'"), 'url' => null, 'type' => null), true); |
171
|
|
|
$this->websocket->updatePresence($game, false); |
172
|
|
|
|
173
|
|
|
// Count the amount of people we are available to.. |
174
|
|
|
/** @var Guild $guild */ |
175
|
|
View Code Duplication |
foreach ($this->discord->getClient()->getGuildsAttribute()->all() as $guild) { |
|
|
|
|
176
|
|
|
$this->extras['memberCount'] += $guild->member_count; |
177
|
|
|
$this->extras['guildCount']++; |
178
|
|
|
$this->extras['guild']['memberCount']["id{$guild->id}"] = $guild->member_count; |
179
|
|
|
$this->extras['onMessagePlugins'] = $this->onMessage; |
180
|
|
|
$this->extras['onVoicePlugins'] = $this->onVoice; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$this->log->addInfo("Member count, currently available to: {$this->extras['memberCount']} people"); |
184
|
|
|
|
185
|
|
|
// Setup the timers for the timer plugins |
186
|
|
|
foreach ($this->onTimer as $command => $data) { |
187
|
|
|
$this->websocket->loop->addPeriodicTimer($data['timer'], function () use ($data, $discord) { |
|
|
|
|
188
|
|
|
try { |
189
|
|
|
$plugin = new $data['class']($discord, $this->log, $this->globalConfig, $this->db, $this->curl, $this->settings, $this->permissions, $this->container->get('serverConfig'), $this->users, $this->extras); |
190
|
|
|
$this->timers->submit($plugin); |
191
|
|
|
} catch (\Exception $e) { |
192
|
|
|
$this->log->addError("Error running the periodic timer: {$e->getMessage()}"); |
193
|
|
|
} |
194
|
|
|
}); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
// Issue periodically recounting and other things (Needed because of pthreads not putting the entire context into children - leading to some weirdness in some plugins) |
198
|
|
|
$this->websocket->loop->addPeriodicTimer(600, function () { |
|
|
|
|
199
|
|
|
$this->extras['memberCount'] = 0; |
200
|
|
|
$this->extras['guildCount'] = 0; |
201
|
|
|
/** @var Guild $guild */ |
202
|
|
View Code Duplication |
foreach ($this->discord->getClient()->getGuildsAttribute()->all() as $guild) { |
|
|
|
|
203
|
|
|
$this->extras['memberCount'] += $guild->member_count; |
204
|
|
|
$this->extras['guildCount']++; |
205
|
|
|
$this->extras['guild']['memberCount']["id{$guild->id}"] = $guild->member_count; |
206
|
|
|
$this->extras['onMessagePlugins'] = $this->onMessage; |
207
|
|
|
$this->extras['onVoicePlugins'] = $this->onVoice; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
// Output periodic information while doing the recounting stuff |
211
|
|
|
$this->log->addInfo('Currently running audio streams: ' . count($this->audioStreams)); |
212
|
|
|
$this->log->addInfo("Member recount, currently available to: {$this->extras['memberCount']} people"); |
213
|
|
|
}); |
214
|
|
|
|
215
|
|
|
// @todo run a timer to check if there are any active voice sessions - and if there are, if there are any people in those voice sessions |
216
|
|
|
// If not, stop the session and leave the channel (To save some bandwidth) |
217
|
|
|
}); |
218
|
|
|
|
219
|
|
|
$this->websocket->on('error', function ($error, $websocket) { |
|
|
|
|
220
|
|
|
$this->log->addError('An error occurred on the websocket', [$error->getMessage()]); |
221
|
|
|
die(1); |
222
|
|
|
}); |
223
|
|
|
|
224
|
|
|
$this->websocket->on('close', function ($opCode, $reason) { |
225
|
|
|
$this->log->addWarning('Websocket got closed', ['code' => $opCode, 'reason' => $reason]); |
226
|
|
|
die(1); |
227
|
|
|
}); |
228
|
|
|
|
229
|
|
|
$this->websocket->on('reconnecting', function () { |
230
|
|
|
$this->log->addInfo('Websocket is reconnecting..'); |
231
|
|
|
}); |
232
|
|
|
|
233
|
|
|
$this->websocket->on('reconnected', function () { |
234
|
|
|
$this->log->addInfo('Websocket was reconnected..'); |
235
|
|
|
}); |
236
|
|
|
|
237
|
|
|
// Handle incoming message logging |
238
|
|
|
$this->websocket->on(Event::MESSAGE_CREATE, function (Message $message, Discord $discord) { |
239
|
|
|
$this->log->addInfo("Message from {$message->author->username}", [$message->content]); |
240
|
|
|
|
241
|
|
|
// Don't update data for ourselves.. |
242
|
|
|
if ($message->author->id !== $discord->getClient()->id) { |
243
|
|
|
$this->users->set($message->author->id, $message->author->username, 'online', null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), $message->content); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
// @todo Create text logs |
247
|
|
|
}); |
248
|
|
|
|
249
|
|
|
// Handle plugin running |
250
|
|
|
$this->websocket->on(Event::MESSAGE_CREATE, function (Message $message, Discord $discord) { |
251
|
|
|
$guildID = $message->getChannelAttribute()->guild_id; |
252
|
|
|
|
253
|
|
|
// Get server config |
254
|
|
|
$config = $this->settings->get($guildID); |
255
|
|
|
|
256
|
|
|
// Is the person admin? |
257
|
|
|
$userDiscordID = $message->author->id; |
258
|
|
|
foreach ($this->globalConfig->get('admins', 'permissions') as $admins) { |
|
|
|
|
259
|
|
|
$message->isAdmin = $admins === $userDiscordID; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// Define the prefix if it isn't already set.. |
263
|
|
|
@$config->prefix = $config->prefix ?? $this->globalConfig->get('prefix', 'bot'); |
|
|
|
|
264
|
|
|
|
265
|
|
|
// Check if the user requested an onMessage plugin |
266
|
|
|
if (substr($message->content, 0, strlen($config->prefix)) === $config->prefix) { |
267
|
|
|
$content = explode(' ', $message->content); |
268
|
|
|
foreach ($this->onMessage as $command => $data) { |
269
|
|
|
$parts = []; |
270
|
|
|
foreach ($content as $index => $c) |
271
|
|
|
foreach (explode("\n", $c) as $p) |
272
|
|
|
$parts[] = $p; |
273
|
|
|
|
274
|
|
|
if ($parts[0] === $config->prefix . $command) { |
275
|
|
|
// If they are listed under the admins array in the bot config, they're the super admins |
276
|
|
|
if (in_array($message->author->id, $this->globalConfig->get('admins', 'permissions'))) |
277
|
|
|
$userPerms = 3; |
278
|
|
|
// If they are guild owner, they're automatically getting permission level 2 |
279
|
|
|
elseif (null !== $message->getChannelAttribute()->getGuildAttribute()->owner_id && ($message->author->id === $message->getChannelAttribute()->getGuildAttribute()->owner_id)) |
280
|
|
|
$userPerms = 2; |
281
|
|
|
// Everyone else are just users |
282
|
|
|
else |
283
|
|
|
$userPerms = 1; |
284
|
|
|
|
285
|
|
|
if ($userPerms >= $data['permissions']) { |
286
|
|
|
try { |
287
|
|
|
$message->getChannelAttribute()->broadcastTyping(); |
288
|
|
|
if ($data['class'] === "\\Sovereign\\Plugins\\onMessage\\auth") { |
289
|
|
|
/** @var \Threaded $plugin */ |
290
|
|
|
$plugin = new $data['class']($message, $discord, $config, $this->log, $this->globalConfig, $this->db, $this->curl, $this->settings, $this->permissions, $this->container->get('serverConfig'), $this->users, $this->extras); |
291
|
|
|
$plugin->run(); |
292
|
|
|
} else { |
293
|
|
|
/** @var \Threaded $plugin */ |
294
|
|
|
$plugin = new $data['class']($message, $discord, $config, $this->log, $this->globalConfig, $this->db, $this->curl, $this->settings, $this->permissions, $this->container->get('serverConfig'), $this->users, $this->extras); |
295
|
|
|
$this->pool->submit($plugin); |
296
|
|
|
} |
297
|
|
|
$this->log->addInfo("{$message->author->username}#{$message->author->discriminator} ({$message->author}) ran command {$config->prefix}{$command}", $content); |
298
|
|
|
} catch (\Exception $e) { |
299
|
|
|
$this->log->addError("Error running command {$config->prefix}{$command}. Command run by {$message->author->username} in {$message->getChannelAttribute()->name}. Error: {$e->getMessage()}"); |
300
|
|
|
$message->reply("**Error:** There was a problem running the command: {$e->getMessage()}"); |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
} |
304
|
|
|
} |
305
|
|
|
} |
306
|
|
|
}); |
307
|
|
|
|
308
|
|
|
// Handle joining a voice channel, and playing.. stuff.... |
309
|
|
|
$this->websocket->on(Event::MESSAGE_CREATE, function (Message $message, Discord $discord) { |
310
|
|
|
// Get the guildID |
311
|
|
|
$guildID = $message->getChannelAttribute()->guild_id; |
312
|
|
|
|
313
|
|
|
// Get this guilds settings |
314
|
|
|
$config = $this->settings->get($guildID); |
315
|
|
|
|
316
|
|
|
// Get the prefix for this guild |
317
|
|
|
@$config->prefix = $config->prefix ?? $this->globalConfig->get('prefix', 'bot'); |
|
|
|
|
318
|
|
|
|
319
|
|
|
if (substr($message->content, 0, strlen($config->prefix)) === $config->prefix) { |
320
|
|
|
$content = explode(' ', $message->content); |
321
|
|
|
foreach ($this->onVoice as $command => $data) { |
322
|
|
|
$parts = []; |
323
|
|
|
foreach ($content as $index => $c) { |
324
|
|
|
foreach (explode("\n", $c) as $p) |
325
|
|
|
$parts[] = $p; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
if ($parts[0] === $config->prefix . $command) { |
329
|
|
|
try { |
330
|
|
|
$voiceChannels = $message->getFullChannelAttribute()->getGuildAttribute()->channels->getAll('type', 'voice'); |
331
|
|
|
foreach ($voiceChannels as $channel) { |
332
|
|
|
if (!empty($channel->members[$message->author->id])) { |
333
|
|
|
$voice = new $data['class'](); |
334
|
|
|
$voice->run($message, $discord, $this->websocket, $this->log, $this->audioStreams, $channel, $this->curl); |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
} catch (\Exception $e) { |
338
|
|
|
$this->log->addError("Error running voice command {$config->prefix}{$command}. Command run by {$message->author->username} in {$message->getChannelAttribute()->name}. Error: {$e->getMessage()}"); |
339
|
|
|
$message->reply("**Error:** There was a problem running the command: {$e->getMessage()}"); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
}); |
345
|
|
|
|
346
|
|
|
// Handle if it's a message for the bot (CleverBot invocation) |
347
|
|
|
$this->websocket->on(Event::MESSAGE_CREATE, function (Message $message, Discord $discord) { |
348
|
|
|
// If we got highlighted we should probably answer back |
349
|
|
|
if (stristr($message->content, $discord->getClient()->id)) { |
350
|
|
|
try { |
351
|
|
|
$this->pool->submit(new cleverBotMessage($message, $discord, $this->log, $this->globalConfig, $this->db, $this->curl, $this->settings, $this->permissions, $this->container->get('serverConfig'), $this->users)); |
352
|
|
|
} catch (\Exception $e) { |
353
|
|
|
$message->reply("**Error:** There was an error with CleverBot: {$e->getMessage()}"); |
354
|
|
|
} |
355
|
|
|
} |
356
|
|
|
}); |
357
|
|
|
|
358
|
|
|
// Handle presence updates |
359
|
|
|
$this->websocket->on(Event::PRESENCE_UPDATE, function (PresenceUpdate $presenceUpdate) { |
360
|
|
|
if ($presenceUpdate->user->id && $presenceUpdate->user->username) { |
361
|
|
|
try { |
362
|
|
|
$this->log->addInfo("Updating presence info for {$presenceUpdate->user->username}"); |
363
|
|
|
$game = $presenceUpdate->getGameAttribute()->name ?? null; |
364
|
|
|
$this->users->set($presenceUpdate->user->id, $presenceUpdate->user->username, $presenceUpdate->status, $game, date('Y-m-d H:i:s'), null, null); |
365
|
|
|
} catch (\Exception $e) { |
366
|
|
|
$this->log->addError("Error: {$e->getMessage()}"); |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
}); |
370
|
|
|
|
371
|
|
|
// Create a new cleverbot \nick\ for this new guild |
372
|
|
|
$this->websocket->on(Event::GUILD_CREATE, function (Guild $guild) { |
373
|
|
|
$cleverBotExists = $this->db->queryField("SELECT serverID FROM cleverbot WHERE serverID = :serverID", "serverID", array(":serverID" => $guild->id)); |
374
|
|
|
$guildExists = $this->db->queryField("SELECT guildID FROM guilds WHERE guildID = :serverID", "guildID", array(":serverID" => $guild->id)); |
375
|
|
|
|
376
|
|
|
// Only create a new server nick if the cleverbot instance doesn't exist.. (Hopefully cleverbot.io is done deleting them at random) |
377
|
|
|
if(!isset($cleverBotExists)) { |
378
|
|
|
$this->log->addInfo("Setting up Cleverbot for {$guild->name}"); |
379
|
|
|
$serverID = $guild->id; |
380
|
|
|
$result = $this->curl->post('https://cleverbot.io/1.0/create', ['user' => $this->globalConfig->get('user', 'cleverbot'), 'key' => $this->globalConfig->get('key', 'cleverbot')]); |
381
|
|
|
|
382
|
|
|
if ($result) { |
383
|
|
|
$result = @json_decode($result); |
384
|
|
|
$nick = $result->nick ?? false; |
385
|
|
|
|
386
|
|
|
if ($nick) { |
387
|
|
|
$this->db->execute('INSERT INTO cleverbot (serverID, nick) VALUES (:serverID, :nick) ON DUPLICATE KEY UPDATE nick = :nick', [':serverID' => $serverID, ':nick' => $nick]); |
388
|
|
|
} |
389
|
|
|
} |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
if(!isset($guildExists)) { |
393
|
|
|
$this->db->execute("INSERT IGNORE INTO guilds (guildID) VALUES (:guildID)", array(":guildID" => $guild->id)); |
394
|
|
|
|
395
|
|
|
// Send a hello message to the channel (Only if it's new!) |
396
|
|
|
//$message = "Hello, i was invited here by someone with admin permissions, i have quite a few features that you can discover by doing %help\n"; |
|
|
|
|
397
|
|
|
//$message .= "I am sorry if i am triggering other bots aswell, you can change my trigger with %config setTrigger newTrigger (Example: %config setTrigger *)\n"; |
|
|
|
|
398
|
|
|
//$message .= "If you for some reason don't want me here after all, just kick me ;)"; |
|
|
|
|
399
|
|
|
// Get the first channel in the list (usually the default channel) |
400
|
|
|
//$channel = $guild->channels->first(); |
|
|
|
|
401
|
|
|
//$channel->sendMessage($message); |
|
|
|
|
402
|
|
|
} |
403
|
|
|
}); |
404
|
|
|
|
405
|
|
|
// Run the websocket, and in turn, the bot! |
406
|
|
|
$this->websocket->run(); |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* Get the configuration/settings container and return it upstream to the calling code. |
411
|
|
|
* |
412
|
|
|
* @return globalConfig |
413
|
|
|
*/ |
414
|
|
|
public function getGlobalConfig() { |
415
|
|
|
return $this->globalConfig; |
416
|
|
|
} |
417
|
|
|
} |
418
|
|
|
|
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.