Compare commits

...

10 Commits

Author SHA1 Message Date
769b034d38 Update path
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2023-06-20 15:17:34 +03:00
fd3d5f7301 Update README
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2023-03-29 15:42:15 +03:00
2fa1b58336 Add session to_file and from_file
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2023-03-28 14:50:52 +03:00
9523350dc5 Add pyproject.toml and pytest.ini
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2023-03-28 11:59:32 +03:00
356db553b7 Sync to maraid/fbchat
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2023-03-28 11:49:48 +03:00
55712756d7 Remove unused features
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2023-03-28 11:44:43 +03:00
Mads Marquart
916a14062d Add unmaintained notice 2020-09-23 12:07:26 +02:00
Mads Marquart
43aa16c32d Remove stupid, obviously flaky test 2020-06-14 23:18:07 +02:00
Mads Marquart
427ae6bc5e Bump version: 2.0.0a4 -> 2.0.0a5 2020-06-14 23:15:50 +02:00
Mads Marquart
d650946531 Add act cookie on login 2020-06-14 23:00:40 +02:00
35 changed files with 145 additions and 886 deletions

View File

@@ -1,34 +0,0 @@
---
name: Bug report
about: Create a report if you're having trouble with `fbchat`
---
## Description of the problem
Example: Logging in fails when the character `%` is in the password. A specific password that fails is `a_password_with_%`
## Code to reproduce
```py
# Example code
from fbchat import Client
client = Client("[REDACTED_USERNAME]", "a_password_with_%")
```
## Traceback
```
Traceback (most recent call last):
File "<test.py>", line 1, in <module>
File "[site-packages]/fbchat/client.py", line 78, in __init__
self.login(email, password, max_tries)
File "[site-packages]/fbchat/client.py", line 407, in login
raise FBchatException('Login failed. Check email/password. (Failed on URL: {})'.format(login_url))
fbchat.FBchatException: Login failed. Check email/password. (Failed on URL: https://m.facebook.com/login.php?login_attempt=1)
```
## Environment information
- Python version
- `fbchat` version
- If relevant, output from `$ python -m pip list`
If you have done any research, include that.
Make sure to redact all personal information.

View File

@@ -1,19 +0,0 @@
---
name: Feature request
about: Suggest a feature that you'd like to see implemented
---
## Description
Example: There's no way to send messages to groups
## Research (if applicable)
Example: I've found the URL `https://facebook.com/send_message.php`, to which you can send a POST requests with the following JSON:
```json
{
"text": message_content,
"fbid": group_id,
"some_variable": ?
}
```
But I don't know how what `some_variable` does, and it doesn't work without it. I've found some examples of `some_variable` to be: `MTIzNDU2Nzg5MA`, `MTIzNDU2Nzg5MQ` and `MTIzNDU2Nzg5Mg`

View File

@@ -1,20 +0,0 @@
# 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
# Disabled, until we can find a way to get sphinx-autodoc-typehints play nice with our
# module renaming!
fail_on_warning: false

View File

@@ -1,53 +0,0 @@
sudo: false
language: python
python: 3.6
cache: pip
before_install: pip install flit
# Use `--deps production` so that we don't install unnecessary dependencies
install: flit install --deps production --extras test
script: pytest
jobs:
include:
- python: 3.5
- python: 3.6
- python: 3.7
- python: pypy3.5
- name: Lint
before_install: skip
install: pip install black
script: black --check --verbose .
- stage: deploy
name: GitHub Releases
if: tag IS present
install: skip
script: flit build
deploy:
provider: releases
api_key: $GITHUB_OAUTH_TOKEN
file_glob: true
file: dist/*
skip_cleanup: true
draft: false
on:
tags: true
- stage: deploy
name: PyPI
if: tag IS present
install: skip
script: skip
deploy:
provider: script
script: flit publish
on:
tags: true
notifications:
email:
on_success: never
on_failure: change

4
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"esbonio.sphinx.confDir": "",
"python.formatting.provider": "autopep8"
}

View File

@@ -1,30 +1,6 @@
``fbchat`` - Facebook Messenger for Python ``fbchat`` - Facebook Messenger for Python
========================================== ==========================================
.. 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/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
:alt: Documentation
.. image:: https://badgen.net/travis/carpedm20/fbchat
:target: https://travis-ci.org/carpedm20/fbchat
:alt: Travis CI
.. image:: https://badgen.net/badge/code%20style/black/black
:target: https://github.com/ambv/black
:alt: Code style
A powerful and efficient library to interact with A powerful and efficient library to interact with
`Facebook's Messenger <https://www.facebook.com/messages/>`__, using just your email and password. `Facebook's Messenger <https://www.facebook.com/messages/>`__, using just your email and password.
@@ -38,16 +14,13 @@ This is *not* an official API, Facebook has that `over here <https://developers.
- Creating groups, setting the group emoji, changing nicknames, creating polls, etc. - Creating groups, setting the group emoji, changing nicknames, creating polls, etc.
- Listening for, an reacting to messages and other events in real-time. - 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). - 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! Essentially, everything you need to make an amazing Facebook bot!
Version Warning 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 <https://github.com/carpedm20/fbchat/tree/v1>`__. ``v2`` is currently being developed at the ``master`` branch and it's highly unstable.
Additionally, you can view the project's progress `here <https://github.com/carpedm20/fbchat/projects/2>`__.
Caveats Caveats
@@ -58,14 +31,6 @@ Caveats
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! 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! 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 <https://fbchat.readthedocs.io/>`__ to see the full documentation!
.. inclusion-marker-installation-start
Installation Installation
@@ -73,40 +38,10 @@ Installation
.. code-block:: .. code-block::
$ pip install fbchat $ pip install git+https://git.karaolidis.com/karaolidis/fbchat.git
If you don't have `pip <https://pip.pypa.io/>`_, `this guide <http://docs.python-guide.org/en/latest/starting/installation/>`_ 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::
import getpass
import fbchat
session = fbchat.Session.login("<email/phone number>", getpass.getpass())
user = fbchat.User(session=session, id=session.user_id)
user.send_text("Test message!")
More examples are available `here <https://github.com/carpedm20/fbchat/tree/master/examples>`__.
Maintainer
----------
- Mads Marquart / `@madsmtm <https://github.com/madsmtm>`__
Acknowledgements Acknowledgements
---------------- ----------------
This project was originally inspired by `facebook-chat-api <https://github.com/Schmavery/facebook-chat-api>`__. This project is a fork of `fbchat <https://github.com/fbchat-dev/fbchat>`__ and was originally inspired by `facebook-chat-api <https://github.com/Schmavery/facebook-chat-api>`__.

View File

@@ -1,19 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,26 +0,0 @@
{% extends '!layout.html' %}
{% block extrahead %}
<script async defer src="https://buttons.github.io/buttons.js"></script>
<!-- Alabaster (krTheme++) Hacks, modified version of Kenneth Reitz' https://github.com/kennethreitz/requests/blob/master/docs/_templates/hacks.html -->
<style type="text/css">
/* Rezzy requires precise alignment. */
img.logo {margin-left: -20px!important;}
/* "Quick Search" should be capitalized. */
div#searchbox h3 {text-transform: capitalize;}
/* Go button should be behind input field */
div.sphinxsidebar div#searchbox input[type="text"] {width: 160px}
div#searchbox form div {display: inline-block;}
/* Make the document a little wider, less code is cut-off. */
div.document {width: 1008px;}
/* Much-improved spacing around code blocks. */
div.highlight pre {padding: 11px 14px;}
/* Remain Responsive! */
@media screen and (max-width: 1008px) {
div.sphinxsidebar {display: none;}
div.document {width: 100%!important;}
/* Have code blocks escape the document right-margin. */
div.highlight pre {margin-right: -30px;}
}
</style>
{% endblock %}

View File

@@ -1,13 +0,0 @@
<h3>
<a href="{{ pathto(master_doc) }}">{{ _(project) }}</a>
</h3>
<p>
<a class="github-button" href="https://github.com/carpedm20/fbchat" data-size="large" data-show-count="true" aria-label="Star carpedm20/fbchat on GitHub">Star</a>
</p>
<p>
{{ _(shorttitle) }}
</p>
{{ toctree() }}

View File

@@ -1,13 +0,0 @@
Attachments
===========
.. autoclass:: Attachment()
.. autoclass:: ShareAttachment()
.. autoclass:: Sticker()
.. autoclass:: LocationAttachment()
.. autoclass:: LiveLocationAttachment()
.. autoclass:: FileAttachment()
.. autoclass:: AudioAttachment()
.. autoclass:: ImageAttachment()
.. autoclass:: VideoAttachment()
.. autoclass:: ImageAttachment()

View File

@@ -1,4 +0,0 @@
Client
======
.. autoclass:: Client

View File

@@ -1,4 +0,0 @@
Events
======
.. autoclass:: Listener

View File

@@ -1,11 +0,0 @@
Exceptions
==========
.. autoexception:: FacebookError()
.. autoexception:: HTTPError()
.. autoexception:: ParseError()
.. autoexception:: NotLoggedIn()
.. autoexception:: ExternalError()
.. autoexception:: GraphQLError()
.. autoexception:: InvalidParameters()
.. autoexception:: PleaseRefresh()

View File

@@ -1,21 +0,0 @@
.. 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

View File

@@ -1,8 +0,0 @@
Messages
========
.. autoclass:: Message
.. autoclass:: Mention
.. autoclass:: EmojiSize(Enum)
:undoc-members:
.. autoclass:: MessageData()

View File

@@ -1,20 +0,0 @@
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:

View File

@@ -1,4 +0,0 @@
Session
=======
.. autoclass:: Session()

View File

@@ -1,6 +0,0 @@
Thread Data
===========
.. autoclass:: PageData()
.. autoclass:: UserData()
.. autoclass:: GroupData()

View File

@@ -1,8 +0,0 @@
Threads
=======
.. autoclass:: ThreadABC()
.. autoclass:: Thread
.. autoclass:: Page
.. autoclass:: User
.. autoclass:: Group

View File

@@ -1,194 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# 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
# -- Path setup --------------------------------------------------------------
import os
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 = "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__
# The full version, including alpha/beta/rc tags
release = fbchat.__version__
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
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
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx.ext.napoleon",
"sphinxcontrib.spelling",
"sphinx_autodoc_typehints",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The master toctree document.
master_doc = "index"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
rst_prolog = ".. currentmodule:: " + project
# 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 -------------------------------------------------
# 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 = {
"show_powered_by": False,
"github_user": "carpedm20",
"github_repo": project,
"github_banner": True,
"show_related": False,
}
# 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 = 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", 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, project, [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, project, author, project, 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 = "class"
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 napoleon extension ----------------------------------------------
# Use Google style docstrings
napoleon_google_docstring = True
napoleon_numpy_docstring = False
# napoleon_use_admonition_for_examples = False
# napoleon_use_admonition_for_notes = False
# napoleon_use_admonition_for_references = False
# -- Options for spelling extension ----------------------------------------------
spelling_word_list_filename = [
"spelling/names.txt",
"spelling/technical.txt",
"spelling/fixes.txt",
]
spelling_ignore_wiki_words = False
# spelling_ignore_acronyms = False
spelling_ignore_python_builtins = False
spelling_ignore_importable_modules = False

View File

@@ -1,55 +0,0 @@
.. _examples:
Examples
========
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``
.. literalinclude:: ../examples/basic_usage.py
Interacting with Threads
------------------------
This will interact with the thread in every way ``fbchat`` supports
.. literalinclude:: ../examples/interract.py
Fetching Information
--------------------
This will show the different ways of fetching information about users and threads
.. literalinclude:: ../examples/fetch.py
``Echobot``
-----------
This will reply to any message with the same message
.. literalinclude:: ../examples/echobot.py
Remove Bot
----------
This will remove a user from a group if they write the message ``Remove me!``
.. literalinclude:: ../examples/removebot.py
"Prevent changes"-Bot
---------------------
This will prevent chat color, emoji, nicknames and chat name from being changed.
It will also prevent people from being added and removed
.. literalinclude:: ../examples/keepbot.py

View File

@@ -1,23 +0,0 @@
Frequently Asked Questions
==========================
The new version broke my application
------------------------------------
``fbchat`` follows `Scemantic Versioning <https://semver.org/>`__ quite rigorously!
That means that breaking changes can *only* occur in major versions (e.g. ``v1.9.6`` -> ``v2.0.0``).
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==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 library is called ``fbCHAT``, after all!

View File

@@ -1,23 +0,0 @@
.. highlight:: sh
.. See README.rst for explanation of these markers
.. include:: ../README.rst
:end-before: inclusion-marker-intro-end
With that said, let's get started!
.. include:: ../README.rst
:start-after: inclusion-marker-installation-start
:end-before: inclusion-marker-installation-end
Documentation Overview
----------------------
.. toctree::
:maxdepth: 2
intro
examples
faq
api/index

View File

@@ -1,152 +0,0 @@
Introduction
============
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.
Logging In
----------
Everything in ``fbchat`` starts with getting an instance of `Session`. Currently there are two ways of doing that, `Session.login` and `Session.from_cookies`.
The follow example will prompt you for you password, and use it to login::
import getpass
import fbchat
session = fbchat.Session.login("<email/phone number>", getpass.getpass())
# If your account requires a two factor authentication code:
session = fbchat.Session.login(
"<your email/phone number>",
getpass.getpass(),
lambda: getpass.getpass("2FA code"),
)
However, **this is not something you should do often!** Logging in/out all the time *will* get your Facebook account locked!
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.
Usability-wise, this is also better, since you won't have to re-type your password every time you want to login.
The following, quite lengthy, yet very import example, illustrates a way to do this:
.. literalinclude:: ../examples/session_handling.py
Assuming you have successfully completed the above, congratulations! Using ``fbchat`` should be mostly trouble free from now on!
Understanding Thread Ids
------------------------
At the core of any thread is its unique identifier, its ID.
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`)
You can get your own user ID from `Session.user` with ``session.user.id``.
Getting the ID of a specific group thread is fairly trivial, you only need to login to `<https://www.messenger.com/>`_, 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.
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
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::
group = fbchat.Group(session=session, id="<The id you found>")
# Or for user threads
user = fbchat.User(session=session, id="<The id you found>")
Just like threads, every message, poll, plan, attachment, action etc. you send or do on Facebook has a unique ID.
Below is an example of using such a message ID to get a `Message` instance::
# Provide the thread the message was created in, and it's ID
message = fbchat.Message(thread=user, id="<The message id>")
Fetching Information
--------------------
Managing these ids yourself quickly becomes very cumbersome! Luckily, there are other, easier ways of getting `Group`/`User` instances.
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::
client = fbchat.Client(session=session)
Now, you could search for threads using `Client.search_for_threads`, or fetch a list of them using `Client.fetch_threads`::
# 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("<name of the thread to search for>", 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 <https://wiki.python.org/moin/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)
Interacting with Threads
------------------------
Once you have a `User`/`Group` instance, you can do things on them as described in `ThreadABC`, since they are subclasses of that.
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.
With that out of the way, let's see some examples!
The simplest way of interacting with a thread is by sending a message::
# Send a message to the user
message = user.send_text("test message")
There are many types of messages you can send, see the full API documentation for more.
Notice how we held on to the sent message? The return type i a `Message` instance, so you can interact with it afterwards::
# React to the message with the 😍 emoji
message.react("😍")
Besides sending messages, you can also interact 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")
Listening & Events
------------------
Now, we are finally at the point we have all been waiting for: Creating an automatic Facebook bot!
To get started, you create the functions you want to call on certain events::
def my_function(event: fbchat.MessageEvent):
print(f"Message from {event.author.id}: {event.message.text}")
Then you create a `fbchat.Listener` object::
listener = fbchat.Listener(session=session, chat_on=False, foreground=False)
Which you can then use to receive events, and send them to your functions::
for event in listener.listen():
if isinstance(event, fbchat.MessageEvent):
my_function(event)
View the :ref:`examples` to see some more examples illustrating the event system.

View File

@@ -1,35 +0,0 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
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/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

View File

@@ -1,3 +0,0 @@
premade
todo
emoji

View File

@@ -1,3 +0,0 @@
Facebook
GraphQL
GitHub

View File

@@ -1,17 +0,0 @@
iterables
iterable
mimetype
timestamp
metadata
spam
spammy
admin
admins
unsend
unsends
unmute
spritemap
online
inbox
subclassing
codebase

View File

@@ -118,7 +118,7 @@ from ._listen import Listener
from ._client import Client from ._client import Client
__version__ = "2.0.0a4" __version__ = "2.0.0a5"
__all__ = ("Session", "Listener", "Client") __all__ = ("Session", "Listener", "Client")

View File

@@ -125,20 +125,12 @@ class Message:
def react(self, reaction: Optional[str]): def react(self, reaction: Optional[str]):
"""React to the message, or removes reaction. """React to the message, or removes reaction.
Currently, you can use "", "😍", "😆", "😮", "😢", "😠", "👍" or "👎". It
should be possible to add support for more, but we haven't figured that out yet.
Args: Args:
reaction: Reaction emoji to use, or if ``None``, removes reaction. reaction: Reaction emoji to use, or if ``None``, removes reaction.
Example: Example:
>>> message.react("😍") >>> message.react("😍")
""" """
if reaction and reaction not in SENDABLE_REACTIONS:
raise ValueError(
"Invalid reaction! Please use one of: {}".format(SENDABLE_REACTIONS)
)
data = { data = {
"action": "ADD_REACTION" if reaction else "REMOVE_REACTION", "action": "ADD_REACTION" if reaction else "REMOVE_REACTION",
"client_mutation_id": "1", "client_mutation_id": "1",

View File

@@ -16,7 +16,10 @@ from typing import Optional, Mapping, Callable, Any
SERVER_JS_DEFINE_REGEX = re.compile( SERVER_JS_DEFINE_REGEX = re.compile(
r'(?:"ServerJS".{,100}\.handle\({.*"define":)|(?:require\("ServerJSDefine"\)\)?\.handleDefines\()' r'(?:"ServerJS".{,100}\.handle\({.*"define":)'
r'|(?:ServerJS.{,100}\.handleWithCustomApplyEach\(ScheduledApplyEach,{.*"define":)'
r'|(?:require\("ServerJSDefine"\)\)?\.handleDefines\()'
r'|(?:"require":\[\["ScheduledServerJS".{,100}"define":)'
) )
SERVER_JS_DEFINE_JSON_DECODER = json.JSONDecoder() SERVER_JS_DEFINE_JSON_DECODER = json.JSONDecoder()
@@ -36,8 +39,6 @@ def parse_server_js_define(html: str) -> Mapping[str, Any]:
raise _exception.ParseError("Could not find any ServerJSDefine", data=html) raise _exception.ParseError("Could not find any ServerJSDefine", data=html)
if len(define_splits) < 2: if len(define_splits) < 2:
raise _exception.ParseError("Could not find enough ServerJSDefine", data=html) raise _exception.ParseError("Could not find enough ServerJSDefine", data=html)
if len(define_splits) > 2:
raise _exception.ParseError("Found too many ServerJSDefine", data=define_splits)
# Parse entries (should be two) # Parse entries (should be two)
for entry in define_splits: for entry in define_splits:
try: try:
@@ -107,8 +108,12 @@ def session_factory() -> requests.Session:
return session return session
def login_cookies(at: datetime.datetime):
return {"act": "{}/0".format(_util.datetime_to_millis(at))}
def client_id_factory() -> str: def client_id_factory() -> str:
return hex(int(random.random() * 2 ** 31))[2:] return hex(int(random.random() * 2**31))[2:]
def find_form_request(html: str): def find_form_request(html: str):
@@ -143,7 +148,9 @@ def two_factor_helper(session: requests.Session, r, on_2fa_callback):
while "approvals_code" in data: while "approvals_code" in data:
data["approvals_code"] = on_2fa_callback() data["approvals_code"] = on_2fa_callback()
log.info("Submitting 2FA code") log.info("Submitting 2FA code")
r = session.post(url, data=data, allow_redirects=False) r = session.post(
url, data=data, allow_redirects=False, cookies=login_cookies(_util.now())
)
log.debug("2FA location: %s", r.headers.get("Location")) log.debug("2FA location: %s", r.headers.get("Location"))
url, data = find_form_request(r.content.decode("utf-8")) url, data = find_form_request(r.content.decode("utf-8"))
@@ -151,7 +158,9 @@ def two_factor_helper(session: requests.Session, r, on_2fa_callback):
if "name_action_selected" in data: if "name_action_selected" in data:
data["name_action_selected"] = "save_device" data["name_action_selected"] = "save_device"
log.info("Saving browser") log.info("Saving browser")
r = session.post(url, data=data, allow_redirects=False) r = session.post(
url, data=data, allow_redirects=False, cookies=login_cookies(_util.now())
)
log.debug("2FA location: %s", r.headers.get("Location")) log.debug("2FA location: %s", r.headers.get("Location"))
url = r.headers.get("Location") url = r.headers.get("Location")
if url and url.startswith("https://www.messenger.com/login/auth_token/"): if url and url.startswith("https://www.messenger.com/login/auth_token/"):
@@ -159,7 +168,9 @@ def two_factor_helper(session: requests.Session, r, on_2fa_callback):
url, data = find_form_request(r.content.decode("utf-8")) url, data = find_form_request(r.content.decode("utf-8"))
log.info("Starting Facebook checkup flow") log.info("Starting Facebook checkup flow")
r = session.post(url, data=data, allow_redirects=False) r = session.post(
url, data=data, allow_redirects=False, cookies=login_cookies(_util.now())
)
log.debug("2FA location: %s", r.headers.get("Location")) log.debug("2FA location: %s", r.headers.get("Location"))
url, data = find_form_request(r.content.decode("utf-8")) url, data = find_form_request(r.content.decode("utf-8"))
@@ -172,7 +183,9 @@ def two_factor_helper(session: requests.Session, r, on_2fa_callback):
data["submit[This was me]"] = "[any value]" data["submit[This was me]"] = "[any value]"
del data["submit[This wasn't me]"] del data["submit[This wasn't me]"]
log.info("Verifying login attempt") log.info("Verifying login attempt")
r = session.post(url, data=data, allow_redirects=False) r = session.post(
url, data=data, allow_redirects=False, cookies=login_cookies(_util.now())
)
log.debug("2FA location: %s", r.headers.get("Location")) log.debug("2FA location: %s", r.headers.get("Location"))
url, data = find_form_request(r.content.decode("utf-8")) url, data = find_form_request(r.content.decode("utf-8"))
@@ -180,7 +193,9 @@ def two_factor_helper(session: requests.Session, r, on_2fa_callback):
raise _exception.ParseError("Could not fill out form properly (3)", data=data) raise _exception.ParseError("Could not fill out form properly (3)", data=data)
data["name_action_selected"] = "save_device" data["name_action_selected"] = "save_device"
log.info("Saving device again") log.info("Saving device again")
r = session.post(url, data=data, allow_redirects=False) r = session.post(
url, data=data, allow_redirects=False, cookies=login_cookies(_util.now())
)
log.debug("2FA location: %s", r.headers.get("Location")) log.debug("2FA location: %s", r.headers.get("Location"))
return r.headers.get("Location") return r.headers.get("Location")
@@ -296,6 +311,22 @@ class Session:
"https://www.messenger.com/login/password/", "https://www.messenger.com/login/password/",
data=data, data=data,
allow_redirects=False, allow_redirects=False,
cookies=login_cookies(_util.now()),
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-HU,en;q=0.9,hu-HU;q=0.8,hu;q=0.7,en-US;q=0.6",
"cache-control": "max-age=0",
"origin": "https://www.messenger.com",
"referer": "https://www.messenger.com/login/",
"sec-ch-ua": '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
"sec-ch-ua-mobile": "?0",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "same-origin",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
},
) )
except requests.RequestException as e: except requests.RequestException as e:
_exception.handle_requests_error(e) _exception.handle_requests_error(e)
@@ -319,18 +350,24 @@ class Session:
if not url.startswith("https://www.facebook.com/checkpoint/start/"): if not url.startswith("https://www.facebook.com/checkpoint/start/"):
raise _exception.ParseError("Failed 2fa flow (1)", data=url) raise _exception.ParseError("Failed 2fa flow (1)", data=url)
r = session.get(url, allow_redirects=False) r = session.get(
url, allow_redirects=False, cookies=login_cookies(_util.now())
)
url = r.headers.get("Location") url = r.headers.get("Location")
if not url or not url.startswith("https://www.facebook.com/checkpoint/"): if not url or not url.startswith("https://www.facebook.com/checkpoint/"):
raise _exception.ParseError("Failed 2fa flow (2)", data=url) raise _exception.ParseError("Failed 2fa flow (2)", data=url)
r = session.get(url, allow_redirects=False) r = session.get(
url, allow_redirects=False, cookies=login_cookies(_util.now())
)
url = two_factor_helper(session, r, on_2fa_callback) url = two_factor_helper(session, r, on_2fa_callback)
if not url.startswith("https://www.messenger.com/login/auth_token/"): if not url.startswith("https://www.messenger.com/login/auth_token/"):
raise _exception.ParseError("Failed 2fa flow (3)", data=url) raise _exception.ParseError("Failed 2fa flow (3)", data=url)
r = session.get(url, allow_redirects=False) r = session.get(
url, allow_redirects=False, cookies=login_cookies(_util.now())
)
url = r.headers.get("Location") url = r.headers.get("Location")
if url != "https://www.messenger.com/": if url != "https://www.messenger.com/":
@@ -390,7 +427,7 @@ class Session:
# Make a request to the main page to retrieve ServerJSDefine entries # Make a request to the main page to retrieve ServerJSDefine entries
try: try:
r = session.get(prefix_url("/"), allow_redirects=False) r = session.get(prefix_url("/"), allow_redirects=True)
except requests.RequestException as e: except requests.RequestException as e:
_exception.handle_requests_error(e) _exception.handle_requests_error(e)
_exception.handle_http_error(r.status_code) _exception.handle_http_error(r.status_code)
@@ -511,3 +548,37 @@ class Session:
return message_ids[0] return message_ids[0]
except (KeyError, IndexError, TypeError) as e: except (KeyError, IndexError, TypeError) as e:
raise _exception.ParseError("No message IDs could be found", data=j) from e raise _exception.ParseError("No message IDs could be found", data=j) from e
def _uri_share_data(self, data):
data["image_height"] = 960
data["image_width"] = 960
data["__user"] = self.user.id
j = self._post("/message_share_attachment/fromURI/", data)
return j["payload"]["share_data"]
def to_file(self, filename):
"""Save the session to a file.
Args:
filename: The file to save the session to
Example:
>>> session = fbchat.Session.from_cookies(cookies)
>>> session.to_file("session.json")
"""
with open(filename, "w") as f:
json.dump(self.get_cookies(), f)
@classmethod
def from_file(cls, filename):
"""Load a session from a file.
Args:
filename: The file to load the session from
Example:
>>> session = fbchat.Session.from_file("session.json")
"""
with open(filename, "r") as f:
cookies = json.load(f)
return cls.from_cookies(cookies)

View File

@@ -105,6 +105,7 @@ class ThreadABC(metaclass=abc.ABCMeta):
mentions: Iterable["_models.Mention"] = None, mentions: Iterable["_models.Mention"] = None,
files: Iterable[Tuple[str, str]] = None, files: Iterable[Tuple[str, str]] = None,
reply_to_id: str = None, reply_to_id: str = None,
uri: str = None
) -> str: ) -> str:
"""Send a message to the thread. """Send a message to the thread.
@@ -114,6 +115,7 @@ class ThreadABC(metaclass=abc.ABCMeta):
files: Optional tuples, each containing an uploaded file's ID and mimetype. files: Optional tuples, each containing an uploaded file's ID and mimetype.
See `ThreadABC.send_files` for an example. See `ThreadABC.send_files` for an example.
reply_to_id: Optional message to reply to reply_to_id: Optional message to reply to
uri: Uri to formulate a sharable attachment with
Example: Example:
Send a message with a mention to a thread. Send a message with a mention to a thread.
@@ -138,6 +140,9 @@ class ThreadABC(metaclass=abc.ABCMeta):
if files: if files:
data["has_attachment"] = True data["has_attachment"] = True
if uri:
data.update(self._generate_shareable_attachment(uri))
for i, (file_id, mimetype) in enumerate(files or ()): for i, (file_id, mimetype) in enumerate(files or ()):
data["{}s[{}]".format(_util.mimetype_to_key(mimetype), i)] = file_id data["{}s[{}]".format(_util.mimetype_to_key(mimetype), i)] = file_id
@@ -235,7 +240,53 @@ class ThreadABC(metaclass=abc.ABCMeta):
>>> thread.send_files(files) >>> thread.send_files(files)
""" """
return self.send_text(text=None, files=files) return self.send_text(text=None, files=files)
def send_uri(self, uri: str, **kwargs):
"""Send a uri preview to a thread.
Args:
uri: uri to preview
"""
if kwargs.get('text') is None:
kwargs['text'] = None
self.send_text(uri=uri, **kwargs)
def _generate_shareable_attachment(self, uri):
"""Send a uri preview to a thread.
Args:
uri: uri to preview
Returns:
:ref:`Message ID <intro_message_ids>` of the sent message
Raises:
FBchatException: If request failed
"""
url_data = self.session._uri_share_data({"uri": uri})
data = self._to_send_data()
data["action_type"] = "ma-type:user-generated-message"
data["shareable_attachment[share_type]"] = url_data["share_type"]
# Most uri params will come back as dict
if isinstance(url_data["share_params"], dict):
data["has_attachment"] = True
for key in url_data["share_params"]:
if isinstance(url_data["share_params"][key], dict):
for key2 in url_data["share_params"][key]:
data[
"shareable_attachment[share_params][{}][{}]".format(
key, key2
)
] = url_data["share_params"][key][key2]
else:
data[
"shareable_attachment[share_params][{}]".format(key)
] = url_data["share_params"][key]
# Some (such as facebook profile pages) will just be a list
else:
data["has_attachment"] = False
for index, val in enumerate(url_data["share_params"]):
data["shareable_attachment[share_params][{}]".format(index)] = val
return data
# xmd = {"quick_replies": []} # xmd = {"quick_replies": []}
# for quick_reply in quick_replies: # for quick_reply in quick_replies:
# # TODO: Move this to `_quick_reply.py` # # TODO: Move this to `_quick_reply.py`

View File

@@ -12,7 +12,7 @@ author = "Taehoon Kim"
author-email = "carpedm20@gmail.com" author-email = "carpedm20@gmail.com"
maintainer = "Mads Marquart" maintainer = "Mads Marquart"
maintainer-email = "madsmtm@gmail.com" maintainer-email = "madsmtm@gmail.com"
home-page = "https://github.com/carpedm20/fbchat/" home-page = "https://git.karaolidis.com/karaolidis/fbchat/"
requires = [ requires = [
"attrs>=19.1", "attrs>=19.1",
"requests~=2.19", "requests~=2.19",
@@ -47,8 +47,7 @@ keywords = "Facebook FB Messenger Library Chat Api Bot"
license = "BSD 3-Clause" license = "BSD 3-Clause"
[tool.flit.metadata.urls] [tool.flit.metadata.urls]
Documentation = "https://fbchat.readthedocs.io/" Repository = "https://git.karaolidis.com/karaolidis/fbchat/"
Repository = "https://github.com/carpedm20/fbchat/"
[tool.flit.metadata.requires-extra] [tool.flit.metadata.requires-extra]
test = [ test = [

View File

@@ -15,7 +15,6 @@ from fbchat._util import (
seconds_to_timedelta, seconds_to_timedelta,
millis_to_timedelta, millis_to_timedelta,
timedelta_to_seconds, timedelta_to_seconds,
now,
) )
@@ -246,7 +245,3 @@ def test_timedelta_to_seconds():
assert timedelta_to_seconds(datetime.timedelta(seconds=1)) == 1 assert timedelta_to_seconds(datetime.timedelta(seconds=1)) == 1
assert timedelta_to_seconds(datetime.timedelta(hours=1)) == 3600 assert timedelta_to_seconds(datetime.timedelta(hours=1)) == 3600
assert timedelta_to_seconds(datetime.timedelta(days=1)) == 86400 assert timedelta_to_seconds(datetime.timedelta(days=1)) == 86400
def test_now():
assert datetime_to_millis(now()) == datetime_to_millis(datetime.datetime.now())