]> git.mxchange.org Git - friendica-addons.git/commitdiff
Bluesky: Support personal data servers
authorMichael <heluecht@pirati.ca>
Sun, 19 Nov 2023 18:55:05 +0000 (18:55 +0000)
committerMichael <heluecht@pirati.ca>
Sun, 19 Nov 2023 18:55:05 +0000 (18:55 +0000)
bluesky/bluesky.php
bluesky/lang/C/messages.po
bluesky/templates/connector_settings.tpl

index 82501594f90ed4eefe96a6a092e8c914d8028b36..3b2ce7e09a465441954da53a5cc6066499955b38 100644 (file)
@@ -36,6 +36,7 @@ use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\Contact;
+use Friendica\Model\GServer;
 use Friendica\Model\Item;
 use Friendica\Model\ItemURI;
 use Friendica\Model\Photo;
@@ -49,9 +50,15 @@ use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Strings;
 
 const BLUESKY_DEFAULT_POLL_INTERVAL = 10; // given in minutes
-const BLUESKY_HOST = 'https://bsky.app'; // Hard wired until Bluesky will run on multiple systems
 const BLUESKY_IMAGE_SIZE = [1000000, 500000, 100000, 50000];
 
+/*
+ * (Currently) hard wired paths for Bluesky services
+ */
+const BLUESKY_DIRECTORY = 'https://plc.directory'; // Path to the directory server service to fetch the PDS of a given DID
+const BLUESKY_PDS       = 'https://bsky.social';   // Path to the personal data server service (PDS) to fetch the DID for a given handle
+const BLUESKY_WEB       = 'https://bsky.app';      // Path to the web interface with the user profile and posts
+
 function bluesky_install()
 {
        Hook::register('load_config',             __FILE__, 'bluesky_load_config');
@@ -106,8 +113,8 @@ function bluesky_probe_detect(array &$hookData)
 
        if (parse_url($hookData['uri'], PHP_URL_SCHEME) == 'did') {
                $did = $hookData['uri'];
-       } elseif (preg_match('#^' . BLUESKY_HOST . '/profile/(.+)#', $hookData['uri'], $matches)) {
-               $did = bluesky_get_did($pconfig['uid'], $matches[1]);
+       } elseif (preg_match('#^' . BLUESKY_WEB . '/profile/(.+)#', $hookData['uri'], $matches)) {
+               $did = bluesky_get_did($matches[1]);
                if (empty($did)) {
                        return;
                }
@@ -127,6 +134,8 @@ function bluesky_probe_detect(array &$hookData)
 
        $hookData['result'] = bluesky_get_contact_fields($data, 0, false);
 
+       $hookData['result']['baseurl'] = bluesky_get_pds($did);
+
        // Preparing probe data. This differs slightly from the contact array
        $hookData['result']['about']    = HTML::toBBCode($data->description ?? '');
        $hookData['result']['photo']    = $data->avatar ?? '';
@@ -152,11 +161,11 @@ function bluesky_item_by_link(array &$hookData)
                return;
        }
 
-       if (!preg_match('#^' . BLUESKY_HOST . '/profile/(.+)/post/(.+)#', $hookData['uri'], $matches)) {
+       if (!preg_match('#^' . BLUESKY_WEB . '/profile/(.+)/post/(.+)#', $hookData['uri'], $matches)) {
                return;
        }
 
-       $did = bluesky_get_did($hookData['uid'], $matches[1]);
+       $did = bluesky_get_did($matches[1]);
        if (empty($did)) {
                return;
        }
@@ -306,7 +315,7 @@ function bluesky_settings(array &$data)
 
        $enabled      = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post') ?? false;
        $def_enabled  = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default') ?? false;
-       $host         = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'host') ?: 'https://bsky.social';
+       $pds          = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
        $handle       = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle');
        $did          = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
        $token        = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'access_token');
@@ -321,7 +330,7 @@ function bluesky_settings(array &$data)
                '$bydefault'    => ['bluesky_bydefault', DI::l10n()->t('Post to Bluesky by default'), $def_enabled],
                '$import'       => ['bluesky_import', DI::l10n()->t('Import the remote timeline'), $import],
                '$import_feeds' => ['bluesky_import_feeds', DI::l10n()->t('Import the pinned feeds'), $import_feeds, DI::l10n()->t('When activated, Posts will be imported from all the feeds that you pinned in Bluesky.')],
-               '$host'         => ['bluesky_host', DI::l10n()->t('Bluesky host'), $host, '', '', 'readonly'],
+               '$pds'          => ['bluesky_pds', DI::l10n()->t('Personal Data Server'), $pds, DI::l10n()->t('The personal data server (PDS) is the system that hosts your profile.'), '', 'readonly'],
                '$handle'       => ['bluesky_handle', DI::l10n()->t('Bluesky handle'), $handle],
                '$did'          => ['bluesky_did', DI::l10n()->t('Bluesky DID'), $did, DI::l10n()->t('This is the unique identifier. It will be fetched automatically, when the handle is entered.'), '', 'readonly'],
                '$password'     => ['bluesky_password', DI::l10n()->t('Bluesky app password'), '', DI::l10n()->t("Please don't add your real password here, but instead create a specific app password in the Bluesky settings.")],
@@ -342,27 +351,29 @@ function bluesky_settings_post(array &$b)
        if (empty($_POST['bluesky-submit'])) {
                return;
        }
-
-       $old_host   = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'host');
+       
+       $old_pds    = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
        $old_handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle');
        $old_did    = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
 
-       $host   = $_POST['bluesky_host'];
        $handle = $_POST['bluesky_handle'];
 
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'post',            intval($_POST['bluesky']));
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default', intval($_POST['bluesky_bydefault']));
-       DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'host',            $host);
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'handle',          $handle);
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import',          intval($_POST['bluesky_import']));
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds',    intval($_POST['bluesky_import_feeds']));
 
        if (!empty($host) && !empty($handle)) {
-               if (empty($old_did) || $old_host != $host || $old_handle != $handle) {
-                       DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'did', bluesky_get_did(DI::userSession()->getLocalUserId(), DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle')));
+               if (empty($old_did) || $old_handle != $handle) {
+                       DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'did', bluesky_get_did(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle')));
+               }
+               if (empty($old_pds) || $old_handle != $handle) {
+                       DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'pds', bluesky_get_pds(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'did')));
                }
        } else {
                DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
+               DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
        }
 
        if (!empty($_POST['bluesky_password'])) {
@@ -1452,10 +1463,9 @@ function bluesky_get_contact_fields(stdClass $author, int $uid, bool $update): a
                'blocked'  => false,
                'readonly' => false,
                'pending'  => false,
-               'baseurl'  => BLUESKY_HOST,
                'url'      => $author->did,
                'nurl'     => $author->did,
-               'alias'    => BLUESKY_HOST . '/profile/' . $author->handle,
+               'alias'    => BLUESKY_WEB . '/profile/' . $author->handle,
                'name'     => $author->displayName ?? $author->handle,
                'nick'     => $author->handle,
                'addr'     => $author->handle,
@@ -1466,6 +1476,12 @@ function bluesky_get_contact_fields(stdClass $author, int $uid, bool $update): a
                return $fields;
        }
 
+       $fields['baseurl'] = bluesky_get_pds($author->did);
+       if (!empty($fields['baseurl'])) {
+               GServer::check($fields['baseurl'], Protocol::BLUESKY);
+               $fields['gsid'] = GServer::getID($fields['baseurl'], true);
+       }
+
        $data = bluesky_xrpc_get($uid, 'app.bsky.actor.getProfile', ['actor' => $author->did]);
        if (empty($data)) {
                Logger::debug('Error fetching contact fields', ['uid' => $uid, 'url' => $fields['url']]);
@@ -1524,9 +1540,9 @@ function bluesky_get_preferences(int $uid): stdClass
        return $data;
 }
 
-function bluesky_get_did(int $uid, string $handle): string
+function bluesky_get_did(string $handle): string
 {
-       $data = bluesky_get($uid, '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
+       $data = bluesky_get(BLUESKY_PDS . '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
        if (empty($data)) {
                return '';
        }
@@ -1534,6 +1550,33 @@ function bluesky_get_did(int $uid, string $handle): string
        return $data->did;
 }
 
+function bluesky_get_user_pds(int $uid): string
+{
+       $pds = DI::pConfig()->get($uid, 'bluesky', 'pds');
+       if (!empty($pds)) {
+               return $pds;
+       }
+       $pds = bluesky_get_pds(DI::pConfig()->get($uid, 'bluesky', 'did'));
+       DI::pConfig()->set($uid, 'bluesky', 'pds', $pds);
+       return $pds;
+}
+
+function bluesky_get_pds(string $did): ?string
+{
+       $data = bluesky_get(BLUESKY_DIRECTORY . '/' . $did);
+       if (empty($data) || empty($data->service)) {
+               return null;
+       }
+
+       foreach ($data->service as $service) {
+               if (($service->id == '#atproto_pds') && ($service->type == 'AtprotoPersonalDataServer') && !empty($service->serviceEndpoint)) {
+                       return $service->serviceEndpoint;
+               }
+       }
+
+       return null;
+}
+
 function bluesky_get_token(int $uid): string
 {
        $token   = DI::pConfig()->get($uid, 'bluesky', 'access_token');
@@ -1588,7 +1631,7 @@ function bluesky_xrpc_post(int $uid, string $url, $parameters): ?stdClass
 function bluesky_post(int $uid, string $url, string $params, array $headers): ?stdClass
 {
        try {
-               $curlResult = DI::httpClient()->post(DI::pConfig()->get($uid, 'bluesky', 'host') . $url, $params, $headers);
+               $curlResult = DI::httpClient()->post(bluesky_get_user_pds($uid) . $url, $params, $headers);
        } catch (\Exception $e) {
                Logger::notice('Exception on post', ['exception' => $e]);
                return null;
@@ -1608,13 +1651,13 @@ function bluesky_xrpc_get(int $uid, string $url, array $parameters = []): ?stdCl
                $url .= '?' . http_build_query($parameters);
        }
 
-       return bluesky_get($uid, '/xrpc/' . $url, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]);
+       return bluesky_get(bluesky_get_user_pds($uid) . '/xrpc/' . $url, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]);
 }
 
-function bluesky_get(int $uid, string $url, string $accept_content = HttpClientAccept::DEFAULT, array $opts = []): ?stdClass
+function bluesky_get(string $url, string $accept_content = HttpClientAccept::DEFAULT, array $opts = []): ?stdClass
 {
        try {
-               $curlResult = DI::httpClient()->get(DI::pConfig()->get($uid, 'bluesky', 'host') . $url, $accept_content, $opts);
+               $curlResult = DI::httpClient()->get($url, $accept_content, $opts);
        } catch (\Exception $e) {
                Logger::notice('Exception on get', ['exception' => $e]);
                return null;
index 139081f78df79b35bc847191a82813383785b0cd..a5349142ea3f7d1f5e8cd4e7dc0d2fbce6c9818c 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-06-05 04:34+0000\n"
+"POT-Creation-Date: 2023-11-19 18:51+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,70 +17,74 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: bluesky.php:314
+#: bluesky.php:325
 msgid ""
 "You are authenticated to Bluesky. For security reasons the password isn't "
 "stored."
 msgstr ""
 
-#: bluesky.php:314
+#: bluesky.php:325
 msgid "You are not authenticated. Please enter the app password."
 msgstr ""
 
-#: bluesky.php:318
+#: bluesky.php:329
 msgid "Enable Bluesky Post Addon"
 msgstr ""
 
-#: bluesky.php:319
+#: bluesky.php:330
 msgid "Post to Bluesky by default"
 msgstr ""
 
-#: bluesky.php:320
+#: bluesky.php:331
 msgid "Import the remote timeline"
 msgstr ""
 
-#: bluesky.php:321
+#: bluesky.php:332
 msgid "Import the pinned feeds"
 msgstr ""
 
-#: bluesky.php:321
+#: bluesky.php:332
 msgid ""
 "When activated, Posts will be imported from all the feeds that you pinned in "
 "Bluesky."
 msgstr ""
 
-#: bluesky.php:322
-msgid "Bluesky host"
+#: bluesky.php:333
+msgid "Personal Data Server"
+msgstr ""
+
+#: bluesky.php:333
+msgid "The personal data server (PDS) is the system that hosts your profile."
 msgstr ""
 
-#: bluesky.php:323
+#: bluesky.php:334
 msgid "Bluesky handle"
 msgstr ""
 
-#: bluesky.php:324
+#: bluesky.php:335
 msgid "Bluesky DID"
 msgstr ""
 
-#: bluesky.php:324
+#: bluesky.php:335
 msgid ""
 "This is the unique identifier. It will be fetched automatically, when the "
 "handle is entered."
 msgstr ""
 
-#: bluesky.php:325
+#: bluesky.php:336
 msgid "Bluesky app password"
 msgstr ""
 
-#: bluesky.php:325
+#: bluesky.php:336
 msgid ""
 "Please don't add your real password here, but instead create a specific app "
 "password in the Bluesky settings."
 msgstr ""
 
-#: bluesky.php:331
+#: bluesky.php:342
 msgid "Bluesky Import/Export"
 msgstr ""
 
-#: bluesky.php:382
+#: bluesky.php:395
 msgid "Post to Bluesky"
 msgstr ""
index c4ffedff0590e0024e9e07464aa1e4a7da88e406..db5ac5d5b2b97dfbe50f2080b4b5f2d221a02f13 100644 (file)
@@ -3,7 +3,7 @@
 {{include file="field_checkbox.tpl" field=$bydefault}}
 {{include file="field_checkbox.tpl" field=$import}}
 {{include file="field_checkbox.tpl" field=$import_feeds}}
-{{include file="field_input.tpl" field=$host}}
+{{include file="field_input.tpl" field=$pds}}
 {{include file="field_input.tpl" field=$handle}}
 {{include file="field_input.tpl" field=$did}}
 {{include file="field_input.tpl" field=$password}}
\ No newline at end of file