Removed deprecations and new event system, improved other things

Removed deprecations
Removed new event system
Added documentation for all events
Added FAQ
Changed Client.uid to Client.id
Improved User model
Prepared for support of pages
This commit is contained in:
Mads Marquart
2017-06-20 14:57:23 +02:00
parent 0885796fa8
commit c81d7d2bfb
15 changed files with 768 additions and 700 deletions

View File

@@ -14,7 +14,7 @@ from datetime import datetime
from .client import *
__copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year)
__version__ = '0.10.4'
__version__ = '1.0.0'
__license__ = 'BSD'
__author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart'
__email__ = 'carpedm20@gmail.com'

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +0,0 @@
# -*- coding: UTF-8 -*-
class EventHook(object):
"""
A simple implementation of the Observer-Pattern.
All listeners added to this will be called, regardless of parameters
"""
def __init__(self, *args):
self._handlers = list(args)
def add(self, handler):
return self.__iadd__(handler)
def remove(self, handler):
return self.__isub__(handler)
def __iadd__(self, handler):
self._handlers.append(handler)
return self
def __isub__(self, handler):
self._handlers.remove(handler)
return self
def __call__(self, *args, **kwargs):
for handler in self._handlers:
handler(*args, **kwargs)

View File

@@ -3,81 +3,76 @@
from __future__ import unicode_literals
import enum
class User(object):
"""Represents a Facebook User"""
class Thread(object):
#: The unique identifier of the user. Can be used a `thread_id`. See :ref:`intro_threads` for more info
uid = None
#: Currently always set to `user`. Might change in the future
type = 'user'
#: The profile picture of the user
id = None
#: Specifies the type of thread. Uses ThreadType
type = None
#: The thread's picture
photo = None
#: The profile url
url = None
#: The name of the user
#: The name of the thread
name = None
#: Only used by :any:`Client.getUsers`. Each user is assigned a score between 0 and 1, based on how likely it is that they were the person being searched for
score = None
#: Dictionary containing raw userdata from when the :class:`User` was created
data = None
def __init__(self, data):
"""Represents a Facebook User"""
if data['type'] != 'user':
raise Exception("[!] %s <%s> is not a user" % (data['text'], data['path']))
self.uid = data['uid']
self.type = data['type']
self.photo = data['photo']
self.url = data['path']
self.name = data['text']
self.score = float(data['score'])
self.data = data
def __init__(self, _type, _id, photo=None, name=None):
"""Represents a Facebook thread"""
self.id = str(_id)
self.type = _type
self.photo = photo
self.name = name
def __repr__(self):
return self.__unicode__()
def __unicode__(self):
return u'<%s %s (%s)>' % (self.type.upper(), self.name, self.url)
return '<{} {} ({})>'.format(self.type.name, self.name, self.id)
@staticmethod
def _adaptFromChat(user_in_chat):
"""Adapts user info from chat to User model acceptable initial dict
:param user_in_chat: user info from chat
:return: :class:`User` object
class User(Thread):
#: The profile url
url = None
#: The users first name
first_name = None
#: The users last name
last_name = None
#: Whether the user and the client are friends
is_friend = None
#: The user's gender
gender = None
'dir': None,
'mThumbSrcSmall': None,
'is_friend': False,
'is_nonfriend_messenger_contact': True,
'alternateName': '',
'i18nGender': 16777216,
'vanity': '',
'type': 'friend',
'searchTokens': ['Voznesenskij', 'Sergej'],
'thumbSrc': 'https://fb-s-b-a.akamaihd.net/h-ak-xfa1/v/t1.0-1/c9.0.32.32/p32x32/10354686_10150004552801856_220367501106153455_n.jpg?oh=71a87d76d4e4d17615a20c43fb8dbb47&oe=59118CE4&__gda__=1493753268_ae75cef40e9785398e744259ccffd7ff',
'mThumbSrcLarge': None,
'firstName': 'Sergej',
'name': 'Sergej Voznesenskij',
'uri': 'https://www.facebook.com/profile.php?id=100014812758264',
'id': '100014812758264',
'gender': 2
"""
def __init__(self, _id, url=None, first_name=None, last_name=None, is_friend=None, gender=None, **kwargs):
"""Represents a Facebook user. Inherits `Thread`"""
super(User, self).__init__(ThreadType.USER, _id, **kwargs)
self.url = url
self.first_name = first_name
self.last_name = last_name
self.is_friend = is_friend
self.gender = gender
return {
'type': 'user',
'uid': user_in_chat['id'],
'photo': user_in_chat['thumbSrc'],
'path': user_in_chat['uri'],
'text': user_in_chat['name'],
'score': 1,
'data': user_in_chat,
}
class Thread(object):
"""Represents a thread. Currently just acts as a dict"""
def __init__(self, **entries):
self.__dict__.update(entries)
class Group(Thread):
def __init__(self, _id, **kwargs):
"""Represents a Facebook group. Inherits `Thread`"""
super(Group, self).__init__(ThreadType.GROUP, _id, **kwargs)
class Page(Thread):
#: The page's custom url
url = None
#: The name of the page's location city
city = None
#: Amount of likes the page has
likees = None
#: Some extra information about the page
sub_text = None
def __init__(self, _id, url=None, city=None, likees=None, sub_text=None, **kwargs):
"""Represents a Facebook page. Inherits `Thread`"""
super(Page, self).__init__(ThreadType.PAGE, _id, **kwargs)
self.url = url
self.city = city
self.likees = likees
self.sub_text = sub_text
class Message(object):
"""Represents a message. Currently just acts as a dict"""
@@ -94,6 +89,7 @@ class ThreadType(Enum):
"""Used to specify what type of Facebook thread is being used. See :ref:`intro_threads` for more info"""
USER = 1
GROUP = 2
PAGE = 3
class TypingStatus(Enum):
"""Used to specify whether the user is typing or has stopped typing"""

View File

@@ -6,8 +6,22 @@ import json
from time import time
from random import random
import warnings
import logging
from .models import *
# Python 2's `input` executes the input, whereas `raw_input` just returns the input
try:
input = raw_input
except NameError:
pass
# Log settings
log = logging.getLogger("client")
log.setLevel(logging.DEBUG)
# Creates the console handler
handler = logging.StreamHandler()
log.addHandler(handler)
#: Default list of user agents
USER_AGENTS = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
@@ -49,7 +63,7 @@ class ReqUrl(object):
STICKY = "https://0-edge-chat.facebook.com/pull"
PING = "https://0-channel-proxy-06-ash2.facebook.com/active_ping"
UPLOAD = "https://upload.facebook.com/ajax/mercury/upload.php"
USER_INFO = "https://www.facebook.com/chat/user_info/"
INFO = "https://www.facebook.com/chat/user_info/"
CONNECT = "https://www.facebook.com/ajax/add_friend/action.php?dpr=1"
REMOVE_USER = "https://www.facebook.com/chat/remove_participants/"
LOGOUT = "https://www.facebook.com/logout.php"
@@ -82,7 +96,7 @@ def get_decoded(r):
def get_json(r):
return json.loads(strip_to_json(get_decoded(r)))
def digit_to_char(digit):
def digitToChar(digit):
if digit < 10:
return str(digit)
return chr(ord('a') + digit - 10)
@@ -92,8 +106,8 @@ def str_base(number, base):
return '-' + str_base(-number, base)
(d, m) = divmod(number, base)
if d > 0:
return str_base(d, base) + digit_to_char(m)
return digit_to_char(m)
return str_base(d, base) + digitToChar(m)
return digitToChar(m)
def generateMessageID(client_id=None):
k = now()
@@ -110,31 +124,20 @@ def generateOfflineThreadingID():
msgs = format(ret, 'b') + string
return str(int(msgs, 2))
def isUserToThreadType(is_user):
return ThreadType.USER if is_user else ThreadType.GROUP
def checkRequest(r, check_json=True):
if not r.ok:
raise Exception('Error when sending request: Got {} response'.format(r.status_code))
def raise_exception(e):
raise e
content = get_decoded(r)
def deprecation(name, deprecated_in=None, removed_in=None, details='', stacklevel=3):
"""Used to mark parameters as deprecated. Will result in a warning being emmitted when the parameter is used."""
warning = "Client.{} is deprecated".format(name)
if deprecated_in:
warning += ' in v. {}'.format(deprecated_in)
if removed_in:
warning += ' and will be removed in v. {}'.format(removed_in)
if details:
warning += '. {}'.format(details)
if content is None or len(content) == 0:
raise Exception('Error when sending request: Got empty response')
warnings.simplefilter('always', DeprecationWarning)
warnings.warn(warning, category=DeprecationWarning, stacklevel=stacklevel)
warnings.simplefilter('default', DeprecationWarning)
def deprecated(deprecated_in=None, removed_in=None, details=''):
"""A decorator used to mark functions as deprecated. Will result in a warning being emmitted when the decorated function is used."""
def wrap(func, *args, **kwargs):
def wrapped_func(*args, **kwargs):
deprecation(func.__name__, deprecated_in=deprecated_in, removed_in=removed_in, details=details, stacklevel=3)
return func(*args, **kwargs)
return wrapped_func
return wrap
if check_json:
j = json.loads(strip_to_json(content))
if 'error' in j:
# 'errorDescription' is in the users own language!
raise Exception('Error #{} when sending request: {}'.format(j['error'], j['errorDescription']))
return j
else:
return r