mirror of
https://git.sr.ht/~tsileo/microblog.pub
synced 2024-12-22 13:14:28 +00:00
parent
f7e6d37dce
commit
f6c26abccb
7 changed files with 115 additions and 44 deletions
|
@ -50,8 +50,6 @@
|
||||||
|
|
||||||
microblog.pub implements an [ActivityPub](http://activitypub.rocks/) server, it implements both the client to server API and the federated server to server API.
|
microblog.pub implements an [ActivityPub](http://activitypub.rocks/) server, it implements both the client to server API and the federated server to server API.
|
||||||
|
|
||||||
Compatible with [Mastodon](https://github.com/tootsuite/mastodon) (which is not following the spec closely), but will drop OStatus messages.
|
|
||||||
|
|
||||||
Activities are verified using HTTP Signatures or by fetching the content on the remote server directly.
|
Activities are verified using HTTP Signatures or by fetching the content on the remote server directly.
|
||||||
|
|
||||||
## Running your instance
|
## Running your instance
|
||||||
|
|
|
@ -14,19 +14,19 @@ from html2text import html2text
|
||||||
import tasks
|
import tasks
|
||||||
from config import BASE_URL
|
from config import BASE_URL
|
||||||
from config import DB
|
from config import DB
|
||||||
|
from config import EXTRA_INBOXES
|
||||||
from config import ID
|
from config import ID
|
||||||
from config import ME
|
from config import ME
|
||||||
|
from config import MEDIA_CACHE
|
||||||
from config import USER_AGENT
|
from config import USER_AGENT
|
||||||
from config import USERNAME
|
from config import USERNAME
|
||||||
from config import MEDIA_CACHE
|
|
||||||
from config import EXTRA_INBOXES
|
|
||||||
from utils.media import Kind
|
|
||||||
from little_boxes import activitypub as ap
|
from little_boxes import activitypub as ap
|
||||||
from little_boxes import strtobool
|
from little_boxes import strtobool
|
||||||
from little_boxes.activitypub import _to_list
|
from little_boxes.activitypub import _to_list
|
||||||
from little_boxes.backend import Backend
|
from little_boxes.backend import Backend
|
||||||
from little_boxes.collection import parse_collection as ap_parse_collection
|
from little_boxes.collection import parse_collection as ap_parse_collection
|
||||||
from little_boxes.errors import Error
|
from little_boxes.errors import Error
|
||||||
|
from utils.media import Kind
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
134
app.py
134
app.py
|
@ -132,12 +132,23 @@ def inject_config():
|
||||||
"type": ActivityType.LIKE.value,
|
"type": ActivityType.LIKE.value,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
followers_q = {
|
||||||
|
"box": Box.INBOX.value,
|
||||||
|
"type": ActivityType.FOLLOW.value,
|
||||||
|
"meta.undo": False,
|
||||||
|
}
|
||||||
|
following_q = {
|
||||||
|
"box": Box.OUTBOX.value,
|
||||||
|
"type": ActivityType.FOLLOW.value,
|
||||||
|
"meta.undo": False,
|
||||||
|
}
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
microblogpub_version=VERSION,
|
microblogpub_version=VERSION,
|
||||||
config=config,
|
config=config,
|
||||||
logged_in=session.get("logged_in", False),
|
logged_in=session.get("logged_in", False),
|
||||||
followers_count=DB.followers.count(),
|
followers_count=DB.activities.count(followers_q),
|
||||||
following_count=DB.following.count(),
|
following_count=DB.activities.count(following_q),
|
||||||
notes_count=notes_count,
|
notes_count=notes_count,
|
||||||
liked_count=liked_count,
|
liked_count=liked_count,
|
||||||
with_replies_count=with_replies_count,
|
with_replies_count=with_replies_count,
|
||||||
|
@ -499,7 +510,14 @@ def authorize_follow():
|
||||||
actor = get_actor_url(request.form.get("profile"))
|
actor = get_actor_url(request.form.get("profile"))
|
||||||
if not actor:
|
if not actor:
|
||||||
abort(500)
|
abort(500)
|
||||||
if DB.following.find({"remote_actor": actor}).count() > 0:
|
|
||||||
|
q = {
|
||||||
|
"box": Box.OUTBOX.value,
|
||||||
|
"type": ActivityType.FOLLOW.value,
|
||||||
|
"meta.undo": False,
|
||||||
|
"activity.object": actor,
|
||||||
|
}
|
||||||
|
if DB.activities.count(q) > 0:
|
||||||
return redirect("/following")
|
return redirect("/following")
|
||||||
|
|
||||||
follow = ap.Follow(actor=MY_PERSON.id, object=actor)
|
follow = ap.Follow(actor=MY_PERSON.id, object=actor)
|
||||||
|
@ -589,6 +607,38 @@ def tmp_migrate3():
|
||||||
return "Done"
|
return "Done"
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/migration3")
|
||||||
|
@login_required
|
||||||
|
def tmp_migrate4():
|
||||||
|
for activity in DB.activities.find(
|
||||||
|
{"box": Box.OUTBOX.value, "type": ActivityType.UNDO.value}
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
activity = ap.parse_activity(activity["activity"])
|
||||||
|
if activity.get_object().type == ActivityType.FOLLOW.value:
|
||||||
|
DB.activities.update_one(
|
||||||
|
{"remote_id": activity.get_object().id},
|
||||||
|
{"$set": {"meta.undo": True}},
|
||||||
|
)
|
||||||
|
print(activity.get_object().to_dict())
|
||||||
|
except Exception:
|
||||||
|
app.logger.exception("failed")
|
||||||
|
for activity in DB.activities.find(
|
||||||
|
{"box": Box.INBOX.value, "type": ActivityType.UNDO.value}
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
activity = ap.parse_activity(activity["activity"])
|
||||||
|
if activity.get_object().type == ActivityType.FOLLOW.value:
|
||||||
|
DB.activities.update_one(
|
||||||
|
{"remote_id": activity.get_object().id},
|
||||||
|
{"$set": {"meta.undo": True}},
|
||||||
|
)
|
||||||
|
print(activity.get_object().to_dict())
|
||||||
|
except Exception:
|
||||||
|
app.logger.exception("failed")
|
||||||
|
return "Done"
|
||||||
|
|
||||||
|
|
||||||
def paginated_query(db, q, limit=25, sort_key="_id"):
|
def paginated_query(db, q, limit=25, sort_key="_id"):
|
||||||
older_than = newer_than = None
|
older_than = newer_than = None
|
||||||
query_sort = -1
|
query_sort = -1
|
||||||
|
@ -765,7 +815,7 @@ def nodeinfo():
|
||||||
q = {
|
q = {
|
||||||
"box": Box.OUTBOX.value,
|
"box": Box.OUTBOX.value,
|
||||||
"meta.deleted": False, # TODO(tsileo): retrieve deleted and expose tombstone
|
"meta.deleted": False, # TODO(tsileo): retrieve deleted and expose tombstone
|
||||||
'type': {'$in': [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]},
|
"type": {"$in": [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]},
|
||||||
}
|
}
|
||||||
return Response(
|
return Response(
|
||||||
headers={
|
headers={
|
||||||
|
@ -781,10 +831,7 @@ def nodeinfo():
|
||||||
"protocols": ["activitypub"],
|
"protocols": ["activitypub"],
|
||||||
"services": {"inbound": [], "outbound": []},
|
"services": {"inbound": [], "outbound": []},
|
||||||
"openRegistrations": False,
|
"openRegistrations": False,
|
||||||
"usage": {
|
"usage": {"users": {"total": 1}, "localPosts": DB.activities.count(q)},
|
||||||
"users": {"total": 1},
|
|
||||||
"localPosts": DB.activities.count(q),
|
|
||||||
},
|
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"sourceCode": "https://github.com/tsileo/microblog.pub",
|
"sourceCode": "https://github.com/tsileo/microblog.pub",
|
||||||
"nodeName": f"@{USERNAME}@{DOMAIN}",
|
"nodeName": f"@{USERNAME}@{DOMAIN}",
|
||||||
|
@ -895,7 +942,7 @@ def outbox():
|
||||||
q = {
|
q = {
|
||||||
"box": Box.OUTBOX.value,
|
"box": Box.OUTBOX.value,
|
||||||
"meta.deleted": False, # TODO(tsileo): retrieve deleted and expose tombstone
|
"meta.deleted": False, # TODO(tsileo): retrieve deleted and expose tombstone
|
||||||
'type': {'$in': [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]},
|
"type": {"$in": [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]},
|
||||||
}
|
}
|
||||||
return jsonify(
|
return jsonify(
|
||||||
**activitypub.build_ordered_collection(
|
**activitypub.build_ordered_collection(
|
||||||
|
@ -1068,9 +1115,9 @@ def outbox_activity_shares(item_id):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/stats", methods=["GET"])
|
@app.route("/admin", methods=["GET"])
|
||||||
@login_required
|
@login_required
|
||||||
def admin_stats():
|
def admin():
|
||||||
q = {
|
q = {
|
||||||
"meta.deleted": False,
|
"meta.deleted": False,
|
||||||
"meta.undo": False,
|
"meta.undo": False,
|
||||||
|
@ -1084,11 +1131,21 @@ def admin_stats():
|
||||||
instances=list(DB.instances.find()),
|
instances=list(DB.instances.find()),
|
||||||
inbox_size=DB.activities.count({"box": Box.INBOX.value}),
|
inbox_size=DB.activities.count({"box": Box.INBOX.value}),
|
||||||
outbox_size=DB.activities.count({"box": Box.OUTBOX.value}),
|
outbox_size=DB.activities.count({"box": Box.OUTBOX.value}),
|
||||||
object_cache_size=0,
|
|
||||||
actor_cache_size=0,
|
|
||||||
col_liked=col_liked,
|
col_liked=col_liked,
|
||||||
col_followers=DB.followers.count(),
|
col_followers=DB.activities.count(
|
||||||
col_following=DB.following.count(),
|
{
|
||||||
|
"box": Box.INBOX.value,
|
||||||
|
"type": ActivityType.FOLLOW.value,
|
||||||
|
"meta.undo": False,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
col_following=DB.activities.count(
|
||||||
|
{
|
||||||
|
"box": Box.OUTBOX.value,
|
||||||
|
"type": ActivityType.FOLLOW.value,
|
||||||
|
"meta.undo": False,
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1452,7 +1509,14 @@ def api_block():
|
||||||
def api_follow():
|
def api_follow():
|
||||||
actor = _user_api_arg("actor")
|
actor = _user_api_arg("actor")
|
||||||
|
|
||||||
existing = DB.following.find_one({"remote_actor": actor})
|
q = {
|
||||||
|
"box": Box.OUTBOX.value,
|
||||||
|
"type": ActivityType.FOLLOW.value,
|
||||||
|
"meta.undo": False,
|
||||||
|
"activity.object": actor,
|
||||||
|
}
|
||||||
|
|
||||||
|
existing = DB.activities.find_one(q)
|
||||||
if existing:
|
if existing:
|
||||||
return _user_api_response(activity=existing["activity"]["id"])
|
return _user_api_response(activity=existing["activity"]["id"])
|
||||||
|
|
||||||
|
@ -1464,36 +1528,50 @@ def api_follow():
|
||||||
|
|
||||||
@app.route("/followers")
|
@app.route("/followers")
|
||||||
def followers():
|
def followers():
|
||||||
|
q = {"box": Box.INBOX.value, "type": ActivityType.FOLLOW.value, "meta.undo": False}
|
||||||
|
|
||||||
if is_api_request():
|
if is_api_request():
|
||||||
return jsonify(
|
return jsonify(
|
||||||
**activitypub.build_ordered_collection(
|
**activitypub.build_ordered_collection(
|
||||||
DB.followers,
|
DB.activities,
|
||||||
|
q=q,
|
||||||
cursor=request.args.get("cursor"),
|
cursor=request.args.get("cursor"),
|
||||||
map_func=lambda doc: doc["remote_actor"],
|
map_func=lambda doc: doc["activity"]["object"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
followers = [
|
followers, older_than, newer_than = paginated_query(DB.activities, q)
|
||||||
ACTOR_SERVICE.get(doc["remote_actor"]) for doc in DB.followers.find(limit=50)
|
followers = [ACTOR_SERVICE.get(doc["activity"]["object"]) for doc in followers]
|
||||||
]
|
return render_template(
|
||||||
return render_template("followers.html", followers_data=followers)
|
"followers.html",
|
||||||
|
followers_data=followers,
|
||||||
|
older_than=older_than,
|
||||||
|
newer_than=newer_than,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/following")
|
@app.route("/following")
|
||||||
def following():
|
def following():
|
||||||
|
q = {"box": Box.OUTBOX.value, "type": ActivityType.FOLLOW.value, "meta.undo": False}
|
||||||
|
|
||||||
if is_api_request():
|
if is_api_request():
|
||||||
return jsonify(
|
return jsonify(
|
||||||
**activitypub.build_ordered_collection(
|
**activitypub.build_ordered_collection(
|
||||||
DB.following,
|
DB.activities,
|
||||||
|
q=q,
|
||||||
cursor=request.args.get("cursor"),
|
cursor=request.args.get("cursor"),
|
||||||
map_func=lambda doc: doc["remote_actor"],
|
map_func=lambda doc: doc["activity"]["object"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
following = [
|
following, older_than, newer_than = paginated_query(DB.activities, q)
|
||||||
ACTOR_SERVICE.get(doc["remote_actor"]) for doc in DB.following.find(limit=50)
|
following = [ACTOR_SERVICE.get(doc["activity"]["object"]) for doc in following]
|
||||||
]
|
return render_template(
|
||||||
return render_template("following.html", following_data=following)
|
"following.html",
|
||||||
|
following_data=following,
|
||||||
|
older_than=older_than,
|
||||||
|
newer_than=newer_than,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/tags/<tag>")
|
@app.route("/tags/<tag>")
|
||||||
|
|
|
@ -5,13 +5,11 @@
|
||||||
<div id="container">
|
<div id="container">
|
||||||
{% include "header.html" %}
|
{% include "header.html" %}
|
||||||
<div id="admin">
|
<div id="admin">
|
||||||
<h3>Stats</h3>
|
<h3>Admin</h3>
|
||||||
<h4>DB</h4>
|
<h4>DB</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Inbox size: <strong>{{ inbox_size }}</strong></li>
|
<li>Inbox size: <strong>{{ inbox_size }}</strong></li>
|
||||||
<li>Outbox size: <strong>{{ outbox_size }}</strong></li>
|
<li>Outbox size: <strong>{{ outbox_size }}</strong></li>
|
||||||
<li>Object cache size: <strong>{{ object_cache_size }}</strong></li>
|
|
||||||
<li>Actor cache size: <strong>{{ actor_cache_size }}</strong></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<h4>Collections</h4>
|
<h4>Collections</h4>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -19,12 +17,6 @@
|
||||||
<li>following: <strong>{{ col_following }}</strong></li>
|
<li>following: <strong>{{ col_following }}</strong></li>
|
||||||
<li>liked: <strong>{{col_liked }}</strong></li>
|
<li>liked: <strong>{{col_liked }}</strong></li>
|
||||||
</ul>
|
</ul>
|
||||||
<h4>Known Instances</h4>
|
|
||||||
<ul>
|
|
||||||
{% for instance in instances %}
|
|
||||||
<li><a href="{{ instance.instance }}">{{ instance.instance }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
{{ utils.display_actor_inline(follower, size=80) }}
|
{{ utils.display_actor_inline(follower, size=80) }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{{ utils.display_pagination(older_than, newer_than) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block links %}{{ utils.display_pagination_links(older_than, newer_than) }}{% endblock %}
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
{{ utils.display_actor_inline(followed, size=80) }}
|
{{ utils.display_actor_inline(followed, size=80) }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{{ utils.display_pagination(older_than, newer_than) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block links %}{{ utils.display_pagination_links(older_than, newer_than) }}{% endblock %}
|
||||||
|
|
|
@ -21,11 +21,10 @@
|
||||||
<body>
|
<body>
|
||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
<ul id="admin-menu">
|
<ul id="admin-menu">
|
||||||
<li class="left"><span class="admin-title">Admin</span></li>
|
<li class="left"><a href="/admin" class="admin-title{% if request.path == "/admin" %} selected{% endif %}">Admin</a></li>
|
||||||
<li class="left"><a href="/admin/new"{% if request.path == "/admin/new" %} class="selected" {% endif %}>New</a></li>
|
<li class="left"><a href="/admin/new"{% if request.path == "/admin/new" %} class="selected" {% endif %}>New</a></li>
|
||||||
<li class="left"><a href="/admin/stream"{% if request.path == "/admin/stream" %} class="selected" {% endif %}>Stream</a></li>
|
<li class="left"><a href="/admin/stream"{% if request.path == "/admin/stream" %} class="selected" {% endif %}>Stream</a></li>
|
||||||
<li class="left"><a href="/admin/notifications"{% if request.path == "/admin/notifications" %} class="selected" {% endif %}>Notifications</a></li>
|
<li class="left"><a href="/admin/notifications"{% if request.path == "/admin/notifications" %} class="selected" {% endif %}>Notifications</a></li>
|
||||||
<li class="left"><a href="/admin/stats"{% if request.path == "/admin/stats" %} class="selected" {% endif %}>Stats</a></li>
|
|
||||||
<li class="right"><a href="/admin/logout">Logout</a></li>
|
<li class="right"><a href="/admin/logout">Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
Loading…
Reference in a new issue