Bridgy Fed
Reference documentation.
activitypub
ActivityPub protocol implementation.
- exception NeedsAlias[source]
Bases:
ValueErrorRaised when a user can’t migrate out because the destination account is missing an
alsoKnownAsalias back to their bridged actor.
- 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_EMOJI = '⁂'
- LOGO_HTML = '<img src="/static/fediverse_logo.svg">'
- CONTENT_TYPE = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
- REQUIRES_NAME = False
- DEFAULT_ENABLED_PROTOCOLS = ('web',)
- SUPPORTED_AS1_TYPES = ('service', 'organization', 'application', 'person', 'group', 'note', 'mention', 'comment', 'article', 'link', 'undo', 'update', 'delete', 'post', 'block', 'react', 'invite', 'accept', 'rsvp-interested', 'rsvp-no', 'rsvp-maybe', 'flag', 'follow', 'reject', 'stop-following', 'undo', 'rsvp-yes', 'like', 'share', 'add', 'audio', 'bookmark', 'image', 'move', 'remove', 'video')
- SUPPORTED_AS2_TYPES = ('Service', 'Organization', 'Application', 'Person', 'Group', 'Note', 'Mention', 'Note', 'Article', 'Link', 'Undo', 'Update', 'Delete', 'Create', 'Block', None, 'Invite', 'Accept', None, 'Reject', 'TentativeAccept', 'Flag', 'Follow', 'Reject', None, 'Undo', 'Accept', 'Like', 'Announce', 'Add', 'Audio', None, 'Image', 'Move', 'Remove', 'Video')
- SUPPORTS_DMS = True
- SEND_REPLIES_TO_ORIG_POSTS_MENTIONS = True
//github.com/snarfed/bridgy-fed/issues/1608 , https://github.com/snarfed/bridgy-fed/issues/1218
- Type:
https
- HTML_PROFILES = True
- BOTS_FOLLOW_BACK = 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
if a profile URL is a top-level URL (ie home page), check that it serves this AP user, and if so, set verified_domain
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
idis 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 bridged_web_url_for(user, fallback=False)[source]
Returns the user’s bridged AP id.
There’s no single canonical web URL for a user bridged into ActivityPub. So, we want some URL that’s reasonable, and when it’s used in a link in a fediverse post, eg in an @-mention, we ideally want it to open that local instance’s view of the remote bridged user. In general, that means it needs to serve the AP actor when requested with AP conneg.
Our translated AP actor ids, served by
/actorbelow, satisfy this. They serve the actor via conneg, and otherwise redirect to the user’s profile in their native protocol.
- classmethod send(obj, inbox_url, from_user=None, orig_obj_id=None)[source]
Delivers an activity to an inbox URL.
If
obj.recipient_objis set, it’s interpreted as the receiving actor who we’re delivering to and its id is populated intocc.
- classmethod fetch(obj, use_fetched_id=True, **_)[source]
Tries to fetch an AS2 object.
Assumes
obj.key.idis 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-Typeheader. If the url is HTML and it has arel-alternatelink with an AS2 content type, fetches and returns that URL.If the fetched AS2 object’s
idis different fromobj.key.id, this method defaults to overwritingobj.key.idwith the fetched id! You can disable that withuse_fetched_id=False.Includes an HTTP Signature with the request.
Mastodon requires this signature if
AUTHORIZED_FETCHaka 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:
- Returns:
True if the object was fetched and populated successfully, False otherwise
- Return type:
- Raises:
HTTPException – will have an additional
requests_responseattribute with the lastrequests.Responsewe received.
- classmethod _convert(obj, orig_obj=None, from_user=None, **kwargs)[source]
Convert a
models.Objectto AS2.- Parameters:
obj (Object)
orig_obj (dict) – AS2 object, optional. The target of activity’s
inReplyToorLike/Announce/etc object, if any. Passed through topostprocess_as2().from_user (User) – user (actor) this activity/object is from
kwargs – unused
- 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
ActivityPubdoesn’t ownto_user_id
- classmethod check_can_migrate_out(user, to_user_id)[source]
Raises an exception if a user can’t yet migrate to a native AP account.
For example, if
to_user_idisn’t an ActivityPub actor id, or if it doesn’t haveuser’s bridged AP id in itsalsoKnownAs.- Parameters:
- Raises:
ValueError – if
usercan’t migrate to ActivityPub orto_user_idyet
- classmethod authed_user_for_request(log_level=10)[source]
Returns the AP actor id of the user who signed the current request.
Verifies the current request’s HTTP Signature. Logs details of the result.
https://swicg.github.io/activitypub-http-signature/
- Parameters:
log_level (int)
- Returns:
signing AP actor id, or None if the request isn’t signed
- Return type:
str or None
- Raises:
RuntimeError – if the signature is invalid or can’t be verified
- 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.
- follower_collection(id, collection)[source]
ActivityPub Followers and Following collections.
TODO: unify page generation with outbox()
- outbox(id)[source]
Serves a user’s AP outbox.
TODO: unify page generation with follower_collection()
- featured(id)[source]
Serves a user’s AP featured collection for pinned posts.
https://docs.joinmastodon.org/spec/activitypub/#featured
We inline the featured collection in users’ actors, but Mastodon (and Pleroma/Akkoma?) require it to be fetchable separately too. :(
Also, it’s critical that the collection items here are expanded objects! Originally they were compacted string ids, but that triggered a massive flood of requests from Pleroma and Akkoma: https://github.com/snarfed/bridgy-fed/issues/1374#issuecomment-2891993190
- as2_request_type()[source]
If this request has conneg (ie the
Acceptheader) 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
atproto
ATProto protocol implementation.
- init(sequences_cls)[source]
Connect arroba’s storage and sequence numbers.
Bridgy Fed uses memcache sequence number allocation in production. If we ever allocated a sequence number from the datastore instead of memcache, we’d allocate a duplicate from memcache and collide. So, we make all services that use this module explicitly initialize it with the sequence number class they need.
https://github.com/snarfed/bridgy-fed/issues/2269
- Parameters:
sequences_cls –
arroba.storage.Sequencessubclass
- chat_client(*, repo, method, **kwargs)[source]
Returns a new Bluesky chat
Clientfor a given XRPC method.- Parameters:
repo (Repo) – ATProto user
method (str) – XRPC method NSID, eg
chat.bsky.convo.sendMessagekwargs – passed through to the
lexrpc.client.Clientconstructor
- Return type:
- class RemoteSequences(base_url)[source]
Bases:
SequencesSequence number implementation that uses remote HTTP endpoints.
Makes requests to
/admin/sequences/allocand/admin/sequences/lastto allocate and retrieve sequence numbers.Used for local shells and scripts, outside GCP.
- class DatastoreClient(remote=True, *args, **kwargs)[source]
Bases:
ClientBluesky client that uses the datastore as well as remote XRPC calls.
Overrides
getRecordandresolveHandle. 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, becausegetRecordpasses through toATProto.loadand then toATProto.fetch, which uses theappviewglobal.- remote = True
- class Cursor(**kwargs)[source]
Bases:
StringIdModelThe 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.cursoris 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_EMOJI = '🦋'
- LOGO_HTML = '<img src="/oauth_dropins_static/bluesky.svg">'
- DEFAULT_TARGET = 'https://atproto.brid.gy'
Note that PDS hostname is atproto.brid.gy here, not bsky.brid.gy. Also note that PDS URLs 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', 'app.bsky.graph.listblock', 'site.standard.document', 'site.standard.publication'})
Which incoming record lexicons we should accept from the firehose.
- SUPPORTED_RECORD_TYPES_BETA_USERS = frozenset({})
Which incoming record lexicons to accept from the firehose only for beta users.
- STORE_RECORD_TYPES = frozenset({'community.lexicon.payments.webMonetization'})
- SUPPORTS_DMS = True
- HTML_PROFILES = False
- 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.gyfor 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.
- 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, pds_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
subscribeReposand then deliver it to AppView(s), which will notify recipients as necessary.Exceptions: *
flag``s are translated to ``createReportto the mod service * DMs are translated tosendMessageto the chat service
- classmethod _convert(obj, fetch_blobs=False, from_user=None, multiple=False, strict_json=False, **kwargs)[source]
Converts a
models.Objecttoapp.bsky.*lexicon JSON record(s).- Parameters:
obj (Object)
fetch_blobs (bool) – whether to fetch images and other blobs, store them in
arroba.datastore_storage.AtpRemoteBlobs if they don’t already exist, and fill them into the returned object.from_user (User) – user (actor) this activity/object is from
multiple – whether to return multiple records. Default False.
strict_json – if True, blob CIDs will be encoded as base32
kwargs – passed through to
granary.bluesky.from_as1()
- Returns:
one or more JSON objects, depending on
multiple- Return type:
- classmethod create_account_for_migrate_out(user, pds, email, password, handle=None, invite_code=None, phone_verification_code=None)[source]
Creates an account on a new PDS that we can migrate out to.
https://atproto.com/guides/account-migration https://github.com/snarfed/bounce/issues/64
- Parameters:
user (User) – must be already bridged to ATProto
pds (str) – new PDS URL, eg
https://pds.comemail (str)
password (str)
handle (str) – optional. defaults to generating one based on the user’s native handle and the new PDS’s first available domain
invite_code (str) – optional
phone_verification_code (str) – optional
- Returns:
- response from
createAccount, includingaccessJwt, refreshJwt,handle,did
- response from
- Return type:
- Raises:
HTTPError – if
createAccountreturned an errorAssertionError – if
userisn’t bridged to ATProto
- classmethod migrate_out(user, to_user_id, to_pds, access_token, refresh_token, handle=None)[source]
Migrates a bridged ATProto account out to a new PDS.
The new PDS must already have an account created for this DID. Use
create_account_for_migrate_out()for that.Deactivates our own repo and disables
userbridging to ATProto.https://atproto.com/guides/account-migration https://github.com/snarfed/bounce/issues/64
- classmethod migrate_out_blobs(user, auth)[source]
Copies a user’s blobs to an external PDS.
- Parameters:
user (User) – the user being migrated
auth (BlueskyAuth) – auth for the new PDS
- generate_rkey(type)[source]
Generates a new rkey based on a collection lexicon’s key type.
https://atproto.com/specs/lexicon#record https://atproto.com/specs/record-key
- postprocess_writes(writes, user)[source]
Applies custom logic to writes before we commit them.
For
site.standard.document``s: * Populate the ``bskyPostRefproperty, if we also have anapp.bsky.feed.post.Create a
site.standard.publicationif we don’t already have one.
- 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
- site_standard_publication()[source]
Serves site.standard.publication records for bridged users.
https://standard.site/#verification
- Query params:
protocol (str) domain (str): web site domain
- bluesky_oauth_client_metadata()[source]
https://docs.bsky.app/docs/advanced-guides/oauth-client#client-and-server-metadata
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.
- error(err, status=400, exc_info=None, **kwargs)[source]
Like
webutil.flask_util.error(), but wraps body in JSON.
- pretty_link(url, text=None, user=None, **kwargs)[source]
Wrapper around
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.socialif it’s not provided.
- content_type(resp)[source]
Returns a
requests.Response’s Content-Type, without charset suffix.
- create_task(queue, app_id='bridgy-federated', delay=None, app=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)app (Flask) – if not provided, defaults to
router.appparams – 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
DEBUGandexceptionareTrue, re-raises the exception instead.Duplicated in
bridgy.util.
- secret_key_auth(fn)[source]
Flask decorator that returns HTTP 401 if the request isn’t authorized.
Right now this only handles internal authorization: the
Authorizationheader has to be set to the Flask secret key in theflask_secret_keyfile.Ignored if
LOCAL_SERVERis True.Must be used below
flask.Flask.route(), eg:@app.route(‘/path’) @secret_key_auth def handler():
…
- make_jwt(*, user, scope, expiration=datetime.timedelta(days=7), **claims)[source]
Makes a per-user JWT signed by our EncryptedProperty symmetric key.
- verify_jwt(token, *, user_id, scope, **claims)[source]
Verifies a per-user JWT and checks that it matches a user, scope, etc.
Raises the appropriate werkzeug HTTPException if the JWT doesn’t verify or match, otherwise returns None.
- Parameters:
- Raises:
Unauthorized – if the token is invalid
Forbidden – if the token is valid but for the wrong user or scope
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
objector its owner isn’t bridged toto_proto, raiseswerkzeug.exceptions.HTTPException.
dms
Protocol-independent code for sending and receiving DMs aka chat messages.
- class CommandSpec(fn: Callable, from_user_bridged: bool | None, to_user_bridged: object, help_text: str | None = None)[source]
Bases:
object- from_user_bridged: bool | None
Whether the user sending the DM should be bridged.
True,False, orNonefor either.
- command(names, *, to_proto=None, from_user_bridged=None, to_user_bridged=None, help_text=None)[source]
Function decorator. Defines and registers a DM command.
The decorated function’s signature determines the cmd_args it accepts. After
(from_user, to_proto), required positionals are required cmd_args, defaulted positionals are optional, and*argsaccepts any number.If the function declares a
to_userparameter,cmd_args[0]is loaded viaload_user()and passed in.- Parameters:
names (sequence of str) – the command strings that trigger this command, or
Noneif this command has no command stringto_proto (str) – if set, only dispatch to this handler when the DM’s recipient protocol has this
LABEL. IfNone, this handler is the generic fallback for anyto_protowithout a specific handler.from_user_bridged (bool) – whether the user sending the DM should be bridged.
True,False, orNonefor either.to_user_bridged – whether
to_usershould already be bridged.True,False,Nonefor either, or'eligible'for not bridged but eligible.
- load_user(handle, proto, from_proto, bridged)[source]
Loads the user for
handleand applies thebridgedpolicy.- Parameters:
handle (str) – the handle or id to look up
proto (Protocol) – the protocol the handle belongs to
from_proto (Protocol) – the sender’s protocol, used for the
bridgedenabled checkbridged (bool or str) – whether the user should be bridged into
from_proto.True,False,Nonefor either, or'eligible'for not bridged but eligible.
- Return type:
Raises: ValueError
- dispatch(spec, from_user, to_proto, cmd, cmd_args, dm_as1)[source]
Dispatches a parsed DM command to its handler.
Validates
cmd_args, optionally loadsto_userviaload_user(), enforcesspec.from_user_bridged, then invokesspec.fnand sends its return value (if any) as a reply.- Parameters:
spec (CommandSpec) – the registered command’s spec
from_user (User) – the user who sent the DM
to_proto (Protocol) – the protocol bot account they sent it to
cmd (str or None) – the command name as typed, used in error messages
dm_as1 (dict) – the inbound DM as AS1;
idis used as the reply’sinReplyTo
- Returns:
- a
(body, status)tuple suitable for returning from a Flask view. Always
('OK', 200)once a reply (if any) is sent.
- a
- Return type:
follow
Remote follow handler.
- class FollowStart(to_path, scopes=None)[source]
Bases:
StartStarts the IndieAuth flow to add a follower to an existing user.
- class FollowCallback(to_path, scopes=None)[source]
Bases:
CallbackIndieAuth 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_ortoare 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.
NOTE: unlike
translate_object_id(), iftois aHAS_COPIESprotocol and has no copy object forid, this function returns None, notid!TODO: unify with
translate_object_id().
- normalize_user_id(*, id, proto)[source]
Normalizes a user id to its canonical representation in a given protocol.
TODO: what should this return if id is not a valid user id in proto? TODO: add and use new is_user_id function for this ^
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
Farcaster: * 123 => farcaster://123
Note that
profile_id()is a narrower inverse of this; it converts user ids to profile ids.
- normalize_object_id(*, id, proto)[source]
Normalizes an object id to its canonical representation in a given protocol.
If
idis a user id, and this protocol’s profile objects have different ids than their user ids, returns the profile id.Examples:
Web: * https://user.com/… (over 1500 chars) => truncated at 1500 chars * user.com => https://user.com/
ATProto: * https://bsky.app/profile/did:plc:123/post/abc =>
at://did:plc:123/app.bsky.feed.post/abc
- 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
Nostr: nostr:ab12 (pubkey) => nostr:cd34 (profile event)
Note that
normalize_user_id()does the inverse of this, ie converts profile ids to user ids.
- translate_handle(*, handle, from_, to, short=False)[source]
Translates a user handle from one protocol to another.
- Parameters:
- 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
- translate_object_id(*, id, from_, to)[source]
Translates a user handle from one protocol to another.
Allows any
idiffrom_isUIProtocolor ifidisui:....NOTE: unlike
translate_user_id(), iftois aHAS_COPIESprotocol and has no copy object forid, this function returnsid, not None!TODO: unify with
translate_user_id().
- handle_as_domain(handle)[source]
Converts a handle to domain-like format.
Converts handle to domain format by removing leading @ and replacing @ with ., and replacing certain characters (_ ~ :) with -.
For example: *
@user@instance.com=>user.instance.com*user_name@instance.com=>user-name.instance.com*@alice@inst~test.com=>alice.inst-test.com
memcache
Utilities for caching data in memcache.
TODO: move most or all of this to webutil?
- 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:
- 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.
- evict(entity_key)[source]
Evict a datastore entity from memcache.
For
models.Userandmodels.Objectentities, also clears their copies from themodels.get_original_user_key()andmodels.get_original_object_key()memoize caches.- Parameters:
entity_key (google.cloud.ndb.Key)
- remote_evict(entity_key)[source]
Send a request to production Bridgy Fed to evict an entity from memcache.
- Parameters:
entity_key (google.cloud.ndb.Key)
- Return type:
- task_eta(queue, user_id, protocol=None)[source]
Get the ETA to use for a given user’s task in a given queue.
Task rate limit delays are per user, stored in memcache with a key based on
queueanduser_idand an integer value of POSIX timestamp (UTC) in seconds.Only generates ETAs for task queues in
PER_USER_TASK_RATES. Calls for other queues always returnNone.Background: https://github.com/snarfed/bridgy-fed/issues/1788
models
Datastore model classes.
- class Target(**kwargs)[source]
Bases:
Modelprotocol.Protocol+ URI pairs for identifying objects.These are currently used for:
delivery destinations, eg ActivityPub inboxes, webmention targets, etc.
copies of
Objects andUsers elsewhere, egat://URIs for ATProto records, nevent etc bech32-encoded Nostr ids, ATProto user DIDs, etc.
Used in
google.cloud.ndb.model.StructuredPropertys insideObjectandUser; 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.
- uri
- protocol
- class DM(**kwargs)[source]
Bases:
Modelprotocol.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, the subset for ineligible users):
dms_not_supported-[RECIPIENT-USER-ID]
moved
no-feed-or-webmention
no-nip05
no-profile
opt-out
over-handle-domain-limit
owns-webfinger
private
replied_to_bridged_user
request_bridging
requires-avatar
requires-name
requires-old-account
unsupported-handle-ap
welcome
- protocol
- class KeyPair(**kwargs)[source]
Bases:
ModelA user’s public/private key pair for a single protocol.
Used in
User.keypairs; not stored as top-level entities in the datastore. The private key is encrypted at rest; the public key is not.Format per
algorithm:rsa:private_key_bytesis PKCS#1 PEM (-----BEGIN RSA PRIVATE KEY-----);public_key_bytesis SPKI PEM (-----BEGIN PUBLIC KEY-----).secp256k1: 32 raw bytes private, 32 raw bytes BIP-340 x-only public.ed25519: 32 raw bytes private, 32 raw bytes public.
Details for each protocol:
ActivityPub: RSA
ATProto: secp256k1, with ECDSA signatures (keypair is stored in
arroba.datastore_storage.AtpRepo, not here) https://atproto.com/specs/cryptographyFarcaster: Ed25519, with EdDSA signatures https://docs.farcaster.xyz/reference/farcaster/intent-urls#resource-urls
Nostr: secp256k1, with Schnorr signatures https://github.com/nostr-protocol/nips/blob/master/01.md#events-and-signatures
- protocol
- algorithm
- public_key_bytes
- private_key_bytes
- class ProtocolUserMeta(name, bases, class_dict)[source]
Bases:
MetaModelUsermetaclass. Registers all subclasses inPROTOCOLS.
- reset_protocol_properties()[source]
Recreates various protocol properties to include choices from
PROTOCOLS.
- get_original_object_key(copy_id)[source]
Finds the
Objectwith 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.- Parameters:
copy_id (str)
- Returns:
google.cloud.ndb.Key or None
- class AddRemoveMixin(*args, **kwargs)[source]
Bases:
objectMixin class that defines the
add()andremove()methods.If a subclass of this mixin defines the
GET_ORIGINAL_FNclass-level attribute, its memoize cache will be cleared whenremove()is called with thecopiesproperty.- add(prop, val)[source]
Adds a value to a multiply-valued property.
- Parameters:
prop (str)
val
- Returns:
True if val was added, ie it wasn’t already in prop, False otherwise
- class User(**kwargs)[source]
Bases:
AddRemoveMixin,StringIdModelAbstract base class for a Bridgy Fed user.
- GET_ORIGINAL_FN()
used by AddRemoveMixin
- obj_key
- use_instead
- copies
Proxy copies of this user elsewhere, eg DIDs for ATProto records, bech32 npub Nostr ids, etc. Similar to
rel-melinks in microformats2,alsoKnownAsin DID docs (and now AS2), etc.
- keypairs
Key pairs for this user, one per bridged protocol. Encrypted at rest.
- manual_opt_out
Set to True to manually disable this user. Set to False to override spam filters and forcibly enable this user.
- enabled_protocols
Protocols that this user has explicitly opted into.
Protocols that don’t require explicit opt in are omitted here.
- has_object_feed_followers_on
Protocol labels of protocols that use
USES_OBJECT_FEEDand have ever had a follower of this user.
- sent_dms
DMs that we’ve attempted to send to this user.
- send_notifs
Which notifications we should send this user.
- blocks
- verified_domain
Domain that we’ve verified this user owns, eg web site top-level NIP-05, etc.
- created
- updated
- classmethod get_by_id(id, allow_opt_out=False, **kwargs)[source]
Override to follow
use_insteadproperty andstatus.Returns None if the user is opted out.
- classmethod get_or_create(id, propagate=False, allow_opt_out=False, reload=False, raise_=False, **kwargs)[source]
Loads and returns a
User. Creates it if necessary.If
allow_opt_outis False andidis the bridged id for a user in another protocol, returns that user instead. Note that they’ll be a different type thancls!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
raise (bool) – passed through to
User.reload_profile(). If False, andUser.reload_profile()returns None when fetching the user’s profile, this method raisesRuntimeErrorkwargs – passed through to
clsconstructor
- 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
objfor multiple users in parallel.- Parameters:
users (sequence of User)
- status_description()[source]
Returns a human-readable description of this user’s status.
…or None if this user’s status is None, or a description isn’t available.
- Returns:
str
- is_enabled(to_proto, explicit=False)[source]
Returns True if this user is 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. *
explicitis True, and this protocol supportsto_protoby, but the user hasn’t explicitly opted into it.
- enable_protocol(to_proto)[source]
Adds
to_prototoenabled_protocols.Also sends a welcome DM to the user (via a send task) if their protocol supports DMs.
- Parameters:
to_proto (
protocol.Protocolsubclass)
- disable_protocol(to_proto)[source]
Removes
to_proto` from :attr:`enabled_protocols.- Parameters:
to_proto (
protocol.Protocolsubclass)
- farcaster_key()[source]
Returns the user’s Farcaster signing key.
TODO: real per-user signer keys, registered on-chain via the KeyRegistry. Messages signed with these stub keys will be rejected by the hub.
- Return type:
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey
- web_url()[source]
Returns this user’s user-facing profile page URL.
…eg
https://bsky.app/profile/snarfed.orgorhttps://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/meATProto: 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(raise_=False, **kwargs)[source]
Reloads this user’s identity and profile from their native protocol.
Populates the reloaded profile
Objectinself.obj.- Parameters:
raise (bool) – passed through to
Protocol.load(). If False, andProtocol.load()returns None when fetching the user’s profile, this method raisesRuntimeErrorkwargs – passed through to
Protocol.load()
- Raises:
RuntimeError – if the user’s profile can’t be loaded
- 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
protois this user, returns this user’s key id.- Parameters:
proto –
Protocolsubclass- Return type:
- html_link(name=True, handle=True, pictures=False, logo=None, 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) – True to include handle, False to exclude it,
'short'to include a shortened version, if availablepictures (bool) – include profile picture and protocol logo
logo (str) – optional path to platform logo to show instead of the protocol’s default
proto (Protocol) – link to this protocol instead of the user’s native protocol
proto_fallback (bool) – if True, and
protois provided and has no no canonical profile URL for bridged users, uses the user’s profile URL in their native protocol
- is_blocking(user_or_id)[source]
Returns True if this user is is blocking
user_or_id, False otherwise.Looks at domain blocklists in
blocks. Eventually we can add support for blocking individual users in that too.
- class Object(*args, **kwargs)[source]
Bases:
AddRemoveMixin,StringIdModelAn activity or other object, eg actor.
Key name is the id, generally a URI. We synthesize ids if necessary.
- GET_ORIGINAL_FN()
used by AddRemoveMixin
- 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.
TODO: nail down whether this is
ABBREV`orLABEL
- as2
ActivityStreams 2, for ActivityPub
- bsky
AT Protocol lexicon, for Bluesky
- csv
Other standalone CSV data, eg domain blocklist.
- farcaster
List of binary serialized Farcaster
Messageprotobufs.Each blob is
SerializeToString()and decodes viaMessage.FromString(blob). Repeated to support actors as multipleUSER_DATA_ADDmessages.
- mf2
HTML microformats2 item (not top level parse object with
itemsfield)
- nostr
Nostr event
- our_as1
ActivityStreams 1, for activities that we generate or modify ourselves
- raw
Other standalone data format, eg DID document
- extra_as1
Additional individual fields to merge into this object’s AS1 representation
- 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.
Objectis new/changed. Seeactivity_changed()for more details.
- html_link()[source]
Returns an HTML link to this object’s user-facing web URL, if any.
- Return type:
str or None
- classmethod get_by_id(id, authed_as=None, **kwargs)[source]
Fetches the
Objectwith the given id, if it exists.
- classmethod get_or_create(id, authed_as=None, **props)[source]
Returns an
Objectwith the given property values.If a matching
Objectdoesn’t exist in the datastore, creates it first. Only populates non-False/empty property values in props into the object. Also populates thenewandchangedproperties.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.
- static from_request()[source]
Creates and returns an
Objectfrom form-encoded JSON parameters.- Parameters:
obj_id (str) – id of
models.Objectto handle* – If
obj_idis unset, all other parameters are properties for a newmodels.Objectto 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.html_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
protoissource_protocol, returns this object’s key id.TODO: for some protocols, we should try harder to find the right copy id. Eg if if copies has some old garbage entries for this protocol, and we can tell that they don’t belong to the user’s copy account in this protocol, eg if the DID in the at:// URI doesn’t match, we should skip those and look for the matching copy. We’d need the user here though. This would help with or fix: https://console.cloud.google.com/errors/detail/COK22a6w4O2JVg;locations=global;time=P30D?project=bridgy-federated
- Parameters:
proto –
Protocolsubclass- Return type:
- get_copies(proto)[source]
Returns all ids of copies of this object in a given protocol.
If
protoissource_protocol, returns this object’s key id.
- resolve_ids()[source]
Replaces “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.copiesandObject.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:xyzATProto 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:
idactorauthorobjectobject.actorobject.authorobject.idobject.inReplyToattachments.[objectType=note].idtags.[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().
- owner_protocol()[source]
Wrapper around
source_protocolthat handlesUIProtocol.- Returns:
source_protocolunless it’s None orUIProtocol, in which case infers and returnsauthor’s oractor’s protocol instead.
- Return type:
Protocol subclass
- class Follower(**kwargs)[source]
Bases:
ModelA 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 not.
dormantmeans the followee isn’t bridged (yet), so the follow can’t be delivered. If they enable the bridge, we notify the follower.
- classmethod get_or_create(*, from_, to, **kwargs)[source]
Returns a Follower with the given
from_andtousers.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
Followerdoesn’t exist in the datastore, creates it first.If
status='dormant'is passed and the existing Follower isactive, the existing Follower is returned unchanged: we never downgrade an active Follower to dormant.
- static fetch_page(collection, user)[source]
Fetches a page of
Followers for a given user.Wraps
fetch_page(). Paging uses thebeforeandafterquery parameters, if available in the request.- Parameters:
- Returns:
results, annotated with an extra
userattribute that holds the follower or followingUser, and new str query param values forbeforeandafterto fetch the previous and next pages, respectively- Return type:
- fetch_objects(query, by=None, user=None, max_age=None)[source]
Fetches a page of
Objectentities from a datastore query.Wraps
fetch_page()and adds attributes to the returnedObjectentities for rendering inobjects.html.- Parameters:
query (ndb.Query)
by (ndb.model.Property) – either
Object.updatedorObject.createduser (User) – current user
max_age (timedelta) – passed through to
fetch_page()
- Returns:
(results, new
beforequery param, newafterquery param) to fetch the previous and next pages, respectively- Return type:
- hydrate(activity, fields=('author', 'actor', 'object'))[source]
Hydrates fields in an AS1 activity, in place.
- Parameters:
- Returns:
- tasklets for hydrating
each field. Wait on these before using
activity.
- Return type:
sequence of
google.cloud.ndb.tasklets.Future
- fetch_page(query, model_class, by=None, max_age=None)[source]
Fetches a page of results from a datastore query.
Uses the
beforeandafterquery params (if provided; should be ISO8601 timestamps) and thebyproperty to identify the page to fetch.Populates a
log_url_pathproperty 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.updatedorObject.createdmax_age (timedelta) – if provided, reject
before/afterparams older than this, and don’t generate paging links past it
- Returns:
(results, new_before, new_after), where new_before and new_after are query param values for
beforeandafterto fetch the previous and next pages, respectively- Return type:
- load_user(handle_or_id, proto=None, create=False, allow_opt_out=False, raise_=False)[source]
Loads a user by handle or id.
- Parameters:
handle_or_id (str) – user handle or id
proto (Protocol subclass or None) – protocol to use. If None, will try to determine protocol via Protocol.for_id and Protocol.for_handle
create (bool) – if True, use get_or_create; if False, use get_by_id
allow_opt_out (bool) – whether to return a user if they’re currently opted out
raise (bool) – passed through to
User.reload_profile(). If False, andUser.reload_profile()returns None when fetching the user’s profile, this method raisesRuntimeError
- Return type:
- Raises:
RuntimeError – if no matching user was found
nostr
Nostr protocol implementation.
https://github.com/nostr-protocol/nostr https://github.com/nostr-protocol/nips/blob/master/01.md https://github.com/nostr-protocol/nips#list
Nostr Object key ids are NIP-21 nostr:… URIs. https://nips.nostr.com/21
- class NostrRelay(**kwargs)[source]
Bases:
StringIdModelThe last
created_atwe’ve seen from a given relay.Key id is full relay URI, eg
wss://nos.lol. Used innostr_hub.https://nips.nostr.com/1#from-client-to-relay-sending-events-and-creating-subscriptions
- since
- created
- updated
- class Nostr(**kwargs)[source]
-
Nostr class.
Key id is hex pubkey with nostr: prefix.
- relays
NIP-65 kind 10002 event with this user’s relays.
- valid_nip05
NIP-05 identifier that we’ve resolved and verified.
- classmethod target_for(obj, shared=False)[source]
Returns the first NIP-65 relay for the given object’s author.
- classmethod check_supported(obj, direction)[source]
Update is only supported for actors and articles, not notes.
- classmethod create_for(user)[source]
Creates a Nostr profile for a non-Nostr user.
- Parameters:
user (User)
- reload_profile(**kwargs)[source]
Reloads this user’s kind 0 profile, NIP-65 relay list, and NIP-05 id.
https://nips.nostr.com/1#kinds https://nips.nostr.com/65 https://nips.nostr.com/5
- classmethod set_username(user, username)[source]
check NIP-05 DNS, then update profile event with nip05?
- classmethod _convert(obj, from_user=None, **kwargs)[source]
Converts a
models.Objectto a Nostr event.
- classmethod send(obj, relay_url, from_user=None, **kwargs)[source]
Sends an event to a relay.
Events are immutable, so all operations happen by sending a new event, including updates and deletes.
granary.nostr.from_as1()translates all of those, so all we have to do here is convert and send the event.
- nip_05()[source]
NIP-05 endpoint that serves handles for users bridged into Nostr.
- Query params:
name (str): should only contain a-z0-9-_.
- Returns a JSON object with:
names: {<name>: <pubkey hex>} relays: optional, {<pubkey hex>: [relay urls]}
- normalize_relay_uri(uri)[source]
Returns a normalized relay URI.
Right now, just adds a trailing slash if the URI has no path, and removes the port if it’s explicitly provided and redundant, ie
:443forwss://or:80forws://.https://github.com/nostr-protocol/nips/issues/1876 https://github.com/nostr-protocol/nips/issues/1198
notifications
Send DM notifications of replies, quote posts, mentions from unbridged users.
- add_notification(user, obj)[source]
Adds a notification for a given user.
The memcache key is
notifs-{user id}. The value is a space-separated list of object ids to notify the user of.Uses gets/cas to create the cache entry if it doesn’t exist.
pages
UI pages.
- load_user(proto, id)[source]
Loads and returns the current request’s user.
- Parameters:
- Return type:
- Raises:
- require_login(fn)[source]
Decorator that requires and loads the current request’s logged in user.
Passes the user in the
userkwarg, as amodels.User.- HTTP POST params:
key (str): url-safe ndb key
- Raises:
- require_token(scope, claims=None)[source]
Decorator that loads a user and checks that they’re authorized by token.
Expects a
user_idkwarg. Loads the matching user and replaces it with auserkwarg passed to the handler with the loadedUser.The per-user JWT should be in the
tokenquery param or form arg.Must be used below
flask.Flask.route(), eg:@app.route(‘/path’) @require_token(‘respond’) def handler():
…
- get_logins()[source]
Returns the user’s current logged in sessions:
- Returns:
list of
oauth_dropins.models.BaseAuth
- login_to_user_key(login)[source]
“Converts an oauth-dropins auth entity to a :model:`User` key.
- Parameters:
login (BaseAuth)
- Return type:
ndb.key.Key
- render(template, **vars)[source]
Renders a Jinja2 template and adds our standard template variables.
- Parameters:
template (str) – file name
- set_username(user=None)[source]
Enables bridging for a given account.
- Parameters:
user (User)
- Query params:
protocol (str) username (str)
- block(user=None)[source]
Blocks a user or blocklist.
TODO: unify with
unblock()?- Parameters:
user (User) – current logged in user, provided by
require_login()
- Query params:
target (str)
- unblock(user=None)[source]
Unblocks a user or blocklist.
TODO: unify with
block()?- Parameters:
user (User) – current logged in user, provided by
require_login()
- Query params:
target (str)
- toggle_notifs(user=None)[source]
Toggles DM notifications for a given account.
- Parameters:
user (User)
- migrate_to_activitypub(user=None)[source]
Migrates a bridged account out to a native fediverse account.
Duplicates
dms.migrate_to_activitypub()and Bounce’s confirm and migrate_out. Keep them in sync!- Parameters:
user (User)
- Form params:
handle (str): the destination fediverse handle or id
- migrate_to_atproto(user=None)[source]
First step of migrating a bridged account out to a new ATProto PDS.
Calls
describeServeron the new PDS, then shows the create account form.Duplicates
dms.migrate_to_atproto()and Bounce’s confirm and migrate_out. Keep them in sync!- Parameters:
user (User)
- Form params:
pds (str): the new PDS’s URL
- migrate_to_atproto_phone_verification(user=None)[source]
Requests a phone verification SMS from the new ATProto PDS.
Duplicates
dms.migrate_to_atproto()and Bounce’s bluesky_phone_verification_post. Keep them in sync!- Parameters:
user (User)
- Form params:
pds (str): the new PDS’s URL phone_number (str)
- migrate_to_atproto_create_account(user=None)[source]
Final step of migrating a bridged account out to a new ATProto PDS.
Creates the account on the new PDS, then migrates the bridged account out.
- Parameters:
user (User)
- Form params:
pds (str): the new PDS’s URL email (str) password (str) handle (str): optional, the handle’s first segment, eg
alicehandle_domain (str): optional, the new PDS’s handle domain, eg.pds.cominvite_code (str): optional phone_verification_code (str): optional
- serve_feed(*, objects, format, user, title, as_snippets=False, quiet=False)[source]
Generates a feed based on
Objects.- Parameters:
- Returns:
Flask response
- Return type:
- respond(user)[source]
Lets a user reply to, like, or repost an unbridged post.
- Query params:
obj_id (str): Object id token (str): JWT token for user authentication
- respond_reply(user)[source]
Creates a reply activity.
- Form params:
obj_id (str): Object id to reply to content (str): reply text content token (str): JWT token for user authentication
- respond_like(user)[source]
Creates a like activity.
- Form params:
obj_id (str): Object id to like token (str): JWT token for user authentication
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:
objectBase 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-TypeHTTP 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:
- DEFAULT_TARGET = None
optional, the default target URI to send this protocol’s activities to. May be used as the “shared” target. Often only set if
HAS_COPIESis true.- Type:
- REQUIRES_AVATAR = False
whether accounts on this protocol are required to have a profile picture. If they don’t, their
User.statuswill 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.statuswill beblocked.- Type:
- REQUIRES_OLD_ACCOUNT = False
whether accounts on this protocol are required to be at least
common.OLD_ACCOUNT_AGEold. If their profile includes creation date and it’s not old enough, theirUser.statuswill 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
- HTML_PROFILES = False
whether this protocol supports HTML in profile descriptions. If False, profile descriptions should be plain text.
- Type:
- SEND_REPLIES_TO_ORIG_POSTS_MENTIONS = False
whether replies to this protocol should include the original post’s mentions as delivery targets
- Type:
- BOTS_FOLLOW_BACK = False
when a user on this protocol follows a bot user to enable bridging, does the bot follow them back?
- Type:
- HANDLES_PER_PAY_LEVEL_DOMAIN = None
how many users to allow with handles on the same pay-level domain. None for no limit.
- Type:
- RECEIVE_FILTERS = ()
filter functions from filters.py to apply to incoming activities. Applied in order, so put the cheapest filters first.
- Type:
tuple of callable
- RATE_LIMIT_TYPE = 1
Whether receive and send task rate limiting increases linearly or exponential.
- 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 or objects, 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 and DIDs. 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
domains.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 authed_user_for_request()[source]
Returns the authenticated user id for the current request.
Checks authentication on the current request, eg HTTP Signature for ActivityPub. To be implemented by subclasses.
- Returns:
authenticated user id, or None if there is no authentication
- Return type:
- Raises:
RuntimeError – if the request’s authentication (eg signature) is
invalid or otherwise can't be verified –
- classmethod key_for(id, allow_opt_out=False)[source]
Returns the
google.cloud.ndb.Keyfor a given id’smodels.User.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 is_user_at_domain(handle, allow_internal=False)[source]
Returns True if handle is formatted
user@domain.tld, False otherwise.Example:
@user@instance.com
- 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, target, from_user=None, orig_obj_id=None)[source]
Sends an outgoing activity.
To be implemented by subclasses. Should call
to_cls.translate_ids(obj.as1)before converting it to this Protocol’s format.NOTE: if this protocol’s
HAS_COPIESis True, and this method creates a copy and sends it, it must add that copy to the object’s (not activity’s)copies, and store it back in the datastore, in a transaction!- Parameters:
obj (Object) – with activity to send
target (str) – destination URL to send to
from_user (User) – user (actor) this activity is from
orig_obj_id (str) –
models.Objectkey 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, werkzeug.HTTPException, –
websockets.WebSocketException, etc – if the fetch fails
- classmethod convert(obj, from_user=None, **kwargs)[source]
Converts an
Objectto this protocol’s data format.For example, an HTML string for
Web, or a dict with AS2 JSON andapplication/activity+jsonforActivityPub.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, or None
- classmethod _convert(obj, from_user=None, **kwargs)[source]
Converts an
Objectto 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(obj, from_user)[source]
Adds “bridged from … by Bridgy Fed” to the user’s actor’s
summary.Uses HTML for protocols that support it, plain text otherwise.
- 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 ifuseris on this protocol or not bridged to this protocol
- classmethod check_can_migrate_out(user, to_user_id)[source]
Raises an exception if a user can’t yet migrate to a native account.
For example, if
to_user_idisn’t on this protocol, or ifuseris on this protocol, or isn’t bridged to this protocol.If the user is ready to migrate, returns
None.Subclasses may override this to add more criteria, but they should call this implementation first.
- Parameters:
- Raises:
ValueError – if
userisn’t ready to migrate to this protocol yet
- classmethod migrate_in(user, from_user_id, **kwargs)[source]
Migrates a native account in to be a bridged account.
The protocol independent parts are done here; protocol-specific parts are done in
_migrate_in(), which this wraps.Reloads the user’s profile before calling
_migrate_in().- Parameters:
- Raises:
ValueError – eg if this protocol doesn’t own
from_user_id, or ifuseris 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_protocolweb, returns its URL, as a webmention target.If obj is an
activitypubactor, returns its inbox.If obj is an
activitypubobject, 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
protoisActivityPub, the ATProto URIat://did:plc:abc/coll/123will be converted tohttps://bsky.brid.gy/ap/at://did:plc:abc/coll/123.Wraps these AS1 fields:
idactorauthorbccbtoccfeatured[].items,featured[].orderedItemsobjectobject.actorobject.authorobject.idobject.inReplyToobject.objectattachments[].idtags[objectType=mention].urlto
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:
translated AS1 version of
obj- Return type:
- classmethod translate_mention_handles(obj)[source]
Translates @-mentions in
obj.contentto this protocol’s handles.Specifically, for each
mentiontag in the object’s tags that hasstartIndexandlength, replaces it inobj.contentwith that user’s translated handle in this protocol and updates the tag’s location.Called by
Protocol.translate_ids().If
obj.contentis HTML, does nothing.
- 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, from_user)[source]
Handles an incoming follow activity.
Sends an
Acceptback, but doesn’t send theFollowitself. 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_maybe_follow_back(user)[source]
Follow a user from a protocol bot user, if their protocol needs that.
…so that the protocol starts sending us their activities, if it needs a follow for that (eg ActivityPub).
- Parameters:
user (User)
- classmethod handle_move(obj, from_user)[source]
Handles an incoming move (account migration) activity.
Updates all of the account’s :class:`Follower`s to point to the new id.
- classmethod handle_bare_object(obj, *, authed_as, from_user)[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
notifyandfeedproperties 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.Objectto.Targets are both objects - original posts, events, etc - and actors.
- Parameters:
- Returns:
maps
models.Targetto original (in response to)models.Object- Return type:
- classmethod load(id, remote=None, local=True, raise_=True, raw=False, csv=False, **kwargs)[source]
Loads and returns an Object from datastore or HTTP fetch.
Sets the
newandchangedattributes 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.RequestExceptionorHTTPExceptionraised byfetch()and returnsNoneinsteadraw (bool) – whether to load this as a “raw” id, as is, without normalizing to an on-protocol object id. Exact meaning varies by subclass.
csv (bool) – whether to specifically load a CSV object TODO: merge this into raw, using returned Content-Type?
kwargs – passed through to
fetch()
- Returns:
loaded object, or None if it isn’t fetchable, eg a non-URL string for Web, or
remoteis False and it isn’t in the datastore- Return type:
- Raises:
- classmethod check_supported(obj, direction)[source]
If this protocol doesn’t support this activity, 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.Objectis created.)
- classmethod block(from_user, arg)[source]
Blocks a user or list.
- Parameters:
- Returns:
user or list that was blocked
- Return type:
- Raises:
ValueError – if arg doesn’t look like a user or list on this protocol
- classmethod unblock(from_user, arg)[source]
Unblocks a user or list.
- Parameters:
- Returns:
user or list that was unblocked
- Return type:
- Raises:
ValueError – if arg doesn’t look like a user or list on this protocol
- 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.Objectto handlereceived_at (str, ISO 8601 timestamp) – when we first saw (received) this activity
* – If
obj_idis unset, all other parameters are properties for a newmodels.Objectto 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.Objectwithnewandchanged, 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.Objectto sendorig_obj_id (str) – optional,
models.Objectkey 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_idis unset, all other parameters are properties for a newmodels.Objectto handle
- user_enabled_task()[source]
Task handler for when a user enables a protocol.
DMs any dormant
models.Followers pointing at the user to let them know the user is now bridged, so they can follow them for real, and flips thoseFollowers fromdormanttoinactive.- Parameters:
user (url-safe google.cloud.ndb.key.Key) – the
models.Userwho enabled bridgingprotocol (str) –
LABELof the protocol they enabled
- migrate_out_task()[source]
Task handler for finishing a migration out.
Currently, for migrating out to ATProto, uploads the user’s blobs to the new PDS. Otherwise, does nothing.
- Parameters:
user (str, url-safe ndb.Key of a User) – the bridged
models.Usermigrating outprotocol (str) – destination protocol
auth (optional url-safe ndb.Key of an oauth-dropins auth entity) – the user’s new account. For ATProto, an
oauth_dropins.bluesky.BlueskyAuth.
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_EMOJI = '🌐'
- CONTENT_TYPE = 'text/html; charset=utf-8'
- DEFAULT_ENABLED_PROTOCOLS = ('activitypub',)
- DEFAULT_SERVE_USER_PAGES = True
- SUPPORTED_AS1_TYPES = ('service', 'organization', 'application', 'person', 'group', 'note', 'mention', 'comment', 'article', 'link', 'undo', 'update', 'delete', 'post', 'audio', 'bookmark', 'event', 'image', 'video', 'follow', 'like', 'share', 'stop-following')
- USES_OBJECT_FEED = True
- HTML_PROFILES = True
- has_redirects
- redirects_error
- 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/meATProto: 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.Keyfor 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, target, from_user=None, orig_obj_id=None, **kwargs)[source]
Sends a webmention to a given webmention 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, csv=False, **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.Modeldoesn’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:
obj (Object)
gateway (bool) – passed through to
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
csv (bool) – if True, fetch CSV instead of microformatted HTML
kwargs – ignored
- poll_feed(user, feed_url, rel_type)[source]
Fetches a
Website’s feed and delivers new/updated posts.
- poll_feed_task()[source]
Task handler for polling a
Webuser’s feed.- Params:
domain(str): key id of theWebuserlast_polled(str): should match the user’slast_polled_feed. Used to detect duplicate poll tasks for the same user.
- webmention_task()[source]
Handles inbound webmention task.
Allows source URLs on brid.gy subdomains if the
Authorizationheader matches the Flask secret key.- Params:
source(str): URL
- 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:
XrdOrJrdServes 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:
XrdOrJrdRenders and serves the
/.well-known/host-metafile.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