nickoala / telepot

Python framework for Telegram Bot API
MIT License
2.43k stars 474 forks source link

RuntimeError: cannot set daemon status of active thread #363

Closed Aditya9009 closed 6 years ago

Aditya9009 commented 6 years ago

i am trying this piece of code in infinite loop: MessageLoop(bot, handle).run_as_thread()

but it shows following error:

Traceback (most recent call last): File "C:\Python34\lib\threading.py", line 911, in _bootstrap_inner self.run() File "C:\Python34\lib\threading.py", line 859, in run self._target(*self._args, **self._kwargs) File "D:\Python FIles\Telepot\telepot\loop.py", line 157, in run_forever self._bot.scheduler.run_as_thread() File "D:\Python FIles\Telepot\telepot__init__.py", line 461, in run_as_thread self.daemon = True File "C:\Python34\lib\threading.py", line 1136, in daemon raise RuntimeError("cannot set daemon status of active thread") RuntimeError: cannot set daemon status of active thread

Help me resolving this.Thanks in advance

Aditya9009 commented 6 years ago

??? Any sort of help i am stuck

nickoala commented 6 years ago

I use threads many times in my Python code, and always set thread.daemon=True, but have never seen this error. Sorry, no clue :sweat_smile:

das7pad commented 6 years ago

@Aditya9009 Would you mind sharing a minimal version of D:\Python FIles\Telepot\telepot_init.py?

Aditya9009 commented 6 years ago

@nickoala It says threads can only start once

Aditya9009 commented 6 years ago

@das7pad Here is the code

import sys import io import time import json import threading import traceback import collections import bisect

try: import Queue as queue except ImportError: import queue

Patch urllib3 for sending unicode filename

from . import hack

from . import exception

version_info = (12, 5) version = '.'.join(map(str, version_info))

def flavor(msg): """ Return flavor of message or event.

A message's flavor may be one of these:

- ``chat``
- ``callback_query``
- ``inline_query``
- ``chosen_inline_result``
- ``shipping_query``
- ``pre_checkout_query``

An event's flavor is determined by the single top-level key.
"""
if 'message_id' in msg:
    return 'chat'
elif 'id' in msg and 'chat_instance' in msg:
    return 'callback_query'
elif 'id' in msg and 'query' in msg:
    return 'inline_query'
elif 'result_id' in msg:
    return 'chosen_inline_result'
elif 'id' in msg and 'shipping_address' in msg:
    return 'shipping_query'
elif 'id' in msg and 'total_amount' in msg:
    return 'pre_checkout_query'
else:
    top_keys = list(msg.keys())
    if len(top_keys) == 1:
        return top_keys[0]

    raise exception.BadFlavor(msg)

chat_flavors = ['chat'] inline_flavors = ['inline_query', 'chosen_inline_result']

def _find_first_key(d, keys): for k in keys: if k in d: return k raise KeyError('No suggested keys %s in %s' % (str(keys), str(d)))

all_content_types = [ 'text', 'audio', 'document', 'game', 'photo', 'sticker', 'video', 'voice', 'video_note', 'contact', 'location', 'venue', 'new_chat_member', 'left_chat_member', 'new_chat_title', 'new_chat_photo', 'delete_chat_photo', 'group_chat_created', 'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id', 'pinned_message', 'new_chat_members', 'invoice', 'successful_payment' ]

def glance(msg, flavor='chat', long=False): """ Extract "headline" info about a message. Use parameter long to control whether a short or long tuple is returned.

When ``flavor`` is ``chat``
(``msg`` being a `Message <https://core.telegram.org/bots/api#message>`_ object):

- short: (content_type, ``msg['chat']['type']``, ``msg['chat']['id']``)
- long: (content_type, ``msg['chat']['type']``, ``msg['chat']['id']``, ``msg['date']``, ``msg['message_id']``)

*content_type* can be: ``text``, ``audio``, ``document``, ``game``, ``photo``, ``sticker``, ``video``, ``voice``,
``video_note``, ``contact``, ``location``, ``venue``, ``new_chat_member``, ``left_chat_member``, ``new_chat_title``,
``new_chat_photo``, ``delete_chat_photo``, ``group_chat_created``, ``supergroup_chat_created``,
``channel_chat_created``, ``migrate_to_chat_id``, ``migrate_from_chat_id``, ``pinned_message``,
``new_chat_members``, ``invoice``, ``successful_payment``.

When ``flavor`` is ``callback_query``
(``msg`` being a `CallbackQuery <https://core.telegram.org/bots/api#callbackquery>`_ object):

- regardless: (``msg['id']``, ``msg['from']['id']``, ``msg['data']``)

When ``flavor`` is ``inline_query``
(``msg`` being a `InlineQuery <https://core.telegram.org/bots/api#inlinequery>`_ object):

- short: (``msg['id']``, ``msg['from']['id']``, ``msg['query']``)
- long: (``msg['id']``, ``msg['from']['id']``, ``msg['query']``, ``msg['offset']``)

When ``flavor`` is ``chosen_inline_result``
(``msg`` being a `ChosenInlineResult <https://core.telegram.org/bots/api#choseninlineresult>`_ object):

- regardless: (``msg['result_id']``, ``msg['from']['id']``, ``msg['query']``)

When ``flavor`` is ``shipping_query``
(``msg`` being a `ShippingQuery <https://core.telegram.org/bots/api#shippingquery>`_ object):

- regardless: (``msg['id']``, ``msg['from']['id']``, ``msg['invoice_payload']``)

When ``flavor`` is ``pre_checkout_query``
(``msg`` being a `PreCheckoutQuery <https://core.telegram.org/bots/api#precheckoutquery>`_ object):

- short: (``msg['id']``, ``msg['from']['id']``, ``msg['invoice_payload']``)
- long: (``msg['id']``, ``msg['from']['id']``, ``msg['invoice_payload']``, ``msg['currency']``, ``msg['total_amount']``)
"""
def gl_chat():
    content_type = _find_first_key(msg, all_content_types)

    if long:
        return content_type, msg['chat']['type'], msg['chat']['id'], msg['date'], msg['message_id']
    else:
        return content_type, msg['chat']['type'], msg['chat']['id']

def gl_callback_query():
    return msg['id'], msg['from']['id'], msg['data']

def gl_inline_query():
    if long:
        return msg['id'], msg['from']['id'], msg['query'], msg['offset']
    else:
        return msg['id'], msg['from']['id'], msg['query']

def gl_chosen_inline_result():
    return msg['result_id'], msg['from']['id'], msg['query']

def gl_shipping_query():
    return msg['id'], msg['from']['id'], msg['invoice_payload']

def gl_pre_checkout_query():
    if long:
        return msg['id'], msg['from']['id'], msg['invoice_payload'], msg['currency'], msg['total_amount']
    else:
        return msg['id'], msg['from']['id'], msg['invoice_payload']

try:
    fn = {'chat': gl_chat,
          'callback_query': gl_callback_query,
          'inline_query': gl_inline_query,
          'chosen_inline_result': gl_chosen_inline_result,
          'shipping_query': gl_shipping_query,
          'pre_checkout_query': gl_pre_checkout_query}[flavor]
except KeyError:
    raise exception.BadFlavor(flavor)

return fn()

def flance(msg, long=False): """ A combination of :meth:telepot.flavor and :meth:telepot.glance, return a 2-tuple (flavor, headline_info), where headline_info is whatever extracted by :meth:telepot.glance depending on the message flavor and the long parameter. """ f = flavor(msg) g = glance(msg, flavor=f, long=long) return f,g

def peel(event): """ Remove an event's top-level skin (where its flavor is determined), and return the core content. """ return list(event.values())[0]

def fleece(event): """ A combination of :meth:telepot.flavor and :meth:telepot.peel, return a 2-tuple (flavor, content) of an event. """ return flavor(event), peel(event)

def isevent(msg): """ Return whether the message looks like an event. That is, whether it has a flavor that starts with an underscore. """ return flavor(msg).startswith('')

def origin_identifier(msg): """ Extract the message identifier of a callback query's origin. Returned value is guaranteed to be a tuple.

``msg`` is expected to be ``callback_query``.
"""
if 'message' in msg:
    return msg['message']['chat']['id'], msg['message']['message_id']
elif 'inline_message_id' in msg:
    return msg['inline_message_id'],
else:
    raise ValueError()

def message_identifier(msg): """ Extract an identifier for message editing. Useful with :meth:telepot.Bot.editMessageText and similar methods. Returned value is guaranteed to be a tuple.

``msg`` is expected to be ``chat`` or ``choson_inline_result``.
"""
if 'chat' in msg and 'message_id' in msg:
    return msg['chat']['id'], msg['message_id']
elif 'inline_message_id' in msg:
    return msg['inline_message_id'],
else:
    raise ValueError()

def _dismantle_message_identifier(f): if isinstance(f, tuple): if len(f) == 2: return {'chat_id': f[0], 'message_id': f[1]} elif len(f) == 1: return {'inline_message_id': f[0]} else: raise ValueError() else: return {'inline_message_id': f}

def _split_input_media_array(media_array): def ensure_dict(input_media): if isinstance(input_media, tuple) and hasattr(input_media, '_asdict'): return input_media._asdict() elif isinstance(input_media, dict): return input_media else: raise ValueError()

def given_attach_name(input_media):
    if isinstance(input_media['media'], tuple):
        return input_media['media'][0]
    else:
        return None

def attach_name_generator(used_names):
    x = 0
    while 1:
        x += 1
        name = 'media' + str(x)
        if name in used_names:
            continue;
        yield name

def split_media(input_media, name_generator):
    file_spec = input_media['media']

    # file_id, URL
    if _isstring(file_spec):
        return (input_media, None)

    # file-object
    # (attach-name, file-object)
    # (attach-name, (filename, file-object))
    if isinstance(file_spec, tuple):
        name, f = file_spec
    else:
        name, f = next(name_generator), file_spec

    m = input_media.copy()
    m['media'] = 'attach://' + name

    return (m, (name, f))

ms = [ensure_dict(m) for m in media_array]

used_names = [given_attach_name(m) for m in ms if given_attach_name(m) is not None]
name_generator = attach_name_generator(used_names)

splitted = [split_media(m, name_generator) for m in ms]

legal_media, attachments = map(list, zip(*splitted))
files_to_attach = dict([a for a in attachments if a is not None])

return (legal_media, files_to_attach)

PY_3 = sys.version_info.major >= 3 _string_type = str if PY_3 else basestring _file_type = io.IOBase if PY_3 else file

def _isstring(s): return isinstance(s, _string_type)

def _isfile(f): return isinstance(f, _file_type)

from . import helper

def flavor_router(routing_table): router = helper.Router(flavor, routing_table) return router.route

class _BotBase(object): def init(self, token): self._token = token self._file_chunk_size = 65536

def _strip(params, more=[]): return {key: value for key,value in params.items() if key not in ['self']+more}

def _rectify(params): def make_jsonable(value): if isinstance(value, list): return [make_jsonable(v) for v in value] elif isinstance(value, dict): return {k:make_jsonable(v) for k,v in value.items() if v is not None} elif isinstance(value, tuple) and hasattr(value, '_asdict'): return {k:make_jsonable(v) for k,v in value._asdict().items() if v is not None} else: return value

def flatten(value):
    v = make_jsonable(value)

    if isinstance(v, (dict, list)):
        return json.dumps(v, separators=(',',':'))
    else:
        return v

# remove None, then json-serialize if needed
return {k: flatten(v) for k,v in params.items() if v is not None}

from . import api

class Bot(_BotBase): class Scheduler(threading.Thread):

A class that is sorted by timestamp. Use bisect module to ensure order in event queue.

    Event = collections.namedtuple('Event', ['timestamp', 'data'])
    Event.__eq__ = lambda self, other: self.timestamp == other.timestamp
    Event.__ne__ = lambda self, other: self.timestamp != other.timestamp
    Event.__gt__ = lambda self, other: self.timestamp > other.timestamp
    Event.__ge__ = lambda self, other: self.timestamp >= other.timestamp
    Event.__lt__ = lambda self, other: self.timestamp < other.timestamp
    Event.__le__ = lambda self, other: self.timestamp <= other.timestamp

    def __init__(self):
        super(Bot.Scheduler, self).__init__()
        self._eventq = []
        self._lock = threading.RLock()  # reentrant lock to allow locked method calling locked method
        self._event_handler = None

    def _locked(fn):
        def k(self, *args, **kwargs):
            with self._lock:
                return fn(self, *args, **kwargs)
        return k

    @_locked
    def _insert_event(self, data, when):
        ev = self.Event(when, data)
        bisect.insort(self._eventq, ev)
        return ev

    @_locked
    def _remove_event(self, event):
        # Find event according to its timestamp.
        # Index returned should be one behind.
        i = bisect.bisect(self._eventq, event)

        # Having two events with identical timestamp is unlikely but possible.
        # I am going to move forward and compare timestamp AND object address
        # to make sure the correct object is found.

        while i > 0:
            i -= 1
            e = self._eventq[i]

            if e.timestamp != event.timestamp:
                raise exception.EventNotFound(event)
            elif id(e) == id(event):
                self._eventq.pop(i)
                return

        raise exception.EventNotFound(event)

    @_locked
    def _pop_expired_event(self):
        if not self._eventq:
            return None

        if self._eventq[0].timestamp <= time.time():
            return self._eventq.pop(0)
        else:
            return None

    def event_at(self, when, data):
        """
        Schedule some data to emit at an absolute timestamp.

        :type when: int or float
        :type data: dictionary
        :return: an internal Event object
        """
        return self._insert_event(data, when)

    def event_later(self, delay, data):
        """
        Schedule some data to emit after a number of seconds.

        :type delay: int or float
        :type data: dictionary
        :return: an internal Event object
        """
        return self._insert_event(data, time.time()+delay)

    def event_now(self, data):
        """
        Emit some data as soon as possible.

        :type data: dictionary
        :return: an internal Event object
        """
        return self._insert_event(data, time.time())

    def cancel(self, event):
        """
        Cancel an event.

        :type event: an internal Event object
        """
        self._remove_event(event)

    def run(self):
        while 1:
            e = self._pop_expired_event()
            while e:
                if callable(e.data):
                    d = e.data()  # call the data-producing function
                    if d is not None:
                        self._event_handler(d)
                else:
                    self._event_handler(e.data)

                e = self._pop_expired_event()
            time.sleep(0.1)

    def run_as_thread(self):
        self.daemon = True
        self.start()

    def on_event(self, fn):
        self._event_handler = fn

def __init__(self, token):
    super(Bot, self).__init__(token)

    self._scheduler = self.Scheduler()

    self._router = helper.Router(flavor, {'chat': lambda msg: self.on_chat_message(msg),
                                          'callback_query': lambda msg: self.on_callback_query(msg),
                                          'inline_query': lambda msg: self.on_inline_query(msg),
                                          'chosen_inline_result': lambda msg: self.on_chosen_inline_result(msg)})
                                          # use lambda to delay evaluation of self.on_ZZZ to runtime because
                                          # I don't want to require defining all methods right here.

@property
def scheduler(self):
    return self._scheduler

@property
def router(self):
    return self._router

def handle(self, msg):
    self._router.route(msg)

def _api_request(self, method, params=None, files=None, **kwargs):
    return api.request((self._token, method, params, files), **kwargs)

def _api_request_with_file(self, method, params, file_key, file_value, **kwargs):
    if _isstring(file_value):
        params[file_key] = file_value
        return self._api_request(method, _rectify(params), **kwargs)
    else:
        files = {file_key: file_value}
        return self._api_request(method, _rectify(params), files, **kwargs)

def getMe(self):
    """ See: https://core.telegram.org/bots/api#getme """
    return self._api_request('getMe')

def sendMessage(self, chat_id, text,
                parse_mode=None,
                disable_web_page_preview=None,
                disable_notification=None,
                reply_to_message_id=None,
                reply_markup=None):
    """ See: https://core.telegram.org/bots/api#sendmessage """
    p = _strip(locals())
    return self._api_request('sendMessage', _rectify(p))

def forwardMessage(self, chat_id, from_chat_id, message_id,
                   disable_notification=None):
    """ See: https://core.telegram.org/bots/api#forwardmessage """
    p = _strip(locals())
    return self._api_request('forwardMessage', _rectify(p))

def sendPhoto(self, chat_id, photo,
              caption=None,
              disable_notification=None,
              reply_to_message_id=None,
              reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#sendphoto

    :param photo:
        - string: ``file_id`` for a photo existing on Telegram servers
        - string: HTTP URL of a photo from the Internet
        - file-like object: obtained by ``open(path, 'rb')``
        - tuple: (filename, file-like object). If the filename contains
          non-ASCII characters and you are using Python 2.7, make sure the
          filename is a unicode string.
    """
    p = _strip(locals(), more=['photo'])
    return self._api_request_with_file('sendPhoto', _rectify(p), 'photo', photo)

def sendAudio(self, chat_id, audio,
              caption=None,
              duration=None,
              performer=None,
              title=None,
              disable_notification=None,
              reply_to_message_id=None,
              reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#sendaudio

    :param audio: Same as ``photo`` in :meth:`telepot.Bot.sendPhoto`
    """
    p = _strip(locals(), more=['audio'])
    return self._api_request_with_file('sendAudio', _rectify(p), 'audio', audio)

def sendDocument(self, chat_id, document,
                 caption=None,
                 disable_notification=None,
                 reply_to_message_id=None,
                 reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#senddocument

    :param document: Same as ``photo`` in :meth:`telepot.Bot.sendPhoto`
    """
    p = _strip(locals(), more=['document'])
    return self._api_request_with_file('sendDocument', _rectify(p), 'document', document)

def sendVideo(self, chat_id, video,
              duration=None,
              width=None,
              height=None,
              caption=None,
              disable_notification=None,
              reply_to_message_id=None,
              reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#sendvideo

    :param video: Same as ``photo`` in :meth:`telepot.Bot.sendPhoto`
    """
    p = _strip(locals(), more=['video'])
    return self._api_request_with_file('sendVideo', _rectify(p), 'video', video)

def sendVoice(self, chat_id, voice,
              caption=None,
              duration=None,
              disable_notification=None,
              reply_to_message_id=None,
              reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#sendvoice

    :param voice: Same as ``photo`` in :meth:`telepot.Bot.sendPhoto`
    """
    p = _strip(locals(), more=['voice'])
    return self._api_request_with_file('sendVoice', _rectify(p), 'voice', voice)

def sendVideoNote(self, chat_id, video_note,
                  duration=None,
                  length=None,
                  disable_notification=None,
                  reply_to_message_id=None,
                  reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#sendvideonote

    :param video_note: Same as ``photo`` in :meth:`telepot.Bot.sendPhoto`

    :param length:
        Although marked as optional, this method does not seem to work without
        it being specified. Supply any integer you want. It seems to have no effect
        on the video note's display size.
    """
    p = _strip(locals(), more=['video_note'])
    return self._api_request_with_file('sendVideoNote', _rectify(p), 'video_note', video_note)

def sendMediaGroup(self, chat_id, media,
                   disable_notification=None,
                   reply_to_message_id=None):
    """
    See: https://core.telegram.org/bots/api#sendmediagroup

    :type media: array of `InputMedia <https://core.telegram.org/bots/api#inputmedia>`_ objects
    :param media:
        To indicate media locations, each InputMedia object's ``media`` field
        should be one of these:

        - string: ``file_id`` for a file existing on Telegram servers
        - string: HTTP URL of a file from the Internet
        - file-like object: obtained by ``open(path, 'rb')``
        - tuple: (form-data name, file-like object)
        - tuple: (form-data name, (filename, file-like object))

        In case of uploading, you may supply customized multipart/form-data
        names for each uploaded file (as in last 2 options above). Otherwise,
        telepot assigns unique names to each uploaded file. Names assigned by
        telepot will not collide with user-supplied names, if any.
    """
    p = _strip(locals(), more=['media'])
    legal_media, files_to_attach = _split_input_media_array(media)

    p['media'] = legal_media
    return self._api_request('sendMediaGroup', _rectify(p), files_to_attach)

def sendLocation(self, chat_id, latitude, longitude,
                 live_period=None,
                 disable_notification=None,
                 reply_to_message_id=None,
                 reply_markup=None):
    """ See: https://core.telegram.org/bots/api#sendlocation """
    p = _strip(locals())
    return self._api_request('sendLocation', _rectify(p))

def editMessageLiveLocation(self, msg_identifier, latitude, longitude,
                            reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#editmessagelivelocation

    :param msg_identifier: Same as in :meth:`.Bot.editMessageText`
    """
    p = _strip(locals(), more=['msg_identifier'])
    p.update(_dismantle_message_identifier(msg_identifier))
    return self._api_request('editMessageLiveLocation', _rectify(p))

def stopMessageLiveLocation(self, msg_identifier,
                            reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#stopmessagelivelocation

    :param msg_identifier: Same as in :meth:`.Bot.editMessageText`
    """
    p = _strip(locals(), more=['msg_identifier'])
    p.update(_dismantle_message_identifier(msg_identifier))
    return self._api_request('stopMessageLiveLocation', _rectify(p))

def sendVenue(self, chat_id, latitude, longitude, title, address,
              foursquare_id=None,
              disable_notification=None,
              reply_to_message_id=None,
              reply_markup=None):
    """ See: https://core.telegram.org/bots/api#sendvenue """
    p = _strip(locals())
    return self._api_request('sendVenue', _rectify(p))

def sendContact(self, chat_id, phone_number, first_name,
                last_name=None,
                disable_notification=None,
                reply_to_message_id=None,
                reply_markup=None):
    """ See: https://core.telegram.org/bots/api#sendcontact """
    p = _strip(locals())
    return self._api_request('sendContact', _rectify(p))

def sendGame(self, chat_id, game_short_name,
             disable_notification=None,
             reply_to_message_id=None,
             reply_markup=None):
    """ See: https://core.telegram.org/bots/api#sendgame """
    p = _strip(locals())
    return self._api_request('sendGame', _rectify(p))

def sendInvoice(self, chat_id, title, description, payload,
                provider_token, start_parameter, currency, prices,
                provider_data=None,
                photo_url=None,
                photo_size=None,
                photo_width=None,
                photo_height=None,
                need_name=None,
                need_phone_number=None,
                need_email=None,
                need_shipping_address=None,
                is_flexible=None,
                disable_notification=None,
                reply_to_message_id=None,
                reply_markup=None):
    """ See: https://core.telegram.org/bots/api#sendinvoice """
    p = _strip(locals())
    return self._api_request('sendInvoice', _rectify(p))

def sendChatAction(self, chat_id, action):
    """ See: https://core.telegram.org/bots/api#sendchataction """
    p = _strip(locals())
    return self._api_request('sendChatAction', _rectify(p))

def getUserProfilePhotos(self, user_id,
                         offset=None,
                         limit=None):
    """ See: https://core.telegram.org/bots/api#getuserprofilephotos """
    p = _strip(locals())
    return self._api_request('getUserProfilePhotos', _rectify(p))

def getFile(self, file_id):
    """ See: https://core.telegram.org/bots/api#getfile """
    p = _strip(locals())
    return self._api_request('getFile', _rectify(p))

def kickChatMember(self, chat_id, user_id,
                   until_date=None):
    """ See: https://core.telegram.org/bots/api#kickchatmember """
    p = _strip(locals())
    return self._api_request('kickChatMember', _rectify(p))

def unbanChatMember(self, chat_id, user_id):
    """ See: https://core.telegram.org/bots/api#unbanchatmember """
    p = _strip(locals())
    return self._api_request('unbanChatMember', _rectify(p))

def restrictChatMember(self, chat_id, user_id,
                       until_date=None,
                       can_send_messages=None,
                       can_send_media_messages=None,
                       can_send_other_messages=None,
                       can_add_web_page_previews=None):
    """ See: https://core.telegram.org/bots/api#restrictchatmember """
    p = _strip(locals())
    return self._api_request('restrictChatMember', _rectify(p))

def promoteChatMember(self, chat_id, user_id,
                      can_change_info=None,
                      can_post_messages=None,
                      can_edit_messages=None,
                      can_delete_messages=None,
                      can_invite_users=None,
                      can_restrict_members=None,
                      can_pin_messages=None,
                      can_promote_members=None):
    """ See: https://core.telegram.org/bots/api#promotechatmember """
    p = _strip(locals())
    return self._api_request('promoteChatMember', _rectify(p))

def exportChatInviteLink(self, chat_id):

    """ See: https://core.telegram.org/bots/api#exportchatinvitelink """
    p = _strip(locals())
    return self._api_request('exportChatInviteLink', _rectify(p))

def relay_to_collector(update):
    key = _find_first_key(update,['message','channel_post','edited_channel_post','edited_message','callback_query','inline_query','chosen_inline_result'])
    collect_queue.put(update[key])
    return update['update_id']    
def setChatPhoto(self, chat_id, photo):
    """ See: https://core.telegram.org/bots/api#setchatphoto """
    p = _strip(locals(), more=['photo'])
    return self._api_request_with_file('setChatPhoto', _rectify(p), 'photo', photo)

def deleteChatPhoto(self, chat_id):
    """ See: https://core.telegram.org/bots/api#deletechatphoto """
    p = _strip(locals())
    return self._api_request('deleteChatPhoto', _rectify(p))

def setChatTitle(self, chat_id, title):
    """ See: https://core.telegram.org/bots/api#setchattitle """
    p = _strip(locals())
    return self._api_request('setChatTitle', _rectify(p))

def setChatDescription(self, chat_id,
                       description=None):
    """ See: https://core.telegram.org/bots/api#setchatdescription """
    p = _strip(locals())
    return self._api_request('setChatDescription', _rectify(p))

def pinChatMessage(self, chat_id, message_id,
                   disable_notification=None):
    """ See: https://core.telegram.org/bots/api#pinchatmessage """
    p = _strip(locals())
    return self._api_request('pinChatMessage', _rectify(p))

def unpinChatMessage(self, chat_id):
    """ See: https://core.telegram.org/bots/api#unpinchatmessage """
    p = _strip(locals())
    return self._api_request('unpinChatMessage', _rectify(p))

def leaveChat(self, chat_id):
    """ See: https://core.telegram.org/bots/api#leavechat """
    p = _strip(locals())
    return self._api_request('leaveChat', _rectify(p))

def getChat(self, chat_id):
    """ See: https://core.telegram.org/bots/api#getchat """
    p = _strip(locals())
    return self._api_request('getChat', _rectify(p))

def getChatAdministrators(self, chat_id):
    """ See: https://core.telegram.org/bots/api#getchatadministrators """
    p = _strip(locals())
    return self._api_request('getChatAdministrators', _rectify(p))

def getChatMembersCount(self, chat_id):
    """ See: https://core.telegram.org/bots/api#getchatmemberscount """
    p = _strip(locals())
    return self._api_request('getChatMembersCount', _rectify(p))

def getChatMember(self, chat_id, user_id):
    """ See: https://core.telegram.org/bots/api#getchatmember """
    p = _strip(locals())
    return self._api_request('getChatMember', _rectify(p))

def setChatStickerSet(self, chat_id, sticker_set_name):
    """ See: https://core.telegram.org/bots/api#setchatstickerset """
    p = _strip(locals())
    return self._api_request('setChatStickerSet', _rectify(p))

def deleteChatStickerSet(self, chat_id):
    """ See: https://core.telegram.org/bots/api#deletechatstickerset """
    p = _strip(locals())
    return self._api_request('deleteChatStickerSet', _rectify(p))

def answerCallbackQuery(self, callback_query_id,
                        text=None,
                        show_alert=None,
                        url=None,
                        cache_time=None):
    """ See: https://core.telegram.org/bots/api#answercallbackquery """
    p = _strip(locals())
    return self._api_request('answerCallbackQuery', _rectify(p))

def answerShippingQuery(self, shipping_query_id, ok,
                        shipping_options=None,
                        error_message=None):
    """ See: https://core.telegram.org/bots/api#answershippingquery """
    p = _strip(locals())
    return self._api_request('answerShippingQuery', _rectify(p))

def answerPreCheckoutQuery(self, pre_checkout_query_id, ok,
                           error_message=None):
    """ See: https://core.telegram.org/bots/api#answerprecheckoutquery """
    p = _strip(locals())
    return self._api_request('answerPreCheckoutQuery', _rectify(p))

def editMessageText(self, msg_identifier, text,
                    parse_mode=None,
                    disable_web_page_preview=None,
                    reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#editmessagetext

    :param msg_identifier:
        a 2-tuple (``chat_id``, ``message_id``),
        a 1-tuple (``inline_message_id``),
        or simply ``inline_message_id``.
        You may extract this value easily with :meth:`telepot.message_identifier`
    """
    p = _strip(locals(), more=['msg_identifier'])
    p.update(_dismantle_message_identifier(msg_identifier))
    return self._api_request('editMessageText', _rectify(p))

def editMessageCaption(self, msg_identifier,
                       caption=None,
                       reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#editmessagecaption

    :param msg_identifier: Same as ``msg_identifier`` in :meth:`telepot.Bot.editMessageText`
    """
    p = _strip(locals(), more=['msg_identifier'])
    p.update(_dismantle_message_identifier(msg_identifier))
    return self._api_request('editMessageCaption', _rectify(p))

def editMessageReplyMarkup(self, msg_identifier,
                           reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#editmessagereplymarkup

    :param msg_identifier: Same as ``msg_identifier`` in :meth:`telepot.Bot.editMessageText`
    """
    p = _strip(locals(), more=['msg_identifier'])
    p.update(_dismantle_message_identifier(msg_identifier))
    return self._api_request('editMessageReplyMarkup', _rectify(p))

def deleteMessage(self, msg_identifier):
    """
    See: https://core.telegram.org/bots/api#deletemessage

    :param msg_identifier:
        Same as ``msg_identifier`` in :meth:`telepot.Bot.editMessageText`,
        except this method does not work on inline messages.
    """
    p = _strip(locals(), more=['msg_identifier'])
    p.update(_dismantle_message_identifier(msg_identifier))
    return self._api_request('deleteMessage', _rectify(p))

def sendSticker(self, chat_id, sticker,
                disable_notification=None,
                reply_to_message_id=None,
                reply_markup=None):
    """
    See: https://core.telegram.org/bots/api#sendsticker

    :param sticker: Same as ``photo`` in :meth:`telepot.Bot.sendPhoto`
    """
    p = _strip(locals(), more=['sticker'])
    return self._api_request_with_file('sendSticker', _rectify(p), 'sticker', sticker)

def getStickerSet(self, name):
    """
    See: https://core.telegram.org/bots/api#getstickerset
    """
    p = _strip(locals())
    return self._api_request('getStickerSet', _rectify(p))

def uploadStickerFile(self, user_id, png_sticker):
    """
    See: https://core.telegram.org/bots/api#uploadstickerfile
    """
    p = _strip(locals(), more=['png_sticker'])
    return self._api_request_with_file('uploadStickerFile', _rectify(p), 'png_sticker', png_sticker)

def createNewStickerSet(self, user_id, name, title, png_sticker, emojis,
                        contains_masks=None,
                        mask_position=None):
    """
    See: https://core.telegram.org/bots/api#createnewstickerset
    """
    p = _strip(locals(), more=['png_sticker'])
    return self._api_request_with_file('createNewStickerSet', _rectify(p), 'png_sticker', png_sticker)

def addStickerToSet(self, user_id, name, png_sticker, emojis,
                    mask_position=None):
    """
    See: https://core.telegram.org/bots/api#addstickertoset
    """
    p = _strip(locals(), more=['png_sticker'])
    return self._api_request_with_file('addStickerToSet', _rectify(p), 'png_sticker', png_sticker)

def setStickerPositionInSet(self, sticker, position):
    """
    See: https://core.telegram.org/bots/api#setstickerpositioninset
    """
    p = _strip(locals())
    return self._api_request('setStickerPositionInSet', _rectify(p))

def deleteStickerFromSet(self, sticker):
    """
    See: https://core.telegram.org/bots/api#deletestickerfromset
    """
    p = _strip(locals())
    return self._api_request('deleteStickerFromSet', _rectify(p))

def answerInlineQuery(self, inline_query_id, results,
                      cache_time=None,
                      is_personal=None,
                      next_offset=None,
                      switch_pm_text=None,
                      switch_pm_parameter=None):
    """ See: https://core.telegram.org/bots/api#answerinlinequery """
    p = _strip(locals())
    return self._api_request('answerInlineQuery', _rectify(p))

def getUpdates(self,
               offset=None,
               limit=None,
               timeout=None,
               allowed_updates=None):
    """ See: https://core.telegram.org/bots/api#getupdates """
    p = _strip(locals())
    return self._api_request('getUpdates', _rectify(p))

def setWebhook(self,
               url=None,
               certificate=None,
               max_connections=None,
               allowed_updates=None):
    """ See: https://core.telegram.org/bots/api#setwebhook """
    p = _strip(locals(), more=['certificate'])

    if certificate:
        files = {'certificate': certificate}
        return self._api_request('setWebhook', _rectify(p), files)
    else:
        return self._api_request('setWebhook', _rectify(p))

def deleteWebhook(self):
    """ See: https://core.telegram.org/bots/api#deletewebhook """
    return self._api_request('deleteWebhook')

def getWebhookInfo(self):
    """ See: https://core.telegram.org/bots/api#getwebhookinfo """
    return self._api_request('getWebhookInfo')

def setGameScore(self, user_id, score, game_message_identifier,
                 force=None,
                 disable_edit_message=None):
    """
    See: https://core.telegram.org/bots/api#setgamescore

    :param game_message_identifier: Same as ``msg_identifier`` in :meth:`telepot.Bot.editMessageText`
    """
    p = _strip(locals(), more=['game_message_identifier'])
    p.update(_dismantle_message_identifier(game_message_identifier))
    return self._api_request('setGameScore', _rectify(p))

def getGameHighScores(self, user_id, game_message_identifier):
    """
    See: https://core.telegram.org/bots/api#getgamehighscores

    :param game_message_identifier: Same as ``msg_identifier`` in :meth:`telepot.Bot.editMessageText`
    """
    p = _strip(locals(), more=['game_message_identifier'])
    p.update(_dismantle_message_identifier(game_message_identifier))
    return self._api_request('getGameHighScores', _rectify(p))

def download_file(self, file_id, dest):
    """
    Download a file to local disk.

    :param dest: a path or a ``file`` object
    """
    f = self.getFile(file_id)
    try:
        d = dest if _isfile(dest) else open(dest, 'wb')

        r = api.download((self._token, f['file_path']), preload_content=False)

        while 1:
            data = r.read(self._file_chunk_size)
            if not data:
                break
            d.write(data)
    finally:
        if not _isfile(dest) and 'd' in locals():
            d.close()

        if 'r' in locals():
            r.release_conn()

def message_loop(self, callback=None, relax=0.1,
                 timeout=20, allowed_updates=None,
                 source=None, ordered=True, maxhold=3,
                 run_forever=False):
    """
    :deprecated: will be removed in future. Use :class:`.MessageLoop` instead.

    Spawn a thread to constantly ``getUpdates`` or pull updates from a queue.
    Apply ``callback`` to every message received. Also starts the scheduler thread
    for internal events.

    :param callback:
        a function that takes one argument (the message), or a routing table.
        If ``None``, the bot's ``handle`` method is used.

    A *routing table* is a dictionary of ``{flavor: function}``, mapping messages to appropriate
    handler functions according to their flavors. It allows you to define functions specifically
    to handle one flavor of messages. It usually looks like this: ``{'chat': fn1,
    'callback_query': fn2, 'inline_query': fn3, ...}``. Each handler function should take
    one argument (the message).

    :param source:
        Source of updates.
        If ``None``, ``getUpdates`` is used to obtain new messages from Telegram servers.
        If it is a synchronized queue (``Queue.Queue`` in Python 2.7 or
        ``queue.Queue`` in Python 3), new messages are pulled from the queue.
        A web application implementing a webhook can dump updates into the queue,
        while the bot pulls from it. This is how telepot can be integrated with webhooks.

    Acceptable contents in queue:

    - ``str``, ``unicode`` (Python 2.7), or ``bytes`` (Python 3, decoded using UTF-8)
      representing a JSON-serialized `Update <https://core.telegram.org/bots/api#update>`_ object.
    - a ``dict`` representing an Update object.

    When ``source`` is ``None``, these parameters are meaningful:

    :type relax: float
    :param relax: seconds between each ``getUpdates``

    :type timeout: int
    :param timeout:
        ``timeout`` parameter supplied to :meth:`telepot.Bot.getUpdates`,
        controlling how long to poll.

    :type allowed_updates: array of string
    :param allowed_updates:
        ``allowed_updates`` parameter supplied to :meth:`telepot.Bot.getUpdates`,
        controlling which types of updates to receive.

    When ``source`` is a queue, these parameters are meaningful:

    :type ordered: bool
    :param ordered:
        If ``True``, ensure in-order delivery of messages to ``callback``
        (i.e. updates with a smaller ``update_id`` always come before those with
        a larger ``update_id``).
        If ``False``, no re-ordering is done. ``callback`` is applied to messages
        as soon as they are pulled from queue.

    :type maxhold: float
    :param maxhold:
        Applied only when ``ordered`` is ``True``. The maximum number of seconds
        an update is held waiting for a not-yet-arrived smaller ``update_id``.
        When this number of seconds is up, the update is delivered to ``callback``
        even if some smaller ``update_id``\s have not yet arrived. If those smaller
        ``update_id``\s arrive at some later time, they are discarded.

    Finally, there is this parameter, meaningful always:

    :type run_forever: bool or str
    :param run_forever:
        If ``True`` or any non-empty string, append an infinite loop at the end of
        this method, so it never returns. Useful as the very last line in a program.
        A non-empty string will also be printed, useful as an indication that the
        program is listening.
    """
    if callback is None:
        callback = self.handle
    elif isinstance(callback, dict):
        callback = flavor_router(callback)

    collect_queue = queue.Queue()

    def collector():
        while 1:
            try:
                item = collect_queue.get(block=True)
                callback(item)
            except:
                # Localize error so thread can keep going.
                traceback.print_exc()

    def relay_to_collector(update):
        key = _find_first_key(update, ['message',
                                       'edited_message',
                                       'channel_post',
                                       'edited_channel_post',
                                       'callback_query',
                                       'inline_query',
                                       'chosen_inline_result',
                                       'shipping_query',
                                       'pre_checkout_query'])
        collect_queue.put(update[key])
        return update['update_id']

    def get_from_telegram_server():
        offset = None  # running offset
        allowed_upd = allowed_updates
        while 1:
            try:
                result = self.getUpdates(offset=offset,
                                         timeout=timeout,
                                         allowed_updates=allowed_upd)

                # Once passed, this parameter is no longer needed.
                allowed_upd = None

                if len(result) > 0:
                    # No sort. Trust server to give messages in correct order.
                    # Update offset to max(update_id) + 1
                    offset = max([relay_to_collector(update) for update in result]) + 1

            except exception.BadHTTPResponse as e:
                traceback.print_exc()

                # Servers probably down. Wait longer.
                if e.status == 502:
                    time.sleep(30)
            except:
                traceback.print_exc()
            finally:
                time.sleep(relax)

    def dictify3(data):
        if type(data) is bytes:
            return json.loads(data.decode('utf-8'))
        elif type(data) is str:
            return json.loads(data)
        elif type(data) is dict:
            return data
        else:
            raise ValueError()

    def dictify27(data):
        if type(data) in [str, unicode]:
            return json.loads(data)
        elif type(data) is dict:
            return data
        else:
            raise ValueError()

    def get_from_queue_unordered(qu):
        dictify = dictify3 if sys.version_info >= (3,) else dictify27
        while 1:
            try:
                data = qu.get(block=True)
                update = dictify(data)
                relay_to_collector(update)
            except:
                traceback.print_exc()

    def get_from_queue(qu):
        dictify = dictify3 if sys.version_info >= (3,) else dictify27

        # Here is the re-ordering mechanism, ensuring in-order delivery of updates.
        max_id = None                 # max update_id passed to callback
        buffer = collections.deque()  # keep those updates which skip some update_id
        qwait = None                  # how long to wait for updates,
                                      # because buffer's content has to be returned in time.

        while 1:
            try:
                data = qu.get(block=True, timeout=qwait)
                update = dictify(data)

                if max_id is None:
                    # First message received, handle regardless.
                    max_id = relay_to_collector(update)

                elif update['update_id'] == max_id + 1:
                    # No update_id skipped, handle naturally.
                    max_id = relay_to_collector(update)

                    # clear contagious updates in buffer
                    if len(buffer) > 0:
                        buffer.popleft()  # first element belongs to update just received, useless now.
                        while 1:
                            try:
                                if type(buffer[0]) is dict:
                                    max_id = relay_to_collector(buffer.popleft())  # updates that arrived earlier, handle them.
                                else:
                                    break  # gap, no more contagious updates
                            except IndexError:
                                break  # buffer empty

                elif update['update_id'] > max_id + 1:
                    # Update arrives pre-maturely, insert to buffer.
                    nbuf = len(buffer)
                    if update['update_id'] <= max_id + nbuf:
                        # buffer long enough, put update at position
                        buffer[update['update_id'] - max_id - 1] = update
                    else:
                        # buffer too short, lengthen it
                        expire = time.time() + maxhold
                        for a in range(nbuf, update['update_id']-max_id-1):
                            buffer.append(expire)  # put expiry time in gaps
                        buffer.append(update)

                else:
                    pass  # discard

            except queue.Empty:
                # debug message
                # print('Timeout')

                # some buffer contents have to be handled
                # flush buffer until a non-expired time is encountered
                while 1:
                    try:
                        if type(buffer[0]) is dict:
                            max_id = relay_to_collector(buffer.popleft())
                        else:
                            expire = buffer[0]
                            if expire <= time.time():
                                max_id += 1
                                buffer.popleft()
                            else:
                                break  # non-expired
                    except IndexError:
                        break  # buffer empty
            except:
                traceback.print_exc()
            finally:
                try:
                    # don't wait longer than next expiry time
                    qwait = buffer[0] - time.time()
                    if qwait < 0:
                        qwait = 0
                except IndexError:
                    # buffer empty, can wait forever
                    qwait = None

                # debug message
                # print ('Buffer:', str(buffer), ', To Wait:', qwait, ', Max ID:', max_id)

    collector_thread = threading.Thread(target=collector)
    collector_thread.daemon = True
    collector_thread.start()

    if source is None:
        message_thread = threading.Thread(target=get_from_telegram_server)
    elif isinstance(source, queue.Queue):
        if ordered:
            message_thread = threading.Thread(target=get_from_queue, args=(source,))
        else:
            message_thread = threading.Thread(target=get_from_queue_unordered, args=(source,))
    else:
        raise ValueError('Invalid source')

    message_thread.daemon = True  # need this for main thread to be killable by Ctrl-C
    message_thread.start()

    self._scheduler.on_event(collect_queue.put)
    self._scheduler.run_as_thread()

    if run_forever:
        if _isstring(run_forever):
            print(run_forever)
        while 1:
            time.sleep(10)

import inspect

class SpeakerBot(Bot): def init(self, token): super(SpeakerBot, self).init(token) self._mic = helper.Microphone()

@property
def mic(self):
    return self._mic

def create_listener(self):
    q = queue.Queue()
    self._mic.add(q)
    ln = helper.Listener(self._mic, q)
    return ln

class DelegatorBot(SpeakerBot): def init(self, token, delegation_patterns): """ :param delegation_patterns: a list of (seeder, delegator) tuples. """ super(DelegatorBot, self).init(token) self._delegate_records = [p+({},) for p in delegation_patterns]

def _startable(self, delegate):
    return ((hasattr(delegate, 'start') and inspect.ismethod(delegate.start)) and
            (hasattr(delegate, 'is_alive') and inspect.ismethod(delegate.is_alive)))

def _tuple_is_valid(self, t):
    return len(t) == 3 and callable(t[0]) and type(t[1]) in [list, tuple] and type(t[2]) is dict

def _ensure_startable(self, delegate):
    if self._startable(delegate):
        return delegate
    elif callable(delegate):
        return threading.Thread(target=delegate)
    elif type(delegate) is tuple and self._tuple_is_valid(delegate):
        func, args, kwargs = delegate
        return threading.Thread(target=func, args=args, kwargs=kwargs)
    else:
        raise RuntimeError('Delegate does not have the required methods, is not callable, and is not a valid tuple.')

def handle(self, msg):
    self._mic.send(msg)

    for calculate_seed, make_delegate, dict in self._delegate_records:
        id = calculate_seed(msg)

        if id is None:
            continue
        elif isinstance(id, collections.Hashable):
            if id not in dict or not dict[id].is_alive():
                d = make_delegate((self, msg, id))
                d = self._ensure_startable(d)

                dict[id] = d
                dict[id].start()
        else:
            d = make_delegate((self, msg, id))
            d = self._ensure_startable(d)
            d.start()
Aditya9009 commented 6 years ago

Any way to create a new flavor for msgs? @nickoala