kopia lustrzana https://github.com/snarfed/bridgy-fed
Porównaj commity
45 Commity
1d168454f2
...
78e6fceb5f
Autor | SHA1 | Data |
---|---|---|
dependabot[bot] | 78e6fceb5f | |
dependabot[bot] | 38ecaf3d0b | |
dependabot[bot] | 7d0ee92da5 | |
Ryan Barrett | 0ddb1255f4 | |
Ryan Barrett | 50dce84d68 | |
Ryan Barrett | edea743c58 | |
Ryan Barrett | 370fcb95a7 | |
Ryan Barrett | fb886ee86a | |
Ryan Barrett | 1111800e6f | |
Ryan Barrett | f668a53f74 | |
Ryan Barrett | 5071685c1b | |
Ryan Barrett | c8ca31554b | |
Ryan Barrett | b6be345921 | |
Ryan Barrett | ea5a4cf9c3 | |
Ryan Barrett | bf70ddd348 | |
Ryan Barrett | 0e5be92f08 | |
Ryan Barrett | 74dc22104f | |
dependabot[bot] | 2e5af56e87 | |
Ryan Barrett | e5c12c4595 | |
Ryan Barrett | be63f45b7d | |
Ryan Barrett | b6845246e9 | |
Ryan Barrett | 2a4146fe74 | |
Ryan Barrett | 9617d8ab71 | |
Ryan Barrett | cbe4b59206 | |
Ryan Barrett | 71cb803840 | |
Ryan Barrett | aa5701e158 | |
Ryan Barrett | 853b4be142 | |
Ryan Barrett | 4012b13a5a | |
Ryan Barrett | 267cd25909 | |
Ryan Barrett | c065c712f1 | |
Ryan Barrett | 1d20befe7e | |
Ryan Barrett | 8fe2bb47f6 | |
Ryan Barrett | dd1d53c28b | |
Ryan Barrett | 3d84dc4d36 | |
Ryan Barrett | d6a10b4be0 | |
Ryan Barrett | 41b2aaa1a8 | |
Ryan Barrett | 06bf3bf534 | |
Ryan Barrett | 3c62f7cfcc | |
Ryan Barrett | a79dc45b28 | |
Ryan Barrett | bfed0452b9 | |
Ryan Barrett | 55ae9fd2bb | |
Ryan Barrett | b543fdb1d5 | |
Ryan Barrett | def8c3d535 | |
Ryan Barrett | ece168fac1 | |
dependabot[bot] | e5df116765 |
|
@ -10,6 +10,8 @@ flask_secret_key
|
|||
make_password
|
||||
private_notes
|
||||
service_account_creds.json
|
||||
smtp_password
|
||||
smtp_user
|
||||
superfeedr_token
|
||||
superfeedr_username
|
||||
TAGS
|
||||
|
|
|
@ -369,7 +369,7 @@ class ActivityPub(User, Protocol):
|
|||
from_proto = PROTOCOLS.get(obj.source_protocol)
|
||||
user_id = from_user.key.id() if from_user and from_user.key else None
|
||||
# TODO: uncomment
|
||||
# if from_proto and not from_proto.is_enabled_to(cls, user=from_user):
|
||||
# if from_proto and not from_user.is_enabled(cls):
|
||||
# error(f'{cls.LABEL} <=> {from_proto.LABEL} not enabled')
|
||||
|
||||
if obj.as2:
|
||||
|
@ -664,12 +664,18 @@ def postprocess_as2(activity, orig_obj=None, wrap=True):
|
|||
# https://chrisbeckstrom.com/2018/12/27/32551/
|
||||
# assert activity.get('id') or (isinstance(obj, dict) and obj.get('id'))
|
||||
|
||||
# drop Link attachments since fediverse instances generate their own link previews
|
||||
# https://github.com/snarfed/bridgy-fed/issues/958
|
||||
obj_or_activity = obj if obj.keys() > set(['id']) else activity
|
||||
obj_or_activity['attachment'] = [
|
||||
a for a in as1.get_objects(obj_or_activity, 'attachment')
|
||||
if a.get('type') != 'Link']
|
||||
|
||||
# copy image(s) into attachment(s). may be Mastodon-specific.
|
||||
# https://github.com/snarfed/bridgy-fed/issues/33#issuecomment-440965618
|
||||
obj_or_activity = obj if obj.keys() > set(['id']) else activity
|
||||
imgs = util.get_list(obj_or_activity, 'image')
|
||||
atts = obj_or_activity.setdefault('attachment', [])
|
||||
if imgs:
|
||||
atts = obj_or_activity['attachment']
|
||||
atts.extend(img for img in imgs if img not in atts)
|
||||
|
||||
# cc target's author(s), recipients, mentions
|
||||
|
@ -860,11 +866,8 @@ def actor(handle_or_id):
|
|||
id = handle_or_id
|
||||
|
||||
assert id
|
||||
if not cls.is_enabled_to(ActivityPub, user=id):
|
||||
error(f'{cls.LABEL} user {id} not found', status=404)
|
||||
|
||||
user = cls.get_or_create(id)
|
||||
if not user:
|
||||
if not user or not user.is_enabled(ActivityPub):
|
||||
error(f'{cls.LABEL} user {id} not found', status=404)
|
||||
|
||||
id = user.id_as(ActivityPub)
|
||||
|
|
|
@ -5,15 +5,13 @@
|
|||
],
|
||||
"type": "Application",
|
||||
"id": "https://ap.brid.gy/ap.brid.gy",
|
||||
"url": "https://ap.brid.gy/",
|
||||
"url": [
|
||||
"https://fed.brid.gy/"
|
||||
"https://ap.brid.gy/",
|
||||
],
|
||||
"preferredUsername": "ap.brid.gy",
|
||||
"summary": "<a href='https://fed.brid.gy/'>Bridgy Fed</a> bot user for the <a href='https://en.wikipedia.org/wiki/Fediverse'>fediverse</a>. To bridge your Bluesky account to the fediverse, follow this account. <a href='https://fed.brid.gy/docs'>More info here.</a>",
|
||||
"summary": "Bridgy Fed (https://fed.brid.gy/) bot user for the fediverse. To bridge your Bluesky account to the fediverse, follow this account. More info: https://fed.brid.gy/docs",
|
||||
"name": "Bridgy Fed for the fediverse",
|
||||
"attachment": [{
|
||||
"name": "Web site",
|
||||
"type": "PropertyValue",
|
||||
"value": "<a rel=\"me\" href=\"https://fed.brid.gy\"><span class=\"invisible\">https://</span>fed.brid.gy</a>"
|
||||
}],
|
||||
"image": [
|
||||
{
|
||||
"type": "Image",
|
||||
|
|
5
app.yaml
5
app.yaml
|
@ -28,11 +28,16 @@ env_variables:
|
|||
# PLC_HOST: plc.bsky-sandbox.dev
|
||||
# APPVIEW_HOST: api.bsky-sandbox.dev
|
||||
# BGS_HOST: bgs.bsky-sandbox.dev
|
||||
# MOD_SERVICE_HOST: ?
|
||||
# MOD_SERVICE_DID: ?
|
||||
|
||||
# prod
|
||||
PLC_HOST: plc.directory
|
||||
APPVIEW_HOST: api.bsky.app
|
||||
BGS_HOST: bsky.network
|
||||
MOD_SERVICE_HOST: mod.bsky.app
|
||||
MOD_SERVICE_DID: did:plc:ar7c4by46qjdydhdevvrndac
|
||||
# ...or test against labeler.dholms.xyz / did:plc:vzxheqfwpbi3lxbgdh22js66
|
||||
|
||||
handlers:
|
||||
|
||||
|
|
55
atproto.py
55
atproto.py
|
@ -375,6 +375,9 @@ class ATProto(User, Protocol):
|
|||
assert repo
|
||||
repo.callback = lambda _: common.create_task(queue='atproto-commit')
|
||||
|
||||
if verb == 'flag':
|
||||
return to_cls.create_report(record, user)
|
||||
|
||||
# write commit
|
||||
type = record['$type']
|
||||
lex_type = LEXICONS[type]['type']
|
||||
|
@ -473,6 +476,7 @@ class ATProto(User, Protocol):
|
|||
obj.key = ndb.Key(Object, id.replace(f'at://{handle}', f'at://{repo}'))
|
||||
|
||||
try:
|
||||
appview.address = f'https://{os.environ["APPVIEW_HOST"]}'
|
||||
ret = appview.com.atproto.repo.getRecord(
|
||||
repo=repo, collection=collection, rkey=rkey)
|
||||
except RequestException as e:
|
||||
|
@ -502,7 +506,7 @@ class ATProto(User, Protocol):
|
|||
"""
|
||||
from_proto = PROTOCOLS.get(obj.source_protocol)
|
||||
# TODO: uncomment
|
||||
# if from_proto and not from_proto.is_enabled_to(cls, user=from_user):
|
||||
# if from_proto and not from_user.is_enabled(cls):
|
||||
# error(f'{cls.LABEL} <=> {from_proto.LABEL} not enabled')
|
||||
|
||||
if obj.bsky:
|
||||
|
@ -540,7 +544,9 @@ class ATProto(User, Protocol):
|
|||
})
|
||||
|
||||
match ret.get('$type'):
|
||||
case 'app.bsky.feed.like' | 'app.bsky.feed.repost':
|
||||
case ('app.bsky.feed.like'
|
||||
| 'app.bsky.feed.repost'
|
||||
| 'com.atproto.moderation.createReport#input'):
|
||||
populate_cid(ret['subject'])
|
||||
case 'app.bsky.feed.post' if ret.get('reply'):
|
||||
populate_cid(ret['reply']['root'])
|
||||
|
@ -549,6 +555,36 @@ class ATProto(User, Protocol):
|
|||
return ret
|
||||
|
||||
|
||||
@classmethod
|
||||
def create_report(cls, input, from_user):
|
||||
"""Sends a ``createReport`` for a ``flag`` activity.
|
||||
|
||||
Args:
|
||||
input (dict): ``createReport`` input
|
||||
from_user (models.User): user (actor) this flag is from
|
||||
|
||||
Returns:
|
||||
bool: True if the report was sent successfully, False if the flag's
|
||||
actor is not bridged into ATProto
|
||||
"""
|
||||
assert input['$type'] == 'com.atproto.moderation.createReport#input'
|
||||
|
||||
repo_did = from_user.get_copy(ATProto)
|
||||
if not repo_did:
|
||||
return False
|
||||
repo = arroba.server.storage.load_repo(repo_did)
|
||||
mod_host = os.environ['MOD_SERVICE_HOST']
|
||||
token = service_jwt(host=mod_host,
|
||||
aud=os.environ['MOD_SERVICE_DID'],
|
||||
repo_did=repo_did,
|
||||
privkey=repo.signing_key)
|
||||
|
||||
client = Client(f'https://{mod_host}', truncate=True,
|
||||
headers={'User-Agent': USER_AGENT})
|
||||
output = client.com.atproto.moderation.createReport(input)
|
||||
logger.info(f'Created report on {mod_host}: {json_dumps(output)}')
|
||||
return True
|
||||
|
||||
# URL route is registered in hub.py
|
||||
def poll_notifications():
|
||||
"""Fetches and enqueueus new activities from the AppView for our users.
|
||||
|
@ -574,7 +610,7 @@ def poll_notifications():
|
|||
headers={'User-Agent': USER_AGENT})
|
||||
|
||||
for user in users:
|
||||
if not user.is_enabled_to(ATProto, user=user):
|
||||
if not user.is_enabled(ATProto):
|
||||
logger.info(f'Skipping {user.key.id()}')
|
||||
continue
|
||||
|
||||
|
@ -591,7 +627,6 @@ def poll_notifications():
|
|||
resp = client.app.bsky.notification.listNotifications(limit=10)
|
||||
for notif in resp['notifications']:
|
||||
actor_did = notif['author']['did']
|
||||
logger.debug(f'Got {notif["reason"]} from {notif["author"]["handle"]} {notif["uri"]} {notif["cid"]} : {json_dumps(notif, indent=2)}')
|
||||
|
||||
# TODO: verify sig. skipping this for now because we're getting
|
||||
# these from the AppView, which is trusted, specifically we expect
|
||||
|
@ -599,6 +634,11 @@ def poll_notifications():
|
|||
obj = Object.get_or_create(id=notif['uri'], bsky=notif['record'],
|
||||
source_protocol=ATProto.ABBREV,
|
||||
actor=actor_did)
|
||||
if obj.status in ('complete', 'ignored'):
|
||||
continue
|
||||
|
||||
logger.debug(f'Got new {notif["reason"]} from {notif["author"]["handle"]} {notif["uri"]} {notif["cid"]} : {json_dumps(notif, indent=2)}')
|
||||
|
||||
if not obj.status:
|
||||
obj.status = 'new'
|
||||
obj.add('notify', user.key)
|
||||
|
@ -633,7 +673,7 @@ def poll_posts():
|
|||
headers={'User-Agent': USER_AGENT})
|
||||
|
||||
for user in users:
|
||||
if not user.is_enabled_to(ATProto, user=user):
|
||||
if not user.is_enabled(ATProto):
|
||||
logger.info(f'Skipping {user.key.id()}')
|
||||
continue
|
||||
|
||||
|
@ -655,7 +695,6 @@ def poll_posts():
|
|||
continue
|
||||
|
||||
post = item['post']
|
||||
logger.debug(f'Got {post["uri"]}: {json_dumps(item, indent=2)}')
|
||||
|
||||
# TODO: verify sig. skipping this for now because we're getting
|
||||
# these from the AppView, which is trusted, specifically we expect
|
||||
|
@ -664,6 +703,10 @@ def poll_posts():
|
|||
obj = Object.get_or_create(id=post['uri'], bsky=post['record'],
|
||||
source_protocol=ATProto.ABBREV,
|
||||
actor=author_did)
|
||||
if obj.status in ('complete', 'ignored'):
|
||||
continue
|
||||
|
||||
logger.debug(f'Got new post: {post["uri"]} : {json_dumps(item, indent=2)}')
|
||||
if not obj.status:
|
||||
obj.status = 'new'
|
||||
obj.add('feed', user.key)
|
||||
|
|
|
@ -5,15 +5,13 @@
|
|||
],
|
||||
"type": "Application",
|
||||
"id": "https://bsky.brid.gy/bsky.brid.gy",
|
||||
"url": "https://bsky.brid.gy/",
|
||||
"url": [
|
||||
"https://fed.brid.gy/",
|
||||
"https://bsky.brid.gy/"
|
||||
],
|
||||
"preferredUsername": "bsky.brid.gy",
|
||||
"summary": "<a href='https://fed.brid.gy/'>Bridgy Fed</a> bot user for <a href='https://bsky.social/'>Bluesky</a>. To bridge your fediverse account to Bluesky, follow this account or reply <em>yes</em> when it promps you with a DM. <a href='https://fed.brid.gy/docs'>More info here.</a>",
|
||||
"name": "Bridgy Fed for Bluesky",
|
||||
"attachment": [{
|
||||
"name": "Web site",
|
||||
"type": "PropertyValue",
|
||||
"value": "<a rel=\"me\" href=\"https://fed.brid.gy\"><span class=\"invisible\">https://</span>fed.brid.gy</a>"
|
||||
}],
|
||||
"image": [
|
||||
{
|
||||
"type": "Image",
|
||||
|
|
21
common.py
21
common.py
|
@ -6,7 +6,7 @@ from pathlib import Path
|
|||
import re
|
||||
import threading
|
||||
import urllib.parse
|
||||
from urllib.parse import urljoin
|
||||
from urllib.parse import urljoin, urlparse
|
||||
|
||||
import cachetools
|
||||
from Crypto.Util import number
|
||||
|
@ -63,6 +63,9 @@ DOMAIN_BLOCKLIST = (
|
|||
'twitter.com',
|
||||
)
|
||||
|
||||
SMTP_HOST = 'smtp.gmail.com'
|
||||
SMTP_PORT = 587
|
||||
|
||||
# populated in models.reset_protocol_properties
|
||||
SUBDOMAIN_BASE_URL_RE = None
|
||||
ID_FIELDS = ['id', 'object', 'actor', 'author', 'inReplyTo', 'url']
|
||||
|
@ -192,7 +195,15 @@ def unwrap(val, field=None):
|
|||
Returns:
|
||||
str: unwrapped url
|
||||
"""
|
||||
|
||||
if isinstance(val, dict):
|
||||
# TODO: clean up. https://github.com/snarfed/bridgy-fed/issues/967
|
||||
id = val.get('id')
|
||||
if (id and urlparse(id).path.strip('/') in DOMAINS + ('',)
|
||||
and util.domain_from_link(id) in DOMAINS):
|
||||
# protocol bot user, don't touch its URLs
|
||||
return {**val, 'id': unwrap(id)}
|
||||
|
||||
return {f: unwrap(v, field=f) for f, v in val.items()}
|
||||
|
||||
elif isinstance(val, list):
|
||||
|
@ -305,3 +316,11 @@ def create_task(queue, delay=None, **params):
|
|||
msg = f'Added {queue} task {task.name} : {params}'
|
||||
logger.info(msg)
|
||||
return msg, 202
|
||||
|
||||
|
||||
def email_me(msg):
|
||||
assert False # not working, SMTP woes :(
|
||||
if not DEBUG:
|
||||
util.send_email(smtp_host=SMTP_HOST, smtp_port=SMTP_PORT,
|
||||
from_='scufflechuck@gmail.com', to='bridgy-fed@ryanb.org',
|
||||
subject=util.ellipsize(msg), body=msg)
|
||||
|
|
|
@ -174,7 +174,7 @@ html_theme_options = {
|
|||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
html_logo = '../static/bridgy_fed_logo.png'
|
||||
html_logo = '../static/bridgy_logo_with_alpha.png'
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
|
|
|
@ -7,13 +7,8 @@
|
|||
"id": "https://fed.brid.gy/fed.brid.gy",
|
||||
"url": "https://fed.brid.gy/",
|
||||
"preferredUsername": "fed.brid.gy",
|
||||
"summary": "Bridging the new social internet",
|
||||
"summary": "<a href='https://fed.brid.gy/'>Bridgy Fed</a> is a bridge between decentralized social networks like the <a href='https://indieweb.org/'>IndieWeb</a>, <a href='https://en.wikipedia.org/wiki/Fediverse'>fediverse</a>, <a href='https://bsky.social/'>Bluesky</a>, and others. <a href='https://fed.brid.gy/docs'>More info here.</a>",
|
||||
"name": "Bridgy Fed",
|
||||
"attachment": [{
|
||||
"name": "Web site",
|
||||
"type": "PropertyValue",
|
||||
"value": "<a rel=\"me\" href=\"https://fed.brid.gy\"><span class=\"invisible\">https://</span>fed.brid.gy</a>"
|
||||
}],
|
||||
"image": [
|
||||
{
|
||||
"type": "Image",
|
||||
|
|
5
hub.yaml
5
hub.yaml
|
@ -23,11 +23,16 @@ env_variables:
|
|||
# PLC_HOST: plc.bsky-sandbox.dev
|
||||
# APPVIEW_HOST: api.bsky-sandbox.dev
|
||||
# BGS_HOST: bgs.bsky-sandbox.dev
|
||||
# MOD_SERVICE_HOST: ?
|
||||
# MOD_SERVICE_DID: ?
|
||||
|
||||
# prod
|
||||
PLC_HOST: plc.directory
|
||||
APPVIEW_HOST: api.bsky.app
|
||||
BGS_HOST: bsky.network
|
||||
MOD_SERVICE_HOST: mod.bsky.app
|
||||
MOD_SERVICE_DID: did:plc:ar7c4by46qjdydhdevvrndac
|
||||
# ...or test against labeler.dholms.xyz / did:plc:vzxheqfwpbi3lxbgdh22js66
|
||||
|
||||
# need only one instance so that new commits can be delivered to subscribeRepos
|
||||
# subscribers in memory
|
||||
|
|
3
ids.py
3
ids.py
|
@ -46,7 +46,8 @@ def web_ap_base_domain(user_domain):
|
|||
Returns:
|
||||
str:
|
||||
"""
|
||||
if request.host in LOCAL_DOMAINS:
|
||||
if (request.host in LOCAL_DOMAINS and
|
||||
not (user_domain == PRIMARY_DOMAIN or user_domain in PROTOCOL_DOMAINS)):
|
||||
return request.host_url
|
||||
|
||||
global _NON_WEB_SUBDOMAIN_SITES
|
||||
|
|
116
models.py
116
models.py
|
@ -42,12 +42,13 @@ PAGE_SIZE = 20
|
|||
# auto delete old objects of these types via the Object.expire property
|
||||
# https://cloud.google.com/datastore/docs/ttl
|
||||
OBJECT_EXPIRE_TYPES = (
|
||||
'post',
|
||||
'update',
|
||||
'delete',
|
||||
'accept',
|
||||
'block',
|
||||
'delete',
|
||||
'post',
|
||||
'reject',
|
||||
'undo',
|
||||
'update',
|
||||
None,
|
||||
)
|
||||
OBJECT_EXPIRE_AGE = timedelta(days=90)
|
||||
|
@ -257,7 +258,7 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
|
|||
for label in ids.COPIES_PROTOCOLS:
|
||||
proto = PROTOCOLS[label]
|
||||
if proto != cls and not user.get_copy(proto):
|
||||
if cls.is_enabled_to(proto, user=id):
|
||||
if user.is_enabled(proto):
|
||||
proto.create_for(user)
|
||||
else:
|
||||
logger.info(f'{cls.LABEL} <=> atproto not enabled, skipping')
|
||||
|
@ -357,6 +358,52 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
|
|||
|
||||
return None
|
||||
|
||||
def is_enabled(self, to_proto):
|
||||
"""Returns True if this user can be bridged to a given protocol.
|
||||
|
||||
Reasons this might return False:
|
||||
* We haven't turned on bridging these two protocols yet.
|
||||
* The user is opted out.
|
||||
* The user is on a domain that's opted out.
|
||||
* The from protocol requires opt in, and the user hasn't opted in.
|
||||
|
||||
Args:
|
||||
to_proto (Protocol subclass)
|
||||
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
from protocol import Protocol
|
||||
assert issubclass(to_proto, Protocol)
|
||||
|
||||
if self.__class__ == to_proto:
|
||||
return True
|
||||
|
||||
from_label = self.LABEL
|
||||
to_label = to_proto.LABEL
|
||||
|
||||
# unit tests
|
||||
if DEBUG and (from_label in ('fake', 'other')
|
||||
or (to_label in ('fake', 'other') and from_label != 'eefake')):
|
||||
return True
|
||||
|
||||
elif bot_protocol := Protocol.for_bridgy_subdomain(self.key.id()):
|
||||
return to_proto != bot_protocol
|
||||
|
||||
elif self.manual_opt_out:
|
||||
return False
|
||||
|
||||
elif to_label in self.enabled_protocols:
|
||||
return True
|
||||
|
||||
elif self.status == 'opt-out':
|
||||
return False
|
||||
|
||||
elif to_label in self.DEFAULT_ENABLED_PROTOCOLS:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@ndb.transactional()
|
||||
def enable_protocol(self, to_proto):
|
||||
"""Adds ``to_proto` to :attr:`enabled_protocols`.
|
||||
|
@ -364,31 +411,40 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
|
|||
Args:
|
||||
to_proto (:class:`protocol.Protocol` subclass)
|
||||
"""
|
||||
logger.info(f'Enabling {to_proto.LABEL} for {self.key}')
|
||||
@ndb.transactional()
|
||||
def enable():
|
||||
user = self.key.get()
|
||||
add(user.enabled_protocols, to_proto.LABEL)
|
||||
if to_proto.LABEL in ids.COPIES_PROTOCOLS and not user.get_copy(to_proto):
|
||||
to_proto.create_for(user)
|
||||
user.put()
|
||||
|
||||
enable()
|
||||
add(self.enabled_protocols, to_proto.LABEL)
|
||||
|
||||
@ndb.transactional()
|
||||
msg = f'Enabled {to_proto.LABEL} for {self.key.id()} : {self.user_page_path()}'
|
||||
logger.info(msg)
|
||||
|
||||
def disable_protocol(self, to_proto):
|
||||
"""Removes ``to_proto` from :attr:`enabled_protocols`.
|
||||
|
||||
Args:
|
||||
to_proto (:class:`protocol.Protocol` subclass)
|
||||
"""
|
||||
logger.info(f'Disabling {to_proto.LABEL} for {self.key}')
|
||||
@ndb.transactional()
|
||||
def disable():
|
||||
user = self.key.get()
|
||||
remove(user.enabled_protocols, to_proto.LABEL)
|
||||
# TODO: delete copy user
|
||||
# https://github.com/snarfed/bridgy-fed/issues/783
|
||||
user.put()
|
||||
|
||||
disable()
|
||||
remove(self.enabled_protocols, to_proto.LABEL)
|
||||
|
||||
msg = f'Disabled {to_proto.LABEL} for {self.key.id()} : {self.user_page_path()}'
|
||||
logger.info(msg)
|
||||
|
||||
def handle_as(self, to_proto):
|
||||
"""Returns this user's handle in a different protocol.
|
||||
|
||||
|
@ -541,14 +597,32 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
|
|||
if copy.protocol in (proto.LABEL, proto.ABBREV):
|
||||
return copy.uri
|
||||
|
||||
def user_link(self):
|
||||
"""Returns a pretty link to the external user with name and profile picture."""
|
||||
def user_link(self, handle=False, maybe_internal_link=True):
|
||||
"""Returns a pretty link to the user with name and profile picture.
|
||||
|
||||
If they're opted in, links to their Bridgy Fed user page. Otherwise,
|
||||
links to their external account.
|
||||
|
||||
TODO: unify with :meth:`Object.actor_link`?
|
||||
|
||||
Args:
|
||||
handle (bool): include handle as well as display name
|
||||
maybe_internal_link (bool): if True, link to Bridgy Fed user page
|
||||
instead of external account
|
||||
"""
|
||||
url = (self.user_page_path()
|
||||
if maybe_internal_link and (self.enabled_protocols
|
||||
or self.LABEL == 'web' or self.direct)
|
||||
else self.web_url())
|
||||
pic = self.profile_picture()
|
||||
img = f'<img src="{pic}" class="profile">' if pic else ''
|
||||
maybe_handle = f'· {self.handle}' if handle else ''
|
||||
|
||||
return f"""\
|
||||
<a class="h-card u-author" href="{self.web_url()}">
|
||||
<span class="logo" title="{self.__class__.__name__}">{self.LOGO_HTML}</span>
|
||||
<a class="h-card u-author" href="{url}" title="{self.name()}">
|
||||
{img}
|
||||
{self.name()}
|
||||
{util.ellipsize(self.name(), chars=40)} {maybe_handle}
|
||||
</a>"""
|
||||
|
||||
def profile_picture(self):
|
||||
|
@ -890,6 +964,8 @@ class Object(StringIdModel):
|
|||
def actor_link(self, image=True, sized=False, user=None):
|
||||
"""Returns a pretty HTML link with the actor's name and picture.
|
||||
|
||||
TODO: unify with :meth:`User.user_link`?
|
||||
|
||||
Args:
|
||||
image (bool): whether to include an ``img`` tag with the actor's picture
|
||||
sized (bool): whether to set an explicit (``width=32``) size on the
|
||||
|
@ -901,11 +977,12 @@ class Object(StringIdModel):
|
|||
"""
|
||||
attrs = {'class': 'h-card u-author'}
|
||||
|
||||
if (self.source_protocol in ('web', 'webmention', 'ui')
|
||||
and (user.key in self.users or user.key.id() in self.domains)):
|
||||
if user and (user.key in self.users or user.key.id() in self.domains):
|
||||
# outbound; show a nice link to the user
|
||||
return user.user_link()
|
||||
|
||||
proto = PROTOCOLS.get(self.source_protocol)
|
||||
|
||||
actor = None
|
||||
if self.as1:
|
||||
actor = (as1.get_object(self.as1, 'actor')
|
||||
|
@ -913,7 +990,6 @@ class Object(StringIdModel):
|
|||
# hydrate from datastore if available
|
||||
# TODO: optimize! this is called serially in loops, eg in home.html
|
||||
if set(actor.keys()) == {'id'} and self.source_protocol:
|
||||
proto = PROTOCOLS[self.source_protocol]
|
||||
actor_obj = proto.load(actor['id'], remote=False)
|
||||
if actor_obj and actor_obj.as1:
|
||||
actor = actor_obj.as1
|
||||
|
@ -929,7 +1005,19 @@ class Object(StringIdModel):
|
|||
if not image or not img_url:
|
||||
return common.pretty_link(url, text=name, attrs=attrs, user=user)
|
||||
|
||||
# from protocol import Protocol
|
||||
# if actor_proto := Protocol.for_id(actor['id']):
|
||||
# if actor_user := actor_proto.get_by_id(actor['id']):
|
||||
# if actor_user and (actor_user.direct or actor_user.enabled_protocols
|
||||
# or actor_user.LABEL == 'web'):
|
||||
# url = actor_user.user_page_path()
|
||||
|
||||
logo = ''
|
||||
if proto:
|
||||
logo = f'<span class="logo" title="{self.__class__.__name__}">{proto.LOGO_HTML}</span>'
|
||||
|
||||
return f"""\
|
||||
{logo}
|
||||
<a class="h-card u-author" href="{url}" title="{name}">
|
||||
<img class="profile" src="{img_url}" {'width="32"' if sized else ''}/>
|
||||
{util.ellipsize(name, chars=40)}
|
||||
|
|
22
pages.py
22
pages.py
|
@ -74,7 +74,7 @@ def load_user(protocol, id):
|
|||
|
||||
if cls.ABBREV != 'web':
|
||||
if not user:
|
||||
user = cls.query(OR(cls.handle == id, cls.handle == id)).get()
|
||||
user = cls.query(cls.handle == id).get()
|
||||
if user and user.use_instead:
|
||||
user = user.use_instead.get()
|
||||
|
||||
|
@ -84,7 +84,7 @@ def load_user(protocol, id):
|
|||
elif user and id != user.key.id(): # use_instead redirect
|
||||
error('', status=302, location=user.user_page_path())
|
||||
|
||||
if user and (user.direct or cls.ABBREV != 'ap'):
|
||||
if user and (user.direct or user.enabled_protocols or cls.ABBREV == 'web'):
|
||||
assert not user.use_instead
|
||||
return user
|
||||
|
||||
|
@ -167,22 +167,20 @@ def notifications(protocol, id):
|
|||
@canonicalize_request_domain(common.PROTOCOL_DOMAINS, common.PRIMARY_DOMAIN)
|
||||
def update_profile(protocol, id):
|
||||
user = load_user(protocol, id)
|
||||
link = f'<a href="{user.web_url()}">{user.handle_or_id()}</a>'
|
||||
|
||||
try:
|
||||
profile_obj = user.load(user.profile_id(), remote=True)
|
||||
if profile_obj:
|
||||
msg, status = user.receive(profile_obj)
|
||||
else:
|
||||
status = 400
|
||||
msg = "couldn't fetch profile"
|
||||
|
||||
except (requests.RequestException, werkzeug.exceptions.HTTPException) as e:
|
||||
status, msg = util.interpret_http_exception(e)
|
||||
_, msg = util.interpret_http_exception(e)
|
||||
flash(f"Couldn't update profile for {link}: {msg}")
|
||||
|
||||
if int(status) // 100 == 2:
|
||||
flash(f'Updating profile for {user.handle_or_id()}')
|
||||
if profile_obj:
|
||||
common.create_task(queue='receive', obj=profile_obj.key.urlsafe(),
|
||||
authed_as=id)
|
||||
flash(f'Updating profile from {link}...')
|
||||
else:
|
||||
flash(f"Couldn't update profile for {user.handle_or_id()}: {msg}")
|
||||
flash(f"Couldn't update profile for {link}")
|
||||
|
||||
return redirect(user.user_page_path(), code=302)
|
||||
|
||||
|
|
74
protocol.py
74
protocol.py
|
@ -11,7 +11,7 @@ from flask import g, request
|
|||
from google.cloud import ndb
|
||||
from google.cloud.ndb import OR
|
||||
from google.cloud.ndb.model import _entity_to_protobuf
|
||||
from granary import as1
|
||||
from granary import as1, as2
|
||||
from oauth_dropins.webutil.appengine_info import DEBUG
|
||||
from oauth_dropins.webutil.flask_util import cloud_tasks_only
|
||||
from oauth_dropins.webutil import models
|
||||
|
@ -143,52 +143,6 @@ class Protocol:
|
|||
label = domain.removesuffix(common.SUPERDOMAIN)
|
||||
return PROTOCOLS.get(label)
|
||||
|
||||
@classmethod
|
||||
def is_enabled_to(from_cls, to_cls, user=None):
|
||||
"""Returns True if two protocols, and optionally a user, can be bridged.
|
||||
|
||||
Reasons this might return False:
|
||||
* We haven't turned on bridging these two protocols yet.
|
||||
* The user is opted out.
|
||||
* The user is on a domain that's opted out.
|
||||
* The from protocol requires opt in, and the user hasn't opted in.
|
||||
|
||||
Args:
|
||||
from_cls (Protocol subclass)
|
||||
to_cls (Protocol subclass)
|
||||
user (:class:`models.User` or str): optional, user or id
|
||||
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
if from_cls == to_cls:
|
||||
return True
|
||||
|
||||
from_label = from_cls.LABEL
|
||||
to_label = to_cls.LABEL
|
||||
|
||||
if DEBUG and (from_label in ('fake', 'other')
|
||||
or (to_label in ('fake', 'other') and from_label != 'eefake')):
|
||||
return True
|
||||
|
||||
user_id = None
|
||||
if isinstance(user, User):
|
||||
user_id = user.key.id() if user.key else None
|
||||
elif isinstance(user, str):
|
||||
user_id = user
|
||||
user = from_cls.get_by_id(user_id, allow_opt_out=True)
|
||||
|
||||
if user:
|
||||
if user.status == 'opt-out':
|
||||
return False
|
||||
elif to_label in user.enabled_protocols:
|
||||
return True
|
||||
|
||||
if to_label in from_cls.DEFAULT_ENABLED_PROTOCOLS:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def owns_id(cls, id):
|
||||
"""Returns whether this protocol owns the id, or None if it's unclear.
|
||||
|
@ -613,8 +567,7 @@ class Protocol:
|
|||
|
||||
def translate(elem, field, fn):
|
||||
elem[field] = as1.get_object(elem, field)
|
||||
id = elem[field].get('id')
|
||||
if id and util.domain_from_link(id) not in DOMAINS:
|
||||
if id := elem[field].get('id'):
|
||||
from_cls = Protocol.for_id(id)
|
||||
# TODO: what if from_cls is None? relax translate_object_id,
|
||||
# make it a noop if we don't know enough about from/to?
|
||||
|
@ -706,6 +659,8 @@ class Protocol:
|
|||
actor = as1.get_owner(obj.as1)
|
||||
if not actor:
|
||||
error('Activity missing actor or author', status=400)
|
||||
elif from_cls.owns_id(actor) is False:
|
||||
error(f"{from_cls.LABEL} doesn't own actor {actor}, this is probably a bridged activity. Skipping.", status=204)
|
||||
|
||||
if authed_as:
|
||||
assert isinstance(authed_as, str)
|
||||
|
@ -828,11 +783,15 @@ class Protocol:
|
|||
elif obj.type == 'post':
|
||||
to_cc = (as1.get_ids(inner_obj_as1, 'to')
|
||||
+ as1.get_ids(inner_obj_as1, 'cc'))
|
||||
content = inner_obj_as1.get('content', '').strip().lower()
|
||||
if len(to_cc) == 1:
|
||||
logger.info(f'got DM to {to_cc}: {content}')
|
||||
if len(to_cc) == 1 and to_cc != [as2.PUBLIC_AUDIENCE]:
|
||||
proto = Protocol.for_bridgy_subdomain(to_cc[0])
|
||||
if proto:
|
||||
# remove @-mentions of bot user in HTML links
|
||||
soup = util.parse_html(inner_obj_as1.get('content', ''))
|
||||
for link in soup.find_all('a'):
|
||||
link.extract()
|
||||
content = soup.get_text().strip().lower()
|
||||
logger.info(f'got DM to {to_cc}: {content}')
|
||||
if content in ('yes', 'ok'):
|
||||
from_user.enable_protocol(proto)
|
||||
elif content == 'no':
|
||||
|
@ -994,13 +953,15 @@ class Protocol:
|
|||
Returns:
|
||||
models.Object: ``obj`` if it's an activity, otherwise a new object
|
||||
"""
|
||||
if obj.type not in set(('note', 'article', 'comment')) | as1.ACTOR_TYPES:
|
||||
is_actor = obj.type in as1.ACTOR_TYPES
|
||||
if not is_actor and obj.type not in ('note', 'article', 'comment'):
|
||||
return obj
|
||||
|
||||
obj_actor = as1.get_owner(obj.as1)
|
||||
now = util.now().isoformat()
|
||||
|
||||
# this is a raw post; wrap it in a create or update activity
|
||||
if obj.changed or is_actor:
|
||||
if obj.changed:
|
||||
logger.info(f'Content has changed from last time at {obj.updated}! Redelivering to all inboxes')
|
||||
id = f'{obj.key.id()}#bridgy-fed-update-{now}'
|
||||
|
@ -1321,8 +1282,8 @@ class Protocol:
|
|||
if obj.source_protocol:
|
||||
logger.warning(f'Object {obj.key.id()} changed protocol from {obj.source_protocol} to {cls.LABEL} ?!')
|
||||
obj.source_protocol = cls.LABEL
|
||||
obj.put()
|
||||
|
||||
obj.put()
|
||||
with objects_cache_lock:
|
||||
objects_cache[id] = obj
|
||||
return obj
|
||||
|
@ -1354,8 +1315,11 @@ def receive_task():
|
|||
|
||||
authed_as = form.get('authed_as')
|
||||
|
||||
internal = (authed_as == common.PRIMARY_DOMAIN
|
||||
or authed_as in common.PROTOCOL_DOMAINS)
|
||||
try:
|
||||
return PROTOCOLS[obj.source_protocol].receive(obj=obj, authed_as=authed_as)
|
||||
return PROTOCOLS[obj.source_protocol].receive(obj=obj, authed_as=authed_as,
|
||||
internal=internal)
|
||||
except ValueError as e:
|
||||
logger.warning(e, exc_info=True)
|
||||
error(e, status=304)
|
||||
|
|
|
@ -32,7 +32,7 @@ feedgen==1.0.0
|
|||
feedparser==6.0.11
|
||||
fixtures==4.1.0
|
||||
Flask==3.0.3
|
||||
Flask-Caching==2.1.0
|
||||
Flask-Caching==2.2.0
|
||||
flask-gae-static==1.0
|
||||
flask-sock==0.7.0
|
||||
google-api-core[grpc]==2.17.1
|
||||
|
@ -48,7 +48,7 @@ google-cloud-ndb==2.3.1
|
|||
google-cloud-tasks==2.16.3
|
||||
googleapis-common-protos==1.63.0
|
||||
grpc-google-iam-v1==0.13.0
|
||||
grpcio==1.62.2
|
||||
grpcio==1.63.0
|
||||
grpcio-status==1.62.2
|
||||
gunicorn==22.0.0
|
||||
h11==0.14.0
|
||||
|
@ -60,7 +60,7 @@ idna==3.7
|
|||
iterators==0.2.0
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.3
|
||||
jsonschema==4.21.1
|
||||
jsonschema==4.22.0
|
||||
lxml==5.2.1
|
||||
MarkupSafe==2.1.5
|
||||
mf2py==2.0.1
|
||||
|
@ -89,7 +89,7 @@ pytz==2024.1
|
|||
PyYAML==6.0.1
|
||||
redis==5.0.4
|
||||
requests==2.31.0
|
||||
requests-oauthlib==1.4.0
|
||||
requests-oauthlib==2.0.0
|
||||
rsa==4.9
|
||||
sgmllib3k==1.0.0
|
||||
simple-websocket==1.0.0
|
||||
|
|
|
@ -2,6 +2,7 @@ User-agent: *
|
|||
Disallow: /*?*
|
||||
Disallow: /ap/
|
||||
Disallow: /atp/
|
||||
Disallow: /bsky/
|
||||
Disallow: /bluesky/
|
||||
Disallow: /bridge-user
|
||||
Disallow: /convert/
|
||||
|
|
|
@ -199,6 +199,10 @@ pre .value, code .value, code.value {
|
|||
color: #88305b;
|
||||
}
|
||||
|
||||
.front-light .bluesky, .front-light .bluesky:hover {
|
||||
color: #305b88;
|
||||
}
|
||||
|
||||
.front-dark .web, .front-dark .web:hover {
|
||||
color: #b7e4c6;
|
||||
}
|
||||
|
@ -207,7 +211,12 @@ pre .value, code .value, code.value {
|
|||
color: #e4b7c6;
|
||||
}
|
||||
|
||||
.fediverse img {
|
||||
.front-dark .bluesky, .front-dark .bluesky:hover {
|
||||
color: #b7c6e4;
|
||||
}
|
||||
|
||||
.fediverse img,
|
||||
.bluesky img {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
|
@ -460,6 +469,13 @@ button[disabled]:hover {
|
|||
color: #337AB7;
|
||||
}
|
||||
|
||||
.btn-copy {
|
||||
color: #66A0D0;
|
||||
background-color: transparent;
|
||||
border-color: transparent !important;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
.btn-default:hover, .btn-default:hover:focus {
|
||||
background-color: #BDE;
|
||||
border-color: #337AB7;
|
||||
|
|
|
@ -3,20 +3,9 @@
|
|||
|
||||
{% for f in followers %}
|
||||
<li class="row">
|
||||
{% with url=f.user.web_url(), user_as1=f.user.obj.as1 or {} %}
|
||||
<a class="follower col-xs-10 col-sm-10 col-lg-6" href="{{ url }}">
|
||||
<span class="logo" title="{{ user.__class__.__name__ }}">
|
||||
{{ f.user.LOGO_HTML|safe }}
|
||||
<span class="follower col-xs-10 col-sm-10 col-lg-6">
|
||||
{{ f.user.user_link(handle=True)|safe }}
|
||||
</span>
|
||||
{% with picture=util.get_url(user_as1, 'icon') or util.get_url(user_as1, 'image') %}
|
||||
{% if picture %}
|
||||
<img class="profile u-photo" src="{{ picture }}" width="48px">
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{{ user_as1.get('displayName') or '' }}
|
||||
{{ f.user.handle or url }}
|
||||
</a>
|
||||
{% endwith %}
|
||||
|
||||
{% if page_name == 'following' %}
|
||||
<form method="post" action="/unfollow/start" class="col-xs-2 col-sm-1 col-lg-1">
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
{% for obj in objects %}
|
||||
<li class="row h-entry">
|
||||
<div class="e-content col-xs-{{ 5 if show_users else 8 }}">
|
||||
{% if obj.source_protocol %}
|
||||
<span class="logo">{{ PROTOCOLS[obj.source_protocol].LOGO_HTML|safe }}</span>
|
||||
{% endif %}
|
||||
{% if show_activity_actors %}
|
||||
{{ obj.actor_link(user=user)|safe }}
|
||||
{% else %}
|
||||
...
|
||||
{% endif %}
|
||||
{{ obj.phrase|safe }}
|
||||
{% if obj.url %}<a target="_blank" href="{{ obj.url }}" class="u-url">{% endif %}
|
||||
{{ obj.content|default('--', true)|striptags|truncate(50) }}
|
||||
|
|
|
@ -46,8 +46,9 @@
|
|||
<p class="header-links">
|
||||
<a href="/docs">Docs</a>
|
||||
<a href="https://snarfed.org/?s=%22bridgy+fed%22">News</a>
|
||||
<a href="mailto:feedback@brid.gy">Feedback</a>
|
||||
<a href="https://github.com/snarfed/bridgy-fed">Source</a>
|
||||
<a href="https://github.com/snarfed/bridgy-fed">Code</a>
|
||||
<a href="https://github.com/snarfed/bridgy-fed/issues">Issues</a>
|
||||
<a href="mailto:feedback@brid.gy">Feedback</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -58,8 +59,9 @@
|
|||
<p class="header-links">
|
||||
<a href="/docs">Docs</a>
|
||||
<a href="https://snarfed.org/?s=%22bridgy+fed%22">News</a>
|
||||
<a href="mailto:feedback@brid.gy">Feedback</a>
|
||||
<a href="https://github.com/snarfed/bridgy-fed">Source</a>
|
||||
<a href="https://github.com/snarfed/bridgy-fed">Code</a>
|
||||
<a href="https://github.com/snarfed/bridgy-fed/issues">Issues</a>
|
||||
<a href="mailto:feedback@brid.gy">Feedback</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -24,28 +24,69 @@
|
|||
|
||||
<ul class="docs">
|
||||
|
||||
<p><em>Using</em></p>
|
||||
<p><em>About</em></p>
|
||||
<li><a href="#which-networks">Which networks are supported?</a></li>
|
||||
<li><a href="#get-started">How do I get started?</a></li>
|
||||
<li><a href="#which-accounts">Which accounts do I need?</a></li>
|
||||
<li><a href="#login">How do I log into Bridgy Fed?</a></li>
|
||||
<li><a href="#visibility">Who can see me and my stuff?</a></li>
|
||||
<li><a href="#other-bridges">What about other bridges?</a></li>
|
||||
<li><a href="#bridgy-classic">What's the difference between this and Bridgy classic, ie non-Fed?</a></li>
|
||||
<li><a href="#behavior-mismatches">How does this handle behavior differences between networks?</a></li>
|
||||
<li><a href="#format-mismatches">How does this handle formatting differences between networks?</a></li>
|
||||
<li><a href="#moderation">What about moderation? How do blocks, mutes, etc work?</a></li>
|
||||
<li><a href="#opt-out">How do I opt out and remove my site or account?</a></li>
|
||||
|
||||
<br>
|
||||
|
||||
<p><em>Using</em></p>
|
||||
<li><a href="#get-started">How do I get started?</a></li>
|
||||
<li><a href="#which-accounts">Which accounts do I need?</a></li>
|
||||
<li><a href="#login">How do I log into Bridgy Fed?</a></li>
|
||||
<li><a href="#web-update-profile">How do I update my profile?</a></li>
|
||||
<li><a href="#user-page">Can I see my account's status and recent activity?</a></li>
|
||||
<li><a href="#troubleshooting">I tried it, and it didn't work!</a></li>
|
||||
<li><a href="#enhanced">The <code>*.brid.gy</code> domain in my bridged account is ugly. Can I get rid of it and use my own domain/web site instead?</a></li>
|
||||
<li><a href="#opt-out">How do I opt out and remove my site or account?</a></li>
|
||||
<!-- <li><a href="#enhanced">The <code>*.brid.gy</code> domain in my bridged account is ugly. Can I get rid of it and use my own domain/web site instead?</a></li> -->
|
||||
|
||||
<br>
|
||||
|
||||
<p><em>From the fediverse</em></p>
|
||||
|
||||
<li><a href="#fediverse-get-started">How do I get started?</a></li>
|
||||
<li><a href="#fediverse-follow-bluesky">How do I find a bridged Bluesky account?</a></li>
|
||||
<li><a href="#fediverse-protocol-bot-user-blocked">I can't find <code>@bsky.brid.gy@bsky.brid.gy</code> on the fediverse!</a></li>
|
||||
<li><a href="#fediverse-follow-web">How do I find a bridged web site?</a></li>
|
||||
<li><a href="#fediverse-DM">What is this DM I got from a <code>*.brid.gy</code> account?</a></li>
|
||||
<li><a href="#fediverse-no-DM">Someone requested to follow me, but I never got the DM!</a></li>
|
||||
<li><a href="#fediverse-what">Which stuff of mine from the fediverse will get bridged into Bluesky?</a></li>
|
||||
|
||||
<br>
|
||||
|
||||
<p><em>To the fediverse</em></p>
|
||||
|
||||
<li><a href="#fediverse-servers">Which fediverse servers are supported?</a></li>
|
||||
<li><a href="#mastodon-link-verification">How do I verify my profile links (ie get green checks) in Mastodon?</a></li>
|
||||
<li><a href="#fediverse-enhanced">Can I use my own domain as my fediverse handle?</a></li>
|
||||
<li><a href="#instance-subdomains">Could other networks' instances get their own brid.gy subdomains, so that fediverse admins can federate with or block them individually?</a></li>
|
||||
|
||||
<br>
|
||||
|
||||
<p><em>From Bluesky</em></p>
|
||||
|
||||
<li><a href="#bluesky-get-started">How do I get started?</a></li>
|
||||
<li><a href="#bluesky-follow">How do I find a bridged fediverse account?</a></li>
|
||||
<li><a href="#bluesky-delay">I followed @ap.brid.gy, or followed someone else or posted or did something else in Bluesky, but it hasn't shown up in the fediverse yet!</a></li>
|
||||
<li><a href="#bluesky-what">Which stuff of mine from Bluesky will get bridged into the fediverse?</a>
|
||||
<li><a href="#bluesky-reply-controls">Can I use Bluesky's reply controls?</a></li>
|
||||
|
||||
<br>
|
||||
|
||||
<p><em>To Bluesky</em></p>
|
||||
|
||||
<li><a href="#bluesky-report">What happens when I report a bridged Bluesky user?</a></li>
|
||||
|
||||
<br>
|
||||
|
||||
<p><em>From the web</em></p>
|
||||
<li><a href="#web-get-started">How do I get started?</a></li>
|
||||
<li><a href="#web-profile">Where does my profile info come from?</a></li>
|
||||
<li><a href="#web-update-profile">How do I update my profile?</a></li>
|
||||
<li><a href="#web-how-post">How does Bridgy Fed find my posts?</a></li>
|
||||
<li><a href="#web-interpret">How does it interpret and translate my posts?</a></li>
|
||||
<li><a href="#web-which-parts">How does it decide which parts of my posts to include?</a></li>
|
||||
|
@ -71,22 +112,7 @@
|
|||
|
||||
<br>
|
||||
|
||||
<p><em>From the fediverse</em></p>
|
||||
|
||||
<li><a href="#fediverse-follow">How do I follow someone from the fediverse?</a></li>
|
||||
|
||||
<br>
|
||||
|
||||
<p><em>To the fediverse</em></p>
|
||||
|
||||
<li><a href="#fediverse-servers">Which fediverse servers are supported?</a></li>
|
||||
<li><a href="#mastodon-link-verification">How do I verify my profile links (ie get green checks) in Mastodon?</a></li>
|
||||
<li><a href="#fediverse-enhanced">Can I use my own domain as my fediverse handle?</a></li>
|
||||
<li><a href="#instance-subdomains">Could other networks' instances get their own brid.gy subdomains, so that fediverse admins can federate with or block them individually?</a></li>
|
||||
|
||||
<br>
|
||||
|
||||
<p><em>About</em></p>
|
||||
<p><em>Background</em></p>
|
||||
<li><a href="#who">Who are you? Why did you make this?</a></li>
|
||||
<li><a href="#cost">How much does it cost?</a></li>
|
||||
<li><a href="#privacy">What do you do with my data?</a></li>
|
||||
|
@ -104,7 +130,7 @@
|
|||
<li><a href="#compare">How do the different protocols compare?</a></li>
|
||||
<li><a href="#translate">How are the different protocols translated?</a></li>
|
||||
<li><a href="#router">How are activities routed?</a></li>
|
||||
<li><a href="#error-handling">How are errors handled?</a></li>
|
||||
<!-- <li><a href="#error-handling">How are errors handled?</a></li> -->
|
||||
|
||||
</ul>
|
||||
|
||||
|
@ -112,34 +138,17 @@
|
|||
|
||||
<br>
|
||||
|
||||
<h3 id="general">Using</h3>
|
||||
<h3 id="about">About</h3>
|
||||
|
||||
<li id="which-networks" class="question">Which networks are supported?</li>
|
||||
<li class="answer">
|
||||
<p>Bridgy Fed currently supports the <a href="https://en.wikipedia.org/wiki/Fediverse">fediverse</a> and the web. We plan to add <a href="https://blueskyweb.xyz/">Bluesky</a>/<a href="https://atproto.com/">AT Protocol</a> and <a href="https://nostr.com/">Nostr</a> in 2024.</p>
|
||||
<p>Bridgy Fed currently supports <a href="https://indieweb.org/">the web</a>, <a href="https://en.wikipedia.org/wiki/Fediverse">fediverse</a>, and <a href="https://bsky.social/">Bluesky</a>. We're considering adding more networks, including <a href="https://nostr.com/">Nostr</a> and <a href="https://www.farcaster.xyz/">Farcaster</a>.</p>
|
||||
<p>All bridging is fully bidirectional. If you're on a supported network, you can use Bridgy Fed to follow and interact with anyone on any other supported network.</p>
|
||||
</li>
|
||||
|
||||
<li id="get-started" class="question">How do I get started?</li>
|
||||
<li class="answer">
|
||||
<p>If you're on the web, first <a href="#web-get-started">set up your web site</a>, then <a href="#web-follow">follow someone on the fediverse</a>.</p>
|
||||
<p>If you're on the fediverse, <a href="#fediverse-follow">try following a web site</a>.</p>
|
||||
</li>
|
||||
|
||||
<li id="which-accounts" class="question">Which accounts do I need?</li>
|
||||
<li class="answer">
|
||||
<p>None! At least, no more than the accounts you already have. Bridgy Fed doesn't cross-post (ie copy posts) between separate accounts. Instead, it <a href="https://en.wikipedia.org/wiki/Federation_(information_technology)"><em>federates</em></a>, or mirrors, your existing accounts into other networks.</p>
|
||||
<p>If you want to cross-post instead, check out <a href="https://brid.gy/">Bridgy classic</a>!</p>
|
||||
</li>
|
||||
|
||||
<li id="login" class="question">How do I log into Bridgy Fed?</li>
|
||||
<li class="answer">
|
||||
<p>You don't! Bridgy Fed doesn't have its own accounts or logins.</p>
|
||||
</li>
|
||||
|
||||
<li id="visibility" class="question">Who can see me and my stuff?</li>
|
||||
<li class="answer">
|
||||
<p>Only the people who can already see you and your stuff, as is. Bridgy Fed only bridges fully public data, so if your account is private or protected or followers-only, it won't (can't!) bridge your account at all. Same with DMs and private/followers-only posts; it ignores those.</p>
|
||||
<p>Only the people who can already see you and your stuff, without bridging. Bridgy Fed only bridges fully public data, so if your account is private or protected or followers-only, it won't (can't!) bridge your account at all. Same with DMs and private/followers-only posts; it ignores those.</p>
|
||||
</li>
|
||||
|
||||
<li id="other-bridges" class="question">What about other bridges?</li>
|
||||
|
@ -153,8 +162,8 @@
|
|||
<li id="bridgy-classic" class="question">What's the difference between this and Bridgy classic, ie non-Fed?</li>
|
||||
<li class="answer">
|
||||
<p><a href="https://fed.brid.gy/">Bridgy Fed</a> and <a href="https://brid.gy/">Bridgy classic</a> are separate services. They both connect web sites and social networks and translate posts and interactions back and forth, but they each do it very differently.</p>
|
||||
<a href="https://fed.brid.gy/">Bridgy Fed</a> - this service - bridges accounts on decentralized social networks like the <a href="https://en.wikipedia.org/wiki/Fediverse">fediverse</a>, <a href="https://blueskyweb.xyz/">Bluesky</a>/<a href="https://atproto.com/">AT Protocol</a>, <a href="https://nostr.com/">Nostr</a>, and the IndieWeb <em>directly</em> across those networks.</p>
|
||||
<p><a href="https://brid.gy/">Bridgy classic</a>, on the other hand, connects <a href="https://indieweb.org/">IndieWeb</a> web sites to <em>existing</em> accounts on social networks, both centralized and decentralized, and provides <a href="https://indieweb.org/backfeed">backfeed</a> and <a href="https://indieweb.org/POSSE">POSSE</a> (aka cross-posting) as a service.</p>
|
||||
<a href="https://fed.brid.gy/">Bridgy Fed</a> - this service - bridges accounts on decentralized social networks like the <a href="https://en.wikipedia.org/wiki/Fediverse">fediverse</a>, <a href="https://bsky.social/">Bluesky</a>/<a href="https://atproto.com/">AT Protocol</a>, <a href="https://nostr.com/">Nostr</a>, and the <a href="https://indieweb.org/">IndieWeb</a> <em>directly</em> across those networks.</p>
|
||||
<p><a href="https://brid.gy/">Bridgy classic</a>, on the other hand, connects IndieWeb web sites to <em>existing</em> accounts on social networks, both centralized and decentralized, and provides <a href="https://indieweb.org/backfeed">backfeed</a> and <a href="https://indieweb.org/POSSE">POSSE</a> (aka cross-posting) as a service.</p>
|
||||
<p>As an example, here's a visualization of how they each connect web sites to the fediverse:</p>
|
||||
|
||||
<br>
|
||||
|
@ -215,27 +224,244 @@
|
|||
<p>For more background, see our <a href="#moderation-policy">content moderation policy</a> and <a href="https://snarfed.org/2024-01-19_moderate-people-not-code">this blog post</a>.</p>
|
||||
</li>
|
||||
|
||||
<li id="opt-out" class="question">How do I opt out and remove my site or account?</li>
|
||||
<li class="answer">
|
||||
<p>If you're on the fediverse or Bluesky, and you've opted in but now want to opt out, block the Bridgy Fed bot user for the network you want to opt out of. For example, on the fediverse, block <code>@bsky.brid.gy@bsky.brid.gy</code>. On Bluesky, block <a href="https://bsky.app/profile/ap.brid.gy">@ap.brid.gy</a>.</p>
|
||||
<p>Also, if you're on Bluesky and you've <a href="https://bsky.app/profile/safety.bsky.app/post/3khhw7s3rtx2s">hidden your account in the logged out view</a>, Bridgy Fed interprets that as opting out and won't bridge your account.</p>
|
||||
<p>If you're on the web, feel free to <a href="mailto:feedback@brid.gy">email me</a>, or you can put the text <code>#nobridge</code> in the <a href="#web-profile">profile on your home page</a> and then <a href="#web-update-profile">update your profile</a> on <a href="#user-page">your user page</a>.</p>
|
||||
</li>
|
||||
|
||||
|
||||
<h3 id="general">Using</h3>
|
||||
|
||||
<li id="get-started" class="question">How do I get started?</li>
|
||||
<li class="answer">
|
||||
<p>Here are instructions if you're coming from:</p>
|
||||
<ul>
|
||||
<li><a href="#web-get-started">the web</a></li>
|
||||
<li><a href="#fediverse-get-started">the fediverse</a></li>
|
||||
<li><a href="#bluesky-get-started">Bluesky</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li id="which-accounts" class="question">Which accounts do I need?</li>
|
||||
<li class="answer">
|
||||
<p>None! At least, no more than the accounts you already have. Bridgy Fed doesn't cross-post (ie copy posts) between separate accounts. Instead, it <a href="https://en.wikipedia.org/wiki/Federation_(information_technology)"><em>federates</em></a>, or mirrors, your existing accounts into other networks.</p>
|
||||
<p>If you want to cross-post instead, check out <a href="https://brid.gy/">Bridgy classic</a>!</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="login" class="question">How do I log into Bridgy Fed?</li>
|
||||
<li class="answer">
|
||||
<p>You don't! Bridgy Fed doesn't have its own accounts or logins.</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="web-update-profile" class="question">How do I update my profile?</li>
|
||||
<li class="answer">
|
||||
<p>Click the <button class="btn btn-default glyphicon glyphicon-refresh"></button> button <a href="/web-site">on your user page</a>. Bridgy Fed will refresh your profile and send it to any networks you're bridged into.
|
||||
</li>
|
||||
|
||||
|
||||
<li id="user-page" class="question">Can I see my account's status and recent activity?</li>
|
||||
<li class="answer">
|
||||
<p>Definitely! Bridgy Fed has a dashboard for every account that it's seen. <a href="/web-site">Enter your domain here</a> to see your user page. It shows your site's current status in Bridgy Fed, recent interactions, remote follow UI, and links to your timeline feeds in various formats.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="troubleshooting" class="question">I tried it, and it didn't work!</li>
|
||||
<li class="answer">
|
||||
<p><a href="#user-page">Check out your user page!</a> It detects and describes common problems with <a href="#web-setup">your setup</a>, and it shows your recent interactions and detailed logs.
|
||||
</li>
|
||||
|
||||
<li id="enhanced" class="question">The <code>*.brid.gy</code> domain in my bridged account's handle is ugly. Can I get rid of it and use my own domain/web site instead?</li>
|
||||
|
||||
<!-- <li id="enhanced" class="question">The <code>*.brid.gy</code> domain in my bridged account's handle is ugly. Can I get rid of it and use my own domain/web site instead?</li> -->
|
||||
<!-- <li class="answer"> -->
|
||||
<!-- <p>Yes! All supported networks let you use <a href="https://indieweb.org/personal-domain">your own domain</a> as your handle in various ways. This takes a bit of technical setup with DNS and/or a web server, but it's very doable. <a href="#fediverse-enhanced">Here are instructions for web sites bridged into the fediverse.</a></p> -->
|
||||
<!-- </li> -->
|
||||
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
<h3 id="from-fediverse">From the fediverse</h3>
|
||||
|
||||
<li id="fediverse-get-started" class="question">How do I get started?</li>
|
||||
<li class="answer">
|
||||
<p>Yes! All supported networks let you use <a href="https://indieweb.org/personal-domain">your own domain</a> as your handle in various ways. This takes a bit of technical setup with DNS and/or a web server, but it's very doable. <a href="#fediverse-enhanced">Here are instructions for web sites bridged into the fediverse.</a></p>
|
||||
<p>To bridge your fediverse account into Bluesky and interact with people there, search for and follow <code>@bsky.brid.gy@bsky.brid.gy</code>.</p>
|
||||
<p>If your fediverse account is <code>@[user]@[instance]</code>, your bridged account will have the handle <code>[user].[instance].ap.brid.gy</code> in Bluesky. For example, <a href="https://indieweb.social/@snarfed">@snarfed@indieweb.social</a> is bridged into Bluesky as <a href="https://bsky.app/profile/snarfed.indieweb.social.ap.brid.gy">@snarfed.indieweb.social.ap.brid.gy</a>.</p>
|
||||
<p>Bluesky limits profile bios to 256 characters, so if yours is longer in the fediverse, it will be truncated and ellipsized.</p>
|
||||
<p>Alternatively, <a href="#fediverse-follow">you can find and follow bridged Bluesky accounts</a> without bridging your own account, but they won't see your posts or interactions.<p>
|
||||
</li>
|
||||
|
||||
<li id="opt-out" class="question">How do I opt out and remove my site or account?</li>
|
||||
|
||||
<li id="fediverse-follow-bluesky" class="question">How do I find a bridged Bluesky account?</li>
|
||||
<li class="answer">
|
||||
<p>Put the text <code>#nobridge</code> in your profile bio, refresh your profile <a href="#user-page">on your user page</a>, and Bridgy Fed will stop bridging your account. Or feel free to <a href="mailto:feedback@brid.gy">send me a request privately</a>.</p>
|
||||
<p>Bridged Bluesky accounts appear in the fediverse as <code>@[handle]@bsky.brid.gy</code>. For example, <a href="https://bsky.app/profile/snarfed.bsky.social">@snarfed.bsky.social</a> on Bluesky is bridged into the fediverse as <code>@snarfed.bsky.social@bsky.brid.gy</code>.</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="fediverse-protocol-bot-user-blocked" class="question">I can't find <code>@bsky.brid.gy@bsky.brid.gy</code> on the fediverse!</li>
|
||||
<li class="answer">
|
||||
<p>If you search for <code>@bsky.brid.gy@bsky.brid.gy</code> on your fediverse instance and you don't see any results, your server may be blocking Bridgy Fed. Check your server's <em>About</em> page to see if <code>bsky.brid.gy</code> or <code>brid.gy</code> are in the <em>Moderated servers</em>section. If they are, you can ask your server admin to reconsider, and include a link to this page for more details.</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="fediverse-follow-web" class="question">How do I find a bridged web site?</li>
|
||||
<li class="answer">
|
||||
<p>You can follow any web site, eg <a class="handle" href="https://example.com/">example.com</a>, by searching for <span class="handle">@example.com@web.brid.gy</span> in your fediverse instance.</p>
|
||||
<p>Bridged web sites appear in the fediverse as either <code>@[domain]@[domain]</code>, <code>@[domain]@web.brid.gy</code>, or <code>@[domain]@fed.brid.gy</code>, depending on the fediverse server and whether the web site owner has <a href="#fediverse-enhanced">connected their domain to Bridgy Fed</a>. All bridged web sites behave the same in the fediverse; the different instances in their handles are purely cosmetic.</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="fediverse-DM" class="question">What is this DM I got from a <code>*.brid.gy</code> account?</li>
|
||||
<li class="answer">
|
||||
<p>In the future, if you haven't opted into the bridge, and someone wants to follow you from a different network, they can request to follow you from the Bridgy Fed home page. The first time this happens, Bridgy Fed will send you a DM to introduce itself and ask if you want to opt into bridging your account. If you reply <em>yes</em>, or follow the Bridgy Fed account that DMed you, your account will be bridged. If you do nothing, or reply <em>no</em> or block the Bridgy Fed account, you won't be bridged.</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="fediverse-no-DM" class="question">Someone requested to follow me, but I never got the DM!</li>
|
||||
<li class="answer">
|
||||
<p>Does your fediverse instance support DMs? Only some do. Otherwise, <a href="#bluesky-protocol-bot-user-blocked">if your instance is blocking Bridgy Fed</a>, you won't receive the DM. Or if it's <a href="https://docs.joinmastodon.org/admin/moderation/#limit-server">limiting</a> Bridgy Fed, or you're filtering notifications (this is sometimes on by default!), check the <em>Filtered notifications</em> section of your mention notifications.</p>
|
||||
<p>Having said that, if you want to bridge your account, you don't need the DM! Just <a href="#fediverse-get-started">follow or DM <em>yes</em> to <code>@bsky.brid.gy@bsky.brid.gy</code></a>.</p>
|
||||
</li>
|
||||
|
||||
<li id="fediverse-what" class="question">Which stuff of mine from the fediverse will get bridged into Bluesky?</li>
|
||||
<li class="answer">
|
||||
<p>Anything that interacts with Bluesky users. This includes replies, @-mentions, likes, reposts, and if you have any Bluesky followers, your own posts. Posts on Bluesky are limited to 300 characters, so longer posts from the fediverse are truncated and ellipsized. Hashtags, links, link previews, images, and even alt text are also included, but not videos since Bluesky itself doesn't support them.</p>
|
||||
</li>
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<h3 id="to-fediverse">To the fediverse</h3>
|
||||
|
||||
<li id="fediverse-servers" class="question">Which fediverse servers are supported?</li>
|
||||
<li class="answer">
|
||||
<p>Lots! <a href="https://joinmastodon.org/">Mastodon</a>, <a href="https://friendi.ca/">Friendica</a>, <a href="https://misskey.page/">Misskey</a>, <a href="https://joinpeertube.org/">PeerTube</a>, <a href="https://hubzilla.org/">Hubzilla</a>, and more. We're working on interoperation with others; <a href="https://github.com/snarfed/bridgy-fed/issues/12">see GitHub issues with the <code>app</code> label for details.</a></p>
|
||||
</li>
|
||||
|
||||
<li id="mastodon-link-verification" class="question">How do I verify my profile links (ie get green checks) in Mastodon?</li>
|
||||
<li class="answer">
|
||||
<p>Mastodon's verified profile links with ✅ green checks are fun! <a href="https://docs.joinmastodon.org/user/profile/#verification"></a> Follow these steps to get one on your Bridgy Fed profile:</p>
|
||||
|
||||
<ul>
|
||||
<li>Add a <a href="https://indieweb.org/rel-me"><code>rel=me</code> link</a> on your site that points to <code>https://web.brid.gy/r/https://[DOMAIN]/</code> for your domain, eg <code>https://web.brid.gy/r/https://snarfed.org/</code></li>
|
||||
<li>Click the <button class="btn btn-default glyphicon glyphicon-refresh"></button> button on your Bridgy Fed <a href="#user-page">user page</a> to update your profile on all of your followers' instances.</li>
|
||||
<li>Log into any Mastodon instance where you have an account.</li>
|
||||
<li>Search for your fediverse handle, eg <code>@snarfed.org@snarfed.org</code>.</li>
|
||||
<li>Click on your fediverse user in the search results.</li>
|
||||
<li>Wait a minute or two (or ten 😐), then refresh the page. You should see a green check on the profile link for your web site.</li>
|
||||
</ul>
|
||||
|
||||
<p>When you're logged into a Mastodon instance, searching for your Bridgy Fed user triggers that instance to check and verify its profile link(s) in the background. This only works when you're logged in with a native Mastodon account. Also, each instance does this independently; verified links are not synched across instances.</p>
|
||||
</li>
|
||||
|
||||
<li id="fediverse-enhanced" class="question">Can I use my own domain as my fediverse handle?</li>
|
||||
<li class="answer">
|
||||
<p>Yes! By default, bridged fediverse handles use a subdomain of <code>brid.gy</code> as their instance, eg <code>@mysite.com@web.brid.gy</code>, but you can change the instance part to your own domain. It takes a bit of setup and technical know-how, but it's very doable.</p>
|
||||
<p>First, your domain needs to serve HTTP requests. You don't need an actual web site, but you do need a minimal web server.<p>
|
||||
<p>Second, your web server needs to support SSL. Bridgy Fed uses your domain as your identity, so it depends on SSL to prove that you own it.</p>
|
||||
<p>Lastly, your web server needs to redirect a couple URL paths, including query parameters, to the same paths on <code>https://fed.brid.gy/</code>:</li>
|
||||
<pre>
|
||||
/.well-known/host-meta
|
||||
/.well-known/webfinger
|
||||
</pre>
|
||||
|
||||
<p>Here are instructions for a few common web servers:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p><em><a href="http://wordpress.org/">WordPress</a> (self-hosted)</em>: install the <a href="https://wordpress.org/plugins/safe-redirect-manager/">Safe Redirect Manager</a> plugin, then add these entries:</p>
|
||||
<code>
|
||||
/.well-known/host-meta* => https://fed.brid.gy/.well-known/host-meta*<br/>
|
||||
/.well-known/webfinger* => https://fed.brid.gy/.well-known/webfinger*
|
||||
</code>
|
||||
</li>
|
||||
|
||||
<li><em><a href="http://withknown.com/">Known</a></em> or <em><a href="https://drupal.org/project/indieweb">Drupal</a></em>: follow the <a href="#apache">Apache</a> or <a href="#nginx">nginx</a> instructions below.
|
||||
</li>
|
||||
|
||||
<li id="apache"><em><a href="http://httpd.apache.org/">Apache</a></em>: add this to your <code>.htaccess</code> file:<br />
|
||||
<pre>RewriteEngine on
|
||||
RewriteBase /
|
||||
RewriteRule ^.well-known/(host-meta|webfinger).* https://fed.brid.gy/$0 [redirect=302,last]</pre>
|
||||
(<code>RewriteEngine on</code> is optional if you already have it earlier in your <code>.htaccess</code>. <code>RewriteBase /</code> is optional if you don't have any other <code>RewriteBase</code> directives, or if you put this <code>RewriteRule</code> inside an existing <code>RewriteBase /</code> section.)
|
||||
</li>
|
||||
|
||||
<li id="nginx"><em><a href="https://nginx.org/">nginx</a></em>: add this to your <code>nginx.conf</code> file, in the <code>server</code> section:<br />
|
||||
<pre>rewrite ^/\.well-known/(host-meta|webfinger).* https://fed.brid.gy$request_uri? redirect;</pre>
|
||||
</li>
|
||||
|
||||
<li id="netlify"><em><a href="https://docs.netlify.com/routing/redirects/">Netlify</a></em>: add this to your <code>netlify.toml</code> file.
|
||||
<pre>
|
||||
[[redirects]]
|
||||
from = "/.well-known/host-meta*"
|
||||
to = "https://fed.brid.gy/.well-known/host-meta:splat"
|
||||
status = 302
|
||||
[[redirects]]
|
||||
from = "/.well-known/webfinger*"
|
||||
to = "https://fed.brid.gy/.well-known/webfinger"
|
||||
status = 302
|
||||
</pre>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
<h3 id="from-bluesky">From Bluesky</h3>
|
||||
|
||||
<li id="bluesky-get-started" class="question">How do I get started?</li>
|
||||
<li class="answer">
|
||||
<p>To bridge your Bluesky account into the fediverse and interact with people there, follow <a href="https://bsky.app/profile/ap.brid.gy">@ap.brid.gy</a> on Bluesky.</p>
|
||||
<p>Your Bluesky account will appear in the fediverse as <code>@[handle]@bsky.brid.gy</code>. For example, <a href="https://bsky.app/profile/snarfed.bsky.social">@snarfed.bsky.social</a> on Bluesky is bridged into the fediverse as <code>@snarfed.bsky.social@bsky.brid.gy</code>.</p>
|
||||
<p>Alternatively, <a href="#bluesky-follow">you can find and follow bridged fediverse accounts</a> without bridging your own account, but they won't see your posts or interactions.<p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="bluesky-follow" class="question">How do I find a bridged fediverse account?</li>
|
||||
<li class="answer">
|
||||
<p>Bridged fediverse accounts appear in Bluesky as <code>@[user].[instance].ap.brid.gy</code>. For example, <a href="https://indieweb.social/@snarfed">@snarfed@indieweb.social</a> is bridged into Bluesky as <a href="https://bsky.app/profile/snarfed.indieweb.social.ap.brid.gy">@snarfed.indieweb.social.ap.brid.gy</a>.</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="bluesky-delay" class="question">I followed <a href="https://bsky.app/profile/ap.brid.gy">@ap.brid.gy</a>, or followed someone else or posted or did something else in Bluesky, but it hasn't shown up in the fediverse yet!</li>
|
||||
<li class="answer">
|
||||
<p>Bluesky uses a pull-based event architecture, so we have to poll every bridged user's notifications to discover and bridge interactions, including your initial follow of <a href="https://bsky.app/profile/ap.brid.gy">@ap.brid.gy</a>. We currently do this every 5 minutes. Sorry for the delay!
|
||||
</li>
|
||||
|
||||
|
||||
<li id="bluesky-what" class="question">Which stuff of mine from Bluesky will get bridged into the fediverse?</li>
|
||||
<li class="answer">
|
||||
<p>Anything that interacts with fediverse users. This includes replies, @-mentions, likes, reposts, and if you have any fediverse followers, your own posts. Hashtags, links, link previews, images, and even alt text are also included.</p>
|
||||
|
||||
|
||||
<li id="bluesky-reply-controls" class="question">Can I use Bluesky's reply controls?</li>
|
||||
<li class="answer">
|
||||
<p>Yes! Bluesky's <a href="https://bsky.app/profile/safety.bsky.app/post/3khhw67cxqg22">reply controls</a> apply to accounts bridged from the fediverse as well as to native Bluesky accounts.</a>.</p>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
<h3 id="to-bluesky">To Bluesky</h3>
|
||||
|
||||
<li id="bluesky-report" class="question">What happens when I report a bridged Bluesky user?</li>
|
||||
<li class="answer">
|
||||
<p>Bridgy Fed sends your report to the Bluesky team's <a href="official moderation service">official moderation service</a>, which handles it, and takes action if necessary, just like with native Bluesky accounts.</p>
|
||||
<p>Also see Bridgy Fed's <a href="#moderation-policy">moderation policy</a> and <a href="#moderation">functionality</a>.</p>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
<h3>From the web</h3>
|
||||
|
@ -247,6 +473,7 @@
|
|||
</p>
|
||||
</li>
|
||||
|
||||
|
||||
<li id="web-profile" class="question">Where does my profile info come from?</li>
|
||||
<li class="answer">
|
||||
<p>Your site's bridged profile can come from a few different things on your home page. Here's what Bridgy Fed looks for, in order of preference:
|
||||
|
@ -276,11 +503,6 @@
|
|||
</p>
|
||||
</li>
|
||||
|
||||
<li id="web-update-profile" class="question">How do I update my profile?</li>
|
||||
<li class="answer">
|
||||
<p> If you've changed the <a href="#web-profile">profile metadata</a> on your site's home page and you want to update your profile in bridged networks, click the <button class="btn btn-default glyphicon glyphicon-refresh"></button> button next to your domain <a href="/web-site">on your user page</a>. Alternatively, you can send a webmention from your home page to <code>https://fed.brid.gy/</code>.
|
||||
</li>
|
||||
|
||||
|
||||
<li id="web-how-post" class="question">How does Bridgy Fed find my posts?</li>
|
||||
<li class="answer">
|
||||
|
@ -305,7 +527,7 @@
|
|||
</p>
|
||||
</li>
|
||||
|
||||
<li id="web-interpret" class="question">How does it interpret and translate my post?</li>
|
||||
<li id="web-interpret" class="question">How does it interpret and translate my posts?</li>
|
||||
<li class="answer">
|
||||
<p>If your post has <a href="http://microformats.org/">microformats</a>, which many web servers include automatically, Bridgy Fed uses them to determine whether it's a <a href="https://indieweb.org/note#How_to">note</a>, <a href="https://indieweb.org/article">article</a>, <a href="#web-like">like</a>, <a href="#web-repost">repost</a>, <a href="#web-reply">reply</a>, or <a href="#web-follow">follow</a>. Here's an example of a note:
|
||||
|
||||
|
@ -528,96 +750,11 @@ To receive likes, reposts, replies, @-mentions, and follows from the fediverse,
|
|||
<p>Another difficulty is that accounts on Bluesky and Nostr have long-lived, server-independent ids. If we used a Bluesky user's PDS domain in their fediverse handle, that handle would change every time they migrated to a new PDS, and they'd lose all of their followers and followings, even though their Bluesky account ID itself hadn't changed.</p>
|
||||
</li>
|
||||
|
||||
<br>
|
||||
|
||||
<h3 id="from-fediverse">From the fediverse</h3>
|
||||
|
||||
<li id="fediverse-follow" class="question">How do I follow someone from the fediverse?</li>
|
||||
<li class="answer">
|
||||
<p>You can follow any web site, eg <a class="handle" href="https://example.com/">example.com</a>, by searching for <span class="handle">@example.com@web.brid.gy</span> in your fediverse instance.</p>
|
||||
<p>Bridged web sites appear in the fediverse as either <code>@[domain]@[domain]</code>, <code>@[domain]@web.brid.gy</code>, or <code>@[domain]@fed.brid.gy</code>, depending on the fediverse server and whether the web site owner has <a href="#fediverse-enhanced">connected their domain to Bridgy Fed</a>. All bridged web sites behave the same in the fediverse; the different instances in their handles are purely cosmetic.</p>
|
||||
</li>
|
||||
|
||||
<br>
|
||||
|
||||
<h3 id="to-fediverse">To the fediverse</h3>
|
||||
|
||||
<li id="fediverse-servers" class="question">Which fediverse servers are supported?</li>
|
||||
<li class="answer">
|
||||
<p>Lots! <a href="https://joinmastodon.org/">Mastodon</a>, <a href="https://friendi.ca/">Friendica</a>, <a href="https://misskey.page/">Misskey</a>, <a href="https://joinpeertube.org/">PeerTube</a>, <a href="https://hubzilla.org/">Hubzilla</a>, and more. We're working on interoperation with others; <a href="https://github.com/snarfed/bridgy-fed/issues/12">see GitHub issues with the <code>app</code> label for details.</a></p>
|
||||
</li>
|
||||
|
||||
<li id="mastodon-link-verification" class="question">How do I verify my profile links (ie get green checks) in Mastodon?</li>
|
||||
<li class="answer">
|
||||
<p>Mastodon's verified profile links with ✅ green checks are fun! <a href="https://docs.joinmastodon.org/user/profile/#verification"></a> Follow these steps to get one on your Bridgy Fed profile:</p>
|
||||
|
||||
<ul>
|
||||
<li>Add a <a href="https://indieweb.org/rel-me"><code>rel=me</code> link</a> on your site that points to <code>https://web.brid.gy/r/https://[DOMAIN]/</code> for your domain, eg <code>https://web.brid.gy/r/https://snarfed.org/</code></li>
|
||||
<li>Click the <button class="btn btn-default glyphicon glyphicon-refresh"></button> button on your Bridgy Fed <a href="#user-page">user page</a> to update your profile on all of your followers' instances.</li>
|
||||
<li>Log into any Mastodon instance where you have an account.</li>
|
||||
<li>Search for your fediverse handle, eg <code>@snarfed.org@snarfed.org</code>.</li>
|
||||
<li>Click on your fediverse user in the search results.</li>
|
||||
<li>Wait a minute or two (or ten 😐), then refresh the page. You should see a green check on the profile link for your web site.</li>
|
||||
</ul>
|
||||
|
||||
<p>When you're logged into a Mastodon instance, searching for your Bridgy Fed user triggers that instance to check and verify its profile link(s) in the background. This only works when you're logged in with a native Mastodon account. Also, each instance does this independently; verified links are not synched across instances.</p>
|
||||
</li>
|
||||
|
||||
<li id="fediverse-enhanced" class="question">Can I use my own domain as my fediverse handle?</li>
|
||||
<li class="answer">
|
||||
<p>Yes! By default, bridged fediverse handles use a subdomain of <code>brid.gy</code> as their instance, eg <code>@mysite.com@web.brid.gy</code>, but you can change the instance part to your own domain. It takes a bit of setup and technical know-how, but it's very doable.</p>
|
||||
<p>First, your domain needs to serve HTTP requests. You don't need an actual web site, but you do need a minimal web server.<p>
|
||||
<p>Second, your web server needs to support SSL. Bridgy Fed uses your domain as your identity, so it depends on SSL to prove that you own it.</p>
|
||||
<p>Lastly, your web server needs to redirect a couple URL paths, including query parameters, to the same paths on <code>https://fed.brid.gy/</code>:</li>
|
||||
<pre>
|
||||
/.well-known/host-meta
|
||||
/.well-known/webfinger
|
||||
</pre>
|
||||
|
||||
<p>Here are instructions for a few common web servers:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p><em><a href="http://wordpress.org/">WordPress</a> (self-hosted)</em>: install the <a href="https://wordpress.org/plugins/safe-redirect-manager/">Safe Redirect Manager</a> plugin, then add these entries:</p>
|
||||
<code>
|
||||
/.well-known/host-meta* => https://fed.brid.gy/.well-known/host-meta*<br/>
|
||||
/.well-known/webfinger* => https://fed.brid.gy/.well-known/webfinger*
|
||||
</code>
|
||||
</li>
|
||||
|
||||
<li><em><a href="http://withknown.com/">Known</a></em> or <em><a href="https://drupal.org/project/indieweb">Drupal</a></em>: follow the <a href="#apache">Apache</a> or <a href="#nginx">nginx</a> instructions below.
|
||||
</li>
|
||||
|
||||
<li id="apache"><em><a href="http://httpd.apache.org/">Apache</a></em>: add this to your <code>.htaccess</code> file:<br />
|
||||
<pre>RewriteEngine on
|
||||
RewriteBase /
|
||||
RewriteRule ^.well-known/(host-meta|webfinger).* https://fed.brid.gy/$0 [redirect=302,last]</pre>
|
||||
(<code>RewriteEngine on</code> is optional if you already have it earlier in your <code>.htaccess</code>. <code>RewriteBase /</code> is optional if you don't have any other <code>RewriteBase</code> directives, or if you put this <code>RewriteRule</code> inside an existing <code>RewriteBase /</code> section.)
|
||||
</li>
|
||||
|
||||
<li id="nginx"><em><a href="https://nginx.org/">nginx</a></em>: add this to your <code>nginx.conf</code> file, in the <code>server</code> section:<br />
|
||||
<pre>rewrite ^/\.well-known/(host-meta|webfinger).* https://fed.brid.gy$request_uri? redirect;</pre>
|
||||
</li>
|
||||
|
||||
<li id="netlify"><em><a href="https://docs.netlify.com/routing/redirects/">Netlify</a></em>: add this to your <code>netlify.toml</code> file.
|
||||
<pre>
|
||||
[[redirects]]
|
||||
from = "/.well-known/host-meta*"
|
||||
to = "https://fed.brid.gy/.well-known/host-meta:splat"
|
||||
status = 302
|
||||
[[redirects]]
|
||||
from = "/.well-known/webfinger*"
|
||||
to = "https://fed.brid.gy/.well-known/webfinger"
|
||||
status = 302
|
||||
</pre>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
<h3 id="about">About</h3>
|
||||
<h3 id="background">Background</h3>
|
||||
|
||||
<li id="who" class="question">Who are you? Why did you make this?</li>
|
||||
<li class="answer">
|
||||
|
@ -659,7 +796,7 @@ I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a
|
|||
<p><a href="https://snarfed.org/2024-01-19_moderate-people-not-code">Content moderation for decentralized social networks, and for bridges, is complicated.</a> Bridgy Fed itself doesn't have an exhaustive content moderation policy. Instead, I try to keep these principles in mind:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>My primary goal is to <a href="#moderation">empower each network's existing moderation ecosystem</a>.</strong> Most decentralized social networks have existing, often mature mechanisms for content moderation. The fediverse has <a href="https://docs.joinmastodon.org/user/moderating/#block">blocks</a> and <a href="https://docs.joinmastodon.org/admin/moderation/#server-wide-moderation">defederation</a> and <a href="https://about.iftas.org/">IFTAS</a>, the IndieWeb has <a href="https://indieweb.org/Vouch">Vouch</a> and <a href="https://akismet.com/">Akismet</a>, Bluesky has <a href="https://blueskyweb.xyz/blog/4-13-2023-moderation">labeling</a>, even Nostr has <a href="https://github.com/nostr-protocol/nips/blob/master/28.md">mutes</a> and <a href="https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists">shared mutelists</a> and <a href="https://github.com/nostr-protocol/nips/blob/master/72.md">moderated groups</a>.</p>
|
||||
<p><strong>My primary goal is to <a href="#moderation">empower each network's existing moderation ecosystem</a>.</strong> Most decentralized social networks have existing, often mature mechanisms for content moderation. The fediverse has <a href="https://docs.joinmastodon.org/user/moderating/#block">blocks</a> and <a href="https://docs.joinmastodon.org/admin/moderation/#server-wide-moderation">defederation</a> and <a href="https://about.iftas.org/">IFTAS</a>, the IndieWeb has <a href="https://indieweb.org/Vouch">Vouch</a> and <a href="https://akismet.com/">Akismet</a>, Bluesky has <a href="https://bsky.social/about/blog/4-13-2023-moderation">labeling</a>, even Nostr has <a href="https://github.com/nostr-protocol/nips/blob/master/28.md">mutes</a> and <a href="https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists">shared mutelists</a> and <a href="https://github.com/nostr-protocol/nips/blob/master/72.md">moderated groups</a>.</p>
|
||||
<p>I try to ensure that those mechanisms all work with bridged accounts just as well as with native accounts, up to and including defederation. I'm always ready to hear admins' concerns and try to help! But if an instance determines that they need to block a Bridgy Fed network (domain) entirely, that's their prerogative, and I'll support them.</p>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -784,7 +921,7 @@ I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a
|
|||
<td><a href="https://websub.net/">push</a>/<a href="https://indieweb.org/Microsub">pull</a> posts</a>,<br>
|
||||
<a href="https://webmention.net/">push responses</a></td>
|
||||
<td><a href="https://www.w3.org/TR/activitypub/#delivery">push</a></td>
|
||||
<td><a href="https://blueskyweb.xyz/blog/5-5-2023-federation-architecture">pull</a></td>
|
||||
<td><a href="https://bsky.social/about/blog/5-5-2023-federation-architecture">pull</a></td>
|
||||
<td><a href="https://github.com/nostr-protocol/nips/blob/master/01.md#from-client-to-relay-sending-events-and-creating-subscriptions">pull</a></td>
|
||||
</tr>
|
||||
|
||||
|
@ -800,8 +937,8 @@ I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a
|
|||
<th>topology</th>
|
||||
<td>peer to peer</td>
|
||||
<td><a href="https://activitypub.rocks/">federated servers, two tier</a></td>
|
||||
<td><a href="https://blueskyweb.xyz/blog/5-5-2023-federation-architecture">federated servers</a>,<br>
|
||||
<a href="https://blueskyweb.xyz/blog/4-13-2023-moderation">decoupled</a> <a href="https://blueskyweb.xyz/blog/3-30-2023-algorithmic-choice">services</a>, multi-tier</td>
|
||||
<td><a href="https://bsky.social/about/blog/5-5-2023-federation-architecture">federated servers</a>,<br>
|
||||
<a href="https://bsky.social/about/blog/4-13-2023-moderation">decoupled</a> <a href="https://bsky.social/about/blog/3-30-2023-algorithmic-choice">services</a>, multi-tier</td>
|
||||
<td><a href="https://github.com/nostr-protocol/nostr#how-does-nostr-work">interchangeable relays,<br>two tier</a></td>
|
||||
</tr>
|
||||
|
||||
|
@ -820,7 +957,7 @@ I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a
|
|||
<th>app semantics live in...</th>
|
||||
<td>servers, clients</td>
|
||||
<td>servers, clients</td>
|
||||
<td><a href="https://blueskyweb.xyz/blog/5-5-2023-federation-architecture">dedicated service</a>, clients</td>
|
||||
<td><a href="https://bsky.social/about/blog/5-5-2023-federation-architecture">dedicated service</a>, clients</td>
|
||||
<td>clients</td>
|
||||
</tr>
|
||||
|
||||
|
@ -836,7 +973,7 @@ I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a
|
|||
<th>monetization</th>
|
||||
<td><a href="https://indieweb.org/business-models">hosting</a></td>
|
||||
<td>donations</td>
|
||||
<td><a href="https://blueskyweb.xyz/blog/7-05-2023-business-plan">services</a></td>
|
||||
<td><a href="https://bsky.social/about/blog/7-05-2023-business-plan">services</a></td>
|
||||
<td><a href="https://nostr.how/en/zaps">microtransactions</a></td>
|
||||
</tr>
|
||||
|
||||
|
@ -865,7 +1002,7 @@ I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a
|
|||
<td><a href="https://indieweb.org/">IndieWeb</a> <a href="https://www.oscollective.org/">non-profit</a>,<br>
|
||||
<a href="https://www.w3.org/community/socialcg/">some W3C</a></td>
|
||||
<td><a href="https://socialhub.activitypub.rocks/t/about-the-fediverse-enhancement-proposals/1168">FEPs</a>, <a href="https://www.w3.org/community/socialcg/">some W3C</a></td>
|
||||
<td><a href="https://blueskyweb.xyz/blog/7-05-2023-business-plan">Bluesky C-corp now</a>,<br>
|
||||
<td><a href="https://bsky.social/about/blog/7-05-2023-business-plan">Bluesky C-corp now</a>,<br>
|
||||
<a href="https://blue.amazingca.dev/user/did:plc:44ybard66vv44zksje25o7dz/post/3k3p6upex4x2l">IETF eventually?</a></td>
|
||||
<td><a href="https://fiatjaf.com/">mildly BDFL(s) now</a>,<br>
|
||||
<a href="https://github.com/nostr-protocol/nips/issues/162">decentralized eventually?</a></td>
|
||||
|
@ -877,7 +1014,7 @@ I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a
|
|||
|
||||
<li id="translate" class="question">How are the different protocols translated?</li>
|
||||
<li class="answer">
|
||||
<p>Here are internal details on how Bridgy Fed translates user identity and events between protocols, including some like <a href="https://github.com/nostr-protocol/nostr">Nostr</a> and <a href="https://blueskyweb.xyz/">Bluesky</a>/<a href="https://atproto.com/">AT Protocol</a> that aren't launched here, or even fully implemented or thought through yet. Caveat hacker!</p>
|
||||
<p>Here are internal details on how Bridgy Fed translates user identity and events between protocols, including some like <a href="https://github.com/nostr-protocol/nostr">Nostr</a> and <a href="https://bsky.social/">Bluesky</a>/<a href="https://atproto.com/">AT Protocol</a> that aren't launched here, or even fully implemented or thought through yet. Caveat hacker!</p>
|
||||
|
||||
<p>In the tables below, BF is Bridgy Fed. <span class="done">Green parts</span> have been implemented and running here for years, the rest are still in the early design phase.</p>
|
||||
|
||||
|
@ -1232,7 +1369,7 @@ I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a
|
|||
<th>Publish outbound</th>
|
||||
<td class="done">serve on BF user page followings <code><a href="https://microformats.org/wiki/h-feed">h-feed</a></code></td>
|
||||
<td class="done">deliver to recipient's <a href="https://www.w3.org/TR/activitypub/#inbox">inbox</a></td>
|
||||
<td class="done">serve repo diff via <a href="https://atproto.com/lexicons/com-atproto-sync"><code>sync</code> XRPCs</a> to subscribing <a href="https://blueskyweb.xyz/blog/5-5-2023-federation-architecture">BGSes</a></td>
|
||||
<td class="done">serve repo diff via <a href="https://atproto.com/lexicons/com-atproto-sync"><code>sync</code> XRPCs</a> to subscribing <a href="https://bsky.social/about/blog/5-5-2023-federation-architecture">BGSes</a></td>
|
||||
<td>serve to subscribers</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "user_base.html" %}
|
||||
{% set tab = "home" %}
|
||||
{% set show_activity_actors = True %}
|
||||
|
||||
{% block subtabs %}
|
||||
<div class="row">
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
</div>
|
||||
|
||||
<div class="row big front-light">
|
||||
<p>Bridgy Fed connects <a class="web" href="https://indieweb.org/">🌐 web sites</a>, the <a class="fediverse" href="https://enwp.org/fediverse"><img src="/static/fediverse_logo.svg"> fediverse</a>, and <a class="bluesky" href="https://bsky.social/"><img src="/oauth_dropins_static/bluesky.svg"> Bluesky</a>. You can use it to make your profile on one visible in another, follow people, see their posts, and reply and like and repost them. Interactions work in both directions as much as possible. <a href="/docs">See the docs for more info.</a></p>
|
||||
</div>
|
||||
|
||||
<div class="row big front-dark">
|
||||
<p>Got a <span class="web">🌐 web</span> site? Enter it here to use it on the <a class="fediverse" href="https://en.wikipedia.org/wiki/Fediverse"><img src="/static/fediverse_logo.svg"> fediverse</a>:
|
||||
<form method="post" action="/web-site">
|
||||
<input required type="url" name="url" id="url" placeholder="example.com" />
|
||||
|
@ -32,20 +36,22 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row big front-dark">
|
||||
<p>Got a <a class="fediverse" href="https://en.wikipedia.org/wiki/Fediverse"><img src="/static/fediverse_logo.svg"> fediverse</a> account? Follow any <span class="web">🌐 web</span> site, eg <a class="handle" href="https://example.com/">example.com</a>, by searching for <span class="handle">@example.com@web.brid.gy</span> in your fediverse instance.</p>
|
||||
</div>
|
||||
|
||||
<div class="row big front-light">
|
||||
<p>Bridgy Fed connects some of the most popular decentralized social networks. You can use it from one network to make your profile visible in another network, follow people there, see their posts, and reply and like and repost them. All interactions work in both directions. <a href="/docs">See the docs for more info.</a></p>
|
||||
<p>Got a <a class="fediverse" href="https://en.wikipedia.org/wiki/Fediverse"><img src="/static/fediverse_logo.svg"> fediverse</a> account? <a href="/docs#fediverse-get-started">Bridge it to <span class="bluesky"><img src="/oauth_dropins_static/bluesky.svg"> Bluesky</span></a> by following <em>@bsky.brid.gy@bsky.brid.gy</em><button class="btn btn-default btn-copy glyphicon glyphicon-duplicate" onclick="navigator.clipboard.writeText('@bsky.brid.gy@bsky.brid.gy')"></button>, or follow a <span class="web">🌐 web</span> site like <a class="handle" href="https://example.com/">example.com</a> by searching for <span class="handle">@example.com@web.brid.gy</span><button class="btn btn-default btn-copy glyphicon glyphicon-duplicate" onclick="navigator.clipboard.writeText('@example.com@web.brid.gy')"></button>.</p>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row big front-dark">
|
||||
<p>Bridgy Fed currently supports web sites and blogs and the <a href="https://enwp.org/fediverse">fediverse</a> via <a href="https://activitypub.rocks/">ActivityPub</a>. <a href="https://blueskyweb.xyz/">Bluesky</a>/<a href="https://atproto.com/">AT Protocol</a> and <a href="https://en.wikipedia.org/wiki/Nostr">Nostr</a> are planned for 2024.</p>
|
||||
<p>Got a <a class="bluesky" href="https://bsky.social/"><img src="/oauth_dropins_static/bluesky.svg"> Bluesky</a> account? <a href="/docs#bluesky-get-started">Bridge it to the <span class="fediverse"><img src="/static/fediverse_logo.svg"> fediverse</span></a> by following <a href="https://bsky.app/profile/ap.brid.gy"><em>@ap.brid.gy</em></a>.</p>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- <div class="row big front-light"> -->
|
||||
<!-- <p><a href="https://en.wikipedia.org/wiki/Nostr">Nostr</a> and <a href="https://www.farcaster.xyz/">Farcaster</a> are under consideration for the future.</p> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<div class="row big front-light">
|
||||
<p>Bridgy Fed is a free, non-commercial, <a href="https://github.com/snarfed/bridgy-fed">open source</a> service. <a href="https://snarfed.org/2023-11-27_re-introducing-bridgy-fed">More background here.</a> <a href="mailto:feedback@brid.gy">Feedback</a> and <a href="https://github.com/snarfed/bridgy-fed/issues">bug reports</a> are welcome!</a></p>
|
||||
<p>Bridgy Fed is a free, non-commercial, <a href="https://github.com/snarfed/bridgy-fed">open source</a> service. <a href="https://snarfed.org/2023-11-27_re-introducing-bridgy-fed">More background here.</a> <a href="https://github.com/snarfed/bridgy-fed/issues">Bug reports, feature requests</a>, and <a href="mailto:feedback@brid.gy">other feedback</a> are welcome!</a></p>
|
||||
</div>
|
||||
|
||||
<div id="front-logo-bottom" class="row">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "user_base.html" %}
|
||||
{% set tab = "notifications" %}
|
||||
{% set show_activity_actors = True %}
|
||||
|
||||
{% block subtabs %}
|
||||
<div class="row">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "user_base.html" %}
|
||||
{% set tab = "profile" %}
|
||||
{% set show_activity_actors = False %}
|
||||
|
||||
{% block subtabs %}
|
||||
<div class="row tabs">
|
||||
|
|
|
@ -30,23 +30,8 @@
|
|||
|
||||
<div class="row">
|
||||
<span class="big">
|
||||
{% if user.name() != user.handle_or_id() %}
|
||||
{{ user.user_link()|safe }}
|
||||
·
|
||||
{% else %}
|
||||
{% if user.profile_picture() %}
|
||||
<img src="{{ user.profile_picture() }}" class="profile">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<nobr>
|
||||
|
||||
<a href="{{ user.web_url() }}"
|
||||
title="{{ user.__class__.__name__ }} (original)">
|
||||
<span class="logo">{{ user.LOGO_HTML|safe }}</span>
|
||||
{{ user.handle_or_id() }}
|
||||
</a>
|
||||
|
||||
{{ user.user_link(handle=True, maybe_internal_link=False)|safe }}
|
||||
<form method="post" action="{{ user.user_page_path('update-profile') }}">
|
||||
<button id="update-profile-button" type="submit" title="Update profile"
|
||||
class="btn btn-default glyphicon glyphicon-refresh"></button>
|
||||
|
@ -57,7 +42,7 @@
|
|||
{% set copies = user.copies|map(attribute='protocol')|list %}
|
||||
{% for proto in set(PROTOCOLS.values()) %}
|
||||
{% if proto and not isinstance(user, proto) and proto.LABEL not in ('ui', 'web')
|
||||
and user.is_enabled_to(proto, user=user) %}
|
||||
and user.is_enabled(proto) %}
|
||||
{% set url = proto.bridged_web_url_for(user) %}
|
||||
·
|
||||
<nobr title="{{ proto.__name__ }} (bridged)">
|
||||
|
|
|
@ -507,7 +507,7 @@ class ActivityPubTest(TestCase):
|
|||
'following', 'publicKey', 'publicKeyPem'])
|
||||
|
||||
# skip _pre_put_hook since it doesn't allow internal domains
|
||||
@patch.object(Web, '_pre_put_hook', new=lambda self: None)
|
||||
# @patch.object(Web, '_pre_put_hook', new=lambda self: None)
|
||||
def test_instance_actor_fetch(self, *_):
|
||||
def reset_instance_actor():
|
||||
activitypub._INSTANCE_ACTOR = testutil.global_user
|
||||
|
@ -515,15 +515,14 @@ class ActivityPubTest(TestCase):
|
|||
|
||||
actor_as2 = json_loads(util.read('fed.brid.gy.as2.json'))
|
||||
self.make_user(common.PRIMARY_DOMAIN, cls=Web, obj_as2=actor_as2,
|
||||
obj_id='https://fed.brid.gy/')
|
||||
obj_id='https://fed.brid.gy/', ap_subdomain='fed',
|
||||
has_redirects=True)
|
||||
|
||||
activitypub._INSTANCE_ACTOR = None
|
||||
got = self.client.get(f'/{common.PRIMARY_DOMAIN}')
|
||||
got = self.client.get(f'/fed.brid.gy', base_url='https://fed.brid.gy/')
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assert_equals({
|
||||
**actor_as2,
|
||||
'id': 'http://localhost/fed.brid.gy',
|
||||
}, got.json, ignore=['inbox', 'outbox', 'endpoints', 'followers',
|
||||
self.assert_equals(actor_as2, got.json,
|
||||
ignore=['inbox', 'outbox', 'endpoints', 'followers',
|
||||
'following', 'publicKey', 'publicKeyPem'])
|
||||
|
||||
def test_individual_inbox_no_user(self, mock_head, mock_get, mock_post):
|
||||
|
@ -869,7 +868,7 @@ class ActivityPubTest(TestCase):
|
|||
def test_follow_bot_user_enables_protocol(self, *mocks):
|
||||
user = self.make_user('https://mas.to/users/swentel', cls=ActivityPub,
|
||||
obj_as2=ACTOR)
|
||||
self.assertFalse(ActivityPub.is_enabled_to(ExplicitEnableFake, user))
|
||||
self.assertFalse(user.is_enabled(ExplicitEnableFake))
|
||||
|
||||
id = 'https://inst/follow'
|
||||
with self.assertRaises(NoContent):
|
||||
|
@ -883,11 +882,11 @@ class ActivityPubTest(TestCase):
|
|||
self.assertEqual(['https://mas.to/users/swentel'],
|
||||
ExplicitEnableFake.created_for)
|
||||
user = user.key.get()
|
||||
self.assertTrue(ActivityPub.is_enabled_to(ExplicitEnableFake, user))
|
||||
self.assertTrue(user.is_enabled(ExplicitEnableFake))
|
||||
|
||||
def test_inbox_dm_yes_to_bot_user_enables_protocol(self, *mocks):
|
||||
user = self.make_user(ACTOR['id'], cls=ActivityPub)
|
||||
self.assertFalse(ActivityPub.is_enabled_to(ExplicitEnableFake, user))
|
||||
self.assertFalse(user.is_enabled(ExplicitEnableFake))
|
||||
|
||||
got = self.post('/ap/sharedInbox', json={
|
||||
'type': 'Create',
|
||||
|
@ -903,7 +902,7 @@ class ActivityPubTest(TestCase):
|
|||
})
|
||||
self.assertEqual(200, got.status_code, got.get_data(as_text=True))
|
||||
user = user.key.get()
|
||||
self.assertTrue(ActivityPub.is_enabled_to(ExplicitEnableFake, user))
|
||||
self.assertTrue(user.is_enabled(ExplicitEnableFake))
|
||||
|
||||
def test_inbox_actor_blocklisted(self, mock_head, mock_get, mock_post):
|
||||
got = self.post('/ap/sharedInbox', json={
|
||||
|
@ -962,11 +961,12 @@ class ActivityPubTest(TestCase):
|
|||
self.assert_user(ActivityPub, 'https://mas.to/actor', obj_as2=LIKE_ACTOR)
|
||||
|
||||
def test_inbox_like_no_object_error(self, *_):
|
||||
Fake.fetchable = {'fake:user': {'id': 'fake:user'}}
|
||||
swentel = self.make_user('https://inst/user', cls=ActivityPub)
|
||||
|
||||
got = self.post('/inbox', json={
|
||||
'id': 'fake:like',
|
||||
'id': 'https://inst/like',
|
||||
'type': 'Like',
|
||||
'actor': 'fake:user',
|
||||
'actor': 'https://inst/user',
|
||||
'object': None,
|
||||
})
|
||||
self.assertEqual(400, got.status_code)
|
||||
|
@ -1960,6 +1960,15 @@ class ActivityPubUtilsTest(TestCase):
|
|||
],
|
||||
}))
|
||||
|
||||
def test_postprocess_as2_strips_link_attachment(self):
|
||||
self.assertNotIn('attachment', postprocess_as2({
|
||||
'type': 'Note',
|
||||
'attachment': [{
|
||||
'type': 'Link',
|
||||
'url': 'http://a/link',
|
||||
}],
|
||||
}))
|
||||
|
||||
def test_postprocess_as2_actor_url_attachments(self):
|
||||
got = postprocess_as2_actor(as2.from_as1({
|
||||
'objectType': 'person',
|
||||
|
@ -2279,13 +2288,6 @@ class ActivityPubUtilsTest(TestCase):
|
|||
'object': ACTOR,
|
||||
}, ActivityPub.convert(obj))
|
||||
|
||||
# TODO: remove
|
||||
@skip
|
||||
def test_convert_protocols_not_enabled(self):
|
||||
obj = Object(our_as1={'foo': 'bar'}, source_protocol='atproto')
|
||||
with self.assertRaises(BadRequest):
|
||||
ActivityPub.convert(obj)
|
||||
|
||||
def test_postprocess_as2_idempotent(self):
|
||||
for obj in (ACTOR, REPLY_OBJECT, REPLY_OBJECT_WRAPPED, REPLY,
|
||||
NOTE_OBJECT, NOTE, MENTION_OBJECT, MENTION, LIKE,
|
||||
|
|
|
@ -283,7 +283,7 @@ class ATProtoTest(TestCase):
|
|||
}, obj.bsky)
|
||||
# eg https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=did:plc:s2koow7r6t7tozgd4slc3dsg&collection=app.bsky.feed.post&rkey=3jqcpv7bv2c2q
|
||||
mock_get.assert_called_once_with(
|
||||
'https://api.bsky-sandbox.dev/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Aabc&collection=app.bsky.feed.post&rkey=123',
|
||||
'https://appview.local/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Aabc&collection=app.bsky.feed.post&rkey=123',
|
||||
json=None, data=None,
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -299,7 +299,7 @@ class ATProtoTest(TestCase):
|
|||
obj = Object(id='at://did:plc:abc/app.bsky.feed.post/123')
|
||||
self.assertFalse(ATProto.fetch(obj))
|
||||
mock_get.assert_called_once_with(
|
||||
'https://api.bsky-sandbox.dev/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Aabc&collection=app.bsky.feed.post&rkey=123',
|
||||
'https://appview.local/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Aabc&collection=app.bsky.feed.post&rkey=123',
|
||||
json=None, data=None, headers=ANY)
|
||||
|
||||
def test_fetch_bsky_app_url_fails(self):
|
||||
|
@ -346,7 +346,7 @@ class ATProtoTest(TestCase):
|
|||
}, obj.bsky)
|
||||
|
||||
mock_get.assert_any_call(
|
||||
'https://api.bsky-sandbox.dev/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.feed.post&rkey=789',
|
||||
'https://appview.local/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.feed.post&rkey=789',
|
||||
json=None, data=None, headers={
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': common.USER_AGENT,
|
||||
|
@ -370,7 +370,7 @@ class ATProtoTest(TestCase):
|
|||
}, obj.bsky)
|
||||
|
||||
mock_get.assert_called_with(
|
||||
'https://api.bsky-sandbox.dev/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.actor.profile&rkey=self',
|
||||
'https://appview.local/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.actor.profile&rkey=self',
|
||||
json=None, data=None, headers={
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': common.USER_AGENT,
|
||||
|
@ -472,7 +472,7 @@ class ATProtoTest(TestCase):
|
|||
'object': 'at://han.dull/app.bsky.feed.post/tid',
|
||||
})))
|
||||
mock_get.assert_called_with(
|
||||
'https://api.bsky-sandbox.dev/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.feed.post&rkey=tid',
|
||||
'https://appview.local/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.feed.post&rkey=tid',
|
||||
json=None, data=None, headers=ANY)
|
||||
|
||||
@patch('dns.resolver.resolve', side_effect=NXDOMAIN())
|
||||
|
@ -521,7 +521,7 @@ class ATProtoTest(TestCase):
|
|||
})))
|
||||
|
||||
mock_get.assert_called_with(
|
||||
'https://api.bsky-sandbox.dev/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.feed.post&rkey=tid',
|
||||
'https://appview.local/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3Auser&collection=app.bsky.feed.post&rkey=tid',
|
||||
json=None, data=None, headers=ANY)
|
||||
|
||||
def test_convert_blobs_false(self):
|
||||
|
@ -957,7 +957,7 @@ class ATProtoTest(TestCase):
|
|||
Object.get_by_id(id='fake:repost').copies)
|
||||
|
||||
mock_get.assert_called_with(
|
||||
'https://api.bsky-sandbox.dev/xrpc/com.atproto.repo.getRecord?repo=did%3Abob&collection=app.bsky.feed.post&rkey=tid',
|
||||
'https://appview.local/xrpc/com.atproto.repo.getRecord?repo=did%3Abob&collection=app.bsky.feed.post&rkey=tid',
|
||||
json=None, data=None, headers=ANY)
|
||||
mock_create_task.assert_called()
|
||||
|
||||
|
@ -1109,6 +1109,47 @@ class ATProtoTest(TestCase):
|
|||
|
||||
mock_create_task.assert_called()
|
||||
|
||||
# createReport
|
||||
@patch('requests.post', return_value=requests_response({'id': 3}))
|
||||
# did:plc:eve
|
||||
@patch('requests.get', return_value=requests_response({
|
||||
**DID_DOC,
|
||||
'id': 'did:plc:eve',
|
||||
}))
|
||||
def test_send_flag_createReport(self, _, mock_post):
|
||||
user = self.make_user_and_repo()
|
||||
|
||||
uri = 'at://did:plc:eve/app.bsky.feed.post/123'
|
||||
obj = self.store_object(id='fake:flag', source_protocol='fake', our_as1={
|
||||
'objectType': 'activity',
|
||||
'verb': 'flag',
|
||||
'actor': 'fake:user',
|
||||
'object': uri,
|
||||
'content': 'foo bar',
|
||||
})
|
||||
self.store_object(id=uri, source_protocol='bsky', bsky={
|
||||
'$type': 'app.bsky.feed.post',
|
||||
'cid': 'bafy...',
|
||||
})
|
||||
|
||||
self.assertTrue(ATProto.send(obj, 'https://bsky.brid.gy/'))
|
||||
|
||||
repo = self.storage.load_repo(user.get_copy(ATProto))
|
||||
self.assertEqual({}, repo.get_contents())
|
||||
|
||||
mock_post.assert_called_with(
|
||||
'https://mod.service.local/xrpc/com.atproto.moderation.createReport',
|
||||
json={
|
||||
'$type': 'com.atproto.moderation.createReport#input',
|
||||
'reasonType': 'com.atproto.moderation.defs#reasonOther',
|
||||
'reason': 'foo bar',
|
||||
'subject': {
|
||||
'$type': 'com.atproto.repo.strongRef',
|
||||
'uri': uri,
|
||||
'cid': 'bafy...',
|
||||
},
|
||||
}, data=None, headers=ANY)
|
||||
|
||||
@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
|
||||
@patch('requests.get')
|
||||
def test_poll_notifications(self, mock_get, mock_create_task):
|
||||
|
@ -1192,7 +1233,7 @@ class ATProtoTest(TestCase):
|
|||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
expected_list_notifs = call(
|
||||
'https://api.bsky-sandbox.dev/xrpc/app.bsky.notification.listNotifications?limit=10',
|
||||
'https://appview.local/xrpc/app.bsky.notification.listNotifications?limit=10',
|
||||
json=None, data=None,
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -1290,7 +1331,7 @@ class ATProtoTest(TestCase):
|
|||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
get_timeline = call(
|
||||
'https://api.bsky-sandbox.dev/xrpc/app.bsky.feed.getTimeline?limit=10',
|
||||
'https://appview.local/xrpc/app.bsky.feed.getTimeline?limit=10',
|
||||
json=None, data=None,
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
@ -30,10 +30,11 @@ class CommonTest(TestCase):
|
|||
|
||||
# current user's homepage gets converted to BF user page
|
||||
self.assert_multiline_equals("""\
|
||||
<a class="h-card u-author" href="https://user.com/">
|
||||
|
||||
<span class="logo" title="Web">🌐</span>
|
||||
<a class="h-card u-author" href="/web/user.com" title="user.com">
|
||||
user.com
|
||||
</a>""", common.pretty_link('https://user.com/', user=Web(id='user.com')))
|
||||
</a>""", common.pretty_link('https://user.com/', user=Web(id='user.com')),
|
||||
ignore_blanks=True)
|
||||
|
||||
def test_redirect_wrap_empty(self):
|
||||
self.assertIsNone(common.redirect_wrap(None))
|
||||
|
|
|
@ -142,7 +142,7 @@ class ConvertTest(testutil.TestCase):
|
|||
|
||||
resp = self.client.get(f'/convert/web/{url}',
|
||||
base_url='https://ap.brid.gy/')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(200, resp.status_code, resp.get_data(as_text=True))
|
||||
self.assertEqual(CONTENT_TYPE_HTML, resp.content_type)
|
||||
self.assert_multiline_equals(HTML, resp.get_data(as_text=True),
|
||||
ignore_blanks=True)
|
||||
|
@ -271,10 +271,8 @@ A ☕ reply
|
|||
# self.assertEqual(f'https://ap.brid.gy/convert/web/https:/foo%3Fbar%23baz',
|
||||
# resp.headers['Location'])
|
||||
|
||||
@patch('requests.get')
|
||||
@patch('requests.get', return_value=requests_response(HTML_NO_ID))
|
||||
def test_web_to_activitypub_object(self, mock_get):
|
||||
mock_get.return_value = requests_response(HTML_NO_ID)
|
||||
|
||||
self.make_user('user.com', cls=Web)
|
||||
|
||||
url = 'https://user.com/bar?baz=baj&biff'
|
||||
|
@ -322,11 +320,9 @@ A ☕ reply
|
|||
'attributedTo': 'https://web.brid.gy/nope.com',
|
||||
}, resp.json, ignore=['to'])
|
||||
|
||||
@patch('requests.get')
|
||||
@patch('requests.get', return_value=requests_response(HTML_NO_ID))
|
||||
def test_web_to_activitypub_url_decode(self, mock_get):
|
||||
"""https://github.com/snarfed/bridgy-fed/issues/581"""
|
||||
mock_get.return_value = requests_response(HTML_NO_ID)
|
||||
|
||||
self.make_user('user.com', cls=Web)
|
||||
self.store_object(id='http://user.com/a#b',
|
||||
mf2=parse_mf2(HTML_NO_ID)['items'][0])
|
||||
|
|
|
@ -11,6 +11,11 @@ from web import Web
|
|||
|
||||
|
||||
class IdsTest(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
Web(id='bsky.brid.gy', ap_subdomain='bsky', has_redirects=True).put()
|
||||
Web(id='fed.brid.gy', ap_subdomain='fed', has_redirects=True).put()
|
||||
|
||||
def test_translate_user_id(self):
|
||||
Web(id='user.com',
|
||||
copies=[Target(uri='did:plc:123', protocol='atproto')]).put()
|
||||
|
@ -34,21 +39,25 @@ class IdsTest(TestCase):
|
|||
(ActivityPub, 'https://bsky.app/profile/user.com', ATProto, 'did:plc:123'),
|
||||
(ActivityPub, 'https://bsky.app/profile/did:plc:123',
|
||||
ATProto, 'did:plc:123'),
|
||||
|
||||
(ATProto, 'did:plc:456', ATProto, 'did:plc:456'),
|
||||
# copies
|
||||
(ATProto, 'did:plc:123', Web, 'user.com'),
|
||||
(ATProto, 'did:plc:456', ActivityPub, 'https://inst/user'),
|
||||
(ATProto, 'did:plc:789', Fake, 'fake:user'),
|
||||
|
||||
# no copies
|
||||
(ATProto, 'did:plc:x', Web, 'https://bsky.brid.gy/web/did:plc:x'),
|
||||
(ATProto, 'did:plc:x', ActivityPub, 'https://bsky.brid.gy/ap/did:plc:x'),
|
||||
(ATProto, 'did:plc:x', Fake, 'fake:u:did:plc:x'),
|
||||
(ATProto, 'https://bsky.app/profile/user.com', ATProto, 'did:plc:123'),
|
||||
(ATProto, 'https://bsky.app/profile/did:plc:123', ATProto, 'did:plc:123'),
|
||||
|
||||
(Fake, 'fake:user', ActivityPub, 'https://fa.brid.gy/ap/fake:user'),
|
||||
(Fake, 'fake:user', ATProto, 'did:plc:789'),
|
||||
(Fake, 'fake:user', Fake, 'fake:user'),
|
||||
(Fake, 'fake:user', Web, 'https://fa.brid.gy/web/fake:user'),
|
||||
|
||||
(Web, 'user.com', ActivityPub, 'http://localhost/user.com'),
|
||||
(Web, 'https://user.com/', ActivityPub, 'http://localhost/user.com'),
|
||||
(Web, 'user.com', ATProto, 'did:plc:123'),
|
||||
|
@ -58,6 +67,10 @@ class IdsTest(TestCase):
|
|||
(Web, 'user.com', Fake, 'fake:u:user.com'),
|
||||
(Web, 'user.com', Web, 'user.com'),
|
||||
(Web, 'https://user.com/', Web, 'user.com'),
|
||||
|
||||
# instance actor / protocol bot users
|
||||
(Web, 'fed.brid.gy', ActivityPub, 'https://fed.brid.gy/fed.brid.gy'),
|
||||
(Web, 'bsky.brid.gy', ActivityPub, 'https://bsky.brid.gy/bsky.brid.gy'),
|
||||
]:
|
||||
with self.subTest(from_=from_.LABEL, to=to.LABEL):
|
||||
self.assertEqual(expected, translate_user_id(
|
||||
|
|
|
@ -9,6 +9,8 @@ from granary import as2
|
|||
from granary.tests.test_bluesky import ACTOR_PROFILE_BSKY, POST_BSKY
|
||||
from oauth_dropins.webutil.flask_util import NoContent
|
||||
from oauth_dropins.webutil.testutil import requests_response
|
||||
from oauth_dropins.webutil import util
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
|
||||
from activitypub import ActivityPub
|
||||
import app
|
||||
|
@ -361,7 +363,7 @@ class IntegrationTests(TestCase):
|
|||
|
||||
@patch('requests.post', return_value=requests_response('OK')) # create DID
|
||||
@patch('requests.get')
|
||||
def test_activitypub_follow_bsky_bot_user_enables_protocol(self, mock_get, _):
|
||||
def test_activitypub_follow_bsky_bot_user_enables_protocol(self, mock_get, mock_post):
|
||||
"""AP follow of @bsky.brid.gy@bsky.brid.gy bridges the account into BLuesky.
|
||||
|
||||
ActivityPub user @alice@inst , https://inst/alice
|
||||
|
@ -388,7 +390,7 @@ class IntegrationTests(TestCase):
|
|||
|
||||
# check results
|
||||
user = ActivityPub.get_by_id('https://inst/alice')
|
||||
self.assertTrue(ActivityPub.is_enabled_to(ATProto, user=user))
|
||||
self.assertTrue(user.is_enabled(ATProto))
|
||||
|
||||
self.assertEqual(1, len(user.copies))
|
||||
self.assertEqual('atproto', user.copies[0].protocol)
|
||||
|
@ -402,6 +404,20 @@ class IntegrationTests(TestCase):
|
|||
self.assertEqual(['app.bsky.actor.profile'], list(records.keys()))
|
||||
self.assertEqual(['self'], list(records['app.bsky.actor.profile'].keys()))
|
||||
|
||||
args, kwargs = mock_post.call_args_list[1]
|
||||
self.assert_equals(('http://inst/inbox',), args)
|
||||
self.assert_equals({
|
||||
'type': 'Accept',
|
||||
'id': 'http://localhost/r/bsky.brid.gy/followers#accept-http://inst/follow',
|
||||
'actor': 'https://bsky.brid.gy/bsky.brid.gy',
|
||||
'object': {
|
||||
'actor': 'https://inst/alice',
|
||||
'id': 'http://inst/follow',
|
||||
'url': 'https://inst/alice#followed-bsky.brid.gy',
|
||||
'type': 'Follow',
|
||||
'object': 'https://bsky.brid.gy/bsky.brid.gy',
|
||||
},
|
||||
}, json_loads(kwargs['data']), ignore=['to', '@context'])
|
||||
|
||||
@patch('requests.post')
|
||||
@patch('requests.get')
|
||||
|
@ -454,4 +470,4 @@ class IntegrationTests(TestCase):
|
|||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
user = ATProto.get_by_id('did:plc:alice')
|
||||
self.assertTrue(ATProto.is_enabled_to(ActivityPub, user=user))
|
||||
self.assertTrue(user.is_enabled(ActivityPub))
|
||||
|
|
|
@ -18,7 +18,7 @@ from oauth_dropins.webutil.testutil import NOW, requests_response
|
|||
from oauth_dropins.webutil import util
|
||||
|
||||
# import first so that Fake is defined before URL routes are registered
|
||||
from .testutil import Fake, OtherFake, TestCase
|
||||
from .testutil import ExplicitEnableFake, Fake, OtherFake, TestCase
|
||||
|
||||
from activitypub import ActivityPub
|
||||
from atproto import ATProto
|
||||
|
@ -167,14 +167,15 @@ class UserTest(TestCase):
|
|||
|
||||
def test_user_link(self):
|
||||
self.assert_multiline_equals("""\
|
||||
<a class="h-card u-author" href="https://y.z/">
|
||||
|
||||
<span class="logo" title="Web">🌐</span>
|
||||
<a class="h-card u-author" href="/web/y.z" title="y.z">
|
||||
y.z
|
||||
</a>""", self.user.user_link())
|
||||
</a>""", self.user.user_link(), ignore_blanks=True)
|
||||
|
||||
self.user.obj = Object(id='a', as2=ACTOR)
|
||||
self.assert_multiline_equals("""\
|
||||
<a class="h-card u-author" href="https://y.z/">
|
||||
<span class="logo" title="Web">🌐</span>
|
||||
<a class="h-card u-author" href="/web/y.z" title="Mrs. ☕ Foo">
|
||||
<img src="https://user.com/me.jpg" class="profile">
|
||||
Mrs. ☕ Foo
|
||||
</a>""", self.user.user_link())
|
||||
|
@ -294,7 +295,6 @@ class UserTest(TestCase):
|
|||
|
||||
self.assertIsNone(OtherFake().get_copy(Fake))
|
||||
|
||||
|
||||
def test_count_followers(self):
|
||||
self.assertEqual((0, 0), self.user.count_followers())
|
||||
|
||||
|
@ -310,6 +310,65 @@ class UserTest(TestCase):
|
|||
del self.user
|
||||
self.assertEqual((1, 2), user.count_followers())
|
||||
|
||||
def test_is_enabled_default_enabled_protocols(self):
|
||||
self.assertTrue(Web(id='').is_enabled(ActivityPub))
|
||||
self.assertTrue(ActivityPub(id='').is_enabled(Web))
|
||||
self.assertTrue(ActivityPub(id='').is_enabled(ActivityPub))
|
||||
self.assertTrue(Fake(id='').is_enabled(OtherFake))
|
||||
self.assertTrue(Fake(id='').is_enabled(ExplicitEnableFake))
|
||||
|
||||
self.assertFalse(ActivityPub(id='').is_enabled(ATProto))
|
||||
self.assertFalse(ATProto(id='').is_enabled(ActivityPub))
|
||||
self.assertFalse(ATProto(id='').is_enabled(Web))
|
||||
self.assertFalse(Web(id='').is_enabled(ATProto))
|
||||
self.assertFalse(ExplicitEnableFake(id='').is_enabled(Fake))
|
||||
self.assertFalse(ExplicitEnableFake(id='').is_enabled(Web))
|
||||
|
||||
def test_is_enabled_enabled_protocols_overrides_bio_opt_out(self):
|
||||
user = self.make_user('eefake:user', cls=ExplicitEnableFake,
|
||||
obj_as1={'summary': '#nobridge'})
|
||||
self.assertFalse(user.is_enabled(Web))
|
||||
|
||||
user.enabled_protocols = ['web']
|
||||
user.put()
|
||||
self.assertTrue(user.is_enabled(Web))
|
||||
|
||||
def test_is_enabled_manual_opt_out(self):
|
||||
user = self.make_user('user.com', cls=Web)
|
||||
self.assertTrue(user.is_enabled(ActivityPub))
|
||||
|
||||
user.manual_opt_out = True
|
||||
user.put()
|
||||
self.assertFalse(user.is_enabled(ActivityPub))
|
||||
|
||||
user.enabled_protocols = ['activitypub']
|
||||
user.put()
|
||||
self.assertFalse(user.is_enabled(ActivityPub))
|
||||
|
||||
def test_is_enabled_enabled_protocols(self):
|
||||
user = self.make_user(id='eefake:foo', cls=ExplicitEnableFake)
|
||||
self.assertFalse(user.is_enabled(Fake))
|
||||
|
||||
user.enabled_protocols = ['web']
|
||||
user.put()
|
||||
self.assertFalse(user.is_enabled(Fake))
|
||||
|
||||
user.enabled_protocols = ['web', 'fake']
|
||||
user.put()
|
||||
self.assertTrue(user.is_enabled(Fake))
|
||||
|
||||
def test_is_enabled_protocol_bot_users(self):
|
||||
# protocol bot users should always be enabled to *other* protocols
|
||||
self.assertTrue(Web(id='eefake.brid.gy').is_enabled(Fake))
|
||||
self.assertTrue(Web(id='fa.brid.gy').is_enabled(ExplicitEnableFake))
|
||||
self.assertTrue(Web(id='other.brid.gy').is_enabled(Fake))
|
||||
self.assertTrue(Web(id='ap.brid.gy').is_enabled(ATProto))
|
||||
self.assertTrue(Web(id='bsky.brid.gy').is_enabled(ActivityPub))
|
||||
|
||||
# ...but not to their own protocol
|
||||
self.assertFalse(Web(id='ap.brid.gy').is_enabled(ActivityPub))
|
||||
self.assertFalse(Web(id='bsky.brid.gy').is_enabled(ATProto))
|
||||
|
||||
|
||||
class ObjectTest(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -520,7 +579,8 @@ class ObjectTest(TestCase):
|
|||
):
|
||||
with self.subTest(expected=expected, as2=as2):
|
||||
obj = Object(id='x', as2=as2)
|
||||
self.assert_multiline_in(expected, obj.actor_link())
|
||||
self.assert_multiline_in(expected, obj.actor_link(),
|
||||
ignore_blanks=True)
|
||||
|
||||
self.assertEqual(
|
||||
'<a class="h-card u-author" href="http://foo">foo</a>',
|
||||
|
@ -531,7 +591,7 @@ class ObjectTest(TestCase):
|
|||
obj = Object(id='x', source_protocol='ui', users=[self.user.key])
|
||||
|
||||
got = obj.actor_link(user=self.user)
|
||||
self.assertIn('href="fake:user">', got)
|
||||
self.assertIn('href="fake:user" title="Alice">', got)
|
||||
self.assertIn('Alice', got)
|
||||
|
||||
def test_actor_link_object_in_datastore(self):
|
||||
|
@ -562,7 +622,7 @@ class ObjectTest(TestCase):
|
|||
<a class="h-card u-author" href="" title="Alice">
|
||||
<img class="profile" src="foo.jpg" width="32"/>
|
||||
Alice
|
||||
</a>""", obj.actor_link(sized=True))
|
||||
</a>""", obj.actor_link(sized=True), ignore_blanks=True)
|
||||
|
||||
def test_actor_link_composite_url(self):
|
||||
obj = Object(id='x', our_as1={
|
||||
|
@ -628,6 +688,22 @@ class ObjectTest(TestCase):
|
|||
self.assertEqual({'id': 'x', 'foo': 'bar'},
|
||||
Object(id='x', our_as1={'foo': 'bar'}).as1)
|
||||
|
||||
def test_as1_from_as2_protocol_bot_user(self):
|
||||
self.assert_equals({
|
||||
'objectType': 'application',
|
||||
'id': 'fed.brid.gy',
|
||||
'url': 'https://fed.brid.gy/',
|
||||
'displayName': 'Bridgy Fed',
|
||||
'username': 'fed.brid.gy',
|
||||
'image': [{
|
||||
'displayName': 'Bridgy Fed',
|
||||
'url': 'https://fed.brid.gy/static/bridgy_logo_square.jpg',
|
||||
}, {
|
||||
'objectType': 'featured',
|
||||
'url': 'https://fed.brid.gy/static/bridgy_logo.jpg',
|
||||
}],
|
||||
}, Web.load('https://fed.brid.gy/').as1, ignore=['summary'])
|
||||
|
||||
def test_atom_url_overrides_id(self):
|
||||
obj = {
|
||||
'objectType': 'note',
|
||||
|
|
|
@ -30,8 +30,8 @@ ACTOR_WITH_PREFERRED_USERNAME = {
|
|||
|
||||
|
||||
def contents(activities):
|
||||
return [util.parse_html((a.get('object') or a)['content'].splitlines()[0]
|
||||
).get_text().strip()
|
||||
return [
|
||||
' '.join(util.parse_html((a.get('object') or a)['content']).get_text().split())
|
||||
for a in activities]
|
||||
|
||||
|
||||
|
@ -40,7 +40,7 @@ class PagesTest(TestCase):
|
|||
EXPECTED_SNIPPETS = [
|
||||
'Dr. Eve replied a comment',
|
||||
'tag:fake.com:44... posted a mention',
|
||||
'tag:fake.com:44... posted my note',
|
||||
'🌐 user.com posted my note',
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
|
@ -99,8 +99,7 @@ class PagesTest(TestCase):
|
|||
self.assert_equals(404, got.status_code)
|
||||
|
||||
def test_user_not_direct(self):
|
||||
fake = self.make_user('fake:foo', cls=Fake, direct=False)
|
||||
got = self.client.get('/fake/fake:foo')
|
||||
got = self.client.get('/web/user.com')
|
||||
self.assert_equals(200, got.status_code)
|
||||
|
||||
fake = self.make_user('http://fo/o', cls=ActivityPub, direct=False)
|
||||
|
@ -180,14 +179,13 @@ class PagesTest(TestCase):
|
|||
got = self.client.post('/fa/fake:user/update-profile')
|
||||
self.assert_equals(302, got.status_code)
|
||||
self.assert_equals('/fa/fake:handle:user', got.headers['Location'])
|
||||
self.assertEqual(['Updating profile for fake:handle:user'],
|
||||
self.assertEqual(['Updating profile from <a href="fake:user">fake:handle:user</a>...'],
|
||||
get_flashed_messages())
|
||||
|
||||
self.assertEqual(['fake:user'], Fake.fetched)
|
||||
self.assert_object('fake:user', source_protocol='fake', our_as1={
|
||||
**actor,
|
||||
'updated': '2022-01-02T03:04:05+00:00',
|
||||
})
|
||||
|
||||
actor['updated'] = '2022-01-02T03:04:05+00:00'
|
||||
self.assert_object('fake:user', source_protocol='fake', our_as1=actor)
|
||||
|
||||
def test_followers(self):
|
||||
Follower.get_or_create(
|
||||
|
|
|
@ -175,41 +175,6 @@ class ProtocolTest(TestCase):
|
|||
def test_for_handle_atproto_resolve(self, _):
|
||||
self.assertEqual((ATProto, 'did:plc:123abc'), Protocol.for_handle('han.dull'))
|
||||
|
||||
def test_is_enabled_to(self):
|
||||
self.assertTrue(Web.is_enabled_to(ActivityPub))
|
||||
self.assertTrue(ActivityPub.is_enabled_to(Web))
|
||||
self.assertTrue(ActivityPub.is_enabled_to(ActivityPub))
|
||||
self.assertTrue(Fake.is_enabled_to(OtherFake))
|
||||
self.assertTrue(Fake.is_enabled_to(ExplicitEnableFake))
|
||||
|
||||
self.assertFalse(ActivityPub.is_enabled_to(ATProto))
|
||||
self.assertFalse(ATProto.is_enabled_to(ActivityPub))
|
||||
self.assertFalse(ATProto.is_enabled_to(Web))
|
||||
self.assertFalse(Web.is_enabled_to(ATProto))
|
||||
self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake))
|
||||
self.assertFalse(ExplicitEnableFake.is_enabled_to(Web))
|
||||
|
||||
def test_is_enabled_to_opt_out(self):
|
||||
user = self.make_user('user.com', cls=Web)
|
||||
self.assertTrue(Web.is_enabled_to(ActivityPub, user))
|
||||
|
||||
user.manual_opt_out = True
|
||||
user.put()
|
||||
protocol.objects_cache.clear()
|
||||
self.assertFalse(Web.is_enabled_to(ActivityPub, 'user.com'))
|
||||
|
||||
def test_is_enabled_to_enabled_protocols(self):
|
||||
user = self.make_user(id='eefake:foo', cls=ExplicitEnableFake)
|
||||
self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake, 'eefake:foo'))
|
||||
|
||||
user.enabled_protocols = ['web']
|
||||
user.put()
|
||||
self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake, 'eefake:foo'))
|
||||
|
||||
user.enabled_protocols = ['web', 'fake']
|
||||
user.put()
|
||||
self.assertTrue(ExplicitEnableFake.is_enabled_to(Fake, 'eefake:foo'))
|
||||
|
||||
def test_load(self):
|
||||
Fake.fetchable['foo'] = {'x': 'y'}
|
||||
|
||||
|
@ -1301,15 +1266,16 @@ class ProtocolReceiveTest(TestCase):
|
|||
def test_update_profile_bare_object(self):
|
||||
self.make_followers()
|
||||
|
||||
actor = {
|
||||
actor = self.user.obj.our_as1 = {
|
||||
'objectType': 'person',
|
||||
'id': 'fake:user',
|
||||
'displayName': 'Ms. ☕ Baz',
|
||||
'summary': 'first',
|
||||
}
|
||||
self.store_object(id='fake:user', our_as1=actor)
|
||||
self.user.obj.put()
|
||||
|
||||
actor['summary'] = 'second'
|
||||
# unchanged from what's already in the datastore. we should send update
|
||||
# anyway (instead of create) since it's an actor.
|
||||
Fake.receive_as1(actor)
|
||||
|
||||
# profile object
|
||||
|
@ -1649,6 +1615,27 @@ class ProtocolReceiveTest(TestCase):
|
|||
self.assertEqual(1, len(followers))
|
||||
self.assertEqual(self.alice.key, followers[0].to)
|
||||
|
||||
def test_skip_bridged_user(self):
|
||||
"""If the actor isn't from the source protocol, skip the activity.
|
||||
|
||||
(It's probably from a bridged user, and we only want to handle source
|
||||
activities, not bridged activities.)
|
||||
"""
|
||||
self.user.copies = [Target(uri='other:user', protocol='other')]
|
||||
self.user.put()
|
||||
|
||||
with self.assertRaises(NoContent):
|
||||
OtherFake.receive_as1({
|
||||
'id': 'other:follow',
|
||||
'objectType': 'activity',
|
||||
'verb': 'follow',
|
||||
'actor': 'fake:user',
|
||||
'object': 'fake:alice',
|
||||
})
|
||||
self.assertEqual(0, len(OtherFake.sent))
|
||||
self.assertEqual(0, len(Fake.sent))
|
||||
self.assertIsNone(Object.get_by_id('other:follow'))
|
||||
|
||||
@patch('requests.post')
|
||||
@patch('requests.get')
|
||||
def test_skip_web_same_domain(self, mock_get, mock_post):
|
||||
|
@ -1836,7 +1823,7 @@ class ProtocolReceiveTest(TestCase):
|
|||
}
|
||||
|
||||
user = self.make_user('eefake:user', cls=ExplicitEnableFake)
|
||||
self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake, user))
|
||||
self.assertFalse(user.is_enabled(Fake))
|
||||
|
||||
# fake protocol isn't enabled yet, block should be a noop
|
||||
self.assertEqual(('OK', 200), ExplicitEnableFake.receive_as1(block))
|
||||
|
@ -1850,7 +1837,7 @@ class ProtocolReceiveTest(TestCase):
|
|||
user = user.key.get()
|
||||
self.assertEqual(['fake'], user.enabled_protocols)
|
||||
self.assertEqual(['eefake:user'], Fake.created_for)
|
||||
self.assertTrue(ExplicitEnableFake.is_enabled_to(Fake, user))
|
||||
self.assertTrue(user.is_enabled(Fake))
|
||||
self.assertEqual([('fa.brid.gy/followers#accept-eefake:follow',
|
||||
'eefake:user:target')],
|
||||
ExplicitEnableFake.sent)
|
||||
|
@ -1870,7 +1857,7 @@ class ProtocolReceiveTest(TestCase):
|
|||
user = user.key.get()
|
||||
self.assertEqual([], user.enabled_protocols)
|
||||
self.assertEqual([], Fake.created_for)
|
||||
self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake, user))
|
||||
self.assertFalse(user.is_enabled(Fake))
|
||||
|
||||
def test_dm_no_yes_sets_enabled_protocols(self):
|
||||
dm = {
|
||||
|
@ -1882,7 +1869,7 @@ class ProtocolReceiveTest(TestCase):
|
|||
}
|
||||
|
||||
user = self.make_user('eefake:user', cls=ExplicitEnableFake)
|
||||
self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake, user))
|
||||
self.assertFalse(user.is_enabled(Fake))
|
||||
|
||||
# fake protocol isn't enabled yet, no DM should be a noop
|
||||
self.assertEqual(('OK', 200), ExplicitEnableFake.receive_as1(dm))
|
||||
|
@ -1892,12 +1879,12 @@ class ProtocolReceiveTest(TestCase):
|
|||
|
||||
# yes DM should add to enabled_protocols
|
||||
dm['id'] += '2'
|
||||
dm['content'] = 'yes'
|
||||
dm['content'] = '<p><a href="...">@bsky.brid.gy</a> yes</p>'
|
||||
self.assertEqual(('OK', 200), ExplicitEnableFake.receive_as1(dm))
|
||||
user = user.key.get()
|
||||
self.assertEqual(['fake'], user.enabled_protocols)
|
||||
self.assertEqual(['eefake:user'], Fake.created_for)
|
||||
self.assertTrue(ExplicitEnableFake.is_enabled_to(Fake, user))
|
||||
self.assertTrue(user.is_enabled(Fake))
|
||||
|
||||
# another yes DM should be a noop
|
||||
dm['id'] += '3'
|
||||
|
@ -1907,14 +1894,14 @@ class ProtocolReceiveTest(TestCase):
|
|||
self.assertEqual(['fake'], user.enabled_protocols)
|
||||
self.assertEqual([], Fake.created_for)
|
||||
|
||||
# block should remove from enabled_protocols
|
||||
# no DM should remove from enabled_protocols
|
||||
dm['id'] += '4'
|
||||
dm['content'] = ' \n NO '
|
||||
dm['content'] = '<p><a href="...">@bsky.brid.gy</a>\n NO \n</p>'
|
||||
self.assertEqual(('OK', 200), ExplicitEnableFake.receive_as1(dm))
|
||||
user = user.key.get()
|
||||
self.assertEqual([], user.enabled_protocols)
|
||||
self.assertEqual([], Fake.created_for)
|
||||
self.assertFalse(ExplicitEnableFake.is_enabled_to(Fake, user))
|
||||
self.assertFalse(user.is_enabled(Fake))
|
||||
|
||||
def test_receive_task_handler(self):
|
||||
note = {
|
||||
|
|
|
@ -2903,7 +2903,7 @@ class WebUtilTest(TestCase):
|
|||
def test_convert(self, mock_get, __):
|
||||
mock_get.return_value = ACTOR_HTML_RESP
|
||||
|
||||
obj = Object(id='http://orig', mf2=ACTOR_MF2)
|
||||
obj = Object(id='http://orig', mf2=ACTOR_MF2, source_protocol='web')
|
||||
self.assert_multiline_equals("""\
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
|
|
@ -366,7 +366,7 @@ class WebfingerTest(TestCase):
|
|||
self.assertEqual('acct:bsky.brid.gy@bsky.brid.gy', got.json['subject'])
|
||||
self.assertEqual(['https://bsky.brid.gy/'], got.json['aliases'])
|
||||
self.assertIn({
|
||||
'href': 'http://localhost/bsky.brid.gy',
|
||||
'href': 'https://bsky.brid.gy/bsky.brid.gy',
|
||||
'rel': 'self',
|
||||
'type': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
}, got.json['links'])
|
||||
|
|
|
@ -71,6 +71,7 @@ class Fake(User, protocol.Protocol):
|
|||
PHRASE = 'fake-phrase'
|
||||
CONTENT_TYPE = 'fa/ke'
|
||||
HAS_COPIES = True
|
||||
LOGO_HTML = '<img src="fake-logo">'
|
||||
|
||||
# maps string ids to dict AS1 objects that can be fetched
|
||||
fetchable = {}
|
||||
|
@ -265,8 +266,12 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
|||
|
||||
# arroba config
|
||||
os.environ.update({
|
||||
'APPVIEW_HOST': 'appview.local',
|
||||
'BGS_HOST': 'bgs.local',
|
||||
'PDS_HOST': 'pds.local',
|
||||
'PLC_HOST': 'plc.local',
|
||||
'MOD_SERVICE_HOST': 'mod.service.local',
|
||||
'MOD_SERVICE_DID': 'did:mod-service',
|
||||
})
|
||||
|
||||
def tearDown(self):
|
||||
|
|
6
web.py
6
web.py
|
@ -554,11 +554,11 @@ class Web(User, Protocol):
|
|||
return ''
|
||||
|
||||
obj_as1 = obj.as1
|
||||
if from_user and not from_user.is_enabled(cls):
|
||||
error(f'{from_user.key.id()} => {cls.LABEL} not enabled')
|
||||
|
||||
from_proto = PROTOCOLS.get(obj.source_protocol)
|
||||
if from_proto:
|
||||
if not from_proto.is_enabled_to(cls, user=from_user):
|
||||
error(f'{cls.LABEL} <=> {from_proto.LABEL} not enabled')
|
||||
|
||||
# fill in author/actor if available
|
||||
for field in 'author', 'actor':
|
||||
val = as1.get_object(obj.as1, field)
|
||||
|
|
|
@ -90,7 +90,7 @@ class Webfinger(flask_util.XrdOrJrd):
|
|||
if user and not user.direct:
|
||||
error(f"{user.key} hasn't signed up yet", status=404)
|
||||
|
||||
if not user or not user.is_enabled_to(activitypub.ActivityPub, user=user):
|
||||
if not user or not user.is_enabled(activitypub.ActivityPub):
|
||||
error(f'No {cls.LABEL} user found for {id}', status=404)
|
||||
|
||||
ap_handle = user.handle_as('activitypub')
|
||||
|
|
Ładowanie…
Reference in New Issue