Skip to content

Commit

Permalink
Updated documentation. Moved FreeMarker templater to module. Created …
Browse files Browse the repository at this point in the history
…default templater working on regex. Improved InMemoryCallbackContentSource.
  • Loading branch information
DEHuckaKpyT committed Jul 27, 2024
1 parent e75a597 commit 9527466
Show file tree
Hide file tree
Showing 66 changed files with 952 additions and 335 deletions.
7 changes: 5 additions & 2 deletions Writerside/ktb.tree
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
<toc-element topic="step-types.md"/>
<toc-element topic="exceptions.md"/>
</toc-element>
<toc-element topic="factories.md">
<toc-element topic="templates.md"/>
<toc-element topic="file-sending.md"/>
<toc-element topic="buttons.md"/>
</toc-element>
<toc-element topic="update-handling.md"/>
<toc-element topic="templates.md"/>
<toc-element topic="file-sending.md"/>
<toc-element topic="testing.md"/>
<toc-element topic="configuration.md">
<toc-element topic="synchronization.md"/>
Expand Down
79 changes: 36 additions & 43 deletions Writerside/topics/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,47 @@

```kotlin
class TelegramBotConfig {
// telegram bot token
var token: String
// telegram bot username - need for command matching in group chats
var username: String

// source for save messages from user and bot
var messageSource: MessageSource?

fun templating(block: TelegramBotTemplatingConfig.() -> Unit)
/** Telegram bot token */
var token: String? = null,
/** Telegram bot username */
var username: String? = null,
/** Source for saving messages */
var messageSource: (TelegramBotActualConfig.() -> MessageSource)? = null,
/** Templater for build message templates */
var templater: (TelegramBotActualConfig.() -> Templater)? = null,

/** Configure receiving */
fun receiving(block: UpdateReceiverConfig.() -> Unit)
}
```

```kotlin
class TelegramBotTemplatingConfig {
// formatter for methods 'cleanHtml()' and 'escapeHtml()' in templating
var htmlFormatter: HtmlFormatter?

// configuration for freemarker library
fun freemarker(block: Configuration.() -> Unit)
}
```

```kotlin
class UpdateReceiverConfig {
// source for working callback data with more than 64 symbols
var callbackContentSource: CallbackContentSource?
// source for save chain state
var chainSource: ChainSource?
// delimiter for callback data values (using in callbackSerializer)
var callbackDataDelimiter: Char?
// converter from/to string callback data (using in callbackSerializer)
var contentConverter: ContentConverter?
// serializer for callback data - need for working callback buttons
var callbackSerializer: CallbackSerializer?
// messages to be display to the user when an internal exception throws
var messageTemplate: MessageTemplate?
// handler for processing exceptions in handlers
var exceptionHandler: ExceptionHandler?
// handler for processing internal errors (like unknown command)
var chainExceptionHandler: ChainExceptionHandler?

// configuration for long polling (webhook coming soon)
/** Source for saving callback.data */
var callbackContentSource: (TelegramBotActualConfig.() -> CallbackContentSource)? = null,
/** Source for saving chain state */
var chainSource: (TelegramBotActualConfig.() -> ChainSource)? = null,
/** Converter from object to string and back */
var contentConverter: (TelegramBotActualConfig.() -> ContentConverter)? = null,
/** Serializer from string to callback.data and back */
var callbackSerializer: (TelegramBotActualConfig.() -> CallbackSerializer)? = null,
/** Text templates for show to user when exception throws */
var messageTemplate: (TelegramBotActualConfig.() -> MessageTemplate)? = null,
/** Strategy for invoking BotHandler actions */
var invocationStrategy: (TelegramBotActualConfig.() -> HandlerInvocationStrategy)? = null,
/** Handler for catch internal exceptions */
var exceptionHandler: (TelegramBotActualConfig.() -> ExceptionHandler)? = null,
/** Handler for process exceptions in message chains */
var chainExceptionHandler: (TelegramBotActualConfig.() -> ChainExceptionHandler)? = null,

/** Activate and configure long polling for receiving updates */
fun longPolling(block: LongPollingConfig.() -> Unit)

// handler registration
/** Declare chain handlers */
fun handling(block: BotHandling.() -> Unit)
/** Declare any update handlers */
fun update(block: BotUpdateHandling.() -> Unit)
}
```

Expand All @@ -70,8 +63,8 @@ You can use already initialized elements for creation.
fun telegramBotConfig(): TelegramBotConfig = TelegramBotConfig().apply {
receiving {
// you can use other initialized elements in lambda
// (for example, 'telegramBot', 'receiving.messageTemplate', 'templating.templater')
exceptionHandler = { CustomExceptionHandler(telegramBot, receiving.messageTemplate, templating.templater) }
// (for example, 'telegramBot', 'receiving.messageTemplate', 'templater')
exceptionHandler = { CustomExceptionHandler(telegramBot, receiving.messageTemplate, templater) }
}
}
}
Expand All @@ -83,8 +76,8 @@ You can use already initialized elements for creation.
install(TelegramBot) {
receiving {
// you can use other initialized elements in lambda
// (for example, 'telegramBot', 'receiving.messageTemplate', 'templating.templater')
exceptionHandler = { CustomExceptionHandler(telegramBot, receiving.messageTemplate, templating.templater) }
// (for example, 'telegramBot', 'receiving.messageTemplate', 'templater')
exceptionHandler = { CustomExceptionHandler(telegramBot, receiving.messageTemplate, templater) }
}
}
}
Expand All @@ -95,8 +88,8 @@ You can use already initialized elements for creation.
val config = TelegramBotConfig().apply {
receiving {
// you can use other initialized elements in lambda
// (for example, 'telegramBot', 'receiving.messageTemplate', 'templating.templater')
exceptionHandler = { CustomExceptionHandler(telegramBot, receiving.messageTemplate, templating.templater) }
// (for example, 'telegramBot', 'receiving.messageTemplate', 'templater')
exceptionHandler = { CustomExceptionHandler(telegramBot, receiving.messageTemplate, templater) }
}
}
val context = TelegramBotFactory.createTelegramBotContext(config)
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ val config = TelegramBotConfig().apply {

How to **save state in database** see <a href="database-integration.md">here</a>.

## Spring only
<tabs id="template-factory-receiving-templates" group="telegram-bot-code">
<tab title="Spring" group-key="spring"></tab>
</tabs>

You can create beans as `ConfigExpression<..Source>` also:

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

To save the state in the database, it is necessary to connect to it using [Exposed](https://github.com/JetBrains/Exposed) ([example](https://github.com/DEHuckaKpyT/telegram-bot/blob/master/example/src/main/kotlin/io/github/dehuckakpyt/telegrambotexample/plugin/DatabaseConnection.kt)).
Then add the dependency and specify the sources:
```Gradle
```kotlin
dependencies {
implementation("io.github.dehuckakpyt.telegrambot:telegram-bot-core:%current_version%")
implementation("io.github.dehuckakpyt.telegrambot:telegram-bot-source-exposed:%current_version%")
Expand Down
File renamed without changes.
3 changes: 3 additions & 0 deletions Writerside/topics/factories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Factories

The library also contains useful factories for creating buttons, templates, files, etc.
48 changes: 48 additions & 0 deletions Writerside/topics/factories/buttons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Buttons

## Inside handlers

```kotlin
fun BotHandling.sendCallHandler() {

command("/call") {
sendMessage("message text", replyMarkup = inlineKeyboard(
callbackButton("first button", "simple"),
callbackButton("second button", "with_obj", AnyObject())
))
}
callback("simple") {
sendMessage("call1")
}
callback("with_obj") {
val obj = transferred<AnyObject>()
sendMessage("call2 ${obj.name}")
}
command("/auth") {
sendMessage("auth message", replyMarkup = inlineKeyboard(
authButton("auth in system", "https://your-system.com/auth-by-telegram"),
))
}
}
```

## In other classes

```kotlin
class PresentationTestClass(
private val bot: TelegramBot,
private val buttonFactory: ButtonFactory,
) {
val chatId = 123L
val fromId = 123L
sendMessage(chatId, "message text", replyMarkup = inlineKeyboard(
buttonFactory.callbackButton("first button", "simple"),
// for transfer objects you need to specify chatId and fromId
buttonFactory.callbackButton(chatId, fromId, "second button", "with_obj", AnyObject())
))
}
```

## More features

More useful methods tou can see [here](https://github.com/DEHuckaKpyT/telegram-bot/blob/master/telegram-bot-core/src/main/kotlin/io/github/dehuckakpyt/telegrambot/factory/keyboard/Keyboards.kt).
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Sending files is easy.

## Inside handlers

```kotlin
fun BotHandling.sendFilesHandler() {

Expand Down Expand Up @@ -49,4 +51,17 @@ fun BotHandling.sendFilesHandler() {
))
}
}
```

## In other classes

```kotlin
class PresentationTestClass(
private val bot: TelegramBot,
private val inputFactory: InputFactory,
) {
val chatId = 123L
bot.sendAudio(chatId, inputFactory.input("/files/Imagine Dragons - Enemy.mp3"))
bot.sendPhoto(chatId, "AgACAgIAAxkDAAIiH2Zdyo_8haA51WiPm24nUe08eVwBAAJ22jEbfvjxSqkxOQjpDDJWAQADAgADcwADNQQ")
}
```
151 changes: 151 additions & 0 deletions Writerside/topics/factories/templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Templates

### Substituting values into templates

Substitution is available using the short `with()` method with overloads.
By default in the BotHandling and BotUpdateHandling classes.
And also for Ktor + Koin and in other classes using `Templating` interface.


<tabs id="factory-tenplate" group="telegram-bot-code">
<tab title="Spring" group-key="spring">
<code-block lang="kotlin">
@Component
class PresentationTestClass(
private val templater: Templater,
) {
val testTemplate = "testing value is '\${value}'"
val instance = PresentationTestModel("some name")
val simpleValue = 1
with(templater) {
val example1 = testTemplate with instance
println(example1) // prints "testing value is 'some name'"
val example2 = testTemplate with mapOf("value" to simpleValue)
println(example2) // prints "testing value is '1'"
val example3 = testTemplate with ("value" to simpleValue)
println(example3) // prints "testing value is '1'"
}
}
data class PresentationTestModel(
val value: String
)
</code-block>
</tab>
<tab title="Ktor + Koin" group-key="ktor">
<code-block lang="kotlin">
@Single
class PresentationTestClass : Templating {
val testTemplate = "testing value is '\${value}'"
val instance = PresentationTestModel("some name")
val simpleValue = 1
val example1 = testTemplate with instance
println(example1) // prints "testing value is 'some name'"
val example2 = testTemplate with mapOf("value" to simpleValue)
println(example2) // prints "testing value is '1'"
val example3 = testTemplate with ("value" to simpleValue)
println(example3) // prints "testing value is '1'"
}
data class PresentationTestModel(
val value: String
)
</code-block>
</tab>
<tab title="Core" group-key="core">
<code-block lang="kotlin">
class PresentationTestClass(
private val templater: Templater,
) : Templater by templater {
val testTemplate = "testing value is '\${value}'"
val instance = PresentationTestModel("some name")
val simpleValue = 1
val example1 = testTemplate with instance
println(example1) // prints "testing value is 'some name'"
val example2 = testTemplate with mapOf("value" to simpleValue)
println(example2) // prints "testing value is '1'"
val example3 = testTemplate with ("value" to simpleValue)
println(example3) // prints "testing value is '1'"
}
data class PresentationTestModel(
val value: String
)
</code-block>
</tab>
</tabs>

### Integration with FreeMarker

#### Configuration

```kotlin
dependencies {
implementation("io.github.dehuckakpyt.telegrambot:telegram-bot-core:%current_version%")
implementation("io.github.dehuckakpyt.telegrambot:telegram-bot-templater-freemarker:%current_version%")
}
```

```kotlin
val config = TelegramBotConfig().apply {
templater = { Templater.dynamicFreeMarker }
}
```

#### Usage

The `escapeHtml()` and `cleanHtml()` methods are available in the template ([implementation](https://github.com/DEHuckaKpyT/telegram-bot/blob/master/telegram-bot-core/src/main/kotlin/io/github/dehuckakpyt/telegrambot/formatter/HtmlFormatterImpl.kt)).
These can be used when sending a message with `parseMode = HTML`.

The `escapeHtml()` method escapes all html characters.
The `cleanHtml()` method leaves only telegram formatted tags (see [official docs](https://core.telegram.org/bots/api#html-style)).
```kotlin
fun BotHandling.templateCommand() {
val cleanExample = "formatted text: \${cleanHtml(param)}"
val escapeExample = "formatted text: \${escapeHtml(param)}"
val htmlFormattedString = "<b><u>formatted</u></b> <center>ignored</center><br>new line"

command("/template_html_formatted") {
// message text: formatted text: <b><u>formatted</u></b> <center>ignored</center><br>new line
sendMessage(cleanExample with ("param" to htmlFormattedString), parseMode = Html)
// message text: formatted text: formatted ignored new line (with underline и line break)
sendMessage(escapeExample with ("param" to htmlFormattedString), parseMode = Html)
}
}
```

### Receiving templates

<tabs id="template-factory-receiving-templates" group="telegram-bot-code">
<tab title="Ktor + Koin" group-key="ktor"></tab>
</tabs>

Using a method with delegates `TemplateFactory.property()` you can take a template from the config.
Without parameters, the method gets the variable name, translates it into kebab-case and takes the value from the config.

```kotlin
// the value will be received from telegram-bot.from-field-name
val BotHandling.fromFieldName by TemplateFactory.property()
// the value will be received from telegram-bot.from-custom-param
val BotHandling.fromParam by TemplateFactory.property("from-custom-param")
// the value will be received from telegram-bot.from-param.
// if no value is specified in the config, "default template when null" will be substituted
val BotHandling.fromParamOrDefault by TemplateFactory.property("from-param", "default template when null")
```

You can get the template anywhere you want it:
```kotlin
import io.github.dehuckakpyt.telegrambot.factory.template.TemplateFactory.property

val fileWithoutClass by property()
val BotHandling.extendedField by property()
class SomeClass {
val inAnyClass by property()
}
fun BotHandling.startCommand() {
val insideMethods by property()

command("/start") {
val insideMethodsInMethods by property()
sendMessage(insideMethods)
sendMessage(insideMethodsInMethods)
}
}
```
Loading

0 comments on commit 9527466

Please sign in to comment.