LANG SELRCT

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

Thursday, February 28, 2019

GmailApp.searchでメールを検索してシートに書き出したい


Gmailで検索した結果を、指定したシートに書き出す手順を書いていきます。


STEP1
Gmailで右端の▼をクリックして


STEP2
検索条件を決めます
ここでは@googleから来たメールで
検索する期間を以下のように設定しておきます


STEP3
検索ボタンをクリックするとこのように検索結果が表示されます


STEP4
スプレッドシートを新規作成してスクリプトエディタを開きます


STEP5
以下のコード.gsの query に検索条件を入れて getMailByQuery() を実行します



コード.gs
/************************************
GmailをsearchQueryで検索した結果をシートに書き出す
************************************/
function getMailByQuery() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("シート1");
  setHeader(sheet);//一行目に見出しを入力する
  
  var query = 'after:2019/2/25 before:2019/2/28';//Gmailで検索したいメール
  var threads = GmailApp.search(query);//スレッドたちを取得
  Logger.log(threads[0].getMessages()[0].getSubject());//1スレッド目の最初のメッセージをログに出してみる
  for(var i = 0; i < threads.length; i++) {
    var thread = threads[i];//スレッドを一つずつ取得
    var messages = thread.getMessages();//スレッドのメッセージたちを取得
    var array = [];
    for(var j = 0; j < messages.length; j++) {
      var message = messages[j];//メッセージを一つずつ取得
      var subject = message.getSubject();//件名を取得
      var body = message.getPlainBody() //本文
      var date = message.getDate(); //送信日時
      array.push([subject, body, date])
    }
    setValues(sheet, array);
  }
}

/************************************
シートに入力する
************************************/
function setValues(sheet, array){
  var lastRow = sheet.getLastRow();
  var startRow = lastRow + 1;
  var startCol = 1;
  var numRows = array.length;
  var numCols = array[0].length;
  var range = sheet.getRange(startRow, startCol, numRows, numCols);
  range.setValues(array); 
}

/************************************
1行目に自動入力する
************************************/
function setHeader(sheet){
  var array = [
    "subject",
    "body",
    "date"
  ];
  var start_row = 1;
  var start_col = 1;
  var num_rows = 1;
  var num_cols = array.length;
  var range = sheet.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues([array]); 
}


補足

実行するとこのような許可が求められます












参考

search(query)
https://developers.google.com/apps-script/reference/gmail/gmail-app#search(String)


Monday, February 25, 2019

AWS Lambdaにzipでコードをアップロードしたい


アップロードは「Code entry type」の「Upload a .zip file」からできます


「Upload」ボタンをクリックして


zipファイルを選択して「開く」をクリックして


右上の「Save」をクリックするとファイルをアップロードできます


zipファイルの作り方

ターミナルで対象のファイルが入っているディレクトリまで cd で移動して

対象のファイルが入っているディレクトリ$ zip -r my_zip ./*

とすると
「my_zip」という名のzipファイルが作成されます


補足

アップロードしたいファイルが入ったフォルダの中で
右クリックして「圧縮」を選択すると


「index.js.zip」ができたので
これをLambdaの「Upload a .zip file」でアップロードしてみると


空の「_MACOSX」ができました


これでもAPI Gatewayでendpointを作ってアクセスしたら動きました


参考

zipファイルの作成はこちらの記事を参考にさせていただきました!
https://www.suzu6.net/posts/83-lambda-zip/

Sunday, February 24, 2019

JavaScriptの非同期処理の理解を試みた備忘録


AWS Lambda で Function を作る際、デフォルトで入っているコードの中に 「async」 というのがありました。
exports.handler = async (event) => {
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

これまで使う機会がなくて使ってきませんでしたが、今回これを使う際にいくつかぶつかった非同期処理の理解に立ちはだかる壁について書きます。


asyncがどんなものかを知ろうとして調べてみると、、

asyncの仕組みを理解しようと思ったら
Promiseの仕組みの理解が必要でした。

Promiseの仕組みを理解しようと思ったら
コールバック関数の仕組みの理解が必要でした。

コールバック関数の仕組みを理解しようとしたら
非同期処理の仕組みの理解が必要でした。

非同期処理の仕組みを理解しようとしたら
同期処理の仕組みの理解が必要でした。


初見では意味不明な言葉ばかりなので、このページでの定義を個人的に理解した言葉で表現し直してみます。

「処理」
  • コードが結果を返すまでの計算作業

「同期処理」(sync)
  • 処理をひとつずつ順番に実行して結果を渡していく
  • その間は他の処理を同時に実行しない

「非同期処理」(async)
  • 同時に複数の処理を実行する
  • 時間のかかる処理の結果が返ってきてから次の処理を実行するには工夫が必要
    • その工夫として「コールバック関数」「Promise」「async/await」がある
  • ボタンを押したら実行する.onclick等は非同期処理
    • そのコードが書かれていてもボタンが押されるまで実行されない
  • ググっていろいろなページを見ていると、「非同期処理を実装する」と表現されているものの、「非同期処理を同期的に実装する」という意味で使われていたりする
    • JavaScript自体は非同期処理
    • 処理の結果を待たず次々と実行されるので時間がかかる処理があると渡したい値が渡らない
    • そこで、結果が返ってきてから値を渡すようにそこだけ同期処理にしてやる
    • それを「コールバック関数」「Promise」「async/await」で実現する

「コールバック関数」
  • 非同期処理で処理の実行タイミングをコントロールしたいときに使える
  • 引数として他の関数に渡す関数
  • コールバック関数が重なると可読性が良くないためPromiseが生まれた

「Promise」とは
  • 非同期処理の結果を待ってから次の処理を実行したいときに使える
    • 時間のかかる処理は先にその処理結果を返すための入れ物だけを返しておいて、処理が終わって結果が返ってきたらその入れ物に結果を渡してやる
  • Callback関数よりも簡潔に処理の実行タイミングをコントロールできる
  • Promise の状態
    • pending: 初期状態
    • fulfilled(resolve): 処理が成功した
    • rejected(reject): 処理が失敗した
    • 入れ物を作ったときはpending状態で「まだ結果が返ってきていないからちょっと待っててね」状態、結果が返ってきたらfulfilled状態になって「お、成功して結果が返ってきたよ、はいこれ」状態、エラーが返ってきたらrejected状態になって「おや、処理がうまく行かなかったみたいだよ、こんなの返ってきたよ」状態
  • promise.then(funcA)
    • promiseの処理が成功したらfuncAを実行する
  • promise.catch(errorFunc)
    • promiseの処理が失敗したらerrorFuncを実行する
  • Promiseの作り方
    1. var promise = new Promise(function (resolve, reject){})
    2. var promise = Promise.resolve(myFunc())

「async/await」とは
  • Promiseの処理を簡素化して書ける


関連記事



参考

Promiseを使う

JavaScriptでasync/awaitの処理を試みる


Promise同様、ある処理が終わってからその処理結果を次の処理に渡したいときに使える。

そんなasync/awaitをGoogle Apps ScriptのHtmlServiceで試してみた備忘録。



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




index.html
<!DOCTYPE html>
<html>
<body>

<script>
func1();
async function func1() {
  console.log(1);
  var result = await func2();
  console.log(result);
}

function func2() {
  return new Promise(func3);
}

function func3 (func4) {
  func4(2);
}

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




試してみる
index.htmlをWebアプリにして開くと
デベロッパーツールのConsoleに
1
2
というログが出ます。



関連記事


JavaScriptでPromiseの処理を試みる


Google Apps ScriptのHtmlServiceでPromiseの処理を試みた備忘録。



[プロローグ]
時間のかかる処理がある時に、その処理が終わってから次の処理を実行したい。
でもJavaScriptは非同期処理で、実行した処理が終わるまで待ってはくれないため、意図した順番通りに実行して値を渡したいときには工夫が必要。

その工夫として、かつてはコールバック関数を重ねていたが、可読性が良くなかった。
その可読性の問題を解決するためにPromiseの処理が考え出された。

と理解しています。

FYI


[個人的な体験]
Promiseの処理は個人的に理解に苦しみました。(まだ理解しきれてはいない)
いくつか書き方を変えて書いてみてなんとなくわかってきた備忘録として、以下にコードを残しました。

言葉で説明しようとするとどんどんわかりにくくなるので、コードを書いて動きを確認しながら、別の書き方で書き直してみてちょっとわかった気になれた。


まずは調べた時によく見る書き方(index.htmlのPromise以下のコード)

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



参考にしたサイトではsetTimeoutで時間のかかる処理を作っていましたが、ここではログを出すだけにします。

index.html
<!DOCTYPE html>
<html lang="ja"> 
  <body>
    <script>
/************************************
Promise
/************************************/
myPromise1();
function myPromise1() {
  var promise = new Promise(function (resolve, reject) {
    console.log(1);
    resolve();
  });

  promise.then(function() {
      console.log('2 エラーを投げる');
      throw new Error("エラーメッセージ");
    })
    .catch(function(error) {
      console.log("3 " + error);
    })
    .then(function() {
      console.log(4);
    });
}

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


次に上の書き方を、いろいろ書き換えてみる

どのボタンを押しても同じように処理が実行されてconsole.logに順番に結果が出る




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




index.html
<!DOCTYPE html>
<html lang="ja"> 
  <body>
  <button id="myPromise1">myPromise1</button>
  <button id="myPromise2">myPromise2</button>
  <button id="myPromise1_2">myPromise1_2</button>
  <button id="myPromise2_2">myPromise2_2</button>
    <script>
/************************************
イベント
/************************************/
elem('myPromise1').onclick = myPromise1;
elem('myPromise2').onclick = myPromise2;
elem('myPromise1_2').onclick = myPromise1_2;
elem('myPromise2_2').onclick = myPromise2_2;

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

/************************************
myPromise1上のコードと同じもの
/************************************/
function myPromise1() {
  var promise = new Promise(function (resolve, reject) {
    console.log(1);
    resolve();
  });

  promise.then(function() {
      console.log('2 エラーを投げる');
      throw new Error("エラーメッセージ");
    })
    .catch(function(error) {
      console.log("3 " + error);
    })
    .then(function() {
      console.log(4);
    });
}

/************************************
myPromise2
myPromise1をPromise.resolve(funcB())で書き換える
/************************************/
function myPromise2() {
  var promise = Promise.resolve(funcB());
  promise.then(funcA)
  .catch(erroeFunc)
  .then(funcC);
}

/************************************
myPromise1_2
myPromise1をnew Promise(promiseFunc)で書き換える
var promiseという変数を作らない
/************************************/
function myPromise1_2() {
  new Promise(promiseFunc)
  .then(funcA)
  .catch(erroeFunc)
  .then(funcC);
}

function promiseFunc(resolve, reject) {
  funcB();
  resolve();
}

/************************************
myPromise2_2
myPromise2をPromise.resolve(funcB())で書き換える
var promiseという変数を作らない
/************************************/
function myPromise2_2() {
  Promise.resolve(funcB())
  .then(funcA)
  .catch(erroeFunc)
  .then(funcC);
}

/************************************
処理
/************************************/
function funcA() {
  console.log('2 エラーを投げる');
  throw new Error("エラーメッセージ");
}

function funcB() {
  console.log(1);
}

function funcC() {
  console.log(4);
}

function erroeFunc(error) {
  console.log("3 " + error);
}

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



実行結果

デベロッパーツールのConsoleに順番にログが出ます。




AWS LambdaでLINEにPUSHしてみる


API Gateway の endpoint にアクセスしたら LINE に PUSH してみます。


ブラウザには SUCCESS という文字列を返してみます。


手順
  1. LINE BOT を作ります
  2. 環境変数にアクセストークンを設定します
  3. Lambda Function と API Gatewayを作ります



index.js
/************************************
require('https')
************************************/
var https = require('https');

var CHANNEL_ACCESS_TOKEN = process.env.token; 
var USER_ID = "ID";
var text = "hello!!";

/************************************
exports.handler
************************************/
exports.handler = async function(event) {
  var body = event['body'];
  var jobj = JSON.parse(body);
  
  var results = await getData();
  var json = returnJson(results);
  return json;
}

//Promise
function getData() {
  return new Promise(resolveFunc);
}

/************************************
https.request
************************************/
function resolveFunc(callback) { 
  var req = https.request(getOptions(), function(){
    callback("SUCCESS");
  });
  req.write(getPostData());  
  req.end();
};

/************************************
getOptions
************************************/
function getOptions() {
  var options = {
    "method": 'POST',
    "host": 'api.line.me',
    "path": '/v2/bot/message/push',
    "headers": {
      "Content-type": "application/json",
      'Content-Length': Buffer.byteLength(getPostData()),
      "Authorization": " Bearer " + CHANNEL_ACCESS_TOKEN
    }
  }
  return options;
}

/************************************
getPostData()
************************************/
function getPostData() {
  var data = {
    "to": USER_ID,
    "messages": [{
      "type": "text",
      "text": text,
    }]
  };
  var postData = JSON.stringify(data);
  return postData;
} 

//JSONを返す
function returnJson(results) {
  const response = {
    body: results,
  };
  return response;
}


AWS LambdaでSlackのchannels.historyを取得してみる(テスト用)


Slack APIには指定したChannelのメッセージ履歴を取得するchannels.historyというAPIがあります。
(FYI:https://api.slack.com/methods/channels.history

今回はこれを利用して、Lambda Functionで実行するコードを書いていきます。

※以下のコードはテスト用です。
SlackのAPI TokenをLambdaの環境変数に入れていて、API Gatewayのsecurityを「Open」にしている想定です。endpointを知っているすべての人がメッセージ履歴を取得できるため、privateのSlack channelの履歴を取得する場合は、API Gatewayのsecurityを「Open with API key」にするなど対策が必要です。


Lambda Functionのコード 

index.js
var https = require('https');

var channel_id = 'CHANNEL_ID';
var oldest = getUnixTime("2019/02/14 13:26:12");//この日時から
var latest = getUnixTime("2019/02/23 22:17:12");//この日時まで
var inclusive = true;//それらの日時を含めるか否か
var count = 2;//取得する件数
var token = process.env.slack_token;//Lambdaの環境変数からslack_tokenを取得する

function getUnixTime(dateTime) {
  var date = new Date(dateTime);
  var milsec = date.getTime();
  var sec = milsec / 1000;
  var time = sec.toString();
  return time;
}

exports.handler = async function(event) {
  var body = event['body'];
  var jobj = JSON.parse(body);
  var results = await getData(); //getData()の処理が終わってから次の処理を実行する
  var json = returnJson(results);
  return json;
};

//Promise
function getData() {
  return new Promise(resolveFunc);
}

//Promise resolved
function resolveFunc(arg) {
  https.get(getOptions(), function(res) {
    var body = '';
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
      body += chunk;
    });
    res.on('end', function(chunk) {
      arg(body);
    });
    res.on('error', function(e) {
      console.log(e.message);
      arg(e.message);
    });
  });
  //ここまでの処理をPromiseでやりたい
}

function getOptions() {
  var options = {
    'method': 'post',
    'host': 'slack.com',
    'path': '/api/channels.history' + getParams()
  };
  return options;
}

function getParams() {
  var params = '?channel=' + channel_id +
              '&latest=' + latest +
              '&inclusive=' + inclusive +
              '&count=' + count +
              '&token=' + token;
  return params;
}

//JSONを返す
function returnJson(results) {
  const response = {
    //body: JSON.stringify(result),
    body: results,
  };
  console.log(JSON.parse(results));
  return response;
}


API Gatewayのendpoitにアクセスして実行結果を見る

FYI:
AWS LambdaとAPI Gatewayを使ってみる
Lambda functionの実行ログを 見たい(CloudWatch)


考えた対策

「Open with API key」にしてhostとpathもpayloadで渡す



Saturday, February 23, 2019

Slack AppのTokenを取得したい


Slack APIの利用に必要なトークンの取得を試した手順です。


SlackのTokenを取得する手順(Slack Appを作ってTokenを発行する)


STEP6を開くリンク
https://api.slack.com/apps


STEP1
Slackでこのアイコンをクリックします


STEP2
「Customize Slack」を選択します


STEP3
「Menu」から「Configure apps」を選択します


STEP4
右上の「Build」をクリックします


STEP5
「Start Building」をクリックします


STEP6
「App Name」「Development Slack Workspace」を決めて
「Create App」をクリックします


STEP7
「Add features and functionality」から「Permissions」を選択します


STEP8
①「Select Permission Scopes」でこのアプリができることを決めて
「Save Changes」をクリック後
②「Install App to Workspace」が押せるようになるのでクリックします


STEP9
「Authorize」をクリックします



STEP10
「OAuth Access Token」にTokenが表示されます




参考

API トークンの生成と再生成 > 内部アプリトークン
https://get.slack.help/hc/ja/articles/215770388-API-%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E7%94%9F%E6%88%90%E3%81%A8%E5%86%8D%E7%94%9F%E6%88%90#%E5%86%85%E9%83%A8%E3%82%A2%E3%83%97%E3%83%AA%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3

API GatewayにAPI Keyの認証をつけてみる


Lambda Function とAPI Gateway を使ったことがない場合
AWS LambdaとAPI Gatewayを使ってみる



API Gateway の Security 設定を「Open」にしておくと誰でも利用できますが、「Open with API key」にすると、API Key なしでは Lambda function を実行できなくなります。

API Keyを設定する手順

STEP1:「Open with API key」を選択します


STEP2:「Add」をクリックします


STEP3:「Save」をクリックします


API Keyが発行されます



API endpointにアクセスしてみると

{"message":"Forbidden"}

と表示されると思います。


API Keyを渡さないとLambda Functionを実行できないことがわかったところで、API Keyを渡す方法を試してみます。


Google Apps ScriptでendpointにAPI Keyを渡して
Lambda functionの戻り値をログに出してみるコード

コード.gs
function run() {
  var url = 'https://API_ID.execute-api.us-east-1.amazonaws.com/default/FUNCTION_NAME';
  var options = {
    'method': 'post',
    'contentType': 'application/json',
    'headers': get_headers()
  }
  var response = UrlFetchApp.fetch(url, options);
  Logger.log(response);
}
  
function get_headers() {
  var headers = {
    "x-api-key": getProp('apigateway_key')
  }
  return headers;
}

function getProp(key) {
  return PropertiesService.getScriptProperties().getProperty(key);
}
意訳
この機能がやること
API Gatewayのendpoint
endpointにわたすoptionsを作成する
methodはpost
contentTypeはapplication/json
headersはスクリプトのプロパティのapigateway_keyの値

urlにoptionsを渡してデータを取得して
ログに出す


この機能がやること
headersという入れ物を作って
スクリプトのプロパティのapigateway_keyをx-api-keyの値として

返す


この機能がやること
受け取ったkeyの値をスクリプトのプロパティから取得して返す



Lambda 側のコードはデフォルトのまま'Hello from Lambda!'を返すだけにしておきます

index.html
exports.handler = async (event) => {
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};
意訳
この機能がやること
コメント
responseの入れ物を作って
statusCodeの値は200
bodyの値はJSON変換したメッセージ

返す



実行結果

コード.gsのrun()を実行すると

"Hello from Lambda!"

がログに出ます。


関連記事

API Gatewayに渡されたpayloadをLambda Function側で取得したい


Lambda Function とAPI Gateway を使ったことがない場合

endpointにパラメータを付けるやり方は
AWS LambdaでAPI Gatewayのクエリパラメータを取得したい(GASでfetch)
に書きました。

ここではpayloadでクエリパラメータと同じようにデータを渡してみます。

  var payload = {
    'data':{
    'key': 'value'
   }
  }


Google Apps Script側のコード

コード.gs
function run() {
  var url = 'https://API_ID.execute-api.us-east-1.amazonaws.com/default/FUNCTION_NAME';
  var payload = {
    'data':{
    'key': 'value'
   }
  }
  var json = JSON.stringify(payload);
  var options = {
    'method': 'post',
    'contentType': 'application/json',
    'payload': json
  }
  var response = UrlFetchApp.fetch(url, options);
  Logger.log(response);
}
意訳
この機能がやること
API Gatewayのendpoint
payloadという入れ物を作って
dataの中で
'key': 'value'を入れて


payloadをJSONに変換して
optionsという入れ物を作って以下のプロパティを入れて
'method': 'post',
'contentType': 'application/json',
'payload': json

urlに渡してデータを取得して
ログに出す



Lambda 側のコード

index.js
exports.handler = async (event) => {
    var body = event['body'];
    var jobj = JSON.parse(body);
    var data = jobj['data'];
    var response = {
        body: JSON.stringify(data),
    };
    return response;
};
意訳
この機能がやること
eventからbodyを取得して
オブジェクトに変換して
dataを取得して
返すオブジェクトを作って
JSONに変換してbodyの値にして

返す



実行結果

Google Apps Script側のログに以下のように出る

 {"key":"value"}


関連記事

AWS LambdaでAPI Gatewayのクエリパラメータを渡したい(GASでfetch)


Lambda Function とAPI Gateway を使ったことがない場合
AWS LambdaとAPI Gatewayを使ってみる



API Gatewayのendpointにブラウザからアクセスする方法は
AWS LambdaでAPI Gatewayのクエリパラメータを取得したい
に書きました。

ここではGoogle Apps Script の UrlFetchApp.fetch で endpoint + クエリパラメータにアクセスして、Lambdaからの戻り値をログに出してみます。

https://API_ID.execute-api.us-east-1.amazonaws.com/default/FUNCTION_NAME?key=value


Google Apps Script側のコード

コード.gs
function run() {
  var url = 'https://API_ID.execute-api.us-east-1.amazonaws.com/default/FUNCTION_NAME?key=value';
  var response = UrlFetchApp.fetch(url);
  Logger.log(response);
}

意訳
この機能がやること
API Gatewayのendpointにクエリパラメータをつけて
urlにアクセスしてデータを取得して
ログに出す




Lambda 側のコード

index.js
exports.handler = async (event) => {
    var  param = event['queryStringParameters'];
    var  response = {
        body: JSON.stringify(param),
    };
    return response;
};
意訳
この機能がやること
受け取ったeventからクエリパラメータを取得して
返すオブジェクトを作って
JSONに変換してbodyの値として

返す



実行結果

Google Apps Script側のログに以下のように出ます

 {"key":"value"}


関連記事

Friday, February 22, 2019

AWS LambdaでAPI Gatewayのクエリパラメータを取得したい


API Gatewayで、endpoint の末尾に ?key=value 等のパラメータをつけて

API endpoint:
https://API_ID.execute-api.us-east-1.amazonaws.com/default/FUNCTION_NAME?key=value


Lambda Function の中で

event['queryStringParameters']

と書くと

{key: value}

と取得できる


Lambda Functionのコード

index.js
exports.handler = async (event) => {
    let param = event['queryStringParameters'];
    const response = {
        body: JSON.stringify(param),
    };
    return response;
};
意訳
この機能がやること
パラメータを取得する
返すオブジェクトを作って
返す本文はパラメータをJSONにしたもの

返す



関連記事


Thursday, February 21, 2019

シートの特定の列で値が入っていない範囲の最終行を見つけたい


こういうシートがあって
kanaの列でデータが入っていない範囲を取得したい


コピペ用データ

hirakana
あいうえお
かきくけこ
さしすせそ
たちつてとタチツテト
なにぬねのナニヌネノ



コード.gs
function getLastBlank() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var startRow = 2;
  var col = 2;
  var values = sheet.getRange(startRow, col, lastRow, 1).getValues();
  Logger.log(values);
  var lastBlank = 0;
  for(var i = 0; i < values.length; i++) {
    var value = values[i][0];
    if(value !== '') {
      lastBlank = i + 1;
      break;
    }
  }
  Logger.log(lastBlank);
  return lastBlank;
}
意訳
この機能がやること
今開いているシートを取得する
データが入っている最終行を取得する
開始行を指定する
対象の列番号を指定する
対象の列で開始行から最終行までの値を取得して
ログに出す
最後の空白行は0にしておく
値の数だけ繰り返す
値を1つずつ取得して
もし空でなければ
最後の空白行は配列の要素番号+1 配列は0始まりでシートは1始まりなので+1
for文から抜ける


それをログに出して
そして返す



実行結果

getLastBlankを実行するとこのようなログが出ます
kanaの列で値が入っていない範囲の最終行は4行目

Wednesday, February 20, 2019

Lambda functionの実行ログを見たい(CloudWatch)


API GatewayのendpointにアクセスしてFunctionを実行したあと
そのログを見たい


「Monitoring」をクリックして


「View logs in CloudWatch」をクリックすると


ログが出ている

ログをクリックすると中身を見ることができる

Lambda functionの中で、console.log("出したいログ")と書くとここに出せるようだ


補足

こんなエラーが出てログを見れないことがありました
「There was an error loading Log Streams. Please try again by refreshing this page.」と書かれていて、ページを読み込み直しても解決しない、、


試して解決した方法を以下に書き残しておきます


「Search Log Group」が強調されているのでクリックしてみると
Log group not found
The log group /aws/lambda/myFunction could not be found. Check if it was correctly created and retry.
というエラーが出て、どうやらロググループが見つからないらしい

correctly createdの確認方法はわかりませんが、retryしても同じでした
Log groupがないなら作ればよいのかなと思い
勘を頼りに左のメニューを開いて「Logs」をクリックしてみます

Log Groupに対象のfunctionが見つからない

「Action」の中を見ると「Create log group」があるので選択してみます

とりあえず「myFunctionLog」と名前をつけて「Create log group」をクリックする

一番下にLog Groupが作成されました

このあと再度API Gatewayのendpointにアクセスするとログが出ました


参考

Node.js の AWS Lambda 関数ログ作成
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-logging.html

AWS Lambda の Amazon CloudWatch ログへのアクセス
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/monitoring-functions-logs.html

Lambda logs not visible : "There was an error loading Log Streams"
https://forums.aws.amazon.com/message.jspa?messageID=623472

Slack APIで特定のchannelの履歴を取得したい

追記 2020/08/02

最初に投稿した時は
https://slack.com/api/channels.history
で取得できていましたが、Slack側の仕様変更で

https://slack.com/api/conversations.history
になったようなので修正しました。


Scopesの設定は
channels:history
のままで良いようです。



Slack APIのchannels.historyで履歴の取得を試みたコードを書き残しておきます。
  1. クエリパラメータで渡すコード
    • URLの末尾にパラメータを付け足す
  2. payloadで渡すコード
    • POSTでpayloadに入れる

の2パターンを試してみました。


1. クエリパラメータで渡してみる


コード.gs
function gethistories() {
  var token = "TOKEN";
  var channel_id = "ID";
  var oldest = getUnixTime("2019/02/14 13:26:12");
  var latest = getUnixTime("2019/02/23 22:17:12");
  var url = 'https://slack.com/api/conversations.history' +
            '?token=' + token +
            '&channel=' + channel_id +
            '&latest=' + latest +
            '&oldest=' + oldest +
            '&inclusive=' + true +
            '&count=' + 2;
  var response = UrlFetchApp.fetch(url)
  var jobj = JSON.parse(response)
  Logger.log(jobj);
}

function getUnixTime(dateTime) {
  var date = new Date(dateTime);
  var milsec = date.getTime();
  var sec = milsec / 1000;
  var time = sec.toString();
  return time;
}



2. payloadで渡してみる


コード.gs
function gethistories() {
  var token = "TOKEN";
  var channel_id = "ID"; //channelの名前ではなくID
  var oldest = getUnixTime("2019/02/14 13:26:12");
  var latest = getUnixTime("2019/02/23 22:17:12");
  var url = "https://slack.com/api/conversations.history";
  var payload = {
    "token": token,//Slack AppのToken
    "channel": channel_id,//ChannelのID
    "oldest": oldest,//この日時から
    "latest": latest,//この日時まで
    "inclusive": true,//oldestとlatestを含めるか true, false
    "count": 2//取得する件数 1〜1000
  }

  var options = {
    'method': 'post',
    'payload': payload
  };
  
  var response = UrlFetchApp.fetch(url, options)
  var jobj = JSON.parse(response)
  Logger.log(jobj);
}

function getUnixTime(dateTime) {
  var date = new Date(dateTime);
  var milsec = date.getTime();
  var sec = milsec / 1000;
  var time = sec.toString();
  return time;
}


補足

日時はそのままでは渡せない仕様のため
getUnixTimeという関数を作ってUNIX時間に変換しました。

FYI:UNIX時間
https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93


channel_idはブラウザでその部屋を開いたときにアドレスバーで確認できます。
NAME.slack.com/messages/CHANNEL_ID/


TOKEN取得の方法
Slack AppのTokenを取得したい


Slack App側の設定
Scope Permission Scopesで「channels:history」を選択します



関連記事


参考

conversations.history
https://api.slack.com/methods/conversations.history

channels.history
https://api.slack.com/methods/channels.history

AWS LambdaでLINE BOTを作ってみる


LambdaでLINE Messaging APIを試したコードを書き残しておきます。

コードは以下のindex.jsに書きましたがLINE Developersでの設定も必要です。
試したことがない場合は LINE側の設定方法 で設定できると思います。



ここでは試しに何を入力してもHelloを返すコードを書きました。

返すテキストはindex.jsの"text": "Hello"で設定しています。



アクセストークンは token という key で環境変数に入れて process.env.token で呼び出しています。

もっといろいろ試したい場合
LINE BOTでできることをGoogle Apps Scriptで試した一覧
(Google Apps Scriptで試した一覧です)


Lambda Function

index.js
/************************************
require('https')
************************************/
var https = require('https');

/************************************
exports.handler
************************************/
exports.handler = function(event) {

  /************************************
  LINEに投げ返すデータ
  ************************************/
  var data = {
    "replyToken": JSON.parse(event.body).events[0].replyToken,
    "messages": [{
      "type": "text",
      "text": "Hello"
    }]
  };
  var postData = JSON.stringify(data);
  console.log(postData);

  /************************************
  options
  ************************************/
  var options = {
    "method": 'POST',
    "host": 'api.line.me',
    "path": '/v2/bot/message/reply',
    "headers": {
      "Content-type": "application/json",
      'Content-Length': Buffer.byteLength(postData),
      "Authorization": " Bearer " + process.env.token
    }
  };
  console.log(options);

  /************************************
  https.request
  ************************************/
  var req = https.request(options, function(res) {
    console.log('statusCode: ' + res.statusCode);
    console.log('headers: ' + JSON.stringify(res.headers));
    res.setEncoding('utf8');
    res.on('postData', function(chunk) {
      console.log("chunk");
    });
    req.on('error', function(err) {
      console.log('message: ' + err.message);
    });
  });
  req.write(postData);
  req.end();
};


関連記事


参考

Messaging APIを利用するには

http.request(options[, callback])
http.request(url[, options][, callback])
https://nodejs.org/api/http.html#http_http_request_options_callback

AWS Lambdaで環境変数を入力する場所


環境変数に保存すると process.env.Key で呼び出せるようになります。

たとえばこのように
"Authorization": " Bearer " + process.env.token


環境変数を入力する場所



ページ全体の中で見るとEnvironment variablesはここにあります



AWS Lambda 環境変数

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...