Format strings using black

This commit is contained in:
Mads Marquart
2019-01-31 20:55:22 +01:00
parent d20fc3b9ce
commit e0710a2ec1
16 changed files with 823 additions and 823 deletions

View File

@@ -20,7 +20,7 @@
import os import os
import sys import sys
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath(".."))
import fbchat import fbchat
import tests import tests
@@ -37,27 +37,27 @@ from fbchat import __copyright__, __author__, __version__, __description__
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
'sphinx.ext.autodoc', "sphinx.ext.autodoc",
'sphinx.ext.intersphinx', "sphinx.ext.intersphinx",
'sphinx.ext.todo', "sphinx.ext.todo",
'sphinx.ext.viewcode', "sphinx.ext.viewcode",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix(es) of source filenames. # The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string: # You can specify multiple suffix as a list of string:
# #
# source_suffix = ['.rst', '.md'] # source_suffix = ['.rst', '.md']
source_suffix = '.rst' source_suffix = ".rst"
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = 'fbchat' project = "fbchat"
title = 'fbchat Documentation' title = "fbchat Documentation"
copyright = __copyright__ copyright = __copyright__
author = __author__ author = __author__
description = __description__ description = __description__
@@ -81,10 +81,10 @@ language = None
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path # This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = "sphinx"
# If true, `todo` and `todoList` produce output, else they produce nothing. # If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True todo_include_todos = True
@@ -96,7 +96,7 @@ todo_include_todos = True
# a list of builtin themes. # a list of builtin themes.
# #
html_theme = 'alabaster' html_theme = "alabaster"
# Theme options are theme-specific and customize the look and feel of a theme # 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 # further. For a list of options available for each theme, see the
@@ -107,13 +107,13 @@ html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here, # 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, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ["_static"]
# -- Options for HTMLHelp output ------------------------------------------ # -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = project + 'doc' htmlhelp_basename = project + "doc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
@@ -136,7 +136,7 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [(master_doc, project + '.tex', title, author, 'manual')] latex_documents = [(master_doc, project + ".tex", title, author, "manual")]
# -- Options for manual page output --------------------------------------- # -- Options for manual page output ---------------------------------------
@@ -152,27 +152,27 @@ man_pages = [(master_doc, project, title, [author], 1)]
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
(master_doc, project, title, author, project, description, 'Miscellaneous') (master_doc, project, title, author, project, description, "Miscellaneous")
] ]
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/3/': None} intersphinx_mapping = {"https://docs.python.org/3/": None}
add_function_parentheses = False add_function_parentheses = False
html_theme_options = { html_theme_options = {
'show_powered_by': False, "show_powered_by": False,
'github_user': 'carpedm20', "github_user": "carpedm20",
'github_repo': project, "github_repo": project,
'github_banner': True, "github_banner": True,
'show_related': False, "show_related": False,
} }
html_sidebars = {'**': ['sidebar.html', 'searchbox.html']} html_sidebars = {"**": ["sidebar.html", "searchbox.html"]}
html_show_sphinx = False html_show_sphinx = False
html_show_sourcelink = False html_show_sourcelink = False
autoclass_content = 'init' autoclass_content = "init"
html_short_title = description html_short_title = description

View File

@@ -3,10 +3,10 @@
from fbchat import Client from fbchat import Client
from fbchat.models import * from fbchat.models import *
client = Client('<email>', '<password>') client = Client("<email>", "<password>")
print('Own id: {}'.format(client.uid)) print("Own id: {}".format(client.uid))
client.send(Message(text='Hi me!'), thread_id=client.uid, thread_type=ThreadType.USER) client.send(Message(text="Hi me!"), thread_id=client.uid, thread_type=ThreadType.USER)
client.logout() client.logout()

View File

@@ -3,7 +3,7 @@
from fbchat import Client from fbchat import Client
from fbchat.models import * from fbchat.models import *
client = Client('<email>', '<password>') client = Client("<email>", "<password>")
# Fetches a list of all users you're currently chatting with, as `User` objects # Fetches a list of all users you're currently chatting with, as `User` objects
users = client.fetchAllUsers() users = client.fetchAllUsers()
@@ -13,9 +13,9 @@ print("users' names: {}".format([user.name for user in users]))
# If we have a user id, we can use `fetchUserInfo` to fetch a `User` object # If we have a user id, we can use `fetchUserInfo` to fetch a `User` object
user = client.fetchUserInfo('<user id>')['<user id>'] user = client.fetchUserInfo("<user id>")["<user id>"]
# We can also query both mutiple users together, which returns list of `User` objects # We can also query both mutiple users together, which returns list of `User` objects
users = client.fetchUserInfo('<1st user id>', '<2nd user id>', '<3rd user id>') users = client.fetchUserInfo("<1st user id>", "<2nd user id>", "<3rd user id>")
print("user's name: {}".format(user.name)) print("user's name: {}".format(user.name))
print("users' names: {}".format([users[k].name for k in users])) print("users' names: {}".format([users[k].name for k in users]))
@@ -23,9 +23,9 @@ print("users' names: {}".format([users[k].name for k in users]))
# `searchForUsers` searches for the user and gives us a list of the results, # `searchForUsers` searches for the user and gives us a list of the results,
# and then we just take the first one, aka. the most likely one: # and then we just take the first one, aka. the most likely one:
user = client.searchForUsers('<name of user>')[0] user = client.searchForUsers("<name of user>")[0]
print('user ID: {}'.format(user.uid)) print("user ID: {}".format(user.uid))
print("user's name: {}".format(user.name)) print("user's name: {}".format(user.name))
print("user's photo: {}".format(user.photo)) print("user's photo: {}".format(user.photo))
print("Is user client's friend: {}".format(user.is_friend)) print("Is user client's friend: {}".format(user.is_friend))
@@ -40,7 +40,7 @@ print("Threads: {}".format(threads))
# Gets the last 10 messages sent to the thread # Gets the last 10 messages sent to the thread
messages = client.fetchThreadMessages(thread_id='<thread id>', limit=10) messages = client.fetchThreadMessages(thread_id="<thread id>", limit=10)
# Since the message come in reversed order, reverse them # Since the message come in reversed order, reverse them
messages.reverse() messages.reverse()
@@ -50,13 +50,13 @@ for message in messages:
# If we have a thread id, we can use `fetchThreadInfo` to fetch a `Thread` object # If we have a thread id, we can use `fetchThreadInfo` to fetch a `Thread` object
thread = client.fetchThreadInfo('<thread id>')['<thread id>'] thread = client.fetchThreadInfo("<thread id>")["<thread id>"]
print("thread's name: {}".format(thread.name)) print("thread's name: {}".format(thread.name))
print("thread's type: {}".format(thread.type)) print("thread's type: {}".format(thread.type))
# `searchForThreads` searches works like `searchForUsers`, but gives us a list of threads instead # `searchForThreads` searches works like `searchForUsers`, but gives us a list of threads instead
thread = client.searchForThreads('<name of thread>')[0] thread = client.searchForThreads("<name of thread>")[0]
print("thread's name: {}".format(thread.name)) print("thread's name: {}".format(thread.name))
print("thread's type: {}".format(thread.type)) print("thread's type: {}".format(thread.type))

View File

@@ -5,11 +5,11 @@ from fbchat.models import *
client = Client("<email>", "<password>") client = Client("<email>", "<password>")
thread_id = '1234567890' thread_id = "1234567890"
thread_type = ThreadType.GROUP thread_type = ThreadType.GROUP
# Will send a message to the thread # Will send a message to the thread
client.send(Message(text='<message>'), thread_id=thread_id, thread_type=thread_type) client.send(Message(text="<message>"), thread_id=thread_id, thread_type=thread_type)
# Will send the default `like` emoji # Will send the default `like` emoji
client.send( client.send(
@@ -18,14 +18,14 @@ client.send(
# Will send the emoji `👍` # Will send the emoji `👍`
client.send( client.send(
Message(text='👍', emoji_size=EmojiSize.LARGE), Message(text="👍", emoji_size=EmojiSize.LARGE),
thread_id=thread_id, thread_id=thread_id,
thread_type=thread_type, thread_type=thread_type,
) )
# Will send the sticker with ID `767334476626295` # Will send the sticker with ID `767334476626295`
client.send( client.send(
Message(sticker=Sticker('767334476626295')), Message(sticker=Sticker("767334476626295")),
thread_id=thread_id, thread_id=thread_id,
thread_type=thread_type, thread_type=thread_type,
) )
@@ -33,7 +33,7 @@ client.send(
# Will send a message with a mention # Will send a message with a mention
client.send( client.send(
Message( Message(
text='This is a @mention', mentions=[Mention(thread_id, offset=10, length=8)] text="This is a @mention", mentions=[Mention(thread_id, offset=10, length=8)]
), ),
thread_id=thread_id, thread_id=thread_id,
thread_type=thread_type, thread_type=thread_type,
@@ -41,16 +41,16 @@ client.send(
# Will send the image located at `<image path>` # Will send the image located at `<image path>`
client.sendLocalImage( client.sendLocalImage(
'<image path>', "<image path>",
message=Message(text='This is a local image'), message=Message(text="This is a local image"),
thread_id=thread_id, thread_id=thread_id,
thread_type=thread_type, thread_type=thread_type,
) )
# Will download the image at the url `<image url>`, and then send it # Will download the image at the url `<image url>`, and then send it
client.sendRemoteImage( client.sendRemoteImage(
'<image url>', "<image url>",
message=Message(text='This is a remote image'), message=Message(text="This is a remote image"),
thread_id=thread_id, thread_id=thread_id,
thread_type=thread_type, thread_type=thread_type,
) )
@@ -59,24 +59,24 @@ client.sendRemoteImage(
# Only do these actions if the thread is a group # Only do these actions if the thread is a group
if thread_type == ThreadType.GROUP: if thread_type == ThreadType.GROUP:
# Will remove the user with ID `<user id>` from the thread # Will remove the user with ID `<user id>` from the thread
client.removeUserFromGroup('<user id>', thread_id=thread_id) client.removeUserFromGroup("<user id>", thread_id=thread_id)
# Will add the user with ID `<user id>` to the thread # Will add the user with ID `<user id>` to the thread
client.addUsersToGroup('<user id>', thread_id=thread_id) client.addUsersToGroup("<user id>", thread_id=thread_id)
# Will add the users with IDs `<1st user id>`, `<2nd user id>` and `<3th user id>` to the thread # Will add the users with IDs `<1st user id>`, `<2nd user id>` and `<3th user id>` to the thread
client.addUsersToGroup( client.addUsersToGroup(
['<1st user id>', '<2nd user id>', '<3rd user id>'], thread_id=thread_id ["<1st user id>", "<2nd user id>", "<3rd user id>"], thread_id=thread_id
) )
# Will change the nickname of the user `<user_id>` to `<new nickname>` # Will change the nickname of the user `<user_id>` to `<new nickname>`
client.changeNickname( client.changeNickname(
'<new nickname>', '<user id>', thread_id=thread_id, thread_type=thread_type "<new nickname>", "<user id>", thread_id=thread_id, thread_type=thread_type
) )
# Will change the title of the thread to `<title>` # Will change the title of the thread to `<title>`
client.changeThreadTitle('<title>', thread_id=thread_id, thread_type=thread_type) client.changeThreadTitle("<title>", thread_id=thread_id, thread_type=thread_type)
# Will set the typing status of the thread to `TYPING` # Will set the typing status of the thread to `TYPING`
client.setTypingStatus( client.setTypingStatus(
@@ -87,7 +87,7 @@ client.setTypingStatus(
client.changeThreadColor(ThreadColor.MESSENGER_BLUE, thread_id=thread_id) client.changeThreadColor(ThreadColor.MESSENGER_BLUE, thread_id=thread_id)
# Will change the thread emoji to `👍` # Will change the thread emoji to `👍`
client.changeThreadEmoji('👍', thread_id=thread_id) client.changeThreadEmoji("👍", thread_id=thread_id)
# Will react to a message with a 😍 emoji # Will react to a message with a 😍 emoji
client.reactToMessage('<message id>', MessageReaction.LOVE) client.reactToMessage("<message id>", MessageReaction.LOVE)

View File

@@ -4,17 +4,17 @@ from fbchat import log, Client
from fbchat.models import * from fbchat.models import *
# Change this to your group id # Change this to your group id
old_thread_id = '1234567890' old_thread_id = "1234567890"
# Change these to match your liking # Change these to match your liking
old_color = ThreadColor.MESSENGER_BLUE old_color = ThreadColor.MESSENGER_BLUE
old_emoji = '👍' old_emoji = "👍"
old_title = 'Old group chat name' old_title = "Old group chat name"
old_nicknames = { old_nicknames = {
'12345678901': "User nr. 1's nickname", "12345678901": "User nr. 1's nickname",
'12345678902': "User nr. 2's nickname", "12345678902": "User nr. 2's nickname",
'12345678903': "User nr. 3's nickname", "12345678903": "User nr. 3's nickname",
'12345678904': "User nr. 4's nickname", "12345678904": "User nr. 4's nickname",
} }

View File

@@ -7,8 +7,8 @@ from fbchat.models import *
class RemoveBot(Client): class RemoveBot(Client):
def onMessage(self, author_id, message_object, thread_id, thread_type, **kwargs): def onMessage(self, author_id, message_object, thread_id, thread_type, **kwargs):
# We can only kick people from group chats, so no need to try if it's a user chat # We can only kick people from group chats, so no need to try if it's a user chat
if message_object.text == 'Remove me!' and thread_type == ThreadType.GROUP: if message_object.text == "Remove me!" and thread_type == ThreadType.GROUP:
log.info('{} will be removed from {}'.format(author_id, thread_id)) log.info("{} will be removed from {}".format(author_id, thread_id))
self.removeUserFromGroup(author_id, thread_id=thread_id) self.removeUserFromGroup(author_id, thread_id=thread_id)
else: else:
# Sends the data to the inherited onMessage, so that we can still see when a message is recieved # Sends the data to the inherited onMessage, so that we can still see when a message is recieved

View File

@@ -9,14 +9,14 @@ from __future__ import unicode_literals
from .client import * from .client import *
__title__ = 'fbchat' __title__ = "fbchat"
__version__ = '1.6.1' __version__ = "1.6.1"
__description__ = 'Facebook Chat (Messenger) for Python' __description__ = "Facebook Chat (Messenger) for Python"
__copyright__ = 'Copyright 2015 - 2019 by Taehoon Kim' __copyright__ = "Copyright 2015 - 2019 by Taehoon Kim"
__license__ = 'BSD 3-Clause' __license__ = "BSD 3-Clause"
__author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart' __author__ = "Taehoon Kim; Moreels Pieter-Jan; Mads Marquart"
__email__ = 'carpedm20@gmail.com' __email__ = "carpedm20@gmail.com"
__all__ = ['Client'] __all__ = ["Client"]

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ from .utils import *
# Shameless copy from https://stackoverflow.com/a/8730674 # Shameless copy from https://stackoverflow.com/a/8730674
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) WHITESPACE = re.compile(r"[ \t\n\r]*", FLAGS)
class ConcatJSONDecoder(json.JSONDecoder): class ConcatJSONDecoder(json.JSONDecoder):
@@ -33,220 +33,220 @@ def graphql_color_to_enum(color):
if not color: if not color:
return ThreadColor.MESSENGER_BLUE return ThreadColor.MESSENGER_BLUE
color = color[2:] # Strip the alpha value color = color[2:] # Strip the alpha value
color_value = '#{}'.format(color.lower()) color_value = "#{}".format(color.lower())
return enum_extend_if_invalid(ThreadColor, color_value) return enum_extend_if_invalid(ThreadColor, color_value)
def get_customization_info(thread): def get_customization_info(thread):
if thread is None or thread.get('customization_info') is None: if thread is None or thread.get("customization_info") is None:
return {} return {}
info = thread['customization_info'] info = thread["customization_info"]
rtn = { rtn = {
'emoji': info.get('emoji'), "emoji": info.get("emoji"),
'color': graphql_color_to_enum(info.get('outgoing_bubble_color')), "color": graphql_color_to_enum(info.get("outgoing_bubble_color")),
} }
if ( if (
thread.get('thread_type') == 'GROUP' thread.get("thread_type") == "GROUP"
or thread.get('is_group_thread') or thread.get("is_group_thread")
or thread.get('thread_key', {}).get('thread_fbid') or thread.get("thread_key", {}).get("thread_fbid")
): ):
rtn['nicknames'] = {} rtn["nicknames"] = {}
for k in info.get('participant_customizations', []): for k in info.get("participant_customizations", []):
rtn['nicknames'][k['participant_id']] = k.get('nickname') rtn["nicknames"][k["participant_id"]] = k.get("nickname")
elif info.get('participant_customizations'): elif info.get("participant_customizations"):
uid = thread.get('thread_key', {}).get('other_user_id') or thread.get('id') uid = thread.get("thread_key", {}).get("other_user_id") or thread.get("id")
pc = info['participant_customizations'] pc = info["participant_customizations"]
if len(pc) > 0: if len(pc) > 0:
if pc[0].get('participant_id') == uid: if pc[0].get("participant_id") == uid:
rtn['nickname'] = pc[0].get('nickname') rtn["nickname"] = pc[0].get("nickname")
else: else:
rtn['own_nickname'] = pc[0].get('nickname') rtn["own_nickname"] = pc[0].get("nickname")
if len(pc) > 1: if len(pc) > 1:
if pc[1].get('participant_id') == uid: if pc[1].get("participant_id") == uid:
rtn['nickname'] = pc[1].get('nickname') rtn["nickname"] = pc[1].get("nickname")
else: else:
rtn['own_nickname'] = pc[1].get('nickname') rtn["own_nickname"] = pc[1].get("nickname")
return rtn return rtn
def graphql_to_sticker(s): def graphql_to_sticker(s):
if not s: if not s:
return None return None
sticker = Sticker(uid=s['id']) sticker = Sticker(uid=s["id"])
if s.get('pack'): if s.get("pack"):
sticker.pack = s['pack'].get('id') sticker.pack = s["pack"].get("id")
if s.get('sprite_image'): if s.get("sprite_image"):
sticker.is_animated = True sticker.is_animated = True
sticker.medium_sprite_image = s['sprite_image'].get('uri') sticker.medium_sprite_image = s["sprite_image"].get("uri")
sticker.large_sprite_image = s['sprite_image_2x'].get('uri') sticker.large_sprite_image = s["sprite_image_2x"].get("uri")
sticker.frames_per_row = s.get('frames_per_row') sticker.frames_per_row = s.get("frames_per_row")
sticker.frames_per_col = s.get('frames_per_column') sticker.frames_per_col = s.get("frames_per_column")
sticker.frame_rate = s.get('frame_rate') sticker.frame_rate = s.get("frame_rate")
sticker.url = s.get('url') sticker.url = s.get("url")
sticker.width = s.get('width') sticker.width = s.get("width")
sticker.height = s.get('height') sticker.height = s.get("height")
if s.get('label'): if s.get("label"):
sticker.label = s['label'] sticker.label = s["label"]
return sticker return sticker
def graphql_to_attachment(a): def graphql_to_attachment(a):
_type = a['__typename'] _type = a["__typename"]
if _type in ['MessageImage', 'MessageAnimatedImage']: if _type in ["MessageImage", "MessageAnimatedImage"]:
return ImageAttachment( return ImageAttachment(
original_extension=a.get('original_extension') original_extension=a.get("original_extension")
or (a['filename'].split('-')[0] if a.get('filename') else None), or (a["filename"].split("-")[0] if a.get("filename") else None),
width=a.get('original_dimensions', {}).get('width'), width=a.get("original_dimensions", {}).get("width"),
height=a.get('original_dimensions', {}).get('height'), height=a.get("original_dimensions", {}).get("height"),
is_animated=_type == 'MessageAnimatedImage', is_animated=_type == "MessageAnimatedImage",
thumbnail_url=a.get('thumbnail', {}).get('uri'), thumbnail_url=a.get("thumbnail", {}).get("uri"),
preview=a.get('preview') or a.get('preview_image'), preview=a.get("preview") or a.get("preview_image"),
large_preview=a.get('large_preview'), large_preview=a.get("large_preview"),
animated_preview=a.get('animated_image'), animated_preview=a.get("animated_image"),
uid=a.get('legacy_attachment_id'), uid=a.get("legacy_attachment_id"),
) )
elif _type == 'MessageVideo': elif _type == "MessageVideo":
return VideoAttachment( return VideoAttachment(
width=a.get('original_dimensions', {}).get('width'), width=a.get("original_dimensions", {}).get("width"),
height=a.get('original_dimensions', {}).get('height'), height=a.get("original_dimensions", {}).get("height"),
duration=a.get('playable_duration_in_ms'), duration=a.get("playable_duration_in_ms"),
preview_url=a.get('playable_url'), preview_url=a.get("playable_url"),
small_image=a.get('chat_image'), small_image=a.get("chat_image"),
medium_image=a.get('inbox_image'), medium_image=a.get("inbox_image"),
large_image=a.get('large_image'), large_image=a.get("large_image"),
uid=a.get('legacy_attachment_id'), uid=a.get("legacy_attachment_id"),
) )
elif _type == 'MessageAudio': elif _type == "MessageAudio":
return AudioAttachment( return AudioAttachment(
filename=a.get('filename'), filename=a.get("filename"),
url=a.get('playable_url'), url=a.get("playable_url"),
duration=a.get('playable_duration_in_ms'), duration=a.get("playable_duration_in_ms"),
audio_type=a.get('audio_type'), audio_type=a.get("audio_type"),
) )
elif _type == 'MessageFile': elif _type == "MessageFile":
return FileAttachment( return FileAttachment(
url=a.get('url'), url=a.get("url"),
name=a.get('filename'), name=a.get("filename"),
is_malicious=a.get('is_malicious'), is_malicious=a.get("is_malicious"),
uid=a.get('message_file_fbid'), uid=a.get("message_file_fbid"),
) )
else: else:
return Attachment(uid=a.get('legacy_attachment_id')) return Attachment(uid=a.get("legacy_attachment_id"))
def graphql_to_extensible_attachment(a): def graphql_to_extensible_attachment(a):
story = a.get('story_attachment') story = a.get("story_attachment")
if story: if story:
target = story.get('target') target = story.get("target")
if target: if target:
_type = target['__typename'] _type = target["__typename"]
if _type == 'MessageLocation': if _type == "MessageLocation":
latitude, longitude = get_url_parameter( latitude, longitude = get_url_parameter(
get_url_parameter(story['url'], 'u'), 'where1' get_url_parameter(story["url"], "u"), "where1"
).split(", ") ).split(", ")
rtn = LocationAttachment( rtn = LocationAttachment(
uid=int(story['deduplication_key']), uid=int(story["deduplication_key"]),
latitude=float(latitude), latitude=float(latitude),
longitude=float(longitude), longitude=float(longitude),
) )
if story['media']: if story["media"]:
rtn.image_url = story['media']['image']['uri'] rtn.image_url = story["media"]["image"]["uri"]
rtn.image_width = story['media']['image']['width'] rtn.image_width = story["media"]["image"]["width"]
rtn.image_height = story['media']['image']['height'] rtn.image_height = story["media"]["image"]["height"]
rtn.url = story['url'] rtn.url = story["url"]
return rtn return rtn
elif _type == 'MessageLiveLocation': elif _type == "MessageLiveLocation":
rtn = LiveLocationAttachment( rtn = LiveLocationAttachment(
uid=int(story['target']['live_location_id']), uid=int(story["target"]["live_location_id"]),
latitude=story['target']['coordinate']['latitude'] latitude=story["target"]["coordinate"]["latitude"]
if story['target'].get('coordinate') if story["target"].get("coordinate")
else None, else None,
longitude=story['target']['coordinate']['longitude'] longitude=story["target"]["coordinate"]["longitude"]
if story['target'].get('coordinate') if story["target"].get("coordinate")
else None, else None,
name=story['title_with_entities']['text'], name=story["title_with_entities"]["text"],
expiration_time=story['target']['expiration_time'] expiration_time=story["target"]["expiration_time"]
if story['target'].get('expiration_time') if story["target"].get("expiration_time")
else None, else None,
is_expired=story['target']['is_expired'], is_expired=story["target"]["is_expired"],
) )
if story['media']: if story["media"]:
rtn.image_url = story['media']['image']['uri'] rtn.image_url = story["media"]["image"]["uri"]
rtn.image_width = story['media']['image']['width'] rtn.image_width = story["media"]["image"]["width"]
rtn.image_height = story['media']['image']['height'] rtn.image_height = story["media"]["image"]["height"]
rtn.url = story['url'] rtn.url = story["url"]
return rtn return rtn
elif _type in ['ExternalUrl', 'Story']: elif _type in ["ExternalUrl", "Story"]:
return ShareAttachment( return ShareAttachment(
uid=a.get('legacy_attachment_id'), uid=a.get("legacy_attachment_id"),
author=story['target']['actors'][0]['id'] author=story["target"]["actors"][0]["id"]
if story['target'].get('actors') if story["target"].get("actors")
else None, else None,
url=story['url'], url=story["url"],
original_url=get_url_parameter(story['url'], 'u') original_url=get_url_parameter(story["url"], "u")
if "/l.php?u=" in story['url'] if "/l.php?u=" in story["url"]
else story['url'], else story["url"],
title=story['title_with_entities'].get('text'), title=story["title_with_entities"].get("text"),
description=story['description'].get('text') description=story["description"].get("text")
if story.get('description') if story.get("description")
else None, else None,
source=story['source']['text'], source=story["source"]["text"],
image_url=story['media']['image']['uri'] image_url=story["media"]["image"]["uri"]
if story.get('media') if story.get("media")
else None, else None,
original_image_url=( original_image_url=(
get_url_parameter(story['media']['image']['uri'], 'url') get_url_parameter(story["media"]["image"]["uri"], "url")
if "/safe_image.php" in story['media']['image']['uri'] if "/safe_image.php" in story["media"]["image"]["uri"]
else story['media']['image']['uri'] else story["media"]["image"]["uri"]
) )
if story.get('media') if story.get("media")
else None, else None,
image_width=story['media']['image']['width'] image_width=story["media"]["image"]["width"]
if story.get('media') if story.get("media")
else None, else None,
image_height=story['media']['image']['height'] image_height=story["media"]["image"]["height"]
if story.get('media') if story.get("media")
else None, else None,
attachments=[ attachments=[
graphql_to_subattachment(attachment) graphql_to_subattachment(attachment)
for attachment in story.get('subattachments') for attachment in story.get("subattachments")
], ],
) )
else: else:
return UnsentMessage(uid=a.get('legacy_attachment_id')) return UnsentMessage(uid=a.get("legacy_attachment_id"))
def graphql_to_subattachment(a): def graphql_to_subattachment(a):
_type = a['target']['__typename'] _type = a["target"]["__typename"]
if _type == 'Video': if _type == "Video":
return VideoAttachment( return VideoAttachment(
duration=a['media'].get('playable_duration_in_ms'), duration=a["media"].get("playable_duration_in_ms"),
preview_url=a['media'].get('playable_url'), preview_url=a["media"].get("playable_url"),
medium_image=a['media'].get('image'), medium_image=a["media"].get("image"),
uid=a['target'].get('video_id'), uid=a["target"].get("video_id"),
) )
def graphql_to_live_location(a): def graphql_to_live_location(a):
return LiveLocationAttachment( return LiveLocationAttachment(
uid=a['id'], uid=a["id"],
latitude=a['coordinate']['latitude'] / (10 ** 8) latitude=a["coordinate"]["latitude"] / (10 ** 8)
if not a.get('stopReason') if not a.get("stopReason")
else None, else None,
longitude=a['coordinate']['longitude'] / (10 ** 8) longitude=a["coordinate"]["longitude"] / (10 ** 8)
if not a.get('stopReason') if not a.get("stopReason")
else None, else None,
name=a.get('locationTitle'), name=a.get("locationTitle"),
expiration_time=a['expirationTime'], expiration_time=a["expirationTime"],
is_expired=bool(a.get('stopReason')), is_expired=bool(a.get("stopReason")),
) )
def graphql_to_poll(a): def graphql_to_poll(a):
rtn = Poll( rtn = Poll(
title=a.get('title') if a.get('title') else a.get('text'), title=a.get("title") if a.get("title") else a.get("text"),
options=[graphql_to_poll_option(m) for m in a.get('options')], options=[graphql_to_poll_option(m) for m in a.get("options")],
) )
rtn.uid = int(a["id"]) rtn.uid = int(a["id"])
rtn.options_count = a.get("total_count") rtn.options_count = a.get("total_count")
@@ -254,90 +254,90 @@ def graphql_to_poll(a):
def graphql_to_poll_option(a): def graphql_to_poll_option(a):
if a.get('viewer_has_voted') is None: if a.get("viewer_has_voted") is None:
vote = None vote = None
elif isinstance(a['viewer_has_voted'], bool): elif isinstance(a["viewer_has_voted"], bool):
vote = a['viewer_has_voted'] vote = a["viewer_has_voted"]
else: else:
vote = a['viewer_has_voted'] == 'true' vote = a["viewer_has_voted"] == "true"
rtn = PollOption(text=a.get('text'), vote=vote) rtn = PollOption(text=a.get("text"), vote=vote)
rtn.uid = int(a["id"]) rtn.uid = int(a["id"])
rtn.voters = ( rtn.voters = (
[m.get('node').get('id') for m in a.get('voters').get('edges')] [m.get("node").get("id") for m in a.get("voters").get("edges")]
if isinstance(a.get('voters'), dict) if isinstance(a.get("voters"), dict)
else a.get('voters') else a.get("voters")
) )
rtn.votes_count = ( rtn.votes_count = (
a.get('voters').get('count') a.get("voters").get("count")
if isinstance(a.get('voters'), dict) if isinstance(a.get("voters"), dict)
else a.get('total_count') else a.get("total_count")
) )
return rtn return rtn
def graphql_to_plan(a): def graphql_to_plan(a):
if a.get('event_members'): if a.get("event_members"):
rtn = Plan( rtn = Plan(
time=a.get('event_time'), time=a.get("event_time"),
title=a.get('title'), title=a.get("title"),
location=a.get('location_name'), location=a.get("location_name"),
) )
if a.get('location_id') != 0: if a.get("location_id") != 0:
rtn.location_id = str(a.get('location_id')) rtn.location_id = str(a.get("location_id"))
rtn.uid = a.get('oid') rtn.uid = a.get("oid")
rtn.author_id = a.get('creator_id') rtn.author_id = a.get("creator_id")
guests = a.get("event_members") guests = a.get("event_members")
rtn.going = [uid for uid in guests if guests[uid] == "GOING"] rtn.going = [uid for uid in guests if guests[uid] == "GOING"]
rtn.declined = [uid for uid in guests if guests[uid] == "DECLINED"] rtn.declined = [uid for uid in guests if guests[uid] == "DECLINED"]
rtn.invited = [uid for uid in guests if guests[uid] == "INVITED"] rtn.invited = [uid for uid in guests if guests[uid] == "INVITED"]
return rtn return rtn
elif a.get('id') is None: elif a.get("id") is None:
rtn = Plan( rtn = Plan(
time=a.get('event_time'), time=a.get("event_time"),
title=a.get('event_title'), title=a.get("event_title"),
location=a.get('event_location_name'), location=a.get("event_location_name"),
location_id=a.get('event_location_id'), location_id=a.get("event_location_id"),
) )
rtn.uid = a.get('event_id') rtn.uid = a.get("event_id")
rtn.author_id = a.get('event_creator_id') rtn.author_id = a.get("event_creator_id")
guests = json.loads(a.get('guest_state_list')) guests = json.loads(a.get("guest_state_list"))
else: else:
rtn = Plan( rtn = Plan(
time=a.get('time'), time=a.get("time"),
title=a.get('event_title'), title=a.get("event_title"),
location=a.get('location_name'), location=a.get("location_name"),
) )
rtn.uid = a.get('id') rtn.uid = a.get("id")
rtn.author_id = a.get('lightweight_event_creator').get('id') rtn.author_id = a.get("lightweight_event_creator").get("id")
guests = a.get('event_reminder_members').get('edges') guests = a.get("event_reminder_members").get("edges")
rtn.going = [ rtn.going = [
m.get('node').get('id') for m in guests if m.get('guest_list_state') == "GOING" m.get("node").get("id") for m in guests if m.get("guest_list_state") == "GOING"
] ]
rtn.declined = [ rtn.declined = [
m.get('node').get('id') m.get("node").get("id")
for m in guests for m in guests
if m.get('guest_list_state') == "DECLINED" if m.get("guest_list_state") == "DECLINED"
] ]
rtn.invited = [ rtn.invited = [
m.get('node').get('id') m.get("node").get("id")
for m in guests for m in guests
if m.get('guest_list_state') == "INVITED" if m.get("guest_list_state") == "INVITED"
] ]
return rtn return rtn
def graphql_to_quick_reply(q, is_response=False): def graphql_to_quick_reply(q, is_response=False):
data = dict() data = dict()
_type = q.get('content_type').lower() _type = q.get("content_type").lower()
if q.get('payload'): if q.get("payload"):
data["payload"] = q["payload"] data["payload"] = q["payload"]
if q.get('data'): if q.get("data"):
data["data"] = q["data"] data["data"] = q["data"]
if q.get('image_url') and _type is not QuickReplyLocation._type: if q.get("image_url") and _type is not QuickReplyLocation._type:
data["image_url"] = q["image_url"] data["image_url"] = q["image_url"]
data["is_response"] = is_response data["is_response"] = is_response
if _type == QuickReplyText._type: if _type == QuickReplyText._type:
if q.get('title') is not None: if q.get("title") is not None:
data["title"] = q["title"] data["title"] = q["title"]
rtn = QuickReplyText(**data) rtn = QuickReplyText(**data)
elif _type == QuickReplyLocation._type: elif _type == QuickReplyLocation._type:
@@ -350,48 +350,48 @@ def graphql_to_quick_reply(q, is_response=False):
def graphql_to_message(message): def graphql_to_message(message):
if message.get('message_sender') is None: if message.get("message_sender") is None:
message['message_sender'] = {} message["message_sender"] = {}
if message.get('message') is None: if message.get("message") is None:
message['message'] = {} message["message"] = {}
rtn = Message( rtn = Message(
text=message.get('message').get('text'), text=message.get("message").get("text"),
mentions=[ mentions=[
Mention( Mention(
m.get('entity', {}).get('id'), m.get("entity", {}).get("id"),
offset=m.get('offset'), offset=m.get("offset"),
length=m.get('length'), length=m.get("length"),
) )
for m in message.get('message').get('ranges', []) for m in message.get("message").get("ranges", [])
], ],
emoji_size=get_emojisize_from_tags(message.get('tags_list')), emoji_size=get_emojisize_from_tags(message.get("tags_list")),
sticker=graphql_to_sticker(message.get('sticker')), sticker=graphql_to_sticker(message.get("sticker")),
) )
rtn.uid = str(message.get('message_id')) rtn.uid = str(message.get("message_id"))
rtn.author = str(message.get('message_sender').get('id')) rtn.author = str(message.get("message_sender").get("id"))
rtn.timestamp = message.get('timestamp_precise') rtn.timestamp = message.get("timestamp_precise")
rtn.unsent = False rtn.unsent = False
if message.get('unread') is not None: if message.get("unread") is not None:
rtn.is_read = not message['unread'] rtn.is_read = not message["unread"]
rtn.reactions = { rtn.reactions = {
str(r['user']['id']): enum_extend_if_invalid(MessageReaction, r['reaction']) str(r["user"]["id"]): enum_extend_if_invalid(MessageReaction, r["reaction"])
for r in message.get('message_reactions') for r in message.get("message_reactions")
} }
if message.get('blob_attachments') is not None: if message.get("blob_attachments") is not None:
rtn.attachments = [ rtn.attachments = [
graphql_to_attachment(attachment) graphql_to_attachment(attachment)
for attachment in message['blob_attachments'] for attachment in message["blob_attachments"]
] ]
if message.get('platform_xmd_encoded'): if message.get("platform_xmd_encoded"):
quick_replies = json.loads(message['platform_xmd_encoded']).get('quick_replies') quick_replies = json.loads(message["platform_xmd_encoded"]).get("quick_replies")
if isinstance(quick_replies, list): if isinstance(quick_replies, list):
rtn.quick_replies = [graphql_to_quick_reply(q) for q in quick_replies] rtn.quick_replies = [graphql_to_quick_reply(q) for q in quick_replies]
elif isinstance(quick_replies, dict): elif isinstance(quick_replies, dict):
rtn.quick_replies = [ rtn.quick_replies = [
graphql_to_quick_reply(quick_replies, is_response=True) graphql_to_quick_reply(quick_replies, is_response=True)
] ]
if message.get('extensible_attachment') is not None: if message.get("extensible_attachment") is not None:
attachment = graphql_to_extensible_attachment(message['extensible_attachment']) attachment = graphql_to_extensible_attachment(message["extensible_attachment"])
if isinstance(attachment, UnsentMessage): if isinstance(attachment, UnsentMessage):
rtn.unsent = True rtn.unsent = True
elif attachment: elif attachment:
@@ -400,157 +400,157 @@ def graphql_to_message(message):
def graphql_to_user(user): def graphql_to_user(user):
if user.get('profile_picture') is None: if user.get("profile_picture") is None:
user['profile_picture'] = {} user["profile_picture"] = {}
c_info = get_customization_info(user) c_info = get_customization_info(user)
plan = None plan = None
if user.get('event_reminders'): if user.get("event_reminders"):
plan = ( plan = (
graphql_to_plan(user['event_reminders']['nodes'][0]) graphql_to_plan(user["event_reminders"]["nodes"][0])
if user['event_reminders'].get('nodes') if user["event_reminders"].get("nodes")
else None else None
) )
return User( return User(
user['id'], user["id"],
url=user.get('url'), url=user.get("url"),
first_name=user.get('first_name'), first_name=user.get("first_name"),
last_name=user.get('last_name'), last_name=user.get("last_name"),
is_friend=user.get('is_viewer_friend'), is_friend=user.get("is_viewer_friend"),
gender=GENDERS.get(user.get('gender')), gender=GENDERS.get(user.get("gender")),
affinity=user.get('affinity'), affinity=user.get("affinity"),
nickname=c_info.get('nickname'), nickname=c_info.get("nickname"),
color=c_info.get('color'), color=c_info.get("color"),
emoji=c_info.get('emoji'), emoji=c_info.get("emoji"),
own_nickname=c_info.get('own_nickname'), own_nickname=c_info.get("own_nickname"),
photo=user['profile_picture'].get('uri'), photo=user["profile_picture"].get("uri"),
name=user.get('name'), name=user.get("name"),
message_count=user.get('messages_count'), message_count=user.get("messages_count"),
plan=plan, plan=plan,
) )
def graphql_to_thread(thread): def graphql_to_thread(thread):
if thread['thread_type'] == 'GROUP': if thread["thread_type"] == "GROUP":
return graphql_to_group(thread) return graphql_to_group(thread)
elif thread['thread_type'] == 'ONE_TO_ONE': elif thread["thread_type"] == "ONE_TO_ONE":
if thread.get('big_image_src') is None: if thread.get("big_image_src") is None:
thread['big_image_src'] = {} thread["big_image_src"] = {}
c_info = get_customization_info(thread) c_info = get_customization_info(thread)
participants = [ participants = [
node['messaging_actor'] for node in thread['all_participants']['nodes'] node["messaging_actor"] for node in thread["all_participants"]["nodes"]
] ]
user = next( user = next(
p for p in participants if p['id'] == thread['thread_key']['other_user_id'] p for p in participants if p["id"] == thread["thread_key"]["other_user_id"]
) )
last_message_timestamp = None last_message_timestamp = None
if 'last_message' in thread: if "last_message" in thread:
last_message_timestamp = thread['last_message']['nodes'][0][ last_message_timestamp = thread["last_message"]["nodes"][0][
'timestamp_precise' "timestamp_precise"
] ]
first_name = user.get('short_name') first_name = user.get("short_name")
if first_name is None: if first_name is None:
last_name = None last_name = None
else: else:
last_name = user.get('name').split(first_name, 1).pop().strip() last_name = user.get("name").split(first_name, 1).pop().strip()
plan = None plan = None
if thread.get('event_reminders'): if thread.get("event_reminders"):
plan = ( plan = (
graphql_to_plan(thread['event_reminders']['nodes'][0]) graphql_to_plan(thread["event_reminders"]["nodes"][0])
if thread['event_reminders'].get('nodes') if thread["event_reminders"].get("nodes")
else None else None
) )
return User( return User(
user['id'], user["id"],
url=user.get('url'), url=user.get("url"),
name=user.get('name'), name=user.get("name"),
first_name=first_name, first_name=first_name,
last_name=last_name, last_name=last_name,
is_friend=user.get('is_viewer_friend'), is_friend=user.get("is_viewer_friend"),
gender=GENDERS.get(user.get('gender')), gender=GENDERS.get(user.get("gender")),
affinity=user.get('affinity'), affinity=user.get("affinity"),
nickname=c_info.get('nickname'), nickname=c_info.get("nickname"),
color=c_info.get('color'), color=c_info.get("color"),
emoji=c_info.get('emoji'), emoji=c_info.get("emoji"),
own_nickname=c_info.get('own_nickname'), own_nickname=c_info.get("own_nickname"),
photo=user['big_image_src'].get('uri'), photo=user["big_image_src"].get("uri"),
message_count=thread.get('messages_count'), message_count=thread.get("messages_count"),
last_message_timestamp=last_message_timestamp, last_message_timestamp=last_message_timestamp,
plan=plan, plan=plan,
) )
else: else:
raise FBchatException( raise FBchatException(
'Unknown thread type: {}, with data: {}'.format( "Unknown thread type: {}, with data: {}".format(
thread.get('thread_type'), thread thread.get("thread_type"), thread
) )
) )
def graphql_to_group(group): def graphql_to_group(group):
if group.get('image') is None: if group.get("image") is None:
group['image'] = {} group["image"] = {}
c_info = get_customization_info(group) c_info = get_customization_info(group)
last_message_timestamp = None last_message_timestamp = None
if 'last_message' in group: if "last_message" in group:
last_message_timestamp = group['last_message']['nodes'][0]['timestamp_precise'] last_message_timestamp = group["last_message"]["nodes"][0]["timestamp_precise"]
plan = None plan = None
if group.get('event_reminders'): if group.get("event_reminders"):
plan = ( plan = (
graphql_to_plan(group['event_reminders']['nodes'][0]) graphql_to_plan(group["event_reminders"]["nodes"][0])
if group['event_reminders'].get('nodes') if group["event_reminders"].get("nodes")
else None else None
) )
return Group( return Group(
group['thread_key']['thread_fbid'], group["thread_key"]["thread_fbid"],
participants=set( participants=set(
[ [
node['messaging_actor']['id'] node["messaging_actor"]["id"]
for node in group['all_participants']['nodes'] for node in group["all_participants"]["nodes"]
] ]
), ),
nicknames=c_info.get('nicknames'), nicknames=c_info.get("nicknames"),
color=c_info.get('color'), color=c_info.get("color"),
emoji=c_info.get('emoji'), emoji=c_info.get("emoji"),
admins=set([node.get('id') for node in group.get('thread_admins')]), admins=set([node.get("id") for node in group.get("thread_admins")]),
approval_mode=bool(group.get('approval_mode')) approval_mode=bool(group.get("approval_mode"))
if group.get('approval_mode') is not None if group.get("approval_mode") is not None
else None, else None,
approval_requests=set( approval_requests=set(
node["requester"]['id'] for node in group['group_approval_queue']['nodes'] node["requester"]["id"] for node in group["group_approval_queue"]["nodes"]
) )
if group.get('group_approval_queue') if group.get("group_approval_queue")
else None, else None,
join_link=group['joinable_mode'].get('link'), join_link=group["joinable_mode"].get("link"),
photo=group['image'].get('uri'), photo=group["image"].get("uri"),
name=group.get('name'), name=group.get("name"),
message_count=group.get('messages_count'), message_count=group.get("messages_count"),
last_message_timestamp=last_message_timestamp, last_message_timestamp=last_message_timestamp,
plan=plan, plan=plan,
) )
def graphql_to_page(page): def graphql_to_page(page):
if page.get('profile_picture') is None: if page.get("profile_picture") is None:
page['profile_picture'] = {} page["profile_picture"] = {}
if page.get('city') is None: if page.get("city") is None:
page['city'] = {} page["city"] = {}
plan = None plan = None
if page.get('event_reminders'): if page.get("event_reminders"):
plan = ( plan = (
graphql_to_plan(page['event_reminders']['nodes'][0]) graphql_to_plan(page["event_reminders"]["nodes"][0])
if page['event_reminders'].get('nodes') if page["event_reminders"].get("nodes")
else None else None
) )
return Page( return Page(
page['id'], page["id"],
url=page.get('url'), url=page.get("url"),
city=page.get('city').get('name'), city=page.get("city").get("name"),
category=page.get('category_type'), category=page.get("category_type"),
photo=page['profile_picture'].get('uri'), photo=page["profile_picture"].get("uri"),
name=page.get('name'), name=page.get("name"),
message_count=page.get('messages_count'), message_count=page.get("messages_count"),
plan=plan, plan=plan,
) )
@@ -561,7 +561,7 @@ def graphql_queries_to_json(*queries):
""" """
rtn = {} rtn = {}
for i, query in enumerate(queries): for i, query in enumerate(queries):
rtn['q{}'.format(i)] = query.value rtn["q{}".format(i)] = query.value
return json.dumps(rtn) return json.dumps(rtn)
@@ -570,20 +570,20 @@ def graphql_response_to_json(content):
try: try:
j = json.loads(content, cls=ConcatJSONDecoder) j = json.loads(content, cls=ConcatJSONDecoder)
except Exception: except Exception:
raise FBchatException('Error while parsing JSON: {}'.format(repr(content))) raise FBchatException("Error while parsing JSON: {}".format(repr(content)))
rtn = [None] * (len(j)) rtn = [None] * (len(j))
for x in j: for x in j:
if 'error_results' in x: if "error_results" in x:
del rtn[-1] del rtn[-1]
continue continue
check_json(x) check_json(x)
[(key, value)] = x.items() [(key, value)] = x.items()
check_json(value) check_json(value)
if 'response' in value: if "response" in value:
rtn[int(key[1:])] = value['response'] rtn[int(key[1:])] = value["response"]
else: else:
rtn[int(key[1:])] = value['data'] rtn[int(key[1:])] = value["data"]
log.debug(rtn) log.debug(rtn)
@@ -595,11 +595,11 @@ class GraphQL(object):
if params is None: if params is None:
params = {} params = {}
if query is not None: if query is not None:
self.value = {'priority': 0, 'q': query, 'query_params': params} self.value = {"priority": 0, "q": query, "query_params": params}
elif doc_id is not None: elif doc_id is not None:
self.value = {'doc_id': doc_id, 'query_params': params} self.value = {"doc_id": doc_id, "query_params": params}
else: else:
raise FBchatUserError('A query or doc_id must be specified') raise FBchatUserError("A query or doc_id must be specified")
FRAGMENT_USER = """ FRAGMENT_USER = """
QueryFragment User: User { QueryFragment User: User {

View File

@@ -74,7 +74,7 @@ class Thread(object):
return self.__unicode__() return self.__unicode__()
def __unicode__(self): def __unicode__(self):
return '<{} {} ({})>'.format(self.type.name, self.name, self.uid) return "<{} {} ({})>".format(self.type.name, self.name, self.uid)
class User(Thread): class User(Thread):
@@ -282,7 +282,7 @@ class Message(object):
return self.__unicode__() return self.__unicode__()
def __unicode__(self): def __unicode__(self):
return '<Message ({}): {}, mentions={} emoji_size={} attachments={}>'.format( return "<Message ({}): {}, mentions={} emoji_size={} attachments={}>".format(
self.uid, repr(self.text), self.mentions, self.emoji_size, self.attachments self.uid, repr(self.text), self.mentions, self.emoji_size, self.attachments
) )
@@ -305,7 +305,7 @@ class Message(object):
offset = 0 offset = 0
f = Formatter() f = Formatter()
field_names = [field_name[1] for field_name in f.parse(text)] field_names = [field_name[1] for field_name in f.parse(text)]
automatic = '' in field_names automatic = "" in field_names
i = 0 i = 0
for (literal_text, field_name, format_spec, conversion) in f.parse(text): for (literal_text, field_name, format_spec, conversion) in f.parse(text):
@@ -315,7 +315,7 @@ class Message(object):
if field_name is None: if field_name is None:
continue continue
if field_name == '': if field_name == "":
field_name = str(i) field_name = str(i)
i += 1 i += 1
elif automatic and field_name.isdigit(): elif automatic and field_name.isdigit():
@@ -584,21 +584,21 @@ class ImageAttachment(Attachment):
if preview is None: if preview is None:
preview = {} preview = {}
self.preview_url = preview.get('uri') self.preview_url = preview.get("uri")
self.preview_width = preview.get('width') self.preview_width = preview.get("width")
self.preview_height = preview.get('height') self.preview_height = preview.get("height")
if large_preview is None: if large_preview is None:
large_preview = {} large_preview = {}
self.large_preview_url = large_preview.get('uri') self.large_preview_url = large_preview.get("uri")
self.large_preview_width = large_preview.get('width') self.large_preview_width = large_preview.get("width")
self.large_preview_height = large_preview.get('height') self.large_preview_height = large_preview.get("height")
if animated_preview is None: if animated_preview is None:
animated_preview = {} animated_preview = {}
self.animated_preview_url = animated_preview.get('uri') self.animated_preview_url = animated_preview.get("uri")
self.animated_preview_width = animated_preview.get('width') self.animated_preview_width = animated_preview.get("width")
self.animated_preview_height = animated_preview.get('height') self.animated_preview_height = animated_preview.get("height")
class VideoAttachment(Attachment): class VideoAttachment(Attachment):
@@ -656,21 +656,21 @@ class VideoAttachment(Attachment):
if small_image is None: if small_image is None:
small_image = {} small_image = {}
self.small_image_url = small_image.get('uri') self.small_image_url = small_image.get("uri")
self.small_image_width = small_image.get('width') self.small_image_width = small_image.get("width")
self.small_image_height = small_image.get('height') self.small_image_height = small_image.get("height")
if medium_image is None: if medium_image is None:
medium_image = {} medium_image = {}
self.medium_image_url = medium_image.get('uri') self.medium_image_url = medium_image.get("uri")
self.medium_image_width = medium_image.get('width') self.medium_image_width = medium_image.get("width")
self.medium_image_height = medium_image.get('height') self.medium_image_height = medium_image.get("height")
if large_image is None: if large_image is None:
large_image = {} large_image = {}
self.large_image_url = large_image.get('uri') self.large_image_url = large_image.get("uri")
self.large_image_width = large_image.get('width') self.large_image_width = large_image.get("width")
self.large_image_height = large_image.get('height') self.large_image_height = large_image.get("height")
class Mention(object): class Mention(object):
@@ -691,7 +691,7 @@ class Mention(object):
return self.__unicode__() return self.__unicode__()
def __unicode__(self): def __unicode__(self):
return '<Mention {}: offset={} length={}>'.format( return "<Mention {}: offset={} length={}>".format(
self.thread_id, self.offset, self.length self.thread_id, self.offset, self.length
) )
@@ -716,7 +716,7 @@ class QuickReply(object):
return self.__unicode__() return self.__unicode__()
def __unicode__(self): def __unicode__(self):
return '<{}: payload={!r}>'.format(self.__class__.__name__, self.payload) return "<{}: payload={!r}>".format(self.__class__.__name__, self.payload)
class QuickReplyText(QuickReply): class QuickReplyText(QuickReply):
@@ -787,7 +787,7 @@ class Poll(object):
return self.__unicode__() return self.__unicode__()
def __unicode__(self): def __unicode__(self):
return '<Poll ({}): {} options={}>'.format( return "<Poll ({}): {} options={}>".format(
self.uid, repr(self.title), self.options self.uid, repr(self.title), self.options
) )
@@ -813,7 +813,7 @@ class PollOption(object):
return self.__unicode__() return self.__unicode__()
def __unicode__(self): def __unicode__(self):
return '<PollOption ({}): {} voters={}>'.format( return "<PollOption ({}): {} voters={}>".format(
self.uid, repr(self.text), self.voters self.uid, repr(self.text), self.voters
) )
@@ -842,8 +842,8 @@ class Plan(object):
"""Represents a plan""" """Represents a plan"""
self.time = int(time) self.time = int(time)
self.title = title self.title = title
self.location = location or '' self.location = location or ""
self.location_id = location_id or '' self.location_id = location_id or ""
self.author_id = None self.author_id = None
self.going = [] self.going = []
self.declined = [] self.declined = []
@@ -853,7 +853,7 @@ class Plan(object):
return self.__unicode__() return self.__unicode__()
def __unicode__(self): def __unicode__(self):
return '<Plan ({}): {} time={}, location={}, location_id={}>'.format( return "<Plan ({}): {} time={}, location={}, location_id={}>".format(
self.uid, self.uid,
repr(self.title), repr(self.title),
self.time, self.time,
@@ -879,7 +879,7 @@ class ActiveStatus(object):
return self.__unicode__() return self.__unicode__()
def __unicode__(self): def __unicode__(self):
return '<ActiveStatus: active={} last_active={} in_game={}>'.format( return "<ActiveStatus: active={} last_active={} in_game={}>".format(
self.active, self.last_active, self.in_game self.active, self.last_active, self.in_game
) )
@@ -889,7 +889,7 @@ class Enum(aenum.Enum):
def __repr__(self): def __repr__(self):
# For documentation: # For documentation:
return '{}.{}'.format(type(self).__name__, self.name) return "{}.{}".format(type(self).__name__, self.name)
class ThreadType(Enum): class ThreadType(Enum):
@@ -904,10 +904,10 @@ class ThreadType(Enum):
class ThreadLocation(Enum): class ThreadLocation(Enum):
"""Used to specify where a thread is located (inbox, pending, archived, other).""" """Used to specify where a thread is located (inbox, pending, archived, other)."""
INBOX = 'INBOX' INBOX = "INBOX"
PENDING = 'PENDING' PENDING = "PENDING"
ARCHIVED = 'ARCHIVED' ARCHIVED = "ARCHIVED"
OTHER = 'OTHER' OTHER = "OTHER"
class TypingStatus(Enum): class TypingStatus(Enum):
@@ -920,38 +920,38 @@ class TypingStatus(Enum):
class EmojiSize(Enum): class EmojiSize(Enum):
"""Used to specify the size of a sent emoji""" """Used to specify the size of a sent emoji"""
LARGE = '369239383222810' LARGE = "369239383222810"
MEDIUM = '369239343222814' MEDIUM = "369239343222814"
SMALL = '369239263222822' SMALL = "369239263222822"
class ThreadColor(Enum): class ThreadColor(Enum):
"""Used to specify a thread colors""" """Used to specify a thread colors"""
MESSENGER_BLUE = '#0084ff' MESSENGER_BLUE = "#0084ff"
VIKING = '#44bec7' VIKING = "#44bec7"
GOLDEN_POPPY = '#ffc300' GOLDEN_POPPY = "#ffc300"
RADICAL_RED = '#fa3c4c' RADICAL_RED = "#fa3c4c"
SHOCKING = '#d696bb' SHOCKING = "#d696bb"
PICTON_BLUE = '#6699cc' PICTON_BLUE = "#6699cc"
FREE_SPEECH_GREEN = '#13cf13' FREE_SPEECH_GREEN = "#13cf13"
PUMPKIN = '#ff7e29' PUMPKIN = "#ff7e29"
LIGHT_CORAL = '#e68585' LIGHT_CORAL = "#e68585"
MEDIUM_SLATE_BLUE = '#7646ff' MEDIUM_SLATE_BLUE = "#7646ff"
DEEP_SKY_BLUE = '#20cef5' DEEP_SKY_BLUE = "#20cef5"
FERN = '#67b868' FERN = "#67b868"
CAMEO = '#d4a88c' CAMEO = "#d4a88c"
BRILLIANT_ROSE = '#ff5ca1' BRILLIANT_ROSE = "#ff5ca1"
BILOBA_FLOWER = '#a695c7' BILOBA_FLOWER = "#a695c7"
class MessageReaction(Enum): class MessageReaction(Enum):
"""Used to specify a message reaction""" """Used to specify a message reaction"""
LOVE = '😍' LOVE = "😍"
SMILE = '😆' SMILE = "😆"
WOW = '😮' WOW = "😮"
SAD = '😢' SAD = "😢"
ANGRY = '😠' ANGRY = "😠"
YES = '👍' YES = "👍"
NO = '👎' NO = "👎"

View File

@@ -48,37 +48,37 @@ USER_AGENTS = [
] ]
LIKES = { LIKES = {
'large': EmojiSize.LARGE, "large": EmojiSize.LARGE,
'medium': EmojiSize.MEDIUM, "medium": EmojiSize.MEDIUM,
'small': EmojiSize.SMALL, "small": EmojiSize.SMALL,
'l': EmojiSize.LARGE, "l": EmojiSize.LARGE,
'm': EmojiSize.MEDIUM, "m": EmojiSize.MEDIUM,
's': EmojiSize.SMALL, "s": EmojiSize.SMALL,
} }
GENDERS = { GENDERS = {
# For standard requests # For standard requests
0: 'unknown', 0: "unknown",
1: 'female_singular', 1: "female_singular",
2: 'male_singular', 2: "male_singular",
3: 'female_singular_guess', 3: "female_singular_guess",
4: 'male_singular_guess', 4: "male_singular_guess",
5: 'mixed', 5: "mixed",
6: 'neuter_singular', 6: "neuter_singular",
7: 'unknown_singular', 7: "unknown_singular",
8: 'female_plural', 8: "female_plural",
9: 'male_plural', 9: "male_plural",
10: 'neuter_plural', 10: "neuter_plural",
11: 'unknown_plural', 11: "unknown_plural",
# For graphql requests # For graphql requests
'UNKNOWN': 'unknown', "UNKNOWN": "unknown",
'FEMALE': 'female_singular', "FEMALE": "female_singular",
'MALE': 'male_singular', "MALE": "male_singular",
# '': 'female_singular_guess', # '': 'female_singular_guess',
# '': 'male_singular_guess', # '': 'male_singular_guess',
# '': 'mixed', # '': 'mixed',
'NEUTER': 'neuter_singular', "NEUTER": "neuter_singular",
# '': 'unknown_singular', # '': 'unknown_singular',
# '': 'female_plural', # '': 'female_plural',
# '': 'male_plural', # '': 'male_plural',
@@ -168,7 +168,7 @@ class ReqUrl(object):
) )
facebookEncoding = 'UTF-8' facebookEncoding = "UTF-8"
def now(): def now():
@@ -177,9 +177,9 @@ def now():
def strip_to_json(text): def strip_to_json(text):
try: try:
return text[text.index('{') :] return text[text.index("{") :]
except ValueError: except ValueError:
raise FBchatException('No JSON object found: {!r}'.format(text)) raise FBchatException("No JSON object found: {!r}".format(text))
def get_decoded_r(r): def get_decoded_r(r):
@@ -201,12 +201,12 @@ def get_json(r):
def digitToChar(digit): def digitToChar(digit):
if digit < 10: if digit < 10:
return str(digit) return str(digit)
return chr(ord('a') + digit - 10) return chr(ord("a") + digit - 10)
def str_base(number, base): def str_base(number, base):
if number < 0: if number < 0:
return '-' + str_base(-number, base) return "-" + str_base(-number, base)
(d, m) = divmod(number, base) (d, m) = divmod(number, base)
if d > 0: if d > 0:
return str_base(d, base) + digitToChar(m) return str_base(d, base) + digitToChar(m)
@@ -226,55 +226,55 @@ def getSignatureID():
def generateOfflineThreadingID(): def generateOfflineThreadingID():
ret = now() ret = now()
value = int(random() * 4294967295) value = int(random() * 4294967295)
string = ("0000000000000000000000" + format(value, 'b'))[-22:] string = ("0000000000000000000000" + format(value, "b"))[-22:]
msgs = format(ret, 'b') + string msgs = format(ret, "b") + string
return str(int(msgs, 2)) return str(int(msgs, 2))
def check_json(j): def check_json(j):
if j.get('error') is None: if j.get("error") is None:
return return
if 'errorDescription' in j: if "errorDescription" in j:
# 'errorDescription' is in the users own language! # 'errorDescription' is in the users own language!
raise FBchatFacebookError( raise FBchatFacebookError(
'Error #{} when sending request: {}'.format( "Error #{} when sending request: {}".format(
j['error'], j['errorDescription'] j["error"], j["errorDescription"]
), ),
fb_error_code=j['error'], fb_error_code=j["error"],
fb_error_message=j['errorDescription'], fb_error_message=j["errorDescription"],
) )
elif 'debug_info' in j['error'] and 'code' in j['error']: elif "debug_info" in j["error"] and "code" in j["error"]:
raise FBchatFacebookError( raise FBchatFacebookError(
'Error #{} when sending request: {}'.format( "Error #{} when sending request: {}".format(
j['error']['code'], repr(j['error']['debug_info']) j["error"]["code"], repr(j["error"]["debug_info"])
), ),
fb_error_code=j['error']['code'], fb_error_code=j["error"]["code"],
fb_error_message=j['error']['debug_info'], fb_error_message=j["error"]["debug_info"],
) )
else: else:
raise FBchatFacebookError( raise FBchatFacebookError(
'Error {} when sending request'.format(j['error']), fb_error_code=j['error'] "Error {} when sending request".format(j["error"]), fb_error_code=j["error"]
) )
def check_request(r, as_json=True): def check_request(r, as_json=True):
if not r.ok: if not r.ok:
raise FBchatFacebookError( raise FBchatFacebookError(
'Error when sending request: Got {} response'.format(r.status_code), "Error when sending request: Got {} response".format(r.status_code),
request_status_code=r.status_code, request_status_code=r.status_code,
) )
content = get_decoded_r(r) content = get_decoded_r(r)
if content is None or len(content) == 0: if content is None or len(content) == 0:
raise FBchatFacebookError('Error when sending request: Got empty response') raise FBchatFacebookError("Error when sending request: Got empty response")
if as_json: if as_json:
content = strip_to_json(content) content = strip_to_json(content)
try: try:
j = json.loads(content) j = json.loads(content)
except ValueError: except ValueError:
raise FBchatFacebookError('Error while parsing JSON: {!r}'.format(content)) raise FBchatFacebookError("Error while parsing JSON: {!r}".format(content))
check_json(j) check_json(j)
log.debug(j) log.debug(j)
return j return j
@@ -283,12 +283,12 @@ def check_request(r, as_json=True):
def get_jsmods_require(j, index): def get_jsmods_require(j, index):
if j.get('jsmods') and j['jsmods'].get('require'): if j.get("jsmods") and j["jsmods"].get("require"):
try: try:
return j['jsmods']['require'][0][index][0] return j["jsmods"]["require"][0][index][0]
except (KeyError, IndexError) as e: except (KeyError, IndexError) as e:
log.warning( log.warning(
'Error when getting jsmods_require: {}. Facebook might have changed protocol'.format( "Error when getting jsmods_require: {}. Facebook might have changed protocol".format(
j j
) )
) )
@@ -298,13 +298,13 @@ def get_jsmods_require(j, index):
def get_emojisize_from_tags(tags): def get_emojisize_from_tags(tags):
if tags is None: if tags is None:
return None return None
tmp = [tag for tag in tags if tag.startswith('hot_emoji_size:')] tmp = [tag for tag in tags if tag.startswith("hot_emoji_size:")]
if len(tmp) > 0: if len(tmp) > 0:
try: try:
return LIKES[tmp[0].split(':')[1]] return LIKES[tmp[0].split(":")[1]]
except (KeyError, IndexError): except (KeyError, IndexError):
log.exception( log.exception(
'Could not determine emoji size from {} - {}'.format(tags, tmp) "Could not determine emoji size from {} - {}".format(tags, tmp)
) )
return None return None
@@ -337,7 +337,7 @@ def get_files_from_urls(file_urls):
( (
basename(file_url), basename(file_url),
r.content, r.content,
r.headers.get('Content-Type') or guess_type(file_url)[0], r.headers.get("Content-Type") or guess_type(file_url)[0],
) )
) )
return files return files
@@ -348,7 +348,7 @@ def get_files_from_paths(filenames):
files = [] files = []
for filename in filenames: for filename in filenames:
files.append( files.append(
(basename(filename), open(filename, 'rb'), guess_type(filename)[0]) (basename(filename), open(filename, "rb"), guess_type(filename)[0])
) )
yield files yield files
for fn, fp, ft in files: for fn, fp, ft in files:

View File

@@ -115,14 +115,14 @@ def compare(client, thread):
def message_with_mentions(request, client, client2, group): def message_with_mentions(request, client, client2, group):
text = "Hi there [" text = "Hi there ["
mentions = [] mentions = []
if 'me' in request.param: if "me" in request.param:
mentions.append(Mention(thread_id=client.uid, offset=len(text), length=2)) mentions.append(Mention(thread_id=client.uid, offset=len(text), length=2))
text += "me, " text += "me, "
if 'other' in request.param: if "other" in request.param:
mentions.append(Mention(thread_id=client2.uid, offset=len(text), length=5)) mentions.append(Mention(thread_id=client2.uid, offset=len(text), length=5))
text += "other, " text += "other, "
# Unused, because Facebook don't properly support sending mentions with groups as targets # Unused, because Facebook don't properly support sending mentions with groups as targets
if 'group' in request.param: if "group" in request.param:
mentions.append(Mention(thread_id=group["id"], offset=len(text), length=5)) mentions.append(Mention(thread_id=group["id"], offset=len(text), length=5))
text += "group, " text += "group, "
text += "nothing]" text += "nothing]"

View File

@@ -120,6 +120,6 @@ def test_send_remote_files(client, catch_event, compare):
assert len(x.res["message_object"].attachments) == len(files) assert len(x.res["message_object"].attachments) == len(files)
@pytest.mark.parametrize('wave_first', [True, False]) @pytest.mark.parametrize("wave_first", [True, False])
def test_wave(client, wave_first): def test_wave(client, wave_first):
client.wave(wave_first) client.wave(wave_first)

View File

@@ -9,4 +9,4 @@ def test_catch_event(client2, catch_event):
mid = "test" mid = "test"
with catch_event("onMessage") as x: with catch_event("onMessage") as x:
client2.onMessage(mid=mid) client2.onMessage(mid=mid)
assert x.res['mid'] == mid assert x.res["mid"] == mid

View File

@@ -135,7 +135,7 @@ def test_typing_status(client, catch_event, compare, status):
assert compare(x, status=status) assert compare(x, status=status)
@pytest.mark.parametrize('require_admin_approval', [True, False]) @pytest.mark.parametrize("require_admin_approval", [True, False])
def test_change_approval_mode(client1, group, catch_event, require_admin_approval): def test_change_approval_mode(client1, group, catch_event, require_admin_approval):
with catch_event("onApprovalModeChange") as x: with catch_event("onApprovalModeChange") as x:
client1.changeGroupApprovalMode(require_admin_approval, group["id"]) client1.changeGroupApprovalMode(require_admin_approval, group["id"])

View File

@@ -106,7 +106,7 @@ def load_client(n, cache):
client = Client( client = Client(
load_variable("client{}_email".format(n), cache), load_variable("client{}_email".format(n), cache),
load_variable("client{}_password".format(n), cache), load_variable("client{}_password".format(n), cache),
user_agent='Mozilla/5.0 (Windows NT 6.3; WOW64; ; NCT50_AAP285C84A1328) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36', user_agent="Mozilla/5.0 (Windows NT 6.3; WOW64; ; NCT50_AAP285C84A1328) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
session_cookies=cache.get("client{}_session".format(n), None), session_cookies=cache.get("client{}_session".format(n), None),
max_tries=1, max_tries=1,
) )