@@ 30,33 30,37 @@ def _do(name:str, obj:dict, **props):
tasks.publish(activity['id'])
+def _undo(name:str, obj:dict):
+ # get last Activity we made on this object
+ act = db.Object.query().select()\
+ .where(
+ actor = settings.USER_URL,
+ type = name,
+ object = obj['id']
+ )\
+ .order_by("published DESC")\
+ .get()
+
+ if act is None :
+ raise Exception(f"Cannot find '{name}' to undo")
+ _do("Undo", act.data)
+
+
+ # tombstone the activity
+ tombstonedata = activitystream.tombstone(act.data)
+ tact = db.Object.from_data(tombstonedata)
+ tact.data = db.JSON(**tombstonedata)
+ tact.save()
+
+
def _toggle(name:str, list:str, obj:dict, **props) -> bool:
"""Add a new Action 'name' or an action 'Undo' for a previous action 'name' on same object
-
+
return True if new action is added, False on Undo
"""
if db.List.contains(list, obj['id']):
- # get last Activity we made on this object
- act = db.Object.query().select()\
- .where(
- actor = settings.USER_URL,
- type = name,
- object = obj['id']
- )\
- .order_by("published DESC")\
- .get()
-
- if act is None :
- raise Exception(f"Cannot find '{name}' to undo")
- _do("Undo", act.data)
+ _undo(name, obj)
db.List.remove(list, obj['id'])
-
- # tombstone the activity
- tombstonedata = activitystream.tombstone(act.data)
- tact = db.Object.from_data(tombstonedata)
- tact.data = db.JSON(**tombstonedata)
- tact.save()
-
return False
else:
_do(name, obj, **props)
@@ 96,7 100,7 @@ def parse_text(text:str) -> tuple[str,list[db.Object],list[str]]:
if not cached:
activitypub.save_recursive(odata)
obj = db.Object.from_data(odata)
- actors[m] = obj
+ actors[m] = obj
# extract tags
match = TAGS_RE.findall(text)
@@ 135,7 139,7 @@ def like(obj:dict, **props):
def announce(obj:dict, **props):
added = _toggle("Announce", 'announced', obj, **props)
-
+
# announced things are published in 'Microblog', "posts" list
if added:
db.List.append('posts', obj['id'])
@@ 198,7 202,7 @@ def new_object(fnc:Callable, listid:str = "", **props) -> str:
html, mentions, tags = parse_text(text)
props['content'] = html
props['source'] = {'content': text, 'mediaType': 'text/markdown'}
-
+
for actor in mentions:
objtags.append({
"href": actor.id,
@@ 219,15 223,15 @@ def new_object(fnc:Callable, listid:str = "", **props) -> str:
props['to'] = list(set(props['to']))
obj = fnc(**props)
-
+
if objtags:
obj['tag'] = objtags
activitypub.save_recursive(obj)
-
+
activity = activitystream.activity("Create", obj)
activitypub.save_recursive(activity)
-
+
db.List.append("outbox", activity['id'])
if listid != "" and consts.AS_PUBLIC in obj['to']:
db.List.append(listid, obj['id'])
@@ 265,7 269,7 @@ def article(title:str, text:str, **props) -> str:
"""
Create an Article with 'title' and 'text' and publish it
"""
- return new_object(activitystream.new_article, 'homepage', name=title, content=text)
+ return new_object(activitystream.new_article, 'homepage', name=title, content=text)
def share_page(url:str, title:Optional[str] = None) -> str:
@@ 277,7 281,7 @@ def share_page(url:str, title:Optional[str] = None) -> str:
activity = activitystream.activity("Announce", obj)
activitypub.save_recursive(activity)
-
+
db.List.append("outbox", activity['id'])
db.List.append("homepage", obj['id'])
db.List.append("network", obj['id'])
@@ 288,7 292,13 @@ def share_page(url:str, title:Optional[str] = None) -> str:
def follow(actordata:dict):
activity = activitystream.activity("Follow", actordata, to=[actordata['id']])
activitypub.save_recursive(activity)
-
+
db.List.append("outbox", activity['id'])
db.List.append("pending", actordata['id']) # pending 'accept' or 'reject'
tasks.publish(activity['id'])
+
+def unfollow(actordata:dict):
+ _undo("Follow", actordata)
+
+ db.List.remove("pending", actordata['id'])
+ db.List.remove("following", actordata['id'])
@@ 443,6 443,26 @@ def thread(hash:str = ""):
return render_template("thread.html.j2", data=objects, object=selected_obj)
+@app.route("/action", methods=['GET'])
+@login_required
+@db.session
+def action_tests():
+ return """
+<form method='post' action='/action'>
+<input type="hidden" name="redirect" value="/action">
+<input name="obj" placeholder="Object id">
+<select name="action">
+ <option value="like">like</option>
+ <option value="reshare">reshare</option>
+ <option value="reply">reply</option>
+ <option value="follow">follow</option>
+ <option value="unfollow">unfollow</option>
+ <option value="block">block</option>
+ <option value="resend-follow">resend-follow</option>
+ <option value="test">test</option>
+</select>
+<button type="submit">Send</button>
+"""
@app.route("/action", methods=['POST'])
@login_required
@@ 502,6 522,26 @@ def action():
if not isincontactspage:
announcer.announce(returnhtml, "contacts")
triggers.append(f"actor-{obj.hash}")
+
+ case "unfollow":
+ activities.unfollow(obj.data)
+ returnhtml = components.actor(actor=obj)
+ lists = [db.List.fqid('following'), db.List.fqid('followers'), db.List.fqid('pending')]
+ isincontactspage = db.List.query().select().where(object = obj.id, id = lists).as_list('count(id)')[0] > 0
+ if not isincontactspage:
+ announcer.announce(returnhtml, "contacts")
+ triggers.append(f"actor-{obj.hash}")
+
+ case "block":
+ raise NotImplementedError()
+
+ case "resend-follow":
+ # re-queue a follow activity to 'obj' to be sent
+ follow = db.Object.query().select().where(type="Follow", object=obj.id).get()
+ if follow is None:
+ abort(400, "User was never sent a follow before")
+ activitypub.tasks.publish(follow.id)
+
case "test":
# triggers.append(f"actions-{obj.hash}")
# returnhtml = components.actions(obj=obj)