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)
def _add_answers_to_question(raw_doc: Dict[str, Any]) -> None: activity = raw_doc["activity"] if (ap._has_type(activity["type"], ap.ActivityType.CREATE) and "object" in activity and ap._has_type( activity["object"]["type"], ap.ActivityType.QUESTION)): for choice in activity["object"].get("oneOf", activity["object"].get("anyOf")): choice["replies"] = { "type": ap.ActivityType.COLLECTION.value, "totalItems": raw_doc["meta"].get("question_answers", {}).get(_answer_key(choice["name"]), 0), } now = datetime.now(timezone.utc) if format_datetime(now) >= activity["object"]["endTime"]: activity["object"]["closed"] = activity["object"]["endTime"]
def migrate(self) -> None: for data in find_activities({"meta.published": {"$exists": False}}): try: raw = data["activity"] # If the activity has its own `published` field, we'll use it if "published" in raw: published = raw["published"] else: # Otherwise, we take the date we received the activity as the published time published = ap.format_datetime(data["_id"].generation_time) # Set the field in the DB update_one_activity( {"_id": data["_id"]}, {"$set": {_meta(MetaKey.PUBLISHED): published}}, ) except Exception: logger.exception(f"failed to process activity {data!r}")
def _load_emojis(root_dir: Path, base_url: str) -> None: if EMOJIS: return for emoji in (root_dir / "static" / "emojis").iterdir(): mt = mimetypes.guess_type(emoji.name)[0] if mt and mt.startswith("image/"): name = emoji.name.split(".")[0] ap_emoji = dict( type=ap.ActivityType.EMOJI.value, name=f":{name}:", updated=ap.format_datetime(datetime.fromtimestamp(0.0).astimezone()), id=f"{base_url}/emoji/{name}", icon={ "mediaType": mt, "type": ap.ActivityType.IMAGE.value, "url": f"{base_url}/static/emojis/{emoji.name}", }, ) EMOJIS[emoji.name] = ap_emoji EMOJIS_BY_NAME[ap_emoji["name"]] = ap_emoji
def gtnow(dtstr): return ap.format_datetime(datetime.now(timezone.utc)) > dtstr
def now() -> str: return ap.format_datetime(datetime.now(timezone.utc))
def published_after(dt: datetime) -> _SubQuery: return flag(MetaKey.PUBLISHED, {"$gt": ap.format_datetime(dt)})