From d6552eb4c66265fc73e8fac8b58af00bcbeb6f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Conde=20G=C3=B3mez?= Date: Tue, 6 Oct 2015 12:06:23 +0200 Subject: [PATCH 1/3] Add webhook examples --- examples/webhook_examples/README.md | 36 +++++++ .../webhook_cherrypy_echo_bot.py | 85 ++++++++++++++++ .../webhook_cpython_echo_bot.py | 99 +++++++++++++++++++ .../webhook_flask_echo_bot.py | 84 ++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 examples/webhook_examples/README.md create mode 100644 examples/webhook_examples/webhook_cherrypy_echo_bot.py create mode 100644 examples/webhook_examples/webhook_cpython_echo_bot.py create mode 100644 examples/webhook_examples/webhook_flask_echo_bot.py diff --git a/examples/webhook_examples/README.md b/examples/webhook_examples/README.md new file mode 100644 index 0000000..2d47346 --- /dev/null +++ b/examples/webhook_examples/README.md @@ -0,0 +1,36 @@ +# Webhook examples using pyTelegramBotAPI + +There are 3 examples in this directory using different libraries: + +* **Python (CPython):** *webhook_cpython_echo_bot.py* + * **Pros:** + * Official python libraries, it works out of the box (doesn't require to + install anything). + * Works with Python 2 and Python 3 (need to be converted with 2to3). + * **Cons:** + * Ugly code. + * Many things to handle yourself, this can lead to errors. + * Not powerful, do the trick but the performance is low. + +* **CherryPy (3.8.0):** *webhook_cherrypy_echo_bot.py* + * **Pros:** + * It's a web application framework, cleaner code, uses objects for defining + the web application. + * Very good performance. + * The project seems to be active, latest version is recent. + * Works with Python 2 and Python 3. + * **Cons:** + * Some things are not very intuitive, reading the doc is a must. + +* **Flask (0.10.1):** *webhook_flask_echo_bot.py* + * **Pros:** + * It's a web application framework, cleaner code, uses decorator which can + be nice. + * Good performance. + * It's intuitive if you know how web application works. + * **Cons:** + * The project seems not to be very active, latest version dates 2013. + * They don't recommend to use it with Python 3, but may work. + * May be a oversized for just handling webhook petitions. + +*Latest update of this document: 2015-10-06* diff --git a/examples/webhook_examples/webhook_cherrypy_echo_bot.py b/examples/webhook_examples/webhook_cherrypy_echo_bot.py new file mode 100644 index 0000000..8faac61 --- /dev/null +++ b/examples/webhook_examples/webhook_cherrypy_echo_bot.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This is a simple echo bot using decorators and webhook with CherryPy +# It echoes any incoming text messages and does not use the polling method. + +import cherrypy +import telebot +import logging + + +API_TOKEN = '' + +WEBHOOK_HOST = '' +WEBHOOK_PORT = 8443 # 443, 80, 88 or 8443 (port need to be 'open') +WEBHOOK_LISTEN = '0.0.0.0' # In some VPS you may need to put here the IP addr + +WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate +WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key + +# Quick'n'dirty SSL certificate generation: +# +# openssl genrsa -out webhook_pkey.pem 2048 +# openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem +# +# When asked for "Common Name (e.g. server FQDN or YOUR name)" you should reply +# with the same value in you put in WEBHOOK_HOST + +WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT) +WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN) + + +logger = telebot.logger +telebot.logger.setLevel(logging.INFO) + +bot = telebot.TeleBot(API_TOKEN) + + +# WebhookServer, process webhook calls +class WebhookServer(object): + @cherrypy.expose + def index(self): + if 'content-length' in cherrypy.request.headers and \ + 'content-type' in cherrypy.request.headers and \ + cherrypy.request.headers['content-type'] == 'application/json': + length = int(cherrypy.request.headers['content-length']) + json_string = cherrypy.request.body.read(length) + update = telebot.types.Update.de_json(json_string) + bot.process_new_messages([update.message]) + return '' + else: + raise cherrypy.HTTPError(403) + + +# Handle '/start' and '/help' +@bot.message_handler(commands=['help', 'start']) +def send_welcome(message): + bot.reply_to(message, + ("Hi there, I am EchoBot.\n" + "I am here to echo your kind words back to you.")) + + +# Handle all other messages +@bot.message_handler(func=lambda message: True, content_types=['text']) +def echo_message(message): + bot.reply_to(message, message.text) + + +# Remove webhook, it fails sometimes the set if there is a previous webhook +bot.remove_webhook() + +# Set webhook +bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH, + certificate=open(WEBHOOK_SSL_CERT, 'r')) + +# Start cherrypy server +cherrypy.config.update({ + 'server.socket_host': WEBHOOK_LISTEN, + 'server.socket_port': WEBHOOK_PORT, + 'server.ssl_module': 'builtin', + 'server.ssl_certificate': WEBHOOK_SSL_CERT, + 'server.ssl_private_key': WEBHOOK_SSL_PRIV +}) + +cherrypy.quickstart(WebhookServer(), WEBHOOK_URL_PATH, {'/': {}}) diff --git a/examples/webhook_examples/webhook_cpython_echo_bot.py b/examples/webhook_examples/webhook_cpython_echo_bot.py new file mode 100644 index 0000000..5c1dfc9 --- /dev/null +++ b/examples/webhook_examples/webhook_cpython_echo_bot.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This is a simple echo bot using decorators and webhook with BaseHTTPServer +# It echoes any incoming text messages and does not use the polling method. + +import BaseHTTPServer +import ssl +import telebot +import logging + + +API_TOKEN = '' + +WEBHOOK_HOST = '' +WEBHOOK_PORT = 8443 # 443, 80, 88 or 8443 (port need to be 'open') +WEBHOOK_LISTEN = '0.0.0.0' # In some VPS you may need to put here the IP addr + +WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate +WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key + +# Quick'n'dirty SSL certificate generation: +# +# openssl genrsa -out webhook_pkey.pem 2048 +# openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem +# +# When asked for "Common Name (e.g. server FQDN or YOUR name)" you should reply +# with the same value in you put in WEBHOOK_HOST + +WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT) +WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN) + + +logger = telebot.logger +telebot.logger.setLevel(logging.INFO) + +bot = telebot.TeleBot(API_TOKEN) + + +# WebhookHandler, process webhook calls +class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler): + server_version = "WebhookHandler/1.0" + + def do_HEAD(self): + self.send_response(200) + self.end_headers() + + def do_GET(self): + self.send_response(200) + self.end_headers() + + def do_POST(self): + if self.path == WEBHOOK_URL_PATH and \ + 'content-type' in self.headers and \ + 'content-length' in self.headers and \ + self.headers['content-type'] == 'application/json': + json_string = self.rfile.read(int(self.headers['content-length'])) + + self.send_response(200) + self.end_headers() + + update = telebot.types.Update.de_json(json_string) + bot.process_new_messages([update.message]) + else: + self.send_error(403) + self.end_headers() + + +# Handle '/start' and '/help' +@bot.message_handler(commands=['help', 'start']) +def send_welcome(message): + bot.reply_to(message, + ("Hi there, I am EchoBot.\n" + "I am here to echo your kind words back to you.")) + + +# Handle all other messages +@bot.message_handler(func=lambda message: True, content_types=['text']) +def echo_message(message): + bot.reply_to(message, message.text) + + +# Remove webhook, it fails sometimes the set if there is a previous webhook +bot.remove_webhook() + +# Set webhook +bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH, + certificate=open(WEBHOOK_SSL_CERT, 'r')) + +# Start server +httpd = BaseHTTPServer.HTTPServer((WEBHOOK_LISTEN, WEBHOOK_PORT), + WebhookHandler) + +httpd.socket = ssl.wrap_socket(httpd.socket, + certfile=WEBHOOK_SSL_CERT, + keyfile=WEBHOOK_SSL_PRIV, + server_side=True) + +httpd.serve_forever() diff --git a/examples/webhook_examples/webhook_flask_echo_bot.py b/examples/webhook_examples/webhook_flask_echo_bot.py new file mode 100644 index 0000000..a20fcc0 --- /dev/null +++ b/examples/webhook_examples/webhook_flask_echo_bot.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This is a simple echo bot using decorators and webhook with flask +# It echoes any incoming text messages and does not use the polling method. + +import flask +import telebot +import logging + + +API_TOKEN = '' + +WEBHOOK_HOST = '' +WEBHOOK_PORT = 8443 # 443, 80, 88 or 8443 (port need to be 'open') +WEBHOOK_LISTEN = '0.0.0.0' # In some VPS you may need to put here the IP addr + +WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate +WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key + +# Quick'n'dirty SSL certificate generation: +# +# openssl genrsa -out webhook_pkey.pem 2048 +# openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem +# +# When asked for "Common Name (e.g. server FQDN or YOUR name)" you should reply +# with the same value in you put in WEBHOOK_HOST + +WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT) +WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN) + + +logger = telebot.logger +telebot.logger.setLevel(logging.INFO) + +bot = telebot.TeleBot(API_TOKEN) + +app = flask.Flask(__name__) + + +# Empty webserver index, return nothing, just http 200 +@app.route('/', methods=['GET', 'HEAD']) +def index(): + return '' + + +# Process webhook calls +@app.route(WEBHOOK_URL_PATH, methods=['POST']) +def webhook(): + if flask.request.headers.get('content-type') == 'application/json': + json_string = flask.request.get_data() + update = telebot.types.Update.de_json(json_string) + bot.process_new_messages([update.message]) + return '' + else: + flask.abort(403) + + +# Handle '/start' and '/help' +@bot.message_handler(commands=['help', 'start']) +def send_welcome(message): + bot.reply_to(message, + ("Hi there, I am EchoBot.\n" + "I am here to echo your kind words back to you.")) + + +# Handle all other messages +@bot.message_handler(func=lambda message: True, content_types=['text']) +def echo_message(message): + bot.reply_to(message, message.text) + + +# Remove webhook, it fails sometimes the set if there is a previous webhook +bot.remove_webhook() + +# Set webhook +bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH, + certificate=open(WEBHOOK_SSL_CERT, 'r')) + +# Start flask server +app.run(host=WEBHOOK_LISTEN, + port=WEBHOOK_PORT, + ssl_context=(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV), + debug=True) From 8eb6e034fe9cd528fb62465d73a53e29346e6c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Conde=20G=C3=B3mez?= Date: Tue, 6 Oct 2015 12:11:42 +0200 Subject: [PATCH 2/3] Update README with webhooks information. --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5853544..e46ce0a 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ To start the bot, simply open up a terminal and enter `python echo_bot.py` to ru All types are defined in types.py. They are all completely in line with the [Telegram API's definition of the types](https://core.telegram.org/bots/api#available-types), except for the Message's `from` field, which is renamed to `from_user` (because `from` is a Python reserved token). Thus, attributes such as `message_id` can be accessed directly with `message.message_id`. Note that `message.chat` can be either an instance of `User` or `GroupChat` (see [How can I distinguish a User and a GroupChat in message.chat?](#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat)). -The Message object also has a `content_type`attribute, which defines the type of the Message. `content_type` can be one of the following strings: +The Message object also has a `content_type`attribute, which defines the type of the Message. `content_type` can be one of the following strings: 'text', 'audio', 'document', 'photo', 'sticker', 'video', 'location', 'contact', 'new_chat_participant', 'left_chat_participant', 'new_chat_title', 'new_chat_photo', 'delete_chat_photo', 'group_chat_created'. ### Methods @@ -208,7 +208,7 @@ tb.polling(none_stop=False, interval=0, block=True) user = tb.get_me() # setWebhook -tb.set_webhook(url="http://example.com", cert=open('mycert.pem')) +tb.set_webhook(url="http://example.com", certificate=open('mycert.pem')) # unset webhook tb.remove_webhook() @@ -373,8 +373,10 @@ bot.set_update_listener(handle_messages) bot.polling() ``` -### Using web hooks -If you prefer using web hooks to the getUpdates method, you can use the `process_new_messages(messages)` function in TeleBot to make it process the messages that you supply. It takes a list of Message objects. This function is still incubating. +### Using webhooks +When using webhooks telegram sends one Update per call, for processing it you should call process_new_messages([update.message]) when you recieve it. + +There are some examples using webhooks in the *examples/webhook_examples* directory. ### Logging @@ -423,4 +425,4 @@ We now have a Telegram Channel as well! Keep yourself up to date with API change ## Bots using this API * [SiteAlert bot](https://telegram.me/SiteAlert_bot) ([source](https://github.com/ilteoood/SiteAlert-Python)) by *ilteoood* - Monitors websites and sends a notification on changes -Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh. \ No newline at end of file +Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh. From a230665424518ca39f2751d0ce3e11ed80ca6001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Conde=20G=C3=B3mez?= Date: Tue, 6 Oct 2015 12:19:20 +0200 Subject: [PATCH 3/3] Update README.rst --- README.rst | 100 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index a0a9675..313dc49 100644 --- a/README.rst +++ b/README.rst @@ -52,6 +52,7 @@ API `__. - `The Telegram Chat Group <#the-telegram-chat-group>`__ - `More examples <#more-examples>`__ +- `Bots using this API <#bots-using-this-api>`__ Getting started. ================ @@ -213,9 +214,39 @@ Outlined below are some general use cases of the API. Message handlers ~~~~~~~~~~~~~~~~ -A message handler is a function which is decorated with the -``message_handler`` decorator of a TeleBot instance. The following -examples illustrate the possibilities of message handlers: +A message handler is a function that is decorated with the +``message_handler`` decorator of a TeleBot instance. Message handlers +consist of one or multiple filters. Each filter much return True for a +certain message in order for a message handler to become eligible to +handle that message. A message handler is declared in the following way +(provided ``bot`` is an instance of TeleBot): + +.. code:: python + + @bot.message_handler(filters) + def function_name(message): + bot.reply_to(message, "This is a message handler") + +``function_name`` is not bound to any restrictions. Any function name is +permitted with message handlers. The function must accept at most one +argument, which will be the message that the function must handle. +``filters`` is a list of keyword arguments. A filter is declared in the +following manner: ``name=argument``. One handler may have multiple +filters. TeleBot supports the following filters: + ++------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| name | argument(s) | Condition | ++==================+=============================================+=================================================================================================================================================================================+ +| content\_types | list of strings (default ``['text']``) | ``True`` if message.content\_type is in the list of strings. | ++------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| regexp | a regular expression as a string | ``True`` if ``re.search(regexp_arg)`` returns ``True`` and ``message.content_type == 'text'`` (See `Python Regular Expressions `__ | ++------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| commands | list of strings | ``True`` if ``message.content_type == 'text'`` and ``message.text`` starts with a command that is in the list of strings. | ++------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| func | a function (lambda or function reference) | ``True`` if the lambda or function reference returns ``True`` | ++------------------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +Here are some examples of using the filters and message handlers: .. code:: python @@ -250,8 +281,15 @@ examples illustrate the possibilities of message handlers: def handle_text_doc(message) pass -*Note: all handlers are tested in the order in which they were declared* -#### TeleBot + # Handlers can be stacked to create a function which will be called if either message_handler is eligible + # This handler will be called if the message starts with '/hello' OR is some emoji + @bot.message_handler(commands=['hello']) + @bot.message_handler(func=lambda msg: msg.text.encode("utf-8") == SOME_FANCY_EMOJI) + def send_something(message): + pass + +**Important: all handlers are tested in the order in which they were +declared** #### TeleBot .. code:: python @@ -270,6 +308,11 @@ examples illustrate the possibilities of message handlers: # getMe user = tb.get_me() + # setWebhook + tb.set_webhook(url="http://example.com", certificate=open('mycert.pem')) + # unset webhook + tb.remove_webhook() + # getUpdates updates = tb.get_updates() updates = tb.get_updates(1234,100,20) #get_Updates(offset, limit, timeout): @@ -322,6 +365,14 @@ examples illustrate the possibilities of message handlers: # 'record_audio', 'upload_audio', 'upload_document' or 'find_location'. tb.send_chat_action(chat_id, action_string) + # getFile + # Downloading a file is straightforward + # Returns a File object + import requests + file_info = tb.get_file(file_id) + + file = requests.get('https://api.telegram.org/file/bot{0}/{1}'.format(API_TOKEN, file_info.file_path)) + Reply markup ~~~~~~~~~~~~ @@ -465,29 +516,31 @@ function as a listener to TeleBot. Example: bot.set_update_listener(handle_messages) bot.polling() -Using web hooks ---------------- +Using webhooks +-------------- -If you prefer using web hooks to the getUpdates method, you can use the -``process_new_messages(messages)`` function in TeleBot to make it -process the messages that you supply. It takes a list of Message -objects. This function is still incubating. +When using webhooks telegram sends one Update per call, for processing +it you should call process\_new\_messages([update.message]) when you +recieve it. + +There are some examples using webhooks in the +*examples/webhook\_examples* directory. Logging ------- You can use the Telebot module logger to log debug info about Telebot. -Use ``telebot.logger`` to get the logger of the TeleBot module. +Use ``telebot.logger`` to get the logger of the TeleBot module. It is +possible to add custom logging Handlers to the logger. Refer to the +`Python logging module +page `__ for more info. .. code:: python + import logging + logger = telebot.logger - formatter = logging.Formatter('[%(asctime)s] %(thread)d {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s', - '%m-%d %H:%M:%S') - ch = logging.StreamHandler(sys.stdout) - logger.addHandler(ch) - logger.setLevel(logging.DEBUG) # or use logging.INFO - ch.setFormatter(formatter) + telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console. F.A.Q. ====== @@ -513,6 +566,8 @@ Get help. Discuss. Chat. Join the `pyTelegramBotAPI Telegram Chat Group `__. +We now have a Telegram Channel as well! Keep yourself up to date with +API changes, and `join it `__. More examples ============= @@ -524,5 +579,14 @@ More examples - `next\_step\_handler Example `__ +Bots using this API +=================== + +- `SiteAlert bot `__ + (`source `__) by + *ilteoood* - Monitors websites and sends a notification on changes + Want to have your bot listed here? Send a Telegram message to + @eternnoir or @pevdh. + .. |Build Status| image:: https://travis-ci.org/eternnoir/pyTelegramBotAPI.svg?branch=master :target: https://travis-ci.org/eternnoir/pyTelegramBotAPI