diff --git a/smileybutton/view/default.css b/smileybutton/view/default.css
index 5fd60928..97ef8468 100644
--- a/smileybutton/view/default.css
+++ b/smileybutton/view/default.css
@@ -1,3 +1,14 @@
+/* fix positioning if more than one jot tool */
+.jotplugins > div,
+#profile-jot-plugin-wrapper > div {
+ float: left;
+}
+ .jotplugins::after,
+ #profile-jot-plugin-wrapper::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
#profile-smiley-wrapper {
display: block;
}
@@ -11,15 +22,36 @@
width: 18px;
}
-table.smiley-preview img.smiley {
+div.smiley-preview img.smiley {
max-height: 25px;
max-width: 25px;
-}
-
-table.smiley-preview {
- border: 1px solid #AAAAAA;
-}
-
-table.smiley-preview td {
cursor: pointer;
+ vertical-align: baseline;
}
+
+div.smiley-preview {
+ border: 1px solid #AAAAAA;
+ max-height: 200px;
+ overflow: auto;
+}
+
+div.smiley-preview > span {
+ cursor: pointer;
+ font-size: 24px;
+ padding: 5px;
+ text-align: center;
+ width: 45px;
+ height: 45px;
+ line-height: 45px;
+ float: left;
+ display: block;
+}
+ div.smiley-preview > span:hover,
+ div.smiley-preview > span:focus {
+ background-color: rgba(0,0,0,.1);
+ }
+ div.smiley-preview::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
diff --git a/smileybutton/view/duepuntozero.css b/smileybutton/view/duepuntozero.css
index 92ff4eba..1ad73d24 100644
--- a/smileybutton/view/duepuntozero.css
+++ b/smileybutton/view/duepuntozero.css
@@ -1,3 +1,15 @@
+/* fix positioning if more than one jot tool */
+.jotplugins > div,
+#profile-jot-plugin-wrapper > div {
+ float: left;
+}
+ .jotplugins::after,
+ #profile-jot-plugin-wrapper::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+
#profile-smiley-wrapper {
display: block;
}
@@ -17,21 +29,41 @@
background: none;
}
-table.smiley-preview img.smiley {
+div.smiley-preview img.smiley {
max-height: 25px;
max-width: 25px;
cursor: pointer;
+ vertical-align: baseline;
}
-table.smiley-preview {
+div.smiley-preview {
border: 1px solid #AAAAAA;
-moz-border-radius: 3px;
border-radius: 3px;
position: relative;
- left: 285px;
+ left: auto;
top: -36px;
+ max-height: 200px;
+ overflow: auto;
}
-table.smiley-preview td {
+div.smiley-preview > span {
cursor: pointer;
+ font-size: 24px;
+ padding: 5px;
+ text-align: center;
+ width: 45px;
+ height: 45px;
+ line-height: 45px;
+ float: left;
+ display: block;
}
+ div.smiley-preview > span:hover,
+ div.smiley-preview > span:focus {
+ background-color: rgba(0,0,0,.1);
+ }
+ div.smiley-preview::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
diff --git a/smileybutton/view/frio.css b/smileybutton/view/frio.css
index dce84826..7c1596bf 100644
--- a/smileybutton/view/frio.css
+++ b/smileybutton/view/frio.css
@@ -1,29 +1,80 @@
+/* fix positioning if more than one jot tool */
+.jotplugins > div,
+#profile-jot-plugin-wrapper > div {
+ float: left;
+}
+ .jotplugins::after,
+ #profile-jot-plugin-wrapper::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+
#profile-smiley-wrapper {
display: block;
}
#smileybutton {
display: none;
+ position: fixed;
+ background-color: #FFF;
+ width: auto;
+ border-radius: 8px;
+ padding: 10px;
+ -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
+ box-shadow: 0 6px 12px rgba(0,0,0,.175);
+}
+.jotplugins #smileybutton {
+ position: absolute;
+}
+/* image does not work with Frio schemes use icon font */
+.smiley_button {
+ -webkit-box-shadow: none !important;
+ box-shadow: none !important;
}
-
.smiley_button > img {
- height: 14px;
- width: 14px;
+ display: none;
}
+ .smiley_button::before {
+ content: '\f055';
+ font-family: ForkAwesome;
+ font-size: inherit;
+ color: inherit;
+ }
-table.smiley-preview img.smiley {
+div.smiley-preview img.smiley {
max-height: 25px;
max-width: 25px;
-}
-
-table.smiley-preview {
- border: 1px solid #AAAAAA;
-}
-
-table.smiley-preview td {
cursor: pointer;
+ vertical-align: baseline;
}
+div.smiley-preview {
+ border: none;
+ max-height: 200px;
+ overflow: auto;
+}
+
+div.smiley-preview > span {
+ cursor: pointer;
+ font-size: 24px;
+ padding: 5px;
+ text-align: center;
+ width: 45px;
+ height: 45px;
+ line-height: 45px;
+ float: left;
+ display: block;
+}
+ div.smiley-preview > span:hover,
+ div.smiley-preview > span:focus {
+ background-color: rgba(0,0,0,.1);
+ }
+ div.smiley-preview::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
#profile-smiley-wrapper > .btn-link {
position: relative;
display: block;
diff --git a/smileybutton/view/quattro.css b/smileybutton/view/quattro.css
new file mode 100644
index 00000000..c48ef2c6
--- /dev/null
+++ b/smileybutton/view/quattro.css
@@ -0,0 +1,64 @@
+/* fix positioning if more than one jot tool */
+.jotplugins > div,
+#profile-jot-plugin-wrapper > div {
+ float: left;
+}
+ .jotplugins::after,
+ #profile-jot-plugin-wrapper::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+#profile-smiley-wrapper {
+ display: block;
+ margin-bottom: -100px;
+}
+
+#smileybutton {
+ display: none;
+ position: absolute;
+ max-width: 770px;
+ z-index: 99;
+ background-color: white;
+}
+.smiley_button {
+ height: 42px;
+}
+.smiley_button > img {
+ height: 18px;
+ width: 18px;
+}
+
+div.smiley-preview img.smiley {
+ max-height: 25px;
+ max-width: 25px;
+ cursor: pointer;
+ vertical-align: baseline;
+}
+
+div.smiley-preview {
+ border: 1px solid #AAAAAA;
+ max-height: 200px;
+ overflow: auto;
+}
+
+div.smiley-preview > span {
+ cursor: pointer;
+ font-size: 24px;
+ padding: 5px;
+ text-align: center;
+ width: 45px;
+ height: 45px;
+ line-height: 45px;
+ float: left;
+ display: block;
+}
+ div.smiley-preview > span:hover,
+ div.smiley-preview > span:focus {
+ background-color: rgba(0,0,0,.1);
+ }
+ div.smiley-preview::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
diff --git a/smileybutton/view/smoothly.css b/smileybutton/view/smoothly.css
index 2f30799f..13dad74e 100644
--- a/smileybutton/view/smoothly.css
+++ b/smileybutton/view/smoothly.css
@@ -1,34 +1,80 @@
+/* fix positioning if more than one jot tool */
+#profile-jot-plugin-wrapper {
+ width: 100%;
+ margin-top: 10px;
+}
+.jotplugins > div,
+#profile-jot-plugin-wrapper > div {
+ float: left;
+}
+ .jotplugins::after,
+ #profile-jot-plugin-wrapper::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+
#profile-smiley-wrapper {
display: block;
}
#smileybutton {
display: none;
+ position: absolute;
+ background-color: #FFF;
+ width: auto;
+ border-radius: 8px;
+ padding: 10px;
+ z-index: 99;
+ -webkit-box-shadow: 0 0 5px rgba(0,0,0,.7);
+ box-shadow: 0 0 5px rgba(0,0,0,.7);
}
-
+ .jotplugins #smileybutton {
+ position: absolute;
+ }
.smiley_button > img {
height: 22px;
width: 22px;
position: relative;
- left: -330px;
+ left: 0px;
margin: 4px;
-moz-border-radius: 0px;
border-radius: 0px;
}
-table.smiley-preview img.smiley {
+div.smiley-preview img.smiley {
max-height: 25px;
max-width: 25px;
cursor: pointer;
+ vertical-align: baseline;
}
-table.smiley-preview {
- border: 1px solid #AAAAAA;
+div.smiley-preview {
+ border: none;
-moz-border-radius: 5px;
border-radius: 5px;
margin: 5px;
+ max-height: 200px;
+ overflow: auto;
}
-table.smiley-preview td {
+div.smiley-preview > span {
cursor: pointer;
+ font-size: 24px;
+ padding: 5px;
+ text-align: center;
+ width: 45px;
+ height: 45px;
+ line-height: 45px;
+ float: left;
+ display: block;
}
+ div.smiley-preview > span:hover,
+ div.smiley-preview > span:focus {
+ background-color: rgba(0,0,0,.1);
+ }
+ div.smiley-preview::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
diff --git a/smileybutton/view/vier.css b/smileybutton/view/vier.css
index 5862759b..41ed7e5c 100644
--- a/smileybutton/view/vier.css
+++ b/smileybutton/view/vier.css
@@ -1,3 +1,15 @@
+/* fix positioning if more than one jot tool */
+.jotplugins > div,
+#profile-jot-plugin-wrapper > div {
+ float: left;
+}
+ .jotplugins::after,
+ #profile-jot-plugin-wrapper::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+
#profile-smiley-wrapper {
float: left;
margin-left: 15px;
@@ -33,17 +45,37 @@
margin-right: 18px;
}
-table.smiley-preview {
+div.smiley-preview {
background-color: #FFF;
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.7);
+ max-height: 200px;
+ overflow: auto;
}
-table.smiley-preview img.smiley {
+div.smiley-preview img.smiley {
max-height: 25px;
max-width: 25px;
cursor: pointer;
+ vertical-align: baseline;
}
-table.smiley-preview td {
+div.smiley-preview > span {
cursor: pointer;
+ font-size: 24px;
+ padding: 5px;
+ text-align: center;
+ width: 45px;
+ height: 45px;
+ line-height: 45px;
+ float: left;
+ display: block;
}
+ div.smiley-preview > span:hover,
+ div.smiley-preview > span:focus {
+ background-color: rgba(0,0,0,.1);
+ }
+ div.smiley-preview::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
From ed70b2ca277bfd26aaa1f2b1d80ef490c1c481ab Mon Sep 17 00:00:00 2001
From: Matthew Exon
Date: Tue, 9 Jul 2024 20:12:09 +0100
Subject: [PATCH 02/16] Mailstream: respect blocked/ignored/collapsed contact
settings
---
mailstream/mailstream.php | 44 +++++++++++++++++++++++++++++++++++----
1 file changed, 40 insertions(+), 4 deletions(-)
diff --git a/mailstream/mailstream.php b/mailstream/mailstream.php
index a5aafdc7..7f68b7cc 100644
--- a/mailstream/mailstream.php
+++ b/mailstream/mailstream.php
@@ -166,7 +166,35 @@ function mailstream_send_hook(array $data)
return;
}
- if (!mailstream_send($data['message_id'], $item, $user)) {
+ $author = DBA::selectFirst('contact', ['nick', 'blocked', 'uri-id'], ['id' => $data['author-id'], 'self' => false]);
+ if (!DBA::isResult($author)) {
+ DI::logger()->error('could not find author', ['guid' => $item['guid'], 'author-id' => $data['author-id']]);
+ return;
+ }
+ if ($author['blocked']) {
+ DI::logger()->info('author is blocked', ['guid' => $item['guid'], 'author-id' => $data['author-id']]);
+ return;
+ }
+ $collapsed = false;
+ $user_contact = DBA::selectFirst('user-contact', ['cid', 'blocked', 'ignored', 'collapsed'], ['uid' => $item['uid'], 'uri-id' => $item['author-uri-id']]);
+ if (!DBA::isResult($user_contact)) {
+ $user_contact = DBA::selectFirst('user-contact', ['cid', 'blocked', 'ignored', 'collapsed'], ['uid' => $item['uid'], 'cid' => $item['author-id']]);
+ }
+ if (DBA::isResult($user_contact)) {
+ if ($user_contact['blocked']) {
+ DI::logger()->info('author is blocked', ['guid' => $item['guid'], 'cid' => $user_contact['cid']]);
+ return;
+ }
+ if ($user_contact['ignored']) {
+ DI::logger()->info('author is ignored', ['guid' => $item['guid'], 'cid' => $user_contact['cid']]);
+ return;
+ }
+ if ($user_contact['collapsed']) {
+ $collapsed = true;
+ }
+ }
+
+ if (!mailstream_send($data['message_id'], $item, $user, $collapsed)) {
DI::logger()->debug('send failed, will retry', $data);
if (!Worker::defer()) {
DI::logger()->error('failed and could not defer', $data);
@@ -220,6 +248,7 @@ function mailstream_post_hook(array &$item)
$send_hook_data = [
'uid' => $item['uid'],
'contact-id' => $item['contact-id'],
+ 'author-id' => $item['author-id'],
'uri' => $item['uri'],
'message_id' => $message_id,
'tries' => 0,
@@ -406,10 +435,11 @@ function mailstream_subject(array $item): string
* @param string $message_id ID of the message (RFC 1036)
* @param array $item content of the item
* @param array $user results from the user table
+ * @param bool $collapsed true if the content should be hidden
*
* @return bool True if this message has been completed. False if it should be retried.
*/
-function mailstream_send(string $message_id, array $item, array $user): bool
+function mailstream_send(string $message_id, array $item, array $user, bool $collapsed): bool
{
if (!is_array($item)) {
DI::logger()->error('item is empty', ['message_id' => $message_id]);
@@ -427,10 +457,16 @@ function mailstream_send(string $message_id, array $item, array $user): bool
require_once(dirname(__file__) . '/phpmailer/class.phpmailer.php');
- $item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
+ if ($collapsed) {
+ $item['body'] = DI::l10n()->t('Content from %s is collapsed', $item['author-name']);
+ } else {
+ $item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
+ }
$attachments = [];
- mailstream_do_images($item, $attachments);
+ if (!$collapsed) {
+ mailstream_do_images($item, $attachments);
+ }
$frommail = DI::config()->get('mailstream', 'frommail');
if ($frommail == '') {
$frommail = 'friendica@localhost.local';
From 31c130436ce7fad27e5b17f3668d185fdeeb7cf7 Mon Sep 17 00:00:00 2001
From: "Marcus F."
Date: Sun, 18 Jan 2026 15:15:37 +0100
Subject: [PATCH 03/16] IRC: Use friendica nickname as suggested IRC nickname,
when signed in
---
irc/irc.php | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/irc/irc.php b/irc/irc.php
index db808a99..a0cb899e 100644
--- a/irc/irc.php
+++ b/irc/irc.php
@@ -74,6 +74,7 @@ function irc_content()
{
$baseurl = DI::baseUrl() . '/addon/irc';
$o = '';
+ $usernick = '';
/* set the list of popular channels */
if (DI::userSession()->getLocalUserId()) {
@@ -81,6 +82,7 @@ function irc_content()
if (!$sitechats) {
$sitechats = DI::config()->get('irc', 'sitechats');
}
+ $usernick = "nick=" . DI::userSession()->getLocalUserNickname() . "&";
} else {
$sitechats = DI::config()->get('irc','sitechats');
}
@@ -117,7 +119,7 @@ function irc_content()
$o .= <<< EOT
IRC chat
A beginner's guide to using IRC. [en]
-
+
EOT;
return $o;
From 3509144228a91c6dae286f83324fcbfe4ecc6247 Mon Sep 17 00:00:00 2001
From: loma-one
Date: Mon, 23 Feb 2026 21:30:27 +0100
Subject: [PATCH 04/16] Add QuickPhoto Addon: Elegant BBCode simplification for
images
This PR introduces the QuickPhoto addon, designed to improve the user experience within the Friendica editor. It addresses the issue of long, cluttered BBCodes (monster-links) that appear when images are inserted via drag-and-drop or the image uploader.
Key Features
- Automatic Simplification: Replaces complex [url=...][img=...]...[/img][/url] structures with a human-readable shorthand: [img]filename|description[/img].
- Seamless Reconstruction: Automatically restores the full, valid Friendica BBCode before previewing or submitting a post, ensuring 100% compatibility with the server.
- Enhanced Readability: Keeps the editor clean and focus-oriented while writing long posts with multiple images.
Technical Highlights & Optimizations
- Resource Efficient: The JavaScript is strictly scoped. It only becomes active if a textarea is present and visible in the DOM.
- Zero Latency Typing: Implements requestIdleCallback and throttling (500ms) to ensure that the simplification process never interferes with the user's typing flow or causes UI lag.
- Background Throttling: Logic is suspended when the browser tab is inactive (document.hidden) to save CPU and battery life.
- LocalStorage Cache: Uses a local cache for image data (auto-cleaned after 12 hours) to ensure reliability during a single editing session.
Testing performed
- Tested with the standard "Jot" editor and the newer Compose/Comment templates.
- Verified that image previews render correctly (reconstruction triggers on preview click).
- Verified that the final post contains the correct full BBCode on the server side.
- Confirmed that Drag & Drop inserts are handled immediately.
---
quickphoto/quickphoto.php | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 quickphoto/quickphoto.php
diff --git a/quickphoto/quickphoto.php b/quickphoto/quickphoto.php
new file mode 100644
index 00000000..a3ba1059
--- /dev/null
+++ b/quickphoto/quickphoto.php
@@ -0,0 +1,32 @@
+
+ */
+
+use Friendica\Core\Hook;
+
+function quickphoto_install() {
+ Hook::register('page_header', 'addon/quickphoto/quickphoto.php', 'quickphoto_header');
+ // Emergency hook: If the JS fails during transmission
+ Hook::register('post_post', 'addon/quickphoto/quickphoto.php', 'quickphoto_post_hook');
+}
+
+function quickphoto_header(&$header) {
+ $header .= '' . "\n";
+}
+
+/**
+ * Processes the text directly upon receipt on the server
+ */
+function quickphoto_post_hook(&$item) {
+ if (!isset($item['body'])) {
+ return;
+ }
+
+ // If the JS couldn't do the job, we have the problem here
+ // that we don't have LocalStorage. That's why the JS is primarily responsible here.
+ // We'll leave this hook as a placeholder for future server validations.
+}
\ No newline at end of file
From ec3b0c5941ba99c2c03c5b9e3db01a3a74769d67 Mon Sep 17 00:00:00 2001
From: loma-one
Date: Mon, 23 Feb 2026 21:33:13 +0100
Subject: [PATCH 05/16] Add QuickPhoto Addon: Elegant BBCode simplification for
images
This PR introduces the QuickPhoto addon, designed to improve the user experience within the Friendica editor. It addresses the issue of long, cluttered BBCodes (monster-links) that appear when images are inserted via drag-and-drop or the image uploader.
Key Features
- Automatic Simplification: Replaces complex [url=...][img=...]...[/img][/url] structures with a human-readable shorthand: [img]filename|description[/img].
- Seamless Reconstruction: Automatically restores the full, valid Friendica BBCode before previewing or submitting a post, ensuring 100% compatibility with the server.
- Enhanced Readability: Keeps the editor clean and focus-oriented while writing long posts with multiple images.
Technical Highlights & Optimizations
- Resource Efficient: The JavaScript is strictly scoped. It only becomes active if a textarea is present and visible in the DOM.
- Zero Latency Typing: Implements requestIdleCallback and throttling (500ms) to ensure that the simplification process never interferes with the user's typing flow or causes UI lag.
- Background Throttling: Logic is suspended when the browser tab is inactive (document.hidden) to save CPU and battery life.
- LocalStorage Cache: Uses a local cache for image data (auto-cleaned after 12 hours) to ensure reliability during a single editing session.
Testing performed
- Tested with the standard "Jot" editor and the newer Compose/Comment templates.
- Verified that image previews render correctly (reconstruction triggers on preview click).
- Verified that the final post contains the correct full BBCode on the server side.
- Confirmed that Drag & Drop inserts are handled immediately.
---
quickphoto/quickphoto.js | 125 +++++++++++++++++++++++++++++++++++++++
1 file changed, 125 insertions(+)
create mode 100644 quickphoto/quickphoto.js
diff --git a/quickphoto/quickphoto.js b/quickphoto/quickphoto.js
new file mode 100644
index 00000000..4f3c12eb
--- /dev/null
+++ b/quickphoto/quickphoto.js
@@ -0,0 +1,125 @@
+(function() {
+ const monsterPattern = /\[url=(.*?)\]\[img=(.*?)\](.*?)\[\/img\]\[\/url\]/gi;
+ let throttleTimer;
+
+ const cleanupOldEntries = () => {
+ const now = Date.now();
+ const twelveHours = 12 * 60 * 60 * 1000;
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i);
+ if (key && key.startsWith('qp_')) {
+ try {
+ const data = JSON.parse(localStorage.getItem(key));
+ if (data && data.timestamp && (now - data.timestamp > twelveHours)) {
+ localStorage.removeItem(key);
+ }
+ } catch (e) { localStorage.removeItem(key); }
+ }
+ }
+ };
+
+ const simplify = (text) => {
+ if (!text || !text.includes('[url=')) return text;
+ return text.replace(monsterPattern, (match, urlPart, imgPart, existingDesc) => {
+ const fileName = imgPart.split('/').pop();
+ const storageKey = `qp_${fileName}`;
+
+ localStorage.setItem(storageKey, JSON.stringify({
+ url: urlPart,
+ img: imgPart,
+ timestamp: Date.now()
+ }));
+
+ let userDesc = existingDesc.trim() || "Bildbeschreibung";
+ return `[img]${fileName}|${userDesc}[/img]`;
+ });
+ };
+
+ const reconstruct = (text) => {
+ if (!text || !text.includes('[img]')) return text;
+ return text.replace(/\[img\](.*?)\|(.*?)\[\/img\]/g, (match, fileName, desc) => {
+ const data = localStorage.getItem(`qp_${fileName}`);
+ if (data) {
+ const parsed = JSON.parse(data);
+ const finalDesc = (desc === "Bildbeschreibung") ? "" : desc;
+ return `[url=${parsed.url}][img=${parsed.img}]${finalDesc}[/img][/url]`;
+ }
+ return match;
+ });
+ };
+
+ const applySimplify = (textarea) => {
+ if (!textarea || !textarea.value || !textarea.value.includes('[/img]')) return;
+
+ (window.requestIdleCallback || function(cb) { return setTimeout(cb, 1); })(() => {
+ const current = textarea.value;
+ const simple = simplify(current);
+ if (current !== simple) {
+ const start = textarea.selectionStart;
+ const end = textarea.selectionEnd;
+ textarea.value = simple;
+ textarea.setSelectionRange(start, end);
+ }
+ });
+ };
+
+ if (typeof jQuery !== 'undefined') {
+ const originalVal = jQuery.fn.val;
+ jQuery.fn.val = function(value) {
+ if (arguments.length === 0 && this.is('textarea')) {
+ return reconstruct(originalVal.call(this));
+ }
+ if (arguments.length > 0 && this.is('textarea')) {
+ return originalVal.call(this, simplify(value));
+ }
+ return originalVal.apply(this, arguments);
+ };
+ }
+
+ document.addEventListener('drop', (e) => {
+ if (e.target.tagName === 'TEXTAREA') {
+ setTimeout(() => applySimplify(e.target), 150);
+ }
+ }, true);
+
+ document.addEventListener('input', (e) => {
+ if (e.target.tagName === 'TEXTAREA') {
+ clearTimeout(throttleTimer);
+ throttleTimer = setTimeout(() => applySimplify(e.target), 500);
+ }
+ });
+
+ document.addEventListener('click', (e) => {
+ const btn = e.target.closest(
+ '#wall-submit-preview, #profile-jot-submit, #wall-submit-submit, #jot-submit, ' +
+ '[id^="comment-edit-submit-"], [id^="comment-edit-preview-link-"]'
+ );
+
+ if (btn) {
+ const textareas = document.querySelectorAll('textarea');
+ if (textareas.length > 0) {
+ textareas.forEach(textarea => {
+ textarea.value = reconstruct(textarea.value);
+ if (btn.id.includes('preview')) {
+ setTimeout(() => applySimplify(textarea), 1000);
+ }
+ });
+ }
+ }
+ }, true);
+
+ setInterval(() => {
+ if (document.hidden) return;
+
+ const textareas = document.querySelectorAll('textarea');
+ if (textareas.length === 0) return;
+
+ textareas.forEach(textarea => {
+ if (textarea.offsetParent !== null) {
+ applySimplify(textarea);
+ }
+ });
+ }, 2500);
+
+ cleanupOldEntries();
+})();
From 0ae86dafa029fef7448ca08d082aa8077328edf1 Mon Sep 17 00:00:00 2001
From: loma-one
Date: Mon, 23 Feb 2026 21:36:18 +0100
Subject: [PATCH 06/16] =?UTF-8?q?quickphoto/README.md=20hinzugef=C3=BCgt?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
quickphoto/README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 quickphoto/README.md
diff --git a/quickphoto/README.md b/quickphoto/README.md
new file mode 100644
index 00000000..2a574064
--- /dev/null
+++ b/quickphoto/README.md
@@ -0,0 +1,45 @@
+# QuickPhoto Addon for Friendica
+
+QuickPhoto is a Friendica addon that simplifies working with images in the editor. It automatically replaces long, cumbersome BBCode structures with a compact shorthand notation, without affecting functionality or compatibility.
+
+---
+
+## Features
+
+- **Automatic Simplification:** Converts "monster BBCodes" like `[url=...][img=...]...[/img][/url]` instantly into the handy format `[img]filename description[/img]`.
+- **Intelligent Reconstruction:** Before submitting or previewing, the shorthand code is quickly converted back into the original, valid Friendica BBCode.
+- **Real-Time Processing:** Responds immediately to drag & drop, copy & paste, and inserting images via editor buttons.
+- **Focus Safety:** Cursor management ensures the focus remains stable during automatic conversion while typing.
+- **Maximum Compatibility:** Supports both the standard Jot editor and the Compose module, as well as reply fields.
+- **Local Cache:** Image data is securely stored in the browser's localStorage and automatically cleared after 12 hours.
+
+---
+
+## How It Works
+
+The addon operates in a hybrid manner:
+
+- **Frontend:** A JavaScript watcher scans textareas and simplifies complex image links for better readability while writing.
+- **Interface:** It integrates deeply with Friendica's jQuery functions to ensure that preview and save functions always receive the correct original data.
+- **Events:** By intercepting submit and preview clicks, it guarantees that shorthand codes are never sent to the server in a format it cannot interpret.
+
+---
+
+## Installation
+
+1. Create a folder named `quickphoto` in the `addon/` directory of your Friendica installation.
+2. Place the file `quickphoto.php` in this folder.
+3. Place the file `quickphoto.js` in the same folder.
+4. Enable the addon in the Friendica administration area under **Addons**.
+
+ ---
+
+MIT License
+
+Copyright (c) 2024-2026 Friendica Project & Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
From 29e8c2875ce8c9538dce5a18492219e81d197bb8 Mon Sep 17 00:00:00 2001
From: loma-one
Date: Sat, 7 Mar 2026 09:09:48 +0100
Subject: [PATCH 07/16] quickphoto/README.md aktualisiert
Inserted | in `[img]|filename description[/img]`
---
quickphoto/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/quickphoto/README.md b/quickphoto/README.md
index 2a574064..34af0e71 100644
--- a/quickphoto/README.md
+++ b/quickphoto/README.md
@@ -6,7 +6,7 @@ QuickPhoto is a Friendica addon that simplifies working with images in the edito
## Features
-- **Automatic Simplification:** Converts "monster BBCodes" like `[url=...][img=...]...[/img][/url]` instantly into the handy format `[img]filename description[/img]`.
+- **Automatic Simplification:** Converts "monster BBCodes" like `[url=...][img=...]...[/img][/url]` instantly into the handy format `[img]|filename description[/img]`.
- **Intelligent Reconstruction:** Before submitting or previewing, the shorthand code is quickly converted back into the original, valid Friendica BBCode.
- **Real-Time Processing:** Responds immediately to drag & drop, copy & paste, and inserting images via editor buttons.
- **Focus Safety:** Cursor management ensures the focus remains stable during automatic conversion while typing.
From 22026ed53359b42bbd3d0b221582605edac9d512 Mon Sep 17 00:00:00 2001
From: loma-one
Date: Sat, 7 Mar 2026 09:13:45 +0100
Subject: [PATCH 08/16] quickphoto/quickphoto.js aktualisiert
"Description" instead of "Bildbeschreibung"
---
quickphoto/quickphoto.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/quickphoto/quickphoto.js b/quickphoto/quickphoto.js
index 4f3c12eb..09151888 100644
--- a/quickphoto/quickphoto.js
+++ b/quickphoto/quickphoto.js
@@ -30,7 +30,7 @@
timestamp: Date.now()
}));
- let userDesc = existingDesc.trim() || "Bildbeschreibung";
+ let userDesc = existingDesc.trim() || "Description";
return `[img]${fileName}|${userDesc}[/img]`;
});
};
@@ -41,7 +41,7 @@
const data = localStorage.getItem(`qp_${fileName}`);
if (data) {
const parsed = JSON.parse(data);
- const finalDesc = (desc === "Bildbeschreibung") ? "" : desc;
+ const finalDesc = (desc === "Description") ? "" : desc;
return `[url=${parsed.url}][img=${parsed.img}]${finalDesc}[/img][/url]`;
}
return match;
From 4a133456cea0ef8f7021496cc24a86e1202f985d Mon Sep 17 00:00:00 2001
From: loma-one
Date: Sat, 7 Mar 2026 10:20:43 +0100
Subject: [PATCH 09/16] quickphoto/quickphoto.php aktualisiert
Provides a translation
---
quickphoto/quickphoto.php | 33 +++++++++++++++------------------
1 file changed, 15 insertions(+), 18 deletions(-)
diff --git a/quickphoto/quickphoto.php b/quickphoto/quickphoto.php
index a3ba1059..7e7bd1a0 100644
--- a/quickphoto/quickphoto.php
+++ b/quickphoto/quickphoto.php
@@ -1,32 +1,29 @@
*/
-use Friendica\Core\Hook;
-
function quickphoto_install() {
- Hook::register('page_header', 'addon/quickphoto/quickphoto.php', 'quickphoto_header');
- // Emergency hook: If the JS fails during transmission
- Hook::register('post_post', 'addon/quickphoto/quickphoto.php', 'quickphoto_post_hook');
+ Friendica\Core\Hook::register('page_header', 'addon/quickphoto/quickphoto.php', 'quickphoto_header');
+ Friendica\Core\Hook::register('post_post', 'addon/quickphoto/quickphoto.php', 'quickphoto_post_hook');
}
function quickphoto_header(&$header) {
- $header .= '' . "\n";
-}
+ $desc_label = 'Image description';
-/**
- * Processes the text directly upon receipt on the server
- */
-function quickphoto_post_hook(&$item) {
- if (!isset($item['body'])) {
- return;
+ if (function_exists('t')) {
+ $desc_label = t('Image description');
}
- // If the JS couldn't do the job, we have the problem here
- // that we don't have LocalStorage. That's why the JS is primarily responsible here.
- // We'll leave this hook as a placeholder for future server validations.
+ $js_label = addslashes($desc_label);
+
+ $header .= "\n" . '';
+ $header .= "\n" . '' . "\n";
+}
+
+function quickphoto_post_hook(&$item) {
+ // Placeholder
}
\ No newline at end of file
From aa47cc6e048de0b48c76aea7b6220c355a4d4eb2 Mon Sep 17 00:00:00 2001
From: loma-one
Date: Sat, 7 Mar 2026 10:22:20 +0100
Subject: [PATCH 10/16] quickphoto/quickphoto.js aktualisiert
Make sure the translation is applied.
---
quickphoto/quickphoto.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/quickphoto/quickphoto.js b/quickphoto/quickphoto.js
index 09151888..f8dc2294 100644
--- a/quickphoto/quickphoto.js
+++ b/quickphoto/quickphoto.js
@@ -2,6 +2,8 @@
const monsterPattern = /\[url=(.*?)\]\[img=(.*?)\](.*?)\[\/img\]\[\/url\]/gi;
let throttleTimer;
+ const i18nDesc = (window.qp_i18n && window.qp_i18n.imageDesc) ? window.qp_i18n.imageDesc : "Image description";
+
const cleanupOldEntries = () => {
const now = Date.now();
const twelveHours = 12 * 60 * 60 * 1000;
@@ -30,7 +32,7 @@
timestamp: Date.now()
}));
- let userDesc = existingDesc.trim() || "Description";
+ let userDesc = existingDesc.trim() || i18nDesc;
return `[img]${fileName}|${userDesc}[/img]`;
});
};
@@ -41,7 +43,7 @@
const data = localStorage.getItem(`qp_${fileName}`);
if (data) {
const parsed = JSON.parse(data);
- const finalDesc = (desc === "Description") ? "" : desc;
+ const finalDesc = (desc === i18nDesc) ? "" : desc;
return `[url=${parsed.url}][img=${parsed.img}]${finalDesc}[/img][/url]`;
}
return match;
@@ -110,7 +112,6 @@
setInterval(() => {
if (document.hidden) return;
-
const textareas = document.querySelectorAll('textarea');
if (textareas.length === 0) return;
From 59ece8856c0e51d391c26ae4aaf306850b571d15 Mon Sep 17 00:00:00 2001
From: loma-one
Date: Sat, 7 Mar 2026 10:23:30 +0100
Subject: [PATCH 11/16] Translation file
---
quickphoto/lang/C/messages.po | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 quickphoto/lang/C/messages.po
diff --git a/quickphoto/lang/C/messages.po b/quickphoto/lang/C/messages.po
new file mode 100644
index 00000000..75476018
--- /dev/null
+++ b/quickphoto/lang/C/messages.po
@@ -0,0 +1,22 @@
+# ADDON quickphoto
+# Copyright (C)
+# This file is distributed under the same license as the Friendica quickphoto addon package.
+#
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-03-07 10:18+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: quickphoto.php:18
+msgid "Image description"
+msgstr ""
From 2e9f640724ebedef866908d50426c1fbe34edb4d Mon Sep 17 00:00:00 2001
From: loma-one
Date: Sat, 7 Mar 2026 17:09:57 +0100
Subject: [PATCH 12/16] quickphoto/quickphoto.php aktualisiert
DI::l10n()->t inserted
---
quickphoto/quickphoto.php | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/quickphoto/quickphoto.php b/quickphoto/quickphoto.php
index 7e7bd1a0..185665a6 100644
--- a/quickphoto/quickphoto.php
+++ b/quickphoto/quickphoto.php
@@ -6,17 +6,16 @@
* Author: Matthias Ebers
*/
+use Friendica\Core\Hook;
+use Friendica\DI;
+
function quickphoto_install() {
- Friendica\Core\Hook::register('page_header', 'addon/quickphoto/quickphoto.php', 'quickphoto_header');
- Friendica\Core\Hook::register('post_post', 'addon/quickphoto/quickphoto.php', 'quickphoto_post_hook');
+ Hook::register('page_header', 'addon/quickphoto/quickphoto.php', 'quickphoto_header');
+ Hook::register('post_post', 'addon/quickphoto/quickphoto.php', 'quickphoto_post_hook');
}
function quickphoto_header(&$header) {
- $desc_label = 'Image description';
-
- if (function_exists('t')) {
- $desc_label = t('Image description');
- }
+ $desc_label = DI::l10n()->t('Image description');
$js_label = addslashes($desc_label);
From d7a380bad53d8e25dd0a706da22097565f024db2 Mon Sep 17 00:00:00 2001
From: Michael
Date: Sun, 15 Mar 2026 23:10:38 +0000
Subject: [PATCH 13/16] "bluesky" is now "AT Protocol"
---
bluesky/bluesky.php | 107 ++++++++++++++++++++++---------------
bluesky/lang/C/messages.po | 87 +++++++++++++++---------------
2 files changed, 108 insertions(+), 86 deletions(-)
diff --git a/bluesky/bluesky.php b/bluesky/bluesky.php
index e1f096c7..cc36eae6 100644
--- a/bluesky/bluesky.php
+++ b/bluesky/bluesky.php
@@ -1,7 +1,7 @@
*
@@ -46,6 +46,7 @@ use Friendica\Protocol\Activity;
use Friendica\Protocol\ATProtocol;
use Friendica\Protocol\Relay;
use Friendica\Util\DateTimeFormat;
+use Friendica\Util\ParseUrl;
use Friendica\Util\Strings;
const BLUESKY_DEFAULT_POLL_INTERVAL = 10; // given in minutes
@@ -81,6 +82,8 @@ function bluesky_check_item_notification(array &$notification_data)
return;
}
+ DI::atProtocol()->setPublicApiForUser($notification_data['uid']);
+
$did = DI::atProtocol()->getUserDid($notification_data['uid']);
if (empty($did)) {
return;
@@ -96,24 +99,16 @@ function bluesky_item_by_link(array &$hookData)
return;
}
- if (substr($hookData['uri'], 0, 5) != 'at://') {
- if (!preg_match('#^' . ATProtocol::WEB . '/profile/(.+)/post/(.+)#', $hookData['uri'], $matches)) {
- return;
- }
+ DI::atProtocol()->setPublicApiForUser($hookData['uid']);
- $did = DI::atProtocol()->getDid($matches[1]);
- if (empty($did)) {
- return;
- }
-
- DI::logger()->debug('Found bluesky post', ['uri' => $hookData['uri'], 'did' => $did, 'cid' => $matches[2]]);
-
- $uri = 'at://' . $did . '/app.bsky.feed.post/' . $matches[2];
+ if (!str_starts_with($hookData['uri'], 'at://')) {
+ $data = ParseUrl::getSiteinfoCached($hookData['uri']);
+ $uri = $data['atprotocol']['uri'] ?? '';
} else {
$uri = $hookData['uri'];
}
- $uri = DI::atpProcessor()->fetchMissingPost($uri, $hookData['uid'], Item::PR_FETCHED, 0, 0);
+ $uri = DI::atpProcessor()->fetchMissingPost($uri, $hookData['uid'], Item::PR_FETCHED, 0, 0, '', false, Conversation::PARCEL_CONNECTOR);
DI::logger()->debug('Got post', ['uri' => $uri]);
if (!empty($uri)) {
$item = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $hookData['uid']]);
@@ -125,20 +120,22 @@ function bluesky_item_by_link(array &$hookData)
function bluesky_support_follow(array &$data)
{
- if ($data['protocol'] == Protocol::BLUESKY) {
+ if ($data['protocol'] == Protocol::ATPROTO) {
$data['result'] = true;
}
}
function bluesky_follow(array &$hook_data)
{
+ DI::atProtocol()->setPublicApiForUser($hook_data['uid']);
+
$token = DI::atProtocol()->getUserToken($hook_data['uid']);
if (empty($token)) {
return;
}
- DI::logger()->debug('Check if contact is bluesky', ['data' => $hook_data]);
- $contact = DBA::selectFirst('contact', [], ['network' => Protocol::BLUESKY, 'nurl' => Strings::normaliseLink($hook_data['url']), 'uid' => [0, $hook_data['uid']]]);
+ DI::logger()->debug('Check if contact is AT Protocol', ['data' => $hook_data]);
+ $contact = DBA::selectFirst('contact', [], ['network' => Protocol::ATPROTO, 'nurl' => Strings::normaliseLink($hook_data['url']), 'uid' => [0, $hook_data['uid']]]);
if (empty($contact)) {
return;
}
@@ -164,12 +161,14 @@ function bluesky_follow(array &$hook_data)
function bluesky_unfollow(array &$hook_data)
{
+ DI::atProtocol()->setPublicApiForUser($hook_data['uid']);
+
$token = DI::atProtocol()->getUserToken($hook_data['uid']);
if (empty($token)) {
return;
}
- if ($hook_data['contact']['network'] != Protocol::BLUESKY) {
+ if ($hook_data['contact']['network'] != Protocol::ATPROTO) {
return;
}
@@ -185,12 +184,14 @@ function bluesky_unfollow(array &$hook_data)
function bluesky_block(array &$hook_data)
{
+ DI::atProtocol()->setPublicApiForUser($hook_data['uid']);
+
$token = DI::atProtocol()->getUserToken($hook_data['uid']);
if (empty($token)) {
return;
}
- if ($hook_data['contact']['network'] != Protocol::BLUESKY) {
+ if ($hook_data['contact']['network'] != Protocol::ATPROTO) {
return;
}
@@ -218,12 +219,14 @@ function bluesky_block(array &$hook_data)
function bluesky_unblock(array &$hook_data)
{
+ DI::atProtocol()->setPublicApiForUser($hook_data['uid']);
+
$token = DI::atProtocol()->getUserToken($hook_data['uid']);
if (empty($token)) {
return;
}
- if ($hook_data['contact']['network'] != Protocol::BLUESKY) {
+ if ($hook_data['contact']['network'] != Protocol::ATPROTO) {
return;
}
@@ -243,7 +246,7 @@ function bluesky_addon_admin(string &$o)
$o = Renderer::replaceMacros($t, [
'$submit' => DI::l10n()->t('Save Settings'),
- '$friendica_handles' => ['friendica_handles', DI::l10n()->t('Allow your users to use your hostname for their Bluesky handles'), DI::config()->get('bluesky', 'friendica_handles'), DI::l10n()->t('Before enabling this option, you have to setup a wildcard domain configuration and you have to enable wildcard requests in your webserver configuration. On Apache this is done by adding "ServerAlias *.%s" to your HTTP configuration. You don\'t need to change the HTTPS configuration.', DI::baseUrl()->getHost())],
+ '$friendica_handles' => ['friendica_handles', DI::l10n()->t('Allow your users to use your hostname for their AT Protocol handles'), DI::config()->get('bluesky', 'friendica_handles'), DI::l10n()->t('Before enabling this option, you have to setup a wildcard domain configuration and you have to enable wildcard requests in your webserver configuration. On Apache this is done by adding "ServerAlias *.%s" to your HTTP configuration. You don\'t need to change the HTTPS configuration.', DI::baseUrl()->getHost())],
]);
}
@@ -258,6 +261,8 @@ function bluesky_settings(array &$data)
return;
}
+ DI::atProtocol()->setPublicApiForUser(DI::userSession()->getLocalUserId());
+
$enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post') ?? false;
$def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default') ?? false;
$pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
@@ -272,7 +277,7 @@ function bluesky_settings(array &$data)
if (DI::config()->get('bluesky', 'friendica_handles')) {
$self = User::getById(DI::userSession()->getLocalUserId(), ['nickname']);
$host_handle = $self['nickname'] . '.' . DI::baseUrl()->getHost();
- $friendica_handle = ['bluesky_friendica_handle', DI::l10n()->t('Allow to use %s as your Bluesky handle.', $host_handle), $custom_handle, DI::l10n()->t('When enabled, you can use %s as your Bluesky handle. After you enabled this option, please go to https://bsky.app/settings and select to change your handle. Select that you have got your own domain. Then enter %s and select "No DNS Panel". Then select "Verify Text File".', $host_handle, $host_handle)];
+ $friendica_handle = ['bluesky_friendica_handle', DI::l10n()->t('Allow to use %s as your AT Protocol handle.', $host_handle), $custom_handle, DI::l10n()->t('When enabled, you can use %s as your AT Protocol handle. After you enabled this option, please go to https://bsky.app/settings and select to change your handle. Select that you have got your own domain. Then enter %s and select "No DNS Panel". Then select "Verify Text File".', $host_handle, $host_handle)];
if ($custom_handle) {
$handle = $host_handle;
}
@@ -282,23 +287,23 @@ function bluesky_settings(array &$data)
$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],
+ '$enable' => ['bluesky', DI::l10n()->t('Enable AT Protocol Addon'), $enabled],
+ '$bydefault' => ['bluesky_bydefault', DI::l10n()->t('Post via AT Protocol 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.')],
+ '$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 AT Protocol.')],
'$complete_threads' => ['bluesky_complete_threads', DI::l10n()->t('Complete the threads'), $complete_threads, DI::l10n()->t('When activated, the system fetches additional replies for the posts in the timeline. This leads to more complete threads.')],
'$custom_handle' => $friendica_handle,
'$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, '', '', $custom_handle ? 'readonly' : ''],
- '$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.")],
+ '$handle' => ['bluesky_handle', DI::l10n()->t('AT Protocol handle'), $handle, '', '', $custom_handle ? 'readonly' : ''],
+ '$did' => ['bluesky_did', DI::l10n()->t('AT Protocol 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('AT Protocol app password'), '', DI::l10n()->t("Please don't add your real password here, but instead create a specific app password in the settings of your AT Protocol system.")],
'$status' => bluesky_get_status($handle, $did, $pds, $token),
]);
$data = [
'connector' => 'bluesky',
- 'title' => DI::l10n()->t('Bluesky Import/Export'),
- 'image' => 'images/bluesky.jpg',
+ 'title' => DI::l10n()->t('AT Protocol (Bluesky, Eurosky, Blacksky, ...) Import/Export'),
+ 'image' => 'images/500px-AT_Protocol_logo.png',
'enabled' => $enabled,
'html' => $html,
];
@@ -328,7 +333,7 @@ function bluesky_get_status(string $handle = null, string $did = null, string $p
switch ($status) {
case ATProtocol::STATUS_TOKEN_OK:
- return DI::l10n()->t("You are authenticated to Bluesky. For security reasons the password isn't stored.");
+ return DI::l10n()->t("You are authenticated to the AT Protocol PDS. For security reasons the password isn't stored.");
case ATProtocol::STATUS_SUCCESS:
return DI::l10n()->t('The communication with the personal data server service (PDS) is established.');
case ATProtocol::STATUS_API_FAIL;
@@ -350,6 +355,8 @@ function bluesky_settings_post(array &$b)
return;
}
+ DI::atProtocol()->setPublicApiForUser(DI::userSession()->getLocalUserId());
+
$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');
@@ -401,7 +408,7 @@ function bluesky_jot_nets(array &$jotnets_fields)
'type' => 'checkbox',
'field' => [
'bluesky_enable',
- DI::l10n()->t('Post to Bluesky'),
+ DI::l10n()->t('Post via the AT Protocol'),
DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default')
]
];
@@ -435,6 +442,8 @@ function bluesky_cron()
$pconfigs = DBA::selectToArray('pconfig', [], ["`cat` = ? AND `k` IN (?, ?) AND `v`", 'bluesky', 'import', 'import_feeds']);
foreach ($pconfigs as $pconfig) {
+ DI::atProtocol()->setPublicApiForUser($pconfig['uid']);
+
if (empty(DI::atProtocol()->getUserDid($pconfig['uid']))) {
DI::logger()->debug('User has got no valid DID', ['uid' => $pconfig['uid']]);
continue;
@@ -478,7 +487,7 @@ function bluesky_cron()
$last_clean = DI::keyValue()->get('bluesky_last_clean');
if (empty($last_clean) || ($last_clean + 86400 < time())) {
DI::logger()->notice('Start contact cleanup');
- $contacts = DBA::select('account-user-view', ['id', 'pid'], ["`network` = ? AND `uid` != ? AND `rel` = ?", Protocol::BLUESKY, 0, Contact::NOTHING]);
+ $contacts = DBA::select('account-user-view', ['id', 'pid'], ["`network` = ? AND `uid` != ? AND `rel` = ?", Protocol::ATPROTO, 0, Contact::NOTHING]);
while ($contact = DBA::fetch($contacts)) {
Worker::add(Worker::PRIORITY_LOW, 'MergeContact', $contact['pid'], $contact['id'], 0);
}
@@ -505,9 +514,9 @@ function bluesky_hook_fork(array &$b)
}
if (DI::pConfig()->get($post['uid'], 'bluesky', 'import')) {
- // Don't post if it isn't a reply to a bluesky post
- if (($post['gravity'] != Item::GRAVITY_PARENT) && !Post::exists(['id' => $post['parent'], 'network' => Protocol::BLUESKY])) {
- DI::logger()->notice('No bluesky parent found', ['item' => $post['id']]);
+ // Don't post if it isn't a reply to an AT Protocol post
+ if (($post['gravity'] != Item::GRAVITY_PARENT) && !Post::exists(['id' => $post['parent'], 'network' => Protocol::ATPROTO])) {
+ DI::logger()->notice('No AT Protocol parent found', ['item' => $post['id']]);
$b['execute'] = false;
return;
}
@@ -549,6 +558,8 @@ function bluesky_post_local(array &$b)
function bluesky_send(array &$b)
{
+ DI::atProtocol()->setPublicApiForUser($b['uid']);
+
if (($b['created'] !== $b['edited']) && !$b['deleted']) {
return;
}
@@ -563,7 +574,7 @@ function bluesky_send(array &$b)
if ($b['deleted']) {
$uri = DI::atpProcessor()->getUriClass($b['uri']);
if (empty($uri)) {
- DI::logger()->debug('Not a bluesky post', ['uri' => $b['uri']]);
+ DI::logger()->debug('Not an AT Protocol post', ['uri' => $b['uri']]);
return;
}
bluesky_delete_post($b['uri'], $b['uid']);
@@ -574,7 +585,7 @@ function bluesky_send(array &$b)
$parent = DI::atpProcessor()->getUriClass($b['thr-parent']);
if (empty($root) || empty($parent)) {
- DI::logger()->debug('No bluesky post', ['parent' => $b['parent'], 'thr-parent' => $b['thr-parent']]);
+ DI::logger()->debug('No AT Protocol post', ['parent' => $b['parent'], 'thr-parent' => $b['thr-parent']]);
return;
}
@@ -593,9 +604,11 @@ function bluesky_send(array &$b)
bluesky_create_post($b);
}
-function bluesky_create_activity(array $item, stdClass $parent = null)
+function bluesky_create_activity(array $item, ?stdClass $parent = null)
{
$uid = $item['uid'];
+ DI::atProtocol()->setPublicApiForUser($uid);
+
$token = DI::atProtocol()->getUserToken($uid);
if (empty($token)) {
return;
@@ -647,6 +660,8 @@ function bluesky_create_activity(array $item, stdClass $parent = null)
function bluesky_create_post(array $item, stdClass $root = null, stdClass $parent = null)
{
$uid = $item['uid'];
+ DI::atProtocol()->setPublicApiForUser($uid);
+
$token = DI::atProtocol()->getUserToken($uid);
if (empty($token)) {
return;
@@ -681,7 +696,7 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
$urls = bluesky_get_urls($item['body']);
$item['body'] = $urls['body'];
- $msg = Plaintext::getPost($item, 300, false, BBCode::BLUESKY);
+ $msg = Plaintext::getPost($item, 300, false, BBCode::ATPROTOCOL);
foreach ($msg['parts'] as $key => $part) {
$facets = bluesky_get_facets($part, $urls['urls']);
@@ -946,7 +961,7 @@ function bluesky_upload_blob(int $uid, array $photo): ?stdClass
return null;
}
- Item::incrementOutbound(Protocol::BLUESKY);
+ Item::incrementOutbound(Protocol::ATPROTO);
DI::logger()->debug('Uploaded blob', ['return' => $data, 'uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
return $data->blob;
}
@@ -964,6 +979,8 @@ function bluesky_delete_post(string $uri, int $uid)
function bluesky_fetch_timeline(int $uid)
{
+ DI::atProtocol()->setPublicApiForUser($uid);
+
$data = DI::atProtocol()->XRPCGet('app.bsky.feed.getTimeline', [], $uid);
if (empty($data)) {
return;
@@ -1020,7 +1037,7 @@ function bluesky_process_reason(stdClass $reason, string $uri, int $uid)
$contact = DI::atpActor()->getContactByDID($reason->by->did, $uid, 0);
$item = [
- 'network' => Protocol::BLUESKY,
+ 'network' => Protocol::ATPROTO,
'protocol' => Conversation::PARCEL_CONNECTOR,
'uid' => $uid,
'wall' => false,
@@ -1057,6 +1074,8 @@ function bluesky_process_reason(stdClass $reason, string $uri, int $uid)
function bluesky_fetch_notifications(int $uid)
{
+ DI::atProtocol()->setPublicApiForUser($uid);
+
$data = DI::atProtocol()->XRPCGet('app.bsky.notification.listNotifications', [], $uid);
if (empty($data->notifications)) {
return;
@@ -1130,6 +1149,8 @@ function bluesky_fetch_notifications(int $uid)
function bluesky_fetch_feed(int $uid, string $feed)
{
+ DI::atProtocol()->setPublicApiForUser($uid);
+
$data = DI::atProtocol()->XRPCGet('app.bsky.feed.getFeed', ['feed' => $feed], $uid);
if (empty($data)) {
return;
diff --git a/bluesky/lang/C/messages.po b/bluesky/lang/C/messages.po
index d573972c..57dc2136 100644
--- a/bluesky/lang/C/messages.po
+++ b/bluesky/lang/C/messages.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-09-29 18:16+0000\n"
+"POT-Creation-Date: 2026-03-15 23:16+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,117 +17,118 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: bluesky.php:335
+#: bluesky.php:248
msgid "Save Settings"
msgstr ""
-#: bluesky.php:336
-msgid "Allow your users to use your hostname for their Bluesky handles"
+#: bluesky.php:249
+msgid "Allow your users to use your hostname for their AT Protocol handles"
msgstr ""
-#: bluesky.php:336
+#: bluesky.php:249
#, php-format
msgid "Before enabling this option, you have to setup a wildcard domain configuration and you have to enable wildcard requests in your webserver configuration. On Apache this is done by adding \"ServerAlias *.%s\" to your HTTP configuration. You don't need to change the HTTPS configuration."
msgstr ""
-#: bluesky.php:365
+#: bluesky.php:280
#, php-format
-msgid "Allow to use %s as your Bluesky handle."
+msgid "Allow to use %s as your AT Protocol handle."
msgstr ""
-#: bluesky.php:365
+#: bluesky.php:280
#, php-format
-msgid "When enabled, you can use %s as your Bluesky handle. After you enabled this option, please go to https://bsky.app/settings and select to change your handle. Select that you have got your own domain. Then enter %s and select \"No DNS Panel\". Then select \"Verify Text File\"."
+msgid "When enabled, you can use %s as your AT Protocol handle. After you enabled this option, please go to https://bsky.app/settings and select to change your handle. Select that you have got your own domain. Then enter %s and select \"No DNS Panel\". Then select \"Verify Text File\"."
msgstr ""
-#: bluesky.php:375
-msgid "Enable Bluesky Post Addon"
+#: bluesky.php:290
+msgid "Enable AT Protocol Addon"
msgstr ""
-#: bluesky.php:376
-msgid "Post to Bluesky by default"
+#: bluesky.php:291
+msgid "Post via AT Protocol by default"
msgstr ""
-#: bluesky.php:377
+#: bluesky.php:292
msgid "Import the remote timeline"
msgstr ""
-#: bluesky.php:378
+#: bluesky.php:293
msgid "Import the pinned feeds"
msgstr ""
-#: bluesky.php:378
-msgid "When activated, Posts will be imported from all the feeds that you pinned in Bluesky."
+#: bluesky.php:293
+msgid "When activated, Posts will be imported from all the feeds that you pinned in AT Protocol."
msgstr ""
-#: bluesky.php:379
+#: bluesky.php:294
msgid "Complete the threads"
msgstr ""
-#: bluesky.php:379
+#: bluesky.php:294
msgid "When activated, the system fetches additional replies for the posts in the timeline. This leads to more complete threads."
msgstr ""
-#: bluesky.php:381
+#: bluesky.php:296
msgid "Personal Data Server"
msgstr ""
-#: bluesky.php:381
+#: bluesky.php:296
msgid "The personal data server (PDS) is the system that hosts your profile."
msgstr ""
-#: bluesky.php:382
-msgid "Bluesky handle"
+#: bluesky.php:297
+msgid "AT Protocol handle"
msgstr ""
-#: bluesky.php:383
-msgid "Bluesky DID"
+#: bluesky.php:298
+msgid "AT Protocol DID"
msgstr ""
-#: bluesky.php:383
+#: bluesky.php:298
msgid "This is the unique identifier. It will be fetched automatically, when the handle is entered."
msgstr ""
-#: bluesky.php:384
-msgid "Bluesky app password"
+#: bluesky.php:299
+msgid "AT Protocol app password"
msgstr ""
-#: bluesky.php:384
-msgid "Please don't add your real password here, but instead create a specific app password in the Bluesky settings."
+#: bluesky.php:299
+msgid "Please don't add your real password here, but instead create a specific app password in the settings of your AT Protocol system."
msgstr ""
-#: bluesky.php:390
-msgid "Bluesky Import/Export"
+#: bluesky.php:305
+msgid "AT Protocol (Bluesky, Eurosky, Blacksky, ...) Import/Export"
msgstr ""
-#: bluesky.php:400
+#: bluesky.php:315
msgid "You are not authenticated. Please enter your handle and the app password."
msgstr ""
-#: bluesky.php:420
-msgid "You are authenticated to Bluesky. For security reasons the password isn't stored."
+#: bluesky.php:336
+msgid "You are authenticated to the AT Protocol PDS. For security reasons the password isn't stored."
msgstr ""
-#: bluesky.php:422
+#: bluesky.php:338
msgid "The communication with the personal data server service (PDS) is established."
msgstr ""
-#: bluesky.php:424
-msgid "Communication issues with the personal data server service (PDS)."
+#: bluesky.php:340
+#, php-format
+msgid "Communication issues with the personal data server service (PDS): %s"
msgstr ""
-#: bluesky.php:426
+#: bluesky.php:342
msgid "The DID for the provided handle could not be detected. Please check if you entered the correct handle."
msgstr ""
-#: bluesky.php:428
+#: bluesky.php:344
msgid "The personal data server service (PDS) could not be detected."
msgstr ""
-#: bluesky.php:430
+#: bluesky.php:346
msgid "The authentication with the provided handle and password failed. Please check if you entered the correct password."
msgstr ""
-#: bluesky.php:492
-msgid "Post to Bluesky"
+#: bluesky.php:411
+msgid "Post via the AT Protocol"
msgstr ""
From 5a099dededb19420ce6ba792e13863298ef18fb7 Mon Sep 17 00:00:00 2001
From: Michael
Date: Tue, 17 Mar 2026 21:30:07 +0000
Subject: [PATCH 14/16] Renamed function
---
bluesky/bluesky.php | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/bluesky/bluesky.php b/bluesky/bluesky.php
index cc36eae6..d9415d68 100644
--- a/bluesky/bluesky.php
+++ b/bluesky/bluesky.php
@@ -82,7 +82,7 @@ function bluesky_check_item_notification(array &$notification_data)
return;
}
- DI::atProtocol()->setPublicApiForUser($notification_data['uid']);
+ DI::atProtocol()->setApiForUser($notification_data['uid']);
$did = DI::atProtocol()->getUserDid($notification_data['uid']);
if (empty($did)) {
@@ -99,7 +99,7 @@ function bluesky_item_by_link(array &$hookData)
return;
}
- DI::atProtocol()->setPublicApiForUser($hookData['uid']);
+ DI::atProtocol()->setApiForUser($hookData['uid']);
if (!str_starts_with($hookData['uri'], 'at://')) {
$data = ParseUrl::getSiteinfoCached($hookData['uri']);
@@ -127,7 +127,7 @@ function bluesky_support_follow(array &$data)
function bluesky_follow(array &$hook_data)
{
- DI::atProtocol()->setPublicApiForUser($hook_data['uid']);
+ DI::atProtocol()->setApiForUser($hook_data['uid']);
$token = DI::atProtocol()->getUserToken($hook_data['uid']);
if (empty($token)) {
@@ -161,7 +161,7 @@ function bluesky_follow(array &$hook_data)
function bluesky_unfollow(array &$hook_data)
{
- DI::atProtocol()->setPublicApiForUser($hook_data['uid']);
+ DI::atProtocol()->setApiForUser($hook_data['uid']);
$token = DI::atProtocol()->getUserToken($hook_data['uid']);
if (empty($token)) {
@@ -184,7 +184,7 @@ function bluesky_unfollow(array &$hook_data)
function bluesky_block(array &$hook_data)
{
- DI::atProtocol()->setPublicApiForUser($hook_data['uid']);
+ DI::atProtocol()->setApiForUser($hook_data['uid']);
$token = DI::atProtocol()->getUserToken($hook_data['uid']);
if (empty($token)) {
@@ -219,7 +219,7 @@ function bluesky_block(array &$hook_data)
function bluesky_unblock(array &$hook_data)
{
- DI::atProtocol()->setPublicApiForUser($hook_data['uid']);
+ DI::atProtocol()->setApiForUser($hook_data['uid']);
$token = DI::atProtocol()->getUserToken($hook_data['uid']);
if (empty($token)) {
@@ -261,7 +261,7 @@ function bluesky_settings(array &$data)
return;
}
- DI::atProtocol()->setPublicApiForUser(DI::userSession()->getLocalUserId());
+ DI::atProtocol()->setApiForUser(DI::userSession()->getLocalUserId());
$enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post') ?? false;
$def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default') ?? false;
@@ -355,7 +355,7 @@ function bluesky_settings_post(array &$b)
return;
}
- DI::atProtocol()->setPublicApiForUser(DI::userSession()->getLocalUserId());
+ DI::atProtocol()->setApiForUser(DI::userSession()->getLocalUserId());
$old_pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
$old_handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle');
@@ -442,7 +442,7 @@ function bluesky_cron()
$pconfigs = DBA::selectToArray('pconfig', [], ["`cat` = ? AND `k` IN (?, ?) AND `v`", 'bluesky', 'import', 'import_feeds']);
foreach ($pconfigs as $pconfig) {
- DI::atProtocol()->setPublicApiForUser($pconfig['uid']);
+ DI::atProtocol()->setApiForUser($pconfig['uid']);
if (empty(DI::atProtocol()->getUserDid($pconfig['uid']))) {
DI::logger()->debug('User has got no valid DID', ['uid' => $pconfig['uid']]);
@@ -558,7 +558,7 @@ function bluesky_post_local(array &$b)
function bluesky_send(array &$b)
{
- DI::atProtocol()->setPublicApiForUser($b['uid']);
+ DI::atProtocol()->setApiForUser($b['uid']);
if (($b['created'] !== $b['edited']) && !$b['deleted']) {
return;
@@ -607,7 +607,7 @@ function bluesky_send(array &$b)
function bluesky_create_activity(array $item, ?stdClass $parent = null)
{
$uid = $item['uid'];
- DI::atProtocol()->setPublicApiForUser($uid);
+ DI::atProtocol()->setApiForUser($uid);
$token = DI::atProtocol()->getUserToken($uid);
if (empty($token)) {
@@ -660,7 +660,7 @@ function bluesky_create_activity(array $item, ?stdClass $parent = null)
function bluesky_create_post(array $item, stdClass $root = null, stdClass $parent = null)
{
$uid = $item['uid'];
- DI::atProtocol()->setPublicApiForUser($uid);
+ DI::atProtocol()->setApiForUser($uid);
$token = DI::atProtocol()->getUserToken($uid);
if (empty($token)) {
@@ -979,7 +979,7 @@ function bluesky_delete_post(string $uri, int $uid)
function bluesky_fetch_timeline(int $uid)
{
- DI::atProtocol()->setPublicApiForUser($uid);
+ DI::atProtocol()->setApiForUser($uid);
$data = DI::atProtocol()->XRPCGet('app.bsky.feed.getTimeline', [], $uid);
if (empty($data)) {
@@ -1074,7 +1074,7 @@ function bluesky_process_reason(stdClass $reason, string $uri, int $uid)
function bluesky_fetch_notifications(int $uid)
{
- DI::atProtocol()->setPublicApiForUser($uid);
+ DI::atProtocol()->setApiForUser($uid);
$data = DI::atProtocol()->XRPCGet('app.bsky.notification.listNotifications', [], $uid);
if (empty($data->notifications)) {
@@ -1149,7 +1149,7 @@ function bluesky_fetch_notifications(int $uid)
function bluesky_fetch_feed(int $uid, string $feed)
{
- DI::atProtocol()->setPublicApiForUser($uid);
+ DI::atProtocol()->setApiForUser($uid);
$data = DI::atProtocol()->XRPCGet('app.bsky.feed.getFeed', ['feed' => $feed], $uid);
if (empty($data)) {
From 3d199ba7d731b21ac033b44827570b40c1a4f586 Mon Sep 17 00:00:00 2001
From: Michael
Date: Wed, 18 Mar 2026 13:21:21 +0000
Subject: [PATCH 15/16] Setting for preferred web front end added
---
bluesky/bluesky.php | 14 ++++++
bluesky/lang/C/messages.po | 60 ++++++++++++++----------
bluesky/templates/connector_settings.tpl | 3 +-
3 files changed, 50 insertions(+), 27 deletions(-)
diff --git a/bluesky/bluesky.php b/bluesky/bluesky.php
index d9415d68..4a26106c 100644
--- a/bluesky/bluesky.php
+++ b/bluesky/bluesky.php
@@ -267,6 +267,7 @@ function bluesky_settings(array &$data)
$def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default') ?? false;
$pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
$handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle');
+ $web = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'web');
$did = DI::atProtocol()->getUserDid(DI::userSession()->getLocalUserId());
$token = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'access_token');
$import = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import') ?? false;
@@ -285,6 +286,13 @@ function bluesky_settings(array &$data)
$friendica_handle = [];
}
+ $web_frontend = [
+ '' => 'System Default',
+ ATProtocol::WEB => 'Bluesky',
+ 'https://blacksky.community' => 'Blacksky',
+ 'https://reddwarf.app' => 'Red Dwarf',
+ ];
+
$t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/bluesky/');
$html = Renderer::replaceMacros($t, [
'$enable' => ['bluesky', DI::l10n()->t('Enable AT Protocol Addon'), $enabled],
@@ -297,6 +305,7 @@ function bluesky_settings(array &$data)
'$handle' => ['bluesky_handle', DI::l10n()->t('AT Protocol handle'), $handle, '', '', $custom_handle ? 'readonly' : ''],
'$did' => ['bluesky_did', DI::l10n()->t('AT Protocol 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('AT Protocol app password'), '', DI::l10n()->t("Please don't add your real password here, but instead create a specific app password in the settings of your AT Protocol system.")],
+ '$web' => ['bluesky_web', DI::l10n()->t('Web front end'), $web, DI::l10n()->t('Choose your preferred external web front end for displaying posts and profiles.'), $web_frontend, ''],
'$status' => bluesky_get_status($handle, $did, $pds, $token),
]);
@@ -370,6 +379,11 @@ function bluesky_settings_post(array &$b)
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds', intval($_POST['bluesky_import_feeds']));
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'complete_threads', intval($_POST['bluesky_complete_threads']));
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'friendica_handle', intval($_POST['bluesky_friendica_handle'] ?? false));
+ if ($_POST['bluesky_web'] <> '') {
+ DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'web', $_POST['bluesky_web']);
+ } else {
+ DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'bluesky', 'web');
+ }
if (!empty($handle)) {
$did = DI::atProtocol()->getUserDid(DI::userSession()->getLocalUserId(), empty($old_did) || $old_handle != $handle);
diff --git a/bluesky/lang/C/messages.po b/bluesky/lang/C/messages.po
index 57dc2136..0307bc6c 100644
--- a/bluesky/lang/C/messages.po
+++ b/bluesky/lang/C/messages.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-03-15 23:16+0000\n"
+"POT-Creation-Date: 2026-03-18 13:20+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -30,105 +30,113 @@ msgstr ""
msgid "Before enabling this option, you have to setup a wildcard domain configuration and you have to enable wildcard requests in your webserver configuration. On Apache this is done by adding \"ServerAlias *.%s\" to your HTTP configuration. You don't need to change the HTTPS configuration."
msgstr ""
-#: bluesky.php:280
+#: bluesky.php:281
#, php-format
msgid "Allow to use %s as your AT Protocol handle."
msgstr ""
-#: bluesky.php:280
+#: bluesky.php:281
#, php-format
msgid "When enabled, you can use %s as your AT Protocol handle. After you enabled this option, please go to https://bsky.app/settings and select to change your handle. Select that you have got your own domain. Then enter %s and select \"No DNS Panel\". Then select \"Verify Text File\"."
msgstr ""
-#: bluesky.php:290
+#: bluesky.php:298
msgid "Enable AT Protocol Addon"
msgstr ""
-#: bluesky.php:291
+#: bluesky.php:299
msgid "Post via AT Protocol by default"
msgstr ""
-#: bluesky.php:292
+#: bluesky.php:300
msgid "Import the remote timeline"
msgstr ""
-#: bluesky.php:293
+#: bluesky.php:301
msgid "Import the pinned feeds"
msgstr ""
-#: bluesky.php:293
+#: bluesky.php:301
msgid "When activated, Posts will be imported from all the feeds that you pinned in AT Protocol."
msgstr ""
-#: bluesky.php:294
+#: bluesky.php:302
msgid "Complete the threads"
msgstr ""
-#: bluesky.php:294
+#: bluesky.php:302
msgid "When activated, the system fetches additional replies for the posts in the timeline. This leads to more complete threads."
msgstr ""
-#: bluesky.php:296
+#: bluesky.php:304
msgid "Personal Data Server"
msgstr ""
-#: bluesky.php:296
+#: bluesky.php:304
msgid "The personal data server (PDS) is the system that hosts your profile."
msgstr ""
-#: bluesky.php:297
+#: bluesky.php:305
msgid "AT Protocol handle"
msgstr ""
-#: bluesky.php:298
+#: bluesky.php:306
msgid "AT Protocol DID"
msgstr ""
-#: bluesky.php:298
+#: bluesky.php:306
msgid "This is the unique identifier. It will be fetched automatically, when the handle is entered."
msgstr ""
-#: bluesky.php:299
+#: bluesky.php:307
msgid "AT Protocol app password"
msgstr ""
-#: bluesky.php:299
+#: bluesky.php:307
msgid "Please don't add your real password here, but instead create a specific app password in the settings of your AT Protocol system."
msgstr ""
-#: bluesky.php:305
+#: bluesky.php:308
+msgid "Web front end"
+msgstr ""
+
+#: bluesky.php:308
+msgid "Choose your preferred external web front end for displaying posts and profiles."
+msgstr ""
+
+#: bluesky.php:314
msgid "AT Protocol (Bluesky, Eurosky, Blacksky, ...) Import/Export"
msgstr ""
-#: bluesky.php:315
+#: bluesky.php:324
msgid "You are not authenticated. Please enter your handle and the app password."
msgstr ""
-#: bluesky.php:336
+#: bluesky.php:345
msgid "You are authenticated to the AT Protocol PDS. For security reasons the password isn't stored."
msgstr ""
-#: bluesky.php:338
+#: bluesky.php:347
msgid "The communication with the personal data server service (PDS) is established."
msgstr ""
-#: bluesky.php:340
+#: bluesky.php:349
#, php-format
msgid "Communication issues with the personal data server service (PDS): %s"
msgstr ""
-#: bluesky.php:342
+#: bluesky.php:351
msgid "The DID for the provided handle could not be detected. Please check if you entered the correct handle."
msgstr ""
-#: bluesky.php:344
+#: bluesky.php:353
msgid "The personal data server service (PDS) could not be detected."
msgstr ""
-#: bluesky.php:346
+#: bluesky.php:355
msgid "The authentication with the provided handle and password failed. Please check if you entered the correct password."
msgstr ""
-#: bluesky.php:411
+#: bluesky.php:425
msgid "Post via the AT Protocol"
msgstr ""
diff --git a/bluesky/templates/connector_settings.tpl b/bluesky/templates/connector_settings.tpl
index a85bcd89..bdfad763 100644
--- a/bluesky/templates/connector_settings.tpl
+++ b/bluesky/templates/connector_settings.tpl
@@ -10,4 +10,5 @@
{{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
+{{include file="field_input.tpl" field=$password}}
+{{include file="field_select.tpl" field=$web}}
\ No newline at end of file
From 7ffd587819e6d8bc3685930a587546d93ab6c4a9 Mon Sep 17 00:00:00 2001
From: Michael
Date: Sun, 29 Mar 2026 13:31:47 +0000
Subject: [PATCH 16/16] AT-Protocol: Configurable web frontend
---
bluesky/bluesky.php | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/bluesky/bluesky.php b/bluesky/bluesky.php
index 4a26106c..7bd5db0d 100644
--- a/bluesky/bluesky.php
+++ b/bluesky/bluesky.php
@@ -286,12 +286,10 @@ function bluesky_settings(array &$data)
$friendica_handle = [];
}
- $web_frontend = [
- '' => 'System Default',
- ATProtocol::WEB => 'Bluesky',
- 'https://blacksky.community' => 'Blacksky',
- 'https://reddwarf.app' => 'Red Dwarf',
- ];
+ $web_frontend = ['' => 'System Default'];
+ foreach (DI::config()->get('atprotocol', 'frontends') as $key => $frontend) {
+ $web_frontend[$key] = $frontend[0];
+ }
$t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/bluesky/');
$html = Renderer::replaceMacros($t, [