LANG SELRCT

コードを書く場所

2019年11月12日火曜日

GASからLambdaにzendesk_idを渡してzendesk APIのGETで情報取得してシートに書き出したい


GASからLambdaにzendesk_idを渡してzendesk APIのGETで情報取得したい
で取得したデータをスプレッドシートに書き出したくて書いたコードです。



コード.gs
var host = 'SITENAME.zendesk.com';
var endPoint = 'https://API_ID.execute-api.us-east-1.amazonaws.com/default/get_zendesk_ticket';

//これを実行する
function getZendeskInfo() {
  var zendesk_id = 2;// zendesk_idを指定する場合
  var response = run(zendesk_id);
  var objs = JSON.parse(response)["results"];
  objsToSheet(objs);
}

function run(zendesk_id) {
  //var path = '/api/v2/tickets/' + zendesk_id + '.json';// zendesk_idを指定したい場合
  var path = '/api/v2/search.json?page=1&query=created%3E2019-01-01%20type:ticket';// queryで取得したい場合
  var payload = {
  'data':{
    'host': host,
    'path': path,
    'zendesk_id': zendesk_id,
    'zendesk_api_token_base64': getProp('zendesk_api_token_base64')
   }
  }
  var options = {
    'method': 'post',
    'headers': get_headers(),
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  }
  var response = UrlFetchApp.fetch(endPoint, options);
  return response;
}
  
function get_headers() {
  var headers = {
    "x-api-key": getProp('x_api_key')
  }
  return headers;
}

function getProp(key) {
  return PropertiesService.getScriptProperties().getProperty(key);
}

//書き込むシート
function targetSheet() {
  var ss_url = 'https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit#gid=0';
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sheet = ss.getSheets()[0];
  return sheet;
}

//シートに書き込むデータを渡す
function objsToSheet(objs) {
  var sheet = targetSheet();
  var obj = objs[0];
  var keys = Object.keys(obj);
  var arrays = [keys];// keysを一行目に入れておく
  for(var i = 0; i < objs.length; i++) {
    var obj = objs[i];
    var values = [];
    for(var j = 0; j < keys.length; j++) {
      var value = obj[keys[j]];
      values.push(value);
    }
    arrays.push(values);
  }
  setDataToSheet(sheet, arrays)
}

//シートにデータを書き込む
function setDataToSheet(sheet, arrays){
  var last_row = sheet.getLastRow();
  var start_row = last_row + 1;
  var start_col = 1;
  var num_rows = arrays.length;
  var num_cols = arrays[0].length;
  var range = sheet.getRange(start_row, start_col, num_rows, num_cols);
  range.setValues(arrays); 
}

//base64tokenを作成する これをスクリプトのプロパティ
function get_base64() {
  var zendesk_api_token = "ZENDESK_API_TOKEN";
  var base64Token = Utilities.base64Encode("EMAIL" + '/token:' + zendesk_api_token);
  Logger.log(base64Token);
}



Lambda側のコード(関連記事のコードと同じ)

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

var zendesk_id;
var zendesk_api_token_base64;
var host;
var path;
exports.handler = async function(event) {
 console.log(event);
 
 /******
 pyloadで受け取る場合
 ******/
 var body = event['body'];
 var jobj = JSON.parse(body);
 host = jobj['data']['host']
 path = jobj['data']['path']
 zendesk_id = jobj['data']['zendesk_id']
 zendesk_api_token_base64 = jobj['data']['zendesk_api_token_base64']
 console.log([zendesk_id]);
 
 var results = await getData(zendesk_id); //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": 'GET',
    "host": host,
    "path": path,
    "headers": {
     "Content-type": "application/json",
     "Authorization": " Basic " + zendesk_api_token_base64// Lambda側に設定して読む場合はprocess.env. zendesk_api_token_base64
    }
 };
 return options;
}

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



関連記事

GASからLambdaにzendesk_idを渡してzendesk APIのGETで情報取得したい

2019年11月10日日曜日

UserAgentを取得してブラウザや端末情報を取得してみる


window.navigator.userAgent
でユーザーエージェントが取得できる


ユーザーエージェントは例えば以下のような文字列で取得できる

Macで開いた場合
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36

Android(Pixel 3a)で開いた場合
Mozilla/5.0 (Linux; Android 10; Pixel 3a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36
OK

開くブラウザや端末によって内容は異なり、OS、端末の種類、ブラウザ、Mobileかどうかなどの情報が書かれている。



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




index.html
<!DOCTYPE html>
<html>
  <body>
    <script>
      getUserAgent();
      function getUserAgent() {
        var userAgent = window.navigator.userAgent;
        alert(userAgent);
      }
    </script>
  </body>
</html>


Android(Pixel3a)でUSBデバッグをしたい(DevToolsでconsoleを見たい)


Webアプリ開発中に、スマホでもconsoleを見たくて


事前準備

Android端末(Pixel3a)で開発者オプションを表示したい
で開発者オプションを表示できるようにしておきます



開発者オプションを開いて「USBデバッグ」をタップしてオンにします


許可を求められるので「OK」をタップします


PCのChromeのアドレスバーに

chrome://inspect/#devices

を入力して開きます


するとこのような画面が開くので
検証したいページの「inspect」をクリックします


DevToolsが表示されて
左側には端末側の画面が表示されます
右側には検証ツールが表示されます



関連記事

Android端末(Pixel3a)で開発者オプションを表示したい


参考

デバイスの開発者向けオプションを設定する
https://developer.android.com/studio/debug/dev-options?hl=ja

Google Chrome inspect devices?
https://stackoverflow.com/questions/45366421/google-chrome-inspect-devices

Android の Chrome で開発者ツールを使う方法
https://qiita.com/hojishi/items/12b726f8b02ef3d713e4

Android端末(Pixel3a)で開発者オプションを表示したい



USBデバッグをしたくて

「設定」アイコンをタップします


「デバイス情報」をタップします


「ビルド番号」を7回タップします


前の画面に戻って「システム」をタップします


「詳細設定」をタップします



「開発者向けオプション」が表示されます



参考

デバイスの開発者向けオプションを設定する
https://developer.android.com/studio/debug/dev-options?hl=ja

2019年11月9日土曜日

gitでbranchを作ってaddしてcommitしてGitHubにpushしたい


普段はSourceTreeを使っているので、gitのコマンドは忘れがち。

ここでやること

  1. 作業用ブランチを作って
  2. リモートにプッシュして
  3. マージして
  4. masterをpullして
  5. 作業用ブランチを削除する




コマンド
ローカルリポジトリのフォルダ $ git branch add-list
ローカルリポジトリのフォルダ $ git checkout add-list
Switched to branch 'add-list'
ローカルリポジトリのフォルダ $ git branch
* add-list
  master


ローカルリポジトリのフォルダ $ git diff
ローカルリポジトリのフォルダ $ git status
ローカルリポジトリのフォルダ $ git add .
ローカルリポジトリのフォルダ $ git commit -m "メッセージ"
ローカルリポジトリのフォルダ $ git push origin add-list



ローカルリポジトリのフォルダ $ git checkout master
ローカルリポジトリのフォルダ $ git pull origin master
ローカルリポジトリのフォルダ $ git branch --delete add-list

意訳
add-listというブランチを作る
add-listというブランチに切り替える $ git checkout -b add-list で作成と切り替えができる
切り替わった
ブランチを確認する
現在のブランチはadd-list

ローカルのファイルやフォルダに変更を加える

変更点を確認する
現在の状態を確認する
変更を追加する
メッセージを付けてコミットする
リモートのadd-listにプッシュする

リモートでプルリクを作ってマージする

masterブランチに切り替える
リモートのmasterブランチをローカルに反映させる
add-listブランチを削除する



参考

https://backlog.com/ja/git-tutorial/stepup/01/

2019年11月7日木曜日

GASのsetNumberFormatで表示形式の数値を自動にしたい場合は.setNumberFormat("General")


公式リファレンスに書かれている場所を見つけられず、Stack Overflow で見つけました。



コード.gs
function set_format() {
 var format = SpreadsheetApp.getActiveSheet().getRange("A2").setNumberFormat("General");
 Logger.log(format);
}


参考

https://stackoverflow.com/questions/38042702/applying-automatic-number-formatting

2019年11月6日水曜日

Cloud Firestoreのルールを安全にしたい


Gmailの件名:
[Firebase] Cloud Firestore データベースに安全でないルールがあります

というメールが来ました。

メールの「詳細」をクリックすると英語版のページが開いて対処方法が書かれていました。



英語版
Fix insecure rules
https://firebase.google.com/docs/firestore/security/insecure-rules

日本語版
安全でないルールを修正する
https://firebase.google.com/docs/firestore/security/insecure-rules?hl=ja



僕は「詳細」ボタンを押さずにGmailの件名でググってしまいましたが、一番上に「安全でないルールを修正する」が出てきたので、たぶんこのブログに辿り着く人はいないと思いますが、備忘録として。

2019年11月4日月曜日

Google Visualization APIでシートのデータを取得する(ヘッダをキーにしたオブジェクトを作る)


Google Visualization APIでシートのデータを取得する
ではシートのデータを配列にして取得しましたが
今回はオブジェクトにして取得するコードを書きました。



例としてこのようなシートを用意して


そのシートのデータを
このようなオブジェクトにして取得したい。

[{
  date = 2019 / 11 / 01,
  task = task1,
  label = label1
}, {
  date = 2019 / 11 / 01,
  task = task2,
  label = label2
}, {
  date = 2019 / 11 / 01,
  task = task3,
  label = label1
}, {
  date = 2019 / 11 / 02,
  task = task4,
  label = label3
}, {
  date = 2019 / 11 / 02,
  task = task5,
  label = label2
}, {
  date = 2019 / 11 / 02,
  task = task6,
  label = label1
}]



デモデータ

datetasklabel
2019/11/01task1label1
2019/11/01task2label2
2019/11/01task3label1
2019/11/02task4label3
2019/11/02task5label2
2019/11/02task6label1



コード.gs
var SS_ID = "スプレッドシートのID";
var SH_ID = 0;// シートのID
var BASE_URL = "https://docs.google.com/spreadsheets/d/";
var URL = BASE_URL + SS_ID + "/gviz/tq?gid=" + SH_ID + "&tqx=out:json&tq=";

SpreadsheetApp.openById(SS_ID);

function get_public_data() {
  var options = null;
  data_via_gviz(options);
}

function get_private_data() {
  var access_token = get_access_token();
  var headers = get_headers(access_token);
  var options = get_options(headers);
  data_via_gviz(options);
}

function get_access_token() {
  var access_token = ScriptApp.getOAuthToken();
  return access_token;
}

function get_headers(access_token) {
  var headers = {
    "Authorization": "Bearer " + access_token
  }
  return headers;
}

function get_options(headers) {
  var options = {
    "contentType": "application/json",
    "headers": headers,
    "muteHttpExceptions": true
  }
  return options;
}

function data_via_gviz(options) {
  var query = encodeURIComponent("SELECT A, B, C");
  var response = UrlFetchApp.fetch(URL + query, options);
  var jobj = get_jobj(response);
  var array = get_array(jobj);
  Logger.log(array);
}

function get_jobj(response) {
  var data = response.getContentText();
  data = data.split("google.visualization.Query.setResponse(")[1];
  data = data.substr(0, data.length - 2);
  var jobj = JSON.parse(data);
  return jobj;
}

function get_array(jobj) {
  var col_labels = get_col_labels(jobj);
  var row = jobj["table"]["rows"];
  var array = [];
  for (var i = 0; i < row.length; i++) {
    var obj = {};
    for (var j = 0; j < col_labels.length; j++) {
      var value = '';
      var icj = jobj["table"]["rows"][i]["c"][j];
      if(icj === null) {// nullならば
        value = '';
      } else {
        value =icj["v"];
        var formatted = icj["f"];
        if(formatted) {// formattedが存在すれば
          value = formatted;// valueに入れる
        }
        obj[col_labels[j]] = value;
      }
    }
      array.push(obj);
  }
  return array;
}

function get_col_labels(jobj) {
  var cols = jobj["table"]["cols"];
  var col_labels = [];
  for(var i = 0; i < cols.length; i++) {
    var label = cols[i]['label'];
    col_labels.push(label)
  }
  return col_labels
}



関連記事

Google Visualization APIでシートのデータを取得する
Google Visualization APIの vはvalueで、fはformettedValueっぽい

オブジェクトの中から条件に一致する要素を抜き出す(filter, 複数プロパティを対象)


オブジェクトの中から条件に一致する要素を抜き出す(filter)
ではオブジェクトの中の一つのプロパティを見て一致するものを取得しました。

今回は2つのプロパティを見て一致するものを取得してみます。

objのようなオブジェクトを用意して
enが「Libra」で
jaが「てんびん座」のidを取得してログに出してみます。



コード.gs
var obj = [
  {"id": 1, "en": "Aries", "ja": "おひつじ座"},
  {"id": 2, "en": "Taurus", "ja": "おうし座"},
  {"id": 3, "en": "Gemini", "ja": "ふたご座"},
  {"id": 4, "en": "Cancer", "ja": "かに座"},
  {"id": 5, "en": "Leo", "ja": "しし座"},
  {"id": 6, "en": "Virgo", "ja": "おとめ座"},
  {"id": 7, "en": "Libra", "ja": "てんびん座"},
  {"id": 8, "en": "Scorpius", "ja": "さそり座"},
  {"id": 9, "en": "Sagittarius", "ja": "いて座"},
  {"id": 10, "en": "Capriconus", "ja": "やぎ座"},
  {"id": 11, "en": "Aquarius", "ja": "みずがめ座"},
  {"id": 12, "en": "Pisces", "ja": "うお座"},
  {"id": 13, "en": "Aries", "ja": "牡羊座"},
  {"id": 14, "en": "Taurus", "ja": "牡牛座"},
  {"id": 15, "en": "Gemini", "ja": "双子座"},
  {"id": 16, "en": "Cancer", "ja": "蟹座"},
  {"id": 17, "en": "Leo", "ja": "獅子座"},
  {"id": 18, "en": "Virgo", "ja": "乙女座"},
  {"id": 19, "en": "Libra", "ja": "天秤座"},
  {"id": 20, "en": "Scorpius", "ja": "蠍座"},
  {"id": 21, "en": "Sagittarius", "ja": "射手座"},
  {"id": 22, "en": "Capriconus", "ja": "山羊座"},
  {"id": 23, "en": "Aquarius", "ja": "水瓶座"},
  {"id": 24, "en": "Pisces", "ja": "魚座"},
]

function filter_obj() {
  var filtered = obj.filter(judge);
  var result = get_result(filtered);
  Logger.log(result);
}

function judge(items) {
  if(items["en"] === "Libra" && items["ja"] === "てんびん座"){
    return true;
  }else{
    return false;
  }
}

function get_result(filtered) {
  var result = [];
  for (var i = 0; i < filtered.length; i++) {
    result.push(filtered[i]["id"]);
  }
  return result;
}



実行結果

filter_obj() を実行すると以下のようなログが出ます。

en が「Libra」で ja が「てんびん座」の id は 「7」



関連記事

オブジェクトの中から条件に一致する要素を抜き出す(filter)

2019年11月3日日曜日

SlackでRSSのフィードをキャッチしたい


公式リファレンスにありました
Slack に RSS フィードを追加する
https://slack.com/intl/ja-jp/help/articles/218688467-add-rss-feeds-to-slack


手元でやった手順

Slackにログインして
https://slack.com/apps/A0F81R7U7-rss
にアクセスして


Add to Slackをクリックします


Add RSS integration をクリックします


ここでFeed URLと通知するChannelを設定できました



参考

Slack に RSS フィードを追加する
https://slack.com/intl/ja-jp/help/articles/218688467-add-rss-feeds-to-slack

RSS wikipedia
https://ja.wikipedia.org/wiki/RSS

2019年11月2日土曜日

Google Visualization APIの vはvalueで、fはformettedValueっぽい


公式のリファレンスを見るとこう書かれていました。

  • v [Optional] The cell value. 
  • f [Optional] A string version of the v value, formatted for display. 


vはセルの値
fはそれをフォーマットした値
ということらしい。


参考

Cell Objects
https://developers.google.com/chart/interactive/docs/reference#cell_object

2019年10月31日木曜日

ドライブのゴミ箱内のファイルを完全削除したい(Drve API)


Googleドライブの指定したフォルダ内のファイルを完全に削除して復帰できなくしたい
では指定したフォルダ内のファイルを完全に削除するコードを書きました。

ここではゴミ箱に捨てたファイルを完全に削除するコードを書きます。

deleteTrashedFiles()を実行すると復帰できないので
Drive.Files.remove(id)
はコメントアウトしています。

Drive.Files.remove(id) を実行する場合は挙動を十分理解した上で使いましょう。

もう一度書きますが、この方法で削除したファイルは二度と復帰することができません。



コード.gs
function deleteTrashedFiles() {
  var contents = DriveApp.getTrashedFiles();
  var i = 0;
  while(contents.hasNext()) {
    var file = contents.next();
    var id = file.getId();
    i++;
    Logger.log(file.getName());// 削除されるファイル
    //Drive.Files.remove(id);// この行のコメントを外して実行すると削除されます
    Utilities.sleep(1000);
  }
}


補足

手元のドライブでやった時はゴミ箱にあるファイル数が多くて途中で何度もエラーになりました。

Utilities.sleep(1000);
を入れることで一度により多くのファイルを削除できるようになりましたが、それでもエラーが出て、何度か deleteTrashedFiles() を実行してゴミ箱のファイルを完全削除できました。


関連記事

Googleドライブのファイルを完全に削除したい(Drive API)
Googleドライブの指定したフォルダ内のファイルを完全に削除して復帰できなくしたい


参考

getTrashedFiles()
https://developers.google.com/apps-script/reference/drive/drive-app#getTrashedFiles()

Googleドライブの指定したフォルダ内のファイルを完全に削除して復帰できなくしたい


Googleドライブのファイルを完全に削除したい(Drive API)
ではファイル名を特定して完全削除しました

ここではファイル名を指定せずにフォルダ内のすべてのファイルを完全削除するコードを書きました。

実行したら二度と復帰できません。
なのでここではremoveのコードはコメントアウトしています。
Drive.Files.removeを利用する場合は、その結果どうなるのか十分理解した上で利用しましょう。



コード.gs
function deleteFilesInFolder() {
  var FOLDER_ID = "FOLDER_ID";
  var folder = DriveApp.getFolderById(FOLDER_ID);
  var contents = folder.getFiles();
  var ids = [];
  while(contents.hasNext()) {
    var file = contents.next();
    var id = file.getId();
    Logger.log(file.getName());// 削除されるファイル
    //Drive.Files.remove(id);// この行のコメントを外して実行すると削除されます
  }
}


関連記事

Googleドライブのファイルを完全に削除したい(Drive API)



2019年10月28日月曜日

TextFinderで正規表現を使って検索したい


TextFinderで正規表現を使いたくて書きました。

AND検索をしたかったので

正規表現でAND検索を試してみる2 

でやった正規表現を使います。


各々設定する箇所
  • SPREADSHEET_URL
  • 文字列1
  • 文字列2

を設定してfindAllRows()を実行すると
正規表現に一致する行が取得できます。


コード.gs
function findAllRows(){
  var rows = [];
  var ss_url = 'SPREADSHEET_URL';
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sheet = ss.getSheets()[0];

  var textFinder = sheet.createTextFinder("^(?=.*文字列1)(?=.*文字列2)").useRegularExpression(true);
  var ranges = textFinder.findAll();
  for ( var i = 0; i < ranges.length; i++ ) {
      rows.push(ranges[i].getRow()) ;
  }
  Logger.log(rows);
}




補足1

複数のセルをまたいで抽出するには別途ループ処理などの対応が必要です。

たとえば

"^(?=.*あめ)(?=.*がむ)"

とした時

A1セルに「あめ、がむ」←同じセル内なのでHITする
A2セルに「あめ」B2セルに「がむ」←セルが異なるのでHITしない


補足2

セルに改行が入っている場合はこれでいけた

"^(?=.*文字列1[\\s\\S])(?=.*文字列2[\\s\\S])"


こちらを参考にさせていただきました。
https://teratail.com/questions/197111


参考

useRegularExpression(useRegEx)
https://developers.google.com/apps-script/reference/spreadsheet/text-finder.html#useRegularExpression(Boolean)

https://teratail.com/questions/197111

2019年10月26日土曜日

iPhoneでiCloud写真への自動保存をオフにしたい(充電50%以上でオフにできた)


「iCloud > 写真」 の設定で

オリジナルをダウンロードにチェックを入れて「iCloud写真」をオフにしようとしたら
160項目のフル解像度の写真とビデオをダウンロードできませんでした。続行すると、これらの写真とビデオの低解像度のバージョンは削除されます。
というメッセージが出た。

ググってみると他にも困っている人がいる情報はあるが、解決策がよくわからない、、


手元の環境で確認した解決策

「iPhoneを充電して50%以上にする」

これで上記メッセージが表示されなくなり、「iCloud写真」をオフにできました。


解決策までの道

iCloudの容量の5GBがいっぱいになった。
容量を増やすにはお金がかるらしい。
そこにお金をかけたくない。
写真はこれからも撮り続けたい。
iPhoneに保存できればいい。
iCloudには保存しなくてもいい。

そこで
iPhoneでiCloud写真への自動保存をオフにしたい。
iCloudにオリジナル解像度の写真があったらiPhone側に移動しておきたい。
(そうしないとiPhone側には低解像度の写真しか残らなくなるらしい)

ググってみると
「iCloud > 写真」 の設定でできそう


「オリジナルをダウンロード」を選択して「iCloud写真」をオフにしようとしたら
160項目のフル解像度の写真とビデオをダウンロードできませんでした。続行すると、これらの写真とビデオの低解像度のバージョンは削除されます。
というメッセージが出た。

iPhone端末には40GB以上の空き容量がある。
iCloud側の容量は5GBなのでiPhone側の容量不足でダウンロードできないことはない。
ダウンロードできない160項目の内容はわからない。
このまま削除を進めると何が消えるかわからない。
こわくて消せない。

もうちょっとググってみる。

知恵袋にこのような回答がされている
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12197326957

電源にさすと行けるかも。
電池の残りを見ると40%。
しばらく充電してみる。

充電したまま50%を越えてから再度試してみる。

「オリジナルをダウンロード」を選択して「iCloud写真」をオフにできた。


2019年10月24日木曜日

GASからLambdaにzendesk_idを渡してzendesk APIのGETで情報取得したい


zendeskにIP制限がかかっているとGASからは zendesk APIにアクセスできない。
Lambdaを利用するとIP固定ができるので
GAS→Lambda→zendesk API という順番でやってみたときの備忘録です。


GAS側のコード

hostとendPointは各々の設定
  • hostは利用しているzendeskのURL
  • endPointはAPI GatewayのURL
  • zendesk_idは取得したいチケットのid

getZendeskInfo()を実行すると
  1. API Gatewayを通ってLambdaにhost, path, zendesk_id, zendesk_api_token_base64が渡される
  2. Lambda側のコードでzendeskのAPIのGETが実行される
  3. 指定したzendesk_idの情報が返ってくる
  4. GAS側のログに出力される


コード.gs
var host = 'SITENAME.zendesk.com';
var endPoint = 'https://API_ID.execute-api.us-east-1.amazonaws.com/default/FUNCTION_NAME';

function getZendeskInfo() {
  var zendesk_id = 2;
  var response = run(zendesk_id);
  Logger.log(response);
}

function run(zendesk_id) {
  var path = '/api/v2/tickets/' + zendesk_id + '.json';// zendesk_idを指定して取得したい場合
  //var path = '/api/v2/search.json?page=1&query=created%3E2019-01-01%20type:ticket'; // queryで取得したい場合
  var payload = {
  'data':{
    'host': host,
    'path': path,
    'zendesk_id': zendesk_id,
    'zendesk_api_token_base64': getProp('zendesk_api_token_base64')
   }
  }
  var options = {
    'method': 'post',
    'headers': get_headers(),
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  }
  Logger.log(options)
  var response = UrlFetchApp.fetch(endPoint, options);
  console.log(response);
  return response;
}
  
function get_headers() {
  var headers = {
    "x-api-key": getProp('x_api_key')
  }
  return headers;
}

function getProp(key) {
  return PropertiesService.getScriptProperties().getProperty(key);
}



Lambda側のコード

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

var zendesk_id;
var zendesk_api_token_base64;
var host;
var path;
exports.handler = async function(event) {
 console.log(event);
 
 /******
 pyloadで受け取る場合
 ******/
 var body = event['body'];
 var jobj = JSON.parse(body);
 host = jobj['data']['host']
 path = jobj['data']['path']
 zendesk_id = jobj['data']['zendesk_id']
 zendesk_api_token_base64 = jobj['data']['zendesk_api_token_base64']
 console.log([zendesk_id]);
 
 var results = await getData(zendesk_id); //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": 'GET',
    "host": host,
    "path": path,
    "headers": {
     "Content-type": "application/json",
     "Authorization": " Basic " + zendesk_api_token_base64// Lambda側に設定して読む場合はprocess.env. zendesk_api_token_base64
    }
 };
 return options;
}

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




スプレッドシートのA列を編集したときに入力規則を設定したい


やりたいこと

  1. スクリプト内にデータの入力規則のリストを設定しておく
  2. A列に値を入れると、B列にデータの入力規則が追加される
  3. 列の値が空になると、B列の入力規則がクリアされる(選択肢が消える)


このように



コード.gs
var sheet = SpreadsheetApp.getActiveSheet();
var editcol = 1;// この列が編集されたら
var listcol = 2;// この列のデータの入力規則を設定する(editcolが空ならクリアする)

var dataList = [
  'TODO',
  'INPROGRESS',
  'DONE'
  ]

function onEdit() {
  var active = sheet.getActiveRange();
  var row = active.getRow();
  var range = sheet.getRange(row, listcol);
  if(active.getColumn() === editcol) {
    if(active.getValue() === '') {
      range.clearDataValidations();// データの入力規則をクリアする
    } else {
      addDataValidation(range);
    }
  }
}

//データの入力規則を設定する
function addDataValidation(range) {
  range.setDataValidation(SpreadsheetApp.newDataValidation()
  .setAllowInvalid(true)
  .requireValueInList(dataList, true)
  .build());
}



参考

Class DataValidationBuilder
https://developers.google.com/apps-script/reference/spreadsheet/data-validation-builder

GASで作ったWebアプリのアクセス権の変更はオーナーだけができる


タイトルそのままですが

スクリプトのオーナー以外が実行権限を変更しようとしても変更できない


disabledを消して無理やり選択可能にしても


おこられる。



2019年10月22日火曜日

GASで実行するユーザを個別に絞りたい


emailsで設定したユーザーだけが実行できるようにしたい


HtmlServiceのスクリプトで書く場合

ウェブアプリの導入の設定が
User accessing the web app(このアプリケーションにアクセスしているユーザー)
全ユーザーがアクセスできる状態で
var emails で設定した
  'foo@gmail.com',
  'bar@gmail.com'
だけが実行できるようにしたい。


コード.gs
var emails = [
  'foo@gmail.com',
  'bar@gmail.com'
  ];

var email = Session.getActiveUser().getEmail();
var checkEmail = emails.some(judge);

function judge(value) {
  return value === email;
}


function doGet(e) {
  if(checkEmail === true) {
    var html = '<label>hello</label>';
  } else {
    var html = '<div>このアプリを開く権限がありません。</div>';
  }
  return HtmlService.createHtmlOutput(html);
}








スプレッドシート内のスクリプトで書く場合
(ちょっと書き方を変えた)

シートを複数人で共有している状態で
  'foo@gmail.com',
  'bar@gmail.com'
だけがスクリプトを実行できるようにしたい。


コード.gs
var email = Session.getActiveUser().getEmail();

function myFunction() {
  var checkEmail = judgeEmail(email);
  var message;
  if(checkEmail === true) {
    message = 'hello';
  } else {
    message = 'このアプリを実行する権限がありません。';
  }
  Browser.msgBox(message)
}

var emails = [
  'foo@gmail.com',
  'bar@gmail.com'
  ];

function judgeEmail() {
  return emails.some(function(value) {
    return value === email;
  });
}




2019年10月21日月曜日

JavaScriptでeval()を使わずに文字列をコードとして使いたい


Googleドライブにtext/plainで保存されたコードを読み込んで動かしたい。

こういうテキストファイルがあって、JavaScriptの中でオブジェクトにして使いたい。



eval()の引数に渡せばできますが、参考サイトに「必要以上に eval を使用しないで!」と書かれているのでeval()を使わない方法で試してみます。

以下のコード2.gsで new Function() を使って引数に文字列を入れる方法でやってみました。

その前にテスト用のデータを用意しておくコードもコード1.gsで書いておきました。


フォルダIDを設定して create_file() を実行すると
作成されたファイルのURLがログに出る

コード1.gs
var FOLDER_ID = "フォルダID";

function create_file() {
  var data = '[{id: 123, message: "hello"}]'
  var content_type = "text/plain";
  var file_name = "test";
  var blob = Utilities.newBlob("", content_type, file_name);
  var file = blob.setDataFromString(data, "UTF-8");
  var folder = DriveApp.getFolderById(FOLDER_ID);
  var result = folder.createFile(file);
  Logger.log(result.getUrl())
}


コード1.gsのcreate_file()実行後のログに出たURLからIDを取得して
コード2.gsのファイルIDに設定し
myFunction() を実行する

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

function myFunction() {
  var data = getData();
  var obj = altEval(data);
  var message = obj()["events"][0]["message"];
  Logger.log(message)
}

function getData() {
  var file = DriveApp.getFileById(FILE_ID);
  var blob = file.getBlob()
  var data = blob.getDataAsString();
  return data;
}

function altEval(data) {
  var obj = '{ events: ' + data + '}';
  var str = 'return ' + obj;
  var func = new Function(str);
  return func;
}


実行結果



補足

ちなみに非推奨のeval()でやると

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

function myFunction() {
  var data = getData();
  var obj = eval(data);
  var message = obj[0]["message"];
  Logger.log(message);
}

function getData() {
  var file = DriveApp.getFileById(FILE_ID);
  var blob = file.getBlob()
  var data = blob.getDataAsString();
  return data;
}

eval()を使う癖がつくと良くないので使わない方がよさそう。


2019年10月19日土曜日

スプレッドシートにチェックボックスを表示したい range.insertCheckboxes()


A列のデータに対して、B列にチェックボックスを追加したい。

チェックを入れたり外したり、チェックボックスを外したり、チェックが付いているか判定したりもできるらしい。



コード.gs
function addCheckboxes() {
  targetRange().insertCheckboxes();
}

function checked() {
  targetRange().check();
}

function unchecked() {
  targetRange().uncheck();
}

function remove() {
  targetRange().removeCheckboxes();
}

function judge() {
  var result = targetRange().isChecked();
  Logger.log(result);
}

function targetRange() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var range = sheet.getRange('B2:B' + lastRow);
  return range;
}

意訳
この機能がやること
指定した範囲にチェックボックスを挿入する


この機能がやること
指定した範囲のチェックボックスにチェックを入れる


この機能がやること
指定した範囲のチェックボックスのチェックを外す


この機能がやること
指定した範囲のチェックボックスを削除する


この機能がやること
指定した範囲のチェックボックスがチェックされているか取得して
ログに出す (すべてチェックされていたらtrueでそれ以外はfalse)


この機能がやること
開いているシートを取得して
データが入っている最終行を取得して
B2からその最終行までの範囲を指定して
その範囲を返す




補足

スクリプトを使わない方法。

メニューから、挿入 > チェックボックスで追加できる。



参考

insertCheckboxes()
https://developers.google.com/apps-script/reference/spreadsheet/range#insertCheckboxes()

check()
https://developers.google.com/apps-script/reference/spreadsheet/range#check()

removeCheckboxes()
https://developers.google.com/apps-script/reference/spreadsheet/range#removeCheckboxes()

isChecked()
https://developers.google.com/apps-script/reference/spreadsheet/range#isChecked()

isChecked()
https://developers.google.com/apps-script/reference/spreadsheet/range#ischecked

チェックボックスを追加して使用する
https://support.google.com/docs/answer/7684717?co=GENIE.Platform%3DDesktop&hl=ja


JIRAのリンクされた課題をAPIで追加したい


以下のように「リンクされた課題」をAPIで設定したくて書いたコードです。





Blocksにする例

コード.gs
var ENDPOINT = "https://SITENAME.atlassian.net/rest/api/3/issueLink";

function doPost() {
  var payload = getPayload();
  var options = getOptions(payload);
  var response = UrlFetchApp.fetch(ENDPOINT, options);
  Logger.log(response.getResponseCode());
}

function getPayload(values) {
  var data = {
    "type": {
      "name": "Blocks"
    },
    "inwardIssue": {
      "key": "KT-9"
    },
    "outwardIssue": {
      "key": "KT-8"
    }
  }
  var payload = JSON.stringify(data);
  return payload;
}

function getOptions(payload) {
  var options = {
    method: "post",
    payload: payload,
    contentType: "application/json",
    headers: {"Authorization": " Basic " + getToken()}
  }
  return options;
}

function getToken() {
  return PropertiesService.getScriptProperties().getProperty("token");
}

意訳
実行するAPIのENDPOINTを指定する

この機能がやること
payloadを取得する
optionsを取得する
ENDPOINTにoptionsを渡して結果を取得する
getResponseCodeをログに出す


この機能がやること
dataオブジェクトを作って
typeは
Blocks

inwardIssueは
KT-9(KT-9のリンクされた課題にblocksでKT-8を追加する)

outwardIssueは
KT-8(KT-8のリンクされた課題にis blocked byでKT-9を追加する)


dataをJSONにして
返す


この機能がやること
optionsオブジェクトを作って
methodはpostにして
payloadには引数で渡されたpayloadを指定して
contentTypeはapplication/jsonにして
headersにはスクリプトのプロパティに入れたtokenをgetToken()で取得して

optionsを返す


この昨日がやること
スクリプトのプロパティにあるのtokenを取得して返す




JIRAで課題のリンクを追加したい


この画面から追加できました。

この画面への道も以下に書いておきます。


参考にした公式ページではこうなっていました

自分の環境で実際にやったキャプチャ
Jira設定 > 課題 > (課題機能配下の)課題のリンク





参考

課題リンクの設定
https://ja.confluence.atlassian.com/adminjiracloud/configuring-issue-linking-818578890.html

2019年10月18日金曜日

Googleドライブ内の特定ファイルをコピーして自動で開きたい


ファイルをコピーして指定フォルダに入れる
というのをワンクリックでやりたくて書きました。



コード.gsの FILE_ID と FOLDER_ID を指定して

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


発行されるWebアプリのURLを開くと

指定したファイルのコピーが指定したフォルダに作成されてブラウザに表示されます。



コード.gs
function doGet() {
  var link = copy_file_to_foder();
  return HtmlService.createHtmlOutput(
    '<a id="alink" href="' + link + '"></a>' + 
    '<script>document.getElementById("alink").click();</script>'
    );
}

var FILE_ID = "コピー元のファイルID";
var FOLDER_ID = "コピーしたファイルを入れたいフォルダID";
function copy_file_to_foder() {
  var file = DriveApp.getFileById(FILE_ID);
  var file_name = file.getName();
  var copy_file_name = "Copy of " + file_name;
  var folder = DriveApp.getFolderById(FOLDER_ID);
  file.makeCopy(copy_file_name, folder);
  var url = file.getUrl();
  Logger.log(url);
  return url;
}


関連記事

スプレッドシートをマイドライブにコピーする
ファイルをコピーして指定フォルダに入れる
スクリプトファイルをコピーして指定したフォルダに入れる
HTMLServiceのテンプレートファイルをワンクリックで作成したい


2019年10月16日水曜日

スプレッドシートのFILTER関数を使ってみる


試行1
「A列を対象にして、A列が空白以外」という条件でフィルタをかけてみる
=filter(A:A,A:A<>"")
=filter(A列を対象にして,A列が空白以外)


試行2
「A列を対象にして、A列がF1に一致する」という条件でフィルタをかけてみる
=filter(A:A,(A:A = F1))
=filter(A列を対象にして,(A列がF1と一致する))


試行3
「C列を対象にして、A列がF1に一致する」という条件でフィルタをかけてみる
=filter(C2:C,(A2:A = F1))
=filter(C列を対象にして,(A列がF1に一致する))

A列が2019/01/04に一致するC列の値は、2019/01/06



参考

FILTER
https://support.google.com/docs/answer/3093197?hl=ja

2019年10月13日日曜日

複数の文章を文章ごとに分けたい(正規表現)


複数の文章を各文章ごとに分けたくて書いた正規表現

var pattern = /。\n+|。|\.\n+|\. |\n+/g;


こういう複数の文章を
var value = '句点と改行で分割する。\n句点で分割する。Split by period. Split by period with newline.\n複数改行でも分割する。\n\n最後の文';

文章単位で配列に入れたい
 [句点と改行で分割する, 句点で分割する, Split by period, Split by period with newline, 複数改行でも分割する, 最後の文]



コード.gs
function myFunction() {
  var value = '句点と改行で分割する。\n句点で分割する。Split by period. Split by period with newline.\n複数改行でも分割する。\n\n最後の文';
  var pattern = /。\n+|。|\.\n+|\. |\n+/g;
  
  var sentences = value.split(pattern);
  Logger.log(sentences);
  
  for(var i = 0; i < sentences.length; i++) {
    var sentence = sentences[i];
    Logger.log(sentence);
  }
}

意訳
この機能がやること
複数の文章
句点と改行 or 句点 or ピリオドと改行 or ピリオドと半角スペース or 改行
  
valueをpatternで分割する
ログに出す
  
文章の数だけ繰り返す
文章ごとに
ログに出す





実行結果

こういうログが出ます。

[19-10-12 19:15:05:900 PDT] [句点と改行で分割する, 句点で分割する, Split by period, Split by period with newline, 複数改行でも分割する, 最後の文]
[19-10-12 19:15:05:901 PDT] 句点と改行で分割する
[19-10-12 19:15:05:901 PDT] 句点で分割する
[19-10-12 19:15:05:902 PDT] Split by period
[19-10-12 19:15:05:902 PDT] Split by period with newline
[19-10-12 19:15:05:903 PDT] 複数改行でも分割する
[19-10-12 19:15:05:903 PDT] 最後の文



2019年10月4日金曜日

Chromeを起動したときに指定したページを開きたい


いつも確認したいページがあるとき、Chromeを起動したらそのページが開く
ということをしたくて調べました。


公式ページに以下の記載がありました。

ホームページと起動ページを設定する
https://support.google.com/chrome/answer/95314?hl=ja


公式のキャプチャはなかったので自分の環境でやった手順をやりながら書き残しました。


文字だけで書くとこの順番でできる
設定 > 起動時 > 特定のページまたはページセットを開く > 新しいページを追加 > 保存


キャプチャした手順

右上の︙から「設定」を選択します


「起動時」で検索して「特定のページまたはページセットを開く」を選択します


「新しいページを追加」を選択して開きたいサイトのURLを入れて「追加」

これで次回Chromeを起動したときには設定したページが開くようになりました。


参考

ホームページと起動ページを設定する
https://support.google.com/chrome/answer/95314?hl=ja

2019年9月23日月曜日

Googleカレンダーに予定を入れるプログラムを作りたい


createEvent(title, startTime, endTime, options)
でできるようです。


guestsが複数の場合は、配列ではなく、文字列でカンマを区切るようです。
a comma-separated list of email addresses that should be added as guests



コード.gs
function addCalendar() {
  var event = CalendarApp.getDefaultCalendar();
  var title = 'タイトル';
  var startTime = new Date('2019/09/23 12:00');
  var endTime = new Date('2019/09/23 13:00');
  var option = {
    description: '説明',
    location: '成田空港',
    guests: 'foo@gmail.com,bar@gmail.com',
    sendInvites: false
  }
  event.createEvent(title, startTime, endTime, option)
  Logger.log('Event ID: ' + event.getId());
}

意訳
この機能がやること
デフォルトのカレンダーを取得する
タイトル
開始日時
終了日時

説明
場所
参加者のメールアドレス(複数の場合はカンマ区切りの文字列)
ゲストに送信するか否か(true / false)

カレンダーに予定を入れる
Event IDをログに出す





2019年9月20日金曜日

G suiteの管理者コンソールでアドオンを削除したい


G suiteの管理者機能で、スプレッドシートのadd onをドメイン内で有効にした際、それを無効にする方法が知りたくなって調べてみると
G Suite から Marketplace アプリを削除する
という公式ヘルプがありました。


Marketplace アプリの管理
https://admin.google.com/AdminHome?hl=ja#AppsList:serviceType=MARKETPLACE

ここでオンオフの切り替えや削除ができるようだ。


参考

G Suite から Marketplace アプリを削除する

2019年9月18日水曜日

IFTTTで特定のエリアに入ったらLINEに通知したい


IFTTTを利用して、地図上であらかじめ指定したエリアに入ったらLINEに通知する

ということをやっていきます。


必要なもの
  • Googleアカウント
  • LINEアカウント
  • 位置情報をONにしたスマートフォン(Android端末でのみ検証済)


まずは「This」をクリックして


検索ボックスに「Location」と入力して出てきたLocationアイコンをクリックします。


左から「エリアに入った時」「エリアから出た時」「その両方」という選択肢があって
ここでは一番右の「You enter or exit an area」を選択します。
(入ったとき(enter)だけなら一番左、出たとき(exit)だけなら真ん中を選択します)


入力ボックスに住所や駅名などを入れるとその場所の地図が表示されるので
「ー」「+」で範囲を調整して
「Create trigger」をクリックします。


「That」をクリックして


入力ボックスに「LINE」と入力して出てくるLINEのアイコンをクリックします。


「Send message」のアイコンをクリックして


「Recipient」で通知先を選択して「Create action」をクリックします。


「Finish」をクリックして設定完了です。


実行結果

その後、スマホを持って指定したエリアに入ると
「You entered an area」と通知が来て
そのエリアから出ると
「You exited an area」と通知がきます。

(エリアに入って(出て)すぐではなく数分後に通知が来ます)


位置情報がトリガーになるため、移動しないと結果を得られないのはちょっとおもしろい体験でした。


IFTTTのアカウントを作成する(Google連携)


IFTTTのアカウントを作成する手順を書きます。
(ここではGoogleアカウントで連携してみます)



ログイン画面を開きます
https://ifttt.com/join



「Continue with Google」をクリックします



自分のGoogleアカウントを選択します


これでアカウントが作成されてIFTTTが使えるようになりました


スマートフォンでもアプリを作成できるので、インストールしておくと便利です。



アプリの作成は

「Get more」の横のアイコンをクリックして



「Create」を選択して



ここから「This」と「That」でやりたいことを選択していきます




関連記事

IFTTTで特定のエリアに入ったらLINEに通知したい