forked from friendica/friendica-addons
Compare commits
33 commits
5f5c53ab49
...
3b518462ab
Author | SHA1 | Date | |
---|---|---|---|
3b518462ab | |||
d53ad98af2 | |||
372e75a91c | |||
f45f6ba992 | |||
26983977c4 | |||
90d897f4fa | |||
668ea972cc | |||
dc2d00b6c6 | |||
0f65c23490 | |||
c98caaf417 | |||
f46980c736 | |||
ebf5ff1276 | |||
9d932e6fa0 | |||
46fdcc1c0e | |||
eadbcc069f | |||
2c2a813324 | |||
9315b185e8 | |||
d685663ac0 | |||
727eca1ce7 | |||
3b5e8901dc | |||
50d8d44489 | |||
b6d575c37f | |||
397282cbb3 | |||
2c6add7aa1 | |||
22bf23b833 | |||
ed8c5945da | |||
14fd900628 | |||
48cde643f6 | |||
e62f6a9586 | |||
c2dfda5d72 | |||
9595760800 | |||
1c91ee200e | |||
00e30b5c2b |
8 changed files with 355 additions and 73 deletions
|
@ -36,10 +36,12 @@ 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;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Object\Image;
|
||||
|
@ -49,9 +51,23 @@ 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];
|
||||
|
||||
const BLUEKSY_STATUS_UNKNOWN = 0;
|
||||
const BLUEKSY_STATUS_TOKEN_OK = 1;
|
||||
const BLUEKSY_STATUS_SUCCESS = 2;
|
||||
const BLUEKSY_STATUS_API_FAIL = 10;
|
||||
const BLUEKSY_STATUS_DID_FAIL = 11;
|
||||
const BLUEKSY_STATUS_PDS_FAIL = 12;
|
||||
const BLUEKSY_STATUS_TOKEN_FAIL = 13;
|
||||
|
||||
/*
|
||||
* (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 +122,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 +143,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 +170,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;
|
||||
}
|
||||
|
@ -165,7 +183,7 @@ function bluesky_item_by_link(array &$hookData)
|
|||
|
||||
$uri = 'at://' . $did . '/app.bsky.feed.post/' . $matches[2];
|
||||
|
||||
$uri = bluesky_fetch_missing_post($uri, $hookData['uid'], 0, 0);
|
||||
$uri = bluesky_fetch_missing_post($uri, $hookData['uid'], $hookData['uid'], 0, 0);
|
||||
Logger::debug('Got post', ['profile' => $matches[1], 'cid' => $matches[2], 'result' => $uri]);
|
||||
if (!empty($uri)) {
|
||||
$item = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $hookData['uid']]);
|
||||
|
@ -306,26 +324,24 @@ 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');
|
||||
$import = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import') ?? false;
|
||||
$import_feeds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds') ?? false;
|
||||
|
||||
$status = $token ? DI::l10n()->t("You are authenticated to Bluesky. For security reasons the password isn't stored.") : DI::l10n()->t('You are not authenticated. Please enter the app password.');
|
||||
|
||||
$t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/bluesky/');
|
||||
$html = Renderer::replaceMacros($t, [
|
||||
'$enable' => ['bluesky', DI::l10n()->t('Enable Bluesky Post Addon'), $enabled],
|
||||
'$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.")],
|
||||
'$status' => $status
|
||||
'$status' => bluesky_get_status($handle, $did, $pds, $token),
|
||||
]);
|
||||
|
||||
$data = [
|
||||
|
@ -337,35 +353,92 @@ function bluesky_settings(array &$data)
|
|||
];
|
||||
}
|
||||
|
||||
function bluesky_get_status(string $handle = null, string $did = null, string $pds = null, string $token = null): string
|
||||
{
|
||||
if (empty($handle)) {
|
||||
return DI::l10n()->t('You are not authenticated. Please enter your handle and the app password.');
|
||||
}
|
||||
|
||||
$status = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'status') ?? BLUEKSY_STATUS_UNKNOWN;
|
||||
|
||||
// Fallback mechanism for connection that had been established before the introduction of the status
|
||||
if ($status == BLUEKSY_STATUS_UNKNOWN) {
|
||||
if (empty($did)) {
|
||||
$status = BLUEKSY_STATUS_DID_FAIL;
|
||||
} elseif (empty($pds)) {
|
||||
$status = BLUEKSY_STATUS_PDS_FAIL;
|
||||
} elseif (!empty($token)) {
|
||||
$status = BLUEKSY_STATUS_TOKEN_OK;
|
||||
} else {
|
||||
$status = BLUEKSY_STATUS_TOKEN_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($status) {
|
||||
case BLUEKSY_STATUS_TOKEN_OK:
|
||||
return DI::l10n()->t("You are authenticated to Bluesky. For security reasons the password isn't stored.");
|
||||
case BLUEKSY_STATUS_SUCCESS:
|
||||
return DI::l10n()->t('The communication with the personal data server service (PDS) is established.');
|
||||
case BLUEKSY_STATUS_API_FAIL;
|
||||
return DI::l10n()->t('Communication issues with the personal data server service (PDS).');
|
||||
case BLUEKSY_STATUS_DID_FAIL:
|
||||
return DI::l10n()->t('The DID for the provided handle could not be detected. Please check if you entered the correct handle.');
|
||||
case BLUEKSY_STATUS_PDS_FAIL:
|
||||
return DI::l10n()->t('The personal data server service (PDS) could not be detected.');
|
||||
case BLUEKSY_STATUS_TOKEN_FAIL:
|
||||
return DI::l10n()->t('The authentication with the provided handle and password failed. Please check if you entered the correct password.');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
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'];
|
||||
$handle = trim($_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($handle)) {
|
||||
if (empty($old_did) || $old_handle != $handle) {
|
||||
$did = bluesky_get_did(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle'));
|
||||
if (empty($did)) {
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'status', BLUEKSY_STATUS_DID_FAIL);
|
||||
}
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'did', $did);
|
||||
} else {
|
||||
$did = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
|
||||
}
|
||||
if (!empty($did) && (empty($old_pds) || $old_handle != $handle)) {
|
||||
$pds = bluesky_get_pds($did);
|
||||
if (empty($pds)) {
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'status', BLUEKSY_STATUS_PDS_FAIL);
|
||||
}
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'pds', $pds);
|
||||
} else {
|
||||
$pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
|
||||
}
|
||||
} else {
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'did');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'access_token');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'refresh_token');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'token_created');
|
||||
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'status');
|
||||
}
|
||||
|
||||
if (!empty($_POST['bluesky_password'])) {
|
||||
if (!empty($did) && !empty($pds) && !empty($_POST['bluesky_password'])) {
|
||||
bluesky_create_token(DI::userSession()->getLocalUserId(), $_POST['bluesky_password']);
|
||||
}
|
||||
}
|
||||
|
@ -682,11 +755,20 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
|
|||
function bluesky_get_urls(string $body): array
|
||||
{
|
||||
// Remove all hashtag and mention links
|
||||
$body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $body);
|
||||
$body = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $body);
|
||||
|
||||
$body = BBCode::expandVideoLinks($body);
|
||||
$urls = [];
|
||||
|
||||
// Search for hash tags
|
||||
if (preg_match_all("/#\[url\=(https?:.*?)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$text = '#' . $match[2];
|
||||
$urls[] = ['tag' => $match[2], 'text' => $text, 'hash' => $text];
|
||||
$body = str_replace($match[0], $text, $body);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for pure links
|
||||
if (preg_match_all("/\[url\](https?:.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
|
@ -752,9 +834,17 @@ function bluesky_get_facets(string $body, array $urls): array
|
|||
$facet->index->byteStart = $pos;
|
||||
|
||||
$feature = new stdClass;
|
||||
$feature->uri = $url['url'];
|
||||
|
||||
$type = '$type';
|
||||
$feature->$type = 'app.bsky.richtext.facet#link';
|
||||
if (!empty($url['tag'])) {
|
||||
$feature->tag = $url['tag'];
|
||||
$feature->$type = 'app.bsky.richtext.facet#tag';
|
||||
} elseif (!empty($url['url'])) {
|
||||
$feature->uri = $url['url'];
|
||||
$feature->$type = 'app.bsky.richtext.facet#link';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$facet->features = [$feature];
|
||||
$facets[] = $facet;
|
||||
|
@ -922,7 +1012,7 @@ function bluesky_fetch_notifications(int $uid)
|
|||
$item['gravity'] = Item::GRAVITY_ACTIVITY;
|
||||
$item['body'] = $item['verb'] = Activity::LIKE;
|
||||
$item['thr-parent'] = bluesky_get_uri($notification->record->subject);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], 0);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $uid, $item['contact-id'], 0);
|
||||
if (!empty($item['thr-parent'])) {
|
||||
$data = Item::insert($item);
|
||||
Logger::debug('Got like', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
|
||||
|
@ -936,7 +1026,7 @@ function bluesky_fetch_notifications(int $uid)
|
|||
$item['gravity'] = Item::GRAVITY_ACTIVITY;
|
||||
$item['body'] = $item['verb'] = Activity::ANNOUNCE;
|
||||
$item['thr-parent'] = bluesky_get_uri($notification->record->subject);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], 0);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $uid, $item['contact-id'], 0);
|
||||
if (!empty($item['thr-parent'])) {
|
||||
$data = Item::insert($item);
|
||||
Logger::debug('Got repost', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
|
||||
|
@ -1031,7 +1121,7 @@ function bluesky_process_post(stdClass $post, int $uid, int $post_reason, $level
|
|||
Logger::debug('Importing post', ['uid' => $uid, 'indexedAt' => $post->indexedAt, 'uri' => $post->uri, 'cid' => $post->cid, 'root' => $post->record->reply->root ?? '']);
|
||||
|
||||
$item = bluesky_get_header($post, $uri, $uid, $uid);
|
||||
$item = bluesky_get_content($item, $post->record, $uri, $uid, $level);
|
||||
$item = bluesky_get_content($item, $post->record, $uri, $uid, $uid, $level);
|
||||
if (empty($item)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1082,7 +1172,7 @@ function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_ui
|
|||
return $item;
|
||||
}
|
||||
|
||||
function bluesky_get_content(array $item, stdClass $record, string $uri, int $uid, int $level): array
|
||||
function bluesky_get_content(array $item, stdClass $record, string $uri, int $uid, int $fetch_uid, int $level): array
|
||||
{
|
||||
if (empty($item)) {
|
||||
return [];
|
||||
|
@ -1091,7 +1181,7 @@ function bluesky_get_content(array $item, stdClass $record, string $uri, int $ui
|
|||
if (!empty($record->reply)) {
|
||||
$item['parent-uri'] = bluesky_get_uri($record->reply->root);
|
||||
if ($item['parent-uri'] != $uri) {
|
||||
$item['parent-uri'] = bluesky_fetch_missing_post($item['parent-uri'], $uid, $item['contact-id'], $level);
|
||||
$item['parent-uri'] = bluesky_fetch_missing_post($item['parent-uri'], $uid, $fetch_uid, $item['contact-id'], $level);
|
||||
if (empty($item['parent-uri'])) {
|
||||
return [];
|
||||
}
|
||||
|
@ -1099,20 +1189,20 @@ function bluesky_get_content(array $item, stdClass $record, string $uri, int $ui
|
|||
|
||||
$item['thr-parent'] = bluesky_get_uri($record->reply->parent);
|
||||
if (!in_array($item['thr-parent'], [$uri, $item['parent-uri']])) {
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], $level, $item['parent-uri']);
|
||||
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $fetch_uid, $item['contact-id'], $level, $item['parent-uri']);
|
||||
if (empty($item['thr-parent'])) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$item['body'] = bluesky_get_text($record);
|
||||
$item['body'] = bluesky_get_text($record, $item['uri-id']);
|
||||
$item['created'] = DateTimeFormat::utc($record->createdAt, DateTimeFormat::MYSQL);
|
||||
$item['transmitted-languages'] = $record->langs ?? [];
|
||||
return $item;
|
||||
}
|
||||
|
||||
function bluesky_get_text(stdClass $record): string
|
||||
function bluesky_get_text(stdClass $record, int $uri_id): string
|
||||
{
|
||||
$text = $record->text ?? '';
|
||||
|
||||
|
@ -1151,8 +1241,14 @@ function bluesky_get_text(stdClass $record): string
|
|||
}
|
||||
break;
|
||||
|
||||
case 'app.bsky.richtext.facet#tag';
|
||||
Tag::store($uri_id, Tag::HASHTAG, $feature->tag);
|
||||
$url = DI::baseUrl() . '/search?tag=' . urlencode($feature->tag);
|
||||
$linktext = '#' . $feature->tag;
|
||||
break;
|
||||
|
||||
default:
|
||||
Logger::notice('Unhandled feature type', ['type' => $feature->$type, 'record' => $record]);
|
||||
Logger::notice('Unhandled feature type', ['type' => $feature->$type, 'feature' => $feature, 'record' => $record]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1200,7 +1296,7 @@ function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $le
|
|||
break;
|
||||
}
|
||||
$shared = bluesky_get_header($embed->record, $uri, 0, $fetch_uid);
|
||||
$shared = bluesky_get_content($shared, $embed->record->value, $uri, $item['uid'], $level);
|
||||
$shared = bluesky_get_content($shared, $embed->record->value, $uri, $item['uid'], $fetch_uid, $level);
|
||||
if (!empty($shared)) {
|
||||
if (!empty($embed->record->embeds)) {
|
||||
foreach ($embed->record->embeds as $single) {
|
||||
|
@ -1220,7 +1316,7 @@ function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $le
|
|||
$shared = Post::selectFirst(['uri-id'], ['uri' => $uri, 'uid' => $item['uid']]);
|
||||
if (empty($shared)) {
|
||||
$shared = bluesky_get_header($embed->record->record, $uri, 0, $fetch_uid);
|
||||
$shared = bluesky_get_content($shared, $embed->record->record->value, $uri, $item['uid'], $level);
|
||||
$shared = bluesky_get_content($shared, $embed->record->record->value, $uri, $item['uid'], $fetch_uid, $level);
|
||||
if (!empty($shared)) {
|
||||
if (!empty($embed->record->record->embeds)) {
|
||||
foreach ($embed->record->record->embeds as $single) {
|
||||
|
@ -1298,7 +1394,7 @@ function bluesky_get_uri_parts(string $uri): ?stdClass
|
|||
return $class;
|
||||
}
|
||||
|
||||
function bluesky_fetch_missing_post(string $uri, int $uid, int $causer, int $level, string $fallback = ''): string
|
||||
function bluesky_fetch_missing_post(string $uri, int $uid, int $fetch_uid, int $causer, int $level, string $fallback = ''): string
|
||||
{
|
||||
$fetched_uri = bluesky_fetch_post($uri, $uid);
|
||||
if (!empty($fetched_uri)) {
|
||||
|
@ -1316,7 +1412,7 @@ function bluesky_fetch_missing_post(string $uri, int $uid, int $causer, int $lev
|
|||
$fetch_uri = $class->uri;
|
||||
|
||||
Logger::debug('Fetch missing post', ['level' => $level, 'uid' => $uid, 'uri' => $uri]);
|
||||
$data = bluesky_xrpc_get($uid, 'app.bsky.feed.getPostThread', ['uri' => $fetch_uri]);
|
||||
$data = bluesky_xrpc_get($fetch_uid, 'app.bsky.feed.getPostThread', ['uri' => $fetch_uri]);
|
||||
if (empty($data)) {
|
||||
Logger::info('Thread was not fetched', ['level' => $level, 'uid' => $uid, 'uri' => $uri, 'fallback' => $fallback]);
|
||||
return $fallback;
|
||||
|
@ -1330,7 +1426,7 @@ function bluesky_fetch_missing_post(string $uri, int $uid, int $causer, int $lev
|
|||
$cdata = [];
|
||||
}
|
||||
|
||||
return bluesky_process_thread($data->thread, $uid, $cdata, $level);
|
||||
return bluesky_process_thread($data->thread, $uid, $fetch_uid, $cdata, $level);
|
||||
}
|
||||
|
||||
function bluesky_fetch_post(string $uri, int $uid): string
|
||||
|
@ -1348,7 +1444,7 @@ function bluesky_fetch_post(string $uri, int $uid): string
|
|||
return '';
|
||||
}
|
||||
|
||||
function bluesky_process_thread(stdClass $thread, int $uid, array $cdata, int $level): string
|
||||
function bluesky_process_thread(stdClass $thread, int $uid, int $fetch_uid, array $cdata, int $level): string
|
||||
{
|
||||
if (empty($thread->post)) {
|
||||
Logger::info('Invalid post', ['post' => $thread]);
|
||||
|
@ -1360,7 +1456,7 @@ function bluesky_process_thread(stdClass $thread, int $uid, array $cdata, int $l
|
|||
if (empty($fetched_uri)) {
|
||||
Logger::debug('Process missing post', ['uri' => $uri]);
|
||||
$item = bluesky_get_header($thread->post, $uri, $uid, $uid);
|
||||
$item = bluesky_get_content($item, $thread->post->record, $uri, $uid, $level);
|
||||
$item = bluesky_get_content($item, $thread->post->record, $uri, $uid, $fetch_uid, $level);
|
||||
if (!empty($item)) {
|
||||
$item['post-reason'] = Item::PR_FETCHED;
|
||||
|
||||
|
@ -1387,7 +1483,7 @@ function bluesky_process_thread(stdClass $thread, int $uid, array $cdata, int $l
|
|||
}
|
||||
|
||||
foreach ($thread->replies ?? [] as $reply) {
|
||||
$reply_uri = bluesky_process_thread($reply, $uid, $cdata, $level);
|
||||
$reply_uri = bluesky_process_thread($reply, $uid, $fetch_uid, $cdata, $level);
|
||||
Logger::debug('Reply has been processed', ['uri' => $uri, 'reply' => $reply_uri]);
|
||||
}
|
||||
|
||||
|
@ -1452,10 +1548,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 +1561,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 +1625,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 +1635,38 @@ 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;
|
||||
}
|
||||
$did = DI::pConfig()->get($uid, 'bluesky', 'did');
|
||||
if (empty($did)) {
|
||||
Logger::notice('Empty did for user', ['uid' => $uid]);
|
||||
return '';
|
||||
}
|
||||
$pds = bluesky_get_pds($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');
|
||||
|
@ -1570,6 +1703,7 @@ function bluesky_create_token(int $uid, string $password): string
|
|||
|
||||
$data = bluesky_post($uid, '/xrpc/com.atproto.server.createSession', json_encode(['identifier' => $did, 'password' => $password]), ['Content-type' => 'application/json']);
|
||||
if (empty($data)) {
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_TOKEN_FAIL);
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -1577,6 +1711,7 @@ function bluesky_create_token(int $uid, string $password): string
|
|||
DI::pConfig()->set($uid, 'bluesky', 'access_token', $data->accessJwt);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'refresh_token', $data->refreshJwt);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'token_created', time());
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_TOKEN_OK);
|
||||
return $data->accessJwt;
|
||||
}
|
||||
|
||||
|
@ -1588,17 +1723,20 @@ 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]);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_API_FAIL);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$curlResult->isSuccess()) {
|
||||
Logger::notice('API Error', ['error' => json_decode($curlResult->getBody()) ?: $curlResult->getBody()]);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_API_FAIL);
|
||||
return null;
|
||||
}
|
||||
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_SUCCESS);
|
||||
return json_decode($curlResult->getBody());
|
||||
}
|
||||
|
||||
|
@ -1608,13 +1746,16 @@ 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)]]]);
|
||||
$data = bluesky_get(bluesky_get_user_pds($uid) . '/xrpc/' . $url, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]);
|
||||
DI::pConfig()->set($uid, 'bluesky', 'status', is_null($data) ? BLUEKSY_STATUS_API_FAIL : BLUEKSY_STATUS_SUCCESS);
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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-12-06 06:30+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,100 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: bluesky.php:314
|
||||
msgid ""
|
||||
"You are authenticated to Bluesky. For security reasons the password isn't "
|
||||
"stored."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:314
|
||||
msgid "You are not authenticated. Please enter the app password."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:318
|
||||
#: bluesky.php:336
|
||||
msgid "Enable Bluesky Post Addon"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:319
|
||||
#: bluesky.php:337
|
||||
msgid "Post to Bluesky by default"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:320
|
||||
#: bluesky.php:338
|
||||
msgid "Import the remote timeline"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:321
|
||||
#: bluesky.php:339
|
||||
msgid "Import the pinned feeds"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:321
|
||||
#: bluesky.php:339
|
||||
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:340
|
||||
msgid "Personal Data Server"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:323
|
||||
#: bluesky.php:340
|
||||
msgid "The personal data server (PDS) is the system that hosts your profile."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:341
|
||||
msgid "Bluesky handle"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:324
|
||||
#: bluesky.php:342
|
||||
msgid "Bluesky DID"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:324
|
||||
#: bluesky.php:342
|
||||
msgid ""
|
||||
"This is the unique identifier. It will be fetched automatically, when the "
|
||||
"handle is entered."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:325
|
||||
#: bluesky.php:343
|
||||
msgid "Bluesky app password"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:325
|
||||
#: bluesky.php:343
|
||||
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:349
|
||||
msgid "Bluesky Import/Export"
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:382
|
||||
#: bluesky.php:359
|
||||
msgid ""
|
||||
"You are not authenticated. Please enter your handle and the app password."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:379
|
||||
msgid ""
|
||||
"You are authenticated to Bluesky. For security reasons the password isn't "
|
||||
"stored."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:381
|
||||
msgid ""
|
||||
"The communication with the personal data server service (PDS) is established."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:383
|
||||
msgid "Communication issues with the personal data server service (PDS)."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:385
|
||||
msgid ""
|
||||
"The DID for the provided handle could not be detected. Please check if you "
|
||||
"entered the correct handle."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:387
|
||||
msgid "The personal data server service (PDS) could not be detected."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:389
|
||||
msgid ""
|
||||
"The authentication with the provided handle and password failed. Please "
|
||||
"check if you entered the correct password."
|
||||
msgstr ""
|
||||
|
||||
#: bluesky.php:457
|
||||
msgid "Post to Bluesky"
|
||||
msgstr ""
|
||||
|
|
|
@ -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}}
|
4
invidious/README.md
Normal file
4
invidious/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
invidious Addon for Friendica
|
||||
==========================
|
||||
|
||||
This addon will replace "youtube.com" with the chosen Invidious instance
|
57
invidious/invidious.php
Normal file
57
invidious/invidious.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/*
|
||||
* Name: invidious
|
||||
* Description: Replaces links to youtube.com to an invidious instance in all displays of postings on a node.
|
||||
* Version: 0.2
|
||||
* Author: Matthias Ebers <https://loma.ml/profile/feb>
|
||||
*
|
||||
*/
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
|
||||
function invidious_install()
|
||||
{
|
||||
Hook::register('prepare_body_final', 'addon/invidious/invidious.php', 'invidious_render');
|
||||
}
|
||||
|
||||
/* Handle the send data from the admin settings
|
||||
*/
|
||||
function invidious_addon_admin_post()
|
||||
{
|
||||
DI::config()->set('invidious', 'server', rtrim(trim($_POST['invidiousserver']), '/'));
|
||||
}
|
||||
|
||||
/* Hook into the admin settings to let the admin choose an
|
||||
* invidious server to use for the replacement.
|
||||
*/
|
||||
function invidious_addon_admin(string &$o)
|
||||
{
|
||||
$invidiousserver = DI::config()->get('invidious', 'server');
|
||||
$t = Renderer::getMarkupTemplate('admin.tpl', 'addon/invidious/');
|
||||
$o = Renderer::replaceMacros($t, [
|
||||
'$settingdescription' => DI::l10n()->t('Which Invidious server shall be used for the replacements in the post bodies? Use the URL with servername and protocol. See %s for a list of available public Invidious servers.', 'https://redirect.invidious.io'),
|
||||
'$invidiousserver' => ['invidiousserver', DI::l10n()->t('Invidious server'), $invidiousserver, 'https://example.com'],
|
||||
'$submit' => DI::l10n()->t('Save Settings'),
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
* replace "youtube.com" with the chosen Invidious instance
|
||||
*/
|
||||
function invidious_render(array &$b)
|
||||
{
|
||||
// this needs to be a system setting
|
||||
$replaced = false;
|
||||
$invidious = DI::config()->get('invidious', 'server', 'https://invidio.us');
|
||||
if (strpos($b['html'], 'https://www.youtube.com/') !== false || strpos($b['html'], 'https://youtube.com/') !== false || strpos($b['html'], 'https://youtu.be/') !== false) {
|
||||
$b['html'] = str_replace('https://youtu.be/', $invidious . '/watch?v=', $b['html']);
|
||||
$b['html'] = str_replace(['https://www.youtube.com/', 'https://youtube.com/'], $invidious . '/', $b['html']);
|
||||
$replaced = true;
|
||||
}
|
||||
if ($replaced) {
|
||||
$b['html'] .= '<hr><p><small>' . DI::l10n()->t('(Invidious addon enabled: YouTube links via %s)', $invidious) . '</small></p>';
|
||||
}
|
||||
}
|
40
invidious/lang/C/messages.po
Normal file
40
invidious/lang/C/messages.po
Normal file
|
@ -0,0 +1,40 @@
|
|||
# ADDON invidious
|
||||
# Copyright (C)
|
||||
# This file is distributed under the same license as the Friendica invidious addon package.
|
||||
#
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-12-03 20:21+0100\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"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: invidious.php:35
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Which Invidious server shall be used for the replacements in the post "
|
||||
"bodies? Use the URL with servername and protocol. See %s for a list of "
|
||||
"available public Invidious servers."
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:36
|
||||
msgid "Invidious server"
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:37
|
||||
msgid "Save Settings"
|
||||
msgstr ""
|
||||
|
||||
#: invidious.php:64
|
||||
#, php-format
|
||||
msgid "(Invidious addon enabled: YouTube links via %s)"
|
||||
msgstr ""
|
||||
|
5
invidious/templates/admin.tpl
Normal file
5
invidious/templates/admin.tpl
Normal file
|
@ -0,0 +1,5 @@
|
|||
<p>{{$settingdescription}}</p>
|
||||
|
||||
{{include file="field_input.tpl" field=$invidiousserver}}
|
||||
|
||||
<div class="submit"><input type="submit" name="page_site" value="{{$submit}}" /></div>
|
|
@ -20,4 +20,9 @@
|
|||
width: 100%;
|
||||
margin-top: 25px;
|
||||
font-size: 20px;
|
||||
/* The pageheader box */
|
||||
padding: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue