Repository: line/line-bot-sdk-python
Pull request #65 (2017-09-12 08:05:20)

Add FileMessage
Add file type message handling.

As described in docs: [https://devdocs.line.me/en/#file-message](https://devdocs.line.me/en/#file-message)

be-hase: Thanks for your contribution. 👍 

Please could you check these 3 points ?

1: Cloud you fix flake8 errors ?
https://travis-ci.org/line/line-bot-sdk-python/jobs/274501933#L555-L558

2: Have you already signed [ICLA](https://feedback.line.me/enquete/public/919-h9Yqmr1u) ?
https://feedback.line.me/enquete/public/919-h9Yqmr1u

3: If possible, I want the test code of parsing webhook.
for example, 
* https://github.com/line/line-bot-sdk-python/blob/master/tests/text/webhook.json#L221-L235
* https://github.com/line/line-bot-sdk-python/blob/master/tests/test_webhook.py#L282-L292
xsfishxs: Fixed :)

Patch:
diff --git a/README.rst b/README.rst
index c4b1328..575bc99 100644
--- a/README.rst
+++ b/README.rst
@@ -660,6 +660,11 @@ Message
     - id
     - package\_id
     - sticker\_id
+_ FileMessage
+    - type
+    - id
+    - file\_size
+    - file\_name
 
 Hints
 -----
diff --git a/examples/flask-kitchensink/app.py b/examples/flask-kitchensink/app.py
index a07916d..c395700 100644
--- a/examples/flask-kitchensink/app.py
+++ b/examples/flask-kitchensink/app.py
@@ -36,7 +36,7 @@ from linebot.models import (
     PostbackTemplateAction, DatetimePickerTemplateAction,
     CarouselTemplate, CarouselColumn, PostbackEvent,
     StickerMessage, StickerSendMessage, LocationMessage, LocationSendMessage,
-    ImageMessage, VideoMessage, AudioMessage,
+    ImageMessage, VideoMessage, AudioMessage, FileMessage,
     UnfollowEvent, FollowEvent, JoinEvent, LeaveEvent, BeaconEvent
 )
 
@@ -231,6 +231,25 @@ def handle_content_message(event):
         ])
 
 
+@handler.add(MessageEvent, message=FileMessage)
+def handle_file_message(event):
+    message_content = line_bot_api.get_message_content(event.message.id)
+    with tempfile.NamedTemporaryFile(dir=static_tmp_path, prefix='file-', delete=False) as tf:
+        for chunk in message_content.iter_content():
+            tf.write(chunk)
+        tempfile_path = tf.name
+
+    dist_path = tempfile_path + '-' + event.message.file_name
+    dist_name = os.path.basename(dist_path)
+    os.rename(tempfile_path, dist_path)
+
+    line_bot_api.reply_message(
+        event.reply_token, [
+            TextSendMessage(text='Save file.'),
+            TextSendMessage(text=request.host_url + os.path.join('static', 'tmp', dist_name))
+        ])
+
+
 @handler.add(FollowEvent)
 def handle_follow(event):
     line_bot_api.reply_message(
diff --git a/linebot/models/__init__.py b/linebot/models/__init__.py
index 03ad966..19c0865 100644
--- a/linebot/models/__init__.py
+++ b/linebot/models/__init__.py
@@ -49,6 +49,7 @@ from .messages import (  # noqa
     AudioMessage,
     LocationMessage,
     StickerMessage,
+    FileMessage
 )
 from .responses import (  # noqa
     Profile,
diff --git a/linebot/models/events.py b/linebot/models/events.py
index 486dec2..132d83c 100644
--- a/linebot/models/events.py
+++ b/linebot/models/events.py
@@ -27,7 +27,8 @@ from .messages import (
     VideoMessage,
     AudioMessage,
     LocationMessage,
-    StickerMessage
+    StickerMessage,
+    FileMessage
 )
 from .sources import SourceUser, SourceGroup, SourceRoom
 
@@ -93,7 +94,8 @@ class MessageEvent(Event):
                 'video': VideoMessage,
                 'audio': AudioMessage,
                 'location': LocationMessage,
-                'sticker': StickerMessage
+                'sticker': StickerMessage,
+                'file': FileMessage
             }
         )
 
diff --git a/linebot/models/messages.py b/linebot/models/messages.py
index f0db415..ace6e23 100644
--- a/linebot/models/messages.py
+++ b/linebot/models/messages.py
@@ -167,3 +167,27 @@ class StickerMessage(Message):
         self.type = 'sticker'
         self.package_id = package_id
         self.sticker_id = sticker_id
+
+
+class FileMessage(Message):
+    """FileMessage.
+
+    https://devdocs.line.me/en/#file-message
+
+    Message object which contains the file content sent from the source.
+    The binary file data can be retrieved with the Content API.
+    """
+
+    def __init__(self, id=None, file_name=None, file_size=None, **kwargs):
+        """__init__ method.
+
+        :param str id: Message ID
+        :param str file_name: File Name
+        :param int file_size: File Size
+        :param kwargs:
+        """
+        super(FileMessage, self).__init__(id=id, **kwargs)
+
+        self.type = 'file'
+        self.file_size = file_size
+        self.file_name = file_name


Test patch:
diff --git a/tests/test_webhook.py b/tests/test_webhook.py
index 4b0c01a..d6ff354 100644
--- a/tests/test_webhook.py
+++ b/tests/test_webhook.py
@@ -25,7 +25,7 @@ from linebot.models import (
     MessageEvent, FollowEvent, UnfollowEvent, JoinEvent,
     LeaveEvent, PostbackEvent, BeaconEvent,
     TextMessage, ImageMessage, VideoMessage, AudioMessage,
-    LocationMessage, StickerMessage,
+    LocationMessage, StickerMessage, FileMessage,
     SourceUser, SourceRoom, SourceGroup
 )
 
@@ -291,6 +291,21 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[17].postback.data, 'action=buyItem&itemId=123123&color=red')
         self.assertEqual(events[17].postback.params['datetime'], '2013-04-01T10:00')
 
+        # MessageEvent, SourceUser, FileMessage
+        self.assertIsInstance(events[18], MessageEvent)
+        self.assertEqual(events[18].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
+        self.assertEqual(events[18].type, 'message')
+        self.assertEqual(events[18].timestamp, 1462629479859)
+        self.assertIsInstance(events[18].source, SourceUser)
+        self.assertEqual(events[18].source.type, 'user')
+        self.assertEqual(events[18].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[18].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertIsInstance(events[18].message, FileMessage)
+        self.assertEqual(events[18].message.id, '325708')
+        self.assertEqual(events[18].message.type, 'file')
+        self.assertEqual(events[18].message.file_name, "file.txt")
+        self.assertEqual(events[18].message.file_size, 2138)
+
 
 class TestWebhookHandler(unittest.TestCase):
     def setUp(self):
diff --git a/tests/text/webhook.json b/tests/text/webhook.json
index 7a0bea8..14c8e91 100644
--- a/tests/text/webhook.json
+++ b/tests/text/webhook.json
@@ -232,6 +232,21 @@
           "datetime": "2013-04-01T10:00"
         }
       }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "message",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "message": {
+        "id": "325708",
+        "type": "file",
+        "fileName": "file.txt",
+        "fileSize": 2138
+      }
     }
   ]
 }