use Mastodon API instead of scraping

fixes #3151

Signed-off-by: call-me-matt <nextcloud@matthiasheinisch.de>
This commit is contained in:
call-me-matt 2023-01-01 15:18:40 +01:00
parent 753a66b309
commit 4d8449d971
2 changed files with 58 additions and 35 deletions

View File

@ -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;
}
}

View File

@ -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 = [
'<html><profile id="profile_page_avatar" data-original="user1.jpg" /></html>',
'<html><profile id="profile_page_avatar" data-original="user2.jpg" /></html>'
$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);
}