diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index 7bb441aa..3e7cb99a 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -23,6 +23,7 @@ import com.pengrad.telegrambot.TelegramBot; import com.pengrad.telegrambot.TelegramException; import com.pengrad.telegrambot.UpdatesListener; +import com.pengrad.telegrambot.model.ResponseParameters; import com.pengrad.telegrambot.model.Update; import com.pengrad.telegrambot.model.request.ReplyParameters; import com.pengrad.telegrambot.model.request.richmessages.InputRichMessage; @@ -33,12 +34,14 @@ import com.pengrad.telegrambot.request.richmessages.SendRichMessage; import com.pengrad.telegrambot.request.richmessages.SendRichMessageDraft; import com.pengrad.telegrambot.response.BaseResponse; +import org.jspecify.annotations.Nullable; import org.slf4j.event.Level; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; @@ -230,9 +233,31 @@ private , R extends BaseResponse> R execute(BaseRequ return response; } + for (int retry = 1; retry <= 3 && isRetriable(response.parameters()); retry++) { + var retryDelay = response.parameters().retryAfter(); + LOGGER.at(Level.WARN).log("The {} request failed: retrying in {} seconds", request.getMethod(), retryDelay); + + try { + Thread.sleep(Duration.ofSeconds(retryDelay)); + } catch (InterruptedException _) { + Thread.currentThread().interrupt(); + break; + } + + response = bot.execute(request); + + if (response.isOk()) { + return response; + } + } + throw new TelegramApiException(request.getMethod(), response.description()); } + private static boolean isRetriable(@Nullable ResponseParameters parameters) { + return parameters != null && parameters.retryAfter() != null; + } + private static void deleteTempFiles(Set pathsToDelete) { for (var path : pathsToDelete) { try { diff --git a/src/test/java/com/github/stickerifier/stickerify/bot/MockResponses.java b/src/test/java/com/github/stickerifier/stickerify/bot/MockResponses.java index 2b3cc8a0..6ee30545 100644 --- a/src/test/java/com/github/stickerifier/stickerify/bot/MockResponses.java +++ b/src/test/java/com/github/stickerifier/stickerify/bot/MockResponses.java @@ -13,6 +13,17 @@ public final class MockResponses { } """).build(); + static final MockResponse FAILURE_RESPONSE = new MockResponse.Builder().body(""" + { + ok: false, + error_code: 429, + description: "Too Many Requests: retry after 3", + parameters: { + retry_after: 3 + } + } + """).build(); + static final MockResponse START_MESSAGE = new MockResponse.Builder().body(""" { ok: true, diff --git a/src/test/java/com/github/stickerifier/stickerify/bot/StickerifyTest.java b/src/test/java/com/github/stickerifier/stickerify/bot/StickerifyTest.java index 00a7483f..aae19dad 100644 --- a/src/test/java/com/github/stickerifier/stickerify/bot/StickerifyTest.java +++ b/src/test/java/com/github/stickerifier/stickerify/bot/StickerifyTest.java @@ -404,4 +404,24 @@ void documentNotSupported() throws Exception { assertResponseContainsMarkdownMessage(sendRichMessage, Answer.ERROR); } } + + @Test + void retryApiCall() throws Exception { + server.enqueue(MockResponses.HELP_MESSAGE); + server.enqueue(MockResponses.FAILURE_RESPONSE); + server.enqueue(MockResponses.SUCCESS_RESPONSE); + + try (var _ = runBot()) { + var getUpdates = server.takeRequest(); + assertEquals("/api/token/getUpdates", getUpdates.getTarget()); + + var firstSendRichMessage = server.takeRequest(); + assertEquals("/api/token/sendRichMessage", firstSendRichMessage.getTarget()); + + var secondSendRichMessage = server.takeRequest(); + assertEquals("/api/token/sendRichMessage", secondSendRichMessage.getTarget()); + + assertResponseContainsMarkdownMessage(secondSendRichMessage, Answer.HELP); + } + } }