diff --git a/mautrix_facebook/portal.py b/mautrix_facebook/portal.py index 298d82c556c9504de87e0a6c257823fe1fe13ded..2c7c6c9466723d1e5b395bd84a4583307862cc92 100644 --- a/mautrix_facebook/portal.py +++ b/mautrix_facebook/portal.py @@ -14,10 +14,12 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. from typing import Dict, Optional, Tuple, Union, TYPE_CHECKING -import aiohttp import asyncio import logging +from yarl import URL +import aiohttp + from fbchat import (ThreadType, Thread, User as FBUser, Group as FBGroup, Page as FBPage, Message as FBMessage) from mautrix.types import RoomID, EventType, ContentURI, MessageEventContent, EventID @@ -47,27 +49,29 @@ class Portal: mxid: Optional[RoomID] name: str - photo: str + photo_id: str avatar_uri: ContentURI - messages_by_fbid: Dict[str, EventID] - messages_by_mxid: Dict[EventID, str] + messages_by_fbid: Dict[str, Optional[EventID]] + messages_by_mxid: Dict[EventID, Optional[str]] _main_intent: Optional[IntentAPI] + _create_room_lock: asyncio.Lock def __init__(self, fbid: str, fb_receiver: str, fb_type: ThreadType, mxid: Optional[RoomID] = None, - name: str = "", photo: str = "", avatar_uri: ContentURI = "") -> None: + name: str = "", photo_id: str = "", avatar_uri: ContentURI = "") -> None: self.fbid = fbid self.fb_receiver = fb_receiver self.fb_type = fb_type self.mxid = mxid self.name = name - self.photo = photo + self.photo_id = photo_id self.avatar_uri = avatar_uri self._main_intent = None + self._create_room_lock = asyncio.Lock() self.messages_by_fbid = {} self.messages_by_mxid = {} @@ -85,7 +89,7 @@ class Portal: "fb_receiver": self.fb_receiver, "mxid": self.mxid, "name": self.name, - "photo": self.photo, + "photo_id": self.photo_id, "avatar_uri": self.avatar_uri, } @@ -93,7 +97,7 @@ class Portal: def from_dict(cls, data: Dict[str, str]) -> 'Portal': return cls(fbid=data["fbid"], fb_receiver=data["fb_receiver"], fb_type=ThreadType(data["fb_type"]), mxid=RoomID(data["mxid"]), - name=data["name"], photo=data["photo"], + name=data["name"], photo_id=data["photo_id"], avatar_uri=ContentURI(data["avatar_uri"])) @property @@ -129,20 +133,31 @@ class Portal: loop=self.loop) return info + @staticmethod + def _get_photo_id(url: str) -> str: + path = URL(url).path + return path[path.rfind("/") + 1:] + + @staticmethod + async def _reupload_photo(url: str, client: IntentAPI) -> ContentURI: + async with aiohttp.ClientSession() as session: + resp = await session.get(url) + data = await resp.read() + return await client.upload_media(data) + async def _update_name(self, name: str) -> None: if self.name != name: self.name = name if self.mxid and not self.is_direct: await self.main_intent.set_room_name(self.mxid, self.name) - async def _update_photo(self, photo: str) -> None: - if self.photo != photo or len(self.avatar_uri) == 0: - self.photo = photo + async def _update_photo(self, photo_url: str) -> None: + photo_id = self._get_photo_id(photo_url) + print(photo_id, self.photo_id) + if self.photo_id != photo_id or len(self.avatar_uri) == 0: + self.photo_id = photo_id if self.mxid and not self.is_direct: - async with aiohttp.ClientSession() as session: - resp = await session.get(self.photo) - data = await resp.read() - self.avatar_uri = await self.main_intent.upload_media(data) + self.avatar_uri = await self._reupload_photo(photo_url, self.main_intent) await self.main_intent.set_room_avatar(self.mxid, self.avatar_uri) async def _update_participants(self, source: 'u.User', info: ThreadClass) -> None: @@ -158,9 +173,22 @@ class Portal: await asyncio.gather(*[puppet.intent.ensure_joined(self.mxid) for puppet in puppets.values()]) - async def create_matrix_room(self, source: 'u.User', info: Optional[Thread] = None) -> RoomID: + async def _update_matrix_room(self, source: 'u.User', + info: Optional[ThreadClass] = None) -> None: + await self.main_intent.invite_user(self.mxid, source.mxid) + + async def create_matrix_room(self, source: 'u.User', info: Optional[ThreadClass] = None + ) -> RoomID: + if self.mxid: + await self._update_matrix_room(source, info) + return self.mxid + async with self._create_room_lock: + await self._create_matrix_room(source, info) + + async def _create_matrix_room(self, source: 'u.User', info: Optional[ThreadClass] = None + ) -> RoomID: if self.mxid: - await self.main_intent.invite_user(self.mxid, source.mxid) + await self._update_matrix_room(source, info) return self.mxid info = await self.update_info(source=source, info=info) @@ -176,7 +204,7 @@ class Portal: invitees=[source.mxid]) self.log.debug(f"Matrix room created: {self.mxid}") if not self.mxid: - raise Exception("Failed to create room") + raise Exception("Failed to create room: no mxid required") self.by_mxid[self.mxid] = self if not self.is_direct: await self._update_participants(source, info) diff --git a/mautrix_facebook/puppet.py b/mautrix_facebook/puppet.py index 31bfc23fdfc3bd36e535138abbc84f2d1d8342e1..bc1aad60b944437023f239fea7ec44d131863da8 100644 --- a/mautrix_facebook/puppet.py +++ b/mautrix_facebook/puppet.py @@ -23,7 +23,7 @@ from mautrix.types import UserID, ContentURI from mautrix.appservice import AppService, IntentAPI from .config import Config -from . import user as u +from . import user as u, portal as p if TYPE_CHECKING: from .context import Context @@ -41,15 +41,15 @@ class Puppet: fbid: str name: str - photo: str + photo_id: str avatar_uri: ContentURI intent: IntentAPI - def __init__(self, fbid: str, name: str = "", photo: str = "", avatar_uri: ContentURI = ""): + def __init__(self, fbid: str, name: str = "", photo_id: str = "", avatar_uri: ContentURI = ""): self.fbid = fbid self.name = name - self.photo = photo + self.photo_id = photo_id self.avatar_uri = avatar_uri self.mxid = self.get_mxid_from_id(fbid) @@ -61,13 +61,13 @@ class Puppet: return { "fbid": self.fbid, "name": self.name, - "photo": self.photo, + "photo_id": self.photo_id, "avatar_uri": self.avatar_uri, } @classmethod def from_dict(cls, data: Dict[str, str]) -> 'Puppet': - return cls(fbid=data["fbid"], name=data["name"], photo=data["photo"], + return cls(fbid=data["fbid"], name=data["name"], photo_id=data["photo_id"], avatar_uri=ContentURI(data["avatar_uri"])) async def update_info(self, source: Optional['u.User'] = None, info: Optional[FBUser] = None @@ -80,17 +80,17 @@ class Puppet: async def _update_name(self, info: FBUser) -> None: # TODO more precise name control + print(info.name, self.name) if info.name != self.name: self.name = info.name await self.intent.set_displayname(self.name) - async def _update_photo(self, photo: str) -> None: - if photo != self.photo or len(self.avatar_uri) == 0: - self.photo = photo - async with aiohttp.ClientSession() as session: - resp = await session.get(self.photo) - data = await resp.read() - self.avatar_uri = await self.intent.upload_media(data) + async def _update_photo(self, photo_url: str) -> None: + photo_id = p.Portal._get_photo_id(photo_url) + print(photo_id, self.photo_id) + if photo_id != self.photo_id or len(self.avatar_uri) == 0: + self.photo_id = photo_id + self.avatar_uri = await p.Portal._reupload_photo(photo_url, self.intent) await self.intent.set_avatar_url(self.avatar_uri) @classmethod diff --git a/mautrix_facebook/user.py b/mautrix_facebook/user.py index d95d859512fe4a349cf2c0218d8b0e782a25d278..5f4d738904ee857885a030c438d3c2fbf4b579d5 100644 --- a/mautrix_facebook/user.py +++ b/mautrix_facebook/user.py @@ -32,7 +32,6 @@ if TYPE_CHECKING: config: Config - GREEN = "\u001b[32m" YELLOW = "\u001b[33m" MAGENTA = "\u001b[35m" @@ -106,6 +105,10 @@ class User(Client): async def post_login(self) -> None: await self.sync_threads() + self.log.debug("Updating own puppet info") + own_info = (await self.fetchUserInfo(self.uid))[self.uid] + puppet = pu.Puppet.get(self.uid, create=True) + await puppet.update_info(source=self, info=own_info) async def sync_threads(self) -> None: try: @@ -171,7 +174,7 @@ class User(Client): async def onMessage(self, mid: str = None, author_id: str = None, message: str = None, message_object: Message = None, thread_id: str = None, thread_type: ThreadType = ThreadType.USER, ts: int = None, - metadata: Any = None, msg: Any = None): + metadata: Any = None, msg: Any = None) -> None: """ Called when the client is listening, and somebody sends a message