mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-11-22 16:23:48 +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:
|
||||
* `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`
|
||||
* 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
|
||||
|
||||
|
@ -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
|
||||
@ -199,6 +200,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 +252,9 @@ const val xShiftField = "x_shift"
|
||||
const val yShiftField = "y_shift"
|
||||
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 bigFileIdField = "big_file_id"
|
||||
|
@ -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>
|
||||
|
@ -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) }
|
||||
|
@ -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.TextPart
|
||||
import com.github.insanusmokrassar.TelegramBotAPI.types.User
|
||||
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.PrivateChat
|
||||
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) }
|
||||
}
|
||||
|
@ -1,11 +1,33 @@
|
||||
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.types.MessageEntity.RawMessageEntity
|
||||
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.asTextParts
|
||||
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.*
|
||||
|
||||
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)
|
||||
sealed class Poll {
|
||||
abstract val id: PollIdentifier
|
||||
@ -14,6 +36,51 @@ 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 ?.let {
|
||||
ExactScheduledCloseInfo(
|
||||
DateTime(unixMillis = it * 1000.0)
|
||||
)
|
||||
} ?: openPeriod ?.let {
|
||||
ApproximateScheduledCloseInfo(
|
||||
TimeSpan(it * 1000.0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@ -30,91 +97,128 @@ 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 ?.let {
|
||||
ExactScheduledCloseInfo(
|
||||
DateTime(unixMillis = it * 1000.0)
|
||||
)
|
||||
} ?: raw.getPrimitiveOrNull(durationField) ?.longOrNull ?.let {
|
||||
ApproximateScheduledCloseInfo(
|
||||
TimeSpan(it * 1000.0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user