Merge pull request #4092 from MrKrautee/telegram
Telegram: specify custom keyboard in config
This commit is contained in:
commit
7cef5ac217
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user