{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Insanus Mokrassar libraries home","text":"
Hello :) It is my libraries docs place and I glad to welcome you here. I hope, this documentation place will help you.
"},{"location":"index.html#projects","title":"Projects","text":"Common and independent TelegramBotAPI Plagubot"},{"location":"index.html#dependencies-graph","title":"Dependencies graph:","text":"flowchart RL\n KSLog[<a href='https://github.com/InsanusMokrassar/kslog'>KSLog</a>]\n MicroUtils[<a href='https://github.com/InsanusMokrassar/MicroUtils'>MicroUtils</a>]\n TelegramBotAPI[<a href='https://github.com/InsanusMokrassar/ktgbotapi'>TelegramBotAPI</a>]\n TelegramBotAPI-examples[<a href='https://github.com/InsanusMokrassar/TelegramBotAPI-examples'>TelegramBotAPI-examples </a>]\n PlaguBot[<a href='https://github.com/InsanusMokrassar/PlaguBot'>PlaguBot</a>]\n TelegramBotAPILibraries[<a href='https://github.com/InsanusMokrassar/TelegramBotAPILibraries'>TelegramBotAPILibraries</a>]\n PlaguBotPlugins[<a href='https://github.com/InsanusMokrassar/PlaguBotPlugins'>PlaguBotPlugins</a>]\n PlaguBotExample[<a href='https://github.com/InsanusMokrassar/PlaguBotExample'>PlaguBotExample</a>]\n BooruGrabberTelegramBot[<a href='https://github.com/InsanusMokrassar/BooruGrabberTelegramBot'>BooruGrabberTelegramBot</a>]\n SauceNaoTelegramBot[<a href='https://github.com/InsanusMokrassar/SauceNaoTelegramBot'>SauceNaoTelegramBot</a>]\n PlaguPoster[<a href='https://github.com/InsanusMokrassar/PlaguPoster'>PlaguPoster</a>]\n PlaguBotSuggestionsBot[<a href='https://github.com/InsanusMokrassar/PlaguBotSuggestionsBot'>PlaguBotSuggestionsBot</a>]\n TelegramBotTutorial[<a href='https://github.com/InsanusMokrassar/TelegramBotTutorial'>TelegramBotTutorial</a>]\n Krontab[<a href='https://github.com/InsanusMokrassar/krontab'>Krontab</a>]\n KJSUiKit[<a href='https://github.com/InsanusMokrassar/JSUiKitKBindings'>KJSUiKit</a>]\n SauceNaoAPI[<a href='https://github.com/InsanusMokrassar/SauceNaoAPI'>SauceNaoAPI</a>]\n Navigation[<a href='https://github.com/InsanusMokrassar/navigation'>Navigation</a>]\n\n TelegramBotAPI-bot_template[<a href='https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template'>TelegramBotAPI-bot_template</a>]\n PlaguBotPluginTemplate[<a href='https://github.com/InsanusMokrassar/PlaguBotPluginTemplate'>PlaguBotPluginTemplate</a>]\n PlaguBotBotTemplate[<a href='https://github.com/InsanusMokrassar/PlaguBotBotTemplate'>PlaguBotBotTemplate</a>]\n\n MicroUtils --> KSLog\n TelegramBotAPI --> MicroUtils\n TelegramBotAPI-examples --> TelegramBotAPI\n PlaguBot --> TelegramBotAPI\n TelegramBotAPILibraries --> PlaguBot\n PlaguBotPlugins --> TelegramBotAPILibraries\n PlaguBotExample --> PlaguBotPlugins\n BooruGrabberTelegramBot --> TelegramBotAPI\n BooruGrabberTelegramBot --> Krontab\n SauceNaoTelegramBot --> TelegramBotAPI\n SauceNaoTelegramBot --> SauceNaoAPI\n TelegramBotTutorial --> PlaguBotPlugins\n PlaguBotSuggestionsBot --> PlaguBotPlugins\n PlaguPoster --> PlaguBotPlugins\n PlaguPoster --> Krontab\n SauceNaoAPI --> MicroUtils\n Navigation --> MicroUtils\n\n TelegramBotAPI-bot_template -.- TelegramBotAPI\n PlaguBotPluginTemplate -.- PlaguBot\n PlaguBotBotTemplate -.- PlaguBot
"},{"location":"krontab/describing/krontabscheduler.html","title":"KrontabScheduler","text":"KronScheduler
is the simple interface with only one function next
. This function optionally get as a parameter DateTime
which will be used as start point for the calculation of next trigger time. This function will return the next DateTime
when something must happen.
Default realisation (CronDateTimeScheduler
) can be created using several ways:
buildSchedule
(or createSimpleScheduler
) functions with crontab-like syntax parameterbuildSchedule
(or SchedulerBuilder
object), which using lambda to configure schedulerIn the examples below the result of created scheduler will be the same.
"},{"location":"krontab/describing/krontabscheduler.html#crontab-like-way","title":"Crontab-like way","text":"Crontab-like syntax
See String format for more info about the crontab-line syntax
This way will be very useful for cases when you need to configure something via external configuration (from file on startup or via some parameter from requests, for example):
val schedule = \"5 * * * *\"\nval scheduler = buildSchedule(schedule)\nscheduler.asFlow().onEach {\n// this block will be called every minute at 5 seconds\n}.launchIn(someCoroutineScope)\n
"},{"location":"krontab/describing/krontabscheduler.html#lambda-way","title":"Lambda way","text":"In case of usage builder (lets call it lambda way
), you will be able to configure scheduler in more type-safe way:
val scheduler = buildSchedule {\nseconds {\nat(5)\n}\n}\nscheduler.asFlow().onEach {\n// this block will be called every minute at 5 seconds\n}.launchIn(someCoroutineScope)\n
"},{"location":"krontab/describing/krontabscheduler.html#custom-scheduler","title":"Custom scheduler","text":"You are always able to use your own realisation of scheduler. For example:
class RandomScheduler : KronScheduler {\noverride suspend fun next(relatively: DateTime): DateTime {\nreturn relatively + DateTimeSpan(seconds = Random.nextInt() % 60)\n}\n}\n
In the example above we have created RandomScheduler
, which will return random next time in range 0-60
seconds since relatively
argument.
As in crontab
util, this library have almost the same format of string:
Int
Any Int
0..6 0..999 Suffix - - - - - - o
w
ms
Optional \u274c \u274c \u274c \u274c \u274c \u2705 \u2705 \u2705 \u2705 Full syntax support \u2705 \u2705 \u2705 \u2705 \u2705 \u2705 \u274c \u2705 \u2705 Position 0 1 2 3 4 Any after months Any after months Any after months Any after months Examples 0
, */15
, 30
0
, */15
, 30
0
, */15
, 22
0
, */15
, 30
0
, */5
, 11
0
, */15
, 30
60o
(UTC+1) 0w
, */2w
, 4w
0ms
, */150ms
, 300ms
Example with almost same description:
/-------------------- (0-59) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Seconds\n| /------------------ (0-59) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Minutes\n| | /---------------- (0-23) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Hours\n| | | /-------------- (0-30) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Days of months\n| | | | /------------ (0-11) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Months\n| | | | | /---------- (optional, any int) Year\n| | | | | | /-------- (optional) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Timezone offset\n| | | | | | | /----- (optional, 0-6) \u00b7\u00b7\u00b7 Week days\n| | | | | | | | /-- (optional, 0-999) \u00b7 Milliseconds (0 by default)\n* * * * * * 0o *w 0ms\n
Years, timezone, week days and milliseconds are optional settings. Next snippets are equal:
*/15 * * * *\n*/15 * * * * * // with year\n*/15 * * * * * 0ms // with year and milliseconds\n
"},{"location":"krontab/describing/string-format.html#supported-syntax","title":"Supported syntax","text":"Currently the library support next syntax for date/time elements:
{int}-{int}
- ranges{int}/{int}
- start/step*/{int}
- every {int}{int}
- just at the time{other_element},{other_element}
- listingF
or f
- first possible valueL
or l
- last possible value (last day of month, for example)Ranges are working like common rangeTo
(or ..
) in kotlin:
0-5 * * * *\n
In the example above scheduler will trigger every second from the beginning of the minute up to fifth second of minute.
"},{"location":"krontab/describing/string-format.html#startstep","title":"Start/Step","text":"Start/step is a little bit more complicated syntax. It means start from the first element, repeat triggering every second element
. Examples:
5/15 * * * *\n
Means that each minute starting from fifth second it will repeat triggering every fifteenth second: 5, 20, 35, 50
.
Every is more simple syntax and could be explained as a shortcut for 0/{int}
. Example:
*/15 * * * *\n
Means that each minute it will repeat triggering every fifteenth second: 0, 15, 30, 45
.
The most simple syntax. It means, that scheduler will call triggering every time when element was reached:
15 * * * *\n
Means that each minute scheduler will call triggering at the fifteenth second.
"},{"location":"krontab/describing/string-format.html#listing","title":"Listing","text":"All the previous elements can be combined with listing. Lets just see several examples:
0,10 * * * *\n
Will trigger every minute at the 0
and 10
seconds (see Just at the time)
0-5,10 * * * *\n
Will trigger every minute from 0
to 5
seconds and at the 10
seconds (see Ranges)
0/5 * * * *
for every five seconds triggering0/5,L * * * *
for every five seconds triggering and on 59 second0/15 30 * * *
for every 15th seconds in a half of each hour0/15 30 * * * 500ms
for every 15th seconds in a half of each hour when milliseconds equal to 5001 2 3 F,4,L 5
for triggering in near first second of second minute of third hour of first, fifth and last days of may1 2 3 F,4,L 5 60o
for triggering in near first second of second minute of third hour of first, fifth and last days of may with timezone UTC+01:001 2 3 F,4,L 5 60o 0-2w
for triggering in near first second of second minute of third hour of first, fifth and last days of may in case if it will be in Sunday-Tuesday week days with timezone UTC+01:001 2 3 F,4,L 5 2021
for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year1 2 3 F,4,L 5 2021 60o
for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year with timezone UTC+01:001 2 3 F,4,L 5 2021 60o 0-2w
for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:001 2 3 F,4,L 5 2021 60o 0-2w 500ms
for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00 when milliseconds will be equal to 500Not very often. It depend on libraries (coroutines, korlibs/klock) updates and on some new awesome, but lightweight, features coming.
"},{"location":"krontab/introduction/faq.html#where-this-library-could-be-useful","title":"Where this library could be useful?","text":"First of all, this library will be useful for long uptime applications which have some tasks to do from time to time.
"},{"location":"krontab/introduction/faq.html#how-to-use-crontab-like-syntax","title":"How to use crontab-like syntax?","text":"In two words, you should call buildSchedule
or createSimpleScheduler
:
buildSchedule(\"5 * * * *\").asFlow().collect { /* do something */ }\n
You can read more about syntax in String format section.
"},{"location":"krontab/introduction/how-to-use.html","title":"How to use","text":""},{"location":"krontab/introduction/how-to-use.html#previous-pages","title":"Previous pages","text":"buildSchedule
","text":"Custom KronScheduler
You always able to create your own scheduler. In this section will be presented different ways and examples around standard CronDateTimeScheduler
builders buildSchedule
. You can read about schedulers in KrontabScheduler
Currently, buildSchedule
is the recommended start point for every scheduler. Usually, it is look like:
val scheduler = buildSchedule(\"5 * * * *\")\n
Or:
val scheduler = buildSchedule {\nseconds {\nat(5)\n}\n}\n
On the top of any KronScheduler
currently there are several groups of extensions:
All executes are look like do...
. All executes are described below:
doOnce
- will get the next time for executing, delay until that time and call block
with returning of the block
resultdoWhile
- will call doOnce
while it will return true
(that means that block
must return true
if it expects that next call must happen). In two words: it will run while block
returning true
doInfinity
- will call the block
using doWhile
with predefined returning true
. In two words: it will call block
while it do not throw errorShortcuts are the constants that are initializing in a lazy way to provide preset KronScheduler
s. For more info about KrontabScheduler
you can read its own page.
AnyTimeScheduler
- will always return incoming DateTime
as nextEvery*Scheduler
- return near * since the passed relatively
:EverySecondScheduler
EveryMinuteScheduler
EveryHourScheduler
EveryDayOfMonthScheduler
EveryMonthScheduler
EveryYearScheduler
Here currently there is only one extension for KronScheduler
: KronScheduler#asFlow
. As a result you will get Flow<DateTime>
(in fact SchedulerFlow
) which will trigger next emit
on each not null next
DateTime
In two words, you must add dependency dev.inmo:krontab:$krontab_version
to your project. The latest version presented by next badge:
To use this library, you will need to include MavenCentral
repository in you project
mavenCentral()\n
"},{"location":"krontab/introduction/including-in-project.html#dependencies","title":"Dependencies","text":"Next snippets must be placed into your dependencies
part of build.gradle
(for gradle) or pom.xml
(for maven).
implementation \"dev.inmo:krontab:$krontab_version\"\n
"},{"location":"krontab/introduction/including-in-project.html#maven","title":"Maven","text":"<dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>krontab</artifactId>\n<version>${krontab_version}</version>\n</dependency>\n
"},{"location":"kslog/index.html","title":"KSLog","text":"KSLog is a simple multiplatform tool for native logging.
"},{"location":"kslog/logging.html","title":"Logging","text":"Message type notice
On this page all the messages will be just simple String
, but you may pass any object as the message
As has been said in the setup section, this library contains next levels of logging with their default representations on each platform:
Weight (by order) LogLevel name JS JVM Loggers Android 0 DEBUG console.log Level.FINEST Log.d 1 VERBOSE console.info Level.FINE Log.v 2 INFO console.info Level.INFO Log.i 3 WARNING console.warn Level.WARNING Log.w 4 ERROR console.error Level.SEVERE Log.e 5 ASSERT console.error Level.SEVERE Log.wtfEach of these levels have fullname and shortname shortcat extensions:
KSLog.debug
/KSLog.d
/KSLog.dS
KSLog.verbose
/KSLog.v
/KSLog.vS
KSLog.info
/KSLog.i
/KSLog.iS
KSLog.warning
/KSLog.w
/KSLog.wS
KSLog.error
/KSLog.e
/KSLog.eS
KSLog.assert
/KSLog.wtf
/KSLog.wtfS
And any of these shortcuts may accept one of several arguments combinations:
S
suffix for suspendable
messages creating, for exampleSo, when you want to log some expected exception, there are three common ways to do it:
val logger = KSLog.default\n// with callback\nlogger.info(tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.infoS(tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.info(\"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.info(tag, \"Some your message for this event\", throwable)\n
Of course, any of this calls can be shortenned:
val logger = KSLog.default\n// with callback\nlogger.i(tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.iS(tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.i(\"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.i(tag, \"Some your message for this event\", throwable)\n
There is special shortcat - for base performLog
. In that case the only change is that you will require to pass the LogLevel
more obviously:
val logger = KSLog.default\n// with callback\nlogger.log(LogLevel.INFO, tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.logS(LogLevel.INFO, tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.log(LogLevel.INFO, \"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.log(LogLevel.INFO, tag, \"Some your message for this event\", throwable)\n
OR
val logger = KSLog.default\n// with callback\nlogger.l(LogLevel.INFO, tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.lS(LogLevel.INFO, tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.l(LogLevel.INFO, \"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.l(LogLevel.INFO, tag, \"Some your message for this event\", throwable)\n
"},{"location":"kslog/setup.html","title":"Setup","text":""},{"location":"kslog/setup.html#dependency-installation","title":"Dependency installation","text":""},{"location":"kslog/setup.html#gradle-groovy","title":"Gradle (Groovy)","text":"implementation \"dev.inmo:kslog:$kslog_version\"\n
"},{"location":"kslog/setup.html#gradle-kotlin-script","title":"Gradle (Kotlin Script)","text":"implementation(\"dev.inmo:kslog:$kslog_version\")\n
"},{"location":"kslog/setup.html#maven-pom","title":"Maven (pom)","text":"<dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>kslog</artifactId>\n<version>${kslog_version}</version>\n</dependency>\n
"},{"location":"kslog/setup.html#setup-in-code","title":"Setup in code","text":"The main point in setup in your code is to setup default logger:
KSLog.default = KSLog(\"defaultTag\")\n
You may use custom messageFormatter
in any of KSLog
factory to customize output of KSLog
logging. For example:
KSLog(\n\"loggingWithCustomFormat\",\nmessageFormatter = { level, tag, message, throwable ->\nprintln(\"[$level] $tag - $message: $throwable\")\n}\n)\n
Additionally you may use one of several different settings:
minLoggingLevel
- minimal logging level for the log which will be logged. The order of log level is next:levels
- and iterable with the levels which should be loggedfirstLevel
,secondLevel
,otherLevels
- as levels
, but vararg
:)In case you are passing minLoggingLevel
, the level and more important levels will be passed to logs. For example, when you are settings up your logger as in next snippet:
val logger = KSLog(\n\"exampleTag\",\nminLoggingLevel = LogLevel.INFO\n)\n
The next levels will be logged with logger
:
INFO
WARNING
ERROR
ASSERT
It is logger which will call incoming performLogCallback
on each logging. This logger can be create simply with one callback:
KSLog { level, tag, message, throwable ->\nprintln(\"[$level] $tag - $message: $throwable\")\n}\n
"},{"location":"kslog/setup.html#taglogger","title":"TagLogger","text":"It is simple value class which can be used for zero-cost usage of some tag and calling for KSLog.default
. For example, if you will create tag logger with next code:
val logger = TagLogger(\"tagLoggerTag\")\n
The logger
will call KSLog.default
with the tag tagLoggerTag
on each calling of logging.
This pretty simple logger will call its fallbackLogger
only in cases when incoming messageFilter
will return true for logging:
val baseLogger = KSLog(\"base\") // log everything with the tag `base` if not set other\nval filtered = baseLogger.filtered { _, t, _ ->\nt == \"base\"\n}\n
In the example above baseLogger
will perform logs in two ways: when it has been called directly or when we call log performing with the tag \"base\"
or null
. Besides, you can see there extension filtered
which allow to create FilterKSLog
logger with simple lambda.
This logger accepts map of types with the target loggers. You may build this logger with the special simple DSL:
val baseLogger = KSLog(\"base\") // log everything with the tag `base` if not set other\nval typed = buildTypedLogger {\non<Int>(baseLogger) // log all ints to the baseLogger\non<Float> { _, _, message, _ ->// log all floats to the passed logger\nprintln(message.toString()) // just print all floats\n}\ndefault { level, tag, message, throwable ->\nKSLog.performLog(level, tag, message, throwable)\n}\n}\n
"},{"location":"kslog/setup.html#automatical-loggers","title":"Automatical loggers","text":"There are two things which can be useful in your code: logger
and logTag
extensions. logTag
is the autocalculated by your object classname tag. logger
extension can be used with applying to any object like in the next snippet:
class SomeClass {\ninit {\nlogger.i(\"inited\")\n}\n}\n
The code above will trigger calling of logging in KSLog.default
with level LogLevel.INFO
using tag SomeClass
and message \"inited\"
. As you could have guessed, logger
is using TagLogger
with logTag
underhood and the most expensive operation here is automatical calculation of logTag
.
logger
For JVM you may setup additionally use java loggers as the second parameter of KSLog
factory. For example:
KSLog(\n\"yourTag\"\nLogger.getLogger(\"YourJavaLoggerName\")\n)\n
"},{"location":"tgbotapi/index.html","title":"TelegramBotAPI","text":"Hello! This is a set of libraries for working with Telegram Bot API.
"},{"location":"tgbotapi/index.html#examples","title":"Examples","text":"There are several things you need to do to launch examples below:
mavenCentral()
to your project repositoriesimplementation \"dev.inmo:tgbotapi:$tgbotapi_version\"
tgbotapi_version
with exact version (see last one in the table above) or put variable with this name in projectMore including instructions available here. Other configuration examples:
suspend fun main() {\nval bot = telegramBot(TOKEN)\nbot.buildBehaviourWithLongPolling {\nprintln(getMe())\nonCommand(\"start\") {\nreply(it, \"Hi:)\")\n}\n}.join()\n}\n
In this example you will see information about this bot at the moment of starting and answer with Hi:)
every time it gets message /start
suspend fun main() {\nval bot = telegramBot(TOKEN)\nval flowsUpdatesFilter = FlowsUpdatesFilter()\nbot.buildBehaviour(flowUpdatesFilter = flowsUpdatesFilter) {\nprintln(getMe())\nonCommand(\"start\") {\nreply(it, \"Hi:)\")\n}\nretrieveAccumulatedUpdates(this).join()\n}\n}\n
The main difference with the previous example is that bot will get only last updates (accumulated before bot launch and maybe some updates it got after launch)
"},{"location":"tgbotapi/index.html#build-a-little-bit-more-complex-behaviour","title":"Build a little bit more complex behaviour","text":"suspend fun main() {\nval bot = telegramBot(TOKEN)\nbot.buildBehaviourWithLongPolling {\nprintln(getMe())\nval nameReplyMarkup = ReplyKeyboardMarkup(\nmatrix {\nrow {\n+SimpleKeyboardButton(\"nope\")\n}\n}\n)\nonCommand(\"start\") {\nval photo = waitPhoto(\nSendTextMessage(it.chat.id, \"Send me your photo please\")\n).first()\nval name = waitText(\nSendTextMessage(\nit.chat.id,\n\"Send me your name or choose \\\"nope\\\"\",\nreplyMarkup = nameReplyMarkup\n)\n).first().text.takeIf { it != \"nope\" }\nsendPhoto(\nit.chat,\nphoto.mediaCollection,\nentities = buildEntities {\nif (name != null) regular(name) // may be collapsed up to name ?.let(::regular)\n}\n)\n}\n}.join()\n}\n
"},{"location":"tgbotapi/index.html#more-examples","title":"More examples","text":"You may find examples in this project. Besides, you are always welcome in our chat.
"},{"location":"tgbotapi/dsls/keyboards.html","title":"Keyboards","text":"In the telegram system there are two types of keyboards:
Reply Inline Keyboard for each user in the chat Keyboard linked to the certain messageLow-level way to create keyboard looks like in the next snippet:
ReplyKeyboardMarkup(\nmatrix {\nrow {\nadd(SimpleKeyboardButton(\"Simple text\"))\n// ...\n}\n// ...\n}\n)\n
In case you wish to create inline keyboard, it will look like the same as for reply keyboard. But there is another way. The next snippet will create the same keyboard as on the screenshots above:
// reply keyboard\nreplyKeyboard {\nrow {\nsimpleButton(\"7\")\nsimpleButton(\"8\")\nsimpleButton(\"9\")\nsimpleButton(\"*\")\n}\nrow {\nsimpleButton(\"4\")\nsimpleButton(\"5\")\nsimpleButton(\"6\")\nsimpleButton(\"/\")\n}\nrow {\nsimpleButton(\"1\")\nsimpleButton(\"2\")\nsimpleButton(\"3\")\nsimpleButton(\"-\")\n}\nrow {\nsimpleButton(\"0\")\nsimpleButton(\".\")\nsimpleButton(\"=\")\nsimpleButton(\"+\")\n}\n}\n// inline keyboard\ninlineKeyboard {\nrow {\ndataButton(\"Get random music\", \"random\")\n}\nrow {\nurlButton(\"Send music to friends\", \"https://some.link\")\n}\n}\n
"},{"location":"tgbotapi/dsls/live-location.html","title":"Live Location","text":"Bot API allows you to send live locations and update them during their lifetime. In this library there are several ways to use this API:
In the Bot API there is no independent sendLiveLocation
method, instead it is suggested to use sendLocation with setting up live_period
. In this library in difference with original Bot API live location is special request. It was required because of in fact live locations and static locations are different types of location info and you as bot developer may interact with them differently.
Anyway, in common case the logic looks like:
In difference with sendLiveLocation, startLiveLocation using LiveLocationProvider. With this provider you need not to handle chat and message ids and keep some other data for location changes. Instead, you workflow with provider will be next:
Besides, LiveLocationProvider
contains different useful parameters about live location
This way of live locations handling is based on coroutines Flow and allow you to pass some external Flow
with EditLiveLocationInfo. So, workflow:
flow {\nvar i = 0\nwhile (isActive) {\nval newInfo = EditLiveLocationInfo(\nlatitude = i.toDouble(),\nlongitude = i.toDouble(),\nreplyMarkup = flatInlineKeyboard {\ndataButton(\"Cancel\", \"cancel\")\n}\n)\nemit(newInfo)\ni++\ndelay(10000L) // 10 seconds\n}\n}\n
val currentMessageState = MutableStateFlow<ContentMessage<LocationContent>?>(null)\n
handleLiveLocation(\nit.chat.id,\nlocationsFlow,\nsentMessageFlow = FlowCollector { currentMessageState.emit(it) }\n)\n// this code will be called after `locationsFlow` will ends\n
OR scope.launch {\nhandleLiveLocation(\nit.chat.id,\nlocationsFlow,\nsentMessageFlow = FlowCollector { currentMessageState.emit(it) }\n)\n}\n// this code will be called right after launch will be completed\n
See our example to get more detailed sample
"},{"location":"tgbotapi/dsls/text.html","title":"Text","text":"For the text creating there are several tools. The most simple one is to concatenate several text sources to make list of text sources as a result:
val sources = \"Regular start of text \" + bold(\"with bold part\") + italic(\"and italic ending\")\n
But there is a little bit more useful way: entities builder:
val items = (0 until 10).map { it.toString() }\nbuildEntities(\" \") {// optional \" \" auto separator which will be pasted between text sources\n+\"It is regular start too\" + bold(\"it is bold as well\")\nitems.forEachIndexed { i, item ->\nif (i % 2) {\nitalic(item)\n} else {\nstrikethrough(item)\n}\n}\n}\n
In the code above we are creating an items list just for demonstrating that inside of buildEntities body we may use any operations for cunstructing our result list of TextSource
s. As a result, will be created the list which will looks like in telegram as \u201cIt is regular start too it is bold as well 0 ~~1~~ 2 ~~3~~ 4 ~~5~~ 6 ~~7~~ 8 ~~9~~\u201d.
There are several places you need to visit for starting work with any Telegram Bot framework on any language:
Anyway, the most important link is How do I create a bot? inside of Telegram Bot API
"},{"location":"tgbotapi/introduction/before-any-bot-project.html#next-steps","title":"Next steps","text":"Examples info
A lot of examples with using of Telegram Bot API you can find in this github repository
"},{"location":"tgbotapi/introduction/first-bot.html#the-most-simple-bot","title":"The most simple bot","text":"The most simple bot will just print information about itself. All source code you can find in this repository. Our interest here will be concentrated on the next example part:
suspend fun main(vararg args: String) {\nval botToken = args.first()\nval bot = telegramBot(botToken)\nprintln(bot.getMe())\n}\n
So, let\u2019s get understanding, about what is going on:
suspend fun main(vararg args: String)
:suspend
required for making of requests inside of this function. For more info you can open official documentation for coroutins. In fact, suspend fun main
is the same that fun main() = runBlocking {}
from examplesval botToken = args.first()
: here we are just getting the bot token from first arguments of command lineval bot = telegramBot(botToken)
: inside of bot
will be RequestsExecutor object which will be used for all requests in any project with this libraryprintln(bot.getMe())
: here happens calling of getMe extensionAs a result, we will see in the command line something like
ExtendedBot(id=ChatId(chatId=123456789), username=Username(username=@first_test_ee17e8_bot), firstName=Your bot name, lastName=, canJoinGroups=false, canReadAllGroupMessages=false, supportsInlineQueries=false)\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html","title":"Including in your project","text":"There are three projects:
TelegramBotAPI Core
- project with base for all working with Telegram Bot APITelegramBotAPI API Extensions
- extension of TelegramBotAPI
with functions for more comfortable work with Telegram Bot APITelegramBotAPI Utils Extensions
- extension of TelegramBotAPI
with functions for extending of different things like retrieving of updatesTelegramBotAPI
Also, there is an aggregator-version tgbotapi
, which will automatically include all projects above. It is most recommended version due to the fact that it is including all necessary tools around TelegramBotAPI Core
, but it is optionally due to the possible restrictions on the result methods count (for android) or bundle size
Examples
You can find full examples info in this repository. In this repository there full codes which are working in normal situation. Currently, there is only one exception when these examples could work incorrectly: you are living in the location where Telegram Bot API is unavailable. For solving this problem you can read Proxy setup part
"},{"location":"tgbotapi/introduction/including-in-your-project.html#notice-about-repository","title":"Notice about repository","text":"To use this library, you will need to include Maven Central
repository in your project
mavenCentral()\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml","title":"pom.xml","text":"<repository>\n<id>central</id>\n<name>mavenCentral</name>\n<url>https://repo1.maven.org/maven2</url>\n</repository>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#dev-channel","title":"Dev channel","text":"Besides, there is developer versions repo. To use it in your project, add the repo in repositories
section:
maven {\nurl \"https://git.inmo.dev/api/packages/InsanusMokrassar/maven\"\n}\n
Maven <repository>\n<id>dev.inmo</id>\n<name>InmoDev</name>\n<url>https://git.inmo.dev/api/packages/InsanusMokrassar/maven</url>\n</repository>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#telegrambotapi","title":"TelegramBotAPI","text":"As tgbotapi_version
variable in next snippets will be used variable with next last published version:
implementation \"dev.inmo:tgbotapi:$tgbotapi_version\"\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml_1","title":"pom.xml","text":"<dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>tgbotapi</artifactId>\n<version>${tgbotapi_version}</version>\n</dependency>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#telegrambotapi-core","title":"TelegramBotAPI Core","text":"As tgbotapi_version
variable in next snippets will be used variable with next last published version:
implementation \"dev.inmo:tgbotapi.core:$tgbotapi_version\"\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml_2","title":"pom.xml","text":"<dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>tgbotapi.core</artifactId>\n<version>${tgbotapi_version}</version>\n</dependency>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#telegrambotapi-api-extensions","title":"TelegramBotAPI API Extensions","text":"As tgbotapi_version
variable in next snippets will be used variable with next last published version:
implementation \"dev.inmo:tgbotapi.api:$tgbotapi_version\"\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml_3","title":"pom.xml","text":"<dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>tgbotapi.api</artifactId>\n<version>${tgbotapi_version}</version>\n</dependency>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#telegrambotapi-utils-extensions","title":"TelegramBotAPI Utils Extensions","text":"As tgbotapi_version
variable in next snippets will be used variable with next last published version:
implementation \"dev.inmo:tgbotapi.utils:$tgbotapi_version\"\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml_4","title":"pom.xml","text":"<dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>tgbotapi.utils</artifactId>\n<version>${tgbotapi_version}</version>\n</dependency>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#next-steps","title":"Next steps","text":"In some locations Telegram Bots API urls will be unavailable. In this case all examples will just throw exception like:
Exception in thread \"main\" java.net.ConnectException: Connection refused\n at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)\nat sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)\nat io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:36)\nat io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)\nat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)\nat kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)\nat kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)\nProcess finished with exit code 1\n
There are several ways to solve this problem:
First of all, you will need to use one more library:
build.gradle:
implementation \"io.ktor:ktor-client-okhttp:2.0.1\"\n
Dependency note
In the snippet above was used version 2.0.1
which is actual for TelegramBotAPI
at the moment of filling this documentation (May 22 2022
, TelegramBotAPI
version 2.0.0
) and you can update version of this dependency in case if it is outdated.
For configuring proxy for your bot inside your program, you can use next snippet:
val botToken = \"HERE MUST BE YOUR TOKEN\"\nval bot = telegramBot(botToken) {\nktorClientEngineFactory = OkHttp\nproxy = ProxyBuilder.socks(\"127.0.0.1\", 1080)\n}\n
Explanation line by line:
val botToken = \"HERE MUST BE YOUR TOKEN\"
- here we are just creating variable botToken
val bot = telegramBot(botToken) {
- start creating botktorClientEngineFactory = OkHttp
- setting up engine factory of our bot. On the time of documentation filling, OkHttp
is one of the engines in Ktor
system which supports socks proxy. More you can read on Ktor site in subparts about engines and proxyproxy = ProxyBuilder.socks(\"127.0.0.1\", 1080)
- here we are setting up our proxy. Here was used local server which (as assumed) will connect to server like shadowsocks
API extensions is a module which you may include in your project in addition to core part. In most cases this module will allow just use syntax like bot.getUpdates()
instead of bot.execute(GetUpdates())
, but there are several other things you will achieve with that syntax.
This functionality allow you to build bot in more unified and comfortable way than standard creating with telegramBot
function
buildBot(\n\"TOKEN\"\n) {\nproxy = ProxyBuilder.socks(host = \"127.0.0.1\", port = 4001) // just an example, more info on https://ktor.io/docs/proxy.html\nktorClientConfig = {\n// configuring of ktor client\n}\nktorClientEngineFactory = {\n// configuring of ktor client engine \n}\n}\n
"},{"location":"tgbotapi/logic/api-extensions.html#downloading-of-files","title":"Downloading of files","text":"In standard library requests there are no way to download some file retrieved in updates or after requests. You may use syntax like bot.downloadFile(file)
where file
is TelegramMediaFile
from telegram, FileId
or even PathedFile
from GetFile request (sources).
By default, you should handle updates of Live location by your code. But with extension bot#startLiveLocation you may provide all necessary startup parameters and handle updates with just calling updateLocation
for retrieved LiveLocationProvider.
There are several things you may read next:
Behaviour builder with FSM is based on the MicroUtils FSM. There are several important things in FSM:
State
- any object which implements State interfaceStateHandler
(or CheckableHandlerHolder) - the handler of statesStatesMachine
have two methods:
start
which will start work of machinestartChain
which will add new state for handlingThe most based way to create StatesMachine
and register StateHandler
s looks like in the next snippet:
buildFSM<TrafficLightState> {\nstrictlyOn<SomeState> {\n// state handling\n}\n}.start(CoroutineScope(...)).join()\n
Full example
You may find full example of FSM usage in the tests of FSM in MicroUtils
So, you must do next steps before you will launch your bot with FSM:
There are several extensions for TelegramBot
to create your bot with FSM:
All of them will take as an callback some object with type CustomBehaviourContextReceiver and will looks like in the next snippet:
telegramBotWithBehaviourAndFSMAndStartLongPolling<YourStateType>(\"BOT_TOKEN\") {\n// here you may use any operations from BehaviourBuilder\n// here you may use any operations from BehaviourContextWithFSMBuilder like strictlyOn and others\n}\n
"},{"location":"tgbotapi/logic/behaviour-builder-with-fsm.html#examples","title":"Examples","text":"In the previous pages about updates handling and was mentioned that currently in the most cases you should use Flows. So, there is an improvement for that system which hide direct work with flows and allow you to create more declarative logic of your bot.
"},{"location":"tgbotapi/logic/behaviour-builder.html#main-parts-of-behaviour-builder","title":"Main parts of Behaviour Builder","text":"There are several things you should know for better understanding of behaviour builder:
on*
extensions for BehaviourContext
which allow you to create reaction on some updatewait*
extensions which you may use in buildBehaviour function, but it is recommended to use it in bodies of triggersAs was said above, there is buildBehaviour function which allow you set up your bot logic. Let\u2019s see an example:
val bot = telegramBot(\"TOKEN\")\nbot.buildBehaviour {\nonCommand(\"start\") { // creating of trigger\nval message = it\nval content = message.content\nreply(message, \"Ok, send me one photo\") // send text message with replying on incoming message\nval photoContent = waitPhoto().first() // waitPhoto will return List, so, get first element\nval photo = downloadFile(photoContent) // ByteArray of photo\n// some logic with saving of photos\n}\n}\n
"},{"location":"tgbotapi/logic/behaviour-builder.html#filters","title":"Filters","text":"In most cases there are opportunity to filter some of messages before starting of main logic. Let\u2019s look at this using the example above:
val bot = telegramBot(\"TOKEN\")\nbot.buildBehaviour {\nonCommand(\n\"start\",\ninitialFilter = {\nit.content.textSources.size == 1 // make sure that user has sent /start without any additions\n}\n) {\n// ...\n}\n}\n
OR
val bot = telegramBot(\"TOKEN\")\nbot.buildBehaviour {\nonCommand(\n\"start\",\nrequireOnlyCommandInMessage = true // it is default, but you can overwrite it with `requireOnlyCommandInMessage = false`\n) {\n// ...\n}\n}\n
"},{"location":"tgbotapi/logic/exceptions-handling.html","title":"Exceptions handling","text":"Unfortunatelly, exceptions handling in this library is a bit difficult in some places, but that have at least two reasons: flexibility and usability.
"},{"location":"tgbotapi/logic/exceptions-handling.html#in-place-handling","title":"\u201cIn place\u201d handling","text":"In case you know, where exceptions are happening, you may use several tools for exceptions catching:
If you prefer to receive Result
objects instead of some weird callbacks, you may use the next syntax:
safelyWithResult {\n// do something\n}.onSuccess { // will be called if everything is right\n// handle success\n}.onFailure { // will be called if something went wrong\n// handle error\nit.printStackTrace()\n}.getOrThrow() // will return value or throw exception\n
"},{"location":"tgbotapi/logic/exceptions-handling.html#catching-with-callback","title":"Catching with callback","text":"Also there is more simple (in some cases) way to handle exceptions with callbacks:
safely(\n{\n// handle error\nit.printStackTrace()\nnull // return value\n}\n) {\n// do something\n}\n
"},{"location":"tgbotapi/logic/exceptions-handling.html#bonus-different-types-of-handling","title":"Bonus: different types of handling","text":"There are two types of handling:
safely(\n{\nit.printStackTrace()\n\"error\"\n}\n) {\nerror(\"Hi :)\") // emulate exception throwing\n\"ok\"\n} // result will be with type String\n
safely
, but this type by default allow to return nullable value (when exception was thrown) instead of just throwing (as with safely
): safelyWithouExceptions {\n// do something\n} // will returns nullable result type\n
The most simple way to configure exceptions handling is to change CoroutineContext
when you are creating your CoroutineScope
for bot processing:
val bot = telegramBot(\"TOKEN\")\nbot.buildBehaviour (\nscope = scope,\ndefaultExceptionsHandler = {\nit.printStackTrace()\n}\n) {\n// ...\n}\n
OR
val bot = telegramBotWithBehaviour (\n\"TOKEN\",\nscope = scope,\ndefaultExceptionsHandler = {\nit.printStackTrace()\n}\n) {\n// ...\n}\n
Here we have used ContextSafelyExceptionHandler
class. It will pass default handling of exceptions and will call the block in most cases when something inside of your bot logic has thrown exception.
According to the documentation there are several ways to work with files:
typealias
for the FileId
)There are several cases you may need in your app to work with files:
FileId
(for sending in future)The most simple way to send some file is to get file id and send it. You may get file id from any message with media. For example, if you have received some Message, you may use asCommonMessage conversation to be able to get its content
and then convert it to some content with media. Full code here:
val message: Message;\nval fileId = message.asCommonMessage() ?.withContent<MediaContent>() ?.content ?.media ?.fileId;\n
WAT? O.o
In the code above we get some message, safely converted it to CommonMessage
with asCommonMessage
, then safely took its content via withContent<MediaContent>() ?.content
and then just get its media file id.
There are three ways to download files:
ByteArray
ByteReadChannelAllocator
which allow to retrieve ByteReadChannel and do whatever you want with it[JVM Only]
Download it directly to file or temporal fileAPI
extensions","text":""},{"location":"tgbotapi/logic/files-handling.html#files-jvmandroid","title":"Files (JVM/Android)","text":"val bot: TelegramBot;\nval fileId: FileId;\nval outputFile: File;\nbot.downloadFile(fileId, outputFile)\n
See downloadFile extension docs in the JVM tab to get more available options
There is also way with saving of data into temporal file. That will allow you to do with data whatever you want without high requirements to memory or network connection:
val bot: TelegramBot;\nval fileId: FileId;\nval tempFile: File = bot.downloadFileToTemp(fileId)\n
See downloadFileToTemp extension docs to get more available options
"},{"location":"tgbotapi/logic/files-handling.html#byte-read-channel","title":"Byte read channel","text":"val bot: TelegramBot;\nval fileId: FileId;\nval bytes: ByteReadChannelAllocator = bot.downloadFileStream(fileId)\n
See downloadFileStream extension docs to get more available options
"},{"location":"tgbotapi/logic/files-handling.html#byte-read-channel-allocator","title":"Byte read channel allocator","text":"val bot: TelegramBot;\nval fileId: FileId;\nval bytes: ByteReadChannelAllocator = bot.downloadFileStreamAllocator(fileId)\n
See downloadFileStreamAllocator extension docs to get more available options
"},{"location":"tgbotapi/logic/files-handling.html#byte-arrays","title":"Byte arrays","text":"val bot: TelegramBot;\nval fileId: FileId;\nval bytes: ByteArray = bot.downloadFile(fileId)\n
See downloadFile extension docs to get more available options
"},{"location":"tgbotapi/logic/files-handling.html#low-level-or-how-does-it-work","title":"Low level orhow does it work?
","text":"You may download file with streams or with downloading into the memory first. On low level you should do several things. They are presented in next snippet:
val bot: TelegramBot;\nval fileId: FileId;\nval pathedFile: PathedFile = bot.execute(GetFile(fileId))\nval downloadedBytes: ByteArray = bot.execute(DownloadFile(pathedFile.filePath))\n
In the snippet above we are getting file PathedFile
by its FileId
and use it to download file bytes into memory using DownloadFile
request.
You may use almost the same way but with byte read channel allocator:
val bot: TelegramBot;\nval fileId: FileId;\nval pathedFile: PathedFile = bot.execute(GetFile(fileId))\nval channelAllocator: ByteReadChannelAllocator = bot.execute(DownloadFileStream(pathedFile.filePath))\nval byteReadChannel: ByteReadChannel = channelAllocator()\n
And then you may look into ByteReadChannel docs to get more info about what you can do with that.
Several useful links
Of course, in most cases you must be sure that file have correct type.
"},{"location":"tgbotapi/logic/files-handling.html#fileid-and-fileurl","title":"FileId and FileUrl","text":"It is the most simple way to send any media in Telegram, but this way have several restrictions:
FileId
which has retrieved for file should not (and probably will not too) equal to the FileId
retrieved by some other botJS Restrictions
Sending via file is accessible from all supported platforms, but there is small note about JS
- due to restrictions of work with streams and stream-like data (JS
have no native support of files streaming) on this platform all the files will be loaded inside of RAM before the sending to the telegram services.
Sending via file is available throw the MultipartFile. There are several wayt to get it:
MultipartFile(\"filename.jpg\") { /* here Input allocation */ }
ByteArray
, ByteReadChannel
, ByteReadChannelAllocator
or File
(on any platform)In most cases, sending via files looks like in the next snippet:
val file: File;\nbot.sendDocument(chatId, file.asMultipartFile())\n
"},{"location":"tgbotapi/logic/low-level-work-with-bots.html","title":"Low-level work with bots","text":"The base version of library was done a lot of time ago and just got several additions related to improvements, updates in Telegram Bot API or some requests from our community.
"},{"location":"tgbotapi/logic/low-level-work-with-bots.html#base-things","title":"Base things","text":"There are several important things in context of this library:
TelegramBot
)So, in most cases all your request calls with simplified api of this library (like bot.getMe()
) will looks like bot.execute(GetMe)
. Result of these calls is defined in type of any request (for example, for GetMe request the result type is ExtendedBot). As a result, you can avoid any extension api (like special API extensions) and use low level request with full controlling of the whole logic flow.
As was written above, it will require some request:
val updates = bot.execute(GetUpdates())\n
Result type of GetUpdates request is Update. You may find inheritors of this interface in Update kdocs.
"},{"location":"tgbotapi/logic/low-level-work-with-bots.html#what-is-next","title":"What is next?","text":"As was said above, you may look into our API extensions in case you wish to use more high-level functions instead of bot.execute(SomeRequest())
. Besides, it will be very useful to know more about updates retrieving.
As you know, Telegram have the feature named Media Groups. Media groups have several differences with the common messages:
Row updates
In tgbotapi there is no any additional handling of media groups by default and in case you will use simple bot.getUpdates, you will get the list of row updates and media groups will be included in this list as separated messages with MediaGroupPartContent. In that case you may use convertWithMediaGroupUpdates to be able to work with media groups as will be described below
In case you are using standard long polling (one of alternatives is telegramBotWithBehaviourAndLongPolling) or webhooks updates will be converted uner the hood and as a result, you will take media groups as a content in one message:
telegramBotWithBehaviourAndLongPolling(\n\"token\"\n) {\nonVisualGallery { // it: CommonMessage<MediaGroupContent<VisualMediaGroupPartContent>>\nit.content // MediaGroupContent<VisualMediaGroupPartContent>\nit.content.group // List<MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>>\nit.content.group.forEach { // it: MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>\nit.messageId // source message id for current media group part\nit.sourceMessage // source message for current media group part\nit.content // VisualMediaGroupPartContent\nprintln(it.content) // will print current content part info\n}\n}\n}\n
KDocs:
In two words, in difference with row Telegram Bot API, you will take media groups in one message instead of messages list.
"},{"location":"tgbotapi/logic/types-conversations.html","title":"Types conversations","text":"One of the most important topics in context of tgbotapi is types conversations. This library is very strong-typed and a lot of things are based on types hierarchy. Lets look into the hierarchy of classes for the Message in 0.35.8:
As you may see, it is a little bit complex and require several tools for types conversation.
"},{"location":"tgbotapi/logic/types-conversations.html#as","title":"As","text":"as
conversations will return new type in case if it is possible. For example, when you got Message
, you may use asContentMessage
conversation to get message with content
:
val message: Message;\nprintln(message.asContentMessage() ?.content)\n
This code will print null
in case when message
is not ContentMessage
, and content
when is.
require
works like as
, but instead of returning nullable type, it will always return object with required type OR throw ClassCastException
:
val message: Message;\nprintln(message.requireContentMessage().content)\n
This code will throw exception when message is not ContentMessage
and print content
when is.
when
extensions will call passed block
when type is correct. For example:
val message: Message;\nmessage.whenContentMessage {\nprintln(it.content)\n}\n
Code placed above will print content
when message
is ContentMessage
and do nothing when not
Of course, in most cases here we will look up the way of using utils extnsions, but you may read deeper about updates retrieving here.
"},{"location":"tgbotapi/logic/updates-with-flows.html#phylosophy-of-flow-updates-retrieving","title":"Phylosophy ofFlow
updates retrieving","text":"In most updates retrieving processes there are two components: UpdatesFiler and its inheritor FlowsUpdatesFilter. It is assumed, that you will do several things in your app to handle updates:
UpdatesFilter
(for example, with flowsUpdatesFilter factory)flowsUpdatesFilter
you will set up updates handling in the lambda passed to this factory)Let\u2019s look how it works with the factory above:
// Step 1 - create filter\nval filter = flowsUpdatesFilter {\n// Step 2 - set up handling. In this case we will print any message from group or user in console\nmessageFlow.onEach {\nprintln(it)\n}.launchIn(someCoroutineScope)\n}\n// Step 3 - passing updates to filter\nbot.getUpdates().forEach {\nfilter.asUpdatesReceiver(it)\n}\n
"},{"location":"tgbotapi/logic/updates-with-flows.html#long-polling","title":"Long polling","text":"Some example with long polling has been described above. But it is more useful to use some factories for it. In this page we will look for simple variant with TelegramBot#longPolling. So, with this function, your handling of updates will looks like:
val bot = telegramBot(\"TOKEN\")\nbot.longPolling {\nmessageFlow.onEach {\nprintln(it)\n}.launchIn(someCoroutineScope)\n}.join()\n
This example looks like the example above with three steps, but there are several important things here:
.join()
will suspend your function \ud83d\ude0a longPolling
function returns Job
and you may use it to:cancel
working of long polling (just call job.cancel()
)join
and wait while the work of longPolling
will not be completed (it will works infinity if you will not cancel it anywhere)longPolling
functionWhat is next?
","text":"As a result you can start listen updates and react on it. Next recommended articles:
Preview reading
It is recommended to visit our pages about UpdatesFilters and Webhooks to have more clear understanding about what is happening in this examples page
Heroku is a popular place for bots hosting. In common case you will need to configure webhooks for your server to include getting updates without problems. There are several things related to heroku you should know:
https://<app name>.herokuapp.com/
System.getenv(\"PORT\").toInt()
Sat Aug 15 5:04:21 +00 2020
) there is only one official server engine for ktor which is correctly working with Heroku: Tomcat server engineServer configuration alternatives
Here will be presented variants of configuration of webhooks and starting server. You always able to set webhook manualy, create your own ktor server and include webhooks handling in it or create and start server with only webhooks handling. More info you can get on page Webhooks
"},{"location":"tgbotapi/updates/heroku.html#short-example-with-behaviour-builder","title":"Short example with Behaviour Builder","text":"suspend fun main {\n// This subroute will be used as random webhook subroute to improve security according to the recommendations of Telegram\nval subroute = uuid4().toString()\n// Input/Output coroutines scope more info here: https://kotlinlang.org/docs/coroutines-guide.html\nval scope = CoroutineScope(Dispatchers.IO)\n// Here will be automatically created bot and available inside of lambda where you will setup your bot behaviour\ntelegramBotWithBehaviour(\n// Pass TOKEN inside of your application environment variables\nSystem.getenv(\"TOKEN\"),\nscope = scope\n) {\n// Set up webhooks and start to listen them\nsetWebhookInfoAndStartListenWebhooks(\n// Automatic env which will be passed by heroku to the app\nSystem.getenv(\"PORT\").toInt(),\n// Server engine. More info here: https://ktor.io/docs/engines.html\nTomcat,\n// Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com\nSetWebhook(\"${System.getenv(\"URL\").removeSuffix(\"/\")}/$subroute\"),\n// Just callback which will be called when exceptions will happen inside of webhooks\n{\nit.printStackTrace()\n},\n// Set up listen requests from outside\n\"0.0.0.0\",\n// Set up subroute to listen webhooks to\nsubroute,\n// BehaviourContext is the CoroutineScope and it is recommended to pass it inside of webhooks server\nscope = this,\n// BehaviourContext is the FlowsUpdatesFilter and it is recommended to pass its asUpdateReceiver as a block to retrieve all the updates\nblock = asUpdateReceiver\n)\n// Test reaction on each command with reply and text `Got it`\nonUnhandledCommand {\nreply(it, \"Got it\")\n}\n}\n// Just potentially infinite await of bot completion\nscope.coroutineContext.job.join()\n}\n
"},{"location":"tgbotapi/updates/heroku.html#configuration-example-without-behaviour-builder","title":"Configuration example without Behaviour Builder","text":"// This subroute will be used as random webhook subroute to improve security according to the recommendations of Telegram\nval subroute = uuid4().toString()\nval bot = telegramBot(TOKEN)\nval scope = CoroutineScope(Dispatchers.Default)\nval filter = flowsUpdatesFilter {\nmessageFlow.onEach {\nprintln(it) // will be printed \n}.launchIn(scope)\n}\nval subroute = UUID.randomUUID().toString() // It will be used as subpath for security target as recommended by https://core.telegram.org/bots/api#setwebhook\nval server = bot.setWebhookInfoAndStartListenWebhooks(\n// Automatic env which will be passed by heroku to the app\nSystem.getenv(\"PORT\").toInt(),\n// Server engine. More info here: https://ktor.io/docs/engines.html\nTomcat,\n// Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com\nSetWebhook(\"${System.getenv(\"URL\").removeSuffix(\"/\")}/$subroute\"),\n// Just callback which will be called when exceptions will happen inside of webhooks\n{\nit.printStackTrace()\n},\n// Set up listen requests from outside\n\"0.0.0.0\",\n// Set up subroute to listen webhooks to\nsubroute,\nscope = scope,\nblock = filter.asUpdateReceiver\n)\nserver.environment.connectors.forEach {\nprintln(it)\n}\nserver.start(false)\n
"},{"location":"tgbotapi/updates/long-polling.html","title":"Long polling","text":"Long polling is a technology of getting updates for cases you do not have some dedicated server or you have no opportunity to receive updates via webhooks. More about this you can read in wiki.
"},{"location":"tgbotapi/updates/long-polling.html#related-topics","title":"Related topics","text":"There are a lot of ways to include work with long polling:
RequestsExecutor#longPollingFlow
Is the base way to get all updates cold Flow. Remember, that this flow will not be launched automaticallyRequestsExecutor#startGettingOfUpdatesByLongPolling
Old and almost deprecated wayRequestsExecutor#longPolling
Works like startGettingOfUpdatesByLongPolling
but shorted in a name :)RequestsExecutor#createAccumulatedUpdatesRetrieverFlow
Works like longPollingFlow
, but flow inside will return only the updates accumulated at the moment of calls (all new updates will not be passed throw this flow)RequestsExecutor#retrieveAccumulatedUpdates
Use createAccumulatedUpdatesRetrieverFlow
to perform all accumulated updatesRequestsExecutor#flushAccumulatedUpdates
Works like retrieveAccumulatedUpdates
but perform all updates directly in a place of callingGetUpdates
request or RequestsExecutor#getUpdates
extensionlongPolling
is a simple way to start getting updates and work with bot:
val bot = telegramBot(token)\nbot.longPolling(\ntextMessages().subscribe(scope) { // here \"scope\" is a CoroutineScope\nprintln(it) // will be printed each update from chats with messages\n}\n)\n
"},{"location":"tgbotapi/updates/long-polling.html#startgettingofupdatesbylongpolling","title":"startGettingOfUpdatesByLongPolling","text":"The main aim of startGettingOfUpdatesByLongPolling
extension was to provide more simple way to get updates in automatic mode:
val bot = telegramBot(token)\nbot.startGettingOfUpdatesByLongPolling(\n{\nprintln(it) // will be printed each update from chats with messages\n}\n)\n
The other way is to use the most basic startGettingOfUpdatesByLongPolling
extension:
val bot = telegramBot(token)\nbot.startGettingOfUpdatesByLongPolling {\nprintln(it) // will be printed each update\n}\n
"},{"location":"tgbotapi/updates/long-polling.html#see-also","title":"See also","text":"Due to the fact, that anyway you will get updates in one format (Update
objects), some time ago was solved to create one point of updates filters for more usefull way of updates handling
UpdatesFilter
currently have two properties:
asUpdateReceiver
- required to represent this filter as common updates receiver which able to get any Update
allowedUpdates
- required to determine, which updates are usefull for this filterAnyway, this filter can\u2019t work with updates by itself. For retrieving updates you should pass this filter to some of getting updates functions (long polling or webhooks).
"},{"location":"tgbotapi/updates/updates-filters.html#simpleupdatesfilter","title":"SimpleUpdatesFilter","text":"SimpleUpdatesFilter
is a simple variant of filters. It have a lot of UpdateReceiver
properties which can be set up on creating of this object. For example, if you wish to get messages from chats (but not from channels), you can use next snippet:
SimpleUpdatesFilter {\nprintln(it)\n}\n
"},{"location":"tgbotapi/updates/updates-filters.html#flowsupdatesfilter","title":"FlowsUpdatesFilter","text":"A little bit more modern way is to use FlowsUpdatesFilter
. It is very powerfull API of Kotlin Coroutines Flows, built-in support of additional extensions for FlowsUpdatesFilter
and Flow<...>
receivers and opportunity to split one filter for as much receivers as you want. Filter creating example:
val scope = CoroutineScope(Dispatchers.Default)\nflowsUpdatesFilter {\nmessageFlow.onEach {\nprintln(it)\n}.launchIn(scope)\n}\n
"},{"location":"tgbotapi/updates/updates-filters.html#combining-of-flows","title":"Combining of flows","text":"In cases you need not separate logic for handling of messages from channels and chats there are three ways to combine different flows into one:
plus
operation and handling of different flows: flowsUpdatesFilter {\n(messageFlow + channelPostFlow).onEach {\nprintln(it) // will be printed each message update from channels and chats both\n}.launchIn(scope)\n}\n
aggregateFlows
: flowsUpdatesFilter {\naggregateFlows(\nscope,\nmessageFlow,\nchannelPostFlow\n).onEach {\nprintln(it) // will be printed each message update from channels and chats both\n}.launchIn(scope)\n}\n
FlowsUpdatesFilter
extensions: flowsUpdatesFilter {\nallSentMessagesFlow.onEach {\nprintln(it) // will be printed each message update from channels and chats both\n}.launchIn(scope)\n}\n
FlowsUpdatesFilter
have a lot of extensions for messages types filtering:
flowsUpdatesFilter {\ntextMessages(scope).onEach {\nprintln(it) // will be printed each message from channels and chats both with content only `TextContent`\n}.launchIn(scope)\n}\n
The same things were created for media groups:
flowsUpdatesFilter {\nmediaGroupMessages(scope).onEach {\nprintln(it) // will be printed each media group messages list from both channels and chats without filtering of content\n}.launchIn(scope)\nmediaGroupPhotosMessages(scope).onEach {\nprintln(it) // will be printed each media group messages list from both channels and chats with PhotoContent only\n}.launchIn(scope)\nmediaGroupVideosMessages(scope).onEach {\nprintln(it) // will be printed each media group messages list from both channels and chats with VideoContent only\n}.launchIn(scope)\n}\n
Besides, there is an opportunity to avoid separation on media groups and common messages and receive photos and videos content in one flow:
flowsUpdatesFilter {\nsentMessagesWithMediaGroups(scope).onEach {\nprintln(it) // will be printed each message including each separated media group message from both channels and chats without filtering of content\n}.launchIn(scope)\nphotoMessagesWithMediaGroups(scope).onEach {\nprintln(it) // will be printed each message including each separated media group message from both channels and chats with PhotoContent only\n}.launchIn(scope)\nvideoMessagesWithMediaGroups(scope).onEach {\nprintln(it) // will be printed each message including each separated media group message from both channels and chats with VideoContent only\n}.launchIn(scope)\n}\n
"},{"location":"tgbotapi/updates/updates-filters.html#see-also","title":"See also","text":"In telegram bot API there is an opportunity to get updates via webhooks. In this case you will be able to retrieve updates without making additional requests. Most of currently available methods for webhooks are working on ktor server for JVM. Currently, next ways are available for using for webhooks:
Route#includeWebhookHandlingInRoute
for ktor serverRoute#includeWebhookHandlingInRouteWithFlows
startListenWebhooks
RequestsExecutor#setWebhookInfoAndStartListenWebhooks
setWebhookInfoAndStartListenWebhooks
","text":"It is the most common way to set updates webhooks and start listening of them. Example:
val bot = telegramBot(TOKEN)\nval filter = flowsUpdatesFilter {\n// ...\n}\nbot.setWebhookInfoAndStartListenWebhooks(\n8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku\nCIO, // default ktor server engine. It is recommended to replace it with something like `Netty`. More info about engines here: https://ktor.io/servers/configuration.html\nSetWebhook(\n\"address.com/webhook_route\",\nFile(\"/path/to/certificate\").toInputFile(), // certificate file. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how\n40, // max allowed updates, by default is null\nfilter.allowedUpdates\n),\n{\nit.printStackTrace() // optional handling of exceptions\n},\n\"0.0.0.0\", // listening host which will be used to bind by server\n\"subroute\", // Optional subroute, if null - will listen root of address\nWebhookPrivateKeyConfig( // optional config of private key. It will be installed in server to use TLS with custom certificate. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how\n\"/path/to/keystore.jks\",\n\"KeystorePassword\",\n\"Keystore key alias name\",\n\"KeystoreAliasPassword\"\n),\nscope, // Kotlin coroutine scope for internal transforming of media groups\nfilter.asUpdateReceiver\n)\n
If you will use previous example, ktor server will bind and listen url 0.0.0.0:8080/subroute
and telegram will send requests to address address.com/webhook_route
with custom certificate. Alternative variant will use the other SetWebhook
request variant:
SetWebhook(\n\"address.com/webhook_route\",\n\"some_file_bot_id\".toInputFile(),\n40, // max allowed updates, by default is null\nfilter.allowedUpdates\n)\n
As a result, request SetWebhook
will be executed and after this server will start its working and handling of updates.
startListenWebhooks
","text":"This function is working almost exactly like previous example, but this one will not set up webhook info in telegram:
val filter = flowsUpdatesFilter {\n// ...\n}\nstartListenWebhooks(\n8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku\nCIO, // default ktor server engine. It is recommended to replace it with something like `Netty`. More info about engines here: https://ktor.io/servers/configuration.html\n{\nit.printStackTrace() // optional handling of exceptions\n},\n\"0.0.0.0\", // listening host which will be used to bind by server\n\"subroute\", // Optional subroute, if null - will listen root of address\nWebhookPrivateKeyConfig( // optional config of private key. It will be installed in server to use TLS with custom certificate. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how\n\"/path/to/keystore.jks\",\n\"KeystorePassword\",\n\"Keystore key alias name\",\n\"KeystoreAliasPassword\"\n),\nscope, // Kotlin coroutine scope for internal transforming of media groups\nfilter.asUpdateReceiver\n)\n
The result will be the same as in previous example: server will start its working and handling of updates on 0.0.0.0:8080/subroute
. The difference here is that in case if this bot must not answer or send some requiests - it will not be necessary to create bot for receiving of updates.
includeWebhookHandlingInRoute
and includeWebhookHandlingInRouteWithFlows
","text":"For these extensions you will need to start your server manualy. In common case it will look like:
val scope = CoroutineScope(Dispatchers.Default)\nval filter = flowsUpdatesFilter {\n// ...\n}\nval environment = applicationEngineEnvironment {\nmodule {\nrouting {\nincludeWebhookHandlingInRoute(\nscope,\n{\nit.printStackTrace()\n},\nfilter.asUpdateReceiver\n)\n}\n}\nconnector {\nhost = \"0.0.0.0\"\nport = 8080\n}\n}\nembeddedServer(CIO, environment).start(true) // will start server and wait its stoping\n
In the example above server will started and binded for listening on 0.0.0.0:8080
.