Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f64c487a2d | ||
|
11534604fe | ||
|
9990952fa6 | ||
|
7ee7361646 | ||
|
89c6af516c | ||
|
c27f599e37 |
@@ -4,5 +4,8 @@ Exceptions
|
|||||||
.. autoexception:: FacebookError()
|
.. autoexception:: FacebookError()
|
||||||
.. autoexception:: HTTPError()
|
.. autoexception:: HTTPError()
|
||||||
.. autoexception:: ParseError()
|
.. autoexception:: ParseError()
|
||||||
|
.. autoexception:: NotLoggedIn()
|
||||||
.. autoexception:: ExternalError()
|
.. autoexception:: ExternalError()
|
||||||
.. autoexception:: GraphQLError()
|
.. autoexception:: GraphQLError()
|
||||||
|
.. autoexception:: InvalidParameters()
|
||||||
|
.. autoexception:: PleaseRefresh()
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
.. highlight:: sh
|
||||||
.. See README.rst for explanation of these markers
|
.. See README.rst for explanation of these markers
|
||||||
|
|
||||||
.. include:: ../README.rst
|
.. include:: ../README.rst
|
||||||
|
@@ -46,7 +46,7 @@ A thread basically just means "something I can chat with", but more precisely, i
|
|||||||
- The conversation between you and a single Facebook user (`User`)
|
- The conversation between you and a single Facebook user (`User`)
|
||||||
- The conversation between you and a Facebook Page (`Page`)
|
- The conversation between you and a Facebook Page (`Page`)
|
||||||
|
|
||||||
You can get your own user ID with `Session.user.id`.
|
You can get your own user ID from `Session.user` with ``session.user.id``.
|
||||||
|
|
||||||
Getting the ID of a specific group thread is fairly trivial, you only need to login to `<https://www.messenger.com/>`_, click on the group you want to find the ID of, and then read the id from the address bar.
|
Getting the ID of a specific group thread is fairly trivial, you only need to login to `<https://www.messenger.com/>`_, click on the group you want to find the ID of, and then read the id from the address bar.
|
||||||
The URL will look something like this: ``https://www.messenger.com/t/1234567890``, where ``1234567890`` would be the ID of the group.
|
The URL will look something like this: ``https://www.messenger.com/t/1234567890``, where ``1234567890`` would be the ID of the group.
|
||||||
@@ -111,19 +111,19 @@ Some functionality, like adding users to a `Group`, or blocking a `User`, logica
|
|||||||
|
|
||||||
With that out of the way, let's see some examples!
|
With that out of the way, let's see some examples!
|
||||||
|
|
||||||
The simplest way of interracting with a thread is by sending a message::
|
The simplest way of interacting with a thread is by sending a message::
|
||||||
|
|
||||||
# Send a message to the user
|
# Send a message to the user
|
||||||
message = user.send_text("test message")
|
message = user.send_text("test message")
|
||||||
|
|
||||||
There are many types of messages you can send, see the full API documentation for more.
|
There are many types of messages you can send, see the full API documentation for more.
|
||||||
|
|
||||||
Notice how we held on to the sent message? The return type i a `Message` instance, so you can interract with it afterwards::
|
Notice how we held on to the sent message? The return type i a `Message` instance, so you can interact with it afterwards::
|
||||||
|
|
||||||
# React to the message with the 😍 emoji
|
# React to the message with the 😍 emoji
|
||||||
message.react("😍")
|
message.react("😍")
|
||||||
|
|
||||||
Besides sending messages, you can also interract with threads in other ways. An example is to change the thread color::
|
Besides sending messages, you can also interact with threads in other ways. An example is to change the thread color::
|
||||||
|
|
||||||
# Will change the thread color to the default blue
|
# Will change the thread color to the default blue
|
||||||
thread.set_color("#0084ff")
|
thread.set_color("#0084ff")
|
||||||
|
@@ -74,6 +74,8 @@ from ._events import (
|
|||||||
Event,
|
Event,
|
||||||
UnknownEvent,
|
UnknownEvent,
|
||||||
ThreadEvent,
|
ThreadEvent,
|
||||||
|
Connect,
|
||||||
|
Disconnect,
|
||||||
# _client_payload
|
# _client_payload
|
||||||
ReactionEvent,
|
ReactionEvent,
|
||||||
UserStatusEvent,
|
UserStatusEvent,
|
||||||
@@ -115,7 +117,7 @@ from ._listen import Listener
|
|||||||
|
|
||||||
from ._client import Client
|
from ._client import Client
|
||||||
|
|
||||||
__version__ = "2.0.0a1"
|
__version__ = "2.0.0a2"
|
||||||
|
|
||||||
__all__ = ("Session", "Listener", "Client")
|
__all__ = ("Session", "Listener", "Client")
|
||||||
|
|
||||||
|
@@ -33,10 +33,10 @@ class Client:
|
|||||||
But does not include deactivated, deleted or memorialized users (logically,
|
But does not include deactivated, deleted or memorialized users (logically,
|
||||||
since you can't chat with those).
|
since you can't chat with those).
|
||||||
|
|
||||||
The order these are returned is arbitary.
|
The order these are returned is arbitrary.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
Get the name of an arbitary user that you're currently chatting with.
|
Get the name of an arbitrary user that you're currently chatting with.
|
||||||
|
|
||||||
>>> users = client.fetch_users()
|
>>> users = client.fetch_users()
|
||||||
>>> users[0].name
|
>>> users[0].name
|
||||||
@@ -211,7 +211,7 @@ class Client:
|
|||||||
Warning! If someone send a message to a thread that matches the query, while
|
Warning! If someone send a message to a thread that matches the query, while
|
||||||
we're searching, some snippets will get returned twice, and some will be lost.
|
we're searching, some snippets will get returned twice, and some will be lost.
|
||||||
|
|
||||||
This is fundamentally unfixable, it's just how the endpoint is implemented.
|
This is fundamentally not fixable, it's just how the endpoint is implemented.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: Text to search for
|
query: Text to search for
|
||||||
|
@@ -24,8 +24,7 @@ class Typing(ThreadEvent):
|
|||||||
return cls(author=author, thread=author, status=status)
|
return cls(author=author, thread=author, status=status)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parse(cls, session, data):
|
def _parse_thread_typing(cls, session, data):
|
||||||
# TODO: Rename this method
|
|
||||||
author = _threads.User(session=session, id=str(data["sender_fbid"]))
|
author = _threads.User(session=session, id=str(data["sender_fbid"]))
|
||||||
thread = _threads.Group(session=session, id=str(data["thread"]))
|
thread = _threads.Group(session=session, id=str(data["thread"]))
|
||||||
status = data["state"] == 1
|
status = data["state"] == 1
|
||||||
@@ -68,6 +67,25 @@ class Presence(Event):
|
|||||||
return cls(statuses=statuses, full=data["list_type"] == "full")
|
return cls(statuses=statuses, full=data["list_type"] == "full")
|
||||||
|
|
||||||
|
|
||||||
|
@attrs_event
|
||||||
|
class Connect(Event):
|
||||||
|
"""The client was connected to Facebook.
|
||||||
|
|
||||||
|
This is not guaranteed to be triggered the same amount of times `Disconnect`!
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@attrs_event
|
||||||
|
class Disconnect(Event):
|
||||||
|
"""The client lost the connection to Facebook.
|
||||||
|
|
||||||
|
This is not guaranteed to be triggered the same amount of times `Connect`!
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The reason / error string for the disconnect
|
||||||
|
reason = attr.ib(type=str)
|
||||||
|
|
||||||
|
|
||||||
def parse_events(session, topic, data):
|
def parse_events(session, topic, data):
|
||||||
# See Mqtt._configure_connect_options for information about these topics
|
# See Mqtt._configure_connect_options for information about these topics
|
||||||
try:
|
try:
|
||||||
@@ -90,7 +108,7 @@ def parse_events(session, topic, data):
|
|||||||
) from e
|
) from e
|
||||||
|
|
||||||
elif topic == "/thread_typing":
|
elif topic == "/thread_typing":
|
||||||
yield Typing._parse(session, data)
|
yield Typing._parse_thread_typing(session, data)
|
||||||
|
|
||||||
elif topic == "/orca_typing_notifications":
|
elif topic == "/orca_typing_notifications":
|
||||||
yield Typing._parse_orca(session, data)
|
yield Typing._parse_orca(session, data)
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import attr
|
import attr
|
||||||
import abc
|
|
||||||
from .._common import kw_only
|
from .._common import kw_only
|
||||||
from .. import _exception, _util, _threads
|
from .. import _exception, _util, _threads
|
||||||
|
|
||||||
@@ -10,14 +9,9 @@ attrs_event = attr.s(slots=True, kw_only=kw_only, frozen=True)
|
|||||||
|
|
||||||
|
|
||||||
@attrs_event
|
@attrs_event
|
||||||
class Event(metaclass=abc.ABCMeta):
|
class Event:
|
||||||
"""Base class for all events."""
|
"""Base class for all events."""
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abc.abstractmethod
|
|
||||||
def _parse(cls, session, data):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_thread(session, data):
|
def _get_thread(session, data):
|
||||||
# TODO: Handle pages? Is it even possible?
|
# TODO: Handle pages? Is it even possible?
|
||||||
@@ -60,3 +54,9 @@ class ThreadEvent(Event):
|
|||||||
thread = cls._get_thread(session, metadata)
|
thread = cls._get_thread(session, metadata)
|
||||||
at = _util.millis_to_datetime(int(metadata["timestamp"]))
|
at = _util.millis_to_datetime(int(metadata["timestamp"]))
|
||||||
return author, thread, at
|
return author, thread, at
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parse_fetch(cls, session, data):
|
||||||
|
author = _threads.User(session=session, id=data["message_sender"]["id"])
|
||||||
|
at = _util.millis_to_datetime(int(data["timestamp_precise"]))
|
||||||
|
return author, at
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import attr
|
import attr
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
# Not frozen, since that doesn't work in PyPy
|
# Not frozen, since that doesn't work in PyPy
|
||||||
@attr.s(slots=True, auto_exc=True)
|
@attr.s(slots=True, auto_exc=True)
|
||||||
@@ -20,7 +20,7 @@ class HTTPError(FacebookError):
|
|||||||
"""Base class for errors with the HTTP(s) connection to Facebook."""
|
"""Base class for errors with the HTTP(s) connection to Facebook."""
|
||||||
|
|
||||||
#: The returned HTTP status code, if relevant
|
#: The returned HTTP status code, if relevant
|
||||||
status_code = attr.ib(None, type=int)
|
status_code = attr.ib(None, type=Optional[int])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if not self.status_code:
|
if not self.status_code:
|
||||||
@@ -58,7 +58,7 @@ class ExternalError(FacebookError):
|
|||||||
#: The error message that Facebook returned (Possibly in the user's own language)
|
#: The error message that Facebook returned (Possibly in the user's own language)
|
||||||
description = attr.ib(type=str)
|
description = attr.ib(type=str)
|
||||||
#: The error code that Facebook returned
|
#: The error code that Facebook returned
|
||||||
code = attr.ib(None, type=int)
|
code = attr.ib(None, type=Optional[int])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.code:
|
if self.code:
|
||||||
@@ -73,7 +73,7 @@ class GraphQLError(ExternalError):
|
|||||||
# TODO: Handle multiple errors
|
# TODO: Handle multiple errors
|
||||||
|
|
||||||
#: Query debug information
|
#: Query debug information
|
||||||
debug_info = attr.ib(None, type=str)
|
debug_info = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.debug_info:
|
if self.debug_info:
|
||||||
|
@@ -5,7 +5,7 @@ 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, Mapping
|
from typing import Iterable, Optional, Mapping, List
|
||||||
|
|
||||||
|
|
||||||
HOST = "edge-chat.facebook.com"
|
HOST = "edge-chat.facebook.com"
|
||||||
@@ -118,7 +118,7 @@ class Listener:
|
|||||||
_mqtt = attr.ib(factory=mqtt_factory, type=paho.mqtt.client.Client)
|
_mqtt = attr.ib(factory=mqtt_factory, type=paho.mqtt.client.Client)
|
||||||
_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=List[_events.Event])
|
||||||
|
|
||||||
def __attrs_post_init__(self):
|
def __attrs_post_init__(self):
|
||||||
# Configure callbacks
|
# Configure callbacks
|
||||||
@@ -152,6 +152,7 @@ class Listener:
|
|||||||
"The MQTT listener was disconnected for too long,"
|
"The MQTT listener was disconnected for too long,"
|
||||||
" events may have been lost"
|
" events may have been lost"
|
||||||
)
|
)
|
||||||
|
# TODO: Find a way to tell the user that they may now be missing events
|
||||||
self._sync_token = None
|
self._sync_token = None
|
||||||
self._sequence_id = None
|
self._sequence_id = None
|
||||||
return False
|
return False
|
||||||
@@ -282,11 +283,12 @@ class Listener:
|
|||||||
path="/chat?sid={}".format(session_id), headers=headers
|
path="/chat?sid={}".format(session_id), headers=headers
|
||||||
)
|
)
|
||||||
|
|
||||||
def _reconnect(self):
|
def _reconnect(self) -> bool:
|
||||||
# Try reconnecting
|
# Try reconnecting
|
||||||
self._configure_connect_options()
|
self._configure_connect_options()
|
||||||
try:
|
try:
|
||||||
self._mqtt.reconnect()
|
self._mqtt.reconnect()
|
||||||
|
return True
|
||||||
except (
|
except (
|
||||||
# Taken from .loop_forever
|
# Taken from .loop_forever
|
||||||
paho.mqtt.client.socket.error,
|
paho.mqtt.client.socket.error,
|
||||||
@@ -296,6 +298,7 @@ class Listener:
|
|||||||
log.debug("MQTT reconnection failed: %s", e)
|
log.debug("MQTT reconnection failed: %s", e)
|
||||||
# Wait before reconnecting
|
# Wait before reconnecting
|
||||||
self._mqtt._reconnect_wait()
|
self._mqtt._reconnect_wait()
|
||||||
|
return False
|
||||||
|
|
||||||
def listen(self) -> Iterable[_events.Event]:
|
def listen(self) -> Iterable[_events.Event]:
|
||||||
"""Run the listening loop continually.
|
"""Run the listening loop continually.
|
||||||
@@ -315,12 +318,10 @@ class Listener:
|
|||||||
self._sequence_id = fetch_sequence_id(self.session)
|
self._sequence_id = fetch_sequence_id(self.session)
|
||||||
|
|
||||||
# Make sure we're connected
|
# Make sure we're connected
|
||||||
while True:
|
while not self._reconnect():
|
||||||
# Beware, internal API, may have to change this to something more stable!
|
pass
|
||||||
if self._mqtt._state == paho.mqtt.client.mqtt_cs_connect_async:
|
|
||||||
self._reconnect()
|
yield _events.Connect()
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
rc = self._mqtt.loop(timeout=1.0)
|
rc = self._mqtt.loop(timeout=1.0)
|
||||||
@@ -339,18 +340,23 @@ class Listener:
|
|||||||
if rc != paho.mqtt.client.MQTT_ERR_SUCCESS:
|
if rc != paho.mqtt.client.MQTT_ERR_SUCCESS:
|
||||||
# If known/expected error
|
# If known/expected error
|
||||||
if rc == paho.mqtt.client.MQTT_ERR_CONN_LOST:
|
if rc == paho.mqtt.client.MQTT_ERR_CONN_LOST:
|
||||||
log.warning("Connection lost, retrying")
|
yield _events.Disconnect(reason="Connection lost, retrying")
|
||||||
elif rc == paho.mqtt.client.MQTT_ERR_NOMEM:
|
elif rc == paho.mqtt.client.MQTT_ERR_NOMEM:
|
||||||
# This error is wrongly classified
|
# This error is wrongly classified
|
||||||
# See https://github.com/eclipse/paho.mqtt.python/issues/340
|
# See https://github.com/eclipse/paho.mqtt.python/issues/340
|
||||||
log.warning("Connection error, retrying")
|
yield _events.Disconnect(reason="Connection error, retrying")
|
||||||
elif rc == paho.mqtt.client.MQTT_ERR_CONN_REFUSED:
|
elif rc == paho.mqtt.client.MQTT_ERR_CONN_REFUSED:
|
||||||
raise _exception.NotLoggedIn("MQTT connection refused")
|
raise _exception.NotLoggedIn("MQTT connection refused")
|
||||||
else:
|
else:
|
||||||
err = paho.mqtt.client.error_string(rc)
|
err = paho.mqtt.client.error_string(rc)
|
||||||
log.error("MQTT Error: %s", err)
|
log.error("MQTT Error: %s", err)
|
||||||
|
reason = "MQTT Error: {}, retrying".format(err)
|
||||||
|
yield _events.Disconnect(reason=reason)
|
||||||
|
|
||||||
self._reconnect()
|
while not self._reconnect():
|
||||||
|
pass
|
||||||
|
|
||||||
|
yield _events.Connect()
|
||||||
|
|
||||||
if self._tmp_events:
|
if self._tmp_events:
|
||||||
yield from self._tmp_events
|
yield from self._tmp_events
|
||||||
@@ -364,7 +370,7 @@ class Listener:
|
|||||||
The `Listener` object should not be used after this is called!
|
The `Listener` object should not be used after this is called!
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
Stop the listener when recieving a message with the text "/stop"
|
Stop the listener when receiving a message with the text "/stop"
|
||||||
|
|
||||||
>>> for event in listener.listen():
|
>>> for event in listener.listen():
|
||||||
... if isinstance(event, fbchat.MessageEvent):
|
... if isinstance(event, fbchat.MessageEvent):
|
||||||
@@ -374,7 +380,7 @@ class Listener:
|
|||||||
self._mqtt.disconnect()
|
self._mqtt.disconnect()
|
||||||
|
|
||||||
def set_foreground(self, value: bool) -> None:
|
def set_foreground(self, value: bool) -> None:
|
||||||
"""Set the `foreground` value while listening."""
|
"""Set the ``foreground`` value while listening."""
|
||||||
# TODO: Document what this actually does!
|
# TODO: Document what this actually does!
|
||||||
payload = _util.json_minimal({"foreground": value})
|
payload = _util.json_minimal({"foreground": value})
|
||||||
info = self._mqtt.publish("/foreground_state", payload=payload, qos=1)
|
info = self._mqtt.publish("/foreground_state", payload=payload, qos=1)
|
||||||
@@ -383,7 +389,7 @@ class Listener:
|
|||||||
# info.wait_for_publish()
|
# info.wait_for_publish()
|
||||||
|
|
||||||
def set_chat_on(self, value: bool) -> None:
|
def set_chat_on(self, value: bool) -> None:
|
||||||
"""Set the `chat_on` value while listening."""
|
"""Set the ``chat_on`` value while listening."""
|
||||||
# TODO: Document what this actually does!
|
# TODO: Document what this actually does!
|
||||||
# TODO: Is this the right request to make?
|
# TODO: Is this the right request to make?
|
||||||
data = {"make_user_available_when_in_foreground": value}
|
data = {"make_user_available_when_in_foreground": value}
|
||||||
|
@@ -3,7 +3,7 @@ from . import Image
|
|||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
from .. import _util
|
from .. import _util
|
||||||
|
|
||||||
from typing import Sequence
|
from typing import Optional, Sequence
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
@@ -11,7 +11,7 @@ class Attachment:
|
|||||||
"""Represents a Facebook attachment."""
|
"""Represents a Facebook attachment."""
|
||||||
|
|
||||||
#: The attachment ID
|
#: The attachment ID
|
||||||
id = attr.ib(None, type=str)
|
id = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
@@ -24,21 +24,21 @@ class ShareAttachment(Attachment):
|
|||||||
"""Represents a shared item (e.g. URL) attachment."""
|
"""Represents a shared item (e.g. URL) attachment."""
|
||||||
|
|
||||||
#: ID of the author of the shared post
|
#: ID of the author of the shared post
|
||||||
author = attr.ib(None, type=str)
|
author = attr.ib(None, type=Optional[str])
|
||||||
#: Target URL
|
#: Target URL
|
||||||
url = attr.ib(None, type=str)
|
url = attr.ib(None, type=Optional[str])
|
||||||
#: Original URL if Facebook redirects the URL
|
#: Original URL if Facebook redirects the URL
|
||||||
original_url = attr.ib(None, type=str)
|
original_url = attr.ib(None, type=Optional[str])
|
||||||
#: Title of the attachment
|
#: Title of the attachment
|
||||||
title = attr.ib(None, type=str)
|
title = attr.ib(None, type=Optional[str])
|
||||||
#: Description of the attachment
|
#: Description of the attachment
|
||||||
description = attr.ib(None, type=str)
|
description = attr.ib(None, type=Optional[str])
|
||||||
#: Name of the source
|
#: Name of the source
|
||||||
source = attr.ib(None, type=str)
|
source = attr.ib(None, type=Optional[str])
|
||||||
#: The attached image
|
#: The attached image
|
||||||
image = attr.ib(None, type=Image)
|
image = attr.ib(None, type=Optional[Image])
|
||||||
#: URL of the original image if Facebook uses ``safe_image``
|
#: URL of the original image if Facebook uses ``safe_image``
|
||||||
original_image_url = attr.ib(None, type=str)
|
original_image_url = attr.ib(None, type=Optional[str])
|
||||||
#: List of additional attachments
|
#: List of additional attachments
|
||||||
attachments = attr.ib(factory=list, type=Sequence[Attachment])
|
attachments = attr.ib(factory=list, type=Sequence[Attachment])
|
||||||
|
|
||||||
|
@@ -4,6 +4,8 @@ import enum
|
|||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
from .. import _util
|
from .. import _util
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class ThreadLocation(enum.Enum):
|
class ThreadLocation(enum.Enum):
|
||||||
"""Used to specify where a thread is located (inbox, pending, archived, other)."""
|
"""Used to specify where a thread is located (inbox, pending, archived, other)."""
|
||||||
@@ -21,11 +23,11 @@ class ThreadLocation(enum.Enum):
|
|||||||
@attrs_default
|
@attrs_default
|
||||||
class ActiveStatus:
|
class ActiveStatus:
|
||||||
#: Whether the user is active now
|
#: Whether the user is active now
|
||||||
active = attr.ib(None, type=bool)
|
active = attr.ib(type=bool)
|
||||||
#: Datetime when the user was last active
|
#: When the user was last active
|
||||||
last_active = attr.ib(None, type=datetime.datetime)
|
last_active = attr.ib(None, type=Optional[datetime.datetime])
|
||||||
#: Whether the user is playing Messenger game now
|
#: Whether the user is playing Messenger game now
|
||||||
in_game = attr.ib(None, type=bool)
|
in_game = attr.ib(None, type=Optional[bool])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_orca_presence(cls, data):
|
def _from_orca_presence(cls, data):
|
||||||
@@ -42,9 +44,9 @@ class Image:
|
|||||||
#: URL to the image
|
#: URL to the image
|
||||||
url = attr.ib(type=str)
|
url = attr.ib(type=str)
|
||||||
#: Width of the image
|
#: Width of the image
|
||||||
width = attr.ib(None, type=int)
|
width = attr.ib(None, type=Optional[int])
|
||||||
#: Height of the image
|
#: Height of the image
|
||||||
height = attr.ib(None, type=int)
|
height = attr.ib(None, type=Optional[int])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_uri(cls, data):
|
def _from_uri(cls, data):
|
||||||
|
@@ -4,7 +4,7 @@ from . import Image, Attachment
|
|||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
from .. import _util
|
from .. import _util
|
||||||
|
|
||||||
from typing import Set
|
from typing import Set, Optional
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
@@ -12,13 +12,13 @@ class FileAttachment(Attachment):
|
|||||||
"""Represents a file that has been sent as a Facebook attachment."""
|
"""Represents a file that has been sent as a Facebook attachment."""
|
||||||
|
|
||||||
#: URL where you can download the file
|
#: URL where you can download the file
|
||||||
url = attr.ib(None, type=str)
|
url = attr.ib(None, type=Optional[str])
|
||||||
#: Size of the file in bytes
|
#: Size of the file in bytes
|
||||||
size = attr.ib(None, type=int)
|
size = attr.ib(None, type=Optional[int])
|
||||||
#: Name of the file
|
#: Name of the file
|
||||||
name = attr.ib(None, type=str)
|
name = attr.ib(None, type=Optional[str])
|
||||||
#: Whether Facebook determines that this file may be harmful
|
#: Whether Facebook determines that this file may be harmful
|
||||||
is_malicious = attr.ib(None, type=bool)
|
is_malicious = attr.ib(None, type=Optional[bool])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data, size=None):
|
def _from_graphql(cls, data, size=None):
|
||||||
@@ -36,13 +36,13 @@ class AudioAttachment(Attachment):
|
|||||||
"""Represents an audio file that has been sent as a Facebook attachment."""
|
"""Represents an audio file that has been sent as a Facebook attachment."""
|
||||||
|
|
||||||
#: Name of the file
|
#: Name of the file
|
||||||
filename = attr.ib(None, type=str)
|
filename = attr.ib(None, type=Optional[str])
|
||||||
#: URL of the audio file
|
#: URL of the audio file
|
||||||
url = attr.ib(None, type=str)
|
url = attr.ib(None, type=Optional[str])
|
||||||
#: Duration of the audio clip as a timedelta
|
#: Duration of the audio clip
|
||||||
duration = attr.ib(None, type=datetime.timedelta)
|
duration = attr.ib(None, type=Optional[datetime.timedelta])
|
||||||
#: Audio type
|
#: Audio type
|
||||||
audio_type = attr.ib(None, type=str)
|
audio_type = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data):
|
def _from_graphql(cls, data):
|
||||||
@@ -63,13 +63,13 @@ class ImageAttachment(Attachment):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
#: The extension of the original image (e.g. ``png``)
|
#: The extension of the original image (e.g. ``png``)
|
||||||
original_extension = attr.ib(None, type=str)
|
original_extension = attr.ib(None, type=Optional[str])
|
||||||
#: Width of original image
|
#: Width of original image
|
||||||
width = attr.ib(None, converter=lambda x: None if x is None else int(x), type=int)
|
width = attr.ib(None, converter=_util.int_or_none, type=Optional[int])
|
||||||
#: Height of original image
|
#: Height of original image
|
||||||
height = attr.ib(None, converter=lambda x: None if x is None else int(x), type=int)
|
height = attr.ib(None, converter=_util.int_or_none, type=Optional[int])
|
||||||
#: Whether the image is animated
|
#: Whether the image is animated
|
||||||
is_animated = attr.ib(None, type=bool)
|
is_animated = attr.ib(None, type=Optional[bool])
|
||||||
#: A set, containing variously sized / various types of previews of the image
|
#: A set, containing variously sized / various types of previews of the image
|
||||||
previews = attr.ib(factory=set, type=Set[Image])
|
previews = attr.ib(factory=set, type=Set[Image])
|
||||||
|
|
||||||
@@ -113,15 +113,15 @@ class VideoAttachment(Attachment):
|
|||||||
"""Represents a video that has been sent as a Facebook attachment."""
|
"""Represents a video that has been sent as a Facebook attachment."""
|
||||||
|
|
||||||
#: Size of the original video in bytes
|
#: Size of the original video in bytes
|
||||||
size = attr.ib(None, type=int)
|
size = attr.ib(None, type=Optional[int])
|
||||||
#: Width of original video
|
#: Width of original video
|
||||||
width = attr.ib(None, type=int)
|
width = attr.ib(None, type=Optional[int])
|
||||||
#: Height of original video
|
#: Height of original video
|
||||||
height = attr.ib(None, type=int)
|
height = attr.ib(None, type=Optional[int])
|
||||||
#: Length of video as a timedelta
|
#: Length of video
|
||||||
duration = attr.ib(None, type=datetime.timedelta)
|
duration = attr.ib(None, type=Optional[datetime.timedelta])
|
||||||
#: URL to very compressed preview video
|
#: URL to very compressed preview video
|
||||||
preview_url = attr.ib(None, type=str)
|
preview_url = attr.ib(None, type=Optional[str])
|
||||||
#: A set, containing variously sized previews of the video
|
#: A set, containing variously sized previews of the video
|
||||||
previews = attr.ib(factory=set, type=Set[Image])
|
previews = attr.ib(factory=set, type=Set[Image])
|
||||||
|
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
import attr
|
import attr
|
||||||
|
import datetime
|
||||||
from . import Image, Attachment
|
from . import Image, Attachment
|
||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
from .. import _util, _exception
|
from .. import _util, _exception
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
class LocationAttachment(Attachment):
|
class LocationAttachment(Attachment):
|
||||||
@@ -12,15 +15,15 @@ class LocationAttachment(Attachment):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
#: Latitude of the location
|
#: Latitude of the location
|
||||||
latitude = attr.ib(None, type=float)
|
latitude = attr.ib(None, type=Optional[float])
|
||||||
#: Longitude of the location
|
#: Longitude of the location
|
||||||
longitude = attr.ib(None, type=float)
|
longitude = attr.ib(None, type=Optional[float])
|
||||||
#: Image showing the map of the location
|
#: Image showing the map of the location
|
||||||
image = attr.ib(None, type=Image)
|
image = attr.ib(None, type=Optional[Image])
|
||||||
#: URL to Bing maps with the location
|
#: URL to Bing maps with the location
|
||||||
url = attr.ib(None, type=str)
|
url = attr.ib(None, type=Optional[str])
|
||||||
# Address of the location
|
# Address of the location
|
||||||
address = attr.ib(None, type=str)
|
address = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data):
|
def _from_graphql(cls, data):
|
||||||
@@ -51,11 +54,11 @@ class LiveLocationAttachment(LocationAttachment):
|
|||||||
"""Represents a live user location."""
|
"""Represents a live user location."""
|
||||||
|
|
||||||
#: Name of the location
|
#: Name of the location
|
||||||
name = attr.ib(None)
|
name = attr.ib(None, type=Optional[str])
|
||||||
#: Datetime when live location expires
|
#: When live location expires
|
||||||
expires_at = attr.ib(None)
|
expires_at = attr.ib(None, type=Optional[datetime.datetime])
|
||||||
#: True if live location is expired
|
#: True if live location is expired
|
||||||
is_expired = attr.ib(None)
|
is_expired = attr.ib(None, type=Optional[bool])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_pull(cls, data):
|
def _from_pull(cls, data):
|
||||||
|
@@ -224,7 +224,7 @@ class MessageSnippet(Message):
|
|||||||
|
|
||||||
#: ID of the sender
|
#: ID of the sender
|
||||||
author = attr.ib(type=str)
|
author = attr.ib(type=str)
|
||||||
#: Datetime of when the message was sent
|
#: When the message was sent
|
||||||
created_at = attr.ib(type=datetime.datetime)
|
created_at = attr.ib(type=datetime.datetime)
|
||||||
#: The actual message
|
#: The actual message
|
||||||
text = attr.ib(type=str)
|
text = attr.ib(type=str)
|
||||||
@@ -252,34 +252,34 @@ class MessageData(Message):
|
|||||||
|
|
||||||
#: ID of the sender
|
#: ID of the sender
|
||||||
author = attr.ib(type=str)
|
author = attr.ib(type=str)
|
||||||
#: Datetime of when the message was sent
|
#: When the message was sent
|
||||||
created_at = attr.ib(type=datetime.datetime)
|
created_at = attr.ib(type=datetime.datetime)
|
||||||
#: The actual message
|
#: The actual message
|
||||||
text = attr.ib(None, type=str)
|
text = attr.ib(None, type=Optional[str])
|
||||||
#: A list of `Mention` objects
|
#: A list of `Mention` objects
|
||||||
mentions = attr.ib(factory=list, type=Sequence[Mention])
|
mentions = attr.ib(factory=list, type=Sequence[Mention])
|
||||||
#: Size of a sent emoji
|
#: Size of a sent emoji
|
||||||
emoji_size = attr.ib(None, type=EmojiSize)
|
emoji_size = attr.ib(None, type=Optional[EmojiSize])
|
||||||
#: Whether the message is read
|
#: Whether the message is read
|
||||||
is_read = attr.ib(None, type=bool)
|
is_read = attr.ib(None, type=Optional[bool])
|
||||||
#: A list of people IDs who read the message, works only with `Client.fetch_thread_messages`
|
#: People IDs who read the message, only works with `ThreadABC.fetch_messages`
|
||||||
read_by = attr.ib(factory=list, type=bool)
|
read_by = attr.ib(factory=list, type=bool)
|
||||||
#: A dictionary with user's IDs as keys, and their reaction as values
|
#: A dictionary with user's IDs as keys, and their reaction as values
|
||||||
reactions = attr.ib(factory=dict, type=Mapping[str, str])
|
reactions = attr.ib(factory=dict, type=Mapping[str, str])
|
||||||
#: A `Sticker`
|
#: A `Sticker`
|
||||||
sticker = attr.ib(None, type=_sticker.Sticker)
|
sticker = attr.ib(None, type=Optional[_sticker.Sticker])
|
||||||
#: A list of attachments
|
#: A list of attachments
|
||||||
attachments = attr.ib(factory=list, type=Sequence[_attachment.Attachment])
|
attachments = attr.ib(factory=list, type=Sequence[_attachment.Attachment])
|
||||||
#: A list of `QuickReply`
|
#: A list of `QuickReply`
|
||||||
quick_replies = attr.ib(factory=list, type=Sequence[_quick_reply.QuickReply])
|
quick_replies = attr.ib(factory=list, type=Sequence[_quick_reply.QuickReply])
|
||||||
#: Whether the message is unsent (deleted for everyone)
|
#: Whether the message is unsent (deleted for everyone)
|
||||||
unsent = attr.ib(False, type=bool)
|
unsent = attr.ib(False, type=Optional[bool])
|
||||||
#: Message ID you want to reply to
|
#: Message ID you want to reply to
|
||||||
reply_to_id = attr.ib(None, type=str)
|
reply_to_id = attr.ib(None, type=Optional[str])
|
||||||
#: Replied message
|
#: Replied message
|
||||||
replied_to = attr.ib(None, type="MessageData")
|
replied_to = attr.ib(None, type=Optional["MessageData"])
|
||||||
#: Whether the message was forwarded
|
#: Whether the message was forwarded
|
||||||
forwarded = attr.ib(False, type=bool)
|
forwarded = attr.ib(False, type=Optional[bool])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_forwarded_from_tags(tags):
|
def _get_forwarded_from_tags(tags):
|
||||||
|
@@ -4,7 +4,7 @@ import enum
|
|||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
from .. import _exception, _util, _session
|
from .. import _exception, _util, _session
|
||||||
|
|
||||||
from typing import Mapping, Sequence
|
from typing import Mapping, Sequence, Optional
|
||||||
|
|
||||||
|
|
||||||
class GuestStatus(enum.Enum):
|
class GuestStatus(enum.Enum):
|
||||||
@@ -132,13 +132,13 @@ class PlanData(Plan):
|
|||||||
#: Plan title
|
#: Plan title
|
||||||
title = attr.ib(type=str)
|
title = attr.ib(type=str)
|
||||||
#: Plan location name
|
#: Plan location name
|
||||||
location = attr.ib(None, converter=lambda x: x or "", type=str)
|
location = attr.ib(None, converter=lambda x: x or "", type=Optional[str])
|
||||||
#: Plan location ID
|
#: Plan location ID
|
||||||
location_id = attr.ib(None, converter=lambda x: x or "", type=str)
|
location_id = attr.ib(None, converter=lambda x: x or "", type=Optional[str])
|
||||||
#: ID of the plan creator
|
#: ID of the plan creator
|
||||||
author_id = attr.ib(None, type=str)
|
author_id = attr.ib(None, type=Optional[str])
|
||||||
#: `User` ids mapped to their `GuestStatus`
|
#: `User` ids mapped to their `GuestStatus`
|
||||||
guests = attr.ib(None, type=Mapping[str, GuestStatus])
|
guests = attr.ib(None, type=Optional[Mapping[str, GuestStatus]])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def going(self) -> Sequence[str]:
|
def going(self) -> Sequence[str]:
|
||||||
|
@@ -2,7 +2,7 @@ import attr
|
|||||||
from . import Attachment
|
from . import Attachment
|
||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
@@ -24,9 +24,9 @@ class QuickReplyText(QuickReply):
|
|||||||
"""Represents a text quick reply."""
|
"""Represents a text quick reply."""
|
||||||
|
|
||||||
#: Title of the quick reply
|
#: Title of the quick reply
|
||||||
title = attr.ib(None, type=str)
|
title = attr.ib(None, type=Optional[str])
|
||||||
#: URL of the quick reply image (optional)
|
#: URL of the quick reply image
|
||||||
image_url = attr.ib(None, type=str)
|
image_url = attr.ib(None, type=Optional[str])
|
||||||
#: Type of the quick reply
|
#: Type of the quick reply
|
||||||
_type = "text"
|
_type = "text"
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@ class QuickReplyLocation(QuickReply):
|
|||||||
class QuickReplyPhoneNumber(QuickReply):
|
class QuickReplyPhoneNumber(QuickReply):
|
||||||
"""Represents a phone number quick reply (Doesn't work on mobile)."""
|
"""Represents a phone number quick reply (Doesn't work on mobile)."""
|
||||||
|
|
||||||
#: URL of the quick reply image (optional)
|
#: URL of the quick reply image
|
||||||
image_url = attr.ib(None, type=str)
|
image_url = attr.ib(None, type=Optional[str])
|
||||||
#: Type of the quick reply
|
#: Type of the quick reply
|
||||||
_type = "user_phone_number"
|
_type = "user_phone_number"
|
||||||
|
|
||||||
@@ -53,8 +53,8 @@ class QuickReplyPhoneNumber(QuickReply):
|
|||||||
class QuickReplyEmail(QuickReply):
|
class QuickReplyEmail(QuickReply):
|
||||||
"""Represents an email quick reply (Doesn't work on mobile)."""
|
"""Represents an email quick reply (Doesn't work on mobile)."""
|
||||||
|
|
||||||
#: URL of the quick reply image (optional)
|
#: URL of the quick reply image
|
||||||
image_url = attr.ib(None, type=str)
|
image_url = attr.ib(None, type=Optional[str])
|
||||||
#: Type of the quick reply
|
#: Type of the quick reply
|
||||||
_type = "user_email"
|
_type = "user_email"
|
||||||
|
|
||||||
|
@@ -2,34 +2,36 @@ import attr
|
|||||||
from . import Image, Attachment
|
from . import Image, Attachment
|
||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
class Sticker(Attachment):
|
class Sticker(Attachment):
|
||||||
"""Represents a Facebook sticker that has been sent to a thread as an attachment."""
|
"""Represents a Facebook sticker that has been sent to a thread as an attachment."""
|
||||||
|
|
||||||
#: The sticker-pack's ID
|
#: The sticker-pack's ID
|
||||||
pack = attr.ib(None, type=str)
|
pack = attr.ib(None, type=Optional[str])
|
||||||
#: Whether the sticker is animated
|
#: Whether the sticker is animated
|
||||||
is_animated = attr.ib(False, type=bool)
|
is_animated = attr.ib(False, type=bool)
|
||||||
|
|
||||||
# If the sticker is animated, the following should be present
|
# If the sticker is animated, the following should be present
|
||||||
#: URL to a medium spritemap
|
#: URL to a medium spritemap
|
||||||
medium_sprite_image = attr.ib(None, type=str)
|
medium_sprite_image = attr.ib(None, type=Optional[str])
|
||||||
#: URL to a large spritemap
|
#: URL to a large spritemap
|
||||||
large_sprite_image = attr.ib(None, type=str)
|
large_sprite_image = attr.ib(None, type=Optional[str])
|
||||||
#: The amount of frames present in the spritemap pr. row
|
#: The amount of frames present in the spritemap pr. row
|
||||||
frames_per_row = attr.ib(None, type=int)
|
frames_per_row = attr.ib(None, type=Optional[int])
|
||||||
#: The amount of frames present in the spritemap pr. column
|
#: The amount of frames present in the spritemap pr. column
|
||||||
frames_per_col = attr.ib(None, type=int)
|
frames_per_col = attr.ib(None, type=Optional[int])
|
||||||
#: The total amount of frames in the spritemap
|
#: The total amount of frames in the spritemap
|
||||||
frame_count = attr.ib(None, type=int)
|
frame_count = attr.ib(None, type=Optional[int])
|
||||||
#: The frame rate the spritemap is intended to be played in
|
#: The frame rate the spritemap is intended to be played in
|
||||||
frame_rate = attr.ib(None, type=int)
|
frame_rate = attr.ib(None, type=Optional[int])
|
||||||
|
|
||||||
#: The sticker's image
|
#: The sticker's image
|
||||||
image = attr.ib(None, type=Image)
|
image = attr.ib(None, type=Optional[Image])
|
||||||
#: The sticker's label/name
|
#: The sticker's label/name
|
||||||
label = attr.ib(None, type=str)
|
label = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data):
|
def _from_graphql(cls, data):
|
||||||
|
@@ -56,10 +56,14 @@ def find_input_fields(html: str):
|
|||||||
|
|
||||||
|
|
||||||
def session_factory() -> requests.Session:
|
def session_factory() -> requests.Session:
|
||||||
|
from . import __version__
|
||||||
|
|
||||||
session = requests.session()
|
session = requests.session()
|
||||||
session.headers["Referer"] = "https://www.facebook.com"
|
session.headers["Referer"] = "https://www.facebook.com"
|
||||||
# TODO: Deprecate setting the user agent manually
|
# We won't try to set a fake user agent to mask our presence!
|
||||||
session.headers["User-Agent"] = random.choice(_util.USER_AGENTS)
|
# Facebook allows us access anyhow, and it makes our motives clearer:
|
||||||
|
# We're not trying to cheat Facebook, we simply want to access their service
|
||||||
|
session.headers["User-Agent"] = "fbchat/{}".format(__version__)
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
|
||||||
@@ -157,7 +161,7 @@ class Session:
|
|||||||
_session = attr.ib(factory=session_factory, type=requests.Session)
|
_session = attr.ib(factory=session_factory, type=requests.Session)
|
||||||
_counter = attr.ib(0, type=int)
|
_counter = attr.ib(0, type=int)
|
||||||
_client_id = attr.ib(factory=client_id_factory, type=str)
|
_client_id = attr.ib(factory=client_id_factory, type=str)
|
||||||
_logout_h = attr.ib(None, type=str)
|
_logout_h = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user(self):
|
def user(self):
|
||||||
|
@@ -319,7 +319,7 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
|||||||
Warning! If someone send a message to the thread that matches the query, while
|
Warning! If someone send a message to the thread that matches the query, while
|
||||||
we're searching, some snippets will get returned twice.
|
we're searching, some snippets will get returned twice.
|
||||||
|
|
||||||
This is fundamentally unfixable, it's just how the endpoint is implemented.
|
This is fundamentally not fixable, it's just how the endpoint is implemented.
|
||||||
|
|
||||||
The returned message snippets are ordered by last sent first.
|
The returned message snippets are ordered by last sent first.
|
||||||
|
|
||||||
|
@@ -4,7 +4,8 @@ from ._abc import ThreadABC
|
|||||||
from . import _user
|
from . import _user
|
||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
from .. import _util, _session, _graphql, _models
|
from .. import _util, _session, _graphql, _models
|
||||||
from typing import Sequence, Iterable, Set, Mapping
|
|
||||||
|
from typing import Sequence, Iterable, Set, Mapping, Optional
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
@@ -179,31 +180,31 @@ class GroupData(Group):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
#: The group's picture
|
#: The group's picture
|
||||||
photo = attr.ib(None, type="_models.Image")
|
photo = attr.ib(None, type=Optional["_models.Image"])
|
||||||
#: The name of the group
|
#: The name of the group
|
||||||
name = attr.ib(None, type=str)
|
name = attr.ib(None, type=Optional[str])
|
||||||
#: When the group was last active / when the last message was sent
|
#: When the group was last active / when the last message was sent
|
||||||
last_active = attr.ib(None, type=datetime.datetime)
|
last_active = attr.ib(None, type=Optional[datetime.datetime])
|
||||||
#: Number of messages in the group
|
#: Number of messages in the group
|
||||||
message_count = attr.ib(None, type=int)
|
message_count = attr.ib(None, type=Optional[int])
|
||||||
#: Set `Plan`
|
#: Set `Plan`
|
||||||
plan = attr.ib(None, type="_models.PlanData")
|
plan = attr.ib(None, type=Optional["_models.PlanData"])
|
||||||
#: The group thread's participant user ids
|
#: The group thread's participant user ids
|
||||||
participants = attr.ib(factory=set, type=Set[str])
|
participants = attr.ib(factory=set, type=Set[str])
|
||||||
#: A dictionary, containing user nicknames mapped to their IDs
|
#: A dictionary, containing user nicknames mapped to their IDs
|
||||||
nicknames = attr.ib(factory=dict, type=Mapping[str, str])
|
nicknames = attr.ib(factory=dict, type=Mapping[str, str])
|
||||||
#: The groups's message color
|
#: The groups's message color
|
||||||
color = attr.ib(None, type=str)
|
color = attr.ib(None, type=Optional[str])
|
||||||
#: The groups's default emoji
|
#: The groups's default emoji
|
||||||
emoji = attr.ib(None, type=str)
|
emoji = attr.ib(None, type=Optional[str])
|
||||||
# User ids of thread admins
|
# User ids of thread admins
|
||||||
admins = attr.ib(factory=set, type=Set[str])
|
admins = attr.ib(factory=set, type=Set[str])
|
||||||
# True if users need approval to join
|
# True if users need approval to join
|
||||||
approval_mode = attr.ib(None, type=bool)
|
approval_mode = attr.ib(None, type=Optional[bool])
|
||||||
# Set containing user IDs requesting to join
|
# Set containing user IDs requesting to join
|
||||||
approval_requests = attr.ib(factory=set, type=Set[str])
|
approval_requests = attr.ib(factory=set, type=Set[str])
|
||||||
# Link for joining group
|
# Link for joining group
|
||||||
join_link = attr.ib(None, type=str)
|
join_link = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, session, data):
|
def _from_graphql(cls, session, data):
|
||||||
|
@@ -4,6 +4,8 @@ from ._abc import ThreadABC
|
|||||||
from .._common import attrs_default
|
from .._common import attrs_default
|
||||||
from .. import _session, _models
|
from .. import _session, _models
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
class Page(ThreadABC):
|
class Page(ThreadABC):
|
||||||
@@ -39,21 +41,21 @@ class PageData(Page):
|
|||||||
#: The name of the page
|
#: The name of the page
|
||||||
name = attr.ib(type=str)
|
name = attr.ib(type=str)
|
||||||
#: When the thread was last active / when the last message was sent
|
#: When the thread was last active / when the last message was sent
|
||||||
last_active = attr.ib(None, type=datetime.datetime)
|
last_active = attr.ib(None, type=Optional[datetime.datetime])
|
||||||
#: Number of messages in the thread
|
#: Number of messages in the thread
|
||||||
message_count = attr.ib(None, type=int)
|
message_count = attr.ib(None, type=Optional[int])
|
||||||
#: Set `Plan`
|
#: Set `Plan`
|
||||||
plan = attr.ib(None, type="_models.PlanData")
|
plan = attr.ib(None, type=Optional["_models.PlanData"])
|
||||||
#: The page's custom URL
|
#: The page's custom URL
|
||||||
url = attr.ib(None, type=str)
|
url = attr.ib(None, type=Optional[str])
|
||||||
#: The name of the page's location city
|
#: The name of the page's location city
|
||||||
city = attr.ib(None, type=str)
|
city = attr.ib(None, type=Optional[str])
|
||||||
#: Amount of likes the page has
|
#: Amount of likes the page has
|
||||||
likes = attr.ib(None, type=int)
|
likes = attr.ib(None, type=Optional[int])
|
||||||
#: Some extra information about the page
|
#: Some extra information about the page
|
||||||
sub_title = attr.ib(None, type=str)
|
sub_title = attr.ib(None, type=Optional[str])
|
||||||
#: The page's category
|
#: The page's category
|
||||||
category = attr.ib(None, type=str)
|
category = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, session, data):
|
def _from_graphql(cls, session, data):
|
||||||
|
@@ -4,6 +4,8 @@ from ._abc import ThreadABC
|
|||||||
from .._common import log, attrs_default
|
from .._common import log, attrs_default
|
||||||
from .. import _util, _session, _models
|
from .. import _util, _session, _models
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
GENDERS = {
|
GENDERS = {
|
||||||
# For standard requests
|
# For standard requests
|
||||||
@@ -111,27 +113,27 @@ class UserData(User):
|
|||||||
#: The users first name
|
#: The users first name
|
||||||
first_name = attr.ib(type=str)
|
first_name = attr.ib(type=str)
|
||||||
#: The users last name
|
#: The users last name
|
||||||
last_name = attr.ib(None, type=str)
|
last_name = attr.ib(None, type=Optional[str])
|
||||||
#: Datetime when the thread was last active / when the last message was sent
|
#: When the thread was last active / when the last message was sent
|
||||||
last_active = attr.ib(None, type=datetime.datetime)
|
last_active = attr.ib(None, type=Optional[datetime.datetime])
|
||||||
#: Number of messages in the thread
|
#: Number of messages in the thread
|
||||||
message_count = attr.ib(None, type=int)
|
message_count = attr.ib(None, type=Optional[int])
|
||||||
#: Set `Plan`
|
#: Set `Plan`
|
||||||
plan = attr.ib(None, type="_models.PlanData")
|
plan = attr.ib(None, type=Optional["_models.PlanData"])
|
||||||
#: The profile URL. ``None`` for Messenger-only users
|
#: The profile URL. ``None`` for Messenger-only users
|
||||||
url = attr.ib(None, type=str)
|
url = attr.ib(None, type=Optional[str])
|
||||||
#: The user's gender
|
#: The user's gender
|
||||||
gender = attr.ib(None, type=str)
|
gender = attr.ib(None, type=Optional[str])
|
||||||
#: From 0 to 1. How close the client is to the user
|
#: From 0 to 1. How close the client is to the user
|
||||||
affinity = attr.ib(None, type=float)
|
affinity = attr.ib(None, type=Optional[float])
|
||||||
#: The user's nickname
|
#: The user's nickname
|
||||||
nickname = attr.ib(None, type=str)
|
nickname = attr.ib(None, type=Optional[str])
|
||||||
#: The clients nickname, as seen by the user
|
#: The clients nickname, as seen by the user
|
||||||
own_nickname = attr.ib(None, type=str)
|
own_nickname = attr.ib(None, type=Optional[str])
|
||||||
#: The message color
|
#: The message color
|
||||||
color = attr.ib(None, type=str)
|
color = attr.ib(None, type=Optional[str])
|
||||||
#: The default emoji
|
#: The default emoji
|
||||||
emoji = attr.ib(None, type=str)
|
emoji = attr.ib(None, type=Optional[str])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_other_user(data):
|
def _get_other_user(data):
|
||||||
|
@@ -9,15 +9,12 @@ from . import _exception
|
|||||||
|
|
||||||
from typing import Iterable, Optional, Any, Mapping, Sequence
|
from typing import Iterable, Optional, Any, Mapping, Sequence
|
||||||
|
|
||||||
#: Default list of user agents
|
|
||||||
USER_AGENTS = [
|
def int_or_none(inp: Any) -> Optional[int]:
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
try:
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/601.1.10 (KHTML, like Gecko) Version/8.0.5 Safari/601.1.10",
|
return int(inp)
|
||||||
"Mozilla/5.0 (Windows NT 6.3; WOW64; ; NCT50_AAP285C84A1328) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
except Exception:
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
|
return None
|
||||||
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
|
|
||||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_limits(limit: Optional[int], max_limit: int) -> Iterable[int]:
|
def get_limits(limit: Optional[int], max_limit: int) -> Iterable[int]:
|
||||||
|
Reference in New Issue
Block a user