From e92bcb00f6ead312525608dea96c758273ef8107 Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 13:43:50 +0100 Subject: [PATCH 01/16] telegram: specify custom shortcut bottons (keyboard) in config.json --- freqtrade/rpc/telegram.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 1d36b7e4d..ea9a3c31d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -5,6 +5,7 @@ This module manage Telegram communication """ import json import logging +from itertools import chain from datetime import timedelta from typing import Any, Callable, Dict, List, Union @@ -862,12 +863,39 @@ class Telegram(RPC): :return: None """ + # default / fallback shortcut buttons 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 + valid_btns: 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 shortcuts specified in config.json + shortcut_btns = self._config['telegram'].get('shortcut_btns', []) + if shortcut_btns: + # check for valid shortcuts + invalid_shortcut_btns = [b for b in chain.from_iterable(shortcut_btns) + if b not in valid_btns] + if len(invalid_shortcut_btns): + logger.warning('rpc.telegram: invalid shortcut_btns %s', + invalid_shortcut_btns) + logger.info('rpc.telegram: using default shortcut_btns %s', + keyboard) + else: + keyboard = shortcut_btns + logger.info( + 'rpc.telegram uses custom shortcut bottons specified in ' + + 'config.json %s', [btn for btn in keyboard] + ) + reply_markup = ReplyKeyboardMarkup(keyboard) try: From 5e6897b278a4b367993e08c79ed5562a40de21ae Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 14:48:49 +0100 Subject: [PATCH 02/16] documentation for custom keyboard --- docs/telegram-usage.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index c940f59ac..11eb831ad 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -87,6 +87,35 @@ Example configuration showing the different settings: }, ``` +## Create a custom keyboard (command shortcuts 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", + "shortcut_btns": [ + ["/daily", "/stats", "/balance", "/profit"], + ["/status table", "/performance"], + ["/reload_config", "/count", "/logs"] + ] + }, +``` +!! NOTE: Only a certain list of commands are allowed. Command arguments are not +supported! +### Supported Commands + `/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 Per default, the Telegram bot shows predefined commands. Some commands From 5b3ffd514138cef8f6c3160f13bdcbc31d8b73d4 Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 15:23:40 +0100 Subject: [PATCH 03/16] better log msg, comments --- freqtrade/rpc/telegram.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ea9a3c31d..ec99e4aa9 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -872,6 +872,9 @@ class Telegram(RPC): # 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 this + # needs refacoring of the whole telegram module (same problem + # in _help()). valid_btns: List[str] = ['/start', '/stop', '/status', '/status table', '/trades', '/profit', '/performance', '/daily', '/stats', '/count', '/locks', '/balance', @@ -885,16 +888,13 @@ class Telegram(RPC): invalid_shortcut_btns = [b for b in chain.from_iterable(shortcut_btns) if b not in valid_btns] if len(invalid_shortcut_btns): - logger.warning('rpc.telegram: invalid shortcut_btns %s', - invalid_shortcut_btns) - logger.info('rpc.telegram: using default shortcut_btns %s', - keyboard) + logger.warning('rpc.telegram: invalid commands for custom ' + f'keyboard: {invalid_shortcut_btns}') + logger.info('rpc.telegram: using default keyboard.') else: keyboard = shortcut_btns - logger.info( - 'rpc.telegram uses custom shortcut bottons specified in ' + - 'config.json %s', [btn for btn in keyboard] - ) + logger.info('rpc.telegram using custom keyboard from ' + f'config.json: {[btn for btn in keyboard]}') reply_markup = ReplyKeyboardMarkup(keyboard) From bf920994868a30113c57b665e7739261f4a91ac8 Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 16:35:54 +0100 Subject: [PATCH 04/16] test for custom keyboard --- tests/rpc/test_rpc_telegram.py | 48 +++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ecad05683..62c60d8e6 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -10,7 +10,7 @@ from unittest.mock import ANY, MagicMock, PropertyMock import arrow import pytest -from telegram import Chat, Message, Update +from telegram import Chat, Message, Update, ReplyKeyboardMarkup from telegram.error import NetworkError from freqtrade import __version__ @@ -1729,3 +1729,49 @@ def test__send_msg_network_error(default_conf, mocker, caplog) -> None: # Bot should've tried to send it twice assert len(bot.method_calls) == 2 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) + telegram = Telegram(freqtradebot) + telegram._updater = MagicMock() + telegram._updater.bot = bot + + 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) + + # no shortcut_btns in config -> default keyboard + telegram._config['telegram']['enabled'] = True + telegram._send_msg('test') + used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] + assert used_keyboard == default_keyboard + + # invalid shortcut_btns in config -> default keyboard + telegram._config['telegram']['enabled'] = True + telegram._config['telegram']['shortcut_btns'] = invalid_keys_list + telegram._send_msg('test') + used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] + assert used_keyboard == default_keyboard + assert log_has("rpc.telegram: invalid commands for custom keyboard: " + "['/not_valid', '/alsoinvalid']", caplog) + assert log_has('rpc.telegram: using default keyboard.', caplog) + + # valid shortcut_btns in config -> custom keyboard + telegram._config['telegram']['enabled'] = True + telegram._config['telegram']['shortcut_btns'] = custom_keys_list + telegram._send_msg('test') + used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] + assert used_keyboard == custom_keyboard + assert log_has("rpc.telegram using custom keyboard from config.json: " + "[['/daily', '/stats', '/balance', '/profit'], ['/count', " + "'/start', '/reload_config', '/help']]", caplog) From 621105df9aecdc7a278cc89f035a7521a0fb1e23 Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 16:50:42 +0100 Subject: [PATCH 05/16] renaming shortcut_btns to keyboard --- docs/telegram-usage.md | 4 ++-- freqtrade/rpc/telegram.py | 14 +++++++------- tests/rpc/test_rpc_telegram.py | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 11eb831ad..950b4df1e 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -87,7 +87,7 @@ Example configuration showing the different settings: }, ``` -## Create a custom keyboard (command shortcuts buttons) +## 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 @@ -104,7 +104,7 @@ You can create your own keyboard in `config.json`: "enabled": true, "token": "your_telegram_token", "chat_id": "your_telegram_chat_id", - "shortcut_btns": [ + "keyboard": [ ["/daily", "/stats", "/balance", "/profit"], ["/status table", "/performance"], ["/reload_config", "/count", "/logs"] diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ec99e4aa9..3260d0d4f 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -882,17 +882,17 @@ class Telegram(RPC): '/logs', '/whitelist', '/blacklist', '/edge', '/help', '/version'] # custom shortcuts specified in config.json - shortcut_btns = self._config['telegram'].get('shortcut_btns', []) - if shortcut_btns: + cust_keyboard = self._config['telegram'].get('keyboard', []) + if cust_keyboard: # check for valid shortcuts - invalid_shortcut_btns = [b for b in chain.from_iterable(shortcut_btns) - if b not in valid_btns] - if len(invalid_shortcut_btns): + invalid_keys = [b for b in chain.from_iterable(cust_keyboard) + if b not in valid_btns] + if len(invalid_keys): logger.warning('rpc.telegram: invalid commands for custom ' - f'keyboard: {invalid_shortcut_btns}') + f'keyboard: {invalid_keys}') logger.info('rpc.telegram: using default keyboard.') else: - keyboard = shortcut_btns + keyboard = cust_keyboard logger.info('rpc.telegram using custom keyboard from ' f'config.json: {[btn for btn in keyboard]}') diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 62c60d8e6..5bbc68639 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1750,15 +1750,15 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: ['/count', '/start', '/reload_config', '/help']] custom_keyboard = ReplyKeyboardMarkup(custom_keys_list) - # no shortcut_btns in config -> default keyboard + # no keyboard in config -> default keyboard telegram._config['telegram']['enabled'] = True telegram._send_msg('test') used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] assert used_keyboard == default_keyboard - # invalid shortcut_btns in config -> default keyboard + # invalid keyboard in config -> default keyboard telegram._config['telegram']['enabled'] = True - telegram._config['telegram']['shortcut_btns'] = invalid_keys_list + telegram._config['telegram']['keyboard'] = invalid_keys_list telegram._send_msg('test') used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] assert used_keyboard == default_keyboard @@ -1766,9 +1766,9 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: "['/not_valid', '/alsoinvalid']", caplog) assert log_has('rpc.telegram: using default keyboard.', caplog) - # valid shortcut_btns in config -> custom keyboard + # valid keyboard in config -> custom keyboard telegram._config['telegram']['enabled'] = True - telegram._config['telegram']['shortcut_btns'] = custom_keys_list + telegram._config['telegram']['keyboard'] = custom_keys_list telegram._send_msg('test') used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] assert used_keyboard == custom_keyboard From 799e6be2eba9f0b385d3bed1d2f9665743f37029 Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 17:13:03 +0100 Subject: [PATCH 06/16] fix tests --- tests/rpc/test_rpc_telegram.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 5bbc68639..5a88edf54 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1753,14 +1753,14 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: # no keyboard in config -> default keyboard telegram._config['telegram']['enabled'] = True telegram._send_msg('test') - used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] + used_keyboard = bot.send_message.call_args[1]['reply_markup'] assert used_keyboard == default_keyboard # invalid keyboard in config -> default keyboard telegram._config['telegram']['enabled'] = True telegram._config['telegram']['keyboard'] = invalid_keys_list telegram._send_msg('test') - used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] + used_keyboard = bot.send_message.call_args[1]['reply_markup'] assert used_keyboard == default_keyboard assert log_has("rpc.telegram: invalid commands for custom keyboard: " "['/not_valid', '/alsoinvalid']", caplog) @@ -1770,7 +1770,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: telegram._config['telegram']['enabled'] = True telegram._config['telegram']['keyboard'] = custom_keys_list telegram._send_msg('test') - used_keyboard = bot.send_message.call_args.kwargs['reply_markup'] + used_keyboard = bot.send_message.call_args[1]['reply_markup'] assert used_keyboard == custom_keyboard assert log_has("rpc.telegram using custom keyboard from config.json: " "[['/daily', '/stats', '/balance', '/profit'], ['/count', " From 6b44545d37bd197e53c56fdc99d0965c14608553 Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 17:22:23 +0100 Subject: [PATCH 07/16] sort order imports --- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 3260d0d4f..466c58878 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -5,8 +5,8 @@ This module manage Telegram communication """ import json import logging -from itertools import chain from datetime import timedelta +from itertools import chain from typing import Any, Callable, Dict, List, Union import arrow diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 5a88edf54..ce3db7130 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -10,7 +10,7 @@ from unittest.mock import ANY, MagicMock, PropertyMock import arrow import pytest -from telegram import Chat, Message, Update, ReplyKeyboardMarkup +from telegram import Chat, Message, ReplyKeyboardMarkup, Update from telegram.error import NetworkError from freqtrade import __version__ From f39dde121ab5d836d1be616d19858c7f35fe1fe2 Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 22:36:56 +0100 Subject: [PATCH 08/16] moved keyboard config validation to __inti__ --- freqtrade/rpc/telegram.py | 79 ++++++++++++++++++---------------- tests/rpc/test_rpc_telegram.py | 14 ++++-- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 466c58878..1ba45e089 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -7,11 +7,11 @@ import json import logging from datetime import timedelta from itertools import chain -from typing import Any, Callable, Dict, List, Union +from typing import Any, Callable, Dict, List import arrow from tabulate import tabulate -from telegram import KeyboardButton, ParseMode, ReplyKeyboardMarkup, Update +from telegram import ParseMode, ReplyKeyboardMarkup, Update from telegram.error import NetworkError, TelegramError from telegram.ext import CallbackContext, CommandHandler, Updater from telegram.utils.helpers import escape_markdown @@ -75,10 +75,48 @@ class Telegram(RPC): self._updater: Updater self._config = freqtrade.config + self._validate_keyboard() self._init() if self._config.get('fiat_display_currency', None): self._fiat_converter = CryptoToFiatConverter() + def _validate_keyboard(self) -> None: + """ + Validates the keyboard configuration from telegram config + section. + """ + self._keyboard: List[List[str]] = [ + ['/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 shortcuts 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): + logger.warning('rpc.telegram: invalid commands for custom ' + f'keyboard: {invalid_keys}') + logger.info('rpc.telegram: using default keyboard.') + else: + self._keyboard = cust_keyboard + logger.info('rpc.telegram using custom keyboard from ' + f'config.json: {self._keyboard}') + def _init(self) -> None: """ Initializes this module with the given config, @@ -862,42 +900,7 @@ class Telegram(RPC): :param parse_mode: telegram parse mode :return: None """ - - # default / fallback shortcut buttons - 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 this - # needs refacoring of the whole telegram module (same problem - # in _help()). - valid_btns: 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 shortcuts 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_btns] - if len(invalid_keys): - logger.warning('rpc.telegram: invalid commands for custom ' - f'keyboard: {invalid_keys}') - logger.info('rpc.telegram: using default keyboard.') - else: - keyboard = cust_keyboard - logger.info('rpc.telegram using custom keyboard from ' - f'config.json: {[btn for btn in keyboard]}') - - reply_markup = ReplyKeyboardMarkup(keyboard) - + reply_markup = ReplyKeyboardMarkup(self._keyboard) try: try: self._updater.bot.send_message( diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ce3db7130..148eb6428 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1751,14 +1751,17 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: custom_keyboard = ReplyKeyboardMarkup(custom_keys_list) # no keyboard in config -> default keyboard - telegram._config['telegram']['enabled'] = True + # telegram._config['telegram']['enabled'] = True 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 - telegram._config['telegram']['enabled'] = True - telegram._config['telegram']['keyboard'] = invalid_keys_list + freqtradebot.config['telegram']['enabled'] = True + freqtradebot.config['telegram']['keyboard'] = invalid_keys_list + telegram = Telegram(freqtradebot) + telegram._updater = MagicMock() + telegram._updater.bot = bot telegram._send_msg('test') used_keyboard = bot.send_message.call_args[1]['reply_markup'] assert used_keyboard == default_keyboard @@ -1767,6 +1770,11 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: assert log_has('rpc.telegram: using default keyboard.', caplog) # valid keyboard in config -> custom keyboard + freqtradebot.config['telegram']['enabled'] = True + freqtradebot.config['telegram']['keyboard'] = custom_keys_list + telegram = Telegram(freqtradebot) + telegram._updater = MagicMock() + telegram._updater.bot = bot telegram._config['telegram']['enabled'] = True telegram._config['telegram']['keyboard'] = custom_keys_list telegram._send_msg('test') From 5423c21be0be73139e9ba4488a14ecb5eb53a2ca Mon Sep 17 00:00:00 2001 From: Christof Date: Sun, 20 Dec 2020 22:51:40 +0100 Subject: [PATCH 09/16] keyboard type --- freqtrade/rpc/telegram.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 1ba45e089..5be880bcc 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -7,11 +7,11 @@ import json import logging from datetime import timedelta from itertools import chain -from typing import Any, Callable, Dict, List +from typing import Any, Callable, Dict, List, Union import arrow from tabulate import tabulate -from telegram import ParseMode, ReplyKeyboardMarkup, Update +from telegram import KeyboardButton, ParseMode, ReplyKeyboardMarkup, Update from telegram.error import NetworkError, TelegramError from telegram.ext import CallbackContext, CommandHandler, Updater from telegram.utils.helpers import escape_markdown @@ -85,7 +85,7 @@ class Telegram(RPC): Validates the keyboard configuration from telegram config section. """ - self._keyboard: List[List[str]] = [ + self._keyboard: List[List[Union[str, KeyboardButton]]] = [ ['/daily', '/profit', '/balance'], ['/status', '/status table', '/performance'], ['/count', '/start', '/stop', '/help'] From 277f3ff47b1ce955a44889158374aa5f24283455 Mon Sep 17 00:00:00 2001 From: Christof Date: Mon, 21 Dec 2020 09:52:10 +0100 Subject: [PATCH 10/16] tests: cleaup --- tests/rpc/test_rpc_telegram.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 148eb6428..26384a507 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1736,9 +1736,6 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: bot = MagicMock() bot.send_message = MagicMock() freqtradebot = get_patched_freqtradebot(mocker, default_conf) - telegram = Telegram(freqtradebot) - telegram._updater = MagicMock() - telegram._updater.bot = bot invalid_keys_list = [['/not_valid', '/profit'], ['/daily'], ['/alsoinvalid']] default_keys_list = [['/daily', '/profit', '/balance'], @@ -1750,8 +1747,15 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: ['/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 - # telegram._config['telegram']['enabled'] = True + 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 @@ -1759,9 +1763,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: # invalid keyboard in config -> default keyboard freqtradebot.config['telegram']['enabled'] = True freqtradebot.config['telegram']['keyboard'] = invalid_keys_list - telegram = Telegram(freqtradebot) - telegram._updater = MagicMock() - telegram._updater.bot = bot + telegram = init_telegram(freqtradebot) telegram._send_msg('test') used_keyboard = bot.send_message.call_args[1]['reply_markup'] assert used_keyboard == default_keyboard @@ -1772,11 +1774,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: # valid keyboard in config -> custom keyboard freqtradebot.config['telegram']['enabled'] = True freqtradebot.config['telegram']['keyboard'] = custom_keys_list - telegram = Telegram(freqtradebot) - telegram._updater = MagicMock() - telegram._updater.bot = bot - telegram._config['telegram']['enabled'] = True - telegram._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 From 78dff3d5103640aa0efccd610d847168de092d25 Mon Sep 17 00:00:00 2001 From: Christof Date: Mon, 21 Dec 2020 10:22:24 +0100 Subject: [PATCH 11/16] docs: Note syntax --- docs/telegram-usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 950b4df1e..da4a2e8dd 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -111,8 +111,8 @@ You can create your own keyboard in `config.json`: ] }, ``` -!! NOTE: Only a certain list of commands are allowed. Command arguments are not -supported! +!!! Note + Only a certain list of commands are allowed. Command arguments are not supported! ### Supported Commands `/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopbuy`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version` From 4dadfd199d8485c3920aab5e128d942c191a508f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 22 Dec 2020 07:36:53 +0100 Subject: [PATCH 12/16] Documentation syntax --- docs/telegram-usage.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index da4a2e8dd..c8d95d743 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -88,17 +88,22 @@ 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 + ["/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, @@ -107,14 +112,15 @@ You can create your own keyboard in `config.json`: "keyboard": [ ["/daily", "/stats", "/balance", "/profit"], ["/status table", "/performance"], - ["/reload_config", "/count", "/logs"] + ["/reload_config", "/count", "/logs"] ] }, ``` -!!! Note - Only a certain list of commands are allowed. Command arguments are not supported! -### Supported Commands - `/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopbuy`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version` + +!!! 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 From be28b42bfa232274c7e3f974c3eee2f8878af14c Mon Sep 17 00:00:00 2001 From: Christof Date: Tue, 22 Dec 2020 12:34:21 +0100 Subject: [PATCH 13/16] Exception for invalid keyboard config --- freqtrade/rpc/telegram.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 5be880bcc..63a98e2b1 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -17,6 +17,7 @@ from telegram.ext import CallbackContext, CommandHandler, Updater from telegram.utils.helpers import escape_markdown from freqtrade.__init__ import __version__ +from freqtrade.exceptions import OperationalException from freqtrade.rpc import RPC, RPCException, RPCMessageType from freqtrade.rpc.fiat_convert import CryptoToFiatConverter @@ -75,12 +76,12 @@ class Telegram(RPC): self._updater: Updater self._config = freqtrade.config - self._validate_keyboard() + self._init_keyboard() self._init() if self._config.get('fiat_display_currency', None): self._fiat_converter = CryptoToFiatConverter() - def _validate_keyboard(self) -> None: + def _init_keyboard(self) -> None: """ Validates the keyboard configuration from telegram config section. @@ -102,19 +103,19 @@ class Telegram(RPC): '/logs', '/whitelist', '/blacklist', '/edge', '/help', '/version'] - # custom shortcuts specified in config.json + # 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): - logger.warning('rpc.telegram: invalid commands for custom ' - f'keyboard: {invalid_keys}') - logger.info('rpc.telegram: using default keyboard.') + err_msg = ('invalid commands for custom keyboard: ' + f'{invalid_keys}') + raise OperationalException(err_msg) else: self._keyboard = cust_keyboard - logger.info('rpc.telegram using custom keyboard from ' + logger.info('using custom keyboard from ' f'config.json: {self._keyboard}') def _init(self) -> None: From cd1a8e2c42a0bccf6c98381f0835289416311502 Mon Sep 17 00:00:00 2001 From: Christof Date: Tue, 22 Dec 2020 12:39:27 +0100 Subject: [PATCH 14/16] better error msg --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 63a98e2b1..b520756a9 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -110,8 +110,8 @@ class Telegram(RPC): invalid_keys = [b for b in chain.from_iterable(cust_keyboard) if b not in valid_keys] if len(invalid_keys): - err_msg = ('invalid commands for custom keyboard: ' - f'{invalid_keys}') + err_msg = ('config.telegram.keyboard: invalid commands for ' + f'custom keyboard: {invalid_keys}') raise OperationalException(err_msg) else: self._keyboard = cust_keyboard From b1fe5940fa7207e2f0c356f362e199b9c7c9fb91 Mon Sep 17 00:00:00 2001 From: Christof Date: Tue, 22 Dec 2020 13:01:01 +0100 Subject: [PATCH 15/16] check for Exception and log msgs --- tests/rpc/test_rpc_telegram.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 26384a507..df8983324 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -16,6 +16,7 @@ from telegram.error import NetworkError from freqtrade import __version__ from freqtrade.constants import CANCEL_REASON from freqtrade.edge import PairInfo +from freqtrade.exceptions import OperationalException from freqtrade.freqtradebot import FreqtradeBot from freqtrade.loggers import setup_logging from freqtrade.persistence import PairLocks, Trade @@ -1763,13 +1764,10 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: # invalid keyboard in config -> default keyboard freqtradebot.config['telegram']['enabled'] = True freqtradebot.config['telegram']['keyboard'] = invalid_keys_list - telegram = init_telegram(freqtradebot) - telegram._send_msg('test') - used_keyboard = bot.send_message.call_args[1]['reply_markup'] - assert used_keyboard == default_keyboard - assert log_has("rpc.telegram: invalid commands for custom keyboard: " - "['/not_valid', '/alsoinvalid']", caplog) - assert log_has('rpc.telegram: using default keyboard.', caplog) + err_msg = re.escape("config.telegram.keyboard: invalid commands for " + "custom keyboard: ['/not_valid', '/alsoinvalid']") + with pytest.raises(OperationalException, match=err_msg): + telegram = init_telegram(freqtradebot) # valid keyboard in config -> custom keyboard freqtradebot.config['telegram']['enabled'] = True @@ -1778,6 +1776,6 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: telegram._send_msg('test') used_keyboard = bot.send_message.call_args[1]['reply_markup'] assert used_keyboard == custom_keyboard - assert log_has("rpc.telegram using custom keyboard from config.json: " + assert log_has("using custom keyboard from config.json: " "[['/daily', '/stats', '/balance', '/profit'], ['/count', " "'/start', '/reload_config', '/help']]", caplog) From 74bcd82c3d28f7ce6c12118cc63f8f0c689eefeb Mon Sep 17 00:00:00 2001 From: Christof Date: Wed, 23 Dec 2020 16:00:01 +0100 Subject: [PATCH 16/16] Exception msg --- freqtrade/rpc/telegram.py | 5 +++-- tests/rpc/test_rpc_telegram.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index b520756a9..e2985fbee 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -110,8 +110,9 @@ class Telegram(RPC): 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 keyboard: {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 diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index df8983324..b8c5d8858 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1764,8 +1764,9 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: # 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 keyboard: ['/not_valid', '/alsoinvalid']") + 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)