mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-11-26 03:58:44 +00:00
add support of income explanation functionality in polls and polls auto close functionality
This commit is contained in:
parent
db8ea0da94
commit
3fb80dd475
16
CHANGELOG.md
16
CHANGELOG.md
@ -5,6 +5,22 @@
|
|||||||
* Versions:
|
* Versions:
|
||||||
* `Kotlin`: `1.3.71` -> `1.3.72`
|
* `Kotlin`: `1.3.71` -> `1.3.72`
|
||||||
* `Klock`: `1.10.3` -> `1.10.5`
|
* `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`
|
||||||
|
* 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`
|
||||||
|
|
||||||
|
|
||||||
## 0.26.0
|
## 0.26.0
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ typealias FileUniqueId = String
|
|||||||
typealias DiceResult = Int
|
typealias DiceResult = Int
|
||||||
|
|
||||||
typealias Seconds = Int
|
typealias Seconds = Int
|
||||||
|
typealias LongSeconds = Long
|
||||||
|
|
||||||
val getUpdatesLimit = 1 .. 100
|
val getUpdatesLimit = 1 .. 100
|
||||||
val callbackQueryAnswerLength = 0 until 200
|
val callbackQueryAnswerLength = 0 until 200
|
||||||
@ -199,6 +200,7 @@ const val tgsStickerField = "tgs_sticker"
|
|||||||
|
|
||||||
const val okField = "ok"
|
const val okField = "ok"
|
||||||
const val captionField = "caption"
|
const val captionField = "caption"
|
||||||
|
const val explanationField = "explanation"
|
||||||
const val idField = "id"
|
const val idField = "id"
|
||||||
const val pollIdField = "poll_id"
|
const val pollIdField = "poll_id"
|
||||||
const val textField = "text"
|
const val textField = "text"
|
||||||
@ -250,6 +252,9 @@ const val xShiftField = "x_shift"
|
|||||||
const val yShiftField = "y_shift"
|
const val yShiftField = "y_shift"
|
||||||
const val scaleField = "scale"
|
const val scaleField = "scale"
|
||||||
|
|
||||||
|
const val explanationEntitiesField = "explanation_entities"
|
||||||
|
const val openPeriodField = "open_period"
|
||||||
|
const val closeDateField = "close_date"
|
||||||
|
|
||||||
const val smallFileIdField = "small_file_id"
|
const val smallFileIdField = "small_file_id"
|
||||||
const val bigFileIdField = "big_file_id"
|
const val bigFileIdField = "big_file_id"
|
||||||
|
@ -83,6 +83,28 @@ internal fun createTextPart(from: String, entities: RawMessageEntities): List<Te
|
|||||||
return resultList
|
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 fun RawMessageEntities.asTextParts(sourceString: String): List<TextPart> = createTextPart(sourceString, this)
|
||||||
|
|
||||||
internal typealias RawMessageEntities = List<RawMessageEntity>
|
internal typealias RawMessageEntities = List<RawMessageEntity>
|
||||||
|
@ -5,7 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.utils.*
|
|||||||
|
|
||||||
class TextLinkTextSource(
|
class TextLinkTextSource(
|
||||||
override val source: String,
|
override val source: String,
|
||||||
url: String
|
val url: String
|
||||||
) : TextSource {
|
) : TextSource {
|
||||||
override val asMarkdownSource: String by lazy { source.linkMarkdown(url) }
|
override val asMarkdownSource: String by lazy { source.linkMarkdown(url) }
|
||||||
override val asMarkdownV2Source: String by lazy { source.linkMarkdownV2(url) }
|
override val asMarkdownV2Source: String by lazy { source.linkMarkdownV2(url) }
|
||||||
|
@ -2,16 +2,17 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsourc
|
|||||||
|
|
||||||
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.MultilevelTextSource
|
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.MultilevelTextSource
|
||||||
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
|
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
|
||||||
|
import com.github.insanusmokrassar.TelegramBotAPI.types.User
|
||||||
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.PrivateChat
|
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.PrivateChat
|
||||||
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
|
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
|
||||||
|
|
||||||
class TextMentionTextSource(
|
class TextMentionTextSource(
|
||||||
override val source: String,
|
override val source: String,
|
||||||
privateChat: PrivateChat,
|
val user: User,
|
||||||
textParts: List<TextPart>
|
textParts: List<TextPart>
|
||||||
) : MultilevelTextSource {
|
) : MultilevelTextSource {
|
||||||
override val textParts: List<TextPart> by lazy { source.fullListOfSubSource(textParts) }
|
override val textParts: List<TextPart> by lazy { source.fullListOfSubSource(textParts) }
|
||||||
override val asMarkdownSource: String by lazy { source.textMentionMarkdown(privateChat.id) }
|
override val asMarkdownSource: String by lazy { source.textMentionMarkdown(user.id) }
|
||||||
override val asMarkdownV2Source: String by lazy { textMentionMarkdownV2(privateChat.id) }
|
override val asMarkdownV2Source: String by lazy { textMentionMarkdownV2(user.id) }
|
||||||
override val asHtmlSource: String by lazy { textMentionHTML(privateChat.id) }
|
override val asHtmlSource: String by lazy { textMentionHTML(user.id) }
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,33 @@
|
|||||||
package com.github.insanusmokrassar.TelegramBotAPI.types.polls
|
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.*
|
||||||
|
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.*
|
||||||
|
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.RawMessageEntity
|
||||||
|
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.asTextParts
|
||||||
import com.github.insanusmokrassar.TelegramBotAPI.utils.nonstrictJsonFormat
|
import com.github.insanusmokrassar.TelegramBotAPI.utils.nonstrictJsonFormat
|
||||||
|
import com.soywiz.klock.DateTime
|
||||||
|
import com.soywiz.klock.TimeSpan
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
|
||||||
|
sealed class ScheduledCloseInfo {
|
||||||
|
abstract val closeDateTime: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ExactScheduledCloseInfo(
|
||||||
|
override val closeDateTime: DateTime
|
||||||
|
) : ScheduledCloseInfo()
|
||||||
|
|
||||||
|
data class ApproximateScheduledCloseInfo(
|
||||||
|
val openDuration: TimeSpan
|
||||||
|
) : ScheduledCloseInfo() {
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
val startPoint = DateTime.now()
|
||||||
|
override val closeDateTime: DateTime = startPoint + openDuration
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable(PollSerializer::class)
|
@Serializable(PollSerializer::class)
|
||||||
sealed class Poll {
|
sealed class Poll {
|
||||||
abstract val id: PollIdentifier
|
abstract val id: PollIdentifier
|
||||||
@ -14,6 +36,51 @@ sealed class Poll {
|
|||||||
abstract val votesCount: Int
|
abstract val votesCount: Int
|
||||||
abstract val isClosed: Boolean
|
abstract val isClosed: Boolean
|
||||||
abstract val isAnonymous: 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 ?.let {
|
||||||
|
ExactScheduledCloseInfo(
|
||||||
|
DateTime(unixMillis = it * 1000.0)
|
||||||
|
)
|
||||||
|
} ?: openPeriod ?.let {
|
||||||
|
ApproximateScheduledCloseInfo(
|
||||||
|
TimeSpan(it * 1000.0)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -30,91 +97,128 @@ data class UnknownPollType internal constructor(
|
|||||||
override val isClosed: Boolean = false,
|
override val isClosed: Boolean = false,
|
||||||
@SerialName(isAnonymousField)
|
@SerialName(isAnonymousField)
|
||||||
override val isAnonymous: Boolean = false,
|
override val isAnonymous: Boolean = false,
|
||||||
val raw: String
|
@Serializable
|
||||||
) : Poll()
|
val raw: JsonObject
|
||||||
|
) : Poll() {
|
||||||
|
@Transient
|
||||||
|
override val scheduledCloseInfo: ScheduledCloseInfo? = raw.getPrimitiveOrNull(closeDateField) ?.longOrNull ?.let {
|
||||||
|
ExactScheduledCloseInfo(
|
||||||
|
DateTime(unixMillis = it * 1000.0)
|
||||||
|
)
|
||||||
|
} ?: raw.getPrimitiveOrNull(durationField) ?.longOrNull ?.let {
|
||||||
|
ApproximateScheduledCloseInfo(
|
||||||
|
TimeSpan(it * 1000.0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable(PollSerializer::class)
|
||||||
data class RegularPoll(
|
data class RegularPoll(
|
||||||
@SerialName(idField)
|
|
||||||
override val id: PollIdentifier,
|
override val id: PollIdentifier,
|
||||||
@SerialName(questionField)
|
|
||||||
override val question: String,
|
override val question: String,
|
||||||
@SerialName(optionsField)
|
|
||||||
override val options: List<PollOption>,
|
override val options: List<PollOption>,
|
||||||
@SerialName(totalVoterCountField)
|
|
||||||
override val votesCount: Int,
|
override val votesCount: Int,
|
||||||
@SerialName(isClosedField)
|
|
||||||
override val isClosed: Boolean = false,
|
override val isClosed: Boolean = false,
|
||||||
@SerialName(isAnonymousField)
|
|
||||||
override val isAnonymous: Boolean = false,
|
override val isAnonymous: Boolean = false,
|
||||||
@SerialName(allowsMultipleAnswersField)
|
val allowMultipleAnswers: Boolean = false,
|
||||||
val allowMultipleAnswers: Boolean = false
|
override val scheduledCloseInfo: ScheduledCloseInfo? = null
|
||||||
) : Poll()
|
) : MultipleAnswersPoll()
|
||||||
|
|
||||||
@Serializable
|
@Serializable(PollSerializer::class)
|
||||||
data class QuizPoll(
|
data class QuizPoll(
|
||||||
@SerialName(idField)
|
|
||||||
override val id: PollIdentifier,
|
override val id: PollIdentifier,
|
||||||
@SerialName(questionField)
|
|
||||||
override val question: String,
|
override val question: String,
|
||||||
@SerialName(optionsField)
|
|
||||||
override val options: List<PollOption>,
|
override val options: List<PollOption>,
|
||||||
@SerialName(totalVoterCountField)
|
|
||||||
override val votesCount: Int,
|
override val votesCount: Int,
|
||||||
/**
|
/**
|
||||||
* Nullable due to documentation (https://core.telegram.org/bots/api#poll)
|
* Nullable due to documentation (https://core.telegram.org/bots/api#poll)
|
||||||
*/
|
*/
|
||||||
@SerialName(correctOptionIdField)
|
|
||||||
val correctOptionId: Int? = null,
|
val correctOptionId: Int? = null,
|
||||||
@SerialName(isClosedField)
|
override val caption: String? = null,
|
||||||
|
override val captionEntities: List<TextPart> = emptyList(),
|
||||||
override val isClosed: Boolean = false,
|
override val isClosed: Boolean = false,
|
||||||
@SerialName(isAnonymousField)
|
override val isAnonymous: Boolean = false,
|
||||||
override val isAnonymous: Boolean = false
|
override val scheduledCloseInfo: ScheduledCloseInfo? = null
|
||||||
) : Poll()
|
) : Poll(), CaptionedInput
|
||||||
|
|
||||||
@Serializer(Poll::class)
|
@Serializer(Poll::class)
|
||||||
internal object PollSerializer : KSerializer<Poll> {
|
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 {
|
override fun deserialize(decoder: Decoder): Poll {
|
||||||
val asJson = JsonObjectSerializer.deserialize(decoder)
|
val asJson = JsonObjectSerializer.deserialize(decoder)
|
||||||
|
val rawPoll = nonstrictJsonFormat.fromJson(RawPoll.serializer(), asJson)
|
||||||
|
|
||||||
return when (asJson.getPrimitive(typeField).content) {
|
return when (rawPoll.type) {
|
||||||
regularPollType -> nonstrictJsonFormat.fromJson(
|
quizPollType -> QuizPoll(
|
||||||
RegularPoll.serializer(),
|
rawPoll.id,
|
||||||
asJson
|
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(
|
regularPollType -> RegularPoll(
|
||||||
QuizPoll.serializer(),
|
rawPoll.id,
|
||||||
asJson
|
rawPoll.question,
|
||||||
|
rawPoll.options,
|
||||||
|
rawPoll.votesCount,
|
||||||
|
rawPoll.isClosed,
|
||||||
|
rawPoll.isAnonymous,
|
||||||
|
rawPoll.allowMultipleAnswers,
|
||||||
|
rawPoll.scheduledCloseInfo
|
||||||
)
|
)
|
||||||
else -> UnknownPollType(
|
else -> UnknownPollType(
|
||||||
asJson.getPrimitive(idField).content,
|
rawPoll.id,
|
||||||
asJson.getPrimitive(questionField).content,
|
rawPoll.question,
|
||||||
nonstrictJsonFormat.fromJson(
|
rawPoll.options,
|
||||||
pollOptionsSerializer,
|
rawPoll.votesCount,
|
||||||
asJson.getArray(optionsField)
|
rawPoll.isClosed,
|
||||||
),
|
rawPoll.isAnonymous,
|
||||||
asJson.getPrimitive(totalVoterCountField).int,
|
asJson
|
||||||
asJson.getPrimitiveOrNull(isClosedField) ?.booleanOrNull ?: false,
|
|
||||||
asJson.getPrimitiveOrNull(isAnonymousField) ?.booleanOrNull ?: true,
|
|
||||||
asJson.toString()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: Poll) {
|
override fun serialize(encoder: Encoder, value: Poll) {
|
||||||
val asJson = when (value) {
|
val closeInfo = value.scheduledCloseInfo
|
||||||
is RegularPoll -> nonstrictJsonFormat.toJson(RegularPoll.serializer(), value)
|
val rawPoll = when (value) {
|
||||||
is QuizPoll -> nonstrictJsonFormat.toJson(QuizPoll.serializer(), value)
|
is RegularPoll -> RawPoll(
|
||||||
is UnknownPollType -> throw IllegalArgumentException("Currently unable to correctly serialize object of poll $value")
|
value.id,
|
||||||
}
|
value.question,
|
||||||
val resultJson = JsonObject(
|
value.options,
|
||||||
asJson.jsonObject + (typeField to when (value) {
|
value.votesCount,
|
||||||
is RegularPoll -> JsonPrimitive(regularPollType)
|
value.isClosed,
|
||||||
is QuizPoll -> JsonPrimitive(quizPollType)
|
value.isAnonymous,
|
||||||
is UnknownPollType -> throw IllegalArgumentException("Currently unable to correctly serialize object of poll $value")
|
regularPollType,
|
||||||
})
|
value.allowMultipleAnswers,
|
||||||
|
openPeriod = (closeInfo as? ApproximateScheduledCloseInfo) ?.openDuration ?.seconds ?.toLong(),
|
||||||
|
closeDate = (closeInfo as? ExactScheduledCloseInfo) ?.closeDateTime ?.unixMillisLong ?.div(1000L)
|
||||||
)
|
)
|
||||||
JsonObjectSerializer.serialize(encoder, resultJson)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RawPoll.serializer().serialize(encoder, rawPoll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user