Bridgy Fed
Reference documentation.
activitypub
ActivityPub protocol implementation.
- class ActivityPub(**kwargs)[source]
-
ActivityPub protocol class.
Key id is AP/AS2 actor id URL. (Not fediverse/WebFinger @-@ handle!)
- ABBREV = 'ap'
- PHRASE = 'the fediverse'
- LOGO_HTML = '<img src="/static/fediverse_logo.svg">'
- CONTENT_TYPE = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
- REQUIRES_AVATAR = True
- REQUIRES_NAME = False
- DEFAULT_ENABLED_PROTOCOLS = ('web',)
- SUPPORTED_AS1_TYPES = ('organization', 'group', 'person', 'application', 'service', 'note', 'article', 'mention', 'comment', 'link', 'post', 'delete', 'undo', 'update', 'rsvp-interested', 'like', 'block', 'stop-following', 'reject', 'flag', 'accept', 'rsvp-no', 'rsvp-maybe', 'share', 'react', 'undo', 'follow', 'rsvp-yes', 'invite', 'audio', 'bookmark', 'image', 'move', 'video')
- SUPPORTED_AS2_TYPES = ('Organization', 'Group', 'Person', 'Application', 'Service', 'Note', 'Article', 'Mention', 'Note', 'Link', 'Create', 'Delete', 'Undo', 'Update', None, 'Like', 'Block', None, 'Reject', 'Flag', 'Accept', 'Reject', 'TentativeAccept', 'Announce', None, 'Undo', 'Follow', 'Accept', 'Invite', 'Audio', None, 'Image', 'Move', 'Video')
- SUPPORTS_DMS = True
- webfinger_addr
Populated by
reload_profile()
.
- reload_profile(**kwargs)[source]
Reloads this user’s AP actor, then resolves their webfinger subject.
load AP actor
fetch Webfinger with preferredUsername
re-fetch Webfinger with subject from first Webfinger
https://www.w3.org/community/reports/socialcg/CG-FINAL-apwf-20240608/#reverse-discovery https://correct.webfinger-canary.fietkau.software/#developers
- classmethod owns_id(id)[source]
Returns None if
id
is an http(s) URL, False otherwise.All AP ids are http(s) URLs, but not all http(s) URLs are AP ids.
https://www.w3.org/TR/activitypub/#obj-id
I used to include a heuristic here that no actor is the root path on its host, which was nice because it let us assume that home pages are Web users without making any network requests…but then I inevitably ran into AP actors that _are_ the root path, eg microblog.pub sites like https://bw3.dev/ .
- classmethod owns_handle(handle, allow_internal=False)[source]
Returns True if handle is a WebFinger
@-@
handle, False otherwise.Example:
@user@instance.com
. The leading@
is optional.https://datatracker.ietf.org/doc/html/rfc7033#section-3.1 https://datatracker.ietf.org/doc/html/rfc7033#section-4.5
- classmethod target_for(obj, shared=False)[source]
Returns
obj
’s or its author’s/actor’s inbox, if available.
- classmethod send(obj, url, from_user=None, orig_obj_id=None)[source]
Delivers an activity to an inbox URL.
If
obj.recipient_obj
is set, it’s interpreted as the receiving actor who we’re delivering to and its id is populated intocc
.
- classmethod fetch(obj, **kwargs)[source]
Tries to fetch an AS2 object.
Assumes
obj.id
is a URL. Any fragment at the end is stripped before loading. This is currently underspecified and somewhat inconsistent across AP implementations:https://socialhub.activitypub.rocks/t/problems-posting-to-mastodon-inbox/801/11
https://socialhub.activitypub.rocks/t/problems-posting-to-mastodon-inbox/801/23
https://socialhub.activitypub.rocks/t/s2s-create-activity/1647/5
Uses HTTP content negotiation via the
Content-Type
header. If the url is HTML and it has arel-alternate
link with an AS2 content type, fetches and returns that URL.Includes an HTTP Signature with the request.
Mastodon requires this signature if
AUTHORIZED_FETCH
aka secure mode is on: https://docs.joinmastodon.org/admin/config/#authorized_fetchSigns the request with the current user’s key. If not provided, defaults to using @snarfed.org@snarfed.org’s key.
See
protocol.Protocol.fetch()
for more details.- Parameters:
obj (Object) – with the id to fetch. Fills data into the as2 property.
kwargs – ignored
- Returns:
True if the object was fetched and populated successfully, False otherwise
- Return type:
- Raises:
HTTPException – will have an additional
requests_response
attribute with the lastrequests.Response
we received.
- classmethod _convert(obj, orig_obj=None, from_user=None)[source]
Convert a
models.Object
to AS2.- Parameters:
obj (Object)
orig_obj (dict) – AS2 object, optional. The target of activity’s
inReplyTo
orLike
/Announce
/etc object, if any. Passed through topostprocess_as2()
.from_user (User) – user (actor) this activity/object is from
- Returns:
AS2 JSON
- Return type:
- classmethod migrate_out(user, to_user_id)[source]
Migrates a bridged account out to be a native account.
- Parameters:
- Raises:
ValueError – eg if
ActivityPub
doesn’t ownto_user_id
- signed_request(fn, url, data=None, headers=None, from_user=None, _redirect_count=None, **kwargs)[source]
Wraps
requests.*
and adds HTTP Signature.https://swicg.github.io/activitypub-http-signature/
- Parameters:
fn (callable) –
util.requests_get()
orutil.requests_post()
url (str)
data (dict) – optional AS2 object
from_user (User) – user to sign request as; optional. If not provided, uses the default user
@fed.brid.gy@fed.brid.gy
._redirect_count – internal, used to count redirects followed so far
kwargs – passed through to requests
- Return type:
- postprocess_as2(activity, orig_obj=None, wrap=True)[source]
Prepare an AS2 object to be served or sent via ActivityPub.
TODO: get rid of orig_obj! https://github.com/snarfed/bridgy-fed/issues/1257
- postprocess_as2_actor(actor, user)[source]
Prepare an AS2 actor object to be served or sent via ActivityPub.
Modifies actor in place.
atproto
ATProto protocol implementation.
- chat_client(*, repo, method, **kwargs)[source]
Returns a new Bluesky chat
Client
for a given XRPC method.- Parameters:
repo (Repo) – ATProto user
method (str) – XRPC method NSID, eg
chat.bsky.convo.sendMessage
kwargs – passed through to the
lexrpc.client.Client
constructor
- Return type:
- class DatastoreClient(*args, **kwargs)[source]
Bases:
Client
Bluesky client that uses the datastore as well as remote XRPC calls.
Overrides
getRecord
andresolveHandle
. If we have a record or DID document stored locally, uses it as is instead of making a remote XRPC call. Otherwise, passes through to the server.Right now, requires that the server address is the same as
$APPVIEW_HOST
, becausegetRecord
passes through toATProto.load
and then toATProto.fetch
, which uses theappview
global.
- class Cursor(**kwargs)[source]
Bases:
StringIdModel
The last cursor (sequence number) we’ve seen for a host and event stream.
https://atproto.com/specs/event-stream#sequence-numbers
Key id is
[HOST] [XRPC]
, where[XRPC]
is the NSID of the XRPC method for the event stream. For example, subscribeRepos on the production relay isbsky.network com.atproto.sync.subscribeRepos
.cursor
is the latest sequence number that we know we’ve seen, so when we re-subscribe to this event stream, we should sendcursor + 1
.- cursor
- created
- updated
- class ATProto(**kwargs)[source]
-
AT Protocol class.
Key id is DID, currently either did:plc or did:web. https://atproto.com/specs/did
- ABBREV = 'bsky'
- PHRASE = 'Bluesky'
- LOGO_HTML = '<img src="/oauth_dropins_static/bluesky.svg">'
- PDS_URL = 'https://atproto.brid.gy'
Note that PDS hostname is atproto.brid.gy here, not bsky.brid.gy. Bluesky team currently has our hostname as atproto.brid.gy in their federation test. also note that PDS URL shouldn’t include trailing slash. https://atproto.com/specs/did#did-documents
- CONTENT_TYPE = 'application/json'
- HAS_COPIES = True
- REQUIRES_AVATAR = True
- REQUIRES_NAME = False
- DEFAULT_ENABLED_PROTOCOLS = ('web',)
- SUPPORTED_AS1_TYPES = frozenset({'application', 'article', 'block', 'comment', 'delete', 'flag', 'follow', 'group', 'like', 'link', 'mention', 'note', 'organization', 'person', 'post', 'service', 'share', 'stop-following', 'undo', 'update'})
- SUPPORTED_RECORD_TYPES = frozenset({'app.bsky.actor.profile', 'app.bsky.feed.like', 'app.bsky.feed.post', 'app.bsky.feed.repost', 'app.bsky.graph.block', 'app.bsky.graph.follow'})
- SUPPORTS_DMS = True
- classmethod bridged_web_url_for(user, fallback=False)[source]
Returns a bridged user’s profile URL on bsky.app.
For example, returns
https://bsky.app/profile/alice.com.web.brid.gy
for Web useralice.com
.
- classmethod target_for(obj, shared=False)[source]
Returns our PDS URL as the target for the given object.
ATProto delivery is indirect. We write all records to the user’s local repo that we host, then relays and other subscribers receive them via the subscribeRepos event streams. So, we use a single target, our base URL (eg
https://atproto.brid.gy
) as the PDS URL, for all activities.
- classmethod create_for(user)[source]
Creates an ATProto repo and profile for a non-ATProto user.
If the repo already exists, reactivates it by emitting an #account event with active: True.
- Parameters:
user (User)
- Raises:
ValueError – if the user’s handle is invalid, eg begins or ends with an underscore or dash
- classmethod set_dns(handle, did)[source]
Create _atproto DNS record for handle resolution.
https://atproto.com/specs/handle#handle-resolution
If the DNS record already exists, or if we’re not in prod, does nothing. If the DNS record exists with a different DID, deletes it and recreates it with this DID.
- Parameters:
handle (str) – Bluesky handle, eg
snarfed.org.web.brid.gy
- classmethod remove_dns(handle)[source]
Removes an _atproto DNS record.
https://atproto.com/specs/handle#handle-resolution
- Parameters:
handle (str) – Bluesky handle, eg
snarfed.org.web.brid.gy
- classmethod send(obj, url, from_user=None, orig_obj_id=None)[source]
Creates a record if we own its repo.
If the repo’s DID doc doesn’t say we’re its PDS, does nothing and returns False.
Doesn’t deliver anywhere externally! Relays will receive this record through
subscribeRepos
and then deliver it to AppView(s), which will notify recipients as necessary.Exceptions: *
flag``s are translated to ``createReport
to the mod service * DMs are translated tosendMessage
to the chat service
- classmethod load(id, did_doc=False, **kwargs)[source]
Thin wrapper that converts DIDs and bsky.app URLs to at:// URIs.
- Parameters:
did_doc (bool) – if True, loads and returns a DID document object instead of an
app.bsky.actor.profile/self
.
- classmethod _convert(obj, fetch_blobs=False, from_user=None)[source]
Converts a
models.Object
toapp.bsky.*
lexicon JSON.- Parameters:
obj (Object)
fetch_blobs (bool) – whether to fetch images and other blobs, store them in
arroba.datastore_storage.AtpRemoteBlob
s if they don’t already exist, and fill them into the returned object.from_user (User) – user (actor) this activity/object is from
- Returns:
JSON object
- Return type:
- classmethod migrate_in(user, from_user_id, plc_code, pds_client)[source]
Migrates an ATProto account on another PDS in to be a bridged account.
https://atproto.com/guides/account-migration
Before calling this, the repo must have already been imported with
com.atproto.repo.importRepo
!- Parameters:
user (User) – native user on another protocol to attach the newly imported bridged account to
from_user_id (str) – DID of the account to be migrated in
plc_code (str) – a PLC operation confirmation code from the account’s old PDS, from
com.atproto.identity.requestPlcOperationSignature
pds_client (lexrpc.Client) – authenticated client for the account’s old PDS
- Raises:
ValueError – if
from_user_id
is not an ATProto DID, oruser
is anATProto
, oruser
is already bridged to Bluesky, or the repo hasn’t been imported yet
- classmethod add_source_links(actor, obj, from_user)[source]
Adds “bridged from … by Bridgy Fed” text to
obj.our_as1
.Overrides the default
protocol.Protocol.add_source_links()
implementation to use plain text URLs becauseapp.bsky.actor.profile
has nodescriptionFacets
for thedescription
field.TODO: much of this duplicates
protocol.Protocol.add_source_links()
. Refactor somehow.
- poll_chat_task()[source]
Polls for incoming chat messages for our protocol bot users.
- Params:
proto: protocol label, eg
activitypub
- atproto_did()[source]
Programmatic handle resolution for bridged users.
https://github.com/snarfed/bridgy-fed/issues/1537 https://atproto.com/specs/handle#handle-resolution
- Query params:
protocol (str) id (str): native user id or handle
atproto_firehose
ATProto firehose client. Enqueues receive tasks for events for bridged users.
https://atproto.com/specs/event-stream https://atproto.com/specs/sync#firehose
common
Misc common utilities.
- base64_to_long(x)[source]
Converts from URL safe base64 encoding to long integer.
Originally from
django_salmon.magicsigs
. Used inUser.public_pem()
andUser.private_pem()
.
- long_to_base64(x)[source]
Converts from long integer to base64 URL safe encoding.
Originally from
django_salmon.magicsigs
. Used inUser.get_or_create()
.
- error(err, status=400, exc_info=None, **kwargs)[source]
Like
oauth_dropins.webutil.flask_util.error()
, but wraps body in JSON.
- pretty_link(url, text=None, user=None, **kwargs)[source]
Wrapper around
oauth_dropins.webutil.util.pretty_link()
that converts Mastodon user URLs to @-@ handles.Eg for URLs like https://mastodon.social/@foo and https://mastodon.social/users/foo, defaults text to
@foo@mastodon.social
if it’s not provided.
- content_type(resp)[source]
Returns a
requests.Response
’s Content-Type, without charset suffix.
- redirect_wrap(url, domain=None)[source]
Returns a URL on our domain that redirects to this URL.
…to satisfy Mastodon’s non-standard domain matching requirement. :(
https://github.com/snarfed/bridgy-fed/issues/16#issuecomment-424799599
https://github.com/tootsuite/mastodon/pull/6219#issuecomment-429142747
- Returns:
redirect url
- Return type:
- subdomain_wrap(proto, path=None)[source]
Returns the URL for a given path on this protocol’s subdomain.
Eg for the path
foo/bar
on ActivityPub, returnshttps://ap.brid.gy/foo/bar
.- Parameters:
proto (subclass of
protocol.Protocol
)- Returns:
URL
- Return type:
- unwrap(val, field=None)[source]
Removes our subdomain/redirect wrapping from a URL, if it’s there.
val
may be a string, dict, or list. dicts and lists are unwrapped recursively.Strings that aren’t wrapped URLs are left unchanged.
- create_task(queue, delay=None, **params)[source]
Adds a Cloud Tasks task.
If running in a local server, runs the task handler inline instead of creating a task.
- Parameters:
queue (str) – queue name
delay (
datetime.timedelta
) – optional, used as task ETA (from now)params – form-encoded and included in the task request body
- Returns:
response from either running the task inline, if running in a local server, or the response from creating the task.
- Return type:
- report_error(msg, *, exception=False, **kwargs)[source]
Reports an error to StackDriver Error Reporting.
If
DEBUG
andexception
areTrue
, re-raises the exception instead.Duplicated in
bridgy.util
.
- cache_policy(key)[source]
In memory ndb cache.
https://github.com/snarfed/bridgy-fed/issues/1149#issuecomment-2261383697
Only cache kinds in memory that are immutable or largely harmless when changed.
Keep an eye on this in case we start seeing problems due to this ndb bug where unstored in-memory modifications get returned by later gets: https://github.com/googleapis/python-ndb/issues/888
- Parameters:
key (google.cloud.datastore.key.Key or Key) – see https://github.com/googleapis/python-ndb/issues/987
- Returns:
whether to cache this object
- Return type:
- global_cache_timeout_policy(key)[source]
Cache everything for 2h.
- Parameters:
key (google.cloud.datastore.key.Key or Key) – see https://github.com/googleapis/python-ndb/issues/987
- Returns:
cache expiration for this object, in seconds
- Return type:
- as2_request_type()[source]
If this request has conneg (ie the
Accept
header) for AS2, returns its type.Specifically, returns either
application/ld+json; profile="https://www.w3.org/ns/activitystreams"
orapplication/activity+json
.If the current request’s conneg isn’t asking for AS2, returns None.
https://www.w3.org/TR/activitypub/#retrieving-objects https://snarfed.org/2023-03-24_49619-2
convert
Serves /convert/...
URLs to convert data from one protocol to another.
URL pattern is /convert/SOURCE/DEST
, where SOURCE
and DEST
are the
LABEL
constants from the protocol.Protocol
subclasses.
- convert(to, _, from_=None)[source]
Converts data from one protocol to another and serves it.
Fetches the source data if it’s not already stored.
Note that we don’t do conneg or otherwise care about the Accept header here, we always serve the “to” protocol’s format.
- Parameters:
to (str) – protocol
from (str) – protocol, only used when called by
convert_source_path_redirect()
- check_bridged_to(obj, to_proto)[source]
If
object
or its owner isn’t bridged toto_proto
, raiseswerkzeug.exceptions.HTTPException
.
dms
Protocol-independent code for sending and receiving DMs aka chat messages.
- command(names, arg=False, user_bridged=None, handle_bridged=None)[source]
Function decorator. Defines and registers a DM command.
- Parameters:
names (sequence of str) – the command strings that trigger this command, or
None
if this command has no command stringarg – whether this command takes an argument.
False
for no,True
for yes, anything,'handle'
for yes, a handle in the bot account’s protocol for a user that must not already be bridged.user_bridged (bool) – whether the user sending the DM should be bridged.
True
for yes,False
for no, ``None` for either.handle_bridged (bool) – whether the handle arg should be bridged.
True
for yes,False
for no, ``None` for either.
- The decorated function should have the signature:
(from_user, to_proto, arg=None, to_user=None) => (str, None)
If it returns a string, that text is sent to the user as a reply to their DM.
- Args for the decorated function:
from_user (models.User): the user who sent the DM to_proto (protocol.Protocol): the protocol bot account they sent it to arg (str or None): the argument to the command, if any to_user (models.User or None): the user for the argument, if it’s a handle
- The decorated function returns:
str: text to reply to the user in a DM, if any
- maybe_send(*, from_proto, to_user, text, type=None, in_reply_to=None)[source]
Sends a DM.
Creates a task to send the DM asynchronously.
If
type
is provided, and we’ve already sent this user a DM of this type from this protocol, does nothing.
follow
Remote follow handler.
- class FollowStart(to_path, scopes=None)[source]
Bases:
Start
Starts the IndieAuth flow to add a follower to an existing user.
- class FollowCallback(to_path, scopes=None)[source]
Bases:
Callback
IndieAuth callback to add a follower to an existing user.
ids
Translates user ids, handles, and object ids between protocols.
https://fed.brid.gy/docs#translate
- validate(id, from_, to)[source]
Validates args.
Asserts that all args are non-None. If
from_
orto
are instances, returns their classes.
- web_ap_base_domain(user_domain)[source]
Returns the full Bridgy Fed domain to use for a given Web user.
Specifically, returns
http://localhost/` if we're running locally, ``https://[ap_subdomain].brid.gy/
for the Web entity for this domain if it exists, otherwisehttps://web.brid.gy/
.
- translate_user_id(*, id, from_, to)[source]
Translate a user id from one protocol to another.
TODO: unify with
translate_object_id()
.
- normalize_user_id(*, id, proto)[source]
Normalizes a user id to its canonical representation in a given protocol.
Examples:
Web: * user.com => user.com * www.user.com => user.com * https://user.com/ => user.com
ATProto: * did:plc:123 => did:plc:123 * https://bsky.app/profile/did:plc:123 => did:plc:123
Note that
profile_id()
is a narrower inverse of this; it converts user ids to profile ids.
- profile_id(*, id, proto)[source]
Returns the profile object id for a given user id.
Examples:
Web: user.com => https://user.com/
ActivityPub: https://inst.ance/alice => https://inst.ance/alice
ATProto: did:plc:123 => at://did:plc:123/app.bsky.actor.profile/self
Note that
normalize_user_id()
does the inverse of this, ie converts profile ids to user ids.
- translate_handle(*, handle, from_, to, enhanced)[source]
Translates a user handle from one protocol to another.
- Parameters:
TODO: drop enhanced arg, always use if available?
- Returns:
the corresponding handle in
to
- Return type:
- Raises:
ValueError – if the user’s handle is invalid, eg begins or ends with an underscore or dash
memcache
Utilities for caching data in memcache.
- key(key)[source]
Preprocesses a memcache key. Right now just truncates it to 250 chars.
https://pymemcache.readthedocs.io/en/latest/apidoc/pymemcache.client.base.html https://github.com/memcached/memcached/wiki/Commands#standard-protocol
TODO: truncate to 250 UTF-8 chars, to handle Unicode chars in URLs. Related: pymemcache Client’s allow_unicode_keys constructor kwarg.
- memoize(expire=None, key=None, write=True, version=2)[source]
Memoize function decorator that stores the cached value in memcache.
- Parameters:
expire (timedelta) – optional, expiration
key (callable) – function that takes the function’s
(*args, **kwargs)
and returns the cache key to use. If it returns None, memcache won’t be used.write (bool or callable) – whether to write to memcache. If this is a callable, it will be called with the function’s
(*args, **kwargs)
and should return True or False.version (int) – overrides our default version number in the memcache key. Bumping this version can have the same effect as clearing the cache for just the affected function.
models
Datastore model classes.
- class Target(**kwargs)[source]
Bases:
Model
protocol.Protocol
+ URI pairs for identifying objects.These are currently used for:
delivery destinations, eg ActivityPub inboxes, webmention targets, etc.
copies of
Object
s andUser
s elsewhere, egat://
URIs for ATProto records, nevent etc bech32-encoded Nostr ids, ATProto user DIDs, etc.
Used in
google.cloud.ndb.model.StructuredProperty
s insideObject
andUser
; not stored as top-level entities in the datastore.ndb implements this by hoisting each property here into a corresponding property on the parent entity, prefixed by the StructuredProperty name below, eg
delivered.uri
,delivered.protocol
, etc.For repeated StructuredPropertys, the hoisted properties are all repeated on the parent entity, and reconstructed into StructuredPropertys based on their order.
https://googleapis.dev/python/python-ndb/latest/model.html#google.cloud.ndb.model.StructuredProperty
- uri
- protocol
- class DM(**kwargs)[source]
Bases:
Model
protocol.Protocol
+ type pairs for identifying sent DMs.Used in
User.sent_dms
.https://googleapis.dev/python/python-ndb/latest/model.html#google.cloud.ndb.model.StructuredProperty
- type
Known values (keep in sync with USER_STATUS_DESCRIPTIONS!): * no-feed-or-webmention * opt-out * owns-webfinger * private * replied_to_bridged_user * request_bridging * requires-avatar * requires-name * requires-old-account * unsupported-handle-ap * welcome
- protocol
- class ProtocolUserMeta(name, bases, class_dict)[source]
Bases:
MetaModel
User
metaclass. Registers all subclasses in thePROTOCOLS
global.
- reset_protocol_properties()[source]
Recreates various protocol properties to include choices from
PROTOCOLS
.
- class User(**kwargs)[source]
Bases:
StringIdModel
Abstract base class for a Bridgy Fed user.
Stores some protocols’ keypairs. Currently:
RSA keypair for ActivityPub HTTP Signatures properties:
mod
,public_exponent
,private_exponent
, all encoded as base64url (ie URL-safe base64) strings as described in RFC 4648 and section 5.1 of the Magic Signatures spec: https://tools.ietf.org/html/draft-cavage-http-signatures-12Not K-256 signing or rotation keys for AT Protocol, those are stored in
arroba.datastore_storage.AtpRepo
entities
- obj_key
- mod
- use_instead
- copies
Proxy copies of this user elsewhere, eg DIDs for ATProto records, bech32 npub Nostr ids, etc. Similar to
rel-me
links in microformats2,alsoKnownAs
in DID docs (and now AS2), etc.
- public_exponent
Part of this user’s bridged ActivityPub actor’s private key.
- private_exponent
Part of this user’s bridged ActivityPub actor’s private key.
- manual_opt_out
Set to True for users who asked to be opted out.
- enabled_protocols
Protocols that this user has explicitly opted into.
Protocols that don’t require explicit opt in are omitted here.
choices
is populated inreset_protocol_properties()
.
- sent_dms
DMs that we’ve attempted to send to this user.
- created
- updated
- add(prop, val)[source]
Adds a value to a multiply-valued property. Uses
self.lock
.- Parameters:
prop (str)
val
- classmethod get_by_id(id, allow_opt_out=False, **kwargs)[source]
Override to follow
use_instead
property andstatus
.Returns None if the user is opted out.
- classmethod get_or_create(id, propagate=False, allow_opt_out=False, reload=False, **kwargs)[source]
Loads and returns a
User
. Creates it if necessary.Not transactional because transactions don’t read or write memcache. :/ Fortunately we don’t really depend on atomicity for much, last writer wins is usually fine.
- Parameters:
propagate (bool) – whether to create copies of this user in push-based protocols, eg ATProto and Nostr.
allow_opt_out (bool) – whether to allow and create the user if they’re currently opted out
reload (bool) – whether to reload profile always, vs only if necessary
kwargs – passed through to
cls
constructor
- Returns:
existing or new user, or None if the user is opted out
- Return type:
- delete(proto=None)[source]
Deletes a user’s bridged actors in all protocols or a specific one.
- Parameters:
proto (Protocol) – optional
- classmethod load_multi(users)[source]
Loads
obj
for multiple users in parallel.- Parameters:
users (sequence of User)
- is_enabled(to_proto, explicit=False)[source]
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 or blocked. * The user is on a domain that’s opted out or blocked. * The from protocol requires opt in, and the user hasn’t opted in. *
explicit
is True, and this protocol supportsto_proto
by, but the user hasn’t explicitly opted into it.
- enable_protocol(to_proto)[source]
Adds
to_proto
toenabled_protocols
.Also sends a welcome DM to the user (via a send task) if their protocol supports DMs.
- Parameters:
to_proto (
protocol.Protocol
subclass)
- disable_protocol(to_proto)[source]
Removes
to_proto` from :attr:`enabled_protocols
.- Parameters:
to_proto (
protocol.Protocol
subclass)
- web_url()[source]
Returns this user’s web URL (homepage), eg
https://foo.com/
.To be implemented by subclasses.
- Returns:
str
- is_web_url(url, ignore_www=False)[source]
Returns True if the given URL is this user’s web URL (homepage).
- id_uri()[source]
Returns the user id as a URI.
Sometimes this is the user id itself, eg ActivityPub actor ids. Sometimes it’s a bit different, eg at://did:plc:… for ATProto user, https://site.com for Web users.
- Returns:
str
- profile_id()[source]
Returns the id of this user’s profile object in its native protocol.
Examples:
Web: home page URL, eg
https://me.com/
ActivityPub: actor URL, eg
https://instance.com/users/me
ATProto: profile AT URI, eg
at://did:plc:123/app.bsky.actor.profile/self
Defaults to this user’s key id.
- Return type:
str or None
- reload_profile(**kwargs)[source]
Reloads this user’s identity and profile from their native protocol.
Populates the reloaded profile
Object
inself.obj
.- Parameters:
kwargs – passed through to
Protocol.load()
- get_copy(proto)[source]
Returns the id for the copy of this user in a given protocol.
…or None if no such copy exists. If
proto
is this user, returns this user’s key id.- Parameters:
proto –
Protocol
subclass- Return type:
- user_link(name=True, handle=True, pictures=False, proto=None, proto_fallback=False)[source]
Returns a pretty HTML link to the user’s profile.
Can optionally include display name, handle, profile picture, and/or link to a different protocol that they’ve enabled.
TODO: unify with
Object.actor_link()
?- Parameters:
name (bool) – include display name
handle (bool) – include handle
pictures (bool) – include profile picture and protocol logo
proto (Protocol) – link to this protocol instead of the user’s native protocol
proto_fallback (bool) – if True, and
proto
is provided and has no no canonical profile URL for bridged users, uses the user’s profile URL in their native protocol
- class Object(*args, **kwargs)[source]
Bases:
StringIdModel
An activity or other object, eg actor.
Key name is the id. We synthesize ids if necessary.
- users
User(s) who created or otherwise own this object.
- notify
User who should see this in their user page, eg in reply to, reaction to, share of, etc.
- feed
User who should see this in their feeds, eg followers of its creator
- source_protocol
The protocol this object originally came from.
choices
is populated inreset_protocol_properties()
, after allUser
subclasses are created, so thatPROTOCOLS
is fully populated.TODO: nail down whether this is
ABBREV`
orLABEL
- as2
ActivityStreams 2, for ActivityPub
- bsky
AT Protocol lexicon, for Bluesky
- mf2
HTML microformats2 item, (ie _not_ top level parse object with
items
field
- our_as1
ActivityStreams 1, for activities that we generate or modify ourselves
- raw
Other standalone data format, eg DID document
- deleted
- copies
// URIs for ATProto records and nevent etc bech32-encoded Nostr ids, where this object is the original. Similar to u-syndication links in microformats2 and upstream/downstreamDuplicates in AS1.
- Type:
Copies of this object elsewhere, eg at
- created
- updated
- new = None
True if this object is new, ie this is the first time we’ve seen it, False otherwise, None if we don’t know.
- changed = None
True if this object’s contents have changed from our existing copy in the datastore, False otherwise, None if we don’t know.
Object
is new/changed. Seeactivity_changed()
for more details.
- classmethod get_by_id(id, authed_as=None, **kwargs)[source]
Fetches the
Object
with the given id, if it exists.
- classmethod get_or_create(id, authed_as=None, **props)[source]
Returns an
Object
with the given property values.If a matching
Object
doesn’t exist in the datastore, creates it first. Only populates non-False/empty property values in props into the object. Also populates thenew
andchanged
properties.Not transactional because transactions don’t read or write memcache. :/ Fortunately we don’t really depend on atomicity for much, last writer wins is usually fine.
- add(prop, val)[source]
Adds a value to a multiply-valued property. Uses
self.lock
.- Parameters:
prop (str)
val
- Returns:
True if val was added, ie it wasn’t already in prop, False otherwise
- remove(prop, val)[source]
Removes a value from a multiply-valued property. Uses
self.lock
.- Parameters:
prop (str)
val
- static from_request()[source]
Creates and returns an
Object
from form-encoded JSON parameters.- Parameters:
obj_id (str) – id of
models.Object
to handle* – If
obj_id
is unset, all other parameters are properties for a newmodels.Object
to handle
- activity_changed(other_as1)[source]
Returns True if this activity is meaningfully changed from
other_as1
.…otherwise False.
Used to populate
changed
.- Parameters:
other_as1 (dict) – AS1 object, or none
- actor_link(image=True, sized=False, user=None)[source]
Returns a pretty HTML link with the actor’s name and picture.
TODO: unify with
User.user_link()
?
- get_copy(proto)[source]
Returns the id for the copy of this object in a given protocol.
…or None if no such copy exists. If
proto
issource_protocol
, returns this object’s key id.- Parameters:
proto –
Protocol
subclass- Return type:
- resolve_ids()[source]
Resolves “copy” ids, subdomain ids, etc with their originals.
The end result is that all ids are original “source” ids, ie in the protocol that they first came from.
Specifically, resolves:
ids in
User.copies
andObject.copies
, eg ATProto records and Nostr events that we bridged, to the ids of their original objects in their source protocol, egat://did:plc:abc/app.bsky.feed.post/123
=>https://mas.to/@user/456
.Bridgy Fed subdomain URLs to the ids embedded inside them, eg
https://bsky.brid.gy/ap/did:plc:xyz
=>did:plc:xyz
ATProto bsky.app URLs to their DIDs or at:// URIs, eg
https://bsky.app/profile/a.com
=>did:plc:123
…in these AS1 fields, in place:
id
actor
author
object
object.actor
object.author
object.id
object.inReplyTo
tags.[objectType=mention].url
protocol.Protocol.translate_ids()
is partly the inverse of this. Much of the same logic is duplicated there!TODO: unify with
normalize_ids()
,Object.normalize_ids()
.
- normalize_ids()[source]
Normalizes ids to their protocol’s canonical representation, if any.
For example, normalizes ATProto
https://bsky.app/...
URLs to DIDs for profiles,at://
URIs for posts.Modifies this object in place.
TODO: unify with
resolve_ids()
,Protocol.translate_ids()
.
- class Follower(**kwargs)[source]
Bases:
Model
A follower of a Bridgy Fed user.
- from_
The follower.
- to
The followee, ie the user being followed.
- follow
The last follow activity.
- status
Whether this follow is active or note.
- classmethod get_or_create(*, from_, to, **kwargs)[source]
Returns a Follower with the given
from_
andto
users.Not transactional because transactions don’t read or write memcache. :/ Fortunately we don’t really depend on atomicity for much, last writer wins is usually fine.
If a matching
Follower
doesn’t exist in the datastore, creates it first.
- static fetch_page(collection, user)[source]
Fetches a page of
Follower
s for a given user.Wraps
fetch_page()
. Paging uses thebefore
andafter
query parameters, if available in the request.- Parameters:
- Returns:
results, annotated with an extra
user
attribute that holds the follower or followingUser
, and new str query param values forbefore
andafter
to fetch the previous and next pages, respectively- Return type:
- fetch_objects(query, by=None, user=None)[source]
Fetches a page of
Object
entities from a datastore query.Wraps
fetch_page()
and adds attributes to the returnedObject
entities for rendering inobjects.html
.- Parameters:
query (ndb.Query)
by (ndb.model.Property) – either
Object.updated
orObject.created
user (User) – current user
- Returns:
(results, new
before
query param, newafter
query param) to fetch the previous and next pages, respectively- Return type:
- fetch_page(query, model_class, by=None)[source]
Fetches a page of results from a datastore query.
Uses the
before
andafter
query params (if provided; should be ISO8601 timestamps) and theby
property to identify the page to fetch.Populates a
log_url_path
property on each result entity that points to a its most recent logged request.- Parameters:
query (Query)
model_class (class)
by (ndb.model.Property) – paging property, eg
Object.updated
orObject.created
- Returns:
(results, new_before, new_after), where new_before and new_after are query param values for
before
andafter
to fetch the previous and next pages, respectively- Return type:
- get_original_object_key(copy_id)[source]
Finds the
Object
with a given copy id, if any.Note that
Object.add()
also updates this function’smemcache.memoize()
cache.- Parameters:
copy_id (str)
- Returns:
google.cloud.ndb.Key or None
- get_original_user_key(copy_id)[source]
Finds the user with a given copy id, if any.
Note that
User.add()
also updates this function’smemcache.memoize()
cache.
pages
UI pages.
- load_user(protocol, id)[source]
Loads and returns the current request’s user.
- Parameters:
- Return type:
- Raises:
- serve_feed(*, objects, format, user, title, as_snippets=False, quiet=False)[source]
Generates a feed based on
Object
s.- Parameters:
- Returns:
Flask response
- Return type:
protocol
Base protocol class and common code.
- error(*args, status=299, **kwargs)[source]
Default HTTP status code to 299 to prevent retrying task.
- class Protocol[source]
Bases:
object
Base protocol class. Not to be instantiated; classmethods only.
- PHRASE = None
human-readable name or phrase. Used in phrases like
Follow this person on {PHRASE}
- Type:
- CONTENT_TYPE = None
MIME type of this protocol’s native data format, appropriate for the
Content-Type
HTTP header.- Type:
- HAS_COPIES = False
whether this protocol is push and needs us to proactively create “copy” users and objects, as opposed to pulling converted objects on demand
- Type:
- REQUIRES_AVATAR = False
whether accounts on this protocol are required to have a profile picture. If they don’t, their
User.status
will beblocked
.- Type:
- REQUIRES_NAME = False
whether accounts on this protocol are required to have a profile name that’s different than their handle or id. If they don’t, their
User.status
will beblocked
.- Type:
- REQUIRES_OLD_ACCOUNT = False
whether accounts on this protocol are required to be at least
common.OLD_ACCOUNT_AGE
old. If their profile includes creation date and it’s not old enough, theirUser.status
will beblocked
.- Type:
- DEFAULT_ENABLED_PROTOCOLS = ()
labels of other protocols that are automatically enabled for this protocol to bridge into
- Type:
sequence of str
- DEFAULT_SERVE_USER_PAGES = False
whether to serve user pages for all of this protocol’s users on the fed.brid.gy. If
False
, user pages will only be served for users who have explictly opted in.- Type:
- SUPPORTED_AS1_TYPES = ()
AS1 objectTypes and verbs that this protocol supports receiving and sending
- Type:
sequence of str
- static for_request(fed=None)[source]
Returns the protocol for the current request.
…based on the request’s hostname.
- static for_bridgy_subdomain(domain_or_url, fed=None)[source]
Returns the protocol for a brid.gy subdomain.
- classmethod owns_id(id)[source]
Returns whether this protocol owns the id, or None if it’s unclear.
To be implemented by subclasses.
IDs are string identities that uniquely identify users, and are intended primarily to be machine readable and usable. Compare to handles, which are human-chosen, human-meaningful, and often but not always unique.
Some protocols’ ids are more or less deterministic based on the id format, eg AT Protocol owns
at://
URIs. Others, like http(s) URLs, could be owned by eg Web or ActivityPub.This should be a quick guess without expensive side effects, eg no external HTTP fetches to fetch the id itself or otherwise perform discovery.
Returns False if the id’s domain is in
common.DOMAIN_BLOCKLIST
.
- classmethod owns_handle(handle, allow_internal=False)[source]
Returns whether this protocol owns the handle, or None if it’s unclear.
To be implemented by subclasses.
Handles are string identities that are human-chosen, human-meaningful, and often but not always unique. Compare to IDs, which uniquely identify users, and are intended primarily to be machine readable and usable.
Some protocols’ handles are more or less deterministic based on the id format, eg ActivityPub (technically WebFinger) handles are
@user@instance.com
. Others, like domains, could be owned by eg Web, ActivityPub, AT Protocol, or others.This should be a quick guess without expensive side effects, eg no external HTTP fetches to fetch the id itself or otherwise perform discovery.
- classmethod handle_to_id(handle)[source]
Converts a handle to an id.
To be implemented by subclasses.
May incur network requests, eg DNS queries or HTTP requests. Avoids blocked or opted out users.
- classmethod key_for(id, allow_opt_out=False)[source]
Returns the
google.cloud.ndb.Key
for a given id’smodels.User
.To be implemented by subclasses. Canonicalizes the id if necessary.
If called via Protocol.key_for, infers the appropriate protocol with
for_id()
. If called with a concrete subclass, uses that subclass as is.
- for_handle()[source]
Returns the protocol for a given handle.
May incur expensive side effects like resolving the handle itself over the network or other discovery.
- classmethod bridged_web_url_for(user, fallback=False)[source]
Returns the web URL for a user’s bridged profile in this protocol.
For example, for Web user
alice.com
,ATProto.bridged_web_url_for()
returnshttps://bsky.app/profile/alice.com.web.brid.gy
- classmethod actor_key(obj, allow_opt_out=False)[source]
Returns the
User
: key for a given object’s author or actor.
- classmethod bot_user_id()[source]
Returns the Web user id for the bot user for this protocol.
For example,
'bsky.brid.gy'
for ATProto.- Return type:
- classmethod create_for(user)[source]
Creates or re-activate a copy user in this protocol.
Should add the copy user to
copies
.If the copy user already exists and active, should do nothing.
- Parameters:
user (User) – original source user. Shouldn’t already have a copy user for this protocol in
copies
.- Raises:
ValueError – if we can’t create a copy of the given user in this protocol
- classmethod send(obj, url, from_user=None, orig_obj_id=None)[source]
Sends an outgoing activity.
To be implemented by subclasses.
NOTE: if this protocol’s
HAS_COPIES
is True, and this method creates a copy and sends it, it must add that copy to the object’s (not activity’s)copies
!- Parameters:
obj (Object) – with activity to send
url (str) – destination URL to send to
from_user (User) – user (actor) this activity is from
orig_obj_id (str) –
models.Object
key id of the “original object” that this object refers to, eg replies to or reposts or likes
- Returns:
True if the activity is sent successfully, False if it is ignored or otherwise unsent due to protocol logic, eg no webmention endpoint, protocol doesn’t support the activity type. (Failures are raised as exceptions.)
- Return type:
- Raises:
werkzeug.HTTPException if the request fails –
- classmethod fetch(obj, **kwargs)[source]
Fetches a protocol-specific object and populates it in an
Object
.Errors are raised as exceptions. If this method returns False, the fetch didn’t fail but didn’t succeed either, eg the id isn’t valid for this protocol, or the fetch didn’t return valid data for this protocol.
To be implemented by subclasses.
- Parameters:
obj (Object) – with the id to fetch. Data is filled into one of the protocol-specific properties, eg
as2
,mf2
,bsky
.kwargs – subclass-specific
- Returns:
True if the object was fetched and populated successfully, False otherwise
- Return type:
- Raises:
RequestException or werkzeug.HTTPException – if the fetch fails
- classmethod convert(obj, from_user=None, **kwargs)[source]
Converts an
Object
to this protocol’s data format.For example, an HTML string for
Web
, or a dict with AS2 JSON andapplication/activity+json
forActivityPub
.Just passes through to
_convert()
, then does minor protocol-independent postprocessing.- Parameters:
obj (Object)
from_user (User) – user (actor) this activity/object is from
kwargs – protocol-specific, passed through to
_convert()
- Returns:
converted object in the protocol’s native format, often a dict
- classmethod _convert(obj, from_user=None, **kwargs)[source]
Converts an
Object
to this protocol’s data format.To be implemented by subclasses. Implementations should generally call
Protocol.translate_ids()
(as their own class) before converting to their format.
- classmethod add_source_links(actor, obj, from_user)[source]
Adds “bridged from … by Bridgy Fed” HTML to
actor['summary']
.Default implementation; subclasses may override.
- classmethod set_username(user, username)[source]
Sets a custom username for a user’s bridged account in this protocol.
- Parameters:
- Raises:
ValueError – if the username is invalid
RuntimeError – if the username could not be set
- classmethod migrate_out(user, to_user_id)[source]
Migrates a bridged account out to be a native account.
- Parameters:
- Raises:
ValueError – eg if this protocol doesn’t own
to_user_id
, or ifuser
is on this protocol or not bridged to this protocol
- classmethod migrate_in(user, from_user_id, **kwargs)[source]
Migrates a native account in to be a bridged account.
- Parameters:
- Raises:
ValueError – eg if this protocol doesn’t own
from_user_id
, or ifuser
is on this protocol or already bridged to this protocol
- classmethod target_for(obj, shared=False)[source]
Returns an
Object
’s delivery target (endpoint).To be implemented by subclasses.
Examples:
If obj has
source_protocol
web
, returns its URL, as a webmention target.If obj is an
activitypub
actor, returns its inbox.If obj is an
activitypub
object, returns it’s author’s or actor’s inbox.
- classmethod is_blocklisted(url, allow_internal=False)[source]
Returns True if we block the given URL and shouldn’t deliver to it.
Default implementation here, subclasses may override.
- classmethod translate_ids(obj)[source]
Translates all ids in an AS1 object to a specific protocol.
Infers source protocol for each id value separately.
For example, if
proto
isActivityPub
, the ATProto URIat://did:plc:abc/coll/123
will be converted tohttps://bsky.brid.gy/ap/at://did:plc:abc/coll/123
.Wraps these AS1 fields:
id
actor
author
bcc
bto
cc
object
object.actor
object.author
object.id
object.inReplyTo
object.object
attachments[].id
tags[objectType=mention].url
to
This is the inverse of
models.Object.resolve_ids()
. Much of the same logic is duplicated there!TODO: unify with
Object.resolve_ids()
,models.Object.normalize_ids()
.- Parameters:
to_proto (Protocol subclass)
obj (dict) – AS1 object or activity (not
models.Object
!)
- Returns:
wrapped AS1 version of
obj
- Return type:
- classmethod receive(obj, authed_as=None, internal=False, received_at=None)[source]
Handles an incoming activity.
If
obj
’s key is unset,obj.as1
’s id field is used. If both are unset, returns HTTP 299.- Parameters:
- Returns:
(response body, HTTP status code) Flask response
- Return type:
- Raises:
werkzeug.HTTPException – if the request is invalid
- classmethod handle_follow(obj)[source]
Handles an incoming follow activity.
Sends an
Accept
back, but doesn’t send theFollow
itself. That happens indeliver()
.- Parameters:
obj (Object) – follow activity
- classmethod respond_to_follow(verb, follower, followee, follow)[source]
Sends an accept or reject activity for a follow.
…if the follower’s protocol supports accepts/rejects. Otherwise, does nothing.
- classmethod bot_follow(user)[source]
Follow a user from a protocol bot user.
…so that the protocol starts sending us their activities, if it needs a follow for that (eg ActivityPub).
- Parameters:
user (User)
- classmethod handle_bare_object(obj, authed_as=None)[source]
If obj is a bare object, wraps it in a create or update activity.
Checks if we’ve seen it before.
- classmethod deliver(obj, from_user, crud_obj=None, to_proto=None)[source]
Delivers an activity to its external recipients.
- Parameters:
obj (Object) – activity to deliver
from_user (User) – user (actor) this activity is from
crud_obj (Object) – if this is a create, update, or delete/undo activity, the inner object that’s being written, otherwise None. (This object’s
notify
andfeed
properties may be updated.)to_proto (Protocol) – optional; if provided, only deliver to targets on this protocol
- Returns:
Flask response
- Return type:
- classmethod targets(obj, from_user, crud_obj=None, internal=False)[source]
Collects the targets to send a
models.Object
to.Targets are both objects - original posts, events, etc - and actors.
- Parameters:
- Returns:
maps
models.Target
to original (in response to)models.Object
, if any, otherwise None- Return type:
- classmethod load(id, remote=None, local=True, raise_=True, **kwargs)[source]
Loads and returns an Object from datastore or HTTP fetch.
Sets the
new
andchanged
attributes if we know either one for the loaded object, ie local is True and remote is True or None.- Parameters:
id (str)
remote (bool) – whether to fetch the object over the network. If True, fetches even if we already have the object stored, and updates our stored copy. If False and we don’t have the object stored, returns None. Default (None) means to fetch over the network only if we don’t already have it stored.
local (bool) – whether to load from the datastore before fetching over the network. If False, still stores back to the datastore after a successful remote fetch.
raise (bool) – if False, catches any
request.RequestException
orHTTPException
raised byfetch()
and returnsNone
insteadkwargs – passed through to
fetch()
- Returns:
loaded object, or None if it isn’t fetchable, eg a non-URL string for Web, or
remote
is False and it isn’t in the datastore- Return type:
- Raises:
- classmethod check_supported(obj)[source]
If this protocol doesn’t support this object, raises HTTP 204.
Also reports an error.
(This logic is duplicated in some protocols, eg ActivityPub, so that they can short circuit out early. It generally uses their native formats instead of AS1, before an
models.Object
is created.)- Parameters:
obj (Object)
- Raises:
werkzeug.HTTPException – if this protocol doesn’t support this object
- receive_task()[source]
Task handler for a newly received
models.Object
.Calls
Protocol.receive()
with the form parameters.- Parameters:
authed_as (str) – passed to
Protocol.receive()
obj_id (str) – key id of
models.Object
to handlereceived_at (str, ISO 8601 timestamp) – when we first saw (received) this activity
* – If
obj_id
is unset, all other parameters are properties for a newmodels.Object
to handle
TODO: migrate incoming webmentions to this. See how we did it for AP. The difficulty is that parts of
protocol.Protocol.receive()
depend on setup inweb.webmention()
, egmodels.Object
withnew
andchanged
, HTTP request details, etc. See stash for attempt at this forweb.Web
.
- send_task()[source]
Task handler for sending an activity to a single specific destination.
Calls
Protocol.send()
with the form parameters.- Parameters:
url (str) – destination URL to send to
obj_id (str) – key id of
models.Object
to sendorig_obj_id (str) – optional,
models.Object
key id of the “original object” that this object refers to, eg replies to or reposts or likesuser (url-safe google.cloud.ndb.key.Key) –
models.User
(actor) this activity is from* – If
obj_id
is unset, all other parameters are properties for a newmodels.Object
to handle
redirect
Simple conneg endpoint that serves AS2 or redirects to to the original post.
Only for web.Web
users. Other protocols (including web.Web
sometimes) use /convert/
in convert.py instead.
Serves /r/https://foo.com/bar
URL paths, where https://foo.com/bar
is a
original post for a Web
user. Needed for Mastodon interop, they require
that AS2 object ids and urls are on the same domain that serves them.
Background:
https://github.com/snarfed/bridgy-fed/issues/16#issuecomment-424799599
https://github.com/tootsuite/mastodon/pull/6219#issuecomment-429142747
The conneg makes these /r/
URLs searchable in Mastodon:
https://github.com/snarfed/bridgy-fed/issues/352
web
Webmention protocol with microformats2 in HTML, aka the IndieWeb stack.
- is_valid_domain(domain, allow_internal=True)[source]
Returns True if this is a valid domain we can use, False otherwise.
- Parameters:
Valid means TLD is ok, not blacklisted, etc.
- class Web(**kwargs)[source]
-
Web user and webmention protocol implementation.
The key name is the domain.
- ABBREV = 'web'
- PHRASE = 'the web'
- OTHER_LABELS = ('webmention',)
- LOGO_HTML = '🌐'
- CONTENT_TYPE = 'text/html; charset=utf-8'
- DEFAULT_ENABLED_PROTOCOLS = ('activitypub',)
- DEFAULT_SERVE_USER_PAGES = True
- SUPPORTED_AS1_TYPES = ('organization', 'group', 'person', 'application', 'service', 'note', 'article', 'mention', 'comment', 'link', 'post', 'delete', 'undo', 'update', 'audio', 'bookmark', 'event', 'image', 'video', 'follow', 'like', 'share', 'stop-following')
- has_redirects
- redirects_error
- has_hcard
remove?
- Type:
Currently unused, and I think now always ends up as
True
. TODO
- last_webmention_in
- last_polled_feed
- feed_etag
- feed_last_modified
- atproto_last_chat_log_cursor
Only used by protocol bot users in Bluesky, for polling their chat messages with
chat.bsky.convo.getLog
.
- ap_subdomain
Originally, BF served Web users’ AP actor ids on fed.brid.gy, eg https://fed.brid.gy/snarfed.org . When we started adding new protocols, we switched to per-protocol subdomains, eg https://web.brid.gy/snarfed.org . However, we need to preserve the old users’ actor ids as is.
Also, our per-protocol bot accounts in ActivityPub are on their own subdomains, eg @bsky.brid.gy@bsky.brid.gy.
So, this property tracks which subdomain a given Web user’s AP actor uses.
- classmethod get_or_create(id, allow_opt_out=False, verify=None, **kwargs)[source]
Normalize domain, then pass through to
User.get_or_create()
.Normalizing currently consists of lower casing and removing leading and trailing dots.
- web_url()
Returns the id of this user’s profile object in its native protocol.
Examples:
Web: home page URL, eg
https://me.com/
ActivityPub: actor URL, eg
https://instance.com/users/me
ATProto: profile AT URI, eg
at://did:plc:123/app.bsky.actor.profile/self
Defaults to this user’s key id.
- Return type:
str or None
- username()[source]
Returns the user’s preferred username.
Uses stored representative h-card if available, falls back to id.
- Return type:
- verify()[source]
Fetches site a couple ways to check for redirects and h-card.
- Returns:
user that was verified. May be different than self! eg if self’s domain started with www and we switch to the root domain.
- Return type:
- classmethod key_for(id, allow_opt_out=False)[source]
Returns the
ndb.Key
for a given id.If id is a domain, uses it as is. If it’s a home page URL or fed.brid.gy or web.brid.gy AP actor URL, extracts the domain and uses that. Otherwise, returns None.
Returns: ndb.Key or None:
- classmethod owns_id(id)[source]
Returns True on domains and internal URLs, None on other URLs.
All web pages are http(s) URLs, but not all http(s) URLs are web pages.
- webmention_endpoint()[source]
Returns this web site’s webmention endpoint, if any.
- Returns:
webmention endpoint URL
- Return type:
- classmethod send(obj, url, from_user=None, orig_obj_id=None, **kwargs)[source]
Sends a webmention to a given target URL.
See
Protocol.send()
for details.Returns False if the target URL doesn’t advertise a webmention endpoint, or if webmention/microformats2 don’t support the activity type. https://fed.brid.gy/docs#error-handling
- classmethod fetch(obj, gateway=False, check_backlink=False, authorship_fetch_mf2=True, metaformats=None, **kwargs)[source]
Fetches a URL over HTTP and extracts its microformats2.
Follows redirects, but doesn’t change the original URL in
obj
’s id!google.cloud.ndb.model.Model
doesn’t allow that anyway, but more importantly, we want to preserve that original URL becase other objects may refer to it instead of the final redirect destination URL.See
Protocol.fetch()
for other background.- Parameters:
gateway (bool) – passed through to
oauth_dropins.webutil.util.fetch_mf2()
check_backlink (bool) – optional, whether to require a link to Bridgy Fed. Ignored if the URL is a homepage, ie has no path.
authorship_fetch_mf2 (bool) – optional, when running the authorship algorithm, fetch author URL if necessary
kwargs – ignored
- webmention_external()[source]
Handles inbound webmention, enqueue task to process.
Use a task queue to deliver to followers because we send to each inbox in serial, which can take a long time with many followers/instances.
- poll_feed(user, feed_url, rel_type)[source]
Fetches a
Web
site’s feed and delivers new/updated posts.
- poll_feed_task()[source]
Task handler for polling a
Web
user’s feed.- Params:
domain
(str): key id of theWeb
userlast_polled
(str): should match the user’slast_polled_feed
. Used to detect duplicate poll tasks for the same user.
- webmention_endpoint_cache_key(url)[source]
Returns cache key for a cached webmention endpoint for a given URL.
Just the domain by default. If the URL is the home page, ie path is
/
, the key includes a/
at the end, so that we cache webmention endpoints for home pages separate from other pages. https://github.com/snarfed/bridgy/issues/701Example:
snarfed.org /
https://github.com/snarfed/bridgy-fed/issues/423
Adapted from
bridgy/util.py
.
webfinger
Handles requests for WebFinger endpoints.
- class Webfinger[source]
Bases:
XrdOrJrd
Serves a user’s WebFinger profile.
Supports both JRD and XRD; defaults to JRD. https://tools.ietf.org/html/rfc7033#section-4
- class HostMeta[source]
Bases:
XrdOrJrd
Renders and serves the
/.well-known/host-meta
file.Supports both JRD and XRD; defaults to XRD. https://tools.ietf.org/html/rfc6415#section-3
- fetch(addr)[source]
Fetches and returns an address’s WebFinger data.
On failure, flashes a message and returns None.
TODO: switch to raising exceptions instead of flashing messages and returning None