diff --git a/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingClient.java b/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingClient.java index 3f2a7ce69..15cbc1803 100644 --- a/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingClient.java +++ b/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingClient.java @@ -30,16 +30,19 @@ import com.linecorp.bot.model.group.GroupSummaryResponse; import com.linecorp.bot.model.profile.MembersIdsResponse; import com.linecorp.bot.model.profile.UserProfileResponse; +import com.linecorp.bot.model.request.TestWebhookEndpointRequest; import com.linecorp.bot.model.response.BotApiResponse; import com.linecorp.bot.model.response.BotInfoResponse; import com.linecorp.bot.model.response.GetMessageEventResponse; import com.linecorp.bot.model.response.GetNumberOfFollowersResponse; import com.linecorp.bot.model.response.GetNumberOfMessageDeliveriesResponse; +import com.linecorp.bot.model.response.GetWebhookEndpointResponse; import com.linecorp.bot.model.response.IssueLinkTokenResponse; import com.linecorp.bot.model.response.MessageQuotaResponse; import com.linecorp.bot.model.response.NarrowcastProgressResponse; import com.linecorp.bot.model.response.NumberOfMessagesResponse; import com.linecorp.bot.model.response.QuotaConsumptionResponse; +import com.linecorp.bot.model.response.TestWebhookEndpointResponse; import com.linecorp.bot.model.response.demographics.GetFriendsDemographicsResponse; import com.linecorp.bot.model.richmenu.RichMenu; import com.linecorp.bot.model.richmenu.RichMenuIdResponse; @@ -378,6 +381,20 @@ public interface LineMessagingClient { */ CompletableFuture getBotInfo(); + /** + * Gets webhook endpoint information. + * + * @see Get webhook endpoint information + */ + CompletableFuture getWebhookEndpoint(); + + /** + * Tests webhook endpoint. + * + * @see Test webhook endpoint + */ + CompletableFuture testWebhookEndpoint(TestWebhookEndpointRequest request); + static LineMessagingClientBuilder builder(String channelToken) { return builder(FixedChannelTokenSupplier.of(channelToken)); } diff --git a/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingClientImpl.java b/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingClientImpl.java index e57858d4f..9822c8fb4 100644 --- a/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingClientImpl.java +++ b/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingClientImpl.java @@ -31,16 +31,19 @@ import com.linecorp.bot.model.group.GroupSummaryResponse; import com.linecorp.bot.model.profile.MembersIdsResponse; import com.linecorp.bot.model.profile.UserProfileResponse; +import com.linecorp.bot.model.request.TestWebhookEndpointRequest; import com.linecorp.bot.model.response.BotApiResponse; import com.linecorp.bot.model.response.BotInfoResponse; import com.linecorp.bot.model.response.GetMessageEventResponse; import com.linecorp.bot.model.response.GetNumberOfFollowersResponse; import com.linecorp.bot.model.response.GetNumberOfMessageDeliveriesResponse; +import com.linecorp.bot.model.response.GetWebhookEndpointResponse; import com.linecorp.bot.model.response.IssueLinkTokenResponse; import com.linecorp.bot.model.response.MessageQuotaResponse; import com.linecorp.bot.model.response.NarrowcastProgressResponse; import com.linecorp.bot.model.response.NumberOfMessagesResponse; import com.linecorp.bot.model.response.QuotaConsumptionResponse; +import com.linecorp.bot.model.response.TestWebhookEndpointResponse; import com.linecorp.bot.model.response.demographics.GetFriendsDemographicsResponse; import com.linecorp.bot.model.richmenu.RichMenu; import com.linecorp.bot.model.richmenu.RichMenuBulkLinkRequest; @@ -285,6 +288,17 @@ public CompletableFuture getBotInfo() { return toFuture(retrofitImpl.getBotInfo()); } + @Override + public CompletableFuture getWebhookEndpoint() { + return toFuture(retrofitImpl.getWebhookEndpoint()); + } + + @Override + public CompletableFuture testWebhookEndpoint( + TestWebhookEndpointRequest request) { + return toFuture(retrofitImpl.testWebhookEndpoint(request)); + } + // TODO: Extract this method. static CompletableFuture toFuture(Call callToWrap) { final CallbackAdaptor completableFuture = new CallbackAdaptor<>(); diff --git a/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingService.java b/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingService.java index aaa0c80ac..b8a16759f 100644 --- a/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingService.java +++ b/line-bot-api-client/src/main/java/com/linecorp/bot/client/LineMessagingService.java @@ -27,15 +27,18 @@ import com.linecorp.bot.model.group.GroupSummaryResponse; import com.linecorp.bot.model.profile.MembersIdsResponse; import com.linecorp.bot.model.profile.UserProfileResponse; +import com.linecorp.bot.model.request.TestWebhookEndpointRequest; import com.linecorp.bot.model.response.BotInfoResponse; import com.linecorp.bot.model.response.GetMessageEventResponse; import com.linecorp.bot.model.response.GetNumberOfFollowersResponse; import com.linecorp.bot.model.response.GetNumberOfMessageDeliveriesResponse; +import com.linecorp.bot.model.response.GetWebhookEndpointResponse; import com.linecorp.bot.model.response.IssueLinkTokenResponse; import com.linecorp.bot.model.response.MessageQuotaResponse; import com.linecorp.bot.model.response.NarrowcastProgressResponse; import com.linecorp.bot.model.response.NumberOfMessagesResponse; import com.linecorp.bot.model.response.QuotaConsumptionResponse; +import com.linecorp.bot.model.response.TestWebhookEndpointResponse; import com.linecorp.bot.model.response.demographics.GetFriendsDemographicsResponse; import com.linecorp.bot.model.richmenu.RichMenu; import com.linecorp.bot.model.richmenu.RichMenuBulkLinkRequest; @@ -382,4 +385,10 @@ Call linkRichMenuToUser( */ @GET("v2/bot/info") Call getBotInfo(); + + @GET("v2/bot/channel/webhook/endpoint") + Call getWebhookEndpoint(); + + @POST("v2/bot/channel/webhook/test") + Call testWebhookEndpoint(@Body TestWebhookEndpointRequest request); } diff --git a/line-bot-api-client/src/test/java/com/linecorp/bot/client/LineMessagingClientImplTest.java b/line-bot-api-client/src/test/java/com/linecorp/bot/client/LineMessagingClientImplTest.java index 50f1ad5c4..1b4b21844 100644 --- a/line-bot-api-client/src/test/java/com/linecorp/bot/client/LineMessagingClientImplTest.java +++ b/line-bot-api-client/src/test/java/com/linecorp/bot/client/LineMessagingClientImplTest.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.net.URI; +import java.time.Instant; import java.util.Collections; import org.junit.Rule; @@ -54,10 +55,12 @@ import com.linecorp.bot.model.narrowcast.filter.GenderDemographicFilter.Gender; import com.linecorp.bot.model.profile.MembersIdsResponse; import com.linecorp.bot.model.profile.UserProfileResponse; +import com.linecorp.bot.model.request.TestWebhookEndpointRequest; import com.linecorp.bot.model.response.BotApiResponse; import com.linecorp.bot.model.response.BotInfoResponse; import com.linecorp.bot.model.response.GetNumberOfFollowersResponse; import com.linecorp.bot.model.response.GetNumberOfMessageDeliveriesResponse; +import com.linecorp.bot.model.response.GetWebhookEndpointResponse; import com.linecorp.bot.model.response.IssueLinkTokenResponse; import com.linecorp.bot.model.response.MessageQuotaResponse; import com.linecorp.bot.model.response.MessageQuotaResponse.QuotaType; @@ -65,6 +68,7 @@ import com.linecorp.bot.model.response.NarrowcastProgressResponse.Phase; import com.linecorp.bot.model.response.NumberOfMessagesResponse; import com.linecorp.bot.model.response.QuotaConsumptionResponse; +import com.linecorp.bot.model.response.TestWebhookEndpointResponse; import com.linecorp.bot.model.richmenu.RichMenu; import com.linecorp.bot.model.richmenu.RichMenuBulkLinkRequest; import com.linecorp.bot.model.richmenu.RichMenuBulkUnlinkRequest; @@ -663,6 +667,39 @@ public void getBotInfo() throws Exception { assertThat(actual).isEqualTo(response); } + @Test + public void getWebhookEndpoint() throws Exception { + final GetWebhookEndpointResponse response = GetWebhookEndpointResponse + .builder() + .endpoint(URI.create("https://line.me/webhook")) + .active(true) + .build(); + whenCall(retrofitMock.getWebhookEndpoint(), response); + final GetWebhookEndpointResponse actual = target.getWebhookEndpoint().get(); + verify(retrofitMock, only()).getWebhookEndpoint(); + assertThat(actual).isEqualTo(response); + } + + @Test + public void testWebhookEndpoint() throws Exception { + final TestWebhookEndpointResponse response = TestWebhookEndpointResponse + .builder() + .success(true) + .timestamp(Instant.now()) + .detail("abc") + .reason("def") + .statusCode(200) + .build(); + final TestWebhookEndpointRequest request = TestWebhookEndpointRequest + .builder() + .endpoint(URI.create("http://example.com/my/great/endpoint")) + .build(); + whenCall(retrofitMock.testWebhookEndpoint(request), response); + final TestWebhookEndpointResponse actual = target.testWebhookEndpoint(request).get(); + verify(retrofitMock, only()).testWebhookEndpoint(request); + assertThat(actual).isEqualTo(response); + } + // Utility methods private static void whenCall(Call call, T value) { diff --git a/line-bot-model/src/main/java/com/linecorp/bot/model/request/TestWebhookEndpointRequest.java b/line-bot-model/src/main/java/com/linecorp/bot/model/request/TestWebhookEndpointRequest.java new file mode 100644 index 000000000..80cc7d116 --- /dev/null +++ b/line-bot-model/src/main/java/com/linecorp/bot/model/request/TestWebhookEndpointRequest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.bot.model.request; + +import java.net.URI; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import com.linecorp.bot.model.request.TestWebhookEndpointRequest.TestWebhookEndpointRequestBuilder; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@JsonDeserialize(builder = TestWebhookEndpointRequestBuilder.class) +public class TestWebhookEndpointRequest { + /** + * A valid webhook URL. If {@literal null}, sends a test webhook event to a webhook endpoint that is already + * set to the channel. + */ + URI endpoint; + + @JsonPOJOBuilder(withPrefix = "") + public static class TestWebhookEndpointRequestBuilder { + } +} diff --git a/line-bot-model/src/main/java/com/linecorp/bot/model/response/GetWebhookEndpointResponse.java b/line-bot-model/src/main/java/com/linecorp/bot/model/response/GetWebhookEndpointResponse.java new file mode 100644 index 000000000..b8987bdb7 --- /dev/null +++ b/line-bot-model/src/main/java/com/linecorp/bot/model/response/GetWebhookEndpointResponse.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.bot.model.response; + +import java.net.URI; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import com.linecorp.bot.model.response.GetWebhookEndpointResponse.GetWebhookEndpointResponseBuilder; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@JsonDeserialize(builder = GetWebhookEndpointResponseBuilder.class) +public class GetWebhookEndpointResponse { + /** + * Webhook URL. + */ + URI endpoint; + + /* + * Webhook usage status. The LINE platform sends a webhook event to {@link #endpoint} only if + * {@literal true}. + */ + boolean active; + + @JsonPOJOBuilder(withPrefix = "") + public static class GetWebhookEndpointResponseBuilder { + } +} diff --git a/line-bot-model/src/main/java/com/linecorp/bot/model/response/TestWebhookEndpointResponse.java b/line-bot-model/src/main/java/com/linecorp/bot/model/response/TestWebhookEndpointResponse.java new file mode 100644 index 000000000..a953a46f0 --- /dev/null +++ b/line-bot-model/src/main/java/com/linecorp/bot/model/response/TestWebhookEndpointResponse.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.bot.model.response; + +import java.time.Instant; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import com.linecorp.bot.model.response.TestWebhookEndpointResponse.TestWebhookEndpointResponseBuilder; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@JsonDeserialize(builder = TestWebhookEndpointResponseBuilder.class) +public class TestWebhookEndpointResponse { + /** + * Result of the communication from the LINE platform to the webhook URL. + */ + boolean success; + + /** + * Please refer to + * Common Properties. + */ + Instant timestamp; + + /** + * The HTTP status code. If the webhook response isn't received, the status code is set to zero or a + * negative number. + */ + int statusCode; + + /** + * Reason for the response. + */ + String reason; + + /** + * Details of the response. + */ + String detail; + + @JsonPOJOBuilder(withPrefix = "") + public static class TestWebhookEndpointResponseBuilder { + } +} diff --git a/sample-manage-audience/src/main/java/com/linecorp/bot/messagingapidemoapp/controller/BotController.java b/sample-manage-audience/src/main/java/com/linecorp/bot/messagingapidemoapp/controller/BotController.java new file mode 100644 index 000000000..75c1a35b5 --- /dev/null +++ b/sample-manage-audience/src/main/java/com/linecorp/bot/messagingapidemoapp/controller/BotController.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +package com.linecorp.bot.messagingapidemoapp.controller; + +import java.net.URI; +import java.util.concurrent.ExecutionException; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import com.linecorp.bot.client.LineMessagingClient; +import com.linecorp.bot.client.exception.NotFoundException; +import com.linecorp.bot.model.request.TestWebhookEndpointRequest; +import com.linecorp.bot.model.request.TestWebhookEndpointRequest.TestWebhookEndpointRequestBuilder; +import com.linecorp.bot.model.response.BotInfoResponse; +import com.linecorp.bot.model.response.GetWebhookEndpointResponse; +import com.linecorp.bot.model.response.TestWebhookEndpointResponse; + +import lombok.AllArgsConstructor; + +@Controller +@AllArgsConstructor +public class BotController { + LineMessagingClient client; + + @GetMapping("/bot/info") + public String info(Model model) throws ExecutionException, InterruptedException { + BotInfoResponse botInfo = client.getBotInfo().get(); + model.addAttribute("botInfo", botInfo); + return "bot/info"; + } + + @GetMapping("/bot/webhook") + public String webhook(Model model) throws InterruptedException, ExecutionException { + try { + GetWebhookEndpointResponse webhook = client.getWebhookEndpoint().get(); + model.addAttribute("webhook", webhook); + } catch (ExecutionException e) { + if (e.getCause() instanceof NotFoundException) { + model.addAttribute("notFoundException", e.getCause()); + } else { + throw e; + } + } + return "bot/get_webhook"; + } + + @PostMapping("/bot/test_webhook") + public String testWebhook(Model model, + @RequestParam("url") URI uri) throws InterruptedException, ExecutionException { + TestWebhookEndpointRequestBuilder builder = TestWebhookEndpointRequest.builder(); + if (uri != null) { + builder.endpoint(uri); + } + TestWebhookEndpointRequest testWebhookEndpointRequest = builder.build(); + TestWebhookEndpointResponse response = client.testWebhookEndpoint( + testWebhookEndpointRequest + ).join(); + model.addAttribute("response", response); + return "bot/test_webhook"; + } + +} diff --git a/sample-manage-audience/src/main/resources/templates/__wrapper.ftlh b/sample-manage-audience/src/main/resources/templates/__wrapper.ftlh index 55c9daf12..8d1ed3e23 100644 --- a/sample-manage-audience/src/main/resources/templates/__wrapper.ftlh +++ b/sample-manage-audience/src/main/resources/templates/__wrapper.ftlh @@ -14,52 +14,63 @@ - - + +
- <#nested/> + <#nested/>
diff --git a/sample-manage-audience/src/main/resources/templates/bot/get_webhook.ftlh b/sample-manage-audience/src/main/resources/templates/bot/get_webhook.ftlh new file mode 100644 index 000000000..aaab5c4bb --- /dev/null +++ b/sample-manage-audience/src/main/resources/templates/bot/get_webhook.ftlh @@ -0,0 +1,35 @@ +<#import "../__wrapper.ftlh" as wrapper> +<@wrapper.main> + +

Webhook

+ +

Webhook information

+ + <#if notFoundException??> +

This bot doesn't have an webhook endpoint: ${notFoundException.message}

+ <#else> + + + + + + + + + +
endpoint${webhook.endpoint}
active${webhook.active?c}
+ + +

Test webhook

+
+ + + + + +
(Optional) + +
+
+ + diff --git a/sample-manage-audience/src/main/resources/templates/bot/info.ftlh b/sample-manage-audience/src/main/resources/templates/bot/info.ftlh new file mode 100644 index 000000000..57637c389 --- /dev/null +++ b/sample-manage-audience/src/main/resources/templates/bot/info.ftlh @@ -0,0 +1,38 @@ +<#import "../__wrapper.ftlh" as wrapper> +<@wrapper.main> + +

Bot information

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
userId${botInfo.userId}
basicId${botInfo.basicId}
premiumId${botInfo.premiumId!"-"}
displayName${botInfo.displayName}
pictureUrl${botInfo.pictureUrl}
+ pictureUrl
chatMode${botInfo.chatMode}
markAsReadMode${botInfo.markAsReadMode}
+ + diff --git a/sample-manage-audience/src/main/resources/templates/bot/test_webhook.ftlh b/sample-manage-audience/src/main/resources/templates/bot/test_webhook.ftlh new file mode 100644 index 000000000..5faa4e066 --- /dev/null +++ b/sample-manage-audience/src/main/resources/templates/bot/test_webhook.ftlh @@ -0,0 +1,29 @@ +<#import "../__wrapper.ftlh" as wrapper> +<@wrapper.main> + +

Webhook testing result

+ + + + + + + + + + + + + + + + + + + + + + +
Success${response.success!?c}
timestamp${response.timestamp.epochSecond!}
statusCode${response.statusCode}
Reason${response.reason!"-"}
Detail${response.detail!"-"}
+ +