예제 #1
0
def test_little_content_helper_simple():
    back = InMemBackend()
    ap.use_backend(back)

    content, tags = content_helper.parse_markdown("hello")
    assert content == "<p>hello</p>"
    assert tags == []
예제 #2
0
def api_new_question() -> _Response:
    source = _user_api_arg("content")
    if not source:
        raise ValueError("missing content")

    content, tags = parse_markdown(source)
    cc = [ID + "/followers"]

    for tag in tags:
        if tag["type"] == "Mention":
            cc.append(tag["href"])

    answers = []
    for i in range(4):
        a = _user_api_arg(f"answer{i}", default=None)
        if not a:
            break
        answers.append({
            "type": ap.ActivityType.NOTE.value,
            "name": a,
            "replies": {
                "type": ap.ActivityType.COLLECTION.value,
                "totalItems": 0
            },
        })

    open_for = int(_user_api_arg("open_for"))
    choices = {
        "endTime":
        ap.format_datetime(
            datetime.now(timezone.utc) + timedelta(minutes=open_for))
    }
    of = _user_api_arg("of")
    if of == "anyOf":
        choices["anyOf"] = answers
    else:
        choices["oneOf"] = answers

    raw_question = dict(
        attributedTo=MY_PERSON.id,
        cc=list(set(cc)),
        to=[ap.AS_PUBLIC],
        content=content,
        tag=tags,
        source={
            "mediaType": "text/markdown",
            "content": source
        },
        inReplyTo=None,
        **choices,
    )

    question = ap.Question(**raw_question)
    create = question.build_create()
    create_id = post_to_outbox(create)

    Tasks.update_question_outbox(create_id, open_for)

    return _user_api_response(activity=create_id)
예제 #3
0
def test_little_content_helper_linkify():
    back = InMemBackend()
    ap.use_backend(back)

    content, tags = content_helper.parse_markdown("hello https://google.com")
    assert content.startswith("<p>hello <a")
    assert "https://google.com" in content
    assert tags == []
예제 #4
0
def test_little_content_helper_tag(_):
    back = InMemBackend()
    ap.use_backend(back)

    content, tags = content_helper.parse_markdown("hello #activitypub")
    base_url = back.base_url()
    assert content == (
        f'<p>hello <a href="{base_url}/tags/activitypub" class="mention hashtag" rel="tag">#'
        f"<span>activitypub</span></a></p>")
    assert tags == [{
        "href": f"{base_url}/tags/activitypub",
        "name": "#activitypub",
        "type": "Hashtag",
    }]
예제 #5
0
def test_little_content_helper_mention(_):
    back = InMemBackend()
    ap.use_backend(back)
    back.FETCH_MOCK["https://microblog.pub"] = {
        "id": "https://microblog.pub",
        "url": "https://microblog.pub",
    }

    content, tags = content_helper.parse_markdown("hello @[email protected]")
    assert content == (
        '<p>hello <span class="h-card"><a href="https://microblog.pub" class="u-url mention">@<span>dev</span></a>'
        "</span></p>")
    assert tags == [{
        "href": "https://microblog.pub",
        "name": "@[email protected]",
        "type": "Mention",
    }]
예제 #6
0
def api_new_note() -> _Response:
    # Basic Micropub (https://www.w3.org/TR/micropub/) query configuration support
    if request.method == "GET" and request.args.get("q") == "config":
        return jsonify({})
    elif request.method == "GET":
        abort(405)

    source = None
    summary = None
    place_tags = []

    # Basic Micropub (https://www.w3.org/TR/micropub/) "create" support
    is_micropub = False
    # First, check if the Micropub specific fields are present
    if (
        _user_api_arg("h", default=None) == "entry"
        or _user_api_arg("type", default=[None])[0] == "h-entry"
    ):
        is_micropub = True
        # Ensure the "create" scope is set
        if "jwt_payload" not in flask.g or "create" not in flask.g.jwt_payload["scope"]:
            abort(403)

        # Handle location sent via form-data
        # `geo:28.5,9.0,0.0`
        location = _user_api_arg("location", default="")
        if location.startswith("geo:"):
            slat, slng, *_ = location[4:].split(",")
            place_tags.append(
                {
                    "type": ap.ActivityType.PLACE.value,
                    "url": "",
                    "name": "",
                    "latitude": float(slat),
                    "longitude": float(slng),
                }
            )

        # Handle JSON microformats2 data
        if _user_api_arg("type", default=None):
            _logger.info(f"Micropub request: {request.json}")
            try:
                source = request.json["properties"]["content"][0]
            except (ValueError, KeyError):
                pass

            # Handle HTML
            if isinstance(source, dict):
                source = source.get("html")

            try:
                summary = request.json["properties"]["name"][0]
            except (ValueError, KeyError):
                pass

        # Try to parse the name as summary if the payload is POSTed using form-data
        if summary is None:
            summary = _user_api_arg("name", default=None)

    # This step will also parse content from Micropub request
    if source is None:
        source = _user_api_arg("content", default=None)

    if not source:
        raise ValueError("missing content")

    if summary is None:
        summary = _user_api_arg("summary", default="")

    if not place_tags:
        if _user_api_arg("location_lat", default=None):
            lat = float(_user_api_arg("location_lat"))
            lng = float(_user_api_arg("location_lng"))
            loc_name = _user_api_arg("location_name", default="")
            place_tags.append(
                {
                    "type": ap.ActivityType.PLACE.value,
                    "url": "",
                    "name": loc_name,
                    "latitude": lat,
                    "longitude": lng,
                }
            )

    # All the following fields are specific to the API (i.e. not Micropub related)
    _reply, reply = None, None
    try:
        _reply = _user_api_arg("reply")
    except ValueError:
        pass

    visibility = ap.Visibility[
        _user_api_arg("visibility", default=ap.Visibility.PUBLIC.name)
    ]

    content, tags = parse_markdown(source)

    # Check for custom emojis
    tags = tags + emojis.tags(content) + place_tags

    to: List[str] = []
    cc: List[str] = []

    if visibility == ap.Visibility.PUBLIC:
        to = [ap.AS_PUBLIC]
        cc = [ID + "/followers"]
    elif visibility == ap.Visibility.UNLISTED:
        to = [ID + "/followers"]
        cc = [ap.AS_PUBLIC]
    elif visibility == ap.Visibility.FOLLOWERS_ONLY:
        to = [ID + "/followers"]
        cc = []

    if _reply:
        reply = ap.fetch_remote_activity(_reply)
        if visibility == ap.Visibility.DIRECT:
            to.append(reply.attributedTo)
        else:
            cc.append(reply.attributedTo)

    context = new_context(reply)

    for tag in tags:
        if tag["type"] == "Mention":
            to.append(tag["href"])

    raw_note = dict(
        attributedTo=MY_PERSON.id,
        cc=list(set(cc) - set([MY_PERSON.id])),
        to=list(set(to) - set([MY_PERSON.id])),
        summary=summary,
        content=content,
        tag=tags,
        source={"mediaType": "text/markdown", "content": source},
        inReplyTo=reply.id if reply else None,
        context=context,
    )

    if request.files:
        for f in request.files.keys():
            if not request.files[f].filename:
                continue

            file = request.files[f]
            rfilename = secure_filename(file.filename)
            with BytesIO() as buf:
                file.save(buf)
                oid = MEDIA_CACHE.save_upload(buf, rfilename)
            mtype = mimetypes.guess_type(rfilename)[0]

            raw_note["attachment"] = [
                {
                    "mediaType": mtype,
                    "name": _user_api_arg("file_description", default=rfilename),
                    "type": "Document",
                    "url": f"{BASE_URL}/uploads/{oid}/{rfilename}",
                }
            ]

    note = ap.Note(**raw_note)
    create = note.build_create()
    create_id = post_to_outbox(create)

    # Return a 201 with the note URL in the Location header if this was a Micropub request
    if is_micropub:
        resp = flask.Response("", headers={"Location": create_id})
        resp.status_code = 201
        return resp

    return _user_api_response(activity=create_id)
예제 #7
0
def api_new_note() -> _Response:
    source = _user_api_arg("content")
    if not source:
        raise ValueError("missing content")

    _reply, reply = None, None
    try:
        _reply = _user_api_arg("reply")
    except ValueError:
        pass

    visibility = ap.Visibility[_user_api_arg(
        "visibility", default=ap.Visibility.PUBLIC.name)]

    content, tags = parse_markdown(source)

    to: List[str] = []
    cc: List[str] = []

    if visibility == ap.Visibility.PUBLIC:
        to = [ap.AS_PUBLIC]
        cc = [ID + "/followers"]
    elif visibility == ap.Visibility.UNLISTED:
        to = [ID + "/followers"]
        cc = [ap.AS_PUBLIC]
    elif visibility == ap.Visibility.FOLLOWERS_ONLY:
        to = [ID + "/followers"]
        cc = []

    if _reply:
        reply = ap.fetch_remote_activity(_reply)
        if visibility == ap.Visibility.DIRECT:
            to.append(reply.attributedTo)
        else:
            cc.append(reply.attributedTo)

    for tag in tags:
        if tag["type"] == "Mention":
            if visibility == ap.Visibility.DIRECT:
                to.append(tag["href"])
            else:
                cc.append(tag["href"])

    raw_note = dict(
        attributedTo=MY_PERSON.id,
        cc=list(set(cc)),
        to=list(set(to)),
        content=content,
        tag=tags,
        source={
            "mediaType": "text/markdown",
            "content": source
        },
        inReplyTo=reply.id if reply else None,
    )

    if "file" in request.files and request.files["file"].filename:
        file = request.files["file"]
        rfilename = secure_filename(file.filename)
        with BytesIO() as buf:
            file.save(buf)
            oid = MEDIA_CACHE.save_upload(buf, rfilename)
        mtype = mimetypes.guess_type(rfilename)[0]

        raw_note["attachment"] = [{
            "mediaType":
            mtype,
            "name":
            rfilename,
            "type":
            "Document",
            "url":
            f"{BASE_URL}/uploads/{oid}/{rfilename}",
        }]

    note = ap.Note(**raw_note)
    create = note.build_create()
    create_id = post_to_outbox(create)

    return _user_api_response(activity=create_id)