【のこるんvol.2】3タップ完了LINEフォトアルバム #LINE #AppSheet #GAS

## スマホ中の写真　埋もれてない？

  本記事は前回記事：[3タップで完了するLINEフォトアルバム](https://qiita.com/okinakamasayoshi/items/1b30f3b8533a61bd2f12) の続編です

 スマホがこの世に誕生し、写真の撮影枚数はそれ以前の1,500倍に、
  **1スマホの中に少なくとも1,000枚以上の写真があるそうです。**</font>
 ![①.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/7d36f1ff-7b01-a432-0aa6-a57eb9eee676.png)

 せっかく撮影しても、整理できずにいませんか？。
 <font color="Red">**思い出が埋もれたり、写真と一緒に消えてしまうのは悲しい。**</font>
 とはいえ新たにアプリを使うのはめんどくさい。(ですよね？)

 そこで・・・

 プログラミングの「プ」の字も知らなかった初心者ですが、
<font color="Red">**日本一簡単操作のLINEフォトアルバムアプリを自分で作ります**</font>💪

 写ルだけじゃなく残ルンです!!　その名も<font color="Red">**「のこるん」**</font>ぜひ覚えて下さい♪

 前回までの記事はこちら👇

 https://qiita.com/okinakamasayoshi/items/1b30f3b8533a61bd2f12

## 最終ゴールはWEBアプリ　今はスプレッドシート

 **【目指すゴール】**
 LINEで写真とコメントを送ると、即アルバムが完成する<font color="Red">**webアプリ**</font>を作りたい。

**【第1ステップ】**
 まずはLINEで送付された　①写真　と　②コメント　の情報を取得し、
 <font color="Red">**スプレッドシート上にデータ保存**</font>していく状態を目指しています。

 ![②.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/c5f4a2d6-776c-c2e1-99ed-32a67c296634.png)

 第一ステップ「スプレッドシート上にデータを保存」のLINE操作と全体図です。

![③.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/3c62e9c2-fca3-6df3-00ae-0811f55b2291.png)

## コメントが取得できない→解決!!

 [前回期の記事](https://qiita.com/okinakamasayoshi/items/1b30f3b8533a61bd2f12)で、画像は取得できるものの
 コメントを取得できず、スプレッドシートに画像しか保存できませんでした。

 苦節数日。やっと解決し、LINE上の写真とコメントをシートに保存できるようになりました!!

   <details><summary>うまくいかなかった前回の全体コード　<font color="Red">**(クリックで表示)**</font></summary><div><br><br>

```js
 
// 応答メッセージURL
const REPLY = "https://api.line.me/v2/bot/message/reply";

// アクセストークン
const ACCESS_TOKEN = "********";

// スプレッドシート情報

const SHEET_ID   = '********';
const SHEET      = SpreadsheetApp.openById(SHEET_ID).getSheetByName('シート1');

// Google Drive ID
const GOOGLE_DRIVE_ID = "********";

// LINEから送られてきたデータを取得 doPost()
function doPost(e) {

  //メッセージ受信
  const data = JSON.parse(e.postData.contents).events[0];
  //ユーザーID取得
  const lineUserId = data.source.userId;
  // リプレイトークン取得
  const replyToken = data.replyToken;
  // 送信されたメッセージの種類を取得
  // https://developers.line.biz/ja/docs/messaging-api/message-types/#sticker-messages
  const postType = data.message.type;

  //コメント入力のために追加しているか箇所①
  const json = JSON.parse(e.postData.contents);
  const message = json.events[0].message.text; 
  const messageParameter = message.split(/\r\n|\n/);
  var lastRow = SHEET.getLastRow();
  //追加①ここまで

  // if文書き変え　写真の時データ取得
  if ( "image" === postType ) {
    imageSave(replyToken, lineUserId, data);
 
   //そうじゃない時はG2に文字入力
 
  } else {
      var lastRow = SHEET.getLastRow();
  SHEET.getRange('G' +  2).setValue(messageParameter[0]);
  }

  //書き変えここまで

}




// 送信された画像を保存 imageSave()
function imageSave(replyToken, lineUserId, data) {

  // LINEから画像取得 getImg()
  const imgData = getImg(data);

  // Googleドライブに保存　saveImg()
  const imgInfo = saveImg(imgData, lineUserId);

  // //「保存完了」とLINEにメッセージを送る
  sendMessage(replyToken, imgInfo);

}

// LINEから画像取得　getImg()
function getImg(data) {

  const IMG_URL = 'https://api-data.line.me/v2/bot/message/' + data.message.id + '/content';
  const HEAD = {
    "method":"get",
    "headers": {
      "Authorization" : "Bearer " + ACCESS_TOKEN
    }
  }
  const imgData = UrlFetchApp.fetch(IMG_URL, HEAD);
  return imgData;

}

// Googleドライブに保存　saveImg()
function saveImg(imgBinary, lineUserId){

  //GoogleDriveフォルダID
  const folder = DriveApp.getFolderById(GOOGLE_DRIVE_ID);
  //ランダムな文字列を生成して、画像のファイル名とする
  const fileName = Math.random().toString(36).slice(-8);
  //Googleドライブのフォルダに画像を生成
  const imageFile = folder.createFile(imgBinary.getBlob().setName(fileName));
  //「保存しました」としたメッセージを変数に代入
  const imgInfo = '保存したよ！https://docs.google.com/spreadsheets/d/1X8sR_aCzZydKSdAInkBZG9TcDeWInoq38TeTKRt9-KA/edit#gid=0';

  //画像ファイルURL取得
  const imageURL = 'https://drive.google.com/uc?export=view&id=' + imageFile.getId();

  //画像ファイルにリンクでアクセスの権限付与
  imageFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);

  debugImgLog(imageURL, lineUserId);
  return imgInfo;

}

// スプレッドシートに画像を保存 debugImgLog()
function debugImgLog(text, userId) {

  if ('' == text) {
    return;
  }

  const date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss')
  const userName = getUserDisplayName(userId)
  const userImg = getUserDisplayImg(userId)

  SHEET.appendRow([userId, userImg, userName, text, date,'=image("'+text+'")']);
  // 日付順に並び替え
  const numColumn = SHEET.getLastColumn(); // 最後列の列番号を取得
  const numRow    = SHEET.getLastRow()-1;  // 最後行の行番号を取得
  const dataRange = SHEET.getRange(2, 1, numRow, numColumn);
  dataRange.sort([{column: 5, ascending: false}]);

}

// ユーザーのプロフィール名取得 getUserDisplayName()
function getUserDisplayName(userId) {

  const url = 'https://api.line.me/v2/bot/profile/' + userId;
  const userProfile = UrlFetchApp.fetch(url,{
    'headers': {
      'Authorization' : 'Bearer ' + ACCESS_TOKEN,
    },
  })
  return JSON.parse(userProfile).displayName;

}

// ユーザーのプロフィール画像取得 getUserDisplayImg()
function getUserDisplayImg(userId) {

  const url = 'https://api.line.me/v2/bot/profile/' + userId;
  const userProfile = UrlFetchApp.fetch(url,{
    'headers': {
      'Authorization' :  'Bearer ' + ACCESS_TOKEN,
    },
  })
  return JSON.parse(userProfile).pictureUrl;

}

// LINEにメッセージ送信 sendMessage()
function sendMessage(replyToken, replyText) {

  const postData = {
    "replyToken" : replyToken,
    "messages" : [
      {
        "type" : "text",
        "text" : replyText
      }
    ]
  };

  const headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    "Authorization" : "Bearer " + ACCESS_TOKEN
  };

  const options = {
    "method" : "POST",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
  };

  return UrlFetchApp.fetch(REPLY, options);

}

```
</div></details>

 **改善① LINE上のメッセージ取得コードを変更**

 改善前の``function doPost(e)``の中身

```js
  const json = JSON.parse(e.postData.contents);
  const message = json.events[0].message.text; 
  const messageParameter = message.split(/\r\n|\n/);
  var lastRow = SHEET.getLastRow();
```

 改善後
 
```js
 const userMessage = JSON.parse(e.postData.contents).events[0].message.text;
```

 **改善②if文の書き変え**

 改善前の``if文``

```js
 if ( "image" === postType ) {
    imageSave(replyToken, lineUserId, data);
  } else {
      var lastRow = SHEET.getLastRow();
  SHEET.getRange('G' +  2).setValue(messageParameter[0]);
  }
```

 改善後

```js
if ( "image" === postType ) {
imageSave(replyToken, lineUserId, data);
} else {
SHEET.getRange('G' + 2).setValue(userMessage);
}
```

   <details><summary>写真・コメントもLINE取得シート貼付成功コード　<font color="Red">**(クリックで表示)**</font></summary><div><br><br>

```js

// 応答メッセージURL
const REPLY = "https://api.line.me/v2/bot/message/reply";

// アクセストークン
const ACCESS_TOKEN = "********";

// スプレッドシート情報

const SHEET_ID = '********';
const SHEET = SpreadsheetApp.openById(SHEET_ID).getSheetByName('シート1');

// Google Drive ID
const GOOGLE_DRIVE_ID = "********";

// LINEから送られてきたデータを取得 doPost()
function doPost(e) {

//メッセージ受信
const data = JSON.parse(e.postData.contents).events[0];
//ユーザーID取得
const lineUserId = data.source.userId;
// リプレイトークン取得
const replyToken = data.replyToken;
// 送信されたメッセージの種類を取得
// https://developers.line.biz/ja/docs/messaging-api/message-types/#sticker-messages
const postType = data.message.type;

//沖中追加①
const userMessage = JSON.parse(e.postData.contents).events[0].message.text;
//追加①ここまで

// 写真の時
if ( "image" === postType ) {
imageSave(replyToken, lineUserId, data);

} else {

SHEET.getRange('G' + 2).setValue(userMessage);
}

}

// 送信された画像を保存 imageSave()
function imageSave(replyToken, lineUserId, data) {

// LINEから画像取得 getImg()
const imgData = getImg(data);

// Googleドライブに保存　saveImg()
const imgInfo = saveImg(imgData, lineUserId);

// //「保存完了」とLINEにメッセージを送る
sendMessage(replyToken, imgInfo);

}

// LINEから画像取得　getImg()
function getImg(data) {

const IMG_URL = 'https://api-data.line.me/v2/bot/message/' + data.message.id + '/content';
const HEAD = {
"method":"get",
"headers": {
"Authorization" : "Bearer " + ACCESS_TOKEN
}
}
const imgData = UrlFetchApp.fetch(IMG_URL, HEAD);
return imgData;

}

// Googleドライブに保存　saveImg()
function saveImg(imgBinary, lineUserId){

//GoogleDriveフォルダID
const folder = DriveApp.getFolderById(GOOGLE_DRIVE_ID);
//ランダムな文字列を生成して、画像のファイル名とする
const fileName = Math.random().toString(36).slice(-8);
//Googleドライブのフォルダに画像を生成
const imageFile = folder.createFile(imgBinary.getBlob().setName(fileName));
//「保存しました」としたメッセージを変数に代入
const imgInfo = '保存したよ！https://docs.google.com/spreadsheets/d/1X8sR_aCzZydKSdAInkBZG9TcDeWInoq38TeTKRt9-KA/edit#gid=0';

//画像ファイルURL取得
const imageURL = 'https://drive.google.com/uc?export=view&id=' + imageFile.getId();

//画像ファイルにリンクでアクセスの権限付与
imageFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);

debugImgLog(imageURL, lineUserId);
return imgInfo;

}

// スプレッドシートに画像を保存 debugImgLog()
function debugImgLog(text, userId) {

if ('' == text) {
return;
}

const date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss')
const userName = getUserDisplayName(userId)
const userImg = getUserDisplayImg(userId)

SHEET.appendRow([userId, userImg, userName, text, date,'=image("'+text+'")']);
// 日付順に並び替え
const numColumn = SHEET.getLastColumn(); // 最後列の列番号を取得
const numRow = SHEET.getLastRow()-1; // 最後行の行番号を取得
const dataRange = SHEET.getRange(2, 1, numRow, numColumn);
dataRange.sort([{column: 5, ascending: false}]);

}

// ユーザーのプロフィール名取得 getUserDisplayName()
function getUserDisplayName(userId) {

const url = 'https://api.line.me/v2/bot/profile/' + userId;
const userProfile = UrlFetchApp.fetch(url,{
'headers': {
'Authorization' : 'Bearer ' + ACCESS_TOKEN,
},
})
return JSON.parse(userProfile).displayName;

}

// ユーザーのプロフィール画像取得 getUserDisplayImg()
function getUserDisplayImg(userId) {

const url = 'https://api.line.me/v2/bot/profile/' + userId;
const userProfile = UrlFetchApp.fetch(url,{
'headers': {
'Authorization' : 'Bearer ' + ACCESS_TOKEN,
},
})
return JSON.parse(userProfile).pictureUrl;

}

// LINEにメッセージ送信 sendMessage()
function sendMessage(replyToken, replyText) {

const postData = {
"replyToken" : replyToken,
"messages" : [
{
"type" : "text",
"text" : replyText
}
]
};

const headers = {
"Content-Type" : "application/json; charset=UTF-8",
"Authorization" : "Bearer " + ACCESS_TOKEN
};

const options = {
"method" : "POST",
"headers" : headers,
"payload" : JSON.stringify(postData)
};

return UrlFetchApp.fetch(REPLY, options);

}

```
</div></details>

 <font color="Red">**LINEで写真・コメントを送付すると、スプレッドシートにデータを保存することに成功!!**</font>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/7ff22d0a-1ab7-328a-6601-923935ebb933.png)

 せっかくなのでデザインも、アルバムっぽく整えました。「のこるん」いい感じ!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/0d15ff3e-79f4-22a5-4c24-5555090784f7.png)

 操作感の分かる動画はこちらです👇

 https://twitter.com/okinakamasayos1/status/1472188974752231426

## いよいよ「のこるん」WEBアプリ化へ挑戦!!

 スプレッドシートではやっぱり見にくいので、<font color="Red">**WEBアプリでの実装にトライ**</font>します。

 <font color="Red">**数か月前までプログラミングの「プ」も知らなかったのに、WEBアプリを作るなんて・・・**</font>

**次回はAppSheet**を使って、スプレッドシートからWEBアプリ化していきます♪
 この先の挑戦もまた記事にします(*^^)vお楽しみに!!

 
 最後に宣伝させてください。
 私には<font color="Red">**『思い出を消さない未来を作りたい』という夢**</font>があります。

 **「のこるん」は夢の実現のための第一歩**です。
 どこまで実現できるか分かりませんが、<font color="Red">**1月中旬にはクラウドファンディングにも挑戦します!**</font>
 **「のこるん」誕生に向け、よければ応援してください!!**(*´ω｀*)　ではまた次回の記事で!!

https://note.com/m_okinaka/n/n30c09e837ae0

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/36e96d36-0194-6a55-1ee4-6a34ec939c45.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/cc0926d6-1262-48a3-52cd-db0a7a2a9066.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/8d810e06-7e3c-d591-3d2d-91640edc67c6.png)