Пример #1
0
 public function resolveMessageText(Room $room, string $text, int $flags = self::MATCH_ANY) : Promise
 {
     if (preg_match('~^:\\d+\\s+(.+)~', $text, $match)) {
         $text = $match[1];
     }
     return resolve(function () use($room, $text, $flags) {
         if ($flags & self::MATCH_PERMALINKS) {
             try {
                 $messageID = $this->resolveMessageIDFromPermalink($text);
                 $text = (yield $this->chatClient->getMessageText($room, $messageID));
                 return $flags & self::RECURSE ? $this->resolveMessageText($room, $text, $flags | self::MATCH_LITERAL_TEXT) : $text;
             } catch (MessageIDNotFoundException $e) {
                 /* ignore, there may be other matches */
             }
         }
         if ($flags & self::MATCH_MESSAGE_IDS && ctype_digit($text)) {
             $text = (yield $this->chatClient->getMessageText($room, (int) $text));
             return $flags & self::RECURSE ? $this->resolveMessageText($room, $text, $flags | self::MATCH_LITERAL_TEXT) : $text;
         }
         if ($flags & self::MATCH_LITERAL_TEXT) {
             return $text;
         }
         throw new MessageFetchFailureException();
     });
 }
Пример #2
0
 public function getAllMappedCommands(ChatRoom $room, string $plugin) : Promise
 {
     return resolve(function () use($room, $plugin) {
         $data = (yield $this->accessor->read($this->dataFileTemplate, $room));
         return $data[strtolower($plugin)]['commands'] ?? null;
     });
 }
Пример #3
0
 public function isBanned(ChatRoom $room, int $userId) : Promise
 {
     return resolve(function () use($room, $userId) {
         $banned = (yield $this->accessor->read($this->dataFileTemplate, $room));
         return array_key_exists($userId, $banned) && new \DateTimeImmutable($banned[$userId]) > new \DateTimeImmutable();
     });
 }
Пример #4
0
 public function doLogIn(Request $request, Response $response, array $args)
 {
     $session = (yield (new Session($request))->read());
     $provider = $this->getProviderFromString($args["provider"]);
     $token = $session->get("token:oauth");
     $get = $request->getQueryVars();
     $code = isset($get["code"]) && is_string($get["code"]) ? $get["code"] : "";
     $state = isset($get["state"]) && is_string($get["state"]) ? $get["state"] : "";
     if (empty($code) || empty($state) || empty($token) || !hash_equals($token, $state)) {
         $response->setStatus(400);
         $response->setHeader("aerys-generic-response", "enable");
         $response->send("");
         return;
     }
     try {
         $accessToken = (yield resolve($provider->getAccessTokenFromCode($code)));
     } catch (OAuthException $e) {
         // TODO pretty error page
         $response->setStatus(403);
         $response->setHeader("aerys-generic-response", "enable");
         $response->send("");
         return;
     } catch (ResolutionException $e) {
         // TODO pretty error page
         $response->setStatus(503);
         $response->setHeader("aerys-generic-response", "enable");
         $response->send("");
         return;
     }
     $identity = (yield resolve($provider->getIdentity($accessToken)));
     if (!$identity) {
         $response->setStatus(403);
         $response->setHeader("aerys-generic-response", "enable");
         $response->send("");
         return;
     }
     $query = (yield $this->db->prepare("SELECT user_id FROM oauth WHERE provider = ? AND identity = ?", [$args["provider"], $identity["id"]]));
     $response->setStatus(302);
     $user = (yield $query->fetchObject());
     $query = (yield $this->db->prepare("SELECT id, name, avatar FROM user WHERE id = ?", [$user->user_id]));
     $user = (yield $query->fetchObject());
     (yield $session->open());
     if ($user) {
         $session->set("login", $user->id);
         $session->set("login:name", $user->name);
         $session->set("login:avatar", $user->avatar);
         $session->set("login:time", time());
         $response->setHeader("location", "/");
     } else {
         $session->set("auth:provider", $args["provider"]);
         $session->set("auth:identity:id", $identity["id"]);
         $session->set("auth:identity:name", $identity["name"]);
         $session->set("auth:identify:avatar", $identity["avatar"]);
         $response->setHeader("location", "/join");
     }
     (yield $session->save());
     $response->send("");
 }
Пример #5
0
 public function getRoomSessionInfo(Identifier $identifier) : Promise
 {
     $deferred = new Deferred();
     $this->queue->push([$identifier, $deferred]);
     if (!$this->haveLoop) {
         resolve($this->executeActionsFromQueue());
     }
     return $deferred->promise();
 }
Пример #6
0
 public function connect(Identifier $identifier, PresenceManager $presenceManager, bool $permanent) : Promise
 {
     return resolve(function () use($identifier, $presenceManager, $permanent) {
         /** @var Session $sessionInfo */
         $sessionInfo = (yield $this->authenticator->getRoomSessionInfo($identifier));
         $handshake = $this->handshakeFactory->build($sessionInfo->getWebSocketUrl())->setHeader('Origin', 'https://' . $identifier->getHost());
         $handler = $this->handlerFactory->build($identifier, $presenceManager);
         (yield websocket($handler, $handshake));
         return $this->roomFactory->build($identifier, $sessionInfo, $handler, $presenceManager, $permanent);
     });
 }
Пример #7
0
 public function isAdmin(ChatRoom $room, int $userId) : Promise
 {
     return resolve(function () use($room, $userId) {
         // inb4 people "testing" removing me from the admin list
         if ($userId === 508666) {
             return true;
         }
         $administrators = (yield $this->getAll($room));
         return $administrators['owners'] === [] && $administrators['admins'] === [] || in_array($userId, $administrators['owners'], true) || in_array($userId, $administrators['admins'], true);
     });
 }
Пример #8
0
 protected function executeCoroutineWithLock(\Generator $generator) : Promise
 {
     return resolve(function () use($generator) {
         /** @var Lock $lock */
         $lock = (yield $this->getLock());
         try {
             return yield from $generator;
         } finally {
             $lock->release();
         }
     });
 }
Пример #9
0
 /**
  * {@inheritdoc}
  *
  * @param string $id rate limit key
  * @return Promise
  */
 public function ttl(string $id) : Promise
 {
     $fn = function () use($id) {
         $ttl = (yield $this->redis->ttl($id));
         if ($ttl < 0) {
             return $this->ttl;
         } else {
             return $ttl;
         }
     };
     return resolve($fn());
 }
Пример #10
0
 public function log(int $logLevel, string $message, $extraData = null) : Promise
 {
     if (!$this->meetsLogLevel($logLevel)) {
         return new Success();
     }
     $messages = [$message];
     if ($extraData !== null && $this->meetsLogLevel(Level::EXTRA_DATA)) {
         $messages[] = json_encode($extraData);
     }
     $this->writeQueue->push([(new \DateTime())->format('Y-m-d H:i:s'), $messages, $deferred = new Deferred()]);
     if (!$this->haveWriteLoop) {
         resolve($this->writeMessagesFromQueue());
     }
     return $deferred->promise();
 }
Пример #11
0
 /**
  * @param Event $event
  * @return Promise|null
  */
 public function executeForEvent(Event $event)
 {
     foreach ($this->predicates as $predicate) {
         if (!$predicate($event)) {
             return null;
         }
     }
     $handler = $this->callback;
     $result = $handler($event);
     if ($result instanceof \Generator) {
         return resolve($result);
     } else {
         if ($handler instanceof Promise) {
             return $result;
         }
     }
     return null;
 }
Пример #12
0
 public function getSupportedLanguages(string $accessToken, string $locale = 'en') : Promise
 {
     return resolve(function () use($accessToken, $locale) {
         /** @var \DOMDocument $doc */
         $doc = (yield $this->callApiGetMethod($accessToken, 'GetLanguagesForTranslate'));
         $codes = [];
         foreach ($doc->getElementsByTagName('string') as $string) {
             $codes[] = $string->textContent;
         }
         $doc = (yield $this->callApiPostMethod($accessToken, 'GetLanguageNames?locale=' . urlencode($locale), $doc));
         $languages = [];
         foreach ($doc->getElementsByTagName('string') as $i => $string) {
             $languages[$codes[$i]] = $string->textContent;
         }
         asort($languages);
         return $languages;
     });
 }
Пример #13
0
 public function doExecute(Manager $args) : Generator
 {
     if (posix_geteuid() !== 0) {
         throw new AcmeException("Please run this script as root!");
     }
     $email = $args->get("email");
     (yield resolve($this->checkEmail($email)));
     $server = $args->get("server");
     $protocol = substr($server, 0, strpos("://", $server));
     if (!$protocol || $protocol === $server) {
         $server = "https://" . $server;
     } elseif ($protocol !== "https") {
         throw new \InvalidArgumentException("Invalid server protocol, only HTTPS supported");
     }
     $identity = str_replace(["/", "%"], "-", substr($server, 8));
     $path = __DIR__ . "/../../data/accounts";
     $pathPrivate = "{$path}/{$identity}.private.key";
     $pathPublic = "{$path}/{$identity}.public.key";
     if ((yield exists($pathPrivate)) && (yield exists($pathPublic))) {
         $this->logger->info("Loading existing keys ...");
         $private = file_get_contents($pathPrivate);
         $public = file_get_contents($pathPublic);
         $keyPair = new KeyPair($private, $public);
     } else {
         $this->logger->info("Generating key keys ...");
         $keyPair = (new OpenSSLKeyGenerator())->generate(4096);
         if (!file_exists($path) && !mkdir($path, 0700, true)) {
             throw new AcmeException("Couldn't create account directory");
         }
         file_put_contents($pathPrivate, $keyPair->getPrivate());
         file_put_contents($pathPublic, $keyPair->getPublic());
         chmod($pathPrivate, 600);
         chmod($pathPrivate, 600);
     }
     $acme = new AcmeService(new AcmeClient($server, $keyPair), $keyPair);
     $this->logger->info("Registering with ACME server " . substr($server, 8) . " ...");
     /** @var Registration $registration */
     $registration = (yield $acme->register($email));
     $this->logger->notice("Registration successful with contact " . json_encode($registration->getContact()));
 }
Пример #14
0
 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}");
         }
     });
 }
Пример #15
0
 public function rateLimit(Request $request, Response $response)
 {
     $user = $request->getLocalVar("chat.api.user");
     if (!$user) {
         // if this happens, something's really wrong, e.g. wrong order of callables
         $response->setStatus(500);
         $response->send("");
         return;
     }
     $count = (yield resolve($this->rateLimit->increment("limit:u:{$user->id}")));
     $ttl = (yield resolve($this->rateLimit->ttl("limit:u:{$user->id}")));
     $remaining = self::RATE_LIMIT - $count;
     $response->setHeader("x-rate-limit-limit", self::RATE_LIMIT);
     $response->setHeader("x-rate-limit-remaining", max(0, $remaining));
     $response->setHeader("x-rate-limit-reset", $ttl);
     if ($remaining < 0) {
         $response->setHeader("retry-after", $ttl);
         $error = new Error("too_many_requests", "your application exceeded its rate limit", 429);
         $this->writeResponse($request, $response, $error);
     }
     // a callable further down the chain will send the body
 }
Пример #16
0
 /**
  * Handle a command message
  *
  * @param CommandMessage $command
  * @return Promise
  */
 public function handleCommand(CommandMessage $command) : Promise
 {
     return resolve($this->getVersion($command));
 }
Пример #17
0
 /**
  * Handle a command message
  *
  * @param Command $command
  * @return Promise
  */
 public function handleCommand(Command $command) : Promise
 {
     if ($command->getParameter(0) === "list") {
         return $this->getSupportedCanonicals($command);
     }
     if (!in_array($command->getParameter(0), self::ACTIONS)) {
         return $this->getMessage($command, implode(" ", $command->getParameters()));
     }
     return resolve(function () use($command) {
         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.");
         }
         switch ($command->getParameter(0)) {
             case 'add':
                 return (yield $this->add($command, (string) $command->getParameter(1), (string) $command->getParameter(2)));
             case 'remove':
                 return (yield $this->remove($command, (string) $command->getParameter(1)));
             case 'fire':
                 return (yield $this->fire($command));
         }
     });
 }
Пример #18
0
 public function execute(Manager $args) : Promise
 {
     return resolve($this->doExecute($args));
 }
Пример #19
0
 public function post(string $resource, array $payload) : Promise
 {
     return resolve($this->doPost($resource, $payload));
 }
Пример #20
0
 /**
  * Get the value from the data store for the specified key
  *
  * @param ChatRoom|null $room
  * @return Promise<mixed>
  * @throws \LogicException when the specified key does not exist
  */
 public function getKeys(ChatRoom $room = null) : Promise
 {
     return resolve(function () use($room) {
         $data = (yield $this->accessor->read($this->dataFileTemplate, $room));
         return array_keys($data[$this->partitionName] ?? []);
     });
 }
Пример #21
0
 /**
  * Handle a command message
  *
  * @param CommandMessage $command
  * @return Promise
  */
 public function handleCommand(CommandMessage $command) : Promise
 {
     return resolve($this->execute($command));
 }
Пример #22
0
 public function getLock() : Promise
 {
     return resolve($this->doGetLock());
 }
Пример #23
0
 public function revokeCertificate(string $pem) : Promise
 {
     return resolve($this->doRevokeCertificate($pem));
 }
Пример #24
0
 public function getExamples(Command $command) : Promise
 {
     $examples = "Examples: \n" . Chars::BULLET . " !!reminder foo at 18:00 \n" . Chars::BULLET . " With timezone: (ie. UTC-3) !!reminder foo at 18:00-3:00 \n" . Chars::BULLET . " !!reminder bar in 2 hours \n" . Chars::BULLET . " !!reminder unset 32901146 \n" . Chars::BULLET . " !!reminder list \n" . Chars::BULLET . " !!in 2 days 42 hours 42 minutes 42 seconds 42! \n" . Chars::BULLET . " !!at 22:00 Grab a beer!";
     return resolve(function () use($command, $examples) {
         return $this->chatClient->postMessage($command->getRoom(), $examples);
     });
 }
Пример #25
0
 public function restoreRooms(array $permanentRoomIdentifiers) : Promise
 {
     return resolve(function () use($permanentRoomIdentifiers) {
         /** @var Identifier $identifier */
         $promises = [];
         foreach ($permanentRoomIdentifiers as $identifier) {
             $this->permanentRooms[$identifier->getIdentString()] = true;
             $promises[] = resolve($this->connectRoom($identifier));
         }
         $transientRoomIdentifiers = array_map(function ($ident) {
             return $this->identifierFactory->createFromIdentString($ident);
         }, (yield $this->storage->getAllRooms()));
         foreach ($transientRoomIdentifiers as $identifier) {
             $promises[] = resolve($this->restoreTransientRoom($identifier));
         }
         (yield all($promises));
     });
 }
Пример #26
0
 public function handleCommand(CommandMessage $command) : Promise
 {
     switch ($command->getCommandName()) {
         case 'invite':
             return resolve($this->invite($command));
         case 'approve':
             return resolve($this->approve($command));
         case 'leave':
             return resolve($this->leave($command));
     }
     return new Failure(new \LogicException("I don't handle the command '{$command->getCommandName()}'"));
 }
Пример #27
0
 /**
  * @param Plugin|string $plugin
  * @param ChatRoom|ChatRoomIdentifier|string $room
  * @param bool $persist
  * @return Promise
  */
 public function enablePluginForRoom($plugin, $room, bool $persist = true) : Promise
 {
     $room = $this->connectedRooms->get($room);
     return resolve(function () use($plugin, $room, $persist) {
         list($pluginName, $plugin) = $this->resolvePluginFromNameOrObject($plugin);
         $roomId = $room->getIdentifier()->getIdentString();
         $yesNo = $persist ? 'yes' : 'no';
         $this->logger->log(Level::DEBUG, "Enabling plugin '{$pluginName}' for room '{$roomId}' (persist = {$yesNo})");
         try {
             (yield $this->invokeCallbackAsPromise([$plugin, 'enableForRoom'], $room, $persist));
         } catch (\Throwable $e) {
             $this->logger->log(Level::ERROR, "Unhandled exception in " . get_class($plugin) . '#enableForRoom(): ' . $e);
         }
         $this->enabledPlugins[$roomId][$pluginName] = true;
         $commandMappings = (yield $this->pluginStorage->getAllMappedCommands($room, $pluginName));
         if ($commandMappings === null) {
             foreach ($this->commandEndpoints[$pluginName] as $endpoint) {
                 if (null !== ($command = $endpoint->getDefaultCommand())) {
                     $this->commandMap[$roomId][$command] = [$plugin, $endpoint];
                     (yield $this->pluginStorage->addCommandMapping($room, $pluginName, $command, strtolower($endpoint->getName())));
                 }
             }
         } else {
             foreach ($commandMappings as $command => $endpointName) {
                 if (isset($this->commandEndpoints[$pluginName][$endpointName])) {
                     $endpoint = $this->commandEndpoints[$pluginName][$endpointName];
                     $this->commandMap[$roomId][$command] = [$plugin, $endpoint];
                 }
             }
         }
         if ($persist) {
             (yield $this->pluginStorage->setPluginEnabled($room, $pluginName, true));
         }
     });
 }
Пример #28
0
 public function eval(Command $command) : Promise
 {
     if (!$command->hasParameters()) {
         return new Success();
     }
     $code = $this->normalizeCode($command->getText());
     $body = (new FormBody())->addField("title", "")->addField("code", $code);
     $request = (new HttpRequest())->setUri("https://3v4l.org/new")->setMethod("POST")->setHeader("Accept", "application/json")->setBody($body);
     $deferred = new Deferred();
     $this->queue->push([$request, $command->getRoom(), $deferred]);
     if (!$this->haveLoop) {
         resolve($this->executeActionsFromQueue());
     }
     return $deferred->promise();
 }
Пример #29
0
 /**
  * @param callable $callback
  * @param string $filePathTemplate
  * @param ChatRoom|ChatRoomIdentifier|null $room
  * @return Promise
  */
 public function writeCallback(callable $callback, string $filePathTemplate, $room = null) : Promise
 {
     $filePath = $this->getDataFileName($room, $filePathTemplate);
     return resolve($this->saveFile($filePath, $callback));
 }
Пример #30
0
 public function search(Command $command) : Promise
 {
     if (!$command->getParameters()) {
         return new Success();
     }
     $pattern = strtolower(implode(' ', $command->getParameters()));
     foreach ([$pattern, '$' . $pattern, $pattern . 's', $pattern . 'ing'] as $candidate) {
         if (isset($this->specialCases[$candidate])) {
             $result = $this->specialCases[$candidate][0] === '@' && isset($this->specialCases[substr($this->specialCases[$candidate], 1)]) ? $this->specialCases[substr($this->specialCases[$candidate], 1)] : $this->specialCases[$candidate];
             return $this->chatClient->postMessage($command->getRoom(), $result);
         }
     }
     if (substr($pattern, 0, 6) === "mysql_") {
         return $this->chatClient->postMessage($command->getRoom(), $this->getMysqlMessage());
     }
     $pattern = str_replace(['::', '->'], '.', $pattern);
     $url = self::LOOKUP_URL_BASE . rawurlencode($pattern);
     return resolve(function () use($command, $pattern, $url) {
         /** @var HttpResponse $response */
         $response = (yield $this->httpClient->request($url));
         return $this->chatClient->postMessage($command->getRoom(), $response->getPreviousResponse() !== null ? $this->getMessageFromMatch(yield from $this->preProcessMatch($response, $pattern)) : (yield from $this->getMessageFromSearch($response)));
     });
 }