LINE Corporation이 2023년 10월 1일부로 LY Corporation이 되었습니다. LY Corporation의 새로운 기술 블로그를 소개합니다. LY Corporation Tech Blog

Blog


Messaging API의 신규 기능, LIFF를 소개합니다

LIFF란?

최근 새로 릴리스한 LIFF에 대해 혹시 알고 계시나요? 이 자리를 빌려 여러분께 LIFF를 소개하고 사용하는 방법까지 간략하게 보여드리고자 합니다. LIFF란 LINE Front-end Framework의 약자로, LINE 앱 속에서 작동하는 웹앱 플랫폼입니다. LIFF 앱을 LINE 앱에서 실행시키면 LINE 플랫폼으로부터 LINE 사용자 ID나 LIFF API를 이용할 때 필요한 토큰을 취득할 수 있습니다. LIFF 앱은 이런 기능을 바탕으로, 사용자 정보를 활용하는 기능을 제공하거나, 대화방에 사용자를 대신하여 메시지를 보낼 수 있습니다.

LIFF animation

LIFF 앱은 다음과 같이 세 종류의 크기로 표시할 수 있습니다. 하나의 LIFF 앱에 설정할 수 있는 크기는 하나뿐이지만 필요하면 설정 후 변경할 수도 있습니다. LIFF 앱은 인터랙티브 컨텐츠를 처리할 수 있고, LIFF SDK를 이용하여 사용자의 프로필을 취득하거나 사용자를 대신해서 대화방에 글이나 그림을 올릴 수 있습니다.

LIFF size

LIFF 앱 만들기

앱을 함께 만들어 보면 LIFF에 대해 빠르게 이해하실 것 같습니다. 우리가 만들어 볼 LIFF 앱은 캔버스에 그림을 그린 후 대화방으로 보내어 다른 사용자들과 그림을 함께 나누는 기능을 제공하는 앱입니다.

LIFF sample app

여태까지는 그림을 그려 대화방에 띄우기 위해 다음과 같은 과정을 거쳐야 했습니다.

  1. 일단 LINE 앱을 닫는다.
  2. 별도의 그림 편집 앱을 실행한다.
  3. 그림을 그린 뒤 저장한다.
  4. LINE 앱으로 돌아온다.
  5. 키보드를 실행시킨다.
  6. 첨부 파일 버튼을 탭한다.
  7. 앨범을 연다.
  8. 이미지를 선택한다.
  9. 선택한 이미지를 전송한다.

위와 같이 복잡한 과정이 LIFF를 사용하면 이렇게 바뀝니다. UX가 간략하고 직관적인 단계로 크게 개선된 것을 바로 보실 수 있습니다.

  1. LIFF 앱을 연다.
  2. 그림을 그린 뒤 전송 버튼을 누른다.

사전 작업

채널 준비하기

우선 LIFF 앱을 만들기 위해 필요한 준비 작업부터 살펴보겠습니다. LIFF 앱을 만들기 위해서는 Messaging API를 사용할 수 있는 LINE@의 채널이 필요합니다. Getting started with the Messaging API 가이드를 참고하여 안내대로 필요한 작업을 수행하고, Building a bot의 'Issue a channel access token' 부분까지 따라하세요. 과정 중에 발급받은 채널 액세스 토큰은 다음 작업에서 필요하니 어딘가에 보관해 두세요.

Bot을 만드려면 Webhook URL을 설정해야 하는데, LIFF만 이용한다면 이러한 설정은 불필요합니다. 참고로, 생성한 채널의 앱 아이콘 이미지가 LIFF 앱의 타이틀바 위에 표시됩니다.

LIFF icon

채널에 LIFF 앱 등록하기

LIFF 앱은 채널 액세스 토큰을 이용해서 채널에 연결되는 형태로, 채널당 LIFF 앱을 열 개까지 제작할 수 있습니다. 먼저, LIFF 앱을 채널에 등록하기 위해 커맨드 프롬프트나 터미널 같은 프로그램에 아래에 안내된 값을 이용하여 다음의 명령어를 입력하세요.

  • YOUR_CHANNEL_ACCESS_TOKEN: 바로 앞 단계에서 보관한 채널 액세스 토큰 값을 사용하세요.
  • SIZE_OF_LIFF: LIFF 앱의 화면 크기("compact", "tall", "full").
  • URL_OF_YOUR_APPLICATION: LIFF 앱에서 열고자 하는 웹 페이지나 웹앱의 URL(HTTPS 필수).
curl -XPOST 
-H "Authorization: Bearer YOUR_CHANNEL_ACCESS_TOKEN" 
-H "Content-Type: application/json" 
-d '{
 "view": {
 "type": "SIZE_OF_LIFF",
 "url": "URL_OF_YOUR_APPLICATION"
 }
}' 
https://api.line.me/liff/v1/apps

명령어가 성공적으로 수행되면 LIFF 앱의 앱 ID가 다음과 같은 형태로 반환되는데, 바로 우리의 LIFF 앱에 대한 URL을 구성하는 값이 됩니다. 우리의 LIFF 앱에 대한 URL은 바로 line://app/1234567890-XXXXXXXX가 됩니다.

{"liffId":"1234567890-XXXXXXXX"}

LIFF 앱은 1대1 대화방이나 그룹 대화방 등 모든 대화방에서 실행할 수 있습니다. 또한 서버 API를 이용해서 채널에 연결된 LIFF 앱의 정보를 확인하거나 업데이트, 혹은 삭제할 수도 있습니다. 이에 대한 자세한 내용은 API Reference를 확인하시기 바랍니다.

LIFF 앱 구현하기

이제부터 본격적인 구현을 시작하겠습니다. 프로그래밍 언어로는 Python 3.6, 프레임워크로는 Flask, 레이아웃을 위해서는 Bootstrap을 사용합니다.

빈 웹 페이지 만들기

먼저 아무 것도 없는 빈 웹 페이지를 만들기 위해 다음의 세 파일을 준비합니다.

app.py

from flask import Flask, request, render_template, make_response
from flask_bootstrap import Bootstrap

import os
import uuid
import base64

from PIL import Image
import warnings
warnings.simplefilter('error', Image.DecompressionBombWarning)

app = Flask(__name__)
bootstrap = Bootstrap(app)

@app.route('/')
def do_get():
    return render_template('index.html')

if __name__ == '__main__':
    app.debug = True
    app.run()

index.html

우리의 LIFF 앱의 화면이 되는 HTML 페이지를 만들겠습니다. HTML 페이지의 타이틀은 'Paint Sample'로 했습니다. 이 타이틀이 바로 LIFF 앱 타이틀바의 문구로 들어갑니다.

{% extends "bootstrap/base.html" %}

{%- block metas %}
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
{%- endblock metas %}

{% block title %}Paint Sample{% endblock %}

{% block content %}
{% endblock %}

requirements.txt

requirements.txt 파일에 설치가 필요한 패키지를 다음과 같이 명시하세요.

flask
gunicorn
flask-bootstrap
Pillow

캔버스와 버튼 추가하기

앞서 만든 빈 웹 페이지에 그림을 그릴 캔버스와 전송 버튼을 추가하겠습니다. 앞 단계에서 준비한 다음의 두 개 파일을 안내된 대로 편집하세요.

app.py

앞서 작성한 파이선 코드를 변경하고 추가하는 작업을 진행하겠습니다. 먼저 다음과 같이 코드를 변경하여, 웹 페이지로부터 취득한 데이터를 디코드하여 imgs 폴더에 저장하고, 이 폴더명을 반환합니다.

# Change the following
# app = Flask(__name__)
app = Flask(__name__, static_folder='imgs')

그리고 같은 파일에 다음의 코드를 추가하세요. LIFF 앱에서 그린 그림을 대화방에 올리려면 가로, 세로 각각 240픽셀 이내의 섬네일이 필요하니, 섬네일도 함께 만듭니다.

# Add the following
@app.route('/saveimage', methods=['POST'])
def saveimage():
    event = request.form.to_dict()

    dir_name = 'imgs'
    img_name = uuid.uuid4().hex

    # Saving image in the 'imgs' folder temporarily. Should be deleted after a certain period of time
    if not os.path.exists(dir_name):
        os.makedirs(dir_name)
    with open(os.path.join(dir_name, '{}.jpg'.format(img_name)), 'wb') as img:
        img.write(base64.b64decode(event['image'].split(",")[1]))

    original = Image.open(os.path.join(dir_name, '{}.jpg'.format(img_name)))
    # Needs simple validation of format for security since Pillow supports various type of Images
    if(original.format != 'JPEG'):
        return make_response('Unsupported image type.', 400)

    original.thumbnail((240, 240), Image.ANTIALIAS)
    original.save(os.path.join(dir_name, '{}_240.jpg'.format(img_name)), 'JPEG')

    return make_response(img_name, 200)

index.html

사용자가 그림을 그릴 수 있는 캔버스를 추가하는 작업을 진행합니다. 또한 그림을 그린 후 저장하는 버튼도 함께 만들어 보겠습니다.

{%- endblock metas %}

<!--추가-->
{% block scripts %}
{{super()}}
  <script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
  <script>
    const canvas = document.querySelector('#canvas');
    const signaturePad = new SignaturePad(canvas, {
    backgroundColor: 'rgb(238,238,238)',
    });

    $(window).on('load', function(){
    canvas.setAttribute('width', $('.container').width());
    canvas.setAttribute('height', window.innerHeight - $('#btn').outerHeight() - 10);
    signaturePad.clear();
    });
 </script>
{% endblock %}
<!--추가 끝-->

{% block title %}Paint Sample{% endblock %}

<!--추가-->
{% block content %}
<div class="container">
  <canvas id="canvas"></canvas>
  <button id="btn" type="button" class="btn btn-primary btn-block">Share</button>
</div>
{% endblock %}
<!--추가 끝-->

그림과 그림 보내는 사람 이름 올리기

마지막으로, 저장한 이미지와 그 이미지를 보내는 사람의 이름을 대화방으로 전송해 보겠습니다. 이때 비로소 LIFF SDK를 사용하게 됩니다. 우리의 index.html 파일을 다음과 같이 수정하세요.

index.html

다음의 코드를 추가하면 사용자가 버튼을 눌렀을 때 캔버스에 그려진 그림은 저장되고, 그림을 그린 사용자의 이름과 함께 대화방에 전송됩니다. LIFF SDK에 대한 자세한 내용을 보려면 API Reference를 참고하세요.

<script>
    $(window).on('load', function(){
    canvas.setAttribute('width', $('.container').width());
    canvas.setAttribute('height', window.innerHeight - $('#btn').outerHeight() - 10);
    signaturePad.clear();
    // 추가
    liff.init(function (data) {});
    });
</script>
 <!-- 추가 -->
 <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
 <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
 <script>
   $('#btn').on('click', function() {
     $.ajax({
       type: 'POST',
       url: '/saveimage',
       data: {
         'image': signaturePad.toDataURL('image/jpeg')
       },
       success: function (res, status) {
         liff.getProfile().then(function (profile) {
           liff.sendMessages([
             {
               type: 'image',
               originalContentUrl: 'https://' + document.domain + '/imgs/' + res + '.jpg',
               previewImageUrl: 'https://' + document.domain + '/imgs/' + res + '_240.jpg'
             },
             {
               type: 'text',
               text: 'From:' + profile.displayName
             }
           ]).then(function () {
             liff.closeWindow();
           }).catch(function (error) {
             window.alert('Error sending message: ' + error.message);
           });
         }).catch(function (error) {
             window.alert("Error getting profile: " + error.message);
         });
       },
       error: function (res) {
         window.alert('Error saving image: ' + res.status);
       },
       complete: function(data) {
       }
     });
   });
 </script>

대화방에서 LIFF 앱 이용하기

구현을 마친 LIFF 앱을 대화방에서 사용해 볼 차례입니다. Bot의 QR 코드를 찍어 친구가 된 뒤, 우리 LIFF 앱의 링크를 전송하고 링크를 눌러보세요. 대화방 위에 LIFF 앱이 뜨는 것을 확인할 수 있습니다. 멋진 그림 솜씨를 뽐낼 일만 남았네요!

LIFF chatroom

마무리

LIFF 앱을 이용하면서부터는, LINE 앱을 닫거나, 웹으로 이동해서 LINE에 로그인해야 했던 복잡한 과정이 사라졌습니다. 간단하게 대화창에 오버레이하는 형태로 인터랙티브 콘텐츠를 표현할 수 있게 되었기 때문입니다. 또한 웹을 기반으로 사용자를 대신하여 글이나 그림을 대화방에 올릴 수도 있게 되었습니다. 참신한 아이디어로 사용자에게 사랑받을 수 있는 LIFF 앱 개발에 여러분도 한번 동참해 보시기 바랍니다. 여러분이 보시기에 괜찮은 앱이 만들어졌다면, 해시태그 '#LINE_API'와 함께 트윗을 남겨 주세요. 여러분의 많은 참여 기다리고 있겠습니다!