Refactor message sending
This commit is contained in:
@@ -9,7 +9,7 @@ print("Own id: {}".format(session.user_id))
|
||||
user = fbchat.Thread(session=session, id=session.user_id)
|
||||
|
||||
# Send a message to yourself
|
||||
user.send(fbchat.Message(text="Hi me!"))
|
||||
user.send_text("Hi me!")
|
||||
|
||||
# Log the user out
|
||||
session.logout()
|
||||
|
@@ -10,7 +10,7 @@ class EchoBot(fbchat.Client):
|
||||
|
||||
# If you're not the author, echo
|
||||
if author_id != self.session.user_id:
|
||||
thread.send(message_object)
|
||||
thread.send_text(message_object.text)
|
||||
|
||||
|
||||
session = fbchat.Session.login("<email>", "<password>")
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import fbchat
|
||||
import requests
|
||||
|
||||
session = fbchat.Session.login("<email>", "<password>")
|
||||
|
||||
@@ -9,34 +10,32 @@ thread = fbchat.User(session=session, id=session.user_id)
|
||||
# thread = fbchat.Group(session=session, id="1234567890")
|
||||
|
||||
# Will send a message to the thread
|
||||
thread.send(fbchat.Message(text="<message>"))
|
||||
thread.send_text("<message>")
|
||||
|
||||
# Will send the default `like` emoji
|
||||
thread.send(fbchat.Message(emoji_size=fbchat.EmojiSize.LARGE))
|
||||
thread.send_sticker(fbchat.EmojiSize.LARGE.value)
|
||||
|
||||
# Will send the emoji `👍`
|
||||
thread.send(fbchat.Message(text="👍", emoji_size=fbchat.EmojiSize.LARGE))
|
||||
thread.send_emoji("👍", size=fbchat.EmojiSize.LARGE)
|
||||
|
||||
# Will send the sticker with ID `767334476626295`
|
||||
thread.send(fbchat.Message(sticker=fbchat.Sticker("767334476626295")))
|
||||
thread.send_sticker("767334476626295")
|
||||
|
||||
# Will send a message with a mention
|
||||
thread.send(
|
||||
fbchat.Message(
|
||||
thread.send_text(
|
||||
text="This is a @mention",
|
||||
mentions=[fbchat.Mention(thread.id, offset=10, length=8)],
|
||||
)
|
||||
)
|
||||
|
||||
# Will send the image located at `<image path>`
|
||||
thread.send_local_image(
|
||||
"<image path>", message=fbchat.Message(text="This is a local image")
|
||||
)
|
||||
with open("<image path>", "rb") as f:
|
||||
files = session._upload([("image_name.png", f, "image/png")])
|
||||
thread.send_text(text="This is a local image", files=files)
|
||||
|
||||
# Will download the image at the URL `<image url>`, and then send it
|
||||
thread.send_remote_image(
|
||||
"<image url>", message=fbchat.Message(text="This is a remote image")
|
||||
)
|
||||
r = requests.get("<image url>")
|
||||
files = session._upload([("image_name.png", r.content, "image/png")])
|
||||
thread.send_files(files) # Alternative to .send_text
|
||||
|
||||
|
||||
# Only do these actions if the thread is a group
|
||||
@@ -46,7 +45,7 @@ if isinstance(thread, fbchat.Group):
|
||||
# Will add the users with IDs `<1st user id>`, `<2nd user id>` and `<3th user id>` to the group
|
||||
thread.add_participants(["<1st user id>", "<2nd user id>", "<3rd user id>"])
|
||||
# Will change the title of the group to `<title>`
|
||||
thread.change_title("<title>")
|
||||
thread.set_title("<title>")
|
||||
|
||||
|
||||
# Will change the nickname of the user `<user_id>` to `<new nickname>`
|
||||
|
@@ -204,52 +204,6 @@ class Message:
|
||||
return False
|
||||
return any(map(lambda tag: "forward" in tag or "copy" in tag, tags))
|
||||
|
||||
def _to_send_data(self):
|
||||
data = {}
|
||||
|
||||
if self.text or self.sticker or self.emoji_size:
|
||||
data["action_type"] = "ma-type:user-generated-message"
|
||||
|
||||
if self.text:
|
||||
data["body"] = self.text
|
||||
|
||||
for i, mention in enumerate(self.mentions):
|
||||
data.update(mention._to_send_data(i))
|
||||
|
||||
if self.emoji_size:
|
||||
if self.text:
|
||||
data["tags[0]"] = "hot_emoji_size:" + self.emoji_size.name.lower()
|
||||
else:
|
||||
data["sticker_id"] = self.emoji_size.value
|
||||
|
||||
if self.sticker:
|
||||
data["sticker_id"] = self.sticker.id
|
||||
|
||||
if self.quick_replies:
|
||||
xmd = {"quick_replies": []}
|
||||
for quick_reply in self.quick_replies:
|
||||
# TODO: Move this to `_quick_reply.py`
|
||||
q = dict()
|
||||
q["content_type"] = quick_reply._type
|
||||
q["payload"] = quick_reply.payload
|
||||
q["external_payload"] = quick_reply.external_payload
|
||||
q["data"] = quick_reply.data
|
||||
if quick_reply.is_response:
|
||||
q["ignore_for_webhook"] = False
|
||||
if isinstance(quick_reply, _quick_reply.QuickReplyText):
|
||||
q["title"] = quick_reply.title
|
||||
if not isinstance(quick_reply, _quick_reply.QuickReplyLocation):
|
||||
q["image_url"] = quick_reply.image_url
|
||||
xmd["quick_replies"].append(q)
|
||||
if len(self.quick_replies) == 1 and self.quick_replies[0].is_response:
|
||||
xmd["quick_replies"] = xmd["quick_replies"][0]
|
||||
data["platform_xmd"] = json.dumps(xmd)
|
||||
|
||||
if self.reply_to_id:
|
||||
data["replied_to_message_id"] = self.reply_to_id
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _parse_quick_replies(data):
|
||||
if data:
|
||||
|
@@ -73,6 +73,17 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
def _to_send_data(self) -> MutableMapping[str, str]:
|
||||
raise NotImplementedError
|
||||
|
||||
# Note:
|
||||
# You can go out of Facebook's spec with `self.session._do_send_request`!
|
||||
#
|
||||
# A few examples:
|
||||
# - You can send a sticker and an emoji at the same time
|
||||
# - You can wave, send a sticker and text at the same time
|
||||
# - You can reply to a message with a sticker
|
||||
#
|
||||
# We won't support those use cases, it'll make for a confusing API!
|
||||
# If we absolutely need to in the future, we can always add extra functionality
|
||||
|
||||
def wave(self, first: bool = True) -> str:
|
||||
"""Wave hello to the thread.
|
||||
|
||||
@@ -85,73 +96,127 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
"INITIATED" if first else "RECIPROCATED"
|
||||
)
|
||||
data["lightweight_action_attachment[lwa_type]"] = "WAVE"
|
||||
# TODO: This!
|
||||
# if isinstance(self, _user.User):
|
||||
# data["specific_to_list[0]"] = "fbid:{}".format(thread_id)
|
||||
message_id, thread_id = self.session._do_send_request(data)
|
||||
return message_id
|
||||
|
||||
def send(self, message) -> str:
|
||||
"""Send message to the thread.
|
||||
def send_text(
|
||||
self,
|
||||
text: str,
|
||||
mentions: Iterable["_message.Mention"] = None,
|
||||
files: Iterable[Tuple[str, str]] = None,
|
||||
reply_to_id: str = None,
|
||||
) -> str:
|
||||
"""Send a message to the thread.
|
||||
|
||||
Args:
|
||||
message (Message): Message to send
|
||||
text: Text to send
|
||||
mentions: Optional mentions
|
||||
files: Optional tuples, each containing an uploaded file's ID and mimetype
|
||||
reply_to_id: Optional message to reply to
|
||||
|
||||
Returns:
|
||||
:ref:`Message ID <intro_message_ids>` of the sent message
|
||||
"""
|
||||
data = self._to_send_data()
|
||||
data.update(message._to_send_data())
|
||||
data["action_type"] = "ma-type:user-generated-message"
|
||||
if text is None: # To support `send_files`
|
||||
data["body"] = text
|
||||
|
||||
for i, mention in enumerate(mentions or ()):
|
||||
data.update(mention._to_send_data(i))
|
||||
|
||||
if files:
|
||||
data["has_attachment"] = True
|
||||
|
||||
for i, (file_id, mimetype) in enumerate(files or ()):
|
||||
data["{}s[{}]".format(_util.mimetype_to_key(mimetype), i)] = file_id
|
||||
|
||||
if reply_to_id:
|
||||
data["replied_to_message_id"] = reply_to_id
|
||||
|
||||
return self.session._do_send_request(data)
|
||||
|
||||
def _send_location(self, current, latitude, longitude, message=None) -> str:
|
||||
def send_emoji(self, emoji: str, size: "_message.EmojiSize") -> str:
|
||||
"""Send an emoji to the thread.
|
||||
|
||||
Args:
|
||||
emoji: The emoji to send
|
||||
size: The size of the emoji
|
||||
|
||||
Returns:
|
||||
:ref:`Message ID <intro_message_ids>` of the sent message
|
||||
"""
|
||||
data = self._to_send_data()
|
||||
data["action_type"] = "ma-type:user-generated-message"
|
||||
data["body"] = emoji
|
||||
data["tags[0]"] = "hot_emoji_size:{}".format(size.name.lower())
|
||||
return self.session._do_send_request(data)
|
||||
|
||||
def send_sticker(self, sticker_id: str) -> str:
|
||||
"""Send a sticker to the thread.
|
||||
|
||||
Args:
|
||||
sticker_id: ID of the sticker to send
|
||||
|
||||
Returns:
|
||||
:ref:`Message ID <intro_message_ids>` of the sent message
|
||||
"""
|
||||
data = self._to_send_data()
|
||||
data["action_type"] = "ma-type:user-generated-message"
|
||||
data["sticker_id"] = sticker_id
|
||||
return self.session._do_send_request(data)
|
||||
|
||||
def _send_location(self, current, latitude, longitude) -> str:
|
||||
data = self._to_send_data()
|
||||
if message is not None:
|
||||
data.update(message._to_send_data())
|
||||
data["action_type"] = "ma-type:user-generated-message"
|
||||
data["location_attachment[coordinates][latitude]"] = latitude
|
||||
data["location_attachment[coordinates][longitude]"] = longitude
|
||||
data["location_attachment[is_current_location]"] = current
|
||||
return self.session._do_send_request(data)
|
||||
|
||||
def send_location(self, latitude: float, longitude: float, message=None):
|
||||
def send_location(self, latitude: float, longitude: float):
|
||||
"""Send a given location to a thread as the user's current location.
|
||||
|
||||
Args:
|
||||
latitude: The location latitude
|
||||
longitude: The location longitude
|
||||
message: Additional message
|
||||
"""
|
||||
self._send_location(
|
||||
True, latitude=latitude, longitude=longitude, message=message,
|
||||
)
|
||||
self._send_location(True, latitude=latitude, longitude=longitude)
|
||||
|
||||
def send_pinned_location(self, latitude: float, longitude: float, message=None):
|
||||
def send_pinned_location(self, latitude: float, longitude: float):
|
||||
"""Send a given location to a thread as a pinned location.
|
||||
|
||||
Args:
|
||||
latitude: The location latitude
|
||||
longitude: The location longitude
|
||||
message: Additional message
|
||||
"""
|
||||
self._send_location(
|
||||
False, latitude=latitude, longitude=longitude, message=message,
|
||||
)
|
||||
self._send_location(False, latitude=latitude, longitude=longitude)
|
||||
|
||||
def send_files(self, files: Iterable[Tuple[str, str]], message):
|
||||
def send_files(self, files: Iterable[Tuple[str, str]]):
|
||||
"""Send files from file IDs to a thread.
|
||||
|
||||
`files` should be a list of tuples, with a file's ID and mimetype.
|
||||
"""
|
||||
data = self._to_send_data()
|
||||
data.update(message._to_send_data())
|
||||
data["action_type"] = "ma-type:user-generated-message"
|
||||
data["has_attachment"] = True
|
||||
return self.send_text(text=None, files=files)
|
||||
|
||||
for i, (file_id, mimetype) in enumerate(files):
|
||||
data["{}s[{}]".format(_util.mimetype_to_key(mimetype), i)] = file_id
|
||||
|
||||
return self.session._do_send_request(data)
|
||||
# xmd = {"quick_replies": []}
|
||||
# for quick_reply in quick_replies:
|
||||
# # TODO: Move this to `_quick_reply.py`
|
||||
# q = dict()
|
||||
# q["content_type"] = quick_reply._type
|
||||
# q["payload"] = quick_reply.payload
|
||||
# q["external_payload"] = quick_reply.external_payload
|
||||
# q["data"] = quick_reply.data
|
||||
# if quick_reply.is_response:
|
||||
# q["ignore_for_webhook"] = False
|
||||
# if isinstance(quick_reply, _quick_reply.QuickReplyText):
|
||||
# q["title"] = quick_reply.title
|
||||
# if not isinstance(quick_reply, _quick_reply.QuickReplyLocation):
|
||||
# q["image_url"] = quick_reply.image_url
|
||||
# xmd["quick_replies"].append(q)
|
||||
# if len(quick_replies) == 1 and quick_replies[0].is_response:
|
||||
# xmd["quick_replies"] = xmd["quick_replies"][0]
|
||||
# data["platform_xmd"] = json.dumps(xmd)
|
||||
|
||||
# TODO: This!
|
||||
# def quick_reply(self, quick_reply, payload=None):
|
||||
|
@@ -50,7 +50,11 @@ class User(_thread.ThreadABC):
|
||||
id = attr.ib(converter=str)
|
||||
|
||||
def _to_send_data(self):
|
||||
return {"other_user_fbid": self.id}
|
||||
return {
|
||||
"other_user_fbid": self.id,
|
||||
# The entry below is to support .wave
|
||||
"specific_to_list[0]": "fbid:{}".format(self.id),
|
||||
}
|
||||
|
||||
def confirm_friend_request(self):
|
||||
"""Confirm a friend request, adding the user to your friend list."""
|
||||
|
@@ -2,11 +2,7 @@ import datetime
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import contextlib
|
||||
import mimetypes
|
||||
import urllib.parse
|
||||
import requests
|
||||
from os import path
|
||||
|
||||
from ._core import log
|
||||
from ._exception import (
|
||||
@@ -188,39 +184,6 @@ def mimetype_to_key(mimetype):
|
||||
return "file_id"
|
||||
|
||||
|
||||
def get_files_from_urls(file_urls):
|
||||
files = []
|
||||
for file_url in file_urls:
|
||||
r = requests.get(file_url)
|
||||
# We could possibly use r.headers.get('Content-Disposition'), see
|
||||
# https://stackoverflow.com/a/37060758
|
||||
file_name = path.basename(file_url).split("?")[0].split("#")[0]
|
||||
files.append(
|
||||
(
|
||||
file_name,
|
||||
r.content,
|
||||
r.headers.get("Content-Type") or mimetypes.guess_type(file_name)[0],
|
||||
)
|
||||
)
|
||||
return files
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def get_files_from_paths(filenames):
|
||||
files = []
|
||||
for filename in filenames:
|
||||
files.append(
|
||||
(
|
||||
path.basename(filename),
|
||||
open(filename, "rb"),
|
||||
mimetypes.guess_type(filename)[0],
|
||||
)
|
||||
)
|
||||
yield files
|
||||
for fn, fp, ft in files:
|
||||
fp.close()
|
||||
|
||||
|
||||
def get_url_parameters(url, *args):
|
||||
params = urllib.parse.parse_qs(urllib.parse.urlparse(url).query)
|
||||
return [params[arg][0] for arg in args if params.get(arg)]
|
||||
|
@@ -46,6 +46,21 @@ def test_graphql_to_extensible_attachment_dispatch(monkeypatch, obj, type_):
|
||||
assert graphql_to_extensible_attachment(data)
|
||||
|
||||
|
||||
def test_mention_to_send_data():
|
||||
assert {
|
||||
"profile_xmd[0][id]": "1234",
|
||||
"profile_xmd[0][length]": 7,
|
||||
"profile_xmd[0][offset]": 4,
|
||||
"profile_xmd[0][type]": "p",
|
||||
} == Mention(thread_id="1234", offset=4, length=7)._to_send_data(0)
|
||||
assert {
|
||||
"profile_xmd[1][id]": "4321",
|
||||
"profile_xmd[1][length]": 7,
|
||||
"profile_xmd[1][offset]": 24,
|
||||
"profile_xmd[1][type]": "p",
|
||||
} == Mention(thread_id="4321", offset=24, length=7)._to_send_data(1)
|
||||
|
||||
|
||||
def test_message_format_mentions():
|
||||
expected = Message(
|
||||
text="Hey 'Peter'! My name is Michael",
|
||||
@@ -70,56 +85,6 @@ def test_message_get_forwarded_from_tags():
|
||||
)
|
||||
|
||||
|
||||
def test_message_to_send_data_minimal():
|
||||
assert {"action_type": "ma-type:user-generated-message", "body": "Hey"} == Message(
|
||||
text="Hey"
|
||||
)._to_send_data()
|
||||
|
||||
|
||||
def test_message_to_send_data_mentions():
|
||||
msg = Message(
|
||||
text="Hey 'Peter'! My name is Michael",
|
||||
mentions=[
|
||||
Mention(thread_id="1234", offset=4, length=7),
|
||||
Mention(thread_id="4321", offset=24, length=7),
|
||||
],
|
||||
)
|
||||
assert {
|
||||
"action_type": "ma-type:user-generated-message",
|
||||
"body": "Hey 'Peter'! My name is Michael",
|
||||
"profile_xmd[0][id]": "1234",
|
||||
"profile_xmd[0][length]": 7,
|
||||
"profile_xmd[0][offset]": 4,
|
||||
"profile_xmd[0][type]": "p",
|
||||
"profile_xmd[1][id]": "4321",
|
||||
"profile_xmd[1][length]": 7,
|
||||
"profile_xmd[1][offset]": 24,
|
||||
"profile_xmd[1][type]": "p",
|
||||
} == msg._to_send_data()
|
||||
|
||||
|
||||
def test_message_to_send_data_sticker():
|
||||
msg = Message(sticker=fbchat.Sticker(id="123"))
|
||||
assert {
|
||||
"action_type": "ma-type:user-generated-message",
|
||||
"sticker_id": "123",
|
||||
} == msg._to_send_data()
|
||||
|
||||
|
||||
def test_message_to_send_data_emoji():
|
||||
msg = Message(text="😀", emoji_size=EmojiSize.LARGE)
|
||||
assert {
|
||||
"action_type": "ma-type:user-generated-message",
|
||||
"body": "😀",
|
||||
"tags[0]": "hot_emoji_size:large",
|
||||
} == msg._to_send_data()
|
||||
msg = Message(emoji_size=EmojiSize.LARGE)
|
||||
assert {
|
||||
"action_type": "ma-type:user-generated-message",
|
||||
"sticker_id": "369239383222810",
|
||||
} == msg._to_send_data()
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="need to be added")
|
||||
def test_message_to_send_data_quick_replies():
|
||||
raise NotImplementedError
|
||||
|
Reference in New Issue
Block a user