1
0
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:
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: * 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

View File

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

View File

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

View File

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

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

View File

@ -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,
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( RawPoll.serializer().serialize(encoder, rawPoll)
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)
} }
} }