add support of income explanation functionality in polls and polls auto close functionality

This commit is contained in:
InsanusMokrassar 2020-04-24 17:51:09 +06:00
parent db8ea0da94
commit 3fb80dd475
6 changed files with 206 additions and 58 deletions

View File

@ -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

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
@ -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"

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,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) }
}

View File

@ -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)
}
}