private function getJoke() : \Generator { /** @var HttpResponse $response */ $response = (yield $this->httpClient->request(self::API_URL)); $result = json_try_decode($response->getBody(), true); if (!isset($result['type']) && $result['type'] !== 'success') { throw new \RuntimeException('Invalid response format'); } return htmlspecialchars_decode($result['value']['joke']); }
private function executeAction(Action $action) { $exceptionClass = $action->getExceptionClassName(); if (!$action->isValid()) { $action->fail(new $exceptionClass('Action no longer valid at point of execution')); return; } $attempt = 0; while ($attempt++ < $action->getMaxAttempts()) { /** @var HttpResponse $response */ $this->logger->log(Level::DEBUG, 'Post attempt ' . $attempt); $response = (yield $this->httpClient->request($action->getRequest())); $this->logger->log(Level::DEBUG, 'Got response from server: ' . $response->getBody()); if ($response->getStatus() === 409) { try { $delay = $this->getBackOffDelay($response->getBody()); } catch (\InvalidArgumentException $e) { $errStr = 'Got a 409 response to an Action request that could not be decoded as a back-off delay'; $this->logger->log(Level::ERROR, $errStr, $response->getBody()); $action->fail(new $exceptionClass($errStr, $e->getCode(), $e)); return; } $this->logger->log(Level::DEBUG, "Backing off chat action execution for {$delay}ms"); (yield new Pause($delay)); continue; } if ($response->getStatus() !== 200) { $errStr = 'Got a ' . $response->getStatus() . ' response to an Action request'; $this->logger->log(Level::ERROR, $errStr, [$action->getRequest(), $response]); $action->fail(new $exceptionClass($errStr)); return; } try { $decoded = json_try_decode($response->getBody(), true); } catch (JSONDecodeErrorException $e) { $errStr = 'A response that could not be decoded as JSON was received' . ' (JSON decode error: ' . $e->getMessage() . ')'; $this->logger->log(Level::ERROR, $errStr, $response->getBody()); $action->fail(new $exceptionClass($errStr, $e->getCode(), $e)); return; } $result = $action->processResponse($decoded, $attempt); if ($result < 1) { return; } if ($attempt >= $action->getMaxAttempts()) { break; } $this->logger->log(Level::DEBUG, "Backing off chat action execution for {$result}ms"); (yield new Pause($result)); } $this->logger->log(Level::ERROR, 'Executing an action failed after ' . $action->getMaxAttempts() . ' attempts and I know when to quit'); }
public function getAccessToken(string $clientID, string $clientSecret) : Promise { $body = (new FormBody())->addField('grant_type', 'client_credentials')->addField('scope', self::BASE_URL)->addField('client_id', $clientID)->addField('client_secret', $clientSecret); $request = (new HttpRequest())->setMethod('POST')->setUri(self::AUTH_URL)->setBody($body); return resolve(function () use($request) { /** @var HttpResponse $response */ $response = (yield $this->httpClient->request($request)); $decoded = json_try_decode($response->getBody()); if (!empty($decoded->error)) { throw new \RuntimeException($decoded->error_description); } return $decoded->access_token; }); }
public function search(Command $command) : \Generator { if (!$command->hasParameters()) { return new Success(); } /** @var HttpResponse $response */ $response = (yield $this->makeAPIRequest(['titles' => implode(' ', $command->getParameters())])); $result = json_try_decode($response->getBody(), true); $firstHit = reset($result['query']['pages']); if (!isset($firstHit['pageid'])) { return $this->chatClient->postReply($command, 'Sorry I couldn\'t find that page.'); } $response = (yield $this->makeAPIRequest(['prop' => 'info', 'inprop' => 'url', 'pageids' => $firstHit['pageid']])); $page = json_try_decode($response->getBody(), true); return $this->chatClient->postMessage($command->getRoom(), $page['query']['pages'][$firstHit['pageid']]['canonicalurl']); }
private function refreshGlobalJokesIfNecessary() { $refreshNeeded = !(yield $this->storage->exists('jokes')) || !(yield $this->storage->exists('refreshtime')) || (yield $this->storage->get('refreshtime')) < time(); if (!$refreshNeeded) { return; } /** @var HttpResponse $response */ $response = (yield $this->httpClient->request(self::JOKE_URL)); if (!preg_match('#JOKES=(\\[[^\\]]+])#', $response->getBody(), $match)) { return; } $jokesJson = preg_replace('#((?<={)setup|(?<=,)punchline)#', '"$1"', $match[1]); $jokesJson = preg_replace_callback('#(?<=:|,|{)\'(.*?)\'(?=:|,|\\))#', function ($match) { return json_encode($match[1]); }, $jokesJson); (yield $this->storage->set('jokes', json_try_decode($jokesJson, true))); (yield $this->storage->set('refreshtime', time() + 86400)); }
public function search(Command $command) : \Generator { $info = explode('/', implode('/', $command->getParameters()), 2); if (count($info) !== 2) { return $this->chatClient->postReply($command, "Usage: `!!packagist vendor package`"); } list($vendor, $package) = $info; $url = 'https://packagist.org/packages/' . urlencode($vendor) . '/' . urldecode($package) . '.json'; try { /** @var HttpResponse $response */ $response = (yield $this->httpClient->request($url)); if ($response->getStatus() !== 200) { $response = (yield from $this->getResultFromSearchFallback($vendor, $package)); } $data = json_try_decode($response->getBody()); return $this->chatClient->postMessage($command->getRoom(), sprintf("[ [%s](%s) ] %s", $data->package->name, $data->package->repository, $data->package->description)); } catch (\Throwable $e) { return $this->chatClient->postReply($command, 'No matching packages found'); } }
private function getJsonRequestBody(AerysRequest $request, AerysResponse $response) : \Generator { if ($request->getHeader('Content-Type') !== 'application/json') { $this->respondWithError($response, 400, 'Type of request body must be application/json'); return null; } $body = $request->getBody(); $bodyText = ''; while ((yield $body->valid())) { $bodyText .= $body->consume(); } try { $json = json_try_decode($bodyText, true); } catch (JSONDecodeErrorException $e) { $this->respondWithError($response, 400, 'Unable to decode request body as application/json'); return null; } if (!is_array($json)) { $this->respondWithError($response, 400, 'Invalid request data structure'); return null; } return $json; }
public function onData(WebsocketMessage $websocketMessage) { try { $rawWebsocketMessage = (yield $websocketMessage); $this->logger->log(Level::DEBUG, "Websocket message received on connection to {$this->roomIdentifier}", $rawWebsocketMessage); $this->clearTimeoutWatcher(); $this->setTimeoutWatcher(); try { $data = json_try_decode($rawWebsocketMessage, true); } catch (JSONDecodeErrorException $e) { $this->logger->log(Level::ERROR, "Error decoding JSON message from server: {$e->getMessage()}"); return; } /** @var Event[] $events */ $events = (yield from $this->eventBuilder->build($data, $this->roomIdentifier)); $this->logger->log(Level::DEBUG, count($events) . " events targeting {$this->roomIdentifier} to process"); foreach ($events as $event) { (yield $this->eventDispatcher->processWebSocketEvent($event)); } } catch (\Throwable $e) { $this->logger->log(Level::DEBUG, "Something went generally wrong while processing events for {$this->roomIdentifier}: {$e}"); } }
private function loadFile(string $filePath) : \Generator { if (isset($this->loadPromises[$filePath])) { (yield $this->loadPromises[$filePath]); return $this->dataCache[$filePath]; } $deferred = new Deferred(); $this->loadPromises[$filePath] = $deferred->promise(); $this->lockMutexes[$filePath] = new QueuedExclusiveMutex(); return (yield $this->lockMutexes[$filePath]->withLock(function () use($filePath, $deferred) { try { // we may have been waiting on a lock and it's been populated by now if (!isset($this->dataCache[$filePath])) { $this->dataCache[$filePath] = (yield exists($filePath)) ? json_try_decode((yield get($filePath)), true) : []; } } catch (\Throwable $e) { $this->dataCache[$filePath] = []; } finally { $deferred->succeed(); unset($this->loadPromises[$filePath]); } return $this->dataCache[$filePath]; })); }
/** * @param ChatRoom|ChatRoomIdentifier $room * @return Promise */ public function getPingableUsers($room) : Promise { $url = $this->urlResolver->getEndpointURL($room, ChatRoomEndpoint::CHATROOM_INFO_PINGABLE); return resolve(function () use($url) { /** @var HttpResponse $response */ $response = (yield $this->httpClient->request($url)); if ($response->getStatus() !== 200) { throw new DataFetchFailureException("Fetching pingable users list failed with response code " . $response->getStatus()); } $result = []; foreach (json_try_decode($response->getBody(), true) as $item) { $result[] = ['id' => (int) $item[0], 'name' => $item[1], 'pingable' => preg_replace('~\\s+~', '', $item[1])]; } return $result; }); }
private function getWebSocketUri(Identifier $identifier, string $fKey) { $authBody = (new FormBody())->addField("roomid", $identifier->getId())->addField("fkey", $fKey); $historyBody = (new FormBody())->addField('since', 0)->addField('mode', 'Messages')->addField("msgCount", 1)->addField("fkey", $fKey); $requests = ['auth' => (new HttpRequest())->setUri($this->urlResolver->getEndpointURL($identifier, Endpoint::CHATROOM_WEBSOCKET_AUTH))->setMethod("POST")->setBody($authBody), 'history' => (new HttpRequest())->setUri($this->urlResolver->getEndpointURL($identifier, Endpoint::CHATROOM_EVENT_HISTORY))->setMethod("POST")->setBody($historyBody)]; /** @var HttpResponse[] $responses */ $responses = (yield all($this->httpClient->requestMulti($requests))); $authInfo = json_try_decode($responses['auth']->getBody(), true); $historyInfo = json_try_decode($responses['history']->getBody(), true); if (!isset($authInfo['url'])) { throw new \RuntimeException("WebSocket auth did not return URL"); } if (!isset($historyInfo['time'])) { throw new \RuntimeException("Could not get time for WebSocket URL"); } return $authInfo['url'] . '?l=' . $historyInfo['time']; }