mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2025-11-16 20:10:18 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d6709baf4 | |||
| b50ad30176 | |||
| 887c51f83a | |||
| c193b512d9 | |||
| ee055873e6 | |||
| 6db6209c88 | |||
| e92563e85f | |||
| 03f2f0e25b | |||
| 4197e13c54 | |||
| e09ea9a9b4 | |||
| 2a32654d57 | |||
| 0fff553ce1 | |||
| 33a1701f5b | |||
| f9a9f958ba | |||
| f686be0271 | |||
| 91307f3ebf | |||
| 8e64205f53 | |||
| 5434df1f02 | |||
| e56199ac9f | |||
| 3e199c6944 | |||
| b43d9aefb9 | |||
| fe133bbde0 | |||
| 332fe95adf | |||
| 1f416d4a28 | |||
| e626d8b5cc | |||
| b906d605f4 | |||
| d3584e793c | |||
| f71ac51461 | |||
| 5fe8cf948a | |||
| 85ea101641 | |||
| dc3ecec5c7 | |||
| ebd5e1a37b | |||
| 3da2e4fffb | |||
| 9aea59b44b | |||
| 7bab96a9cd | |||
| a0c10d9a9a | |||
| e2901cb9f9 | |||
| 3c60d074ba | |||
| f10cfdc5f1 | |||
| 270f9a60c1 | |||
| 2b49a75a42 | |||
| 9f7a348000 | |||
| 8cec74aa6a | |||
| d1914c8045 | |||
| baeb4848cf | |||
| dbadcb3f5a | |||
| 13dd73dd63 | |||
| ceba86abcb | |||
| f6a0b1a19d | |||
| 4ed6e5c7b2 | |||
| 7f43dc0815 |
7
.github/workflows/packages_publishing.yml
vendored
7
.github/workflows/packages_publishing.yml
vendored
@@ -16,7 +16,12 @@ jobs:
|
||||
mv gradle.properties.tmp gradle.properties
|
||||
- name: Build
|
||||
run: ./gradlew build
|
||||
- name: Publish
|
||||
- name: Publish to Gitea
|
||||
continue-on-error: true
|
||||
run: ./gradlew publishAllPublicationsToGiteaRepository
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
- name: Publish to GithubPackages
|
||||
continue-on-error: true
|
||||
run: ./gradlew publishAllPublicationsToGithubPackagesRepository --no-parallel
|
||||
env:
|
||||
|
||||
51
CHANGELOG.md
51
CHANGELOG.md
@@ -1,5 +1,56 @@
|
||||
# TelegramBotAPI changelog
|
||||
|
||||
## 4.2.4
|
||||
|
||||
* `Core`:
|
||||
* Fixes in webhook parts adapter
|
||||
* `BehaviourBuilderWithFSM`:
|
||||
* Fixes in `DefaultBehaviourContextWithFSM`
|
||||
|
||||
## 4.2.3
|
||||
|
||||
* `Versions`:
|
||||
* `MicroUtils`: `0.16.2` -> `0.16.4`
|
||||
* `Core`:
|
||||
* Simplify default `RequestsLimiter` (`ExceptionsOnlyLimiter`) (thanks to [@y9san9](https://github.com/y9san9) for help)
|
||||
|
||||
## 4.2.2
|
||||
|
||||
* `Versions`:
|
||||
* `MicroUtils`: `0.16.0` -> `0.16.2`
|
||||
* `Core`:
|
||||
* Fix of [#694](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/694): add opportunity to create `ChatId` and `ChatIdWithThreadId` from `IdChatIdentifier`
|
||||
|
||||
## 4.2.1
|
||||
|
||||
* `Versions`:
|
||||
* `MicroUtils`: `0.15.0` -> `0.16.0`
|
||||
* `Ktor`: `2.1.3` -> `2.2.1`
|
||||
* `Utils`:
|
||||
* Improve support of `makeLinkToMessage` extensions
|
||||
|
||||
## 4.2.0
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.7.21` -> `1.7.22`
|
||||
* `MicroUtils`: `0.14.4` -> `0.15.0`
|
||||
|
||||
## 4.1.3
|
||||
|
||||
* `Versions`:
|
||||
* `MicroUtils`: `0.14.2` -> `0.14.4`
|
||||
* `Core`:
|
||||
* `ContentMessage`, `CommonMessage`, `PossiblyMediaGroupMessage` and `PossiblySentViaBotCommonMessage` got `out`
|
||||
variance
|
||||
* `UserId` now is `ChatId` instead of `IdChatIdentififer`
|
||||
|
||||
## 4.1.2
|
||||
|
||||
* `Versions`:
|
||||
* `MicroUtils`: `0.14.1` -> `0.14.2`
|
||||
* `BehaviourBuilder`:
|
||||
* Fixes in `CallbackQuery` waiters
|
||||
|
||||
## 4.1.1
|
||||
|
||||
* `Core`:
|
||||
|
||||
@@ -20,6 +20,7 @@ allprojects {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" }
|
||||
}
|
||||
if (it != rootProject.findProject("docs")) {
|
||||
tasks.whenTaskAdded { task ->
|
||||
|
||||
@@ -6,4 +6,4 @@ kotlin.incremental=true
|
||||
kotlin.incremental.js=true
|
||||
|
||||
library_group=dev.inmo
|
||||
library_version=4.1.1
|
||||
library_version=4.2.4
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[versions]
|
||||
|
||||
kotlin = "1.7.21"
|
||||
kotlin = "1.7.22"
|
||||
kotlin-serialization = "1.4.1"
|
||||
kotlin-coroutines = "1.6.4"
|
||||
|
||||
@@ -8,12 +8,12 @@ javax-activation = "1.1.1"
|
||||
|
||||
korlibs = "3.4.0"
|
||||
uuid = "0.6.0"
|
||||
ktor = "2.1.3"
|
||||
ktor = "2.2.1"
|
||||
|
||||
ksp = "1.7.21-1.0.8"
|
||||
ksp = "1.7.22-1.0.8"
|
||||
kotlin-poet = "1.12.0"
|
||||
|
||||
microutils = "0.14.1"
|
||||
microutils = "0.16.4"
|
||||
|
||||
github-release-plugin = "2.4.1"
|
||||
dokka = "1.7.20"
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
|
||||
@@ -42,20 +42,40 @@ publishing {
|
||||
maven {
|
||||
name = "GithubPackages"
|
||||
url = uri("https://maven.pkg.github.com/InsanusMokrassar/TelegramBotAPI")
|
||||
|
||||
credentials {
|
||||
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
||||
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
|
||||
maven {
|
||||
name = "Gitea"
|
||||
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
|
||||
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Authorization"
|
||||
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
|
||||
}
|
||||
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
|
||||
credentials {
|
||||
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"${project.description}","url":"https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI","vcsUrl":"https://github.com/insanusmokrassar/TelegramBotAPI.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/TelegramBotAPI"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
|
||||
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"${project.description}","url":"https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI","vcsUrl":"https://github.com/insanusmokrassar/TelegramBotAPI.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/TelegramBotAPI"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
|
||||
@@ -10,6 +10,8 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.T
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
@@ -133,6 +135,9 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
||||
private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>()
|
||||
private var actualHandlersList = additionalHandlers + handlers
|
||||
|
||||
protected val statesJobs = mutableMapOf<T, Job>()
|
||||
protected val statesJobsMutex = Mutex()
|
||||
|
||||
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
|
||||
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
|
||||
}
|
||||
@@ -164,21 +169,49 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
||||
statesManager.endChain(state)
|
||||
}
|
||||
}
|
||||
|
||||
fun Job.enableRemoveOnCompletion(state: T) {
|
||||
invokeOnCompletion {
|
||||
launchSafelyWithoutExceptions {
|
||||
statesJobsMutex.withLock {
|
||||
if (this@enableRemoveOnCompletion === statesJobs[state]) {
|
||||
statesJobs.remove(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
||||
launch { statePerformer(it) }
|
||||
statesJobsMutex.withLock {
|
||||
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
|
||||
|
||||
statesJobs[it] = launch { statePerformer(it) }.apply { enableRemoveOnCompletion(it) }
|
||||
}
|
||||
}
|
||||
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) {
|
||||
statesJobsMutex.withLock {
|
||||
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
|
||||
}
|
||||
updatesFlows.remove(it.context)
|
||||
}
|
||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { (old, new) ->
|
||||
statesJobsMutex.withLock {
|
||||
runCatchingSafely { statesJobs.remove(old) ?.cancel() }
|
||||
runCatchingSafely { statesJobs.remove(new) ?.cancel() }
|
||||
statesJobs[new] = launch { statePerformer(new) }.apply { enableRemoveOnCompletion(new) }
|
||||
}
|
||||
if (old.context != new.context) {
|
||||
updatesFlows.remove(old.context)
|
||||
}
|
||||
launch { statePerformer(new) }
|
||||
}
|
||||
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) {
|
||||
updatesFlows.remove(it.context)
|
||||
}
|
||||
|
||||
statesManager.getActiveStates().forEach {
|
||||
launch { statePerformer(it) }
|
||||
statesJobsMutex.withLock {
|
||||
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
|
||||
|
||||
statesJobs[it] = launch { statePerformer(it) }.apply { enableRemoveOnCompletion(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@ suspend inline fun <reified O> BehaviourContext.waitCallbackQueries(
|
||||
initRequest,
|
||||
errorFactory
|
||||
) {
|
||||
(it.callbackQueryUpdateOrNull() ?.data as O).let(::listOfNotNull)
|
||||
(it.callbackQueryUpdateOrNull() ?.data as? O).let(::listOfNotNull)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class KtorRequestsExecutor(
|
||||
client: HttpClient = HttpClient(),
|
||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||
excludeDefaultFactories: Boolean = false,
|
||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter(),
|
||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||
private val jsonFormatter: Json = nonstrictJsonFormat,
|
||||
private val pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
@@ -51,7 +51,7 @@ class KtorRequestsExecutor(
|
||||
override suspend fun <T : Any> execute(request: Request<T>): T {
|
||||
return runCatchingSafely {
|
||||
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
|
||||
requestsLimiter.limit {
|
||||
requestsLimiter.limit(request) {
|
||||
var result: T? = null
|
||||
lateinit var factoryHandledRequest: KtorCallFactory
|
||||
for (potentialFactory in callsFactories) {
|
||||
@@ -111,7 +111,7 @@ class KtorRequestsExecutorBuilder(
|
||||
var client: HttpClient = HttpClient()
|
||||
var callsFactories: List<KtorCallFactory> = emptyList()
|
||||
var excludeDefaultFactories: Boolean = false
|
||||
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter()
|
||||
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter
|
||||
var jsonFormatter: Json = nonstrictJsonFormat
|
||||
|
||||
fun build() = KtorRequestsExecutor(telegramAPIUrlsKeeper, client, callsFactories, excludeDefaultFactories, requestsLimiter, jsonFormatter)
|
||||
|
||||
@@ -1,66 +1,18 @@
|
||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException
|
||||
import dev.inmo.tgbotapi.types.MilliSeconds
|
||||
import dev.inmo.tgbotapi.types.RetryAfterError
|
||||
import io.ktor.client.plugins.ClientRequestException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
/**
|
||||
* This limiter will limit requests only after getting a [RetryAfterError] or [ClientRequestException] with
|
||||
* [HttpStatusCode.TooManyRequests] status code. Important thing is that in case if some of block has been blocked, all
|
||||
* the others will wait until it will be possible to be called
|
||||
*
|
||||
* @param defaultTooManyRequestsDelay This parameter will be used in case of getting [ClientRequestException] with
|
||||
* [HttpStatusCode.TooManyRequests] as a parameter for delay like it would be [TooMuchRequestsException]. The reason of
|
||||
* it is that in [ClientRequestException] there is no information about required delay between requests
|
||||
* Simple limiter which will lock any request when [TooMuchRequestsExceptions] is thrown and rerun request after lock time
|
||||
*/
|
||||
class ExceptionsOnlyLimiter(
|
||||
private val defaultTooManyRequestsDelay: MilliSeconds = 1000L
|
||||
) : RequestLimiter {
|
||||
private val lockState = MutableStateFlow(false)
|
||||
private suspend fun lock(timeMillis: MilliSeconds) {
|
||||
try {
|
||||
safely {
|
||||
lockState.emit(true)
|
||||
delay(timeMillis)
|
||||
}
|
||||
} finally {
|
||||
lockState.emit(false)
|
||||
}
|
||||
}
|
||||
|
||||
object ExceptionsOnlyLimiter : RequestLimiter {
|
||||
override suspend fun <T> limit(block: suspend () -> T): T {
|
||||
while (true) {
|
||||
lockState.first { !it }
|
||||
var throwable: Throwable? = null
|
||||
val result = safely({
|
||||
throwable = when (it) {
|
||||
is TooMuchRequestsException -> {
|
||||
lock(it.retryAfter.leftToRetry)
|
||||
it
|
||||
}
|
||||
is ClientRequestException -> {
|
||||
if (it.response.status == HttpStatusCode.TooManyRequests) {
|
||||
lock(defaultTooManyRequestsDelay)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
it
|
||||
}
|
||||
else -> throw it
|
||||
}
|
||||
null
|
||||
}) {
|
||||
block()
|
||||
}
|
||||
if (throwable == null) {
|
||||
return result!!
|
||||
}
|
||||
return try {
|
||||
block()
|
||||
} catch (e: TooMuchRequestsException) {
|
||||
delay(e.retryAfter.leftToRetry)
|
||||
limit(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||
|
||||
object NoLimitsLimiter : RequestLimiter {
|
||||
override suspend fun <T> limit(block: suspend () -> T): T = block()
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
|
||||
interface RequestLimiter {
|
||||
/**
|
||||
* Use limit for working of block (like delay between or after, for example)
|
||||
*/
|
||||
suspend fun <T> limit(block: suspend () -> T): T
|
||||
|
||||
suspend fun <T : Any> limit(request: Request<T>, block: suspend () -> T) = limit(block)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,13 @@ value class ChatIdWithThreadId(val chatIdWithThreadId: Pair<Identifier, MessageT
|
||||
val ChatIdentifier.threadId: MessageThreadId?
|
||||
get() = (this as? IdChatIdentifier) ?.threadId
|
||||
|
||||
fun IdChatIdentifier.toChatId() = when (this) {
|
||||
is ChatId -> this
|
||||
is ChatIdWithThreadId -> ChatId(chatId)
|
||||
}
|
||||
|
||||
fun IdChatIdentifier.toChatWithThreadId(threadId: MessageThreadId) = IdChatIdentifier(chatId, threadId)
|
||||
|
||||
/**
|
||||
* https://core.telegram.org/bots/api#formatting-options
|
||||
*/
|
||||
@@ -69,9 +76,9 @@ val UserId.userLink: String
|
||||
val User.link: String
|
||||
get() = id.userLink
|
||||
|
||||
typealias UserId = IdChatIdentifier
|
||||
typealias UserId = ChatId
|
||||
|
||||
fun Identifier.toChatId(): IdChatIdentifier = ChatId(this)
|
||||
fun Identifier.toChatId(): ChatId = ChatId(this)
|
||||
fun Int.toChatId(): IdChatIdentifier = toLong().toChatId()
|
||||
fun Byte.toChatId(): IdChatIdentifier = toLong().toChatId()
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ data class ExtendedGroupChatImpl(
|
||||
@Serializable
|
||||
data class ExtendedPrivateChatImpl(
|
||||
@SerialName(idField)
|
||||
override val id: IdChatIdentifier,
|
||||
override val id: UserId,
|
||||
@SerialName(photoField)
|
||||
override val chatPhoto: ChatPhoto? = null,
|
||||
@SerialName(usernameField)
|
||||
|
||||
@@ -18,7 +18,7 @@ data class GroupChatImpl(
|
||||
@Serializable
|
||||
data class PrivateChatImpl(
|
||||
@SerialName(idField)
|
||||
override val id: IdChatIdentifier,
|
||||
override val id: UserId,
|
||||
@SerialName(usernameField)
|
||||
override val username: Username? = null,
|
||||
@SerialName(firstNameField)
|
||||
|
||||
@@ -42,6 +42,7 @@ sealed interface Sticker : TelegramMediaFile, SizedMediaFile, ThumbedMediaFile {
|
||||
get() = false
|
||||
}
|
||||
|
||||
@OptIn(RiskFeature::class)
|
||||
object StickerSerializer : KSerializer<Sticker> {
|
||||
override val descriptor: SerialDescriptor = StickerSurrogate.serializer().descriptor
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package dev.inmo.tgbotapi.types.message.abstracts
|
||||
|
||||
import dev.inmo.tgbotapi.types.message.content.MessageContent
|
||||
|
||||
sealed interface CommonMessage<T: MessageContent> : Message,
|
||||
sealed interface CommonMessage<out T: MessageContent> : Message,
|
||||
PossiblyForwardedMessage,
|
||||
PossiblyEditedMessage,
|
||||
PossiblyReplyMessage,
|
||||
|
||||
@@ -2,7 +2,7 @@ package dev.inmo.tgbotapi.types.message.abstracts
|
||||
|
||||
import dev.inmo.tgbotapi.types.message.content.MessageContent
|
||||
|
||||
interface ContentMessage<T: MessageContent>: Message {
|
||||
interface ContentMessage<out T: MessageContent>: Message {
|
||||
val hasProtectedContent: Boolean
|
||||
val content: T
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@ package dev.inmo.tgbotapi.types.message.abstracts
|
||||
import dev.inmo.tgbotapi.types.MediaGroupIdentifier
|
||||
import dev.inmo.tgbotapi.types.message.content.MessageContent
|
||||
|
||||
interface PossiblyMediaGroupMessage<T : MessageContent> : ContentMessage<T> {
|
||||
interface PossiblyMediaGroupMessage<out T : MessageContent> : ContentMessage<T> {
|
||||
val mediaGroupId: MediaGroupIdentifier?
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ package dev.inmo.tgbotapi.types.message.abstracts
|
||||
|
||||
import dev.inmo.tgbotapi.types.message.content.MessageContent
|
||||
|
||||
sealed interface PossiblySentViaBotCommonMessage<T: MessageContent> : CommonMessage<T>, PossiblySentViaBot
|
||||
sealed interface PossiblySentViaBotCommonMessage<out T: MessageContent> : CommonMessage<T>, PossiblySentViaBot
|
||||
|
||||
@@ -7,39 +7,61 @@ import dev.inmo.tgbotapi.types.message.textsources.link
|
||||
import io.ktor.http.encodeURLQueryComponent
|
||||
|
||||
|
||||
fun makeUsernameLink(username: String) = "$internalLinkBeginning/$username"
|
||||
fun makeUsernameLink(username: String, threadId: MessageThreadId? = null) = "$internalLinkBeginning/$username${threadId ?.let { "/$it" } ?: ""}"
|
||||
fun makeChatLink(identifier: Identifier, threadId: MessageThreadId? = null) = identifier.toString().replace(
|
||||
linkIdRedundantPartRegex,
|
||||
""
|
||||
).let { bareId ->
|
||||
"$internalLinkBeginning/c/$bareId${threadId ?.let { "/$it" } ?: ""}"
|
||||
}
|
||||
fun makeUsernameDeepLinkPrefix(username: String) = "${makeUsernameLink(username)}?start="
|
||||
fun makeUsernameStartattachPrefix(username: String) = "$internalLinkBeginning/$username?startattach"
|
||||
fun makeUsernameStartattachLink(username: String, data: String? = null) = "${makeUsernameStartattachPrefix(username)}${data?.let { "=$it" } ?: ""}"
|
||||
inline val Username.link
|
||||
get() = makeUsernameLink(usernameWithoutAt)
|
||||
val IdChatIdentifier.link: String
|
||||
get() = makeChatLink(chatId, threadId)
|
||||
fun ChatId.link(threadId: MessageThreadId?) = makeChatLink(chatId, threadId)
|
||||
inline fun Username.link(threadId: MessageThreadId?) = makeUsernameLink(usernameWithoutAt, threadId)
|
||||
inline val Username.deepLinkPrefix
|
||||
get() = makeUsernameDeepLinkPrefix(usernameWithoutAt)
|
||||
inline val Username.startattachPrefix
|
||||
get() = makeUsernameStartattachPrefix(usernameWithoutAt)
|
||||
inline fun makeLink(username: Username) = username.link
|
||||
inline fun makeLink(username: Username, threadId: MessageThreadId? = null) = username.link(threadId)
|
||||
inline fun makeTelegramDeepLink(username: String, startParameter: String) = "${makeUsernameDeepLinkPrefix(username)}$startParameter".encodeURLQueryComponent()
|
||||
inline fun makeTelegramStartattach(username: String, data: String? = null) = makeUsernameStartattachLink(username, data)
|
||||
inline fun makeDeepLink(username: Username, startParameter: String) = makeTelegramDeepLink(username.usernameWithoutAt, startParameter)
|
||||
inline fun makeTelegramDeepLink(username: Username, startParameter: String) = makeDeepLink(username, startParameter)
|
||||
inline fun makeTelegramStartattach(username: Username, data: String? = null) = makeTelegramStartattach(username.usernameWithoutAt, data)
|
||||
|
||||
fun makeLinkToMessage(
|
||||
username: String,
|
||||
messageId: MessageId
|
||||
): String = "$internalLinkBeginning/$username/$messageId"
|
||||
fun makeLinkToMessage(
|
||||
username: Username,
|
||||
messageId: MessageId
|
||||
): String = makeLinkToMessage(username.username, messageId)
|
||||
fun makeLinkToMessage(
|
||||
chat: UsernameChat,
|
||||
messageId: MessageId
|
||||
): String? = chat.username ?.let { makeLinkToMessage(it, messageId) }
|
||||
|
||||
private val linkIdRedundantPartRegex = Regex("^-100")
|
||||
private val usernameBeginSymbolRegex = Regex("^@")
|
||||
|
||||
fun makeLinkToMessage(
|
||||
username: String,
|
||||
messageId: MessageId,
|
||||
threadId: MessageThreadId? = null
|
||||
): String = "${makeUsernameLink(username, threadId)}/$messageId"
|
||||
fun makeLinkToMessage(
|
||||
username: Username,
|
||||
messageId: MessageId,
|
||||
threadId: MessageThreadId? = null
|
||||
): String = makeLinkToMessage(username.username, messageId, threadId)
|
||||
fun makeLinkToMessage(
|
||||
chatId: Identifier,
|
||||
messageId: MessageId,
|
||||
threadId: MessageThreadId? = null
|
||||
): String = chatId.toString().replace(
|
||||
linkIdRedundantPartRegex,
|
||||
""
|
||||
).let { bareId ->
|
||||
"$internalLinkBeginning/c/$bareId/${threadId ?.let { "$it/" } ?: ""}$messageId"
|
||||
}
|
||||
fun makeLinkToMessage(
|
||||
chatId: IdChatIdentifier,
|
||||
messageId: MessageId,
|
||||
): String = makeLinkToMessage(chatId.chatId, messageId, chatId.threadId)
|
||||
|
||||
/**
|
||||
* Link which can be used as by any user to get access to [Message]. Returns null in case when there are no
|
||||
* known way to build link (for [PrivateChat]s, for example)
|
||||
@@ -49,16 +71,10 @@ fun makeLinkToMessage(
|
||||
messageId: MessageId
|
||||
): String? {
|
||||
return when {
|
||||
chat is UsernameChat && chat.username != null -> {
|
||||
"$internalLinkBeginning/${chat.username ?.username ?.replace(
|
||||
usernameBeginSymbolRegex, "")}/$messageId"
|
||||
}
|
||||
chat !is PrivateChat -> chat.id.chatId.toString().replace(
|
||||
linkIdRedundantPartRegex,
|
||||
""
|
||||
).let { bareId ->
|
||||
"$internalLinkBeginning/c/$bareId/$messageId"
|
||||
}
|
||||
chat is UsernameChat && chat.username != null -> chat.username ?.let {
|
||||
makeLinkToMessage(it, messageId, chat.id.threadId)
|
||||
} ?: makeLinkToMessage(chat.id, messageId)
|
||||
chat !is PrivateChat -> makeLinkToMessage(chat.id, messageId)
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
@@ -79,7 +95,7 @@ val Message.link: String?
|
||||
val Chat.link: String?
|
||||
get() {
|
||||
if (this is UsernameChat) {
|
||||
username ?.link
|
||||
username ?.link ?: id.link
|
||||
}
|
||||
if (this is ExtendedPublicChat) {
|
||||
inviteLink ?.let { return it }
|
||||
|
||||
@@ -200,7 +200,7 @@ infix fun String.mention(parseMode: ParseMode): String = when (parseMode) {
|
||||
is MarkdownV2 -> mentionMarkdownV2()
|
||||
}
|
||||
|
||||
infix fun Pair<String, IdChatIdentifier>.mention(parseMode: ParseMode): String = when (parseMode) {
|
||||
infix fun Pair<String, UserId>.mention(parseMode: ParseMode): String = when (parseMode) {
|
||||
is HTML -> first.textMentionHTML(second)
|
||||
is Markdown -> first.textMentionMarkdown(second)
|
||||
is MarkdownV2 -> first.textMentionMarkdownV2(second)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package dev.inmo.tgbotapi.extensions.utils.updates.retrieving
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.launchSafely
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.utils.updates.convertWithMediaGroupUpdates
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.PossiblyMediaGroupMessage
|
||||
import dev.inmo.tgbotapi.types.update.abstracts.BaseMessageUpdate
|
||||
@@ -29,15 +31,18 @@ fun CoroutineScope.updateHandlerWithMediaGroupsAdaptation(
|
||||
)
|
||||
|
||||
launch {
|
||||
launch {
|
||||
launchSafelyWithoutExceptions {
|
||||
for (update in updatesChannel) {
|
||||
when (val data = update.data) {
|
||||
is PossiblyMediaGroupMessage<*> -> mediaGroupChannel.send("${data.mediaGroupId}${update::class.simpleName}" to update as BaseMessageUpdate)
|
||||
val data = update.data
|
||||
when {
|
||||
data is PossiblyMediaGroupMessage<*> && data.mediaGroupId != null -> {
|
||||
mediaGroupChannel.send("${data.mediaGroupId}${update::class.simpleName}" to update as BaseMessageUpdate)
|
||||
}
|
||||
else -> output(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
launchSafelyWithoutExceptions {
|
||||
for ((_, mediaGroup) in mediaGroupAccumulatedChannel) {
|
||||
mediaGroup.convertWithMediaGroupUpdates().forEach {
|
||||
output(it)
|
||||
|
||||
@@ -41,10 +41,9 @@ fun Route.includeWebhookHandlingInRoute(
|
||||
post {
|
||||
try {
|
||||
runCatchingSafely {
|
||||
val asJson = nonstrictJsonFormat.parseToJsonElement(call.receiveText())
|
||||
val update = nonstrictJsonFormat.decodeFromJsonElement(
|
||||
val update = nonstrictJsonFormat.decodeFromString(
|
||||
UpdateDeserializationStrategy,
|
||||
asJson
|
||||
call.receiveText()
|
||||
)
|
||||
transformer(update)
|
||||
}.onSuccess {
|
||||
|
||||
Reference in New Issue
Block a user