Created util.py to clean up __init__.py and apihelper.py and updated README accordingly

Fixed failing send_document_by_id and send_photo_by_id
This commit is contained in:
pieter 2015-08-31 11:46:18 +02:00
parent 6f34a22c4b
commit 3c8faa155f
6 changed files with 161 additions and 171 deletions

View File

@ -282,7 +282,7 @@ To enable this behaviour, create an instance of AsyncTeleBot instead of TeleBot.
```python
tb = telebot.AsyncTeleBot("TOKEN")
```
Now, every function that calls the Telegram API is executed in a separate Thread. The functions are modified to return an AsyncTask instance (defined in \__init__.py). Using AsyncTeleBot allows you to do the following:
Now, every function that calls the Telegram API is executed in a separate Thread. The functions are modified to return an AsyncTask instance (defined in util.py). Using AsyncTeleBot allows you to do the following:
```python
import telebot
@ -300,12 +300,12 @@ result = task.wait() # Get the result of the execution
### Sending large text messages
Sometimes you must send messages that exceed 5000 characters. The Telegram API can not handle that many characters in one request, so we need to split the message in multiples. Here is how to do that using the API:
```python
from telebot import apihelper
from telebot import util
large_text = open("large_text.txt", "rb").read()
# Split the text each 3000 characters.
# split_string returns a list with the splitted text.
splitted_text = apihelper.split_string(large_text, 3000)
splitted_text = util.split_string(large_text, 3000)
for text in splitted_text:
tb.send_message(chat_id, text)
```

View File

@ -2,18 +2,14 @@
from __future__ import print_function
import threading
# Python3 queue support.
try:
import Queue
except ImportError:
import queue as Queue
import time
import logging
logging.basicConfig()
logger = logging.getLogger('Telebot')
import re
from telebot import apihelper, types
from telebot import apihelper, types, util
"""
Module : telebot
@ -21,48 +17,6 @@ Module : telebot
API_URL = r"https://api.telegram.org/"
class ThreadPool:
class WorkerThread(threading.Thread):
count = 0
def __init__(self, queue):
threading.Thread.__init__(self, name="WorkerThread{0}".format(self.__class__.count + 1))
self.__class__.count += 1
self.queue = queue
self.daemon = True
self._running = True
self.start()
def run(self):
while self._running:
try:
task, args, kwargs = self.queue.get()
task(*args, **kwargs)
except Queue.Empty:
time.sleep(0)
pass
def stop(self):
self._running = False
def __init__(self, num_threads=4):
self.tasks = Queue.Queue()
self.workers = [self.WorkerThread(self.tasks) for _ in range(num_threads)]
self.num_threads = num_threads
def put(self, func, *args, **kwargs):
self.tasks.put((func, args, kwargs))
def close(self):
for worker in self.workers:
worker.stop()
for worker in self.workers:
worker.join()
class TeleBot:
""" This is TeleBot Class
Methods:
@ -82,7 +36,6 @@ class TeleBot:
def __init__(self, token, create_threads=True, num_threads=4):
"""
:param token: bot API token
:param create_threads: Create thread for message handler
:param num_threads: Number of worker in thread pool.
@ -104,7 +57,7 @@ class TeleBot:
self.message_handlers = []
if self.__create_threads:
self.worker_pool = ThreadPool(num_threads)
self.worker_pool = util.ThreadPool(num_threads)
def get_update(self):
"""
@ -158,7 +111,6 @@ class TeleBot:
if block:
self.__stop_polling.wait()
def __polling(self, none_stop, interval):
logger.info('TeleBot: Started polling.')
@ -249,7 +201,7 @@ class TeleBot:
:param duration:Duration of the audio in seconds
:param performer:Performer
:param title:Track name
:param reply_to_message_id:If the message is a reply, ID of the original messag
:param reply_to_message_id:If the message is a reply, ID of the original message
:param reply_markup:
:return: Message
"""
@ -438,7 +390,7 @@ class TeleBot:
if message.content_type not in message_handler['content_types']:
return False
if 'commands' in message_handler and message.content_type == 'text':
return apihelper.extract_command(message.text) in message_handler['commands']
return util.extract_command(message.text) in message_handler['commands']
if 'regexp' in message_handler and message.content_type == 'text' and re.search(message_handler['regexp'],
message.text):
return True
@ -452,93 +404,55 @@ class TeleBot:
if self._test_message_handler(message_handler, message):
if self.__create_threads:
self.worker_pool.put(message_handler['function'], message)
# t = threading.Thread(target=message_handler['function'], args=(message,))
# t.start()
else:
message_handler['function'](message)
break
class AsyncTask:
def __init__(self, target, *args, **kwargs):
self.target = target
self.args = args
self.kwargs = kwargs
self.done = False
self.thread = threading.Thread(target=self._run)
self.thread.start()
def _run(self):
try:
self.result = self.target(*self.args, **self.kwargs)
except Exception as e:
self.result = e
self.done = True
def wait(self):
if not self.done:
self.thread.join()
if isinstance(self.result, Exception):
raise self.result
else:
return self.result
def async():
def decorator(fn):
def wrapper(*args, **kwargs):
return AsyncTask(fn, *args, **kwargs)
return wrapper
return decorator
class AsyncTeleBot(TeleBot):
def __init__(self, *args, **kwargs):
TeleBot.__init__(self, *args, **kwargs)
@async()
@util.async()
def get_me(self):
return TeleBot.get_me(self)
@async()
@util.async()
def get_user_profile_photos(self, *args, **kwargs):
return TeleBot.get_user_profile_photos(self, *args, **kwargs)
@async()
@util.async()
def send_message(self, *args, **kwargs):
return TeleBot.send_message(self, *args, **kwargs)
@async()
@util.async()
def forward_message(self, *args, **kwargs):
return TeleBot.forward_message(self, *args, **kwargs)
@async()
@util.async()
def send_photo(self, *args, **kwargs):
return TeleBot.send_photo(self, *args, **kwargs)
@async()
@util.async()
def send_audio(self, *args, **kwargs):
return TeleBot.send_audio(self, *args, **kwargs)
@async()
@util.async()
def send_document(self, *args, **kwargs):
return TeleBot.send_document(self, *args, **kwargs)
@async()
@util.async()
def send_sticker(self, *args, **kwargs):
return TeleBot.send_sticker(self, *args, **kwargs)
@async()
@util.async()
def send_video(self, *args, **kwargs):
return TeleBot.send_video(self, *args, **kwargs)
@async()
@util.async()
def send_location(self, *args, **kwargs):
return TeleBot.send_location(self, *args, **kwargs)
@async()
@util.async()
def send_chat_action(self, *args, **kwargs):
return TeleBot.send_chat_action(self, *args, **kwargs)

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import requests
from six import string_types
import telebot
from telebot import types
from telebot import util
logger = telebot.logger
@ -115,7 +115,7 @@ def send_photo(token, chat_id, photo, caption=None, reply_to_message_id=None, re
method_url = r'sendPhoto'
payload = {'chat_id': chat_id}
files = None
if not is_string(photo):
if not util.is_string(photo):
files = {'photo': photo}
else:
payload['photo'] = photo
@ -148,7 +148,7 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa
method_url = r'sendVideo'
payload = {'chat_id': chat_id}
files = None
if not is_string(data):
if not util.is_string(data):
files = {'video': data}
else:
payload['video'] = data
@ -167,7 +167,7 @@ def send_voice(token, chat_id, voice, duration=None, reply_to_message_id=None, r
method_url = r'sendVoice'
payload = {'chat_id': chat_id}
files = None
if not is_string(voice):
if not util.is_string(voice):
files = {'voice': voice}
else:
payload['voice'] = voice
@ -185,7 +185,7 @@ def send_audio(token, chat_id, audio, duration=None, performer=None, title=None,
method_url = r'sendAudio'
payload = {'chat_id': chat_id}
files = None
if not is_string(audio):
if not util.is_string(audio):
files = {'audio': audio}
else:
payload['audio'] = audio
@ -206,7 +206,7 @@ def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_m
method_url = get_method_by_type(data_type)
payload = {'chat_id': chat_id}
files = None
if not is_string(data):
if not util.is_string(data):
files = {data_type: data}
else:
payload[data_type] = data
@ -229,49 +229,6 @@ def _convert_markup(markup):
return markup.to_json()
return markup
def is_string(var):
return isinstance(var, string_types)
def is_command(text):
"""
Checks if `text` is a command. Telegram chat commands start with the '/' character.
:param text: Text to check.
:return: True if `text` is a command, else False.
"""
return text.startswith('/')
def extract_command(text):
"""
Extracts the command from `text` (minus the '/') if `text` is a command (see is_command).
If `text` is not a command, this function returns None.
Examples:
extract_command('/help'): 'help'
extract_command('/help@BotName'): 'help'
extract_command('/search black eyed peas'): 'search'
extract_command('Good day to you'): None
:param text: String to extract the command from
:return: the command if `text` is a command (according to is_command), else None.
"""
return text.split()[0].split('@')[0][1:] if is_command(text) else None
def split_string(text, chars_per_string):
"""
Splits one string into multiple strings, with a maximum amount of `chars_per_string` characters per string.
This is very useful for splitting one giant message into multiples.
:param text: The text to split
:param chars_per_string: The number of characters per line the text is split into.
:return: The splitted text as a list of strings.
"""
return [text[i:i + chars_per_string] for i in range(0, len(text), chars_per_string)]
class ApiException(Exception):
"""
This class represents an Exception thrown when a call to the Telegram API fails.

View File

@ -28,7 +28,6 @@ class JsonSerializable:
Subclasses of this class are guaranteed to be able to be converted to JSON format.
All subclasses of this class must override to_json.
"""
def to_json(self):
"""
Returns a JSON string representation of this class.
@ -44,7 +43,6 @@ class JsonDeserializable:
Subclasses of this class are guaranteed to be able to be created from a json-style dict or json formatted string.
All subclasses of this class must override de_json.
"""
@classmethod
def de_json(cls, json_type):
"""

126
telebot/util.py Normal file
View File

@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
import threading
from six import string_types
# Python3 queue support.
try:
import Queue
except ImportError:
import queue as Queue
class ThreadPool:
class WorkerThread(threading.Thread):
count = 0
def __init__(self, queue):
threading.Thread.__init__(self, name="WorkerThread{0}".format(self.__class__.count + 1))
self.__class__.count += 1
self.queue = queue
self.daemon = True
self._running = True
self.start()
def run(self):
while self._running:
try:
task, args, kwargs = self.queue.get(block=True, timeout=.01)
task(*args, **kwargs)
except Queue.Empty:
pass
def stop(self):
self._running = False
def __init__(self, num_threads=4):
self.tasks = Queue.Queue()
self.workers = [self.WorkerThread(self.tasks) for _ in range(num_threads)]
self.num_threads = num_threads
def put(self, func, *args, **kwargs):
self.tasks.put((func, args, kwargs))
def close(self):
for worker in self.workers:
worker.stop()
for worker in self.workers:
worker.join()
class AsyncTask:
def __init__(self, target, *args, **kwargs):
self.target = target
self.args = args
self.kwargs = kwargs
self.done = False
self.thread = threading.Thread(target=self._run)
self.thread.start()
def _run(self):
try:
self.result = self.target(*self.args, **self.kwargs)
except Exception as e:
self.result = e
self.done = True
def wait(self):
if not self.done:
self.thread.join()
if isinstance(self.result, Exception):
raise self.result
else:
return self.result
def async():
def decorator(fn):
def wrapper(*args, **kwargs):
return AsyncTask(fn, *args, **kwargs)
return wrapper
return decorator
def is_string(var):
return isinstance(var, string_types)
def is_command(text):
"""
Checks if `text` is a command. Telegram chat commands start with the '/' character.
:param text: Text to check.
:return: True if `text` is a command, else False.
"""
return text.startswith('/')
def extract_command(text):
"""
Extracts the command from `text` (minus the '/') if `text` is a command (see is_command).
If `text` is not a command, this function returns None.
Examples:
extract_command('/help'): 'help'
extract_command('/help@BotName'): 'help'
extract_command('/search black eyed peas'): 'search'
extract_command('Good day to you'): None
:param text: String to extract the command from
:return: the command if `text` is a command (according to is_command), else None.
"""
return text.split()[0].split('@')[0][1:] if is_command(text) else None
def split_string(text, chars_per_string):
"""
Splits one string into multiple strings, with a maximum amount of `chars_per_string` characters per string.
This is very useful for splitting one giant message into multiples.
:param text: The text to split
:param chars_per_string: The number of characters per line the text is split into.
:return: The splitted text as a list of strings.
"""
return [text[i:i + chars_per_string] for i in range(0, len(text), chars_per_string)]

View File

@ -9,6 +9,7 @@ import os
import telebot
from telebot import types
from telebot import apihelper
from telebot import util
should_skip = 'TOKEN' and 'CHAT_ID' not in os.environ
@ -67,18 +68,15 @@ class TestTeleBot:
time.sleep(1)
assert not msg.text == 'got'
def test_send_file_by_id(self):
file_id = 'BQADBQADjAIAAsYifgbvqwq1he9REAI'
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_document(CHAT_ID, file_id)
assert ret_msg.message_id
def test_send_file(self):
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_document(CHAT_ID, file_data)
assert ret_msg.message_id
ret_msg = tb.send_document(CHAT_ID, ret_msg.document.file_id)
assert ret_msg.message_id
def test_send_video(self):
file_data = open('./test_data/test_video.mp4', 'rb')
tb = telebot.TeleBot(TOKEN)
@ -100,18 +98,15 @@ class TestTeleBot:
print(e)
assert True
def test_send_photo_by_id(self):
photo_id = 'AgADBQADTKgxG8YifgbcWQAB7Da9yYIx1rEyAAT-HYJ3CrJEqdA2AQABAg'
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_photo(CHAT_ID, photo_id)
assert ret_msg.message_id
def test_send_photo(self):
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_photo(CHAT_ID, file_data)
assert ret_msg.message_id
ret_msg = tb.send_photo(CHAT_ID, ret_msg.photo[0].file_id)
assert ret_msg.message_id
def test_send_audio(self):
file_data = open('./test_data/record.mp3', 'rb')
tb = telebot.TeleBot(TOKEN)
@ -174,12 +169,12 @@ class TestTeleBot:
def test_is_string_unicode(self):
s1 = u'string'
assert apihelper.is_string(s1)
assert util.is_string(s1)
def test_is_string_string(self):
s1 = 'string'
assert apihelper.is_string(s1)
assert util.is_string(s1)
def test_not_string(self):
i1 = 10
assert not apihelper.is_string(i1)
assert not util.is_string(i1)