Skip to content

Commit

Permalink
Changed the way the commands are imported
Browse files Browse the repository at this point in the history
Now there's no need to register each command in the commands.py file
  • Loading branch information
Ale Sánchez committed Jul 31, 2018
1 parent a695fa8 commit 9c29132
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 71 deletions.
1 change: 1 addition & 0 deletions Pipfile
Expand Up @@ -5,6 +5,7 @@ name = "pypi"

[packages]
python-telegram-bot = "*"
inflection = "*"

[dev-packages]
pylint = "*"
Expand Down
9 changes: 8 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 23 additions & 60 deletions README.md
Expand Up @@ -26,14 +26,11 @@ The project also allows you to deploy the bot as a webhook or as a normal pollin

The very first thing is creating a bot for having a *token*. You can register your own bot by following the instructions at [Telegram.org](https://core.telegram.org/bots#3-how-do-i-create-a-bot) (you can read the full guide for learning a bit more about bots and everything about what can the do for you).

If you want to deploy your bot as a webhook, you have to register it via Telegram api with the following call (the URL can be even pasted in your browser and it'll work):
`https://api.telegram.org/botYOUR:BOT_TOKEN/setWebhook?url=https://example.com/YOUR:BOT_TOKEN`

`YOUR:BOT_TOKEN` is the token that BotFather has assigned to your bot. `https://example.com` is your domain (it can be an IP if you don't have a domain name).
Even if you want to deploy your bot as a webhook, you don't have to register it via Telegram api because python-telegram-bot takes care of it.

**Important:** you have to take into account two things:
1. Your domain **has to be https**. Telegram does not allow bots in non-https environments. The good news are that the certificate you use can be a self-signed one. You can also use a free [letsencrypt](https://letsencrypt.org/) certificate.
2. In this guide we assume that you have your bot listening in `https://example.com/YOUR:BOT_TOKEN` rather than just in `https://example.com/`. That's why we believe that making your bot listen in just a domain leaves it *open* to anyone son anyone can send your bot fakes updates. This guide also assumes that you have a reverse proxy that allows you to deploy an arbitrary number of bots on the same domain. If this is not your case, you can also use this project deploying only one bot per domain (or IP) and per allowed port. Remember that as [setWebhook](https://core.telegram.org/bots/api#setwebhook) documentation says, it currently supports 80, 443, 88 and 8443 pors. This allows you to deploy up to four bots per domain without needing any reverse proxy.
2. In this guide we assume that you have your bot listening in `https://example.com/YOUR:BOT_TOKEN` rather than just in `https://example.com/`. That's because we believe that making your bot listen in just a domain leaves it *open* to anyone so anyone can send your bot fakes updates. This guide also assumes that your server allows you to deploy an arbitrary number of bots on the same domain, e.g. having one bot on `https://example.com/path/to/bot1` and another on `https://example.com/path/to/bot2`... If this is not your case, you can also use this project deploying only one bot per domain (or IP) and per allowed port. Remember that as [setWebhook](https://core.telegram.org/bots/api#setwebhook) documentation says, it currently supports 80, 443, 88 and 8443 pors. This allows you to deploy up to four bots per domain without needing any reverse proxy.

## Install dependencies

Expand All @@ -52,89 +49,55 @@ NAME = "seed_bot"
WEBHOOK = True
## The following configuration is only needed if you setted WEBHOOK to True ##
IP = '0.0.0.0'
PORT = '443'
PORT = 443
URL_PATH = TOKEN # This is recommended for avoiding random people making fake updates to your bot
KEY = 'certs/private.key'
CERT = 'certs/cert.pem'
WEBHOOK_URL = f'https://example.com/{TOKEN}'
```

- **TOKEN**: Your bot's token. You have to edit this and place the token that BotFather gave to your bot.
- **NAME**: Your bot's name. Optional. This is used for identifying your bot in places like the log file.
- **WEBHOOK**: If set to True, you will have to edit the other configuration options, for making a full-working webhook bot. If you just want to deploy a standard bot that automatically polls to the Telegram [getUpdates](https://core.telegram.org/bots/api#getupdates) method, leave it to False.
- **IP**: The IP your bot will be listening on. Usually you'll leave it a 0.0.0.0 as your bot will be listening on localhost.
- **PORT**: The port your bot will be listening on. If you have a reverse proxy you'll leave it to 443. If not, you'll change it to the port on which you want your bot to listen on (remember you only can choose between 80, 443, 88 and 8443).
- **PORT**: The port your bot will be listening on. If you don't have a reverse proxy you'll leave it to 443 (or any other supported port like 80, 88 or 8443). If you have a reverse proxy, you'll change it to the port on which you want your bot to listen on. After that, you have to configure nginx to redirect all incoming traffic from https://example.com/TOKEN to IP:PORT.
- **URL_PATH**: The path you setted in the setWebhook call. It is recommended to leave it equal to your bot's token, but you can change it if you want.
- **KEY**: The path to the private key file you generated if you generated a self-signed certificated o the private key you got if you bought a certificate (or generated a letsencrypt one).
- **CERT**: The path to your .pem file that was generated the same way as your key file.
- **WEBHOOK_URL**: Your full webhook URL. Normally here you'll just edit the domain name. But you can edit the full URL if you want.

## Begin implementing your commands
Implementing new commands is easy, but you might want to take a look at the [python-telegram-bot documentation](https://python-telegram-bot.org/). Now you are quite an expert at python-telegram bot, let's see how to implement new commands.
Implementing new commands is easy, but you might want to take a look at the [python-telegram-bot documentation](https://python-telegram-bot.org/).

Now that you are quite an expert at python-telegram bot, let's see how to implement new commands.

First, create your command file at the `bot/` directory. For example `bot/start.py`. This file will hold all of your command code. The second thing you have to do is... yes, implement your command logic. I'll give you an example for the *start* command:
You just have to create a file named the samy way as your command and implement your command functionality in a function called `main`. Let's take a closer look at this process.

Imagine you want to implement a `start` command. First, create your command file at the `bot/` directory. As the command would be `start` the filename has to be `start` too, i.e. `bot/start.py`. This file will hold all of your command code. The only requirement is that your command file has to implement a **at least** a `main` function. So t the second thing you have to do is... yes, implement your command logic. I'll give you an example for the *start* command:
```python
def main(bot, update, args):
bot.send_message(chat_id=update.message.chat_id, text="I'm a bot, please talk to me!")
```

For now this project only supports adding `CommandHandler`. We will add all of the [other handlers](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.html#handlers) in the future.

All of your commands functions will receive the same three arguments, [bot](https://python-telegram-bot.readthedocs.io/en/latest/telegram.bot.html) which is the actual bot, [update](https://python-telegram-bot.readthedocs.io/en/latest/telegram.update.html) which holds all the data from the incoming update and `args`, which is an array with the text sent by the user (if any) splitted by spaces. So if you want to rebuild the user input you'll have to do `args.join(' ')`.
All of your commands functions will receive the same three arguments, [bot](https://python-telegram-bot.readthedocs.io/en/latest/telegram.bot.html) which is the actual bot, [update](https://python-telegram-bot.readthedocs.io/en/latest/telegram.update.html) which holds all the data from the incoming update and `args`, which is an array with the text sent by the user (if any) splitted by spaces. So if you want to rebuild the user input you'll have to do `' '.join(args)`.

The last step is to register your new command in the `commands.py` file. First I'll give you an example of the `commands.py` file with the `/start` command already registered and then I'll explain it to you:
```python
from bot.start import main as start
And you're done. Next time you launch your bot, it'll respont to the `/start` command.

commands = [
{
"command": 'start',
"function": start
}
]
```
But... what if you want to name your files in some way and the commands implemented by those files in some other way? For that purpose exists the `commands.py` file.

We begin by importing our command function (in our example, `start.py > main`) an assigning it an alias by doing:
```python
from bot.start import main
```
And then create a new item in the commands array with a dictionary holding two keys, `command` and `function`:
```python
commands = [
{
"command": '',
"function":
}
]
```
Let's imagine that you want to implement a command named `other`, but you cant to name your command file `test.py`. First, you'll create your `bot/test.py` file with a `main` function inside it implementing all your command's logic. And then you register it in the `commands.py` file this way:

The first key is the actual command to which your bot will respond, but without the slash (in our example `'start'`). The second one is the function you implemented for handling that command inside the `.py` file (in our example, `main`). So our commands array will look like:
```python
commands = [
{
"command": 'start',
"function": main
commands = {
'test': {
'command': 'other'
}
]
}
```

If you want to register another command just import its file and add it to the commands array, e.g.
```python
from bot.start import main as start
from bot.another_command import main as another_command

commands = [
{
"command": 'start',
"function": start
},
{
"command": 'another',
"function": another_command
}
]
```
And you're done! Repeat that process once per new command and the `main.py` will take care of adding one `CommandHandler` for every command you registered without bothering you.
Each file has its own entry. The key has to be the filename of your command, in our example `test`. The value is another dict, with a key `command`. That property will hold your custom command name, in our example `other`.

Next time you launch your bot it'll respond to the command `/other`.

And you're done! The project will take care of adding one `CommandHandler` for every file in your `bot/` folder without bothering you.

## Feedback and new ideas
Feel free to give me some feedback or new ideas to improve the project. If you have any suggestions please feel free to create an [issue](https://github.com/alesanmed/python-telegram-bot-seed/issues) and tag it as `enhancement`
Expand Down
Empty file removed bot/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions bot/test.py
@@ -0,0 +1,4 @@
# encoding: utf-8

def main(bot, update):
bot.send_message(chat_id=update.message.chat_id, text="I'm a bot, please talk to me!")
10 changes: 4 additions & 6 deletions commands.py
@@ -1,9 +1,7 @@
# encoding: utf-8
from bot.start import main as start

commands = [
{
"command": 'start',
"function": start
commands = {
'test': {
'command': 'other'
}
]
}
39 changes: 35 additions & 4 deletions main.py
@@ -1,21 +1,52 @@
#coding: utf-8
import signal
import sys
import os

from telegram.ext import Updater, CommandHandler
from importlib import import_module
import inflection

import utils.logger as logger
import configurations.bot_config as bot_config
from commands import commands

updater = None

def load_commands(dispatcher):
base_path = os.path.join(os.path.dirname(__file__), 'bot')
files = os.listdir(base_path)

for file_name in files:
command_name, _ = os.path.splitext(file_name)

module = import_module(f'.{command_name}', 'bot')

if command_name in commands:
command_name = commands[command_name]['command']

command_handler = CommandHandler(inflection.underscore(command_name), module.main)

dispatcher.add_handler(command_handler)


def graceful_exit(signum, frame):
if(updater is not None):
updater.bot.delete_webhook()

sys.exit(1)

if __name__ == "__main__":
logger.init_logger(f'logs/{bot_config.NAME}.log')

updater = Updater(token=bot_config.TOKEN)

dispatcher = updater.dispatcher
signal.signal(signal.SIGINT, graceful_exit)

for command in commands:
command_handler = CommandHandler(command['command'], command['function'])
dispatcher = updater.dispatcher

dispatcher.add_handler(command_handler)
load_commands(dispatcher)

if(bot_config.WEBHOOK):
updater.start_webhook(listen=bot_config.IP, port=bot_config.PORT, url_path=bot_config.URL_PATH)
updater.bot.set_webhook(url=bot_config.WEBHOOK_URL)
Expand Down

0 comments on commit 9c29132

Please sign in to comment.