Merge pull request #4092 from MrKrautee/telegram

Telegram: specify custom keyboard in config
This commit is contained in:
Matthias 2020-12-23 16:25:37 +01:00 committed by GitHub
commit 7cef5ac217
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 129 additions and 10 deletions

View File

@ -87,6 +87,41 @@ Example configuration showing the different settings:
}, },
``` ```
## Create a custom keyboard (command shortcut buttons)
Telegram allows us to create a custom keyboard with buttons for commands.
The default custom keyboard looks like this.
```python
[
["/daily", "/profit", "/balance"], # row 1, 3 commands
["/status", "/status table", "/performance"], # row 2, 3 commands
["/count", "/start", "/stop", "/help"] # row 3, 4 commands
]
```
### Usage
You can create your own keyboard in `config.json`:
``` json
"telegram": {
"enabled": true,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id",
"keyboard": [
["/daily", "/stats", "/balance", "/profit"],
["/status table", "/performance"],
["/reload_config", "/count", "/logs"]
]
},
```
!!! Note "Supported Commands"
Only the following commands are allowed. Command arguments are not supported!
`/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopbuy`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version`
## Telegram commands ## Telegram commands
Per default, the Telegram bot shows predefined commands. Some commands Per default, the Telegram bot shows predefined commands. Some commands

View File

@ -6,6 +6,7 @@ This module manage Telegram communication
import json import json
import logging import logging
from datetime import timedelta from datetime import timedelta
from itertools import chain
from typing import Any, Callable, Dict, List, Union from typing import Any, Callable, Dict, List, Union
import arrow import arrow
@ -16,6 +17,7 @@ from telegram.ext import CallbackContext, CommandHandler, Updater
from telegram.utils.helpers import escape_markdown from telegram.utils.helpers import escape_markdown
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.exceptions import OperationalException
from freqtrade.rpc import RPC, RPCException, RPCMessageType from freqtrade.rpc import RPC, RPCException, RPCMessageType
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@ -74,10 +76,49 @@ class Telegram(RPC):
self._updater: Updater self._updater: Updater
self._config = freqtrade.config self._config = freqtrade.config
self._init_keyboard()
self._init() self._init()
if self._config.get('fiat_display_currency', None): if self._config.get('fiat_display_currency', None):
self._fiat_converter = CryptoToFiatConverter() self._fiat_converter = CryptoToFiatConverter()
def _init_keyboard(self) -> None:
"""
Validates the keyboard configuration from telegram config
section.
"""
self._keyboard: List[List[Union[str, KeyboardButton]]] = [
['/daily', '/profit', '/balance'],
['/status', '/status table', '/performance'],
['/count', '/start', '/stop', '/help']
]
# do not allow commands with mandatory arguments and critical cmds
# like /forcesell and /forcebuy
# TODO: DRY! - its not good to list all valid cmds here. But otherwise
# this needs refacoring of the whole telegram module (same
# problem in _help()).
valid_keys: List[str] = ['/start', '/stop', '/status', '/status table',
'/trades', '/profit', '/performance', '/daily',
'/stats', '/count', '/locks', '/balance',
'/stopbuy', '/reload_config', '/show_config',
'/logs', '/whitelist', '/blacklist', '/edge',
'/help', '/version']
# custom keyboard specified in config.json
cust_keyboard = self._config['telegram'].get('keyboard', [])
if cust_keyboard:
# check for valid shortcuts
invalid_keys = [b for b in chain.from_iterable(cust_keyboard)
if b not in valid_keys]
if len(invalid_keys):
err_msg = ('config.telegram.keyboard: Invalid commands for '
f'custom Telegram keyboard: {invalid_keys}'
f'\nvalid commands are: {valid_keys}')
raise OperationalException(err_msg)
else:
self._keyboard = cust_keyboard
logger.info('using custom keyboard from '
f'config.json: {self._keyboard}')
def _init(self) -> None: def _init(self) -> None:
""" """
Initializes this module with the given config, Initializes this module with the given config,
@ -861,15 +902,7 @@ class Telegram(RPC):
:param parse_mode: telegram parse mode :param parse_mode: telegram parse mode
:return: None :return: None
""" """
reply_markup = ReplyKeyboardMarkup(self._keyboard)
keyboard: List[List[Union[str, KeyboardButton]]] = [
['/daily', '/profit', '/balance'],
['/status', '/status table', '/performance'],
['/count', '/start', '/stop', '/help']
]
reply_markup = ReplyKeyboardMarkup(keyboard)
try: try:
try: try:
self._updater.bot.send_message( self._updater.bot.send_message(

View File

@ -10,12 +10,13 @@ from unittest.mock import ANY, MagicMock, PropertyMock
import arrow import arrow
import pytest import pytest
from telegram import Chat, Message, Update from telegram import Chat, Message, ReplyKeyboardMarkup, Update
from telegram.error import NetworkError from telegram.error import NetworkError
from freqtrade import __version__ from freqtrade import __version__
from freqtrade.constants import CANCEL_REASON from freqtrade.constants import CANCEL_REASON
from freqtrade.edge import PairInfo from freqtrade.edge import PairInfo
from freqtrade.exceptions import OperationalException
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging from freqtrade.loggers import setup_logging
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
@ -1729,3 +1730,53 @@ def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
# Bot should've tried to send it twice # Bot should've tried to send it twice
assert len(bot.method_calls) == 2 assert len(bot.method_calls) == 2
assert log_has('Telegram NetworkError: Oh snap! Trying one more time.', caplog) assert log_has('Telegram NetworkError: Oh snap! Trying one more time.', caplog)
def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
bot = MagicMock()
bot.send_message = MagicMock()
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
invalid_keys_list = [['/not_valid', '/profit'], ['/daily'], ['/alsoinvalid']]
default_keys_list = [['/daily', '/profit', '/balance'],
['/status', '/status table', '/performance'],
['/count', '/start', '/stop', '/help']]
default_keyboard = ReplyKeyboardMarkup(default_keys_list)
custom_keys_list = [['/daily', '/stats', '/balance', '/profit'],
['/count', '/start', '/reload_config', '/help']]
custom_keyboard = ReplyKeyboardMarkup(custom_keys_list)
def init_telegram(freqtradebot):
telegram = Telegram(freqtradebot)
telegram._updater = MagicMock()
telegram._updater.bot = bot
return telegram
# no keyboard in config -> default keyboard
freqtradebot.config['telegram']['enabled'] = True
telegram = init_telegram(freqtradebot)
telegram._send_msg('test')
used_keyboard = bot.send_message.call_args[1]['reply_markup']
assert used_keyboard == default_keyboard
# invalid keyboard in config -> default keyboard
freqtradebot.config['telegram']['enabled'] = True
freqtradebot.config['telegram']['keyboard'] = invalid_keys_list
err_msg = re.escape("config.telegram.keyboard: Invalid commands for custom "
"Telegram keyboard: ['/not_valid', '/alsoinvalid']"
"\nvalid commands are: ") + r"*"
with pytest.raises(OperationalException, match=err_msg):
telegram = init_telegram(freqtradebot)
# valid keyboard in config -> custom keyboard
freqtradebot.config['telegram']['enabled'] = True
freqtradebot.config['telegram']['keyboard'] = custom_keys_list
telegram = init_telegram(freqtradebot)
telegram._send_msg('test')
used_keyboard = bot.send_message.call_args[1]['reply_markup']
assert used_keyboard == custom_keyboard
assert log_has("using custom keyboard from config.json: "
"[['/daily', '/stats', '/balance', '/profit'], ['/count', "
"'/start', '/reload_config', '/help']]", caplog)