Merge pull request #472 from carpedm20/use-datetime

Use datetime/timedelta objects
This commit is contained in:
Mads Marquart
2019-09-08 18:41:41 +02:00
committed by GitHub
9 changed files with 257 additions and 186 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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"),

View File

@@ -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,
)

View File

@@ -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")

View File

@@ -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:

View File

@@ -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"),
)

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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())