mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2025-12-07 14:55:55 +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
|
||||
|
||||
## 0.38.13
|
||||
|
||||
* `Core`:
|
||||
* Fixes in `mention` creation
|
||||
* Deprecate `StorageFileInfo`
|
||||
|
||||
## 0.38.12
|
||||
|
||||
* `Common`:
|
||||
|
||||
@@ -12,7 +12,7 @@ klock_version=2.7.0
|
||||
uuid_version=0.4.0
|
||||
ktor_version=1.6.8
|
||||
|
||||
micro_utils_version=0.9.19
|
||||
micro_utils_version=0.9.20
|
||||
|
||||
javax_activation_version=1.1.1
|
||||
|
||||
@@ -20,6 +20,6 @@ javax_activation_version=1.1.1
|
||||
dokka_version=1.6.10
|
||||
|
||||
library_group=dev.inmo
|
||||
library_version=0.38.12
|
||||
library_version=0.38.13
|
||||
|
||||
github_release_plugin_version=2.2.12
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.inmo.tgbotapi.bot.Ktor
|
||||
|
||||
import dev.inmo.micro_utils.common.Optional
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
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.TelegramBot
|
||||
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.RequestLimiter
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
@@ -56,7 +57,8 @@ class KtorRequestsExecutor(
|
||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||
excludeDefaultFactories: Boolean = false,
|
||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter(),
|
||||
private val jsonFormatter: Json = nonstrictJsonFormat
|
||||
private val jsonFormatter: Json = nonstrictJsonFormat,
|
||||
private val pipelineStepsHolder: KtorPipelineStepsHolder = TODO()
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
||||
if (!excludeDefaultFactories) {
|
||||
@@ -73,37 +75,51 @@ class KtorRequestsExecutor(
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> execute(request: Request<T>): T {
|
||||
return safely(
|
||||
{ e ->
|
||||
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
|
||||
}
|
||||
}
|
||||
) {
|
||||
requestsLimiter.limit {
|
||||
var result: T? = null
|
||||
for (potentialFactory in callsFactories) {
|
||||
result = potentialFactory.makeCall(
|
||||
client,
|
||||
telegramAPIUrlsKeeper,
|
||||
request,
|
||||
jsonFormatter
|
||||
)
|
||||
if (result != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return runCatching {
|
||||
safely(
|
||||
{ e ->
|
||||
pipelineStepsHolder.onRequestException(request, e) ?.let { return@safely it }
|
||||
|
||||
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
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.coroutines.*
|
||||
import dev.inmo.tgbotapi.bot.Ktor.KtorCallFactory
|
||||
import dev.inmo.tgbotapi.requests.DownloadFileStream
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
@@ -25,13 +25,16 @@ object DownloadFileChannelRequestCallFactory : KtorCallFactory {
|
||||
val fullUrl = urlsKeeper.createFileLinkUrl(it.filePath)
|
||||
|
||||
ByteReadChannelAllocator {
|
||||
val scope = CoroutineScope(coroutineContext)
|
||||
val scope = CoroutineScope(currentCoroutineContext() + SupervisorJob())
|
||||
val outChannel = ByteChannel()
|
||||
scope.launchSafelyWithoutExceptions {
|
||||
client.get<HttpStatement>(fullUrl).execute { httpResponse ->
|
||||
val channel: ByteReadChannel = httpResponse.receive()
|
||||
channel.copyAndClose(outChannel)
|
||||
scope.launch {
|
||||
runCatchingSafely {
|
||||
client.get<HttpStatement>(fullUrl).execute { httpResponse ->
|
||||
val channel: ByteReadChannel = httpResponse.receive()
|
||||
channel.copyAndClose(outChannel)
|
||||
}
|
||||
}
|
||||
scope.cancel()
|
||||
}
|
||||
outChannel
|
||||
} 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)
|
||||
data class MultipartFile (
|
||||
val file: StorageFile,
|
||||
val filename: String = file.storageFileInfo.fileName
|
||||
val filename: String = file.fileName
|
||||
) : InputFile() {
|
||||
override val fileId: String = file.storageFileInfo.generateCustomName()
|
||||
override val fileId: String = file.generateCustomName()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
@@ -33,7 +33,12 @@ inline fun mention(parts: TextSourcesList, id: Identifier) = mention(parts, User
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Identifier.mention(parts: TextSourcesList) = mention(parts, this)
|
||||
@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")
|
||||
inline fun mention(text: String, user: User) = mention(user, regular(text))
|
||||
@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
|
||||
|
||||
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.core.ByteReadPacket
|
||||
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
|
||||
*/
|
||||
@Serializable
|
||||
@Deprecated("Will be removed soon")
|
||||
data class StorageFileInfo(
|
||||
val fileName: String
|
||||
) {
|
||||
@@ -25,18 +28,35 @@ data class StorageFileInfo(
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @see StorageFileInfo
|
||||
* @see asStorageFile
|
||||
*/
|
||||
data class StorageFile(
|
||||
val storageFileInfo: StorageFileInfo,
|
||||
val fileName: String,
|
||||
private val inputSource: () -> Input
|
||||
) {
|
||||
val input: Input
|
||||
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")
|
||||
@@ -44,7 +64,7 @@ inline fun StorageFile(
|
||||
fileName: String,
|
||||
bytes: ByteArray
|
||||
) = StorageFile(
|
||||
StorageFileInfo(fileName)
|
||||
fileName
|
||||
) {
|
||||
ByteReadPacket(bytes)
|
||||
}
|
||||
@@ -54,8 +74,8 @@ suspend inline fun StorageFile(
|
||||
fileName: String,
|
||||
byteReadChannel: ByteReadChannel
|
||||
) = StorageFile(
|
||||
StorageFileInfo(fileName),
|
||||
byteReadChannel.asInput().let { { it } }
|
||||
fileName,
|
||||
inputSource = byteReadChannel.asInput().let { { it } }
|
||||
)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.nio.file.Files
|
||||
fun StorageFile(
|
||||
file: File
|
||||
) = StorageFile(
|
||||
StorageFileInfo(file.name)
|
||||
file.name
|
||||
) {
|
||||
file.inputStream().asInput()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user