Repository: line/line-bot-sdk-python
Pull request #153 (2019-04-03 15:18:10)

add ThingsEvent, for things-compatible device link/unlink event
- implement #143

be-hase: @clsung 
Thank you always for your PR.
Could you resolve conflict?
I'm sorry because it's a conflict because the review has been delayed...

Patch:
diff --git a/linebot/models/__init__.py b/linebot/models/__init__.py
index cb7e941..e7233e8 100644
--- a/linebot/models/__init__.py
+++ b/linebot/models/__init__.py
@@ -48,6 +48,7 @@ from .events import (  # noqa
     MemberJoinedEvent,
     MemberLeftEvent,
     BeaconEvent,
+    ThingsEvent,
     Postback,
     Beacon,
     Link,
diff --git a/linebot/models/events.py b/linebot/models/events.py
index 2f570b9..4783251 100644
--- a/linebot/models/events.py
+++ b/linebot/models/events.py
@@ -356,6 +356,37 @@ class AccountLinkEvent(Event):
         )
 
 
+class ThingsEvent(Event):
+    """Webhook ThingsEvent.
+
+    https://developers.line.biz/en/reference/messaging-api/#device-link-event
+
+    Indicates that a LINE Things-compatible device has been linked with LINE by
+    a user operation.
+    """
+
+    def __init__(self, timestamp=None, source=None, reply_token=None, things=None, **kwargs):
+        """__init__ method.
+
+        :param long timestamp: Time of the event in milliseconds
+        :param source: Source object
+        :type source: T <= :py:class:`linebot.models.sources.Source`
+        :param str reply_token: Reply token
+        :param things: Things object
+        :type things: :py:class:`linebot.models.events.Things`
+        :param kwargs:
+        """
+        super(ThingsEvent, self).__init__(
+            timestamp=timestamp, source=source, **kwargs
+        )
+
+        self.type = 'things'
+        self.reply_token = reply_token
+        self.things = self.get_or_new_from_json_dict(
+            things, Things
+        )
+
+
 class Postback(Base):
     """Postback.
 
@@ -467,3 +498,21 @@ class Link(Base):
 
         self.result = result
         self.nonce = nonce
+
+
+class Things(Base):
+    """Things.
+
+    https://developers.line.biz/en/docs/line-things/develop-bot/#link-event
+    """
+
+    def __init__(self, device_id=None, type=None, **kwargs):
+        """__init__ method.
+
+        :param str device_id: Device ID of the device that was linked with LINE.
+        :param str type: link or unlink
+        """
+        super(Things, self).__init__(**kwargs)
+
+        self.device_id = device_id
+        self.type = type
diff --git a/linebot/webhook.py b/linebot/webhook.py
index 415ceb1..1fed867 100644
--- a/linebot/webhook.py
+++ b/linebot/webhook.py
@@ -34,6 +34,7 @@ from .models.events import (
     AccountLinkEvent,
     MemberJoinedEvent,
     MemberLeftEvent,
+    ThingsEvent,
 )
 from .utils import LOGGER, PY3, safe_compare_digest
 
@@ -150,6 +151,8 @@ class WebhookParser(object):
                 events.append(MemberJoinedEvent.new_from_json_dict(event))
             elif event_type == 'memberLeft':
                 events.append(MemberLeftEvent.new_from_json_dict(event))
+            elif event_type == 'things':
+                events.append(ThingsEvent.new_from_json_dict(event))
             else:
                 LOGGER.warn('Unknown event type. type=' + event_type)
 


Test patch:
diff --git a/tests/test_webhook.py b/tests/test_webhook.py
index 980b5a5..a6e3b5f 100644
--- a/tests/test_webhook.py
+++ b/tests/test_webhook.py
@@ -24,7 +24,7 @@ from linebot import (
 from linebot.models import (
     MessageEvent, FollowEvent, UnfollowEvent, JoinEvent,
     LeaveEvent, PostbackEvent, BeaconEvent, AccountLinkEvent,
-    MemberJoinedEvent, MemberLeftEvent,
+    MemberJoinedEvent, MemberLeftEvent, ThingsEvent,
     TextMessage, ImageMessage, VideoMessage, AudioMessage,
     LocationMessage, StickerMessage, FileMessage,
     SourceUser, SourceRoom, SourceGroup
@@ -304,20 +304,16 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[18].postback.data, 'action=buyItem&itemId=123123&color=red')
         self.assertEqual(events[18].postback.params['datetime'], '2013-04-01T10:00')
 
-        # MessageEvent, SourceUser, FileMessage
-        self.assertIsInstance(events[19], MessageEvent)
+        # ThingsEvent, SourceUser, link
+        self.assertIsInstance(events[19], ThingsEvent)
         self.assertEqual(events[19].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
-        self.assertEqual(events[19].type, 'message')
+        self.assertEqual(events[19].type, 'things')
         self.assertEqual(events[19].timestamp, 1462629479859)
         self.assertIsInstance(events[19].source, SourceUser)
         self.assertEqual(events[19].source.type, 'user')
         self.assertEqual(events[19].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
-        self.assertEqual(events[19].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
-        self.assertIsInstance(events[19].message, FileMessage)
-        self.assertEqual(events[19].message.id, '325708')
-        self.assertEqual(events[19].message.type, 'file')
-        self.assertEqual(events[19].message.file_name, "file.txt")
-        self.assertEqual(events[19].message.file_size, 2138)
+        self.assertEqual(events[19].things.device_id, 't2c449c9d1')
+        self.assertEqual(events[19].things.type, 'link')
 
         # MemberJoinedEvent
         self.assertIsInstance(events[20], MemberJoinedEvent)
@@ -344,6 +340,32 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[21].left.members[0].user_id, 'U4af4980629...')
         self.assertEqual(events[21].left.members[1].user_id, 'U91eeaf62d9...')
 
+        # ThingsEvent, SourceUser, unlink
+        self.assertIsInstance(events[22], ThingsEvent)
+        self.assertEqual(events[22].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
+        self.assertEqual(events[22].type, 'things')
+        self.assertEqual(events[22].timestamp, 1462629479859)
+        self.assertIsInstance(events[22].source, SourceUser)
+        self.assertEqual(events[22].source.type, 'user')
+        self.assertEqual(events[22].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[22].things.device_id, 't2c449c9d1')
+        self.assertEqual(events[22].things.type, 'unlink')
+
+        # MessageEvent, SourceUser, FileMessage
+        self.assertIsInstance(events[23], MessageEvent)
+        self.assertEqual(events[23].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
+        self.assertEqual(events[23].type, 'message')
+        self.assertEqual(events[23].timestamp, 1462629479859)
+        self.assertIsInstance(events[23].source, SourceUser)
+        self.assertEqual(events[23].source.type, 'user')
+        self.assertEqual(events[23].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[23].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertIsInstance(events[23].message, FileMessage)
+        self.assertEqual(events[23].message.id, '325708')
+        self.assertEqual(events[23].message.type, 'file')
+        self.assertEqual(events[23].message.file_name, "file.txt")
+        self.assertEqual(events[23].message.file_size, 2138)
+
 
 class TestWebhookHandler(unittest.TestCase):
     def setUp(self):
diff --git a/tests/text/webhook.json b/tests/text/webhook.json
index 09ab5a9..bfa54ec 100644
--- a/tests/text/webhook.json
+++ b/tests/text/webhook.json
@@ -248,17 +248,15 @@
     },
     {
       "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
-      "type": "message",
+      "type": "things",
       "timestamp": 1462629479859,
       "source": {
         "type": "user",
         "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
       },
-      "message": {
-        "id": "325708",
-        "type": "file",
-        "fileName": "file.txt",
-        "fileSize": 2138
+      "things": {
+        "deviceId": "t2c449c9d1",
+        "type": "link"
       }
     },
     {
@@ -301,6 +299,34 @@
           }
         ]
       }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "things",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "things": {
+        "deviceId": "t2c449c9d1",
+        "type": "unlink"
+      }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "file",
+        "fileName": "file.txt",
+        "fileSize": 2138
+      }
     }
   ]
 }