LANG SELRCT

Apps Script Reference  (Create: Create new Spreadsheet | Create new Apps Script

Friday, August 31, 2018

LINE BOTで「LIFF」を使う(LIFF V1)


※LIFF V2は以下の手順とは異なります。



HtmlServiceでviewportを使いたい
で作ったアプリをLINE上に表示してみます

このように



作る手順
  1. HtmlServiceでWebアプリ(画面に表示するページ)を作る [GASアプリ1]
  2. チャネルを作る [LINE チャネル基本設定]
  3. そのチャネルにGASアプリ1を登録してappIdを取得する [コマンドライン]
  4. 特定のテキストが投稿されたらappIdを返すアプリを作る [GASアプリ2]
  5. webhookを有効にしてGASアプリ2のURLを設定する [LINE チャネル基本設定]



1.HtmlServiceでWebアプリ(ページ)を作成する


コード.gs
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile("index")
                    .addMetaTag("viewport", "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=10.0");
}
意訳
この機能がやること
指定したHTMLファイルを表示する
メタタグのviewportを追加する




index.html
<!DOCTYPE html>
<html>
  <body>
    <button id="bt">button</button>
  <script>
  document.getElementById("bt").onclick = bt_clicked;
  
  function bt_clicked(){
    alert("clicked!");
  }
  </script>
  </body>
</html>
意訳
 


ボタンを配置する

ボタンがクリックされたらbt_clickedを実行する

この機能がやること
clicked!という文字列をアラートに出す







コード.gsを書いて保存する


HTMLファイルを新規作成して

indexという名前にして

htmlを書いて保存する


ウェブアプリケーションとして導入...


バージョンの説明を入力:first version
次のユーザーとしてアプリケーションを実行:自分
アプリケーションにアクセスできるユーザー:全員(匿名ユーザーを含む)


現在のウェブアプリケーションのURLを控えておく


2.チャネルを作成する 


LINE Developersコンソールにログインしてチャネルを作成して
チャネルアクセストークンを取得する
https://developers.line.me/ja/docs/liff/getting-started/


LINE BOTの作り方


3.2のチャネルに1で作ったWebアプリを登録する


これはコマンドラインで実行

https://engineering.linecorp.com/ja/blog/detail/299
で書かれているように以下のコマンドをターミナルやiTermなどで実行する


以下のコマンド内で設定する箇所
  • チャネルアクセストークン
  • SIZE_OF_LIFF(compact, tall, fullの画面サイズ)
  • WebアプリのURL


curl -XPOST \
-H "Authorization: Bearer チャネルアクセストークン" \
-H "Content-Type: application/json" \
-d '{
    "view": {
        "type": "SIZE_OF_LIFF",
        "url": "WebアプリのURL"
    }
}' \
https://api.line.me/liff/v1/apps


以下のような形でappIdが返って来たら登録完了
{"liffId":"appId"}

4.特定のテキストが投稿されたらappIdを返すアプリを作る


コード.gs
var CHANNEL_ACCESS_TOKEN = "TOKEN";
var appId = "ID";

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"){
      reply_message(events[i]);
    }
  }
}

function reply_message(e) {
  if (e.message.type == "text") {
    if (e.message.text == "hello") {
  var postData = {
    "replyToken" : e.replyToken,
    "messages" : [
      {
        "type" : "text",
        "text" : appId
      }
    ]
  };
  }
  var options = {
    "method" : "post",
    "headers" : {
      "Content-Type" : "application/json",
      "Authorization" : "Bearer " + CHANNEL_ACCESS_TOKEN
    },
    "payload" : JSON.stringify(postData)
  };
  UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", options);
  }
}


5.webhookを有効にしてGASアプリ2のURLを設定する


LINE BOTの作り方>10.「Use webhooks」の「編集ボタン」をクリックします


試してみる


hello と送るとappIdのリンクが返ってくる


リンクをクリックすると作ったHTMLがLINEの画面内に表示される


関連記事


補足

LIFFで表示したHTML内のボタンを押したときなどにトーク画面へメッセージを返すこともできますが、以下のindex.htmlでは Could not authenticate LIFF app, Error が出てしまいました。


エラー内容でリファレンスを見るとこのように書かれている
ちょっとよくわからない
https://developers.line.biz/ja/reference/liff/

FirebaseのHostingではうまく行ったので、それは別途書こうと思います。


GASのHtmlServiceでエラーが出た index.html
Developing a LIFF app を参考に開いたときにuserIdを表示して、ボタンを押したときにdisplayNameを返そうと試みた)

index.html
<!DOCTYPE html>
<html>
  <body>
   <button id="bt">button</button>
   
   <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
  
   <script>
     document.getElementById("bt").onclick = bt_clicked;

     liff.init(
       function(data) {
         const userId = data.context.userId;
         alert(userId)
       },
       function(err) {
         alert([err, err.stack])
       }
     );

     function bt_clicked() {
       liff.getProfile()
         .then(function(profile) {

           liff.sendMessages([{
               type: 'text',
               text: 'From:' + profile.displayName
             }])

             .then(function() {
               liff.closeWindow();
             })

             .catch(function(error) {
               window.alert('Error sending message: ' + error.message);
             });

         })
     }

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



参考

LINE Engineering Blog
https://engineering.linecorp.com/ja/blog/detail/299

LINE Developers
https://developers.line.me/ja/

LINE Front-end Framework
https://developers.line.me/ja/docs/liff/

Adding a LIFF app
https://developers.line.me/en/docs/liff/registering-liff-apps/

Getting started with LIFF
https://developers.line.me/en/docs/liff/getting-started/

Developing a LIFF app
https://developers.line.biz/en/docs/liff/developing-liff-apps/

LINE BOTで「Flex Message」を使ってみる


Flex Messageの要素のテキストとボタンを試してみました

何かメッセージを投稿した時に
設定したテキストとボタンを返してみる




コード.gs
var CHANNEL_ACCESS_TOKEN = "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") {
      reply_message(events[i]);
    }
  }
}

function reply_message(e) {
  var postData = {
    "replyToken": e.replyToken,
    "messages": [{
      "type": "flex",
      "altText": "this is a flex message",
      "contents":
      {
        "type": "bubble",
        "body": {
          "type": "box",
          "layout": "vertical",
          "spacing": "md",
          "contents": [

            {
              "type": "text",
              "text": "hello"
            },

            {
              "type": "button",
              "style": "primary",
              "action": {
                "type": "uri",
                "label": "Primary style button",
                "uri": "https://example.com"
              }
            }

          ]
        }
      }
    }]
  };
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + CHANNEL_ACCESS_TOKEN
    },
    "payload": JSON.stringify(postData)
  };
  UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", options);
}


関連記事




Tuesday, August 28, 2018

Fusion Tableでテーブルのレコード数を取得したい


 'SELECT COUNT() FROM ' + table_id

で table_id のレコード数を取得できたので書き残しておこう



コード.gs
function get_count_data(){
  var table_id = "テーブルID";
  var sql = 'SELECT COUNT() FROM ' + table_id;
  var rows = FusionTables.Query.sqlGet(sql)["rows"];
  var result = parseInt(rows);
  Logger.log(result);
  return result;
}



Sunday, August 26, 2018

テキストエリアの末尾でenterキーを押したときに反応させたい


テキストエリア内の末尾にキャレットがあるときに
確定キーを押したらアラートを出したい


デモ





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




index.html
<!DOCTYPE html>
<html>
<body>
  <textarea id="ta"></textarea>
  <script>
    /************************************
    elem(id)
    ************************************/
    function elem(id) {
      return document.getElementById(id);
    }

    /************************************
    イベント
    ************************************/
    ta.onkeyup = ta_keyup;

    /************************************
    ta_keyup
    テキストエリアの末尾で確定キーを押したらアラートを出す

    必要な情報
    キャレットの位置
    テキストエリアの文字数
    押されたキーコード
    ************************************/
    function ta_keyup(e) {
      var where_caret = get_where_caret("ta");
      var len_ta = get_len_elem("ta");
      var key_code = get_key_code(e);
      result_ta_keyup(where_caret, len_ta, key_code);
    }

    function get_where_caret(id) {
      return elem(id).selectionStart; //キャレットの位置を返す
    }

    function get_len_elem(id) {
      return elem(id).value.length; //elemの文字列数を返す
    }

    function get_key_code(e) {
      return e.keyCode; //キーコードを返す
    }

    function result_ta_keyup(where_caret, len_ta, key_code) {
      if (where_caret === len_ta && key_code === 13) {
        alert("hello");
      }
    }
  </script>
</body>
</html>




Saturday, August 25, 2018

キャレットの位置を取得したい


テキストエリア内でカーソルの位置(キャレット)がどこにあるか取得したい

selectionStartで取得できたので書き残しておこう



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




index.html
<!DOCTYPE html>
<html>
<body>
  <textarea id="ta"></textarea>
  <script>
    var ta = document.getElementById("ta");
    ta.onkeyup = ta_keyup;

    function ta_keyup() {
      var caret = ta.selectionStart;
      alert(caret);
    }
  </script>
</body>
</html>
意訳
 


テキストエリア

テキストエリアを取得する
テキストエリアでキーが上がったら

この機能がやること
テキストエリアのキャレットの位置(選択された文字の先頭のindex)を取得して
アラートに出す






スプレッドシートで特定の列の値を任意の個数で区切りたい


A列に10個のデータがあって


3つずつに区切ってこのようにしたい



コード.gs
function run(){
  var count = 3;
  split_str_by_count(count);
}

function split_str_by_count(count) {
  var sh = SpreadsheetApp.getActiveSheet();
  var range = sh.getDataRange();
  var values = range.getValues();
  var array = array_push_apply(values);
  var result = [];
  for(var i = 0; i < (values.length/count)-1; i++){
    var spliced = array.splice(0, count);
    result.push(spliced);
  }
  set_values(result);
  set_last_values(array, count);//set_values([result]);でも同じ
}

function array_push_apply(values){
  for(var i = 1; i < values.length; i++){
    Array.prototype.push.apply(values[0], values[i]);
  }
  return values[0];
}

function set_values(array){
  var sh = SpreadsheetApp.getActiveSheet();
  var last_row = sh.getLastRow();
  var start_row = last_row + 1;
  var start_col = 1;
  var num_rows = array.length;
  var num_cols = array[0].length;
  var range = sh.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues(array); 
}

function set_last_values(array, count){
    var blanks = count - array.length;
    for(var j = 0; j < blanks; j++){
      array.push("");
    }
    set_values([array]);
}
意訳
この機能がやること
長さを決めて
split_str_by_countに渡す


この機能がやること
アクティブシートを取得して
データが入っている範囲を取得して
その範囲内の値たちを取得すると二次元配列なので
array_push_applyに渡して一次元配列にして
result配列を用意して
countの数で割り切れた分だけ以下を繰り返す
一次元配列からcountの分だけ取り出して
result配列に追加して

追加し終わったresult配列をset_valuesに渡してシートに入力する
countの数で割り切れなかった残りをset_last_valuesに渡して入力する


この機能がやること
受け取ったvaluesの数だけ以下を繰り返す
valuesの先頭の要素に後ろの要素を追加していく

追加し終わったら返す


この機能がやること
アクティブシートを取得して
受け取ったshの最後でデータが入っている最後の行を取得して
その次の行をstartにして
1列目をstartにして
arrayの要素数を取得して
array[0]内の要素数を取得して
入力する範囲を指定して
入力する


この機能がやること
受け取ったcount - arrayの長さ で空白にする要素数を取得して
その数だけ以下を繰り返す
arrayに空白を追加して

set_valuesに2次元配列にして渡して入力する



実行結果

実行したいシートで run() の countを決めて
run() を実行すると


例でみた10個のデータをこのように区切ることができる

区切った値を書き出す場所は
例では同じシートの最終行の下にしていますが
set_values(array) の中で任意の場所を指定できます



Friday, August 24, 2018

JIRA APIでwachersを追加する


API経由でJIRAの課題にwatcherを追加するコードがよくわからず
試したコードを書き残しておきます

tokenとjiraのURLはスクリプトファイルのPropertiesServiceに保存しておきます


KEY-1の課題のウォッチャーに
username1 と username2 を追加する例です



コード.gs
function add_watchers() {
  var key = "KEY-1";
  var watchers = ["username1", "username2"];
  for(var i = 0; i < watchers.length; i++){
    var options = {
      method : "post",
      payload : '"' + watchers[i] + '"',//"で囲まないと400が返される
      contentType: "application/json",
      headers: {Authorization: 'Basic ' + get_jira_token()}
    };
    var response = UrlFetchApp.fetch(get_jira_base_url() + "rest/api/2/issue/" + key + "/watchers", options);
  }; 
}

function get_jira_token() {
  return PropertiesService.getScriptProperties().getProperty('jira_token')
}

function get_jira_base_url() {
  return PropertiesService.getScriptProperties().getProperty('jira_base_url')
}


参考

Add watcher
https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-watchers-post

Monday, August 20, 2018

スプレッドシートを作成して指定のフォルダに入れたい


新規スプレッドシートを作成して指定のフォルダに入れたくて書いたコードです

SpreadsheetApp.create()
で作るとマイドライブに作成されるので
指定したフォルダに追加してから
マイドライブから削除するという
ちょっと回りくどいことをしています

一発でできる方法もありそうですが
持っている知識を組み合わせてやってしまいました



コード.gs
var TARGET_FOLDER_ID = "移動先のフォルダID";

function create_sheet() {
  var name = "new sheet name";
  var sheet = SpreadsheetApp.create(name);
  var file_id = sheet.getId();
  add_file(file_id)
  remove_file(file_id, TARGET_FOLDER_ID);
}

function remove_file(file_id){
  var file = DriveApp.getFileById(file_id);
  var parent_folder = file.getParents().next();
  parent_folder.removeFile(file);
}

function add_file(file_id) {
  var file = DriveApp.getFileById(file_id);
  var folder = DriveApp.getFolderById(TARGET_FOLDER_ID);
  folder.addFile(file);
}


関連記事



要素の中身を編集可能にしたい contenteditable=true


このように書くとdiv要素の中身を編集可能にできる

<div contenteditable=true>edit here</div>


デモ
edit here



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




index.html
<!DOCTYPE html>
<html>
<body>
  <div contenteditable=true>edit here</div>
</body>
</html>

意訳
 


編集可能なdiv要素





補足


JSで指定したいときはsetAttributeでこのように↓

ELEMENT.setAttribute("contenteditable", true);



参考

HTMLElement.contentEditable
https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/contentEditable

配列内のオブジェクトで特定のvalueのみの配列を作りたい


このような配列があって

var obj_array = [
  { title: "かきくけこ", id: 1},
  { title: "あいうえお", id: 2},
  { title: "さしすせそ", id: 3}
];


titleだけ抜き出したこのような配列を作りたい

[かきくけこ, あいうえお, さしすせそ]


ということを実現するコードの例です


コード.gs
var obj_array = [ 
  { title: "かきくけこ", id: 1},
  { title: "あいうえお", id: 2},
  { title: "さしすせそ", id: 3}
];
 
function get_array(){
  var array = [];
  var target = "title";
  for(var i = 0; i < obj_array.length; i++){
    var title = obj_array[i][target];
    array.push(title);
  }
  Logger.log(array);
}



実行結果




配列内のオブジェクトをvalueでソートしたい


このような配列があって

var obj_array = [
  { title: "かきくけこ", id: 1},
  { title: "あいうえお", id: 2},
  { title: "さしすせそ", id: 3}
];

titleで並べ替えたい



titleを昇順で並べ替えると
[{id=2.0, title=あいうえお}, {id=1.0, title=かきくけこ}, {id=3.0, title=さしすせそ}, {id=4.0, title=タチツテト}]


titleを降順で並べ替えると
[{id=4.0, title=タチツテト}, {id=3.0, title=さしすせそ}, {id=1.0, title=かきくけこ}, {id=2.0, title=あいうえお}]


ということを実現するコードの例です


コード.gs
var obj_array = [ 
  { title: "かきくけこ", id: 1},
  { title: "あいうえお", id: 2},
  { title: "さしすせそ", id: 3}
];
 
function str_sort() {
  var ascending = obj_array.sort(asc);
  Logger.log(ascending);
  var descending = obj_array.sort(desc);
  Logger.log(descending);
}

function asc(a, b){
  var targetA = a.title;
  var targetB = b.title;
  if(targetA > targetB){
    return 1;
  }else if(targetA < targetB ){
    return -1;
  }else{
   return 0;
  }
}

function desc(a, b){
  var targetA = a.title;
  var targetB = b.title;
  if(targetA > targetB){
    return -1;
  }else if(targetA < targetB ){
    return 1;
  }else{
   return 0;
  }
}


実行結果



関連記事

Thursday, August 16, 2018

JavaScriptで配列内の順番を逆にしたい


array.reverse()でやってみる


このような配列があって
[1,2,3,4,5]


このように順番を逆にしたい
[5,4,3,2,1]



コード.gs
function myFunction() {
  var array = [1,2,3,4,5];
  Logger.log(array.reverse());
}
意訳
この機能がやること
配列を用意する
順番を逆にしてログに出す



実行結果



HTMLServiceのテンプレートファイルをワンクリックで作成したい


HTMLServiceでアプリケーションを作る時に


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

みたいなコードを書いたり
.htmlファイルを新規作成するのは毎回やることなので

ワンクリックで基本的なコードが書かれたファイルを作成したくなった



実現するための手順
  1. テンプレート用のスクリプトファイルをGoogleドライブ内に用意する
  2. そのファイルをコピーするスクリプトファイルを用意してWebアプリにする
  3. そのアプリを開いたらマイドライブにテンプレートファイルのコピーが作成される


1. テンプレートファイルを用意する


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



ここでは.htmlファイルはデフォルトのままにしておきます

index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    
  </body>
</html>


2. 1のファイルをコピーするスクリプトファイルを用意する


コード2.gs
var FILE_ID = "ファイルID";

function doGet() {
  var copy_file_url = copy_file_to_mydrive();
  var open = '<script>window.open("' + copy_file_url + '","_top")</script>';
  var html = HtmlService.createHtmlOutput(open);
  return html;
}

function copy_file_to_mydrive() {
  var file = DriveApp.getFileById(FILE_ID);
  var name = "Copy of " + file.getName();
  var copy_file = file.makeCopy(name);
  return copy_file.getUrl();
}
意訳
ファイルIDを指定する

この機能がやること
ファイルをコピーしてそのURLを取得して
そのファイルを開くスクリプトを書いて
そのスクリプトを含むHTMLを作成して
返す


この機能がやること
対象のファイルを取得して
名前を決めて
コピーを作成して
作成したファイルのURLを返す


これをWebアプリケーションとして導入します

やり方はこちらに↓
HTMLでページを作る


3.2で作ったWebアプリケーションを開く


うまくいくとテンプレートファイルがマイドライブにコピーされます

リンクとして使う場合はこのWebアプリケーションのURLを aタグ の href に指定します
(自分以外に共有する場合は共有設定の変更が必要です)



Wednesday, August 15, 2018

選択した文字列をクリップボードに保存したい(ボタン)


getSelection().toString()

execCommand("copy")

で実現できたので備忘録として



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




index.html
<!DOCTYPE html>
<html>
<body>
    <p>あいうえおかきくけこ</p>
    <button id="bt">ボタン</button>
    <script>
        document.getElementById("bt").onclick = get_copy;

        function get_copy() {
          var value = window.getSelection().toString();
          document.execCommand("copy");
        }
    </script>
</body>
</html>
意訳
 


コピー対象の文字列
実行ボタン

実行ボタンをクリックしたらget_copyを実行する
この機能がやること

選択されている文字列を取得して
クリップボードにコピーする






参考

window.getSelection
https://developer.mozilla.org/ja/docs/Web/API/Window/getSelection

document.execCommand
https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand

Monday, August 13, 2018

[Deprecated]Google Driveの特定のフォルダ内のファイルを別のフォルダに移動したい


本投稿で書いた移動方法は廃止されました。

代わりにmoveToで移動できるようになりました。


移動元のフォルダから
移動先のフォルダにファイルを移動したい


この「移動する」を言い換えると


移動先のフォルダにファイルを追加して
移動元のフォルダからファイルを削除する


ことになるので


移動元のフォルダIDを
ORIGINAL_FOLDER_ID

移動先のフォルダIDを
TARGET_FOLDER_ID

として以下のコード.gsのように書くと実現できました


できましたが、、
Google Drive上で素直にファイルを選択して移動する方が早いですね

ファイル数が多い場合は起動時間を越えるのでその対応も必要になります



コード.gs
var ORIGINAL_FOLDER_ID = "移動元のフォルダID";
var TARGET_FOLDER_ID = "移動先のフォルダID";

function move_files() {
  var original = DriveApp.getFolderById(ORIGINAL_FOLDER_ID);
  var target = DriveApp.getFolderById(TARGET_FOLDER_ID);
  var contents = original.getFiles();
  while(contents.hasNext()) {
    var file = contents.next();
    target.addFile(file);
    original.removeFile(file);
  }
}
意訳
移動元のフォルダのIDを指定する(このフォルダから)
移動先のフォルダIDを指定する(このフォルダへ)

この機能がやること
移動元のフォルダを取得して
移動先のフォルダを取得して
移動元のフォルダ内のファイルを取得して
フォルダ内にファイルが有る限り以下を繰り返す
フォルダ内のファイルを順番に取得して
移動先のフォルダに追加して
移動元のフォルダからは削除する




参考

addFile(child)
https://developers.google.com/apps-script/reference/drive/folder#addFile(File)

removeFile(child)
https://developers.google.com/apps-script/reference/drive/folder#removeFile(File)

Sunday, August 12, 2018

imgタグで配置した画像のコントラストを変えたい


スタイルの設定でやってみる

filter: contrast(200%);



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




index.html
<html>
<head>
    <style>
        img {
            filter: contrast(200%);
        }
    </style>
</head>
<body>
    <img src="画像URL">
</body>
</html>
意訳
 


imgタグの
コントラストを200%にする




URLを指定して画像を配置する




Canvasで画像を切り抜いて表示する


drawImageを使って元画像を切り取って表示してみる



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




index.html
<html>
  <body onload="draw();">
    <canvas id="canvas" width="1200" height="1200"></canvas>
    <div style="display:none;">
      <img id="source" src="画像ファイルのURL">
    </div>
    <script>
   function draw() {
     var canvas = document.getElementById('canvas');
     var source = document.getElementById('source');
     var context = canvas.getContext('2d');

     var sx = 120;//source_x←元画像の左からこの位置を起点に
     var sy = 120;//source_y←元画像の上からこの位置を起点に
     var sw = 2000;//source_width←元画像で切り取る幅
     var sh = 2000;//source_height←元画像で切り取る高さ
     
     var dx = 150;//destination_x←表示するときの画面上の左からの距離
     var dy = 15;//destination_y←表示するときの画面上の上からの距離
     var dw = 1200;//destination_width←表示するときの幅
     var dh = 1200;//destination_height←表示するときの高さ
     
     context.drawImage(source, sx, sy, sw, sh, dx, dy, dw, dh);
   }
</script>


Google Drive内の画像ファイルをHTMLのimgタグで表示したい

Update 2024/02

現在、この書き方で表示できるようです。
<img src="https://lh3.googleusercontent.com/d/FILE_ID">



補足:

2024/01に
<img id="source" src="https://drive.google.com/uc?export=view&id=FILE_ID">

で403が返ってきた時の回避策として見つけた

<image src="https://drive.google.com/thumbnail?sz=w1000&id=FILE_ID"/>  

でも以下のようなエラーが出てきました。

Failed to load resource: net::ERR_BLOCKED_BY_RESPONSE.NotSameSite


回避する書き方の一つとして、上に書いた
https://lh3.googleusercontent.com/d/FILE_ID
FILE_IDにGoogleドライブにある画像ファイルのIDを入れると、Google Apps ScriptでデプロイしたWebアプリに、Googleドライブに保存してある画像を表示することができました。

以下3つでも表示できました。
<img src="https://lh4.googleusercontent.com/d/FILE_ID">
<img src="https://lh5.googleusercontent.com/d/FILE_ID">
<img src="https://lh6.googleusercontent.com/d/FILE_ID">


参考

Thursday, August 9, 2018

HTML要素を動的に作りたい(チェックボックス)

HTML Serviceでこういうチェックボックスを作る

選択した時にアラートに値を表示してみる




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




index.html
<!DOCTYPE html>
<html>
<body>
  <div id="main_div">
  </div>
</body>
<script>
  var values = ["aaa", "bbb"];
  create_checkbox(values);

  function create_checkbox(values) {
    var main_div = document.getElementById('main_div');
    for (var i = 0; i < values.length; i++) {
      var checkbox = document.createElement('input');
      var label = document.createElement('label');
      var br = document.createElement('br');
      var id = "checkbox" + i;
      var name = "group1";
      var value = values[i];
      checkbox.setAttribute("type", "checkbox");
      checkbox.setAttribute("value", value);
      checkbox.setAttribute("id", id);
      checkbox.setAttribute("name", name);
      label.setAttribute("for", id);
      label.innerHTML = value;
      
      if(value == "aaa"){
      checkbox.checked = true;
      }

      checkbox.onclick = function() {
        alert(this.value);
      }

      main_div.appendChild(br);
      main_div.appendChild(checkbox);
      main_div.appendChild(label);
    }
  }
</script>
</html>



スプレッドシートURLから対象のシートを取得する


スプレッドシートのURLを渡してシートIDからシートを取得して返す例です

このようなスプレッドシートのURLがあって
var ss_url = https://docs.google.com/spreadsheets/d/スプレッドシートID/edit#gid=シートID


"gid=" で区切ったときの後半部分のシートIDを取得する
var sh_id = ss_url.split("gid=")[1];



以下のコード.gsスプレッドシートURLを設定して
get_sheet_by_url() を実行すると
対象シートのA1の値がログに出ます



コード.gs
function get_sheet_by_url() {
  var ss_url = "スプレッドシートURL";
  var sheet = get_sheet(ss_url);
  Logger.log(sheet.getRange("A1").getValue());
}


function get_sheet(ss_url) {
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sh_id = ss_url.split("gid=")[1];
  var shs = ss.getSheets();
  for (var i = 0; i < shs.length; i++) {
    if (shs[i].getSheetId() == sh_id) {
      break;
    }
  }
  var sheet = ss.getSheets()[i];
  return sheet;
}



処理をまとめて書いてsheetを返す例



コード2.gs
function getTargetSheet() {
  var ss_url = "https://docs.google.com/spreadsheets/d/スプレッドシートID/edit#gid=シートID";
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sheet_id = ss_url.split("gid=")[1];
  var sheets = ss.getSheets();
  for (var i = 0; i < sheets.length; i++) {
    if (sheets[i].getSheetId() == sheet_id) {
      break;
    }
  }
  var sheet = ss.getSheets()[i];
  return sheet;
}



厳密等価演算子(===)でやる場合

var sheetId = sheetUrl.split("gid=")[1];
はstringで返ってくるので、parseIntで整数値にする。

なぜ?
if (sheets[i].getSheetId() === parseInt(sheetId)) {
で数値同士で比較できるようにするため。


コード3.gs
function getTargetSheet() {
  var sheetUrl = "https://docs.google.com/spreadsheets/d/スプレッドシートID/edit#gid=シートID";
  var ss = SpreadsheetApp.openByUrl(sheetUrl);
  var sheetId = sheetUrl.split("gid=")[1];
  var sheets = ss.getSheets();
  for (var i = 0; i < sheets.length; i++) {
    if (sheets[i].getSheetId() === parseInt(sheetId)) {
      break;
    }
  }
  var sheet = ss.getSheets()[i];
  return sheet;
}



Saturday, August 4, 2018

HtmlServiceでviewportを使いたい


.addMetaTag()を使います


コード.gsにこのように書くと動いてくれました
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile("index")
                    .addMetaTag("viewport", "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=10.0");
}


.htmlファイルの<head></head>の中に以下のように書いても動いてくれない
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">



コード.gs
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile("index")
                    .addMetaTag("viewport", "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=10.0");
}
意訳
この機能がやること
指定したHTMLファイルを表示する
メタタグのviewportを追加する




index.html
<!DOCTYPE html>
<html>
  <body>
    <button id="bt">button</button>
  <script>
  document.getElementById("bt").onclick = bt_clicked;
  
  function bt_clicked(){
    alert("clicked!");
  }
  </script>
  </body>
</html>

意訳
 


ボタンを配置する

ボタンがクリックされたらbt_clickedを実行する

この機能がやること
clicked!という文字列をアラートに出す







実行結果

スマホで上記のWebアプリを開くと

buttonをタップするとこのようにアラートが出る



補足


viewportを設定しないとこのような表示になる



上部に表示されているこれを消す方法は今のところないようです(2018/08/04現在)

issueには上がっているようです
https://issuetracker.google.com/issues/63521070


末尾の文字が特定の文字だったら削除したい


このようなテキストがあって

"aaa, bbb, ccc,"


末尾のカンマを削除して
このようにしたい

"aaa, bbb, ccc"


ということを実現するコードの例です



コード.gs
function remove_end_text(){
  var values = "aaa, bbb, ccc,"
  if(values.slice(-1) === ","){
    values = values.slice(0,  -1);
  }
  Logger.log(values);
}
意訳
この機能がやること
末尾にカンマを含む文字列を用意する
valuesから末尾を取り出してもし , なら
末尾を取り除いて

結果をログに出す




実行結果


スプレッドシートからJSONファイルを作ってGoogleドライブに保存する(keyは1行目)(外部スクリプトファイル)


スプレッドシートからJSONファイルを作ってGoogleドライブに保存する(keyは1行目)
ではアクティブシートを対象にしましたが

今回はスタンドアロンのスクリプトファイルで書いてみます



コード.gs
/************************************
global
************************************/
var FILE_NAME = "作成するファイル名";
var FOLDER_ID = "保存先のGoogleドライブのフォルダID";
var SS_URL = "https://docs.google.com/spreadsheets/d/スプレッドシートID/edit#gid=シートID";//対象のシートのURL
var SHEET_ID = SS_URL.split("gid=")[1];

/************************************
create_json
************************************/
function create_json(){
  var ss = SpreadsheetApp.openByUrl(SS_URL);
  var sh = get_sheet(ss, SHEET_ID);
  var values = get_values(sh);
  var keys = values.shift();
  var array = get_array(values, keys);
  var json = get_json(array);
  var file_url = create_file(json);
  Logger.log(file_url);
}

/************************************
get_sheet
************************************/
function get_sheet(ss, id) {
  var shs = ss.getSheets();
  for (var i = 0; i < shs.length; i++) {
    if (shs[i].getSheetId() == id) {
      break;
    }
  }
  var sh = ss.getSheets()[i];
  return sh;
}

/************************************
get_values
************************************/
function get_values(sh) {
  var last_row = sh.getLastRow();
  var last_col = sh.getLastColumn();
  var start_row = 1;
  var start_col = 1;
  var numRows = last_row - start_row + 1; 
  var numColumns = last_col - start_col + 1;
  var range = sh.getRange(start_row, start_col, numRows, numColumns);
  var values = range.getDisplayValues();
  return values;
}

/************************************
get_array
************************************/
function get_array(values, keys){
  var array = [];
  for (var i = 0; i < values.length; i++) {
    var obj = create_obj(values[i], keys);
    array.push(obj)
  }
  return array;
}

/************************************
create_obj
************************************/
function create_obj(values, keys) {
  var obj = {}
  for (var j = 0; j < keys.length; j++) {
    obj[keys[j]] = values[j];
  }
  return obj;
}

/************************************
get_json
************************************/
function get_json(array){
  var json = JSON.stringify(array);
  return json;
}

/************************************
create_file
************************************/
function create_file(json) {
  var content_type = "application/json";
  var file_name = FILE_NAME;
  var blob = Utilities.newBlob("", content_type, file_name);
  var file = blob.setDataFromString(json, "utf-8");
  var drive = DriveApp.getFolderById(FOLDER_ID);
  var created_file = drive.createFile(file);
  var url = created_file.getUrl();
  return url;
}


関連記事



HTML要素を動的に作りたい(チェックボックス)


こういうチェックボックスをJavaScriptで作りたい



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




index.html
<!DOCTYPE html>
<html>
  <body>
    <form id="checkbox_form"></form>
    <script>
      var values = ["aaa", "bbb", "ccc"];
      create_checkbox(values);
      
      function create_checkbox(values) {
        var parent = document.getElementById("checkbox_form"); 
        for (var i = 0; i < values.length; i++) {
          var checkbox = document.createElement('input');
          var label = document.createElement('label');
          var br = document.createElement('br');
          var id = "check" + i;
          var name = "check1";
          var value = values[i];
          checkbox.setAttribute("type", "checkbox");
          checkbox.setAttribute("id", id);
          checkbox.setAttribute("name", name);
          checkbox.setAttribute("value", value);
          label.setAttribute("for", id);
          label.textContent = value;
      
          checkbox.onclick = function() {
            alert(this.value);
          }
          parent.appendChild(br);
          parent.appendChild(checkbox);
          parent.appendChild(label);
        }
      }
    </script>
  </body>
</html>

意訳
 


フォームを用意する

チェックボックスの選択肢
create_checkboxでチェックボックスを作る

この機能がやること(valuesを受け取る)
親要素を取得して
valuesの数だけ以下を繰り返す
チェックボックスを作って
ラベルを作って
改行を作って
チェックボックスのidを決めて
チェックボックスのnameを決めて(チェックボックスのグループ名)
チェックボックスのvalueを決めて
チェックボックスのtypeを設定して
チェックボックスのidを設定して
チェックボックスのnameを設定して
チェックボックスのvalueを設定して
ラベルのforにチェックボックスのidを設定して
ラベルのtextContentにvalueを設定して表示する

チェックボックスがクリックされた時に
そのvalueをアラートに出す

親要素に改行を追加して
親要素にチェックボックスを追加して
親要素にラベルを追加する








Wednesday, August 1, 2018

LINE BOTで「クイックリプライ」を使う


LINEのMessaging APIで「クイックリプライ」が公開されたので試してみました

このように送ったメッセージ(hello)に対して
メッセージと選択肢を返せるようになりました

LINE 8.11.0 以降から対応しているそうなので
それ以前のバージョンの場合はスマホのLINEアプリの更新が必要です



例として
hello と送ると

Select a button below というメッセージを返して

world
Send location
camera
camera roll
という選択肢(ボタン)を返す例を以下のコード.gsに書きました



コード.gs
var CHANNEL_ACCESS_TOKEN = "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") {
            reply_message(events[i]);
        }
    }
}

function reply_message(e) {
    if (e.message.type == "text") {
        if (e.message.text == "hello") {
            var postData = {
                "replyToken": e.replyToken,
                "messages": [{
                        "type": "text",
                        "text": "Select a button below",
                        "quickReply": {
                            "items": [{
                                    "type": "action",
                                    "action": {
                                        "type": "message",
                                        "label": "world",
                                        "text": "World"
                                    }
                                },
                                {
                                    "type": "action",
                                    "action": {
                                        "type": "location",
                                        "label": "Send location"
                                    }
                                },
                                {
                                    "type": "action",
                                    "action": {
                                        "type": "camera",
                                        "label": "camera"
                                    }
                                },
                                {
                                    "type": "action",
                                    "action": {
                                        "type": "cameraRoll",
                                        "label": "Camera roll"
                                    }
                                }
                            ]
                        }
                    }

                ]
            };
        }
    }
    var options = {
        "method": "post",
        "headers": {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + CHANNEL_ACCESS_TOKEN
        },
        "payload": JSON.stringify(postData)
    };
    UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", options);
}


返せる選択肢について
  • text:テキストを返す
  • location:場所を送る
  • camera:カメラを開く
  • camera roll:保存された画像を開く

関連記事

LINE BOTの作り方


参考

using-quick-reply
https://developers.line.me/en/docs/messaging-api/using-quick-reply/

quick-reply
https://developers.line.me/en/reference/messaging-api/#quick-reply

【LINE】「Messaging API」の新機能、「クイックリプライ」を公開 返答内容を選択・タップするだけで、メッセージへ返信
https://linecorp.com/ja/pr/news/ja/2018/2331

Latest post

Extracting data from Google Sheets with regular expressions

Introduction Regular expressions are a powerful tool that can be used to extract data from text.  In Google Sheets, regular expressions ca...