sovereignbot /
citadel
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
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
|
|||
| 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) { |
||
|
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) { |
|
| 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) { |
||
|
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.
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'); |
||
| 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.