Skip to content

Commit

Permalink
Add task to upload apk into Telegram
Browse files Browse the repository at this point in the history
  • Loading branch information
rinekri committed Jul 24, 2024
1 parent 01fefde commit cf5bf90
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 8 deletions.
8 changes: 8 additions & 0 deletions example/build-types/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -210,13 +212,14 @@ abstract class BuildPublishPlugin : Plugin<Project> {
findByName(buildVariant.name) ?: findByName(DEFAULT_CONTAINER_NAME)
}
if (telegramConfig != null) {
tasks.registerSendTelegramChangelogTask(
tasks.registerTelegramTasks(
outputConfig,
changelogConfig,
telegramConfig,
buildVariant,
generateChangelogFileProvider,
tagBuildProvider,
apkOutputFileProvider,
)
}
val slackConfig =
Expand Down Expand Up @@ -464,13 +467,14 @@ abstract class BuildPublishPlugin : Plugin<Project> {
}

@Suppress("LongParameterList") // TODO Get parameters inside
private fun TaskContainer.registerSendTelegramChangelogTask(
private fun TaskContainer.registerTelegramTasks(
outputConfig: OutputConfig,
changelogConfig: ChangelogConfig,
telegramConfig: TelegramConfig,
buildVariant: BuildVariant,
changelogFileProvider: Provider<RegularFile>,
tagBuildProvider: Provider<RegularFile>,
apkOutputFileProvider: Provider<RegularFile>,
) {
register(
"$SEND_TELEGRAM_CHANGELOG_TASK_PREFIX${buildVariant.capitalizedName()}",
Expand All @@ -486,6 +490,33 @@ abstract class BuildPublishPlugin : Plugin<Project> {
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<String>,
chatId: Provider<String>,
topicId: Provider<String>,
buildVariant: BuildVariant,
apkOutputFileProvider: Provider<RegularFile>,
) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,12 @@ interface TelegramConfig {
*/
@get:Input
val userMentions: SetProperty<String>

/**
* 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<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private class AttachTokenInterceptor(
}
}

private fun createPartFromString(value: String): RequestBody {
fun createPartFromString(value: String): RequestBody {
return value.toRequestBody(MultipartBody.FORM)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String>

@get:Input
@get:Option(option = "chatId", description = "Chat id where webhook posted")
abstract val chatId: Property<String>

@get:Input
@get:Optional
@get:Option(option = "topicId", description = "Unique identifier for the target message thread")
abstract val topicId: Property<String>

@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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String, RequestBody>,
@Part filePart: MultipartBody.Part,
): Call<TelegramUploadResponse>
}
Original file line number Diff line number Diff line change
@@ -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?,
)
Original file line number Diff line number Diff line change
@@ -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/"
Original file line number Diff line number Diff line change
@@ -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<String>
val chatId: Property<String>
val topicId: Property<String>
}

abstract class TelegramUploadWork : WorkAction<TelegramUploadParameters> {
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"

0 comments on commit cf5bf90

Please sign in to comment.