def api_new_question() -> _Response: source = _user_api_arg("content") if not source: raise ValueError("missing content") content, tags = parse_markdown(source) tags = tags + emojis.tags(content) 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], context=new_context(), 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)
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)