Compare commits

...

63 Commits

Author SHA1 Message Date
Mads Marquart
27e5d1baae Bump version: 1.7.0 -> 1.7.1 2019-07-03 23:59:45 +02:00
Mads Marquart
3a0b9867bc Merge pull request #440 from carpedm20/fix-docs
Fix and clean up documentation
2019-07-03 23:55:16 +02:00
Mads Marquart
a9c681818a Enable strict/explicit code highlighting 2019-07-03 23:42:32 +02:00
Mads Marquart
d279c96dd5 Make docs parsing "nitpicky" 2019-07-03 23:18:02 +02:00
Mads Marquart
d30589d1fa Add rst_prolog to docs/conf.py 2019-07-03 17:46:42 +02:00
Mads Marquart
47c744e5e2 Fix reST any roles/references 2019-07-03 17:35:38 +02:00
Mads Marquart
708869ea93 Include missing models in auto-generated API docs 2019-07-03 17:19:11 +02:00
Mads Marquart
8b47bf3e5d Add instructions for installing with pip > 19.0 2019-07-03 17:16:25 +02:00
Mads Marquart
a2930b4386 Deprecate the doc url at /en/master/ in favor of /en/latest/ 2019-07-03 17:15:21 +02:00
Mads Marquart
2dc93ed18b Add .readthedocs.yml 2019-07-03 15:18:11 +02:00
Mads Marquart
2bd08c8254 Update Sphinx to version 2.0 2019-07-03 14:22:09 +02:00
Mads Marquart
81278ed553 Remove doc configuration entries that are set to the default 2019-07-03 14:21:40 +02:00
Mads Marquart
589cec66e1 Refactor doc files to match format generated by sphinx-quickstart 2019-07-03 14:21:25 +02:00
Mads Marquart
281a20f56a Fix dependency pinning 2019-07-03 12:55:13 +02:00
Mads Marquart
ae8d205dbe Loosely pin dependencies 2019-07-03 11:17:53 +02:00
Mads Marquart
1e6222f46a Optimize BeautifulSoup input field parsing 2019-07-03 11:09:41 +02:00
Mads Marquart
4f2a24848e Use default black "exclude" string 2019-07-03 11:05:16 +02:00
Mads Marquart
e670c80971 Merge pull request #439 from carpedm20/graphql-cleanup
Clean up GraphQL helpers
2019-07-02 19:52:16 +02:00
Mads Marquart
ba7572eddd Merge branch 'master' into graphql-cleanup 2019-07-02 19:17:53 +02:00
Mads Marquart
a5c6fac976 Merge pull request #438 from carpedm20/explicit-error-handling
Add more explicit error handling and improve error handling in general
2019-07-02 18:57:47 +02:00
Mads Marquart
1293814c3a Remove GraphQL object in favor of helper functions 2019-07-02 18:26:35 +02:00
Mads Marquart
1b2aeb01ce Move GraphQL constants into the module 2019-07-02 18:23:29 +02:00
Mads Marquart
cab8abd1a0 Properly namespace GraphQL utility functions 2019-07-02 18:21:00 +02:00
Mads Marquart
edda2386fb Merge pull request #436 from carpedm20/clean-up-requests
Normalize a lot of request parsing
2019-07-02 18:08:19 +02:00
Mads Marquart
b0ad5f6097 Merge pull request #435 from carpedm20/state-refactor
Move state handling into new State model
2019-07-02 18:04:10 +02:00
Mads Marquart
6862bd7be3 Handle errors in payload explicitly 2019-07-02 17:52:10 +02:00
Mads Marquart
bc551a63c2 Improve GraphQL error handling 2019-07-02 17:50:33 +02:00
Mads Marquart
c9f11b924d Add more explicit error handling 2019-07-02 17:32:35 +02:00
Mads Marquart
3236ea5b97 Improve state refresh handler 2019-07-02 17:23:42 +02:00
Mads Marquart
794696d327 Improve payload error handling 2019-07-02 17:23:42 +02:00
Mads Marquart
7345de149a Improve HTTP error handling 2019-07-02 17:23:42 +02:00
Mads Marquart
4fdf0bbc57 Remove JSON conversion from _util.check_request 2019-07-02 17:23:42 +02:00
Mads Marquart
d17f741f97 Refactor _util.check_json 2019-07-02 17:23:42 +02:00
Mads Marquart
4a898b3ff5 Use Client._payload_post helper where relevant 2019-07-02 15:52:02 +02:00
Mads Marquart
7f84ca8d0c Add Client._payload_post helper 2019-07-02 15:50:58 +02:00
Mads Marquart
c3a974a495 Refactor _util.check_request 2019-07-02 15:34:23 +02:00
Mads Marquart
5b57d49a3e Remove Client._postFile 2019-07-02 15:14:02 +02:00
Mads Marquart
7af83c04c0 Remove as_json parameter
The requests that didn't need this parameter were moved to the State model
2019-07-01 22:53:26 +02:00
Mads Marquart
b5ba338f86 Remove fix_request parameter
The requests that don't need this parameter is handled in the State model
2019-07-01 22:49:21 +02:00
Mads Marquart
50bfeb92b2 Add fix_request=True and as_json=True to missing requests
I've tested, these endpoints actually all return JSON data
2019-07-01 22:47:05 +02:00
Mads Marquart
8d41ea5bfd Use POST in Client.fetchImageUrl
Reduces the amount of different request methods we're using.

Not really sure whether this is actually the best option:
- Each request includes `fb_dtsg` and such, so using POST everywhere might be the more secure option?
- But at the same time, the request is more opaque, and harder to debug (urllib3 logs all request urls automatically, so using GET would make that easy)
2019-07-01 18:43:00 +02:00
Mads Marquart
b10b14c8e9 Update url in Client.removeFriend 2019-07-01 18:23:03 +02:00
Mads Marquart
144e81bd46 Add Python 2 support 2019-07-01 13:40:15 +02:00
Mads Marquart
230c849b60 Always create the State object in a valid state 2019-07-01 13:31:42 +02:00
Mads Marquart
466f27a8c5 Move login check code into State 2019-07-01 13:31:42 +02:00
Mads Marquart
dc12e01fc7 Move logout code to State 2019-07-01 13:31:42 +02:00
Mads Marquart
d0e9a7f693 Move login/2fa code to State 2019-07-01 13:31:42 +02:00
Mads Marquart
1ba21e03c6 Handle headers in State 2019-07-01 13:31:42 +02:00
Mads Marquart
bcc8b44bb5 Handle ssl verification in State 2019-07-01 13:31:42 +02:00
Mads Marquart
b01b371c66 Refactor session cookie handling into State 2019-07-01 13:31:42 +02:00
Mads Marquart
94a0f6b3df Move client session into State 2019-07-01 13:31:42 +02:00
Mads Marquart
5df10ecc31 Remove _cleanGet and _cleanPost Client methods 2019-07-01 13:31:42 +02:00
Mads Marquart
56786406ec Refactor most of _postLogin into the State model 2019-07-01 13:31:42 +02:00
Mads Marquart
a4268f36cf Move logout h into the State model 2019-07-01 13:31:42 +02:00
Mads Marquart
8e7afa2edf Move request counter into State model 2019-07-01 13:31:30 +02:00
Mads Marquart
f07122d446 Move request payload into State model 2019-07-01 13:30:29 +02:00
Mads Marquart
78c307780b Clean up a few utility functions 2019-06-29 20:40:11 +02:00
Mads Marquart
ad705d544a Merge pull request #433 from carpedm20/remove-req-url-model
Remove ReqUrl model
2019-06-29 20:24:20 +02:00
Mads Marquart
77f28315c9 Inline urls from ReqUrl 2019-06-29 20:14:49 +02:00
Mads Marquart
e0754031ad Extract pull channel handling from ReqUrl 2019-06-29 20:10:55 +02:00
Mads Marquart
f97d36b41f Add ability to specify urls relative to www.facebook.com 2019-06-29 20:05:16 +02:00
Mads Marquart
bb2afe8e40 Remove redundant timeout parameter 2019-06-23 18:25:30 +02:00
Mads Marquart
faa0383af3 Remove unnecessary default payload attributes
This has been fairly thoroughly tested on all URLs, so it should be safe to do
2019-06-23 18:08:25 +02:00
25 changed files with 1058 additions and 1088 deletions

1
.gitignore vendored
View File

@@ -35,3 +35,4 @@ tests.data
# Virtual environment
venv/
.venv*/

18
.readthedocs.yml Normal file
View File

@@ -0,0 +1,18 @@
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
formats:
- pdf
- htmlzip
python:
version: 3.6
install:
- path: .
extra_requirements:
- docs
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
fail_on_warning: true

View File

@@ -35,14 +35,12 @@ Installation:
$ pip install fbchat
You can also install from source, by using `flit`:
You can also install from source if you have ``pip>=19.0``:
.. code-block::
$ pip install flit
$ git clone https://github.com/carpedm20/fbchat.git
$ cd fbchat
$ flit install
$ pip install fbchat
Maintainer

View File

@@ -3,8 +3,7 @@
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python3.6 -msphinx
SPHINXPROJ = fbchat
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build

View File

@@ -1,33 +1,79 @@
.. module:: fbchat
.. highlight:: python
.. _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.
.. _api_client:
Client
------
This is the main class of `fbchat`, which contains all the methods you use to interact with Facebook.
You can extend this class, and overwrite the events, to provide custom event handling (mainly used while listening)
.. autoclass:: Client
.. autoclass:: Client(email, password, user_agent=None, max_tries=5, session_cookies=None, logging_level=logging.INFO)
:members:
Threads
-------
.. _api_models:
Models
------
These models are used in various functions, both as inputs and return values.
A good tip is to write ``from fbchat.models import *`` at the start of your source, so you can use these models freely
.. automodule:: fbchat.models
:members:
.. autoclass:: Thread()
.. autoclass:: ThreadType(Enum)
:undoc-members:
.. autoclass:: Page()
.. autoclass:: User()
.. autoclass:: Group()
Messages
--------
.. autoclass:: Message
.. autoclass:: Mention
.. autoclass:: EmojiSize(Enum)
:undoc-members:
.. autoclass:: MessageReaction(Enum)
:undoc-members:
Exceptions
----------
.. autoexception:: FBchatException()
.. autoexception:: FBchatFacebookError()
.. autoexception:: FBchatUserError()
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:: ThreadColor(Enum)
:undoc-members:
.. autoclass:: ActiveStatus()
.. autoclass:: TypingStatus(Enum)
:undoc-members:
.. autoclass:: QuickReply
.. autoclass:: QuickReplyText
.. autoclass:: QuickReplyLocation
.. autoclass:: QuickReplyPhoneNumber
.. autoclass:: QuickReplyEmail
.. autoclass:: Poll
.. autoclass:: PollOption
.. autoclass:: Plan
.. autoclass:: GuestStatus(Enum)
:undoc-members:

View File

@@ -1,21 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# fbchat documentation build configuration file, created by
# sphinx-quickstart on Thu May 25 15:43:01 2017.
# Configuration file for the Sphinx documentation builder.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# -- Path setup --------------------------------------------------------------
import os
import sys
@@ -23,15 +14,24 @@ import sys
sys.path.insert(0, os.path.abspath(".."))
import fbchat
import tests
from fbchat import __copyright__, __author__, __version__, __description__
# -- Project information -----------------------------------------------------
project = fbchat.__name__
copyright = fbchat.__copyright__
author = fbchat.__author__
# The short X.Y version
version = fbchat.__version__
# The full version, including alpha/beta/rc tags
release = fbchat.__version__
# -- General configuration ------------------------------------------------
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
needs_sphinx = "2.0"
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@@ -46,122 +46,45 @@ extensions = [
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "fbchat"
title = "fbchat Documentation"
copyright = __copyright__
author = __author__
description = __description__
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __version__
# The full version, including alpha/beta/rc tags.
release = __version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
rst_prolog = ".. currentmodule:: " + project
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# The reST default role (used for this markup: `text`) to use for all
# documents.
#
default_role = "any"
# Make the reference parsing more strict
#
nitpicky = True
# Prefer strict Python highlighting
#
highlight_language = "python3"
# If true, '()' will be appended to :func: etc. cross-reference text.
#
add_function_parentheses = False
# -- Options for HTML output ----------------------------------------------
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "alabaster"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = project + "doc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# 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", title, 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, title, [author], 1)]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, project, title, author, project, description, "Miscellaneous")
]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"https://docs.python.org/3/": None}
add_function_parentheses = False
html_theme_options = {
"show_powered_by": False,
"github_user": "carpedm20",
@@ -170,9 +93,92 @@ html_theme_options = {
"show_related": False,
}
html_extra_path = ["robots.txt"]
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
html_sidebars = {"**": ["sidebar.html", "searchbox.html"]}
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#
html_show_sphinx = False
# If true, links to the reST sources are added to the pages.
#
html_show_sourcelink = False
# A shorter title for the navigation bar. Default is the same as html_title.
#
html_short_title = fbchat.__description__
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = project + "doc"
# -- Options for LaTeX output ------------------------------------------------
# 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")]
# -- 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)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
project,
fbchat.__title__,
author,
project,
fbchat.__description__,
"Miscellaneous",
)
]
# -- Options for Epub output -------------------------------------------------
# A list of files that should not be packed into the epub file.
epub_exclude_files = ["search.html"]
# -- Extension configuration -------------------------------------------------
# -- Options for autodoc extension ---------------------------------------
autoclass_content = "both"
html_short_title = description
autodoc_member_order = "bysource"
autodoc_default_options = {"members": True}
# -- Options for intersphinx extension ---------------------------------------
# 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

View File

@@ -1,16 +1,15 @@
.. highlight:: python
.. _examples:
Examples
========
These are a few examples on how to use `fbchat`. Remember to swap out `<email>` and `<password>` for your email and password
These are a few examples on how to use ``fbchat``. Remember to swap out ``<email>`` and ``<password>`` for your email and password
Basic example
-------------
This will show basic usage of `fbchat`
This will show basic usage of ``fbchat``
.. literalinclude:: ../examples/basic_usage.py
@@ -18,7 +17,7 @@ This will show basic usage of `fbchat`
Interacting with Threads
------------------------
This will interact with the thread in every way `fbchat` supports
This will interact with the thread in every way ``fbchat`` supports
.. literalinclude:: ../examples/interract.py
@@ -42,7 +41,7 @@ This will reply to any message with the same message
Remove Bot
----------
This will remove a user from a group if they write the message `Remove me!`
This will remove a user from a group if they write the message ``Remove me!``
.. literalinclude:: ../examples/removebot.py

View File

@@ -1,5 +1,3 @@
.. highlight:: python
.. module:: fbchat
.. _faq:
FAQ
@@ -23,7 +21,7 @@ Where you replace ``<X>`` with the version you want to use
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 ;)
We won't be focusing on anything else than chat-related things. This API is called ``fbCHAT``, after all ;)
Submitting Issues

View File

@@ -1,5 +1,3 @@
.. 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
@@ -30,9 +28,9 @@ This project was inspired by `facebook-chat-api <https://github.com/Schmavery/fa
**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:
Currently ``fbchat`` support Python 2.7, 3.4, 3.5 and 3.6:
`fbchat` works by emulating the browser.
``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.
@@ -48,7 +46,7 @@ Therefore, this API requires the credentials of a Facebook account.
Facebook now has an `official API <https://developers.facebook.com/docs/messenger-platform>`_ 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`
If you're already familiar with the basics of how Facebook works internally, go to :ref:`examples` to see example usage of ``fbchat``
Overview

View File

@@ -1,4 +1,3 @@
.. highlight:: sh
.. _install:
Installation
@@ -7,7 +6,9 @@ Installation
Pip Install fbchat
------------------
To install fbchat, run this command::
To install fbchat, run this command:
.. code-block:: sh
$ pip install fbchat
@@ -21,16 +22,22 @@ Get the Source Code
fbchat is developed on GitHub, where the code is
`always available <https://github.com/carpedm20/fbchat>`_.
You can either clone the public repository::
You can either clone the public repository:
.. code-block:: sh
$ git clone git://github.com/carpedm20/fbchat.git
Or, download a `tarball <https://github.com/carpedm20/fbchat/tarball/master>`_::
Or, download a `tarball <https://github.com/carpedm20/fbchat/tarball/master>`_:
.. 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::
package, or install it into your site-packages easily:
.. code-block:: sh
$ python setup.py install

View File

@@ -1,11 +1,9 @@
.. highlight:: python
.. module:: fbchat
.. _intro:
Introduction
============
`fbchat` uses your email and password to communicate with the Facebook server.
``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
@@ -28,7 +26,7 @@ Replace ``<email>`` and ``<password>`` with your email and password respectively
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`)
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`::
@@ -48,9 +46,9 @@ 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``.
:class:`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 differentiates between these two internally
This is required for many of ``fbchat``'s functions, since Facebook differentiates between these two internally
Searching for group chats and finding their ID can be done via. :func:`Client.searchForGroups`,
and searching for users is possible via. :func:`Client.searchForUsers`. See :ref:`intro_fetching`
@@ -87,7 +85,7 @@ 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`,
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::
@@ -100,17 +98,17 @@ This snippet shows how to send a message, and then use the returned ID to react
Interacting with Threads
------------------------
`fbchat` provides multiple functions for 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::
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.send(Message(text='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`
You can see a full example showing all the possible thread interactions with ``fbchat`` by going to :ref:`examples`
.. _intro_fetching:
@@ -118,7 +116,7 @@ You can see a full example showing all the possible thread interactions with `fb
Fetching Information
--------------------
You can use `fbchat` to fetch basic information like user names, profile pictures, thread names and user IDs
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.searchForUsers`.
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::
@@ -132,7 +130,7 @@ The following snippet will search for users by their name, take the first (and m
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`
You can see a full example showing all the possible ways to fetch information with ``fbchat`` by going to :ref:`examples`
.. _intro_sessions:
@@ -140,7 +138,7 @@ You can see a full example showing all the possible ways to fetch information wi
Sessions
--------
`fbchat` provides functions to retrieve and set the session cookies.
``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 :func:`Client.getSession` to retrieve the cookies::
@@ -164,13 +162,13 @@ Or you can set the ``session_cookies`` on your initial login.
Listening & Events
------------------
To use the listening functions `fbchat` offers (like :func:`Client.listen`),
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`
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::

View File

@@ -5,21 +5,20 @@ pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=python -msphinx
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=fbchat
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
echo.then set the SPHINXBUILD environment variable to point to the full
echo.path of the 'sphinx-build' executable. Alternatively you may add the
echo.Sphinx directory to PATH.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/

2
docs/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /en/master/

View File

@@ -1,5 +1,3 @@
.. highlight:: sh
.. module:: fbchat
.. _testing:
Testing
@@ -15,7 +13,9 @@ To use the tests, copy ``tests/data.json`` to ``tests/my_data.json`` or type the
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::
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
@@ -23,7 +23,3 @@ If you only want to execute specific tests, pass the function names in the comma
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)
.. automodule:: tests
:members: TestFbchat
:undoc-members: TestFbchat

View File

@@ -1,5 +1,3 @@
.. highlight:: python
.. module:: fbchat
.. _todo:
Todo

View File

@@ -13,7 +13,7 @@ from ._client import Client
from ._util import log # TODO: Remove this (from examples too)
__title__ = "fbchat"
__version__ = "1.7.0"
__version__ = "1.7.1"
__description__ = "Facebook Chat (Messenger) for Python"
__copyright__ = "Copyright 2015 - 2019 by Taehoon Kim"

View File

@@ -36,7 +36,7 @@ class ShareAttachment(Attachment):
source = attr.ib(None)
#: URL of the attachment image
image_url = attr.ib(None)
#: URL of the original image if Facebook uses `safe_image`
#: URL of the original image if Facebook uses ``safe_image``
original_image_url = attr.ib(None)
#: Width of the image
image_width = attr.ib(None)

File diff suppressed because it is too large Load Diff

View File

@@ -28,5 +28,30 @@ class FBchatFacebookError(FBchatException):
self.request_status_code = request_status_code
class FBchatInvalidParameters(FBchatFacebookError):
"""Raised by Facebook if:
- Some function supplied invalid parameters.
- Some content is not found.
- Some content is no longer available.
"""
class FBchatNotLoggedIn(FBchatFacebookError):
"""Raised by Facebook if the client has been logged out."""
fb_error_code = "1357001"
class FBchatPleaseRefresh(FBchatFacebookError):
"""Raised by Facebook if the client has been inactive for too long.
This error usually happens after 1-2 days of inactivity.
"""
fb_error_code = "1357004"
fb_error_message = "Please try closing and re-opening your browser window."
class FBchatUserError(FBchatException):
"""Thrown by fbchat when wrong values are entered"""

View File

@@ -4,7 +4,7 @@ from __future__ import unicode_literals
import json
import re
from . import _util
from ._exception import FBchatException, FBchatUserError
from ._exception import FBchatException
# Shameless copy from https://stackoverflow.com/a/8730674
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
@@ -27,18 +27,18 @@ class ConcatJSONDecoder(json.JSONDecoder):
# End shameless copy
def graphql_queries_to_json(*queries):
def queries_to_json(*queries):
"""
Queries should be a list of GraphQL objects
"""
rtn = {}
for i, query in enumerate(queries):
rtn["q{}".format(i)] = query.value
rtn["q{}".format(i)] = query
return json.dumps(rtn)
def graphql_response_to_json(content):
content = _util.strip_to_json(content) # Usually only needed in some error cases
def response_to_json(content):
content = _util.strip_json_cruft(content) # Usually only needed in some error cases
try:
j = json.loads(content, cls=ConcatJSONDecoder)
except Exception:
@@ -49,9 +49,9 @@ def graphql_response_to_json(content):
if "error_results" in x:
del rtn[-1]
continue
_util.check_json(x)
_util.handle_payload_error(x)
[(key, value)] = x.items()
_util.check_json(value)
_util.handle_graphql_errors(value)
if "response" in value:
rtn[int(key[1:])] = value["response"]
else:
@@ -62,16 +62,21 @@ def graphql_response_to_json(content):
return rtn
class GraphQL(object):
def __init__(self, query=None, doc_id=None, params=None):
if params is None:
params = {}
if query is not None:
self.value = {"priority": 0, "q": query, "query_params": params}
elif doc_id is not None:
self.value = {"doc_id": doc_id, "query_params": params}
else:
raise FBchatUserError("A query or doc_id must be specified")
def from_query(query, params):
return {"priority": 0, "q": query, "query_params": params}
def from_query_id(query_id, params):
return {"query_id": query_id, "query_params": params}
def from_doc(doc, params):
return {"doc": doc, "query_params": params}
def from_doc_id(doc_id, params):
return {"doc_id": doc_id, "query_params": params}
FRAGMENT_USER = """
QueryFragment User: User {

View File

@@ -99,13 +99,11 @@ class Message(object):
Returns a `Message` object, with the formatted string and relevant mentions.
```
>>> Message.formatMentions("Hey {!r}! My name is {}", ("1234", "Peter"), ("4321", "Michael"))
<Message (None): "Hey 'Peter'! My name is Michael", mentions=[<Mention 1234: offset=4 length=7>, <Mention 4321: offset=24 length=7>] emoji_size=None attachments=[]>
>>> Message.formatMentions("Hey {p}! My name is {}", ("1234", "Michael"), p=("4321", "Peter"))
<Message (None): 'Hey Peter! My name is Michael', mentions=[<Mention 4321: offset=4 length=5>, <Mention 1234: offset=22 length=7>] emoji_size=None attachments=[]>
```
"""
result = ""
mentions = list()

187
fbchat/_state.py Normal file
View File

@@ -0,0 +1,187 @@
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
import attr
import bs4
import re
import requests
import random
from . import _util, _exception
FB_DTSG_REGEX = re.compile(r'name="fb_dtsg" value="(.*?)"')
def find_input_fields(html):
return bs4.BeautifulSoup(html, "html.parser", parse_only=bs4.SoupStrainer("input"))
def session_factory(user_agent=None):
session = requests.session()
session.headers["Referer"] = "https://www.facebook.com"
# TODO: Deprecate setting the user agent manually
session.headers["User-Agent"] = user_agent or random.choice(_util.USER_AGENTS)
return session
def _2fa_helper(session, code, r):
soup = find_input_fields(r.text)
data = dict()
url = "https://m.facebook.com/login/checkpoint/"
data["approvals_code"] = code
data["fb_dtsg"] = soup.find("input", {"name": "fb_dtsg"})["value"]
data["nh"] = soup.find("input", {"name": "nh"})["value"]
data["submit[Submit Code]"] = "Submit Code"
data["codes_submitted"] = 0
_util.log.info("Submitting 2FA code.")
r = session.post(url, data=data)
if "home" in r.url:
return r
del data["approvals_code"]
del data["submit[Submit Code]"]
del data["codes_submitted"]
data["name_action_selected"] = "save_device"
data["submit[Continue]"] = "Continue"
_util.log.info("Saving browser.")
# At this stage, we have dtsg, nh, name_action_selected, submit[Continue]
r = session.post(url, data=data)
if "home" in r.url:
return r
del data["name_action_selected"]
_util.log.info("Starting Facebook checkup flow.")
# At this stage, we have dtsg, nh, submit[Continue]
r = session.post(url, data=data)
if "home" in r.url:
return r
del data["submit[Continue]"]
data["submit[This was me]"] = "This Was Me"
_util.log.info("Verifying login attempt.")
# At this stage, we have dtsg, nh, submit[This was me]
r = session.post(url, data=data)
if "home" in r.url:
return r
del data["submit[This was me]"]
data["submit[Continue]"] = "Continue"
data["name_action_selected"] = "save_device"
_util.log.info("Saving device again.")
# At this stage, we have dtsg, nh, submit[Continue], name_action_selected
r = session.post(url, data=data)
return r
@attr.s(slots=True) # TODO i Python 3: Add kw_only=True
class State(object):
"""Stores and manages state required for most Facebook requests."""
fb_dtsg = attr.ib()
_revision = attr.ib()
_session = attr.ib(factory=session_factory)
_counter = attr.ib(0)
_logout_h = attr.ib(None)
def get_user_id(self):
rtn = self.get_cookies().get("c_user")
if rtn is None:
return None
return str(rtn)
def get_params(self):
self._counter += 1 # TODO: Make this operation atomic / thread-safe
return {
"__a": 1,
"__req": _util.str_base(self._counter, 36),
"__rev": self._revision,
"fb_dtsg": self.fb_dtsg,
}
@classmethod
def login(cls, email, password, user_agent=None):
session = session_factory(user_agent=user_agent)
soup = find_input_fields(session.get("https://m.facebook.com/").text)
data = dict(
(elem["name"], elem["value"])
for elem in soup
if elem.has_attr("value") and elem.has_attr("name")
)
data["email"] = email
data["pass"] = password
data["login"] = "Log In"
r = session.post("https://m.facebook.com/login.php?login_attempt=1", data=data)
# Usually, 'Checkpoint' will refer to 2FA
if "checkpoint" in r.url and ('id="approvals_code"' in r.text.lower()):
code = on_2fa_callback()
r = _2fa_helper(session, code, r)
# Sometimes Facebook tries to show the user a "Save Device" dialog
if "save-device" in r.url:
r = session.get("https://m.facebook.com/login/save-device/cancel/")
if "home" in r.url:
return cls.from_session(session=session)
else:
raise _exception.FBchatUserError(
"Login failed. Check email/password. "
"(Failed on url: {})".format(r.url)
)
def is_logged_in(self):
# Send a request to the login url, to see if we're directed to the home page
url = "https://m.facebook.com/login.php?login_attempt=1"
r = self._session.get(url, allow_redirects=False)
return "Location" in r.headers and "home" in r.headers["Location"]
def logout(self):
logout_h = self._logout_h
if not logout_h:
url = _util.prefix_url("/bluebar/modern_settings_menu/")
h_r = self._session.post(url, data={"pmid": "4"})
logout_h = re.search(r'name=\\"h\\" value=\\"(.*?)\\"', h_r.text).group(1)
url = _util.prefix_url("/logout.php")
return self._session.get(url, params={"ref": "mb", "h": logout_h}).ok
@classmethod
def from_session(cls, session):
r = session.get(_util.prefix_url("/"))
soup = find_input_fields(r.text)
fb_dtsg_element = soup.find("input", {"name": "fb_dtsg"})
if fb_dtsg_element:
fb_dtsg = fb_dtsg_element["value"]
else:
# Fall back to searching with a regex
fb_dtsg = FB_DTSG_REGEX.search(r.text).group(1)
revision = int(r.text.split('"client_revision":', 1)[1].split(",", 1)[0])
logout_h_element = soup.find("input", {"name": "h"})
logout_h = logout_h_element["value"] if logout_h_element else None
return cls(
fb_dtsg=fb_dtsg, revision=revision, session=session, logout_h=logout_h
)
def get_cookies(self):
return self._session.cookies.get_dict()
@classmethod
def from_cookies(cls, cookies, user_agent=None):
session = session_factory(user_agent=user_agent)
session.cookies = requests.cookies.merge_cookies(session.cookies, cookies)
return cls.from_session(session=session)

View File

@@ -62,9 +62,9 @@ class ThreadColor(Enum):
class Thread(object):
"""Represents a Facebook thread"""
#: The unique identifier of the thread. Can be used a `thread_id`. See :ref:`intro_threads` for more info
#: The unique identifier of the thread. Can be used a ``thread_id``. See :ref:`intro_threads` for more info
uid = attr.ib(converter=str)
#: Specifies the type of thread. Can be used a `thread_type`. See :ref:`intro_threads` for more info
#: Specifies the type of thread. Can be used a ``thread_type``. See :ref:`intro_threads` for more info
type = attr.ib()
#: A url to the thread's picture
photo = attr.ib(None)

View File

@@ -11,7 +11,13 @@ from os.path import basename
import warnings
import logging
import requests
from ._exception import FBchatException, FBchatFacebookError
from ._exception import (
FBchatException,
FBchatFacebookError,
FBchatInvalidParameters,
FBchatNotLoggedIn,
FBchatPleaseRefresh,
)
try:
from urllib.parse import urlencode, parse_qs, urlparse
@@ -47,96 +53,12 @@ USER_AGENTS = [
]
class ReqUrl(object):
"""A class containing all urls used by `fbchat`"""
SEARCH = "https://www.facebook.com/ajax/typeahead/search.php"
LOGIN = "https://m.facebook.com/login.php?login_attempt=1"
SEND = "https://www.facebook.com/messaging/send/"
UNREAD_THREADS = "https://www.facebook.com/ajax/mercury/unread_threads.php"
UNSEEN_THREADS = "https://www.facebook.com/mercury/unseen_thread_ids/"
THREADS = "https://www.facebook.com/ajax/mercury/threadlist_info.php"
MOVE_THREAD = "https://www.facebook.com/ajax/mercury/move_thread.php"
ARCHIVED_STATUS = (
"https://www.facebook.com/ajax/mercury/change_archived_status.php?dpr=1"
)
PINNED_STATUS = (
"https://www.facebook.com/ajax/mercury/change_pinned_status.php?dpr=1"
)
MESSAGES = "https://www.facebook.com/ajax/mercury/thread_info.php"
READ_STATUS = "https://www.facebook.com/ajax/mercury/change_read_status.php"
DELIVERED = "https://www.facebook.com/ajax/mercury/delivery_receipts.php"
MARK_SEEN = "https://www.facebook.com/ajax/mercury/mark_seen.php"
BASE = "https://www.facebook.com"
MOBILE = "https://m.facebook.com/"
STICKY = "https://0-edge-chat.facebook.com/pull"
PING = "https://0-edge-chat.facebook.com/active_ping"
UPLOAD = "https://upload.facebook.com/ajax/mercury/upload.php"
INFO = "https://www.facebook.com/chat/user_info/"
CONNECT = "https://www.facebook.com/ajax/add_friend/action.php?dpr=1"
REMOVE_USER = "https://www.facebook.com/chat/remove_participants/"
LOGOUT = "https://www.facebook.com/logout.php"
ALL_USERS = "https://www.facebook.com/chat/user_info_all"
SAVE_DEVICE = "https://m.facebook.com/login/save-device/cancel/"
CHECKPOINT = "https://m.facebook.com/login/checkpoint/"
THREAD_COLOR = "https://www.facebook.com/messaging/save_thread_color/?source=thread_settings&dpr=1"
THREAD_NICKNAME = "https://www.facebook.com/messaging/save_thread_nickname/?source=thread_settings&dpr=1"
THREAD_EMOJI = "https://www.facebook.com/messaging/save_thread_emoji/?source=thread_settings&dpr=1"
THREAD_IMAGE = "https://www.facebook.com/messaging/set_thread_image/?dpr=1"
THREAD_NAME = "https://www.facebook.com/messaging/set_thread_name/?dpr=1"
MESSAGE_REACTION = "https://www.facebook.com/webgraphql/mutation"
TYPING = "https://www.facebook.com/ajax/messaging/typ.php"
GRAPHQL = "https://www.facebook.com/api/graphqlbatch/"
ATTACHMENT_PHOTO = "https://www.facebook.com/mercury/attachments/photo/"
PLAN_CREATE = "https://www.facebook.com/ajax/eventreminder/create"
PLAN_INFO = "https://www.facebook.com/ajax/eventreminder"
PLAN_CHANGE = "https://www.facebook.com/ajax/eventreminder/submit"
PLAN_PARTICIPATION = "https://www.facebook.com/ajax/eventreminder/rsvp"
MODERN_SETTINGS_MENU = "https://www.facebook.com/bluebar/modern_settings_menu/"
REMOVE_FRIEND = "https://m.facebook.com/a/removefriend.php"
BLOCK_USER = "https://www.facebook.com/messaging/block_messages/?dpr=1"
UNBLOCK_USER = "https://www.facebook.com/messaging/unblock_messages/?dpr=1"
SAVE_ADMINS = "https://www.facebook.com/messaging/save_admins/?dpr=1"
APPROVAL_MODE = "https://www.facebook.com/messaging/set_approval_mode/?dpr=1"
CREATE_GROUP = "https://m.facebook.com/messages/send/?icm=1"
DELETE_THREAD = "https://www.facebook.com/ajax/mercury/delete_thread.php?dpr=1"
DELETE_MESSAGES = "https://www.facebook.com/ajax/mercury/delete_messages.php?dpr=1"
MUTE_THREAD = "https://www.facebook.com/ajax/mercury/change_mute_thread.php?dpr=1"
MUTE_REACTIONS = (
"https://www.facebook.com/ajax/mercury/change_reactions_mute_thread/?dpr=1"
)
MUTE_MENTIONS = (
"https://www.facebook.com/ajax/mercury/change_mentions_mute_thread/?dpr=1"
)
CREATE_POLL = "https://www.facebook.com/messaging/group_polling/create_poll/?dpr=1"
UPDATE_VOTE = "https://www.facebook.com/messaging/group_polling/update_vote/?dpr=1"
GET_POLL_OPTIONS = "https://www.facebook.com/ajax/mercury/get_poll_options"
SEARCH_MESSAGES = "https://www.facebook.com/ajax/mercury/search_snippets.php?dpr=1"
MARK_SPAM = "https://www.facebook.com/ajax/mercury/mark_spam.php?dpr=1"
UNSEND = "https://www.facebook.com/messaging/unsend_message/?dpr=1"
FORWARD_ATTACHMENT = "https://www.facebook.com/mercury/attachments/forward/"
pull_channel = 0
def change_pull_channel(self, channel=None):
if channel is None:
self.pull_channel = (self.pull_channel + 1) % 5 # Pull channel will be 0-4
else:
self.pull_channel = channel
self.STICKY = "https://{}-edge-chat.facebook.com/pull".format(self.pull_channel)
self.PING = "https://{}-edge-chat.facebook.com/active_ping".format(
self.pull_channel
)
facebookEncoding = "UTF-8"
def now():
return int(time() * 1000)
def strip_to_json(text):
def strip_json_cruft(text):
"""Removes `for(;;);` (and other cruft) that preceeds JSON responses."""
try:
return text[text.index("{") :]
except ValueError:
@@ -148,15 +70,14 @@ def get_decoded_r(r):
def get_decoded(content):
return content.decode(facebookEncoding)
return content.decode("utf-8")
def parse_json(content):
try:
return json.loads(content)
def get_json(r):
return json.loads(strip_to_json(get_decoded_r(r)))
except ValueError:
raise FBchatFacebookError("Error while parsing JSON: {!r}".format(content))
def digitToChar(digit):
@@ -192,61 +113,74 @@ def generateOfflineThreadingID():
return str(int(msgs, 2))
def check_json(j):
if hasattr(j.get("payload"), "get") and j["payload"].get("error"):
raise FBchatFacebookError(
"Error when sending request: {}".format(j["payload"]["error"]),
fb_error_code=None,
fb_error_message=j["payload"]["error"],
)
elif j.get("error"):
if "errorDescription" in j:
# 'errorDescription' is in the users own language!
raise FBchatFacebookError(
"Error #{} when sending request: {}".format(
j["error"], j["errorDescription"]
),
fb_error_code=j["error"],
def handle_payload_error(j):
if "error" not in j:
return
error = j["error"]
if j["error"] == 1357001:
error_cls = FBchatNotLoggedIn
elif j["error"] == 1357004:
error_cls = FBchatPleaseRefresh
elif j["error"] in (1357031, 1545010, 1545003):
error_cls = FBchatInvalidParameters
else:
error_cls = FBchatFacebookError
# TODO: Use j["errorSummary"]
# "errorDescription" is in the users own language!
raise error_cls(
"Error #{} when sending request: {}".format(error, j["errorDescription"]),
fb_error_code=error,
fb_error_message=j["errorDescription"],
)
elif "debug_info" in j["error"] and "code" in j["error"]:
def handle_graphql_errors(j):
errors = []
if "error" in j:
errors = [j["error"]]
if "errors" in j:
errors = j["errors"]
if errors:
error = errors[0] # TODO: Handle multiple errors
# TODO: Use `summary`, `severity` and `description`
raise FBchatFacebookError(
"Error #{} when sending request: {}".format(
j["error"]["code"], repr(j["error"]["debug_info"])
"GraphQL error #{}: {} / {!r}".format(
error.get("code"), error.get("message"), error.get("debug_info")
),
fb_error_code=j["error"]["code"],
fb_error_message=j["error"]["debug_info"],
)
else:
raise FBchatFacebookError(
"Error {} when sending request".format(j["error"]),
fb_error_code=j["error"],
fb_error_code=error.get("code"),
fb_error_message=error.get("message"),
)
def check_request(r, as_json=True):
if not r.ok:
raise FBchatFacebookError(
"Error when sending request: Got {} response".format(r.status_code),
request_status_code=r.status_code,
)
def check_request(r):
check_http_code(r.status_code)
content = get_decoded_r(r)
check_content(content)
return content
def check_http_code(code):
msg = "Error when sending request: Got {} response.".format(code)
if code == 404:
raise FBchatFacebookError(
msg + " This is either because you specified an invalid URL, or because"
" you provided an invalid id (Facebook usually requires integer ids).",
request_status_code=code,
)
if 400 <= code < 600:
raise FBchatFacebookError(msg, request_status_code=code)
def check_content(content, as_json=True):
if content is None or len(content) == 0:
raise FBchatFacebookError("Error when sending request: Got empty response")
if as_json:
content = strip_to_json(content)
try:
j = json.loads(content)
except ValueError:
raise FBchatFacebookError("Error while parsing JSON: {!r}".format(content))
check_json(j)
def to_json(content):
content = strip_json_cruft(content)
j = parse_json(content)
log.debug(j)
return j
else:
return content
def get_jsmods_require(j, index):
@@ -255,9 +189,8 @@ def get_jsmods_require(j, index):
return j["jsmods"]["require"][0][index][0]
except (KeyError, IndexError) as e:
log.warning(
"Error when getting jsmods_require: {}. Facebook might have changed protocol".format(
j
)
"Error when getting jsmods_require: "
"{}. Facebook might have changed protocol".format(j)
)
return None
@@ -315,3 +248,9 @@ def get_url_parameters(url, *args):
def get_url_parameter(url, param):
return get_url_parameters(url, param)[0]
def prefix_url(url):
if url.startswith("/"):
return "https://www.facebook.com" + url
return url

View File

@@ -1,14 +1,5 @@
[tool.black]
line-length = 88
exclude = '''
/(
\.git
| \.pytest_cache
| build
| dist
| venv
)/
'''
[build-system]
requires = ["flit"]
@@ -22,10 +13,10 @@ maintainer = "Mads Marquart"
maintainer-email = "madsmtm@gmail.com"
home-page = "https://github.com/carpedm20/fbchat/"
requires = [
"aenum",
"attrs~=18.2.0",
"requests",
"beautifulsoup4",
"aenum~=2.0",
"attrs>=18.2",
"requests~=2.19",
"beautifulsoup4~=4.0",
]
description-file = "README.rst"
classifiers = [
@@ -61,5 +52,11 @@ Repository = "https://github.com/carpedm20/fbchat/"
[tool.flit.metadata.requires-extra]
test = [
"pytest~=4.0",
"six",
"six~=1.0",
]
docs = [
"sphinx~=2.0",
]
lint = [
"black",
]