Issues (81)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Sovereign.php (14 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 () {
0 ignored issues
show
The method addPeriodicTimer() does not seem to exist on object<React\EventLoop\Factory>.

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.

Loading history...
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) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
0 ignored issues
show
The method addPeriodicTimer() does not seem to exist on object<React\EventLoop\Factory>.

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.

Loading history...
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 () {
0 ignored issues
show
The method addPeriodicTimer() does not seem to exist on object<React\EventLoop\Factory>.

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.

Loading history...
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) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
0 ignored issues
show
The parameter $websocket 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...
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) {
0 ignored issues
show
The expression $this->globalConfig->get('admins', 'permissions') of type null|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
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');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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";
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
398
                //$message .= "If you for some reason don't want me here after all, just kick me ;)";
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
399
                // Get the first channel in the list (usually the default channel)
400
                //$channel = $guild->channels->first();
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
401
                //$channel->sendMessage($message);
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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