This is an echo-bot, which can work with Telegram and Vk. Whenever a user of a messager sends a message to bot, it is sent back to the user again.
The bot may not support some types of attachments for Vk, if they appear in any message, a warning message is logged. Currently, for Vk stickers, photos, videos, audios, documents, wall posts and market units are supported. For Telegram, all types of messages are supported, including media groups. Also, you can roll a dice using /dice command; the same effect is when you forward a dice message to bot.
Also, the two bots supports some shared commands:
/help
shows help message;/set
tells the bot to show keyboard with five buttons, after pressing one of those buttons every message will be repeated the respective amount of times.
The main logic scenario is the same for both messagers versions. The first thing to do is to get updates from messager API server. When updates are fetched, the bot starts to process them. If the bot failed to get updates due to nonfatal error, it tries to handle it (handlers are separate for two messagers implementations).
The first thing to do is to define update type. The update type can be one of these:
- Request for help message
- Request for keyboard, which buttons have a text label with a possible number of repeats (from 1 to 5);
- Button pressed, which indicates to set the number of echo repeats for one user;
- Other messages, that are simply sent back with the same information.
For this event, the text message with help message is sent away, which is included into the configuration file.
For this event, the JSON-serialized object with keyboard is sent to the user with the text message.
This event is usually caused by pressing a button with a corresponding repeat number, which causes a callback update to be sent to the bot. The latter one parses callback payload and gets this number, after what it is saved inside bot's internal state (currently, a map is used for this, but it's possible to use database, too).
For messages that don't have any specific meaning the bot is trying to obtain an HTTP request, which can be used to send the same message to the user. This is not possible only for a member of a media group in Telegram; all such messages are saved to the internal bot state and sent again as a media groups after all other updates have been already processed. Before sending a reply bot gets a number of repeats for the target user and replicates the HTTP request the corresponding number of times.
The main application logic is inside the Execute.hs
.
How it works is totally written in the sections above.
Its main peculiarity is that for different messagers, though
logic is similar, but not exactly the same, and even types are not the same too.
Hence, all functions should be parameterized by a "messager", which can be done
in some way.
The type family (BotClassTypes
) is defined in module BotClass/ClassTypes.hs
.
For example when we need to get user from a received message, we can use something like
getUserFromMessage :: (BotClassTypes s) :: Msg s -> User s
. As we go down to
the specific messager, we can add an instance of this type family for it. For instance,
Msg 'Vkontakte = VkMessage, User 'Vkontakte = VkUser, so we finally get something like
getUserFromMessage :: VkMessage -> VkUser
.
For messager specific functions there are some typeclasses:
- BotClassConfigurable for messager specific coniguration (see
Config.hs
); - BotClassUtility for utility functions (see
BotClass/Class.hs
); - BotClass for main functions (the same).
The latter class is responsible for the logic that is not common for both
messagers. Especially it is true for the functions processMessage
and
epilogue
, since their implementations are completely different for Vk and Telegram.
The first one has a type of
processMessage :: (Monad m) => Handle s m -> s -> Msg s -> m (Maybe (m H.HTTPRequest))
.
For a given message this function tries to build a computation within a given monad,
that results in a HTTP request, used to send back the received message.
The possibility of returning Nothing
is added for some cases when there is no
data to send immediately. The specific case is processing Telegram mediagroups,
when bot has to aggregate some messages and send them back as a one.
The epilogue
function is responsible for post-processing actions:
epilogue :: (Monad m) => Handle s m -> s -> [Upd s] -> RepSucc s -> m ()
.
For example, it can send mediagroups.
As it can be seen above, some functions can take a parameter of type Handle s m
.
It serves two purposes. Firstly, it is used to carry some environment, i.e.
it contains help message, default number of repeats and so on. This is similar
to the Reader monad.
Its second purpose is to provide some effectful functions that use internal
state of the bot. For example, for both messagers it provides functions
that are working with (User, Repeat number) map (findUser, insertUser and so on).
The definition of handle can be found in App/Handle.hs
module, Handle s m
.
s
parameter refers to a specific messager, m
corresponds to a monad to execute
computations within. It also has a field named specH
that keeps messager specific effectful functions.
The specific instances of it can be found in App/Handle/Vkontakte.hs
and App/Handle/Telegram.hs
modules.
The Telegram handle provides some functions working with updateID and mediaGroups, the Vk one -
with Vk timestamps and random ID's.
Logging is also implemented as a handle, that is passed to all other functions.
Its type is Priority -> Text -> IO ()
. For initial steps stderr
logger is used, and for the other part of the application the self-sufficient logger
is used. If any exception is raised inside it, it is handled inside it too.
If it becomes impossible to continue logging to file, the logger starts using
stderr handle.
- GenericPretty.hs is used for pretty logging;
- Config.hs is used for reading the configuration files;
- Stuff.hs includes some useful functions that have no application-logic sense only by themselves.
First of all, you need to clone the repository with the bot sources and build it using stack.
Usually it is enough to just run stack build
, all the libraries will be compiled automatically.
Eventually building can crash because of some external libraries that are not installed on your machine.
If you are using Linux, you can just install them yourself. For example, if you are using Ubuntu and
the pq library is missing, just install libpq-dev
package using # apt install libpq-dev
. Generally, for most cases if (libname) is
missing, the package lib(libname)-dev is what you need. There are some exceptions for this rule, for example, zlib.
See https://core.telegram.org/bots#6-botfather
for Telegram, https://vk.com/dev/access_token
for Vk.
Here the only thing to do is to get correct configuration file. Its format is discussed below.
There are some pure logic tests done. They can be found in the test/
directory.
An example of configuration file follows:
general {
help_message = "Hello. Available commands:\n-- /help - get help\n-- /set - change current number of messages repeats"
repeat_question = "How many times would you like to repeat every reply?"
default_repeat_num = 1
timeout = 20
help_command = "/help"
set_rep_num_command = "/set"
}
telegram {
initial_update_id = 0
bot_url = "telegram_token"
}
vkontakte {
bot_url = "https://api.vk.com/method/"
access_token = "vk_access_token"
group_id = 666666
api_version = "5.124"
}
For correct work just replace values with correct ones. If you need only telegram bot, you can omit Vk configuration, and vice versa.
The first command line options always should be a path to the configuration file. The order of all subsequent parameters is arbitrary.
--test-config
is used to test bot configuration. Data from config file will be get and then process will terminate with success exitcode.--tl
or--telegram
is used to indicate that Telegram bot will be used,--vk
- for Vk bot. One of these arguments is required, otherwise application will terminate with error.-l
is used to define logging settings. Currently logger can only log messages with given priority or higher. For example, to log Warning, Error and Fatal messages use-l Warning
. The default is-l Debug
, which logs all messages.--logpath <path>
is used to define the path to the log file.-h
or--help
for help message.