Skip to content

Commit 5ab6ba2

Browse files
authored
Support webhook setting APIs (#300)
* Add function prototypes * update feature: get_webhook_endpoint * Implent put method request * Implent feature: set_webhook_endpoint * Implent feature: test_webhook_endpoint * Add documentations * Add test * fix CI errors * remove unused variable * Update feature: Use GetWebhookResponse model * fix bug * Update feature: use TestWebhookResponse * fix: set webhook response body * Update documentation * Update README.rst: get/set/test_webhook_endpoint()
1 parent 6d83355 commit 5ab6ba2

File tree

8 files changed

+378
-1
lines changed

8 files changed

+378
-1
lines changed

README.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,46 @@ https://developers.line.biz/en/reference/messaging-api/#get-bot-info
610610
print(bot_info.chat_mode)
611611
print(bot_info.mark_as_read_mode)
612612
613+
set\_webhook\_endpoint(self, webhook_endpoint, timeout=None)
614+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
615+
616+
Set the webhook endpoint URL.
617+
618+
https://developers.line.biz/en/reference/messaging-api/#set-webhook-endpoint-url
619+
620+
.. code:: python
621+
622+
line_bot_api.set_webhook_endpoint(<webhook_endpoint_URL>)
623+
624+
get\_webhook\_endpoint(self, timeout=None)
625+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
626+
627+
Get information on a webhook endpoint.
628+
629+
https://developers.line.biz/en/reference/messaging-api/#get-webhook-endpoint-information
630+
631+
.. code:: python
632+
633+
webhook = line_bot_api.get_webhook_endpoint()
634+
print(webhook.endpoint)
635+
print(webhook.active)
636+
637+
test\_webhook\_endpoint(self, webhook_endpoint=None, timeout=None)
638+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
639+
640+
Check if the configured webhook endpoint can receive a test webhook event.
641+
642+
https://developers.line.biz/en/reference/messaging-api/#test-webhook-endpoint
643+
644+
.. code:: python
645+
646+
test_result = line_bot_api.test_webhook_endpoint()
647+
print(test_result.success)
648+
print(test_result.timestamp)
649+
print(test_result.status_code)
650+
print(test_result.reason)
651+
print(test_result.detail)
652+
613653
※ Error handling
614654
^^^^^^^^^^^^^^^^^
615655

linebot/api.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
MessageDeliveryPushResponse, MessageDeliveryReplyResponse,
2929
InsightMessageDeliveryResponse, InsightFollowersResponse, InsightDemographicResponse,
3030
InsightMessageEventResponse, BroadcastResponse, NarrowcastResponse,
31-
MessageProgressNarrowcastResponse, BotInfo,
31+
MessageProgressNarrowcastResponse, BotInfo, GetWebhookResponse, TestWebhookResponse,
3232
)
3333
from .models.responses import Group
3434

@@ -1133,6 +1133,7 @@ def get_bot_info(self, timeout=None):
11331133
"""Get a bot's basic information.
11341134
11351135
https://developers.line.biz/en/reference/messaging-api/#get-bot-info
1136+
11361137
:param timeout: (optional) How long to wait for the server
11371138
to send data before giving up, as a float,
11381139
or a (connect timeout, read timeout) float tuple.
@@ -1147,6 +1148,76 @@ def get_bot_info(self, timeout=None):
11471148

11481149
return BotInfo.new_from_json_dict(response.json)
11491150

1151+
def set_webhook_endpoint(self, webhook_endpoint, timeout=None):
1152+
"""Set the webhook endpoint URL.
1153+
1154+
https://developers.line.biz/en/reference/messaging-api/#set-webhook-endpoint-url
1155+
1156+
:param str webhook_endpoint: A valid webhook URL to be set.
1157+
:param timeout: (optional) How long to wait for the server
1158+
to send data before giving up, as a float,
1159+
or a (connect timeout, read timeout) float tuple.
1160+
Default is self.http_client.timeout
1161+
:type timeout: float | tuple(float, float)
1162+
:rtype: dict
1163+
:return: Empty dict.
1164+
"""
1165+
data = {
1166+
'endpoint': webhook_endpoint
1167+
}
1168+
1169+
response = self._put(
1170+
'/v2/bot/channel/webhook/endpoint',
1171+
data=json.dumps(data),
1172+
timeout=timeout,
1173+
)
1174+
1175+
return response.json
1176+
1177+
def get_webhook_endpoint(self, timeout=None):
1178+
"""Get information on a webhook endpoint.
1179+
1180+
https://developers.line.biz/en/reference/messaging-api/#get-webhook-endpoint-information
1181+
1182+
:rtype: :py:class:`linebot.models.responses.GetWebhookResponse`
1183+
:return: Webhook information, including `endpoint` for webhook
1184+
URL and `active` for webhook usage status.
1185+
"""
1186+
response = self._get(
1187+
'/v2/bot/channel/webhook/endpoint',
1188+
timeout=timeout,
1189+
)
1190+
1191+
return GetWebhookResponse.new_from_json_dict(response.json)
1192+
1193+
def test_webhook_endpoint(self, webhook_endpoint=None, timeout=None):
1194+
"""Checks if the configured webhook endpoint can receive a test webhook event.
1195+
1196+
https://developers.line.biz/en/reference/messaging-api/#test-webhook-endpoint
1197+
1198+
:param webhook_endpoint: (optional) Set this parameter to
1199+
specific the webhook endpoint of the webhook. Default is the webhook
1200+
endpoint that is already set to the channel.
1201+
:param timeout: (optional) How long to wait for the server
1202+
to send data before giving up, as a float,
1203+
or a (connect timeout, read timeout) float tuple.
1204+
Default is self.http_client.timeout
1205+
:type timeout: float | tuple(float, float)
1206+
:rtype: :py:class:`linebot.models.responses.TestWebhookResponse`
1207+
"""
1208+
data = {}
1209+
1210+
if webhook_endpoint is not None:
1211+
data['endpoint'] = webhook_endpoint
1212+
1213+
response = self._post(
1214+
'/v2/bot/channel/webhook/test',
1215+
data=json.dumps(data),
1216+
timeout=timeout,
1217+
)
1218+
1219+
return TestWebhookResponse.new_from_json_dict(response.json)
1220+
11501221
def _get(self, path, endpoint=None, params=None, headers=None, stream=False, timeout=None):
11511222
url = (endpoint or self.endpoint) + path
11521223

@@ -1189,6 +1260,20 @@ def _delete(self, path, endpoint=None, data=None, headers=None, timeout=None):
11891260
self.__check_error(response)
11901261
return response
11911262

1263+
def _put(self, path, endpoint=None, data=None, headers=None, timeout=None):
1264+
url = (endpoint or self.endpoint) + path
1265+
1266+
if headers is None:
1267+
headers = {'Content-Type': 'application/json'}
1268+
headers.update(self.headers)
1269+
1270+
response = self.http_client.put(
1271+
url, headers=headers, data=data, timeout=timeout
1272+
)
1273+
1274+
self.__check_error(response)
1275+
return response
1276+
11921277
@staticmethod
11931278
def __check_error(response):
11941279
if 200 <= response.status_code < 300:

linebot/http_client.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@ def delete(self, url, headers=None, data=None, timeout=None):
9292
"""
9393
raise NotImplementedError
9494

95+
@abstractmethod
96+
def put(self, url, headers=None, data=None, timeout=None):
97+
"""PUT request.
98+
99+
:param str url: Request url
100+
:param dict headers: (optional) Request headers
101+
:param data: (optional) Dictionary, bytes, or file-like object to send in the body
102+
:param timeout: (optional), How long to wait for the server
103+
to send data before giving up, as a float,
104+
or a (connect timeout, read timeout) float tuple.
105+
Default is :py:attr:`self.timeout`
106+
:type timeout: float | tuple(float, float)
107+
:rtype: :py:class:`RequestsHttpResponse`
108+
:return: RequestsHttpResponse instance
109+
"""
110+
raise NotImplementedError
111+
95112

96113
class RequestsHttpClient(HttpClient):
97114
"""HttpClient implemented by requests."""
@@ -177,6 +194,29 @@ def delete(self, url, headers=None, data=None, timeout=None):
177194

178195
return RequestsHttpResponse(response)
179196

197+
def put(self, url, headers=None, data=None, timeout=None):
198+
"""PUT request.
199+
200+
:param str url: Request url
201+
:param dict headers: (optional) Request headers
202+
:param data: (optional) Dictionary, bytes, or file-like object to send in the body
203+
:param timeout: (optional), How long to wait for the server
204+
to send data before giving up, as a float,
205+
or a (connect timeout, read timeout) float tuple.
206+
Default is :py:attr:`self.timeout`
207+
:type timeout: float | tuple(float, float)
208+
:rtype: :py:class:`RequestsHttpResponse`
209+
:return: RequestsHttpResponse instance
210+
"""
211+
if timeout is None:
212+
timeout = self.timeout
213+
214+
response = requests.put(
215+
url, headers=headers, data=data, timeout=timeout
216+
)
217+
218+
return RequestsHttpResponse(response)
219+
180220

181221
class HttpResponse(with_metaclass(ABCMeta)):
182222
"""HttpResponse."""

linebot/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@
152152
NarrowcastResponse,
153153
MessageProgressNarrowcastResponse,
154154
BotInfo,
155+
GetWebhookResponse,
156+
TestWebhookResponse,
155157
)
156158
from .rich_menu import ( # noqa
157159
RichMenu,

linebot/models/responses.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,49 @@ def __init__(self, user_id=None, basic_id=None, premium_id=None,
520520
self.picture_url = picture_url
521521
self.chat_mode = chat_mode
522522
self.mark_as_read_mode = mark_as_read_mode
523+
524+
525+
class GetWebhookResponse(Base):
526+
"""Response of `get_webhook_endpoint()` .
527+
528+
https://developers.line.biz/en/reference/messaging-api/#get-webhook-endpoint-information
529+
"""
530+
531+
def __init__(self, endpoint=None, active=None, **kwargs):
532+
"""__init__ method.
533+
534+
:param str endpoint: The webhook endpoint URL.
535+
:param bool active: Whether the webhook is in use.
536+
:param kwargs:
537+
"""
538+
super(GetWebhookResponse, self).__init__(**kwargs)
539+
540+
self.endpoint = endpoint
541+
self.active = active
542+
543+
544+
class TestWebhookResponse(Base):
545+
"""Response of `test_webhook_endpoint()` .
546+
547+
https://developers.line.biz/en/reference/messaging-api/#test-webhook-endpoint
548+
"""
549+
550+
def __init__(self, success=None, timestamp=None, status_code=None,
551+
reason=None, detail=None, **kwargs):
552+
"""__init__ method.
553+
554+
:param bool success: Result of the communication from the LINE platform
555+
to the webhook URL.
556+
:param str timestamp: Timestamp
557+
:param int status_code: The HTTP status code.
558+
:param str reason: Reason for the response.
559+
:param str detail: Details of the response.
560+
:param kwargs:
561+
"""
562+
super(TestWebhookResponse, self).__init__(**kwargs)
563+
564+
self.success = success
565+
self.timestamp = timestamp
566+
self.status_code = status_code
567+
self.reason = reason
568+
self.detail = detail
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from __future__ import unicode_literals, absolute_import
16+
17+
import unittest
18+
19+
import responses
20+
21+
from linebot import (
22+
LineBotApi
23+
)
24+
25+
26+
class TestLineBotApi(unittest.TestCase):
27+
def setUp(self):
28+
self.tested = LineBotApi('channel_secret')
29+
30+
@responses.activate
31+
def test_get_webhook_endpoint(self):
32+
responses.add(
33+
responses.GET,
34+
LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/channel/webhook/endpoint',
35+
json={
36+
"endpoint": "https://example.herokuapp.com/test",
37+
"active": True,
38+
},
39+
status=200
40+
)
41+
42+
webhook = self.tested.get_webhook_endpoint()
43+
44+
request = responses.calls[0].request
45+
self.assertEqual(request.method, 'GET')
46+
self.assertEqual(
47+
request.url,
48+
LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/channel/webhook/endpoint')
49+
self.assertEqual(webhook.endpoint, 'https://example.herokuapp.com/test')
50+
self.assertEqual(webhook.active, True)
51+
52+
53+
if __name__ == '__main__':
54+
unittest.main()
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from __future__ import unicode_literals, absolute_import
16+
17+
import unittest
18+
19+
import responses
20+
21+
from linebot import (
22+
LineBotApi
23+
)
24+
25+
26+
class TestLineBotApi(unittest.TestCase):
27+
def setUp(self):
28+
self.tested = LineBotApi('channel_secret')
29+
30+
@responses.activate
31+
def test_set_webhook_endpoint(self):
32+
responses.add(
33+
responses.PUT,
34+
LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/channel/webhook/endpoint',
35+
json={},
36+
status=200
37+
)
38+
39+
result = self.tested.set_webhook_endpoint('endpoint')
40+
41+
request = responses.calls[0].request
42+
self.assertEqual(request.method, 'PUT')
43+
self.assertEqual(
44+
request.url,
45+
LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/channel/webhook/endpoint')
46+
self.assertEqual(result, {})
47+
48+
49+
if __name__ == '__main__':
50+
unittest.main()

0 commit comments

Comments
 (0)