From 742009621ee837e0dec8a50839e29b16d3c05cf0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 5 Feb 2021 21:53:59 +0100 Subject: [PATCH 1/4] readme: added support me --- readme.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 9c09818..6b2088f 100644 --- a/readme.md +++ b/readme.md @@ -6,8 +6,6 @@ Twitter for PHP is a very small and easy-to-use library for sending messages to Twitter and receiving status updates. -If you like this, **[please make a donation now](https://nette.org/make-donation?to=twitter-php)**. Thank you! - It requires PHP 5.4 or newer with CURL extension and is licensed under the New BSD License. You can obtain the latest version from our [GitHub repository](https://github.com/dg/twitter-php) or install it via Composer: @@ -15,6 +13,16 @@ or install it via Composer: composer require dg/twitter-php +[Support Me](https://github.com/sponsors/dg) +-------------------------------------------- + +Do you like Nette DI? Are you looking forward to the new features? + +[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg) + +Thank you! + + Usage ----- Sign in to the https://twitter.com and register an application from the https://apps.twitter.com page. Remember From 5b7dcbbc2622c4a944de5350d259c2f1e2bd5c97 Mon Sep 17 00:00:00 2001 From: Greaby Date: Sun, 18 Apr 2021 08:38:52 +0200 Subject: [PATCH 2/4] add required id parameter to destroy function --- src/Twitter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Twitter.php b/src/Twitter.php index 952e568..eec96b5 100644 --- a/src/Twitter.php +++ b/src/Twitter.php @@ -228,7 +228,7 @@ public function loadUserFollowersList(string $username, int $count = 200, int $c */ public function destroy($id) { - $res = $this->request("statuses/destroy/$id", 'POST'); + $res = $this->request("statuses/destroy/$id", 'POST', ['id' => $id]); return $res->id ?: false; } From bcec11bd5591969b7cffc55e73dab168f15e26d7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 22 Apr 2021 19:10:49 +0200 Subject: [PATCH 3/4] coding style --- examples/custom-request.php | 4 ++-- examples/load.php | 4 ++-- examples/search.php | 4 ++-- src/OAuth.php | 31 ++++++++++++++++++++++++------- src/Twitter.php | 33 ++++++++++++++++++++++++--------- 5 files changed, 54 insertions(+), 22 deletions(-) diff --git a/examples/custom-request.php b/examples/custom-request.php index d4c7778..36d77e5 100644 --- a/examples/custom-request.php +++ b/examples/custom-request.php @@ -16,11 +16,11 @@ Twitter retweets of me diff --git a/examples/load.php b/examples/load.php index a223e3a..6a1fa39 100644 --- a/examples/load.php +++ b/examples/load.php @@ -19,11 +19,11 @@ Twitter timeline demo diff --git a/examples/search.php b/examples/search.php index 6488efb..49344ad 100644 --- a/examples/search.php +++ b/examples/search.php @@ -16,11 +16,11 @@ Twitter search demo diff --git a/src/OAuth.php b/src/OAuth.php index 38d3505..db096e2 100644 --- a/src/OAuth.php +++ b/src/OAuth.php @@ -302,12 +302,18 @@ public function __construct(string $http_method, string $http_url, array $parame /** * attempt to build up a request from what was passed to the server */ - public static function from_request(string $http_method = null, string $http_url = null, array $parameters = null): self + public static function from_request( + string $http_method = null, + string $http_url = null, + array $parameters = null + ): self { $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') ? 'http' : 'https'; - $http_url = ($http_url) ? $http_url : $scheme . + $http_url = ($http_url) + ? $http_url + : $scheme . '://' . $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'] . @@ -339,7 +345,10 @@ public static function from_request(string $http_method = null, string $http_url // We have a Authorization-header with OAuth data. Parse the header // and add those overriding any duplicates from GET or POST - if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') { + if ( + isset($request_headers['Authorization']) + && substr($request_headers['Authorization'], 0, 6) == 'OAuth ' + ) { $header_parameters = Util::split_header( $request_headers['Authorization'] ); @@ -354,7 +363,13 @@ public static function from_request(string $http_method = null, string $http_url /** * pretty much a helper function to set up the request */ - public static function from_consumer_and_token(Consumer $consumer, ?Token $token, string $http_method, string $http_url, array $parameters = null): self + public static function from_consumer_and_token( + Consumer $consumer, + ?Token $token, + string $http_method, + string $http_url, + array $parameters = null + ): self { $parameters = $parameters ?: []; $defaults = [ @@ -392,7 +407,7 @@ public function set_parameter(string $name, $value, bool $allow_duplicates = tru public function get_parameter(string $name) { - return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + return $this->parameters[$name] ?? null; } @@ -465,7 +480,9 @@ public function get_normalized_http_url(): string $parts = parse_url($this->http_url); $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; - $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); + $port = (isset($parts['port'])) + ? $parts['port'] + : (($scheme == 'https') ? '443' : '80'); $host = (isset($parts['host'])) ? $parts['host'] : ''; $path = (isset($parts['path'])) ? $parts['path'] : ''; @@ -581,7 +598,7 @@ class Util public static function urlencode_rfc3986($input) { if (is_array($input)) { - return array_map([__CLASS__, 'urlencode_rfc3986'], $input); + return array_map([self::class, 'urlencode_rfc3986'], $input); } elseif (is_scalar($input)) { return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode((string) $input))); } else { diff --git a/src/Twitter.php b/src/Twitter.php index eec96b5..fb17c8f 100644 --- a/src/Twitter.php +++ b/src/Twitter.php @@ -54,8 +54,12 @@ class Twitter * Creates object using consumer and access keys. * @throws Exception when CURL extension is not loaded */ - public function __construct(string $consumerKey, string $consumerSecret, string $accessToken = null, string $accessTokenSecret = null) - { + public function __construct( + string $consumerKey, + string $consumerSecret, + string $accessToken = null, + string $accessTokenSecret = null + ) { if (!extension_loaded('curl')) { throw new Exception('PHP extension CURL is not loaded.'); } @@ -196,7 +200,12 @@ public function loadUserInfoById(string $id): stdClass * https://dev.twitter.com/rest/reference/get/followers/ids * @throws Exception */ - public function loadUserFollowers(string $username, int $count = 5000, int $cursor = -1, $cacheExpiry = null): stdClass + public function loadUserFollowers( + string $username, + int $count = 5000, + int $cursor = -1, + $cacheExpiry = null + ): stdClass { return $this->cachedRequest('followers/ids', [ 'screen_name' => $username, @@ -211,7 +220,12 @@ public function loadUserFollowers(string $username, int $count = 5000, int $curs * https://dev.twitter.com/rest/reference/get/followers/list * @throws Exception */ - public function loadUserFollowersList(string $username, int $count = 200, int $cursor = -1, $cacheExpiry = null): stdClass + public function loadUserFollowersList( + string $username, + int $count = 200, + int $cursor = -1, + $cacheExpiry = null + ): stdClass { return $this->cachedRequest('followers/list', [ 'screen_name' => $username, @@ -347,9 +361,8 @@ public function request(string $resource, string $method, array $data = [], arra $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); if ($code >= 400) { - throw new Exception(isset($payload->errors[0]->message) - ? $payload->errors[0]->message - : "Server error #$code with answer $result", + throw new Exception( + $payload->errors[0]->message ?? "Server error #$code with answer $result", $code ); } elseif ($code === 204) { @@ -379,7 +392,9 @@ public function cachedRequest(string $resource, array $data = [], $cacheExpire = . '.json'; $cache = @json_decode((string) @file_get_contents($cacheFile)); // intentionally @ - $expiration = is_string($cacheExpire) ? strtotime($cacheExpire) - time() : $cacheExpire; + $expiration = is_string($cacheExpire) + ? strtotime($cacheExpire) - time() + : $cacheExpire; if ($cache && @filemtime($cacheFile) + $expiration > time()) { // intentionally @ return $cache; } @@ -424,7 +439,7 @@ public static function clickable(stdClass $status): string } krsort($all); - $s = isset($status->full_text) ? $status->full_text : $status->text; + $s = $status->full_text ?? $status->text; foreach ($all as $pos => $item) { $s = iconv_substr($s, 0, $pos, 'UTF-8') . '' . htmlspecialchars($item[1]) . '' From 03210dcf59c345019cf5fdc29785aa26e7aa9cb3 Mon Sep 17 00:00:00 2001 From: Andrew Male Date: Tue, 29 Nov 2022 12:19:23 -0500 Subject: [PATCH 4/4] Making adjustments to allow operation w/ API 2 --- readme.md | 8 +++++- src/Twitter.php | 69 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 6b2088f..85009c3 100644 --- a/readme.md +++ b/readme.md @@ -116,7 +116,7 @@ if (!$twitter->authenticate()) { Other commands -------------- -You can use all commands defined by [Twitter API 1.1](https://dev.twitter.com/rest/public). +You can use all commands defined by [Twitter API](https://dev.twitter.com/rest/public). For example [GET statuses/retweets_of_me](https://dev.twitter.com/rest/reference/get/statuses/retweets_of_me) returns the array of most recent tweets authored by the authenticating user: @@ -124,6 +124,12 @@ returns the array of most recent tweets authored by the authenticating user: $statuses = $twitter->request('statuses/retweets_of_me', 'GET', ['count' => 20]); ``` +You can also specify which API version to use with the API_*_SUFFIX constants: + +```php +$statuses = $twitter->request('tweets', 'GET', [], [], Twitter::API_2_SUFFIX); +``` + Changelog --------- v4.1 (11/2019) diff --git a/src/Twitter.php b/src/Twitter.php index fb17c8f..a78ceed 100644 --- a/src/Twitter.php +++ b/src/Twitter.php @@ -28,7 +28,10 @@ class Twitter public const REPLIES = 3; public const RETWEETS = 128; // include retweets? - private const API_URL = 'https://api.twitter.com/1.1/'; + public const API_1_SUFFIX = "1.1"; + public const API_2_SUFFIX = "2"; + + private const API_URL = 'https://api.twitter.com/'; /** @var int */ public static $cacheExpire = '30 minutes'; @@ -284,18 +287,62 @@ public function getTrends(int $WOEID): array /** - * Process HTTP request. - * @param string $method GET|POST|JSONPOST|DELETE - * @return mixed + * Generates an API url, requires at minimum 2 parts (version & path). + * + * @param string ...$parts Collection of URL parts to combine * @throws Exception + * @return string */ - public function request(string $resource, string $method, array $data = [], array $files = []) + protected static function makeApiURL(string ...$parts) { + $url = []; + $partsCount = count($parts); + + if ($partsCount < 1) { + throw new Exception("Invalid API URL components provided. Must have at least 2 parts (version & path)"); + } + + $url[] = substr(self::API_URL, 0, strlen(self::API_URL) - 1); + + for ($i = 0; $i < $partsCount; $i++) { + $part = $parts[$i]; + $partLen = strlen($part); + + if ($part[$partLen - 1] == '/') { + $part = substr($part, 0, $partLen - 1); + } + + if ($part[0] == '/') { + $part = substr($part, 1); + } + + $url[] = $part; + } + + return implode('/', $url); + } + + + /** + * Process HTTP request. If $resource contains only endpoint path (no http://|https://), API_URL will be prefixed + * onto resource path. If $apiSuffix is '1.1' (default), resource will have '.json' added as a suffix if '.' + * character not found. + * + * @param string $resource API endpoint + * @param string $method GET|POST|JSONPOST|DELETE + * @param array $data Optional query/body data + * @param array $files Optional file data + * @param string $apiSuffix Optional API version suffix (1.1 by default) + * @throws Exception|\DG\Twitter\OAuth\Exception + * @return mixed + */ + public function request(string $resource, string $method, array $data = [], array $files = [], string $apiSuffix = self::API_1_SUFFIX) { if (!strpos($resource, '://')) { - if (!strpos($resource, '.')) { + if ($apiSuffix == self::API_1_SUFFIX && !strpos($resource, '.')) { $resource .= '.json'; } - $resource = self::API_URL . $resource; + + $resource = static::makeApiURL($apiSuffix, $resource); } foreach ($data as $key => $val) { @@ -308,6 +355,7 @@ public function request(string $resource, string $method, array $data = [], arra if (!is_file($file)) { throw new Exception("Cannot read the file $file. Check if file exists on disk and check its permissions."); } + $data[$key] = new \CURLFile($file); } @@ -317,7 +365,6 @@ public function request(string $resource, string $method, array $data = [], arra $method = 'POST'; $data = json_encode($data); $headers[] = 'Content-Type: application/json'; - } elseif (($method === 'GET' || $method === 'DELETE') && $data) { $resource .= '?' . http_build_query($data, '', '&'); } @@ -348,12 +395,16 @@ public function request(string $resource, string $method, array $data = [], arra $curl = curl_init(); curl_setopt_array($curl, $options); $result = curl_exec($curl); + if (curl_errno($curl)) { throw new Exception('Server error: ' . curl_error($curl)); } - if (strpos(curl_getinfo($curl, CURLINFO_CONTENT_TYPE), 'application/json') !== false) { + $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); + + if ($contentType === false || strpos($contentType, 'application/json') !== false) { $payload = @json_decode($result, false, 128, JSON_BIGINT_AS_STRING); // intentionally @ + if ($payload === false) { throw new Exception('Invalid server response'); }