Merge pull request #472 from carpedm20/use-datetime
Use datetime/timedelta objects
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
import attr
|
||||
from . import _util
|
||||
from ._attachment import Attachment
|
||||
|
||||
|
||||
@@ -36,7 +37,7 @@ class AudioAttachment(Attachment):
|
||||
filename = attr.ib(None)
|
||||
#: URL of the audio file
|
||||
url = attr.ib(None)
|
||||
#: Duration of the audio clip in milliseconds
|
||||
#: Duration of the audio clip as a timedelta
|
||||
duration = attr.ib(None)
|
||||
#: Audio type
|
||||
audio_type = attr.ib(None)
|
||||
@@ -49,7 +50,7 @@ class AudioAttachment(Attachment):
|
||||
return cls(
|
||||
filename=data.get("filename"),
|
||||
url=data.get("playable_url"),
|
||||
duration=data.get("playable_duration_in_ms"),
|
||||
duration=_util.millis_to_timedelta(data.get("playable_duration_in_ms")),
|
||||
audio_type=data.get("audio_type"),
|
||||
)
|
||||
|
||||
@@ -175,7 +176,7 @@ class VideoAttachment(Attachment):
|
||||
width = attr.ib(None)
|
||||
#: Height of original video
|
||||
height = attr.ib(None)
|
||||
#: Length of video in milliseconds
|
||||
#: Length of video as a timedelta
|
||||
duration = attr.ib(None)
|
||||
#: URL to very compressed preview video
|
||||
preview_url = attr.ib(None)
|
||||
@@ -243,7 +244,7 @@ class VideoAttachment(Attachment):
|
||||
return cls(
|
||||
width=data.get("original_dimensions", {}).get("width"),
|
||||
height=data.get("original_dimensions", {}).get("height"),
|
||||
duration=data.get("playable_duration_in_ms"),
|
||||
duration=_util.millis_to_timedelta(data.get("playable_duration_in_ms")),
|
||||
preview_url=data.get("playable_url"),
|
||||
small_image=data.get("chat_image"),
|
||||
medium_image=data.get("inbox_image"),
|
||||
@@ -255,7 +256,7 @@ class VideoAttachment(Attachment):
|
||||
def _from_subattachment(cls, data):
|
||||
media = data["media"]
|
||||
return cls(
|
||||
duration=media.get("playable_duration_in_ms"),
|
||||
duration=_util.millis_to_timedelta(media.get("playable_duration_in_ms")),
|
||||
preview_url=media.get("playable_url"),
|
||||
medium_image=media.get("image"),
|
||||
uid=data["target"].get("video_id"),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import attr
|
||||
from . import _plan
|
||||
from . import _util, _plan
|
||||
from ._thread import ThreadType, Thread
|
||||
|
||||
|
||||
@@ -63,11 +63,11 @@ class Group(Thread):
|
||||
if data.get("image") is None:
|
||||
data["image"] = {}
|
||||
c_info = cls._parse_customization_info(data)
|
||||
last_message_timestamp = None
|
||||
last_active = None
|
||||
if "last_message" in data:
|
||||
last_message_timestamp = data["last_message"]["nodes"][0][
|
||||
"timestamp_precise"
|
||||
]
|
||||
last_active = _util.millis_to_datetime(
|
||||
int(data["last_message"]["nodes"][0]["timestamp_precise"])
|
||||
)
|
||||
plan = None
|
||||
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
||||
plan = _plan.Plan._from_graphql(data["event_reminders"]["nodes"][0])
|
||||
@@ -97,7 +97,7 @@ class Group(Thread):
|
||||
photo=data["image"].get("uri"),
|
||||
name=data.get("name"),
|
||||
message_count=data.get("messages_count"),
|
||||
last_message_timestamp=last_message_timestamp,
|
||||
last_active=last_active,
|
||||
plan=plan,
|
||||
)
|
||||
|
||||
|
@@ -59,14 +59,14 @@ class LiveLocationAttachment(LocationAttachment):
|
||||
|
||||
#: Name of the location
|
||||
name = attr.ib(None)
|
||||
#: Timestamp when live location expires
|
||||
expiration_time = attr.ib(None)
|
||||
#: Datetime when live location expires
|
||||
expires_at = attr.ib(None)
|
||||
#: True if live location is expired
|
||||
is_expired = attr.ib(None)
|
||||
|
||||
def __init__(self, name=None, expiration_time=None, is_expired=None, **kwargs):
|
||||
def __init__(self, name=None, expires_at=None, is_expired=None, **kwargs):
|
||||
super(LiveLocationAttachment, self).__init__(**kwargs)
|
||||
self.expiration_time = expiration_time
|
||||
self.expires_at = expires_at
|
||||
self.is_expired = is_expired
|
||||
|
||||
@classmethod
|
||||
@@ -80,7 +80,7 @@ class LiveLocationAttachment(LocationAttachment):
|
||||
if not data.get("stopReason")
|
||||
else None,
|
||||
name=data.get("locationTitle"),
|
||||
expiration_time=data["expirationTime"],
|
||||
expires_at=_util.millis_to_datetime(data["expirationTime"]),
|
||||
is_expired=bool(data.get("stopReason")),
|
||||
)
|
||||
|
||||
@@ -96,7 +96,7 @@ class LiveLocationAttachment(LocationAttachment):
|
||||
if target.get("coordinate")
|
||||
else None,
|
||||
name=data["title_with_entities"]["text"],
|
||||
expiration_time=target.get("expiration_time"),
|
||||
expires_at=_util.millis_to_datetime(target.get("expiration_time")),
|
||||
is_expired=target.get("is_expired"),
|
||||
)
|
||||
media = data.get("media")
|
||||
|
@@ -68,8 +68,8 @@ class Message:
|
||||
uid = attr.ib(None, init=False)
|
||||
#: ID of the sender
|
||||
author = attr.ib(None, init=False)
|
||||
#: Timestamp of when the message was sent
|
||||
timestamp = attr.ib(None, init=False)
|
||||
#: Datetime of when the message was sent
|
||||
created_at = attr.ib(None, init=False)
|
||||
#: Whether the message is read
|
||||
is_read = attr.ib(None, init=False)
|
||||
#: A list of people IDs who read the message, works only with :func:`fbchat.Client.fetchThreadMessages`
|
||||
@@ -220,7 +220,7 @@ class Message:
|
||||
rtn.forwarded = cls._get_forwarded_from_tags(tags)
|
||||
rtn.uid = str(data["message_id"])
|
||||
rtn.author = str(data["message_sender"]["id"])
|
||||
rtn.timestamp = data.get("timestamp_precise")
|
||||
rtn.created_at = _util.millis_to_datetime(int(data.get("timestamp_precise")))
|
||||
rtn.unsent = False
|
||||
if data.get("unread") is not None:
|
||||
rtn.is_read = not data["unread"]
|
||||
@@ -271,7 +271,7 @@ class Message:
|
||||
rtn.forwarded = cls._get_forwarded_from_tags(tags)
|
||||
rtn.uid = metadata.get("messageId")
|
||||
rtn.author = str(metadata.get("actorFbId"))
|
||||
rtn.timestamp = metadata.get("timestamp")
|
||||
rtn.created_at = _util.millis_to_datetime(metadata.get("timestamp"))
|
||||
rtn.unsent = False
|
||||
if data.get("data", {}).get("platform_xmd"):
|
||||
quick_replies = json.loads(data["data"]["platform_xmd"]).get(
|
||||
@@ -307,11 +307,11 @@ class Message:
|
||||
return rtn
|
||||
|
||||
@classmethod
|
||||
def _from_pull(cls, data, mid=None, tags=None, author=None, timestamp=None):
|
||||
def _from_pull(cls, data, mid=None, tags=None, author=None, created_at=None):
|
||||
rtn = cls(text=data.get("body"))
|
||||
rtn.uid = mid
|
||||
rtn.author = author
|
||||
rtn.timestamp = timestamp
|
||||
rtn.created_at = created_at
|
||||
|
||||
if data.get("data") and data["data"].get("prng"):
|
||||
try:
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import attr
|
||||
import json
|
||||
from ._core import Enum
|
||||
from . import _util
|
||||
|
||||
|
||||
class GuestStatus(Enum):
|
||||
@@ -15,8 +16,8 @@ class Plan:
|
||||
|
||||
#: ID of the plan
|
||||
uid = attr.ib(None, init=False)
|
||||
#: Plan time (timestamp), only precise down to the minute
|
||||
time = attr.ib(converter=int)
|
||||
#: Plan time (datetime), only precise down to the minute
|
||||
time = attr.ib()
|
||||
#: Plan title
|
||||
title = attr.ib()
|
||||
#: Plan location name
|
||||
@@ -58,7 +59,7 @@ class Plan:
|
||||
@classmethod
|
||||
def _from_pull(cls, data):
|
||||
rtn = cls(
|
||||
time=data.get("event_time"),
|
||||
time=_util.seconds_to_datetime(int(data.get("event_time"))),
|
||||
title=data.get("event_title"),
|
||||
location=data.get("event_location_name"),
|
||||
location_id=data.get("event_location_id"),
|
||||
@@ -74,7 +75,7 @@ class Plan:
|
||||
@classmethod
|
||||
def _from_fetch(cls, data):
|
||||
rtn = cls(
|
||||
time=data.get("event_time"),
|
||||
time=_util.seconds_to_datetime(data.get("event_time")),
|
||||
title=data.get("title"),
|
||||
location=data.get("location_name"),
|
||||
location_id=str(data["location_id"]) if data.get("location_id") else None,
|
||||
@@ -87,7 +88,7 @@ class Plan:
|
||||
@classmethod
|
||||
def _from_graphql(cls, data):
|
||||
rtn = cls(
|
||||
time=data.get("time"),
|
||||
time=_util.seconds_to_datetime(data.get("time")),
|
||||
title=data.get("event_title"),
|
||||
location=data.get("location_name"),
|
||||
)
|
||||
|
@@ -81,8 +81,8 @@ class Thread:
|
||||
photo = attr.ib(None)
|
||||
#: The name of the thread
|
||||
name = attr.ib(None)
|
||||
#: Timestamp of last message
|
||||
last_message_timestamp = attr.ib(None)
|
||||
#: Datetime when the thread was last active / when the last message was sent
|
||||
last_active = attr.ib(None)
|
||||
#: Number of messages in the thread
|
||||
message_count = attr.ib(None)
|
||||
#: Set :class:`Plan`
|
||||
@@ -94,7 +94,7 @@ class Thread:
|
||||
uid,
|
||||
photo=None,
|
||||
name=None,
|
||||
last_message_timestamp=None,
|
||||
last_active=None,
|
||||
message_count=None,
|
||||
plan=None,
|
||||
):
|
||||
@@ -102,7 +102,7 @@ class Thread:
|
||||
self.type = _type
|
||||
self.photo = photo
|
||||
self.name = name
|
||||
self.last_message_timestamp = last_message_timestamp
|
||||
self.last_active = last_active
|
||||
self.message_count = message_count
|
||||
self.plan = plan
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import attr
|
||||
from ._core import Enum
|
||||
from . import _plan
|
||||
from . import _util, _plan
|
||||
from ._thread import ThreadType, Thread
|
||||
|
||||
|
||||
@@ -131,11 +131,11 @@ class User(Thread):
|
||||
user = next(
|
||||
p for p in participants if p["id"] == data["thread_key"]["other_user_id"]
|
||||
)
|
||||
last_message_timestamp = None
|
||||
last_active = None
|
||||
if "last_message" in data:
|
||||
last_message_timestamp = data["last_message"]["nodes"][0][
|
||||
"timestamp_precise"
|
||||
]
|
||||
last_active = _util.millis_to_datetime(
|
||||
int(data["last_message"]["nodes"][0]["timestamp_precise"])
|
||||
)
|
||||
|
||||
first_name = user.get("short_name")
|
||||
if first_name is None:
|
||||
@@ -162,7 +162,7 @@ class User(Thread):
|
||||
own_nickname=c_info.get("own_nickname"),
|
||||
photo=user["big_image_src"].get("uri"),
|
||||
message_count=data.get("messages_count"),
|
||||
last_message_timestamp=last_message_timestamp,
|
||||
last_active=last_active,
|
||||
plan=plan,
|
||||
)
|
||||
|
||||
@@ -183,7 +183,7 @@ class User(Thread):
|
||||
class ActiveStatus:
|
||||
#: Whether the user is active now
|
||||
active = attr.ib(None)
|
||||
#: Timestamp when the user was last active
|
||||
#: Datetime when the user was last active
|
||||
last_active = attr.ib(None)
|
||||
#: Whether the user is playing Messenger game now
|
||||
in_game = attr.ib(None)
|
||||
@@ -192,7 +192,7 @@ class ActiveStatus:
|
||||
def _from_chatproxy_presence(cls, id_, data):
|
||||
return cls(
|
||||
active=data["p"] in [2, 3] if "p" in data else None,
|
||||
last_active=data.get("lat"),
|
||||
last_active=_util.millis_to_datetime(data.get("lat")),
|
||||
in_game=int(id_) in data.get("gamers", {}),
|
||||
)
|
||||
|
||||
@@ -200,6 +200,6 @@ class ActiveStatus:
|
||||
def _from_buddylist_overlay(cls, data, in_game=None):
|
||||
return cls(
|
||||
active=data["a"] in [2, 3] if "a" in data else None,
|
||||
last_active=data.get("la"),
|
||||
last_active=_util.millis_to_datetime(data.get("la")),
|
||||
in_game=None,
|
||||
)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
@@ -233,3 +234,57 @@ def prefix_url(url):
|
||||
if url.startswith("/"):
|
||||
return "https://www.facebook.com" + url
|
||||
return url
|
||||
|
||||
|
||||
def seconds_to_datetime(timestamp_in_seconds):
|
||||
"""Convert an UTC timestamp to a timezone-aware datetime object."""
|
||||
# `.utcfromtimestamp` will return a "naive" datetime object, which is why we use the
|
||||
# following:
|
||||
return datetime.datetime.fromtimestamp(
|
||||
timestamp_in_seconds, tz=datetime.timezone.utc
|
||||
)
|
||||
|
||||
|
||||
def millis_to_datetime(timestamp_in_milliseconds):
|
||||
"""Convert an UTC timestamp, in milliseconds, to a timezone-aware datetime."""
|
||||
return seconds_to_datetime(timestamp_in_milliseconds / 1000)
|
||||
|
||||
|
||||
def datetime_to_seconds(dt):
|
||||
"""Convert a datetime to an UTC timestamp.
|
||||
|
||||
Naive datetime objects are presumed to represent time in the system timezone.
|
||||
|
||||
The returned seconds will be rounded to the nearest whole number.
|
||||
"""
|
||||
# We could've implemented some fancy "convert naive timezones to UTC" logic, but
|
||||
# it's not really worth the effort.
|
||||
return round(dt.timestamp())
|
||||
|
||||
|
||||
def datetime_to_millis(dt):
|
||||
"""Convert a datetime to an UTC timestamp, in milliseconds.
|
||||
|
||||
Naive datetime objects are presumed to represent time in the system timezone.
|
||||
|
||||
The returned milliseconds will be rounded to the nearest whole number.
|
||||
"""
|
||||
return round(dt.timestamp() * 1000)
|
||||
|
||||
|
||||
def seconds_to_timedelta(seconds):
|
||||
"""Convert seconds to a timedelta."""
|
||||
return datetime.timedelta(seconds=seconds)
|
||||
|
||||
|
||||
def millis_to_timedelta(milliseconds):
|
||||
"""Convert a duration (in milliseconds) to a timedelta object."""
|
||||
return datetime.timedelta(milliseconds=milliseconds)
|
||||
|
||||
|
||||
def timedelta_to_seconds(td):
|
||||
"""Convert a timedelta to seconds.
|
||||
|
||||
The returned seconds will be rounded to the nearest whole number.
|
||||
"""
|
||||
return round(td.total_seconds())
|
||||
|
Reference in New Issue
Block a user