├── .gitignore ├── README.md ├── samples ├── files │ ├── ACDC_back_in_black_sample.ogg │ ├── another_day_in_paradise_sample.ogg │ ├── bubblegum.png │ ├── cat1.jpeg │ ├── cat2.jpeg │ ├── gunter.png │ ├── sample1.odt │ └── sample2.ods ├── send_audio_bot.py ├── send_document_bot.py ├── send_location_bot.py ├── send_photo_bot.py ├── send_sticker_bot.py └── simple_echo_bot.py ├── setup.py └── telegram ├── __init__.py └── botapi ├── __init__.py ├── actions.py ├── api.py ├── bot.py ├── botbuilder.py ├── connector.py ├── tests ├── __init__.py ├── test_connector.py ├── test_util.py ├── testdata.py └── testtools.py └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | samples/api_key.txt 2 | __pycache__ 3 | build 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-telegram-bot-api 2 | A python wrapper and bot builder for the telegram bot api. 3 | 4 | Can be found on PyPI https://pypi.python.org/pypi/telegram-bot-api/0.2 5 | 6 | ## Some simple samples 7 | The simplest way to build a bot with this package is the botbuilder, here are some short and easy ways to create a bot. 8 | 9 | ### A simple echo bot 10 | ```python 11 | import telegram.botapi.botbuilder as botbuilder 12 | 13 | def echo_text(update): 14 | if len(update.text) >= 7: 15 | return update.text[6:] 16 | else: 17 | return "" 18 | 19 | if __name__ == "__main__": 20 | botbuilder.BotBuilder(apikey_file="apikey.txt") \ 21 | .send_message_when("echo", echo_text) \ 22 | .build().start() 23 | ``` 24 | To explain, the `send_message_when` method takes as first argument a command (without the preceding /) and as second 25 | argument a function which acts upon the update which trigged the event (a /echo command) and returns a response message. 26 | Long story short, you send `/echo foobar` to the bot, and it answers with `foobar`. 27 | 28 | ### A simple photo-bot with a logger 29 | ```python 30 | import telegram.botapi.botbuilder as botbuilder 31 | 32 | def logger(update): 33 | print("received update with text:", update.text) 34 | 35 | def contains_photo(update): 36 | return "photo" in update.text.lower() 37 | 38 | def photo_file_handle(update): 39 | if "cat" in update.text.lower(): 40 | return open("cat.png", "rb") 41 | else if "dog" in update.text.lower(): 42 | return open("dog.png", "rb") 43 | else: 44 | return open("emu.png", "rb") 45 | 46 | if __name__ == "__main__": 47 | botbuilder.BotBuilder(apikey_file="apikey.txt") \ 48 | .do_when(lambda update: return True, logger, botbuilder.DO_NOT_CONSUME) \ 49 | .send_photo_when("penguin", "penguin.png") \ 50 | .send_photo_when(contains_photo, photo_file_handle) \ 51 | .build().start() 52 | ``` 53 | In this example, a logger is registered to receive all events via the `do_when` method and a simple lambda which 54 | matches all update events, to allow the event to get passed to other matchers, the `botbuilder.DO_NOT_CONSUME` 55 | constant is used, you can also use `botbuilder.CONSUME` to explicitly state that an update event should be consumed 56 | by the action. 57 | 58 | Next, an action is registered, which receives `/penguin` commands and sends back the `penguin.png` file, 59 | the last action is registered to be executed when `contains_photo` matches, that is, if a message contains the the 60 | text "photo", the action then looks for other words specifying the photo to be sent and returns a file handle to it. 61 | 62 | | input | output | 63 | | ----- | ------ | 64 | | "/penguin" | contents of the "penguin.png" picture | 65 | | "send me a photo of a cat" | contents of the "cat.png" picture | 66 | | "i want a photo of a dog" | contents of the "dog.png" picture | 67 | | "send me a photo" | contents of the "emu.png" picture | 68 | 69 | ### Available builder methods 70 | 71 | * `do_when(matcher, result_generator, consume=True, optionals={})` 72 | * `send_message_when(matcher, result_generator (or string), consume=True, optionals={})` 73 | * `forward_message_when(matcher, result_generator (or tuple, see below), ...)` 74 | * `send_location_when(matcher, result_generator (or tuple, see below), ...)` 75 | * `send_photo_when(matcher, result_generator (or filename), ...)` 76 | * `send_audio_when(matcher, result_generator (or filename), ...)` 77 | * `send_document_when(matcher, result_generator (or filename), ...)` 78 | * `send_sticker_when(matcher, result_generator (or filename), ...)` 79 | * `send_video_when(matcher, result_generator (or filename), ...)` 80 | 81 | Instead of the `result_generator` function, you can instead directly use values as parameters, in the case of 82 | `send_location_when`, you could replace the `result_generator` with a `(latitude, longitude)` tuple. 83 | 84 | Optionals are the optional parameters that can be used by the telegram api, in the case of `send_photo_when`, 85 | `{"caption": "A photo of some mountains"}` would be a valid optional parameter. The `result_generator` function 86 | can also return optional parameters as the last item in a tuple, for example 87 | ```python 88 | def get_photo(update): 89 | return open("photo.png", "rb"), {"caption": "some photo"} 90 | ``` 91 | This can be used if the optional parameters are dependent on the content of the update. 92 | 93 | Based on the type of response to send, the `result_generator` has to return different values, this is where it gets 94 | confusing: 95 | 96 | * in `do_when`, it can return anything, the value is not used. 97 | * in `send_message_when`, it can return `string` or `string, optionals` 98 | * in `forward_message_when`, it can return `from_chat_id, message_id[, optionals]` 99 | * in `send_location_when`, it can return `latitude, longitude[, optionals]` or a list 100 | * in `send_[filetype]_when`, it can return `filehandle_filename_or_id[, is_id][, optionals]` 101 | 102 | The `is_id` parameter defaults to `False` and effects the handling of the return value, if it is a string. 103 | If `is_id` is `False`, the returned string will be treated as a filename, if it is `True`, the returned string 104 | will be treated as a file_id for the telegram api. If the returned value is not a string, the `is_id` parameter 105 | is not taken into account. 106 | 107 | ## Coming Soon 108 | 109 | * **connector** explanation 110 | * **bot** explanation 111 | -------------------------------------------------------------------------------- /samples/files/ACDC_back_in_black_sample.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/samples/files/ACDC_back_in_black_sample.ogg -------------------------------------------------------------------------------- /samples/files/another_day_in_paradise_sample.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/samples/files/another_day_in_paradise_sample.ogg -------------------------------------------------------------------------------- /samples/files/bubblegum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/samples/files/bubblegum.png -------------------------------------------------------------------------------- /samples/files/cat1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/samples/files/cat1.jpeg -------------------------------------------------------------------------------- /samples/files/cat2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/samples/files/cat2.jpeg -------------------------------------------------------------------------------- /samples/files/gunter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/samples/files/gunter.png -------------------------------------------------------------------------------- /samples/files/sample1.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/samples/files/sample1.odt -------------------------------------------------------------------------------- /samples/files/sample2.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/samples/files/sample2.ods -------------------------------------------------------------------------------- /samples/send_audio_bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | 5 | import telegram.botapi.botbuilder as botbuilder 6 | 7 | 8 | def match_all(update): 9 | return True 10 | 11 | def logger(update): 12 | print("got update:", update) 13 | 14 | def get_filehandle(update): 15 | return open(os.path.join("files", "ACDC_back_in_black_sample.ogg"), "rb") 16 | 17 | if __name__ == "__main__": 18 | botbuilder.BotBuilder(apikey_file="api_key.txt") \ 19 | .do_when(match_all, logger, botbuilder.DO_NOT_CONSUME) \ 20 | .send_audio_when("bib", get_filehandle) \ 21 | .send_audio_when("anotherday", os.path.join("files", "another_day_in_paradise_sample.ogg")) \ 22 | .build().start() 23 | -------------------------------------------------------------------------------- /samples/send_document_bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | 5 | import telegram.botapi.botbuilder as botbuilder 6 | 7 | 8 | def match_all(update): 9 | return True 10 | 11 | def logger(update): 12 | print("got update:", update) 13 | 14 | def get_filehandle(update): 15 | return open(os.path.join("files", "sample1.odt"), "rb") 16 | 17 | if __name__ == "__main__": 18 | botbuilder.BotBuilder(apikey_file="api_key.txt") \ 19 | .do_when(match_all, logger, botbuilder.DO_NOT_CONSUME) \ 20 | .send_document_when("doc1", get_filehandle) \ 21 | .send_document_when("doc2", os.path.join("files", "sample2.ods")) \ 22 | .build().start() 23 | -------------------------------------------------------------------------------- /samples/send_location_bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import random 4 | 5 | import telegram.botapi.botbuilder as botbuilder 6 | 7 | 8 | def match_all(update): 9 | return True 10 | 11 | def logger(update): 12 | print("got update:", update) 13 | 14 | def get_random_location(update): 15 | latitude = get_random_latitude() 16 | longitude = get_random_longitude() 17 | return latitude, longitude 18 | 19 | def get_random_latitude(): 20 | return random.randrange(-90, 90) 21 | 22 | def get_random_longitude(): 23 | return random.randrange(-180, 180) 24 | 25 | if __name__ == "__main__": 26 | botbuilder.BotBuilder(apikey_file="api_key.txt") \ 27 | .do_when(match_all, logger, botbuilder.DO_NOT_CONSUME) \ 28 | .send_location_when("location", get_random_location) \ 29 | .send_location_when("tuple", (20,50)) \ 30 | .build().start() 31 | -------------------------------------------------------------------------------- /samples/send_photo_bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | 5 | import telegram.botapi.botbuilder as botbuilder 6 | 7 | 8 | def match_all(update): 9 | return True 10 | 11 | def logger(update): 12 | print("got update:", update) 13 | 14 | def get_filehandle(update): 15 | return open(os.path.join("files", "cat1.jpeg"), "rb") 16 | 17 | def get_filehandle_and_caption(update): 18 | print("cat1 with caption") 19 | optionals = {"caption": "omg catz!"} 20 | return open(os.path.join("files", "cat1.jpeg"), "rb"), optionals 21 | 22 | if __name__ == "__main__": 23 | optionals = {"caption": "so sweet!!!"} 24 | 25 | botbuilder.BotBuilder(apikey_file="api_key.txt") \ 26 | .do_when(match_all, logger, botbuilder.DO_NOT_CONSUME) \ 27 | .send_photo_when("cat1caption", get_filehandle_and_caption, ) \ 28 | .send_photo_when("cat2caption", os.path.join("files", "cat2.jpeg"), optionals=optionals) \ 29 | .send_photo_when("cat1", get_filehandle, botbuilder.CONSUME) \ 30 | .send_photo_when("cat2", os.path.join("files", "cat2.jpeg")) \ 31 | .build().start() 32 | -------------------------------------------------------------------------------- /samples/send_sticker_bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | 5 | import telegram.botapi.botbuilder as botbuilder 6 | 7 | 8 | def match_all(update): 9 | return True 10 | 11 | def logger(update): 12 | print("got update:", update) 13 | 14 | def get_filehandle(update): 15 | return open(os.path.join("files", "bubblegum.png"), "rb") 16 | 17 | if __name__ == "__main__": 18 | botbuilder.BotBuilder(apikey_file="api_key.txt") \ 19 | .do_when(match_all, logger, botbuilder.DO_NOT_CONSUME) \ 20 | .send_sticker_when("bubblegum", get_filehandle) \ 21 | .send_sticker_when("gunter", os.path.join("files", "gunter.png")) \ 22 | .build().start() 23 | -------------------------------------------------------------------------------- /samples/simple_echo_bot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import telegram.botapi.botbuilder as botbuilder 4 | 5 | def match_all(update): 6 | return True 7 | 8 | def sweet(update): 9 | return "sweet" in update.text.lower() 10 | 11 | def logger(update): 12 | print("got update:", update) 13 | 14 | def message_generator(update): 15 | return "awesome" 16 | 17 | def dude(update): 18 | return "dude" 19 | 20 | def echo(update): 21 | return update.text[6:] 22 | 23 | if __name__ == "__main__": 24 | botbuilder.BotBuilder(apikey_file="api_key.txt") \ 25 | .do_when(match_all, logger, botbuilder.DO_NOT_CONSUME) \ 26 | .send_message_when("echo", echo, botbuilder.CONSUME) \ 27 | .send_message_when(sweet, dude) \ 28 | .send_message_when(match_all, message_generator) \ 29 | .build().start() 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from distutils.core import setup 4 | 5 | setup( name = "telegram-bot-api", 6 | version = "0.2", 7 | description = "A python wrapper and bot builder for the Telegram Bot API", 8 | url = "https://github.com/puehcl/python-telegram-bot-api", 9 | download_url = "https://github.com/puehcl/python-telegram-bot-api/tarball/0.2", 10 | author = "Clemens Puehringer", 11 | author_email = "misc-telegramapi@pueh.at", 12 | py_modules = [ "telegram.botapi.util", \ 13 | "telegram.botapi.api", \ 14 | "telegram.botapi.actions", \ 15 | "telegram.botapi.connector", \ 16 | "telegram.botapi.bot", \ 17 | "telegram.botapi.botbuilder"], 18 | 19 | keywords = [ "telegram", \ 20 | "bot", \ 21 | "api", \ 22 | "builder"], 23 | 24 | classifiers = [ "Development Status :: 4 - Beta", 25 | "Intended Audience :: Developers", 26 | "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", 27 | "Operating System :: OS Independent", 28 | "Topic :: Communications :: Chat", 29 | "Topic :: Internet", 30 | "Programming Language :: Python :: 3 :: Only", 31 | "Programming Language :: Python :: 3", 32 | "Programming Language :: Python :: 3.4"] 33 | ) 34 | -------------------------------------------------------------------------------- /telegram/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/telegram/__init__.py -------------------------------------------------------------------------------- /telegram/botapi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/telegram/botapi/__init__.py -------------------------------------------------------------------------------- /telegram/botapi/actions.py: -------------------------------------------------------------------------------- 1 | 2 | class UpdateMatcher(object): 3 | def matches(self, update): 4 | pass 5 | 6 | class FunctionMatcher(UpdateMatcher): 7 | def __init__(self, function): 8 | self.function = function 9 | def matches(self, update): 10 | return self.function(update) 11 | 12 | class CommandMatcher(UpdateMatcher): 13 | def __init__(self, commandstr): 14 | self.commandstr = str(commandstr) 15 | def matches(self, update): 16 | if update.text: 17 | return update.text.lower().startswith("/" + self.commandstr) 18 | return False 19 | 20 | class Generator(object): 21 | def generate(self, update): 22 | pass 23 | def get_optionals(self): 24 | return {} 25 | 26 | class StringGenerator(Generator): 27 | def __init__(self, obj, optionals={}): 28 | self.obj = obj 29 | self.optionals = optionals 30 | def generate(self, update): 31 | return str(self.obj) 32 | def get_optionals(self): 33 | return self.optionals 34 | 35 | class TupleGenerator(Generator): 36 | def __init__(self, tup, optionals={}): 37 | self.tuple = tup 38 | self.optionals = optionals 39 | def generate(self, update): 40 | return self.tuple 41 | def get_optionals(self): 42 | return self.optionals 43 | 44 | class FunctionGenerator(Generator): 45 | def __init__(self, function, optionals={}): 46 | self.function = function 47 | self.optionals = optionals 48 | self.base_optionals = optionals 49 | def generate(self, update): 50 | result = self.function(update) 51 | if result and isinstance(result, (tuple,list)): 52 | if len(result) >= 2: 53 | if isinstance(result[-1], dict): 54 | optionals = result[-1] 55 | result = result[:-1] 56 | self.optionals = self.base_optionals.copy() 57 | self.optionals.update(optionals) 58 | return result 59 | def get_optionals(self): 60 | return self.optionals 61 | 62 | class Action(object): 63 | def __init__(self, function): 64 | self.function = function 65 | def execute(self, update): 66 | return self.function(update) 67 | 68 | class SendAction(Action): 69 | def __init__(self, generator, connector): 70 | self.generator = generator 71 | self.connector = connector 72 | def execute(self, update): 73 | return None 74 | 75 | class SendMessageAction(SendAction): 76 | def __init__(self, generator, connector): 77 | super().__init__(generator, connector) 78 | def execute(self, update): 79 | message = self.generator.generate(update) 80 | optionals = self.generator.get_optionals() 81 | return self.connector.send_message( update.chat.id, \ 82 | message, \ 83 | optionals=optionals) 84 | 85 | class ForwardMessageAction(SendAction): 86 | def __init__(self, generator, connector): 87 | super().__init__(generator, connector) 88 | def execute(self, update): 89 | from_chat_id, message_id = self.generator.generate(update) 90 | optionals = self.generator.get_optionals() 91 | return self.connector.forward_message( update.chat.id, \ 92 | from_chat_id, \ 93 | message_id, \ 94 | optionals=optionals) 95 | 96 | class SendFileAction(SendAction): 97 | def __init__(self, generator, connector, is_id): 98 | super().__init__(generator, connector) 99 | self.is_id = is_id 100 | def _get_kwargs(self, update, type_name): 101 | file_filename_or_id = self.generator.generate(update) 102 | if isinstance(file_filename_or_id, (tuple,list)): 103 | if len(file_filename_or_id) == 2: 104 | file_filename_or_id, is_id = file_filename_or_id 105 | self.is_id = self.is_id or is_id 106 | optionals = self.generator.get_optionals() 107 | kwargs = {"optionals": optionals} 108 | if isinstance(file_filename_or_id, str): 109 | if self.is_id: 110 | kwargs["{}_id".format(type_name)] = file_filename_or_id 111 | else: 112 | kwargs["file_or_filename"] = file_filename_or_id 113 | else: 114 | kwargs["file_or_filename"] = file_filename_or_id 115 | return kwargs 116 | 117 | class SendPhotoAction(SendFileAction): 118 | def __init__(self, generator, connector, is_id): 119 | super().__init__(generator, connector, is_id) 120 | def execute(self, update): 121 | kwargs = self._get_kwargs(update, "photo") 122 | return self.connector.send_photo(update.chat.id, **kwargs) 123 | 124 | class SendAudioAction(SendFileAction): 125 | def __init__(self, generator, connector, is_id): 126 | super().__init__(generator, connector, is_id) 127 | def execute(self, update): 128 | kwargs = self._get_kwargs(update, "audio") 129 | return self.connector.send_audio(update.chat.id, **kwargs) 130 | 131 | class SendDocumentAction(SendFileAction): 132 | def __init__(self, generator, connector, is_id): 133 | super().__init__(generator, connector, is_id) 134 | def execute(self, update): 135 | kwargs = self._get_kwargs(update, "document") 136 | print("sending documents with", kwargs) 137 | return self.connector.send_document(update.chat.id, **kwargs) 138 | 139 | class SendStickerAction(SendFileAction): 140 | def __init__(self, generator, connector, is_id): 141 | super().__init__(generator, connector, is_id) 142 | def execute(self, update): 143 | kwargs = self._get_kwargs(update, "sticker") 144 | return self.connector.send_sticker(update.chat.id, **kwargs) 145 | 146 | class SendVideoAction(SendFileAction): 147 | def __init__(self, generator, connector, is_id): 148 | super().__init__(generator, connector, is_id) 149 | def execute(self, update): 150 | kwargs = self._get_kwargs(update, "video") 151 | return self.connector.send_video(update.chat.id, **kwargs) 152 | 153 | class SendLocationAction(SendAction): 154 | def __init__(self, generator, connector): 155 | super().__init__(generator, connector) 156 | def execute(self, update): 157 | latitude, longitude = self.generator.generate(update) 158 | optionals = self.generator.get_optionals() 159 | return self.connector.send_location(update.chat.id, \ 160 | latitude, longitude, \ 161 | optionals) 162 | -------------------------------------------------------------------------------- /telegram/botapi/api.py: -------------------------------------------------------------------------------- 1 | 2 | get_me_method = "getMe" 3 | get_updates_method = "getUpdates" 4 | get_user_profile_photos_method = "getUserProfilePhotos" 5 | 6 | forward_message_method = "forwardMessage" 7 | send_message_method = "sendMessage" 8 | send_photo_method = "sendPhoto" 9 | send_audio_method = "sendAudio" 10 | send_document_method = "sendDocument" 11 | send_sticker_method = "sendSticker" 12 | send_video_method = "sendVideo" 13 | send_location_method = "sendLocation" 14 | send_chat_action_method = "sendChatAction" 15 | 16 | def get_url(base_url, method): 17 | if base_url[-1] != "/": 18 | base_url = base_url + "/" 19 | return base_url + method 20 | 21 | def get_me_url(base_url): 22 | return get_url(base_url, get_me_method) 23 | 24 | def get_updates_url(base_url): 25 | return get_url(base_url, get_updates_method) 26 | 27 | def get_user_profile_photos_url(base_url): 28 | return get_url(base_url, get_user_profile_photos_method) 29 | 30 | def forward_message_url(base_url): 31 | return get_url(base_url, forward_message_method) 32 | 33 | def send_message_url(base_url): 34 | return get_url(base_url, send_message_method) 35 | 36 | def send_photo_url(base_url): 37 | return get_url(base_url, send_photo_method) 38 | 39 | def send_audio_url(base_url): 40 | return get_url(base_url, send_audio_method) 41 | 42 | def send_document_url(base_url): 43 | return get_url(base_url, send_document_method) 44 | 45 | def send_sticker_url(base_url): 46 | return get_url(base_url, send_sticker_method) 47 | 48 | def send_video_url(base_url): 49 | return get_url(base_url, send_video_method) 50 | 51 | def send_location_url(base_url): 52 | return get_url(base_url, send_location_method) 53 | 54 | def send_chat_action_url(base_url): 55 | return get_url(base_url, send_chat_action_method) 56 | -------------------------------------------------------------------------------- /telegram/botapi/bot.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import telegram.botapi.connector as connector 4 | import telegram.botapi.util as util 5 | 6 | 7 | 8 | class TelegramBot(object): 9 | 10 | def __init__(self, apikey=None, connector_=None): 11 | if not connector_: 12 | if not apikey: 13 | raise 14 | self.connector = connector.Connector(apikey) 15 | else: 16 | self.connector = connector_ 17 | self.actions = [] 18 | 19 | def add_action(self, matcher, action, consume=True): 20 | self.actions.append((matcher, action, consume)) 21 | 22 | def start(self): 23 | for update in self.connector.stream_updates(): 24 | self._execute_actions(update) 25 | 26 | def _execute_actions(self, update): 27 | for (matcher, action, consume) in self.actions: 28 | if matcher.matches(update): 29 | result = action.execute(update) 30 | print(result) 31 | if consume: 32 | return 33 | -------------------------------------------------------------------------------- /telegram/botapi/botbuilder.py: -------------------------------------------------------------------------------- 1 | 2 | import telegram.botapi.util as util 3 | import telegram.botapi.actions as actions 4 | import telegram.botapi.bot as bot 5 | import telegram.botapi.connector as connector 6 | 7 | CONSUME = True 8 | DO_NOT_CONSUME = False 9 | 10 | class BotBuilder(object): 11 | 12 | def __init__(self, apikey=None, apikey_file=None): 13 | key = None 14 | if not apikey: 15 | if not apikey_file: 16 | raise ValueError("Either apikey or apikey_file must be specified") 17 | else: 18 | with open(apikey_file, "r") as kf: 19 | key = kf.readline()[:-1] 20 | else: 21 | key = apikey 22 | self.bot = bot.TelegramBot(apikey=key) 23 | 24 | def do_when(self, cmd_or_predicate, function, consume): 25 | matcher = self._get_matcher(cmd_or_predicate) 26 | self.bot.add_action(matcher, actions.Action(function), consume) 27 | return self 28 | 29 | def send_message_when( self, cmd_or_predicate, msg_or_function, \ 30 | consume=True, optionals={}): 31 | matcher = self._get_matcher(cmd_or_predicate) 32 | generator = self._get_generator(msg_or_function, optionals) 33 | self.bot.add_action(matcher, \ 34 | actions.SendMessageAction(\ 35 | generator, \ 36 | self.bot.connector), \ 37 | consume) 38 | return self 39 | 40 | def forward_message_when( self, cmd_or_predicate, msg_or_function, \ 41 | consume=True, optionals={}): 42 | matcher = self._get_matcher(cmd_or_predicate) 43 | generator = self._get_generator(msg_or_function, optionals) 44 | self.bot.add_action(matcher, \ 45 | actions.ForwardMessageAction(\ 46 | generator, \ 47 | self.bot.connector), \ 48 | consume) 49 | return self 50 | 51 | def send_photo_when(self, cmd_or_predicate, filename_or_function, \ 52 | is_id=False, consume=True, optionals={}): 53 | matcher = self._get_matcher(cmd_or_predicate) 54 | generator = self._get_generator(filename_or_function, optionals) 55 | self.bot.add_action(matcher, \ 56 | actions.SendPhotoAction( \ 57 | generator, \ 58 | self.bot.connector, \ 59 | is_id), \ 60 | consume) 61 | return self 62 | 63 | def send_audio_when(self, cmd_or_predicate, filename_or_function, \ 64 | is_id=False, consume=True, optionals={}): 65 | matcher = self._get_matcher(cmd_or_predicate) 66 | generator = self._get_generator(filename_or_function, optionals) 67 | self.bot.add_action(matcher, \ 68 | actions.SendAudioAction( \ 69 | generator, \ 70 | self.bot.connector, \ 71 | is_id), \ 72 | consume) 73 | return self 74 | 75 | def send_document_when( self, cmd_or_predicate, filename_or_function, \ 76 | is_id=False, consume=True, optionals={}): 77 | matcher = self._get_matcher(cmd_or_predicate) 78 | generator = self._get_generator(filename_or_function, optionals) 79 | self.bot.add_action(matcher, \ 80 | actions.SendDocumentAction( \ 81 | generator, \ 82 | self.bot.connector, \ 83 | is_id), \ 84 | consume) 85 | return self 86 | 87 | def send_sticker_when( self, cmd_or_predicate, filename_or_function, \ 88 | is_id=False, consume=True, optionals={}): 89 | matcher = self._get_matcher(cmd_or_predicate) 90 | generator = self._get_generator(filename_or_function, optionals) 91 | self.bot.add_action(matcher, \ 92 | actions.SendStickerAction( \ 93 | generator, \ 94 | self.bot.connector, \ 95 | is_id), \ 96 | consume) 97 | return self 98 | 99 | def send_video_when(self, cmd_or_predicate, filename_or_function, \ 100 | is_id=False, consume=True, optionals={}): 101 | matcher = self._get_matcher(cmd_or_predicate) 102 | generator = self._get_generator(filename_or_function, optionals) 103 | self.bot.add_action(matcher, \ 104 | actions.SendVideoAction( \ 105 | generator, \ 106 | self.bot.connector, \ 107 | is_id), \ 108 | consume) 109 | return self 110 | 111 | def send_location_when( self, cmd_or_predicate, tuple_or_function, \ 112 | consume=True, optionals={}): 113 | matcher = self._get_matcher(cmd_or_predicate) 114 | generator = self._get_generator(tuple_or_function, optionals) 115 | self.bot.add_action(matcher, \ 116 | actions.SendLocationAction( \ 117 | generator, \ 118 | self.bot.connector), \ 119 | consume) 120 | return self 121 | 122 | def build(self): 123 | return self.bot 124 | 125 | def _get_matcher(self, cmd_or_predicate): 126 | if util.iscallable(cmd_or_predicate): 127 | return actions.FunctionMatcher(cmd_or_predicate) 128 | else: 129 | return actions.CommandMatcher(cmd_or_predicate) 130 | 131 | def _get_generator(self, str_or_tuple_or_function, optionals): 132 | if util.iscallable(str_or_tuple_or_function): 133 | return actions.FunctionGenerator( str_or_tuple_or_function, \ 134 | optionals=optionals) 135 | elif isinstance(str_or_tuple_or_function, tuple): 136 | if len(str_or_tuple_or_function) == 2: 137 | return actions.TupleGenerator( str_or_tuple_or_function, \ 138 | optionals=optionals) 139 | else: 140 | return actions.StringGenerator( str_or_tuple_or_function, \ 141 | optionals=optionals) 142 | -------------------------------------------------------------------------------- /telegram/botapi/connector.py: -------------------------------------------------------------------------------- 1 | 2 | import requests 3 | 4 | import telegram.botapi.util as util 5 | import telegram.botapi.api as api 6 | 7 | DEFAULT_ENDPOINT_URL = "https://api.telegram.org/bot" 8 | DEFAULT_LONGPOLLING_TIMEOUT = 120 9 | 10 | class Connector(object): 11 | 12 | def __init__(self, api_key, endpoint_url=DEFAULT_ENDPOINT_URL): 13 | self.last_update_id = -1 14 | self.longpolling_timeout = DEFAULT_LONGPOLLING_TIMEOUT 15 | self.base_url = endpoint_url + api_key + "/" 16 | 17 | def get_raw_updates(self): 18 | params = { "timeout": self.longpolling_timeout, \ 19 | "offset": (self.last_update_id + 1) } 20 | return self._do_get( api.get_updates_url(self.base_url), \ 21 | params, \ 22 | timeout=self.longpolling_timeout) 23 | 24 | def get_updates(self): 25 | jobj = self.get_raw_updates() 26 | if not jobj.ok: 27 | pass 28 | #TODO: raise 29 | if not jobj.result or len(jobj.result) == 0: 30 | return [] 31 | updates = sorted(jobj.result, key=lambda update: update.update_id) 32 | self.last_update_id = updates[-1].update_id 33 | return [update.message for update in updates] 34 | 35 | def stream_updates(self): 36 | while True: 37 | for update in self.get_updates(): 38 | yield update 39 | 40 | def get_me(self): 41 | return self._do_get(api.get_me_url(self.base_url), {}) 42 | 43 | def get_user_profile_photos(self, user_id, optionals={}): 44 | params = {"user_id": str(user_id)} 45 | params.update(optionals) 46 | return self._do_get(api.get_user_profile_photos_url(self.base_url), params) 47 | 48 | def forward_message(self, chat_id, from_chat_id, message_id): 49 | params = self._default_params(chat_id) 50 | params["from_chat_id"] = from_chat_id 51 | params["message_id"] = message_id 52 | return self._do_post(api.forward_message_url(self.base_url), params) 53 | 54 | def send_message(self, chat_id, message, optionals={}): 55 | params = self._default_params(chat_id, optionals) 56 | params["text"] = message 57 | return self._do_post(api.send_message_url(self.base_url), params) 58 | 59 | def send_photo(self, chat_id, file_or_filename=None, photo_id=None, optionals={}): 60 | return self._do_multipart_post( api.send_photo_url(self.base_url), \ 61 | chat_id, "photo", file_or_filename, \ 62 | photo_id, optionals) 63 | 64 | def send_audio(self, chat_id, file_or_filename=None, audio_id=None, optionals={}): 65 | return self._do_multipart_post( api.send_audio_url(self.base_url), \ 66 | chat_id, "audio", file_or_filename, \ 67 | audio_id, optionals) 68 | 69 | def send_document(self, chat_id, file_or_filename=None, doc_id=None, optionals={}): 70 | return self._do_multipart_post( api.send_document_url(self.base_url), \ 71 | chat_id, "document", file_or_filename, \ 72 | doc_id, optionals) 73 | 74 | def send_sticker(self, chat_id, file_or_filename=None, sticker_id=None, optionals={}): 75 | return self._do_multipart_post( api.send_sticker_url(self.base_url), \ 76 | chat_id, "sticker", file_or_filename, \ 77 | sticker_id, optionals) 78 | 79 | def send_video(self, chat_id, file_or_filename=None, video_id=None, optionals={}): 80 | return self._do_multipart_post( api.send_video_url(self.base_url), \ 81 | chat_id, "video", file_or_filename, \ 82 | video_id, optionals) 83 | 84 | def send_location(self, chat_id, latitude, longitude, optionals={}): 85 | params = self._default_params(chat_id, optionals) 86 | params["latitude"] = latitude 87 | params["longitude"] = longitude 88 | return self._do_post(api.send_location_url(self.base_url), params) 89 | 90 | def _do_get(self, url, params, timeout=0): 91 | json_response = requests.get(url, params=params, timeout=timeout).json() 92 | return util.fromjson(json_response) 93 | 94 | def _do_post(self, url, params, files={}): 95 | json_response = requests.post(url, params=params, files=files).json() 96 | return util.fromjson(json_response) 97 | 98 | def _do_multipart_post( self, url, chat_id, param_name, \ 99 | file_or_filename=None, file_id=None, \ 100 | optionals={}): 101 | fil = util.getfile(file_or_filename) 102 | multipart_data = util.getmultipart(param_name, fil) 103 | params = self._default_params(chat_id, optionals) 104 | params.update(util.getparam(param_name, file_id)) 105 | return self._do_post(url, params, files=multipart_data) 106 | 107 | def _default_params(self, chat_id, extra_params={}): 108 | params = {"chat_id": str(chat_id)} 109 | params.update(extra_params) 110 | return params 111 | -------------------------------------------------------------------------------- /telegram/botapi/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puehcl/python-telegram-bot-api/16b11ff9e89715824e4fc4ecee503c47f1efdc5c/telegram/botapi/tests/__init__.py -------------------------------------------------------------------------------- /telegram/botapi/tests/test_connector.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import unittest.mock 3 | import sys 4 | import json 5 | 6 | import requests 7 | 8 | ORIGINAL_REQUESTS = sys.modules["requests"] 9 | sys.modules["requests"] = unittest.mock.Mock(spec=ORIGINAL_REQUESTS) 10 | import telegram.botapi.util as util 11 | import telegram.botapi.api as api 12 | import telegram.botapi.connector as connector 13 | import telegram.botapi.tests.testdata as testdata 14 | from telegram.botapi.tests.testtools import * 15 | 16 | 17 | class TestConnector(unittest.TestCase): 18 | 19 | def setUp(self): 20 | self.original_requests = ORIGINAL_REQUESTS 21 | self.mocked_requests = sys.modules["requests"] 22 | self.mocked_requests.reset_mock() 23 | 24 | self.mocked_response = unittest.mock.Mock(spec=ORIGINAL_REQUESTS.Response) 25 | self.mocked_response.json = json.loads 26 | 27 | self.dummy_api_key = "0000000000" 28 | self.connector = connector.Connector(self.dummy_api_key) 29 | self.expected_base_url = connector.DEFAULT_ENDPOINT_URL + \ 30 | self.dummy_api_key + "/" 31 | 32 | 33 | def test_get_me(self): 34 | self.mocked_response.json = lambda: json.loads(testdata.USER_RESPONSE) 35 | self.mocked_requests.get.return_value = self.mocked_response 36 | response = self.connector.get_me() 37 | 38 | expected_full_url = api.get_me_url(self.expected_base_url) 39 | self.mocked_requests.get.assert_called_once_with( expected_full_url, \ 40 | params={}, \ 41 | timeout=0) 42 | 43 | def test_get_raw_updates(self): 44 | self.mocked_response.json = lambda: json.loads(testdata.MULTIPLE_UPDATES) 45 | self.mocked_requests.get.return_value = self.mocked_response 46 | response = self.connector.get_raw_updates() 47 | expected_full_url = api.get_updates_url(self.expected_base_url) 48 | expected_params = { "timeout": connector.DEFAULT_LONGPOLLING_TIMEOUT, \ 49 | "offset": 0 } 50 | expected_timeout = connector.DEFAULT_LONGPOLLING_TIMEOUT 51 | 52 | self.mocked_requests.get.assert_called_once_with( expected_full_url, \ 53 | params=expected_params, \ 54 | timeout=expected_timeout) 55 | self.assertTrue(isinstance(response, util.JsonObject)) 56 | self.assertEqual(3, len(response.result)) 57 | self.assertTrue(inlist(response.result, \ 58 | lambda x: x.update_id == testdata.MULTIPLE_UPDATES_LAST_ID-2)) 59 | self.assertTrue(inlist(response.result, \ 60 | lambda x: x.update_id == testdata.MULTIPLE_UPDATES_LAST_ID-1)) 61 | self.assertTrue(inlist(response.result, \ 62 | lambda x: x.update_id == testdata.MULTIPLE_UPDATES_LAST_ID)) 63 | 64 | def test_get_updates(self): 65 | self.mocked_response.json = lambda: json.loads(testdata.MULTIPLE_UPDATES) 66 | self.mocked_requests.get.return_value = self.mocked_response 67 | updates = self.connector.get_updates() 68 | 69 | self.assertEqual(testdata.MULTIPLE_UPDATES_TEXTS[0], updates[0].text) 70 | self.assertEqual(testdata.MULTIPLE_UPDATES_TEXTS[-1], updates[-1].text) 71 | self.assertEqual(testdata.MULTIPLE_UPDATES_LAST_ID, self.connector.last_update_id) 72 | 73 | def test_stream_updates(self): 74 | self.mocked_response.json = lambda: json.loads(testdata.MULTIPLE_UPDATES) 75 | self.mocked_requests.get.return_value = self.mocked_response 76 | first_update = next(self.connector.stream_updates()) 77 | 78 | self.assertEqual(testdata.MULTIPLE_UPDATES_TEXTS[0], first_update.text) 79 | all_updates = take(3, self.connector.stream_updates()) 80 | self.assertEqual(testdata.MULTIPLE_UPDATES_TEXTS[2], all_updates[2].text) 81 | 82 | def test_send_message(self): 83 | self.mocked_response.json = lambda: json.loads(testdata.MESSAGE_RESPONSE) 84 | self.mocked_requests.post.return_value = self.mocked_response 85 | chat_id = 1337 86 | testmessage = "testmessage" 87 | response = self.connector.send_message(chat_id, testmessage) 88 | expected_full_url = api.send_message_url(self.expected_base_url) 89 | expected_params = { "chat_id": str(chat_id), 90 | "text": testmessage} 91 | 92 | self.mocked_requests.post.assert_called_once_with( expected_full_url, \ 93 | params=expected_params, 94 | files={}) 95 | 96 | def test_send_message_optional_args(self): 97 | self.mocked_response.json = lambda: json.loads(testdata.MESSAGE_RESPONSE) 98 | self.mocked_requests.post.return_value = self.mocked_response 99 | chat_id = 1337 100 | testmessage = "testmessage" 101 | optionals = {"disable_web_page_preview": True, \ 102 | "reply_to_message_id": 42, \ 103 | "reply_markup": None} 104 | response = self.connector.send_message(chat_id, testmessage, optionals=optionals) 105 | 106 | expected_full_url = api.send_message_url(self.expected_base_url) 107 | expected_params = { "chat_id": str(chat_id), 108 | "text": testmessage} 109 | expected_params.update(optionals) 110 | self.mocked_requests.post.assert_called_once_with( expected_full_url, \ 111 | params=expected_params, 112 | files={}) 113 | 114 | def test_send_photo_file_name(self): 115 | self.mocked_response.json = lambda: json.loads(testdata.MESSAGE_RESPONSE) 116 | self.mocked_requests.post.return_value = self.mocked_response 117 | chat_id = 1337 118 | filename = "testphoto.jpg" 119 | filehandle = unittest.mock.sentinel.file_handle 120 | mock = unittest.mock.MagicMock(return_value=filehandle) 121 | with unittest.mock.patch('builtins.open', mock): 122 | response = self.connector.send_photo(chat_id, file_or_filename=filename) 123 | 124 | expected_full_url = api.send_photo_url(self.expected_base_url) 125 | expected_params = { "chat_id": str(chat_id) } 126 | expected_files = {"photo": filehandle} 127 | self.mocked_requests.post.assert_called_once_with( expected_full_url, \ 128 | params=expected_params, 129 | files=expected_files) 130 | 131 | def test_send_photo_file_handle(self): 132 | self.mocked_response.json = lambda: json.loads(testdata.MESSAGE_RESPONSE) 133 | self.mocked_requests.post.return_value = self.mocked_response 134 | chat_id = 1337 135 | filehandle = unittest.mock.sentinel.file_handle 136 | mock = unittest.mock.MagicMock(return_value=filehandle) 137 | with unittest.mock.patch('builtins.open', mock): 138 | response = self.connector.send_photo(chat_id, file_or_filename=filehandle) 139 | 140 | expected_full_url = api.send_photo_url(self.expected_base_url) 141 | expected_params = { "chat_id": str(chat_id) } 142 | expected_files = {"photo": filehandle} 143 | self.mocked_requests.post.assert_called_once_with( expected_full_url, \ 144 | params=expected_params, 145 | files=expected_files) 146 | 147 | def test_send_photo_id(self): 148 | self.mocked_response.json = lambda: json.loads(testdata.MESSAGE_RESPONSE) 149 | self.mocked_requests.post.return_value = self.mocked_response 150 | chat_id = 1337 151 | photo_id = "deadbeef" 152 | optionals = { "caption": "testcaption", \ 153 | "reply_to_message_id": 42, \ 154 | "reply_markup": None } 155 | response = self.connector.send_photo(chat_id, photo_id=photo_id, optionals=optionals) 156 | 157 | expected_full_url = api.send_photo_url(self.expected_base_url) 158 | expected_params = { "chat_id": str(chat_id), "photo": photo_id } 159 | expected_params.update(optionals) 160 | expected_files = {} 161 | self.mocked_requests.post.assert_called_once_with( expected_full_url, \ 162 | params=expected_params, 163 | files=expected_files) 164 | -------------------------------------------------------------------------------- /telegram/botapi/tests/test_util.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import telegram.botapi.util as util 4 | import telegram.botapi.tests.testdata as testdata 5 | 6 | class TestIsCallable(unittest.TestCase): 7 | 8 | def callable_test_method(self): 9 | pass 10 | 11 | def test_iscallable_method_should_be_callable(self): 12 | self.assertTrue(util.iscallable(self.callable_test_method)) 13 | 14 | def test_iscallable_string_should_not_be_callable(self): 15 | self.assertFalse(util.iscallable("teststring")) 16 | 17 | 18 | class TestJsonObject(unittest.TestCase): 19 | 20 | def setUp(self): 21 | self.json_dict = testdata.JSON_DICT 22 | self.jobj = util.fromjson(self.json_dict) 23 | 24 | def test_jsonobj(self): 25 | obj = util.fromjson(self.json_dict) 26 | 27 | 28 | def test_access_should_work(self): 29 | self.assertTrue(self.jobj.intattr) 30 | self.assertTrue(self.jobj.subobj.subobj_strattr) 31 | self.assertTrue(self.jobj.subobj.subobj_listattr[0].sublistitem1) 32 | 33 | def test_access_should_not_work(self): 34 | self.assertFalse(self.jobj.absentattr) 35 | self.assertFalse(self.jobj.subobj.absentattr) 36 | -------------------------------------------------------------------------------- /telegram/botapi/tests/testdata.py: -------------------------------------------------------------------------------- 1 | 2 | MULTIPLE_UPDATES_LAST_ID = 483858683 3 | MULTIPLE_UPDATES_SENDER_ID = 4858386 4 | MULTIPLE_UPDATES_SENDER_USERNAME = "johndoe" 5 | MULTIPLE_UPDATES_CHAT_ID = 4858386 6 | MULTIPLE_UPDATES_TEXTS = ["first foobar", "second to last foobar", "last foobar"] 7 | 8 | MULTIPLE_UPDATES = "{ \"ok\":true, \ 9 | \"result\":[ { \ 10 | \"update_id\":" + str(MULTIPLE_UPDATES_LAST_ID) + ", \ 11 | \"message\": { \ 12 | \"message_id\":34, \ 13 | \"from\": { \ 14 | \"id\":" + str(MULTIPLE_UPDATES_SENDER_ID) + ", \ 15 | \"first_name\":\"john\", \ 16 | \"username\":\"" + MULTIPLE_UPDATES_SENDER_USERNAME + "\" }, \ 17 | \"chat\": { \ 18 | \"id\":" + str(MULTIPLE_UPDATES_CHAT_ID) + ", \ 19 | \"first_name\":\"john\", \ 20 | \"username\":\"" + MULTIPLE_UPDATES_SENDER_USERNAME + "\" }, \ 21 | \"date\":1435233444, \ 22 | \"text\":\"" + MULTIPLE_UPDATES_TEXTS[2] + "\" \ 23 | }}, { \ 24 | \"update_id\":" + str(MULTIPLE_UPDATES_LAST_ID-1) + ", \ 25 | \"message\": { \ 26 | \"message_id\":33, \ 27 | \"from\": { \ 28 | \"id\":" + str(MULTIPLE_UPDATES_SENDER_ID) + ", \ 29 | \"first_name\":\"john\", \ 30 | \"username\":\"" + MULTIPLE_UPDATES_SENDER_USERNAME + "\" }, \ 31 | \"chat\": { \ 32 | \"id\":" + str(MULTIPLE_UPDATES_CHAT_ID-1) + ", \ 33 | \"first_name\":\"john\", \ 34 | \"username\":\"" + MULTIPLE_UPDATES_SENDER_USERNAME + "\" }, \ 35 | \"date\":1435233444, \ 36 | \"text\":\"" + MULTIPLE_UPDATES_TEXTS[1] + "\" \ 37 | }}, { \ 38 | \"update_id\":" + str(MULTIPLE_UPDATES_LAST_ID-2) + ", \ 39 | \"message\": { \ 40 | \"message_id\":32, \ 41 | \"from\": { \ 42 | \"id\":" + str(MULTIPLE_UPDATES_SENDER_ID) + ", \ 43 | \"first_name\":\"john\", \ 44 | \"username\":\"" + MULTIPLE_UPDATES_SENDER_USERNAME + "\" }, \ 45 | \"chat\": { \ 46 | \"id\":" + str(MULTIPLE_UPDATES_CHAT_ID-2) + ", \ 47 | \"first_name\":\"john\", \ 48 | \"username\":\"" + MULTIPLE_UPDATES_SENDER_USERNAME + "\" }, \ 49 | \"date\":1435233444, \ 50 | \"text\":\"" + MULTIPLE_UPDATES_TEXTS[0] + "\" }}]}".replace(" ", "") 51 | 52 | 53 | MESSAGE_RESPONSE = "{ \"ok\":true, \ 54 | \"result\": { \ 55 | \"message_id\":344, \ 56 | \"from\": { \ 57 | \"id\":483848483, \ 58 | \"first_name\":\"TestBot\", \ 59 | \"username\":\"test_bot\"}, \ 60 | \"chat\": { \ 61 | \"id\":4838586, \ 62 | \"first_name\": \"tester\", \ 63 | \"username\":\"tester\"}, \ 64 | \"date\":1234567890, \ 65 | \"text\":\"testtext\"}}" 66 | 67 | USER_RESPONSE = "{ \"id\": 22342342, \ 68 | \"first_name\": \"TestBot\", \ 69 | \"user_name\": \"test_bot\" }" 70 | 71 | 72 | JSON_DICT ={ "intattr": 5, \ 73 | "strattr": "teststr", \ 74 | "listattr": [ \ 75 | {"listitem1": 1}, \ 76 | {"listitem2": "foo"} \ 77 | ], \ 78 | "subobj": { \ 79 | "subobj_intattr": 4, \ 80 | "subobj_strattr": "substr", \ 81 | "subobj_listattr": [ \ 82 | {"sublistitem1": "subliststr"}\ 83 | ]}} 84 | -------------------------------------------------------------------------------- /telegram/botapi/tests/testtools.py: -------------------------------------------------------------------------------- 1 | 2 | def inlist(lst, matcher): 3 | result = [x for x in lst if matcher(x)] 4 | return len(result) > 0 5 | 6 | def take(n, generator): 7 | i = 0 8 | result = [] 9 | while i < n: 10 | result.append(next(generator)) 11 | i = i + 1 12 | return result 13 | -------------------------------------------------------------------------------- /telegram/botapi/util.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | class JsonObject(object): 4 | 5 | def __init__(self, json_data): 6 | self.__dict__ = dict() 7 | for key in json_data: 8 | value = json_data[key] 9 | if isinstance(value, dict): 10 | value = JsonObject(value) 11 | if isinstance(value, list): 12 | values = [] 13 | for item in value: 14 | values.append(JsonObject(item)) 15 | value = values 16 | self.__dict__[key] = value 17 | 18 | def __getattr__(self, attr): 19 | if not attr in self.__dict__: 20 | return None 21 | return self.__dict__[attr] 22 | 23 | def __str__(self): 24 | result = "JsonObject{" 25 | for key in self.__dict__: 26 | result = result + str(key) + ":" + str(self.__dict__[key]) + ", " 27 | result = result[:-2] + "}" 28 | return result 29 | 30 | def __repr__(self): 31 | return self.__str__() 32 | 33 | 34 | def fromjson(json_dict): 35 | return JsonObject(json_dict) 36 | 37 | def iscallable(obj): 38 | return hasattr(obj, "__call__") 39 | 40 | def getfile(file_or_filename): 41 | if file_or_filename: 42 | if isinstance(file_or_filename, str): 43 | return open(file_or_filename, "rb") 44 | else: 45 | return file_or_filename 46 | else: 47 | return None 48 | 49 | def getparam(name, file_id): 50 | if file_id: 51 | return {name: file_id} 52 | else: 53 | return {} 54 | 55 | def getmultipart(name, fil): 56 | if fil: 57 | return {name: fil} 58 | else: 59 | return {} 60 | --------------------------------------------------------------------------------