Improve listening usability
Add Listener.register and Listener.run
This commit is contained in:
@@ -134,17 +134,18 @@ Listening & Events
|
|||||||
|
|
||||||
Now, we are finally at the point we have all been waiting for: Creating an automatic Facebook bot!
|
Now, we are finally at the point we have all been waiting for: Creating an automatic Facebook bot!
|
||||||
|
|
||||||
To get started, you create your methods that will handle your events::
|
To get started, you create a listener object::
|
||||||
|
|
||||||
def on_message(event):
|
listener = fbchat.Listener(session=session, chat_on=False, foreground=False)
|
||||||
|
|
||||||
|
The you use that to register methods that will handle your events::
|
||||||
|
|
||||||
|
@listener.register
|
||||||
|
def on_message(event: fbchat.MessageEvent):
|
||||||
print(f"Message from {event.author.id}: {event.message.text}")
|
print(f"Message from {event.author.id}: {event.message.text}")
|
||||||
|
|
||||||
And then you create a listener object, and start handling the incoming events::
|
And then you start handling the incoming events::
|
||||||
|
|
||||||
listener = fbchat.Listener.connect(session, False, False)
|
listener.run()
|
||||||
|
|
||||||
for event in listener.listen():
|
|
||||||
if isinstance(event, fbchat.MessageEvent):
|
|
||||||
on_message(event)
|
|
||||||
|
|
||||||
View the :ref:`examples` to see some more examples illustrating the event system.
|
View the :ref:`examples` to see some more examples illustrating the event system.
|
||||||
|
@@ -1,17 +1,15 @@
|
|||||||
import fbchat
|
import fbchat
|
||||||
|
|
||||||
session = fbchat.Session.login("<email>", "<password>")
|
session = fbchat.Session.login("<email>", "<password>")
|
||||||
|
listener = fbchat.Listener(session=session, chat_on=False, foreground=False)
|
||||||
listener = fbchat.Listener.connect(session, chat_on=False, foreground=False)
|
|
||||||
|
|
||||||
|
|
||||||
def on_message(event):
|
@listener.register
|
||||||
|
def on_message(event: fbchat.MessageEvent):
|
||||||
print(f"{event.message.text} from {event.author.id} in {event.thread.id}")
|
print(f"{event.message.text} from {event.author.id} in {event.thread.id}")
|
||||||
# If you're not the author, echo
|
# If you're not the author, echo
|
||||||
if event.author.id != session.user.id:
|
if event.author.id != session.user.id:
|
||||||
event.thread.send_text(event.message.text)
|
event.thread.send_text(event.message.text)
|
||||||
|
|
||||||
|
|
||||||
for event in listener.listen():
|
listener.run()
|
||||||
if isinstance(event, fbchat.MessageEvent):
|
|
||||||
on_message(event)
|
|
||||||
|
@@ -15,10 +15,10 @@ old_nicknames = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session = fbchat.Session.login("<email>", "<password>")
|
session = fbchat.Session.login("<email>", "<password>")
|
||||||
|
listener = fbchat.Listener(session=session, chat_on=False, foreground=False)
|
||||||
listener = fbchat.Listener.connect(session, chat_on=False, foreground=False)
|
|
||||||
|
|
||||||
|
|
||||||
|
@listener.register
|
||||||
def on_color_set(event: fbchat.ColorSet):
|
def on_color_set(event: fbchat.ColorSet):
|
||||||
if old_thread_id != event.thread.id:
|
if old_thread_id != event.thread.id:
|
||||||
return
|
return
|
||||||
@@ -27,6 +27,7 @@ def on_color_set(event: fbchat.ColorSet):
|
|||||||
event.thread.set_color(old_color)
|
event.thread.set_color(old_color)
|
||||||
|
|
||||||
|
|
||||||
|
@listener.register
|
||||||
def on_emoji_set(event: fbchat.EmojiSet):
|
def on_emoji_set(event: fbchat.EmojiSet):
|
||||||
if old_thread_id != event.thread.id:
|
if old_thread_id != event.thread.id:
|
||||||
return
|
return
|
||||||
@@ -35,6 +36,7 @@ def on_emoji_set(event: fbchat.EmojiSet):
|
|||||||
event.thread.set_emoji(old_emoji)
|
event.thread.set_emoji(old_emoji)
|
||||||
|
|
||||||
|
|
||||||
|
@listener.register
|
||||||
def on_title_set(event: fbchat.TitleSet):
|
def on_title_set(event: fbchat.TitleSet):
|
||||||
if old_thread_id != event.thread.id:
|
if old_thread_id != event.thread.id:
|
||||||
return
|
return
|
||||||
@@ -43,6 +45,7 @@ def on_title_set(event: fbchat.TitleSet):
|
|||||||
event.thread.set_title(old_title)
|
event.thread.set_title(old_title)
|
||||||
|
|
||||||
|
|
||||||
|
@listener.register
|
||||||
def on_nickname_set(event: fbchat.NicknameSet):
|
def on_nickname_set(event: fbchat.NicknameSet):
|
||||||
if old_thread_id != event.thread.id:
|
if old_thread_id != event.thread.id:
|
||||||
return
|
return
|
||||||
@@ -55,6 +58,7 @@ def on_nickname_set(event: fbchat.NicknameSet):
|
|||||||
event.thread.set_nickname(event.subject.id, old_nickname)
|
event.thread.set_nickname(event.subject.id, old_nickname)
|
||||||
|
|
||||||
|
|
||||||
|
@listener.register
|
||||||
def on_people_added(event: fbchat.PeopleAdded):
|
def on_people_added(event: fbchat.PeopleAdded):
|
||||||
if old_thread_id != event.thread.id:
|
if old_thread_id != event.thread.id:
|
||||||
return
|
return
|
||||||
@@ -64,6 +68,7 @@ def on_people_added(event: fbchat.PeopleAdded):
|
|||||||
event.thread.remove_participant(added.id)
|
event.thread.remove_participant(added.id)
|
||||||
|
|
||||||
|
|
||||||
|
@listener.register
|
||||||
def on_person_removed(event: fbchat.PersonRemoved):
|
def on_person_removed(event: fbchat.PersonRemoved):
|
||||||
if old_thread_id != event.thread.id:
|
if old_thread_id != event.thread.id:
|
||||||
return
|
return
|
||||||
@@ -75,16 +80,4 @@ def on_person_removed(event: fbchat.PersonRemoved):
|
|||||||
event.thread.add_participants([removed.id])
|
event.thread.add_participants([removed.id])
|
||||||
|
|
||||||
|
|
||||||
for event in listener.listen():
|
listener.run()
|
||||||
if isinstance(event, fbchat.ColorSet):
|
|
||||||
on_color_set(event)
|
|
||||||
elif isinstance(event, fbchat.EmojiSet):
|
|
||||||
on_emoji_set(event)
|
|
||||||
elif isinstance(event, fbchat.TitleSet):
|
|
||||||
on_title_set(event)
|
|
||||||
elif isinstance(event, fbchat.NicknameSet):
|
|
||||||
on_nickname_set(event)
|
|
||||||
elif isinstance(event, fbchat.PeopleAdded):
|
|
||||||
on_people_added(event)
|
|
||||||
elif isinstance(event, fbchat.PersonRemoved):
|
|
||||||
on_person_removed(event)
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import fbchat
|
import fbchat
|
||||||
|
|
||||||
session = fbchat.Session.login("<email>", "<password>")
|
session = fbchat.Session.login("<email>", "<password>")
|
||||||
|
listener = fbchat.Listener(session=session, chat_on=False, foreground=False)
|
||||||
listener = fbchat.Listener.connect(session, chat_on=False, foreground=False)
|
|
||||||
|
|
||||||
|
|
||||||
|
@listener.register
|
||||||
def on_message(event):
|
def on_message(event):
|
||||||
# We can only kick people from group chats, so no need to try if it's a user chat
|
# We can only kick people from group chats, so no need to try if it's a user chat
|
||||||
if not isinstance(event.thread, fbchat.Group):
|
if not isinstance(event.thread, fbchat.Group):
|
||||||
@@ -14,6 +14,4 @@ def on_message(event):
|
|||||||
event.thread.remove_participant(event.author.id)
|
event.thread.remove_participant(event.author.id)
|
||||||
|
|
||||||
|
|
||||||
for event in listener.listen():
|
listener.run()
|
||||||
if isinstance(event, fbchat.MessageEvent):
|
|
||||||
on_message(event)
|
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import attr
|
import attr
|
||||||
|
import inspect
|
||||||
import random
|
import random
|
||||||
import paho.mqtt.client
|
import paho.mqtt.client
|
||||||
import requests
|
import requests
|
||||||
from ._common import log, kw_only
|
from ._common import log, kw_only
|
||||||
from . import _util, _exception, _session, _graphql, _events
|
from . import _util, _exception, _session, _graphql, _events
|
||||||
|
|
||||||
from typing import Iterable, Optional
|
from typing import Iterable, Optional, Mapping, Callable
|
||||||
|
|
||||||
|
|
||||||
HOST = "edge-chat.facebook.com"
|
HOST = "edge-chat.facebook.com"
|
||||||
@@ -97,6 +98,9 @@ def fetch_sequence_id(session: _session.Session) -> int:
|
|||||||
return int(sequence_id)
|
return int(sequence_id)
|
||||||
|
|
||||||
|
|
||||||
|
HandlerT = Callable[[_events.Event], None]
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True, kw_only=kw_only, eq=False)
|
@attr.s(slots=True, kw_only=kw_only, eq=False)
|
||||||
class Listener:
|
class Listener:
|
||||||
"""Helper, to listen for incoming Facebook events.
|
"""Helper, to listen for incoming Facebook events.
|
||||||
@@ -119,6 +123,7 @@ class Listener:
|
|||||||
_sync_token = attr.ib(None, type=Optional[str])
|
_sync_token = attr.ib(None, type=Optional[str])
|
||||||
_sequence_id = attr.ib(None, type=Optional[int])
|
_sequence_id = attr.ib(None, type=Optional[int])
|
||||||
_tmp_events = attr.ib(factory=list, type=Iterable[_events.Event])
|
_tmp_events = attr.ib(factory=list, type=Iterable[_events.Event])
|
||||||
|
_handlers = attr.ib(factory=dict, type=Mapping[HandlerT, _events.Event])
|
||||||
|
|
||||||
def __attrs_post_init__(self):
|
def __attrs_post_init__(self):
|
||||||
# Configure callbacks
|
# Configure callbacks
|
||||||
@@ -302,7 +307,8 @@ class Listener:
|
|||||||
|
|
||||||
Yields events when they arrive.
|
Yields events when they arrive.
|
||||||
|
|
||||||
This will automatically reconnect on errors.
|
This will automatically reconnect on errors, except if the errors are one of
|
||||||
|
`PleaseRefresh` or `NotLoggedIn`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
Print events continually.
|
Print events continually.
|
||||||
@@ -398,3 +404,57 @@ class Listener:
|
|||||||
#
|
#
|
||||||
# def browser_close(self):
|
# def browser_close(self):
|
||||||
# info = self._mqtt.publish("/browser_close", payload=b"{}", qos=1)
|
# info = self._mqtt.publish("/browser_close", payload=b"{}", qos=1)
|
||||||
|
|
||||||
|
def register(func: HandlerT) -> HandlerT:
|
||||||
|
"""Register a function that will be called when .run is called.
|
||||||
|
|
||||||
|
The input function must take a single annotated argument.
|
||||||
|
|
||||||
|
Should be used as a function decorator.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> @listener.register
|
||||||
|
>>> def my_handler(event: fbchat.Event):
|
||||||
|
... print(f"New event: {event}")
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
parameter = next(iter(inspect.signature(func).parameters.values()))
|
||||||
|
except Exception as e: # TODO: More precise exceptions
|
||||||
|
raise ValueError("Invalid function. Must have at least an argument") from e
|
||||||
|
|
||||||
|
if parameter.annotation is parameter.empty:
|
||||||
|
raise ValueError("Invalid function. Must be annotated")
|
||||||
|
|
||||||
|
if not issubclass(parameter.annotation, _events.Event):
|
||||||
|
raise ValueError("Invalid function. Annotation must be an event class")
|
||||||
|
|
||||||
|
# TODO: More error checks, e.g. kw_only parameters
|
||||||
|
|
||||||
|
self._handlers[func] = parameter.annotation
|
||||||
|
|
||||||
|
def unregister(func: HandlerT):
|
||||||
|
"""Unregister a previously registered function."""
|
||||||
|
try:
|
||||||
|
self._handlers.pop(func)
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError("Tried to unregister a function that was not registered")
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
"""Run the listening loop, and dispatch incoming events to registered handlers.
|
||||||
|
|
||||||
|
This uses `.listen`, which reconnect on errors, except if the errors are one of
|
||||||
|
`PleaseRefresh` or `NotLoggedIn`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Print incoming messages.
|
||||||
|
|
||||||
|
>>> @listener.register
|
||||||
|
>>> def print_msg(event: fbchat.MessageEvent):
|
||||||
|
... print(event.message.text)
|
||||||
|
...
|
||||||
|
>>> listener.run()
|
||||||
|
"""
|
||||||
|
for event in self.listen():
|
||||||
|
for handler, event_cls in self._handlers.items():
|
||||||
|
if isinstance(event, event_cls):
|
||||||
|
handler(event)
|
||||||
|
Reference in New Issue
Block a user