kopia lustrzana https://github.com/snarfed/bridgy-fed
initial code skeleton and app scaffolding, largely copied from Bridgy Fed
rodzic
2a7266e61e
commit
0277675cee
|
@ -0,0 +1,42 @@
|
|||
# -*- conf -*-
|
||||
# Duplicated in other App Engine project repos: Bridgy, Bridgy Fed, granary,
|
||||
# oauth-dropins, etc. If you make a change here, change them too!
|
||||
#
|
||||
# https://cloud.google.com/appengine/docs/standard/python/config/appref#Python_app_yaml_Includes
|
||||
|
||||
*.bak
|
||||
*.c
|
||||
*.cc
|
||||
*.cpp
|
||||
*.h
|
||||
*.o
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.so
|
||||
.git*
|
||||
|
||||
__pycache__/
|
||||
.coverage/
|
||||
.git*/
|
||||
coverage/
|
||||
debian/
|
||||
doc/
|
||||
docs/
|
||||
example/
|
||||
examples/
|
||||
l/
|
||||
l3/
|
||||
local/
|
||||
local3/
|
||||
local3.7/
|
||||
pydoc/
|
||||
pydocs/
|
||||
python3/
|
||||
RCS/
|
||||
ref/
|
||||
sample/
|
||||
samples/
|
||||
TAGS
|
||||
TAGS/
|
||||
test/
|
||||
tests/
|
|
@ -0,0 +1,10 @@
|
|||
.coverage
|
||||
/.well-known/acme-challenge/
|
||||
datastore.dat*
|
||||
/docs/_build/
|
||||
flask_secret_key
|
||||
/l
|
||||
/local*
|
||||
private_notes
|
||||
service_account_creds.json
|
||||
TAGS
|
|
@ -0,0 +1,8 @@
|
|||
"""app.bsky.actor.* XRPC methods."""
|
||||
|
||||
# lexicons/app/bsky/actor/createScene.json
|
||||
# lexicons/app/bsky/actor/getProfile.json
|
||||
# lexicons/app/bsky/actor/getSuggestions.json
|
||||
# lexicons/app/bsky/actor/search.json
|
||||
# lexicons/app/bsky/actor/searchTypeahead.json
|
||||
# lexicons/app/bsky/actor/updateProfile.json
|
|
@ -0,0 +1,36 @@
|
|||
"""Main Flask application."""
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Flask
|
||||
from flask_caching import Cache
|
||||
import flask_gae_static
|
||||
from oauth_dropins.webutil import (
|
||||
appengine_info,
|
||||
appengine_config,
|
||||
flask_util,
|
||||
util,
|
||||
)
|
||||
|
||||
|
||||
app = Flask(__name__, static_folder=None)
|
||||
app.template_folder = './templates'
|
||||
app.json.compact = False
|
||||
app.config.from_pyfile(Path(__file__).parent / 'config.py')
|
||||
app.url_map.converters['regex'] = flask_util.RegexConverter
|
||||
app.after_request(flask_util.default_modern_headers)
|
||||
app.register_error_handler(Exception, flask_util.handle_exception)
|
||||
if appengine_info.LOCAL:
|
||||
flask_gae_static.init_app(app)
|
||||
|
||||
# don't redirect API requests with blank path elements
|
||||
app.url_map.redirect_defaults = True
|
||||
|
||||
app.wsgi_app = flask_util.ndb_context_middleware(
|
||||
app.wsgi_app, client=appengine_config.ndb_client)
|
||||
|
||||
cache = Cache(app)
|
||||
|
||||
util.set_user_agent('Bridgy AT (https://at.brid.gy/)')
|
||||
|
||||
|
||||
import pages
|
|
@ -0,0 +1,50 @@
|
|||
# https://cloud.google.com/appengine/docs/standard/python/config/appref
|
||||
|
||||
# application: bridgy-at
|
||||
|
||||
runtime: python39
|
||||
|
||||
# default_expiration: 1h
|
||||
|
||||
# https://cloud.google.com/appengine/docs/standard/python3/runtime#entrypoint_best_practices
|
||||
# https://docs.gunicorn.org/en/latest/settings.html#timeout
|
||||
entrypoint: gunicorn --workers 1 --threads 10 --timeout 60 -b :$PORT app:app
|
||||
|
||||
# background: https://github.com/snarfed/bridgy/issues/578
|
||||
# https://github.com/snarfed/bridgy/issues/1051
|
||||
automatic_scaling:
|
||||
max_idle_instances: 1
|
||||
target_cpu_utilization: .9
|
||||
min_pending_latency: 3000ms
|
||||
max_concurrent_requests: 30
|
||||
|
||||
inbound_services:
|
||||
- warmup
|
||||
|
||||
handlers:
|
||||
|
||||
# static
|
||||
- url: /static
|
||||
static_dir: static
|
||||
secure: always
|
||||
|
||||
- url: /oauth_dropins_static
|
||||
static_dir: oauth_dropins_static
|
||||
|
||||
- url: /fonts
|
||||
static_dir: oauth_dropins_fonts
|
||||
|
||||
- url: /favicon.ico
|
||||
static_files: static/favicon.ico
|
||||
upload: static/favicon.ico
|
||||
secure: always
|
||||
|
||||
- url: /robots.txt
|
||||
static_files: static/robots.txt
|
||||
upload: static/robots.txt
|
||||
secure: always
|
||||
|
||||
# dynamic
|
||||
- url: .*
|
||||
script: auto
|
||||
secure: always
|
|
@ -0,0 +1,23 @@
|
|||
"""Misc common utilities."""
|
||||
import datetime
|
||||
|
||||
DOMAIN_RE = r'([^/:]+\.[^/:]+)'
|
||||
TLD_BLOCKLIST = ('7z', 'asp', 'aspx', 'gif', 'html', 'ico', 'jpg', 'jpeg', 'js',
|
||||
'json', 'php', 'png', 'rar', 'txt', 'yaml', 'yml', 'zip')
|
||||
|
||||
PRIMARY_DOMAIN = 'at.brid.gy'
|
||||
OTHER_DOMAINS = (
|
||||
'bridgy-at.appspot.com',
|
||||
'localhost',
|
||||
)
|
||||
DOMAINS = (PRIMARY_DOMAIN,) + OTHER_DOMAINS
|
||||
# TODO: unify with Bridgy's, Bridgy Fed's
|
||||
DOMAIN_BLOCKLIST = frozenset((
|
||||
'facebook.com',
|
||||
'fb.com',
|
||||
't.co',
|
||||
'twitter.com',
|
||||
) + DOMAINS)
|
||||
|
||||
# alias allows unit tests to mock the function
|
||||
utcnow = datetime.datetime.utcnow
|
|
@ -0,0 +1,21 @@
|
|||
"""Flask config.
|
||||
|
||||
https://flask.palletsprojects.com/en/latest/config/
|
||||
"""
|
||||
from oauth_dropins.webutil import appengine_info, util
|
||||
|
||||
# This is primarily for flashed messages, since we don't use session data
|
||||
# otherwise.
|
||||
SESSION_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
# Change to Lax if/when we add IndieAuth for anything.
|
||||
SESSION_COOKIE_SAMESITE = 'Strict'
|
||||
|
||||
if appengine_info.DEBUG:
|
||||
ENV = 'development'
|
||||
CACHE_TYPE = 'NullCache'
|
||||
SECRET_KEY = 'sooper seekret'
|
||||
else:
|
||||
ENV = 'production'
|
||||
CACHE_TYPE = 'SimpleCache'
|
||||
SECRET_KEY = util.read('flask_secret_key')
|
|
@ -0,0 +1,58 @@
|
|||
# CircleCI automatically reads this file from our repo and uses it for
|
||||
# configuration. Docs:
|
||||
# https://circleci.com/docs/2.1/configuration-reference/
|
||||
# https://circleci.com/docs/2.1/sample-config/
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: cimg/python:3.9
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
key: venv-1-{{ .Branch }}-{{ checksum "requirements.txt" }}
|
||||
|
||||
- run:
|
||||
name: Base dependencies
|
||||
command: |
|
||||
# google-cloud-sdk: https://cloud.google.com/sdk/docs/install#deb
|
||||
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y apt-transport-https ca-certificates gnupg google-cloud-sdk google-cloud-sdk-datastore-emulator default-jre
|
||||
|
||||
- run:
|
||||
name: Python dependencies
|
||||
command: |
|
||||
pip install -U pip
|
||||
pip -V
|
||||
# make sure we install these at head, below
|
||||
pip uninstall -y granary lexrpc oauth-dropins
|
||||
pip install -U -r requirements.txt
|
||||
pip install coverage coveralls
|
||||
|
||||
- run:
|
||||
name: Build and test
|
||||
command: |
|
||||
CLOUDSDK_CORE_PROJECT=bridgy-at gcloud beta emulators datastore start --no-store-on-disk --consistency=1.0 --host-port=localhost:8089 < /dev/null >& /dev/null &
|
||||
sleep 5s
|
||||
python -m coverage run --source=. --omit=tests/\* -m unittest discover -v
|
||||
python -m coverage html -d /tmp/coverage_html
|
||||
if [ "$COVERALLS_REPO_TOKEN" != "" ]; then coveralls || true; fi
|
||||
|
||||
- save_cache:
|
||||
key: venv-1-{{ .Branch }}-{{ checksum "requirements.txt" }}
|
||||
paths:
|
||||
- /home/circleci/.pyenv
|
||||
# Ideally we'd cache these, but they need root, and the cimg/python
|
||||
# Docker image's default user is circleci :/
|
||||
# https://github.com/cypress-io/circleci-orb/issues/269
|
||||
#
|
||||
# - /usr/lib/google-cloud-sdk
|
||||
# - /usr/lib/jvm/java-11-openjdk-amd64
|
||||
|
||||
- store_artifacts:
|
||||
path: /tmp/coverage_html
|
|
@ -0,0 +1,8 @@
|
|||
# GitHub Dependabot config
|
||||
# https://docs.github.com/en/github/administering-a-repository/keeping-your-dependencies-updated-automatically
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
|
@ -0,0 +1,8 @@
|
|||
"""app.bsky.feed.* XRPC methods."""
|
||||
|
||||
# lexicons/app/bsky/feed/getAuthorFeed.json
|
||||
# lexicons/app/bsky/feed/getPostThread.json
|
||||
# lexicons/app/bsky/feed/getRepostedBy.json
|
||||
# lexicons/app/bsky/feed/getTimeline.json
|
||||
# lexicons/app/bsky/feed/getVotes.json
|
||||
# lexicons/app/bsky/feed/setVote.json
|
|
@ -0,0 +1,7 @@
|
|||
"""app.bsky.graph.* XRPC methods."""
|
||||
|
||||
# lexicons/app/bsky/graph/getAssertions.json
|
||||
# lexicons/app/bsky/graph/getFollowers.json
|
||||
# lexicons/app/bsky/graph/getFollows.json
|
||||
# lexicons/app/bsky/graph/getMembers.json
|
||||
# lexicons/app/bsky/graph/getMemberships.json
|
|
@ -0,0 +1,11 @@
|
|||
"""Datastore model classes."""
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from google.cloud import ndb
|
||||
from oauth_dropins.webutil.models import StringIdModel
|
||||
from oauth_dropins.webutil import util
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
|
||||
logger = logging.getLogger(__name__)
|
|
@ -0,0 +1,146 @@
|
|||
"""UI pages."""
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from flask import redirect, render_template, request
|
||||
import humanize
|
||||
from oauth_dropins.webutil import flask_util, logs, util
|
||||
from oauth_dropins.webutil.flask_util import error, flash, redirect
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
|
||||
from app import app, cache
|
||||
import common
|
||||
#from models import ...
|
||||
|
||||
PAGE_SIZE = 20
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
@flask_util.cached(cache, datetime.timedelta(days=1))
|
||||
def front_page():
|
||||
"""View for the front page."""
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@app.route('/docs')
|
||||
@flask_util.cached(cache, datetime.timedelta(days=1))
|
||||
def docs():
|
||||
"""View for the docs page."""
|
||||
return render_template('docs.html')
|
||||
|
||||
|
||||
@app.get('/web-site')
|
||||
@flask_util.cached(cache, datetime.timedelta(days=1))
|
||||
def enter_web_site():
|
||||
return render_template('enter_web_site.html')
|
||||
|
||||
|
||||
@app.post('/web-site')
|
||||
def check_web_site():
|
||||
url = request.values['url']
|
||||
domain = util.domain_from_link(url, minimize=False)
|
||||
if not domain:
|
||||
error(f'No domain found in {url}')
|
||||
|
||||
user = User.get_or_create(domain)
|
||||
try:
|
||||
user = user.verify()
|
||||
except BaseException as e:
|
||||
if util.is_connection_failure(e):
|
||||
flash(f"Couldn't connect to {url}")
|
||||
return render_template('enter_web_site.html')
|
||||
raise
|
||||
|
||||
user.put()
|
||||
return redirect(f'/user/{user.key.id()}')
|
||||
|
||||
|
||||
@app.get(f'/user/<regex("{common.DOMAIN_RE}"):domain>')
|
||||
def user(domain):
|
||||
user = User.get_by_id(domain)
|
||||
...
|
||||
|
||||
|
||||
@app.get(f'/user/<regex("{common.DOMAIN_RE}"):domain>/feed')
|
||||
def feed(domain):
|
||||
...
|
||||
|
||||
|
||||
@app.get('/responses') # deprecated
|
||||
def recent_deprecated():
|
||||
return redirect('/recent', code=301)
|
||||
|
||||
|
||||
@app.get('/recent')
|
||||
def recent():
|
||||
"""Renders recent activities, with links to logs."""
|
||||
...
|
||||
activities, before, after = fetch_activities(query)
|
||||
return render_template(
|
||||
'recent.html',
|
||||
show_domains=True,
|
||||
logs=logs,
|
||||
util=util,
|
||||
**locals(),
|
||||
)
|
||||
|
||||
|
||||
def fetch_page(query, model_class):
|
||||
"""Fetches a page of results from a datastore query.
|
||||
|
||||
Uses the `before` and `after` query params (if provided; should be ISO8601
|
||||
timestamps) and the queried model class's `updated` 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.
|
||||
|
||||
Args:
|
||||
query: :class:`ndb.Query`
|
||||
model_class: ndb model class
|
||||
|
||||
Returns:
|
||||
(results, new_before, new_after) tuple with:
|
||||
results: list of query result entities
|
||||
new_before, new_after: str query param values for `before` and `after`
|
||||
to fetch the previous and next pages, respectively
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
def fetch_activities(query):
|
||||
"""Fetches a page of Activity entities from a datastore query.
|
||||
|
||||
Wraps :func:`fetch_page` and adds attributes to the returned Activity
|
||||
entities for rendering in activities.html.
|
||||
|
||||
Args:
|
||||
query: :class:`ndb.Query`
|
||||
|
||||
Returns:
|
||||
(results, new_before, new_after) tuple with:
|
||||
results: list of Activity entities
|
||||
new_before, new_after: str query param values for `before` and `after`
|
||||
to fetch the previous and next pages, respectively
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@app.get('/stats')
|
||||
def stats():
|
||||
def count(kind):
|
||||
return humanize.intcomma(
|
||||
KindStat.query(KindStat.kind_name == kind).get().count)
|
||||
|
||||
return render_template(
|
||||
'stats.html',
|
||||
...,
|
||||
)
|
||||
|
||||
|
||||
@app.get('/log')
|
||||
@flask_util.cached(cache, logs.CACHE_TIME)
|
||||
def log():
|
||||
return logs.log()
|
|
@ -0,0 +1,84 @@
|
|||
git+https://github.com/snarfed/oauth-dropins.git#egg=oauth_dropins
|
||||
git+https://github.com/snarfed/granary.git#egg=granary
|
||||
git+https://github.com/snarfed/lexrpc.git#egg=lexrpc
|
||||
git+https://github.com/dvska/gdata-python3.git#egg=gdata
|
||||
|
||||
attrs==22.2.0
|
||||
beautifulsoup4==4.11.1
|
||||
brevity==0.2.17
|
||||
cachetools==4.2.4
|
||||
certifi==2022.12.7
|
||||
charset-normalizer==2.1.1
|
||||
click==8.1.3
|
||||
colorama==0.4.6
|
||||
Deprecated==1.2.13
|
||||
domain2idna==1.12.0
|
||||
ecdsa==0.18.0
|
||||
extras==1.0.0
|
||||
feedgen==0.9.0
|
||||
feedparser==6.0.10
|
||||
fixtures==4.0.1
|
||||
Flask==2.2.2
|
||||
Flask-Caching==2.0.1
|
||||
flask-gae-static==1.0
|
||||
google-api-core==2.11.0
|
||||
google-auth==2.15.0
|
||||
google-cloud-appengine-logging==1.2.0
|
||||
google-cloud-audit-log==0.2.4
|
||||
google-cloud-core==2.3.2
|
||||
google-cloud-datastore==2.11.0
|
||||
google-cloud-logging==3.3.1
|
||||
google-cloud-ndb==2.1.0
|
||||
googleapis-common-protos==1.57.0
|
||||
grpc-google-iam-v1==0.12.4
|
||||
grpcio==1.51.1
|
||||
gunicorn==20.1.0
|
||||
html2text==2020.1.16
|
||||
html5lib==1.1
|
||||
humanfriendly==10.0
|
||||
humanize==4.4.0
|
||||
idna==3.4
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.2
|
||||
jsonschema==4.17.3
|
||||
lxml==4.9.2
|
||||
MarkupSafe==2.1.1
|
||||
mf2py==1.1.2
|
||||
mf2util==0.5.1
|
||||
mox3==1.1.0
|
||||
oauthlib==3.2.2
|
||||
packaging==22.0
|
||||
pbr==5.11.0
|
||||
praw==7.6.1
|
||||
prawcore==2.3.0
|
||||
proto-plus==1.22.1
|
||||
protobuf==3.20.3
|
||||
pyasn1==0.4.8
|
||||
pyasn1-modules==0.2.8
|
||||
pycryptodome==3.16.0
|
||||
pymemcache==4.0.0
|
||||
pyparsing==3.0.9
|
||||
pyrsistent==0.19.2
|
||||
python-dateutil==2.8.2
|
||||
python-tumblpy==1.1.4
|
||||
pytz==2022.7
|
||||
PyYAML==6.0
|
||||
redis==4.4.0
|
||||
requests==2.28.1
|
||||
requests-oauthlib==1.3.1
|
||||
rsa==4.9
|
||||
sgmllib3k==1.0.0
|
||||
six==1.16.0
|
||||
soupsieve==2.3.2.post1
|
||||
testtools==2.5.0
|
||||
tlslite-ng==0.7.6
|
||||
tweepy==4.12.1
|
||||
ujson==5.6.0
|
||||
update-checker==0.18.0
|
||||
urllib3==1.26.13
|
||||
webapp2==3.0.0b1
|
||||
webencodings==0.5.1
|
||||
WebOb==1.8.7
|
||||
websocket-client==1.4.2
|
||||
Werkzeug==2.2.2
|
||||
wrapt==1.14.1
|
|
@ -0,0 +1,4 @@
|
|||
User-agent: *
|
||||
Disallow: /*?*
|
||||
Disallow: /user/*
|
||||
Disallow: /recent*
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>{% block title %}Bridgy AT{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="mobile-web-app-capable" content="yes"/>
|
||||
<link rel="shortcut icon" sizes="128x128" href="https://brid.gy/static/bridgy_logo_128.jpg">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="https://brid.gy/static/bridgy_logo_128.jpg">
|
||||
<!-- 32x32 last so that browsers prefer it -->
|
||||
<link rel="shortcut icon" sizes="32x32" href="https://brid.gy/static/favicon.png">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="32x32" href="https://brid.gy/static/favicon.png">
|
||||
<link rel="stylesheet" href="/oauth_dropins_static/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="/static/style.css" type="text/css" />
|
||||
<link rel="webmention" href="/webmention" />
|
||||
<!-- Not async because it adds a DOMContentLoaded event listener, which may
|
||||
-- happen before an async script loads. -->
|
||||
<script src="/oauth_dropins_static/util.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div id="messages">
|
||||
{% for message in messages %}
|
||||
<p class="message shadow">{{ message|safe }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<main class="tp-main lead container">
|
||||
|
||||
<div id="intro" class="row">
|
||||
<div id="logo" class="col-xs-4">
|
||||
<a href="/"><img id="logo-img" class="img-responsive" src="/static/bridgy_fed_logo.png" /></a>
|
||||
</div>
|
||||
|
||||
<div id="header" class="col-xs-7">
|
||||
<p class="big">
|
||||
<a id="title" href="/" style="white-space: nowrap">Bridgy AT</a>
|
||||
connects <a href="https://indieweb.org/">your web site</a> <br>
|
||||
to <a href="https://joinmastodon.org/">Mastodon</a> and the
|
||||
<a href="https://en.wikipedia.org/wiki/Fediverse">fediverse</a>. <br>
|
||||
<a href="/docs">Learn more...</a>
|
||||
</p></div>
|
||||
</div>
|
||||
|
||||
<p class="clearfix"></p>
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
<div id="footer">
|
||||
<a href="/docs">Docs</a>
|
||||
| <a href="https://github.com/snarfed/bridgy-at/issues">Bug?</a>
|
||||
| <a href="https://github.com/snarfed/bridgy-at">Source</a>
|
||||
| <a href="https://indieweb.org/">#IndieWeb</a>
|
||||
| <a href="https://brid.gy/">Bridgy</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="/static/fragmention.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,419 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="docs">
|
||||
|
||||
<div class="right third">
|
||||
<a href="/static/snarfed_web_site.png">
|
||||
<img class="shadow" src="/static/snarfed_web_site.png" />
|
||||
</a>
|
||||
<p><em>Personal web site</em></p>
|
||||
<a href="/static/snarfed_mastodon.png">
|
||||
<img class="shadow" src="/static/snarfed_mastodon.png" />
|
||||
</a>
|
||||
<p><em>Fediverse profile via Bridgy AT</em></p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Bridgy AT turns your web site into its own <a href="https://en.wikipedia.org/wiki/Fediverse">fediverse</a> account, visible in <a href="https://joinmastodon.org/">Mastodon</a> and beyond. You can post, reply, like, repost, and follow fediverse accounts by posting on your site with <a href="https://microformats.org/wiki/microformats2">microformats2</a> and sending <a href="https://webmention.net/">webmentions</a>. Bridgy AT translates those posts to fediverse protocols like <a href="https://activitypub.rocks/">ActivityPub</a> and <a href="https://en.wikipedia.org/wiki/OStatus">OStatus</a>, and sends fediverse interactions back to your site as webmentions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This isn't <a href="https://indieweb.org/syndication">syndication</a> or <a href="https://indieweb.org/POSSE">POSSE</a>! You don't need an account on Mastodon or anywhere else. Bridgy AT lets your site act like a first class member of the fediverse. People there will see your posts directly from your site, and vice versa.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Bridgy AT takes some technical know-how to set up, and there are simpler (but less powerful) alternatives. If you just want your site's posts to show up in the fediverse, without any other interactions, <a href="https://www.google.com/search?q=rss+atom+mastodon+bot">consider an RSS or Atom feed bot instead</a>. Or, if you want to cross-post to an existing Mastodon account, <a href="https://brid.gy/">try Bridgy</a>.
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
<ul class="docs">
|
||||
<p><em>Setup</em></p>
|
||||
<li><a href="#setup">How do I set it up?</a></li>
|
||||
<li><a href="#profile">How do I set up my profile?</a></li>
|
||||
<li><a href="#user-page">Where's my user page and dashboard?</a></li>
|
||||
<!-- <li><a href="#sites">Which sites are supported?</a></li> -->
|
||||
|
||||
<br>
|
||||
<p><em>Usage</em></p>
|
||||
<li><a href="#how-post">How do I post to the fediverse?</a></li>
|
||||
<li><a href="#which-posts">Which of my posts will show up in the fediverse?</a></li>
|
||||
<li><a href="#which-parts">How does it decide which parts of my posts to include?</a></li>
|
||||
<li><a href="#visible-links">What's up with the visible fed.brid.gy links in my fediverse posts?</a></li>
|
||||
<li><a href="#reply">How do I reply to a fediverse post?</a></li>
|
||||
<li><a href="#like">How do I favorite (aka like) or boost (aka repost) a fediverse post?</a></li>
|
||||
<li><a href="#follow">How do I follow someone?</a></li>
|
||||
<li><a href="#image">How do I include an image in a post?</a></li>
|
||||
<li><a href="#update">How do I edit an existing post?</a></li>
|
||||
<li><a href="#fragment">Can I publish just one part of a page?</a></li>
|
||||
<li><a href="#backfeed">How do fediverse replies, likes, and other interactions show up on my site?</a></li>
|
||||
<li><a href="#read">How do I read my fediverse timeline/feed?</a></li>
|
||||
<li><a href="#discovery">How can people on the fediverse find and follow me?</a></li>
|
||||
<li><a href="#troubleshooting">I tried it, and it didn't work!</a></li>
|
||||
|
||||
<br>
|
||||
<p><em>About</em></p>
|
||||
<li><a href="#who">Who are you? Why did you make this?</a></li>
|
||||
<li><a href="#cost">How much does it cost?</a></li>
|
||||
<li><a href="#privacy">What do you do with my data?</a></li>
|
||||
<li><a href="#history">How long has this been around?</a></li>
|
||||
<li><a href="#terms">What are the terms of service?</a></li>
|
||||
<li><a href="#bug">I found a bug! I have a feature request!</a></li>
|
||||
<li><a href="#vulnerability">I found a security vulnerability!</a></li>
|
||||
</ul>
|
||||
|
||||
<ul class="docs">
|
||||
|
||||
<br>
|
||||
<h3 id="setup">Setup</h3>
|
||||
|
||||
<li id="setup" class="question">How do I set it up?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
<ol>
|
||||
<li>Your site needs to support SSL. Bridgy AT uses your domain as your identity, so it depends on SSL to prove that you own it.</li>
|
||||
<li>Configure your site to redirect these URL paths to the same paths on <code>https://fed.brid.gy/</code>, including query parameters:</li>
|
||||
<pre>
|
||||
/.well-known/host-meta
|
||||
/.well-known/webfinger
|
||||
</pre>
|
||||
|
||||
<p>Here are instructions for a few common web servers:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p><em><a href="http://wordpress.org/">WordPress</a> (self-hosted)</em>: install the <a href="https://wordpress.org/plugins/safe-redirect-manager/">Safe Redirect Manager</a> plugin, then add these entries:</p>
|
||||
<code>
|
||||
/.well-known/host-meta* => https://fed.brid.gy/.well-known/host-meta*<br/>
|
||||
/.well-known/webfinger* => https://fed.brid.gy/.well-known/webfinger*
|
||||
</code>
|
||||
</li>
|
||||
|
||||
<li><em><a href="http://withknown.com/">Known</a></em> or <em><a href="https://drupal.org/project/indieweb">Drupal</a></em>: follow the <a href="#apache">Apache</a> or <a href="#nginx">nginx</a> instructions below.
|
||||
</li>
|
||||
|
||||
<li id="apache"><em><a href="http://httpd.apache.org/">Apache</a></em>: add this to your <code>.htaccess</code> file:<br />
|
||||
<pre>RewriteEngine on
|
||||
RewriteBase /
|
||||
RewriteRule ^.well-known/(host-meta|webfinger).* https://fed.brid.gy/$0 [redirect=302,last]</pre>
|
||||
(<code>RewriteEngine on</code> is optional if you already have it earlier in your <code>.htaccess</code>. <code>RewriteBase /</code> is optional if you don't have any other <code>RewriteBase</code> directives, or if you put this <code>RewriteRule</code> inside an existing <code>RewriteBase /</code> section.)
|
||||
</li>
|
||||
|
||||
<li id="nginx"><em><a href="https://nginx.org/">nginx</a></em>: add this to your <code>nginx.conf</code> file, in the <code>server</code> section:<br />
|
||||
<pre>rewrite ^/\.well-known/(host-meta|webfinger).* https://fed.brid.gy$request_uri? redirect;</pre>
|
||||
</li>
|
||||
|
||||
<li id="netlify"><em><a href="https://docs.netlify.com/routing/redirects/">Netlify</a></em>: add this to your <code>netlify.toml</code> file.
|
||||
<pre>
|
||||
[[redirects]]
|
||||
from = "/.well-known/host-meta*"
|
||||
to = "https://fed.brid.gy/.well-known/host-meta:splat"
|
||||
status = 302
|
||||
[[redirects]]
|
||||
from = "/.well-known/webfinger*"
|
||||
to = "https://fed.brid.gy/.well-known/webfinger"
|
||||
status = 302
|
||||
</pre>
|
||||
</li>
|
||||
|
||||
|
||||
<!--
|
||||
<em><a href="https://www.blogger.com/">Blogger</a></em>:
|
||||
Not to other domains
|
||||
https://helplogger.blogspot.com/2014/07/how-to-set-custom-redirects-for-blogger-post.html
|
||||
|
||||
<em><a href="https://medium.com/">Medium</a>: TODO</em>
|
||||
Redirects but not custom
|
||||
https://help.medium.com/hc/en-us/articles/213475208-301-Redirects
|
||||
|
||||
<em><a href="http://www.tumblr.com/">Tumblr</a></em>:
|
||||
Haven't found how yet. "Link pages" here mention redirects but aren't what we need:
|
||||
https://tumblr.zendesk.com/hc/en-us/articles/231449328-Redirect-pages
|
||||
|
||||
<em><a href="http://wordpress.com/">WordPress.com</a></em>:
|
||||
Site Redirect, but not per URL
|
||||
https://en.support.wordpress.com/site-redirect/
|
||||
-->
|
||||
|
||||
</ul>
|
||||
|
||||
<li>Add <a href="https://webmention.net/">webmention</a> support to your site. This is strongly recommended, but technically optional. You don't have to automate the webmentions to Bridgy AT to federate your posts, and you don't have to accept the inbound webmentions that Bridgy AT sends, but you'll have a much better experience if you do. <a href="https://indieweb.org/webmention#Publishing_Software">Check out the IndieWeb wiki</a> for instructions for your web server.</li>
|
||||
</ol>
|
||||
</li>
|
||||
|
||||
<li id="profile" class="question">How do I set up my profile?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
Your site's fediverse profile comes from the <a href="https://microformats.org/wiki/microformats2">microformats2</a> <a href="https://indieweb.org/representative_h-card">representative h-card</a> on your site's home page. Here's a minimal example to set your name and a profile picture:
|
||||
|
||||
<pre>
|
||||
<span class="<span class='keyword'>h-card</span>">
|
||||
<a rel="<span class='keyword'>me</span>" href="<span class='value'>/</span>"><span class='value'>Alice Foo</span></a>
|
||||
<img class="<span class='keyword'>u-photo</span>" src="<span class='value'>/me.jpg</span>" />
|
||||
</span>
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p>If you want to set a <a href="https://docs.joinmastodon.org/user/profile/#header">header image</a>, add a <code><a href="https://indieweb.org/featured">u-featured</a></code> image to your h-card, eg:
|
||||
|
||||
<pre>
|
||||
<img class="<span class='keyword'>u-featured</span>" src="<span class='value'>/my-header.png</span>" />
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p>By default, your fediverse address will be <code>@yourdomain.com@yourdomain.com</code>. Many services (eg Mastodon) default to only showing the username, so this generally shows up as just <code>@yourdomain.com</code> in posts, and the full address appears on hover.</p>
|
||||
|
||||
<p>We recommend this for simplicity and predictability, for everyone else as well as you, but if you want a different username, you can set it by adding an <code>acct:</code> <a href="https://microformats.org/wiki/rel-me">u-url</a> link inside your h-card with <code>username@yourdomain.com</code>, eg:
|
||||
|
||||
<pre>
|
||||
<a class="<span class='keyword'>u-url</span>" href="<span class='value'>acct:alice@yourdomain.com</span>"><span class='value'></span></a>
|
||||
</pre>
|
||||
</li>
|
||||
|
||||
<li id="user-page" class="question">Where's my user page and dashboard?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
<a href="/web-site">Enter your domain here</a> to see your user page. It shows your site's current status, recent interactions, remote follow UI, and links to your timeline feeds in various formats.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<!-- <li id="sites" class="question">Which sites are supported?</li> -->
|
||||
<!-- <li class="answer"> -->
|
||||
<!-- <p> -->
|
||||
<!-- These sites are currently supported: -->
|
||||
<!-- </p> -->
|
||||
<!-- <ul> -->
|
||||
<!-- <li><em><a href="https://joinmastodon.org/">Mastodon</a></em>: posts, replies, likes, reposts aka boosts, @-mentions, and follows, both directions, via ActivityPub.<br /> -->
|
||||
<!-- The instance must be running at least <a href="https://hackernoon.com/mastodon-and-the-w3c-f75f376f422">Mastodon 1.6</a>, and more reliably with 2.0 and up. You can find its version on the bottom or right of its <code>/about/more</code> page, e.g. <a href="https://mastodon.social/about/more">mastodon.social/about/more</a>. -->
|
||||
<!-- </li> -->
|
||||
<!-- <li><em><a href="https://project.hubzilla.org/">Hubzilla</a></em>: replies, likes, and reposts aka shares, both directions, via OStatus.<br /> -->
|
||||
<!-- The instance must be running <a href="https://hub.somaton.com/channel/mario/?f=&mid=6db16e0e253c3c376cb921e7b31f94c24522933d7e54c6cf9febaa05359ab2fe@hub.somaton.com">Hubzilla 2.6</a> or higher. You can find its version on its <code>/siteinfo</code> page, e.g. <a href="https://hub.somaton.com/siteinfo">hub.somaton.com/siteinfo</a>. It also needs the GNU Social addon installed and enabled, and you also need to enable it in your account settings on the <em>Feature/Addon settings</em> page (<code>/settings/featured</code>). -->
|
||||
<!-- </li> -->
|
||||
<!-- </ul> -->
|
||||
|
||||
<!-- <p> -->
|
||||
<!-- We're aware of the sites below, and we've made progress on some, but they're not yet supported. Click through and vote for their feature requests if you're interested in any of them! -->
|
||||
<!-- </p> -->
|
||||
<!-- <ul> -->
|
||||
<!-- <li><em><a href="https://github.com/snarfed/bridgy-at/issues/7">Diaspora</a></em>, via OStatus.</li> -->
|
||||
<!-- <li><em><a href="https://github.com/snarfed/bridgy-at/issues/9">Friendica</a></em>, via OStatus.</li> -->
|
||||
<!-- <li><em><a href="https://github.com/snarfed/bridgy-at/issues/8">GNU Social</a></em> (née StatusNet), via OStatus.</li> -->
|
||||
<!-- <li><em><a href="https://github.com/snarfed/bridgy-at/issues/11">MediaGoblin</a></em>, via ActivityPub?</li> -->
|
||||
<!-- <li><em><a href="https://github.com/snarfed/bridgy-at/issues/12">Pleroma</a></em>, via ActivityPub.</li> -->
|
||||
<!-- </ul> -->
|
||||
<!-- </li> -->
|
||||
|
||||
<br>
|
||||
<h3 id="usage">Usage</h3>
|
||||
|
||||
<li id="how-post" class="question">How do I post to the fediverse?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
Create a <a href="https://indieweb.org/post">post</a> with the <a href="https://microformats.org/wiki/h-entry"><code>h-entry</code> microformat</a> on your web site. Many web servers include this or compatible microformats automatically. The post can be a <a href="https://indieweb.org/note#How_to">note</a>, <a href="https://indieweb.org/article">article</a>, <a href="https://indieweb.org/like">like</a>, <a href="https://indieweb.org/repost">repost</a>, <a href="https://indieweb.org/reply">reply</a>, or <a href="https://indieweb.org/follow">follow</a>. For example:
|
||||
|
||||
<pre><div class="<span class='keyword'>h-entry</span>">
|
||||
<p class="<span class='keyword'>e-content</span>"><span class='value'>Two naked tags walk into a bar. The bartender exclaims, "Hey, you can't come in here without microformats, this is a classy joint!"</span></p>
|
||||
<a class="<span class='keyword'>u-bridgy-at</span>" href="<a href='https://fed.brid.gy/'>https://fed.brid.gy/</a>"></a>
|
||||
</div>
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p>Basic HTML formatting like links, bold, and italics are generally preserved and visible in the fediverse, but specifics vary from site to site.
|
||||
</p>
|
||||
|
||||
<p>Then, include a link (optionally blank) to <code><a href="https://fed.brid.gy/">https://fed.brid.gy/</a></code> in that post and <a href="#setup">send Bridgy AT a webmention</a>. That webmention will trigger Bridgy AT to forward your post into the fediverse. Your web server may send the webmention automatically if it supports them, or <a href="https://indieweb.org/Webmention#Manual_Webmentions">you can send it manually.</a>
|
||||
</p>
|
||||
|
||||
<p>(The <code>u-bridgy-at</code> class isn't strictly necessary, but it's useful in some cases to prevent microformats2 parsers from <a href="https://microformats.org/wiki/microformats2-implied-properties#hyperlink_and_url_property">interpreting the link as an implied <code>u-url</code></a>.)
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="which-posts" class="question">Which of my posts will show up in the fediverse?</li>
|
||||
<li class="answer">
|
||||
<p>Only the ones you <a href="#send+Bridgy+Fed+a+webmention">explicitly trigger with a webmention</a>. Bridgy AT doesn't automatically create posts in the fediverse based on your site's Atom feed, HTML, or anything else. It only create posts in the fediverse on an opt in basis, per post, via webmention.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="which-parts" class="question">How does it decide which parts of my posts to include?</li>
|
||||
<li class="answer">
|
||||
<p>Magic! Most major blog engines and CMSes are supported out of the box, no setup necessary. Bridgy AT looks for <a href="http://microformats.org/">microformats</a> in your HTML, first the <a href="http://microformats.org/wiki/microformats2">microformats2</a> <code><a href="https://microformats.org/wiki/h-entry#Core_Properties">e-content</a></code> class and then the legacy <code><a href="https://microformats.org/wiki/hatom#Entry_Content">entry-content</a></code> class. It also understands more advanced microformats2 classes like <code><a href="#reply">in-reply-to</a></code>, <code><a href="#like">u-like-of</a></code>, <code><a href="#like">u-repost-of</a></code>, and <code><a href="#image">u-photo</a></code>.
|
||||
</p>
|
||||
|
||||
<p>Bridgy AT sends the full contents of all posts, specifically everything inside <code><a href="https://microformats.org/wiki/h-entry#Core_Properties">e-content</a></code>, to the fediverse. However, not all fediverse apps currently <em>show</em> the full contents of all posts.
|
||||
</p>
|
||||
|
||||
<p>For example, text-based posts fall into two broad buckets: short <a href="https://indieweb.org/note">notes</a>, eg tweets and toots, and longer <a href="https://indieweb.org/article">articles</a>, eg blog posts. In the IndieWeb, <a href="https://indieweb.org/post-type-discovery#Algorithm">we differentiate based on whether the post has a title</a>: articles generally have titles, notes don't.
|
||||
</p>
|
||||
|
||||
<p>Mastodon currently shows the full text of notes, but for articles, it only shows their titles and a link to the full article. This is because Mastodon and most other fediverse apps are designed primarily for smaller notes, not longer articles.
|
||||
</li>
|
||||
|
||||
<li id="visible-links" class="question">What's up with the visible fed.brid.gy links in my fediverse posts?</li>
|
||||
<li class="answer">
|
||||
<p>These can happen for a couple reasons. For articles, this is expected behavior, <a href="#which-parts">as described above</a>. The link is a Bridgy AT URL that redirects to the original post on your web site. This is <a href="https://github.com/mastodon/mastodon/pull/6219#issuecomment-429142747">because</a> Mastodon <a href="https://github.com/snarfed/bridgy-at/issues/16#issuecomment-424799599">requires</a> ActivityPub (ie fediverse) object URLs to use the same domain that serves them, which in this case is fed.brid.gy. We know it's awkward; sorry for the ugliness!
|
||||
</p>
|
||||
|
||||
<p>Otherwise, this may be the <a href="#how-post">invisible fed.brid.gy link that's required to trigger Bridgy AT</a>. Mastodon will show a preview of links even if their text is blank, so if your link is inside your <code><a href="https://microformats.org/wiki/h-entry#Core_Properties">e-content</a></code> microformats2 element, that's probably what's happening. You can prevent that by moving it outside of <code>e-content</code>. It can go anywhere in your HTML!
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="reply" class="question">How do I reply to a fediverse post?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
Put the reply in a new post on your web site, and include a link to the fediverse post you're replying to with class <code><a href="http://microformats.org/wiki/rel-in-reply-to">u-in-reply-to</a></code>, as if you were publishing a normal <a href="https://indieweb.org/comment">IndieWeb reply</a>. For example:
|
||||
|
||||
<pre><div class="<span class='keyword'>h-entry</span>">
|
||||
<p class="<span class='keyword'>e-content</span>"><span class='value'>Highly entertaining. Please subscribe me to your newsletter.</span></p>
|
||||
<a class="<span class='keyword'>u-in-reply-to</span>" href="<a href='https://indieweb.social/@tchambers/109243684867780200'>https://indieweb.social/@tchambers/109243684867780200</a>"></a>
|
||||
<a class="<span class='keyword'>u-bridgy-at</span>" href="<a href='https://fed.brid.gy/'>https://fed.brid.gy/</a>"></a>
|
||||
</div>
|
||||
</pre>
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="like" class="question">How do I favorite (aka like) or boost (aka repost) a fediverse post?</li>
|
||||
<li class="answer">
|
||||
<p>Favoriting and boosting are almost exactly the <a href="#reply">same as replying</a>. The only difference is that you use <code><a href="https://indieweb.org/like">u-like-of</a></code> for a favorite/like or <code><a href="https://indieweb.org/repost">u-repost-of</a></code> for a boost/repost.
|
||||
|
||||
<pre>
|
||||
<a class="<span class='keyword'>u-like-of</span>" href="<a href='https://octodon.social/@cwebber/109405439825087368'>https://octodon.social/@cwebber/109405439825087368</a>"></a>
|
||||
</pre>
|
||||
|
||||
<pre>
|
||||
<a class="<span class='keyword'>u-repost-of</span>" href="<a href='https://prodromou.pub/@evan/109390803478257847'>https://prodromou.pub/@evan/109390803478257847</a>"></a>
|
||||
</pre>
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="follow" class="question">How do I follow someone?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
Post an <a href="https://indieweb.org/follow#How_to_markup">IndieWeb follow</a> on your site with <code>u-follow-of</code> microformats2, then send a webmention to Bridgy AT. Your site may do that automatically if it supports webmentions. For example:
|
||||
</p>
|
||||
|
||||
<pre><div class="<span class='keyword'>h-entry</span>">
|
||||
I'm now following <a class="<span class='keyword'>u-follow-of</span>" href="<a href='https://octodon.social/@cwebber'>https://octodon.social/@cwebber</a>">@cwebber@octodon.social</a>!
|
||||
<a class="<span class='keyword'>u-bridgy-at</span>" href="<a href='https://fed.brid.gy/'>https://fed.brid.gy/</a>"></a>
|
||||
</div>
|
||||
</pre>
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="image" class="question">How do I include an image in a post?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
Use <code><img class="<span class='keyword'>u-photo</span>"></code> for the image in your post. For example:
|
||||
|
||||
<pre>
|
||||
<img class="<span class='keyword'>u-photo</span>" src="<span class='value'>/full_glass.jpg</span>" />
|
||||
I love scotch. Scotchy scotchy scotch.
|
||||
</pre>
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="update" class="question">How do I edit an existing post?</li>
|
||||
<li class="answer">
|
||||
<p>Edit the post on your web site, then send another webmention to Bridgy AT for it. Bridgy AT will refetch the post and send an <a href="https://www.w3.org/TR/activitypub/#update-activity-outbox"><code>Update</code> activity</a> for it to the fediverse.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="fragment" class="question">Can I publish just one part of a page?</li>
|
||||
<li class="answer">
|
||||
<p>If that HTML element has its own id, then sure! Just put the id in the fragment of the URL that you publish. For example, to publish the <code>bar</code> post here:</p>
|
||||
<pre><div id="<span class='value'>a</span>" class="<span class='keyword'>h-entry</span>"><span class='value'>foo</span></div>
|
||||
<div id="<span class='value'>b</span>" class="<span class='keyword'>h-entry</span>"><span class='value'>bar</span></div>
|
||||
<div id="<span class='value'>c</span>" class="<span class='keyword'>h-entry</span>"><span class='value'>baz</span></div>
|
||||
</pre>
|
||||
<p>...just add the id to your page's URL in a fragment, e.g. <code>http://site/post#b</code> here.</p>
|
||||
</li>
|
||||
|
||||
<li id="backfeed" class="question">How do fediverse replies, likes, and other interactions show up on my site?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
To receive likes, reposts, replies, @-mentions, and follows from the fediverse, just make sure your site accepts <a href="https://webmention.net/">webmentions</a>! Bridgy AT translates those interactions and sends them to your site as webmentions. The source URL will usually be a proxy page on <code>fed.brid.gy</code>. For best results, <a href="https://brid.gy/about#appspot">make sure your webmention handler detects and handles <code>u-url</code> links</a>.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="read" class="question">How do I read my fediverse timeline/feed?</li>
|
||||
<li class="answer">
|
||||
<p><a href="#user-page">Your user page</a> has links to your fediverse timeline/feed, ie posts from people you follow, in HTML, Atom, and RSS formats. Add them to your feed reader or read them in your browser!
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="discovery" class="question">How can people on the fediverse find and follow me?</li>
|
||||
<li class="answer">
|
||||
<p>They can search for your web site in any Mastodon instance! Often you can just enter your domain, eg <code>yourdomain.com</code>, in any Mastodon search box. If that doesn't work, try your full fediverse address, eg <code>@yourdomain.com@yourdomain.com</code>. This can be finicky now and then, but it usually works.
|
||||
</p>
|
||||
|
||||
<p><a href="#user-page">Your user page</a> also has a "remote follow" form that lets people enter their fediverse address and follow you directly.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="troubleshooting" class="question">I tried it, and it didn't work!</li>
|
||||
<li class="answer">
|
||||
<p><a href="#user-page">Check out your user page!</a> It detects and describes common problems with <a href="#setup">your setup</a>, and it shows your recent interactions and detailed logs.
|
||||
</li>
|
||||
|
||||
|
||||
<br>
|
||||
<h3 id="about">About</h3>
|
||||
|
||||
<li id="who" class="question">Who are you? Why did you make this?</li>
|
||||
<li class="answer">
|
||||
<p>
|
||||
I'm <a href="https://snarfed.org/">Ryan Barrett</a>. I'm just a guy who likes <a href="https://snarfed.org/2012-07-25_why_i_have_my_own_web_site">the web</a> and <a href="https://indieweb.org/why">owning my data</a>.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="cost" class="question">How much does it cost?</li>
|
||||
<li class="answer">
|
||||
<p>Nothing! Bridgy AT is small, and it doesn't cost much to run. We don't need donations, promise.
|
||||
</p>
|
||||
<p>If you <em>really</em> want to contribute, <a href="https://github.com/snarfed/bridgy-at/issues">file an issue</a> or <a href="https://github.com/snarfed/bridgy-at">send a pull request</a>, or <a href="https://opencollective.com/indieweb">donate to the IndieWeb</a>!
|
||||
</p></li>
|
||||
|
||||
<li id="privacy" class="question">What do you do with my data?</li>
|
||||
<li class="answer">
|
||||
<p>Nothing! Bridgy AT isn't a business, and never will be, so we don't have the same motivations to abuse your data that other services might. More concretely, Bridgy AT won't ever send you email, it stores as little of your <a href="http://en.wikipedia.org/wiki/Personally_identifiable_information">PII</a> (personally identifiable information) as possible, and it <em>never</em> has access to any of your passwords.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="history" class="question">How long has this been around?</li>
|
||||
<li class="answer">
|
||||
<p>I started thinking about bridging federated social networks and peer to peer networks when I discovered them in the early 2000s. I started talking about bridging them to the IndieWeb in 2016, <a href="http://indieweb.org/2017/ostatusbridge">led a session on it at IndieWeb Summit</a> in July 2017, wrote up <a href="https://snarfed.org/indieweb-activitypub-bridge">concrete</a> <a href="https://snarfed.org/indieweb-ostatus-bridge"> designs</a> soon after, started working on Bridgy AT in August 2017, and <a href="https://snarfed.org/2017-10-22_bridgy-at">launched it on October 22, 2017</a>.
|
||||
</li>
|
||||
|
||||
<li id="terms" class="question">What are the terms of service?</li>
|
||||
<li class="answer">
|
||||
<p>Bridgy AT's terms of service are very simple. You agree not to deliberately attack, breach, or otherwise harm the service. If you manage to access private keys or other private data, you agree to <a href="#vulnerability">report the vulnerability</a> and not use or disclose that data.
|
||||
</p>
|
||||
<p>Otherwise, you may use the service for any purpose you see fit. However, we may terminate or block your access for any reason, or no reason at all. (We've never done this, and we expect we never will. Just playing it safe.)
|
||||
</p>
|
||||
<p>Do you an administer an instance or other service that Bridgy AT interacts with? If you have any concerns or questions, feel free to <a href="https://github.com/snarfed/bridgy-at/issues">file an issue</a>!
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="bug" class="question">I found a bug! I have a feature request!</li>
|
||||
<li class="answer">
|
||||
<p>Great! Please <a href="https://github.com/snarfed/bridgy-at/issues">file it in GitHub</a>. Thank you!
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li id="vulnerability" class="question">I found a security vulnerability!</li>
|
||||
<li class="answer">
|
||||
<p>Oof. Thank you for reporting it! Please send details to <a href="mailto:security@brid.gy">security@brid.gy</a>. We may provide monetary awards for reports of significant vulnerabilities, eg reading or modifying stored access tokens, <em>if</em> you follow these rules:</p>
|
||||
<ul>
|
||||
<li>Vulnerabilities must be in the application itself, not unrelated services like email (eg SPF/DKIM/DMARC).</li>
|
||||
<li>Out of scope: rate limiting, XSS/CSRF attacks (Bridgy AT has no authenticated sessions or private data accessible to users), <code>/admin/*</code> pages.
|
||||
<li>Public user data is intentionally public. That's not a vulnerability.</li>
|
||||
<li>No automated fuzzing, DoSes, or other high volume traffic. We block this traffic, and it will disqualify you from any possible award.</li>
|
||||
</ul>
|
||||
<p>Otherwise, <a href="https://github.com/snarfed/bridgy-at/">the code is open source</a>, feel free to try to break in, let us know if you succeed!</p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p class="row big">What's your web site?</p>
|
||||
|
||||
<div class="row big">
|
||||
<form method="post" action=""> <!-- empty action means post to same URL -->
|
||||
<input required type="url" name="url" id="url" placeholder="snarfed.org" />
|
||||
<input type="submit" class="btn btn-default" value="OK" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ domain }}'s feed - Bridgy AT{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% include "user_addresses.html" %}
|
||||
|
||||
<div class="row big">Feed</div>
|
||||
|
||||
<link rel="stylesheet" href="/static/feed.css" type="text/css" />
|
||||
<div class="row h-feed">
|
||||
{% for e in entries %}
|
||||
{{ e|safe }}
|
||||
{% else %}
|
||||
Nothing yet. <a href="/#To+use+it">Follow more people</a>, check back soon!
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,48 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row big">
|
||||
What do you want to do?
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-6 col-lg-offset-0 col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12">
|
||||
<a class="btn btn-default btn-home" href="https://brid.gy/mastodon/start">
|
||||
<p class="bigger">Cross-post to a Mastodon account:<br>
|
||||
@you@mastodon.server</p>
|
||||
<pre class="bigger"> <img title="Pleroma" style="height: 1.5em; margin-bottom: -.2em" src="/static/pleroma_logo.svg">
|
||||
/
|
||||
<span class="highlight" style="background-color: khaki" title="Your web site">🌐</span> — <span class="highlight"><img title="Mastodon" src="/oauth_dropins_static/mastodon_2x.png"></span> — <img title="Friendica" src="/static/friendica_logo.svg">
|
||||
\
|
||||
<img title="Mastodon" src="/oauth_dropins_static/mastodon_2x.png">
|
||||
</pre>
|
||||
<p class="big">You have a Mastodon account. You want to post on your web site, copy those posts to that account, and send responses back to your site.</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 col-lg-offset-0 col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12">
|
||||
<a class="btn btn-default btn-home" href="/web-site">
|
||||
<p class="bigger">Connect directly to the fediverse:<br>
|
||||
@yoursite.com</p>
|
||||
<pre class="bigger"><img title="Fediverse" src="/static/fediverse_logo.svg"> <img title="PeerTube" src="/static/peertube_logo.svg">
|
||||
\ /
|
||||
<img title="Misskey" src="/static/misskey_logo.png"> — <span class="highlight" title="Your web site">🌐</span> — <img title="Mastodon" src="/oauth_dropins_static/mastodon_2x.png">
|
||||
/ \
|
||||
<img title="GNU Social" src="/static/gnu_social_logo.svg"> <img title="Pixelfed" src="/oauth_dropins_static/pixelfed_2x.png">
|
||||
</pre>
|
||||
<p class="big">Your web site will be its own fediverse account, including its posts. Its username and instance will be your domain. This takes a bit of technical known-how.</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="big col-lg-6 col-lg-offset-3 col-md-12">
|
||||
...or create an account on <a href="https://micro.blog/">micro.blog</a> or <a href="https://joinmastodon.org/">Mastodon</a> to try out the <a href="https://fediverse.party/en/fediverse/">fediverse</a> first. <a href="https://indieweb.org/discuss">Ask us more in chat!</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,13 @@
|
|||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
{% if after %}
|
||||
<a href="?after={{ after }}">← Newer</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-sm-offset-6">
|
||||
{% if before %}
|
||||
<a href="?before={{ before }}">Older →</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,16 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Stats - Bridgy AT{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>Stats</h3>
|
||||
<ul>
|
||||
<li><a href="https://snarfed.org/...">Launched ...?</a>
|
||||
<li>{{ users }} users</li>
|
||||
<li>{{ followers }} fediverse followers</li>
|
||||
<li>{{ activities }} activities handled</li>
|
||||
</ul>
|
||||
</h3>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,68 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ domain }} - Bridgy AT{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if user.has_redirects == False %}
|
||||
<div class="row promo warning">
|
||||
<form method="post" action="/web-site">
|
||||
Next step:
|
||||
<a href="/docs#redirect+these+URL+paths">
|
||||
add the .well-known redirects.
|
||||
</a>
|
||||
<input type="hidden" name="url" value="https://{{ domain }}" />
|
||||
<input type="submit" class="btn btn-default" value="Check now" />
|
||||
</form>
|
||||
|
||||
{% if user.redirects_error %}
|
||||
<details class="small">
|
||||
{{ user.redirects_error|safe }}
|
||||
</details>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if user.has_hcard == False %}
|
||||
<div class="row promo warning">
|
||||
<form method="post" action="/web-site">
|
||||
Next step:
|
||||
<a href="https://indiewebify.me/validate-h-card/?url=https://{{ domain }}">
|
||||
add a representative h-card.
|
||||
</a>
|
||||
<input type="hidden" name="url" value="https://{{ domain }}" />
|
||||
<input type="submit" class="btn btn-default" value="Check now" />
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include "user_addresses.html" %}
|
||||
|
||||
<div class="row">
|
||||
<a href="/user/{{ domain }}/followers">{{ followers }} follower{% if followers != '1' %}s{% endif %}</a>
|
||||
· <a href="/user/{{ domain }}/following">following {{ following }}</a>
|
||||
· <a href="/user/{{ domain }}/feed">HTML</a>
|
||||
· <a href="/user/{{ domain }}/feed?format=atom">Atom</a>
|
||||
· <a href="/user/{{ domain }}/feed?format=rss">RSS</a>
|
||||
· <a href="/.well-known/webfinger?resource=acct:{{ domain }}@{{ domain }}">Webfinger</a>
|
||||
· <a href="/{{ domain }}">ActivityPub</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<form method="post" action="/follow">
|
||||
<p>
|
||||
<label for="follow-address">Enter your fediverse address to follow:</label>
|
||||
<input id="follow-address" name="address" type="text" required
|
||||
placeholder="@user@domain.social" alt="fediverse address"
|
||||
value="{{ follow_url or '' }}"></input>
|
||||
<input name="domain" type="hidden" value="{{ domain }}"></input>
|
||||
<button type="submit" class="btn btn-default">Follow</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- <div class="row">Recent activity</div> -->
|
||||
|
||||
{% include "activities.html" %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,16 @@
|
|||
<div class="row">
|
||||
<div class="big" style="display: inline">
|
||||
{{ user.user_page_link()|safe }}
|
||||
·
|
||||
<span title="Fediverse address">
|
||||
<nobr>
|
||||
<img class="logo" src="/static/fediverse_logo.svg">
|
||||
{{ user.address() }}
|
||||
</nobr>
|
||||
</span>
|
||||
·
|
||||
<nobr>
|
||||
<a title="Web site" href="https://{{ domain }}/">🌐 {{ domain }}</a>
|
||||
</nobr>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}User not found - Bridgy AT{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="big row">
|
||||
<div class="col-sm-12">
|
||||
<a href="https://{{ domain }}/">{{ domain }}</a> is not (yet!) a Bridgy AT user.
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,2 @@
|
|||
# configure logging
|
||||
import oauth_dropins.webutil.tests
|
|
@ -0,0 +1,8 @@
|
|||
"""Unit tests for actor.py."""
|
||||
from . import testutil
|
||||
|
||||
|
||||
class ActorTest(testutil.TestCase):
|
||||
|
||||
def test_noop(self):
|
||||
pass
|
|
@ -0,0 +1 @@
|
|||
"""Unit tests for feed.py."""
|
|
@ -0,0 +1 @@
|
|||
"""Unit tests for graph.py."""
|
|
@ -0,0 +1,33 @@
|
|||
"""Common test utility code."""
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
|
||||
from app import app, cache
|
||||
import common
|
||||
from oauth_dropins.webutil import testutil, util
|
||||
from oauth_dropins.webutil.appengine_config import ndb_client
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
|
||||
NOW = datetime.datetime(2022, 12, 24, 22, 29, 19)
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase, testutil.Asserts):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
app.testing = True
|
||||
cache.clear()
|
||||
self.client = app.test_client()
|
||||
common.utcnow = lambda: NOW
|
||||
|
||||
# clear datastore
|
||||
requests.post('http://%s/reset' % ndb_client.host)
|
||||
self.ndb_context = ndb_client.context()
|
||||
self.ndb_context.__enter__()
|
||||
|
||||
def tearDown(self):
|
||||
self.ndb_context.__exit__(None, None, None)
|
||||
super().tearDown()
|
|
@ -0,0 +1,37 @@
|
|||
# Auto-merge Dependabot PRs that upgrade patch or minor versions if CI passes
|
||||
# Copied from https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request
|
||||
# Also see https://github.com/dependabot/fetch-metadata
|
||||
|
||||
name: Dependabot auto-merge
|
||||
on:
|
||||
pull_request:
|
||||
branches: main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v1.1.1
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Enable auto-merge for Dependabot PRs
|
||||
if: >
|
||||
! contains(steps.metadata.outputs.dependency-names, 'tlslite-ng') &&
|
||||
steps.metadata.outputs.update-type != 'version-update:semver-major'
|
||||
run: gh pr merge --auto --rebase "$PR_URL"
|
||||
|
||||
- name: "Warn that we won't auto-merge major version updates"
|
||||
if: steps.metadata.outputs.update-type == 'version-update:semver-major'
|
||||
run: gh pr comment "$PR_URL" -b "Looks like a major version upgrade! Skipping auto-merge."
|
|
@ -0,0 +1,64 @@
|
|||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 19 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['python', 'javascript']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# Command-line programs to run using the OS shell. https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
- name: DefenseCode ThunderScan Action
|
||||
uses: defensecode/thunderscan-action@v1.0
|
|
@ -0,0 +1,17 @@
|
|||
# Prevents merging dependency versions w/vulnerabilities
|
||||
# https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review
|
||||
# https://github.com/actions/dependency-review-action#installation=
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v1
|
Ładowanie…
Reference in New Issue