diff --git a/README.rst b/README.rst
index 2641595..89cd2d1 100644
--- a/README.rst
+++ b/README.rst
@@ -1,118 +1,26 @@
-======
-fbchat
-======
+fbchat: Facebook Chat (Messenger) for Python
+============================================
+.. image:: docs/_static/license.svg
+ :target: LICENSE.txt
+ :alt: License: BSD
-Facebook Chat (`Messenger `__) for Python. This project was inspired by `facebook-chat-api `__.
+.. image:: docs/_static/python-versions.svg
+ :target: https://pypi.python.org/pypi/fbchat
+ :alt: Supported python versions: 2.7, 3.4, 3.5 and 3.6
-**No XMPP or API key is needed**. Just use your EMAIL and PASSWORD.
+Facebook Chat (`Messenger `__) for Python.
+This project was inspired by `facebook-chat-api `__.
+**No XMPP or API key is needed**. Just use your email and password.
-Installation
-============
+Go to `ReadTheDocs `__ to see the full documentation,
+or jump right into the code by viewing the `examples`_
-Simple:
+Installation:
.. code-block:: console
$ pip install fbchat
-
-Example Login
-=============
-
-.. code-block:: python
-
- import fbchat
-
- client = fbchat.Client('YOUR_EMAIL', 'YOUR_PASSWORD')
-
-Sending a Message
-=================
-
-.. code-block:: python
-
- friends = client.getUsers("FRIEND'S NAME") # return a list of names
- friend = friends[0]
- sent = client.send(friend.uid, "Your Message")
- if sent:
- print("Message sent successfully!")
- # IMAGES
- client.sendLocalImage(friend.uid,message='',image='') # send local image
- imgurl = "http://i.imgur.com/LDQ2ITV.jpg"
- client.sendRemoteImage(friend.uid,message='', image=imgurl) # send image from image url
-
-
-Getting user info from user id
-==============================
-
-.. code-block:: python
-
- friend1 = client.getUsers('')[0]
- friend2 = client.getUsers('')[0]
- friend1_info = client.getUserInfo(friend1.uid) # returns dict with details
- both_info = client.getUserInfo(friend1.uid,friend2.uid) # query both together, returns list of dicts
- friend1_name = friend1_info['name']
-
-
-Getting last messages sent
-==========================
-
-.. code-block:: python
-
- last_messages = client.getThreadInfo(friend.uid, last_n=20)
- last_messages.reverse() # messages come in reversed order
-
- for message in last_messages:
- print(message.body)
-
-
-Example Echobot
-===============
-
-.. code-block:: python
-
- import fbchat
- #subclass fbchat.Client and override required methods
- class EchoBot(fbchat.Client):
-
- def __init__(self,email, password, debug=True, user_agent=None):
- fbchat.Client.__init__(self,email, password, debug, user_agent)
-
- def on_message(self, mid, author_id, author_name, message, metadata):
- self.markAsDelivered(author_id, mid) #mark delivered
- self.markAsRead(author_id) #mark read
-
- print("%s said: %s"%(author_id, message))
-
- #if you are not the author, echo
- if str(author_id) != str(self.uid):
- self.send(author_id,message)
-
- bot = EchoBot("", "")
- bot.listen()
-
-
-Saving session
-==========================
-
-.. code-block:: python
-
- session_cookies = client.setSession()
- # save session_cookies
-
-
-Loading session
-==========================
-
-.. code-block:: python
-
- client = fbchat.Client(None, None, session_cookies=session_cookies)
- # OR
- client.setSession(session_cookies)
-
-
-Authors
-=======
-
-Taehoon Kim / `@carpedm20 `__
+© Copyright 2015 - 2017 by Taehoon Kim / `@carpedm20 `__
diff --git a/docs/_static/find-group-id.png b/docs/_static/find-group-id.png
new file mode 100644
index 0000000..e19010d
Binary files /dev/null and b/docs/_static/find-group-id.png differ
diff --git a/docs/api.rst b/docs/api.rst
index 8fc3a70..1290f35 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -13,10 +13,13 @@ If you are looking for information on a specific function, class, or method, thi
Client
------
-.. todo::
- Write introduction text about `Client`, and add documentation for all events
+This is the main class of `fbchat`, which contains all the methods you use to interract with Facebook.
+You can extend this class, and overwrite the events, to provide custom event handling (mainly used while listening)
-.. autoclass:: Client(email, password, user_agent=None, max_retries=5, session_cookies=None, logging_level=logging.INFO, set_default_events=True)
+.. todo::
+ Add documentation for all events
+
+.. autoclass:: Client(email, password, user_agent=None, max_retries=5, session_cookies=None, logging_level=logging.INFO)
:members:
.. automethod:: sendRemoteImage(image_url, message=None, thread_id=None, thread_type=ThreadType.USER)
@@ -33,6 +36,7 @@ A good tip is to write ``from fbchat.models import *`` at the start of your sour
.. automodule:: fbchat.models
:members:
+ :undoc-members:
.. _api_utils:
diff --git a/docs/examples.rst b/docs/examples.rst
index 1371017..50f0fa3 100644
--- a/docs/examples.rst
+++ b/docs/examples.rst
@@ -7,22 +7,20 @@ Examples
These are a few examples on how to use `fbchat`. Remember to swap out `` and `` for your email and password
-Sending messages
-----------------
+Interacting with Threads
+------------------------
-This will send one of each message type to the specified thread
+This will interract with the thread in every way `fbchat` supports
-.. literalinclude:: ../examples/send.py
- :language: python
+.. literalinclude:: ../examples/interract.py
-Getting information
--------------------
+Fetching Information
+--------------------
This will show the different ways of fetching information about users and threads
-.. literalinclude:: ../examples/get.py
- :language: python
+.. literalinclude:: ../examples/fetch.py
Echobot
@@ -31,22 +29,20 @@ Echobot
This will reply to any message with the same message
.. literalinclude:: ../examples/echobot.py
- :language: python
-Remove bot
+Remove Bot
----------
This will remove a user from a group if they write the message `Remove me!`
.. literalinclude:: ../examples/removebot.py
- :language: python
-"Keep it"-bot
--------------
+"Prevent changes"-Bot
+---------------------
-This will prevent chat color, emoji, nicknames and chat name from being changed. It will also prevent people from being added and removed
+This will prevent chat color, emoji, nicknames and chat name from being changed.
+It will also prevent people from being added and removed
.. literalinclude:: ../examples/keepbot.py
- :language: python
diff --git a/docs/index.rst b/docs/index.rst
index 5c16436..0f083a6 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,4 +1,5 @@
.. highlight:: python
+.. module:: fbchat
.. fbchat documentation master file, created by
sphinx-quickstart on Thu May 25 15:43:01 2017.
You can adapt this file completely to your liking, but it should at least
@@ -16,26 +17,36 @@ Release v\ |version|. (:ref:`install`)
.. image:: /_static/license.svg
:target: https://github.com/carpedm20/fbchat/blob/master/LICENSE.txt
+ :alt: License: BSD
.. generated with: https://img.shields.io/badge/python-2.7%2C%203.4%2C%203.5%2C%203.6-blue.svg
.. image:: /_static/python-versions.svg
:target: https://pypi.python.org/pypi/fbchat
+ :alt: Supported python versions: 2.7, 3.4, 3.5 and 3.6
-Facebook Chat (`Messenger `__) for Python. This project was inspired by `facebook-chat-api `__.
+Facebook Chat (`Messenger `_) for Python.
+This project was inspired by `facebook-chat-api `_.
**No XMPP or API key is needed**. Just use your email and password.
Currently `fbchat` support Python 2.7, 3.4, 3.5 and 3.6:
-`fbchat` works by emulating the browser. This means doing the exact same GET/POST requests and tricking Facebook into thinking we're accessing the website normally.
-Because we're doing it this way, this API requires the credentials of a Facebook account.
+`fbchat` works by emulating the browser.
+This means doing the exact same GET/POST requests and tricking Facebook into thinking it's accessing the website normally.
+Therefore, this API requires the credentials of a Facebook account.
.. warning::
- We are not responsible if your account gets banned for spammy activities such as sending lots of messages to people you don't know, sending messages very quickly, sending spammy looking URLs, logging in and out very quickly... Be responsible Facebook citizens.
+ We are not responsible if your account gets banned for spammy activities,
+ such as sending lots of messages to people you don't know, sending messages very quickly,
+ sending spammy looking URLs, logging in and out very quickly... Be responsible Facebook citizens.
.. note::
- Facebook now has an `official API `_ for chat bots, so if you're familiar with node.js, this might be what you're looking for.
+ Facebook now has an `official API `_ for chat bots,
+ so if you're familiar with node.js, this might be what you're looking for.
+
+If you're already familiar with the basics of how Facebook works internally, go to :ref:`examples` to see example usage of `fbchat`
+
Overview
--------
diff --git a/docs/intro.rst b/docs/intro.rst
index 7cd1ec4..3e58bad 100644
--- a/docs/intro.rst
+++ b/docs/intro.rst
@@ -1,20 +1,138 @@
.. highlight:: python
+.. module:: fbchat
.. _intro:
Introduction
============
-.. todo::
- Make a general introduction to `fbchat`
+`fbchat` uses your email and password to communicate with the Facebook server.
+That means that you should always store your password in a seperate file, in case e.g. someone looks over your shoulder while you're writing code.
+You should also make sure that the file's access control is appropriately restrictive
.. _intro_logging_in:
-Logging in
+Logging In
----------
-.. todo::
- Write something about logging in, logging out, checking login, 2FA and the event `on2FACode`, here
+Simply create an instance of :class:`Client`. If you have two factor authentication enabled, type the code in the terminal prompt
+(If you want to supply the code in another fasion, overwrite :func:`Client.on2FACode`)::
+
+ from fbchat import Client
+ from fbchat.models import *
+ client = Client('', '')
+
+Replace ```` and ```` with your email and password respectively
+
+.. note::
+ For ease of use then most of the code snippets in this document will assume you've already completed the login process
+ Though the second line, ``from fbchat.models import *``, is not strictly neccesary here, later code snippets will assume you've done this
+
+If you want to change how verbose `fbchat` is, change the logging level (in :class:`Client`)
+
+Throughout your code, if you want to check whether you are still logged in, use :func:`Client.isLoggedIn`.
+An example would be to login again if you've been logged out, using :func:`Client.login`::
+
+ if not client.isLoggedIn():
+ client.login('', '')
+
+When you're done using the client, and want to securely logout, use :func:`Client.logout`::
+
+ client.logout()
+
+
+.. _intro_threads:
+
+Threads
+-------
+
+A thread can refer to two things: A Messenger group chat or a single Facebook user
+
+:class:`models.ThreadType` is an enumerator with two values: ``USER`` and ``GROUP``.
+These will specify whether the thread is a single user chat or a group chat.
+This is required for many of `fbchat`'s functions, since Facebook differetiates between these two internally
+
+Searching for group chats and finding their ID is not yet possible with `fbchat`,
+but searching for users is possible via. :func:`Client.getUsers`. See :ref:`intro_fetching`
+
+You can get your own user ID by using :any:`Client.uid`
+
+Getting the ID of a group chat is fairly trivial though, since you only need to navigate to ``_,
+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.facebook.com/messages/t/1234567890``, where ``1234567890`` would be the ID of the group.
+An image to illustrate this is shown below:
+
+.. image:: /_static/find-group-id.png
+ :alt: An image illustrating how to find the ID of a group
+
+The same method can be applied to some user accounts, though if they've set a custom URL, then you'll just see that URL instead
+
+Here's an snippet showing the usage of thread IDs and thread types, where ```` and ````
+corresponds to the ID of a single user, and the ID of a group respectively::
+
+ client.sendMessage('', thread_id='', thread_type=ThreadType.USER)
+ client.sendMessage('', thread_id='', thread_type=ThreadType.GROUP)
+
+Some functions (e.g. :func:`Client.changeThreadColor`) don't require a thread type, so in these cases you just provide the thread ID::
+
+ client.changeThreadColor(ThreadColor.BILOBA_FLOWER, thread_id='')
+ client.changeThreadColor(ThreadColor.MESSENGER_BLUE, thread_id='')
+
+
+.. _intro_message_ids:
+
+Message IDs
+-----------
+
+Every message you send on Facebook has a unique ID, and every action you do in a thread,
+like changing a nickname or adding a person, has a unique ID too.
+
+Some of `fbchat`'s functions require these ID's, like :func:`Client.reactToMessage`,
+and some of then provide this ID, like :func:`Client.sendMessage`.
+This snippet shows how to send a message, and then use the returned ID to react to that message with a 😍 emoji::
+
+ message_id = client.sendMessage('message', thread_id=thread_id, thread_type=thread_type)
+ client.reactToMessage(message_id, MessageReaction.LOVE)
+
+
+.. _intro_interacting:
+
+Interacting with Threads
+------------------------
+
+`fbchat` provides multiple functions for interacting with threads
+
+Most functionality works on all threads, though some things,
+like adding users to and removing users from a group chat, logically only works on group chats
+
+The simplest way of using `fbchat` is to send a message.
+The following snippet will, as you've probably already figured out, send the message `test message` to your account::
+
+ message_id = client.sendMessage('test message', thread_id=client.uid, thread_type=ThreadType.USER)
+
+You can see a full example showing all the possible thread interactions with `fbchat` by going to :ref:`examples`
+
+
+.. _intro_fetching:
+
+Fetching Information
+--------------------
+
+You can use `fbchat` to fetch basic information like user names, profile pictures, thread names and user IDs
+
+You can retrieve a user's ID with :func:`Client.getUsers`.
+The following snippet will search for users by their name, take the first (and most likely) user, and then get their user ID from the result::
+
+ users = client.getUsers('')
+ user = users[0]
+ print("User's ID: {}".format(user.uid))
+ print("User's name: {}".format(user.name))
+ print("User's profile picture url: {}".format(user.photo))
+ print("User's main url: {}".format(user.url))
+
+Since this uses Facebook's search functions, you don't have to specify the whole name, first names will usually be enough
+
+You can see a full example showing all the possible ways to fetch information with `fbchat` by going to :ref:`examples`
.. _intro_sessions:
@@ -22,65 +140,81 @@ Logging in
Sessions
--------
-.. todo::
- Make an introduction to and show example usage of sessions
+`fbchat` provides functions to retrieve and set the session cookies.
+This will enable you to store the session cookies in a seperate file, so that you don't have to login each time you start your script.
+Use :func:`Client.getSession` to retrieve the cookies::
+ session_cookies = client.getSession()
-.. _intro_sending:
+Then you can use :func:`Client.setSession`::
-Sending messages
-----------------
+ client.setSession(session_cookies)
-.. todo::
- Make an introduction to and show example usage of how you send information
+Or you can set the ``session_cookies`` on your initial login.
+(If the session cookies are invalid, your email and password will be used to login instead)::
-.. literalinclude:: ../examples/send.py
- :language: python
+ client = Client('', '', session_cookies=session_cookies)
-
-.. _intro_fetching:
-
-Fetching information
---------------------
-
-.. todo::
- Make an introduction to and show example usage of fetching information
-
-.. literalinclude:: ../examples/get.py
- :language: python
-
-
-.. _intro_thread_id:
-
-Thread ids
-----------
-
-.. todo::
- Make an introduction to and show example usage of thread ids
-
-
-.. _intro_thread_type:
-
-Thread types
-------------
-
-.. todo::
- Make an introduction to and show example usage of thread types
-
-
-.. _intro_message_ids:
-
-Message ids
------------
-
-.. todo::
- Make an introduction to and show example usage of message ids
+.. warning::
+ You session cookies can be just as valueable as you password, so store them with equal care
.. _intro_events:
-Events
-------
+Listening & Events
+------------------
-.. todo::
- Make an introduction to and show example usage of the event system
+To use the listening functions `fbchat` offers (like :func:`Client.listen`),
+you have to define what should be executed when certain events happen.
+By default, (most) events will just be a `logging.info` statement,
+meaning it will simply print information to the console when an event happens
+
+.. note::
+ You can identify the event methods by their `on` prefix, e.g. `onMessage`
+
+The event actions can be changed by subclassing the :class:`Client`, and then overwriting the event methods::
+
+ class CustomClient(Client):
+ def onMessage(self, mid, author_id, message, thread_id, thread_type, ts, metadata, msg, **kwargs):
+ # Do something with the message here
+ pass
+
+ client = CustomClient('', '')
+
+**Notice:** The following snippet is as equally valid as the previous one::
+
+ class CustomClient(Client):
+ def onMessage(self, message, author_id, thread_id, thread_type, **kwargs):
+ # Do something with the message here
+ pass
+
+ client = CustomClient('', '')
+
+The change was in the parameters that our `onMessage` method took: ``message`` and ``author_id`` got swapped,
+and ``mid``, ``ts``, ``metadata`` and ``msg`` got removed, but the function still works, since we included ``**kwargs``
+
+.. note::
+ Therefore, for both backwards and forwards compatability,
+ the API actually requires that you include ``**kwargs`` as your final argument.
+
+View the :ref:`examples` to see some more examples illustrating the event system
+
+
+.. _intro_submitting:
+
+Submitting Issues
+-----------------
+
+If you're having trouble with some of the snippets shown here, or you think some of the functionality is broken,
+please feel free to submit an issue on `Github `_.
+One side note is that you should first login with ``logging_level`` set to ``logging.DEBUG``::
+
+ from fbchat import Client
+ import logging
+ client = Client('', '', logging_level=logging.DEBUG)
+
+Then you can submit the relevant parts of this log, and detailed steps on how to reproduce
+
+.. warning::
+ Always remove your credentials from any debug information you may provide us.
+ Preferably, use a test account, in case you miss anything
diff --git a/docs/testing.rst b/docs/testing.rst
index 1921234..a3b2f13 100644
--- a/docs/testing.rst
+++ b/docs/testing.rst
@@ -1,10 +1,11 @@
.. highlight:: sh
+.. module:: fbchat
.. _testing:
Testing
=======
-To use these tests copy ``tests/data.json`` to ``tests/my_data.json`` or type the information manually in the terminal prompts.
+To use the tests, copy ``tests/data.json`` to ``tests/my_data.json`` or type the information manually in the terminal prompts.
- email: Your (or a test user's) email / phone number
- password: Your (or a test user's) password
diff --git a/docs/todo.rst b/docs/todo.rst
index 8d40721..e2d773a 100644
--- a/docs/todo.rst
+++ b/docs/todo.rst
@@ -1,19 +1,23 @@
.. highlight:: python
+.. module:: fbchat
.. _todo:
Todo
====
+This page will be periodically updated to show missing features and documentation
-Functionality
--------------
-- Implement Client.changeThreadEmoji
-- Implement Client.changeNickname
+Missing Functionality
+---------------------
+
- Implement Client.searchForThread
- This will use the graphql request API
- Implement Client.searchForMessage
- This will use the graphql request API
+- Implement chatting with pages
+ - This might require a new :class:`models.ThreadType`, something like ``ThreadType.PAGE``
+- Rework `User`, `Thread` and `Message` models, and rework fething methods, to make the whole process more streamlined
Documentation
diff --git a/examples/fetch.py b/examples/fetch.py
new file mode 100644
index 0000000..13af24e
--- /dev/null
+++ b/examples/fetch.py
@@ -0,0 +1,50 @@
+# -*- coding: UTF-8 -*-
+
+from fbchat import Client
+from fbchat.models import *
+
+client = Client("", "")
+
+# Fetches a list of all users you're currently chatting with, as `User` objects
+users = client.getAllUsers()
+
+print('user IDs: {}'.format(user.uid for user in users))
+print("user's names: {}".format(user.name for user in users))
+
+
+# If we have a user id, we can use `getUserInfo` to fetch a `User` object
+user = client.getUserInfo('')
+# We can also query both mutiple users together, which returns list of `User` objects
+users = client.getUserInfo('<1st user id>', '<2nd user id>', '<3rd user id>')
+
+print('User INFO: {}'.format(user))
+print("User's INFO: {}".format(users))
+
+
+# `getUsers` searches for the user and gives us a list of the results,
+# and then we just take the first one, aka. the most likely one:
+user = client.getUsers('')[0]
+
+print('user ID: {}'.format(user.uid))
+print("user's name: {}".format(user.name))
+
+
+# Fetches a list of all threads you're currently chatting with
+threads = client.getThreadList()
+# Fetches the next 10 threads
+threads += client.getThreadList(start=20, length=10)
+
+print("Thread's INFO: {}".format(threads))
+
+
+# Gets the last 10 messages sent to the thread
+messages = client.getThreadInfo(last_n=10, thread_id='', thread_type=ThreadType)
+# Since the message come in reversed order, reverse them
+messages.reverse()
+
+# Prints the content of all the messages
+for message in messages:
+ print(message.body)
+
+
+# Here should be an example of `getUnread`
diff --git a/examples/get.py b/examples/get.py
deleted file mode 100644
index e253e5a..0000000
--- a/examples/get.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- coding: UTF-8 -*-
-
-from fbchat import Client
-from fbchat.models import *
-
-client = Client("", "")
-
-# This should show example usage of getAllUsers, getUsers, getUserInfo, getThreadInfo, getThreadList and getUnread
\ No newline at end of file
diff --git a/examples/interract.py b/examples/interract.py
new file mode 100644
index 0000000..d216a67
--- /dev/null
+++ b/examples/interract.py
@@ -0,0 +1,55 @@
+# -*- coding: UTF-8 -*-
+
+from fbchat import Client
+from fbchat.models import *
+
+client = Client("", "")
+
+thread_id = '1234567890'
+thread_type = ThreadType.GROUP
+
+# Will send a message to the thread
+client.sendMessage('', thread_id=thread_id, thread_type=thread_type)
+
+# Will send the default `like` emoji
+client.sendEmoji(emoji=None, size=EmojiSize.LARGE, thread_id=thread_id, thread_type=thread_type)
+
+# Will send the emoji `👍`
+client.sendEmoji(emoji='👍', size=EmojiSize.LARGE, thread_id=thread_id, thread_type=thread_type)
+
+# Will send the image located at ``
+client.sendLocalImage('', message='This is a local image', thread_id=thread_id, thread_type=thread_type)
+
+# Will download the image at the url ``, and then send it
+client.sendRemoteImage('', message='This is a remote image', thread_id=thread_id, thread_type=thread_type)
+
+
+# Only do these actions if the thread is a group
+if thread_type == ThreadType.GROUP:
+ # Will remove the user with ID `` from the thread
+ client.removeUserFromGroup('', thread_id=thread_id)
+
+ # Will add the user with ID `` to the thread
+ client.addUsersToGroup('', thread_id=thread_id)
+
+ # Will add the users with IDs `<1st user id>`, `<2nd user id>` and `<3th user id>` to the thread
+ client.addUsersToGroup(['<1st user id>', '<2nd user id>', '<3rd user id>'], thread_id=thread_id)
+
+
+# Will change the nickname of the user `` to ``
+client.changeNickname('', '', thread_id=thread_id, thread_type=thread_type)
+
+# Will change the title of the thread to ``
+client.changeThreadTitle('', thread_id=thread_id, thread_type=thread_type)
+
+# Will set the typing status of the thread to `TYPING`
+client.setTypingStatus(TypingStatus.TYPING, thread_id=thread_id, thread_type=thread_type)
+
+# Will change the thread color to `MESSENGER_BLUE`
+client.changeThreadColor(ThreadColor.MESSENGER_BLUE, thread_id=thread_id)
+
+# Will change the thread emoji to `👍`
+client.changeThreadEmoji('👍', thread_id=thread_id)
+
+# Will react to a message with a 😍 emoji
+client.reactToMessage('', MessageReaction.LOVE)
diff --git a/examples/keepbot.py b/examples/keepbot.py
index 702216c..1189f7b 100644
--- a/examples/keepbot.py
+++ b/examples/keepbot.py
@@ -9,12 +9,12 @@ old_thread_id = '1234567890'
# Change these to match your liking
old_color = ThreadColor.MESSENGER_BLUE
old_emoji = '👍'
-old_title = 'Old school'
+old_title = 'Old group chat name'
old_nicknames = {
- '12345678901': 'Old School user nr. 1',
- '12345678902': 'Old School user nr. 2',
- '12345678903': 'Old School user nr. 3',
- '12345678904': 'Old School user nr. 4'
+ '12345678901': "User nr. 1's nickname",
+ '12345678902': "User nr. 2's nickname",
+ '12345678903': "User nr. 3's nickname",
+ '12345678904': "User nr. 4's nickname"
}
class KeepBot(Client):
diff --git a/examples/send.py b/examples/send.py
deleted file mode 100644
index 3a9791e..0000000
--- a/examples/send.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: UTF-8 -*-
-
-from fbchat import Client
-from fbchat.models import *
-
-client = Client("", "")
-
-# Change these to match your thread
-thread_id = '1234567890'
-thread_type = ThreadType.GROUP # Or ThreadType.USER
-
-# This will send a message to the thread
-client.sendMessage('Hey there', thread_id=thread_id, thread_type=thread_type)
-
-# This will send the default emoji
-client.sendEmoji(emoji=None, size=EmojiSize.LARGE, thread_id=thread_id, thread_type=thread_type)
-
-# This will send the emoji `👍`
-client.sendEmoji(emoji='👍', size=EmojiSize.LARGE, thread_id=thread_id, thread_type=thread_type)
-
-# This will send the image called `image.png`
-client.sendLocalImage('image.png', message='This is a local image', thread_id=thread_id, thread_type=thread_type)
-
-# This will send the image at the url `https://example.com/image.png`
-client.sendRemoteImage('https://example.com/image.png', message='This is a remote image', thread_id=thread_id, thread_type=thread_type)
diff --git a/fbchat/client.py b/fbchat/client.py
index a8b17bd..14ae441 100644
--- a/fbchat/client.py
+++ b/fbchat/client.py
@@ -28,12 +28,46 @@ handler = logging.StreamHandler()
log.addHandler(handler)
+
+#: This function needs the logger
+def check_request(r, check_json=True):
+ if not r.ok:
+ log.warning('Error when sending request: Got {} response'.format(r.status_code))
+ return None
+
+ content = get_decoded(r)
+
+ if content is None or len(content) == 0:
+ log.warning('Error when sending request: Got empty response')
+ return None
+
+ if check_json:
+ j = json.loads(strip_to_json(content))
+ if 'error' in j:
+ # 'errorDescription' is in the users own language!
+ log.warning('Error #{} when sending request: {}'.format(j['error'], j['errorDescription']))
+ return None
+ return j
+ else:
+ return r
+
+
class Client(object):
"""A client for the Facebook Chat (Messenger).
See https://fbchat.readthedocs.io for complete documentation of the API.
"""
+ listening = False
+ """Whether the client is listening. Used when creating an external event loop to determine when to stop listening"""
+ uid = None
+ """
+ The id of the client.
+ Can be used a `thread_id`. See :ref:`intro_threads` for more info.
+
+ Note: Modifying this results in undefined behaviour
+ """
+
def __init__(self, email, password, debug=False, info_log=False, user_agent=None, max_retries=5,
session_cookies=None, logging_level=logging.INFO):
"""Initializes and logs in the client
@@ -56,7 +90,6 @@ class Client(object):
self.seq = "0"
self.payloadDefault = {}
self.client = 'mercury'
- self.listening = False
self.default_thread_id = None
self.default_thread_type = None
self.threads = []
@@ -359,19 +392,6 @@ class Client(object):
r = self._cleanPost(ReqUrl.CHECKPOINT, data)
return r
- def _checkRequest(self, r):
- if not r.ok:
- log.warning('Error when sending request: Got {} response'.format(r.status_code))
- return None
-
- j = get_json(r)
- if 'error' in j:
- # 'errorDescription' is in the users own language!
- log.warning('Error #{} when sending request: {}'.format(j['error'], j['errorDescription']))
- return None
-
- return j
-
def isLoggedIn(self):
"""
Sends a request to Facebook to check the login status
@@ -469,8 +489,8 @@ class Client(object):
def setDefaultThread(self, thread_id, thread_type):
"""Sets default thread to send messages to
- :param thread_id: User/Group ID to default to. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to default to. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type thread_type: models.ThreadType
"""
self.default_thread_id = thread_id
@@ -497,7 +517,7 @@ class Client(object):
return given_thread_id, given_thread_type
"""
- GET METHODS
+ FETCH METHODS
"""
def getAllUsers(self):
@@ -559,8 +579,11 @@ class Client(object):
def getUserInfo(self, *user_ids):
"""Get user info from id. Unordered.
+ .. todo::
+ Make this return a list of User objects
+
:param user_ids: One or more user ID(s) to query
- :return: A raw dataset containing user information
+ :return: (list of) raw user data
"""
def fbidStrip(_fbid):
@@ -587,8 +610,8 @@ class Client(object):
"""Get the last messages in a thread
:param last_n: Number of messages to retrieve
- :param thread_id: User/Group ID to retrieve from. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to retrieve from. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type last_n: int
:type thread_type: models.ThreadType
:return: Dictionaries, containing message data
@@ -679,19 +702,15 @@ class Client(object):
# 'last_action_timestamp': 0
}
- r = self._post(ReqUrl.THREAD_SYNC, form)
- if not r.ok or len(r.text) == 0:
- return None
+ j = check_request(self._post(ReqUrl.THREAD_SYNC, form))
- j = get_json(r)
- result = {
+ return {
"message_counts": j['payload']['message_counts'],
"unseen_threads": j['payload']['unseen_thread_ids']
}
- return result
"""
- END GET METHODS
+ END FETCH METHODS
"""
"""
@@ -741,10 +760,10 @@ class Client(object):
return data
def _doSendRequest(self, data):
- """Sends the data to `SendURL`, and returns the message id"""
+ """Sends the data to `SendURL`, and returns the message id or None on failure"""
r = self._post(ReqUrl.SEND, data)
- j = self._checkRequest(r)
+ j = check_request(r)
if j is None:
return None
@@ -782,10 +801,10 @@ class Client(object):
Sends a message to a thread
:param message: Message to send
- :param thread_id: User/Group ID to send to. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to send to. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type thread_type: models.ThreadType
- :return: :ref:`Message ID ` of the sent message
+ :return: :ref:`Message ID ` of the sent message or `None` on failure
"""
thread_id, thread_type = self._getThread(thread_id, thread_type)
data = self._getSendData(thread_id, thread_type)
@@ -802,13 +821,13 @@ class Client(object):
"""
Sends an emoji to a thread
- :param emoji: The chosen emoji to send. If not specified, the thread's default emoji is sent
+ :param emoji: The chosen emoji to send. If not specified, the default `like` emoji is sent
:param size: If not specified, a small emoji is sent
- :param thread_id: User/Group ID to send to. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to send to. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type size: models.EmojiSize
:type thread_type: models.ThreadType
- :return: :ref:`Message ID ` of the sent emoji
+ :return: :ref:`Message ID ` of the sent emoji or `None` on failure
"""
thread_id, thread_type = self._getThread(thread_id, thread_type)
data = self._getSendData(thread_id, thread_type)
@@ -844,14 +863,14 @@ class Client(object):
def sendImage(self, image_id, message=None, thread_id=None, thread_type=ThreadType.USER):
"""
- Sends an already uploaded image to a thread. (Used by :any:`Client.sendRemoteImage` and :any:`Client.sendLocalImage`)
+ Sends an already uploaded image to a thread. (Used by :func:`Client.sendRemoteImage` and :func:`Client.sendLocalImage`)
:param image_id: ID of an image that's already uploaded to Facebook
:param message: Additional message
- :param thread_id: User/Group ID to send to. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to send to. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type thread_type: models.ThreadType
- :return: :ref:`Message ID ` of the sent image
+ :return: :ref:`Message ID ` of the sent image or `None` on failure
"""
thread_id, thread_type = self._getThread(thread_id, thread_type)
data = self._getSendData(thread_id, thread_type)
@@ -873,10 +892,10 @@ class Client(object):
:param image_url: URL of an image to upload and send
:param message: Additional message
- :param thread_id: User/Group ID to send to. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to send to. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type thread_type: models.ThreadType
- :return: :ref:`Message ID ` of the sent image
+ :return: :ref:`Message ID ` of the sent image or `None` on failure
"""
if recipient_id is not None:
deprecation('sendRemoteImage(recipient_id)', deprecated_in='0.10.2', removed_in='0.15.0', details='Use sendRemoteImage(thread_id) instead')
@@ -902,10 +921,10 @@ class Client(object):
:param image_path: URL of an image to upload and send
:param message: Additional message
- :param thread_id: User/Group ID to send to. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to send to. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type thread_type: models.ThreadType
- :return: :ref:`Message ID ` of the sent image
+ :return: :ref:`Message ID ` of the sent image or `None` on failure
"""
if recipient_id is not None:
deprecation('sendLocalImage(recipient_id)', deprecated_in='0.10.2', removed_in='0.15.0', details='Use sendLocalImage(thread_id) instead')
@@ -922,14 +941,14 @@ class Client(object):
image_id = self._uploadImage(image_path, open(image_path, 'rb'), mimetype)
return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type)
- def addUsersToGroup(self, *user_ids, thread_id=None):
+ def addUsersToGroup(self, user_ids, thread_id=None):
"""
Adds users to a group.
- :param user_ids: User ids to add
- :param thread_id: Group ID to add people to. See :ref:`intro_thread_id`
- :type user_ids: list of positional arguments
- :return: :ref:`Message ID ` of the sent "message"
+ :param user_ids: One or more user ids to add
+ :param thread_id: Group ID to add people to. See :ref:`intro_threads`
+ :type user_ids: list
+ :return: :ref:`Message ID ` of the executed action or `None` on failure
"""
thread_id, thread_type = self._getThread(thread_id, None)
data = self._getSendData(thread_id, ThreadType.GROUP)
@@ -937,9 +956,14 @@ class Client(object):
data['action_type'] = 'ma-type:log-message'
data['log_message_type'] = 'log:subscribe'
+ if type(user_ids) is not list:
+ user_ids = [user_ids]
+
# Make list of users unique
user_ids = set(user_ids)
+ print(user_ids)
+
for i, user_id in enumerate(user_ids):
if user_id == self.uid:
log.warning('Error when adding users: Cannot add self to group chat')
@@ -955,8 +979,9 @@ class Client(object):
Removes users from a group.
:param user_id: User ID to remove
- :param thread_id: Group ID to remove people from. See :ref:`intro_thread_id`
- :return: :ref:`Message ID ` of the sent "message"
+ :param thread_id: Group ID to remove people from. See :ref:`intro_threads`
+ :return: True if the action was successful
+ :rtype: bool
"""
thread_id, thread_type = self._getThread(thread_id, None)
@@ -966,7 +991,7 @@ class Client(object):
"tid": thread_id
}
- j = self._checkRequest(self._post(ReqUrl.REMOVE_USER, data))
+ j = check_request(self._post(ReqUrl.REMOVE_USER, data))
return False if j is None else True
@@ -982,11 +1007,12 @@ class Client(object):
def changeThreadTitle(self, title, thread_id=None, thread_type=ThreadType.USER):
"""
- Changes title of a thread
+ Changes title of a thread.
+ If this is executed on a user thread, this will change the nickname of that user, effectively changing the title
:param title: New group chat title
- :param thread_id: Group ID to change title of. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: Group ID to change title of. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type thread_type: models.ThreadType
:return: True if the action was successful
:rtype: bool
@@ -1018,8 +1044,8 @@ class Client(object):
:param nickname: New nickname
:param user_id: User that will have their nickname changed
- :param thread_id: User/Group ID to change color of. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to change color of. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type thread_type: models.ThreadType
:return: True if the action was successful
:rtype: bool
@@ -1032,7 +1058,7 @@ class Client(object):
'thread_or_other_fbid': thread_id
}
- j = self._checkRequest(self._post(ReqUrl.THREAD_NICKNAME, data))
+ j = check_request(self._post(ReqUrl.THREAD_NICKNAME, data))
return False if j is None else True
@@ -1041,7 +1067,7 @@ class Client(object):
Changes thread color
:param color: New thread color
- :param thread_id: User/Group ID to change color of. See :ref:`intro_thread_id`
+ :param thread_id: User/Group ID to change color of. See :ref:`intro_threads`
:type color: models.ThreadColor
:return: True if the action was successful
:rtype: bool
@@ -1053,7 +1079,7 @@ class Client(object):
'thread_or_other_fbid': thread_id
}
- j = self._checkRequest(self._post(ReqUrl.THREAD_COLOR, data))
+ j = check_request(self._post(ReqUrl.THREAD_COLOR, data))
return False if j is None else True
@@ -1064,7 +1090,7 @@ class Client(object):
Trivia: While changing the emoji, the Facebook web client actually sends multiple different requests, though only this one is required to make the change
:param color: New thread emoji
- :param thread_id: User/Group ID to change emoji of. See :ref:`intro_thread_id`
+ :param thread_id: User/Group ID to change emoji of. See :ref:`intro_threads`
:return: True if the action was successful
:rtype: bool
"""
@@ -1075,7 +1101,7 @@ class Client(object):
'thread_or_other_fbid': thread_id
}
- j = self._checkRequest(self._post(ReqUrl.THREAD_EMOJI, data))
+ j = check_request(self._post(ReqUrl.THREAD_EMOJI, data))
return False if j is None else True
@@ -1110,7 +1136,7 @@ class Client(object):
.replace('u%27', '%27')\
.replace('%5CU{}'.format(MessageReactionFix[reaction.value][0]), MessageReactionFix[reaction.value][1])
- j = self._checkRequest(self._post('{}/?{}'.format(ReqUrl.MESSAGE_REACTION, url_part)))
+ j = check_request(self._post('{}/?{}'.format(ReqUrl.MESSAGE_REACTION, url_part)))
return False if j is None else True
@@ -1119,8 +1145,8 @@ class Client(object):
Sets users typing status in a thread
:param status: Specify the typing status
- :param thread_id: User/Group ID to change status in. See :ref:`intro_thread_id`
- :param thread_type: See :ref:`intro_thread_type`
+ :param thread_id: User/Group ID to change status in. See :ref:`intro_threads`
+ :param thread_type: See :ref:`intro_threads`
:type status: models.TypingStatus
:type thread_type: models.ThreadType
:return: True if the action was successful
@@ -1135,7 +1161,7 @@ class Client(object):
"source": "mercury-chat"
}
- j = self._checkRequest(self._post(ReqUrl.TYPING, data))
+ j = check_request(self._post(ReqUrl.TYPING, data))
return False if j is None else True
@@ -1195,11 +1221,7 @@ class Client(object):
r = self._post(ReqUrl.CONNECT, data)
return r.ok
- def ping(self, sticky):
- """
- .. todo::
- Documenting this
- """
+ def _ping(self, sticky):
data = {
'channel': self.user_channel,
'clientid': self.client_id,
@@ -1209,8 +1231,7 @@ class Client(object):
'sticky': sticky,
'viewer_uid': self.uid
}
- r = self._get(ReqUrl.PING, data)
- return r.ok
+ return False if check_request(self._get(ReqUrl.PING, data), check_json=False) is None else True
def _getSticky(self):
"""Call pull api to get sticky and pool parameter,
@@ -1435,7 +1456,7 @@ class Client(object):
:rtype: bool
"""
try:
- if markAlive: self.ping(self.sticky)
+ if markAlive: self._ping(self.sticky)
try:
content = self._pullMessage(self.sticky, self.pool)
if content: self._parseMessage(content)
diff --git a/fbchat/models.py b/fbchat/models.py
index a708a67..657cc4e 100644
--- a/fbchat/models.py
+++ b/fbchat/models.py
@@ -6,7 +6,7 @@ import enum
class User(object):
"""Represents a Facebook User"""
- #: The unique identifier of the user. Can be used a `thread_id`. See :ref:`intro_thread_id` for more info
+ #: The unique identifier of the user. Can be used a `thread_id`. See :ref:`intro_threads` for more info
uid = None
#: Currently always set to `user`. Might change in the future
type = 'user'
@@ -75,10 +75,12 @@ class User(object):
}
class Thread(object):
- def __init__(self, **entries):
+ """Represents a thread. Currently just acts as a dict"""
+ def __init__(self, **entries):
self.__dict__.update(entries)
class Message(object):
+ """Represents a message. Currently just acts as a dict"""
def __init__(self, **entries):
self.__dict__.update(entries)
@@ -89,7 +91,7 @@ class Enum(enum.Enum):
return '{}.{}'.format(type(self).__name__, self.name)
class ThreadType(Enum):
- """Used to specify what type of Facebook thread is being used. See :ref:`intro_thread_type` for more info"""
+ """Used to specify what type of Facebook thread is being used. See :ref:`intro_threads` for more info"""
USER = 1
GROUP = 2
diff --git a/tests.py b/tests.py
index 1b83a56..001e178 100644
--- a/tests.py
+++ b/tests.py
@@ -15,6 +15,13 @@ import py_compile
logging_level = logging.ERROR
+"""
+
+Testing script for `fbchat`.
+Full documentation on https://fbchat.readthedocs.io/
+
+"""
+
class CustomClient(Client):
def __init__(self, *args, **kwargs):
self.got_qprimer = False
@@ -168,8 +175,8 @@ class TestFbchat(unittest.TestCase):
def test_setTypingStatus(self):
self.assertTrue(client.sendMessage('Hi', thread_id=user_uid, thread_type=ThreadType.USER))
self.assertTrue(client.setTypingStatus(TypingStatus.TYPING, thread_id=user_uid, thread_type=ThreadType.USER))
- self.assertTrue(client.setTypingStatus(TypingStatus.TYPING, thread_id=group_uid, thread_type=ThreadType.GROUP))
self.assertTrue(client.setTypingStatus(TypingStatus.STOPPED, thread_id=user_uid, thread_type=ThreadType.USER))
+ self.assertTrue(client.setTypingStatus(TypingStatus.TYPING, thread_id=group_uid, thread_type=ThreadType.GROUP))
self.assertTrue(client.setTypingStatus(TypingStatus.STOPPED, thread_id=group_uid, thread_type=ThreadType.GROUP))