~fabrixxm/activist

e74dc47faf1bef0da26ed3c9d3d964de5444b0b5 — fabrixxm a month ago ad611fe master
Add 'unfollow' activity

add debug activity view
3 files changed, 81 insertions(+), 31 deletions(-)

M activist/activities.py
M activist/web.py
M wsgi.py
M activist/activities.py => activist/activities.py +40 -30
@@ 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'])

M activist/web.py => activist/web.py +40 -0
@@ 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)

M wsgi.py => wsgi.py +1 -1
@@ 1,2 1,2 @@
from activist import web
from activist.app import app
\ No newline at end of file
from activist.app import app