Compare commits

...

54 Commits

Author SHA1 Message Date
Jannes Höke aa8966832b release 3.2 and update copyright notice to 2015-2016 2016-01-05 14:12:03 +01:00
Jannes Höke e27427676d fix jobqueue creation 2016-01-05 13:40:07 +01:00
Jannes Höke 8175fd7b53 update timerbot example 2016-01-05 13:39:51 +01:00
Jannes Höke 2875bae881 Integrate JobQueue with Updater 2016-01-05 13:32:19 +01:00
Jannes Höke c756aa1c02 remove error raising in dispatchType 2016-01-05 12:49:48 +01:00
Jannes Höke c2f04331f3 Merge pull request #145 from python-telegram-bot/jobqueue
JobQueue by @franciscod
2016-01-04 12:34:29 +01:00
Jannes Höke 5293ec49ff Add section about JobQueue to README 2016-01-04 11:34:47 +01:00
Jannes Höke 9319903343 rename testclass 2016-01-04 11:08:43 +01:00
Jannes Höke 95e2aad9bd fix sphinx build 2016-01-04 02:36:27 +01:00
Jannes Höke 73f4f26da5 more tests 2016-01-04 02:36:15 +01:00
Jannes Höke 24b2bf335c Increase time for travis testing 2016-01-04 02:12:24 +01:00
Jannes Höke fc3fa0f515 Increase time for travis testing 2016-01-04 02:11:42 +01:00
Jannes Höke 87657bcd70 flake8 and string formatting 2016-01-04 02:05:39 +01:00
Jannes Höke 81b58c7d00 remove bot token (once again...) 2016-01-04 01:58:50 +01:00
Jannes Höke dd7d1255d1 fixes, tests and examples for job queue 2016-01-04 01:56:22 +01:00
Jannes Höke f813d4f5ec improve docstring 2016-01-04 01:32:47 +01:00
Jannes Höke 623863cee1 fix exception logging 2016-01-04 01:32:00 +01:00
Jannes Höke aa125e59fb Merge branch 'master' into jobqueue 2016-01-04 00:01:27 +01:00
Jannes Höke 68c844df0c implement starting jobqueue in thread and fix errors 2016-01-04 00:01:00 +01:00
Jannes Höke 3779d454f3 Merge pull request #136 from tsnoam/master
consistent TelegramError exceptions
2016-01-03 23:58:14 +01:00
Noam Meltzer 5d168bd479 improve unitest coverage 2016-01-04 00:34:39 +02:00
Noam Meltzer 1c8bb21790 fix python 2.6 unitests which with the backport package unittest2 2016-01-04 00:32:17 +02:00
Noam Meltzer 054c976ad6 add tsnoam to AUTHORS 2016-01-04 00:32:17 +02:00
Noam Meltzer bc2679f2ae utils.request: provide HTTP error code with unknown errors 2016-01-04 00:32:17 +02:00
Noam Meltzer 17dae68654 gitignore: files generated by unitests 2016-01-04 00:32:17 +02:00
Noam Meltzer 7734c1d6a6 fix unitests to work with new token validator 2016-01-04 00:32:13 +02:00
Noam Meltzer aeca3ca174 basic token validation
refs #134
2016-01-04 00:31:40 +02:00
Noam Meltzer 21fdaa4ff7 utils.request: decorate functions with general try... except...
refs #134
2016-01-04 00:31:40 +02:00
Noam Meltzer 79f29c4b9e properly handle utf-8 server response & identify json parsing errors
refs #134
2016-01-04 00:31:40 +02:00
Noam Meltzer a2d8ca3663 some errors are prefixed with '[Error]: ' - strip that down
use custom _lstrip_str() func instead of regex. a little performance
improvment and (IMO) a bit more readable.
2016-01-04 00:31:40 +02:00
Jannes Höke 6f76bfc2de Merge branch 'master' into jobqueue 2015-12-31 15:45:19 +01:00
Jannes Höke 97ebebefc1 Merge branch 'Tengah-master' 2015-12-31 15:41:53 +01:00
Jannes Höke 119743433d merge Tengah-master into master 2015-12-31 15:41:37 +01:00
Bob Pasker b0ad07f30f remove traceback import 2015-12-31 09:29:02 -05:00
Jannes Höke 114cb787ea Merge branch 'master' into jobqueue 2015-12-31 15:22:08 +01:00
Jannes Höke 5d1ef673a3 flake8 2015-12-31 15:04:42 +01:00
Jannes Höke 3e1cb08567 flake8 2015-12-31 15:03:40 +01:00
Jannes Höke 29a4062945 properly lock updater and dispatcher start/stop methods 2015-12-31 14:58:01 +01:00
Jannes Höke 2ff6fe9901 initial commit for JobQueue by @franciscod 2015-12-31 14:56:48 +01:00
Jannes Höke 34b91f5062 properly lock updater and dispatcher start/stop methods 2015-12-31 14:52:28 +01:00
Bob Pasker cc6c4a6eb9 Change a print statement to use logger instead 2015-12-31 08:33:42 -05:00
Jannes Höke fc9456e1a8 release v3.1.2 2015-12-29 14:46:00 +01:00
Jannes Höke 41ba57f064 don't stop the dispatcher thread on uncaught errors 2015-12-29 14:28:39 +01:00
Jannes Höke b205751aee change default value for poll_interval to 0 2015-12-29 14:28:39 +01:00
Jannes Höke c4f13bd1bb Merge pull request #138 from Gems/master
Fix file.download with custom_path
2015-12-28 12:27:26 +01:00
Eduard Gomolyako 7d7397317e Fix file.download with custom_path 2015-12-27 04:01:13 +03:00
Jannes Höke 631954e5d9 Merge pull request #133 from rahiel/master
dedicate examples to the public domain (CC0)
2015-12-25 17:56:07 +01:00
Rahiel Kasim 90ed128a61 dedicate examples to the public domain (CC0) 2015-12-25 15:19:17 +01:00
Jannes Höke 2a2b679362 improved and renamed examples 2015-12-22 13:23:59 +01:00
Jannes Höke f75e329225 fix docstring 2015-12-21 21:40:41 +01:00
Jannes Höke ea7c54ba07 Release v3.1.1, change author email everywhere 2015-12-21 21:18:53 +01:00
Jannes Höke 3c7b037eaa add additional arguments 'groups' and 'groupdict' for regex handlers 2015-12-21 20:25:31 +01:00
Jannes Höke c15a1bc151 fix for #123 2015-12-21 19:36:17 +01:00
Jannes Höke 9e7314134e fix docstring 2015-12-17 14:55:20 +01:00
62 changed files with 1159 additions and 345 deletions
+7
View File
@@ -61,3 +61,10 @@ target/
# Sublime Text 2
*.sublime*
# unitests files
telegram.mp3
telegram.mp4
telegram.ogg
telegram.png
telegram.webp
+1
View File
@@ -9,6 +9,7 @@ python:
install:
- pip install pylint flake8 coveralls
- pip install -r requirements.txt
- 'if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi'
script:
- nosetests --with-coverage --cover-package telegram/
- flake8 telegram
+1
View File
@@ -19,6 +19,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `macrojames <https://github.com/macrojames>`_
- `naveenvhegde <https://github.com/naveenvhegde>`_
- `njittam <https://github.com/njittam>`_
- `Noam Meltzer <https://github.com/tsnoam>`_
- `Rahiel Kasim <https://github.com/rahiel>`_
- `sooyhwang <https://github.com/sooyhwang>`_
- `wjt <https://github.com/wjt>`_
+23
View File
@@ -1,3 +1,26 @@
**2016-01-05**
*Released 3.2.0*
- Introducing ``JobQueue`` (original author: @franciscod)
- Streamlining all exceptions to ``TelegramError`` (Special thanks to @tsnoam)
- Proper locking of ``Updater`` and ``Dispatcher`` ``start`` and ``stop`` methods
- Small bugfixes
**2015-12-29**
*Released 3.1.2*
- Fix custom path for file downloads
- Don't stop the dispatcher thread on uncaught errors in handlers
**2015-12-21**
*Released 3.1.1*
- Fix a bug where asynchronous handlers could not have additional arguments
- Add ``groups`` and ``groupdict`` as additional arguments for regex-based handlers
**2015-12-16**
*Released 3.1.0*
+52 -8
View File
@@ -57,11 +57,13 @@ Table of contents
2. `API`_
3. `Logging`_
3. `JobQueue`_
4. `Examples`_
4. `Logging`_
5. `Documentation`_
5. `Examples`_
6. `Documentation`_
- `License`_
@@ -155,9 +157,9 @@ _`Getting started`
View the last release API documentation at: https://core.telegram.org/bots/api
------
--------------------
_`The Updater class`
------
--------------------
The ``Updater`` class is the new way to create bots with ``python-telegram-bot``. It provides an easy-to-use interface to the ``telegram.Bot`` by caring about getting new updates from telegram and forwarding them to the ``Dispatcher`` class. We can register handler functions in the ``Dispatcher`` to make our bot react to Telegram commands, messages and even arbitrary updates.
@@ -299,9 +301,39 @@ There are many more API methods, to read the full API documentation::
$ pydoc telegram.Bot
-----------
_`Logging`
_`JobQueue`
-----------
The ``JobQueue`` allows you to perform tasks with a delay or even periodically. The ``Updater`` will create one for you::
>>> from telegram import Updater
>>> u = Updater('TOKEN')
>>> j = u.job_queue
The job queue uses functions for tasks, so we define one and add it to the queue. Usually, when the first job is added to the queue, it wil start automatically. We can prevent this by setting ``prevent_autostart=True``::
>>> def job1(bot):
... bot.sendMessage(chat_id='@examplechannel', text='One message every minute')
>>> j.put(job1, 60, next_t=0, prevent_autostart=True)
You can also have a job that will not be executed repeatedly::
>>> def job2(bot):
... bot.sendMessage(chat_id='@examplechannel', text='A single message with 30s delay')
>>> j.put(job2, 30, repeat=False)
Now, because we didn't prevent the auto start this time, the queue will start ticking. It runs in a seperate thread, so it is non-blocking. When we stop the Updater, the related queue will be stopped as well::
>>> u.stop()
We can also stop the job queue by itself::
>>> j.stop()
----------
_`Logging`
----------
You can get logs in your main application by calling `logging` and setting the log level you want::
>>> import logging
@@ -318,14 +350,26 @@ _`Examples`
Here follows some examples to help you to get your own Bot up to speed:
- `echobot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py>`_ replies back messages.
- `echobot2 <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot2.py>`_ replies back messages.
- `roboed <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/roboed.py>`_ talks to `Robô Ed <http://www.ed.conpet.gov.br/br/converse.php>`_.
- `clibot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/clibot.py>`_ has a command line interface.
- `timerbot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py>`_ uses the ``JobQueue`` to send timed messages.
- `Welcome Bot <https://github.com/jh0ker/welcomebot>`_ greets everyone who joins a group chat.
Legacy examples (pre-3.0):
- `echobot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/legacy/echobot.py>`_ replies back messages.
- `roboed <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/legacy/roboed.py>`_ talks to `Robô Ed <http://www.ed.conpet.gov.br/br/converse.php>`_.
- `Simple-Echo-Telegram-Bot <https://github.com/sooyhwang/Simple-Echo-Telegram-Bot>`_ simple Python Telegram bot that echoes your input with Flask microframework, setWebhook method, and Google App Engine (optional) - by @sooyhwang.
- `DevOps Reaction Bot <https://github.com/leandrotoledo/gae-devops-reaction-telegram-bot>`_ sends latest or random posts from `DevOps Reaction <http://devopsreactions.tumblr.com/>`_. Running on `Google App Engine <https://cloud.google.com/appengine>`_ (billing has to be enabled for fully Socket API support).
Other notable examples:
- `TwitterForwarderBot <https://github.com/franciscod/telegram-twitter-forwarder-bot>`_ forwards you tweets from people that you have subscribed to.
================
+3 -3
View File
@@ -50,7 +50,7 @@ master_doc = 'index'
# General information about the project.
project = u'Python Telegram Bot'
copyright = u'2015, Leandro Toledo'
copyright = u'2015-2016, Leandro Toledo'
author = u'Leandro Toledo'
# The version info for the project you're documenting, acts as replacement for
@@ -58,9 +58,9 @@ author = u'Leandro Toledo'
# built documents.
#
# The short X.Y version.
version = '3.1'
version = '3.2'
# The full version, including alpha/beta/rc tags.
release = '3.1.0'
release = '3.2.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+7
View File
@@ -0,0 +1,7 @@
telegram.jobqueue module
========================
.. automodule:: telegram.jobqueue
:members:
:undoc-members:
:show-inheritance:
+1
View File
@@ -11,6 +11,7 @@ Submodules
telegram.bot
telegram.updater
telegram.dispatcher
telegram.jobqueue
telegram.chataction
telegram.contact
telegram.document
+116
View File
@@ -0,0 +1,116 @@
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>
+41 -29
View File
@@ -1,4 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Example Bot to show some of the functionality of the library
# This program is dedicated to the public domain under the CC0 license.
"""
This Bot uses the Updater class to handle the bot.
@@ -9,8 +13,8 @@ Then, the bot is started and the CLI-Loop is entered, where all text inputs are
inserted into the update queue for the bot to handle.
Usage:
Basic Echobot example, repeats messages. Reply to last chat from the command
line by typing "/reply <text>"
Repeats messages with a delay.
Reply to last chat from the command line by typing "/reply <text>"
Type 'stop' on the command line to stop the bot.
"""
@@ -18,24 +22,20 @@ from telegram import Updater
from telegram.dispatcher import run_async
from time import sleep
import logging
import sys
root = logging.getLogger()
root.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO)
formatter = \
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
root.addHandler(ch)
last_chat_id = 0
# Enable Logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
# We use this var to save the last chat id, so we can reply to it
last_chat_id = 0
# Command Handlers
# Define a few (command) handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
""" Answer in Telegram """
bot.sendMessage(update.message.chat_id, text='Hi!')
@@ -65,10 +65,11 @@ def unknown_command(bot, update):
@run_async
def message(bot, update):
def message(bot, update, **kwargs):
"""
Example for an asynchronous handler. It's not guaranteed that replies will
be in order when using @run_async.
be in order when using @run_async. Also, you have to include **kwargs in
your parameter list. The kwargs contain all optional parameters that are
"""
sleep(2) # IO-heavy operation here
@@ -76,15 +77,12 @@ def message(bot, update):
update.message.text)
def error(bot, update, error):
""" Print error to console """
logger.warn('Update %s caused error %s' % (update, error))
# These handlers are for updates of type str. We use them to react to inputs
# on the command line interface
def cli_reply(bot, update, args):
"""
For any update of type telegram.Update or str, you can get the argument
list by appending args to the function parameters.
For any update of type telegram.Update or str that contains a command, you
can get the argument list by appending args to the function parameters.
Here, we reply to the last active chat with the text after the command.
"""
if last_chat_id is not 0:
@@ -96,6 +94,9 @@ def cli_noncommand(bot, update, update_queue):
You can also get the update queue as an argument in any handler by
appending it to the argument list. Be careful with this though.
Here, we put the input string back into the queue, but as a command.
To learn more about those optional handler parameters, read:
http://python-telegram-bot.readthedocs.org/en/latest/telegram.dispatcher.html
"""
update_queue.put('/%s' % update)
@@ -104,28 +105,39 @@ def unknown_cli_command(bot, update):
logger.warn("Command not found: %s" % update)
def error(bot, update, error):
""" Print error to console """
logger.warn('Update %s caused error %s' % (update, error))
def main():
# Create the EventHandler and pass it your bot's token.
token = 'token'
updater = Updater(token, workers=2)
token = 'TOKEN'
updater = Updater(token, workers=10)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# This is how we add handlers for Telegram messages
dp.addTelegramCommandHandler("start", start)
dp.addTelegramCommandHandler("help", help)
dp.addUnknownTelegramCommandHandler(unknown_command)
# Message handlers only receive updates that don't contain commands
dp.addTelegramMessageHandler(message)
# Regex handlers will receive all updates on which their regex matches
dp.addTelegramRegexHandler('.*', any_message)
# String handlers work pretty much the same
dp.addStringCommandHandler('reply', cli_reply)
dp.addUnknownStringCommandHandler(unknown_cli_command)
dp.addStringRegexHandler('[^/].*', cli_noncommand)
# All TelegramErrors are caught for you and delivered to the error
# handler(s). Other types of Errors are not caught.
dp.addErrorHandler(error)
# Start the Bot and store the update Queue, so we can insert updates
update_queue = updater.start_polling(poll_interval=0.1, timeout=20)
update_queue = updater.start_polling(poll_interval=0.1, timeout=10)
'''
# Alternatively, run with webhook:
@@ -157,9 +169,9 @@ def main():
updater.stop()
break
# else, put the text into the update queue
# else, put the text into the update queue to be handled by our handlers
elif len(text) > 0:
update_queue.put(text) # Put command into queue
update_queue.put(text)
if __name__ == '__main__':
main()
@@ -1,36 +1,35 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Simple Bot to reply to Telegram messages
# This program is dedicated to the public domain under the CC0 license.
"""
This Bot uses the Updater class to handle the bot.
First, a few handler functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and the CLI-Loop is entered.
Then, the bot is started and runs until we press Ctrl-C on the command line.
Usage:
Basic Echobot example, repeats messages.
Type 'stop' on the command line to stop the bot.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram import Updater
import logging
import sys
# Enable logging
root = logging.getLogger()
root.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.DEBUG)
formatter = \
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
root.addHandler(ch)
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
# Command Handlers
# Define a few command handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
bot.sendMessage(update.message.chat_id, text='Hi!')
@@ -61,14 +60,15 @@ def main():
# on noncommand i.e message - echo the message on Telegram
dp.addTelegramMessageHandler(echo)
# on error - print error to stdout
# log all errors
dp.addErrorHandler(error)
# Start the Bot
updater.start_polling(timeout=5)
updater.start_polling()
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT
# Run the bot until the you presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT. This should be used most of the time, since
# start_polling() is non-blocking and will stop the bot gracefully.
updater.idle()
if __name__ == '__main__':
@@ -2,21 +2,7 @@
# -*- coding: utf-8 -*-
#
# Simple Bot to reply to Telegram messages
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# This program is dedicated to the public domain under the CC0 license.
import logging
import telegram
@@ -2,23 +2,7 @@
# encoding: utf-8
#
# Robô Ed Telegram Bot
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
__author__ = 'leandrotoledodesouza@gmail.com'
# This program is dedicated to the public domain under the CC0 license.
import logging
import telegram
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Simple Bot to send timed Telegram messages
# This program is dedicated to the public domain under the CC0 license.
"""
This Bot uses the Updater class to handle the bot and the JobQueue to send
timed messages.
First, a few handler functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
Usage:
Basic Alarm Bot example, sends a message after a set time.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram import Updater
import logging
# Enable logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
job_queue = None
# Define a few command handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
bot.sendMessage(update.message.chat_id, text='Hi! Use /set <seconds> to '
'set a timer')
def set(bot, update, args):
""" Adds a job to the queue """
chat_id = update.message.chat_id
try:
# args[0] should contain the time for the timer in seconds
due = int(args[0])
def alarm(bot):
""" Inner function to send the alarm message """
bot.sendMessage(chat_id, text='Beep!')
# Add job to queue
job_queue.put(alarm, due, repeat=False)
bot.sendMessage(chat_id, text='Timer successfully set!')
except IndexError:
bot.sendMessage(chat_id, text='Usage: /set <seconds>')
except ValueError:
bot.sendMessage(chat_id, text='Usage: /set <seconds>')
def error(bot, update, error):
logger.warn('Update "%s" caused error "%s"' % (update, error))
def main():
global job_queue
updater = Updater("148447715:AAFj2qskb3_sMMzlDhgTuoP7VAABNHpT0BU")
job_queue = updater.job_queue
# Get the dispatcher to register handlers
dp = updater.dispatcher
# on different commands - answer in Telegram
dp.addTelegramCommandHandler("start", start)
dp.addTelegramCommandHandler("help", start)
dp.addTelegramCommandHandler("set", set)
# log all errors
dp.addErrorHandler(error)
# Start the Bot
updater.start_polling()
# Block until the you presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT. This should be used most of the time, since
# start_polling() is non-blocking and will stop the bot gracefully.
updater.idle()
if __name__ == '__main__':
main()
+2 -2
View File
@@ -26,9 +26,9 @@ def requirements():
setup(
name='python-telegram-bot',
version='3.1.0',
version='3.2.0',
author='Leandro Toledo',
author_email='leandrotoledodesouza@gmail.com',
author_email='devs@python-telegram-bot.org',
license='LGPLv3',
url='https://github.com/python-telegram-bot/python-telegram-bot',
keywords='python telegram bot api wrapper',
+6 -4
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
@@ -18,8 +19,8 @@
"""A library that provides a Python interface to the Telegram Bot API"""
__author__ = 'leandrotoledodesouza@gmail.com'
__version__ = '3.1.0'
__author__ = 'devs@python-telegram-bot.org'
__version__ = '3.2.0'
from .base import TelegramObject
from .user import User
@@ -48,6 +49,7 @@ from .message import Message
from .update import Update
from .bot import Bot
from .dispatcher import Dispatcher
from .jobqueue import JobQueue
from .updater import Updater
__all__ = ['Bot', 'Updater', 'Dispatcher', 'Emoji', 'TelegramError',
@@ -55,4 +57,4 @@ __all__ = ['Bot', 'Updater', 'Dispatcher', 'Emoji', 'TelegramError',
'ReplyKeyboardMarkup', 'UserProfilePhotos', 'ChatAction',
'Location', 'Contact', 'Video', 'Sticker', 'Document', 'File',
'Audio', 'PhotoSize', 'Chat', 'Update', 'ParseMode', 'Message',
'User', 'TelegramObject', 'NullHandler', 'Voice']
'User', 'TelegramObject', 'NullHandler', 'Voice', 'JobQueue']
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+11 -2
View File
@@ -2,7 +2,8 @@
# pylint: disable=E0611,E0213,E1102,C0103,E1101,W0613,R0913,R0904
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
@@ -52,7 +53,7 @@ class Bot(TelegramObject):
def __init__(self,
token,
base_url=None):
self.token = token
self.token = self._valid_token(token)
if base_url is None:
self.base_url = 'https://api.telegram.org/bot%s' % self.token
@@ -744,3 +745,11 @@ class Bot(TelegramObject):
def __reduce__(self):
return (self.__class__, (self.token,
self.base_url.replace(self.token, '')))
@staticmethod
def _valid_token(token):
"""a very basic validation on token"""
left, sep, _right = token.partition(':')
if (not sep) or (not left.isdigit()) or (len(left) < 3):
raise TelegramError('Invalid token')
return token
+2 -1
View File
@@ -2,7 +2,8 @@
# pylint: disable=C0103,W0622
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -2,7 +2,8 @@
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+111 -74
View File
@@ -1,4 +1,22 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""
This module contains the Dispatcher class.
@@ -21,7 +39,9 @@ async_lock = Lock()
def run_async(func):
"""
Function decorator that will run the function in a new thread.
Function decorator that will run the function in a new thread. A function
decorated with this will have to include **kwargs in their parameter list,
which will contain all optional parameters.
Args:
func (function): The function to run in the thread.
@@ -31,24 +51,24 @@ def run_async(func):
"""
@wraps(func)
def pooled(*args, **kwargs):
def pooled(*pargs, **kwargs):
"""
A wrapper to run a thread in a thread pool
"""
global running_async, async_lock
result = func(*args, **kwargs)
result = func(*pargs, **kwargs)
semaphore.release()
with async_lock:
running_async -= 1
return result
@wraps(func)
def async_func(*args, **kwargs):
def async_func(*pargs, **kwargs):
"""
A wrapper to run a function in a thread
"""
global running_async, async_lock
thread = Thread(target=pooled, args=args, kwargs=kwargs)
thread = Thread(target=pooled, args=pargs, kwargs=kwargs)
semaphore.acquire()
with async_lock:
running_async += 1
@@ -89,12 +109,21 @@ class Dispatcher:
Example: '/add item1 item2 item3' -> ['item1', 'item2', 'item3']
For other updates, args will be None
Attributes:
For regex-based handlers, you can also request information about the match.
For all other handlers, these will be None
groups:
A tuple that contains the result of
re.match(matcher, ...).groups()
groupdict:
A dictionary that contains the result of
re.match(matcher, ...).groupdict()
Args:
bot (telegram.Bot): The bot object that should be passed to the
handlers update_queue (queue.Queue): The synchronized queue that will
contain the updates.
handlers
update_queue (queue.Queue): The synchronized queue that will
contain the updates.
"""
def __init__(self, bot, update_queue, workers=4):
self.bot = bot
@@ -110,6 +139,7 @@ class Dispatcher:
self.error_handlers = []
self.logger = logging.getLogger(__name__)
self.running = False
self.__lock = Lock()
global semaphore
if not semaphore:
@@ -130,37 +160,48 @@ class Dispatcher:
the update queue.
"""
self.running = True
self.logger.info('Dispatcher thread started')
self.__lock.acquire()
if not self.running:
self.running = True
self.__lock.release()
self.logger.info('Dispatcher thread started')
while True:
update = None
while True:
update = None
try:
# Pop update from update queue.
# Blocks if no updates are available.
update = self.update_queue.get()
try:
# Pop update from update queue.
# Blocks if no updates are available.
update = self.update_queue.get()
if type(update) is self._Stop:
break
if type(update) is self._Stop:
break
self.processUpdate(update)
self.logger.debug('Processed Update: %s' % update)
self.processUpdate(update)
self.logger.debug('Processed Update: %s' % update)
# Dispatch any errors
except TelegramError as te:
self.logger.warn("Error was raised while processing Update.")
self.dispatchError(update, te)
# Dispatch any errors
except TelegramError as te:
self.logger.warn("Error was raised while processing "
"Update.")
self.dispatchError(update, te)
# All other errors should not stop the thread, just print them
except:
self.logger.exception("An uncaught error was raised while "
"processing an update")
else:
self.__lock.release()
self.logger.info('Dispatcher thread stopped')
def stop(self):
"""
Stops the thread
"""
if self.running:
self.running = False
self.update_queue.put(self._Stop())
with self.__lock:
if self.running:
self.running = False
self.update_queue.put(self._Stop())
def processUpdate(self, update):
"""
@@ -183,7 +224,7 @@ class Dispatcher:
self.dispatchStringCommand(update)
handled = True
elif type(update) is str:
self.dispatchStringRegex(update)
self.dispatchRegex(update)
handled = True
# An error happened while polling
@@ -193,7 +234,7 @@ class Dispatcher:
# Telegram update (regex)
if isinstance(update, Update):
self.dispatchTelegramRegex(update)
self.dispatchRegex(update)
handled = True
# Telegram update (command)
@@ -467,25 +508,30 @@ class Dispatcher:
else:
self.dispatchTo(self.unknown_telegram_command_handlers, update)
def dispatchTelegramRegex(self, update):
def dispatchRegex(self, update):
"""
Dispatches an update to all regex handlers that match the message
string.
Dispatches an update to all string or telegram regex handlers that
match the string/message content.
Args:
command (str): The command keyword
update (telegram.Update): The Telegram update that contains the
command
update (str, Update): The update that should be checked for matches
"""
matching_handlers = []
if isinstance(update, Update):
handlers = self.telegram_regex_handlers
to_match = update.message.text
elif isinstance(update, str):
handlers = self.string_regex_handlers
to_match = update
for matcher in self.telegram_regex_handlers:
if match(matcher, update.message.text):
for handler in self.telegram_regex_handlers[matcher]:
matching_handlers.append(handler)
self.dispatchTo(matching_handlers, update)
for matcher in handlers:
m = match(matcher, to_match)
if m:
for handler in handlers[matcher]:
self.call_handler(handler,
update,
groups=m.groups(),
groupdict=m.groupdict())
def dispatchStringCommand(self, update):
"""
@@ -502,26 +548,6 @@ class Dispatcher:
else:
self.dispatchTo(self.unknown_string_command_handlers, update)
def dispatchStringRegex(self, update):
"""
Dispatches an update to all string regex handlers that match the
string.
Args:
command (str): The command keyword
update (telegram.Update): The Telegram update that contains the
command
"""
matching_handlers = []
for matcher in self.string_regex_handlers:
if match(matcher, update):
for handler in self.string_regex_handlers[matcher]:
matching_handlers.append(handler)
self.dispatchTo(matching_handlers, update)
def dispatchType(self, update):
"""
Dispatches an update of any type.
@@ -533,9 +559,6 @@ class Dispatcher:
for t in self.type_handlers:
if isinstance(update, t):
self.dispatchTo(self.type_handlers[t], update)
else:
self.dispatchError(update, TelegramError(
"Received update of unknown type %s" % type(update)))
def dispatchTelegramMessage(self, update):
"""
@@ -560,7 +583,7 @@ class Dispatcher:
for handler in self.error_handlers:
handler(self.bot, update, error)
def dispatchTo(self, handlers, update):
def dispatchTo(self, handlers, update, **kwargs):
"""
Dispatches an update to a list of handlers.
@@ -570,9 +593,9 @@ class Dispatcher:
"""
for handler in handlers:
self.call_handler(handler, update)
self.call_handler(handler, update, **kwargs)
def call_handler(self, handler, update):
def call_handler(self, handler, update, **kwargs):
"""
Calls an update handler. Checks the handler for keyword arguments and
fills them, if possible.
@@ -581,13 +604,21 @@ class Dispatcher:
handler (function): An update handler function
update (any): An update
"""
kwargs = {}
target_kwargs = {}
fargs = getargspec(handler).args
if 'update_queue' in fargs:
kwargs['update_queue'] = self.update_queue
'''
async handlers will receive all optional arguments, since we can't
their argument list.
'''
if 'args' in fargs:
is_async = 'pargs' == getargspec(handler).varargs
if is_async or 'update_queue' in fargs:
target_kwargs['update_queue'] = self.update_queue
if is_async or 'args' in fargs:
if isinstance(update, Update):
args = update.message.text.split(' ')[1:]
elif isinstance(update, str):
@@ -595,6 +626,12 @@ class Dispatcher:
else:
args = None
kwargs['args'] = args
target_kwargs['args'] = args
handler(self.bot, update, **kwargs)
if is_async or 'groups' in fargs:
target_kwargs['groups'] = kwargs.get('groups', None)
if is_async or 'groupdict' in fargs:
target_kwargs['groupdict'] = kwargs.get('groupdict', None)
handler(self.bot, update, **target_kwargs)
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -3,7 +3,8 @@
# pylint: disable=C0103,C0301,R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+28 -8
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
@@ -18,7 +19,22 @@
"""This module contains a object that represents a Telegram Error"""
import re
def _lstrip_str(in_s, lstr):
"""
Args:
in_s (str): in string
lstr (str): substr to strip from left side
Returns:
str:
"""
if in_s.startswith(lstr):
res = in_s[len(lstr):]
else:
res = in_s
return res
class TelegramError(Exception):
@@ -26,16 +42,20 @@ class TelegramError(Exception):
def __init__(self, message):
"""
Args:
message (str):
Returns:
str:
"""
super(TelegramError, self).__init__()
api_error = re.match(r'^Error: (?P<message>.*)', message)
if api_error:
self.message = api_error.group('message').capitalize()
else:
self.message = message
msg = _lstrip_str(message, 'Error: ')
msg = _lstrip_str(msg, '[Error]: ')
if msg != message:
# api_error - capitalize the msg...
msg = msg.capitalize()
self.message = msg
def __str__(self):
return '%s' % (self.message)
+3 -2
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
@@ -74,7 +75,7 @@ class File(TelegramObject):
url = self.file_path
if custom_path:
filename = basename(custom_path)
filename = custom_path
else:
filename = basename(url)
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -2,7 +2,8 @@
# pylint: disable=W0622,E0611
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""
This module contains the class JobQueue
"""
import logging
import time
from threading import Thread, Lock
try:
from queue import PriorityQueue
except ImportError:
from Queue import PriorityQueue
class JobQueue(object):
"""
This class allows you to periodically perform tasks with the bot.
Attributes:
tick_interval (float):
queue (PriorityQueue):
bot (Bot):
running (bool):
Args:
bot (Bot): The bot instance that should be passed to the jobs
Keyword Args:
tick_interval (Optional[float]): The interval this queue should check
the newest task in seconds. Defaults to 1.0
"""
def __init__(self, bot, tick_interval=1.0):
self.tick_interval = tick_interval
self.queue = PriorityQueue()
self.bot = bot
self.logger = logging.getLogger(__name__)
self.__lock = Lock()
self.running = False
def put(self,
run,
interval,
repeat=True,
next_t=None,
prevent_autostart=False):
"""
Queue a new job. If the JobQueue is not running, it will be started.
Args:
run (function): A function that takes the parameter `bot`
interval (float): The interval in seconds in which `run` should be
executed
repeat (Optional[bool]): If `False`, job will only be executed once
next_t (Optional[float]): Time in seconds in which run should be
executed first. Defaults to `interval`
prevent_autostart (Optional[bool]): If `True`, the job queue will
not be started automatically if it is not running.
"""
name = run.__name__
job = JobQueue.Job()
job.run = run
job.interval = interval
job.name = name
job.repeat = repeat
if next_t is None:
next_t = interval
next_t += time.time()
self.logger.debug("Putting a %s with t=%f" % (job.name, next_t))
self.queue.put((next_t, job))
if not self.running and not prevent_autostart:
self.logger.info("Auto-starting JobQueue")
self.start()
def tick(self):
"""
Run all jobs that are due and re-enqueue them with their interval
"""
now = time.time()
self.logger.debug("Ticking jobs with t=%f" % now)
while not self.queue.empty():
t, j = self.queue.queue[0]
self.logger.debug("Peeked at %s with t=%f" % (j.name, t))
if t < now:
self.queue.get()
self.logger.info("Running job %s" % j.name)
try:
j.run(self.bot)
except:
self.logger.exception("An uncaught error was raised while "
"executing job %s" % j.name)
if j.repeat:
self.put(j.run, j.interval)
continue
self.logger.debug("Next task isn't due yet. Finished!")
break
def start(self):
"""
Starts the job_queue thread.
"""
self.__lock.acquire()
if not self.running:
self.running = True
self.__lock.release()
job_queue_thread = Thread(target=self._start,
name="job_queue")
job_queue_thread.start()
self.logger.info('Job Queue thread started')
else:
self.__lock.release()
def _start(self):
"""
Thread target of thread 'job_queue'. Runs in background and performs
ticks on the job queue.
"""
while self.running:
self.tick()
time.sleep(self.tick_interval)
self.logger.info('Job Queue thread stopped')
def stop(self):
"""
Stops the thread
"""
with self.__lock:
self.running = False
class Job(object):
""" Inner class that represents a job """
interval = None
name = None
repeat = None
def run(self):
pass
def __lt__(self, other):
return False
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+3 -2
View File
@@ -2,7 +2,8 @@
# pylint: disable=R0902,R0912,R0913
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
@@ -60,7 +61,7 @@ class Message(TelegramObject):
message_id (int):
from_user (:class:`telegram.User`):
date (:class:`datetime.datetime`):
chat (:class:`telegram.User` or :class:`telegram.Chat`):
chat (:class:`telegram.Chat`):
**kwargs: Arbitrary keyword arguments.
Keyword Args:
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -2,7 +2,8 @@
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+103 -51
View File
@@ -1,4 +1,22 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""
This module contains the class Updater, which tries to make creating Telegram
@@ -7,12 +25,12 @@ Bots intuitive!
import logging
import os
import ssl
from threading import Thread
from threading import Thread, Lock
from time import sleep
import subprocess
from signal import signal, SIGINT, SIGTERM, SIGABRT
from telegram import (Bot, TelegramError, dispatcher, Dispatcher,
NullHandler)
NullHandler, JobQueue)
from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
# Adjust for differences in Python versions
@@ -47,16 +65,33 @@ class Updater:
Attributes:
Args:
token (str): The bots token given by the @BotFather
token (Optional[str]): The bot's token given by the @BotFather
base_url (Optional[str]):
workers (Optional[int]): Amount of threads in the thread pool for
functions decorated with @run_async
bot (Optional[Bot]):
Raises:
ValueError: If both `token` and `bot` are passed or none of them.
"""
def __init__(self, token, base_url=None, workers=4):
def __init__(self,
token=None,
base_url=None,
workers=4,
bot=None,
job_queue_tick_interval=1.0):
if (token is None) and (bot is None):
raise ValueError('`token` or `bot` must be passed')
if (token is not None) and (bot is not None):
raise ValueError('`token` and `bot` are mutually exclusive')
self.bot = Bot(token, base_url)
if bot is not None:
self.bot = bot
else:
self.bot = Bot(token, base_url)
self.update_queue = Queue()
self.job_queue = JobQueue(self.bot, job_queue_tick_interval)
self.dispatcher = Dispatcher(self.bot, self.update_queue,
workers=workers)
self.last_update_id = 0
@@ -64,14 +99,15 @@ class Updater:
self.running = False
self.is_idle = False
self.httpd = None
self.__lock = Lock()
def start_polling(self, poll_interval=1.0, timeout=10, network_delay=2):
def start_polling(self, poll_interval=0.0, timeout=10, network_delay=2):
"""
Starts polling updates from Telegram.
Args:
poll_interval (Optional[float]): Time to wait between polling
updates from Telegram in seconds. Default is 1.0
updates from Telegram in seconds. Default is 0.0
timeout (Optional[float]): Passed to Bot.getUpdates
network_delay (Optional[float]): Passed to Bot.getUpdates
@@ -79,22 +115,25 @@ class Updater:
Queue: The update queue that can be filled from the main thread
"""
# Create Thread objects
dispatcher_thread = Thread(target=self.dispatcher.start,
name="dispatcher")
event_handler_thread = Thread(target=self._start_polling,
name="updater",
args=(poll_interval, timeout,
network_delay))
with self.__lock:
if not self.running:
self.running = True
self.running = True
# Create Thread objects
dispatcher_thread = Thread(target=self.dispatcher.start,
name="dispatcher")
updater_thread = Thread(target=self._start_polling,
name="updater",
args=(poll_interval,
timeout,
network_delay))
# Start threads
dispatcher_thread.start()
event_handler_thread.start()
# Start threads
dispatcher_thread.start()
updater_thread.start()
# Return the update queue so the main thread can insert updates
return self.update_queue
# Return the update queue so the main thread can insert updates
return self.update_queue
def start_webhook(self,
listen='127.0.0.1',
@@ -120,21 +159,27 @@ class Updater:
Queue: The update queue that can be filled from the main thread
"""
# Create Thread objects
dispatcher_thread = Thread(target=self.dispatcher.start,
name="dispatcher")
event_handler_thread = Thread(target=self._start_webhook,
name="updater",
args=(listen, port, url_path, cert, key))
with self.__lock:
if not self.running:
self.running = True
self.running = True
# Create Thread objects
dispatcher_thread = Thread(target=self.dispatcher.start,
name="dispatcher")
updater_thread = Thread(target=self._start_webhook,
name="updater",
args=(listen,
port,
url_path,
cert,
key))
# Start threads
dispatcher_thread.start()
event_handler_thread.start()
# Start threads
dispatcher_thread.start()
updater_thread.start()
# Return the update queue so the main thread can insert updates
return self.update_queue
# Return the update queue so the main thread can insert updates
return self.update_queue
def _start_polling(self, poll_interval, timeout, network_delay):
"""
@@ -175,9 +220,11 @@ class Updater:
except URLError as e:
self.logger.error("Error while getting Updates: %s" % e)
# increase waiting times on subsequent errors up to 30secs
if current_interval < 30:
if current_interval == 0:
current_interval = 1
elif current_interval < 30:
current_interval += current_interval / 2
if current_interval > 30:
elif current_interval > 30:
current_interval = 30
self.logger.info('Updater thread stopped')
@@ -217,26 +264,31 @@ class Updater:
def stop(self):
"""
Stops the polling/webhook thread and the dispatcher
Stops the polling/webhook thread, the dispatcher and the job queue
"""
self.logger.info('Stopping Updater and Dispatcher...')
self.logger.debug('This might take a long time if you set a high value'
' as polling timeout.')
self.running = False
if self.httpd:
self.logger.info(
'Waiting for current webhook connection to be closed... '
'Send a Telegram message to the bot to exit immediately.')
self.httpd.shutdown()
self.httpd = None
self.job_queue.stop()
with self.__lock:
if self.running:
self.running = False
self.logger.info('Stopping Updater and Dispatcher...')
self.logger.debug('This might take a long time if you set a '
'high value as polling timeout.')
self.logger.debug("Requesting Dispatcher to stop...")
self.dispatcher.stop()
while dispatcher.running_async > 0:
sleep(1)
if self.httpd:
self.logger.info(
'Waiting for current webhook connection to be '
'closed... Send a Telegram message to the bot to exit '
'immediately.')
self.httpd.shutdown()
self.httpd = None
self.logger.debug("Dispatcher stopped.")
self.logger.debug("Requesting Dispatcher to stop...")
self.dispatcher.stop()
while dispatcher.running_async > 0:
sleep(1)
self.logger.debug("Dispatcher stopped.")
def signal_handler(self, signum, frame):
self.is_idle = False
@@ -244,7 +296,7 @@ class Updater:
def idle(self, stop_signals=(SIGINT, SIGTERM, SIGABRT)):
"""
Waits for the user to press Ctrl-C and stops the updater
Blocks until one of the signals are received and stops the updater
Args:
stop_signals: Iterable containing signals from the signal module
+2 -1
View File
@@ -2,7 +2,8 @@
# pylint: disable=C0103,W0622
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+49 -31
View File
@@ -2,7 +2,8 @@
# pylint: disable=no-name-in-module,unused-import
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
@@ -19,6 +20,7 @@
"""This module contains methods to make POST and GET requests"""
import functools
import json
import socket
from ssl import SSLError
@@ -45,7 +47,11 @@ def _parse(json_data):
Returns:
A JSON parsed as Python dict with results.
"""
data = json.loads(json_data.decode())
decoded_s = json_data.decode('utf-8')
try:
data = json.loads(decoded_s)
except ValueError:
raise TelegramError('Invalid server response')
if not data.get('ok') and data.get('description'):
return data['description']
@@ -53,6 +59,34 @@ def _parse(json_data):
return data['result']
def _try_except_req(func):
"""Decorator for requests to handle known exceptions"""
@functools.wraps(func)
def decorator(*args, **kwargs):
try:
return func(*args, **kwargs)
except HTTPError as error:
if error.getcode() == 403:
raise TelegramError('Unauthorized')
if error.getcode() == 502:
raise TelegramError('Bad Gateway')
try:
message = _parse(error.read())
except ValueError:
message = 'Unknown HTTPError {0}'.format(error.getcode())
raise TelegramError(message)
except (SSLError, socket.timeout) as error:
if "operation timed out" in str(error):
raise TelegramError("Timed out")
raise TelegramError(str(error))
return decorator
@_try_except_req
def get(url):
"""Request an URL.
Args:
@@ -67,6 +101,7 @@ def get(url):
return _parse(result)
@_try_except_req
def post(url,
data,
network_delay=2.):
@@ -91,39 +126,22 @@ def post(url,
else:
timeout = None
try:
if InputFile.is_inputfile(data):
data = InputFile(data)
request = Request(url,
data=data.to_form(),
headers=data.headers)
else:
data = json.dumps(data)
request = Request(url,
data=data.encode(),
headers={'Content-Type': 'application/json'})
if InputFile.is_inputfile(data):
data = InputFile(data)
request = Request(url,
data=data.to_form(),
headers=data.headers)
else:
data = json.dumps(data)
request = Request(url,
data=data.encode(),
headers={'Content-Type': 'application/json'})
result = urlopen(request, timeout=timeout).read()
except HTTPError as error:
if error.getcode() == 403:
raise TelegramError('Unauthorized')
if error.getcode() == 502:
raise TelegramError('Bad Gateway')
try:
message = _parse(error.read())
except ValueError:
message = 'Unknown HTTPError'
raise TelegramError(message)
except (SSLError, socket.timeout) as error:
if "operation timed out" in str(error):
raise TelegramError("Timed out")
raise TelegramError(str(error))
result = urlopen(request, timeout=timeout).read()
return _parse(result)
@_try_except_req
def download(url,
filename):
"""Download a file by its URL.
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+36 -2
View File
@@ -2,7 +2,8 @@
# encoding: utf-8
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,9 +21,14 @@
"""This module contains a object that represents Tests for Telegram Bot"""
import os
import unittest
from datetime import datetime
import sys
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
sys.path.append('.')
import telegram
@@ -137,5 +143,33 @@ class BotTest(BaseTest, unittest.TestCase):
self.assertTrue(self.is_json(upf.to_json()))
self.assertEqual(upf.photos[0][0].file_size, 12421)
def _test_invalid_token(self, token):
print('Testing invalid token: {0}'.format(token))
self.assertRaisesRegexp(telegram.TelegramError, 'Invalid token', telegram.Bot, token)
def testInvalidToken1(self):
self._test_invalid_token('123')
def testInvalidToken2(self):
self._test_invalid_token('12a:')
def testInvalidToken3(self):
self._test_invalid_token('12:')
def testUnauthToken(self):
print('Testing unauthorized token')
with self.assertRaisesRegexp(telegram.TelegramError, 'Unauthorized'):
bot = telegram.Bot('1234:abcd1234')
bot.getMe()
def testInvalidSrvResp(self):
print('Testing invalid server response')
with self.assertRaisesRegexp(telegram.TelegramError, 'Invalid server response'):
# bypass the valid token check
bot_cls = type('bot_cls', (telegram.Bot, ), {'_valid_token': lambda self, token: token})
bot = bot_cls('12')
bot.getMe()
if __name__ == '__main__':
unittest.main()
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python
# encoding: utf-8
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""
This module contains a object that represents Tests for JobQueue
"""
import logging
import sys
from time import sleep
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
try:
from urllib2 import urlopen, Request
except ImportError:
from urllib.request import Request, urlopen
sys.path.append('.')
from telegram import JobQueue, Updater
from tests.base import BaseTest
# Enable logging
root = logging.getLogger()
root.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.WARN)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
root.addHandler(ch)
class JobQueueTest(BaseTest, unittest.TestCase):
"""
This object represents Tests for Updater, Dispatcher, WebhookServer and
WebhookHandler
"""
def setUp(self):
self.jq = JobQueue("Bot", tick_interval=0.005)
self.result = 0
def tearDown(self):
if self.jq is not None:
self.jq.stop()
def job1(self, bot):
self.result += 1
def job2(self, bot):
raise Exception("Test Error")
def test_basic(self):
print('Testing basic job queue function')
self.jq.put(self.job1, 0.1)
sleep(1.5)
self.assertGreaterEqual(self.result, 10)
def test_noRepeat(self):
print('Testing job queue without repeat')
self.jq.put(self.job1, 0.1, repeat=False)
sleep(0.5)
self.assertEqual(1, self.result)
def test_nextT(self):
print('Testing job queue with a set next_t value')
self.jq.put(self.job1, 0.1, next_t=0.5)
sleep(0.45)
self.assertEqual(0, self.result)
sleep(0.1)
self.assertEqual(1, self.result)
def test_multiple(self):
print('Testing job queue with multiple jobs')
self.jq.put(self.job1, 0.1, repeat=False)
self.jq.put(self.job1, 0.2, repeat=False)
self.jq.put(self.job1, 0.4)
sleep(1)
self.assertEqual(4, self.result)
def test_error(self):
print('Testing job queue starting twice with an erroneous job')
self.jq.put(self.job2, 0.1)
self.jq.put(self.job1, 0.2)
self.jq.start()
sleep(0.4)
self.assertEqual(1, self.result)
def test_inUpdater(self):
print('Testing job queue created by updater')
u = Updater(bot="MockBot", job_queue_tick_interval=0.005)
u.job_queue.put(self.job1, 0.1)
sleep(0.15)
self.assertEqual(1, self.result)
u.stop()
sleep(0.4)
self.assertEqual(1, self.result)
if __name__ == '__main__':
unittest.main()
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+86 -43
View File
@@ -2,7 +2,8 @@
# encoding: utf-8
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,7 +23,6 @@ This module contains a object that represents Tests for Updater, Dispatcher,
WebhookServer and WebhookHandler
"""
import logging
import unittest
import sys
import re
import os
@@ -31,6 +31,11 @@ from random import randrange
from time import sleep
from datetime import datetime
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
try:
from urllib2 import urlopen, Request
except ImportError:
@@ -38,7 +43,7 @@ except ImportError:
sys.path.append('.')
from telegram import Update, Message, TelegramError, User, Chat, Updater
from telegram import Update, Message, TelegramError, User, Chat, Updater, Bot
from telegram.dispatcher import run_async
from tests.base import BaseTest
from threading import Lock, Thread
@@ -61,14 +66,18 @@ class UpdaterTest(BaseTest, unittest.TestCase):
"""
def setUp(self):
self.updater = Updater('', workers=2)
self.updater = None
self.received_message = None
self.message_count = 0
self.lock = Lock()
def _setup_updater(self, *args, **kwargs):
bot = MockBot(*args, **kwargs)
self.updater = Updater(workers=2, bot=bot)
def tearDown(self):
self.updater.stop()
if self.updater is not None:
self.updater.stop()
def reset(self):
self.message_count = 0
@@ -79,7 +88,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.message_count += 1
@run_async
def asyncHandlerTest(self, bot, update):
def asyncHandlerTest(self, bot, update, **kwargs):
sleep(1)
with self.lock:
self.received_message = update.message.text
@@ -89,6 +98,10 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.received_message = update
self.message_count += 1
def regexGroupHandlerTest(self, bot, update, groups=None, groupdict=None):
self.received_message = (groups, groupdict)
self.message_count += 1
def additionalArgsTest(self, bot, update, update_queue, args):
self.received_message = update
self.message_count += 1
@@ -97,6 +110,15 @@ class UpdaterTest(BaseTest, unittest.TestCase):
elif args[0] == 'noresend':
pass
@run_async
def asyncAdditionalHandlerTest(self, bot, update, update_queue=None,
**kwargs):
sleep(1)
with self.lock:
if update_queue is not None:
self.received_message = update.message.text
self.message_count += 1
def errorRaisingHandlerTest(self, bot, update):
raise TelegramError(update)
@@ -106,8 +128,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveTelegramMessageHandler(self):
print('Testing add/removeTelegramMessageHandler')
bot = MockBot('Test')
self.updater.bot = bot
self._setup_updater('Test')
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.telegramHandlerTest)
@@ -119,13 +140,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeTelegramMessageHandler(self.telegramHandlerTest)
self.reset()
bot.send_messages = 1
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addTelegramMessageHandlerMultipleMessages(self):
print('Testing addTelegramMessageHandler and send 100 messages...')
self.updater.bot = MockBot('Multiple', 100)
self._setup_updater('Multiple', 100)
self.updater.dispatcher.addTelegramMessageHandler(
self.telegramHandlerTest)
self.updater.start_polling(0.0)
@@ -135,8 +156,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveTelegramRegexHandler(self):
print('Testing add/removeStringRegexHandler')
bot = MockBot('Test2')
self.updater.bot = bot
self._setup_updater('Test2')
d = self.updater.dispatcher
regobj = re.compile('Te.*')
self.updater.dispatcher.addTelegramRegexHandler(regobj,
@@ -149,14 +169,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeTelegramRegexHandler(regobj, self.telegramHandlerTest)
self.reset()
bot.send_messages = 1
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveTelegramCommandHandler(self):
print('Testing add/removeTelegramCommandHandler')
bot = MockBot('/test')
self.updater.bot = bot
self._setup_updater('/test')
d = self.updater.dispatcher
self.updater.dispatcher.addTelegramCommandHandler(
'test', self.telegramHandlerTest)
@@ -168,14 +187,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeTelegramCommandHandler('test', self.telegramHandlerTest)
self.reset()
bot.send_messages = 1
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveUnknownTelegramCommandHandler(self):
print('Testing add/removeUnknownTelegramCommandHandler')
bot = MockBot('/test2')
self.updater.bot = bot
self._setup_updater('/test2')
d = self.updater.dispatcher
self.updater.dispatcher.addUnknownTelegramCommandHandler(
self.telegramHandlerTest)
@@ -187,14 +205,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeUnknownTelegramCommandHandler(self.telegramHandlerTest)
self.reset()
bot.send_messages = 1
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveStringRegexHandler(self):
print('Testing add/removeStringRegexHandler')
bot = MockBot('', messages=0)
self.updater.bot = bot
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addStringRegexHandler('Te.*', self.stringHandlerTest)
queue = self.updater.start_polling(0.01)
@@ -212,8 +229,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveStringCommandHandler(self):
print('Testing add/removeStringCommandHandler')
bot = MockBot('', messages=0)
self.updater.bot = bot
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addStringCommandHandler(
'test3', self.stringHandlerTest)
@@ -233,8 +249,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveUnknownStringCommandHandler(self):
print('Testing add/removeUnknownStringCommandHandler')
bot = MockBot('/test')
self.updater.bot = bot
self._setup_updater('/test')
d = self.updater.dispatcher
d.addUnknownStringCommandHandler(
self.stringHandlerTest)
@@ -247,14 +262,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeUnknownStringCommandHandler(self.stringHandlerTest)
self.reset()
bot.send_messages = 1
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveErrorHandler(self):
print('Testing add/removeErrorHandler')
bot = MockBot('', messages=0)
self.updater.bot = bot
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addErrorHandler(self.errorHandlerTest)
queue = self.updater.start_polling(0.01)
@@ -273,8 +287,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_errorInHandler(self):
print('Testing error in Handler')
bot = MockBot('', messages=0)
self.updater.bot = bot
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addStringRegexHandler('.*',
self.errorRaisingHandlerTest)
@@ -287,8 +300,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_errorOnGetUpdates(self):
print('Testing error on getUpdates')
bot = MockBot('', raise_error=True)
self.updater.bot = bot
self._setup_updater('', raise_error=True)
d = self.updater.dispatcher
d.addErrorHandler(self.errorHandlerTest)
self.updater.start_polling(0.01)
@@ -297,8 +309,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveTypeHandler(self):
print('Testing add/removeTypeHandler')
bot = MockBot('', messages=0)
self.updater.bot = bot
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addTypeHandler(dict, self.stringHandlerTest)
queue = self.updater.start_polling(0.01)
@@ -317,8 +328,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_runAsync(self):
print('Testing @run_async')
bot = MockBot('Test5', messages=2)
self.updater.bot = bot
self._setup_updater('Test5', messages=2)
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.asyncHandlerTest)
@@ -329,7 +339,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_additionalArgs(self):
print('Testing additional arguments for handlers')
self.updater.bot = MockBot('', messages=0)
self._setup_updater('', messages=0)
self.updater.dispatcher.addStringCommandHandler(
'test5', self.additionalArgsTest)
@@ -339,10 +349,33 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.assertEqual(self.received_message, '/test5 noresend')
self.assertEqual(self.message_count, 2)
def test_regexGroupHandler(self):
print('Testing optional groups and groupdict parameters')
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addStringRegexHandler('^(This).*?(?P<testgroup>regex group).*',
self.regexGroupHandlerTest)
queue = self.updater.start_polling(0.01)
queue.put('This is a test message for regex group matching.')
sleep(.1)
self.assertEqual(self.received_message, (('This', 'regex group'),
{'testgroup': 'regex group'}))
def test_runAsyncWithAdditionalArgs(self):
print('Testing @run_async with additional parameters')
self._setup_updater('Test6', messages=2)
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.asyncAdditionalHandlerTest)
self.updater.start_polling(0.01)
sleep(1.2)
self.assertEqual(self.received_message, 'Test6')
self.assertEqual(self.message_count, 2)
def test_webhook(self):
print('Testing Webhook')
bot = MockBot('', messages=0)
self.updater.bot = bot
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.telegramHandlerTest)
@@ -403,8 +436,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_webhook_no_ssl(self):
print('Testing Webhook without SSL')
bot = MockBot('', messages=0)
self.updater.bot = bot
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.telegramHandlerTest)
@@ -446,7 +478,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_idle(self):
print('Testing idle')
self.updater.bot = MockBot('Test6', messages=0)
self._setup_updater('Test6', messages=0)
self.updater.start_polling(poll_interval=0.01)
Thread(target=self.signalsender).start()
self.updater.idle()
@@ -454,6 +486,17 @@ class UpdaterTest(BaseTest, unittest.TestCase):
sleep(1)
self.updater.running = False
def test_createBot(self):
updater = Updater('123:abcd')
self.assertIsNotNone(updater.bot)
def test_mutualExclusiveTokenBot(self):
bot = Bot('123:zyxw')
self.assertRaises(ValueError, Updater, token='123:abcd', bot=bot)
def test_noTokenOrBot(self):
self.assertRaises(ValueError, Updater)
class MockBot:
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by