Skip to content
Snippets Groups Projects
Commit 4af855c6 authored by Tulir Asokan's avatar Tulir Asokan :cat2:
Browse files

Update fbchat-asyncio to disable infinite reconnect loops

parent 55f26402
No related branches found
No related tags found
No related merge requests found
...@@ -61,6 +61,8 @@ class User(BaseUser): ...@@ -61,6 +61,8 @@ class User(BaseUser):
_community_helper: CommunityHelper _community_helper: CommunityHelper
_community_id: Optional[CommunityID] _community_id: Optional[CommunityID]
_handlers: Dict[Type[fbchat.Event], Callable[[Any], Awaitable[None]]]
def __init__(self, mxid: UserID, session: Optional[Dict[str, str]] = None, def __init__(self, mxid: UserID, session: Optional[Dict[str, str]] = None,
notice_room: Optional[RoomID] = None, notice_room: Optional[RoomID] = None,
db_instance: Optional[DBUser] = None) -> None: db_instance: Optional[DBUser] = None) -> None:
...@@ -87,6 +89,22 @@ class User(BaseUser): ...@@ -87,6 +89,22 @@ class User(BaseUser):
self.listener = None self.listener = None
self.listen_task = None self.listen_task = None
self._handlers = {
fbchat.MessageEvent: self.on_message,
fbchat.MessageReplyEvent: self.on_message,
fbchat.TitleSet: self.on_title_change,
fbchat.UnsendEvent: self.on_message_unsent,
fbchat.ThreadsRead: self.on_message_seen,
fbchat.ReactionEvent: self.on_reaction,
fbchat.Presence: self.on_presence,
fbchat.Typing: self.on_typing,
fbchat.PeopleAdded: self.on_members_added,
fbchat.PersonRemoved: self.on_member_removed,
fbchat.Connect: self.on_connect,
fbchat.Disconnect: self.on_disconnect,
fbchat.Resync: self.on_resync,
}
@property @property
def is_connected(self) -> Optional[bool]: def is_connected(self) -> Optional[bool]:
return self._is_connected return self._is_connected
...@@ -382,16 +400,18 @@ class User(BaseUser): ...@@ -382,16 +400,18 @@ class User(BaseUser):
self.save() self.save()
return self.notice_room return self.notice_room
async def send_bridge_notice(self, text: str, edit: Optional[EventID] = None async def send_bridge_notice(self, text: str, edit: Optional[EventID] = None,
) -> Optional[EventID]: important: bool = False) -> Optional[EventID]:
event_id = None event_id = None
try: try:
content = TextMessageEventContent(msgtype=MessageType.NOTICE, body=text) self.log.debug("Sending bridge notice: %s", text)
content = TextMessageEventContent(body=text, msgtype=(MessageType.TEXT if important
else MessageType.NOTICE))
if edit: if edit:
content.set_edit(edit) content.set_edit(edit)
event_id = await self.az.intent.send_message(await self.get_notice_room(), content) event_id = await self.az.intent.send_message(await self.get_notice_room(), content)
except Exception: except Exception:
self.log.warning("Failed to send bridge notice '%s'", text, exc_info=True) self.log.warning("Failed to send bridge notice", exc_info=True)
return edit or event_id return edit or event_id
# region Facebook event handling # region Facebook event handling
...@@ -399,53 +419,49 @@ class User(BaseUser): ...@@ -399,53 +419,49 @@ class User(BaseUser):
async def try_listen(self) -> None: async def try_listen(self) -> None:
try: try:
await self.listen() await self.listen()
except Exception: except Exception as e:
self.is_connected = False self.is_connected = False
await self.send_bridge_notice("Fatal error in listener (see logs for more info)") if isinstance(e, fbchat.NotLoggedIn):
self.log.exception("Fatal error in listener") message = f"Disconnected from Facebook Messenger: {e}"
self.log.warning(message)
elif isinstance(e, fbchat.NotConnected):
message = f"Failed to connect to Facebook Messenger: {e}"
self.log.warning(message)
else:
message = "Fatal error in listener (see logs for more info)"
self.log.exception("Fatal error in listener")
await self.send_bridge_notice(message, important=True)
try: try:
self.listener.disconnect() self.listener.disconnect()
except Exception: except Exception:
self.log.debug("Error disconnecting listener after error", exc_info=True) self.log.debug("Error disconnecting listener after error", exc_info=True)
async def _handle_event(self, handler: Callable[[Any], Awaitable[None]], event: Any) -> None:
await self._sync_lock.wait("event")
try:
await handler(event)
except Exception:
self.log.exception(f"Failed to handle {type(event)} event from Facebook")
async def listen(self) -> None: async def listen(self) -> None:
if not self.listener: if not self.listener:
self.listener = fbchat.Listener(session=self.session, chat_on=True, foreground=False) self.listener = fbchat.Listener(session=self.session, chat_on=True, foreground=False)
handlers: Dict[Type[fbchat.Event], Callable[[Any], Awaitable[None]]] = {
fbchat.MessageEvent: self.on_message,
fbchat.MessageReplyEvent: self.on_message,
fbchat.TitleSet: self.on_title_change,
fbchat.UnsendEvent: self.on_message_unsent,
fbchat.ThreadsRead: self.on_message_seen,
fbchat.ReactionEvent: self.on_reaction,
fbchat.Presence: self.on_presence,
fbchat.Typing: self.on_typing,
fbchat.PeopleAdded: self.on_members_added,
fbchat.PersonRemoved: self.on_member_removed,
fbchat.Connect: self.on_connect,
fbchat.Disconnect: self.on_disconnect,
fbchat.Resync: self.on_resync,
}
self.log.debug("Starting fbchat listener") self.log.debug("Starting fbchat listener")
async for event in self.listener.listen(): async for event in self.listener.listen():
self.log.debug("Handling facebook event %s", event) await self._handle_event(event)
try:
handler = handlers[type(event)]
except KeyError:
self.log.debug(f"Received unknown event type {type(event)}")
else:
self.loop.create_task(self._handle_event(handler, event))
self.is_connected = False self.is_connected = False
await self.send_bridge_notice("Facebook Messenger connection closed without error") await self.send_bridge_notice("Facebook Messenger connection closed without error")
async def _handle_event(self, event: Any) -> None:
self.log.debug("Handling facebook event %s", event)
try:
handler = self._handlers[type(event)]
except KeyError:
self.log.debug(f"Received unknown event type {type(event)}")
else:
self.loop.create_task(self._call_handler(handler, event))
async def _call_handler(self, handler: Callable[[Any], Awaitable[None]], event: Any) -> None:
await self._sync_lock.wait("event")
try:
await handler(event)
except Exception:
self.log.exception(f"Failed to handle {type(event)} event from Facebook")
async def on_connect(self, evt: fbchat.Connect) -> None: async def on_connect(self, evt: fbchat.Connect) -> None:
now = time.monotonic() now = time.monotonic()
disconnected_at = self._connection_time disconnected_at = self._connection_time
......
...@@ -5,4 +5,4 @@ ruamel.yaml>=0.15.94,<0.17 ...@@ -5,4 +5,4 @@ ruamel.yaml>=0.15.94,<0.17
commonmark>=0.8,<0.10 commonmark>=0.8,<0.10
python-magic>=0.4,<0.5 python-magic>=0.4,<0.5
mautrix==0.5.0 mautrix==0.5.0
fbchat-asyncio==0.5.1 fbchat-asyncio==0.6.0b1
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment