private function disable(CommandMessage $command) : Promise { return resolve(function () use($command) { if (!(yield $this->adminStorage->isAdmin($command->getRoom(), $command->getUserId()))) { return $this->chatClient->postReply($command, "I'm sorry Dave, I'm afraid I can't do that"); } if (null === ($plugin = $command->getParameter(1))) { return $this->chatClient->postReply($command, "No plugin name supplied"); } if (!$this->pluginManager->isPluginRegistered($plugin)) { return $this->chatClient->postReply($command, "Invalid plugin name"); } if (!$this->pluginManager->isPluginEnabledForRoom($plugin, $command->getRoom())) { return $this->chatClient->postReply($command, "Plugin already disabled in this room"); } (yield $this->pluginManager->disablePluginForRoom($plugin, $command->getRoom())); return $this->chatClient->postMessage($command->getRoom(), "Plugin '{$plugin}' is now disabled in this room"); }); }
public function handleCommand(Command $command) : Promise { return resolve(function () use($command) { $commandName = $command->getCommandName(); if (!isset($this->commands[$commandName])) { return; } $eventId = $command->getEvent()->getId(); $userId = $command->getUserId(); try { $userIsBanned = (yield $this->banStorage->isBanned($command->getRoom(), $userId)); if ($userIsBanned) { $this->logger->log(Level::DEBUG, "User #{$userId} is banned, ignoring event #{$eventId} for built in commands"); return; } $this->logger->log(Level::DEBUG, "Passing event #{$eventId} to built in command handler " . get_class($this->commands[$commandName])); (yield $this->commands[$commandName]->handleCommand($command)); } catch (\Throwable $e) { $this->logger->log(Level::ERROR, "Something went wrong while handling #{$eventId} for built-in commands: {$e}"); } }); }
public function unpin(Command $command) { $owners = (yield $this->chatClient->getRoomOwners($command->getRoom())); if (!isset($owners[$command->getUserId()])) { return $this->chatClient->postReply($command, "I'm sorry Dave, I'm afraid I can't do that"); } if (!$command->hasParameters(1)) { return $this->chatClient->postReply($command, "You must supply a valid message ID to unpin"); } if (!preg_match('#(?:messages?/)?([0-9]+)#', $command->getParameter(0), $match)) { return $this->chatClient->postReply($command, "You must supply a valid message ID to unpin"); } $key = $match[1]; $id = (int) $key; if (!(yield $this->keyValueStore->exists($key, $command->getRoom()))) { return $this->chatClient->postReply($command, "You must supply a valid message ID to unpin"); } (yield $this->keyValueStore->unset($key, $command->getRoom())); if (in_array($id, (yield $this->chatClient->getPinnedMessages($command->getRoom())))) { (yield $this->chatClient->pinOrUnpinMessage($id, $command->getRoom())); } return $this->chatClient->postMessage($command->getRoom(), "I will no longer repin message #{$id}"); }
private function execute(CommandMessage $command) : \Generator { if (!(yield $command->getRoom()->isApproved())) { return; } if (!(yield $this->adminStorage->isAdmin($command->getRoom(), $command->getUserId()))) { return $this->chatClient->postReply($command, "I'm sorry Dave, I'm afraid I can't do that"); } if ($command->getCommandName() === "ban" && $command->getParameter(0) === 'list') { yield from $this->list($command); } else { if ($command->getCommandName() === "ban") { if (!$command->hasParameters(2)) { return $this->chatClient->postReply($command, "Ban length must be specified"); } yield from $this->add($command, (int) $command->getParameter(0), $command->getParameter(1)); } else { if ($command->getCommandName() === "unban") { yield from $this->remove($command, (int) $command->getParameter(0)); } } } }
public function tweet(Command $command) { if (!$this->isMessageValid($command->getParameter(0))) { return new Success(); } if (!(yield $this->admin->isAdmin($command->getRoom(), $command->getUserId()))) { return $this->chatClient->postReply($command, "I'm sorry Dave, I'm afraid I can't do that"); } $tweetText = (yield from $this->getMessage($command, $command->getParameters()[0])); if (mb_strlen($tweetText, "UTF-8") > 140) { return $this->chatClient->postReply($command, "Boo! The message exceeds the 140 character limit. :-("); } $keys = ['oauth.consumer_key', 'oauth.access_token', 'oauth.consumer_secret', 'oauth.access_token_secret']; $config = []; foreach ($keys as $key) { if (!(yield $this->storage->exists($key, $command->getRoom()))) { return $this->chatClient->postReply($command, "I'm not currently configured for tweeting :-("); } $config[$key] = (yield $this->storage->get($key, $command->getRoom())); } $oauthParameters = ["oauth_consumer_key" => $config['oauth.consumer_key'], "oauth_token" => $config['oauth.access_token'], "oauth_nonce" => $this->getNonce(), "oauth_timestamp" => (new \DateTimeImmutable())->format("U"), "oauth_signature_method" => "HMAC-SHA1", "oauth_version" => "1.0", "status" => $tweetText]; $oauthParameters = array_map("rawurlencode", $oauthParameters); asort($oauthParameters); ksort($oauthParameters); $queryString = urldecode(http_build_query($oauthParameters, '', '&')); $baseString = "POST&" . rawurlencode(self::BASE_URI . "/statuses/update.json") . "&" . rawurlencode($queryString); $key = $config['oauth.consumer_secret'] . "&" . $config['oauth.access_token_secret']; $signature = rawurlencode(base64_encode(hash_hmac('sha1', $baseString, $key, true))); $oauthParameters["oauth_signature"] = $signature; $oauthParameters = array_map(function ($value) { return '"' . $value . '"'; }, $oauthParameters); unset($oauthParameters["status"]); asort($oauthParameters); ksort($oauthParameters); $authorizationHeader = $auth = "OAuth " . urldecode(http_build_query($oauthParameters, '', ', ')); $request = (new HttpRequest())->setUri(self::BASE_URI . "/statuses/update.json")->setMethod('POST')->setProtocol('1.1')->setBody('status=' . urlencode($tweetText))->setAllHeaders(['Authorization' => $authorizationHeader, 'Content-Type' => 'application/x-www-form-urlencoded']); /** @var HttpResponse $result */ $result = (yield $this->httpClient->request($request)); $tweetInfo = json_decode($result->getBody(), true); $tweetUri = 'https://twitter.com/' . $tweetInfo['user']['screen_name'] . '/status/' . $tweetInfo['id_str']; return $this->chatClient->postMessage($command->getRoom(), $tweetUri); }
private function setReminder(Command $command, string $commandName) : Promise { return resolve(function () use($command, $commandName) { $intervalParser = new IntervalParser(); switch ($commandName) { case 'in': $parameters = $intervalParser->normalizeTimeInterval(implode(" ", $command->getParameters())); $expression = IntervalParser::$intervalSeparatorDefinitions . IntervalParser::$intervalWithTrailingData; if (preg_match($expression, $parameters, $matches)) { $time = $matches['interval'] ?? false; $text = $matches['trailing'] ?? false; } break; case 'at': $time = $command->getParameter(0) ?? false; // 24hrs if ($time && preg_match(self::TIME_FORMAT_REGEX, $time)) { // maybe @TODO support !!at monday next week remind? $text = implode(" ", array_diff($command->getParameters(), array($time))); } break; case 'reminder': $parameters = implode(" ", $command->getParameters()); if (!preg_match(self::REMINDER_REGEX, $parameters, $matches)) { return $this->chatClient->postMessage($command->getRoom(), self::USAGE); } $time = $matches[2] ?? ''; $text = $matches[1] ?? false; if ($time !== '') { $time = $intervalParser->normalizeTimeInterval($time); } break; } if (!isset($time) || !$time) { return $this->chatClient->postMessage($command->getRoom(), "Have a look at the time again, yo!"); } if (!isset($text) || !$text) { return $this->chatClient->postMessage($command->getRoom(), self::USAGE); } $timestamp = strtotime($time) ?: strtotime("+{$time}"); // false|int if (!$timestamp) { return $this->chatClient->postMessage($command->getRoom(), "Have a look at the time again, yo!"); } $key = (string) $command->getId(); $value = ['id' => $key, 'text' => $text, 'delay' => $time, 'userId' => $command->getUserId(), 'username' => $command->getUserName(), 'timestamp' => $timestamp]; $seconds = $timestamp - time(); if ($seconds <= 0) { return $this->chatClient->postReply($command, "I guess I'm late: " . $text); } if ((yield $this->storage->set($key, $value, $command->getRoom()))) { $watcher = once(function () use($command, $value, $key) { (yield $this->storage->unset($key, $command->getRoom())); return $this->chatClient->postReply($command, $value['text']); }, $seconds * 1000); $this->watchers[] = $watcher; return $this->chatClient->postMessage($command->getRoom(), "Reminder set."); } return $this->chatClient->postMessage($command->getRoom(), "Dunno what happened but I couldn't set the reminder."); }); }
private function invokeHandlerForCommand(Command $command) : Promise { $roomIdent = $command->getRoom()->getIdentifier()->getIdentString(); $commandName = $command->getCommandName(); if (!isset($this->commandMap[$roomIdent][$commandName])) { return new Success(); } return resolve(function () use($command, $roomIdent, $commandName) { $userId = $command->getUserId(); $userIsBanned = (yield $this->banStorage->isBanned($command->getRoom(), $userId)); if ($userIsBanned) { $this->logger->log(Level::DEBUG, "User #{$userId} is banned, ignoring event #{$command->getEvent()->getId()} for plugin command endpoints" . " (command: {$commandName})"); return; } /** @var Plugin $plugin */ /** @var PluginCommandEndpoint $endpoint */ list($plugin, $endpoint) = $this->commandMap[$roomIdent][$commandName]; // just a sanity check, shouldn't ever be false but in case something goes horribly wrong if (!$this->isPluginEnabledForRoom($plugin, $command->getRoom())) { $this->logger->log(Level::DEBUG, "Command {$commandName} still present for {$roomIdent} but plugin {$plugin->getName()}" . " is disabled! (endpoint: {$endpoint->getName()})"); return; } (yield $this->invokeCallbackAsPromise($endpoint->getCallback(), $command)); }); }
public function dadGreet(Command $command) { $room = $command->getRoom(); switch ($command->getParameter(0)) { case 'on': if (!(yield $this->admin->isAdmin($room, $command->getUserId()))) { return $this->chatClient->postReply($command, "I'm sorry Dave, I'm afraid I can't do that"); } if (preg_match('#[0-9]+#', $command->getParameter(1))) { if (1 > ($frequency = (int) $command->getParameter(1))) { return $this->chatClient->postReply($command, 'Frequency cannot be less than 1'); } yield from $this->setDadGreetFrequency($room, $frequency); } yield from $this->setDadGreetEnabled($room, true); return $this->chatClient->postMessage($room, 'Dad greeting is now enabled with a frequency of ' . (yield from $this->getDadGreetFrequency($room))); case 'off': if (!(yield $this->admin->isAdmin($room, $command->getUserId()))) { return $this->chatClient->postReply($command, "I'm sorry Dave, I'm afraid I can't do that"); } yield from $this->setDadGreetEnabled($room, false); return $this->chatClient->postMessage($room, 'Dad greeting is now disabled'); case 'status': $state = (yield from $this->isDadGreetEnabled($room)) ? 'enabled with a frequency of ' . (yield from $this->getDadGreetFrequency($room)) : 'disabled'; return $this->chatClient->postMessage($room, 'Dad greeting is currently ' . $state); } return $this->chatClient->postReply($command, 'Syntax: ' . $command->getCommandName() . ' on|off|status [frequency]'); }
public function tweet(Command $command) { $room = $command->getRoom(); if (!(yield $this->admin->isAdmin($room, $command->getUserId()))) { return $this->chatClient->postReply($command, "I'm sorry Dave, I'm afraid I can't do that"); } try { /** @var TwitterClient $client */ $client = (yield from $this->getClientForRoom($room)); // do this first to make sure it's worth going further $message = (yield from $this->getRawMessage($room, $command->getParameter(0))); $request = (yield from $this->buildTwitterRequest($room, $message)); $result = (yield $client->request($request)); $tweetURL = sprintf('https://twitter.com/%s/status/%s', $result['user']['screen_name'], $result['id_str']); return $this->chatClient->postMessage($room, $tweetURL); } catch (NotConfiguredException $e) { return $this->chatClient->postReply($command, "I'm not currently configured for tweeting :-("); } catch (LibXMLFatalErrorException $e) { return $this->chatClient->postReply($command, 'Totally failed to parse the chat message :-('); } catch (MessageIDNotFoundException $e) { return $this->chatClient->postReply($command, 'I need a chat message link to tweet'); } catch (TweetIDNotFoundException $e) { return $this->chatClient->postReply($command, "That looks like a retweet but I can't find the tweet ID :-S"); } catch (TextProcessingFailedException $e) { return $this->chatClient->postReply($command, "Processing the message text failed :-S"); } catch (TweetLengthLimitExceededException $e) { return $this->chatClient->postReply($command, "Boo! The message exceeds the 140 character limit. :-("); } catch (TwitterRequestFailedException $e) { return $this->chatClient->postReply($command, 'Posting to Twitter failed :-( ' . $e->getMessage()); } }
private function alias(CommandMessage $command) : \Generator { $room = $command->getRoom(); if (!(yield $this->adminStorage->isAdmin($room, $command->getUserId()))) { return $this->chatClient->postReply($command, self::message('user_not_admin')); } if (!$command->hasParameters(2)) { return $this->chatClient->postReply($command, self::message('syntax')); } $newCmd = $command->getParameter(1); $oldCmd = $command->getParameter(2); if (in_array($newCmd, $this->builtInCommandManager->getRegisteredCommands())) { return $this->chatClient->postReply($command, self::message('command_built_in', $newCmd)); } if ($this->pluginManager->isCommandMappedForRoom($room, $newCmd)) { return $this->chatClient->postReply($command, self::message('command_already_mapped', $newCmd)); } if (in_array($oldCmd, $this->builtInCommandManager->getRegisteredCommands())) { return $this->chatClient->postReply($command, self::message('command_built_in', $oldCmd)); } if (!$this->pluginManager->isCommandMappedForRoom($room, $oldCmd)) { return $this->chatClient->postReply($command, self::message('command_not_mapped', $oldCmd)); } $mapping = $this->pluginManager->getMappedCommandsForRoom($room)[$oldCmd]; if (!$this->pluginManager->isPluginEnabledForRoom($mapping['plugin_name'], $room)) { return $this->chatClient->postReply($command, self::message('plugin_not_enabled', $mapping['plugin_name'])); } (yield $this->pluginManager->mapCommandForRoom($room, $mapping['plugin_name'], $mapping['endpoint_name'], $newCmd)); return $this->chatClient->postMessage($room, self::message('command_map_success', $newCmd, $mapping['plugin_name'], $mapping['endpoint_name'])); }