diff --git a/src/APnutI.php b/src/APnutI.php index ad07ce3..13d6fdf 100644 --- a/src/APnutI.php +++ b/src/APnutI.php @@ -26,7 +26,7 @@ class APnutI protected string $auth_url = 'https://pnut.io/oauth/authenticate'; protected string $client_secret; protected string $client_id; - protected string $scope = ""; + protected string $scope = ''; protected string $redirect_uri; protected int $rate_limit = 40; protected int $rate_limit_remaining = 40; @@ -73,23 +73,24 @@ class APnutI public static $USER_USERNAME_MAX_LENGTH; public function __construct( - ?string $client_secret = null, - ?string $client_id = null, - ?string $needed_scope = null, - ?string $app_name = null, - ?string $redirect_uri = null, - ?string $log_path = null, - $log_level = null + ?string $client_secret = null, + ?string $client_id = null, + ?string $needed_scope = null, + ?string $app_name = null, + ?string $redirect_uri = null, + ?string $log_path = null, + $log_level = null ) { if (empty($log_level)) { $log_level = Logger::INFO; } elseif (is_string($log_level)) { - $log_level = constant('Monolog\Logger::'.$log_level); + $log_level = constant('Monolog\Logger::' . $log_level); } - $this->logger = empty($log_path) ? new NullLogger() : new Logger($this->app_name); - $this->token_session_key = $this->app_name.'access_token'; - $this->token_redirect_after_auth = $this->app_name - .'redirect_after_auth'; + $this->logger = empty($log_path) + ? new NullLogger() + : new Logger($this->app_name); + $this->token_session_key = $this->app_name . 'access_token'; + $this->token_redirect_after_auth = $this->app_name . 'redirect_after_auth'; $handler = new RotatingFileHandler($log_path, 5, $log_level, true); $this->logger->pushHandler($handler); $this->server_token = null; @@ -151,48 +152,45 @@ class APnutI if (count($header) < 2) { continue; } - list($k,$v) = $header; + list($k, $v) = $header; switch ($k) { case 'X-RateLimit-Remaining': - $this->rate_limit_remaining = (int)$v; - break; + $this->rate_limit_remaining = (int) $v; + break; case 'X-RateLimit-Limit': - $this->rate_limit = (int)$v; - break; + $this->rate_limit = (int) $v; + break; case 'X-RateLimit-Reset': - $this->rate_limit_reset = (int)$v; - break; + $this->rate_limit_reset = (int) $v; + break; case 'X-OAuth-Scopes': $this->scope = $v; $this->scopes = explode(',', $v); - break; + break; case 'location': case 'Location': $this->logger->debug( - 'Is redirect. Headers: '.json_encode($this->headers) - ); - $this->logger->debug( - 'Is redirect. Target: '. $v + 'Is redirect. Headers: ' . json_encode($this->headers) ); + $this->logger->debug('Is redirect. Target: ' . $v); $this->redirect_target = $v; - break; + break; } } return $content; } public function makeRequest( - string $method, - string $end_point, - array $parameters, - string $content_type = 'application/x-www-form-urlencoded', - bool $follow_redirect = true + string $method, + string $end_point, + array $parameters, + string $content_type = 'application/x-www-form-urlencoded', + bool $follow_redirect = true ): array { - $this->redirect_target = null; $this->meta = null; $method = strtoupper($method); - $url = $this->api_url.$end_point; + $url = $this->api_url . $end_point; $this->logger->info("{$method} Request to {$url}"); $ch = curl_init($url); $headers = []; @@ -201,26 +199,29 @@ class APnutI // if they passed an array, build a list of parameters from it curl_setopt($ch, CURLOPT_POST, true); if (is_array($parameters) && $method !== 'POST-RAW') { - $parameters = $content_type === 'application/json' ? json_encode($parameters) : http_build_query($parameters); + $parameters = + $content_type === 'application/json' + ? json_encode($parameters) + : http_build_query($parameters); } curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters); - $headers[] = "Content-Type: ".$content_type; + $headers[] = 'Content-Type: ' . $content_type; } if ($method !== 'POST' && $method !== 'POST-RAW') { - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); } if ($method === 'GET' && isset($parameters['access_token'])) { - $this->logger->info("Using provided token for auth"); - $headers[] = 'Authorization: Bearer '.$params['access_token']; + $this->logger->info('Using provided token for auth'); + $headers[] = 'Authorization: Bearer ' . $params['access_token']; } elseif (!empty($this->access_token)) { - $this->logger->info("Using access token for auth"); - $headers[] = 'Authorization: Bearer '.$this->access_token; + $this->logger->info('Using access token for auth'); + $headers[] = 'Authorization: Bearer ' . $this->access_token; } elseif (!empty($this->server_token)) { $use_server_token = true; - $this->logger->info("Using server token for auth"); - $headers[] = 'Authorization: Bearer '.$this->server_token; + $this->logger->info('Using server token for auth'); + $headers[] = 'Authorization: Bearer ' . $this->server_token; } else { - $this->logger->info("Using no auth"); + $this->logger->info('Using no auth'); } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); @@ -233,13 +234,17 @@ class APnutI $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $effectiveURL = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); curl_close($ch); - $this->logger->debug("{$method} Request to {$url}. Received status: {$http_status}. Response: {$response}"); + $this->logger->debug( + "{$method} Request to {$url}. Received status: {$http_status}. Response: {$response}" + ); if ($http_status === 0) { throw new \Exception('Unable to connect to Pnut ' . $url); } if ($request === false) { if (!curl_getinfo($ch, CURLINFO_SSL_VERIFYRESULT)) { - throw new \Exception('SSL verification failed, connection terminated: ' . $url); + throw new \Exception( + 'SSL verification failed, connection terminated: ' . $url + ); } } if (!empty($response)) { @@ -281,18 +286,26 @@ class APnutI // look for errors if (isset($response['error'])) { if (is_array($response['error'])) { - throw new PnutException($response['error']['message'], $response['error']['code']); + throw new PnutException( + $response['error']['message'], + $response['error']['code'] + ); } else { throw new PnutException($response['error']); } - // look for response migration errors - } elseif (isset($response['meta'], $response['meta']['error_message'])) { - throw new PnutException($response['meta']['error_message'], $response['meta']['code']); + // look for response migration errors + } elseif ( + isset($response['meta'], $response['meta']['error_message']) + ) { + throw new PnutException( + $response['meta']['error_message'], + $response['meta']['code'] + ); } } } if ($http_status < 200 || $http_status >= 300) { - throw new HttpPnutException('HTTP error '.$http_status); + throw new HttpPnutException('HTTP error ' . $http_status); } elseif (isset($response['meta'], $response['data'])) { return $response['data']; } elseif (isset($response['access_token'])) { @@ -300,31 +313,33 @@ class APnutI } elseif (!empty($this->redirect_target)) { return [$this->redirect_target]; } else { - throw new PnutException("No response ".json_encode($response).", http status: ${http_status}"); + throw new PnutException( + 'No response ' . + json_encode($response) . + ", http status: ${http_status}" + ); } } public function post( - string $end_point, - array $parameters, - string $content_type = 'application/x-www-form-urlencoded' + string $end_point, + array $parameters, + string $content_type = 'application/x-www-form-urlencoded' ): array { $method = $content_type === 'multipart/form-data' ? 'POST-RAW' : 'POST'; return $this->makeRequest($method, $end_point, $parameters, $content_type); } - public function postJson( - string $end_point, - array $parameters - ): array { + public function postJson(string $end_point, array $parameters): array + { return $this->post($end_point, $parameters, 'application/json'); } public function get( - string $end_point, - array $parameters = [], - string $content_type = 'application/json', - bool $follow_redirect = true + string $end_point, + array $parameters = [], + string $content_type = 'application/json', + bool $follow_redirect = true ): array { if (!empty($parameters)) { $parsed = parse_url($end_point); @@ -332,7 +347,13 @@ class APnutI $end_point .= $separator . http_build_query($parameters); $parameters = []; } - return $this->makeRequest('get', $end_point, $parameters, $content_type, $follow_redirect); + return $this->makeRequest( + 'get', + $end_point, + $parameters, + $content_type, + $follow_redirect + ); } public function getAuthURL($append_redirect_query_string = null) @@ -341,30 +362,35 @@ class APnutI if (!empty($append_redirect_query_string)) { $redirect_uri .= $append_redirect_query_string; } - $url = $this->auth_url - . '?client_id=' - . $this->client_id - . '&redirect_uri=' - . urlencode($redirect_uri) - . '&scope='.$this->needed_scope - . '&response_type=code'; + $url = + $this->auth_url . + '?client_id=' . + $this->client_id . + '&redirect_uri=' . + urlencode($redirect_uri) . + '&scope=' . + $this->needed_scope . + '&response_type=code'; $this->logger->debug('Auth URL: ' . $url); return $url; } //TODO: Ping server and validate token - public function isAuthenticated(bool $allow_server_token = false, bool $skip_verify_token = false): bool - { - $is_authenticated = ($allow_server_token && !empty($this->server_token)) - || isset($this->access_token); - $log_str = $is_authenticated - ? 'Authenticated' - : 'Not authenticated'; + public function isAuthenticated( + bool $allow_server_token = false, + bool $skip_verify_token = false + ): bool { + $is_authenticated = + ($allow_server_token && !empty($this->server_token)) || + isset($this->access_token); + $log_str = $is_authenticated ? 'Authenticated' : 'Not authenticated'; $this->logger->info( - "Checking auth status for app: {$this->app_name}: {$log_str}" + "Checking auth status for app: {$this->app_name}: {$log_str}" ); if (isset($_SERVER['HTTP_REFERER'])) { - $this->logger->info('Referrer: '.($_SERVER['HTTP_REFERER'] ?? 'Unknown')); + $this->logger->info( + 'Referrer: ' . ($_SERVER['HTTP_REFERER'] ?? 'Unknown') + ); $_SESSION[$this->token_redirect_after_auth] = $_SERVER['HTTP_REFERER']; } return $is_authenticated; @@ -378,16 +404,16 @@ class APnutI 'client_secret' => $this->client_secret, 'code' => $auth_code, 'redirect_uri' => $this->redirect_uri, - 'grant_type'=> 'authorization_code' + 'grant_type' => 'authorization_code', ]; $resp = $this->post( - '/oauth/access_token', - $parameters, - 'application/x-www-form-urlencoded' + '/oauth/access_token', + $parameters, + 'application/x-www-form-urlencoded' ); if (empty($resp['access_token'])) { - $this->logger->error("No access token ".json_encode($resp)); + $this->logger->error('No access token ' . json_encode($resp)); return false; } else { $this->logger->debug('Received access token ' . $resp['access_token']); @@ -419,7 +445,7 @@ class APnutI } */ if (mb_substr($username, 0, 1) !== '@') { - $username = '@'.$username; + $username = '@' . $username; } $params = []; if ($count > 0) { @@ -434,9 +460,9 @@ class APnutI } public function searchPosts( - array $args, - bool $order_by_id = true, - int $count = 0 + array $args, + bool $order_by_id = true, + int $count = 0 ): array { if ($order_by_id) { $args['order'] = 'id'; @@ -458,9 +484,11 @@ class APnutI foreach ($posts as $post) { $post_obj[] = new Post($post, $this); } - } while ($this->meta != null - && $this->meta->more - && (count($post_obj) < $count || $count !== 0)); + } while ( + $this->meta != null && + $this->meta->more && + (count($post_obj) < $count || $count !== 0) + ); return $post_obj; } @@ -476,7 +504,7 @@ class APnutI 'include_html' => false, 'include_mention_posts' => false, 'include_copy_mentions' => false, - 'include_post_raw' => true + 'include_post_raw' => true, ]; foreach ($params as $param => $value) { $parameters[$param] = $value; @@ -504,7 +532,7 @@ class APnutI $res = $this->get($endpoint, $args); return new Poll($res, $this); } catch (NotSupportedPollException $e) { - $this->logger->error('Poll not supported: '.json_encode($res)); + $this->logger->error('Poll not supported: ' . json_encode($res)); throw $e; } catch (HttpPnutForbiddenException $fe) { $this->logger->error('Poll token required and not provided!'); @@ -515,8 +543,10 @@ class APnutI } } - public function getPollFromToken(int $poll_id, ?string $poll_token = null): Poll - { + public function getPollFromToken( + int $poll_id, + ?string $poll_token = null + ): Poll { $poll_token_query = empty($poll_token) ? '' : '?poll_token=' . $poll_token; return $this->getPollFromEndpoint('/polls/' . $poll_id . $poll_token_query); } @@ -528,16 +558,17 @@ class APnutI } $this->logger->debug('Poll token provided'); - $re = '/((http(s)?:\/\/)?((posts)|(beta))\.pnut\.io\/(@.*\/)?)?(?(1)|^)(?\d+)/'; + $re = + '/((http(s)?:\/\/)?((posts)|(beta))\.pnut\.io\/(@.*\/)?)?(?(1)|^)(?\d+)/$'; preg_match($re, $poll_token, $matches); if (!empty($matches['postid'])) { $this->logger->debug('Poll token is post ' . $matches['postid']); - $post_id = (int)$matches['postid']; + $post_id = (int) $matches['postid']; $args = [ 'include_raw' => true, 'include_counts' => false, 'include_html' => false, - 'include_post_raw' => true + 'include_post_raw' => true, ]; return $this->getPollFromEndpoint('/posts/' . $post_id, $arg); } else { @@ -557,13 +588,16 @@ class APnutI $poll_types = Poll::$poll_types; $poll_types[] = Poll::$notice_type; $poll_types_param = implode(',', $poll_types); - $this->logger->info('No list of polls provided, using post search for poll types: '.$poll_types_param); - $endpoint = '/posts/search?raw_types='.$poll_types_param; + $this->logger->info( + 'No list of polls provided, using post search for poll types: ' . + $poll_types_param + ); + $endpoint = '/posts/search?raw_types=' . $poll_types_param; $params = [ 'include_raw' => true, 'include_counts' => false, 'include_html' => false, - 'include_post_raw' => true + 'include_post_raw' => true, ]; } try { @@ -575,7 +609,7 @@ class APnutI } return $polls; } catch (NotSupportedPollException $e) { - $this->logger->error('Poll not supported: '.json_encode($res)); + $this->logger->error('Poll not supported: ' . json_encode($res)); throw $e; } catch (HttpPnutForbiddenException $fe) { $this->logger->error('Poll token required and not provided!'); @@ -586,20 +620,23 @@ class APnutI } } - public function voteInPoll(int $poll_id, array $options, ?string $poll_token): Poll - { + public function voteInPoll( + int $poll_id, + array $options, + ?string $poll_token + ): Poll { $params = [ - 'positions' => $options + 'positions' => $options, ]; if (!empty($poll_token)) { $params['poll_token'] = $poll_token; } $this->logger->debug(json_encode($params)); $resp = $this->makeRequest( - 'PUT', - "/polls/$poll_id/response", - $params, - 'application/json' + 'PUT', + "/polls/$poll_id/response", + $params, + 'application/json' ); #TODO: Use getPollFromEndpoint return new Poll($resp, $this); @@ -613,7 +650,7 @@ class APnutI public function getUser(int $user_id, array $args = []) { - return new User($this->get('/users/'.$user_id, $args), $this); + return new User($this->get('/users/' . $user_id, $args), $this); } public function getPost(int $post_id, array $args = []) @@ -621,25 +658,25 @@ class APnutI if (!empty($this->access_token)) { #$this->logger->info("AT:".$this->access_token); } else { - $this->logger->info("No AT"); + $this->logger->info('No AT'); } // Remove in production again try { - $p = new Post($this->get('/posts/'.$post_id, $args), $this); + $p = new Post($this->get('/posts/' . $post_id, $args), $this); $this->logger->debug(json_encode($p)); return $p; } catch (NotAuthorizedException $nae) { $this->logger->warning( - 'NotAuthorizedException when getting post, trying without access token' + 'NotAuthorizedException when getting post, trying without access token' ); //try again not authorized $r = $this->makeRequest( - '/get', - '/posts/' . $post_id, - $args, - 'application/json', - true + '/get', + '/posts/' . $post_id, + $args, + 'application/json', + true ); return new Post($r, $this); } @@ -650,18 +687,25 @@ class APnutI //get returns an array with the url at idx 0 $r = null; try { - $r = $this->get('/users/'.$user_id.'/avatar', $args, 'application/json', false); + $r = $this->get( + '/users/' . $user_id . '/avatar', + $args, + 'application/json', + false + ); } catch (HttpPnutRedirectException $re) { return $re->response; } - $this->logger->error('Could not fetch avatar: No redirection! ' . json_encode($r)); + $this->logger->error( + 'Could not fetch avatar: No redirection! ' . json_encode($r) + ); throw new PnutException('Could not fetch avatar: No redirection!'); } public function getAvatarUrl( - int $user_id, - ?int $width = null, - ?int $height = null + int $user_id, + ?int $width = null, + ?int $height = null ): string { //get returns an array with the url at idx 0 $args = []; @@ -675,9 +719,9 @@ class APnutI } public function updateAvatar( - string $file_path, - ?string $filename = null, - ?string $content_type = null + string $file_path, + ?string $filename = null, + ?string $content_type = null ): User { if (empty($content_type)) { $content_type = mime_content_type($file_path); @@ -689,8 +733,8 @@ class APnutI $cf = new \CURLFile($file_path, $content_type, $filename); $parameters = ['avatar' => $cf]; return new User( - $this->post('/users/me/avatar', $parameters, 'multipart/form-data'), - $this + $this->post('/users/me/avatar', $parameters, 'multipart/form-data'), + $this ); } @@ -703,21 +747,21 @@ class APnutI } public function replyToPost( - string $text, - int $reply_to, - bool $is_nsfw = false, - bool $auto_crop = false, - array $raw = [] + string $text, + int $reply_to, + bool $is_nsfw = false, + bool $auto_crop = false, + array $raw = [] ): Post { return createPost($text, $reply_to, $is_nsfw, $auto_crop, $raw); } public function createPost( - string $text, - bool $is_nsfw = false, - bool $auto_crop = false, - ?int $reply_to = null, - array $raw = [] + string $text, + bool $is_nsfw = false, + bool $auto_crop = false, + ?int $reply_to = null, + array $raw = [] ): Post { $text = $auto_crop ? substr($text, 0, $this->getMaxPostLength()) : $text; $parameters = [ @@ -728,13 +772,16 @@ class APnutI if (!empty($raw)) { $parameters['raw'] = $parameters; } - return new Post($this->post('/posts', $parameters, 'application/json'), $this); + return new Post( + $this->post('/posts', $parameters, 'application/json'), + $this + ); } public function createPostWithParameters( - string $text, - array $params = [], - bool $auto_crop = false + string $text, + array $params = [], + bool $auto_crop = false ): Post { $text = $auto_crop ? substr($text, 0, $this->getMaxPostLength()) : $text; $parameters = [ @@ -743,20 +790,28 @@ class APnutI $parameters = array_merge($parameters, $params); $this->logger->debug('Post with params'); $this->logger->debug(json_encode($parameters)); - return new Post($this->post('/posts', $parameters, 'application/json'), $this); + return new Post( + $this->post('/posts', $parameters, 'application/json'), + $this + ); } public function getChannel(int $channel_id): Channel { # Always include channel raw, it contains the channel name $parameters = [ - 'include_channel_raw' => true + 'include_channel_raw' => true, ]; - return new Channel($this->get('/channels/'.$channel_id, $parameters), $this); + return new Channel( + $this->get('/channels/' . $channel_id, $parameters), + $this + ); } - public function getSubscribedChannels(bool $include_pms = true, bool $include_channels = true): array - { + public function getSubscribedChannels( + bool $include_pms = true, + bool $include_channels = true + ): array { # Always include channel raw, it contains the channel name $channel_types = []; if ($include_pms) { @@ -768,7 +823,7 @@ class APnutI $parameters = [ 'include_channel_raw' => true, - 'channel_types' => implode(',', $channel_types) + 'channel_types' => implode(',', $channel_types), ]; $channels = []; $resp = $this->get('/users/me/channels/subscribed', $parameters); @@ -784,19 +839,28 @@ class APnutI self::$POST_MAX_LENGTH = $config['post']['max_length']; //self::$POST_MAX_LENGTH_REPOST = $config['post']['repost_max_length']; self::$POST_MAX_LENGTH_REPOST = self::$POST_MAX_LENGTH; - self::$POST_SECONDS_BETWEEN_DUPLICATES = $config['post']['seconds_between_duplicates']; + self::$POST_SECONDS_BETWEEN_DUPLICATES = + $config['post']['seconds_between_duplicates']; self::$MESSAGE_MAX_LENGTH = $config['message']['max_length']; self::$RAW_MAX_LENGTH = $config['raw']['max_length']; - self::$USER_DESCRIPTION_MAX_LENGTH = $config['user']['description_max_length']; + self::$USER_DESCRIPTION_MAX_LENGTH = + $config['user']['description_max_length']; self::$USER_USERNAME_MAX_LENGTH = $config['user']['username_max_length']; $this->logger->info('-----------Pnut API config-----------'); $this->logger->info(''); - $this->logger->info("Max post length: ".self::$POST_MAX_LENGTH); - $this->logger->info("Max repost length: ".self::$POST_MAX_LENGTH_REPOST); - $this->logger->info("Seconds between post duplicates: ".self::$POST_SECONDS_BETWEEN_DUPLICATES); - $this->logger->info("Max raw length: ".self::$RAW_MAX_LENGTH); - $this->logger->info("Max user description length: ".self::$USER_DESCRIPTION_MAX_LENGTH); - $this->logger->info("Max username length: ".self::$USER_USERNAME_MAX_LENGTH); + $this->logger->info('Max post length: ' . self::$POST_MAX_LENGTH); + $this->logger->info('Max repost length: ' . self::$POST_MAX_LENGTH_REPOST); + $this->logger->info( + 'Seconds between post duplicates: ' . + self::$POST_SECONDS_BETWEEN_DUPLICATES + ); + $this->logger->info('Max raw length: ' . self::$RAW_MAX_LENGTH); + $this->logger->info( + 'Max user description length: ' . self::$USER_DESCRIPTION_MAX_LENGTH + ); + $this->logger->info( + 'Max username length: ' . self::$USER_USERNAME_MAX_LENGTH + ); $this->logger->info('--------------------------------------'); } @@ -812,7 +876,7 @@ class APnutI { $token = $this->getServerToken(); $this->server_token = $token; - $this->logger->info("ST:".$this->server_token); + $this->logger->info('ST:' . $this->server_token); } protected function getServerToken(): string @@ -821,14 +885,16 @@ class APnutI $params = [ 'client_id' => $this->client_id, 'client_secret' => $this->client_secret, - 'grant_type' => 'client_credentials' + 'grant_type' => 'client_credentials', ]; $resp = $this->post('/oauth/access_token', $params); if (!empty($resp['access_token'])) { $this->logger->info(json_encode($resp)); return $resp['access_token']; } else { - throw new PnutException("Error retrieving app access token: ".json_encode($resp)); + throw new PnutException( + 'Error retrieving app access token: ' . json_encode($resp) + ); } }