diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatJoinRequest.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatJoinRequest.kt new file mode 100644 index 0000000000..f3efaaf195 --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatJoinRequest.kt @@ -0,0 +1,51 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations + +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter +import dev.inmo.tgbotapi.extensions.utils.* +import dev.inmo.tgbotapi.requests.abstracts.Request +import dev.inmo.tgbotapi.types.ChatJoinRequest +import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery +import dev.inmo.tgbotapi.types.payments.ShippingQuery +import kotlinx.coroutines.flow.toList + +typealias ChatJoinRequestsMapper = suspend ChatJoinRequest.() -> ChatJoinRequest? + +private suspend fun BehaviourContext.waitChatJoinRequests( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: SimpleFilter? = null, + mapper: suspend ChatJoinRequest.() -> O? +): List = expectFlow( + initRequest, + count, + errorFactory +) { + val data = it.asChatJoinRequestUpdate() ?.data + if (data != null && (filter == null || filter(data))) { + data.mapper().let(::listOfNotNull) + } else { + emptyList() + } +}.toList().toList() + + +suspend fun BehaviourContext.waitChatJoinRequests( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + count: Int = 1, + filter: SimpleFilter? = null, + mapper: ChatJoinRequestsMapper? = null +) : List = waitChatJoinRequests( + count, + initRequest, + errorFactory, + filter +) { + if (mapper == null) { + this + } else { + mapper(this) + } +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/filters/MessageFilterByChat.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/filters/MessageFilterByChat.kt index 370db3c006..c0877ce42b 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/filters/MessageFilterByChat.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/filters/MessageFilterByChat.kt @@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.filters import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat import dev.inmo.tgbotapi.types.CallbackQuery.CallbackQuery +import dev.inmo.tgbotapi.types.ChatJoinRequest import dev.inmo.tgbotapi.types.ChatMemberUpdated import dev.inmo.tgbotapi.types.InlineQueries.query.InlineQuery import dev.inmo.tgbotapi.types.message.abstracts.Message @@ -54,3 +55,9 @@ val InlineQueryFilterByUser: BehaviourContextAndTwoTypesReceiver = { updated, update -> update.sourceChat() ?.id == updated.chat.id } +/** + * Allow only events from the same chat as base [ChatMemberUpdated] + */ +val ChatJoinRequestFilterByChat: BehaviourContextAndTwoTypesReceiver = { updated, update -> + update.sourceChat() ?.id == updated.chat.id +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatJoinRequestTriggers.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatJoinRequestTriggers.kt new file mode 100644 index 0000000000..b201725d57 --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatJoinRequestTriggers.kt @@ -0,0 +1,34 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling + +import dev.inmo.tgbotapi.extensions.behaviour_builder.* +import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.* +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.* +import dev.inmo.tgbotapi.extensions.utils.asChatJoinRequestUpdate +import dev.inmo.tgbotapi.extensions.utils.asShippingQueryUpdate +import dev.inmo.tgbotapi.types.ChatJoinRequest +import dev.inmo.tgbotapi.types.payments.ShippingQuery +import dev.inmo.tgbotapi.types.update.abstracts.Update + +/** + * Please, remember that your bot must have `can_invite_users` to receive these requests + * + * @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call + * @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example, + * this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage]. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times] + * to combinate several filters + * @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously + * in one "stream". Output of [markerFactory] will be used as a key for "stream" + * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that + * data + */ +suspend fun BC.onChatJoinRequest( + initialFilter: SimpleFilter? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = ChatJoinRequestFilterByChat, + markerFactory: MarkerFactory = ByChatChatJoinRequestMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver +) = on(markerFactory, initialFilter, subcontextUpdatesFilter, scenarioReceiver) { + (it.asChatJoinRequestUpdate() ?.data) ?.let(::listOfNotNull) +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/QueryMarkerFactories.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/QueryMarkerFactories.kt index ccb7cd7734..5f831e6fbd 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/QueryMarkerFactories.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/QueryMarkerFactories.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories import dev.inmo.tgbotapi.types.CallbackQuery.CallbackQuery +import dev.inmo.tgbotapi.types.ChatJoinRequest import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery import dev.inmo.tgbotapi.types.payments.ShippingQuery @@ -8,6 +9,10 @@ object ByUserCallbackQueryMarkerFactory : MarkerFactory { override suspend fun invoke(data: CallbackQuery) = data.user } +object ByChatChatJoinRequestMarkerFactory : MarkerFactory { + override suspend fun invoke(data: ChatJoinRequest) = data.chat +} + object ByUserShippingQueryMarkerFactory : MarkerFactory { override suspend fun invoke(data: ShippingQuery) = data.user } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatJoinRequest.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatJoinRequest.kt new file mode 100644 index 0000000000..f8c138a1d8 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatJoinRequest.kt @@ -0,0 +1,20 @@ +package dev.inmo.tgbotapi.types + +import dev.inmo.tgbotapi.CommonAbstracts.FromUser +import dev.inmo.tgbotapi.types.chat.abstracts.PublicChat +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ChatJoinRequest( + @SerialName(chatField) + val chat: PublicChat, + @SerialName(userField) + override val from: User, + @SerialName(dateField) + val date: TelegramDate, + @SerialName(bioField) + val bio: String, + @SerialName(inviteLinkField) + val inviteLink: ChatInviteLink +) : FromUser diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatJoinRequestUpdate.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatJoinRequestUpdate.kt new file mode 100644 index 0000000000..2827be9ace --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatJoinRequestUpdate.kt @@ -0,0 +1,10 @@ +package dev.inmo.tgbotapi.types.update + +import dev.inmo.tgbotapi.types.ChatJoinRequest +import dev.inmo.tgbotapi.types.UpdateIdentifier +import dev.inmo.tgbotapi.types.update.abstracts.Update + +data class ChatJoinRequestUpdate( + override val updateId: UpdateIdentifier, + override val data: ChatJoinRequest +) : Update diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/RawUpdate.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/RawUpdate.kt index 87e46b45d3..bde9d23dd1 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/RawUpdate.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/RawUpdate.kt @@ -35,7 +35,8 @@ internal data class RawUpdate constructor( private val poll: Poll? = null, private val poll_answer: PollAnswer? = null, private val my_chat_member: ChatMemberUpdated? = null, - private val chat_member: ChatMemberUpdated? = null + private val chat_member: ChatMemberUpdated? = null, + private val chat_join_request: ChatJoinRequest? = null ) { private var initedUpdate: Update? = null /** @@ -61,6 +62,7 @@ internal data class RawUpdate constructor( poll_answer != null -> PollAnswerUpdate(updateId, poll_answer) my_chat_member != null -> MyChatMemberUpdatedUpdate(updateId, my_chat_member) chat_member != null -> CommonChatMemberUpdatedUpdate(updateId, chat_member) + chat_join_request != null -> ChatJoinRequestUpdate(updateId, chat_join_request) else -> UnknownUpdate( updateId, raw.toString(), diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt index b37ceacb1c..db8de2ec2f 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt @@ -32,6 +32,7 @@ interface FlowsUpdatesFilter : UpdatesFilter { val pollAnswersFlow: Flow val chatMemberUpdatesFlow: Flow val myChatMemberUpdatesFlow: Flow + val chatJoinRequestUpdateFlow: Flow val unknownUpdatesFlow: Flow } @@ -62,6 +63,7 @@ abstract class AbstractFlowsUpdatesFilter : FlowsUpdatesFilter { override val pollAnswersFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } override val chatMemberUpdatesFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } override val myChatMemberUpdatesFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } + override val chatJoinRequestUpdateFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } override val unknownUpdatesFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } } diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt index ae3d35a63c..491ae1c958 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt @@ -2204,6 +2204,15 @@ inline fun Update.asChatMemberUpdatedUpdate(): ChatMemberUpdatedUpdate? = this a @PreviewFeature inline fun Update.requireChatMemberUpdatedUpdate(): ChatMemberUpdatedUpdate = this as ChatMemberUpdatedUpdate +@PreviewFeature +inline fun Update.whenChatJoinRequestUpdate(block: (ChatJoinRequestUpdate) -> T) = asChatJoinRequestUpdate() ?.let(block) + +@PreviewFeature +inline fun Update.asChatJoinRequestUpdate(): ChatJoinRequestUpdate? = this as? ChatJoinRequestUpdate + +@PreviewFeature +inline fun Update.requireChatJoinRequestUpdate(): ChatJoinRequestUpdate = this as ChatJoinRequestUpdate + @PreviewFeature inline fun TelegramMediaFile.whenAnimationFile(block: (AnimationFile) -> T) = asAnimationFile() ?.let(block) diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt index 9507f7052b..4ce905c52f 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt @@ -5,6 +5,7 @@ import dev.inmo.tgbotapi.CommonAbstracts.WithUser import dev.inmo.tgbotapi.extensions.utils.asFromUser import dev.inmo.tgbotapi.extensions.utils.asUser import dev.inmo.tgbotapi.extensions.utils.shortcuts.chat +import dev.inmo.tgbotapi.types.ChatJoinRequest import dev.inmo.tgbotapi.types.User import dev.inmo.tgbotapi.types.chat.abstracts.Chat import dev.inmo.tgbotapi.types.update.* @@ -14,12 +15,13 @@ import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.utils.PreviewFeature @PreviewFeature -fun Update.sourceChat(): Chat? = when { - this is MediaGroupUpdate -> when (this) { +fun Update.sourceChat(): Chat? = when (this) { + is MediaGroupUpdate -> when (this) { is SentMediaGroupUpdate -> data.chat is EditMediaGroupUpdate -> data.chat } - this is BaseMessageUpdate -> data.chat + is BaseMessageUpdate -> data.chat + is ChatJoinRequestUpdate -> data.chat else -> { when (val data = data) { is FromUser -> data.from