Merge pull request #84 from InsanusMokrassar/0.27.0

0.27.0
This commit is contained in:
InsanusMokrassar 2020-04-24 21:25:11 +06:00 committed by GitHub
commit fad27ede78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 493 additions and 105 deletions

View File

@ -1,5 +1,54 @@
# TelegramBotAPI changelog
## 0.27.0
* `Common`:
* Versions updates:
* `Kotlin`: `1.3.71` -> `1.3.72`
* `Klock`: `1.10.3` -> `1.10.5`
* `TelegramBotAPI`:
* Typealias `LongSeconds` was added for correct explanation of seconds in `Long` primitive type
* Several new fields was added:
* `explanationField`
* `explanationEntitiesField`
* `openPeriodField`
* `closeDateField`
* Extension `List<TextPart>#justTextSources` was added for mapping of `List<TextPart>` to `List<TextSource>`
* Field `SendPoll#closeInfo` was added
* Range `openPeriodPollSecondsLimit` was added and used in all `SendPoll` requests for checking income data
* `SendQuizPoll` now able to use fields `caption` and `parseMode` for `explanation` functionality
* `quizPollExplanationLimit` was added for checking `QuizPoll` explanation size
* Field `TextLinkTextSource#url` was added
* Field `TextMentionTextSource#user` was added
* Sealed class `ScheduledCloseInfo` was added
* Class `ExactScheduledCloseInfo` was added for cases with `close_date`
* Class `ApproximateScheduledCloseInfo` was added for cases with `open_period`
* Field `Poll#scheduledCloseInfo` was added
* Sealed class `MultipleAnswersPoll` was added
* Class `RegularPoll` now extends `MultipleAnswersPoll`
* `Dice` class was replaced into new package
* Sealed class `DiceAnimationType` was added
* Field `Dice#animationType` was added as `emoji` API representation
* `SendDice` now receive `animationType` as second parameter
* For `List<TextSource>` was added several extensions:
* `toMarkdownCaptions`
* `toMarkdownTexts`
* `toMarkdownV2Captions`
* `toMarkdownV2Texts`
* `toHtmlCaptions`
* `toHtmlTexts`
* `TelegramBotAPI-extensions-api`:
* All `RequestsExecutor#sendDice` extensions now accept `DiceAnimationType?` as second parameter
* All `RequestsExecutor#sendRegularPoll` extensions now accept `ScheduledCloseInfo` fourth parameter
* All `RequestsExecutor#sendQuizPoll` extensions now accept additional parameters `caption: String` and
`parseMode: ParseMode` for `explanation` functionality and `closeInfo: ScheduledCloseInfo?` for autoclose poll
functionality
* `TelegramBotAPI-extensions-utils`:
* Several shortcuts for `ScheduledCloseInfo` was added:
* `closePollExactAt`
* `closePollExactAfter`
* `closePollAfter`
## 0.26.0
* `Common`:

View File

@ -6,19 +6,22 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.ChatIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.dice.DiceAnimationType
suspend fun RequestsExecutor.sendDice(
chatId: ChatIdentifier,
animationType: DiceAnimationType? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendDice(chatId, disableNotification, replyToMessageId, replyMarkup)
SendDice(chatId, animationType, disableNotification, replyToMessageId, replyMarkup)
)
suspend fun RequestsExecutor.sendDice(
chat: Chat,
animationType: DiceAnimationType? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendDice(chat.id, disableNotification, replyToMessageId, replyMarkup)
) = sendDice(chat.id, animationType, disableNotification, replyToMessageId, replyMarkup)

View File

@ -5,10 +5,10 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls.SendQuizPo
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls.SendRegularPoll
import com.github.insanusmokrassar.TelegramBotAPI.types.ChatIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.QuizPoll
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.RegularPoll
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.*
suspend fun RequestsExecutor.sendRegularPoll(
chatId: ChatIdentifier,
@ -17,12 +17,13 @@ suspend fun RequestsExecutor.sendRegularPoll(
isAnonymous: Boolean = true,
isClosed: Boolean = false,
allowMultipleAnswers: Boolean = false,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendRegularPoll(
chatId, question, options, isAnonymous, isClosed, allowMultipleAnswers, disableNotification, replyToMessageId, replyMarkup
chatId, question, options, isAnonymous, isClosed, allowMultipleAnswers, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
)
suspend fun RequestsExecutor.sendRegularPoll(
@ -33,12 +34,13 @@ suspend fun RequestsExecutor.sendRegularPoll(
options: List<String> = poll.options.map { it.text },
isAnonymous: Boolean = poll.isAnonymous,
allowMultipleAnswers: Boolean = poll.allowMultipleAnswers,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendRegularPoll(
chatId, question, options, isAnonymous, isClosed, allowMultipleAnswers, disableNotification, replyToMessageId, replyMarkup
chatId, question, options, isAnonymous, isClosed, allowMultipleAnswers, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
)
@ -49,11 +51,12 @@ suspend fun RequestsExecutor.sendRegularPoll(
isAnonymous: Boolean = true,
isClosed: Boolean = false,
allowMultipleAnswers: Boolean = false,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendRegularPoll(
chat.id, question, options, isAnonymous, isClosed, allowMultipleAnswers, disableNotification, replyToMessageId, replyMarkup
chat.id, question, options, isAnonymous, isClosed, allowMultipleAnswers, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
suspend fun RequestsExecutor.sendRegularPoll(
@ -64,11 +67,12 @@ suspend fun RequestsExecutor.sendRegularPoll(
options: List<String> = poll.options.map { it.text },
isAnonymous: Boolean = poll.isAnonymous,
allowMultipleAnswers: Boolean = poll.allowMultipleAnswers,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendRegularPoll(
chat.id, question, options, isAnonymous, isClosed, allowMultipleAnswers, disableNotification, replyToMessageId, replyMarkup
chat.id, question, options, isAnonymous, isClosed, allowMultipleAnswers, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
@ -79,12 +83,15 @@ suspend fun RequestsExecutor.sendQuizPoll(
correctOptionId: Int,
isAnonymous: Boolean = true,
isClosed: Boolean = false,
caption: String? = null,
parseMode: ParseMode? = null,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendQuizPoll(
chatId, question, options, correctOptionId, isAnonymous, isClosed, disableNotification, replyToMessageId, replyMarkup
chatId, question, options, correctOptionId, isAnonymous, isClosed, caption, parseMode, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
)
@ -95,11 +102,14 @@ suspend fun RequestsExecutor.sendQuizPoll(
correctOptionId: Int,
isAnonymous: Boolean = true,
isClosed: Boolean = false,
caption: String? = null,
parseMode: ParseMode? = null,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendQuizPoll(
chat.id, question, options, correctOptionId, isAnonymous, isClosed, disableNotification, replyToMessageId, replyMarkup
chat.id, question, options, correctOptionId, isAnonymous, isClosed, caption, parseMode, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
suspend fun RequestsExecutor.sendQuizPoll(
@ -110,12 +120,15 @@ suspend fun RequestsExecutor.sendQuizPoll(
options: List<String> = quizPoll.options.map { it.text },
correctOptionId: Int = quizPoll.correctOptionId ?: error("Correct option ID must be provided by income QuizPoll or by developer"),
isAnonymous: Boolean = quizPoll.isAnonymous,
caption: String? = null,
parseMode: ParseMode? = null,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendQuizPoll(
chatId, question, options, correctOptionId, isAnonymous, isClosed, disableNotification, replyToMessageId, replyMarkup
chatId, question, options, correctOptionId, isAnonymous, isClosed, caption, parseMode, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
)
@ -127,9 +140,12 @@ suspend fun RequestsExecutor.sendQuizPoll(
options: List<String> = quizPoll.options.map { it.text },
correctOptionId: Int = quizPoll.correctOptionId ?: error("Correct option ID must be provided by income QuizPoll or by developer"),
isAnonymous: Boolean = quizPoll.isAnonymous,
caption: String? = null,
parseMode: ParseMode? = null,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendQuizPoll(
chat.id, question, options, correctOptionId, isAnonymous, isClosed, disableNotification, replyToMessageId, replyMarkup
chat.id, question, options, correctOptionId, isAnonymous, isClosed, caption, parseMode, closeInfo, disableNotification, replyToMessageId, replyMarkup
)

View File

@ -12,7 +12,6 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.PreviewFeature
import com.github.insanusmokrassar.TelegramBotAPI.utils.handleSafely
import io.ktor.client.features.HttpRequestTimeoutException
import kotlinx.coroutines.*
fun RequestsExecutor.startGettingOfUpdates(

View File

@ -78,4 +78,35 @@ filter.asContentMessagesFlow().onlyTextContentMessages().onEach {
)
```
As a result, each received message which will be just text message will be printed out with full list of its internal entities
As a result, each received message which will be just text message will be printed out with full list of its internal entities
## Shortcuts
With shortcuts you are able to use simple factories for several things.
### ScheduledCloseInfo
In case if you are creating some poll, you able to use next shortcuts.
Next sample will use info with closing at the 10 seconds after now:
```kotlin
closePollExactAt(DateTime.now() + TimeSpan(10000.0))
```
In this example we will do the same, but in another way:
```kotlin
closePollExactAfter(10)
```
Here we have passed `10` seconds and will get the same result object.
In opposite to previous shortcuts, the next one will create `approximate` closing schedule:
```kotlin
closePollAfter(10)
```
The main difference here is that the last one will be closed after 10 seconds since the sending. With first samples
will be created **exact** time for closing of poll

View File

@ -0,0 +1,35 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.shortcuts
import com.github.insanusmokrassar.TelegramBotAPI.types.LongSeconds
import com.github.insanusmokrassar.TelegramBotAPI.types.Seconds
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.ApproximateScheduledCloseInfo
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.ExactScheduledCloseInfo
import com.soywiz.klock.DateTime
import com.soywiz.klock.TimeSpan
fun closePollExactAt(
dateTime: DateTime
) = ExactScheduledCloseInfo(
dateTime
)
fun closePollExactAfter(
seconds: LongSeconds
) = closePollExactAt(
DateTime.now() + TimeSpan(seconds.toDouble() * 1000L)
)
fun closePollExactAfter(
seconds: Seconds
) = closePollExactAfter(
seconds.toLong()
)
fun closePollAfter(
seconds: LongSeconds
) = ApproximateScheduledCloseInfo(
TimeSpan(seconds.toDouble() * 1000L)
)
fun closePollAfter(
seconds: Seconds
) = closePollAfter(seconds.toLong())

View File

@ -2,7 +2,7 @@ package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.types.ChatId
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.SentMediaGroupUpdate
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter

View File

@ -10,7 +10,7 @@ moments are describing by official [Telegram Bot API](https://core.telegram.org/
## Compatibility
This version compatible with [30th of March 2020 update of TelegramBotAPI (version 4.7)](https://core.telegram.org/bots/api#march-30-2020).
This version compatible with [24th of April 2020 update of TelegramBotAPI (version 4.8)](https://core.telegram.org/bots/api#april-24-2020).
There is only one exception of implemented functionality - Telegram Passport API, which was presented in
[August 2018 update of TelegramBotAPI](https://core.telegram.org/bots/api-changelog#august-27-2018) update. It will be implemented
as soon as possible.

View File

@ -16,3 +16,5 @@ data class TextPart(
val range: IntRange,
val source: TextSource
)
fun List<TextPart>.justTextSources() = map { it.source }

View File

@ -10,7 +10,6 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import com.github.insanusmokrassar.TelegramBotAPI.types.Response
import com.github.insanusmokrassar.TelegramBotAPI.types.RetryAfterError
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.nonstrictJsonFormat
import io.ktor.client.HttpClient
import io.ktor.client.call.receive
import io.ktor.client.features.*

View File

@ -5,6 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.types.ReplyMes
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.ReplyingMarkupSendMessageRequest
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.dice.DiceAnimationType
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.DiceContent
@ -17,6 +18,8 @@ internal val DiceContentMessageResultDeserializer: DeserializationStrategy<Conte
data class SendDice(
@SerialName(chatIdField)
override val chatId: ChatIdentifier,
@SerialName(emojiField)
val animationType: DiceAnimationType? = null,
@SerialName(disableNotificationField)
override val disableNotification: Boolean = false,
@SerialName(replyToMessageIdField)

View File

@ -1,13 +1,20 @@
package com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.CaptionedOutput
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.justTextSources
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.ReplyingMarkupSendMessageRequest
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.SendMessageRequest
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.MarkdownV2
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.PollContent
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.fullListOfSubSource
import com.github.insanusmokrassar.TelegramBotAPI.utils.toMarkdownV2Captions
import com.soywiz.klock.DateTime
import kotlinx.serialization.*
private val commonResultDeserializer: DeserializationStrategy<ContentMessage<PollContent>> = TelegramBotAPIMessageDeserializationStrategyClass()
@ -66,6 +73,7 @@ fun Poll.createRequest(
isAnonymous,
isClosed,
allowMultipleAnswers,
scheduledCloseInfo,
disableNotification,
replyToMessageId,
replyMarkup
@ -78,6 +86,9 @@ fun Poll.createRequest(
correctOptionId,
isAnonymous,
isClosed,
caption ?.fullListOfSubSource(captionEntities) ?.justTextSources() ?.toMarkdownV2Captions() ?.firstOrNull(),
MarkdownV2,
scheduledCloseInfo,
disableNotification,
replyToMessageId,
replyMarkup
@ -89,6 +100,7 @@ fun Poll.createRequest(
isAnonymous,
isClosed,
false,
scheduledCloseInfo,
disableNotification,
replyToMessageId,
replyMarkup
@ -100,20 +112,35 @@ fun Poll.createRequest(
isAnonymous,
isClosed,
false,
scheduledCloseInfo,
disableNotification,
replyToMessageId,
replyMarkup
)
}
private fun ScheduledCloseInfo.checkSendData() {
val span = when (this) {
is ExactScheduledCloseInfo -> (closeDateTime - DateTime.now()).seconds
is ApproximateScheduledCloseInfo -> openDuration.seconds
}.toInt()
if (span !in openPeriodPollSecondsLimit) {
error("Duration of autoclose for polls must be in range $openPeriodPollSecondsLimit, but was $span")
}
}
sealed class SendPoll : SendMessageRequest<ContentMessage<PollContent>>,
ReplyingMarkupSendMessageRequest<ContentMessage<PollContent>> {
abstract val question: String
abstract val options: List<String>
abstract val isAnonymous: Boolean
abstract val isClosed: Boolean
abstract val closeInfo: ScheduledCloseInfo?
abstract val type: String
internal abstract val openPeriod: LongSeconds?
internal abstract val closeDate: LongSeconds?
override fun method(): String = "sendPoll"
override val resultDeserializer: DeserializationStrategy<ContentMessage<PollContent>>
get() = commonResultDeserializer
@ -133,6 +160,8 @@ data class SendRegularPoll(
override val isClosed: Boolean = false,
@SerialName(allowsMultipleAnswersField)
val allowMultipleAnswers: Boolean = false,
@Transient
override val closeInfo: ScheduledCloseInfo? = null,
@SerialName(disableNotificationField)
override val disableNotification: Boolean = false,
@SerialName(replyToMessageIdField)
@ -144,8 +173,17 @@ data class SendRegularPoll(
override val requestSerializer: SerializationStrategy<*>
get() = serializer()
@SerialName(openPeriodField)
override val openPeriod: LongSeconds?
= (closeInfo as? ApproximateScheduledCloseInfo) ?.openDuration ?.millisecondsLong ?.div(1000)
@SerialName(closeDateField)
override val closeDate: LongSeconds?
= (closeInfo as? ExactScheduledCloseInfo) ?.closeDateTime ?.unixMillisLong ?.div(1000)
init {
checkPollInfo(question, options)
closeInfo ?.checkSendData()
}
}
@ -163,23 +201,42 @@ data class SendQuizPoll(
override val isAnonymous: Boolean = true,
@SerialName(isClosedField)
override val isClosed: Boolean = false,
@SerialName(explanationField)
override val caption: String? = null,
@SerialName(explanationParseModeField)
override val parseMode: ParseMode? = null,
@Transient
override val closeInfo: ScheduledCloseInfo? = null,
@SerialName(disableNotificationField)
override val disableNotification: Boolean = false,
@SerialName(replyToMessageIdField)
override val replyToMessageId: MessageIdentifier? = null,
@SerialName(replyMarkupField)
override val replyMarkup: KeyboardMarkup? = null
) : SendPoll() {
) : SendPoll(), CaptionedOutput {
override val type: String = quizPollType
override val requestSerializer: SerializationStrategy<*>
get() = serializer()
@SerialName(openPeriodField)
override val openPeriod: LongSeconds?
= (closeInfo as? ApproximateScheduledCloseInfo) ?.openDuration ?.millisecondsLong ?.div(1000)
@SerialName(closeDateField)
override val closeDate: LongSeconds?
= (closeInfo as? ExactScheduledCloseInfo) ?.closeDateTime ?.unixMillisLong ?.div(1000)
init {
checkPollInfo(question, options)
closeInfo ?.checkSendData()
val correctOptionIdRange = 0 .. options.size
if (correctOptionId !in correctOptionIdRange) {
throw IllegalArgumentException("Correct option id must be in range of $correctOptionIdRange, but actual " +
"value is $correctOptionId")
}
if (caption != null && caption.length !in quizPollExplanationLimit) {
error("Quiz poll explanation size must be in range $quizPollExplanationLimit," +
"but actual explanation contains ${caption.length} symbols")
}
}
}

View File

@ -22,6 +22,7 @@ typealias FileUniqueId = String
typealias DiceResult = Int
typealias Seconds = Int
typealias LongSeconds = Long
val getUpdatesLimit = 1 .. 100
val callbackQueryAnswerLength = 0 until 200
@ -54,6 +55,10 @@ val botCommandLimit = botCommandLengthLimit
val botCommandDescriptionLimit = 3 .. 256
val botCommandsLimit = 0 .. 100
val quizPollExplanationLimit = 0 .. 200
val openPeriodPollSecondsLimit = 5 .. 600
const val chatIdField = "chat_id"
const val messageIdField = "message_id"
const val updateIdField = "update_id"
@ -199,6 +204,7 @@ const val tgsStickerField = "tgs_sticker"
const val okField = "ok"
const val captionField = "caption"
const val explanationField = "explanation"
const val idField = "id"
const val pollIdField = "poll_id"
const val textField = "text"
@ -250,6 +256,10 @@ const val xShiftField = "x_shift"
const val yShiftField = "y_shift"
const val scaleField = "scale"
const val explanationEntitiesField = "explanation_entities"
const val explanationParseModeField = "explanation_parse_mode"
const val openPeriodField = "open_period"
const val closeDateField = "close_date"
const val smallFileIdField = "small_file_id"
const val bigFileIdField = "big_file_id"

View File

@ -1,10 +1,9 @@
package com.github.insanusmokrassar.TelegramBotAPI.types
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import com.github.insanusmokrassar.TelegramBotAPI.types.dice.Dice
@Serializable
data class Dice(
@SerialName(valueField)
val value: DiceResult
@Deprecated(
"Replaced",
ReplaceWith("Dice", "com.github.insanusmokrassar.TelegramBotAPI.types.dice.Dice")
)
typealias Dice = Dice

View File

@ -83,6 +83,28 @@ internal fun createTextPart(from: String, entities: RawMessageEntities): List<Te
return resultList
}
internal fun List<TextPart>.asRawMessageEntities() = mapNotNull {
val source = it.source
when (source) {
is MentionTextSource -> RawMessageEntity("mention", it.range.first, it.range.last - it.range.first)
is HashTagTextSource -> RawMessageEntity("hashtag", it.range.first, it.range.last - it.range.first)
is CashTagTextSource -> RawMessageEntity("cashtag", it.range.first, it.range.last - it.range.first)
is BotCommandTextSource -> RawMessageEntity("bot_command", it.range.first, it.range.last - it.range.first)
is URLTextSource -> RawMessageEntity("url", it.range.first, it.range.last - it.range.first)
is EMailTextSource -> RawMessageEntity("email", it.range.first, it.range.last - it.range.first)
is PhoneNumberTextSource -> RawMessageEntity("phone_number", it.range.first, it.range.last - it.range.first)
is BoldTextSource -> RawMessageEntity("bold", it.range.first, it.range.last - it.range.first)
is ItalicTextSource -> RawMessageEntity("italic", it.range.first, it.range.last - it.range.first)
is CodeTextSource -> RawMessageEntity("code", it.range.first, it.range.last - it.range.first)
is PreTextSource -> RawMessageEntity("pre", it.range.first, it.range.last - it.range.first, language = source.language)
is TextLinkTextSource -> RawMessageEntity("text_link", it.range.first, it.range.last - it.range.first, source.url)
is TextMentionTextSource -> RawMessageEntity("text_mention", it.range.first, it.range.last - it.range.first, user = source.user)
is UnderlineTextSource -> RawMessageEntity("underline", it.range.first, it.range.last - it.range.first)
is StrikethroughTextSource -> RawMessageEntity("strikethrough", it.range.first, it.range.last - it.range.first)
else -> null
}
}
internal fun RawMessageEntities.asTextParts(sourceString: String): List<TextPart> = createTextPart(sourceString, this)
internal typealias RawMessageEntities = List<RawMessageEntity>

View File

@ -5,7 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.utils.*
class TextLinkTextSource(
override val source: String,
url: String
val url: String
) : TextSource {
override val asMarkdownSource: String by lazy { source.linkMarkdown(url) }
override val asMarkdownV2Source: String by lazy { source.linkMarkdownV2(url) }

View File

@ -2,16 +2,16 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsourc
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.MultilevelTextSource
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.PrivateChat
import com.github.insanusmokrassar.TelegramBotAPI.types.User
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
class TextMentionTextSource(
override val source: String,
privateChat: PrivateChat,
val user: User,
textParts: List<TextPart>
) : MultilevelTextSource {
override val textParts: List<TextPart> by lazy { source.fullListOfSubSource(textParts) }
override val asMarkdownSource: String by lazy { source.textMentionMarkdown(privateChat.id) }
override val asMarkdownV2Source: String by lazy { textMentionMarkdownV2(privateChat.id) }
override val asHtmlSource: String by lazy { textMentionHTML(privateChat.id) }
override val asMarkdownSource: String by lazy { source.textMentionMarkdown(user.id) }
override val asMarkdownV2Source: String by lazy { textMentionMarkdownV2(user.id) }
override val asHtmlSource: String by lazy { textMentionHTML(user.id) }
}

View File

@ -5,7 +5,6 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.games.CallbackGame
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
@Serializable(InlineKeyboardButtonSerializer::class)
sealed class InlineKeyboardButton {

View File

@ -3,7 +3,8 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.buttons.InlineKeyboardB
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.nonstrictJsonFormat
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.json.JsonElementSerializer
import kotlinx.serialization.json.JsonObject
internal object InlineKeyboardButtonSerializer : KSerializer<InlineKeyboardButton> {
override val descriptor: SerialDescriptor = SerialDescriptor(

View File

@ -0,0 +1,13 @@
package com.github.insanusmokrassar.TelegramBotAPI.types.dice
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Dice(
@SerialName(valueField)
val value: DiceResult,
@SerialName(emojiField)
val animationType: DiceAnimationType
)

View File

@ -0,0 +1,36 @@
package com.github.insanusmokrassar.TelegramBotAPI.types.dice
import kotlinx.serialization.*
@Serializable(DiceAnimationTypeSerializer::class)
sealed class DiceAnimationType {
abstract val emoji: String
}
@Serializable(DiceAnimationTypeSerializer::class)
object CubeDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFB2"
}
@Serializable(DiceAnimationTypeSerializer::class)
object DartsDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFAF"
}
@Serializable(DiceAnimationTypeSerializer::class)
class UnknownDiceAnimationType(
override val emoji: String
) : DiceAnimationType()
@Serializer(DiceAnimationType::class)
internal object DiceAnimationTypeSerializer : KSerializer<DiceAnimationType> {
override val descriptor: SerialDescriptor = PrimitiveDescriptor("DiceAnimationType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): DiceAnimationType {
return when (val type = decoder.decodeString()) {
CubeDiceAnimationType.emoji -> CubeDiceAnimationType
DartsDiceAnimationType.emoji -> DartsDiceAnimationType
else -> UnknownDiceAnimationType(type)
}
}
override fun serialize(encoder: Encoder, value: DiceAnimationType) {
encoder.encodeString(value.emoji)
}
}

View File

@ -5,6 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.RawMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.asTextParts
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.InlineKeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.types.dice.Dice
import com.github.insanusmokrassar.TelegramBotAPI.types.files.*
import com.github.insanusmokrassar.TelegramBotAPI.types.games.RawGame
import com.github.insanusmokrassar.TelegramBotAPI.types.message.ChatEvents.*

View File

@ -2,8 +2,10 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.message.content
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.SendDice
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.ChatIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.dice.Dice
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.abstracts.MessageContent
@ -15,5 +17,11 @@ data class DiceContent(
disableNotification: Boolean,
replyToMessageId: MessageIdentifier?,
replyMarkup: KeyboardMarkup?
): Request<ContentMessage<DiceContent>> = SendDice(chatId, disableNotification, replyToMessageId, replyMarkup)
): Request<ContentMessage<DiceContent>> = SendDice(
chatId,
dice.animationType,
disableNotification,
replyToMessageId,
replyMarkup
)
}

View File

@ -1,10 +1,40 @@
package com.github.insanusmokrassar.TelegramBotAPI.types.polls
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.CaptionedInput
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.nonstrictJsonFormat
import com.soywiz.klock.DateTime
import com.soywiz.klock.TimeSpan
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.*
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonObjectSerializer
sealed class ScheduledCloseInfo {
abstract val closeDateTime: DateTime
}
data class ExactScheduledCloseInfo(
override val closeDateTime: DateTime
) : ScheduledCloseInfo()
data class ApproximateScheduledCloseInfo(
val openDuration: TimeSpan,
@Suppress("MemberVisibilityCanBePrivate")
val startPoint: DateTime = DateTime.now()
) : ScheduledCloseInfo() {
override val closeDateTime: DateTime = startPoint + openDuration
}
val LongSeconds.asApproximateScheduledCloseInfo
get() = ApproximateScheduledCloseInfo(
TimeSpan(this * 1000.0)
)
val LongSeconds.asExactScheduledCloseInfo
get() = ExactScheduledCloseInfo(
DateTime(unixMillis = this * 1000.0)
)
@Serializable(PollSerializer::class)
sealed class Poll {
@ -14,6 +44,44 @@ sealed class Poll {
abstract val votesCount: Int
abstract val isClosed: Boolean
abstract val isAnonymous: Boolean
abstract val scheduledCloseInfo: ScheduledCloseInfo?
}
@Serializable(PollSerializer::class)
sealed class MultipleAnswersPoll : Poll()
@Serializable
private class RawPoll(
@SerialName(idField)
val id: PollIdentifier,
@SerialName(questionField)
val question: String,
@SerialName(optionsField)
val options: List<PollOption>,
@SerialName(totalVoterCountField)
val votesCount: Int,
@SerialName(isClosedField)
val isClosed: Boolean = false,
@SerialName(isAnonymousField)
val isAnonymous: Boolean = false,
@SerialName(typeField)
val type: String,
@SerialName(allowsMultipleAnswersField)
val allowMultipleAnswers: Boolean = false,
@SerialName(correctOptionIdField)
val correctOptionId: Int? = null,
@SerialName(explanationField)
val caption: String? = null,
@SerialName(explanationEntitiesField)
val captionEntities: List<RawMessageEntity> = emptyList(),
@SerialName(openPeriodField)
val openPeriod: LongSeconds? = null,
@SerialName(closeDateField)
val closeDate: LongSeconds? = null
) {
@Transient
val scheduledCloseInfo: ScheduledCloseInfo?
= closeDate ?.asExactScheduledCloseInfo ?: openPeriod ?.asApproximateScheduledCloseInfo
}
@Serializable
@ -30,91 +98,124 @@ data class UnknownPollType internal constructor(
override val isClosed: Boolean = false,
@SerialName(isAnonymousField)
override val isAnonymous: Boolean = false,
val raw: String
) : Poll()
@Serializable
val raw: JsonObject
) : Poll() {
@Transient
override val scheduledCloseInfo: ScheduledCloseInfo? = raw.getPrimitiveOrNull(
closeDateField
) ?.longOrNull ?.asExactScheduledCloseInfo ?: raw.getPrimitiveOrNull(
openPeriodField
) ?.longOrNull ?.asApproximateScheduledCloseInfo
}
@Serializable
@Serializable(PollSerializer::class)
data class RegularPoll(
@SerialName(idField)
override val id: PollIdentifier,
@SerialName(questionField)
override val question: String,
@SerialName(optionsField)
override val options: List<PollOption>,
@SerialName(totalVoterCountField)
override val votesCount: Int,
@SerialName(isClosedField)
override val isClosed: Boolean = false,
@SerialName(isAnonymousField)
override val isAnonymous: Boolean = false,
@SerialName(allowsMultipleAnswersField)
val allowMultipleAnswers: Boolean = false
) : Poll()
val allowMultipleAnswers: Boolean = false,
override val scheduledCloseInfo: ScheduledCloseInfo? = null
) : MultipleAnswersPoll()
@Serializable
@Serializable(PollSerializer::class)
data class QuizPoll(
@SerialName(idField)
override val id: PollIdentifier,
@SerialName(questionField)
override val question: String,
@SerialName(optionsField)
override val options: List<PollOption>,
@SerialName(totalVoterCountField)
override val votesCount: Int,
/**
* Nullable due to documentation (https://core.telegram.org/bots/api#poll)
*/
@SerialName(correctOptionIdField)
val correctOptionId: Int? = null,
@SerialName(isClosedField)
override val caption: String? = null,
override val captionEntities: List<TextPart> = emptyList(),
override val isClosed: Boolean = false,
@SerialName(isAnonymousField)
override val isAnonymous: Boolean = false
) : Poll()
override val isAnonymous: Boolean = false,
override val scheduledCloseInfo: ScheduledCloseInfo? = null
) : Poll(), CaptionedInput
@Serializer(Poll::class)
internal object PollSerializer : KSerializer<Poll> {
private val pollOptionsSerializer = ListSerializer(PollOption.serializer())
override val descriptor: SerialDescriptor
get() = RawPoll.serializer().descriptor
override fun deserialize(decoder: Decoder): Poll {
val asJson = JsonObjectSerializer.deserialize(decoder)
val rawPoll = nonstrictJsonFormat.fromJson(RawPoll.serializer(), asJson)
return when (asJson.getPrimitive(typeField).content) {
regularPollType -> nonstrictJsonFormat.fromJson(
RegularPoll.serializer(),
asJson
return when (rawPoll.type) {
quizPollType -> QuizPoll(
rawPoll.id,
rawPoll.question,
rawPoll.options,
rawPoll.votesCount,
rawPoll.correctOptionId,
rawPoll.caption,
rawPoll.caption?.let { rawPoll.captionEntities.asTextParts(it) } ?: emptyList(),
rawPoll.isClosed,
rawPoll.isAnonymous,
rawPoll.scheduledCloseInfo
)
quizPollType -> nonstrictJsonFormat.fromJson(
QuizPoll.serializer(),
asJson
regularPollType -> RegularPoll(
rawPoll.id,
rawPoll.question,
rawPoll.options,
rawPoll.votesCount,
rawPoll.isClosed,
rawPoll.isAnonymous,
rawPoll.allowMultipleAnswers,
rawPoll.scheduledCloseInfo
)
else -> UnknownPollType(
asJson.getPrimitive(idField).content,
asJson.getPrimitive(questionField).content,
nonstrictJsonFormat.fromJson(
pollOptionsSerializer,
asJson.getArray(optionsField)
),
asJson.getPrimitive(totalVoterCountField).int,
asJson.getPrimitiveOrNull(isClosedField) ?.booleanOrNull ?: false,
asJson.getPrimitiveOrNull(isAnonymousField) ?.booleanOrNull ?: true,
asJson.toString()
rawPoll.id,
rawPoll.question,
rawPoll.options,
rawPoll.votesCount,
rawPoll.isClosed,
rawPoll.isAnonymous,
asJson
)
}
}
override fun serialize(encoder: Encoder, value: Poll) {
val asJson = when (value) {
is RegularPoll -> nonstrictJsonFormat.toJson(RegularPoll.serializer(), value)
is QuizPoll -> nonstrictJsonFormat.toJson(QuizPoll.serializer(), value)
is UnknownPollType -> throw IllegalArgumentException("Currently unable to correctly serialize object of poll $value")
val closeInfo = value.scheduledCloseInfo
val rawPoll = when (value) {
is RegularPoll -> RawPoll(
value.id,
value.question,
value.options,
value.votesCount,
value.isClosed,
value.isAnonymous,
regularPollType,
value.allowMultipleAnswers,
openPeriod = (closeInfo as? ApproximateScheduledCloseInfo) ?.openDuration ?.seconds ?.toLong(),
closeDate = (closeInfo as? ExactScheduledCloseInfo) ?.closeDateTime ?.unixMillisLong ?.div(1000L)
)
is QuizPoll -> RawPoll(
value.id,
value.question,
value.options,
value.votesCount,
value.isClosed,
value.isAnonymous,
regularPollType,
correctOptionId = value.correctOptionId,
caption = value.caption,
captionEntities = value.captionEntities.asRawMessageEntities(),
openPeriod = (closeInfo as? ApproximateScheduledCloseInfo) ?.openDuration ?.seconds ?.toLong(),
closeDate = (closeInfo as? ExactScheduledCloseInfo) ?.closeDateTime ?.unixMillisLong ?.div(1000L)
)
is UnknownPollType -> {
JsonObjectSerializer.serialize(encoder, value.raw)
return
}
}
val resultJson = JsonObject(
asJson.jsonObject + (typeField to when (value) {
is RegularPoll -> JsonPrimitive(regularPollType)
is QuizPoll -> JsonPrimitive(quizPollType)
is UnknownPollType -> throw IllegalArgumentException("Currently unable to correctly serialize object of poll $value")
})
)
JsonObjectSerializer.serialize(encoder, resultJson)
RawPoll.serializer().serialize(encoder, rawPoll)
}
}

View File

@ -5,7 +5,6 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.ChosenInli
import com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.query.RawInlineQuery
import com.github.insanusmokrassar.TelegramBotAPI.types.UpdateIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializeOnlySerializer
import com.github.insanusmokrassar.TelegramBotAPI.types.payments.PreCheckoutQuery
import com.github.insanusmokrassar.TelegramBotAPI.types.payments.ShippingQuery
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.Poll

View File

@ -53,15 +53,17 @@ fun createMarkdownText(
partLength: Int = 4096
): List<String> = createFormattedText(entities, partLength, MarkdownParseMode)
fun CaptionedInput.toMarkdownCaptions(): List<String> = createMarkdownText(
fullEntitiesList(),
fun List<TextSource>.toMarkdownCaptions(): List<String> = createMarkdownText(
this,
captionLength.last + 1
)
fun CaptionedInput.toMarkdownCaptions(): List<String> = fullEntitiesList().toMarkdownCaptions()
fun TextContent.toMarkdownTexts(): List<String> = createMarkdownText(
fullEntitiesList(),
fun List<TextSource>.toMarkdownTexts(): List<String> = createMarkdownText(
this,
textLength.last + 1
)
fun TextContent.toMarkdownTexts(): List<String> = fullEntitiesList().toMarkdownTexts()
fun createMarkdownV2Text(
@ -69,15 +71,17 @@ fun createMarkdownV2Text(
partLength: Int = 4096
): List<String> = createFormattedText(entities, partLength, MarkdownV2ParseMode)
fun CaptionedInput.toMarkdownV2Captions(): List<String> = createMarkdownV2Text(
fullEntitiesList(),
fun List<TextSource>.toMarkdownV2Captions(): List<String> = createMarkdownV2Text(
this,
captionLength.last + 1
)
fun CaptionedInput.toMarkdownV2Captions(): List<String> = fullEntitiesList().toMarkdownV2Captions()
fun TextContent.toMarkdownV2Texts(): List<String> = createMarkdownV2Text(
fullEntitiesList(),
fun List<TextSource>.toMarkdownV2Texts(): List<String> = createMarkdownV2Text(
this,
textLength.last + 1
)
fun TextContent.toMarkdownV2Texts(): List<String> = fullEntitiesList().toMarkdownV2Texts()
fun createHtmlText(
@ -85,14 +89,16 @@ fun createHtmlText(
partLength: Int = 4096
): List<String> = createFormattedText(entities, partLength, HTMLParseMode)
fun CaptionedInput.toHtmlCaptions(): List<String> = createHtmlText(
fullEntitiesList(),
fun List<TextSource>.toHtmlCaptions(): List<String> = createHtmlText(
this,
captionLength.last + 1
)
fun CaptionedInput.toHtmlCaptions(): List<String> = fullEntitiesList().toHtmlCaptions()
fun TextContent.toHtmlTexts(): List<String> = createHtmlText(
fullEntitiesList(),
fun List<TextSource>.toHtmlTexts(): List<String> = createHtmlText(
this,
textLength.last + 1
)
fun TextContent.toHtmlTexts(): List<String> = fullEntitiesList().toHtmlTexts()

View File

@ -3,7 +3,6 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils
import com.benasher44.uuid.uuid4
import io.ktor.utils.io.core.Input
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
data class StorageFileInfo(

View File

@ -1,12 +1,12 @@
kotlin.code.style=official
kotlin_version=1.3.71
kotlin_version=1.3.72
kotlin_coroutines_version=1.3.5
kotlin_serialisation_runtime_version=0.20.0
klock_version=1.10.3
klock_version=1.10.5
uuid_version=0.1.0
ktor_version=1.3.2
library_group=com.github.insanusmokrassar
library_version=0.26.4
library_version=0.27.0
gradle_bintray_plugin_version=1.8.4