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.
|
||||
|
||||
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.
|
||||
|
||||
## Running your instance
|
||||
|
|
|
@ -14,19 +14,19 @@ from html2text import html2text
|
|||
import tasks
|
||||
from config import BASE_URL
|
||||
from config import DB
|
||||
from config import EXTRA_INBOXES
|
||||
from config import ID
|
||||
from config import ME
|
||||
from config import MEDIA_CACHE
|
||||
from config import USER_AGENT
|
||||
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 strtobool
|
||||
from little_boxes.activitypub import _to_list
|
||||
from little_boxes.backend import Backend
|
||||
from little_boxes.collection import parse_collection as ap_parse_collection
|
||||
from little_boxes.errors import Error
|
||||
from utils.media import Kind
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
134
app.py
134
app.py
|
@ -132,12 +132,23 @@ def inject_config():
|
|||
"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(
|
||||
microblogpub_version=VERSION,
|
||||
config=config,
|
||||
logged_in=session.get("logged_in", False),
|
||||
followers_count=DB.followers.count(),
|
||||
following_count=DB.following.count(),
|
||||
followers_count=DB.activities.count(followers_q),
|
||||
following_count=DB.activities.count(following_q),
|
||||
notes_count=notes_count,
|
||||
liked_count=liked_count,
|
||||
with_replies_count=with_replies_count,
|
||||
|
@ -499,7 +510,14 @@ def authorize_follow():
|
|||
actor = get_actor_url(request.form.get("profile"))
|
||||
if not actor:
|
||||
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")
|
||||
|
||||
follow = ap.Follow(actor=MY_PERSON.id, object=actor)
|
||||
|
@ -589,6 +607,38 @@ def tmp_migrate3():
|
|||
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"):
|
||||
older_than = newer_than = None
|
||||
query_sort = -1
|
||||
|
@ -765,7 +815,7 @@ def nodeinfo():
|
|||
q = {
|
||||
"box": Box.OUTBOX.value,
|
||||
"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(
|
||||
headers={
|
||||
|
@ -781,10 +831,7 @@ def nodeinfo():
|
|||
"protocols": ["activitypub"],
|
||||
"services": {"inbound": [], "outbound": []},
|
||||
"openRegistrations": False,
|
||||
"usage": {
|
||||
"users": {"total": 1},
|
||||
"localPosts": DB.activities.count(q),
|
||||
},
|
||||
"usage": {"users": {"total": 1}, "localPosts": DB.activities.count(q)},
|
||||
"metadata": {
|
||||
"sourceCode": "https://github.com/tsileo/microblog.pub",
|
||||
"nodeName": f"@{USERNAME}@{DOMAIN}",
|
||||
|
@ -895,7 +942,7 @@ def outbox():
|
|||
q = {
|
||||
"box": Box.OUTBOX.value,
|
||||
"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(
|
||||
**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
|
||||
def admin_stats():
|
||||
def admin():
|
||||
q = {
|
||||
"meta.deleted": False,
|
||||
"meta.undo": False,
|
||||
|
@ -1084,11 +1131,21 @@ def admin_stats():
|
|||
instances=list(DB.instances.find()),
|
||||
inbox_size=DB.activities.count({"box": Box.INBOX.value}),
|
||||
outbox_size=DB.activities.count({"box": Box.OUTBOX.value}),
|
||||
object_cache_size=0,
|
||||
actor_cache_size=0,
|
||||
col_liked=col_liked,
|
||||
col_followers=DB.followers.count(),
|
||||
col_following=DB.following.count(),
|
||||
col_followers=DB.activities.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():
|
||||
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:
|
||||
return _user_api_response(activity=existing["activity"]["id"])
|
||||
|
||||
|
@ -1464,36 +1528,50 @@ def api_follow():
|
|||
|
||||
@app.route("/followers")
|
||||
def followers():
|
||||
q = {"box": Box.INBOX.value, "type": ActivityType.FOLLOW.value, "meta.undo": False}
|
||||
|
||||
if is_api_request():
|
||||
return jsonify(
|
||||
**activitypub.build_ordered_collection(
|
||||
DB.followers,
|
||||
DB.activities,
|
||||
q=q,
|
||||
cursor=request.args.get("cursor"),
|
||||
map_func=lambda doc: doc["remote_actor"],
|
||||
map_func=lambda doc: doc["activity"]["object"],
|
||||
)
|
||||
)
|
||||
|
||||
followers = [
|
||||
ACTOR_SERVICE.get(doc["remote_actor"]) for doc in DB.followers.find(limit=50)
|
||||
]
|
||||
return render_template("followers.html", followers_data=followers)
|
||||
followers, older_than, newer_than = paginated_query(DB.activities, q)
|
||||
followers = [ACTOR_SERVICE.get(doc["activity"]["object"]) for doc in followers]
|
||||
return render_template(
|
||||
"followers.html",
|
||||
followers_data=followers,
|
||||
older_than=older_than,
|
||||
newer_than=newer_than,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/following")
|
||||
def following():
|
||||
q = {"box": Box.OUTBOX.value, "type": ActivityType.FOLLOW.value, "meta.undo": False}
|
||||
|
||||
if is_api_request():
|
||||
return jsonify(
|
||||
**activitypub.build_ordered_collection(
|
||||
DB.following,
|
||||
DB.activities,
|
||||
q=q,
|
||||
cursor=request.args.get("cursor"),
|
||||
map_func=lambda doc: doc["remote_actor"],
|
||||
map_func=lambda doc: doc["activity"]["object"],
|
||||
)
|
||||
)
|
||||
|
||||
following = [
|
||||
ACTOR_SERVICE.get(doc["remote_actor"]) for doc in DB.following.find(limit=50)
|
||||
]
|
||||
return render_template("following.html", following_data=following)
|
||||
following, older_than, newer_than = paginated_query(DB.activities, q)
|
||||
following = [ACTOR_SERVICE.get(doc["activity"]["object"]) for doc in following]
|
||||
return render_template(
|
||||
"following.html",
|
||||
following_data=following,
|
||||
older_than=older_than,
|
||||
newer_than=newer_than,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/tags/<tag>")
|
||||
|
|
|
@ -5,13 +5,11 @@
|
|||
<div id="container">
|
||||
{% include "header.html" %}
|
||||
<div id="admin">
|
||||
<h3>Stats</h3>
|
||||
<h3>Admin</h3>
|
||||
<h4>DB</h4>
|
||||
<ul>
|
||||
<li>Inbox size: <strong>{{ inbox_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>
|
||||
<h4>Collections</h4>
|
||||
<ul>
|
||||
|
@ -19,12 +17,6 @@
|
|||
<li>following: <strong>{{ col_following }}</strong></li>
|
||||
<li>liked: <strong>{{col_liked }}</strong></li>
|
||||
</ul>
|
||||
<h4>Known Instances</h4>
|
||||
<ul>
|
||||
{% for instance in instances %}
|
||||
<li><a href="{{ instance.instance }}">{{ instance.instance }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
{{ utils.display_actor_inline(follower, size=80) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ utils.display_pagination(older_than, newer_than) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block links %}{{ utils.display_pagination_links(older_than, newer_than) }}{% endblock %}
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
{{ utils.display_actor_inline(followed, size=80) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ utils.display_pagination(older_than, newer_than) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block links %}{{ utils.display_pagination_links(older_than, newer_than) }}{% endblock %}
|
||||
|
|
|
@ -21,11 +21,10 @@
|
|||
<body>
|
||||
{% if logged_in %}
|
||||
<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/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/stats"{% if request.path == "/admin/stats" %} class="selected" {% endif %}>Stats</a></li>
|
||||
<li class="right"><a href="/admin/logout">Logout</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in a new issue