Skip to content

Commit 6939c39

Browse files
committed
Splitted Helpers from the types
Added EventBus mechanism (+ tests) Better to dict implementation
1 parent eb726ea commit 6939c39

File tree

7 files changed

+278
-177
lines changed

7 files changed

+278
-177
lines changed

telebot/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22
from telebot import telegram_types as types
3+
from telebot import type_helpers as helpers
34
from telebot import util
45
from telebot import listeners
56
from telebot import apihelper

telebot/listeners.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@
66
from telebot import util
77

88

9-
def same_chat(chat):
10-
return lambda message: message.chat.id == chat.id
9+
def _same_chat(message):
10+
return lambda m: m.chat.id == message.chat.id
1111

1212

13-
def has_content_types(content_types):
13+
def _has_content_types(content_types):
1414
return lambda m: m.content_type in content_types
1515

1616

17-
def has_commands(commands):
17+
def _has_commands(commands):
1818
return lambda m: m.content_type == 'text' and util.extract_command(m.text) in commands
1919

2020

21-
def has_regex(regex):
21+
def _has_regex(regex):
2222
return lambda m: m.content_type == 'text' and re.search(regex, m.text)
2323

2424

@@ -47,24 +47,45 @@ def remove_event_listener(self, listener):
4747
return False
4848

4949
def dispatch(self, event, *args, **kwargs):
50+
"""
51+
Dispatches an event to registered listeners
52+
All listeners that registered to the event are called until one returns a truthy value.
53+
54+
Usage:
55+
>>> bus.dispatch('some_event', some_argument, extra=keyword_argument)
56+
57+
:param event: An event. May be an int, string or any unique identifier for this event. This parameter
58+
should be the same as the listeners used to register for this event using append_event_listener().
59+
:param args: Any arguments passed to the listeners
60+
:param kwargs: Any keyword arguments passed to the listeners
61+
:return: False if no listeners accepted this event and True otherwise
62+
"""
5063
if event not in self.event_listeners:
5164
util.logger.debug("Event {0} dispatched but no listeners".format(event))
52-
return
65+
return False
5366

5467
for listener in self.event_listeners[event]:
5568
if listener(*args, **kwargs):
56-
break
69+
return True
70+
71+
util.logger.debug("No listener accepted the event")
72+
return False
5773

5874

5975
class GeneratorListener:
76+
"""
77+
This listener sends messages to a generator until that generator raises StopIteration and then
78+
it removes itself from the event listeners.
6079
61-
def __init__(self, event_bus, chat_id, generator):
80+
This listener only accepts messages with for which `func` returns True
81+
"""
82+
def __init__(self, event_bus, func, generator):
6283
self.event_bus = event_bus
63-
self.chat_id = chat_id
84+
self.func = func
6485
self.generator = generator
6586

6687
def __call__(self, message):
67-
if message.chat.id != self.chat_id:
88+
if not self.func(message):
6889
return False
6990

7091
try:
@@ -82,13 +103,13 @@ def __init__(self, event_bus, handler, commands=None, regexp=None, func=None, co
82103
self.handler = handler
83104
self.tests = []
84105
if content_types is not None:
85-
self.tests.append(has_content_types(content_types))
106+
self.tests.append(_has_content_types(content_types))
86107

87108
if commands is not None:
88-
self.tests.append(has_commands(commands))
109+
self.tests.append(_has_commands(commands))
89110

90111
if regexp is not None:
91-
self.tests.append(has_regex(regexp))
112+
self.tests.append(_has_regex(regexp))
92113

93114
if func is not None:
94115
self.tests.append(func)
@@ -100,7 +121,8 @@ def __call__(self, message):
100121
returned = self.handler(message)
101122
if isinstance(returned, types.GeneratorType):
102123
returned.next()
103-
generator_listener = GeneratorListener(self.event_bus, message.chat.id, returned)
124+
generator_listener = GeneratorListener(self.event_bus, _same_chat(message), returned)
125+
# Generators are prioritized over ordinary listeners
104126
self.event_bus.prepend_event_listener('on_message', generator_listener)
105127
return True
106128

telebot/telegram_types.py

Lines changed: 7 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# -*- coding: utf-8 -*-
22

33
import json
4-
import six
54

65
import telebot.util as util
76

@@ -43,8 +42,7 @@ def to_json(obj):
4342

4443
class Dictionaryable:
4544
"""
46-
Subclasses of this class are guaranteed to be able to be converted to dictionary.
47-
All subclasses of this class must override to_dict.
45+
Subclasses are guaranteed to be able to be converted to a dictionary using to_dict()
4846
"""
4947
pass
5048

@@ -149,9 +147,9 @@ def __init__(self, message_id=None, from_user=None, date=None, chat=None,
149147
self.migrate_from_chat_id = migrate_from_chat_id
150148
self.pinned_message = de_json(Message, pinned_message)
151149

152-
self.content_type = self.determine_content_type()
150+
self.content_type = self.__determine_content_type()
153151

154-
def determine_content_type(self):
152+
def __determine_content_type(self):
155153
for content_type in self.CONTENT_TYPES:
156154
if getattr(self, content_type) is not None:
157155
return content_type
@@ -294,53 +292,11 @@ def __init__(self, selective=None):
294292

295293

296294
class ReplyKeyboardMarkup(Dictionaryable):
297-
def __init__(self, resize_keyboard=None, one_time_keyboard=None, selective=None, row_width=3):
295+
def __init__(self, keyboard, resize_keyboard=None, one_time_keyboard=None, selective=None):
296+
self.keyboard = keyboard
298297
self.resize_keyboard = resize_keyboard
299298
self.one_time_keyboard = one_time_keyboard
300299
self.selective = selective
301-
self.row_width = row_width
302-
303-
self.keyboard = []
304-
305-
def add(self, *args):
306-
"""
307-
This function adds strings to the keyboard, while not exceeding row_width.
308-
E.g. ReplyKeyboardMarkup#add("A", "B", "C") yields the json result {keyboard: [["A"], ["B"], ["C"]]}
309-
when row_width is set to 1.
310-
When row_width is set to 2, the following is the result of this function: {keyboard: [["A", "B"], ["C"]]}
311-
See https://core.telegram.org/bots/api#replykeyboardmarkup
312-
:param args: KeyboardButton to append to the keyboard
313-
"""
314-
i = 1
315-
row = []
316-
for button in args:
317-
if util.is_string(button):
318-
row.append({'text': button})
319-
else:
320-
row.append(to_dict(button))
321-
if i % self.row_width == 0:
322-
self.keyboard.append(row)
323-
row = []
324-
i += 1
325-
if len(row) > 0:
326-
self.keyboard.append(row)
327-
328-
def row(self, *args):
329-
"""
330-
Adds a list of KeyboardButton to the keyboard. This function does not consider row_width.
331-
ReplyKeyboardMarkup#row("A")#row("B", "C")#to_json() outputs '{keyboard: [["A"], ["B", "C"]]}'
332-
See https://core.telegram.org/bots/api#replykeyboardmarkup
333-
:param args: strings
334-
:return: self, to allow function chaining.
335-
"""
336-
btn_array = []
337-
for button in args:
338-
if util.is_string(button):
339-
btn_array.append({'text': button})
340-
else:
341-
btn_array.append(button.to_dic())
342-
self.keyboard.append(btn_array)
343-
return self
344300

345301

346302
class KeyboardButton(Dictionaryable):
@@ -350,46 +306,9 @@ def __init__(self, text, request_contact=None, request_location=None):
350306
self.request_location = request_location
351307

352308

353-
@util.json_exclude('row_width')
354309
class InlineKeyboardMarkup(Dictionaryable):
355-
def __init__(self, row_width=3):
356-
self.row_width = row_width
357-
358-
self.keyboard = []
359-
360-
def add(self, *args):
361-
"""
362-
This function adds strings to the keyboard, while not exceeding row_width.
363-
E.g. ReplyKeyboardMarkup#add("A", "B", "C") yields the json result {keyboard: [["A"], ["B"], ["C"]]}
364-
when row_width is set to 1.
365-
When row_width is set to 2, the following is the result of this function: {keyboard: [["A", "B"], ["C"]]}
366-
See https://core.telegram.org/bots/api#replykeyboardmarkup
367-
:param args: KeyboardButton to append to the keyboard
368-
"""
369-
i = 1
370-
row = []
371-
for button in args:
372-
row.append(to_dict(button))
373-
if i % self.row_width == 0:
374-
self.keyboard.append(row)
375-
row = []
376-
i += 1
377-
if len(row) > 0:
378-
self.keyboard.append(row)
379-
380-
def row(self, *args):
381-
"""
382-
Adds a list of KeyboardButton to the keyboard. This function does not consider row_width.
383-
ReplyKeyboardMarkup#row("A")#row("B", "C")#to_json() outputs '{keyboard: [["A"], ["B", "C"]]}'
384-
See https://core.telegram.org/bots/api#inlinekeyboardmarkup
385-
:param args: strings
386-
:return: self, to allow function chaining.
387-
"""
388-
btn_array = []
389-
for button in args:
390-
btn_array.append(to_dict(button))
391-
self.keyboard.append(btn_array)
392-
return self
310+
def __init__(self, inline_keyboard):
311+
self.inline_keyboard = inline_keyboard
393312

394313

395314
class InlineKeyboardButton(Dictionaryable):
@@ -479,54 +398,3 @@ def __init__(self, phone_number, first_name, last_name=None):
479398
self.first_name = first_name
480399
self.last_name = last_name
481400

482-
483-
class InlineQueryResultFactory:
484-
TYPES = [
485-
'article', 'audio', 'contact', 'document', 'gif', 'location',
486-
'mpeg4_gif', 'photo', 'venue', 'video', 'voice'
487-
]
488-
CACHED_TYPES = ['photo', 'gif', 'mpeg4_gif', 'sticker', 'document', 'video', 'voice', 'audio']
489-
__CACHED_FILE_ID_KEYS = {
490-
'photo': 'photo_file_id',
491-
'gif': 'gif_file_id',
492-
'mpeg4_gif': 'mpeg4_file_id',
493-
'sticker': 'sticker_file_id',
494-
'document': 'document_file_id',
495-
'video': 'video_file_id',
496-
'voice': 'voice_file_id',
497-
'audio': 'audio_file_id'
498-
}
499-
500-
def __init__(self):
501-
raise NotImplementedError('Instantiation not allowed')
502-
503-
@staticmethod
504-
def create_result(type, id, **kwargs):
505-
if type not in InlineQueryResultFactory.TYPES:
506-
raise ValueError('Unknown type: {0}. Known types: {1}'.format(type, InlineQueryResultFactory.TYPES))
507-
508-
json_dict = kwargs
509-
json_dict['type'] = type
510-
json_dict['id'] = id
511-
512-
for k, v in six.iteritems(json_dict):
513-
json_dict[k] = to_dict(v)
514-
515-
return json_dict
516-
517-
@staticmethod
518-
def create_cached_result(type, id, file_id, **kwargs):
519-
if type not in InlineQueryResultFactory.CACHED_TYPES:
520-
raise ValueError(
521-
'Unknown cached type: {0}. Known types: {1}'.format(type, InlineQueryResultFactory.CACHED_TYPES)
522-
)
523-
524-
json_dict = kwargs
525-
json_dict['type'] = type
526-
json_dict['id'] = id
527-
json_dict[InlineQueryResultFactory.__CACHED_FILE_ID_KEYS[type]] = file_id
528-
529-
for k, v in six.iteritems(json_dict):
530-
json_dict[k] = to_dict(v)
531-
532-
return json_dict

0 commit comments

Comments
 (0)