GoogleスプレッドシートにあるURLをLIFF化する

#概要
今まで手作業でLINE Developer portalからLIFFを作成していたのですが、効率化＋複数人管理対応のためにスプレッドシート管理にしたよ、という記事です
#背景
社会人バドミントンサークルをLINE公式アカウントを利用して運営しており、集客の仕組みでLIFFを利用しています。
これまでのフローでは、主催者（私）が練習申し込み用のWebアプリから練習会を設定し、URLを発行します。URLは?date=2020-01-01のようにパラメータで管理され、それをダッシュボードから手動でLIFF化して利用していました。

今までそのフローで何とかやっていたのですが、自分のコミットが保証できなくなってきたことに伴い運営メンバーを追加したことで、作業を自動化しつつ誰でもできるようにする必要が出てきました。そこで今回はスプレッドシートを利用して、作成する仕組みを作りました。

#使うもの
Google スプレッドシート
Google Apps Script
LINE Frontend Framework v1 サーバーAPI

#成果物
A列に記載されたURLをLIFF化してB列に出力するようなGASを書きました。
実行前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148084/53aef946-acf2-df03-d970-5dad2b8c13ca.png)
実行後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148084/e1527744-968c-1483-d9de-61ec9bcecfad.png)

## A列からデータを読み出し
スプレッドシートを作成し、スクリプトエディタを起動します
まず、元となるURLを取り出します

```javascript:urltoliff.gs
function urltoliff() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  let range = sheet.getRange('A2');
  //A2セルに入ったURLを受け取る
  let url = range.getValue();
}
```

## LIFF サーバーAPIからLIFFIDを取得する
ドキュメントはこちら
https://developers.line.biz/ja/reference/liff-v1/#bluetooth-referring-device

まず[LINE Developers](https://developers.line.biz/)からプロバイダーとチャンネルを作成する必要があります。私はもともと持っていたLINE Login API用のチャンネルを利用して行いました。

### チャンネルIDとチャンネルsecretの確認
チャンネルのダッシュボードに入ったらBasic settingsから、**Channel ID**と**Channel secret**をメモっておきます。下部にYour Client IDというのもあるのですがそれは今回使わないので注意してください。ここでしばらくハマっていました。
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148084/86a7c8f4-a738-acdb-9b58-fde64aaf79bf.png)

### チャンネルアクセストークンを取得する
LINE Login向けのチャンネルではロングタームのチャンネルアクセストークンは発行できないため、別途accesstokenをLINE側に投げて取得しておく必要があります。
GASから外部へpostする場合は、`UrlFetchApp.fetch();`を利用して行います
https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app

LINEのドキュメントでは以下のようにPOSTすればアクセストークンがもらえるそうです

```shell
curl -v -X POST https://api.line.me/v2/oauth/accessToken \
-H "Content-Type:application/x-www-form-urlencoded" \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id={channel ID}' \
--data-urlencode 'client_secret={channel secret}'
```

これをGASで書き直すと以下のようになります

```javascript
function getAccessToken(){
   let payload =
   {
     'grant_type':'client_credentials',
     'client_id':{channel ID},
     'client_secret':{channel secret}
   };

   let options =
   {
     "method" : "post",
     'contentType': 'application/x-www-form-urlencoded',
     "payload" : payload
   };

  let response= sendHttpPost("https://api.line.me/v2/oauth/accessToken",options);
  return response.access_token;
}

function sendHttpPost(endpoint,options){
   let response = UrlFetchApp.fetch(endpoint, options);
   return JSON.parse(response.getContentText());
}
```
これで`getAccessToken();`を呼べばAccesstokenが手に入るようになりました。

ここまででコードは以下のようになりました


```javascript:urltoliff.gs
function urltoliff() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  let range = sheet.getRange('A2');
  //A2セルに入ったURLを受け取る
  let url = range.getValue();
  //アクセストークンを取得
  let token = getAccessToken();
}
function getAccessToken(){...}
function sendHttpPost(endpoint,options){...}
```

### LIFFの作成
LIFFを作成する場合は以下のようなリクエストを投げれば作れるとのことです

```shell
curl -X POST https://api.line.me/liff/v1/apps \
-H "Authorization: Bearer {channel access token}" \
-H "Content-Type: application/json" \
-d '{
  "view":{
    "type":"full",
    "url":"https://example.com/myservice"
  },
  "description": "Service Example",
  "features": {
    "ble": true
  }
}'
```
これをGASでは以下のように書き直せます

```javascript
function createLIFF(token){  
  let payload=
  {
    "view":{
      "type":"full",
      "url":"https://example.com/myservice"
    },
    "description": "Service Example",
    "features": {
      "ble": true
    }
  }

  let options = 
  {
    "headers" : {
      "Authorization":"Bearer "+token
      },
      "method":"post",
    "contentType":"application/json",
    "payload" : JSON.stringify(payload)
  }
  
  let response = sendHttpPost("https://api.line.me/liff/v1/apps",options);
  return liffResponse.liffId;
}
```

ポイントは、shellでは -d で渡しているためURLエンコードせず生のjsonで渡す必要がある点です。トークンの時のようにそのままpayloadを渡すとデフォルトでURLエンコードされてしまうため、URLエンコードしたくないものについては、payloadを`JSON.stringify()`してから文字列で渡す必要があります。以下公式ドキュメントにも書いてありますがここでしばらくハマりました
https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchurl,-params

ここまででコードは以下のようになりました


```javascript:urltoliff.gs
function urltoliff() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  let range = sheet.getRange('A2');
  //A2セルに入ったURLを受け取る
  let url = range.getValue();
  //アクセストークンを取得
  let token = getAccessToken();
  //LIFFIDを取得
　let liffid = createLIFF(token);
}
function getAccessToken(){...}
function sendHttpPost(endpoint,options){...}
function createLIFF(token){...}
```

##スプレッドシートに出力する
最後に取得したLIFFをスプレッドシートに出力します。
サーバーAPIから得られるIDはURL全体ではないので、LIFFアプリとして起動するよう、接頭に`https://liff.line.me/`を追加して出力します

```javascript:urltoliff.gs
function urltoliff() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  let range = sheet.getRange('A2');
  //A2セルに入ったURLを受け取る
  let url = range.getValue();
  //アクセストークンを取得
  let token = getAccessToken();
  //LIFFIDを取得
　let liffid = createLIFF(token);
　
  //LIFFアプリURLを出力する
　let liffRange = sheet.getRange('B2');
  liffRange.setValue('https://liff.line.me/' + liffid);
}
function getAccessToken(){...}
function sendHttpPost(endpoint,options){...}
function createLIFF(token){...}
```

以上でLIFFアプリをスプレッドシートから作成可能です。
LIFFは仕様上1チャンネルで30LIFFアプリしか作れないため、このまま量産していくとすぐ飽和してしまいます。
サーバーAPIでは取得や削除のAPIも用意しているため、それは次で紹介します。

GASでpostリクエストする際の参考等にもしていただけたら嬉しいです。