1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2024-11-24 11:08:43 +00:00

Compare commits

...

9 Commits

14 changed files with 502 additions and 75 deletions

View File

@ -1,5 +1,13 @@
# TelegramBotAPI changelog
## 0.38.13
* `Core`:
* Fixes in `mention` creation
* Deprecate `StorageFileInfo`
* `BehaviourBuilder`:
* In the expectations a lot of `on*Message` extensions have been added (like `onContentMessage`). These extensions could be useful when with the `Content` its message info is important
## 0.38.12
* `Common`:

View File

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

View File

@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.toList
typealias CommonMessageToContentMapper<T> = suspend CommonMessage<T>.() -> T?
private suspend fun <O> BehaviourContext.waitCommonMessage(
private suspend fun <O> BehaviourContext.waitCommonContent(
count: Int = 1,
initRequest: Request<*>? = null,
includeMediaGroups: Boolean = true,
@ -77,7 +77,7 @@ private suspend inline fun <reified T : MessageContent> BehaviourContext.waitCon
noinline errorFactory: NullableRequestBuilder<*> = { null },
noinline filter: SimpleFilter<CommonMessage<T>>? = null,
noinline mapper: CommonMessageToContentMapper<T>? = null
) : List<T> = waitCommonMessage<T>(
) : List<T> = waitCommonContent<T>(
count,
initRequest,
includeMediaGroups,
@ -90,7 +90,8 @@ private suspend inline fun <reified T : MessageContent> BehaviourContext.waitCon
contentConverter(mapper)
).toList()
suspend fun BehaviourContext.waitContentMessage(
suspend fun BehaviourContext.waitContent(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,

View File

@ -0,0 +1,281 @@
@file:Suppress("unused")
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.utils.withContent
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.*
import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.types.message.content.media.*
import dev.inmo.tgbotapi.types.message.content.media.AudioMediaGroupContent
import dev.inmo.tgbotapi.types.message.content.media.DocumentMediaGroupContent
import dev.inmo.tgbotapi.types.message.content.media.MediaGroupContent
import dev.inmo.tgbotapi.types.message.content.media.VisualMediaGroupContent
import dev.inmo.tgbotapi.types.message.payments.InvoiceContent
import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.SentMediaGroupUpdate
import dev.inmo.tgbotapi.types.update.abstracts.BaseSentMessageUpdate
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
typealias CommonMessageToCommonMessageMapper<T> = suspend CommonMessage<T>.() -> CommonMessage<T>?
internal suspend fun <O : MessageContent> BehaviourContext.waitCommonMessage(
count: Int = 1,
initRequest: Request<*>? = null,
includeMediaGroups: Boolean = true,
errorFactory: NullableRequestBuilder<*> = { null },
filter: SimpleFilter<CommonMessage<MessageContent>>? = null,
mapper: suspend CommonMessage<MessageContent>.() -> CommonMessage<O>?
): Flow<CommonMessage<O>> = expectFlow(
initRequest,
count,
errorFactory
) {
val messages = when (it) {
is SentMediaGroupUpdate -> {
if (includeMediaGroups) {
it.data.map { it as CommonMessage<MessageContent> }
} else {
emptyList()
}
}
is BaseSentMessageUpdate -> listOf(it.data)
else -> return@expectFlow emptyList()
}
messages.mapNotNull { message ->
val asCommonMessage = message as CommonMessage<MessageContent>
if (filter == null || filter(asCommonMessage)) {
asCommonMessage.mapper()
} else {
null
}
}
}
internal inline fun <reified T : MessageContent> contentMessageConverter(
noinline mapper: CommonMessageToCommonMessageMapper<T>? = null
): suspend CommonMessage<MessageContent>.() -> CommonMessage<T>? = mapper ?.let {
{
if (content is T) {
@Suppress("UNCHECKED_CAST")
val message = (this as CommonMessage<T>)
safelyWithoutExceptions { mapper(message) }
} else {
null
}
}
} ?: {
@Suppress("UNCHECKED_CAST")
if (content is T) this as CommonMessage<T> else null
}
private suspend inline fun <reified T : MessageContent> BehaviourContext.waitContentMessage(
count: Int = 1,
initRequest: Request<*>? = null,
includeMediaGroups: Boolean = true,
noinline errorFactory: NullableRequestBuilder<*> = { null },
noinline filter: SimpleFilter<CommonMessage<T>>? = null,
noinline mapper: CommonMessageToCommonMessageMapper<T>? = null
) : List<CommonMessage<T>> = waitCommonMessage<T>(
count,
initRequest,
includeMediaGroups,
errorFactory,
filter ?.let {
{
it.withContent<T>() ?.let { filter(it) } == true
}
},
contentMessageConverter(mapper)
).toList()
suspend fun BehaviourContext.waitContentMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: SimpleFilter<CommonMessage<MessageContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<MessageContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitContactMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<ContactContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<ContactContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitDiceMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<DiceContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<DiceContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitGameMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<GameContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<GameContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitLocationMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<LocationContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<LocationContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitLiveLocationMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<LiveLocationContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<LiveLocationContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitStaticLocationMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<StaticLocationContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<StaticLocationContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitPollMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<PollContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<PollContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitTextMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<TextContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<TextContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitVenueMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<VenueContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<VenueContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitAudioMediaGroupContentMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: SimpleFilter<CommonMessage<AudioMediaGroupContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<AudioMediaGroupContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitDocumentMediaGroupContentMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: SimpleFilter<CommonMessage<DocumentMediaGroupContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<DocumentMediaGroupContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitMediaMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = false,
filter: SimpleFilter<CommonMessage<MediaContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<MediaContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitAnyMediaGroupContentMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: SimpleFilter<CommonMessage<MediaGroupContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<MediaGroupContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitVisualMediaGroupContentMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: SimpleFilter<CommonMessage<VisualMediaGroupContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<VisualMediaGroupContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitTextedMediaContentMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: SimpleFilter<CommonMessage<TextedMediaContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<TextedMediaContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitAnimationMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<AnimationContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<AnimationContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitAudioMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = false,
filter: SimpleFilter<CommonMessage<AudioContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<AudioContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitDocumentMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = false,
filter: SimpleFilter<CommonMessage<DocumentContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<DocumentContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitPhotoMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = false,
filter: SimpleFilter<CommonMessage<PhotoContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<PhotoContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitStickerMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<StickerContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<StickerContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitVideoMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = false,
filter: SimpleFilter<CommonMessage<VideoContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<VideoContent>? = null
) = waitContentMessage(count, initRequest, includeMediaGroups, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitVideoNoteMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<VideoNoteContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<VideoNoteContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitVoiceMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<VoiceContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<VoiceContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)
suspend fun BehaviourContext.waitInvoiceMessage(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: SimpleFilter<CommonMessage<InvoiceContent>>? = null,
mapper: CommonMessageToCommonMessageMapper<InvoiceContent>? = null
) = waitContentMessage(count, initRequest, false, errorFactory, filter, mapper)

View File

@ -24,7 +24,7 @@ import dev.inmo.tgbotapi.types.update.abstracts.Update
typealias CommonMessageFilter<T> = SimpleFilter<CommonMessage<T>>
inline fun <T : MessageContent> CommonMessageFilter(noinline block: CommonMessageFilter<T>) = block
internal suspend inline fun <BC : BehaviourContext, reified T : MessageContent> BC.onContent(
internal suspend inline fun <BC : BehaviourContext, reified T : MessageContent> BC.onContentMessageWithType(
noinline initialFilter: CommonMessageFilter<T>? = null,
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<T>, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<T>, Any> = ByChatMessageMarkerFactory,
@ -57,7 +57,7 @@ suspend fun <BC : BehaviourContext> BC.onContentMessage(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<MessageContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<MessageContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<MessageContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -81,7 +81,7 @@ suspend fun <BC : BehaviourContext> BC.onContact(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<ContactContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<ContactContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<ContactContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -105,7 +105,7 @@ suspend fun <BC : BehaviourContext> BC.onDice(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<DiceContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<DiceContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<DiceContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -129,7 +129,7 @@ suspend fun <BC : BehaviourContext> BC.onGame(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<GameContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<GameContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<GameContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -153,7 +153,7 @@ suspend fun <BC : BehaviourContext> BC.onLocation(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<LocationContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<LocationContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<LocationContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -177,7 +177,7 @@ suspend fun <BC : BehaviourContext> BC.onLiveLocation(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<LiveLocationContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<LiveLocationContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<LiveLocationContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -201,7 +201,7 @@ suspend fun <BC : BehaviourContext> BC.onStaticLocation(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<StaticLocationContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<StaticLocationContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<StaticLocationContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -225,7 +225,7 @@ suspend fun <BC : BehaviourContext> BC.onPoll(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<PollContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<PollContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<PollContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -249,7 +249,7 @@ suspend fun <BC : BehaviourContext> BC.onText(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -273,7 +273,7 @@ suspend fun <BC : BehaviourContext> BC.onVenue(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<VenueContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<VenueContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<VenueContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -297,7 +297,7 @@ suspend fun <BC : BehaviourContext> BC.onAudioMediaGroup(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<AudioMediaGroupContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<AudioMediaGroupContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<AudioMediaGroupContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -321,7 +321,7 @@ suspend fun <BC : BehaviourContext> BC.onDocumentMediaGroupContent(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<DocumentMediaGroupContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<DocumentMediaGroupContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<DocumentMediaGroupContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -345,7 +345,7 @@ suspend fun <BC : BehaviourContext> BC.onTextedMediaContent(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextedMediaContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextedMediaContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextedMediaContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -369,7 +369,7 @@ suspend fun <BC : BehaviourContext> BC.onMediaCollection(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<MediaCollectionContent<TelegramMediaFile>>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<MediaCollectionContent<TelegramMediaFile>>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<MediaCollectionContent<TelegramMediaFile>>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -393,7 +393,7 @@ suspend fun <BC : BehaviourContext> BC.onMedia(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<MediaContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<MediaContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<MediaContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -417,7 +417,7 @@ suspend fun <BC : BehaviourContext> BC.onAnimation(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<AnimationContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<AnimationContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<AnimationContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -441,7 +441,7 @@ suspend fun <BC : BehaviourContext> BC.onAudio(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<AudioContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<AudioContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<AudioContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -465,7 +465,7 @@ suspend fun <BC : BehaviourContext> BC.onDocument(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<DocumentContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<DocumentContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<DocumentContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -489,7 +489,7 @@ suspend fun <BC : BehaviourContext> BC.onPhoto(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<PhotoContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<PhotoContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<PhotoContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -513,7 +513,7 @@ suspend fun <BC : BehaviourContext> BC.onSticker(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<StickerContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<StickerContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<StickerContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -537,7 +537,7 @@ suspend fun <BC : BehaviourContext> BC.onVideo(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<VideoContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<VideoContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<VideoContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -561,7 +561,7 @@ suspend fun <BC : BehaviourContext> BC.onVideoNote(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<VideoNoteContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<VideoNoteContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<VideoNoteContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -585,7 +585,7 @@ suspend fun <BC : BehaviourContext> BC.onVoice(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<VoiceContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<VoiceContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<VoiceContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@ -609,7 +609,7 @@ suspend fun <BC : BehaviourContext> BC.onInvoice(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<InvoiceContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<InvoiceContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<InvoiceContent>>
) = onContent(
) = onContentMessageWithType(
initialFilter,
subcontextUpdatesFilter,
markerFactory,

View File

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

View File

@ -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 = KtorPipelineStepsHolder
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
if (!excludeDefaultFactories) {
@ -73,8 +75,11 @@ class KtorRequestsExecutor(
}
override suspend fun <T : Any> execute(request: Request<T>): T {
return safely(
return runCatching {
safely(
{ e ->
pipelineStepsHolder.onRequestException(request, e) ?.let { return@safely it }
throw if (e is ClientRequestException) {
val content = e.response.readText()
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
@ -86,25 +91,36 @@ class KtorRequestsExecutor(
} 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 ?: error("Can't execute request: $request")
result ?.let {
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
}
}
}.let {
pipelineStepsHolder.onRequestReturnResult(it, request, callsFactories)
}
}
override fun close() {

View File

@ -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,14 +25,17 @@ object DownloadFileChannelRequestCallFactory : KtorCallFactory {
val fullUrl = urlsKeeper.createFileLinkUrl(it.filePath)
ByteReadChannelAllocator {
val scope = CoroutineScope(coroutineContext)
val scope = CoroutineScope(currentCoroutineContext() + SupervisorJob())
val outChannel = ByteChannel()
scope.launchSafelyWithoutExceptions {
scope.launch {
runCatchingSafely {
client.get<HttpStatement>(fullUrl).execute { httpResponse ->
val channel: ByteReadChannel = httpResponse.receive()
channel.copyAndClose(outChannel)
}
}
scope.cancel()
}
outChannel
} as T
}

View File

@ -0,0 +1,75 @@
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()
companion object : KtorPipelineStepsHolder
}

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import java.nio.file.Files
fun StorageFile(
file: File
) = StorageFile(
StorageFileInfo(file.name)
file.name
) {
file.inputStream().asInput()
}