From 4d8449d9710073ad2610be6c57a593dc3f204493 Mon Sep 17 00:00:00 2001 From: call-me-matt Date: Sun, 1 Jan 2023 15:18:40 +0100 Subject: [PATCH] use Mastodon API instead of scraping fixes #3151 Signed-off-by: call-me-matt --- lib/Service/Social/MastodonProvider.php | 54 +++++++++++-------- .../Service/Social/MastodonProviderTest.php | 39 +++++++++----- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/lib/Service/Social/MastodonProvider.php b/lib/Service/Social/MastodonProvider.php index ed8ff1a1..b43bd17f 100644 --- a/lib/Service/Social/MastodonProvider.php +++ b/lib/Service/Social/MastodonProvider.php @@ -31,7 +31,7 @@ class MastodonProvider implements ISocialProvider { private $httpClient; /** @var string */ - public $name = "mastodon"; + public $name = 'mastodon'; public function __construct(IClientService $httpClient) { $this->httpClient = $httpClient->NewClient(); @@ -45,7 +45,7 @@ class MastodonProvider implements ISocialProvider { * @return bool */ public function supportsContact(array $contact):bool { - if (!array_key_exists("X-SOCIALPROFILE",$contact)) { + if (!array_key_exists('X-SOCIALPROFILE', $contact)) { return false; } $profiles = $this->getProfileIds($contact); @@ -62,7 +62,6 @@ class MastodonProvider implements ISocialProvider { public function getImageUrls(array $contact):array { $profileIds = $this->getProfileIds($contact); $urls = []; - foreach ($profileIds as $profileId) { $url = $this->getImageUrl($profileId); if (isset($url)) { @@ -82,21 +81,15 @@ class MastodonProvider implements ISocialProvider { public function getImageUrl(string $profileUrl):?string { try { $result = $this->httpClient->get($profileUrl); - - $htmlResult = new \DOMDocument(); - $htmlResult->loadHTML($result->getBody()); - $img = $htmlResult->getElementById('profile_page_avatar'); - if (!is_null($img)) { - return $img->getAttribute("data-original"); - } - return null; + $jsonResult = json_decode($result->getBody()); + return $jsonResult->avatar; } catch (\Exception $e) { return null; } } /** - * Returns all possible profile ids for contact + * Returns all possible profile URI for contact by searching the mastodon instance * * @param {array} contact information * @@ -108,9 +101,21 @@ class MastodonProvider implements ISocialProvider { if (isset($socialprofiles)) { foreach ($socialprofiles as $profile) { if (strtolower($profile['type']) == $this->name) { - $profileId = $this->cleanupId($profile['value']); - if (isset($profileId)) { - $profileIds[] = $profileId; + $masto_user_server = $this->cleanupId($profile['value']); + if (isset($masto_user_server)) { + try { + [$masto_user, $masto_server] = $masto_user_server; + # search for user on Mastodon + $search = $masto_server . '/api/v2/search?q=' . $masto_user; + $result = $this->httpClient->get($search); + $jsonResult = json_decode($result->getBody()); + # take first search result + $masto_id = $jsonResult->accounts[0]->id; + $profileId = $masto_server . "/api/v1/accounts/" . $masto_id; + $profileIds[] = $profileId; + } catch (\Exception $e) { + continue; + } } } } @@ -123,18 +128,25 @@ class MastodonProvider implements ISocialProvider { * * @param {string} the value from the contact's x-socialprofile * - * @return string + * @return array username and server instance */ - protected function cleanupId(string $candidate):?string { + protected function cleanupId(string $candidate):?array { $candidate = preg_replace('/^' . preg_quote('x-apple:', '/') . '/', '', $candidate); try { + $user_server = explode('@', $candidate); if (strpos($candidate, 'http') !== 0) { - $user_server = explode('@', $candidate); - $candidate = 'https://' . array_pop($user_server) . '/@' . array_pop($user_server); + $masto_server = "https://" . array_pop($user_server); + $masto_user = array_pop($user_server); + } else { + $masto_user = array_pop($user_server); + $masto_server = array_pop($user_server); } + if ((empty($masto_server)) || (empty($masto_user))) { + return null; + } + return [$masto_user, $masto_server]; } catch (\Exception $e) { - $candidate = null; + return null; } - return $candidate; } } diff --git a/tests/unit/Service/Social/MastodonProviderTest.php b/tests/unit/Service/Social/MastodonProviderTest.php index 9611bf6b..db1d6fc3 100644 --- a/tests/unit/Service/Social/MastodonProviderTest.php +++ b/tests/unit/Service/Social/MastodonProviderTest.php @@ -90,20 +90,33 @@ class MastodonProviderTest extends TestCase { $contactWithSocial = [ 'X-SOCIALPROFILE' => [ ["value" => "user1@cloud1", "type" => "mastodon"], - ["value" => "user2@cloud2", "type" => "mastodon"] + ["value" => "@user2@cloud2", "type" => "mastodon"], + ["value" => "https://cloud3/@user3", "type" => "mastodon"], + ["value" => "https://cloud/wrongSyntax", "type" => "mastodon"], + ["value" => "@wrongSyntax", "type" => "mastodon"], + ["value" => "wrongSyntax", "type" => "mastodon"] ] ]; $contactWithSocialUrls = [ - "https://cloud1/@user1", - "https://cloud2/@user2", + "https://cloud1/api/v2/search?q=user1", + "https://cloud2/api/v2/search?q=user2", + "https://cloud3//api/v2/search?q=user3", + "https://cloud1/api/v1/accounts/1", + "https://cloud2/api/v1/accounts/2", + "https://cloud3//api/v1/accounts/3" ]; - $contactWithSocialHtml = [ - '', - '' + $contactWithSocialApi = [ + '{"accounts":[{"id":"1","username":"user1"}]}', + '{"accounts":[{"id":"2","username":"user2"}]}', + '{"accounts":[{"id":"3","username":"user3"}]}', + '{"id":"1","avatar":"user1.jpg"}', + '{"id":"2","avatar":"user2.jpg"}', + '{"id":"3","avatar":"user3.jpg"}' ]; $contactWithSocialImgs = [ "user1.jpg", - "user2.jpg" + "user2.jpg", + "user3.jpg" ]; $contactWithoutSocial = [ @@ -113,19 +126,19 @@ class MastodonProviderTest extends TestCase { ] ]; $contactWithoutSocialUrls = []; - $contactWithoutSocialHtml = []; + $contactWithoutSocialApi = []; $contactWithoutSocialImgs = []; return [ 'contact with mastodon fields' => [ $contactWithSocial, - $contactWithSocialHtml, + $contactWithSocialApi, $contactWithSocialUrls, $contactWithSocialImgs ], 'contact without mastodon fields' => [ $contactWithoutSocial, - $contactWithoutSocialHtml, + $contactWithoutSocialApi, $contactWithoutSocialUrls, $contactWithoutSocialImgs ] @@ -135,9 +148,9 @@ class MastodonProviderTest extends TestCase { /** * @dataProvider dataProviderGetImageUrls */ - public function testGetImageUrls($contact, $htmls, $urls, $imgs) { + public function testGetImageUrls($contact, $api, $urls, $imgs) { if (count($urls)) { - $this->response->method("getBody")->willReturnOnConsecutiveCalls(...$htmls); + $this->response->method("getBody")->willReturnOnConsecutiveCalls(...$api); $this->client ->expects($this->exactly(count($urls))) ->method("get") @@ -146,8 +159,6 @@ class MastodonProviderTest extends TestCase { }, $urls)) ->willReturn($this->response); } - - $result = $this->provider->getImageUrls($contact); $this->assertEquals($imgs, $result); }