clientId; } public function hasClientId(): bool { return $this->clientId->hasClientId(); } public function getAccessToken(): TwitterAccessToken { return $this->accessToken; } public function hasAccessToken(): bool { return $this->accessToken->hasAccessToken(); } public function hasRefreshToken(): bool { return $this->accessToken->hasRefreshToken(); } public function authorise(array $scope, string $redirect): TwitterAuthorisation { return new TwitterAuthorisation($this->clientId, $scope, $redirect); } public function token(string $code, string $verifier, string $redirect): object { if(!$this->clientId->hasClientId()) throw new RuntimeException('Need OAuth2 info in order to manage tokens.'); $req = json_decode(self::request('POST', self::API_OAUTH2_TOKEN, [ 'Authorization: ' . (string)$this->clientId, ], [], [ 'code' => $code, 'grant_type' => 'authorization_code', 'code_verifier' => $verifier, 'redirect_uri' => $redirect, // needed because???????? ], false)); if($req === false) return new RuntimeException('Unable to parse token response.'); return $req; } public function authRefresh(): object { if(!$this->clientId->hasClientId()) throw new RuntimeException('Need OAuth2 info in order to manage tokens.'); if(!$this->accessToken->hasRefreshToken()) throw new RuntimeException('There is no refresh token.'); $req = json_decode(self::request('POST', self::API_OAUTH2_TOKEN, [ 'Authorization: ' . (string)$this->clientId, ], [], [ 'refresh_token' => $this->accessToken->getRefreshToken(), 'grant_type' => 'refresh_token', ], false)); if($req === false) return new RuntimeException('Unable to parse token response.'); return $req; } public function authRevoke(): object { if(!$this->clientId->hasClientId()) throw new RuntimeException('Need OAuth2 info in order to manage tokens.'); if(!$this->accessToken->hasAccessToken()) throw new RuntimeException('Cannot revoke an access token we do not have.'); $req = json_decode(self::request('POST', self::API_OAUTH2_REVOKE, [ 'Authorization: ' . (string)$this->clientId, ], [], [ 'token' => $this->accessToken->getAccessToken(), 'token_type_hint' => 'access_token', ], false)); if($req === false) return new RuntimeException('Unable to parse token response.'); return $req; } public function sendTweet(string $text): object { if(!$this->accessToken->hasAccessToken()) throw new RuntimeException('Need access token in order to post Tweets.'); $req = json_decode(self::request('POST', self::API_TWEETS, [ 'Authorization: ' . (string)$this->accessToken, ], [], [ 'text' => $text, ])); if($req === false) return new RuntimeException('Unable to parse Tweet response.'); return $req; } public function request( string $method, string $uri, array $headers = [], array $queryFields = [], mixed $bodyFields = [], bool $bodyAsJson = true, ): string|bool { if(!empty($queryFields)) $uri .= '?' . http_build_query($queryFields, '', null, PHP_QUERY_RFC3986); $curl = curl_init($uri); curl_setopt_array($curl, [ CURLOPT_AUTOREFERER => true, CURLOPT_FAILONERROR => false, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TCP_NODELAY => true, CURLOPT_HEADER => false, CURLOPT_NOBODY => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_TCP_FASTOPEN => true, CURLOPT_MAXREDIRS => 3, CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_TIMEOUT => 2, CURLOPT_USERAGENT => 'Misuzu TwitterClient/20230105', ]); if($method === 'GET') curl_setopt($curl, CURLOPT_HTTPGET, true); elseif($method === 'HEAD') { curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_NOBODY, true); } elseif($method === 'POST') curl_setopt($curl, CURLOPT_POST, true); else curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); if(!empty($bodyFields)) { if($bodyAsJson) { $headers[] = 'Content-Type: application/json'; $bodyFields = json_encode($bodyFields); } elseif(is_array($bodyFields)) { $headers[] = 'Content-Type: application/x-www-form-urlencoded'; $bodyFields = http_build_query($bodyFields, '', null, PHP_QUERY_RFC3986); } curl_setopt($curl, CURLOPT_POSTFIELDS, $bodyFields); } if(!empty($headers)) curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); $out = curl_exec($curl); curl_close($curl); return $out; } public static function create(IConfig $config): self { return new static( TwitterClientId::load($config->scopeTo('oauth2')), TwitterAccessToken::load($config->scopeTo('access')) ); } }