Merge pull request #15596 from annando/atproto

The AT Protocol handling is now less Bluesky centric
This commit is contained in:
Philipp 2026-03-29 11:38:27 +02:00 committed by GitHub
commit a46884e818
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 1029 additions and 340 deletions

View file

@ -1,6 +1,6 @@
# Konnektoren installieren
Friendica verwendet Konnektoren, um sich mit einigen Netzwerken zu verbinden, wie Tumblr oder Bluesky.
Friendica verwendet Konnektoren, um sich mit einigen Netzwerken zu verbinden, wie Tumblr oder den auf dem AT Protokoll basierenden Systemen wie Bluesky, Eurosky oder Blacksky.
Alle diese Konnektoren erfordern einen Account im Zielnetzwerk.
Außerdem musst du (oder die Server-Administration) in der Regel einen API-Schlüssel erhalten, um die Verbindung zu ermöglichen.
@ -12,16 +12,16 @@ Dies geschieht über die Server-Verwaltung.
Einige der Konnektoren erfordern auch einen „API-Schlüssel“ des Dienstes, mit dem du dich verbinden möchtest.
Für Tumblr findet man diese Informationen auf den Seiten der Server-Verwaltung, während für Twitter (X) jede Person einen eigenen API-Schlüssel erstellen muss.
Andere Konnektoren, wie Bluesky, benötigen überhaupt keinen API-Schlüssel.
Andere Konnektoren, wie das AT Protokoll, benötigen überhaupt keinen API-Schlüssel.
Weitere Informationen zu den spezifischen Anforderungen findest du auf der Einstellungsseite des jeweiligen Addons, entweder auf der Verwaltungsseite oder auf der Benutzerseite.
## Bluesky Jetstream
## AT Protokoll Jetstream
Um die Konnektivität mit Bluesky weiter zu verbessern, kann die „Jetstream“-Konnektivität aktiviert werden.
Jetstream ist ein Dienst, der sich mit dem Bluesky-Firehose verbindet.
Um die Konnektivität über das AT Protokoll weiter zu verbessern, kann die „Jetstream“-Konnektivität aktiviert werden.
Jetstream ist ein Dienst, der sich mit einer AT Protokoll-Firehose verbindet.
Mit Jetstream kommen die Nachrichten in Echtzeit an und müssen nicht erst abgefragt werden.
Es ermöglicht auch die Echtzeitverarbeitung von Blöcken oder Tracking-Aktivitäten, die über die Bluesky-Website oder -Anwendung durchgeführt werden.
Es ermöglicht auch die Echtzeitverarbeitung von Blöcken oder Tracking-Aktivitäten, die über eine AT Protokoll-Website oder -Anwendung durchgeführt werden.
Um die Jetstream-Verarbeitung zu aktivieren, führe `bin/console.php daemon' über die Befehlszeile aus.
Du musst vorher die Prozess-ID-Datei in local.config.php im Abschnitt „jetstream“ mit dem Schlüssel „pidfile“ definieren.

View file

@ -78,9 +78,9 @@ Alternativen werden durch "|" dargestellt.
* network:dspr | network:diaspora - Das Diaspora-Protokoll wird hauptsächlich von Diaspora selbst genutzt. Ein paar andere Systeme unterstützen dieses Protokoll ebenfalls, wie Hubzilla, Socialhome or Ganggo.
* network:feed - RSS/Atom feeds
* network:mail - Mails die via IMAP importiert worden sind.
* network:dscs | network:discourse - Beiträge, die über den Discourse connector empfangen werden.
* network:tmbl | network:tumblr - Beiträge, die über den Tumblr connector empfangen werden.
* network:bsky | network:bluesky - Beiträge, die über den Bluesky connector empfangen werden.
* network:dscs | network:discourse - Beiträge, die über den Discourse Connector empfangen werden.
* network:tmbl | network:tumblr - Beiträge, die über den Tumblr Connector empfangen werden.
* network:bsky | network:bluesky - Beiträge, die über den AT Protokoll Connector empfangen werden.
* platform - Benutze dies, um Plattformen in deinen Kanal einzuschließen, oder von ihm auszuschließen, d.h. "+platform:friendica". Im Falle eines Gruppen-Postings enthält der Suchtext beides, die Plattform des Gruppen-Servers und die Plattform des Autors.
* visibility - Du hast die Wahl zwischen verschiedenen Sichtbarkeiten. Du kannst nur die ungelisteten oder privaten Beiträge sehen, zu denen du Zugang hast.
* visibility:public - (öffentlich)

View file

@ -1,8 +1,8 @@
# Konnektoren (Connectors)
Konnektoren erlauben es Dir, Dich mit anderen sozialen Netzwerken zu verbinden.
Mit diesen Konnektoren kannst Du z.B. zu Bluesky, Tumblr oder Twitter posten.
Für Bluesky und Tumblr gibt es eine bidirektionale Verbindung, d.h. du kannst Friendica nutzen, um deine Timeline von diesen Diensten zu lesen.
Mit diesen Konnektoren kannst Du z.B. über das AT Protokoll mit Bluesky, Eurosky oder Blacksky verbinden oder zu Tumblr oder Twitter posten.
Für das AT Protokoll und Tumblr gibt es eine bidirektionale Verbindung, d.h. du kannst Friendica nutzen, um deine Timeline von diesen Diensten zu lesen.
Außerdem gibt es einen Konnektor, um Deinen Email-Posteingang zu nutzen.
Wenn Du keinen eigenen Knoten betreibst und wissen willst, ob der Server Deiner Wahl diese Konnektoren installiert hat, kannst Du Dich darüber auf der Seite '<domain_des_friendica-servers>/friendica' informieren.

View file

@ -1,6 +1,6 @@
# Installing Connectors
Friendica uses add-ons to connect to some networks, such as Tumblr or Bluesky.
Friendica uses add-ons to connect to some networks, such as Tumblr or AT Protocol based systems like Bluesky, Eurosky or Blacksky.
All of these add-ons require an account on the target network.
In addition, you (or usually the server administrator) will need to obtain an API key to allow authenticated access to your Friendica server.
@ -12,16 +12,16 @@ This is done through the site administration panel.
Some of the connectors also require an "API key" from the service you wish to connect to.
For Tumblr, this information can be found in the site administration pages, while for Twitter (X) each user has to create their own API key.
Other connectors, such as Bluesky, don't require an API key at all.
Other connectors, such as the AT Protocol, don't require an API key at all.
You can find more information about specific requirements on each addon's settings page, either on the admin page or the user page.
## Bluesky Jetstream
## AT Protocol Jetstream
To further improve connectivity to Bluesky, Admins can choose to enable 'Jetstream' connectivity.
Jetstream is a service that connects to the Bluesky firehose.
To further improve connectivity via the AT Protocol, Admins can choose to enable 'Jetstream' connectivity.
Jetstream is a service that connects to an AT Protocol firehose.
With Jetstream, messages arrive in real time rather than having to be polled.
It also enables real-time processing of blocks or tracking activities performed by the user via the Bluesky website or application.
It also enables real-time processing of blocks or tracking activities performed by the user via an AT Protocol website or application.
To enable Jetstream processing, run `bin/console.php jetstream' from the command line.
You will need to define the process id file in local.config.php in the 'jetstream' section using the key 'pidfile'.

View file

@ -601,7 +601,7 @@ Hook data:
### follow
Called before adding a new contact for a user to handle non-native network remote contact (like Bluesky).
Called before adding a new contact for a user to handle non-native network remote contact (like the AT Protocol).
Hook data:
@ -610,7 +610,7 @@ Hook data:
### unfollow
Called when unfollowing a remote contact on a non-native network (like Bluesky)
Called when unfollowing a remote contact on a non-native network (like the AT Protocol)
Hook data:
- **contact** (input): the target public contact (uid = 0) array.
@ -619,7 +619,7 @@ Hook data:
### revoke_follow
Called when making a remote contact on a non-native network (like Bluesky) unfollow you.
Called when making a remote contact on a non-native network (like the AT Protocol) unfollow you.
Hook data:
- **contact** (input): the target public contact (uid = 0) array.
@ -628,7 +628,7 @@ Hook data:
### block
Called when blocking a remote contact on a non-native network (like Bluesky).
Called when blocking a remote contact on a non-native network (like the AT Protocol).
Hook data:
- **contact** (input): the remote contact (uid = 0) array.
@ -637,7 +637,7 @@ Hook data:
### unblock
Called when unblocking a remote contact on a non-native network (like Bluesky).
Called when unblocking a remote contact on a non-native network (like the AT Protocol).
Hook data:
- **contact** (input): the remote contact (uid = 0) array.

View file

@ -80,7 +80,7 @@ Alternatives are presented with "|".
* network:mail - Mails that had been imported via IMAP.
* network:dscs | network:discourse - Posts that are received by the Discourse connector.
* network:tmbl | network:tumblr - Posts that are received by the Tumblr connector.
* network:bsky | network:bluesky - Posts that are received by the Bluesky connector.
* network:bsky | network:bluesky - Posts that are received by the AT Protocol connector.
* platform - Use this to include or exclude some platforms from your channel, e.g. "+platform:friendica". In the case of group postings, the search text contains both the platform of the group server and the author's platform.
* visibility - You have the choice between different visibilities. You can only see unlisted or private posts that you have the access for.
* visibility:public

View file

@ -1,8 +1,8 @@
# Connectors
Connectors allow you to connect with external social networks and services.
They are only required for posting to existing accounts on for example Bluesky, Tumblr or Twitter.
For Bluesky and Tumblr you can also enable a bidirectional synchronisation, so that you can use Friendica to read your timeline from Tumblr or Bluesky.
They are only required for posting to existing accounts on for example systems that use the AT Protocol (Bluesky, Eurosky, Blacksky), Tumblr or Twitter.
For the AT Protocol and Tumblr you can also enable a bidirectional synchronisation, so that you can use Friendica to read your timeline from Tumblr or an AT Protocol based system.
There is also a connector for accessing your email INBOX.
# Instructions For Connecting To People On Specific Services

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Bluesky Social https://commons.wikimedia.org/wiki/File:AT_Protocol_logo.png
SPDX-License-Identifier: CC-BY-4.0

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="27"
height="27"
fill="#000000"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<path
d="M13.482 27q-2.934 0-5.4-.972t-4.284-2.754A12.25 12.25 0 0 1 .99 19.116Q0 16.722 0 13.914q0-3.348 1.026-5.94 1.044-2.592 2.88-4.356A12.5 12.5 0 0 1 8.19.918Q10.638 0 13.428 0q3.42 0 5.994 1.062t4.284 2.88A11.7 11.7 0 0 1 26.244 8.1q.828 2.322.756 4.86-.09 3.492-1.458 5.364-1.368 1.854-4.248 1.854a5.84 5.84 0 0 1-2.826-.702 3.7 3.7 0 0 1-1.764-2.07l1.044.054q-.738 1.386-2.016 1.944a6.5 6.5 0 0 1-2.61.558q-1.818 0-3.204-.774a5.7 5.7 0 0 1-2.178-2.214q-.792-1.422-.792-3.294 0-1.926.828-3.33a5.77 5.77 0 0 1 2.232-2.196q1.404-.774 3.168-.774 1.17 0 2.43.486 1.278.486 1.98 1.368l-.738.936V7.884h2.412l-.054 6.462q0 1.386.54 2.088t1.602.702q.936 0 1.44-.522.522-.54.72-1.458a10.7 10.7 0 0 0 .252-2.106q.054-2.79-.828-4.698t-2.394-3.078a9.5 9.5 0 0 0-3.294-1.71q-1.8-.54-3.582-.54-2.52 0-4.482.81-1.962.792-3.312 2.25-1.332 1.44-2.016 3.42-.666 1.962-.63 4.32.072 2.34.846 4.212a9.3 9.3 0 0 0 2.16 3.204 9.4 9.4 0 0 0 3.276 2.034q1.89.702 4.14.702 1.26 0 2.502-.288 1.26-.27 2.304-.774l1.026 2.808q-1.386.648-2.916.954a14.7 14.7 0 0 1-3.078.324m-.144-10.098q1.278 0 2.142-.738t.864-2.502q0-1.602-.774-2.412-.756-.828-2.142-.828-1.638 0-2.448.864t-.81 2.376q0 1.548.828 2.394.846.846 2.34.846" />
<metadata>
<rdf:RDF>
<cc:License
rdf:about="http://creativecommons.org/licenses/by/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
<cc:Work
rdf:about="">
<cc:license
rdf:resource="http://creativecommons.org/licenses/by/4.0/" />
<dc:source>https://commons.wikimedia.org/wiki/File:AT_Protocol_Logo.svg</dc:source>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Derived from https://commons.wikimedia.org/wiki/File:AT_Protocol_Logo.svg
SPDX-License-Identifier: CC-BY-4.0

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.0620117 0.348442 87.9941 74.9653">
<path d="M41.9565 74.9643L24.0161 74.9653L41.9565 74.9643ZM63.8511 74.9653H45.9097L63.8501 74.9643V57.3286H63.8511V74.9653ZM45.9097 44.5893C45.9099 49.2737 49.7077 53.0707 54.3921 53.0707H63.8501V57.3286H54.3921C49.7077 57.3286 45.9099 61.1257 45.9097 65.81V74.9643H41.9565V65.81C41.9563 61.1258 38.1593 57.3287 33.4751 57.3286H24.0161V53.0707H33.4741C38.1587 53.0707 41.9565 49.2729 41.9565 44.5883V35.1303H45.9097V44.5893ZM63.8511 53.0707H63.8501V35.1303H63.8511V53.0707Z" fill="black"></path>
<path d="M52.7272 9.83198C49.4148 13.1445 49.4148 18.5151 52.7272 21.8275L59.4155 28.5158L56.4051 31.5262L49.7169 24.8379C46.4044 21.5254 41.0338 21.5254 37.7213 24.8379L31.2482 31.3111L28.4527 28.5156L34.9259 22.0424C38.2383 18.7299 38.2383 13.3594 34.9259 10.0469L28.2378 3.35883L31.2482 0.348442L37.9365 7.03672C41.2489 10.3492 46.6195 10.3492 49.932 7.03672L56.6203 0.348442L59.4155 3.14371L52.7272 9.83198Z" fill="black"/>
<path d="M24.3831 23.2335C23.1706 27.7584 25.8559 32.4095 30.3808 33.6219L39.5172 36.07L38.4154 40.182L29.2793 37.734C24.7544 36.5215 20.1033 39.2068 18.8909 43.7317L16.5215 52.5745L12.7028 51.5513L15.0721 42.7088C16.2846 38.1839 13.5993 33.5328 9.07434 32.3204L-0.0620117 29.8723L1.03987 25.76L10.1762 28.2081C14.7011 29.4206 19.3522 26.7352 20.5647 22.2103L23.0127 13.074L26.8311 14.0971L24.3831 23.2335Z" fill="black"/>
<path d="M67.3676 22.0297C68.5801 26.5546 73.2311 29.2399 77.756 28.0275L86.8923 25.5794L87.9941 29.6914L78.8578 32.1394C74.3329 33.3519 71.6476 38.003 72.86 42.5279L75.2294 51.3707L71.411 52.3938L69.0417 43.5513C67.8293 39.0264 63.1782 36.3411 58.6533 37.5535L49.5169 40.0016L48.415 35.8894L57.5514 33.4413C62.0763 32.2288 64.7616 27.5778 63.5492 23.0528L61.1011 13.9165L64.9195 12.8934L67.3676 22.0297Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: https://commons.wikimedia.org/wiki/File:Blacksky_Algorithms_Logo_(black).svg
SPDX-License-Identifier: CC0-1.0

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 461 510" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g transform="matrix(1,0,0,1,128.375,27.0069)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:black;stroke-width:0.67px;"/>
</g>
</g>
<g>
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:black;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,67.5926,221.36)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:black;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,374.121,172.829)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:black;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,202.563,-210.015)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:black;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,380.732,-81.8617)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:black;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(0.759576,-2.35171,0.365844,0.118164,-102.007,679.384)">
<path d="M120.752,330.756L173.954,330.756C173.714,340.398 173.702,350.374 173.944,360.361L121.004,360.361C121.159,350.66 121.083,340.686 120.752,330.756Z"/>
</g>
<g transform="matrix(0.759576,-2.35171,0.365844,0.118164,-102.007,679.384)">
<path d="M120.752,329.456L173.954,329.456C174.013,329.456 174.07,329.623 174.108,329.912C174.147,330.202 174.163,330.586 174.154,330.962C173.917,340.472 173.905,350.31 174.144,360.161C174.153,360.536 174.136,360.919 174.098,361.207C174.06,361.496 174.003,361.662 173.944,361.662L121.004,361.662C120.946,361.662 120.892,361.506 120.853,361.233C120.815,360.959 120.796,360.594 120.802,360.228C120.955,350.659 120.881,340.822 120.554,331.029C120.542,330.645 120.557,330.244 120.595,329.939C120.633,329.633 120.691,329.456 120.752,329.456ZM121,332.057C121.278,341.113 121.347,350.192 121.226,359.061C121.226,359.061 173.71,359.061 173.71,359.061C173.512,349.957 173.521,340.877 173.719,332.057L121,332.057Z"/>
</g>
<g transform="matrix(2.99645,1.73569,-0.192701,0.332673,-79.3508,-17.2321)">
<path d="M176.303,360.361L114.623,360.361C114.865,350.716 114.943,340.724 114.844,330.756L176.361,330.756C176.173,340.462 176.15,350.453 176.303,360.361Z"/>
</g>
<g transform="matrix(2.99645,1.73569,-0.192701,0.332673,-79.3508,-17.2321)">
<path d="M176.303,361.662L114.623,361.662C114.579,361.662 114.537,361.482 114.51,361.173C114.482,360.864 114.472,360.46 114.482,360.074C114.721,350.56 114.798,340.703 114.701,330.871C114.697,330.508 114.711,330.147 114.738,329.878C114.765,329.609 114.804,329.456 114.844,329.456L176.361,329.456C176.404,329.456 176.444,329.625 176.472,329.919C176.499,330.213 176.511,330.602 176.504,330.98C176.318,340.554 176.295,350.409 176.446,360.182C176.452,360.555 176.44,360.931 176.412,361.215C176.385,361.499 176.345,361.662 176.303,361.662ZM176.139,359.061C176.015,350.022 176.035,340.931 176.191,332.057C172.647,332.057 115.001,332.057 115.001,332.057C115.076,341.143 115.007,350.236 114.802,359.061L176.139,359.061Z"/>
</g>
<g transform="matrix(1.036,0.23923,-0.0865007,0.374596,-16.6075,98.5597)">
<path d="M117.078,330.756L172.417,330.756C171.947,340.383 172.006,350.349 172.66,360.361L117.606,360.361C117.983,350.708 117.828,340.743 117.078,330.756Z"/>
</g>
<g transform="matrix(1.036,0.23923,-0.0865007,0.374596,-16.6075,98.5597)">
<path d="M117.078,329.456L172.417,329.456C172.553,329.456 172.682,329.618 172.771,329.9C172.86,330.183 172.901,330.558 172.883,330.93C172.42,340.425 172.478,350.255 173.123,360.13C173.148,360.51 173.11,360.9 173.021,361.195C172.932,361.491 172.799,361.662 172.66,361.662L117.606,361.662C117.472,361.662 117.345,361.505 117.256,361.23C117.167,360.956 117.124,360.589 117.138,360.222C117.51,350.7 117.358,340.871 116.617,331.021C116.589,330.638 116.624,330.239 116.713,329.935C116.802,329.632 116.936,329.456 117.078,329.456ZM117.651,332.057C118.28,341.159 118.422,350.232 118.126,359.061C118.126,359.061 172.102,359.061 172.102,359.061C171.56,349.938 171.504,340.864 171.883,332.057L117.651,332.057Z"/>
</g>
<g transform="matrix(0.891455,-2.76001,0.365844,0.118164,-52.8187,518.104)">
<path d="M175.685,360.361L115.21,360.361C115.311,350.635 115.215,340.662 114.9,330.756L175.749,330.756C175.526,340.383 175.497,350.359 175.685,360.361Z"/>
</g>
<g transform="matrix(0.891455,-2.76001,0.365844,0.118164,-52.8187,518.104)">
<path d="M175.685,361.662L115.21,361.662C115.161,361.662 115.116,361.51 115.083,361.244C115.05,360.978 115.034,360.621 115.038,360.259C115.138,350.666 115.043,340.829 114.733,331.059C114.72,330.672 114.732,330.264 114.765,329.951C114.798,329.638 114.847,329.456 114.9,329.456L175.749,329.456C175.8,329.456 175.848,329.625 175.881,329.919C175.914,330.213 175.927,330.602 175.919,330.98C175.699,340.475 175.671,350.314 175.856,360.179C175.863,360.552 175.848,360.93 175.815,361.214C175.782,361.499 175.735,361.662 175.685,361.662ZM175.488,359.061C175.336,349.946 175.361,340.865 175.545,332.057C175.545,332.057 115.117,332.057 115.117,332.057C115.383,341.094 115.47,350.172 115.395,359.061L175.488,359.061Z"/>
</g>
<g transform="matrix(3.13548,-1.44294,0.160722,0.349247,-217.31,314.036)">
<path d="M174.941,360.361L115.971,360.361C116.331,350.612 116.506,340.628 116.509,330.756L175.008,330.756C174.811,340.66 174.793,350.644 174.941,360.361Z"/>
</g>
<g transform="matrix(3.13548,-1.44294,0.160722,0.349247,-217.31,314.036)">
<path d="M174.941,361.662L115.971,361.662C115.924,361.662 115.881,361.461 115.853,361.123C115.826,360.784 115.819,360.348 115.833,359.952C116.189,350.336 116.361,340.489 116.364,330.752C116.364,330.036 116.429,329.456 116.509,329.456L175.008,329.456C175.051,329.456 175.092,329.626 175.12,329.922C175.147,330.217 175.159,330.606 175.151,330.985C174.956,340.754 174.939,350.601 175.085,360.185C175.09,360.558 175.078,360.934 175.05,361.217C175.023,361.5 174.983,361.662 174.941,361.662ZM174.776,359.061C174.657,350.179 174.673,341.092 174.837,332.057C174.837,332.057 116.652,332.057 116.652,332.057C116.636,341.064 116.479,350.15 116.17,359.061L174.776,359.061Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: derived from https://github.com/eurosky-social/.github/blob/main/profile/logo.png by xje4@chaos.social
SPDX-License-Identifier: CC0-1.0

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="27"
height="27"
fill="#3b82f5"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<path
d="M13.482 27q-2.934 0-5.4-.972t-4.284-2.754A12.25 12.25 0 0 1 .99 19.116Q0 16.722 0 13.914q0-3.348 1.026-5.94 1.044-2.592 2.88-4.356A12.5 12.5 0 0 1 8.19.918Q10.638 0 13.428 0q3.42 0 5.994 1.062t4.284 2.88A11.7 11.7 0 0 1 26.244 8.1q.828 2.322.756 4.86-.09 3.492-1.458 5.364-1.368 1.854-4.248 1.854a5.84 5.84 0 0 1-2.826-.702 3.7 3.7 0 0 1-1.764-2.07l1.044.054q-.738 1.386-2.016 1.944a6.5 6.5 0 0 1-2.61.558q-1.818 0-3.204-.774a5.7 5.7 0 0 1-2.178-2.214q-.792-1.422-.792-3.294 0-1.926.828-3.33a5.77 5.77 0 0 1 2.232-2.196q1.404-.774 3.168-.774 1.17 0 2.43.486 1.278.486 1.98 1.368l-.738.936V7.884h2.412l-.054 6.462q0 1.386.54 2.088t1.602.702q.936 0 1.44-.522.522-.54.72-1.458a10.7 10.7 0 0 0 .252-2.106q.054-2.79-.828-4.698t-2.394-3.078a9.5 9.5 0 0 0-3.294-1.71q-1.8-.54-3.582-.54-2.52 0-4.482.81-1.962.792-3.312 2.25-1.332 1.44-2.016 3.42-.666 1.962-.63 4.32.072 2.34.846 4.212a9.3 9.3 0 0 0 2.16 3.204 9.4 9.4 0 0 0 3.276 2.034q1.89.702 4.14.702 1.26 0 2.502-.288 1.26-.27 2.304-.774l1.026 2.808q-1.386.648-2.916.954a14.7 14.7 0 0 1-3.078.324m-.144-10.098q1.278 0 2.142-.738t.864-2.502q0-1.602-.774-2.412-.756-.828-2.142-.828-1.638 0-2.448.864t-.81 2.376q0 1.548.828 2.394.846.846 2.34.846" />
<metadata>
<rdf:RDF>
<cc:License
rdf:about="http://creativecommons.org/licenses/by/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
<cc:Work
rdf:about="">
<cc:license
rdf:resource="http://creativecommons.org/licenses/by/4.0/" />
<dc:source>https://commons.wikimedia.org/wiki/File:AT_Protocol_Logo.svg</dc:source>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Derived from https://commons.wikimedia.org/wiki/File:AT_Protocol_Logo.svg
SPDX-License-Identifier: CC-BY-4.0

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 461 510" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g transform="matrix(1,0,0,1,128.375,27.0069)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:rgb(255,210,35);"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:rgb(255,195,35);stroke-width:0.67px;"/>
</g>
</g>
<g>
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:rgb(255,210,35);"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:rgb(255,195,35);stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,67.5926,221.36)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:rgb(255,210,35);"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:rgb(255,195,35);stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,374.121,172.829)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:rgb(255,210,35);"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:rgb(255,195,35);stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,202.563,-210.015)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:rgb(255,210,35);"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:rgb(255,195,35);stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,380.732,-81.8617)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:rgb(255,210,35);"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:rgb(255,195,35);stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(0.759576,-2.35171,0.365844,0.118164,-102.007,679.384)">
<path d="M120.752,330.756L173.954,330.756C173.714,340.398 173.702,350.374 173.944,360.361L121.004,360.361C121.159,350.66 121.083,340.686 120.752,330.756Z" style="fill:rgb(20,106,190);"/>
</g>
<g transform="matrix(0.759576,-2.35171,0.365844,0.118164,-102.007,679.384)">
<path d="M120.752,329.456L173.954,329.456C174.013,329.456 174.07,329.623 174.108,329.912C174.147,330.202 174.163,330.586 174.154,330.962C173.917,340.472 173.905,350.31 174.144,360.161C174.153,360.536 174.136,360.919 174.098,361.207C174.06,361.496 174.003,361.662 173.944,361.662L121.004,361.662C120.946,361.662 120.892,361.506 120.853,361.233C120.815,360.959 120.796,360.594 120.802,360.228C120.955,350.659 120.881,340.822 120.554,331.029C120.542,330.645 120.557,330.244 120.595,329.939C120.633,329.633 120.691,329.456 120.752,329.456ZM121,332.057C121.278,341.113 121.347,350.192 121.226,359.061C121.226,359.061 173.71,359.061 173.71,359.061C173.512,349.957 173.521,340.877 173.719,332.057L121,332.057Z" style="fill:rgb(5,75,159);"/>
</g>
<g transform="matrix(2.99645,1.73569,-0.192701,0.332673,-79.3508,-17.2321)">
<path d="M176.303,360.361L114.623,360.361C114.865,350.716 114.943,340.724 114.844,330.756L176.361,330.756C176.173,340.462 176.15,350.453 176.303,360.361Z" style="fill:rgb(20,106,190);"/>
</g>
<g transform="matrix(2.99645,1.73569,-0.192701,0.332673,-79.3508,-17.2321)">
<path d="M176.303,361.662L114.623,361.662C114.579,361.662 114.537,361.482 114.51,361.173C114.482,360.864 114.472,360.46 114.482,360.074C114.721,350.56 114.798,340.703 114.701,330.871C114.697,330.508 114.711,330.147 114.738,329.878C114.765,329.609 114.804,329.456 114.844,329.456L176.361,329.456C176.404,329.456 176.444,329.625 176.472,329.919C176.499,330.213 176.511,330.602 176.504,330.98C176.318,340.554 176.295,350.409 176.446,360.182C176.452,360.555 176.44,360.931 176.412,361.215C176.385,361.499 176.345,361.662 176.303,361.662ZM176.139,359.061C176.015,350.022 176.035,340.931 176.191,332.057C172.647,332.057 115.001,332.057 115.001,332.057C115.076,341.143 115.007,350.236 114.802,359.061L176.139,359.061Z" style="fill:rgb(5,75,159);"/>
</g>
<g transform="matrix(1.036,0.23923,-0.0865007,0.374596,-16.6075,98.5597)">
<path d="M117.078,330.756L172.417,330.756C171.947,340.383 172.006,350.349 172.66,360.361L117.606,360.361C117.983,350.708 117.828,340.743 117.078,330.756Z" style="fill:rgb(20,106,190);"/>
</g>
<g transform="matrix(1.036,0.23923,-0.0865007,0.374596,-16.6075,98.5597)">
<path d="M117.078,329.456L172.417,329.456C172.553,329.456 172.682,329.618 172.771,329.9C172.86,330.183 172.901,330.558 172.883,330.93C172.42,340.425 172.478,350.255 173.123,360.13C173.148,360.51 173.11,360.9 173.021,361.195C172.932,361.491 172.799,361.662 172.66,361.662L117.606,361.662C117.472,361.662 117.345,361.505 117.256,361.23C117.167,360.956 117.124,360.589 117.138,360.222C117.51,350.7 117.358,340.871 116.617,331.021C116.589,330.638 116.624,330.239 116.713,329.935C116.802,329.632 116.936,329.456 117.078,329.456ZM117.651,332.057C118.28,341.159 118.422,350.232 118.126,359.061C118.126,359.061 172.102,359.061 172.102,359.061C171.56,349.938 171.504,340.864 171.883,332.057L117.651,332.057Z" style="fill:rgb(5,75,159);"/>
</g>
<g transform="matrix(0.891455,-2.76001,0.365844,0.118164,-52.8187,518.104)">
<path d="M175.685,360.361L115.21,360.361C115.311,350.635 115.215,340.662 114.9,330.756L175.749,330.756C175.526,340.383 175.497,350.359 175.685,360.361Z" style="fill:rgb(20,106,190);"/>
</g>
<g transform="matrix(0.891455,-2.76001,0.365844,0.118164,-52.8187,518.104)">
<path d="M175.685,361.662L115.21,361.662C115.161,361.662 115.116,361.51 115.083,361.244C115.05,360.978 115.034,360.621 115.038,360.259C115.138,350.666 115.043,340.829 114.733,331.059C114.72,330.672 114.732,330.264 114.765,329.951C114.798,329.638 114.847,329.456 114.9,329.456L175.749,329.456C175.8,329.456 175.848,329.625 175.881,329.919C175.914,330.213 175.927,330.602 175.919,330.98C175.699,340.475 175.671,350.314 175.856,360.179C175.863,360.552 175.848,360.93 175.815,361.214C175.782,361.499 175.735,361.662 175.685,361.662ZM175.488,359.061C175.336,349.946 175.361,340.865 175.545,332.057C175.545,332.057 115.117,332.057 115.117,332.057C115.383,341.094 115.47,350.172 115.395,359.061L175.488,359.061Z" style="fill:rgb(5,75,159);"/>
</g>
<g transform="matrix(3.13548,-1.44294,0.160722,0.349247,-217.31,314.036)">
<path d="M174.941,360.361L115.971,360.361C116.331,350.612 116.506,340.628 116.509,330.756L175.008,330.756C174.811,340.66 174.793,350.644 174.941,360.361Z" style="fill:rgb(20,106,190);"/>
</g>
<g transform="matrix(3.13548,-1.44294,0.160722,0.349247,-217.31,314.036)">
<path d="M174.941,361.662L115.971,361.662C115.924,361.662 115.881,361.461 115.853,361.123C115.826,360.784 115.819,360.348 115.833,359.952C116.189,350.336 116.361,340.489 116.364,330.752C116.364,330.036 116.429,329.456 116.509,329.456L175.008,329.456C175.051,329.456 175.092,329.626 175.12,329.922C175.147,330.217 175.159,330.606 175.151,330.985C174.956,340.754 174.939,350.601 175.085,360.185C175.09,360.558 175.078,360.934 175.05,361.217C175.023,361.5 174.983,361.662 174.941,361.662ZM174.776,359.061C174.657,350.179 174.673,341.092 174.837,332.057C174.837,332.057 116.652,332.057 116.652,332.057C116.636,341.064 116.479,350.15 116.17,359.061L174.776,359.061Z" style="fill:rgb(5,75,159);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: derived from https://github.com/eurosky-social/.github/blob/main/profile/logo.png by xje4@chaos.social
SPDX-License-Identifier: CC0-1.0

View file

@ -0,0 +1,10 @@
<svg width="367" height="367" viewBox="0 0 367 367" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M169.298 279.39C167.548 277.86 166.519 275.67 166.439 273.35C166.359 271.02 167.249 268.77 168.899 267.13C180.289 255.72 204.238 231.77 204.238 231.77C207.398 228.6 211.689 226.83 216.169 226.83C221.159 226.83 225.348 228.76 228.508 231.92C231.668 235.08 233.448 239.64 233.448 244.11C233.448 248.58 231.668 252.87 228.508 256.04C231.418 253.12 235.799 251.1 240.439 251.1C245.069 251.1 249.348 252.76 252.818 256.24C256.388 259.8 257.718 264.34 257.718 268.38C257.718 272.85 255.938 277.14 252.778 280.3L251.899 281.18C233.779 299.24 195.758 302.8 169.298 279.39Z" fill="url(#paint0_linear_617_810)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.75 260.15L135.15 250.54C101.81 217.2 101.81 163.15 135.15 129.81L191.4 73.5595C194.62 70.3395 199.839 70.3395 203.059 73.5595L213.71 84.2094C246.47 116.969 246.47 170.09 213.71 202.85L156.409 260.15C153.189 263.37 147.97 263.37 144.75 260.15ZM172.279 163.17C181.789 172.68 197.209 172.68 206.719 163.17C216.229 153.66 216.229 138.239 206.719 128.729C197.209 119.219 181.789 119.219 172.279 128.729C162.769 138.239 162.769 153.66 172.279 163.17Z" fill="#3B364C"/>
<defs>
<linearGradient id="paint0_linear_617_810" x1="184.288" y1="357.541" x2="112.557" y2="271.225" gradientUnits="userSpaceOnUse">
<stop stop-color="#9A92FF"/>
<stop offset="1" stop-color="#8372F5"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Laura Hausmann. Created by Kilcat https://kilcat.carrd.co/. https://iceshrimp.dev/iceshrimp/branding/src/branch/dev/SVG/Default/Dark/Logo%20Default%20Dark.svg
SPDX-License-Identifier: CC-BY-4.0

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="27"
height="27"
fill="#ffffff"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<path
d="M13.482 27q-2.934 0-5.4-.972t-4.284-2.754A12.25 12.25 0 0 1 .99 19.116Q0 16.722 0 13.914q0-3.348 1.026-5.94 1.044-2.592 2.88-4.356A12.5 12.5 0 0 1 8.19.918Q10.638 0 13.428 0q3.42 0 5.994 1.062t4.284 2.88A11.7 11.7 0 0 1 26.244 8.1q.828 2.322.756 4.86-.09 3.492-1.458 5.364-1.368 1.854-4.248 1.854a5.84 5.84 0 0 1-2.826-.702 3.7 3.7 0 0 1-1.764-2.07l1.044.054q-.738 1.386-2.016 1.944a6.5 6.5 0 0 1-2.61.558q-1.818 0-3.204-.774a5.7 5.7 0 0 1-2.178-2.214q-.792-1.422-.792-3.294 0-1.926.828-3.33a5.77 5.77 0 0 1 2.232-2.196q1.404-.774 3.168-.774 1.17 0 2.43.486 1.278.486 1.98 1.368l-.738.936V7.884h2.412l-.054 6.462q0 1.386.54 2.088t1.602.702q.936 0 1.44-.522.522-.54.72-1.458a10.7 10.7 0 0 0 .252-2.106q.054-2.79-.828-4.698t-2.394-3.078a9.5 9.5 0 0 0-3.294-1.71q-1.8-.54-3.582-.54-2.52 0-4.482.81-1.962.792-3.312 2.25-1.332 1.44-2.016 3.42-.666 1.962-.63 4.32.072 2.34.846 4.212a9.3 9.3 0 0 0 2.16 3.204 9.4 9.4 0 0 0 3.276 2.034q1.89.702 4.14.702 1.26 0 2.502-.288 1.26-.27 2.304-.774l1.026 2.808q-1.386.648-2.916.954a14.7 14.7 0 0 1-3.078.324m-.144-10.098q1.278 0 2.142-.738t.864-2.502q0-1.602-.774-2.412-.756-.828-2.142-.828-1.638 0-2.448.864t-.81 2.376q0 1.548.828 2.394.846.846 2.34.846" />
<metadata>
<rdf:RDF>
<cc:License
rdf:about="http://creativecommons.org/licenses/by/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
<cc:Work
rdf:about="">
<cc:license
rdf:resource="http://creativecommons.org/licenses/by/4.0/" />
<dc:source>https://commons.wikimedia.org/wiki/File:AT_Protocol_Logo.svg</dc:source>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Derived from https://commons.wikimedia.org/wiki/File:AT_Protocol_Logo.svg
SPDX-License-Identifier: CC-BY-4.0

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.0620117 0.348442 87.9941 74.9653">
<path d="M41.9565 74.9643L24.0161 74.9653L41.9565 74.9643ZM63.8511 74.9653H45.9097L63.8501 74.9643V57.3286H63.8511V74.9653ZM45.9097 44.5893C45.9099 49.2737 49.7077 53.0707 54.3921 53.0707H63.8501V57.3286H54.3921C49.7077 57.3286 45.9099 61.1257 45.9097 65.81V74.9643H41.9565V65.81C41.9563 61.1258 38.1593 57.3287 33.4751 57.3286H24.0161V53.0707H33.4741C38.1587 53.0707 41.9565 49.2729 41.9565 44.5883V35.1303H45.9097V44.5893ZM63.8511 53.0707H63.8501V35.1303H63.8511V53.0707Z" fill="white"></path>
<path d="M52.7272 9.83198C49.4148 13.1445 49.4148 18.5151 52.7272 21.8275L59.4155 28.5158L56.4051 31.5262L49.7169 24.8379C46.4044 21.5254 41.0338 21.5254 37.7213 24.8379L31.2482 31.3111L28.4527 28.5156L34.9259 22.0424C38.2383 18.7299 38.2383 13.3594 34.9259 10.0469L28.2378 3.35883L31.2482 0.348442L37.9365 7.03672C41.2489 10.3492 46.6195 10.3492 49.932 7.03672L56.6203 0.348442L59.4155 3.14371L52.7272 9.83198Z" fill="white"/>
<path d="M24.3831 23.2335C23.1706 27.7584 25.8559 32.4095 30.3808 33.6219L39.5172 36.07L38.4154 40.182L29.2793 37.734C24.7544 36.5215 20.1033 39.2068 18.8909 43.7317L16.5215 52.5745L12.7028 51.5513L15.0721 42.7088C16.2846 38.1839 13.5993 33.5328 9.07434 32.3204L-0.0620117 29.8723L1.03987 25.76L10.1762 28.2081C14.7011 29.4206 19.3522 26.7352 20.5647 22.2103L23.0127 13.074L26.8311 14.0971L24.3831 23.2335Z" fill="white"/>
<path d="M67.3676 22.0297C68.5801 26.5546 73.2311 29.2399 77.756 28.0275L86.8923 25.5794L87.9941 29.6914L78.8578 32.1394C74.3329 33.3519 71.6476 38.003 72.86 42.5279L75.2294 51.3707L71.411 52.3938L69.0417 43.5513C67.8293 39.0264 63.1782 36.3411 58.6533 37.5535L49.5169 40.0016L48.415 35.8894L57.5514 33.4413C62.0763 32.2288 64.7616 27.5778 63.5492 23.0528L61.1011 13.9165L64.9195 12.8934L67.3676 22.0297Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: https://commons.wikimedia.org/wiki/File:Blacksky_Algorithms_Logo.svg
SPDX-License-Identifier: CC0-1.0

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 461 510" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g transform="matrix(1,0,0,1,128.375,27.0069)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:white;"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:white;stroke-width:0.67px;"/>
</g>
</g>
<g>
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:white;"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:white;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,67.5926,221.36)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:white;"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:white;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,374.121,172.829)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:white;"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:white;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,202.563,-210.015)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:white;"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:white;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,380.732,-81.8617)">
<g transform="matrix(1.53024,0,0,1.53024,-36.1411,-154.896)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:white;"/>
</g>
<g transform="matrix(1.49162,0,0,1.49162,-34.2351,-144.687)">
<circle cx="49.353" cy="264.366" r="23.985" style="fill:none;stroke:white;stroke-width:0.67px;"/>
</g>
</g>
<g transform="matrix(0.759576,-2.35171,0.365844,0.118164,-102.007,679.384)">
<path d="M120.752,330.756L173.954,330.756C173.714,340.398 173.702,350.374 173.944,360.361L121.004,360.361C121.159,350.66 121.083,340.686 120.752,330.756Z" style="fill:white;"/>
</g>
<g transform="matrix(0.759576,-2.35171,0.365844,0.118164,-102.007,679.384)">
<path d="M120.752,329.456L173.954,329.456C174.013,329.456 174.07,329.623 174.108,329.912C174.147,330.202 174.163,330.586 174.154,330.962C173.917,340.472 173.905,350.31 174.144,360.161C174.153,360.536 174.136,360.919 174.098,361.207C174.06,361.496 174.003,361.662 173.944,361.662L121.004,361.662C120.946,361.662 120.892,361.506 120.853,361.233C120.815,360.959 120.796,360.594 120.802,360.228C120.955,350.659 120.881,340.822 120.554,331.029C120.542,330.645 120.557,330.244 120.595,329.939C120.633,329.633 120.691,329.456 120.752,329.456ZM121,332.057C121.278,341.113 121.347,350.192 121.226,359.061C121.226,359.061 173.71,359.061 173.71,359.061C173.512,349.957 173.521,340.877 173.719,332.057L121,332.057Z" style="fill:white;"/>
</g>
<g transform="matrix(2.99645,1.73569,-0.192701,0.332673,-79.3508,-17.2321)">
<path d="M176.303,360.361L114.623,360.361C114.865,350.716 114.943,340.724 114.844,330.756L176.361,330.756C176.173,340.462 176.15,350.453 176.303,360.361Z" style="fill:white;"/>
</g>
<g transform="matrix(2.99645,1.73569,-0.192701,0.332673,-79.3508,-17.2321)">
<path d="M176.303,361.662L114.623,361.662C114.579,361.662 114.537,361.482 114.51,361.173C114.482,360.864 114.472,360.46 114.482,360.074C114.721,350.56 114.798,340.703 114.701,330.871C114.697,330.508 114.711,330.147 114.738,329.878C114.765,329.609 114.804,329.456 114.844,329.456L176.361,329.456C176.404,329.456 176.444,329.625 176.472,329.919C176.499,330.213 176.511,330.602 176.504,330.98C176.318,340.554 176.295,350.409 176.446,360.182C176.452,360.555 176.44,360.931 176.412,361.215C176.385,361.499 176.345,361.662 176.303,361.662ZM176.139,359.061C176.015,350.022 176.035,340.931 176.191,332.057C172.647,332.057 115.001,332.057 115.001,332.057C115.076,341.143 115.007,350.236 114.802,359.061L176.139,359.061Z" style="fill:white;"/>
</g>
<g transform="matrix(1.036,0.23923,-0.0865007,0.374596,-16.6075,98.5597)">
<path d="M117.078,330.756L172.417,330.756C171.947,340.383 172.006,350.349 172.66,360.361L117.606,360.361C117.983,350.708 117.828,340.743 117.078,330.756Z" style="fill:white;"/>
</g>
<g transform="matrix(1.036,0.23923,-0.0865007,0.374596,-16.6075,98.5597)">
<path d="M117.078,329.456L172.417,329.456C172.553,329.456 172.682,329.618 172.771,329.9C172.86,330.183 172.901,330.558 172.883,330.93C172.42,340.425 172.478,350.255 173.123,360.13C173.148,360.51 173.11,360.9 173.021,361.195C172.932,361.491 172.799,361.662 172.66,361.662L117.606,361.662C117.472,361.662 117.345,361.505 117.256,361.23C117.167,360.956 117.124,360.589 117.138,360.222C117.51,350.7 117.358,340.871 116.617,331.021C116.589,330.638 116.624,330.239 116.713,329.935C116.802,329.632 116.936,329.456 117.078,329.456ZM117.651,332.057C118.28,341.159 118.422,350.232 118.126,359.061C118.126,359.061 172.102,359.061 172.102,359.061C171.56,349.938 171.504,340.864 171.883,332.057L117.651,332.057Z" style="fill:white;"/>
</g>
<g transform="matrix(0.891455,-2.76001,0.365844,0.118164,-52.8187,518.104)">
<path d="M175.685,360.361L115.21,360.361C115.311,350.635 115.215,340.662 114.9,330.756L175.749,330.756C175.526,340.383 175.497,350.359 175.685,360.361Z" style="fill:white;"/>
</g>
<g transform="matrix(0.891455,-2.76001,0.365844,0.118164,-52.8187,518.104)">
<path d="M175.685,361.662L115.21,361.662C115.161,361.662 115.116,361.51 115.083,361.244C115.05,360.978 115.034,360.621 115.038,360.259C115.138,350.666 115.043,340.829 114.733,331.059C114.72,330.672 114.732,330.264 114.765,329.951C114.798,329.638 114.847,329.456 114.9,329.456L175.749,329.456C175.8,329.456 175.848,329.625 175.881,329.919C175.914,330.213 175.927,330.602 175.919,330.98C175.699,340.475 175.671,350.314 175.856,360.179C175.863,360.552 175.848,360.93 175.815,361.214C175.782,361.499 175.735,361.662 175.685,361.662ZM175.488,359.061C175.336,349.946 175.361,340.865 175.545,332.057C175.545,332.057 115.117,332.057 115.117,332.057C115.383,341.094 115.47,350.172 115.395,359.061L175.488,359.061Z" style="fill:white;"/>
</g>
<g transform="matrix(3.13548,-1.44294,0.160722,0.349247,-217.31,314.036)">
<path d="M174.941,360.361L115.971,360.361C116.331,350.612 116.506,340.628 116.509,330.756L175.008,330.756C174.811,340.66 174.793,350.644 174.941,360.361Z" style="fill:white;"/>
</g>
<g transform="matrix(3.13548,-1.44294,0.160722,0.349247,-217.31,314.036)">
<path d="M174.941,361.662L115.971,361.662C115.924,361.662 115.881,361.461 115.853,361.123C115.826,360.784 115.819,360.348 115.833,359.952C116.189,350.336 116.361,340.489 116.364,330.752C116.364,330.036 116.429,329.456 116.509,329.456L175.008,329.456C175.051,329.456 175.092,329.626 175.12,329.922C175.147,330.217 175.159,330.606 175.151,330.985C174.956,340.754 174.939,350.601 175.085,360.185C175.09,360.558 175.078,360.934 175.05,361.217C175.023,361.5 174.983,361.662 174.941,361.662ZM174.776,359.061C174.657,350.179 174.673,341.092 174.837,332.057C174.837,332.057 116.652,332.057 116.652,332.057C116.636,341.064 116.479,350.15 116.17,359.061L174.776,359.061Z" style="fill:white;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: derived from https://github.com/eurosky-social/.github/blob/main/profile/logo.png by xje4@chaos.social
SPDX-License-Identifier: CC0-1.0

View file

@ -93,7 +93,7 @@ HELP;
'jetstream' => [
'pidfile' => '/path/to/jetstream.pid',
],
TXT
TXT,
);
}
@ -101,7 +101,7 @@ HELP;
Hook::loadHooks();
if (!$this->addonHelper->isAddonEnabled('bluesky')) {
throw new RuntimeException("Bluesky has to be enabled.\n");
throw new RuntimeException("the AT Protocol addon has to be enabled.\n");
}
$pidfile = $this->config->get('jetstream', 'pidfile');

View file

@ -18,11 +18,11 @@ use Friendica\Util\Strings;
*/
class ContactSelector
{
const SVG_DISABLED = -1;
const SVG_COLOR_BLACK = 0;
const SVG_BLACK = 1;
const SVG_COLOR_WHITE = 2;
const SVG_WHITE = 3;
public const SVG_DISABLED = -1;
public const SVG_COLOR_BLACK = 0;
public const SVG_BLACK = 1;
public const SVG_COLOR_WHITE = 2;
public const SVG_WHITE = 3;
public static $serverdata = [];
public static $server_id = [];
@ -44,7 +44,7 @@ class ContactSelector
2 => DI::l10n()->t('Twice daily'),
3 => DI::l10n()->t('Daily'),
4 => DI::l10n()->t('Weekly'),
5 => DI::l10n()->t('Monthly')
5 => DI::l10n()->t('Monthly'),
];
foreach ($rep as $k => $v) {
@ -111,7 +111,7 @@ class ContactSelector
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function networkToName(string $network, string $protocol = '', int $gsid = null): string
public static function networkToName(string $network, string $protocol = '', ?int $gsid = null): string
{
$eventDispatcher = DI::eventDispatcher();
@ -134,7 +134,7 @@ class ContactSelector
Protocol::ACTIVITYPUB => DI::l10n()->t('ActivityPub'),
Protocol::PNUT => DI::l10n()->t('pnut'),
Protocol::TUMBLR => DI::l10n()->t('Tumblr'),
Protocol::BLUESKY => DI::l10n()->t('Bluesky'),
Protocol::ATPROTO => DI::l10n()->t('AT Protocol'),
];
$nets = $eventDispatcher->dispatch(
@ -159,6 +159,11 @@ class ContactSelector
if ($platform !== '') {
$networkname = $platform;
}
} elseif ($network === Protocol::ATPROTO && !empty($gsid)) {
$gserver = self::getServerForId($gsid);
if (isset($gserver['url'])) {
$networkname = self::getAtProviderName($gserver['url']);
}
}
if (!empty($protocol) && ($protocol != $network) && $network != Protocol::DFRN) {
@ -184,7 +189,7 @@ class ContactSelector
* @param integer $uid
* @return string
*/
public static function networkToSVG(string $network, int $gsid = null, string $platform = '', int $uid = 0): string
public static function networkToSVG(string $network, ?int $gsid = null, string $platform = '', int $uid = 0): string
{
$platform_icon_style = $uid ? (DI::pConfig()->get($uid, 'accessibility', 'platform_icon_style') ?? self::SVG_COLOR_BLACK) : self::SVG_COLOR_BLACK;
@ -194,7 +199,7 @@ class ContactSelector
$nets = [
Protocol::ACTIVITYPUB => 'activitypub', // https://commons.wikimedia.org/wiki/File:ActivityPub-logo-symbol.svg
Protocol::BLUESKY => 'bluesky', // https://commons.wikimedia.org/wiki/File:Bluesky_Logo.svg
Protocol::ATPROTO => 'atprotocol', // https://freesvg.org/at-sign-symbol
Protocol::DFRN => 'friendica',
Protocol::DIASPORA => 'diaspora', // https://www.svgrepo.com/svg/362315/diaspora
Protocol::DIASPORA2 => 'diaspora', // https://www.svgrepo.com/svg/362315/diaspora
@ -218,12 +223,15 @@ class ContactSelector
if (in_array($network, Protocol::FEDERATED) && !empty($gsid)) {
$gserver = self::getServerForId($gsid);
$platform = $gserver['platform'];
} elseif ($network === Protocol::ATPROTO && !empty($gsid)) {
$gserver = self::getServerForId($gsid);
$platform = self::getAtProtoProvider($gserver['url']);
}
$svg = ['aardwolf', 'activitypods', 'activitypub', 'akkoma', 'anfora', 'awakari', 'azorius',
'bluesky', 'bonfire', 'bookwyrm', 'bridgy_fed', 'brighteon_social', 'brutalinks', 'calckey',
$svg = ['aardwolf', 'activitypods', 'activitypub', 'akkoma', 'anfora', 'atprotocol', 'awakari', 'azorius',
'blacksky', 'bluesky', 'bonfire', 'bookwyrm', 'bridgy_fed', 'brighteon_social', 'brutalinks', 'calckey',
'castopod', 'catodon', 'chatter_net', 'chuckya', 'clubsall', 'communecter', 'decodon',
'diaspora', 'discourse', 'dolphin', 'drupal', 'email', 'emissary', 'epicyon', 'f2ap',
'diaspora', 'discourse', 'dolphin', 'drupal', 'email', 'emissary', 'epicyon', 'eurosky', 'f2ap',
'fedibird', 'fedify', 'firefish', 'flipboard', 'flohmarkt', 'forgefriends', 'forgejo',
'forte', 'foundkey', 'friendica', 'funkwhale', 'gancio', 'gath.io', 'ghost', 'gitlab',
'glitch-soc', 'glitchsoc', 'gnu_social', 'gnusocial', 'goblin', 'go-fed', 'gotosocial',
@ -238,8 +246,8 @@ class ContactSelector
'wildebeest', 'wordpress', 'write.as', 'writefreely', 'wxwclub', 'xwiki', 'zap'];
if (in_array($platform_icon_style, [self::SVG_WHITE, self::SVG_COLOR_WHITE])) {
$svg = ['activitypub', 'akkoma', 'andstatus', 'bluesky', 'bonfire', 'bookwyrm', 'bridgy_fed',
'calckey', 'castopod', 'diaspora', 'discourse', 'dolphin', 'drupal', 'email', 'firefish',
$svg = ['activitypub', 'akkoma', 'andstatus', 'atprotocol', 'blacksky', 'bluesky', 'bonfire', 'bookwyrm', 'bridgy_fed',
'calckey', 'castopod', 'diaspora', 'discourse', 'dolphin', 'drupal', 'email', 'eurosky', 'firefish',
'flipboard', 'flohmarkt', 'forgejo', 'friendica', 'funkwhale', 'ghost', 'gitlab',
'glitch-soc', 'gnusocial', 'gotosocial', 'guppe', 'hollo', 'hubzilla', 'iceshrimp', 'kbin',
'lemmy', 'loforo', 'loops', 'mastodon', 'mbin', 'microblog', 'minds', 'misskey', 'mobilizon',
@ -273,8 +281,8 @@ class ContactSelector
return '';
}
$color = ['aardwolf', 'activitypods', 'activitypub', 'akkoma', 'bluesky', 'chuckya', 'decodon',
'discourse', 'fedify', 'firefish', 'flipboard', 'friendica', 'gitlab', 'gnusocial', 'kookie',
$color = ['aardwolf', 'activitypods', 'activitypub', 'akkoma', 'atprotocol', 'bluesky', 'chuckya', 'decodon',
'discourse', 'eurosky', 'fedify', 'firefish', 'flipboard', 'friendica', 'gitlab', 'gnusocial', 'iceshrimp', 'kookie',
'loops', 'mastodon', 'mbin', 'misskey', 'neodb', 'newsmast', 'nodebb', 'peertube', 'pixelfed',
'pleroma', 'rss', 'sharky', 'tumblr', 'vervis', 'vocata', 'wordpress'];
@ -286,4 +294,35 @@ class ContactSelector
return 'images/platforms/black/' . $network_svg . '.svg';
}
}
private static function getAtProtoProvider(string $pds): string
{
$host = parse_url($pds, PHP_URL_HOST);
if (str_ends_with($host, '.host.bsky.network')) {
return 'bluesky';
} elseif ($host === 'fed.brid.gy') {
return 'bridgy_fed';
} elseif ($host === 'eurosky.social') {
return 'eurosky';
} elseif ($host === 'blacksky.app') {
return 'blacksky';
}
return 'atprotocol';
}
private static function getAtProviderName(string $pds): string
{
switch (self::getAtProtoProvider($pds)) {
case 'bluesky':
return 'Bluesky';
case 'bridgy_fed':
return 'Bridgy Fed';
case 'eurosky':
return 'Eurosky';
case 'blacksky':
return 'Blacksky';
default:
return parse_url($pds, PHP_URL_HOST);
}
}
}

View file

@ -72,12 +72,13 @@ class GroupManager
while ($contact = DBA::fetch($contacts)) {
$groupList[] = [
'url' => $contact['url'],
'alias' => $contact['alias'],
'name' => $contact['name'],
'id' => $contact['id'],
'micro' => $contact['micro'],
'thumb' => $contact['thumb'],
'url' => $contact['url'],
'alias' => $contact['alias'],
'name' => $contact['name'],
'id' => $contact['id'],
'micro' => $contact['micro'],
'thumb' => $contact['thumb'],
'network' => $contact['network'],
];
}
DBA::close($contacts);
@ -208,7 +209,7 @@ class GroupManager
Protocol::DFRN,
Protocol::ACTIVITYPUB,
Contact::TYPE_COMMUNITY,
DI::userSession()->getLocalUserId()
DI::userSession()->getLocalUserId(),
);
return DBA::toArray($stmtContacts);

View file

@ -711,7 +711,7 @@ class Item
// If it is a reshared post then reformat it to avoid display problems with two share elements
if (!empty($shared)) {
if (($item['network'] != Protocol::BLUESKY) && !empty($shared['guid']) && ($encapsulated_share = $this->createSharedPostByGuid($shared['guid'], true))) {
if (($item['network'] != Protocol::ATPROTO) && !empty($shared['guid']) && ($encapsulated_share = $this->createSharedPostByGuid($shared['guid'], true))) {
if (!empty(BBCode::fetchShareAttributes($item['body']))) {
$item['body'] = preg_replace("/\[share.*?\](.*)\[\/share\]/ism", $encapsulated_share, $item['body']);
} else {

View file

@ -70,11 +70,18 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
$row['embed-width'],
$row['embed-height'],
$row['page-type'],
$row['schematypes'] ? json_decode($row['schematypes'], true) : null
$row['schematypes'] ? json_decode($row['schematypes'], true) : null,
);
}
public function createFromBlueskyImageEmbed(int $uriId, stdClass $image): PostMediaEntity
/**
* Create a PostMedia entity from an image element
*
* @param integer $uriId
* @param stdClass $image
* @return PostMediaEntity
*/
public function createFromATProtocolImageEmbed(int $uriId, stdClass $image): PostMediaEntity
{
return new PostMediaEntity(
$uriId,
@ -92,8 +99,14 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
);
}
public function createFromBlueskyExternalEmbed(int $uriId, stdClass $external): PostMediaEntity
/**
* Create a PostMedia entity from an external embed element
*
* @param integer $uriId
* @param stdClass $external
* @return PostMediaEntity
*/
public function createFromATProtocolExternalEmbed(int $uriId, stdClass $external): PostMediaEntity
{
return new PostMediaEntity(
$uriId,
@ -108,10 +121,18 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
null,
null,
$external->description,
$external->title
$external->title,
);
}
/**
* Create a PostMedia entity from an attachment array
*
* @param array $attachment
* @param integer $uriId
* @param integer $id
* @return PostMediaEntity
*/
public function createFromAttachment(array $attachment, int $uriId = 0, int $id = 0)
{
$row = [
@ -163,6 +184,14 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
return $this->createFromTableRow($row);
}
/**
* Create a PostMedia entity from parsed URL data
*
* @param array $data
* @param integer $uriId
* @param integer $id
* @return PostMediaEntity
*/
public function createFromParseUrl(array $data, int $uriId = 0, int $id = 0)
{
$row = [

View file

@ -48,7 +48,9 @@ class BBCode
public const TWITTER = 8;
public const BACKLINK = 8;
public const ACTIVITYPUB = 9;
public const BLUESKY = 10;
public const ATPROTOCOL = 10;
/** @deprecated use @see ATPROTOCOL instead */
public const BLUESKY = 10; // @deprecated
public const SHARED_ANCHOR = '<hr class="shared-anchor">';
public const TOP_ANCHOR = '<br class="top-anchor">';
@ -1999,7 +2001,7 @@ class BBCode
$text,
);
if (in_array($simple_html, [self::TWITTER, self::BLUESKY])) {
if (in_array($simple_html, [self::TWITTER, self::ATPROTOCOL])) {
$text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", [self::class, 'expandLinksCallback'], $text);
//$text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $text);
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]', $text);

View file

@ -122,8 +122,8 @@ class Plaintext
$abstract = BBCode::getAbstract($item['body'], Protocol::TWITTER);
break;
case BBCode::BLUESKY:
$abstract = BBCode::getAbstract($item['body'], Protocol::BLUESKY);
case BBCode::ATPROTOCOL:
$abstract = BBCode::getAbstract($item['body'], Protocol::ATPROTO);
break;
default: // We don't know the exact target.

View file

@ -39,7 +39,7 @@ class VCard
$contact_url = Contact::getProfileLink($contact);
if ($contact['network'] != '') {
$network_link = Strings::formatNetworkName($contact['network'], $contact_url);
$network_link = Strings::formatNetworkName($contact['network'], $contact_url, $contact['gsid']);
$network_svg = ContactSelector::networkToSVG($contact['network'], $contact['gsid'], '', DI::userSession()->getLocalUserId());
} else {
$network_link = '';

View file

@ -35,7 +35,9 @@ class Protocol
public const SUPPORT_PRIVATE = [self::DFRN, self::DIASPORA, self::MAIL, self::ACTIVITYPUB, self::PUMPIO];
// Supported through a connector
public const BLUESKY = 'bsky'; // Bluesky
/** @deprecated use @see ATPROTO instead */
public const BLUESKY = 'bsky'; // @deprecated name for the AT Protocol
public const ATPROTO = 'bsky'; // AT Protocol, short name "atproto" (Formerly known as Bluesky)
public const DIASPORA2 = 'dspc'; // Diaspora connector
public const DISCOURSE = 'dscs'; // Discourse
public const PNUT = 'pnut'; // pnut.io
@ -334,7 +336,7 @@ class Protocol
return false;
}
if (in_array($protocol, array_merge(self::NATIVE_SUPPORT, [self::ZOT, self::BLUESKY, self::PHANTOM]))) {
if (in_array($protocol, array_merge(self::NATIVE_SUPPORT, [self::ZOT, self::ATPROTO, self::PHANTOM]))) {
return true;
}

View file

@ -29,6 +29,7 @@ use Friendica\Network\Probe;
use Friendica\Object\Image;
use Friendica\Protocol\Activity;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\ATProtocol;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Images;
@ -3592,13 +3593,23 @@ class Contact
}
/**
* Return the link to the profile
* Return the profile link for a given contact array
*
* @param array $contact
* @return string
* Returns the alias if it is set, otherwise returns the URL
*
* @param array $contact Contact record
* @return string The profile link (alias or url)
*/
public static function getProfileLink(array $contact): string
{
if ($contact['network'] === Protocol::ATPROTO) {
$web = DI::atProtocol()->getWebForUser(DI::userSession()->getLocalUserId());
if ($web) {
return str_replace(ATProtocol::WEB, rtrim($web, '/'), $contact['alias']);
} else {
return $contact['alias'];
}
}
if (!empty($contact['alias']) && Network::isValidHttpUrl($contact['alias']) && (($contact['network'] ?? '') != Protocol::DFRN)) {
return $contact['alias'];
} else {

View file

@ -70,6 +70,7 @@ class GServer
public const DETECT_V1_CONFIG = 18;
public const DETECT_SYSTEM_ACTOR = 20; // Mistpark, Osada, Roadhouse, Zap
public const DETECT_THREADS = 21;
public const DETECT_ATPROTO_PDS = 22;
// Standardized endpoints
public const DETECT_STATISTICS_JSON = 100;
@ -714,6 +715,10 @@ class GServer
$serverdata['platform'] = 'threads';
}
if ($serverdata['network'] === Protocol::PHANTOM) {
$serverdata = self::detectATProto($url, $serverdata);
}
// All following checks are done for systems that always have got a "host-meta" endpoint.
// With this check we don't have to waste time and resources for dead systems.
// Also this hopefully prevents us from receiving abuse messages.
@ -1643,6 +1648,24 @@ class GServer
return $serverdata;
}
private static function detectATProto(string $url, array $serverdata): array
{
$data = DI::atProtocol()->get($url . '/xrpc/com.atproto.server.describeServer');
if (!isset($data->did)) {
return $serverdata;
}
$serverdata['detection-method'] = self::DETECT_ATPROTO_PDS;
$serverdata['network'] = Protocol::ATPROTO;
$serverdata['platform'] = 'atproto';
if (isset($data->inviteCodeRequired)) {
$serverdata['register_policy'] = Register::APPROVE;
}
return $serverdata;
}
/**
* Fetches server data via an ActivityPub account with url of that server
*

View file

@ -30,6 +30,7 @@ use Friendica\Network\HTTPException\ServiceUnavailableException;
use Friendica\Protocol\Activity;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\ActivityPub\Processor;
use Friendica\Protocol\ATProtocol;
use Friendica\Protocol\Delivery;
use Friendica\Protocol\Diaspora;
use Friendica\Util\DateTimeFormat;
@ -1539,7 +1540,7 @@ class Item
}
}
if (($source_uid == 0) && (($item['private'] == self::PRIVATE) || !in_array($item['network'], array_merge(Protocol::FEDERATED, [Protocol::BLUESKY])))) {
if (($source_uid == 0) && (($item['private'] == self::PRIVATE) || !in_array($item['network'], array_merge(Protocol::FEDERATED, [Protocol::ATPROTO])))) {
DI::logger()->notice('Item is private or not from a federated network. It will not be stored for the user.', ['uri-id' => $uri_id, 'uid' => $uid, 'private' => $item['private'], 'network' => $item['network']]);
return 0;
}
@ -3713,7 +3714,14 @@ class Item
*/
public static function getPlink(array $item)
{
if (!empty($item['plink']) && Network::isValidHttpUrl($item['plink'])) {
if ($item['network'] === Protocol::ATPROTO) {
$web = DI::atProtocol()->getWebForUser(DI::userSession()->getLocalUserId());
if ($web) {
$plink = str_replace(ATProtocol::WEB, rtrim($web, '/'), $item['plink']);
} else {
$plink = $item['plink'];
}
} elseif (!empty($item['plink']) && Network::isValidHttpUrl($item['plink'])) {
$plink = $item['plink'];
} elseif (!empty($item['uri']) && Network::isValidHttpUrl($item['uri']) && !DI::baseUrl()->isLocalUrl($item['uri'])) {
$plink = $item['uri'];

View file

@ -103,10 +103,15 @@ class Nodeinfo
'outbound' => [],
];
if ($addonHelper->isAddonEnabled('bluesky')) {
$services['inbound'][] = 'bluesky';
$services['outbound'][] = 'bluesky';
}
// @see discussion at https://github.com/jhass/nodeinfo/issues/96#issuecomment-3240296214
// Currently disabled, since this currently isn't covered by the Nodeinfo specification
//if ($addonHelper->isAddonEnabled('bluesky')) {
// $services['inbound'][] = 'atproto-pds';
// $services['outbound'][] = 'atproto-pds';
// if (!is_null(DI::config()->get('jetstream', 'pidfile'))) {
// $services['inbound'][] = 'atproto-jetstream';
// }
//}
if ($addonHelper->isAddonEnabled('dwpost')) {
$services['outbound'][] = 'dreamwidth';
}
@ -131,6 +136,8 @@ class Nodeinfo
$services['outbound'][] = 'smtp';
if ($addonHelper->isAddonEnabled('tumblr')) {
// Currently disabled, since this currently isn't covered by the Nodeinfo specification
// $services['inbound'][] = 'tumblr';
$services['outbound'][] = 'tumblr';
}
if ($addonHelper->isAddonEnabled('twitter')) {

View file

@ -24,19 +24,20 @@ use Friendica\Util\DateTimeFormat;
class Engagement
{
const KEYWORDS = ['source', 'server', 'from', 'to', 'group', 'application', 'tag', 'network', 'platform', 'visibility', 'language', 'media'];
const SHORTCUTS = ['lang' => 'language', 'net' => 'network', 'relay' => 'application'];
const ALTERNATIVES = ['source:news' => 'source:service', 'source:relay' => 'source:application',
'media:picture' => 'media:image', 'media:photo' => 'media:image',
'network:activitypub' => 'network:apub', 'network:friendica' => 'network:dfrn',
'network:diaspora' => 'network:dspr', 'network:discourse' => 'network:dscs',
'network:tumblr' => 'network:tmbl', 'network:bluesky' => 'network:bsky'];
const MEDIA_NONE = 0;
const MEDIA_IMAGE = 1;
const MEDIA_VIDEO = 2;
const MEDIA_AUDIO = 4;
const MEDIA_CARD = 8;
const MEDIA_POST = 16;
public const KEYWORDS = ['source', 'server', 'from', 'to', 'group', 'application', 'tag', 'network', 'platform', 'visibility', 'language', 'media'];
public const SHORTCUTS = ['lang' => 'language', 'net' => 'network', 'relay' => 'application'];
public const ALTERNATIVES = ['source:news' => 'source:service', 'source:relay' => 'source:application',
'media:picture' => 'media:image', 'media:photo' => 'media:image',
'network:activitypub' => 'network:apub', 'network:friendica' => 'network:dfrn',
'network:diaspora' => 'network:dspr', 'network:discourse' => 'network:dscs',
'network:tumblr' => 'network:tmbl', 'network:bluesky' => 'network:bsky',
'network:atprotocol' => 'network:bsky', 'network:atproto' => 'network:bsky'];
public const MEDIA_NONE = 0;
public const MEDIA_IMAGE = 1;
public const MEDIA_VIDEO = 2;
public const MEDIA_AUDIO = 4;
public const MEDIA_CARD = 8;
public const MEDIA_POST = 16;
/**
* Store engagement data from an item array
@ -56,7 +57,7 @@ class Engagement
'contact-contact-type', 'network', 'title', 'content-warning', 'body', 'language',
'author-id', 'author-contact-type', 'author-nick', 'author-addr', 'author-gsid',
'owner-id', 'owner-contact-type', 'owner-nick', 'owner-addr', 'owner-gsid'],
['uri-id' => $item['parent-uri-id']]
['uri-id' => $item['parent-uri-id']],
);
if ($parent['created'] < self::getCreationDateLimit(false)) {
@ -112,7 +113,7 @@ class Engagement
'activities' => DBA::count('post', [
"`parent-uri-id` = ? AND `gravity` = ? AND NOT `vid` IN (?, ?, ?)",
$item['parent-uri-id'], Item::GRAVITY_ACTIVITY,
Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ)
Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ),
]),
];
if (!$store && ($engagement['comments'] == 0) && ($engagement['activities'] == 0)) {
@ -319,7 +320,7 @@ class Engagement
{
$result = Post::selectPosts(
['author-addr', 'author-nick', 'author-contact-type'],
['thr-parent-id' => $uri_id, 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => Activity::ANNOUNCE, 'author-contact-type' => [Contact::TYPE_RELAY, Contact::TYPE_COMMUNITY]]
['thr-parent-id' => $uri_id, 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => Activity::ANNOUNCE, 'author-contact-type' => [Contact::TYPE_RELAY, Contact::TYPE_COMMUNITY]],
);
while ($reshare = Post::fetch($result)) {
$prefix = '';

View file

@ -26,7 +26,6 @@ use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Object\Image;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\ATProtocol;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Images;
use Friendica\Util\Network;
@ -309,10 +308,6 @@ class Media
return false;
}
if (Strings::compareLink($baseurl, ATProtocol::WEB)) {
return true;
}
return DBA::exists('gserver', ['nurl' => Strings::normaliseLink($baseurl), 'network' => Protocol::FEDERATED]);
} catch (\Throwable $e) {
DI::logger()->notice('Invalid URL provided', ['url' => $url, 'exception' => $e, 'callstack' => System::callstack(10)]);
@ -474,6 +469,7 @@ class Media
private static function addPage(array $media): array
{
$data = ParseUrl::getSiteinfoCached($media['url'], $media['mimetype'] ?? '');
// @todo Add detected AT Protocol activities and accounts here
if (empty($data['images'][0]['src']) && empty($data['text']) && empty($data['title'])) {
if (!empty($media['preview'])) {
$media = self::addPreviewData($media);

View file

@ -37,10 +37,10 @@ class Reblog extends BaseApi
if ($item['network'] == Protocol::DIASPORA) {
Diaspora::performReshare($this->parameters['id'], $uid);
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::BLUESKY, Protocol::TUMBLR, Protocol::TWITTER])) {
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::ATPROTO, Protocol::TUMBLR, Protocol::TWITTER])) {
$this->logAndJsonError(
422,
$this->errorFactory->UnprocessableEntity($this->t("Posts from %s can't be shared", ContactSelector::networkToName($item['network'])))
$this->errorFactory->UnprocessableEntity($this->t("Posts from %s can't be shared", ContactSelector::networkToName($item['network']))),
);
} else {
Item::performActivity($item['id'], 'announce', $uid);

View file

@ -43,10 +43,10 @@ class Unreblog extends BaseApi
if (!Item::markForDeletionById($item['id'])) {
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
}
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::BLUESKY, Protocol::TUMBLR, Protocol::TWITTER])) {
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::ATPROTO, Protocol::TUMBLR, Protocol::TWITTER])) {
$this->logAndJsonError(
422,
$this->errorFactory->UnprocessableEntity($this->t("Posts from %s can't be unshared", ContactSelector::networkToName($item['network'])))
$this->errorFactory->UnprocessableEntity($this->t("Posts from %s can't be unshared", ContactSelector::networkToName($item['network']))),
);
} else {
Item::performActivity($item['id'], 'unannounce', $uid);

View file

@ -40,7 +40,7 @@ class Retweet extends BaseApi
$item = Post::selectFirst($fields, ['uri-id' => $id, 'uid' => [0, $uid], 'private' => [Item::PUBLIC, Item::UNLISTED]], ['order' => ['uid' => true]]);
if (DBA::isResult($item) && !empty($item['body'])) {
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::BLUESKY, Protocol::TUMBLR, Protocol::TWITTER])) {
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::ATPROTO, Protocol::TUMBLR, Protocol::TWITTER])) {
if (!Item::performActivity($id, 'announce', $uid)) {
throw new InternalServerErrorException();
}

View file

@ -159,8 +159,8 @@ class Follow extends BaseModule
'$action' => $requestUrl,
'$name' => $contact['name'],
'$url' => $contact['alias'] ?: $contact['url'],
'$zrl' => OpenWebAuth::getZrlUrl($contact['alias'] ?: $contact['url']),
'$url' => Contact::getProfileLink($contact),
'$zrl' => OpenWebAuth::getZrlUrl(Contact::getProfileLink($contact)),
'$myaddr' => $myaddr,
'$keywords' => $contact['keywords'],

View file

@ -452,7 +452,7 @@ class Profile extends BaseModule
'$sparkle' => $sparkle,
'$url' => $url,
'$profileurllabel' => $this->t('Profile URL'),
'$profileurl' => $contact['alias'] ?: $contact['url'],
'$profileurl' => ContactModel::getProfileLink($contact),
'$account_type' => ContactModel::getAccountType($contact['contact-type']),
'$location' => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['location']),
'$location_label' => $this->t('Location:'),

View file

@ -110,7 +110,7 @@ class Unfollow extends \Friendica\BaseModule
'$invite_desc' => '',
'$submit' => $this->t('Submit Request'),
'$cancel' => $this->t('Cancel'),
'$url' => $contact['url'],
'$url' => Contact::getProfileLink($contact),
'$zrl' => Contact::magicLinkByContact($contact),
'$url_label' => $this->t('Profile URL'),
'$myaddr' => $self['url'],

View file

@ -58,12 +58,12 @@ class Statistics extends BaseModule
/// @todo mark the "service" addons and load them dynamically here
$services = [
'appnet' => $this->addonHelper->isAddonEnabled('appnet'),
'bluesky' => $this->addonHelper->isAddonEnabled('bluesky'),
'atprotocol' => $this->addonHelper->isAddonEnabled('bluesky'),
'dreamwidth' => $this->addonHelper->isAddonEnabled('dreamwidth'),
'gnusocial' => $this->addonHelper->isAddonEnabled('gnusocial'),
'libertree' => $this->addonHelper->isAddonEnabled('libertree'),
'livejournal' => $this->addonHelper->isAddonEnabled('livejournal'),
'pnut' => $this->addonHelper->isAddonEnabled('pnut'),
'pumpio' => $this->addonHelper->isAddonEnabled('pumpio'),
'twitter' => $this->addonHelper->isAddonEnabled('twitter'),
'tumblr' => $this->addonHelper->isAddonEnabled('tumblr'),

View file

@ -92,8 +92,8 @@ class Stats extends BaseModule
$statistics = [
'cron' => [
'lastExecution' => [
'datetime' => date(DateTimeFormat::JSON, (int)$this->keyValue->get('last_cron')),
'timestamp' => (int)$this->keyValue->get('last_cron'),
'datetime' => date(DateTimeFormat::JSON, (int) $this->keyValue->get('last_cron')),
'timestamp' => (int) $this->keyValue->get('last_cron'),
],
],
'worker' => [
@ -150,7 +150,7 @@ class Stats extends BaseModule
Protocol::OSTATUS => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::OSTATUS) ?? 0),
Protocol::FEED => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::FEED) ?? 0),
Protocol::MAIL => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::MAIL) ?? 0),
]
],
],
'reports' => [
'newest' => [
@ -181,8 +181,8 @@ class Stats extends BaseModule
];
if ($this->addonHelper->isAddonEnabled('bluesky')) {
$statistics['packets']['inbound'][Protocol::BLUESKY] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::BLUESKY) ?? 0);
$statistics['packets']['outbound'][Protocol::BLUESKY] = intval($this->keyValue->get('stats_packets_outbound_' . Protocol::BLUESKY) ?? 0);
$statistics['packets']['inbound'][Protocol::ATPROTO] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::ATPROTO) ?? 0);
$statistics['packets']['outbound'][Protocol::ATPROTO] = intval($this->keyValue->get('stats_packets_outbound_' . Protocol::ATPROTO) ?? 0);
}
if ($this->addonHelper->isAddonEnabled('tumblr')) {
$statistics['packets']['inbound'][Protocol::TUMBLR] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::TUMBLR) ?? 0);

View file

@ -24,7 +24,6 @@ use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Protocol\ActivityNamespace;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\ATProtocol;
use Friendica\Protocol\Diaspora;
use Friendica\Protocol\Email;
use Friendica\Protocol\Feed;
@ -32,6 +31,7 @@ use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
use Friendica\Util\Strings;
use Friendica\Util\XML;
use GuzzleHttp\Psr7\Uri;
@ -1189,7 +1189,7 @@ class Probe
}
/**
* Check for AT Protocol (Bluesky)
* Check for AT Protocol
*
* @param string $uri Profile link
* @return array Profile data or empty array
@ -1221,13 +1221,12 @@ class Probe
$name = $profile->displayName ?? $nick;
$data = [
'network' => Protocol::BLUESKY,
'network' => Protocol::ATPROTO,
'url' => $profile->did,
'alias' => ATProtocol::WEB . '/profile/' . $profile->did,
'alias' => DI::atpActor()->getProfileLink($profile->did),
'name' => $name ?: $nick,
'nick' => $nick,
'addr' => $nick,
'poll' => ATProtocol::WEB . '/profile/' . $profile->did . '/rss',
'photo' => $profile->avatar ?? '',
];
@ -1239,7 +1238,12 @@ class Probe
$data['header'] = $profile->banner;
}
$directory = DI::atProtocol()->get(ATProtocol::DIRECTORY . '/' . $profile->did);
$profile_page = ParseUrl::getSiteinfoCached($data['alias']);
if (isset($profile_page['atprotocol']['feed'])) {
$data['poll'] = $profile_page['atprotocol']['feed'];
}
$directory = DI::atProtocol()->get(DI::atProtocol()->getPLCDirectory() . '/' . $profile->did);
if (!empty($directory)) {
foreach ($directory->service as $service) {
if (($service->id == '#atproto_pds') && ($service->type == 'AtprotoPersonalDataServer') && !empty($service->serviceEndpoint)) {

View file

@ -194,7 +194,7 @@ class Post
$connector = !in_array($item['network'], Protocol::NATIVE_SUPPORT) && ($item['protocol'] != Conversation::PARCEL_JETSTREAM) ? DI::l10n()->t('Connector Message') : false;
$shareable = in_array($conv->getProfileOwner(), [0, DI::userSession()->getLocalUserId()]) && $item['private'] != Item::PRIVATE;
$announceable = $shareable && in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER, Protocol::TUMBLR, Protocol::BLUESKY]);
$announceable = $shareable && in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER, Protocol::TUMBLR, Protocol::ATPROTO]);
$commentable = ($item['network'] != Protocol::TUMBLR);
$likeable = true;

View file

@ -7,8 +7,6 @@
namespace Friendica\Protocol;
use DOMDocument;
use DOMXPath;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Protocol;
@ -18,8 +16,8 @@ use Friendica\Model\User;
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\ParseUrl;
use Psr\Log\LoggerInterface;
use stdClass;
@ -29,18 +27,19 @@ use stdClass;
*/
final class ATProtocol
{
const STATUS_UNKNOWN = 0;
const STATUS_TOKEN_OK = 1;
const STATUS_SUCCESS = 2;
const STATUS_API_FAIL = 10;
const STATUS_DID_FAIL = 11;
const STATUS_PDS_FAIL = 12;
const STATUS_TOKEN_FAIL = 13;
public const STATUS_UNKNOWN = 0;
public const STATUS_TOKEN_OK = 1;
public const STATUS_SUCCESS = 2;
public const STATUS_API_FAIL = 10;
public const STATUS_DID_FAIL = 11;
public const STATUS_PDS_FAIL = 12;
public const STATUS_TOKEN_FAIL = 13;
const APPVIEW_API = 'https://public.api.bsky.app'; // Path to the public Bluesky AppView API.
const DIRECTORY = 'https://plc.directory'; // Path to the directory server service to fetch the PDS of a given DID
const WEB = 'https://bsky.app'; // Path to the web interface with the user profile and posts
const HOSTNAME = 'bsky.social'; // Host name to be added to the handle if incomplete
/**
* Path to the web interface with the user profile and posts.
* This string can then be replaced when displaying the post and profile url.
*/
public const WEB = 'https://bsky.app';
/** @var LoggerInterface */
private $logger;
@ -57,6 +56,9 @@ final class ATProtocol
/** @var ICanSendHttpRequests */
private $httpClient;
private string $api;
private int $uid = 0;
public function __construct(LoggerInterface $logger, Database $database, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, ICanSendHttpRequests $httpClient)
{
$this->logger = $logger;
@ -67,7 +69,21 @@ final class ATProtocol
}
/**
* Returns an array of user ids who want to import the Bluesky timeline
* Get the AppView API URL
*
* @return string
*/
public function getApi(): string
{
if (!isset($this->api)) {
$this->logger->notice('Public API not set.');
$this->setApiForUser(0);
}
return $this->api;
}
/**
* Returns an array of user ids who want to import the AT Protocol timeline
*
* @return array user ids
*/
@ -112,8 +128,12 @@ final class ATProtocol
$url .= '?' . http_build_query($parameters);
}
if ($uid == 0) {
return $this->get(ATProtocol::APPVIEW_API . '/xrpc/' . $url);
if ($uid === 0 && $this->uid !== 0) {
$uid = $this->uid;
}
if ($uid === 0) {
return $this->get($this->getApi() . '/xrpc/' . $url);
}
$pds = $this->getUserPds($uid);
@ -171,7 +191,7 @@ final class ATProtocol
$data = json_decode($curlResult->getBodyString());
if (!$curlResult->isSuccess()) {
$this->logger->notice('API Error', ['url' => $url, 'code' => $curlResult->getReturnCode(), 'error' => $data ?: $curlResult->getBodyString()]);
if (!$data) {
if (!$data || !is_object($data)) {
return null;
}
$data->code = $curlResult->getReturnCode();
@ -180,7 +200,7 @@ final class ATProtocol
$data->code = $curlResult->getReturnCode();
}
Item::incrementInbound(Protocol::BLUESKY);
Item::incrementInbound(Protocol::ATPROTO);
return $data;
}
@ -268,7 +288,7 @@ final class ATProtocol
private function getUserPds(int $uid): ?string
{
if ($uid == 0) {
return self::APPVIEW_API;
return $this->getApi();
}
$pds = $this->pConfig->get($uid, 'bluesky', 'pds');
@ -334,12 +354,8 @@ final class ATProtocol
return '';
}
if (strpos($handle, '.') === false) {
$handle .= '.' . self::HOSTNAME;
}
// At first we use the AppView API which *should* cover all cases.
$data = $this->get(self::APPVIEW_API . '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
$data = $this->get($this->getApi() . '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
if (!empty($data) && !empty($data->did)) {
$this->logger->debug('Got DID by system PDS call', ['handle' => $handle, 'did' => $data->did]);
return $data->did;
@ -371,51 +387,8 @@ final class ATProtocol
*/
public function getDidByProfile(string $url): string
{
if (preg_match('#^' . self::WEB . '/profile/(.+)#', $url, $matches)) {
$did = $this->getDid($matches[1]);
if (!empty($did)) {
return $did;
}
}
try {
$curlResult = $this->httpClient->get($url, HttpClientAccept::HTML, [HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO]);
} catch (\Throwable $th) {
return '';
}
if (!$curlResult->isSuccess()) {
return '';
}
$profile = $curlResult->getBodyString();
if (empty($profile)) {
return '';
}
$doc = new DOMDocument();
try {
@$doc->loadHTML($profile);
} catch (\Throwable $th) {
return '';
}
$xpath = new DOMXPath($doc);
$list = $xpath->query('//p[@id]');
foreach ($list as $node) {
foreach ($node->attributes as $attribute) {
if ($attribute->name == 'id') {
$ids[$attribute->value] = $node->textContent;
}
}
}
if (empty($ids['bsky_handle']) || empty($ids['bsky_did'])) {
return '';
}
if (!$this->isValidDid($ids['bsky_did'], $ids['bsky_handle'])) {
$this->logger->notice('Invalid DID', ['handle' => $ids['bsky_handle'], 'did' => $ids['bsky_did']]);
return '';
}
return $ids['bsky_did'];
$data = ParseUrl::getSiteinfoCached($url);
return $data['atprotocol']['did'] ?? '';
}
/**
@ -473,7 +446,7 @@ final class ATProtocol
*/
public function getPdsOfDid(string $did): ?string
{
$data = $this->get(self::DIRECTORY . '/' . $did);
$data = $this->get($this->getPLCDirectory() . '/' . $did);
if (empty($data) || empty($data->service)) {
return null;
}
@ -487,6 +460,51 @@ final class ATProtocol
return null;
}
/**
* Set the AppView API for this class for a given uid
*
* @param integer $uid
* @return void
*/
public function setApiForUser(int $uid)
{
$this->api = $this->config->get('atprotocol', 'appview_api');
if ($uid !== 0 && $this->getUserPds($uid)) {
$this->uid = $uid;
}
}
/**
* Get the DID PLC Directory
*
* @return string
*/
public function getPLCDirectory(): string
{
return $this->config->get('atprotocol', 'plc_directory');
}
/**
* Get the Jetstream address
*
* @return string
*/
public function getJetstream(): string
{
return $this->config->get('atprotocol', 'jetstream');
}
/**
* Get the web address for a given uid
*
* @param integer $uid
* @return string
*/
public function getWebForUser(int $uid): string
{
return $this->pConfig->get($uid, 'bluesky', 'web') ?? $this->config->get('atprotocol', 'web');
}
/**
* Checks if the provided DID matches the handle
*
@ -494,9 +512,9 @@ final class ATProtocol
* @param string $handle The user handle
* @return boolean
*/
private function isValidDid(string $did, string $handle): bool
public function isValidDid(string $did, string $handle): bool
{
$data = $this->get(self::DIRECTORY . '/' . $did);
$data = $this->get($this->getPLCDirectory() . '/' . $did);
if (empty($data) || empty($data->alsoKnownAs)) {
return false;
}

View file

@ -44,7 +44,7 @@ class Actor
public function syncContacts(int $uid): void
{
$this->logger->info('Sync contacts for user - start', ['uid' => $uid]);
$contacts = Contact::selectToArray(['id', 'url', 'rel'], ['uid' => $uid, 'network' => Protocol::BLUESKY, 'rel' => [Contact::FRIEND, Contact::SHARING, Contact::FOLLOWER]]);
$contacts = Contact::selectToArray(['id', 'url', 'rel'], ['uid' => $uid, 'network' => Protocol::ATPROTO, 'rel' => [Contact::FRIEND, Contact::SHARING, Contact::FOLLOWER]]);
$follows = [];
$cursor = '';
@ -54,7 +54,7 @@ class Actor
$parameters = [
'actor' => $this->atprotocol->getUserDid($uid),
'limit' => 100,
'cursor' => $cursor
'cursor' => $cursor,
];
$data = $this->atprotocol->XRPCGet('app.bsky.graph.getFollows', $parameters);
@ -72,7 +72,7 @@ class Actor
$parameters = [
'actor' => $this->atprotocol->getUserDid($uid),
'limit' => 100,
'cursor' => $cursor
'cursor' => $cursor,
];
$data = $this->atprotocol->XRPCGet('app.bsky.graph.getFollowers', $parameters);
@ -117,7 +117,7 @@ class Actor
$name = $profile->displayName ?? $nick;
$fields = [
'alias' => ATProtocol::WEB . '/profile/' . $profile->did,
'alias' => $this->getProfileLink($profile->did),
'name' => $name ?: $nick,
'nick' => $nick,
'addr' => $nick,
@ -132,7 +132,7 @@ class Actor
$fields['header'] = $profile->banner;
}
$directory = $this->atprotocol->get(ATProtocol::DIRECTORY . '/' . $profile->did);
$directory = $this->atprotocol->get($this->atprotocol->getPLCDirectory() . '/' . $profile->did);
if (!empty($directory->service)) {
foreach ($directory->service as $service) {
if (($service->id == '#atproto_pds') && ($service->type == 'AtprotoPersonalDataServer') && !empty($service->serviceEndpoint)) {
@ -141,7 +141,7 @@ class Actor
}
if (!empty($fields['baseurl'])) {
GServer::check($fields['baseurl'], Protocol::BLUESKY);
GServer::check($fields['baseurl'], Protocol::ATPROTO);
$fields['gsid'] = GServer::getRealID($fields['baseurl'], true);
}
@ -154,10 +154,10 @@ class Actor
}
}
Contact::update($fields, ['nurl' => $profile->did, 'network' => Protocol::BLUESKY]);
Contact::update($fields, ['nurl' => $profile->did, 'network' => Protocol::ATPROTO]);
if (!empty($profile->avatar)) {
$contact = Contact::selectFirst(['id', 'avatar'], ['network' => Protocol::BLUESKY, 'nurl' => $did, 'uid' => 0]);
$contact = Contact::selectFirst(['id', 'avatar'], ['network' => Protocol::ATPROTO, 'nurl' => $did, 'uid' => 0]);
if (!empty($contact['id']) && ($contact['avatar'] != $profile->avatar)) {
Contact::updateAvatar($contact['id'], $profile->avatar);
}
@ -175,7 +175,7 @@ class Actor
} else {
$user_fields = ['rel' => Contact::NOTHING];
}
Contact::update($user_fields, ['nurl' => $profile->did, 'network' => Protocol::BLUESKY, 'uid' => $contact_uid]);
Contact::update($user_fields, ['nurl' => $profile->did, 'network' => Protocol::ATPROTO, 'uid' => $contact_uid]);
$this->logger->notice('Update user profile', ['uid' => $contact_uid, 'did' => $profile->did, 'fields' => $user_fields]);
}
}
@ -191,7 +191,7 @@ class Actor
*/
public function getContactByDID(string $did, int $uid, int $contact_uid, bool $auto_update = false): array
{
$contact = Contact::selectFirst([], ['network' => Protocol::BLUESKY, 'nurl' => $did, 'uid' => [$contact_uid, $uid]], ['order' => ['uid' => true]]);
$contact = Contact::selectFirst([], ['network' => Protocol::ATPROTO, 'nurl' => $did, 'uid' => [$contact_uid, $uid]], ['order' => ['uid' => true]]);
if (!empty($contact) && (!$auto_update || ($contact['updated'] > DateTimeFormat::utc('now -24 hours')))) {
return $contact;
@ -200,7 +200,7 @@ class Actor
if (empty($contact)) {
$fields = [
'uid' => $contact_uid,
'network' => Protocol::BLUESKY,
'network' => Protocol::ATPROTO,
'priority' => 1,
'writable' => true,
'blocked' => false,
@ -208,7 +208,7 @@ class Actor
'pending' => false,
'url' => $did,
'nurl' => $did,
'alias' => ATProtocol::WEB . '/profile/' . $did,
'alias' => $this->getProfileLink($did),
'name' => $did,
'nick' => $did,
'addr' => $did,
@ -224,4 +224,9 @@ class Actor
return Contact::getById($cid);
}
public function getProfileLink(string $did): string
{
return ATProtocol::WEB . '/profile/' . $did;
}
}

View file

@ -24,7 +24,7 @@ use Psr\Log\LoggerInterface;
use stdClass;
/**
* Class to handle the Bluesky Jetstream firehose
* Class to handle the AT Protocol Jetstream firehose
*
* Existing collections:
* app.bsky.feed.like, app.bsky.graph.follow, app.bsky.feed.repost, app.bsky.feed.post, app.bsky.graph.block,
@ -44,18 +44,18 @@ class Jetstream
* Maximum drift values in seconds for the threads completion.
* If the drift is higher than this value, only a few posts in a thread will be fetched.
*/
const MAX_DRIFT_THREAD_COMPLETION = 30;
public const MAX_DRIFT_THREAD_COMPLETION = 30;
/**
* Maximum drift values in seconds for the DID cap.
* If the drift is higher than this value, the number of DIDs will be capped.
*/
const MAX_DRIFT_DID_CAP = 60;
public const MAX_DRIFT_DID_CAP = 60;
/**
* Maximum drift values in seconds for creating posts.
* If the drift is higher than this value, posts and reshares will not be created.
* The other collections will still be processed.
*/
const MAX_DRIFT_CREATE_POSTS = 1200;
public const MAX_DRIFT_CREATE_POSTS = 1200;
private $uids = [];
private $self = [];
@ -90,6 +90,8 @@ class Jetstream
$this->atprotocol = $atprotocol;
$this->actor = $actor;
$this->processor = $processor;
$this->atprotocol->setApiForUser(0);
}
/**
@ -114,7 +116,7 @@ class Jetstream
$this->syncContacts();
try {
// @todo make the path configurable
$this->client = new \WebSocket\Client('wss://jetstream1.us-west.bsky.network/subscribe?requireHello=true' . $cursor);
$this->client = new \WebSocket\Client('wss://' . $this->atprotocol->getJetstream() . '/subscribe?requireHello=true' . $cursor);
$this->client->setTimeout($timeout);
$this->client->setLogger($this->logger);
} catch (\WebSocket\ConnectionException $e) {
@ -177,7 +179,7 @@ class Jetstream
*/
private function incrementMessages(): void
{
$packets = (int)($this->keyValue->get('jetstream_messages') ?? 0);
$packets = (int) ($this->keyValue->get('jetstream_messages') ?? 0);
if ($packets >= PHP_INT_MAX) {
$packets = 0;
}
@ -213,7 +215,7 @@ class Jetstream
return;
}
$contacts = Contact::selectToArray(['uid', 'url'], ['uid' => $active_uids, 'network' => Protocol::BLUESKY, 'rel' => [Contact::FRIEND, Contact::SHARING]]);
$contacts = Contact::selectToArray(['uid', 'url'], ['uid' => $active_uids, 'network' => Protocol::ATPROTO, 'rel' => [Contact::FRIEND, Contact::SHARING]]);
$self = [];
foreach ($active_uids as $uid) {
@ -233,17 +235,17 @@ class Jetstream
$dids = array_keys($uids);
if (count($dids) > $did_limit) {
$contacts = Contact::selectToArray(['url'], ['uid' => $active_uids, 'network' => Protocol::BLUESKY, 'rel' => [Contact::FRIEND, Contact::SHARING]], ['order' => ['last-item' => true]]);
$contacts = Contact::selectToArray(['url'], ['uid' => $active_uids, 'network' => Protocol::ATPROTO, 'rel' => [Contact::FRIEND, Contact::SHARING]], ['order' => ['last-item' => true]]);
$dids = $this->addDids($contacts, $uids, $did_limit, array_keys($self));
}
if (count($dids) < $did_limit) {
$contacts = Contact::selectToArray(['url'], ['uid' => $active_uids, 'network' => Protocol::BLUESKY, 'rel' => Contact::FOLLOWER], ['order' => ['last-item' => true]]);
$contacts = Contact::selectToArray(['url'], ['uid' => $active_uids, 'network' => Protocol::ATPROTO, 'rel' => Contact::FOLLOWER], ['order' => ['last-item' => true]]);
$dids = $this->addDids($contacts, $uids, $did_limit, $dids);
}
if (!$this->capped && count($dids) < $did_limit) {
$condition = ["`uid` = ? AND `network` = ? AND EXISTS(SELECT `author-id` FROM `post-user` WHERE `author-id` = `contact`.`id` AND `post-user`.`uid` != ?)", 0, Protocol::BLUESKY, 0];
$condition = ["`uid` = ? AND `network` = ? AND EXISTS(SELECT `author-id` FROM `post-user` WHERE `author-id` = `contact`.`id` AND `post-user`.`uid` != ?)", 0, Protocol::ATPROTO, 0];
$contacts = Contact::selectToArray(['url'], $condition, ['order' => ['last-item' => true], 'limit' => $did_limit]);
$dids = $this->addDids($contacts, $uids, $did_limit, $dids);
}
@ -257,8 +259,8 @@ class Jetstream
'payload' => [
'wantedCollections' => ['app.bsky.feed.post', 'app.bsky.feed.repost', 'app.bsky.feed.like', 'app.bsky.graph.block', 'app.bsky.actor.profile', 'app.bsky.graph.follow'],
'wantedDids' => $dids,
'maxMessageSizeBytes' => 1000000
]
'maxMessageSizeBytes' => 1000000,
],
];
try {
$this->client->send(json_encode($update));
@ -271,7 +273,7 @@ class Jetstream
* Returns an array of DIDs provided by an array of contacts
*
* @param array $contacts Array of contact records
* @param array $uids Array with the user ids with enabled bluesky timeline import
* @param array $uids Array with the user ids with enabled AT Protocol timeline import
* @param integer $did_limit Maximum limit of entries
* @param array $dids Array of DIDs that are added to the output list
* @return array DIDs
@ -306,7 +308,7 @@ class Jetstream
]);
}
Item::incrementInbound(Protocol::BLUESKY);
Item::incrementInbound(Protocol::ATPROTO);
switch ($data->kind) {
case 'account':

View file

@ -66,7 +66,7 @@ class Processor
$this->logger->notice('Process account', ['did' => $data->identity->did, 'fields' => $fields]);
Contact::update($fields, ['nurl' => $data->identity->did, 'network' => Protocol::BLUESKY]);
Contact::update($fields, ['nurl' => $data->identity->did, 'network' => Protocol::ATPROTO]);
}
public function processIdentity(stdClass $data)
@ -77,7 +77,7 @@ class Processor
}
$fields = [
'alias' => ATProtocol::WEB . '/profile/' . $data->identity->did,
'alias' => $this->actor->getProfileLink($data->identity->did),
'nick' => $data->identity->handle,
'addr' => $data->identity->handle,
'updated' => DateTimeFormat::utc($data->identity->time, DateTimeFormat::MYSQL),
@ -85,7 +85,7 @@ class Processor
$this->logger->notice('Process identity', ['did' => $data->identity->did, 'fields' => $fields]);
Contact::update($fields, ['nurl' => $data->identity->did, 'network' => Protocol::BLUESKY]);
Contact::update($fields, ['nurl' => $data->identity->did, 'network' => Protocol::ATPROTO]);
}
public function performBlocks(stdClass $data, int $uid)
@ -120,7 +120,7 @@ class Processor
return;
}
$condition = ['uri-id' => $itemuri['id'], 'author-link' => $data->did, 'network' => Protocol::BLUESKY];
$condition = ['uri-id' => $itemuri['id'], 'author-link' => $data->did, 'network' => Protocol::ATPROTO];
if (!Post::exists($condition)) {
$this->logger->info('Record not found', $condition);
return;
@ -339,7 +339,7 @@ class Processor
}
$item = [
'network' => Protocol::BLUESKY,
'network' => Protocol::ATPROTO,
'protocol' => $protocol,
'uid' => $uid,
'wall' => false,
@ -404,7 +404,7 @@ class Processor
}
$item = [
'network' => Protocol::BLUESKY,
'network' => Protocol::ATPROTO,
'protocol' => $protocol,
'uid' => $uid,
'wall' => false,

View file

@ -504,6 +504,8 @@ class ParseUrl
}
}
$siteinfo = self::checkATProtocol($siteinfo, $xpath);
$siteinfo['schematypes'] = [];
$list = $xpath->query("//script[@type='application/ld+json']");
@ -575,6 +577,90 @@ class ParseUrl
return $siteinfo;
}
/**
* Check if the page is a AT protocol profile page or post page
*
* @param array $siteinfo
* @param DOMXPath $xpath
* @return array
*/
private static function checkATProtocol(array $siteinfo, DOMXPath $xpath): array
{
$handle = '';
$list = $xpath->query('//meta[@property]');
foreach ($list as $node) {
$meta_tag = [];
if ($node->attributes->length) {
foreach ($node->attributes as $attribute) {
$meta_tag[$attribute->name] = $attribute->value;
}
}
if ($meta_tag['property'] !== 'profile:username' || !isset($meta_tag['content'])) {
continue;
}
$handle = $meta_tag['content'];
}
if ($handle === '') {
return $siteinfo;
}
$siteinfo['atprotocol']['handle'] = $handle;
// Publically visible posts have got a link element that points to the at address
$list = $xpath->query('//link[@rel]');
foreach ($list as $node) {
$meta_tag = [];
if ($node->attributes->length) {
foreach ($node->attributes as $attribute) {
$meta_tag[$attribute->name] = $attribute->value;
}
}
if ($meta_tag['rel'] !== 'alternate' || !isset($meta_tag['href'])) {
continue;
}
if (isset($meta_tag['type']) && $meta_tag['type'] === 'application/rss+xml') {
$siteinfo['atprotocol']['feed'] = $meta_tag['href'];
}
if (!str_starts_with($meta_tag['href'], 'at://')) {
continue;
}
if (str_ends_with($meta_tag['href'], '/app.bsky.actor.profile/self')) {
$siteinfo['atprotocol']['did'] = str_replace(['at://', '/app.bsky.actor.profile/self'], '', $meta_tag['href']);
DI::logger()->debug('Found AT Protocol post via alternate link', ['url' => $siteinfo['url'], 'uri' => $meta_tag['href']]);
continue;
}
$siteinfo['atprotocol']['uri'] = $meta_tag['href'];
$parts = explode('/', $siteinfo['atprotocol']['uri']);
if (count($parts) === 5) {
$siteinfo['atprotocol']['did'] = $parts[2];
}
}
if (!isset($siteinfo['atprotocol']['did'])) {
$did = DI::atProtocol()->getDid($handle);
if ($did === '') {
unset($siteinfo['atprotocol']);
return $siteinfo;
}
$siteinfo['atprotocol']['did'] = $did;
}
if (!DI::atProtocol()->isValidDid($siteinfo['atprotocol']['did'], $siteinfo['atprotocol']['handle'])) {
unset($siteinfo['atprotocol']);
return $siteinfo;
}
// When the post is not publically visible, we have to do some guess work. At first we check if that page is an AT Protocol page
if (!isset($siteinfo['atprotocol']['uri']) && preg_match('#^https://.+/profile/(.+)/post/(.+)#', $siteinfo['url'], $matches)) {
$siteinfo['atprotocol']['uri'] = 'at://' . $siteinfo['atprotocol']['did'] . '/app.bsky.feed.post/' . $matches[2];
DI::logger()->debug('Found AT Protocol post via url structure', ['uri' => $siteinfo['url'], 'did' => $siteinfo['atprotocol']['did'], 'cid' => $matches[2]]);
}
return $siteinfo;
}
/**
* Check the attached media elements.
* Fix existing data and add missing data.
@ -836,6 +922,10 @@ class ParseUrl
return $siteinfo;
}
if (isset($siteinfo['atprotocol']) && $type === 'ProfilePage') {
$siteinfo = self::parseJsonLdProfilePage($siteinfo, $jsonld);
}
switch ($type) {
case 'Article':
case 'AdvertiserContentArticle':
@ -907,6 +997,41 @@ class ParseUrl
}
}
/**
* Fetch AT Protocol related profile page data
*
* @param array $siteinfo
* @param array $jsonld
*
* @return array siteinfo
*/
private static function parseJsonLdProfilePage(array $siteinfo, array $jsonld): array
{
if (isset($jsonld['dateCreated'])) {
$siteinfo['atprotocol']['created'] = DateTimeFormat::utc($jsonld['dateCreated']);
}
if (!isset($jsonld['mainEntity']) || !is_array($jsonld['mainEntity'])) {
return $siteinfo;
}
$main = $jsonld['mainEntity'];
if (!isset($main['@type']) || $main['@type'] !== 'Person') {
return $siteinfo;
}
if (isset($main['name'])) {
$siteinfo['atprotocol']['name'] = $main['name'];
}
if (isset($main['identifier'])) {
$siteinfo['atprotocol']['did'] = $main['identifier'];
}
if (isset($main['description'])) {
$siteinfo['atprotocol']['description'] = $main['description'];
}
if (isset($main['image'])) {
$siteinfo['atprotocol']['image'] = $main['image'];
}
return $siteinfo;
}
/**
* Fetch author and publisher data
*

View file

@ -147,15 +147,16 @@ class Strings
*
* @param string $network Network name of the contact (e.g. dfrn, rss and so on)
* @param string $url The contact url
* @param int $gsid Server id
*
* @return string Formatted network name
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function formatNetworkName(string $network, string $url = ''): string
public static function formatNetworkName(string $network, string $url = '', ?int $gsid = null): string
{
if ($network != '') {
if ($url != '') {
$gsid = ContactSelector::getServerIdForProfile($url);
$gsid = $gsid ?? ContactSelector::getServerIdForProfile($url);
$network_name = '<a href="' . $url . '">' . ContactSelector::networkToName($network, '', $gsid) . '</a>';
} else {
$network_name = ContactSelector::networkToName($network);

View file

@ -792,4 +792,20 @@ return [
// The higher the number, the more likely the system won't be able to process the posts on time.
'did_limit' => 1000,
],
'atprotocol' => [
// appview_api (URL)
// Path to the public Bluesky AppView API.
'appview_api' => 'https://public.api.bsky.app',
// jetstream (URL)
// Path to the jetstream service. Available servers are:
// jetstream1.us-east.bsky.network, jetstream2.us-east.bsky.network, jetstream1.us-west.bsky.network, jetstream2.us-west.bsky.network
'jetstream' => 'jetstream1.us-west.bsky.network',
// directory (URL)
// Path to the directory server service to fetch the PDS of a given DID
'plc_directory' => 'https://plc.directory',
// web (URL)
// Path to the web interface with the user profile and posts.
// Other options are https://reddwarf.app or https://blacksky.community
'web' => 'https://bsky.app',
],
];

View file

@ -30,7 +30,7 @@ class PlaintextTest extends FixtureTestCase
'Wenn die Person noch nichts gepostet hat, ignoriere ich die Anfragen und schaue ggf. nach einiger Zeit wieder nach, ob jetzt was gepostet wurde! (3/6)',
'Wenn die Posts in eine Richtung gehen, die ich nicht mag, lehne ich die Anfragen ab. (4/6)',
'Ich ignoriere auch Anfragen, wenn sie von Accounts kommen, die ggf. tausenden von anderen Accounts folgen, da ich davon ausgehe, (5/6)',
'dass da niemand ernsthaft so vielen Accounts folgen kann. (6/6)'
'dass da niemand ernsthaft so vielen Accounts folgen kann. (6/6)',
],
],
'test-2' => [
@ -41,7 +41,7 @@ class PlaintextTest extends FixtureTestCase
'Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. (3/6)',
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, (4/6)',
'sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. (5/6)',
'Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. (6/6)'
'Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. (6/6)',
],
],
];
@ -66,7 +66,7 @@ class PlaintextTest extends FixtureTestCase
'plink' => '',
'body' => $text,
];
$output = Plaintext::getPost($item, 160, false, BBCode::BLUESKY);
$output = Plaintext::getPost($item, 160, false, BBCode::ATPROTOCOL);
self::assertEquals($expected, $output['parts']);
}
}

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2026.04-dev\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-04 20:06+0100\n"
"POT-Creation-Date: 2026-03-15 22:51+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -262,7 +262,7 @@ msgstr ""
msgid "Failed to delete the photo."
msgstr ""
#: mod/photos.php:332 src/Module/Conversation/Community.php:150
#: mod/photos.php:332 src/Module/Conversation/Community.php:145
#: src/Module/Directory.php:34 src/Module/Profile/Photos.php:270
#: src/Module/Search/Index.php:50
msgid "Public access denied."
@ -917,7 +917,7 @@ msgid "Tumblr"
msgstr ""
#: src/Content/ContactSelector.php:137
msgid "Bluesky"
msgid "AT Protocol"
msgstr ""
#: src/Content/ContactSelector.php:165
@ -1182,7 +1182,7 @@ msgid "Preview"
msgstr ""
#: src/Content/Conversation.php:429 src/Content/Item.php:442
#: src/Content/Widget/VCard.php:120 src/Model/Contact.php:1307
#: src/Content/Widget/VCard.php:120 src/Model/Contact.php:1306
#: src/Model/Profile.php:472 src/Module/Admin/Logs/View.php:79
#: src/Module/Post/Edit.php:181
msgid "Message"
@ -1538,7 +1538,7 @@ msgid "Display posts that have been created by accounts of the selected circle."
msgstr ""
#: src/Content/Feature.php:128 src/Content/GroupManager.php:131
#: src/Content/Nav.php:275 src/Content/Text/HTML.php:877
#: src/Content/Nav.php:275 src/Content/Text/HTML.php:884
#: src/Content/Widget.php:564 src/Model/User.php:1400
msgid "Groups"
msgstr ""
@ -1598,7 +1598,7 @@ msgstr ""
msgid "Display a list of folders in which posts are stored."
msgstr ""
#: src/Content/Feature.php:135 src/Module/Conversation/Timeline.php:188
#: src/Content/Feature.php:135 src/Module/Conversation/Timeline.php:189
msgid "Own Contacts"
msgstr ""
@ -1693,27 +1693,27 @@ msgstr ""
msgid "Complete Thread"
msgstr ""
#: src/Content/Item.php:437 src/Model/Contact.php:1302
#: src/Content/Item.php:437 src/Model/Contact.php:1301
msgid "View Status"
msgstr ""
#: src/Content/Item.php:438 src/Content/Item.php:461 src/Model/Contact.php:1237
#: src/Model/Contact.php:1293 src/Model/Contact.php:1303
#: src/Content/Item.php:438 src/Content/Item.php:461 src/Model/Contact.php:1236
#: src/Model/Contact.php:1292 src/Model/Contact.php:1302
#: src/Module/Directory.php:143 src/Module/Settings/Profile/Index.php:279
msgid "View Profile"
msgstr ""
#: src/Content/Item.php:439 src/Model/Contact.php:1304
#: src/Content/Item.php:439 src/Model/Contact.php:1303
msgid "View Photos"
msgstr ""
#: src/Content/Item.php:440 src/Model/Contact.php:1271
#: src/Content/Item.php:440 src/Model/Contact.php:1270
#: src/Model/Profile.php:444
msgid "Network Posts"
msgstr ""
#: src/Content/Item.php:441 src/Model/Contact.php:1295
#: src/Model/Contact.php:1306
#: src/Content/Item.php:441 src/Model/Contact.php:1294
#: src/Model/Contact.php:1305
msgid "View Contact"
msgstr ""
@ -1751,7 +1751,7 @@ msgid "Raw content"
msgstr ""
#: src/Content/Item.php:458 src/Content/Widget.php:71
#: src/Model/Contact.php:1296 src/Model/Contact.php:1308
#: src/Model/Contact.php:1295 src/Model/Contact.php:1307
#: src/Module/Contact/Follow.php:152 view/theme/vier/theme.php:182
msgid "Connect/Follow"
msgstr ""
@ -1902,7 +1902,7 @@ msgstr ""
msgid "Addon applications, utilities, games"
msgstr ""
#: src/Content/Nav.php:266 src/Content/Text/HTML.php:862
#: src/Content/Nav.php:266 src/Content/Text/HTML.php:869
#: src/Module/Search/Index.php:99
msgid "Search"
msgstr ""
@ -1911,17 +1911,17 @@ msgstr ""
msgid "Search site content"
msgstr ""
#: src/Content/Nav.php:269 src/Content/Text/HTML.php:871
#: src/Content/Nav.php:269 src/Content/Text/HTML.php:878
msgid "Full Text"
msgstr ""
#: src/Content/Nav.php:270 src/Content/Text/HTML.php:872
#: src/Content/Nav.php:270 src/Content/Text/HTML.php:879
#: src/Content/Widget/TagCloud.php:54
msgid "Tags"
msgstr ""
#: src/Content/Nav.php:271 src/Content/Nav.php:326
#: src/Content/Text/HTML.php:873 src/Module/BaseProfile.php:112
#: src/Content/Nav.php:271 src/Content/Nav.php:330
#: src/Content/Text/HTML.php:880 src/Module/BaseProfile.php:112
#: src/Module/BaseProfile.php:115 src/Module/Contact.php:181
#: src/Module/Contact.php:411 src/Module/Contact.php:521
#: view/theme/frio/theme.php:231
@ -2013,7 +2013,7 @@ msgstr ""
msgid "Outbox"
msgstr ""
#: src/Content/Nav.php:321
#: src/Content/Nav.php:320
msgid "Accounts"
msgstr ""
@ -2021,30 +2021,30 @@ msgstr ""
msgid "Manage other accounts, including groups and pages"
msgstr ""
#: src/Content/Nav.php:324 src/Module/Admin/Addons/Details.php:140
#: src/Content/Nav.php:328 src/Module/Admin/Addons/Details.php:140
#: src/Module/Admin/Themes/Details.php:85 src/Module/BaseAdmin.php:88
#: src/Module/BaseSettings.php:177 src/Module/Welcome.php:39
#: view/theme/frio/theme.php:230
msgid "Settings"
msgstr ""
#: src/Content/Nav.php:324 view/theme/frio/theme.php:230
#: src/Content/Nav.php:328 view/theme/frio/theme.php:230
msgid "Account settings"
msgstr ""
#: src/Content/Nav.php:326 view/theme/frio/theme.php:231
#: src/Content/Nav.php:330 view/theme/frio/theme.php:231
msgid "Manage/edit friends and contacts"
msgstr ""
#: src/Content/Nav.php:331 src/Module/BaseAdmin.php:114
#: src/Content/Nav.php:335 src/Module/BaseAdmin.php:114
msgid "Admin"
msgstr ""
#: src/Content/Nav.php:331
#: src/Content/Nav.php:335
msgid "Site setup and configuration"
msgstr ""
#: src/Content/Nav.php:332 src/Module/BaseModeration.php:117
#: src/Content/Nav.php:336 src/Module/BaseModeration.php:117
#: src/Module/Moderation/Blocklist/Contact.php:98
#: src/Module/Moderation/Blocklist/Contact/Import.php:101
#: src/Module/Moderation/Blocklist/Server/Add.php:110
@ -2061,15 +2061,15 @@ msgstr ""
msgid "Moderation"
msgstr ""
#: src/Content/Nav.php:332
#: src/Content/Nav.php:336
msgid "Content and user moderation"
msgstr ""
#: src/Content/Nav.php:335
#: src/Content/Nav.php:339
msgid "Navigation"
msgstr ""
#: src/Content/Nav.php:335
#: src/Content/Nav.php:339
msgid "Site map"
msgstr ""
@ -2089,49 +2089,49 @@ msgstr ""
msgid "last"
msgstr ""
#: src/Content/Text/BBCode.php:917
#: src/Content/Text/BBCode.php:923
#, php-format
msgid "<a href=\"%1$s\" target=\"_blank\" rel=\"noopener noreferrer\">%2$s</a> %3$s"
msgstr ""
#: src/Content/Text/BBCode.php:945 src/Model/Item.php:3750
#: src/Content/Text/BBCode.php:951 src/Model/Item.php:3750
#: src/Model/Item.php:3756 src/Model/Item.php:3757
msgid "Link to source"
msgstr ""
#: src/Content/Text/BBCode.php:1735 src/Content/Text/HTML.php:901
#: src/Content/Text/BBCode.php:1741 src/Content/Text/HTML.php:908
msgid "Click to open/close"
msgstr ""
#: src/Content/Text/BBCode.php:1790
#: src/Content/Text/BBCode.php:1796
msgid "$1 wrote:"
msgstr ""
#: src/Content/Text/BBCode.php:1864 src/Content/Text/BBCode.php:1865
#: src/Content/Text/BBCode.php:1870 src/Content/Text/BBCode.php:1871
msgid "Encrypted content"
msgstr ""
#: src/Content/Text/BBCode.php:2181
#: src/Content/Text/BBCode.php:2187
msgid "Invalid source protocol"
msgstr ""
#: src/Content/Text/BBCode.php:2200
#: src/Content/Text/BBCode.php:2206
msgid "Invalid link protocol"
msgstr ""
#: src/Content/Text/HTML.php:779
#: src/Content/Text/HTML.php:786
msgid "Loading more entries..."
msgstr ""
#: src/Content/Text/HTML.php:780
#: src/Content/Text/HTML.php:787
msgid "The end"
msgstr ""
#: src/Content/Text/HTML.php:856
#: src/Content/Text/HTML.php:863
msgid "Save search"
msgstr ""
#: src/Content/Text/HTML.php:864
#: src/Content/Text/HTML.php:871
msgid "@name, !group, #tags, content"
msgstr ""
@ -2249,7 +2249,7 @@ msgstr ""
msgid "Organisations"
msgstr ""
#: src/Content/Widget.php:563 src/Model/Contact.php:1814
#: src/Content/Widget.php:563 src/Model/Contact.php:1797
msgid "News"
msgstr ""
@ -2311,12 +2311,12 @@ msgstr ""
msgid "Show Less"
msgstr ""
#: src/Content/Widget/VCard.php:94 src/Model/Contact.php:1265
#: src/Content/Widget/VCard.php:94 src/Model/Contact.php:1264
#: src/Model/Profile.php:438
msgid "Post to group"
msgstr ""
#: src/Content/Widget/VCard.php:99 src/Model/Contact.php:1269
#: src/Content/Widget/VCard.php:99 src/Model/Contact.php:1268
#: src/Model/Profile.php:442 src/Module/Moderation/Item/Source.php:81
msgid "Mention"
msgstr ""
@ -2349,13 +2349,13 @@ msgstr ""
msgid "Follow"
msgstr ""
#: src/Content/Widget/VCard.php:118 src/Model/Contact.php:1297
#: src/Model/Contact.php:1309 src/Model/Profile.php:468
#: src/Content/Widget/VCard.php:118 src/Model/Contact.php:1296
#: src/Model/Contact.php:1308 src/Model/Profile.php:468
#: src/Module/Contact/Profile.php:521
msgid "Unfollow"
msgstr ""
#: src/Content/Widget/VCard.php:124 src/Model/Contact.php:1267
#: src/Content/Widget/VCard.php:124 src/Model/Contact.php:1266
#: src/Model/Profile.php:440
msgid "View group"
msgstr ""
@ -2900,83 +2900,83 @@ msgstr ""
msgid "Edit circles"
msgstr ""
#: src/Model/Contact.php:1316 src/Module/Moderation/Users/Pending.php:88
#: src/Model/Contact.php:1315 src/Module/Moderation/Users/Pending.php:88
#: src/Module/Notifications/Introductions.php:128
msgid "Approve"
msgstr ""
#: src/Model/Contact.php:1658 src/Model/Contact.php:1730
#: src/Model/Contact.php:1653 src/Model/Contact.php:1718
#: src/Module/Contact/Profile.php:399
#, php-format
msgid "%s has blocked you"
msgstr ""
#: src/Model/Contact.php:1810
#: src/Model/Contact.php:1793
msgid "Organisation"
msgstr ""
#: src/Model/Contact.php:1818
#: src/Model/Contact.php:1801
msgid "Group"
msgstr ""
#: src/Model/Contact.php:1822 src/Module/Moderation/BaseUsers.php:150
#: src/Model/Contact.php:1805 src/Module/Moderation/BaseUsers.php:150
msgid "Relay"
msgstr ""
#: src/Model/Contact.php:3152
#: src/Model/Contact.php:3131
msgid "Disallowed profile URL."
msgstr ""
#: src/Model/Contact.php:3157 src/Module/Friendica.php:106
#: src/Model/Contact.php:3136 src/Module/Friendica.php:106
msgid "Blocked domain"
msgstr ""
#: src/Model/Contact.php:3162
#: src/Model/Contact.php:3141
msgid "Connect URL missing."
msgstr ""
#: src/Model/Contact.php:3175
#: src/Model/Contact.php:3154
msgid "The contact could not be added. Please check the relevant network credentials in your Settings -> Social Networks page."
msgstr ""
#: src/Model/Contact.php:3193
#: src/Model/Contact.php:3172
#, php-format
msgid "Expected network %s does not match actual network %s"
msgstr ""
#: src/Model/Contact.php:3210
#: src/Model/Contact.php:3189
msgid "This seems to be a relay account. They can't be followed by users."
msgstr ""
#: src/Model/Contact.php:3217
#: src/Model/Contact.php:3196
msgid "The profile address specified does not provide adequate information."
msgstr ""
#: src/Model/Contact.php:3219
#: src/Model/Contact.php:3198
msgid "No compatible communication protocols or feeds were discovered."
msgstr ""
#: src/Model/Contact.php:3222
#: src/Model/Contact.php:3201
msgid "An author or name was not found."
msgstr ""
#: src/Model/Contact.php:3225
#: src/Model/Contact.php:3204
msgid "No browser URL could be matched to this address."
msgstr ""
#: src/Model/Contact.php:3228
#: src/Model/Contact.php:3207
msgid "Unable to match @-style Identity Address with a known protocol or email contact."
msgstr ""
#: src/Model/Contact.php:3229
#: src/Model/Contact.php:3208
msgid "Use mailto: in front of address to force email check."
msgstr ""
#: src/Model/Contact.php:3235
#: src/Model/Contact.php:3214
msgid "Limited profile. This person will be unable to receive direct/personal notifications from you."
msgstr ""
#: src/Model/Contact.php:3294
#: src/Model/Contact.php:3273
msgid "Unable to retrieve contact information."
msgstr ""
@ -3699,7 +3699,7 @@ msgstr ""
#: src/Module/Admin/Addons/Details.php:137 src/Module/Admin/Addons/Index.php:83
#: src/Module/Admin/Federation.php:218 src/Module/Admin/Logs/Settings.php:74
#: src/Module/Admin/Logs/View.php:71 src/Module/Admin/Queue.php:59
#: src/Module/Admin/Logs/View.php:71 src/Module/Admin/Queue.php:64
#: src/Module/Admin/Site.php:457 src/Module/Admin/Storage.php:123
#: src/Module/Admin/Summary.php:206 src/Module/Admin/Themes/Details.php:82
#: src/Module/Admin/Themes/Index.php:103 src/Module/Admin/Tos.php:63
@ -4063,28 +4063,28 @@ msgstr ""
msgid "This page lists the currently queued worker jobs. These jobs are handled by the worker cronjob you've set up during install."
msgstr ""
#: src/Module/Admin/Queue.php:62
#: src/Module/Admin/Queue.php:67
msgid "ID"
msgstr ""
#: src/Module/Admin/Queue.php:63
#: src/Module/Admin/Queue.php:68
msgid "Command"
msgstr ""
#: src/Module/Admin/Queue.php:64
#: src/Module/Admin/Queue.php:69
msgid "Job Parameters"
msgstr ""
#: src/Module/Admin/Queue.php:65 src/Module/Moderation/Reports.php:110
#: src/Module/Admin/Queue.php:70 src/Module/Moderation/Reports.php:110
#: src/Module/Settings/OAuth.php:60
msgid "Created"
msgstr ""
#: src/Module/Admin/Queue.php:66
#: src/Module/Admin/Queue.php:71
msgid "Next Try"
msgstr ""
#: src/Module/Admin/Queue.php:67
#: src/Module/Admin/Queue.php:72
msgid "Priority"
msgstr ""
@ -5605,9 +5605,9 @@ msgid "Tips for New Members"
msgstr ""
#: src/Module/BaseProfile.php:141 src/Module/Contact.php:395
#: src/Module/Contact.php:542 src/Module/Conversation/Channel.php:98
#: src/Module/Conversation/Community.php:91
#: src/Module/Conversation/Network.php:384
#: src/Module/Contact.php:542 src/Module/Conversation/Channel.php:93
#: src/Module/Conversation/Community.php:86
#: src/Module/Conversation/Network.php:379
#: src/Module/Moderation/BaseUsers.php:130 src/Object/Post.php:618
msgid "More"
msgstr ""
@ -5622,8 +5622,8 @@ msgstr ""
msgid "Group Search - %s"
msgstr ""
#: src/Module/BaseSearch.php:115 src/Module/Conversation/Channel.php:125
#: src/Module/Conversation/Community.php:115 src/Module/Search/Index.php:140
#: src/Module/BaseSearch.php:115 src/Module/Conversation/Channel.php:120
#: src/Module/Conversation/Community.php:110 src/Module/Search/Index.php:140
#: src/Module/Search/Index.php:192
msgid "No results."
msgstr ""
@ -5987,7 +5987,7 @@ msgstr ""
msgid "Search your contacts"
msgstr ""
#: src/Module/Contact.php:444 src/Module/Search/Index.php:206
#: src/Module/Contact.php:444 src/Module/Search/Index.php:201
#, php-format
msgid "Results for: %s"
msgstr ""
@ -6159,7 +6159,7 @@ msgstr[0] ""
msgstr[1] ""
#: src/Module/Contact/Follow.php:56 src/Module/Contact/Redir.php:47
#: src/Module/Contact/Redir.php:208 src/Module/Conversation/Community.php:156
#: src/Module/Contact/Redir.php:208 src/Module/Conversation/Community.php:151
#: src/Module/Debug/ItemBody.php:30 src/Module/Diaspora/Receive.php:45
#: src/Module/Item/Complete.php:25 src/Module/Item/Display.php:84
#: src/Module/Item/Feed.php:45 src/Module/Item/Follow.php:27
@ -6579,7 +6579,7 @@ msgstr ""
msgid "Unable to unfollow this contact, please contact your administrator"
msgstr ""
#: src/Module/Conversation/Channel.php:163
#: src/Module/Conversation/Channel.php:158
msgid "Channel not available."
msgstr ""
@ -6587,37 +6587,37 @@ msgstr ""
msgid "This community stream shows all public posts received by this node. They may not reflect the opinions of this nodes users."
msgstr ""
#: src/Module/Conversation/Community.php:170
#: src/Module/Conversation/Community.php:165
msgid "Community option not available."
msgstr ""
#: src/Module/Conversation/Community.php:186
#: src/Module/Conversation/Community.php:181
msgid "Not available."
msgstr ""
#: src/Module/Conversation/Network.php:276
#: src/Module/Conversation/Network.php:271
msgid "No such circle"
msgstr ""
#: src/Module/Conversation/Network.php:280
#: src/Module/Conversation/Network.php:275
#, php-format
msgid "Circle: %s"
msgstr ""
#: src/Module/Conversation/Network.php:300
#: src/Module/Conversation/Network.php:295
#, php-format
msgid "Error %d (%s) while fetching the timeline."
msgstr ""
#: src/Module/Conversation/Network.php:396
#: src/Module/Conversation/Network.php:391
msgid "Network feed not available."
msgstr ""
#: src/Module/Conversation/Timeline.php:192
#: src/Module/Conversation/Timeline.php:193
msgid "Include"
msgstr ""
#: src/Module/Conversation/Timeline.php:193
#: src/Module/Conversation/Timeline.php:194
msgid "Hide"
msgstr ""
@ -7113,15 +7113,15 @@ msgstr ""
msgid "Unable to follow this item."
msgstr ""
#: src/Module/LostPass.php:50
#: src/Module/LostPass.php:76
msgid "No valid account found."
msgstr ""
#: src/Module/LostPass.php:62
#: src/Module/LostPass.php:88
msgid "Password reset request issued. Check your email."
msgstr ""
#: src/Module/LostPass.php:68
#: src/Module/LostPass.php:94
#, php-format
msgid ""
"\n"
@ -7137,7 +7137,7 @@ msgid ""
"\t\t\tissued this request."
msgstr ""
#: src/Module/LostPass.php:79
#: src/Module/LostPass.php:105
#, php-format
msgid ""
"\n"
@ -7154,64 +7154,64 @@ msgid ""
"\t\t\tLogin Name:\t%3$s"
msgstr ""
#: src/Module/LostPass.php:93
#: src/Module/LostPass.php:119
#, php-format
msgid "Password reset requested at %s"
msgstr ""
#: src/Module/LostPass.php:109
#: src/Module/LostPass.php:141
msgid "Request could not be verified. (You may have previously submitted it.) Password reset failed."
msgstr ""
#: src/Module/LostPass.php:122
#: src/Module/LostPass.php:154
msgid "Request has expired, please make a new one."
msgstr ""
#: src/Module/LostPass.php:137
#: src/Module/LostPass.php:174
msgid "Forgot your Password?"
msgstr ""
#: src/Module/LostPass.php:138
#: src/Module/LostPass.php:175
msgid "Enter your email address and submit to have your password reset. Then check your email for further instructions."
msgstr ""
#: src/Module/LostPass.php:139 src/Module/Security/Login.php:167
#: src/Module/LostPass.php:176 src/Module/Security/Login.php:167
msgid "Nickname or email"
msgstr ""
#: src/Module/LostPass.php:140
#: src/Module/LostPass.php:177
msgid "Reset my password"
msgstr ""
#: src/Module/LostPass.php:155 src/Module/Security/Login.php:179
#: src/Module/LostPass.php:198 src/Module/Security/Login.php:179
msgid "Password Reset"
msgstr ""
#: src/Module/LostPass.php:156
#: src/Module/LostPass.php:199
msgid "Your password has been reset as requested."
msgstr ""
#: src/Module/LostPass.php:157
#: src/Module/LostPass.php:200
msgid "Your new password is"
msgstr ""
#: src/Module/LostPass.php:158
#: src/Module/LostPass.php:201
msgid "Save or copy your new password - and then"
msgstr ""
#: src/Module/LostPass.php:159
#: src/Module/LostPass.php:202
msgid "click here to login"
msgstr ""
#: src/Module/LostPass.php:160
#: src/Module/LostPass.php:203
msgid "Your password may be changed from the <em>Settings</em> page after successful login."
msgstr ""
#: src/Module/LostPass.php:164
#: src/Module/LostPass.php:207
msgid "Your password has been reset."
msgstr ""
#: src/Module/LostPass.php:167
#: src/Module/LostPass.php:210
#, php-format
msgid ""
"\n"
@ -7222,7 +7222,7 @@ msgid ""
"\t\t\t"
msgstr ""
#: src/Module/LostPass.php:173
#: src/Module/LostPass.php:216
#, php-format
msgid ""
"\n"
@ -7236,7 +7236,7 @@ msgid ""
"\t\t\t"
msgstr ""
#: src/Module/LostPass.php:184
#: src/Module/LostPass.php:227
#, php-format
msgid "Your password has been changed at %s"
msgstr ""
@ -8352,11 +8352,11 @@ msgstr ""
msgid "Show unread"
msgstr ""
#: src/Module/Notifications/Ping.php:208
#: src/Module/Notifications/Ping.php:211
msgid "{0} requested registration"
msgstr ""
#: src/Module/Notifications/Ping.php:217
#: src/Module/Notifications/Ping.php:220
#, php-format
msgid "{0} and %d others requested registration"
msgstr ""
@ -8903,7 +8903,7 @@ msgstr ""
msgid "Only one search per minute is permitted for not logged in users."
msgstr ""
#: src/Module/Search/Index.php:204
#: src/Module/Search/Index.php:199
#, php-format
msgid "Items tagged with: %s"
msgstr ""