3タップで完了するLINEフォトアルバム #LINE #GAS #GoogleSpreadSheet

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

 スマホがこの世に誕生し、撮られる写真はそれ以前の10倍に。
  <font color="Red">**世界で1年に撮影される写真は瞬時に1兆枚を突破。
 1スマホの中に少なくとも1,000枚以上の写真があるそうです。**</font>


![③.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/48c8fc75-991c-130e-dfab-dd1f1f88a0b1.png)

   <font color="Red">**せっかく撮影しても、整理できずにいませんか？。**</font>
 「子供と仕事の写真ぐちゃぐちゃ・・」「あの思い出はどこ・・？」

 思い出が埋もれたり、写真と一緒に消えてしまうのは悲しい。
 とはいえ整理はめんどくさい。よくあるフォトアプリも挫折した。

 そんな方々、そして誰より私たち夫婦の悩みを解決するために
   <font color="Red">**日本一簡単操作で作れるフォトアルバムを作ります!**</font>

## 3タップフォトアルバムの試作品

 できたものはこちら

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">プログラミング初心者の<br>日本一楽 LINEフォトアルバム挑戦記💪<br><br>プロトタイプ作成中<br><br>写真送付で<br>フォトアルバム完成！<br>まではおおよそオッケー👌<br><br>コメント入れたい。。。<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a><a href="https://twitter.com/hashtag/LINEDC?src=hash&amp;ref_src=twsrc%5Etfw">#LINEDC</a><a href="https://twitter.com/hashtag/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%88%9D%E5%BF%83%E8%80%85?src=hash&amp;ref_src=twsrc%5Etfw">#プログラミング初心者</a> <a href="https://twitter.com/hashtag/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%88%9D%E5%BF%83%E8%80%85%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#プログラミング初心者と繋がりたい</a> <a href="https://t.co/wOpjn86ndt">pic.twitter.com/wOpjn86ndt</a></p>&mdash; OKINAKA(おきなかがいこつ) (@okinakamasayos1) <a href="https://twitter.com/okinakamasayos1/status/1472046476440514560?ref_src=twsrc%5Etfw">December 18, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

  :::note warn
 ①写真ボタンを押す
 ②写真を選ぶ
 ③送信
 :::

 <font color="Red">**なんと3タップでアルバム投稿は完成!**</font>
 **入力せずとも、「投稿者」と「投稿日時」も自動で記入!
 いつの、誰のお気に入り写真か、一瞬で振り返ることができます!**

 「日本一簡単」と謳うからには、私たちが挫折した
 「意識高い系フォトアルバム」よりも楽に投稿できなければなりません。
 実際に比較して、 <font color="Red">**「日本一楽に投稿」の基準を掲げました。**</font>

 :::note warn
 3タップで写真投稿　6工程でコメント投稿
 :::

 「6工程でのコメント投稿」はまだ実現できていませんが、
 今後絶対制作してみせます。(頑張る💪)

![⑦.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/89b544c8-a3d2-c81e-4e76-fb9c05bfe5cd.png)


 全体の流れ・コードはこちら

 ![⑥.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/9654d819-c240-02a4-f11b-62a60955307d.png)


 ```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 = "1WZOXuCbs8yb574GHQLp5Ov6SJYPKFLTz";

// 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;

  // 写真の時
  if ( "image" === postType ) {
    imageSave(replyToken, lineUserId, data);
  } else {
    sendMessage(replyToken, '画像を送信してください')
  }

}

// 送信された画像を保存 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 = '****スプシURL****' + 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);
 ```

参考記事(ありがたい😢)　[LINEで送った画像をGoogleドライブに保存するメモ](https://qiita.com/njn0te/items/6058c2fe0317b9bd62fa)

## 詰まっているところ コメントを入れられない

　写真＋コメントも「思い出」として残したい。そこで
 `function doPost(e)`の中身に下記を追加し、
 `if文`を下記のように変更することで実装を試みました。

 いまのところ<font color="Red">**コメントは指定セルに入るのですが、画像が保存されなくなってしまいます。
 もし原因・対策分かる方、よければ教えてください。。。**</font>


 `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/);
  const lastRow = SHEET.getLastRow();
 ```
 書き変えた`if文`と意図 コメントは入るが画像が入らない

 ```js
  // 写真の時はデータ取得
  if ( "image" === postType ) {
    imageSave(replyToken, lineUserId, data);
 
   //写真でなければG2のセルに入力
 
  } else {
      var lastRow = SHEET.getLastRow();
  SHEET.getRange('G' +  2).setValue(messageParameter[0]);
  }
 ```
 
  <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>

## 工夫した点と目指したいこと

 送られた写真をそのままスプレッドシートに張り付けたかったのですが断念。
 そこで一旦GoogleDriveに写真を保存し、Drive内写真のURLをシートに入力させ、
 `=image("写真のURL")`とセル内で表示させることで、画像表示させました。

 ![⑩.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/8a7d9c37-5e60-cdce-6276-dc5135f0eb7f.png)

 アルバムっぽく表示させるため、別シートを設定、
 LINEから飛んできた情報を一旦原紙シートに記載し、それとは別のシートで、
 自動的にアルバムのように表示させることにしました。

![⑪.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/37561645-0497-678a-8f4c-9eadcbd7b8bc.png)

 とはいえまだ手作りExcel感満載ですので、今後はwebアプリとして表現できるように
 実装を進めたいと思っています。

 まだまだ雑ですがこのような形に仕上げていきたいです。

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

## 思い出が消えない世界を作りたい

　<font color="Red">**将来「写真分類・投稿・アルバム化」も全部「自動」にして
　あとは振り返るだけ！そんな「思い出が消えない世界」を作りたいと思っています。**</font>

　「日本一簡単なLINEフォトアルバム」は、その夢へのはじめの一歩です。

　夢を叶えるために、1月中旬には**人生初のクラウドファンディングにも挑戦しますので、
　これからも応援してくださるととても嬉しいです!!**

 @shiracamus さん編集リクエストありがとうございました!!#見落としていたのでありがたいです!!

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

https://camp-fire.jp/projects/528736/preview?token=2oax6gt3

![⑫.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/185ed01d-a743-25fb-b3e1-b3557f79ba3a.png)
![⑬.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793443/53264709-8ee1-b522-d2dd-6e0bc852a35c.png)