Compare commits

..

1 Commits

Author SHA1 Message Date
7aafb32bed Merge d289c2101d into 281f0840eb 2023-08-19 20:31:29 +00:00
16 changed files with 132 additions and 496 deletions

View File

@@ -11,9 +11,9 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt install -y libcurl4-openssl-dev sudo apt install -y libcurl4-openssl-dev
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 17 java-version: 11
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build

View File

@@ -32,16 +32,16 @@ suspend fun main(vararg args: String) {
val chat = message.chat val chat = message.chat
val answerText = when (val chat = message.chat) { val answerText = when (val chat = message.chat) {
is PreviewChannelChat -> { is ChannelChat -> {
val answer = "Hi everybody in this channel \"${chat.title}\"" val answer = "Hi everybody in this channel \"${chat.title}\""
reply(message, answer, MarkdownV2) reply(message, answer, MarkdownV2)
return@onMentionWithAnyContent return@onMentionWithAnyContent
} }
is PreviewPrivateChat -> { is PrivateChat -> {
reply(message, "Hi, " + "${chat.firstName} ${chat.lastName}".textMentionMarkdownV2(chat.id), MarkdownV2) reply(message, "Hi, " + "${chat.firstName} ${chat.lastName}".textMentionMarkdownV2(chat.id), MarkdownV2)
return@onMentionWithAnyContent return@onMentionWithAnyContent
} }
is PreviewGroupChat -> { is GroupChat -> {
message.ifFromChannelGroupContentMessage { message.ifFromChannelGroupContentMessage {
val answer = "Hi, ${it.senderChat.title}" val answer = "Hi, ${it.senderChat.title}"
reply(message, answer, MarkdownV2) reply(message, answer, MarkdownV2)
@@ -56,7 +56,9 @@ suspend fun main(vararg args: String) {
} ?: chat.title } ?: chat.title
} }
} }
is UnknownExtendedChat,
is UnknownChatType -> "Unknown :(".escapeMarkdownV2Common() is UnknownChatType -> "Unknown :(".escapeMarkdownV2Common()
else -> error("Something went wrong: unknown type of chat $chat")
} }
reply( reply(
message, message,

View File

@@ -16,9 +16,15 @@ apply plugin: 'application'
mainClassName="InlineQueriesBotKt" mainClassName="InlineQueriesBotKt"
apply from: "$nativePartTemplate"
kotlin { kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
def nativeTarget
if (hostOs == "Linux") nativeTarget = linuxX64("native") { binaries { executable() } }
else if (isMingwX64) nativeTarget = mingwX64("native") { binaries { executable() } }
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
jvm() jvm()
sourceSets { sourceSets {
@@ -29,6 +35,18 @@ kotlin {
api "dev.inmo:tgbotapi:$telegram_bot_api_version" api "dev.inmo:tgbotapi:$telegram_bot_api_version"
} }
} }
nativeMain {
dependencies {
def engine
if (hostOs == "Linux") engine = "curl"
else if (isMingwX64) engine = "winhttp"
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
api "io.ktor:ktor-client-$engine:$ktor_version"
}
}
} }
} }

View File

@@ -2,7 +2,6 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.EditLiveLocationInfo import dev.inmo.tgbotapi.extensions.api.EditLiveLocationInfo
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.edit.location.live.stopLiveLocation
import dev.inmo.tgbotapi.extensions.api.handleLiveLocation import dev.inmo.tgbotapi.extensions.api.handleLiveLocation
import dev.inmo.tgbotapi.extensions.api.send.* import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
@@ -76,7 +75,7 @@ suspend fun main(vararg args: String) {
sendingJob.cancel() // ends live location sendingJob.cancel() // ends live location
currentMessageState.value ?.let { currentMessageState.value ?.let {
stopLiveLocation(it, replyMarkup = null) edit(it, replyMarkup = null) // removing reply keyboard
} }
} }
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) } allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }

View File

@@ -5,7 +5,10 @@ import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.send.* import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.api.send.polls.sendRegularPoll import dev.inmo.tgbotapi.extensions.api.send.polls.sendRegularPoll
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.* import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMentionWithAnyContent
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollAnswer
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.sender_chat import dev.inmo.tgbotapi.extensions.utils.extensions.raw.sender_chat
import dev.inmo.tgbotapi.extensions.utils.formatting.linkMarkdownV2 import dev.inmo.tgbotapi.extensions.utils.formatting.linkMarkdownV2
import dev.inmo.tgbotapi.extensions.utils.formatting.textMentionMarkdownV2 import dev.inmo.tgbotapi.extensions.utils.formatting.textMentionMarkdownV2
@@ -30,11 +33,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
/** /**
* This bot will answer with anonymous or public poll and send message on * The main purpose of this bot is just to answer "Oh, hi, " and add user mention here
* updates of any of it.
*
* * Use `/anonymous` to take anonymous regular poll
* * Use `/public` to take public regular poll
*/ */
@OptIn(PreviewFeature::class) @OptIn(PreviewFeature::class)
suspend fun main(vararg args: String) { suspend fun main(vararg args: String) {
@@ -80,17 +79,8 @@ suspend fun main(vararg args: String) {
val chatId = pollToChat[it.pollId] ?: return@onPollAnswer val chatId = pollToChat[it.pollId] ?: return@onPollAnswer
when(it) { when(it) {
is PollAnswer.Public -> send(chatId, "[onPollAnswer] User ${it.user} have answered") is PollAnswer.Public -> send(chatId, "User ${it.user} have answered")
is PollAnswer.Anonymous -> send(chatId, "[onPollAnswer] Chat ${it.voterChat} have answered") is PollAnswer.Anonymous -> send(chatId, "Chat ${it.voterChat} have answered")
}
}
onPollUpdates {
val chatId = pollToChat[it.id] ?: return@onPollUpdates
when(it.isAnonymous) {
false -> send(chatId, "[onPollUpdates] Public poll updated: ${it.options.joinToString()}")
true -> send(chatId, "[onPollUpdates] Anonymous poll updated: ${it.options.joinToString()}")
} }
} }

View File

@@ -16,7 +16,15 @@ apply plugin: 'application'
mainClassName="RandomFileSenderBotKt" mainClassName="RandomFileSenderBotKt"
kotlin { kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
def nativeTarget
if (hostOs == "Linux") nativeTarget = linuxX64("native") { binaries { executable() } }
else if (isMingwX64) nativeTarget = mingwX64("native") { binaries { executable() } }
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
jvm() jvm()
sourceSets { sourceSets {
@@ -27,8 +35,18 @@ kotlin {
api "dev.inmo:tgbotapi:$telegram_bot_api_version" api "dev.inmo:tgbotapi:$telegram_bot_api_version"
} }
} }
nativeMain {
dependencies {
def engine
if (hostOs == "Linux") engine = "curl"
else if (isMingwX64) engine = "winhttp"
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
api "io.ktor:ktor-client-$engine:$ktor_version"
}
}
} }
} }
apply from: "$nativePartTemplate"

View File

@@ -22,7 +22,6 @@ kotlin {
} }
linuxX64() linuxX64()
mingwX64() mingwX64()
linuxArm64()
sourceSets { sourceSets {
commonMain { commonMain {

View File

@@ -12,9 +12,15 @@ plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
} }
apply from: "$nativePartTemplate"
kotlin { kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
def nativeTarget
if (hostOs == "Linux") nativeTarget = linuxX64("native") { binaries { executable() } }
else if (isMingwX64) nativeTarget = mingwX64("native") { binaries { executable() } }
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
@@ -23,6 +29,18 @@ kotlin {
api project(":ResenderBot:ResenderBotLib") api project(":ResenderBot:ResenderBotLib")
} }
} }
nativeMain {
dependencies {
def engine
if (hostOs == "Linux") engine = "curl"
else if (isMingwX64) engine = "winhttp"
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
api "io.ktor:ktor-client-$engine:$ktor_version"
}
}
} }
} }

View File

@@ -18,5 +18,4 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version" implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
implementation 'io.ktor:ktor-client-logging-jvm:2.3.3'
} }

View File

@@ -1,50 +1,34 @@
import dev.inmo.micro_utils.coroutines.firstOf import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.chat.members.getChatMember import dev.inmo.tgbotapi.extensions.api.chat.members.getChatMember
import dev.inmo.tgbotapi.extensions.api.chat.members.promoteChannelAdministrator
import dev.inmo.tgbotapi.extensions.api.chat.members.restrictChatMember import dev.inmo.tgbotapi.extensions.api.chat.members.restrictChatMember
import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithFSMAndStartLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.asContentMessage
import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat import dev.inmo.tgbotapi.extensions.utils.asPossiblyReplyMessage
import dev.inmo.tgbotapi.extensions.utils.types.buttons.* import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.extendedGroupChatOrNull
import dev.inmo.tgbotapi.extensions.utils.fromUserMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.restrictedChatMemberOrNull
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
import dev.inmo.tgbotapi.extensions.utils.whenMemberChatMember
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
import dev.inmo.tgbotapi.types.chat.ChannelChat
import dev.inmo.tgbotapi.types.chat.ChatPermissions import dev.inmo.tgbotapi.types.chat.ChatPermissions
import dev.inmo.tgbotapi.types.chat.PublicChat import dev.inmo.tgbotapi.types.chat.PublicChat
import dev.inmo.tgbotapi.types.chat.member.*
import dev.inmo.tgbotapi.types.commands.BotCommandScope import dev.inmo.tgbotapi.types.commands.BotCommandScope
import dev.inmo.tgbotapi.types.request.RequestId import dev.inmo.tgbotapi.types.toChatId
import dev.inmo.tgbotapi.utils.* import dev.inmo.tgbotapi.utils.row
import dev.inmo.tgbotapi.utils.mention
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapNotNull
sealed interface UserRetrievingStep : State {
data class RetrievingChannelChatState(
override val context: ChatId
) : UserRetrievingStep
data class RetrievingUserIdChatState(
override val context: ChatId,
val channelId: ChatId
) : UserRetrievingStep
data class RetrievingChatInfoDoneState(
override val context: ChatId,
val channelId: ChatId,
val userId: UserId
) : UserRetrievingStep
}
suspend fun main(args: Array<String>) { suspend fun main(args: Array<String>) {
val botToken = args.first() val botToken = args.first()
@@ -76,31 +60,20 @@ suspend fun main(args: Array<String>) {
val otherMessagesToggleCommonData = "$commonDataPrefix other messages" val otherMessagesToggleCommonData = "$commonDataPrefix other messages"
val webPagePreviewToggleCommonData = "$commonDataPrefix web page preview" val webPagePreviewToggleCommonData = "$commonDataPrefix web page preview"
val adminRightsDataPrefix = "admin"
val refreshAdminRightsData = "${adminRightsDataPrefix}_refresh"
val postMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_post_messages"
val editMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_edit_messages"
val deleteMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_delete_messages"
val editStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_edit_stories"
val deleteStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_delete_stories"
val postStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_post_stories"
suspend fun BehaviourContext.getUserChatPermissions(chatId: ChatId, userId: UserId): ChatPermissions? { suspend fun BehaviourContext.getUserChatPermissions(chatId: ChatId, userId: UserId): ChatPermissions? {
val chatMember = getChatMember(chatId, userId) val chatMember = getChatMember(chatId, userId)
return chatMember.restrictedChatMemberOrNull() ?: chatMember.whenMemberChatMember { return chatMember.restrictedChatMemberOrNull() ?: chatMember.whenMemberChatMember {
getChat(chatId).extendedGroupChatOrNull() ?.permissions getChat(chatId).extendedGroupChatOrNull() ?.permissions
} }
} }
fun buildGranularKeyboard(
permissions: ChatPermissions suspend fun BehaviourContext.buildGranularKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? {
): InlineKeyboardMarkup { val permissions = getUserChatPermissions(chatId, userId) ?: return null
return inlineKeyboard { return inlineKeyboard {
row { row {
dataButton("Send messages${permissions.canSendMessages.allowedSymbol()}", messagesToggleGranularData) dataButton("Send messages${permissions.canSendMessages.allowedSymbol()}", messagesToggleGranularData)
dataButton( dataButton("Send other messages${permissions.canSendOtherMessages.allowedSymbol()}", otherMessagesToggleGranularData)
"Send other messages${permissions.canSendOtherMessages.allowedSymbol()}",
otherMessagesToggleGranularData
)
} }
row { row {
dataButton("Send audios${permissions.canSendAudios.allowedSymbol()}", audiosToggleGranularData) dataButton("Send audios${permissions.canSendAudios.allowedSymbol()}", audiosToggleGranularData)
@@ -108,17 +81,11 @@ suspend fun main(args: Array<String>) {
} }
row { row {
dataButton("Send videos${permissions.canSendVideos.allowedSymbol()}", videosToggleGranularData) dataButton("Send videos${permissions.canSendVideos.allowedSymbol()}", videosToggleGranularData)
dataButton( dataButton("Send video notes${permissions.canSendVideoNotes.allowedSymbol()}", videoNotesToggleGranularData)
"Send video notes${permissions.canSendVideoNotes.allowedSymbol()}",
videoNotesToggleGranularData
)
} }
row { row {
dataButton("Send photos${permissions.canSendPhotos.allowedSymbol()}", photosToggleGranularData) dataButton("Send photos${permissions.canSendPhotos.allowedSymbol()}", photosToggleGranularData)
dataButton( dataButton("Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}", webPagePreviewToggleGranularData)
"Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}",
webPagePreviewToggleGranularData
)
} }
row { row {
dataButton("Send polls${permissions.canSendPolls.allowedSymbol()}", pollsToggleGranularData) dataButton("Send polls${permissions.canSendPolls.allowedSymbol()}", pollsToggleGranularData)
@@ -126,41 +93,6 @@ suspend fun main(args: Array<String>) {
} }
} }
} }
fun buildAdminRightsKeyboard(
permissions: AdministratorChatMember?,
channelId: ChatId,
userId: UserId
): InlineKeyboardMarkup {
return inlineKeyboard {
permissions ?.also {
row {
dataButton("Refresh", "$refreshAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
row {
dataButton("Edit messages${permissions.canEditMessages.allowedSymbol()}", "$editMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
dataButton("Delete messages${permissions.canRemoveMessages.allowedSymbol()}", "$deleteMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
row {
dataButton("Post messages${permissions.canPostMessages.allowedSymbol()}", "$postMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
row {
dataButton("Edit stories${permissions.canEditStories.allowedSymbol()}", "$editStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
dataButton("Delete stories${permissions.canDeleteStories.allowedSymbol()}", "$deleteStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
row {
dataButton("Post stories${permissions.canPostStories.allowedSymbol()}", "$postStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
} ?: row {
dataButton("Promote to admin", "$postMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
}
}
suspend fun BehaviourContext.buildGranularKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? {
return buildGranularKeyboard(
getUserChatPermissions(chatId, userId) ?: return null
)
}
suspend fun BehaviourContext.buildCommonKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? { suspend fun BehaviourContext.buildCommonKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? {
val permissions = getUserChatPermissions(chatId, userId) ?: return null val permissions = getUserChatPermissions(chatId, userId) ?: return null
@@ -178,32 +110,23 @@ suspend fun main(args: Array<String>) {
} }
} }
bot.buildBehaviourWithFSMAndStartLongPolling<UserRetrievingStep>( bot.buildBehaviourWithLongPolling(
defaultExceptionsHandler = { defaultExceptionsHandler = {
it.printStackTrace() it.printStackTrace()
}, }
) { ) {
onCommand( onCommand("simple", initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull() ?.user ?.id == allowedAdmin }) {
"simple",
initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull()?.user?.id == allowedAdmin }) {
val replyMessage = it.replyTo val replyMessage = it.replyTo
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand val userInReply = replyMessage ?.fromUserMessageOrNull() ?.user ?.id ?: return@onCommand
reply( reply(
replyMessage, replyMessage,
"Manage keyboard:", "Manage keyboard:",
replyMarkup = buildCommonKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand replyMarkup = buildCommonKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand
) )
} }
onCommand( onCommand("granular", initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull() ?.user ?.id == allowedAdmin }) {
"granular",
initialFilter = {
it.chat is ChannelChat || (it.chat is PublicChat && it.fromUserMessageOrNull()?.user?.id == allowedAdmin)
}
) {
val replyMessage = it.replyTo val replyMessage = it.replyTo
val usernameInText = it.content.textSources.firstNotNullOfOrNull { it.mentionTextSourceOrNull() } ?.username val userInReply = replyMessage ?.fromUserMessageOrNull() ?.user ?.id ?: return@onCommand
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
reply( reply(
replyMessage, replyMessage,
"Manage keyboard:", "Manage keyboard:",
@@ -215,72 +138,60 @@ suspend fun main(args: Array<String>) {
Regex("^${granularDataPrefix}.*"), Regex("^${granularDataPrefix}.*"),
initialFilter = { it.user.id == allowedAdmin } initialFilter = { it.user.id == allowedAdmin }
) { ) {
val messageReply = val messageReply = it.message.commonMessageOrNull() ?.replyTo ?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
val userId = messageReply.user.id val userId = messageReply.user.id
val permissions = val permissions = getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
val newPermission = when (it.data) { val newPermission = when (it.data) {
messagesToggleGranularData -> { messagesToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendMessages = permissions.canSendMessages?.let { !it } ?: false canSendMessages = permissions.canSendMessages ?.let { !it } ?: false
) )
} }
otherMessagesToggleGranularData -> { otherMessagesToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false canSendOtherMessages = permissions.canSendOtherMessages ?.let { !it } ?: false
) )
} }
audiosToggleGranularData -> { audiosToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendAudios = permissions.canSendAudios?.let { !it } ?: false canSendAudios = permissions.canSendAudios ?.let { !it } ?: false
) )
} }
voicesToggleGranularData -> { voicesToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendVoiceNotes = permissions.canSendVoiceNotes?.let { !it } ?: false canSendVoiceNotes = permissions.canSendVoiceNotes ?.let { !it } ?: false
) )
} }
videosToggleGranularData -> { videosToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendVideos = permissions.canSendVideos?.let { !it } ?: false canSendVideos = permissions.canSendVideos ?.let { !it } ?: false
) )
} }
videoNotesToggleGranularData -> { videoNotesToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendVideoNotes = permissions.canSendVideoNotes?.let { !it } ?: false canSendVideoNotes = permissions.canSendVideoNotes ?.let { !it } ?: false
) )
} }
photosToggleGranularData -> { photosToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendPhotos = permissions.canSendPhotos?.let { !it } ?: false canSendPhotos = permissions.canSendPhotos ?.let { !it } ?: false
) )
} }
webPagePreviewToggleGranularData -> { webPagePreviewToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false canAddWebPagePreviews = permissions.canAddWebPagePreviews ?.let { !it } ?: false
) )
} }
pollsToggleGranularData -> { pollsToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendPolls = permissions.canSendPolls?.let { !it } ?: false canSendPolls = permissions.canSendPolls ?.let { !it } ?: false
) )
} }
documentsToggleGranularData -> { documentsToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendDocuments = permissions.canSendDocuments?.let { !it } ?: false canSendDocuments = permissions.canSendDocuments ?.let { !it } ?: false
) )
} }
else -> permissions.copyGranular() else -> permissions.copyGranular()
} }
@@ -293,8 +204,7 @@ suspend fun main(args: Array<String>) {
edit( edit(
it.message, it.message,
replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId) replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
?: return@onMessageDataCallbackQuery
) )
} }
@@ -302,30 +212,25 @@ suspend fun main(args: Array<String>) {
Regex("^${commonDataPrefix}.*"), Regex("^${commonDataPrefix}.*"),
initialFilter = { it.user.id == allowedAdmin } initialFilter = { it.user.id == allowedAdmin }
) { ) {
val messageReply = val messageReply = it.message.commonMessageOrNull() ?.replyTo ?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
val userId = messageReply.user.id val userId = messageReply.user.id
val permissions = val permissions = getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
val newPermission = when (it.data) { val newPermission = when (it.data) {
pollsToggleCommonData -> { pollsToggleCommonData -> {
permissions.copyCommon( permissions.copyCommon(
canSendPolls = permissions.canSendPolls?.let { !it } ?: false canSendPolls = permissions.canSendPolls ?.let { !it } ?: false
) )
} }
otherMessagesToggleCommonData -> { otherMessagesToggleCommonData -> {
permissions.copyCommon( permissions.copyCommon(
canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false canSendOtherMessages = permissions.canSendOtherMessages ?.let { !it } ?: false
) )
} }
webPagePreviewToggleCommonData -> { webPagePreviewToggleCommonData -> {
permissions.copyCommon( permissions.copyCommon(
canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false canAddWebPagePreviews = permissions.canAddWebPagePreviews ?.let { !it } ?: false
) )
} }
else -> permissions.copyCommon() else -> permissions.copyCommon()
} }
@@ -338,169 +243,13 @@ suspend fun main(args: Array<String>) {
edit( edit(
it.message, it.message,
replyMarkup = buildCommonKeyboard(it.message.chat.id.toChatId(), userId) replyMarkup = buildCommonKeyboard(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
?: return@onMessageDataCallbackQuery
) )
} }
onMessageDataCallbackQuery(
Regex("^${adminRightsDataPrefix}.*"),
initialFilter = { it.user.id == allowedAdmin }
) {
val (channelIdString, userIdString) = it.data.split(" ").drop(1)
val channelId = ChatId(channelIdString.toLong())
val userId = ChatId(userIdString.toLong())
val chatMember = getChatMember(channelId, userId)
val asAdmin = chatMember.administratorChatMemberOrNull()
val asMember = chatMember.memberChatMemberOrNull()
val realData = it.data.takeWhile { it != ' ' }
fun Boolean?.toggleIfData(data: String) = if (realData == data) {
!(this ?: false)
} else {
null
}
if (realData != refreshAdminRightsData) {
promoteChannelAdministrator(
channelId,
userId,
canPostMessages = asAdmin ?.canPostMessages.toggleIfData(postMessagesToggleAdminRightsData),
canEditMessages = asAdmin ?.canEditMessages.toggleIfData(editMessagesToggleAdminRightsData),
canDeleteMessages = asAdmin ?.canRemoveMessages.toggleIfData(deleteMessagesToggleAdminRightsData),
canEditStories = asAdmin ?.canEditStories.toggleIfData(editStoriesToggleAdminRightsData),
canDeleteStories = asAdmin ?.canDeleteStories.toggleIfData(deleteStoriesToggleAdminRightsData),
canPostStories = asAdmin ?.canPostStories.toggleIfData(postStoriesToggleAdminRightsData),
)
}
edit(
it.message,
replyMarkup = buildAdminRightsKeyboard(
getChatMember(
channelId,
userId
).administratorChatMemberOrNull(),
channelId,
userId
)
)
}
strictlyOn<UserRetrievingStep.RetrievingChannelChatState> { state ->
val requestId = RequestId.random()
send(
state.context,
replyMarkup = replyKeyboard(
oneTimeKeyboard = true,
resizeKeyboard = true
) {
row {
requestChatButton(
"Choose channel",
requestId = requestId,
isChannel = true,
botIsMember = true,
botRightsInChat = ChatCommonAdministratorRights(
canPromoteMembers = true,
canRestrictMembers = true
),
userRightsInChat = ChatCommonAdministratorRights(
canPromoteMembers = true,
canRestrictMembers = true
)
)
}
}
) {
regular("Ok, send me the channel in which you wish to manage user, or use ")
botCommand("cancel")
regular(" to cancel the request")
}
firstOf {
include {
val chatId = waitChatSharedEventsMessages().mapNotNull {
it.chatEvent.chatId.takeIf { _ ->
it.chatEvent.requestId == requestId && it.sameChat(state.context)
}
}.first()
UserRetrievingStep.RetrievingUserIdChatState(state.context, chatId)
}
include {
waitCommandMessage("cancel").filter { it.sameChat(state.context) }.first()
null
}
}
}
strictlyOn<UserRetrievingStep.RetrievingUserIdChatState> { state ->
val requestId = RequestId.random()
send(
state.context,
replyMarkup = replyKeyboard(
oneTimeKeyboard = true,
resizeKeyboard = true
) {
row {
requestUserButton(
"Choose user",
requestId = requestId
)
}
}
) {
regular("Ok, send me the user for which you wish to change rights, or use ")
botCommand("cancel")
regular(" to cancel the request")
}
firstOf {
include {
val userContactChatId = waitUserSharedEventsMessages().filter {
it.sameChat(state.context)
}.first().chatEvent.chatId
UserRetrievingStep.RetrievingChatInfoDoneState(
state.context,
state.channelId,
userContactChatId
)
}
include {
waitCommandMessage("cancel").filter { it.sameChat(state.context) }.first()
null
}
}
}
strictlyOn<UserRetrievingStep.RetrievingChatInfoDoneState> { state ->
val chatMember = getChatMember(state.channelId, state.userId).administratorChatMemberOrNull()
if (chatMember == null) {
return@strictlyOn null
}
send(
state.context,
replyMarkup = buildAdminRightsKeyboard(
chatMember,
state.channelId,
state.userId
)
) {
regular("Rights of ")
mentionln(chatMember.user)
regular("Please, remember, that to be able to change user rights bot must promote user by itself to admin")
}
null
}
onCommand("rights_in_channel") {
startChain(UserRetrievingStep.RetrievingChannelChatState(it.chat.id.toChatId()))
}
setMyCommands( setMyCommands(
BotCommand("simple", "Trigger simple keyboard. Use with reply to user"), BotCommand("simple", "Trigger simple keyboard. Use with reply to user"),
BotCommand("granular", "Trigger granular keyboard. Use with reply to user"), BotCommand("granular", "Trigger granular keyboard. Use with reply to user"),
BotCommand("rights_in_channel", "Trigger granular keyboard. Use with reply to user"),
scope = BotCommandScope.AllGroupChats scope = BotCommandScope.AllGroupChats
) )
}.join() }.join()

View File

@@ -1,7 +1,6 @@
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.tgbotapi.types.webAppQueryIdField import dev.inmo.tgbotapi.types.webAppQueryIdField
import dev.inmo.tgbotapi.webapps.* import dev.inmo.tgbotapi.webapps.*
import dev.inmo.tgbotapi.webapps.cloud.*
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType
import dev.inmo.tgbotapi.webapps.popup.* import dev.inmo.tgbotapi.webapps.popup.*
@@ -15,11 +14,8 @@ import kotlinx.browser.window
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.dom.appendElement import kotlinx.dom.appendElement
import kotlinx.dom.appendText import kotlinx.dom.appendText
import kotlinx.dom.clear
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.w3c.dom.* import org.w3c.dom.HTMLElement
import kotlin.random.Random
import kotlin.random.nextUBytes
fun HTMLElement.log(text: String) { fun HTMLElement.log(text: String) {
appendText(text) appendText(text)
@@ -71,12 +67,9 @@ fun main() {
} }
} }
}) })
appendText("Answer in chat button") appendText("Exit button")
} ?: window.alert("Unable to load body") } ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
document.body ?.appendText("Allow to write in private messages: ${webApp.initDataUnsafe.user ?.allowsWriteToPM ?: "User unavailable"}")
document.body ?.appendElement("p", {}) document.body ?.appendElement("p", {})
document.body ?.appendText("Alerts:") document.body ?.appendText("Alerts:")
@@ -117,32 +110,6 @@ fun main() {
appendText("Alert") appendText("Alert")
} ?: window.alert("Unable to load body") } ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestWriteAccess() })
appendText("Request write access without callback")
} ?: window.alert("Unable to load body")
document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestWriteAccess { document.body ?.log("Write access request result: $it") } })
appendText("Request write access with callback")
} ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestContact() })
appendText("Request contact without callback")
} ?: window.alert("Unable to load body")
document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestContact { document.body ?.log("Contact request result: $it") } })
appendText("Request contact with callback")
} ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
document.body ?.appendElement("button") { document.body ?.appendElement("button") {
addEventListener("click", { addEventListener("click", {
webApp.showConfirm( webApp.showConfirm(
@@ -175,91 +142,6 @@ fun main() {
document.body ?.appendElement("p", {}) document.body ?.appendElement("p", {})
document.body ?.appendElement("button") {
fun updateHeaderColor() {
val (r, g, b) = Random.nextUBytes(3)
val hex = Color.Hex(r, g, b)
webApp.setHeaderColor(hex)
(this as? HTMLButtonElement) ?.style ?.backgroundColor = hex.value
textContent = "Header color: ${hex.value.uppercase()} (click to change)"
}
addEventListener("click", {
updateHeaderColor()
})
updateHeaderColor()
} ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
fun Element.updateCloudStorageContent() {
clear()
webApp.cloudStorage.getAll {
it.onSuccess {
document.body ?.log(it.toString())
appendElement("label") { textContent = "Cloud storage" }
appendElement("p", {})
it.forEach { (k, v) ->
appendElement("div") {
val kInput = appendElement("input", {}) as HTMLInputElement
val vInput = appendElement("input", {}) as HTMLInputElement
kInput.value = k.key
vInput.value = v.value
appendElement("button") {
addEventListener("click", {
if (k.key == kInput.value) {
webApp.cloudStorage.set(k.key, vInput.value) {
document.body ?.log(it.toString())
this@updateCloudStorageContent.updateCloudStorageContent()
}
} else {
webApp.cloudStorage.remove(k.key) {
it.onSuccess {
webApp.cloudStorage.set(kInput.value, vInput.value) {
document.body ?.log(it.toString())
this@updateCloudStorageContent.updateCloudStorageContent()
}
}
}
}
})
this.textContent = "Save"
}
}
appendElement("p", {})
}
appendElement("label") { textContent = "Cloud storage: add new" }
appendElement("p", {})
appendElement("div") {
val kInput = appendElement("input", {}) as HTMLInputElement
appendElement("button") {
textContent = "Add key"
addEventListener("click", {
webApp.cloudStorage.set(kInput.value, kInput.value) {
document.body ?.log(it.toString())
this@updateCloudStorageContent.updateCloudStorageContent()
}
})
}
}
appendElement("p", {})
}.onFailure {
document.body ?.log(it.stackTraceToString())
}
}
}
val cloudStorageContentDiv = document.body ?.appendElement("div") {} as HTMLDivElement
document.body ?.appendElement("p", {})
webApp.apply { webApp.apply {
onThemeChanged { onThemeChanged {
document.body ?.log("Theme changed: ${webApp.themeParams}") document.body ?.log("Theme changed: ${webApp.themeParams}")
@@ -289,18 +171,8 @@ fun main() {
onSettingsButtonClicked { onSettingsButtonClicked {
document.body ?.log("Settings button clicked") document.body ?.log("Settings button clicked")
} }
onWriteAccessRequested {
document.body ?.log("Write access request result: $it")
}
onContactRequested {
document.body ?.log("Contact request result: $it")
}
} }
webApp.ready() webApp.ready()
document.body ?.appendElement("input", {
(this as HTMLInputElement).value = window.location.href
})
cloudStorageContentDiv.updateCloudStorageContent()
}.onFailure { }.onFailure {
window.alert(it.stackTraceToString()) window.alert(it.stackTraceToString())
} }

View File

@@ -52,13 +52,8 @@ suspend fun main(vararg args: String) {
} }
) { ) {
routing { routing {
val baseJsFolder = File("WebApp/build/dist/js/") staticFiles("", File("WebApp/build/distributions")) {
baseJsFolder.list() ?.forEach { default("WebApp/build/distributions/index.html")
if (it == "productionExecutable" || it == "developmentExecutable") {
staticFiles("", File(baseJsFolder, it)) {
default("WebApp/build/dist/js/$it/index.html")
}
}
} }
post("inline") { post("inline") {
val requestBody = call.receiveText() val requestBody = call.receiveText()

View File

@@ -10,9 +10,6 @@ buildscript {
} }
allprojects { allprojects {
ext {
nativePartTemplate = "${rootProject.projectDir.absolutePath}/native_template.gradle"
}
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()

View File

@@ -4,8 +4,8 @@ org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx2g
kotlin_version=1.9.20 kotlin_version=1.8.22
telegram_bot_api_version=9.3.0 telegram_bot_api_version=9.1.0
micro_utils_version=0.20.12 micro_utils_version=0.19.9
serialization_version=1.6.0 serialization_version=1.5.1
ktor_version=2.3.5 ktor_version=2.3.3

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip

View File

@@ -1,20 +0,0 @@
kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
def isArch64 = System.getProperty("os.arch") == "aarch64"
def nativeTarget
if (hostOs == "Linux") {
if (isArch64) {
nativeTarget = linuxArm64("native") { binaries { executable() } }
} else {
nativeTarget = linuxX64("native") { binaries { executable() } }
}
} else {
if (isMingwX64) {
nativeTarget = mingwX64("native") { binaries { executable() } }
} else {
throw new GradleException("Host OS is not supported in Kotlin/Native.")
}
}
}