mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI-examples.git
synced 2026-08-29 12:26:25 +00:00
927 lines
39 KiB
Kotlin
927 lines
39 KiB
Kotlin
import dev.inmo.kslog.common.KSLog
|
|
import dev.inmo.kslog.common.LogLevel
|
|
import dev.inmo.kslog.common.defaultMessageFormatter
|
|
import dev.inmo.kslog.common.setDefaultKSLog
|
|
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
|
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
|
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
|
import dev.inmo.tgbotapi.extensions.api.send.reply
|
|
import dev.inmo.tgbotapi.extensions.api.send.sendRichMessage
|
|
import dev.inmo.tgbotapi.extensions.api.send.sendRichMessageDraft
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitRichMessage
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onBaseInlineQuery
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGuestRequestMessage
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPhoto
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onRichMessage
|
|
import dev.inmo.tgbotapi.extensions.utils.baseSentMessageUpdateOrNull
|
|
import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull
|
|
import dev.inmo.tgbotapi.extensions.utils.onlyRichMessageContentMessages
|
|
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
|
import dev.inmo.tgbotapi.requests.edit.text.EditChatMessageRichText
|
|
import dev.inmo.tgbotapi.requests.abstracts.InputFile
|
|
import dev.inmo.tgbotapi.types.BotCommand
|
|
import dev.inmo.tgbotapi.types.CustomEmojiId
|
|
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
|
|
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputRichMessageContent
|
|
import dev.inmo.tgbotapi.types.InlineQueryId
|
|
import dev.inmo.tgbotapi.types.TelegramDate
|
|
import dev.inmo.tgbotapi.types.message.content.TextContent
|
|
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
|
|
import dev.inmo.tgbotapi.types.media.TelegramMediaAnimation
|
|
import dev.inmo.tgbotapi.types.media.TelegramMediaAudio
|
|
import dev.inmo.tgbotapi.types.media.TelegramMediaPhoto
|
|
import dev.inmo.tgbotapi.types.media.TelegramMediaVideo
|
|
import dev.inmo.tgbotapi.types.media.TelegramMediaVoiceNote
|
|
import dev.inmo.tgbotapi.types.rich.InputRichMessage
|
|
import dev.inmo.tgbotapi.types.rich.InputRichMessageBlocks
|
|
import dev.inmo.tgbotapi.types.rich.InputRichMessageHTML
|
|
import dev.inmo.tgbotapi.types.rich.InputRichMessageMarkdown
|
|
import dev.inmo.tgbotapi.types.rich.InputRichMessageMedia
|
|
import dev.inmo.tgbotapi.types.rich.RichBlockCaption
|
|
import dev.inmo.tgbotapi.types.rich.RichBlockTableCellAlign
|
|
import dev.inmo.tgbotapi.types.rich.RichBlockTableCellVAlign
|
|
import dev.inmo.tgbotapi.types.rich.RichTextPlain
|
|
import dev.inmo.tgbotapi.types.rich.buildRichText
|
|
import dev.inmo.tgbotapi.types.toChatId
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.flow.first
|
|
import kotlinx.coroutines.flow.mapNotNull
|
|
|
|
/**
|
|
* Runs a long-polling showcase of the rich-message APIs introduced in Telegram Bot API 10.1 and 10.2.
|
|
*
|
|
* Outgoing [dev.inmo.tgbotapi.types.rich.InputRichMessage] values use one of three representations:
|
|
* [InputRichMessageHTML], [InputRichMessageMarkdown], or a typed [InputRichMessageBlocks] tree of
|
|
* [dev.inmo.tgbotapi.types.rich.InputRichBlock] values. The handlers demonstrate [sendRichMessage],
|
|
* [sendRichMessageDraft] revisions sharing a draft ID (including draft-only `thinking()` blocks), and edits
|
|
* through [EditChatMessageRichText]. Media is shown both as [InputRichMessageMedia] references such as
|
|
* `tg://photo?id=...` and as typed blocks; [dev.inmo.tgbotapi.requests.send.SendRichMessage] also turns
|
|
* multipart files inside an input tree into `attach://` uploads.
|
|
*
|
|
* Incoming [dev.inmo.tgbotapi.types.message.content.RichMessageContent] and user-selected content covers
|
|
* [onRichMessage], [waitRichMessage], [onlyRichMessageContentMessages], photo reuse, and
|
|
* [InputRichMessageContent] in inline and guest-query results. Parsed content is exposed as a
|
|
* [dev.inmo.tgbotapi.types.rich.RichMessage] containing [dev.inmo.tgbotapi.types.rich.RichBlock]s.
|
|
*
|
|
* @param args the bot token followed by optional, case-sensitive `debug` and `testServer` flags. The token
|
|
* must be present; unrecognized later arguments are ignored.
|
|
*/
|
|
suspend fun main(vararg args: String) {
|
|
val botToken = args.first()
|
|
val isDebug = args.any { it == "debug" }
|
|
val isTestServer = args.any { it == "testServer" }
|
|
|
|
if (isDebug) {
|
|
setDefaultKSLog(
|
|
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
|
println(defaultMessageFormatter(level, tag, message, throwable))
|
|
}
|
|
)
|
|
}
|
|
|
|
val testMarkdownText = """
|
|
**bold text**
|
|
__bold text__
|
|
*italic text*
|
|
_italic text_
|
|
~~strikethrough text~~
|
|
`inline fixed-width code`
|
|
==marked text==
|
|
||spoiler||
|
|
|
|
[inline URL](https://t.me/)
|
|
[inline e-mail](mailto:user@example.com)
|
|
[inline phone number](tel:+123456789)
|
|
[inline mention of a user](tg://user?id=123456789)
|
|

|
|

|
|
${'$'}x^2 + y^2$
|
|
\#hashtag ${'$'}USD +12345678901, card: 4242 4242 4242 4242, https://t.me t.me a@t.me /command @username
|
|
all the text above was on the same line
|
|
|
|
# Heading 1
|
|
## Heading 2
|
|
### Heading 3
|
|
#### Heading 4
|
|
##### Heading 5
|
|
###### Heading 6
|
|
|
|
Paragraph text
|
|
|
|
```python
|
|
print('pre-formatted fixed-width code block written in the Python programming language')
|
|
```
|
|
|
|
---
|
|
|
|
- unordered list item
|
|
* unordered list item
|
|
+ unordered list item
|
|
|
|
1. ordered list item
|
|
2. ordered list item
|
|
|
|
- [ ] task list item
|
|
- [x] completed task list item
|
|
|
|
>Block quotation started
|
|
>
|
|
>Block quotation continued on the next line
|
|
>Block quotation continued on the same line
|
|
>
|
|
>The last line of the block quotation
|
|
|
|

|
|

|
|

|
|

|
|

|
|
|
|

|
|

|
|

|
|

|
|

|
|
|
|
| Header 1 | Header 2 |
|
|
|:---------|:--------:|
|
|
| left | center |
|
|
|
|
Text with a reference[^id1] and another one[^id2].
|
|
|
|
[^id1]: Definition of the first footnote.
|
|
[^id2]: Definition of the second footnote.
|
|
|
|
$${'$'}E = mc^2$$
|
|
|
|
```math
|
|
E = mc^2
|
|
```
|
|
|
|
## Example Nested Syntax Report for _Q1_
|
|
Intro with <u>underlined text</u>, ==marked text==, and ${'$'}x^2 + y^2$.
|
|
**Bold _italic <u>underlined italic bold</u> italic_ bold**
|
|
<u>In inline tags, nested **markdown** is parsed</u>
|
|
>Quote with **bold text, ~~strikethrough, and <tg-spoiler>spoiler</tg-spoiler>~~**, plus [a link](https://t.me/).
|
|
|
|
- List item with `code`, <sup>superscript</sup>, <sub>subscript</sub>, and a footnote[^note]
|
|
- Another item with **bold <tg-spoiler><code>spoiler code</code></tg-spoiler>**
|
|
- Another item with ~~strikethrough and <ins>inserted text</ins>~~
|
|
|
|
| Metric | Value |
|
|
|:-------|------:|
|
|
| Speed | **42** <sup>ms</sup> |
|
|
| Status | <tg-spoiler>ready</tg-spoiler> |
|
|
|
|
[^note]: Footnote with _italic text_ and <u>HTML underline</u>.
|
|
|
|
---
|
|
|
|
# Details blocks can contain Markdown content:
|
|
|
|
<details open><summary>Summary with **bold text**</summary>
|
|
|
|
### Details heading
|
|
- List item with _italic text_
|
|
- List item with <tg-spoiler>spoiler</tg-spoiler>
|
|
|
|
</details>
|
|
|
|
# Collages and slideshows can contain Markdown media blocks:
|
|
|
|
<tg-collage>
|
|
|
|

|
|

|
|
|
|
</tg-collage>
|
|
|
|
<tg-slideshow>
|
|
|
|

|
|

|
|
|
|
</tg-slideshow>
|
|
""".trimIndent()
|
|
val testMarkdownMediaLessText = """
|
|
**bold text**
|
|
__bold text__
|
|
*italic text*
|
|
_italic text_
|
|
~~strikethrough text~~
|
|
`inline fixed-width code`
|
|
==marked text==
|
|
||spoiler||
|
|
|
|
[inline URL](https://t.me/)
|
|
[inline e-mail](mailto:user@example.com)
|
|
[inline phone number](tel:+123456789)
|
|
[inline mention of a user](tg://user?id=123456789)
|
|

|
|

|
|
${'$'}x^2 + y^2$
|
|
\#hashtag ${'$'}USD +12345678901, card: 4242 4242 4242 4242, https://t.me t.me a@t.me /command @username
|
|
all the text above was on the same line
|
|
|
|
# Heading 1
|
|
## Heading 2
|
|
### Heading 3
|
|
#### Heading 4
|
|
##### Heading 5
|
|
###### Heading 6
|
|
|
|
Paragraph text
|
|
|
|
```python
|
|
print('pre-formatted fixed-width code block written in the Python programming language')
|
|
```
|
|
|
|
---
|
|
|
|
- unordered list item
|
|
* unordered list item
|
|
+ unordered list item
|
|
|
|
1. ordered list item
|
|
2. ordered list item
|
|
|
|
- [ ] task list item
|
|
- [x] completed task list item
|
|
|
|
>Block quotation started
|
|
>
|
|
>Block quotation continued on the next line
|
|
>Block quotation continued on the same line
|
|
>
|
|
>The last line of the block quotation
|
|
|
|
| Header 1 | Header 2 |
|
|
|:---------|:--------:|
|
|
| left | center |
|
|
|
|
Text with a reference[^id1] and another one[^id2].
|
|
|
|
[^id1]: Definition of the first footnote.
|
|
[^id2]: Definition of the second footnote.
|
|
|
|
$${'$'}E = mc^2$$
|
|
|
|
```math
|
|
E = mc^2
|
|
```
|
|
|
|
## Example Nested Syntax Report for _Q1_
|
|
Intro with <u>underlined text</u>, ==marked text==, and ${'$'}x^2 + y^2$.
|
|
**Bold _italic <u>underlined italic bold</u> italic_ bold**
|
|
<u>In inline tags, nested **markdown** is parsed</u>
|
|
>Quote with **bold text, ~~strikethrough, and <tg-spoiler>spoiler</tg-spoiler>~~**, plus [a link](https://t.me/).
|
|
|
|
- List item with `code`, <sup>superscript</sup>, <sub>subscript</sub>, and a footnote[^note]
|
|
- Another item with **bold <tg-spoiler><code>spoiler code</code></tg-spoiler>**
|
|
- Another item with ~~strikethrough and <ins>inserted text</ins>~~
|
|
|
|
| Metric | Value |
|
|
|:-------|------:|
|
|
| Speed | **42** <sup>ms</sup> |
|
|
| Status | <tg-spoiler>ready</tg-spoiler> |
|
|
|
|
[^note]: Footnote with _italic text_ and <u>HTML underline</u>.
|
|
|
|
---
|
|
|
|
# Details blocks can contain Markdown content:
|
|
|
|
<details open><summary>Summary with **bold text**</summary>
|
|
|
|
### Details heading
|
|
- List item with _italic text_
|
|
- List item with <tg-spoiler>spoiler</tg-spoiler>
|
|
|
|
</details>
|
|
""".trimIndent()
|
|
val testMarkdownMediaLessInputRichMessageBlocks = InputRichMessageBlocks {
|
|
paragraph {
|
|
bold("bold text")
|
|
plain("\n")
|
|
bold("bold text")
|
|
plain("\n")
|
|
italic("italic text")
|
|
plain("\n")
|
|
italic("italic text")
|
|
plain("\n")
|
|
strikethrough("strikethrough text")
|
|
plain("\n")
|
|
code("inline fixed-width code")
|
|
plain("\n")
|
|
marked("marked text")
|
|
plain("\n")
|
|
spoiler("spoiler")
|
|
}
|
|
paragraph {
|
|
url("inline URL", "https://t.me/")
|
|
plain("\n")
|
|
email("inline e-mail", "user@example.com")
|
|
plain("\n")
|
|
phone("inline phone number", "+123456789")
|
|
plain("\n")
|
|
url("inline mention of a user", "tg://user?id=123456789")
|
|
plain("\n")
|
|
customEmoji(CustomEmojiId("5368324170671202286"), "👍")
|
|
plain("\n")
|
|
dateTime("22:45 tomorrow", TelegramDate(1647531900L), "wDT")
|
|
plain("\n")
|
|
mathematicalExpression("x^2 + y^2")
|
|
plain("\n#hashtag ${'$'}USD +12345678901, card: 4242 4242 4242 4242, https://t.me t.me a@t.me /command @username\n")
|
|
plain("all the text above was on the same line")
|
|
}
|
|
|
|
h1("Heading 1")
|
|
h2("Heading 2")
|
|
h3("Heading 3")
|
|
h4("Heading 4")
|
|
h5("Heading 5")
|
|
h6("Heading 6")
|
|
paragraph("Paragraph text")
|
|
preformatted(
|
|
" print('pre-formatted fixed-width code block written in the Python programming language')",
|
|
language = "python"
|
|
)
|
|
divider()
|
|
|
|
unorderedList {
|
|
item("unordered list item")
|
|
item("unordered list item")
|
|
item("unordered list item")
|
|
}
|
|
orderedList {
|
|
item(1) { paragraph("ordered list item") }
|
|
item(2) { paragraph("ordered list item") }
|
|
}
|
|
unorderedList {
|
|
item(hasCheckbox = true, isChecked = false) { paragraph("task list item") }
|
|
item(hasCheckbox = true, isChecked = true) { paragraph("completed task list item") }
|
|
}
|
|
blockQuotation {
|
|
paragraph("Block quotation started\nBlock quotation continued on the next line\nBlock quotation continued on the same line\nThe last line of the block quotation")
|
|
}
|
|
|
|
table {
|
|
row {
|
|
headerCell(align = RichBlockTableCellAlign.Left, valign = RichBlockTableCellVAlign.Top) { plain("Header 1") }
|
|
headerCell(align = RichBlockTableCellAlign.Center, valign = RichBlockTableCellVAlign.Middle) { plain("Header 2") }
|
|
headerCell(align = RichBlockTableCellAlign.Right, valign = RichBlockTableCellVAlign.Bottom) { plain("Header 2") }
|
|
}
|
|
row {
|
|
cell(align = RichBlockTableCellAlign.Left, valign = RichBlockTableCellVAlign.Top) { plain("left") }
|
|
cell(align = RichBlockTableCellAlign.Center, valign = RichBlockTableCellVAlign.Middle) { plain("center") }
|
|
}
|
|
}
|
|
paragraph {
|
|
plain("Text with a reference")
|
|
referenceLink("id1", "id1")
|
|
plain(" and another one")
|
|
referenceLink("id2", "id2")
|
|
plain(".")
|
|
}
|
|
paragraph { reference("Definition of the first footnote.", "id1") }
|
|
paragraph { reference("Definition of the second footnote.", "id2") }
|
|
mathematicalExpression("E = mc^2")
|
|
preformatted("E = mc^2", language = "math")
|
|
|
|
h2 {
|
|
plain("Example Nested Syntax Report for ")
|
|
italic("Q1")
|
|
}
|
|
paragraph {
|
|
plain("Intro with ")
|
|
underline("underlined text")
|
|
plain(", ")
|
|
marked("marked text")
|
|
plain(", and ")
|
|
mathematicalExpression("x^2 + y^2")
|
|
plain(".")
|
|
}
|
|
paragraph {
|
|
bold {
|
|
plain("Bold ")
|
|
italic {
|
|
plain("italic ")
|
|
underline("underlined italic bold")
|
|
plain(" italic")
|
|
}
|
|
plain(" bold")
|
|
}
|
|
}
|
|
paragraph {
|
|
underline {
|
|
plain("In inline tags, nested ")
|
|
bold("markdown")
|
|
plain(" is parsed")
|
|
}
|
|
}
|
|
blockQuotation {
|
|
paragraph {
|
|
plain("Quote with ")
|
|
bold {
|
|
plain("bold text, ")
|
|
strikethrough {
|
|
plain("strikethrough, and ")
|
|
spoiler("spoiler")
|
|
}
|
|
}
|
|
plain(", plus ")
|
|
url("a link", "https://t.me/")
|
|
plain(".")
|
|
}
|
|
}
|
|
unorderedList {
|
|
item {
|
|
paragraph {
|
|
plain("List item with ")
|
|
code("code")
|
|
plain(", ")
|
|
superscript("superscript")
|
|
plain(", ")
|
|
subscript("subscript")
|
|
plain(", and a footnote")
|
|
referenceLink("note", "note")
|
|
}
|
|
}
|
|
item {
|
|
paragraph {
|
|
plain("Another item with ")
|
|
bold { spoiler { code("spoiler code") } }
|
|
}
|
|
}
|
|
item {
|
|
paragraph {
|
|
plain("Another item with ")
|
|
strikethrough {
|
|
plain("strikethrough and ")
|
|
underline("inserted text")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
table {
|
|
row {
|
|
headerCell(align = RichBlockTableCellAlign.Left, valign = RichBlockTableCellVAlign.Middle) { plain("Metric") }
|
|
headerCell(align = RichBlockTableCellAlign.Right, valign = RichBlockTableCellVAlign.Middle) { plain("Value") }
|
|
}
|
|
row {
|
|
cell(align = RichBlockTableCellAlign.Left, valign = RichBlockTableCellVAlign.Middle) { plain("Speed") }
|
|
cell(align = RichBlockTableCellAlign.Right, valign = RichBlockTableCellVAlign.Middle) {
|
|
bold("42")
|
|
plain(" ")
|
|
superscript("ms")
|
|
}
|
|
}
|
|
row {
|
|
cell(align = RichBlockTableCellAlign.Left, valign = RichBlockTableCellVAlign.Middle) { plain("Status") }
|
|
cell(align = RichBlockTableCellAlign.Right, valign = RichBlockTableCellVAlign.Middle) { spoiler("ready") }
|
|
}
|
|
}
|
|
paragraph {
|
|
reference("note") {
|
|
plain("Footnote with ")
|
|
italic("italic text")
|
|
plain(" and ")
|
|
underline("HTML underline")
|
|
plain(".")
|
|
}
|
|
}
|
|
divider()
|
|
h1("Details blocks can contain Markdown content:")
|
|
details(
|
|
summary = buildRichText {
|
|
plain("Summary with ")
|
|
bold("bold text")
|
|
},
|
|
isOpen = true
|
|
) {
|
|
h3("Details heading")
|
|
unorderedList {
|
|
item { paragraph { plain("List item with "); italic("italic text") } }
|
|
item { paragraph { plain("List item with "); spoiler("spoiler") } }
|
|
}
|
|
}
|
|
}
|
|
val testMarkdownInputRichMessageBlocks = InputRichMessageBlocks {
|
|
testMarkdownMediaLessInputRichMessageBlocks.blocks.orEmpty().forEach(::add)
|
|
|
|
val photo = TelegramMediaPhoto(InputFile.fromUrl("https://telegram.org/example/photo.jpg"))
|
|
val video = TelegramMediaVideo(InputFile.fromUrl("https://telegram.org/example/video.mp4"))
|
|
val audio = TelegramMediaAudio(InputFile.fromUrl("https://telegram.org/example/audio.mp3"))
|
|
val voiceNote = TelegramMediaVoiceNote(InputFile.fromUrl("https://telegram.org/example/audio.ogg"))
|
|
val animation = TelegramMediaAnimation(InputFile.fromUrl("https://telegram.org/example/animation.gif"))
|
|
|
|
photo(photo)
|
|
video(video)
|
|
audio(audio)
|
|
voiceNote(voiceNote)
|
|
animation(animation)
|
|
|
|
photo(photo, RichBlockCaption(RichTextPlain("Photo caption")))
|
|
video(video, RichBlockCaption(RichTextPlain("Video caption")))
|
|
audio(audio, RichBlockCaption(RichTextPlain("Audio caption")))
|
|
voiceNote(voiceNote, RichBlockCaption(RichTextPlain("Voice note caption")))
|
|
animation(animation, RichBlockCaption(RichTextPlain("Animation caption")))
|
|
|
|
collage {
|
|
photo(photo)
|
|
video(video)
|
|
}
|
|
collage(RichBlockCaption(RichTextPlain("Collage caption"))) {
|
|
video(video)
|
|
photo(photo)
|
|
}
|
|
slideshow {
|
|
photo(photo)
|
|
video(video)
|
|
}
|
|
slideshow(RichBlockCaption(RichTextPlain("Slideshow caption"))) {
|
|
video(video)
|
|
photo(photo)
|
|
}
|
|
}
|
|
|
|
telegramBotWithBehaviourAndLongPolling(
|
|
botToken,
|
|
CoroutineScope(Dispatchers.IO),
|
|
testServer = isTestServer
|
|
) {
|
|
// sendRichMessage with HTML-formatted content
|
|
onCommand("rich_html") {
|
|
sendRichMessage(
|
|
it.chat.id,
|
|
// InputRichMessageHTML factory — content described using HTML formatting
|
|
InputRichMessageHTML(
|
|
"""
|
|
<a name="chapter-0"></a>
|
|
<b>bold text</b>, <strong>bold text</strong>
|
|
<i>italic text</i>, <em>italic text</em>
|
|
<u>underlined text</u>, <ins>underlined text</ins>
|
|
<s>strikethrough text</s>, <strike>strikethrough text</strike>, <del>strikethrough text</del>
|
|
<code>inline fixed-width code</code>
|
|
<mark>marked text</mark>
|
|
<sub>subscript text</sub>
|
|
<sup>superscript text</sup>
|
|
<tg-spoiler>spoiler</tg-spoiler>
|
|
|
|
<a href="#note-1">Reference</a>
|
|
<a href="https://t.me/">inline URL</a>
|
|
<a href="mailto:user@example.com">inline e-mail</a>
|
|
<a href="tel:+123456789">inline phone number</a>
|
|
<a href="tg://user?id=123456789">inline mention of a user</a>
|
|
<a href="#chapter-1">in-document link</a>
|
|
<a name="chapter-1"></a>
|
|
|
|
<tg-reference name="note-1">Referenced text</tg-reference>
|
|
<tg-emoji emoji-id="5368324170671202286">👍</tg-emoji>
|
|
<img src="tg://emoji?id=5368324170671202286" alt="👍"/>
|
|
<tg-time unix="1647531900" format="wDT">22:45 tomorrow</tg-time>
|
|
<tg-math>x^2 + y^2</tg-math>
|
|
|
|
#hashtag ${'$'}USD +12345678901, card: 4242 4242 4242 4242, https://t.me t.me a@t.me /command @username
|
|
|
|
all the text above was on the same line
|
|
|
|
<h1>Heading 1</h1>
|
|
<h2>Heading 2</h2>
|
|
<h3>Heading 3</h3>
|
|
<h4>Heading 4</h4>
|
|
<h5>Heading 5</h5>
|
|
<h6>Heading 6</h6>
|
|
|
|
<a name="chapter-2"></a>
|
|
|
|
<p>Paragraph text</p>
|
|
<pre>pre-formatted fixed-width code block</pre>
|
|
<pre><code class="language-python"> print('pre-formatted fixed-width code block written in the Python programming language')</code></pre>
|
|
<footer>Footer text</footer>
|
|
<hr/>
|
|
<ul><li>unordered list item</li></ul>
|
|
<ol><li>ordered list item</li></ol>
|
|
<ol start="3" type="a" reversed><li>ordered list item</li></ol>
|
|
<ol><li value="7" type="i">ordered list item with explicit number</li></ol>
|
|
<ul>
|
|
<li><input type="checkbox" checked>Checked checkbox</li>
|
|
<li><input type="checkbox">Unchecked checkbox</li>
|
|
</ul>
|
|
|
|
<blockquote>Block quotation started<br>Block quotation continued<br>The last line of the block quotation<cite>The Author</cite></blockquote>
|
|
<aside>Pull quote<cite>The Author</cite></aside>
|
|
|
|
<img src="https://telegram.org/example/photo.jpg"/>
|
|
<video src="https://telegram.org/example/video.mp4"></video>
|
|
<audio src="https://telegram.org/example/audio.mp3"></audio>
|
|
<audio src="https://telegram.org/example/audio.ogg"></audio>
|
|
<video src="https://telegram.org/example/animation.gif"></video>
|
|
|
|
<figure><img src="https://telegram.org/example/photo.jpg" tg-spoiler/><figcaption>Photo caption<cite>Photo credit</cite></figcaption></figure>
|
|
<figure><video src="https://telegram.org/example/video.mp4" tg-spoiler></video><figcaption>Video caption</figcaption></figure>
|
|
<figure><audio src="https://telegram.org/example/audio.mp3"></audio><figcaption>Audio caption</figcaption></figure>
|
|
<figure><audio src="https://telegram.org/example/audio.ogg"></audio><figcaption>Voice note caption</figcaption></figure>
|
|
<figure><video src="https://telegram.org/example/animation.gif" tg-spoiler></video><figcaption>Animation caption</figcaption></figure>
|
|
|
|
<tg-map lat="41.9" long="12.5" zoom="14"/>
|
|
<figure><tg-map lat="41.9" long="12.5" zoom="14"/><figcaption>Map caption</figcaption></figure>
|
|
|
|
<tg-collage><img src="https://telegram.org/example/photo.jpg"/><video src="https://telegram.org/example/video.mp4"/></tg-collage>
|
|
<tg-collage><video src="https://telegram.org/example/video.mp4"/><img src="https://telegram.org/example/photo.jpg"/><figcaption>Collage caption</figcaption></tg-collage>
|
|
<tg-slideshow><img src="https://telegram.org/example/photo.jpg"/><video src="https://telegram.org/example/video.mp4"/></tg-slideshow>
|
|
<tg-slideshow><video src="https://telegram.org/example/video.mp4"/><img src="https://telegram.org/example/photo.jpg"/><figcaption>Slideshow caption</figcaption></tg-slideshow>
|
|
|
|
<table><tr><th>Header 1</th><th>Header 2</th></tr><tr><td>Value 1</td><td>Value 2</td></tr></table>
|
|
<table bordered striped><caption>Table caption</caption>
|
|
<tr><td colspan="2" rowspan="2" align="left">Value</td><td align="center">Value2</td><td align="right">Value3</td></tr>
|
|
<tr><td valign="top">Value4</td><td valign="middle">Value5</td><td valign="bottom">Value6</td></tr>
|
|
<tr><td>Value7</td></tr></table>
|
|
|
|
<details><summary>Title</summary>Content</details>
|
|
<details open><summary>Title</summary>Content</details>
|
|
<tg-math-block>E = mc^2</tg-math-block>
|
|
""".trimIndent()
|
|
)
|
|
)
|
|
}
|
|
|
|
// sendRichMessage with Markdown-formatted content
|
|
onCommand("rich_markdown") {
|
|
val sent = sendRichMessage(
|
|
it.chat.id,
|
|
InputRichMessageMarkdown(
|
|
testMarkdownText
|
|
)
|
|
)
|
|
println(sent)
|
|
}
|
|
|
|
// sendRichMessage with Markdown-formatted content
|
|
onCommand("rich_markdown_medialess") {
|
|
val sent = sendRichMessage(
|
|
it.chat.id,
|
|
InputRichMessageMarkdown(
|
|
testMarkdownMediaLessText
|
|
)
|
|
)
|
|
println(sent)
|
|
}
|
|
|
|
// sendRichMessage with Markdown-formatted content
|
|
onCommand("rich_markdown_blocks") {
|
|
val sent = sendRichMessage(
|
|
it.chat.id,
|
|
testMarkdownInputRichMessageBlocks
|
|
)
|
|
println(sent)
|
|
}
|
|
|
|
// sendRichMessage with Markdown-formatted content
|
|
onCommand("rich_markdown_medialess_blocks") {
|
|
val sent = sendRichMessage(
|
|
it.chat.id,
|
|
testMarkdownMediaLessInputRichMessageBlocks
|
|
)
|
|
println(sent)
|
|
}
|
|
|
|
// sendRichMessageDraft: stream partial rich messages sharing one draftId, then finalize
|
|
// with a full sendRichMessage. Emulates streaming of an AI-generated reply.
|
|
onCommand("rich_draft") {
|
|
val chatId = it.chat.id.toChatId()
|
|
val draftId = 1L
|
|
val parts = listOf(
|
|
"Thinking",
|
|
"Thinking about *rich* messages",
|
|
"Thinking about *rich* messages and how to _stream_ them"
|
|
)
|
|
parts.forEach { part ->
|
|
sendRichMessageDraft(chatId, draftId, InputRichMessageMarkdown(part))
|
|
delay(1000)
|
|
}
|
|
// finalize the streamed draft with the real message
|
|
sendRichMessage(chatId, InputRichMessageMarkdown("Done! Here is the *final* rich message."))
|
|
}
|
|
|
|
// EditChatMessageRichText: send a rich message, then edit it with new rich content
|
|
onCommand("rich_edit") {
|
|
val sent = sendRichMessage(it.chat.id, InputRichMessageMarkdown("*Original* rich message"))
|
|
delay(2000)
|
|
execute(
|
|
EditChatMessageRichText(
|
|
chatId = sent.chat.id,
|
|
messageId = sent.messageId,
|
|
// the new rich_message parameter of editMessageText
|
|
richMessage = InputRichMessageMarkdown("*Edited* rich message — now _updated_")
|
|
)
|
|
)
|
|
}
|
|
|
|
// === Bots API 10.2 additions: InputRichBlocks DSL + rich message media ===
|
|
|
|
// InputRichMessageBlocks { } — build a rich message from a typed InputRichBlock tree instead
|
|
// of an HTML/Markdown string (exactly one of html/markdown/blocks may be used). The lambda is an
|
|
// InputRichBlocksBuilder; buildInputRichBlocks { } returns the raw List<InputRichBlock> the same way.
|
|
onCommand("rich_blocks") {
|
|
sendRichMessage(
|
|
it.chat.id,
|
|
InputRichMessageBlocks {
|
|
heading("Rich blocks (Bots API 10.2)", level = 1)
|
|
paragraph {
|
|
plain("This message is built from ")
|
|
bold("structured InputRichBlocks")
|
|
plain(" — no HTML or Markdown string is involved.")
|
|
}
|
|
|
|
h2("Lists")
|
|
h3("Ordered")
|
|
orderedList {
|
|
item(0) { paragraph("A plain list item") }
|
|
item(1) { paragraph { url("google", "google.com") } }
|
|
item(2, hasCheckbox = true, isChecked = true) { paragraph("A plain list item") }
|
|
item(3, hasCheckbox = true, isChecked = false) { paragraph("A plain list item") }
|
|
}
|
|
divider()
|
|
h3("Unordered")
|
|
unorderedList {
|
|
item { paragraph("A plain list item") }
|
|
item { paragraph { url("google", "google.com") } }
|
|
item(hasCheckbox = true, isChecked = true) { paragraph("A plain list item") }
|
|
item(hasCheckbox = true, isChecked = false) { paragraph("A plain list item") }
|
|
}
|
|
|
|
divider()
|
|
|
|
heading("Code", level = 2)
|
|
preformatted("val answer = 42", language = "kotlin")
|
|
|
|
heading("Quotation", level = 2)
|
|
blockQuotation {
|
|
paragraph {
|
|
plain("Quotations are themselves made of nested blocks — ")
|
|
italic("including inline formatting")
|
|
plain(".")
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
// sendRichMessageDraft with blocks: the thinking() block is only valid inside a draft and is used
|
|
// to stream a model's reasoning before the finalized rich message is sent via sendRichMessage.
|
|
onCommand("rich_blocks_draft") {
|
|
val chatId = it.chat.id.toChatId()
|
|
val draftId = 2L
|
|
listOf("Analyzing your request", "Composing a structured answer").forEach { step ->
|
|
sendRichMessageDraft(
|
|
chatId,
|
|
draftId,
|
|
InputRichMessageBlocks { thinking(step) }
|
|
)
|
|
delay(1000)
|
|
}
|
|
// finalize the streamed draft with the real (non-thinking) blocks
|
|
sendRichMessage(
|
|
chatId,
|
|
InputRichMessageBlocks {
|
|
heading("Answer", level = 2)
|
|
paragraph("Here is the finalized, structured reply.")
|
|
}
|
|
)
|
|
}
|
|
|
|
// Rich message media: send me a photo and it gets embedded into a rich message two ways.
|
|
onPhoto { message ->
|
|
// reuse the received file by its fileId (no upload). To upload a brand-new file instead,
|
|
// build the TelegramMedia from file.asMultipartFile() — SendRichMessage collects any
|
|
// MultipartFile inside the rich message and uploads it as attach://<id> automatically.
|
|
val photoMedia = TelegramMediaPhoto(message.content.media.fileId)
|
|
|
|
// (1) referenced from HTML via tg://photo?id=<id>, resolved through InputRichMessage.media
|
|
sendRichMessage(
|
|
message.chat.id,
|
|
InputRichMessageHTML(
|
|
"""
|
|
<h2>Your photo, referenced from HTML</h2>
|
|
<p>Below is your photo, referenced via <code>tg://photo?id=userphoto</code>:</p>
|
|
<img src="tg://photo?id=userphoto"/>
|
|
""".trimIndent(),
|
|
media = listOf(
|
|
InputRichMessageMedia(id = "userphoto", media = photoMedia)
|
|
)
|
|
)
|
|
)
|
|
|
|
// (2) as a first-class media block inside an InputRichBlocks tree
|
|
sendRichMessage(
|
|
message.chat.id,
|
|
InputRichMessageBlocks {
|
|
heading("Your photo, as a media block", level = 2)
|
|
paragraph("The same photo, this time a photo() block inside the blocks tree:")
|
|
photo(photoMedia)
|
|
}
|
|
)
|
|
}
|
|
|
|
// waitRichMessage expectation: wait for the user to send a rich message
|
|
onCommand("wait_rich") {
|
|
reply(it, "Send me a rich message now")
|
|
val richMessageContent = waitRichMessage().first()
|
|
reply(
|
|
it,
|
|
"Got rich message with ${richMessageContent.richMessage.blocks.size} block(s)"
|
|
)
|
|
}
|
|
|
|
onGuestRequestMessage {
|
|
val withTextContent = it.withContentOrNull<TextContent>() ?: return@onGuestRequestMessage
|
|
val haveCommand = withTextContent.content.text.contains("/rich_guest")
|
|
if (haveCommand) {
|
|
answer(
|
|
it.guestQueryId,
|
|
InlineQueryResultArticle(
|
|
InlineQueryId("rich_content"),
|
|
"Send rich message",
|
|
InputRichMessageContent(
|
|
InputRichMessageMarkdown(
|
|
testMarkdownText
|
|
)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
// onRichMessage trigger: incoming messages carrying the new rich_message field
|
|
onRichMessage { message ->
|
|
val richMessage = message.content.richMessage
|
|
println("=== Rich message received ===")
|
|
println(" isRtl: ${richMessage.isRtl}")
|
|
println(" blocks: ${richMessage.blocks.size}")
|
|
richMessage.blocks.forEachIndexed { index, block ->
|
|
println(" [$index] $block")
|
|
}
|
|
reply(message, "Received a rich message with ${richMessage.blocks.size} block(s)")
|
|
execute(
|
|
message.content.createResend(
|
|
message.chat.id,
|
|
)
|
|
)
|
|
}
|
|
|
|
// InputRichMessageContent as InputMessageContent of inline query results
|
|
onBaseInlineQuery { query ->
|
|
answer(
|
|
query,
|
|
results = listOf(
|
|
InlineQueryResultArticle(
|
|
InlineQueryId("rich_html"),
|
|
"Rich message (HTML)",
|
|
// InputRichMessageContent wraps an InputRichMessage and is a valid InputMessageContent
|
|
InputRichMessageContent(
|
|
InputRichMessageHTML("<b>Bold</b> rich message sent via inline query")
|
|
),
|
|
description = "InputRichMessageContent built from HTML"
|
|
),
|
|
InlineQueryResultArticle(
|
|
InlineQueryId("rich_markdown"),
|
|
"Rich message (Markdown)",
|
|
InputRichMessageContent(
|
|
InputRichMessageMarkdown("*Bold* rich message sent via inline query")
|
|
),
|
|
description = "InputRichMessageContent built from Markdown"
|
|
)
|
|
),
|
|
cachedTime = 0
|
|
)
|
|
}
|
|
|
|
// onlyRichMessageContentMessages: filter a Flow<ContentMessage<*>> down to rich message content
|
|
allUpdatesFlow
|
|
.mapNotNull { it.baseSentMessageUpdateOrNull() ?.data ?.contentMessageOrNull() }
|
|
.onlyRichMessageContentMessages()
|
|
.subscribeLoggingDropExceptions(scope = this) { richMessageContentMessage ->
|
|
println("[onlyRichMessageContentMessages] ${richMessageContentMessage.content.richMessage.blocks.size} blocks")
|
|
}
|
|
|
|
setMyCommands(
|
|
BotCommand("rich_html", "Send a rich message described with HTML"),
|
|
BotCommand("rich_markdown", "Send a rich message described with Markdown"),
|
|
BotCommand("rich_blocks", "Send a rich message built from the InputRichBlocks DSL"),
|
|
BotCommand("rich_draft", "Stream a rich message draft, then finalize it"),
|
|
BotCommand("rich_blocks_draft", "Stream a blocks draft with thinking(), then finalize it"),
|
|
BotCommand("rich_edit", "Send a rich message and edit it with new rich content"),
|
|
BotCommand("wait_rich", "Wait for you to send a rich message"),
|
|
)
|
|
|
|
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
|
|
println(it)
|
|
}
|
|
}.second.join()
|
|
}
|