diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index c32f920..0cd5c08 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -3,36 +3,40 @@ Contributing to ``fbchat``
Thanks for reading this, all contributions are very much welcome!
-Please be aware that ``fbchat`` uses `Scemantic Versioning `__
+Please be aware that ``fbchat`` uses `Scemantic Versioning `__ quite rigorously!
That means that if you're submitting a breaking change, it will probably take a while before it gets considered.
-In that case, you can point your PR to the ``2.0.0-dev`` branch, where the API is being properly developed.
-Otherwise, just point it to ``master``.
-
Development Environment
-----------------------
-You can use `flit` to install the package as a symlink:
+This project uses ``flit`` to configure development environments. You can install it using:
-.. code-block::
+.. code-block:: sh
+ $ pip install flit
+
+And now you can install ``fbchat`` as a symlink:
+
+.. code-block:: sh
+
+ $ git clone https://github.com/carpedm20/fbchat.git
+ $ cd fbchat
$ # *nix:
$ flit install --symlink
$ # Windows:
$ flit install --pth-file
+This will also install required development tools like ``black``, ``pytest`` and ``sphinx``.
+
After that, you can ``import`` the module as normal.
-Before committing, you should run ``black .`` in the main directory, to format your code.
+Checklist
+---------
-Testing Environment
--------------------
+Once you're done with your work, please follow the steps below:
-The tests use `pytest `__, and to work they need two Facebook accounts, and a group thread between these.
-To set these up, you should export the following environment variables:
-
-``client1_email``, ``client1_password``, ``client2_email``, ``client2_password`` and ``group_id``
-
-If you're not able to do this, consider simply running ``pytest -m offline``.
-
-And if you're adding new functionality, if possible, make sure to create a new test for it.
+- Run ``black .`` to format your code.
+- Run ``pytest`` to test your code.
+- Run ``make -C docs html``, and view the generated docs, to verify that the docs still work.
+- Run ``make -C docs spelling`` to check your spelling in docstrings.
+- Create a pull request, and point it to ``master`` `here `__.
diff --git a/README.rst b/README.rst
index eb3cc5f..09c3540 100644
--- a/README.rst
+++ b/README.rst
@@ -1,17 +1,17 @@
-``fbchat``: Facebook Chat (Messenger) for Python
-================================================
+``fbchat`` - Facebook Messenger for Python
+==========================================
-.. image:: https://badgen.net/pypi/license/fbchat
- :target: https://github.com/carpedm20/fbchat/tree/master/LICENSE
- :alt: License: BSD 3-Clause
+.. image:: https://badgen.net/pypi/v/fbchat
+ :target: https://pypi.python.org/pypi/fbchat
+ :alt: Project version
.. image:: https://badgen.net/badge/python/3.5,3.6,3.7,3.8,pypy?list=|
:target: https://pypi.python.org/pypi/fbchat
:alt: Supported python versions: 3.5, 3.6, 3.7, 3.8 and pypy
-.. image:: https://badgen.net/pypi/v/fbchat
- :target: https://pypi.python.org/pypi/fbchat
- :alt: Project version
+.. image:: https://badgen.net/pypi/license/fbchat
+ :target: https://github.com/carpedm20/fbchat/tree/master/LICENSE
+ :alt: License: BSD 3-Clause
.. image:: https://readthedocs.org/projects/fbchat/badge/?version=stable
:target: https://fbchat.readthedocs.io
@@ -25,37 +25,88 @@
:target: https://github.com/ambv/black
:alt: Code style
-Facebook Chat (`Messenger `__) for Python.
-This project was inspired by `facebook-chat-api `__.
+A powerful and efficient library to interact with
+`Facebook's Messenger `__, using just your email and password.
-**No XMPP or API key is needed**. Just use your email and password.
+This is *not* an official API, Facebook has that `over here `__ for chat bots. This library differs by using a normal Facebook account instead.
-Go to `Read the Docs `__ to see the full documentation,
-or jump right into the code by viewing the `examples `__
+``fbchat`` currently support:
-Version warning:
-----------------
+- Sending many types of messages, with files, stickers, mentions, etc.
+- Fetching all messages, threads and images in threads.
+- Searching for messages and threads.
+- Creating groups, setting the group emoji, changing nicknames, creating polls, etc.
+- Listening for, an reacting to messages and other events in real-time.
+- Type hints, and it has a modern codebase (e.g. only Python 3.5 and upwards).
+- ``async``/``await`` (COMING).
+
+Essentially, everything you need to make an amazing Facebook bot!
+
+
+Version Warning
+---------------
``v2`` is currently being developed at the ``master`` branch and it's highly unstable. If you want to view the old ``v1``, go `here `__.
Additionally, you can view the project's progress `here `__.
-Installation:
+
+Caveats
+-------
+
+``fbchat`` works by imitating what the browser does, and thereby tricking Facebook into thinking it's accessing the website normally.
+
+However, there's a catch! **Using this library may not comply with Facebook's Terms Of Service!**, so be responsible Facebook citizens! We are not responsible if your account gets banned!
+
+Additionally, **the APIs the library is calling is undocumented!** In theory, this means that your code could break tomorrow, without the slightest warning!
+If this happens to you, please report it, so that we can fix it as soon as possible!
+
+.. inclusion-marker-intro-end
+.. This message doesn't make sense in the docs at Read The Docs, so we exclude it
+
+With that out of the way, you may go to `Read The Docs `__ to see the full documentation!
+
+.. inclusion-marker-installation-start
+
+
+Installation
+------------
+
+.. code-block::
+
+ $ pip install fbchat
+
+If you don't have `pip `_, `this guide `_ can guide you through the process.
+
+You can also install directly from source, provided you have ``pip>=19.0``:
+
+.. code-block::
+
+ $ pip install git+https://github.com/carpedm20/fbchat.git
+
+.. inclusion-marker-installation-end
+
+
+Example Usage
-------------
.. code-block::
- $ pip install fbchat
+ import getpass
+ import fbchat
+ session = fbchat.Session.login("", getpass.getpass())
+ user = fbchat.User(session=session, id=session.user_id)
+ user.send_text("Test message!")
-You can also install from source if you have ``pip>=19.0``:
-
-.. code-block::
-
- $ git clone https://github.com/carpedm20/fbchat.git
- $ pip install fbchat
+More examples are available `here `__.
Maintainer
----------
- Mads Marquart / `@madsmtm `__
-- Taehoon Kim / `@carpedm20 `__
+
+
+Acknowledgements
+----------------
+
+This project was originally inspired by `facebook-chat-api `__.
diff --git a/docs/_static/find-group-id.png b/docs/_static/find-group-id.png
index e19010d..b60f143 100644
Binary files a/docs/_static/find-group-id.png and b/docs/_static/find-group-id.png differ
diff --git a/docs/api.rst b/docs/api.rst
deleted file mode 100644
index 274bf24..0000000
--- a/docs/api.rst
+++ /dev/null
@@ -1,70 +0,0 @@
-.. module:: fbchat
-.. _api:
-
-.. Note: we're using () to hide the __init__ method where relevant
-
-Full API
-========
-
-If you are looking for information on a specific function, class, or method, this part of the documentation is for you.
-
-Client
-------
-
-.. autoclass:: Client
-
-Threads
--------
-
-.. autoclass:: Thread()
-.. autoclass:: Page()
-.. autoclass:: User()
-.. autoclass:: Group()
-
-Messages
---------
-
-.. autoclass:: Message
-.. autoclass:: Mention
-.. autoclass:: EmojiSize(Enum)
- :undoc-members:
-
-Exceptions
-----------
-
-.. autoexception:: FBchatException()
-.. autoexception:: FBchatFacebookError()
-
-Attachments
------------
-
-.. autoclass:: Attachment()
-.. autoclass:: ShareAttachment()
-.. autoclass:: Sticker()
-.. autoclass:: LocationAttachment()
-.. autoclass:: LiveLocationAttachment()
-.. autoclass:: FileAttachment()
-.. autoclass:: AudioAttachment()
-.. autoclass:: ImageAttachment()
-.. autoclass:: VideoAttachment()
-.. autoclass:: ImageAttachment()
-
-Miscellaneous
--------------
-
-.. autoclass:: ThreadLocation(Enum)
- :undoc-members:
-.. autoclass:: ActiveStatus()
-
-.. autoclass:: QuickReply
-.. autoclass:: QuickReplyText
-.. autoclass:: QuickReplyLocation
-.. autoclass:: QuickReplyPhoneNumber
-.. autoclass:: QuickReplyEmail
-
-.. autoclass:: Poll
-.. autoclass:: PollOption
-
-.. autoclass:: Plan
-.. autoclass:: GuestStatus(Enum)
- :undoc-members:
diff --git a/docs/api/attachments.rst b/docs/api/attachments.rst
new file mode 100644
index 0000000..01841a4
--- /dev/null
+++ b/docs/api/attachments.rst
@@ -0,0 +1,13 @@
+Attachments
+===========
+
+.. autoclass:: Attachment()
+.. autoclass:: ShareAttachment()
+.. autoclass:: Sticker()
+.. autoclass:: LocationAttachment()
+.. autoclass:: LiveLocationAttachment()
+.. autoclass:: FileAttachment()
+.. autoclass:: AudioAttachment()
+.. autoclass:: ImageAttachment()
+.. autoclass:: VideoAttachment()
+.. autoclass:: ImageAttachment()
diff --git a/docs/api/client.rst b/docs/api/client.rst
new file mode 100644
index 0000000..b392eb0
--- /dev/null
+++ b/docs/api/client.rst
@@ -0,0 +1,4 @@
+Client
+======
+
+.. autoclass:: Client
diff --git a/docs/api/events.rst b/docs/api/events.rst
new file mode 100644
index 0000000..233e9bd
--- /dev/null
+++ b/docs/api/events.rst
@@ -0,0 +1,4 @@
+Events
+======
+
+.. autoclass:: Listener
diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst
new file mode 100644
index 0000000..9219745
--- /dev/null
+++ b/docs/api/exceptions.rst
@@ -0,0 +1,8 @@
+Exceptions
+==========
+
+.. autoexception:: FacebookError()
+.. autoexception:: HTTPError()
+.. autoexception:: ParseError()
+.. autoexception:: ExternalError()
+.. autoexception:: GraphQLError()
diff --git a/docs/api/index.rst b/docs/api/index.rst
new file mode 100644
index 0000000..0cc6ccf
--- /dev/null
+++ b/docs/api/index.rst
@@ -0,0 +1,21 @@
+.. module:: fbchat
+
+.. Note: we're using () to hide the __init__ method where relevant
+
+Full API
+========
+
+If you are looking for information on a specific function, class, or method, this part of the documentation is for you.
+
+.. toctree::
+ :maxdepth: 1
+
+ session
+ client
+ threads
+ thread_data
+ messages
+ exceptions
+ attachments
+ events
+ misc
diff --git a/docs/api/messages.rst b/docs/api/messages.rst
new file mode 100644
index 0000000..1f1cf53
--- /dev/null
+++ b/docs/api/messages.rst
@@ -0,0 +1,8 @@
+Messages
+========
+
+.. autoclass:: Message
+.. autoclass:: Mention
+.. autoclass:: EmojiSize(Enum)
+ :undoc-members:
+.. autoclass:: MessageData()
diff --git a/docs/api/misc.rst b/docs/api/misc.rst
new file mode 100644
index 0000000..89430b3
--- /dev/null
+++ b/docs/api/misc.rst
@@ -0,0 +1,20 @@
+Miscellaneous
+=============
+
+.. autoclass:: ThreadLocation(Enum)
+ :undoc-members:
+.. autoclass:: ActiveStatus()
+
+.. autoclass:: QuickReply
+.. autoclass:: QuickReplyText
+.. autoclass:: QuickReplyLocation
+.. autoclass:: QuickReplyPhoneNumber
+.. autoclass:: QuickReplyEmail
+
+.. autoclass:: Poll
+.. autoclass:: PollOption
+
+.. autoclass:: Plan
+.. autoclass:: PlanData()
+.. autoclass:: GuestStatus(Enum)
+ :undoc-members:
diff --git a/docs/api/session.rst b/docs/api/session.rst
new file mode 100644
index 0000000..09ab89a
--- /dev/null
+++ b/docs/api/session.rst
@@ -0,0 +1,4 @@
+Session
+=======
+
+.. autoclass:: Session()
diff --git a/docs/api/thread_data.rst b/docs/api/thread_data.rst
new file mode 100644
index 0000000..7ed8306
--- /dev/null
+++ b/docs/api/thread_data.rst
@@ -0,0 +1,6 @@
+Thread Data
+===========
+
+.. autoclass:: PageData()
+.. autoclass:: UserData()
+.. autoclass:: GroupData()
diff --git a/docs/api/threads.rst b/docs/api/threads.rst
new file mode 100644
index 0000000..79de8f2
--- /dev/null
+++ b/docs/api/threads.rst
@@ -0,0 +1,8 @@
+Threads
+=======
+
+.. autoclass:: ThreadABC()
+.. autoclass:: Thread
+.. autoclass:: Page
+.. autoclass:: User
+.. autoclass:: Group
diff --git a/docs/conf.py b/docs/conf.py
index 4f95bb1..f942d18 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -11,13 +11,18 @@ import sys
sys.path.insert(0, os.path.abspath(".."))
+os.environ["_FBCHAT_DISABLE_FIX_MODULE_METADATA"] = "1"
+
import fbchat
+del os.environ["_FBCHAT_DISABLE_FIX_MODULE_METADATA"]
+
# -- Project information -----------------------------------------------------
project = fbchat.__name__
-copyright = fbchat.__copyright__
-author = fbchat.__author__
+copyright = "Copyright 2015 - 2018 by Taehoon Kim and 2018 - 2020 by Mads Marquart"
+author = "Taehoon Kim; Moreels Pieter-Jan; Mads Marquart"
+description = fbchat.__doc__.split("\n")[0]
# The short X.Y version
version = fbchat.__version__
@@ -37,10 +42,10 @@ needs_sphinx = "2.0"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
- "sphinx.ext.todo",
"sphinx.ext.viewcode",
"sphinx.ext.napoleon",
"sphinxcontrib.spelling",
+ "sphinx_autodoc_typehints",
]
# Add any paths that contain templates here, relative to this directory.
@@ -113,7 +118,7 @@ html_show_sourcelink = False
# A shorter title for the navigation bar. Default is the same as html_title.
#
-html_short_title = fbchat.__description__
+html_short_title = description
# -- Options for HTMLHelp output ---------------------------------------------
@@ -127,16 +132,14 @@ htmlhelp_basename = project + "doc"
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
-latex_documents = [(master_doc, project + ".tex", fbchat.__title__, author, "manual")]
+latex_documents = [(master_doc, project + ".tex", project, author, "manual")]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
-man_pages = [
- (master_doc, project, fbchat.__title__, [x.strip() for x in author.split(";")], 1)
-]
+man_pages = [(master_doc, project, project, [x.strip() for x in author.split(";")], 1)]
# -- Options for Texinfo output ----------------------------------------------
@@ -145,15 +148,7 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- (
- master_doc,
- project,
- fbchat.__title__,
- author,
- project,
- fbchat.__description__,
- "Miscellaneous",
- )
+ (master_doc, project, project, author, project, description, "Miscellaneous",)
]
@@ -167,7 +162,7 @@ epub_exclude_files = ["search.html"]
# -- Options for autodoc extension ---------------------------------------
-autoclass_content = "both"
+autoclass_content = "class"
autodoc_member_order = "bysource"
autodoc_default_options = {"members": True}
@@ -176,13 +171,6 @@ autodoc_default_options = {"members": True}
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"https://docs.python.org/": None}
-# -- Options for todo extension ----------------------------------------------
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = True
-
-todo_link_only = True
-
# -- Options for napoleon extension ----------------------------------------------
# Use Google style docstrings
diff --git a/docs/faq.rst b/docs/faq.rst
index 311ee06..28183b8 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -1,42 +1,23 @@
-.. _faq:
+Frequently Asked Questions
+==========================
-FAQ
-===
+The new version broke my application
+------------------------------------
-Version X broke my installation
--------------------------------
+``fbchat`` follows `Scemantic Versioning `__ quite rigorously!
-We try to provide backwards compatibility where possible, but since we're not part of Facebook,
-most of the things may be broken at any point in time
+That means that breaking changes can *only* occur in major versions (e.g. ``v1.9.6`` -> ``v2.0.0``).
-Downgrade to an earlier version of ``fbchat``, run this command
+If you find that something breaks, and you didn't update to a new major version, then it is a bug, and we would be grateful if you reported it!
+
+In case you're stuck with an old codebase, you can downgrade to a previous version of ``fbchat``, e.g. version ``1.9.6``:
.. code-block:: sh
- $ pip install fbchat==
-
-Where you replace ```` with the version you want to use
+ $ pip install fbchat==1.9.6
Will you be supporting creating posts/events/pages and so on?
-------------------------------------------------------------
-We won't be focusing on anything else than chat-related things. This API is called ``fbCHAT``, after all ;)
-
-
-Submitting Issues
------------------
-
-If you're having trouble with some of the snippets, or you think some of the functionality is broken,
-please feel free to submit an issue on `GitHub `_.
-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
+We won't be focusing on anything else than chat-related things. This library is called ``fbCHAT``, after all!
diff --git a/docs/index.rst b/docs/index.rst
index 7efdb5a..d5c1225 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,52 +1,22 @@
-.. 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
- contain the root `toctree` directive.
+.. See README.rst for explanation of these markers
-.. This documentation's layout is heavily inspired by requests' layout: https://requests.readthedocs.io
- Some documentation is also partially copied from facebook-chat-api: https://github.com/Schmavery/facebook-chat-api
+.. include:: ../README.rst
+ :end-before: inclusion-marker-intro-end
-``fbchat``: Facebook Chat (Messenger) for Python
-================================================
+With that said, let's get started!
-Release v\ |version|. (:ref:`install`)
-
-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 3.5, 3.6, 3.7 and 3.8:
-
-``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.
-
-.. note::
- If you're having problems, please check the :ref:`faq`, before asking questions on GitHub
-
-.. 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.
-
-.. 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.
-
-If you're already familiar with the basics of how Facebook works internally, go to :ref:`examples` to see example usage of ``fbchat``
+.. include:: ../README.rst
+ :start-after: inclusion-marker-installation-start
+ :end-before: inclusion-marker-installation-end
-Overview
---------
+Documentation Overview
+----------------------
.. toctree::
:maxdepth: 2
- install
intro
examples
- testing
- api
- todo
faq
+ api/index
diff --git a/docs/install.rst b/docs/install.rst
deleted file mode 100644
index c8a1102..0000000
--- a/docs/install.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-.. _install:
-
-Installation
-============
-
-Install using pip
------------------
-
-To install ``fbchat``, run this command:
-
-.. code-block:: sh
-
- $ pip install fbchat
-
-If you don't have `pip `_ installed,
-`this Python installation guide `_
-can guide you through the process.
-
-Get the Source Code
--------------------
-
-``fbchat`` is developed on GitHub, where the code is
-`always available `_.
-
-You can either clone the public repository:
-
-.. code-block:: sh
-
- $ git clone git://github.com/carpedm20/fbchat.git
-
-Or, download a `tarball `_:
-
-.. code-block:: sh
-
- $ curl -OL https://github.com/carpedm20/fbchat/tarball/master
- # optionally, zipball is also available (for Windows users).
-
-Once you have a copy of the source, you can embed it in your own Python
-package, or install it into your site-packages easily:
-
-.. code-block:: sh
-
- $ python setup.py install
diff --git a/docs/intro.rst b/docs/intro.rst
index 46fa972..0e90837 100644
--- a/docs/intro.rst
+++ b/docs/intro.rst
@@ -1,194 +1,150 @@
-.. _intro:
-
Introduction
============
-``fbchat`` uses your email and password to communicate with the Facebook server.
-That means that you should always store your password in a separate 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
+Welcome, this page will guide you through the basic concepts of using ``fbchat``.
+The hardest, and most error prone part is logging in, and managing your login session, so that is what we will look at first.
-.. _intro_logging_in:
Logging In
----------
-Simply create an instance of `Client`. If you have two factor authentication enabled, type the code in the terminal prompt
-(If you want to supply the code in another fashion, overwrite `Client.on_2fa_code`)::
+Everything in ``fbchat`` starts with getting an instance of `Session`. Currently there are two ways of doing that, `Session.login` and `Session.from_cookies`.
- from fbchat import Client
- from fbchat.models import *
- client = Client('', '')
+The follow example will prompt you for you password, and use it to login::
-Replace ```` and ```` with your email and password respectively
+ import getpass
+ import fbchat
+ session = fbchat.Session.login("", getpass.getpass())
+ # If your account requires a two factor authentication code:
+ session = fbchat.Session.login(
+ "",
+ getpass.getpass(),
+ lambda: getpass.getpass("2FA code"),
+ )
-.. 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 necessary here, later code snippets will assume you've done this
+However, **this is not something you should do often!** Logging in/out all the time *will* get your Facebook account locked!
-If you want to change how verbose ``fbchat`` is, change the logging level (in `Client`)
+Instead, you should start by using `Session.login`, and then store the cookies with `Session.get_cookies`, so that they can be used instead the next time your application starts.
-Throughout your code, if you want to check whether you are still logged in, use `Client.is_logged_in`.
-An example would be to login again if you've been logged out, using `Client.login`::
+Usability-wise, this is also better, since you won't have to re-type your password every time you want to login.
- if not client.is_logged_in():
- client.login('', '')
+The following, quite lengthy, yet very import example, illustrates a way to do this:
-When you're done using the client, and want to securely logout, use `Client.logout`::
+.. literalinclude:: ../examples/session_handling.py
- client.logout()
+Assuming you have successfully completed the above, congratulations! Using ``fbchat`` should be mostly trouble free from now on!
-.. _intro_threads:
+Understanding Thread Ids
+------------------------
-Threads
--------
+At the core of any thread is its unique identifier, its ID.
-A thread can refer to two things: A Messenger group chat (`Group`) or a single Facebook user (`User`).
+A thread basically just means "something I can chat with", but more precisely, it can refer to a few things:
+- A Messenger group thread (`Group`)
+- The conversation between you and a single Facebook user (`User`)
+- The conversation between you and a Facebook Page (`Page`)
-Searching for group chats and finding their ID can be done via. `Client.search_for_groups`,
-and searching for users is possible via. `Client.search_for_users`. See :ref:`intro_fetching`
+You can get your own user ID with `Session.user_id`.
-You can get your own user ID by using `Session.user_id`
+Getting the ID of a specific group thread is fairly trivial, you only need to login 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.messenger.com/t/1234567890``, where ``1234567890`` would be the ID of the group.
-Getting the ID of a group chat is fairly trivial otherwise, 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:
+The same method can be applied to some user accounts, though if they have set a custom URL, then you will have to use a different method.
+
+An image to illustrate the process 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
+Once you have an ID, you can use it to create a `Group` or a `User` instance, which will allow you to do all sorts of things. To do this, you need a valid, logged in session::
-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::
+ group = fbchat.Group(session=session, id="")
+ # Or for user threads
+ user = fbchat.User(session=session, id="")
- user.send(Message(text=''))
- group.send(Message(text=''))
+Just like threads, every message, poll, plan, attachment, action etc. you send or do on Facebook has a unique ID.
-Some functions don't require a thread type, so in these cases you just provide the thread ID::
+Below is an example of using such a message ID to get a `Message` instance::
- thread = fbchat.Thread(session=session, id="")
- thread.set_color("#a695c7")
- thread.set_color("#67b868")
+ # Provide the thread the message was created in, and it's ID
+ message = fbchat.Message(thread=user, 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.
-
-This snippet shows how to send a message, and then use the returned ID to react to that message with a 😍 emoji::
-
- message = thread.send_text("A message!")
- message.react("😍")
-
-
-.. _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::
-
- user = User(session=session, id=session.user_id)
- message_id = user.send(Message(text='test message'))
-
-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
+Managing these ids yourself quickly becomes very cumbersome! Luckily, there are other, easier ways of getting `Group`/`User` instances.
-You can retrieve a user's ID with `Client.search_for_users`.
-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::
+You would start by creating a `Client` instance, which is basically just a helper on top of `Session`, that will allow you to do various things::
- users = client.search_for_users('')
- user = users[0]
- print("User's ID: {}".format(user.id))
- print("User's name: {}".format(user.name))
- print("User's profile picture URL: {}".format(user.photo))
- print("User's main URL: {}".format(user.url))
+ client = fbchat.Client(session=session)
-Since this uses Facebook's search functions, you don't have to specify the whole name, first names will usually be enough
+Now, you could search for threads using `Client.search_for_threads`, or fetch a list of them using `Client.fetch_threads`::
-You can see a full example showing all the possible ways to fetch information with ``fbchat`` by going to :ref:`examples`
+ # Fetch the 5 most likely search results
+ # Uses Facebook's search functions, you don't have to specify the whole name, first names will usually be enough
+ threads = list(client.search_for_threads("", limit=5))
+ # Fetch the 5 most recent threads in your account
+ threads = list(client.fetch_threads(limit=5))
+
+Note the `list` statements; this is because the methods actually return `generators `__. If you don't know what that means, don't worry, it is just something you can use to make your code faster later.
+
+The examples above will actually fetch `UserData`/`GroupData`, which are subclasses of `User`/`Group`. These model have extra properties, so you could for example print the names and ids of the fetched threads like this::
+
+ for thread in threads:
+ print(f"{thread.id}: {thread.name}")
+
+Once you have a thread, you can use that to fetch the messages therein::
+
+ for message in thread.fetch_messages(limit=20):
+ print(message.text)
-.. _intro_sessions:
+Interacting with Threads
+------------------------
-Sessions
---------
+Once you have a `User`/`Group` instance, you can do things on them as described in `ThreadABC`, since they are subclasses of that.
-``fbchat`` provides functions to retrieve and set the session cookies.
-This will enable you to store the session cookies in a separate file, so that you don't have to login each time you start your script.
-Use `Client.get_gession` to retrieve the cookies::
+Some functionality, like adding users to a `Group`, or blocking a `User`, logically only works the relevant threads, so see the full API documentation for that.
- session_cookies = client.get_gession()
+With that out of the way, let's see some examples!
-Then you can use `Client.set_gession`::
+The simplest way of interracting with a thread is by sending a message::
- client.set_gession(session_cookies)
+ # Send a message to the user
+ message = user.send_text("test message")
-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)::
+There are many types of messages you can send, see the full API documentation for more.
- client = Client('', '', session_cookies=session_cookies)
+Notice how we held on to the sent message? The return type i a `Message` instance, so you can interract with it afterwards::
-.. warning::
- You session cookies can be just as valuable as you password, so store them with equal care
+ # React to the message with the 😍 emoji
+ message.react("😍")
+Besides sending messages, you can also interract with threads in other ways. An example is to change the thread color::
+
+ # Will change the thread color to the default blue
+ thread.set_color("#0084ff")
-.. _intro_events:
Listening & Events
------------------
-To use the listening functions ``fbchat`` offers (like `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
+Now, we are finally at the point we have all been waiting for: Creating an automatic Facebook bot!
-.. note::
- You can identify the event methods by their ``on`` prefix, e.g. ``on_message``
+To get started, you create your methods that will handle your events::
-The event actions can be changed by subclassing the `Client`, and then overwriting the event methods::
+ def on_message(event):
+ print(f"Message from {event.author.id}: {event.message.text}")
- class CustomClient(Client):
- def on_message(self, mid, author_id, message_object, thread, ts, metadata, msg, **kwargs):
- # Do something with message_object here
- pass
+And then you create a listener object, and start handling the incoming events::
- client = CustomClient('', '')
+ listener = fbchat.Listener.connect(session, False, False)
-**Notice:** The following snippet is as equally valid as the previous one::
+ for event in listener.listen():
+ if isinstance(event, fbchat.MessageEvent):
+ on_message(event)
- class CustomClient(Client):
- def on_message(self, message_object, author_id, thread, **kwargs):
- # Do something with message_object here
- pass
-
- client = CustomClient('', '')
-
-The change was in the parameters that our ``on_message`` method took: ``message_object`` 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 compatibility,
- 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
+View the :ref:`examples` to see some more examples illustrating the event system.
diff --git a/docs/spelling/technical.txt b/docs/spelling/technical.txt
index 2595ac9..9453d88 100644
--- a/docs/spelling/technical.txt
+++ b/docs/spelling/technical.txt
@@ -1,4 +1,6 @@
iterables
+iterable
+mimetype
timestamp
metadata
spam
@@ -12,3 +14,4 @@ spritemap
online
inbox
subclassing
+codebase
diff --git a/docs/testing.rst b/docs/testing.rst
deleted file mode 100644
index 16dbd57..0000000
--- a/docs/testing.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-.. _testing:
-
-Testing
-=======
-
-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
-- group_thread_id: A test group that will be used to test group functionality
-- user_thread_id: A person that will be used to test kick/add functionality (This user should be in the group)
-
-Please remember to test all supported python versions.
-If you've made any changes to the 2FA functionality, test it with a 2FA enabled account.
-
-If you only want to execute specific tests, pass the function names in the command line (not including the ``test_`` prefix). Example:
-
-.. code-block:: sh
-
- $ python tests.py sendMessage sessions sendEmoji
-
-.. warning::
-
- Do not execute the full set of tests in too quick succession. This can get your account temporarily blocked for spam!
- (You should execute the script at max about 10 times a day)
diff --git a/docs/todo.rst b/docs/todo.rst
deleted file mode 100644
index 18d063d..0000000
--- a/docs/todo.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-.. _todo:
-
-Todo
-====
-
-This page will be periodically updated to show missing features and documentation
-
-
-Missing Functionality
----------------------
-
-- Implement ``Client.search_for_message``
- - This will use the GraphQL request API
-- Implement chatting with pages properly
-- Write better FAQ
-- Explain usage of GraphQL
-
-
-Documentation
--------------
-
-.. todolist::
diff --git a/examples/session_handling.py b/examples/session_handling.py
new file mode 100644
index 0000000..d82a0dd
--- /dev/null
+++ b/examples/session_handling.py
@@ -0,0 +1,42 @@
+# TODO: Consider adding Session.from_file and Session.to_file,
+# which would make this example a lot easier!
+
+import atexit
+import json
+import getpass
+import fbchat
+
+
+def load_cookies(filename):
+ try:
+ # Load cookies from file
+ with open(filename) as f:
+ return json.load(f)
+ except FileNotFoundError:
+ return # No cookies yet
+
+
+def save_cookies(filename, cookies):
+ with open(filename, "w") as f:
+ json.dump(f, cookies)
+
+
+def load_session(cookies):
+ if not cookies:
+ return
+ try:
+ return fbchat.Session.from_cookies(cookies)
+ except fbchat.FacebookError:
+ return # Failed loading from cookies
+
+
+cookies = load_cookies("session.json")
+session = load_session(cookies)
+if not session:
+ # Session could not be loaded, login instead!
+ session = fbchat.Session.login("", getpass.getpass())
+
+# Save session cookies to file when the program exits
+atexit.register(lambda: save_cookies("session.json", session.get_cookies()))
+
+# Do stuff with session here
diff --git a/fbchat/__init__.py b/fbchat/__init__.py
index 93f300d..3d7c390 100644
--- a/fbchat/__init__.py
+++ b/fbchat/__init__.py
@@ -1,7 +1,11 @@
-"""Facebook Chat (Messenger) for Python
+"""Facebook Messenger for Python.
-:copyright: (c) 2015 - 2019 by Taehoon Kim
-:license: BSD 3-Clause, see LICENSE for more details.
+Copyright:
+ (c) 2015 - 2018 by Taehoon Kim
+ (c) 2018 - 2020 by Mads Marquart
+
+License:
+ BSD 3-Clause, see LICENSE for more details.
"""
import logging as _logging
@@ -84,47 +88,12 @@ from ._mqtt import Listener
from ._client import Client
-__title__ = "fbchat"
__version__ = "1.9.6"
-__description__ = "Facebook Chat (Messenger) for Python"
-
-__copyright__ = "Copyright 2015 - 2019 by Taehoon Kim"
-__license__ = "BSD 3-Clause"
-
-__author__ = "Taehoon Kim; Moreels Pieter-Jan; Mads Marquart"
-__email__ = "carpedm20@gmail.com"
__all__ = ("Session", "Listener", "Client")
-# Everything below is taken from the excellent trio project:
+from . import _fix_module_metadata
-def fixup_module_metadata(namespace):
- def fix_one(qualname, name, obj):
- mod = getattr(obj, "__module__", None)
- if mod is not None and mod.startswith("fbchat."):
- obj.__module__ = "fbchat"
- # Modules, unlike everything else in Python, put fully-qualitied
- # names into their __name__ attribute. We check for "." to avoid
- # rewriting these.
- if hasattr(obj, "__name__") and "." not in obj.__name__:
- obj.__name__ = name
- obj.__qualname__ = qualname
- if isinstance(obj, type):
- # Fix methods
- for attr_name, attr_value in obj.__dict__.items():
- fix_one(objname + "." + attr_name, attr_name, attr_value)
-
- for objname, obj in namespace.items():
- if not objname.startswith("_"): # ignore private attributes
- fix_one(objname, objname, obj)
-
-
-# Having the public path in .__module__ attributes is important for:
-# - exception names in printed tracebacks
-# - sphinx :show-inheritance:
-# - deprecation warnings
-# - pickle
-# - probably other stuff
-fixup_module_metadata(globals())
-del fixup_module_metadata
+_fix_module_metadata.fixup_module_metadata(globals())
+del _fix_module_metadata
diff --git a/fbchat/_client.py b/fbchat/_client.py
index 0cc0de7..5f526aa 100644
--- a/fbchat/_client.py
+++ b/fbchat/_client.py
@@ -477,15 +477,12 @@ class Client:
Args:
threads: Threads to set as unread
- at: Timestam to signal the read cursor at
+ at: Timestamp to signal the read cursor at
"""
return self._read_status(False, threads, at)
def mark_as_seen(self):
- """
- Todo:
- Documenting this
- """
+ # TODO: Documenting this
j = self.session._payload_post(
"/ajax/mercury/mark_seen.php", {"seen_timestamp": _util.now()}
)
diff --git a/fbchat/_fix_module_metadata.py b/fbchat/_fix_module_metadata.py
new file mode 100644
index 0000000..d2faa38
--- /dev/null
+++ b/fbchat/_fix_module_metadata.py
@@ -0,0 +1,39 @@
+"""Everything in this module is taken from the excellent trio project.
+
+Having the public path in .__module__ attributes is important for:
+- exception names in printed tracebacks
+- ~sphinx :show-inheritance:~
+- deprecation warnings
+- pickle
+- probably other stuff
+"""
+
+import os
+
+
+def fixup_module_metadata(namespace):
+ def fix_one(qualname, name, obj):
+ mod = getattr(obj, "__module__", None)
+ if mod is not None and mod.startswith("fbchat."):
+ obj.__module__ = "fbchat"
+ # Modules, unlike everything else in Python, put fully-qualitied
+ # names into their __name__ attribute. We check for "." to avoid
+ # rewriting these.
+ if hasattr(obj, "__name__") and "." not in obj.__name__:
+ obj.__name__ = name
+ obj.__qualname__ = qualname
+ if isinstance(obj, type):
+ # Fix methods
+ for attr_name, attr_value in obj.__dict__.items():
+ fix_one(objname + "." + attr_name, attr_name, attr_value)
+
+ for objname, obj in namespace.items():
+ if not objname.startswith("_"): # ignore private attributes
+ fix_one(objname, objname, obj)
+
+
+# Allow disabling this when running Sphinx
+# This is done so that Sphinx autodoc can detect the file's source
+# TODO: Find a better way to detect when we're running Sphinx!
+if os.environ.get("_FBCHAT_DISABLE_FIX_MODULE_METADATA") == "1":
+ fixup_module_metadata = lambda namespace: None
diff --git a/fbchat/_page.py b/fbchat/_page.py
index 18e8a25..ff89bfe 100644
--- a/fbchat/_page.py
+++ b/fbchat/_page.py
@@ -8,6 +8,8 @@ from . import _session, _plan, _thread
class Page(_thread.ThreadABC):
"""Represents a Facebook page. Implements `ThreadABC`."""
+ # TODO: Implement pages properly, the implementation is lacking in a lot of places!
+
#: The session to use when making requests.
session = attr.ib(type=_session.Session)
#: The unique identifier of the page.
diff --git a/fbchat/_session.py b/fbchat/_session.py
index 27f2f1f..a190813 100644
--- a/fbchat/_session.py
+++ b/fbchat/_session.py
@@ -329,6 +329,8 @@ class Session:
raise _exception.ParseError("Missing payload", data=j) from e
def _graphql_requests(self, *queries):
+ # TODO: Explain usage of GraphQL, probably in the docs
+ # Perhaps provide this API as public?
data = {
"method": "GET",
"response_format": "json",
diff --git a/fbchat/_thread.py b/fbchat/_thread.py
index 4baf780..0d8a0f0 100644
--- a/fbchat/_thread.py
+++ b/fbchat/_thread.py
@@ -110,7 +110,7 @@ class ThreadABC(metaclass=abc.ABCMeta):
reply_to_id: Optional message to reply to
Returns:
- :ref:`Message ID ` of the sent message
+ The sent message
"""
data = self._to_send_data()
data["action_type"] = "ma-type:user-generated-message"
@@ -139,7 +139,7 @@ class ThreadABC(metaclass=abc.ABCMeta):
size: The size of the emoji
Returns:
- :ref:`Message ID ` of the sent message
+ The sent message
"""
data = self._to_send_data()
data["action_type"] = "ma-type:user-generated-message"
@@ -154,7 +154,7 @@ class ThreadABC(metaclass=abc.ABCMeta):
sticker_id: ID of the sticker to send
Returns:
- :ref:`Message ID ` of the sent message
+ The sent message
"""
data = self._to_send_data()
data["action_type"] = "ma-type:user-generated-message"
@@ -420,7 +420,7 @@ class ThreadABC(metaclass=abc.ABCMeta):
def set_color(self, color: str):
"""Change thread color.
- The new color must be one of the following:
+ The new color must be one of the following::
"#0084ff", "#44bec7", "#ffc300", "#fa3c4c", "#d696bb", "#6699cc", "#13cf13",
"#ff7e29", "#e68585", "#7646ff", "#20cef5", "#67b868", "#d4a88c", "#ff5ca1",
diff --git a/pyproject.toml b/pyproject.toml
index 5510324..fe4b5b7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -56,7 +56,8 @@ test = [
]
docs = [
"sphinx~=2.0",
- "sphinxcontrib-spelling~=4.0"
+ "sphinxcontrib-spelling~=4.0",
+ "sphinx-autodoc-typehints~=1.10",
]
lint = [
"black",