kopia lustrzana https://github.com/Aonrud/ila-microblog.pub
				
				
				
			
		
			
				
	
	
		
			206 wiersze
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			206 wiersze
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
| import base64
 | |
| import hashlib
 | |
| from datetime import datetime
 | |
| from typing import Any
 | |
| 
 | |
| import pydantic
 | |
| from dateutil.parser import isoparse
 | |
| from markdown import markdown
 | |
| 
 | |
| from app import activitypub as ap
 | |
| from app.actor import LOCAL_ACTOR
 | |
| from app.actor import Actor
 | |
| from app.actor import RemoteActor
 | |
| from app.utils import opengraph
 | |
| 
 | |
| 
 | |
| class Object:
 | |
|     @property
 | |
|     def is_from_db(self) -> bool:
 | |
|         return False
 | |
| 
 | |
|     @property
 | |
|     def ap_type(self) -> str:
 | |
|         return self.ap_object["type"]
 | |
| 
 | |
|     @property
 | |
|     def ap_object(self) -> ap.RawObject:
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     @property
 | |
|     def ap_id(self) -> str:
 | |
|         return ap.get_id(self.ap_object["id"])
 | |
| 
 | |
|     @property
 | |
|     def ap_actor_id(self) -> str:
 | |
|         return ap.get_actor_id(self.ap_object)
 | |
| 
 | |
|     @property
 | |
|     def ap_published_at(self) -> datetime | None:
 | |
|         # TODO: default to None? or now()?
 | |
|         if "published" in self.ap_object:
 | |
|             return isoparse(self.ap_object["published"])
 | |
|         elif "created" in self.ap_object:
 | |
|             return isoparse(self.ap_object["created"])
 | |
|         return None
 | |
| 
 | |
|     @property
 | |
|     def actor(self) -> Actor:
 | |
|         raise NotImplementedError()
 | |
| 
 | |
|     @property
 | |
|     def visibility(self) -> ap.VisibilityEnum:
 | |
|         return ap.object_visibility(self.ap_object)
 | |
| 
 | |
|     @property
 | |
|     def ap_context(self) -> str | None:
 | |
|         return self.ap_object.get("context") or self.ap_object.get("conversation")
 | |
| 
 | |
|     @property
 | |
|     def sensitive(self) -> bool:
 | |
|         return self.ap_object.get("sensitive", False)
 | |
| 
 | |
|     @property
 | |
|     def attachments(self) -> list["Attachment"]:
 | |
|         attachments = []
 | |
|         for obj in self.ap_object.get("attachment", []):
 | |
|             proxied_url = _proxied_url(obj["url"])
 | |
|             attachments.append(
 | |
|                 Attachment.parse_obj(
 | |
|                     {
 | |
|                         "proxiedUrl": proxied_url,
 | |
|                         "resizedUrl": proxied_url + "/740"
 | |
|                         if obj["mediaType"].startswith("image")
 | |
|                         else None,
 | |
|                         **obj,
 | |
|                     }
 | |
|                 )
 | |
|             )
 | |
| 
 | |
|         # Also add any video Link (for PeerTube compat)
 | |
|         if self.ap_type == "Video":
 | |
|             for link in ap.as_list(self.ap_object.get("url", [])):
 | |
|                 if (isinstance(link, dict)) and link.get("type") == "Link":
 | |
|                     if link.get("mediaType", "").startswith("video"):
 | |
|                         proxied_url = _proxied_url(link["href"])
 | |
|                         attachments.append(
 | |
|                             Attachment(
 | |
|                                 type="Video",
 | |
|                                 mediaType=link["mediaType"],
 | |
|                                 url=link["href"],
 | |
|                                 proxiedUrl=proxied_url,
 | |
|                             )
 | |
|                         )
 | |
|                         break
 | |
| 
 | |
|         return attachments
 | |
| 
 | |
|     @property
 | |
|     def url(self) -> str | None:
 | |
|         obj_url = self.ap_object.get("url")
 | |
|         if isinstance(obj_url, str):
 | |
|             return obj_url
 | |
|         elif obj_url:
 | |
|             for u in ap.as_list(obj_url):
 | |
|                 if u["mediaType"] == "text/html":
 | |
|                     return u["href"]
 | |
| 
 | |
|         return None
 | |
| 
 | |
|     @property
 | |
|     def content(self) -> str | None:
 | |
|         content = self.ap_object.get("content")
 | |
|         if not content:
 | |
|             return None
 | |
| 
 | |
|         # PeerTube returns the content as markdown
 | |
|         if self.ap_object.get("mediaType") == "text/markdown":
 | |
|             return markdown(content, extensions=["mdx_linkify"])
 | |
| 
 | |
|         return content
 | |
| 
 | |
|     @property
 | |
|     def permalink_id(self) -> str:
 | |
|         return (
 | |
|             "permalink-"
 | |
|             + hashlib.md5(
 | |
|                 self.ap_id.encode(),
 | |
|                 usedforsecurity=False,
 | |
|             ).hexdigest()
 | |
|         )
 | |
| 
 | |
|     @property
 | |
|     def activity_object_ap_id(self) -> str | None:
 | |
|         if "object" in self.ap_object:
 | |
|             return ap.get_id(self.ap_object["object"])
 | |
| 
 | |
|         return None
 | |
| 
 | |
|     @property
 | |
|     def in_reply_to(self) -> str | None:
 | |
|         return self.ap_object.get("inReplyTo")
 | |
| 
 | |
| 
 | |
| def _to_camel(string: str) -> str:
 | |
|     cased = "".join(word.capitalize() for word in string.split("_"))
 | |
|     return cased[0:1].lower() + cased[1:]
 | |
| 
 | |
| 
 | |
| class BaseModel(pydantic.BaseModel):
 | |
|     class Config:
 | |
|         alias_generator = _to_camel
 | |
| 
 | |
| 
 | |
| def _proxied_url(url: str) -> str:
 | |
|     return "/proxy/media/" + base64.urlsafe_b64encode(url.encode()).decode()
 | |
| 
 | |
| 
 | |
| class Attachment(BaseModel):
 | |
|     type: str
 | |
|     media_type: str
 | |
|     name: str | None
 | |
|     url: str
 | |
| 
 | |
|     # Extra fields for the templates
 | |
|     proxied_url: str
 | |
|     resized_url: str | None = None
 | |
| 
 | |
| 
 | |
| class RemoteObject(Object):
 | |
|     def __init__(self, raw_object: ap.RawObject, actor: Actor | None = None):
 | |
|         self._raw_object = raw_object
 | |
|         self._actor: Actor
 | |
| 
 | |
|         # Pre-fetch the actor
 | |
|         actor_id = ap.get_actor_id(raw_object)
 | |
|         if actor_id == LOCAL_ACTOR.ap_id:
 | |
|             self._actor = LOCAL_ACTOR
 | |
|         elif actor:
 | |
|             if actor.ap_id != actor_id:
 | |
|                 raise ValueError(
 | |
|                     f"Invalid actor, got {actor.ap_id}, " f"expected {actor_id}"
 | |
|                 )
 | |
|             self._actor = actor
 | |
|         else:
 | |
|             self._actor = RemoteActor(
 | |
|                 ap_actor=ap.fetch(ap.get_actor_id(raw_object)),
 | |
|             )
 | |
| 
 | |
|         self._og_meta = None
 | |
|         if self.ap_type == "Note":
 | |
|             self._og_meta = opengraph.og_meta_from_note(self._raw_object)
 | |
| 
 | |
|     @property
 | |
|     def og_meta(self) -> list[dict[str, Any]] | None:
 | |
|         if self._og_meta:
 | |
|             return [og_meta.dict() for og_meta in self._og_meta]
 | |
|         return None
 | |
| 
 | |
|     @property
 | |
|     def ap_object(self) -> ap.RawObject:
 | |
|         return self._raw_object
 | |
| 
 | |
|     @property
 | |
|     def actor(self) -> Actor:
 | |
|         return self._actor
 |