189 lines
6.3 KiB
PHP
189 lines
6.3 KiB
PHP
|
<?php
|
||
|
namespace Misuzu\Twitter;
|
||
|
|
||
|
use RuntimeException;
|
||
|
use Misuzu\Config\IConfig;
|
||
|
|
||
|
class TwitterClient {
|
||
|
public const SYSTEM_SCOPES = [
|
||
|
'tweet.read', 'tweet.write',
|
||
|
'users.read', 'offline.access',
|
||
|
'follows.read', 'follows.write',
|
||
|
'like.read', 'like.write',
|
||
|
];
|
||
|
|
||
|
private const API_BASE = 'https://api.twitter.com';
|
||
|
private const API_V2 = self::API_BASE . '/2';
|
||
|
|
||
|
private const API_OAUTH2 = self::API_V2 . '/oauth2';
|
||
|
private const API_OAUTH2_TOKEN = self::API_OAUTH2 . '/token';
|
||
|
private const API_OAUTH2_REVOKE = self::API_OAUTH2 . '/revoke';
|
||
|
|
||
|
private const API_TWEETS = self::API_V2 . '/tweets';
|
||
|
|
||
|
public function __construct(
|
||
|
private TwitterClientId $clientId,
|
||
|
private TwitterAccessToken $accessToken
|
||
|
) {}
|
||
|
|
||
|
public function getClientId(): TwitterClientId {
|
||
|
return $this->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'))
|
||
|
);
|
||
|
}
|
||
|
}
|