mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2025-12-29 01:19:24 +00:00
Compare commits
7 Commits
0.38.12
...
08ef146daa
| Author | SHA1 | Date | |
|---|---|---|---|
| 08ef146daa | |||
| a404a6c59c | |||
| 1089c716f3 | |||
| 81ad55b19f | |||
| 863c872f35 | |||
| ece6a917ed | |||
| 12248aa702 |
@@ -1,5 +1,11 @@
|
|||||||
# TelegramBotAPI changelog
|
# TelegramBotAPI changelog
|
||||||
|
|
||||||
|
## 0.38.13
|
||||||
|
|
||||||
|
* `Core`:
|
||||||
|
* Fixes in `mention` creation
|
||||||
|
* Deprecate `StorageFileInfo`
|
||||||
|
|
||||||
## 0.38.12
|
## 0.38.12
|
||||||
|
|
||||||
* `Common`:
|
* `Common`:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ klock_version=2.7.0
|
|||||||
uuid_version=0.4.0
|
uuid_version=0.4.0
|
||||||
ktor_version=1.6.8
|
ktor_version=1.6.8
|
||||||
|
|
||||||
micro_utils_version=0.9.19
|
micro_utils_version=0.9.20
|
||||||
|
|
||||||
javax_activation_version=1.1.1
|
javax_activation_version=1.1.1
|
||||||
|
|
||||||
@@ -20,6 +20,6 @@ javax_activation_version=1.1.1
|
|||||||
dokka_version=1.6.10
|
dokka_version=1.6.10
|
||||||
|
|
||||||
library_group=dev.inmo
|
library_group=dev.inmo
|
||||||
library_version=0.38.12
|
library_version=0.38.13
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package dev.inmo.tgbotapi.bot.Ktor
|
package dev.inmo.tgbotapi.bot.Ktor
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.Optional
|
||||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
|||||||
import dev.inmo.tgbotapi.bot.Ktor.base.*
|
import dev.inmo.tgbotapi.bot.Ktor.base.*
|
||||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||||
|
import dev.inmo.tgbotapi.bot.ktor.KtorPipelineStepsHolder
|
||||||
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
||||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||||
@@ -56,7 +57,8 @@ class KtorRequestsExecutor(
|
|||||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||||
excludeDefaultFactories: Boolean = false,
|
excludeDefaultFactories: Boolean = false,
|
||||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter(),
|
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter(),
|
||||||
private val jsonFormatter: Json = nonstrictJsonFormat
|
private val jsonFormatter: Json = nonstrictJsonFormat,
|
||||||
|
private val pipelineStepsHolder: KtorPipelineStepsHolder = TODO()
|
||||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||||
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
||||||
if (!excludeDefaultFactories) {
|
if (!excludeDefaultFactories) {
|
||||||
@@ -73,37 +75,51 @@ class KtorRequestsExecutor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun <T : Any> execute(request: Request<T>): T {
|
override suspend fun <T : Any> execute(request: Request<T>): T {
|
||||||
return safely(
|
return runCatching {
|
||||||
{ e ->
|
safely(
|
||||||
throw if (e is ClientRequestException) {
|
{ e ->
|
||||||
val content = e.response.readText()
|
pipelineStepsHolder.onRequestException(request, e) ?.let { return@safely it }
|
||||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
|
||||||
newRequestException(
|
|
||||||
responseObject,
|
|
||||||
content,
|
|
||||||
"Can't get result object from $content"
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
requestsLimiter.limit {
|
|
||||||
var result: T? = null
|
|
||||||
for (potentialFactory in callsFactories) {
|
|
||||||
result = potentialFactory.makeCall(
|
|
||||||
client,
|
|
||||||
telegramAPIUrlsKeeper,
|
|
||||||
request,
|
|
||||||
jsonFormatter
|
|
||||||
)
|
|
||||||
if (result != null) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result ?: error("Can't execute request: $request")
|
throw if (e is ClientRequestException) {
|
||||||
|
val content = e.response.readText()
|
||||||
|
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||||
|
newRequestException(
|
||||||
|
responseObject,
|
||||||
|
content,
|
||||||
|
"Can't get result object from $content"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
e
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
|
||||||
|
requestsLimiter.limit {
|
||||||
|
var result: T? = null
|
||||||
|
lateinit var factoryHandledRequest: KtorCallFactory
|
||||||
|
for (potentialFactory in callsFactories) {
|
||||||
|
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
|
||||||
|
result = potentialFactory.makeCall(
|
||||||
|
client,
|
||||||
|
telegramAPIUrlsKeeper,
|
||||||
|
request,
|
||||||
|
jsonFormatter
|
||||||
|
)
|
||||||
|
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
|
||||||
|
if (result != null) {
|
||||||
|
factoryHandledRequest = potentialFactory
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result ?.let {
|
||||||
|
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
|
||||||
|
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}.let {
|
||||||
|
pipelineStepsHolder.onRequestReturnResult(it, request, callsFactories)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.tgbotapi.bot.Ktor.base
|
package dev.inmo.tgbotapi.bot.Ktor.base
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
import dev.inmo.tgbotapi.bot.Ktor.KtorCallFactory
|
import dev.inmo.tgbotapi.bot.Ktor.KtorCallFactory
|
||||||
import dev.inmo.tgbotapi.requests.DownloadFileStream
|
import dev.inmo.tgbotapi.requests.DownloadFileStream
|
||||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||||
@@ -25,13 +25,16 @@ object DownloadFileChannelRequestCallFactory : KtorCallFactory {
|
|||||||
val fullUrl = urlsKeeper.createFileLinkUrl(it.filePath)
|
val fullUrl = urlsKeeper.createFileLinkUrl(it.filePath)
|
||||||
|
|
||||||
ByteReadChannelAllocator {
|
ByteReadChannelAllocator {
|
||||||
val scope = CoroutineScope(coroutineContext)
|
val scope = CoroutineScope(currentCoroutineContext() + SupervisorJob())
|
||||||
val outChannel = ByteChannel()
|
val outChannel = ByteChannel()
|
||||||
scope.launchSafelyWithoutExceptions {
|
scope.launch {
|
||||||
client.get<HttpStatement>(fullUrl).execute { httpResponse ->
|
runCatchingSafely {
|
||||||
val channel: ByteReadChannel = httpResponse.receive()
|
client.get<HttpStatement>(fullUrl).execute { httpResponse ->
|
||||||
channel.copyAndClose(outChannel)
|
val channel: ByteReadChannel = httpResponse.receive()
|
||||||
|
channel.copyAndClose(outChannel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
scope.cancel()
|
||||||
}
|
}
|
||||||
outChannel
|
outChannel
|
||||||
} as T
|
} as T
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package dev.inmo.tgbotapi.bot.ktor
|
||||||
|
|
||||||
|
import dev.inmo.tgbotapi.bot.Ktor.KtorCallFactory
|
||||||
|
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||||
|
|
||||||
|
interface KtorPipelineStepsHolder {
|
||||||
|
/**
|
||||||
|
* Will be called when any exception will happen due to the [request] handling. If returns value - that value
|
||||||
|
* will be returned from [dev.inmo.tgbotapi.bot.RequestsExecutor.execute] instead
|
||||||
|
*/
|
||||||
|
suspend fun <T: Any> onRequestException(
|
||||||
|
request: Request<T>,
|
||||||
|
t: Throwable
|
||||||
|
): T? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will always be called before requests executor will check all [callsFactories] for an opportunity to make call of
|
||||||
|
* [request]
|
||||||
|
*/
|
||||||
|
suspend fun onBeforeSearchCallFactory(
|
||||||
|
request: Request<*>,
|
||||||
|
callsFactories: List<KtorCallFactory>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will always be called before [potentialFactory] will try to make [request]
|
||||||
|
*/
|
||||||
|
suspend fun onBeforeCallFactoryMakeCall(
|
||||||
|
request: Request<*>,
|
||||||
|
potentialFactory: KtorCallFactory
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will always be called after [potentialFactory] has tried to make [request] and got some [result]. If returns
|
||||||
|
* value - that value will be returned from [dev.inmo.tgbotapi.bot.RequestsExecutor.execute] instead
|
||||||
|
*/
|
||||||
|
suspend fun <T: Any> onAfterCallFactoryMakeCall(
|
||||||
|
result: T?,
|
||||||
|
request: Request<T>,
|
||||||
|
potentialFactory: KtorCallFactory
|
||||||
|
): T? = result
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be called when [resultCallFactory] is the [KtorCallFactory] from [callsFactories] which has successfully
|
||||||
|
* handled [request] and returned [result]. If returns value - that value will be returned from
|
||||||
|
* [dev.inmo.tgbotapi.bot.RequestsExecutor.execute] instead
|
||||||
|
*/
|
||||||
|
suspend fun <T: Any> onRequestResultPresented(
|
||||||
|
result: T,
|
||||||
|
request: Request<T>,
|
||||||
|
resultCallFactory: KtorCallFactory,
|
||||||
|
callsFactories: List<KtorCallFactory>
|
||||||
|
): T? = result
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be called when there is no [KtorCallFactory] from [callsFactories] which may handle [request]. If returns
|
||||||
|
* value - that value will be returned from [dev.inmo.tgbotapi.bot.RequestsExecutor.execute] instead
|
||||||
|
*/
|
||||||
|
suspend fun <T: Any> onRequestResultAbsent(
|
||||||
|
request: Request<T>,
|
||||||
|
callsFactories: List<KtorCallFactory>
|
||||||
|
): T? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This step will be called when the [result] has been retrieved (or exception has happened). If returns value -
|
||||||
|
* that value will be returned from [dev.inmo.tgbotapi.bot.RequestsExecutor.execute] instead
|
||||||
|
*/
|
||||||
|
suspend fun <T: Any> onRequestReturnResult(
|
||||||
|
result: Result<T>,
|
||||||
|
request: Request<T>,
|
||||||
|
callsFactories: List<KtorCallFactory>
|
||||||
|
): T = result.getOrThrow()
|
||||||
|
}
|
||||||
@@ -61,9 +61,9 @@ object InputFileSerializer : KSerializer<InputFile> {
|
|||||||
@Serializable(InputFileSerializer::class)
|
@Serializable(InputFileSerializer::class)
|
||||||
data class MultipartFile (
|
data class MultipartFile (
|
||||||
val file: StorageFile,
|
val file: StorageFile,
|
||||||
val filename: String = file.storageFileInfo.fileName
|
val filename: String = file.fileName
|
||||||
) : InputFile() {
|
) : InputFile() {
|
||||||
override val fileId: String = file.storageFileInfo.generateCustomName()
|
override val fileId: String = file.generateCustomName()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE", "unused")
|
@Suppress("NOTHING_TO_INLINE", "unused")
|
||||||
|
|||||||
@@ -33,7 +33,12 @@ inline fun mention(parts: TextSourcesList, id: Identifier) = mention(parts, User
|
|||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun Identifier.mention(parts: TextSourcesList) = mention(parts, this)
|
inline fun Identifier.mention(parts: TextSourcesList) = mention(parts, this)
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun mention(user: User, vararg parts: TextSource) = mention(parts.toList(), user)
|
inline fun mention(user: User, vararg parts: TextSource) = mention(
|
||||||
|
textSourcesOrElseTextSource(parts.toList()) {
|
||||||
|
RegularTextSource("${user.lastName} ${user.firstName}")
|
||||||
|
},
|
||||||
|
user
|
||||||
|
)
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun mention(text: String, user: User) = mention(user, regular(text))
|
inline fun mention(text: String, user: User) = mention(user, regular(text))
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package dev.inmo.tgbotapi.types.MessageEntity.textsources
|
||||||
|
|
||||||
|
import dev.inmo.tgbotapi.utils.RiskFeature
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
@RiskFeature
|
||||||
|
inline fun textSourcesOrElse(
|
||||||
|
textSources: TextSourcesList,
|
||||||
|
block: () -> TextSourcesList
|
||||||
|
): TextSourcesList = textSources.takeIf { it.isNotEmpty() } ?: block()
|
||||||
|
|
||||||
|
@RiskFeature
|
||||||
|
inline fun textSourcesOrElseTextSource(
|
||||||
|
textSources: TextSourcesList,
|
||||||
|
block: () -> TextSource
|
||||||
|
): TextSourcesList = textSources.takeIf { it.isNotEmpty() } ?: listOf(block())
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package dev.inmo.tgbotapi.utils
|
package dev.inmo.tgbotapi.utils
|
||||||
|
|
||||||
import com.benasher44.uuid.uuid4
|
import com.benasher44.uuid.uuid4
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import dev.inmo.micro_utils.common.filename
|
||||||
import io.ktor.utils.io.*
|
import io.ktor.utils.io.*
|
||||||
import io.ktor.utils.io.core.ByteReadPacket
|
import io.ktor.utils.io.core.ByteReadPacket
|
||||||
import io.ktor.utils.io.core.Input
|
import io.ktor.utils.io.core.Input
|
||||||
@@ -13,6 +15,7 @@ import kotlinx.serialization.Serializable
|
|||||||
* @param fileName This filename will be used in telegram system as name of file
|
* @param fileName This filename will be used in telegram system as name of file
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@Deprecated("Will be removed soon")
|
||||||
data class StorageFileInfo(
|
data class StorageFileInfo(
|
||||||
val fileName: String
|
val fileName: String
|
||||||
) {
|
) {
|
||||||
@@ -25,18 +28,35 @@ data class StorageFileInfo(
|
|||||||
/**
|
/**
|
||||||
* Contains info about file, which potentially can be sent to telegram system.
|
* Contains info about file, which potentially can be sent to telegram system.
|
||||||
*
|
*
|
||||||
* @param storageFileInfo Information about this file
|
* @param fileName Filename
|
||||||
* @param inputSource Lambda which able to allocate [Input] for uploading/manipulating data
|
* @param inputSource Lambda which able to allocate [Input] for uploading/manipulating data
|
||||||
*
|
*
|
||||||
* @see StorageFileInfo
|
* @see StorageFileInfo
|
||||||
* @see asStorageFile
|
* @see asStorageFile
|
||||||
*/
|
*/
|
||||||
data class StorageFile(
|
data class StorageFile(
|
||||||
val storageFileInfo: StorageFileInfo,
|
val fileName: String,
|
||||||
private val inputSource: () -> Input
|
private val inputSource: () -> Input
|
||||||
) {
|
) {
|
||||||
val input: Input
|
val input: Input
|
||||||
get() = inputSource()
|
get() = inputSource()
|
||||||
|
@Deprecated("This field will be removed soon. Use fileName instead of StorageFileInfo")
|
||||||
|
val storageFileInfo: StorageFileInfo
|
||||||
|
get() = StorageFileInfo(fileName)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This methods is required for random generation of name for keeping warranties about unique file name
|
||||||
|
*/
|
||||||
|
fun generateCustomName() = "${uuid4()}.${fileName.fileExtension}"
|
||||||
|
|
||||||
|
@Deprecated("This constructor will be removed soon. Use constructor with fileName instead of StorageFileInfo")
|
||||||
|
constructor(
|
||||||
|
storageFileInfo: StorageFileInfo,
|
||||||
|
inputSource: () -> Input
|
||||||
|
) : this(
|
||||||
|
storageFileInfo.fileName,
|
||||||
|
inputSource
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
@@ -44,7 +64,7 @@ inline fun StorageFile(
|
|||||||
fileName: String,
|
fileName: String,
|
||||||
bytes: ByteArray
|
bytes: ByteArray
|
||||||
) = StorageFile(
|
) = StorageFile(
|
||||||
StorageFileInfo(fileName)
|
fileName
|
||||||
) {
|
) {
|
||||||
ByteReadPacket(bytes)
|
ByteReadPacket(bytes)
|
||||||
}
|
}
|
||||||
@@ -54,8 +74,8 @@ suspend inline fun StorageFile(
|
|||||||
fileName: String,
|
fileName: String,
|
||||||
byteReadChannel: ByteReadChannel
|
byteReadChannel: ByteReadChannel
|
||||||
) = StorageFile(
|
) = StorageFile(
|
||||||
StorageFileInfo(fileName),
|
fileName,
|
||||||
byteReadChannel.asInput().let { { it } }
|
inputSource = byteReadChannel.asInput().let { { it } }
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE", "unused")
|
@Suppress("NOTHING_TO_INLINE", "unused")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.nio.file.Files
|
|||||||
fun StorageFile(
|
fun StorageFile(
|
||||||
file: File
|
file: File
|
||||||
) = StorageFile(
|
) = StorageFile(
|
||||||
StorageFileInfo(file.name)
|
file.name
|
||||||
) {
|
) {
|
||||||
file.inputStream().asInput()
|
file.inputStream().asInput()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user