LANG SELRCT

コードを書く場所についてはこちら

2019年1月31日木曜日

clasp pushでスクリプトエディタのコードが更新されない


clasp push

を2回やらないと更新されないことがあるらしい。
(2019/01/31現在)


数日前からpushが反映されなくて、ググってみるとissueが上がっていました


issue

issuetracker
Editor not updated after files added/removed via clasp
https://issuetracker.google.com/issues/123311608


ここにも上がっていた
Clasp push does not update remote code #507
https://github.com/google/clasp/issues/507


issuetrackerのスターをチェックしておくと、更新時にメール通知が来ます

フォルダ内の複数画像を取得してHtmlServiceで表示したい


Googleドライブのフォルダ内に複数の画像を保存してHtml Serviceに読み込みたい

今回考えた手順
  1. Googleドライブのフォルダを指定する
  2. フォルダ内のファイルを取得する
  3. 取得したファイル名とIDを配列に入れて昇順に並び替える
  4. それを不可視のtextareaに入れる
  5. ファイルIDから表示用のURLを作ってimgのsrcに入れる
  6. next, prevボタンで前後の画像を表示する
  7. jumpボタンで指定した番号の画像を表示する



コード.gs
//フォルダを指定する
var FOLDER_ID = "フォルダID";

/************************************
doGet
************************************/
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile("index");
}

/************************************
getFiles
フォルダ内のファイルを取得して
昇順にして返す
************************************/
function getFiles() {
  var array = [];
  var folder = DriveApp.getFolderById(FOLDER_ID);
  var contents = folder.getFiles();
  while(contents.hasNext()) {
    var file = contents.next();
    var name = file.getName();
    var id = file.getId();
    var obj = {};
    obj["name"] = name;
    obj["id"] = id;
    array.push(obj);
  }
  var sorted = objSort(array);
  return array;
}

/************************************
objSort
並べ替える
************************************/
function objSort(array) {
  var ascending = array.sort(asc);
  return ascending;
}

/************************************
asc
nameで昇順に並べ替える
************************************/
function asc(a, b){
  var targetA = a.name;
  var targetB = b.name;
  if(targetA > targetB){
    return 1;
  }else if(targetA < targetB ){
    return -1;
  }else{
   return 0;
  }
}




index.html
<!DOCTYPE html>
<html>

<head>
    <style>
    #image {
      height: 100vh;
    }
    
    .display_none {
      display: none;
    }
    </style>
</head>

<body>
  <textarea id="ta" class="display_none"></textarea>
  <input id="num_tb" type="text">
  <button id="jump_bt">jump</button>
  <button id="prev_bt">prev</button>
  <button id="next_bt">next</button>
  <br>
  <img id="image" src="">
<script>
/************************************
グローバル
************************************/
var jump_bt = elem("jump_bt");
var next_bt = elem("next_bt");
var prev_bt = elem("prev_bt");
var num_tb = elem("num_tb");
var image = elem("image");
var ta = elem("ta");

/************************************
イベント
************************************/
jump_bt.onclick = jumpClicked;
next_bt.onclick = nextClicked;
prev_bt.onclick = prevClicked;

/************************************
elem(id)
************************************/
function elem(id) {
  return document.getElementById(id);
}

/************************************
起動時に実行
************************************/
getFilesNameId();

/************************************
get_data()
************************************/
function getFilesNameId() {
  google.script.run
  .withFailureHandler(onFailure)
  .withSuccessHandler(onSuccess)
  .getFiles();
}

/************************************
onSuccess(result)
************************************/
function onSuccess(array) {
  ta.value = JSON.stringify(array);//一度JSON形式にする
  showImage(0);
}

/************************************
showImage
ファイルのURLをimageのsrcに入れて
テキストボックスにページの数値を入れる
************************************/
function showImage(num) {
  var array = JSON.parse(ta.value);//JSON文字列をオブジェクトに変換する
  var view_path = "https://drive.google.com/uc?export=view&id=";
  image.src = view_path + array[num]["id"];
  num_tb.value = num;
}

/************************************
onFailure(e)
************************************/
function onFailure(e) {
  alert([e.message, e.stack]);
}

/************************************
jumpClicked
テキストボックスの数値をみてそのページを開く
************************************/
function jumpClicked() {
  var num = getNumTotal()["num"];
  var total = getNumTotal()["total"];
  if(num > total-1 || num < 0) {
    alert("有効範囲は、0〜" + (total-1).toString() + "ページ");
  } else {
    showImage(num);
  }
}

/************************************
nextClicked
ひとつ次の画像を開く
************************************/
function nextClicked() {
  var num = getNumTotal()["num"];
  var total = getNumTotal()["total"];
  num = num + 1;
  if(num > total-1) {
    num = total-1;
  } else {
    showImage(num);
  }
}

/************************************
prevClicked
ひとつ前の画像を開く
************************************/
function prevClicked() {
  var num = getNumTotal()["num"];
  num = num - 1;
  if(num < 0) {
    num = 0;
  } else {
    showImage(num);
  }
}

/************************************
getNumTotal
テキストボックスの数値とファイルの総数をオブジェクトで返す
************************************/
function getNumTotal() {
  var num = parseInt(num_tb.value);
  var array = JSON.parse(ta.value)
  var total = array.length;
  var obj = {};
  obj["num"] = num;
  obj["total"] = total;
  return obj;
}

</script>
</body>
</html>



2019年1月30日水曜日

setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)を使ってみる


Google Apps ScriptのHtml Servimceで作ったページやアプリを
外部ページのiframeで読み込めるようにしたくて調べた時に書いたコード

これでいけた
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)



コード.gs
function doGet() {
  var output = HtmlService.createHtmlOutputFromFile('index');
  output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
  return output;
}
意訳
 
index.htmlのHTMLを出力する
XFrameOptionsModeをHtmlService.XFrameOptionsMode.ALLOWALLに設定して
返す




index.html
<!DOCTYPE html>
<html>
  <body>
    <input type="text">
  </body>
</html>
意訳
 


テキストボックスを置く




補足

クリックジャッキング対策は別途必要になる
  • クリックジャッキングのメモ
    • ユーザの意図しない動作が実行されてしまう
    • iframeで読み込んだページの上に透明のページを配置して、クリックしたり入力した内容が透明のページに送られる

2019年1月27日日曜日

base64にencodeされたデータをGoogleドライブに画像保存したい


こういう画像データを
data:image/png;base64, ここにbase64変換した文字列

Googleドライブに画像ファイルにして保存したい



コード.gs
function upload_image(){
  var data = "data:image/png;base64, ここにbase64変換した文字列";
  var base64 = data.split("base64,")[1];
  var content_type = data.split("data:")[1].split(";base64")[0];
  var decoded = Utilities.base64Decode(base64);
  var file_name = "NAME";
  var url = create_file(file_name, content_type, decoded);
  Logger.log(url);
  return url;
}

function create_file(file_name, content_type, decoded) {
  var folder = DriveApp.getFolderById("0B2O4f6oD8gwNTjJjNFAzeU1jb0U");
  var blob = Utilities.newBlob(decoded, content_type, file_name);
  var created_file = folder.createFile(blob);
  var url = created_file.getUrl();
  return url;
}


参考

base64Decode(encoded)
https://developers.google.com/apps-script/reference/utilities/utilities#base64Decode(String)


Googleドライブ内の画像ファイルをbase64にencodeしたい


Googleドライブ内の画像ファイル
https://drive.google.com/file/d/ID/view?usp=sharing



data:image/png;base64, ここにbase64変換した文字列

の形にして返したい



コード.gs
function getBase64Data() {
  var url = "https://drive.google.com/file/d/ID/view?usp=sharing";
  var id = url.split("/d/")[1].split("/")[0];
  var file = DriveApp.getFileById(id);
  var blob = file.getBlob();
  var content_type = blob.getContentType();
  var base64 = Utilities.base64Encode(blob.getBytes());
  var data = "data:" + content_type + ";base64, " + base64;
  return data;
}



参考

base64Encode(data)
https://developers.google.com/apps-script/reference/utilities/utilities#base64Encode(Byte)

2019年1月26日土曜日

canvasに直線を描く


こういう青い直線を描くコード




コード.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index");
}
意訳
この機能がやること
指定したHTMLファイルを表示する




index.html
<!DOCTYPE html>
<html>
  <style>
  #canvas1 {
    width: 30vw;
    height: 30vh;
    border: solid 1px lightgray;
  }
</style>
  <body>
    <canvas id="canvas1"></canvas>
    <script>

var color = "royalblue";
var width = 10;

var canvas = document.getElementById('canvas1');
var context = canvas.getContext('2d');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;

context.strokeStyle = color;
context.lineWidth = width;

function draw() {
  var TopX = 0;
  var TopY = 0;
  var BottomX = 0;
  var height = canvasHeight;
  context.beginPath();
  context.moveTo(TopX, TopY);//線の出発点(X座標の出発点とY座標の出発点)
  context.lineTo(BottomX, height);//線の終点(X座標の終点, Y座標の終点)
  context.closePath();
  context.stroke();
};

draw();

</script>
  </body>
</html>


JavaScript関数の定義についてメモ


関数の定義にもいくつか種類がある


関数宣言


よく使うfunction 文
function showAlert() {
   alert("hello");
}


関数式


無名関数
var myFunction = function() {
    alert("hello");
}



名前付き関数
var myFunction = function showAlert(){
    alert("hello");
}



即時関数
(function() {
    alert("hello");
})();



アロー関数
() => {
  alert("hello")
  };



これはまだ個人的に使う機会がない
ジェネレータ関数
function* name([param[, param[, ... param]]]) {
   statements
}


LINEで絵文字を出す方法


忘れがちなのでメモ


「スタンプ選択画面」





「絵文字選択画面」



2019年1月25日金曜日

画像内で選択した範囲を取得してcanvasで書き出す


画像内を範囲選択して切り取りたい




コード.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index");
}
意訳
この機能がやること
指定したHTMLファイルを表示する




index.html
<!DOCTYPE html>
<html>

<body>
<div id="body_div">
<img id="source" src="https://4.bp.blogspot.com/-21du8qaTVhw/V2plI3Jl7aI/AAAAAAAAAYY/0cxXsUZVo5ky6kisOTRhe7LminP6na3ogCLcB/s1600/icon.png" draggable="false">

<div id="canvas_div2">

</div>
<br>
  X座標移動中:<input type="text" id="moveX" />
  Y座標移動中:<input type="text" id="moveY" />
  <br>
  X座標ここから:<input type="text" id="fromX" />
  Y座標ここから:<input type="text" id="fromY" />
  <br>
  X座標ここまで:<input type="text" id="toX" />
  Y座標ここまで:<input type="text" id="toY" />
  <br>
  切り取る幅:<input type="text" id="cutX" />
  切り取る高さ:<input type="text" id="cutY" />
</div>
<script>

/************************************
elem
************************************/
function elem(id){
  return document.getElementById(id);
}

/************************************
グローバル変数
************************************/
var canvas_div2 = elem("canvas_div2");
var fromX = elem("fromX");
var fromY = elem("fromY");
var moveX = elem("moveX");
var moveY = elem("moveY");
var toX = elem("toX");
var toY = elem("toY");
var cutX = elem("cutX");
var cutY = elem("cutY");

/************************************
イベント
************************************/
document.onmousedown = on_mousedown; 
document.onmouseup = on_mouseup; 
document.onmousemove = on_mousemove; 


/************************************
on_mousemove
************************************/
function on_mousemove(e){
  //座標を取得する
  var X = e.pageX;  //X座標
  var Y = e.pageY;  //Y座標
  
  //座標を表示する
  moveX.value = X;
  moveY.value = Y;
};

/************************************
on_mousedown
************************************/
function on_mousedown(e){
  //座標を取得する
  var X = e.pageX;  //X座標
  var Y = e.pageY;  //Y座標
  
  //座標を表示する
  fromX.value = X;
  fromY.value = Y;
};

/************************************
on_mouseup
************************************/
function on_mouseup(e){
  //座標を取得する
  var X = e.pageX;  //X座標
  var Y = e.pageY;  //Y座標
  
  //座標を表示する
  toX.value = X;
  toY.value = Y;
  get_trimed();
}

/************************************
get_trimed
************************************/
function get_trimed(){
  var X1 = parseInt(fromX.value);
  var Y1 = parseInt(fromY.value);
  var X2 = parseInt(toX.value);
  var Y2 = parseInt(toY.value);
  
  //逆向きからの選択に対応
  var X = nomalize_position(X1, X2);
  X1 = X[0];
  X2 = X[1];

  //逆向きからの選択に対応
  var Y = nomalize_position(Y1, Y2);
  Y1 = Y[0];
  Y2 = Y[1];

  //範囲を取得する
  var X3 = Math.abs(X2 - X1);
  var Y3 = Math.abs(Y2 - Y1);

  //座標を表示する
  cutX.value = X3;
  cutY.value = Y3;
  
  //描画する
  draw_image(X1, Y1, X3, Y3);
}

/************************************
nomalize_position
上から下、左から右に選択すれば問題ないが
下から上や右から左に選択すると
起点よりも終点の方が小さくなり
範囲が正しく取得できないので
起点が終点よりも大きい場合は両者を入れ替えて
逆向きからの選択に対応する
************************************/
function nomalize_position(A, B){
  if(A > B){
    var A0 = A;
    A = B;
    B = A0;
  }
  var result = [A, B];
  return result;
}

/************************************
draw_image
************************************/
function draw_image(X1, Y1, X3, Y3) {
  var source = elem('source');
  remove_child("canvas_div2");
  var canvas = create_canvas();
  var context = canvas.getContext('2d');
  
  canvas.style.width = X3;
  canvas.style.width = Y3;
  context.drawImage(source, X1, Y1, X3, Y3, 0, 0, X3, Y3);
}

/************************************
create_canvas
************************************/
function create_canvas(){
  var canvas = document.createElement("canvas");
  canvas.setAttribute("id", "image_canvas");
  canvas_div2.appendChild(canvas);
  return canvas;
}

/************************************
remove_child
************************************/
function remove_child(id) {
  var elem = document.getElementById(id);
  for (var i = elem.childNodes.length - 1; i >= 0; i--) {
    elem.removeChild(elem.childNodes[i]);
  }
}
</script>
</body>
</html>



2019年1月19日土曜日

Googleドキュメントにテキストを追加したい


ドキュメントに hello を入力しておいて



hi と hey を追加する




コード.gs
function addText() {
  var url = "https://docs.google.com/document/d/ID/edit";
  var doc = DocumentApp.openByUrl(url);
  var body = doc.getBody();
  body.appendParagraph("hi" + "\n" + "hey");
}


getBody()しなくても動いた。
DocumentApp.openByUrl(url).appendParagraph("hi" + "\n" + "hey")


参考

appendParagraph(text)
https://developers.google.com/apps-script/reference/document/body#appendParagraph(String)

Googleドキュメント全体のフォントサイズを変更したい


ドキュメント全体のフォントサイズを14に変更してみる

これで実現できました。

 DocumentApp.openByUrl(ドキュメントのURL)
  .editAsText()
  .setFontSize(14);



コード.gs
function changeFontSize() {
  var url = "https://docs.google.com/document/d/ID/edit";
  var doc = DocumentApp.openByUrl(url);
  doc.editAsText().setFontSize(14); 
}


参考

editAsText()
https://developers.google.com/apps-script/reference/document/text#editAsText()

ドライブ内の画像ファイルをテキストに変換したい(OCR)


Google Drive内の画像ファイルをGoogleドキュメントで開くと、画像内のテキストを抽出することができます。PDF や写真のファイルをテキストに変換する


それをスクリプトで実現する方法を調べて試したので書き残しておきます。


事前準備



コード.gs
function get_ocr() {
  var id = "画像ファイルのID";
  var file = DriveApp.getFileById(id);
  var blob = file.getBlob();
  var name = file.getName();

  var file = {
    title: name,
    mimeType: 'image/png'
  };
  
  var option = {
    ocr: true
  }
  
  file = Drive.Files.insert(file, blob, option);
  var doc = DocumentApp.openByUrl(file.embedLink);
  var text = doc.getBody().getText();
  
  var docUrl = "https://docs.google.com/document/d/" + doc.getId() + "/edit";
  Logger.log(docUrl);
  Logger.log(text);
}


試してみる


スクリプトエディタのスクリーンショットを撮ってDriveにアップロードして、そのファイルを対象にやってみます。


Logger.log(docUrl)に出力したURLにアクセスすると
以下のようにテキストに変換されたドキュメントが開きます

function get_ocr() {
var id = " 771)LOID"; var file = DriveApp.getFileById(id); var blob = file.getBlob();
var file = {
title: "file_name", mimeType: 'image/png
var option = {
ocr: true
file = Drive. Files.insert(file, blob, option); var doc - DocumentApp.openByUrl(file.embedlink); var body = doc.getBody().getText():

var docUrl = "https://docs.google.com/document/d/" + doc.getId() + "/edit"; Logger.log(docUrl);


Google Drive web APIを有効にする


Drive web APIをGoogle Apps Scriptで利用する場合は以下の手順で有効化できます。

Google Apps Scriptに組み込まれているDiveAppではできないことができるようになります。(Google Driveのファイルやフォルダを作成、検索、変更等)


Google Drive web APIを有効にする手順


リソース > Googleの拡張サービス



Drive APIをONにします

 Google Cloud Platform API ダッシュボード のリンクをクリックします


右側のメニューから「ライブラリ」を選択します



検索欄に「Drive」と入力して、「Google Drive API」をクリックします



「管理」になっているところが「有効にする」になっていたらクリックして有効にします



これでGoogle Apps Scriptの中でDrive.Files.insertなどが使えるようになります。


テキストファイルをGoogleドキュメントで開きたい


ドキュメントの場合はMIME TYPEをこうすると良いらしい
mimeType: 'application/vnd.google-apps.document'


事前準備





コード.gs
function createDocFromText() {
  var url = "https://drive.google.com/file/d/ID/view?usp=sharing";//テキストファイルのURL
  var id = url.split("/d/")[1].split("/")[0];
  var file = DriveApp.getFileById(id);
  var name = file.getName();
  var blob = file.getBlob();

  var file = {
    title: name,
    mimeType: 'application/vnd.google-apps.document'
  };
  
  file = Drive.Files.insert(file, blob);
  var id = file.id;
  var docUrl = "https://docs.google.com/document/d/" + id + "/edit";
  Logger.log(docUrl);
}



参考

Supported MIME Types
https://developers.google.com/drive/api/v3/mime-types

Uploading files
https://developers.google.com/apps-script/advanced/drive#uploading_files



2019年1月17日木曜日

2次元配列で指定した値が何番目の配列にあるか知りたい


1次元配列ならindexOfで指定した値が配列の何番目にあるか知りたいのようにできました

2次元配列の場合はどうしたらいいのかちょっと迷いました。

例えば
var array = [[5,4], [12,20], [8,5]]
の中で
0番目が
12
である配列が何番目にあるかを知りたい

もっと簡単な方法がありそうな気もしますが、以下のコードで目的を達成できました。


コード.gs
function getIndex2d() {
  var index;
  var target = 12;
  var array = [[5,4], [12,20], [8,5]];
  for(var i = 0; i < array.length; i++) {
    if(array[i][0] === target) {
      index = i;
      break;
    }
  }
  Logger.log(index);
}

意訳
この機能がやること
見つかったindexを入れる入れ物を用意して
見つけたい値を決めて
探したい対象の2次元配列を用意して
配列の数だけ繰り返す
ひとつずつ要素を見て0番目がtargetに一致したら
indexに繰り返した回数を入れて
繰り返しから抜ける


indexをログに出す



0番目:[5,4]
1番目:[12,20]
2番目:[8,5]

実行結果

ちょっとややこしい言い回しになりますが
var array = [[5,4], [12,20], [8,5]]
内の各要素の0番目が12であるのは、1番目の配列なので1が返ってきます。




2次元配列をソートしたい(数値)


JavaScriptで配列内の数値をソートしたいでは
var array = [1,2,3,5,4,2]を昇順・降順に並べ替えました。

今回は
var array = [[1,7],[2,0],[3,9],[5,6],[4,8],[2,5]]を昇順・降順に並べ替えてみます。



コード.gs
function get_sort2d() {
  var array = [[1,7],[2,0],[3,9],[5,6],[4,8],[2,5]];
  var ascending = array.sort(sorting_asc);
  Logger.log(ascending);
  var descending = array.sort(sorting_desc);
  Logger.log(descending);
}

function sorting_asc(a, b){
  return a[0] - b[0];
}

function sorting_desc(a, b){
  return b[0] - a[0];
}



関連記事


2次元配列をソートしたい(文字列)


配列内の文字列をソートしたいでは
以下のような配列を昇順・降順に並べ替えました。
array = ["え", "あ", "い", "お", "い"]

今回は
以下のような2次元配列を昇順・降順に並べ替えてみます。
array = [["お","か"], ["う","き"], ["あ","く"], ["え","け"], ["い","こ"]]


2次元配列の各要素の0番目の要素で昇順・降順にしてみる

コード.gs
function str_sort2d(array) {
  array = [["お","か"], ["う","き"], ["あ","く"], ["え","け"], ["い","こ"]];
  var ascending = array.sort(sorting_asc);
  Logger.log(ascending);
  var descending = array.sort(sorting_desc);
  Logger.log(descending);
}

function sorting_asc(a, b){
  if(a[0] < b[0]){
    return -1;
  }else if(a[0] > b[0] ){
    return 1;
  }else{
   return 0;
  }
}

function sorting_desc(a, b){
  if(a[0] > b[0]){
    return -1;
  }else if(a[0] < b[0] ){
    return 1;
  }else{
   return 0;
  }
}


実行結果

Logger.log(ascending)は昇順の結果
Logger.log(descending)で降順の結果



補足

2次元配列の各要素の1番目の要素で昇順・降順にする場合は
[0]の箇所を[1]に変えます。


Googleドキュメントを作成してテキストを渡す


新規ドキュメントを作成してテキストを入れてみます





コード.gs
function createNewDoc() {
  var doc = DocumentApp.create('Document Name');
  var body = doc.getBody();
  body.setText("テキスト1\nテキスト2");
  var url = doc.getUrl();
  Logger.log(url);
}
意訳
この機能がやること
新規ドキュメントを作成する
bodyを取得する
テキストを入力する
urlを取得する
ログに出す



補足

渡すテキストが多いとこのようなエラーがでます



関連記事


参考

setText(text)
https://developers.google.com/apps-script/reference/document/body#setText(String)

Googleドキュメントをスクリプトで作成する


DocumentApp.create で新規ドキュメントを作ってみます



コード.gs
function createNewDoc() {
  var doc = DocumentApp.create('Document Name');
  var url = doc.getUrl();
  Logger.log(url);
}
意訳
この機能がやること
新規ドキュメントを作成する(名前を決める)
作成したドキュメントのurlを取得して
ログに出す



createNewDoc()を実行すると
マイドライブ内に新規ドキュメントが作成されます




関連記事

指定したフォルダに入れたい場合




参考

Class DocumentApp
https://developers.google.com/apps-script/reference/document/document-app

LINE BOTでprofileを取得してみる


トーク画面にメッセージを投稿したユーザのprofileをトーク画面に返してみます

profileで取得できるのは
  • userId
  • displayName
  • pictureUrl
  • statusMessage

値はマスクしていますが、こんな感じで取得できます



公式リファレンスのこれを参考にしました
https://developers.line.biz/en/reference/messaging-api/#get-profile




コード.gs
function getToken() {
  return PropertiesService.getScriptProperties().getProperty('token');
}

function doPost(e) {
  var contents = e.postData.contents;
  var obj = JSON.parse(contents)
  var events = obj["events"];
  for(var i = 0; i < events.length; i++){
    if(events[i].type == "message"){
      replyMessage(events[i]);
    }
  }
}

function replyMessage(e) {
  var userId = e.source.userId;
  var profile = getProfile(userId)
  var postData = {
    "replyToken" : e.replyToken,
    "messages" : [
      {
        "type" : "text",
        "text" : profile.toString()
      }
    ]
  };
  var options = {
    "method" : "post",
    "headers" : getHeaders(),
    "payload" : JSON.stringify(postData)
  };
  var url = "https://api.line.me/v2/bot/message/reply"; 
  UrlFetchApp.fetch(url, options);
}

function getHeaders() {
  var headers = {
      "Content-Type" : "application/json",
      "Authorization" : "Bearer " + getToken()
    };
  return headers;
}

function getProfile(userId) {
  var options = {
    "method" : "get",
    "headers" : getHeaders()
  };
  var url = 'https://api.line.me/v2/bot/profile/' + userId; 
  var response = UrlFetchApp.fetch(url, options);
  return response;
}

意訳
この機能がやること
スクリプトのプロパティに保存してあるtokenの値を返す


この機能がやること
送られてきた投稿内容を取り出して
オブジェクトに変換して
eventsを取り出して
eventsの数だけ繰り返す
eventsをひとつずつ見てtypeがmessageなら
replyMessageに渡す




この機能がやること
userIdを取得して
getProfile()に渡した戻り値を取得して
postDataを作る
replyTokenを取り出して設定して
メッセージを作る

typeを決める
textを決める



optionsを作る
methodを決める
headersを決める
payloadを決める

urlを指定して
UrlFetchApp.fetchでLINEに送る


この機能がやること
headersを作る
Content-Typeを決める
Authorizationを決める

返す


この機能がやること
optionsを作る
methodを決める
headersを決める

profileのURLにuserIdを付けて 
UrlFetchApp.fetchでLINEに送って
返ってきたデータを返す




関連記事


参考

Get profile
https://developers.line.biz/en/reference/messaging-api/#get-profile

2019年1月13日日曜日

スプレッドシートのアドオンを作りたい(公開・限定公開する)


スプレッドシートのアドオンを公開・限定公開するまでの道がわからず、その壁を越えた手順を順番にキャプチャしながら書き残しました。

公式のリファレンスに沿って試しましたが、設定する画面が多く、
初めてやる場合は動作確認やリファレンス確認で1時間以上はかかると思います。
(もっと簡単な方法があるかもしれません)


アドオン公開手順がメインのため、実行するコードはメッセージを表示するだけの短いものにしています。


今回調べて書いた手順
  1. Apps Scriptの standalone script にコードを書いて保存する
  2. テストする
  3. バージョンを保存する
  4. Cloud Platformプロジェクトで設定する
    • APIとサービスの認証情報
      • OAuth同意画面で「承認済みドメイン」「[アプリケーション ホームページ] リンク」を設定して保存する
    • APIとサービスのライブラリ
      • G Suite Marketplace SDKを有効にする
    • G Suite Marketplace SDKの設定
      • 各項目を設定する
  5. ウェブアドオンとしてデプロイする
    • 各項目を設定する
  6. Chrome web storeで公開する
    • 各項目を設定する



この記事を書く前の事前知識


ここから実際に作っていきます


1. Apps Scriptの standalone script にコードを書いて保存する
FYI:createAddonMenu()onInstall(e)


新規スクリプトファイルを作成します
https://script.google.com/macros/create


以下のコード.gsを書いて保存します



プロジェクト名を任意で変更します




コード.gs
function onOpen(e) {
  SpreadsheetApp.getUi()
      .createAddonMenu()
      .addItem('Message', 'showMessage')
      .addToUi();
}

function onInstall(e) {
  onOpen(e);
}

function showMessage() {
  Browser.msgBox('メッセージを表示する');
}
意訳
この機能がやること
スプレッドシートのUIに
アドオンのメニューを追加して
項目名と実行する関数を設定して
追加する


この機能がやること
onOpen(e)を実行する


この機能がやること
メッセージを表示する



2. テストする
FYI:Test an editor add-on


アドオンをテストするためのシートを作ります
sheet.new
スプレッドシートの名前を任意で変更します(例ではAdd-on test sheet)



アドオンのプロジェクトに戻って
実行 > アドオンとしてテスト を選択します



「ドキュメントを選択」をクリックします



「スプレッドシート」を選択して
先程作ったスプレッドシート名で検索して
対象のシートをクリックして
「選択」をクリックします



「保存」をクリックします



「最新のコード」をチェックして
「テスト」をクリックします



新規タブでスプレッドシートが開きます
アドオンの中に
Messageを表示する > Message
が追加されているので選択します



「続行」をクリックします



アカウントを選択します



「詳細」を選択します



一番下に現れるリンクをクリックします



「許可」をクリックします



アドオンのコードに書いたメッセージがシートに表示されたら成功です




3. バージョンを保存する
Create a script version


ファイル > 版を管理 を選択します



説明を入力して(例ではfirstversion)「新しいバージョンを保存」をクリックします



保存後「OK」をクリックします




4. Cloud Platformプロジェクトで設定する



リソース > Cloud Platform プロジェクト を選択します



プロジェクトのリンクをクリックします



このような画面が開きます

※チームドライブ内のスクリプトはCloud Platformプロジェクトにアクセスできないことがあるそうです


左のメニューで APIとサービス > ダッシュボードを選択します



ダッシュボードに移動後
左のメニューから「認証情報」を選択して「OAuth 同意画面」を選択します



・承認済みドメイン
・[アプリケーション ホームページ] リンク
を入力して「保存」をクリックします





APIとサービス > ライブラリ を選択します



検索ボックスに G Suite Marketplace SDK と入力して
結果に表示される「G Suite Marketplace SDK」をクリックします



「有効にする」ボタンをクリックします





左のメニューで「設定」を選択して「アプリケーションの説明」を入力します



「アプリケーションのアイコン」を設定します



「利用規約のURL」を設定します



「OAuth 2.0 スコープ」のURLを入力します
https://www.googleapis.com/auth/spreadsheets


ちなみにこれはアドオンのコードのファイル > プロジェクトのプロパティの


このスコープと解釈して入力しました



「拡張機能」の項目を適宜チェックします


「ユニバーサルナビゲーションバー」って何?
と思いググってみましたが「これ」という情報が見つかりませんでした。
わかったら追記します。


アドオン拡張機能からスプレッドシートアドオン拡張機能をチェックします
・「スクリプトのプロジェクトキー」には「スクリプト ID」を入力します
・バージョンは保存したバージョンの数値を入力します



「変更を保存」をクリックします




5. ウェブアドオンとしてデプロイする
FYI:Configure the Chrome Web Store listing and publish


公開 > ウェブアドオンとしてデプロイ を選択します




  1. 「簡単な説明」を入力します
  2. 「アドオンの種類」を選択します
  3. 「バージョン」を入力します
  4. 「ヘルプのURL」を入力します
  5. 「問題の報告用 URL」を入力します
  6. 「インストール後のヒント」を入力します
  7. 「G Suite Marketplace で公開する」にチェック
  8. 「ウェブストア アイテムの下書きを作成」をクリックします


アカウントのパスワードを求められたら入力して「Next」をクリックします




6. Chrome web storeで公開する
FYI:Chrome の限定公開アプリの公開





公開するには一番下までスクロールして
Visibility optionsを決めて
右下の「Publish changes」ボタンをクリックします



「OK」をクリックします



するとこのようなエラーが出るので、ひとつずつ設定して行きます


At least one screenshot or video is required.
Small tile image is missing.
Please select a Primary Category for your item.
Language is not selected.

「Icon image is missing」
128 ✕ 128 ピクセルのpng画像をアップロードすると解決します

アイコンの作り方は色々あると思いますがDrawingsで作る例を以下に書きました
Google Drawingsでアイコンを作ってみる


「At least one screenshot or video is required」
1280✕800または640✕400 ピクセルの画像をアップロードすると解決します
(Drawingsで画像を作ったり、スクリーンショットを撮ったり)



「Small tile image is missing」
440✕280 ピクセルの画像をアップロードすると解決します

「Please select a Primary Category for your item」
カテゴリーを選択すると解決します


「Language is not selected」
言語を選択すると解決します

「Visibility options」
Publicにすると誰でも見ることができます(レビューを通過する必要があります)
Privateにすると限定したユーザにだけ公開できます(レビュー不要)
G suiteの場合はドメイン内だけに限定することもできます
Chrome の限定公開アプリの公開

ちなみにPrivateのtrusted testersはデベロッパーズダッシュボード
「Edit your tester accounts」から設定できます



これでテスターに設定したアカウントに公開することができました


世界に公開する場合は、PrivateではなくPublicを選択して公開し、その後Google側のレビューを通過すると正式に公開されます。