mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-19 23:55:29 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f26bdd18f | |||
| 808945b623 | |||
| 63a83d4cc2 | |||
| c43b348117 | |||
| 45a47d54bd | |||
| 5e7f2688be | |||
| 7199d2894f | |||
| d9a58fd904 | |||
| cf9897e74b | |||
| 30308bdcc6 | |||
| af62c5be8e | |||
| cd7bc8dfac | |||
| 150914cf25 | |||
| 19390b9659 | |||
| fac4eb6438 | |||
| 2939885c5b | |||
| f07f33e160 | |||
| 0ddcb16889 | |||
| 594b81e463 | |||
| 41d0d45e2f | |||
| 1e4ae6546f | |||
| fd170773e2 | |||
| d1516f66ac | |||
| 739e218eb7 | |||
| dcea2c8015 | |||
| 98112d3987 | |||
| 45a4689fd0 | |||
| 82030c4109 | |||
| 25595e6d9e | |||
| 9637a8748c | |||
| 00e2b4815a | |||
| fb34f81533 | |||
| 3d89f6b284 | |||
| 7a8e84b46f | |||
| 8ad34fc3c0 | |||
| 00f4328bab | |||
| 8b196ce71f | |||
| 196d1fcc3d | |||
| 7ff7397d0f | |||
| 0c676b1a75 | |||
| dc9b77e02c | |||
| e7b339d527 | |||
| 5958da0031 | |||
| 0600b2634e | |||
| f8f16f0fe9 | |||
| 433110abe9 | |||
| 793437fec7 | |||
| a0a040a9c2 | |||
| f0e7a3316c |
@@ -4,6 +4,7 @@ python:
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "pypy"
|
||||
- "pypy3"
|
||||
install:
|
||||
|
||||
@@ -23,6 +23,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Noam Meltzer <https://github.com/tsnoam>`_
|
||||
- `Oleg Shlyazhko <https://github.com/ollmer>`_
|
||||
- `Rahiel Kasim <https://github.com/rahiel>`_
|
||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||
- `wjt <https://github.com/wjt>`_
|
||||
|
||||
|
||||
+10
@@ -1,3 +1,13 @@
|
||||
**2016-03-22**
|
||||
|
||||
*Released 3.4*
|
||||
|
||||
- Move ``Updater``, ``Dispatcher`` and ``JobQueue`` to new ``telegram.ext`` submodule (thanks to @rahiel)
|
||||
- Add ``disable_notification`` parameter (thanks to @aidarbiktimirov)
|
||||
- Fix bug where commands sent by Telegram Web would not be recognized (thanks to @shelomentsevd)
|
||||
- Add option to skip old updates on bot startup
|
||||
- Send files from ``BufferedReader``
|
||||
|
||||
**2016-02-28**
|
||||
|
||||
*Released 3.3*
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ Here's how to make a one-off code change.
|
||||
|
||||
``$ git checkout master``
|
||||
|
||||
``$ git checkout merge upstream/master``
|
||||
``$ git merge upstream/master``
|
||||
|
||||
``$ git checkout -b your-branch-name``
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: clean pep8 lint test install
|
||||
|
||||
PYLINT := pylint
|
||||
@@ -12,6 +13,7 @@ clean:
|
||||
find . -name '*.pyc' -exec rm -f {} \;
|
||||
find . -name '*.pyo' -exec rm -f {} \;
|
||||
find . -name '*~' -exec rm -f {} \;
|
||||
find . -regex "./telegram.\(mp3\|mp4\|ogg\|png\|webp\)" -exec rm {} \;
|
||||
|
||||
pep257:
|
||||
$(PEP257) telegram
|
||||
|
||||
+83
-79
@@ -36,7 +36,7 @@ A Python wrapper around the Telegram Bot API.
|
||||
:alt: Coveralls
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg
|
||||
:target: https://telegram.me/joinchat/ALnA-AJQm5TcNEiy2G_4cQ
|
||||
:target: https://telegram.me/pythontelegrambotgroup
|
||||
:alt: Telegram Group
|
||||
|
||||
=================
|
||||
@@ -57,9 +57,9 @@ Table of contents
|
||||
|
||||
- `Getting started`_
|
||||
|
||||
1. `The Updater class`_
|
||||
1. `API`_
|
||||
|
||||
2. `API`_
|
||||
2. `Extensions`_
|
||||
|
||||
3. `JobQueue`_
|
||||
|
||||
@@ -107,6 +107,7 @@ getUpdates Yes
|
||||
getUserProfilePhotos Yes
|
||||
getFile Yes
|
||||
setWebhook Yes
|
||||
answerInlineQuery Yes
|
||||
========================= ============
|
||||
|
||||
-------------------------
|
||||
@@ -120,6 +121,7 @@ Python Version *Supported?*
|
||||
2.7 Yes
|
||||
3.3 Yes
|
||||
3.4 Yes
|
||||
3.5 Yes
|
||||
PyPy Yes
|
||||
PyPy3 Yes
|
||||
============== ============
|
||||
@@ -173,85 +175,13 @@ This library uses the `logging` module. To set up logging to standard output, pu
|
||||
|
||||
at the beginning of your script.
|
||||
|
||||
--------------------
|
||||
_`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.
|
||||
|
||||
As with the old method, we'll need an Access Token. To generate an Access Token, we have to talk to `BotFather <https://telegram.me/botfather>`_ and follow a few simple steps (described `here <https://core.telegram.org/bots#botfather>`_).
|
||||
|
||||
First, we create an ``Updater`` object::
|
||||
|
||||
>>> from telegram import Updater
|
||||
>>> updater = Updater(token='token')
|
||||
|
||||
For quicker access to the ``Dispatcher`` used by our ``Updater``, we can introduce it locally::
|
||||
|
||||
>>> dispatcher = updater.dispatcher
|
||||
|
||||
Now, we need to define a function that should process a specific type of update::
|
||||
|
||||
>>> def start(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text="I'm a bot, please talk to me!")
|
||||
|
||||
We want this function to be called on a Telegram message that contains the ``/start`` command, so we need to register it in the dispatcher::
|
||||
|
||||
>>> dispatcher.addTelegramCommandHandler('start', start)
|
||||
|
||||
The last step is to tell the ``Updater`` to start working::
|
||||
|
||||
>>> updater.start_polling()
|
||||
|
||||
Our bot is now up and running (go ahead and try it)! It's not doing anything yet, besides answering to the ``/start`` command. Let's add another handler function and register it::
|
||||
|
||||
>>> def echo(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text=update.message.text)
|
||||
...
|
||||
>>> dispatcher.addTelegramMessageHandler(echo)
|
||||
|
||||
Our bot should now reply to all messages that are not a command with a message that has the same content.
|
||||
|
||||
People might try to send commands to the bot that it doesn't understand, so we should get that covered as well::
|
||||
|
||||
>>> def unknown(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text="Sorry, I didn't understand that command.")
|
||||
...
|
||||
>>> dispatcher.addUnknownTelegramCommandHandler(unknown)
|
||||
|
||||
Let's add some functionality to our bot. We want to add the ``/caps`` command, that will take some text as parameter and return it in all caps. We can get the arguments that were passed to the command in the handler function simply by adding it to the parameter list::
|
||||
|
||||
>>> def caps(bot, update, args):
|
||||
... text_caps = ' '.join(args).upper()
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text=text_caps)
|
||||
...
|
||||
>>> dispatcher.addTelegramCommandHandler('caps', caps)
|
||||
|
||||
To enable our bot to respond to inline queries, we can add the following (you will also have to talk to BotFather)::
|
||||
|
||||
>>> from telegram import InlineQueryResultArticle
|
||||
>>> def inline_caps(bot, update):
|
||||
... # If you activated inline feedback, updates might either contain
|
||||
... # inline_query or chosen_inline_result, the other one will be None
|
||||
... if update.inline_query:
|
||||
... query = bot.update.inline_query.query
|
||||
... results = list()
|
||||
... results.append(InlineQueryResultArticle(query.upper(), 'Caps', text_caps))
|
||||
... bot.answerInlineQuery(update.inline_query.id, results)
|
||||
...
|
||||
>>> dispatcher.addTelegramInlineHandler(inline_caps)
|
||||
|
||||
Now it's time to stop the bot::
|
||||
|
||||
>>> updater.stop()
|
||||
|
||||
Check out more examples in the `examples folder <https://github.com/python-telegram-bot/python-telegram-bot/tree/master/examples>`_!
|
||||
**Note:** The ``telegram.ext`` module will catch errors that would cause the bot to crash. All these are logged to the ``logging`` module, so it's recommended to use this if you are looking for error causes.
|
||||
|
||||
------
|
||||
_`API`
|
||||
------
|
||||
|
||||
Note: Using the ``Bot`` class directly is the 'old' method, but some of this is still important information, even if you're using the ``Updater`` class!
|
||||
Note: Using the ``Bot`` class directly is the 'old' method, but almost all of this is still important information, even if you're using the ``telegram.ext`` submodule!
|
||||
|
||||
The API is exposed via the ``telegram.Bot`` class.
|
||||
|
||||
@@ -338,13 +268,87 @@ There are many more API methods, to read the full API documentation::
|
||||
|
||||
$ pydoc telegram.Bot
|
||||
|
||||
-------------
|
||||
_`Extensions`
|
||||
-------------
|
||||
|
||||
The ``telegram.ext`` submodule is built on top of the bare-metal API. It provides an easy-to-use interface to the ``telegram.Bot`` by caring about getting new updates with the ``Updater`` class 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.
|
||||
|
||||
We'll need an Access Token. **Note:** If you have done this in the previous step, you can use that one. To generate an Access Token, we have to talk to `BotFather <https://telegram.me/botfather>`_ and follow a few simple steps (described `here <https://core.telegram.org/bots#botfather>`_).
|
||||
|
||||
First, we create an ``Updater`` object::
|
||||
|
||||
>>> from telegram.ext import Updater
|
||||
>>> updater = Updater(token='token')
|
||||
|
||||
For quicker access to the ``Dispatcher`` used by our ``Updater``, we can introduce it locally::
|
||||
|
||||
>>> dispatcher = updater.dispatcher
|
||||
|
||||
Now, we need to define a function that should process a specific type of update::
|
||||
|
||||
>>> def start(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text="I'm a bot, please talk to me!")
|
||||
|
||||
We want this function to be called on a Telegram message that contains the ``/start`` command, so we need to register it in the dispatcher::
|
||||
|
||||
>>> dispatcher.addTelegramCommandHandler('start', start)
|
||||
|
||||
The last step is to tell the ``Updater`` to start working::
|
||||
|
||||
>>> updater.start_polling()
|
||||
|
||||
Our bot is now up and running (go ahead and try it)! It's not doing anything yet, besides answering to the ``/start`` command. Let's add another handler function and register it::
|
||||
|
||||
>>> def echo(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text=update.message.text)
|
||||
...
|
||||
>>> dispatcher.addTelegramMessageHandler(echo)
|
||||
|
||||
Our bot should now reply to all messages that are not a command with a message that has the same content.
|
||||
|
||||
People might try to send commands to the bot that it doesn't understand, so we should get that covered as well::
|
||||
|
||||
>>> def unknown(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text="Sorry, I didn't understand that command.")
|
||||
...
|
||||
>>> dispatcher.addUnknownTelegramCommandHandler(unknown)
|
||||
|
||||
Let's add some functionality to our bot. We want to add the ``/caps`` command, that will take some text as parameter and return it in all caps. We can get the arguments that were passed to the command in the handler function simply by adding it to the parameter list::
|
||||
|
||||
>>> def caps(bot, update, args):
|
||||
... text_caps = ' '.join(args).upper()
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text=text_caps)
|
||||
...
|
||||
>>> dispatcher.addTelegramCommandHandler('caps', caps)
|
||||
|
||||
To enable our bot to respond to inline queries, we can add the following (you will also have to talk to BotFather)::
|
||||
|
||||
>>> from telegram import InlineQueryResultArticle
|
||||
>>> def inline_caps(bot, update):
|
||||
... # If you activated inline feedback, updates might either contain
|
||||
... # inline_query or chosen_inline_result, the other one will be None
|
||||
... if update.inline_query:
|
||||
... query = bot.update.inline_query.query
|
||||
... results = list()
|
||||
... results.append(InlineQueryResultArticle(query.upper(), 'Caps', text_caps))
|
||||
... bot.answerInlineQuery(update.inline_query.id, results)
|
||||
...
|
||||
>>> dispatcher.addTelegramInlineHandler(inline_caps)
|
||||
|
||||
Now it's time to stop the bot::
|
||||
|
||||
>>> updater.stop()
|
||||
|
||||
Check out more examples in the `examples folder <https://github.com/python-telegram-bot/python-telegram-bot/tree/master/examples>`_!
|
||||
|
||||
-----------
|
||||
_`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
|
||||
>>> from telegram.ext import Updater
|
||||
>>> u = Updater('TOKEN')
|
||||
>>> j = u.job_queue
|
||||
|
||||
@@ -426,7 +430,7 @@ You may copy, distribute and modify the software provided that modifications are
|
||||
_`Contact`
|
||||
==========
|
||||
|
||||
Feel free to join to our `Telegram group <https://telegram.me/joinchat/ALnA-AJQm5TcNEiy2G_4cQ>`_.
|
||||
Feel free to join to our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_.
|
||||
|
||||
=======
|
||||
_`TODO`
|
||||
|
||||
+2
-2
@@ -58,9 +58,9 @@ author = u'Leandro Toledo'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '3.3'
|
||||
version = '3.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '3.3.0'
|
||||
release = '3.4.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
telegram.dispatcher module
|
||||
=========================
|
||||
|
||||
.. automodule:: telegram.dispatcher
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
telegram.ext.dispatcher module
|
||||
==============================
|
||||
|
||||
.. automodule:: telegram.ext.dispatcher
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
telegram.ext.updater module
|
||||
===========================
|
||||
|
||||
.. automodule:: telegram.ext.updater
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -9,8 +9,8 @@ Submodules
|
||||
telegram.audio
|
||||
telegram.base
|
||||
telegram.bot
|
||||
telegram.updater
|
||||
telegram.dispatcher
|
||||
telegram.ext.updater
|
||||
telegram.ext.dispatcher
|
||||
telegram.jobqueue
|
||||
telegram.inlinequery
|
||||
telegram.inlinequeryresult
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
telegram.updater module
|
||||
=========================
|
||||
|
||||
.. automodule:: telegram.updater
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
+2
-2
@@ -18,8 +18,8 @@ Reply to last chat from the command line by typing "/reply <text>"
|
||||
Type 'stop' on the command line to stop the bot.
|
||||
"""
|
||||
|
||||
from telegram import Updater
|
||||
from telegram.dispatcher import run_async
|
||||
from telegram.ext import Updater
|
||||
from telegram.ext.dispatcher import run_async
|
||||
from time import sleep
|
||||
import logging
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
from telegram import Updater
|
||||
from telegram.ext import Updater
|
||||
import logging
|
||||
|
||||
# Enable logging
|
||||
|
||||
@@ -20,7 +20,8 @@ from random import getrandbits
|
||||
|
||||
import re
|
||||
|
||||
from telegram import Updater, Update, InlineQueryResultArticle, ParseMode
|
||||
from telegram import InlineQueryResultArticle, ParseMode
|
||||
from telegram.ext import Updater
|
||||
import logging
|
||||
|
||||
# Enable logging
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Basic example for a bot that awaits an answer from the user
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
import logging
|
||||
from telegram import Updater, ReplyKeyboardMarkup, Emoji, ForceReply
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - '
|
||||
'%(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
# Define the different states a chat can be in
|
||||
MENU, AWAIT_CONFIRMATION, AWAIT_INPUT = range(3)
|
||||
|
||||
# Python 2 and 3 unicode differences
|
||||
try:
|
||||
YES, NO = (Emoji.THUMBS_UP_SIGN.decode('utf-8'),
|
||||
Emoji.THUMBS_DOWN_SIGN.decode('utf-8'))
|
||||
except AttributeError:
|
||||
YES, NO = (Emoji.THUMBS_UP_SIGN, Emoji.THUMBS_DOWN_SIGN)
|
||||
|
||||
# States are saved in a dict that maps chat_id -> state
|
||||
state = dict()
|
||||
# Sometimes you need to save data temporarily
|
||||
context = dict()
|
||||
# This dict is used to store the settings value for the chat.
|
||||
# Usually, you'd use persistence for this (e.g. sqlite).
|
||||
values = dict()
|
||||
|
||||
|
||||
# Example handler. Will be called on the /set command and on regular messages
|
||||
def set_value(bot, update):
|
||||
chat_id = update.message.chat_id
|
||||
user_id = update.message.from_user.id
|
||||
text = update.message.text
|
||||
chat_state = state.get(chat_id, MENU)
|
||||
chat_context = context.get(chat_id, None)
|
||||
|
||||
# Since the handler will also be called on messages, we need to check if
|
||||
# the message is actually a command
|
||||
if chat_state == MENU and text[0] == '/':
|
||||
state[chat_id] = AWAIT_INPUT # set the state
|
||||
context[chat_id] = user_id # save the user id to context
|
||||
bot.sendMessage(chat_id,
|
||||
text="Please enter your settings value or send "
|
||||
"/cancel to abort",
|
||||
reply_markup=ForceReply())
|
||||
|
||||
# If we are waiting for input and the right user answered
|
||||
elif chat_state == AWAIT_INPUT and chat_context == user_id:
|
||||
state[chat_id] = AWAIT_CONFIRMATION
|
||||
|
||||
# Save the user id and the answer to context
|
||||
context[chat_id] = (user_id, update.message.text)
|
||||
reply_markup = ReplyKeyboardMarkup([[YES, NO]], one_time_keyboard=True)
|
||||
bot.sendMessage(chat_id, text="Are you sure?",
|
||||
reply_markup=reply_markup)
|
||||
|
||||
# If we are waiting for confirmation and the right user answered
|
||||
elif chat_state == AWAIT_CONFIRMATION and chat_context[0] == user_id:
|
||||
state[chat_id] = MENU
|
||||
context[chat_id] = None
|
||||
if text == YES:
|
||||
values[chat_id] = chat_context[1]
|
||||
bot.sendMessage(chat_id,
|
||||
text="Changed value to %s." % values[chat_id])
|
||||
else:
|
||||
bot.sendMessage(chat_id,
|
||||
text="Value not changed: %s."
|
||||
% values.get(chat_id, '<not set>'))
|
||||
|
||||
|
||||
# Handler for the /cancel command.
|
||||
# Sets the state back to MENU and clears the context
|
||||
def cancel(bot, update):
|
||||
chat_id = update.message.chat_id
|
||||
state[chat_id] = MENU
|
||||
context[chat_id] = None
|
||||
|
||||
|
||||
def help(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text="Use /set to test this bot.")
|
||||
|
||||
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# The command
|
||||
updater.dispatcher.addTelegramCommandHandler('set', set_value)
|
||||
# The answer and confirmation
|
||||
updater.dispatcher.addTelegramMessageHandler(set_value)
|
||||
updater.dispatcher.addTelegramCommandHandler('cancel', cancel)
|
||||
updater.dispatcher.addTelegramCommandHandler('start', help)
|
||||
updater.dispatcher.addTelegramCommandHandler('help', help)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT
|
||||
updater.idle()
|
||||
@@ -18,7 +18,7 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
from telegram import Updater
|
||||
from telegram.ext import Updater
|
||||
import logging
|
||||
|
||||
# Enable logging
|
||||
|
||||
@@ -26,7 +26,7 @@ def requirements():
|
||||
|
||||
setup(
|
||||
name='python-telegram-bot',
|
||||
version='3.3',
|
||||
version='3.4',
|
||||
author='Leandro Toledo',
|
||||
author_email='devs@python-telegram-bot.org',
|
||||
license='LGPLv3',
|
||||
@@ -50,8 +50,8 @@ setup(
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
],
|
||||
)
|
||||
|
||||
+41
-14
@@ -49,21 +49,48 @@ from .inlinequeryresult import InlineQueryResultArticle, InlineQueryResultGif,\
|
||||
InlineQueryResultMpeg4Gif, InlineQueryResultPhoto, InlineQueryResultVideo
|
||||
from .update import Update
|
||||
from .bot import Bot
|
||||
from .dispatcher import Dispatcher
|
||||
from .jobqueue import JobQueue
|
||||
from .updatequeue import UpdateQueue
|
||||
from .updater import Updater
|
||||
|
||||
|
||||
def Updater(*args, **kwargs):
|
||||
"""
|
||||
Load the updater module on invocation and return an Updater instance.
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn("telegram.Updater is being deprecated, please use "
|
||||
"telegram.ext.Updater from now on.")
|
||||
from .ext.updater import Updater as Up
|
||||
return Up(*args, **kwargs)
|
||||
|
||||
|
||||
def Dispatcher(*args, **kwargs):
|
||||
"""
|
||||
Load the dispatcher module on invocation and return an Dispatcher instance.
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn("telegram.Dispatcher is being deprecated, please use "
|
||||
"telegram.ext.Dispatcher from now on.")
|
||||
from .ext.dispatcher import Dispatcher as Dis
|
||||
return Dis(*args, **kwargs)
|
||||
|
||||
|
||||
def JobQueue(*args, **kwargs):
|
||||
"""
|
||||
Load the jobqueue module on invocation and return a JobQueue instance.
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn("telegram.JobQueue is being deprecated, please use "
|
||||
"telegram.ext.JobQueue from now on.")
|
||||
from .ext.jobqueue import JobQueue as JobQ
|
||||
return JobQ(*args, **kwargs)
|
||||
|
||||
|
||||
__author__ = 'devs@python-telegram-bot.org'
|
||||
__version__ = '3.3'
|
||||
__all__ = ('Bot', 'Updater', 'Dispatcher', 'Emoji', 'TelegramError',
|
||||
'InputFile', 'ReplyMarkup', 'ForceReply', 'ReplyKeyboardHide',
|
||||
'ReplyKeyboardMarkup', 'UserProfilePhotos', 'ChatAction',
|
||||
'Location', 'Contact', 'Video', 'Sticker', 'Document', 'File',
|
||||
'Audio', 'PhotoSize', 'Chat', 'Update', 'ParseMode', 'Message',
|
||||
'User', 'TelegramObject', 'NullHandler', 'Voice', 'JobQueue',
|
||||
'InlineQuery', 'ChosenInlineResult', 'InlineQueryResultArticle',
|
||||
__version__ = '3.4'
|
||||
__all__ = ('Audio', 'Bot', 'Chat', 'Emoji', 'TelegramError', 'InputFile',
|
||||
'Contact', 'ForceReply', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup',
|
||||
'UserProfilePhotos', 'ChatAction', 'Location', 'Video', 'Document',
|
||||
'Sticker', 'File', 'PhotoSize', 'Update', 'ParseMode', 'Message',
|
||||
'User', 'TelegramObject', 'NullHandler', 'Voice', 'InlineQuery',
|
||||
'ReplyMarkup', 'ChosenInlineResult', 'InlineQueryResultArticle',
|
||||
'InlineQueryResultGif', 'InlineQueryResultPhoto',
|
||||
'InlineQueryResultMpeg4Gif', 'InlineQueryResultVideo',
|
||||
'UpdateQueue')
|
||||
'InlineQueryResultMpeg4Gif', 'InlineQueryResultVideo')
|
||||
|
||||
+46
-7
@@ -29,8 +29,7 @@ from telegram.error import InvalidToken
|
||||
from telegram.utils import request
|
||||
from telegram.utils.validate import validate_string
|
||||
|
||||
H = NullHandler()
|
||||
logging.getLogger(__name__).addHandler(H)
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
|
||||
class Bot(TelegramObject):
|
||||
@@ -161,6 +160,10 @@ class Bot(TelegramObject):
|
||||
reply_to_message_id = kwargs.get('reply_to_message_id')
|
||||
data['reply_to_message_id'] = reply_to_message_id
|
||||
|
||||
if kwargs.get('disable_notification'):
|
||||
disable_notification = kwargs.get('disable_notification')
|
||||
data['disable_notification'] = disable_notification
|
||||
|
||||
if kwargs.get('reply_markup'):
|
||||
reply_markup = kwargs.get('reply_markup')
|
||||
if isinstance(reply_markup, ReplyMarkup):
|
||||
@@ -206,13 +209,17 @@ class Bot(TelegramObject):
|
||||
chat_id:
|
||||
Unique identifier for the message recipient - telegram.Chat id.
|
||||
parse_mode:
|
||||
Send Markdown, if you want Telegram apps to show bold, italic and
|
||||
inline URLs in your bot's message. For the moment, only Telegram
|
||||
for Android supports this. [Optional]
|
||||
Send 'Markdown', if you want Telegram apps to show bold, italic and
|
||||
inline URLs in your bot's message. [Optional]
|
||||
text:
|
||||
Text of the message to be sent.
|
||||
Text of the message to be sent. The current maximum length is 4096
|
||||
UTF8 characters.
|
||||
disable_web_page_preview:
|
||||
Disables link previews for links in this message. [Optional]
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
reply_to_message_id:
|
||||
If the message is a reply, ID of the original message. [Optional]
|
||||
reply_markup:
|
||||
@@ -252,6 +259,10 @@ class Bot(TelegramObject):
|
||||
- Chat id.
|
||||
message_id:
|
||||
Unique message identifier.
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
|
||||
Returns:
|
||||
A telegram.Message instance representing the message forwarded.
|
||||
@@ -288,6 +299,10 @@ class Bot(TelegramObject):
|
||||
caption:
|
||||
Photo caption (may also be used when resending photos by file_id).
|
||||
[Optional]
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
reply_to_message_id:
|
||||
If the message is a reply, ID of the original message. [Optional]
|
||||
reply_markup:
|
||||
@@ -342,6 +357,10 @@ class Bot(TelegramObject):
|
||||
Performer of sent audio. [Optional]
|
||||
title:
|
||||
Title of sent audio. [Optional]
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
reply_to_message_id:
|
||||
If the message is a reply, ID of the original message. [Optional]
|
||||
reply_markup:
|
||||
@@ -386,6 +405,10 @@ class Bot(TelegramObject):
|
||||
filename:
|
||||
File name that shows in telegram message (it is usefull when you
|
||||
send file generated by temp module, for example). [Optional]
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
reply_to_message_id:
|
||||
If the message is a reply, ID of the original message. [Optional]
|
||||
reply_markup:
|
||||
@@ -422,6 +445,10 @@ class Bot(TelegramObject):
|
||||
Sticker to send. You can either pass a file_id as String to resend
|
||||
a sticker that is already on the Telegram servers, or upload a new
|
||||
sticker using multipart/form-data.
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
reply_to_message_id:
|
||||
If the message is a reply, ID of the original message. [Optional]
|
||||
reply_markup:
|
||||
@@ -466,6 +493,10 @@ class Bot(TelegramObject):
|
||||
timeout:
|
||||
float. If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations. [Optional]
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
reply_to_message_id:
|
||||
If the message is a reply, ID of the original message. [Optional]
|
||||
reply_markup:
|
||||
@@ -512,6 +543,10 @@ class Bot(TelegramObject):
|
||||
a new audio file using multipart/form-data.
|
||||
duration:
|
||||
Duration of sent audio in seconds. [Optional]
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
reply_to_message_id:
|
||||
If the message is a reply, ID of the original message. [Optional]
|
||||
reply_markup:
|
||||
@@ -549,6 +584,10 @@ class Bot(TelegramObject):
|
||||
Latitude of location.
|
||||
longitude:
|
||||
Longitude of location.
|
||||
disable_notification:
|
||||
Sends the message silently. iOS users will not receive
|
||||
a notification, Android users will receive a notification
|
||||
with no sound. Other apps coming soon. [Optional]
|
||||
reply_to_message_id:
|
||||
If the message is a reply, ID of the original message. [Optional]
|
||||
reply_markup:
|
||||
@@ -757,7 +796,7 @@ class Bot(TelegramObject):
|
||||
result = request.post(url, data, network_delay=network_delay)
|
||||
|
||||
if result:
|
||||
self.logger.info(
|
||||
self.logger.debug(
|
||||
'Getting updates: %s', [u['update_id'] for u in result])
|
||||
else:
|
||||
self.logger.debug('No new updates found.')
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/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/].
|
||||
|
||||
"""Extensions over the Telegram Bot API to facilitate bot making"""
|
||||
|
||||
from .dispatcher import Dispatcher
|
||||
from .jobqueue import JobQueue
|
||||
from .updater import Updater
|
||||
|
||||
__all__ = ('Dispatcher', 'JobQueue', 'Updater')
|
||||
@@ -23,14 +23,13 @@ import logging
|
||||
from functools import wraps
|
||||
from inspect import getargspec
|
||||
from threading import Thread, BoundedSemaphore, Lock, Event, current_thread
|
||||
from re import match
|
||||
from re import match, split
|
||||
from time import sleep
|
||||
|
||||
from telegram import (TelegramError, Update, NullHandler)
|
||||
from telegram.updatequeue import Empty
|
||||
from telegram.utils.updatequeue import Empty
|
||||
|
||||
H = NullHandler()
|
||||
logging.getLogger(__name__).addHandler(H)
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
semaphore = None
|
||||
async_threads = set()
|
||||
@@ -158,7 +157,7 @@ class Dispatcher:
|
||||
if not semaphore:
|
||||
semaphore = BoundedSemaphore(value=workers)
|
||||
else:
|
||||
self.logger.info("Semaphore already initialized, skipping.")
|
||||
self.logger.debug('Semaphore already initialized, skipping.')
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
@@ -176,7 +175,7 @@ class Dispatcher:
|
||||
raise TelegramError(msg)
|
||||
|
||||
self.running = True
|
||||
self.logger.info('Dispatcher started')
|
||||
self.logger.debug('Dispatcher started')
|
||||
|
||||
while 1:
|
||||
try:
|
||||
@@ -184,7 +183,7 @@ class Dispatcher:
|
||||
update, context = self.update_queue.get(True, 1, True)
|
||||
except Empty:
|
||||
if self.__stop_event.is_set():
|
||||
self.logger.info('orderly stopping')
|
||||
self.logger.debug('orderly stopping')
|
||||
break
|
||||
elif self.__stop_event.is_set():
|
||||
self.logger.critical(
|
||||
@@ -214,7 +213,7 @@ class Dispatcher:
|
||||
"processing an update")
|
||||
|
||||
self.running = False
|
||||
self.logger.info('Dispatcher thread stopped')
|
||||
self.logger.debug('Dispatcher thread stopped')
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
@@ -548,7 +547,7 @@ class Dispatcher:
|
||||
command
|
||||
"""
|
||||
|
||||
command = update.message.text.split(' ')[0][1:].split('@')[0]
|
||||
command = split('\W', update.message.text[1:])[0]
|
||||
|
||||
if command in self.telegram_command_handlers:
|
||||
self.dispatchTo(self.telegram_command_handlers[command], update,
|
||||
@@ -41,10 +41,9 @@ class JobQueue(object):
|
||||
|
||||
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):
|
||||
@@ -87,11 +86,11 @@ class JobQueue(object):
|
||||
|
||||
next_t += time.time()
|
||||
|
||||
self.logger.debug("Putting a %s with t=%f" % (job.name, next_t))
|
||||
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.logger.debug('Auto-starting JobQueue')
|
||||
self.start()
|
||||
|
||||
def tick(self):
|
||||
@@ -100,24 +99,24 @@ class JobQueue(object):
|
||||
"""
|
||||
now = time.time()
|
||||
|
||||
self.logger.debug("Ticking jobs with t=%f" % now)
|
||||
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))
|
||||
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)
|
||||
self.logger.debug('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)
|
||||
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!")
|
||||
self.logger.debug('Next task isn\'t due yet. Finished!')
|
||||
break
|
||||
|
||||
def start(self):
|
||||
@@ -131,7 +130,7 @@ class JobQueue(object):
|
||||
job_queue_thread = Thread(target=self._start,
|
||||
name="job_queue")
|
||||
job_queue_thread.start()
|
||||
self.logger.info('Job Queue thread started')
|
||||
self.logger.debug('Job Queue thread started')
|
||||
else:
|
||||
self.__lock.release()
|
||||
|
||||
@@ -144,7 +143,7 @@ class JobQueue(object):
|
||||
self.tick()
|
||||
time.sleep(self.tick_interval)
|
||||
|
||||
self.logger.info('Job Queue thread stopped')
|
||||
self.logger.debug('Job Queue thread stopped')
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
@@ -28,12 +28,13 @@ from threading import Thread, Lock, current_thread, Event
|
||||
from time import sleep
|
||||
import subprocess
|
||||
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
||||
from telegram import (Bot, TelegramError, dispatcher, Dispatcher,
|
||||
NullHandler, JobQueue, UpdateQueue)
|
||||
from telegram import Bot, TelegramError, NullHandler
|
||||
from telegram.ext import dispatcher, Dispatcher, JobQueue
|
||||
from telegram.error import Unauthorized, InvalidToken
|
||||
from telegram.utils.updatequeue import UpdateQueue
|
||||
from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
|
||||
|
||||
H = NullHandler()
|
||||
logging.getLogger(__name__).addHandler(H)
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
|
||||
class Updater:
|
||||
@@ -58,6 +59,8 @@ class Updater:
|
||||
workers (Optional[int]): Amount of threads in the thread pool for
|
||||
functions decorated with @run_async
|
||||
bot (Optional[Bot]):
|
||||
job_queue_tick_interval(Optional[float]): The interval the queue should
|
||||
be checked for new tasks. Defaults to 1.0
|
||||
|
||||
Raises:
|
||||
ValueError: If both `token` and `bot` are passed or none of them.
|
||||
@@ -92,7 +95,8 @@ class Updater:
|
||||
self.__threads = []
|
||||
""":type: list[Thread]"""
|
||||
|
||||
def start_polling(self, poll_interval=0.0, timeout=10, network_delay=2):
|
||||
def start_polling(self, poll_interval=0.0, timeout=10, network_delay=2,
|
||||
clean=False, bootstrap_retries=0):
|
||||
"""
|
||||
Starts polling updates from Telegram.
|
||||
|
||||
@@ -101,19 +105,30 @@ class Updater:
|
||||
updates from Telegram in seconds. Default is 0.0
|
||||
timeout (Optional[float]): Passed to Bot.getUpdates
|
||||
network_delay (Optional[float]): Passed to Bot.getUpdates
|
||||
clean (Optional[bool]): Whether to clean any pending updates on
|
||||
Telegram servers before actually starting to poll. Default is
|
||||
False.
|
||||
bootstrap_retries (Optional[int[): Whether the bootstrapping phase
|
||||
of the `Updater` will retry on failures on the Telegram server.
|
||||
< 0 - retry indefinitely
|
||||
0 - no retries (default)
|
||||
> 0 - retry up to X times
|
||||
|
||||
Returns:
|
||||
Queue: The update queue that can be filled from the main thread
|
||||
"""
|
||||
|
||||
"""
|
||||
with self.__lock:
|
||||
if not self.running:
|
||||
self.running = True
|
||||
if clean:
|
||||
self._clean_updates()
|
||||
|
||||
# Create & start threads
|
||||
self._init_thread(self.dispatcher.start, "dispatcher")
|
||||
self._init_thread(self._start_polling, "updater",
|
||||
poll_interval, timeout, network_delay)
|
||||
poll_interval, timeout, network_delay,
|
||||
bootstrap_retries)
|
||||
|
||||
# Return the update queue so the main thread can insert updates
|
||||
return self.update_queue
|
||||
@@ -140,7 +155,10 @@ class Updater:
|
||||
port=80,
|
||||
url_path='',
|
||||
cert=None,
|
||||
key=None):
|
||||
key=None,
|
||||
clean=False,
|
||||
bootstrap_retries=0,
|
||||
webhook_url=None):
|
||||
"""
|
||||
Starts a small http server to listen for updates via webhook. If cert
|
||||
and key are not provided, the webhook will be started directly on
|
||||
@@ -154,35 +172,49 @@ class Updater:
|
||||
url_path (Optional[str]): Path inside url
|
||||
cert (Optional[str]): Path to the SSL certificate file
|
||||
key (Optional[str]): Path to the SSL key file
|
||||
clean (Optional[bool]): Whether to clean any pending updates on
|
||||
Telegram servers before actually starting the webhook. Default
|
||||
is False.
|
||||
bootstrap_retries (Optional[int[): Whether the bootstrapping phase
|
||||
of the `Updater` will retry on failures on the Telegram server.
|
||||
< 0 - retry indefinitely
|
||||
0 - no retries (default)
|
||||
> 0 - retry up to X times
|
||||
webhook_url (Optional[str]): Explicitly specifiy the webhook url.
|
||||
Useful behind NAT, reverse proxy, etc. Default is derived from
|
||||
`listen`, `port` & `url_path`.
|
||||
|
||||
Returns:
|
||||
Queue: The update queue that can be filled from the main thread
|
||||
"""
|
||||
|
||||
"""
|
||||
with self.__lock:
|
||||
if not self.running:
|
||||
self.running = True
|
||||
if clean:
|
||||
self._clean_updates()
|
||||
|
||||
# Create & start threads
|
||||
self._init_thread(self.dispatcher.start, "dispatcher"),
|
||||
self._init_thread(self._start_webhook, "updater", listen,
|
||||
port, url_path, cert, key)
|
||||
port, url_path, cert, key, bootstrap_retries,
|
||||
webhook_url)
|
||||
|
||||
# Return the update queue so the main thread can insert updates
|
||||
return self.update_queue
|
||||
|
||||
def _start_polling(self, poll_interval, timeout, network_delay):
|
||||
def _start_polling(self, poll_interval, timeout, network_delay,
|
||||
bootstrap_retries):
|
||||
"""
|
||||
Thread target of thread 'updater'. Runs in background, pulls
|
||||
updates from Telegram and inserts them in the update queue of the
|
||||
Dispatcher.
|
||||
|
||||
"""
|
||||
|
||||
cur_interval = poll_interval
|
||||
self.logger.info('Updater thread started')
|
||||
self.logger.debug('Updater thread started')
|
||||
|
||||
# Remove webhook
|
||||
self.bot.setWebhook(webhook_url=None)
|
||||
self._set_webhook(None, bootstrap_retries)
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
@@ -201,8 +233,8 @@ class Updater:
|
||||
else:
|
||||
if not self.running:
|
||||
if len(updates) > 0:
|
||||
self.logger.info('Updates ignored and will be pulled '
|
||||
'again on restart.')
|
||||
self.logger.debug('Updates ignored and will be pulled '
|
||||
'again on restart.')
|
||||
break
|
||||
|
||||
if updates:
|
||||
@@ -214,6 +246,27 @@ class Updater:
|
||||
|
||||
sleep(cur_interval)
|
||||
|
||||
def _set_webhook(self, webhook_url, max_retries):
|
||||
retries = 0
|
||||
while 1:
|
||||
try:
|
||||
# Remove webhook
|
||||
self.bot.setWebhook(webhook_url=webhook_url)
|
||||
except (Unauthorized, InvalidToken):
|
||||
raise
|
||||
except TelegramError:
|
||||
msg = 'failed to set webhook; try={0} max_retries={1}'.format(
|
||||
retries, max_retries)
|
||||
if max_retries < 0 or retries < max_retries:
|
||||
self.logger.info(msg)
|
||||
retries += 1
|
||||
else:
|
||||
self.logger.exception(msg)
|
||||
raise
|
||||
else:
|
||||
break
|
||||
sleep(1)
|
||||
|
||||
@staticmethod
|
||||
def _increase_poll_interval(current_interval):
|
||||
# increase waiting times on subsequent errors up to 30secs
|
||||
@@ -225,14 +278,19 @@ class Updater:
|
||||
current_interval = 30
|
||||
return current_interval
|
||||
|
||||
def _start_webhook(self, listen, port, url_path, cert, key):
|
||||
self.logger.info('Updater thread started')
|
||||
def _start_webhook(self, listen, port, url_path, cert, key,
|
||||
bootstrap_retries, webhook_url):
|
||||
self.logger.debug('Updater thread started')
|
||||
use_ssl = cert is not None and key is not None
|
||||
url_path = "/%s" % url_path
|
||||
|
||||
# Create and start server
|
||||
self.httpd = WebhookServer((listen, port), WebhookHandler,
|
||||
self.update_queue, url_path)
|
||||
if not webhook_url:
|
||||
webhook_url = self._gen_webhook_url(listen, port, url_path,
|
||||
use_ssl)
|
||||
self._set_webhook(webhook_url, bootstrap_retries)
|
||||
|
||||
if use_ssl:
|
||||
# Check SSL-Certificate with openssl, if possible
|
||||
@@ -258,6 +316,19 @@ class Updater:
|
||||
|
||||
self.httpd.serve_forever(poll_interval=1)
|
||||
|
||||
def _gen_webhook_url(self, listen, port, url_path, use_ssl):
|
||||
return '{proto}://{listen}:{port}{path}'.format(
|
||||
proto='https' if use_ssl else 'http',
|
||||
listen=listen,
|
||||
port=port,
|
||||
path=url_path)
|
||||
|
||||
def _clean_updates(self):
|
||||
self.logger.debug('Cleaning updates from Telegram server')
|
||||
updates = self.bot.getUpdates()
|
||||
while updates:
|
||||
updates = self.bot.getUpdates(updates[-1].update_id + 1)
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stops the polling/webhook thread, the dispatcher and the job queue
|
||||
@@ -266,7 +337,7 @@ class Updater:
|
||||
self.job_queue.stop()
|
||||
with self.__lock:
|
||||
if self.running:
|
||||
self.logger.info('Stopping Updater and Dispatcher...')
|
||||
self.logger.debug('Stopping Updater and Dispatcher...')
|
||||
|
||||
self.running = False
|
||||
|
||||
@@ -280,7 +351,7 @@ class Updater:
|
||||
|
||||
def _stop_httpd(self):
|
||||
if self.httpd:
|
||||
self.logger.info(
|
||||
self.logger.debug(
|
||||
'Waiting for current webhook connection to be '
|
||||
'closed... Send a Telegram message to the bot to exit '
|
||||
'immediately.')
|
||||
@@ -288,7 +359,7 @@ class Updater:
|
||||
self.httpd = None
|
||||
|
||||
def _stop_dispatcher(self):
|
||||
self.logger.debug("Requesting Dispatcher to stop...")
|
||||
self.logger.debug('Requesting Dispatcher to stop...')
|
||||
self.dispatcher.stop()
|
||||
|
||||
def _join_async_threads(self):
|
||||
@@ -296,7 +367,7 @@ class Updater:
|
||||
threads = list(dispatcher.async_threads)
|
||||
total = len(threads)
|
||||
for i, thr in enumerate(threads):
|
||||
self.logger.info(
|
||||
self.logger.debug(
|
||||
'Waiting for async thread {0}/{1} to end'.format(i, total))
|
||||
thr.join()
|
||||
self.logger.debug(
|
||||
@@ -304,7 +375,7 @@ class Updater:
|
||||
|
||||
def _join_threads(self):
|
||||
for thr in self.__threads:
|
||||
self.logger.info(
|
||||
self.logger.debug(
|
||||
'Waiting for {0} thread to end'.format(thr.name))
|
||||
thr.join()
|
||||
self.logger.debug('{0} thread has ended'.format(thr.name))
|
||||
@@ -81,7 +81,8 @@ class InputFile(object):
|
||||
self.input_file_content = self.input_file.read()
|
||||
if 'filename' in data:
|
||||
self.filename = self.data.pop('filename')
|
||||
elif isinstance(self.input_file, file):
|
||||
elif isinstance(self.input_file, file) and \
|
||||
hasattr(self.input_file, 'name'):
|
||||
self.filename = os.path.basename(self.input_file.name)
|
||||
elif from_url:
|
||||
self.filename = os.path.basename(self.input_file.url)\
|
||||
@@ -134,7 +135,7 @@ class InputFile(object):
|
||||
form_boundary,
|
||||
'Content-Disposition: form-data; name="%s"; filename="%s"' % (
|
||||
self.input_name, self.filename
|
||||
),
|
||||
),
|
||||
'Content-Type: %s' % self.mimetype,
|
||||
'',
|
||||
self.input_file_content
|
||||
|
||||
@@ -56,6 +56,10 @@ class Message(TelegramObject):
|
||||
new_chat_photo (List[:class:`telegram.PhotoSize`]):
|
||||
delete_chat_photo (bool):
|
||||
group_chat_created (bool):
|
||||
supergroup_chat_created (bool):
|
||||
migrate_to_chat_id (int):
|
||||
migrate_from_chat_id (int):
|
||||
channel_chat_created (bool):
|
||||
|
||||
Args:
|
||||
message_id (int):
|
||||
@@ -84,6 +88,10 @@ class Message(TelegramObject):
|
||||
new_chat_photo (Optional[List[:class:`telegram.PhotoSize`]):
|
||||
delete_chat_photo (Optional[bool]):
|
||||
group_chat_created (Optional[bool]):
|
||||
supergroup_chat_created (Optional[bool]):
|
||||
migrate_to_chat_id (Optional[int]):
|
||||
migrate_from_chat_id (Optional[int]):
|
||||
channel_chat_created (Optional[bool]):
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
|
||||
@@ -12,8 +12,7 @@ except ImportError:
|
||||
from urllib import quote
|
||||
from urllib2 import URLError, HTTPError
|
||||
|
||||
H = NullHandler()
|
||||
logging.getLogger(__name__).addHandler(H)
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
|
||||
class Botan(object):
|
||||
|
||||
@@ -98,7 +98,7 @@ def _try_except_req(func):
|
||||
|
||||
except (SSLError, socket.timeout) as error:
|
||||
err_s = str(error)
|
||||
if "operation timed out" in err_s:
|
||||
if 'operation timed out' in err_s:
|
||||
raise TimedOut()
|
||||
|
||||
raise NetworkError(err_s)
|
||||
|
||||
@@ -35,4 +35,4 @@ def validate_string(arg, name):
|
||||
name (str): The name of the argument, for the error message
|
||||
"""
|
||||
if not isinstance(arg, basestring) and arg is not None:
|
||||
raise ValueError(name + " is not a string")
|
||||
raise ValueError(name + ' is not a string')
|
||||
|
||||
@@ -10,8 +10,7 @@ except ImportError:
|
||||
import http.server as BaseHTTPServer
|
||||
|
||||
|
||||
H = NullHandler()
|
||||
logging.getLogger(__name__).addHandler(H)
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
|
||||
class _InvalidPost(Exception):
|
||||
@@ -36,14 +35,14 @@ class WebhookServer(BaseHTTPServer.HTTPServer, object):
|
||||
def serve_forever(self, poll_interval=0.5):
|
||||
with self.server_lock:
|
||||
self.is_running = True
|
||||
self.logger.info("Webhook Server started.")
|
||||
self.logger.debug('Webhook Server started.')
|
||||
super(WebhookServer, self).serve_forever(poll_interval)
|
||||
self.logger.info("Webhook Server stopped.")
|
||||
self.logger.debug('Webhook Server stopped.')
|
||||
|
||||
def shutdown(self):
|
||||
with self.shutdown_lock:
|
||||
if not self.is_running:
|
||||
self.logger.warn("Webhook Server already stopped.")
|
||||
self.logger.warn('Webhook Server already stopped.')
|
||||
return
|
||||
else:
|
||||
super(WebhookServer, self).shutdown()
|
||||
@@ -54,7 +53,7 @@ class WebhookServer(BaseHTTPServer.HTTPServer, object):
|
||||
# Based on: https://github.com/eternnoir/pyTelegramBotAPI/blob/master/
|
||||
# examples/webhook_examples/webhook_cpython_echo_bot.py
|
||||
class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
|
||||
server_version = "WebhookHandler/1.0"
|
||||
server_version = 'WebhookHandler/1.0'
|
||||
|
||||
def __init__(self, request, client_address, server):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
@@ -69,7 +68,7 @@ class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
|
||||
self.end_headers()
|
||||
|
||||
def do_POST(self):
|
||||
self.logger.debug("Webhook triggered")
|
||||
self.logger.debug('Webhook triggered')
|
||||
try:
|
||||
self._validate_post()
|
||||
clen = self._get_content_len()
|
||||
@@ -83,11 +82,11 @@ class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
|
||||
self.logger.debug("Webhook received data: " + json_string)
|
||||
self.logger.debug('Webhook received data: ' + json_string)
|
||||
|
||||
update = Update.de_json(json.loads(json_string))
|
||||
self.logger.info("Received Update with ID %d on Webhook" %
|
||||
update.update_id)
|
||||
self.logger.debug('Received Update with ID %d on Webhook' %
|
||||
update.update_id)
|
||||
self.server.update_queue.put(update)
|
||||
|
||||
def _validate_post(self):
|
||||
|
||||
@@ -61,6 +61,17 @@ class BotTest(BaseTest, unittest.TestCase):
|
||||
self.assertEqual(message.text, u'Моё судно на воздушной подушке полно угрей')
|
||||
self.assertTrue(isinstance(message.date, datetime))
|
||||
|
||||
@flaky(3, 1)
|
||||
@timeout(10)
|
||||
def testSilentSendMessage(self):
|
||||
message = self._bot.sendMessage(chat_id=self._chat_id,
|
||||
text='Моё судно на воздушной подушке полно угрей',
|
||||
disable_notification=True)
|
||||
|
||||
self.assertTrue(self.is_json(message.to_json()))
|
||||
self.assertEqual(message.text, u'Моё судно на воздушной подушке полно угрей')
|
||||
self.assertTrue(isinstance(message.date, datetime))
|
||||
|
||||
@flaky(3, 1)
|
||||
@timeout(10)
|
||||
def testGetUpdates(self):
|
||||
@@ -93,6 +104,18 @@ class BotTest(BaseTest, unittest.TestCase):
|
||||
self.assertEqual(message.photo[0].file_size, 1451)
|
||||
self.assertEqual(message.caption, 'testSendPhoto')
|
||||
|
||||
@flaky(3, 1)
|
||||
@timeout(10)
|
||||
def testSilentSendPhoto(self):
|
||||
message = self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'),
|
||||
caption='testSendPhoto',
|
||||
chat_id=self._chat_id,
|
||||
disable_notification=True)
|
||||
|
||||
self.assertTrue(self.is_json(message.to_json()))
|
||||
self.assertEqual(message.photo[0].file_size, 1451)
|
||||
self.assertEqual(message.caption, 'testSendPhoto')
|
||||
|
||||
@flaky(3, 1)
|
||||
@timeout(10)
|
||||
def testResendPhoto(self):
|
||||
|
||||
@@ -37,7 +37,7 @@ except ImportError:
|
||||
|
||||
sys.path.append('.')
|
||||
|
||||
from telegram import JobQueue, Updater
|
||||
from telegram.ext import JobQueue, Updater
|
||||
from tests.base import BaseTest
|
||||
|
||||
# Enable logging
|
||||
|
||||
+58
-7
@@ -47,8 +47,10 @@ except ImportError:
|
||||
|
||||
sys.path.append('.')
|
||||
|
||||
from telegram import Update, Message, TelegramError, User, Chat, Updater, Bot
|
||||
from telegram.dispatcher import run_async
|
||||
from telegram import Update, Message, TelegramError, User, Chat, Bot
|
||||
from telegram.ext import Updater
|
||||
from telegram.ext.dispatcher import run_async
|
||||
from telegram.error import Unauthorized, InvalidToken
|
||||
from tests.base import BaseTest
|
||||
from threading import Lock, Thread
|
||||
|
||||
@@ -301,6 +303,15 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, 'Test Error 1')
|
||||
|
||||
def test_cleanBeforeStart(self):
|
||||
self._setup_updater('')
|
||||
d = self.updater.dispatcher
|
||||
d.addTelegramMessageHandler(self.telegramHandlerTest)
|
||||
self.updater.start_polling(0.01, clean=True)
|
||||
sleep(.1)
|
||||
self.assertEqual(self.message_count, 0)
|
||||
self.assertIsNone(self.received_message)
|
||||
|
||||
def test_errorOnGetUpdates(self):
|
||||
self._setup_updater('', raise_error=True)
|
||||
d = self.updater.dispatcher
|
||||
@@ -396,7 +407,6 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.assertEqual(self.received_message, (('This', 'regex group'),
|
||||
{'testgroup': 'regex group'}))
|
||||
|
||||
|
||||
def test_runAsyncWithAdditionalArgs(self):
|
||||
self._setup_updater('Test6', messages=2)
|
||||
d = self.updater.dispatcher
|
||||
@@ -475,13 +485,46 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
sleep(1)
|
||||
self.assertEqual(self.received_message, 'Webhook Test 2')
|
||||
|
||||
def test_bootstrap_retries_success(self):
|
||||
retries = 3
|
||||
self._setup_updater('', messages=0, bootstrap_retries=retries)
|
||||
|
||||
self.updater._set_webhook('path', retries)
|
||||
self.assertEqual(self.updater.bot.bootstrap_attempts, retries)
|
||||
|
||||
def test_bootstrap_retries_unauth(self):
|
||||
retries = 3
|
||||
self._setup_updater('', messages=0, bootstrap_retries=retries,
|
||||
bootstrap_err=Unauthorized())
|
||||
|
||||
self.assertRaises(Unauthorized, self.updater._set_webhook, 'path',
|
||||
retries)
|
||||
self.assertEqual(self.updater.bot.bootstrap_attempts, 1)
|
||||
|
||||
def test_bootstrap_retries_invalid_token(self):
|
||||
retries = 3
|
||||
self._setup_updater('', messages=0, bootstrap_retries=retries,
|
||||
bootstrap_err=InvalidToken())
|
||||
|
||||
self.assertRaises(InvalidToken, self.updater._set_webhook, 'path',
|
||||
retries)
|
||||
self.assertEqual(self.updater.bot.bootstrap_attempts, 1)
|
||||
|
||||
def test_bootstrap_retries_fail(self):
|
||||
retries = 1
|
||||
self._setup_updater('', messages=0, bootstrap_retries=retries)
|
||||
|
||||
self.assertRaisesRegexp(TelegramError, 'test',
|
||||
self.updater._set_webhook, 'path', retries - 1)
|
||||
self.assertEqual(self.updater.bot.bootstrap_attempts, 1)
|
||||
|
||||
def test_webhook_invalid_posts(self):
|
||||
self._setup_updater('', messages=0)
|
||||
|
||||
ip = '127.0.0.1'
|
||||
port = randrange(1024, 49152) # select random port for travis
|
||||
thr = Thread(target=self.updater._start_webhook,
|
||||
args=(ip, port, '', None, None))
|
||||
args=(ip, port, '', None, None, 0, None))
|
||||
thr.start()
|
||||
|
||||
sleep(0.5)
|
||||
@@ -570,12 +613,15 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
|
||||
class MockBot:
|
||||
|
||||
def __init__(self, text, messages=1, raise_error=False):
|
||||
def __init__(self, text, messages=1, raise_error=False,
|
||||
bootstrap_retries=None, bootstrap_err=TelegramError('test')):
|
||||
self.text = text
|
||||
self.send_messages = messages
|
||||
self.raise_error = raise_error
|
||||
self.token = "TOKEN"
|
||||
pass
|
||||
self.bootstrap_retries = bootstrap_retries
|
||||
self.bootstrap_attempts = 0
|
||||
self.bootstrap_err = bootstrap_err
|
||||
|
||||
@staticmethod
|
||||
def mockUpdate(text):
|
||||
@@ -586,7 +632,12 @@ class MockBot:
|
||||
return update
|
||||
|
||||
def setWebhook(self, webhook_url=None, certificate=None):
|
||||
pass
|
||||
if self.bootstrap_retries is None:
|
||||
return
|
||||
|
||||
if self.bootstrap_attempts < self.bootstrap_retries:
|
||||
self.bootstrap_attempts += 1
|
||||
raise self.bootstrap_err
|
||||
|
||||
def getUpdates(self,
|
||||
offset=None,
|
||||
|
||||
Reference in New Issue
Block a user