isWeb() || !in_array($url->getHost(), self::TWITTER_DOMAINS)) return false; return preg_match('#^/@?(?:[A-Za-z0-9_]{1,20})/status(?:es)?/([0-9]+)/?$#', $url->getPath()) || preg_match('#^/@?([A-Za-z0-9_]{1,20})/?$#', $url->getPath()); } private function lookupUser(string $userName): ?object { $curl = curl_init("https://api.twitter.com/2/users/by?usernames={$userName}&user.fields=description,entities,id,name,profile_image_url,protected,url,username,verified"); curl_setopt_array($curl, [ CURLOPT_AUTOREFERER => false, CURLOPT_CERTINFO => false, CURLOPT_FAILONERROR => false, CURLOPT_FOLLOWLOCATION => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_TCP_FASTOPEN => true, CURLOPT_CONNECTTIMEOUT => 2, CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_TIMEOUT => 5, CURLOPT_USERAGENT => 'Uiharu/' . UIH_VERSION, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . Config::get('Twitter', 'apiToken'), 'Accept: application/json', ], ]); $resp = curl_exec($curl); curl_close($curl); return json_decode($resp); } private function lookupTweet(string $tweetId): ?object { $curl = curl_init("https://api.twitter.com/2/tweets?ids={$tweetId}&expansions=attachments.media_keys,author_id,entities.mentions.username,referenced_tweets.id,referenced_tweets.id.author_id&media.fields=height,width,media_key,preview_image_url,url,type&tweet.fields=attachments,conversation_id,text,source,possibly_sensitive,created_at&user.fields=id,name,profile_image_url,protected,username,verified"); curl_setopt_array($curl, [ CURLOPT_AUTOREFERER => false, CURLOPT_CERTINFO => false, CURLOPT_FAILONERROR => false, CURLOPT_FOLLOWLOCATION => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_TCP_FASTOPEN => true, CURLOPT_CONNECTTIMEOUT => 2, CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_TIMEOUT => 5, CURLOPT_USERAGENT => 'Uiharu/' . UIH_VERSION, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . Config::get('Twitter', 'apiToken'), 'Accept: application/json', ], ]); $resp = curl_exec($curl); curl_close($curl); return json_decode($resp); } public function lookup(Url $url): TwitterLookupResult { if(preg_match('#^/@?(?:[A-Za-z0-9_]{1,20})/status(?:es)?/([0-9]+)/?$#', $url->getPath(), $matches)) { $tweetId = strval($matches[1] ?? '0'); $tweetInfo = $this->lookupTweet($tweetId); if($tweetInfo === null) throw new RuntimeException('Tweet lookup failed.'); return new TwitterLookupTweetResult($url, $tweetInfo); } if(preg_match('#^/@?([A-Za-z0-9_]{1,20})/?$#', $url->getPath(), $matches)) { $userName = strval($matches[1] ?? ''); $userInfo = $this->lookupUser($userName); if($userInfo === null) throw new RuntimeException('Twitter user lookup failed.'); return new TwitterLookupUserResult($url, $userInfo); } throw new RuntimeException('Unknown Twitter URL format.'); } }