From cf5bf9095f17b0b098de40a7b8f0a7cd1bc7c4a0 Mon Sep 17 00:00:00 2001 From: rinekri Date: Wed, 24 Jul 2024 19:16:30 +0200 Subject: [PATCH] Add task to upload apk into Telegram --- example/build-types/build.gradle.kts | 8 +++ .../publish/plugin/BuildPublishPlugin.kt | 35 ++++++++- .../plugin/extension/config/TelegramConfig.kt | 8 +++ .../distribution/uploader/SlackUploader.kt | 2 +- .../changelog/SendTelegramChangelogTask.kt | 2 +- .../changelog/sender/TelegramWebhookSender.kt | 2 +- .../sender/api/TelegramWebhookSenderApi.kt | 2 +- .../work/SendTelegramChangelogWork.kt | 4 +- .../distribution/TelegramDistributionTask.kt | 61 ++++++++++++++++ .../telegram/distribution/api/TelegramApi.kt | 21 ++++++ .../entity/TelegramUploadResponse.kt | 10 +++ .../distribution/uploader/TelegramUploader.kt | 72 +++++++++++++++++++ .../distribution/work/TelegramUploadWork.kt | 41 +++++++++++ 13 files changed, 260 insertions(+), 8 deletions(-) create mode 100644 plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/TelegramDistributionTask.kt create mode 100644 plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/api/TelegramApi.kt create mode 100644 plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/entity/TelegramUploadResponse.kt create mode 100644 plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/uploader/TelegramUploader.kt create mode 100644 plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/work/TelegramUploadWork.kt diff --git a/example/build-types/build.gradle.kts b/example/build-types/build.gradle.kts index 7a8925e..08e7cc5 100644 --- a/example/build-types/build.gradle.kts +++ b/example/build-types/build.gradle.kts @@ -41,4 +41,12 @@ buildPublish { testerGroups.set(setOf("android-testers")) } } + telegram { + register("default") { + botId.set("313123131231") + chatId.set("-00000000") + userMentions.set(emptyList()) + uploadBuild.set(true) + } + } } diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/BuildPublishPlugin.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/BuildPublishPlugin.kt index fde22fc..62a9627 100644 --- a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/BuildPublishPlugin.kt +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/BuildPublishPlugin.kt @@ -48,6 +48,7 @@ import ru.kode.android.build.publish.plugin.task.slack.distribution.SlackDistrib import ru.kode.android.build.publish.plugin.task.tag.GetLastTagTask import ru.kode.android.build.publish.plugin.task.tag.PrintLastIncreasedTag import ru.kode.android.build.publish.plugin.task.telegram.changelog.SendTelegramChangelogTask +import ru.kode.android.build.publish.plugin.task.telegram.distribution.TelegramDistributionTask import ru.kode.android.build.publish.plugin.util.capitalizedName import java.io.File import java.time.LocalDate @@ -66,6 +67,7 @@ internal const val CHANGELOG_FILENAME = "changelog.txt" internal const val APP_CENTER_DISTRIBUTION_UPLOAD_TASK_PREFIX = "appCenterDistributionUpload" internal const val PLAY_DISTRIBUTION_UPLOAD_TASK_PREFIX = "playUpload" internal const val SLACK_DISTRIBUTION_UPLOAD_TASK_PREFIX = "slackDistributionUpload" +internal const val TELEGRAM_DISTRIBUTION_UPLOAD_TASK_PREFIX = "telegramDistributionUpload" internal const val JIRA_AUTOMATION_TASK = "jiraAutomation" internal const val CLICK_UP_AUTOMATION_TASK = "clickUpAutomation" internal const val DEFAULT_CONTAINER_NAME = "default" @@ -210,13 +212,14 @@ abstract class BuildPublishPlugin : Plugin { findByName(buildVariant.name) ?: findByName(DEFAULT_CONTAINER_NAME) } if (telegramConfig != null) { - tasks.registerSendTelegramChangelogTask( + tasks.registerTelegramTasks( outputConfig, changelogConfig, telegramConfig, buildVariant, generateChangelogFileProvider, tagBuildProvider, + apkOutputFileProvider, ) } val slackConfig = @@ -464,13 +467,14 @@ abstract class BuildPublishPlugin : Plugin { } @Suppress("LongParameterList") // TODO Get parameters inside - private fun TaskContainer.registerSendTelegramChangelogTask( + private fun TaskContainer.registerTelegramTasks( outputConfig: OutputConfig, changelogConfig: ChangelogConfig, telegramConfig: TelegramConfig, buildVariant: BuildVariant, changelogFileProvider: Provider, tagBuildProvider: Provider, + apkOutputFileProvider: Provider, ) { register( "$SEND_TELEGRAM_CHANGELOG_TASK_PREFIX${buildVariant.capitalizedName()}", @@ -486,6 +490,33 @@ abstract class BuildPublishPlugin : Plugin { it.topicId.set(telegramConfig.topicId) it.userMentions.set(telegramConfig.userMentions) } + if (telegramConfig.uploadBuild.orNull == true) { + registerTelegramUploadTask( + telegramConfig.botId, + telegramConfig.chatId, + telegramConfig.topicId, + buildVariant, + apkOutputFileProvider, + ) + } + } + + private fun TaskContainer.registerTelegramUploadTask( + botId: Provider, + chatId: Provider, + topicId: Provider, + buildVariant: BuildVariant, + apkOutputFileProvider: Provider, + ) { + register( + "$TELEGRAM_DISTRIBUTION_UPLOAD_TASK_PREFIX${buildVariant.capitalizedName()}", + TelegramDistributionTask::class.java, + ) { + it.buildVariantOutputFile.set(apkOutputFileProvider) + it.botId.set(botId) + it.chatId.set(chatId) + it.topicId.set(topicId) + } } @Suppress("LongParameterList") // TODO Get parameters inside diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/extension/config/TelegramConfig.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/extension/config/TelegramConfig.kt index d04dcb3..0e7acbf 100644 --- a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/extension/config/TelegramConfig.kt +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/extension/config/TelegramConfig.kt @@ -34,4 +34,12 @@ interface TelegramConfig { */ @get:Input val userMentions: SetProperty + + /** + * Should upload build at the same chat or not + * Works only if file size is smaller then 50 mb + */ + @get:Input + @get:Optional + val uploadBuild: Property } diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/slack/distribution/uploader/SlackUploader.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/slack/distribution/uploader/SlackUploader.kt index 0425501..a26689b 100644 --- a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/slack/distribution/uploader/SlackUploader.kt +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/slack/distribution/uploader/SlackUploader.kt @@ -87,7 +87,7 @@ private class AttachTokenInterceptor( } } -private fun createPartFromString(value: String): RequestBody { +fun createPartFromString(value: String): RequestBody { return value.toRequestBody(MultipartBody.FORM) } diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/SendTelegramChangelogTask.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/SendTelegramChangelogTask.kt index 9acf46e..9e83d31 100644 --- a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/SendTelegramChangelogTask.kt +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/SendTelegramChangelogTask.kt @@ -13,7 +13,7 @@ import org.gradle.api.tasks.options.Option import org.gradle.workers.WorkQueue import org.gradle.workers.WorkerExecutor import ru.kode.android.build.publish.plugin.enity.mapper.fromJson -import ru.kode.android.build.publish.plugin.task.telegram.work.SendTelegramChangelogWork +import ru.kode.android.build.publish.plugin.task.telegram.changelog.work.SendTelegramChangelogWork import javax.inject.Inject import kotlin.collections.joinToString import kotlin.collections.orEmpty diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/sender/TelegramWebhookSender.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/sender/TelegramWebhookSender.kt index 16d4f6e..3030caa 100644 --- a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/sender/TelegramWebhookSender.kt +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/sender/TelegramWebhookSender.kt @@ -6,7 +6,7 @@ import okhttp3.logging.HttpLoggingInterceptor import org.gradle.api.logging.Logger import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory -import ru.kode.android.build.publish.plugin.task.telegram.sender.api.TelegramWebhookSenderApi +import ru.kode.android.build.publish.plugin.task.telegram.changelog.sender.api.TelegramWebhookSenderApi import ru.kode.android.build.publish.plugin.util.executeOrThrow import java.util.concurrent.TimeUnit diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/sender/api/TelegramWebhookSenderApi.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/sender/api/TelegramWebhookSenderApi.kt index 1ee1e55..57ad7ca 100644 --- a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/sender/api/TelegramWebhookSenderApi.kt +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/sender/api/TelegramWebhookSenderApi.kt @@ -1,4 +1,4 @@ -package ru.kode.android.build.publish.plugin.task.telegram.sender.api +package ru.kode.android.build.publish.plugin.task.telegram.changelog.sender.api import retrofit2.Call import retrofit2.http.POST diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/work/SendTelegramChangelogWork.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/work/SendTelegramChangelogWork.kt index 4fc08a6..c9264ea 100644 --- a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/work/SendTelegramChangelogWork.kt +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/changelog/work/SendTelegramChangelogWork.kt @@ -1,10 +1,10 @@ -package ru.kode.android.build.publish.plugin.task.telegram.work +package ru.kode.android.build.publish.plugin.task.telegram.changelog.work import org.gradle.api.logging.Logging import org.gradle.api.provider.Property import org.gradle.workers.WorkAction import org.gradle.workers.WorkParameters -import ru.kode.android.build.publish.plugin.task.telegram.sender.TelegramWebhookSender +import ru.kode.android.build.publish.plugin.task.telegram.changelog.sender.TelegramWebhookSender import java.net.URLEncoder import javax.inject.Inject diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/TelegramDistributionTask.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/TelegramDistributionTask.kt new file mode 100644 index 0000000..5bdc9be --- /dev/null +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/TelegramDistributionTask.kt @@ -0,0 +1,61 @@ +package ru.kode.android.build.publish.plugin.task.telegram.distribution + +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import org.gradle.workers.WorkQueue +import org.gradle.workers.WorkerExecutor +import ru.kode.android.build.publish.plugin.task.telegram.distribution.work.TelegramUploadWork +import javax.inject.Inject + +abstract class TelegramDistributionTask + @Inject + constructor( + private val workerExecutor: WorkerExecutor, + ) : DefaultTask() { + init { + description = "Task to send apk to Telegram" + group = BasePlugin.BUILD_GROUP + } + + @get:InputFile + @get:Option( + option = "buildVariantOutputFile", + description = "Artifact output file (absolute path is expected)", + ) + abstract val buildVariantOutputFile: RegularFileProperty + + @get:Input + @get:Option(option = "botId", description = "Bot id where webhook posted") + abstract val botId: Property + + @get:Input + @get:Option(option = "chatId", description = "Chat id where webhook posted") + abstract val chatId: Property + + @get:Input + @get:Optional + @get:Option(option = "topicId", description = "Unique identifier for the target message thread") + abstract val topicId: Property + + @TaskAction + fun upload() { + val outputFile = buildVariantOutputFile.asFile.get() + val botId = botId.get() + val chatId = chatId.get() + val topicId = topicId.orNull + val workQueue: WorkQueue = workerExecutor.noIsolation() + workQueue.submit(TelegramUploadWork::class.java) { parameters -> + parameters.outputFile.set(outputFile) + parameters.botId.set(botId) + parameters.chatId.set(chatId) + parameters.topicId.set(topicId) + } + } + } diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/api/TelegramApi.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/api/TelegramApi.kt new file mode 100644 index 0000000..bf11b2d --- /dev/null +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/api/TelegramApi.kt @@ -0,0 +1,21 @@ +package ru.kode.android.build.publish.plugin.task.telegram.distribution.api + +import okhttp3.MultipartBody +import okhttp3.RequestBody +import retrofit2.Call +import retrofit2.http.Multipart +import retrofit2.http.POST +import retrofit2.http.Part +import retrofit2.http.PartMap +import retrofit2.http.Url +import ru.kode.android.build.publish.plugin.task.telegram.distribution.entity.TelegramUploadResponse + +internal interface TelegramApi { + @POST + @Multipart + fun upload( + @Url webhookUrl: String, + @PartMap params: HashMap, + @Part filePart: MultipartBody.Part, + ): Call +} diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/entity/TelegramUploadResponse.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/entity/TelegramUploadResponse.kt new file mode 100644 index 0000000..029c92e --- /dev/null +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/entity/TelegramUploadResponse.kt @@ -0,0 +1,10 @@ +package ru.kode.android.build.publish.plugin.task.telegram.distribution.entity + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +@Suppress("ConstructorParameterNaming") // network model +internal data class TelegramUploadResponse( + val ok: Boolean, + val error_code: String?, +) diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/uploader/TelegramUploader.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/uploader/TelegramUploader.kt new file mode 100644 index 0000000..d93da18 --- /dev/null +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/uploader/TelegramUploader.kt @@ -0,0 +1,72 @@ +package ru.kode.android.build.publish.plugin.task.telegram.distribution.uploader + +import com.squareup.moshi.Moshi +import okhttp3.MultipartBody +import okhttp3.OkHttpClient +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.logging.HttpLoggingInterceptor +import org.gradle.api.logging.Logger +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import ru.kode.android.build.publish.plugin.task.slack.distribution.uploader.createPartFromString +import ru.kode.android.build.publish.plugin.task.telegram.distribution.api.TelegramApi +import ru.kode.android.build.publish.plugin.util.UploadException +import ru.kode.android.build.publish.plugin.util.executeOrThrow +import java.io.File +import java.util.concurrent.TimeUnit + +internal class TelegramUploader(logger: Logger) { + private val client = + OkHttpClient.Builder() + .connectTimeout(HTTP_CONNECT_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .readTimeout(HTTP_CONNECT_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .writeTimeout(HTTP_CONNECT_TIMEOUT_MINUTES, TimeUnit.MINUTES) + .apply { + val loggingInterceptor = HttpLoggingInterceptor { message -> logger.info(message) } + loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY + addNetworkInterceptor(loggingInterceptor) + } + .build() + + private val moshi = Moshi.Builder().build() + + private val api = + Retrofit.Builder() + .baseUrl(STUB_BASE_URL) + .client(client) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + .create(TelegramApi::class.java) + + fun upload( + webhookUrl: String, + chatId: String, + topicId: String?, + file: File, + ) { + val filePart = + MultipartBody.Part.createFormData( + "document", + file.name, + file.asRequestBody(), + ) + val params = + if (topicId != null) { + hashMapOf( + "message_thread_id" to createPartFromString(topicId), + "chat_id" to createPartFromString(chatId), + ) + } else { + hashMapOf( + "chat_id" to createPartFromString(chatId), + ) + } + val response = api.upload(webhookUrl, params, filePart).executeOrThrow() + if (!response.ok) { + throw UploadException("Telegram uploading failed ${response.error_code}") + } + } +} + +private const val HTTP_CONNECT_TIMEOUT_MINUTES = 3L +private const val STUB_BASE_URL = "http://localhost/" diff --git a/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/work/TelegramUploadWork.kt b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/work/TelegramUploadWork.kt new file mode 100644 index 0000000..ded3cc3 --- /dev/null +++ b/plugin-build/plugin/src/main/java/ru/kode/android/build/publish/plugin/task/telegram/distribution/work/TelegramUploadWork.kt @@ -0,0 +1,41 @@ +package ru.kode.android.build.publish.plugin.task.telegram.distribution.work + +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.logging.Logging +import org.gradle.api.provider.Property +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import ru.kode.android.build.publish.plugin.task.telegram.distribution.uploader.TelegramUploader +import ru.kode.android.build.publish.plugin.util.UploadStreamTimeoutException + +interface TelegramUploadParameters : WorkParameters { + val outputFile: RegularFileProperty + val botId: Property + val chatId: Property + val topicId: Property +} + +abstract class TelegramUploadWork : WorkAction { + private val logger = Logging.getLogger(this::class.java) + private val uploader = TelegramUploader(logger) + + @Suppress("SwallowedException") // see logs below + override fun execute() { + try { + val url = SEND_DOCUMENT_WEB_HOOK.format(parameters.botId.get()) + uploader.upload( + url, + parameters.chatId.get(), + parameters.topicId.orNull, + parameters.outputFile.asFile.get(), + ) + } catch (ex: UploadStreamTimeoutException) { + logger.error( + "telegram upload failed with timeout exception, " + + "but probably uploaded", + ) + } + } +} + +private const val SEND_DOCUMENT_WEB_HOOK = "https://api.telegram.org/bot%s/sendDocument"