google apps script の覚え書き

初版作成  2022.3.27
最終更新  2022.04.24


◆◆ デバッグ用ログ出力 ◆◆


  Logger.log() を使う

  let a = 15;
  let b = "16";
  let contents = "123";
  contents = contents + "\n" + "456";
  Logger.log(contents);
  Logger.log("a = " + String(a) + "   b = " + b);

String( ) を忘れると、エラーは起こらず、それ以降が表示されない
というタチが悪い動作をする

◆◆ ファイル書き出し ◆◆


  let fname = "out.txt";
  let contents = "123" + "\n" + "abc";
  let rf = DriveApp.getRootFolder();
  rf.createFile(fname, contents);

◆◆ ファイル読み込み ◆◆


  fname = 'out.txt"
  const contents = DriveApp.getRootFolder().getFilesByName(fname)
                   .next().getBlob().getDataAsString("utf-8");
  lines = contents.split('\n');
  for(let i = 0; i < lines.length; i++){
    Logger.log(lines[i]);
  }


◆◆ google Apps Script (GAS) のしくみ ◆◆


Google Drive に Google Apps Script 作成する。
そのプログラムを「デプロイ」すると、url が与えられ、
ブラウザでその url にアクセスすると、実行される。

その url を引数なしで GET すると doGet() という
コールバック関数が実行され、引数ありで GET すると
doGet(e) が実行される。GET するときの引数の有無で
コールされる関数が異なるので注意。

POST すると doPost(e) というコールバック関数が実行される。

push するプログラムを定期的に実行するには、
その url を GET すればよい。

reply するプログラムを配置しておけば、line に投稿があったとき
配置したプログラムが実行される。

このサイトが大変わかりやすい。
http://pineplanter.moo.jp/non-it-salaryman/2021/04/23/line-bot-gas/

◆◆ スクリプトの作成とテスト実行 ◆◆


google drive で 新規 --> その他 --> Google Apps Script
新しいエディタを使う方がやりやすい。
デプロイボタンがある

「関数」のボタンを押して実行する場合、
選択した関数に引数があると、エラーになる。

reply 用の関数を debug のために実行したいときは、
引数なし関数を作成し、その関数内で引数 e を設定し、
doPost(e) を呼ぶ必要があると思われる。

関数を実行しようとすると
認証を要求されるので「権限を確認」をクリックし、
アカウントを選択する。

  このアプリは Google で確認されていません

と表示される。「詳細」-->「プログラム名(安全でないページに移動)」
で許可をする


◆◆ デプロイして実行する ◆◆


デプロイすると url が割り当てられる。
その url を GET あるいは POST すると実行される。

プログラムを変更するたびに新たにデプロイしないと、
更新が有効にならない。
新たにデプロイすると url が変わるので、ちょっと厄介である。

デプロイすると、アプリの種類を聞いてくるので
「ウェブアプリ」を選択し、
アクセスできるユーザーは「全員」に設定する。

push の場合、
doGet() (引数なしに注意)という関数を新設し、
そこから投稿する関数を呼ぶ。
url にアクセスすると doGet() が実行される。


◆◆ サンプル : text を push ◆◆


var CHANNEL_ACCESS_TOKEN = 'トークンの内容';
var USER_ID = 'user ID の内容';

function pushMessage() {
    //deleteTrigger();
  var message = "message line";
  var postData = {
    "to": USER_ID,
    "messages": [{
      "type": "text",
      "text": message,
    }]
  };

  var url = "https://api.line.me/v2/bot/message/push";
  var headers = {
    "Content-Type": "application/json",
    'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
  };

  var options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(postData)
  };
  var response = UrlFetchApp.fetch(url, options);
}

◆◆ サンプル : image を push ◆◆


https://www.ultra-noob.com/blog/2020/94/ によると、Line に
画像を投稿するには、画像が置いてある https サーバーを
用意する必要がある。

google drive の画像を共有する場合、

  image_url = 'https//drive.google.com/uc?id=xxxxxxxx';

という形式で書き、xxxxxxx の部分は、
ファイルのリンクを作成したときに

  ...file/d/xxxxxxxxxxxxxxxxxx/view?usp=drivesdk

の xxxx の部分である。
リンクを作成するときに、リンクを知っている全員が
見れるようにしておく。しかし、Google Drive 上の
画像を使うのは、は以下の 2 点から実用的でない。

(1) リダイレクトするのアドレスなので、
    PC 版 Line では見ることができない。
(2) 同名のファイルをアップロードすると、新版が
  次々に作成されてゆく。

lolipop は月 100 円程度で借りられるので、
それを使う方がよい。

  var CHANNEL_ACCESS_TOKEN = 'トークンの内容';
  var USER_ID = 'user ID の内容';

  image_url = 'https://xxx.yyy.zzz/dir/image.jpg';

  var postData = {
    "to": USER_ID,
    "messages": [{
      "type": "image",
      "originalContentUrl": image_url,
      "previewImageUrl" : image_url
    }]
  };

  var url = "https://api.line.me/v2/bot/message/push";
  var headers = {
    "Content-Type": "application/json",
    'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
  };

  var options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(postData)
  };
  var response = UrlFetchApp.fetch(url, options);


◆◆ サンプル : text を reply ◆◆


投稿した内容が time なら時刻を返し、
ondo なら現在の温度を返す。

const TOKEN = 'トークン';

function doPost(e) {
  const responseLine = e.postData.getDataAsString();
  const event = JSON.parse(responseLine).events[0];
  const replyToken = event.replyToken;

  let input = event.message.text;
  let output;
  let command = input.substr(0, 4);
  if ( command == "time") {
    output = "command<" + command + "> now a:b:c";
  } else if ( command == "ondo" ){
    output = "command<" + command + "> 25 c";
  } else {
    output = "command<" + command + "> no action";
  }

  const LineMessageObject = [{
    'type': 'text',
    'text': output
  }];
  const replyHeaders = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + TOKEN
  };
  const replyBody = {
    'replyToken': replyToken,
    'messages': LineMessageObject
  };
  const replyOptions = {
    'method': 'POST',
    'headers': replyHeaders,
    'payload': JSON.stringify(replyBody)
  };
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', replyOptions);
}


◆◆ Google Drive に python でファイルをアップロードする(準備編) ◆◆


(1) モジュールのインストール

$ pip3 install PyDrive

(2) Google 側の設定

https://console.developers.google.com/

同意して続行する
検索窓に GoogleDriveAPI と入力し、
Google Drive API をクリックし、
「有効にする」をクリックする。

左側の「認証情報」をクリックし、
上側の「認証情報を作成」をクリックする

・OAuth の同意

OAuth 同意画面をクリック

「外部」をクリック

アプリ名を入れる
メルアドは gmail のアドレスでよいだろう

他は空欄で「保存して次へ」

「テスト」は「アプリを公開」を押す

・認証情報の作成

左側「認証情報」をクリック
上の「認証情報を作成」をクリック
「OAuth クライアントID」
「アプリの種類はデスクトップアプリ」
名前は google-upload
「作成」をクリック

クライアント ID, クライアントシークレットが
生成される。この 2 つが必要

json をダウンロードし、
ファイル名を client_secrets.json に変更(これは不要なようだ)

・アップロードすると拒否されるとき

https://console.developers.google.com/

から設定を変更する。

OAuth 同意画面 ---> テスト の下の「アプリを公開」をクリック

もう一度 upload.py を実行
チェックを入れて「続行」をクリック


◆◆ Google Drive へテキストファイルをアップロード (python) ◆◆


settings.yaml を同一ディレクトリに置く

------------ settings.yaml ------------

client_config_backend: settings
client_config:
  client_id: client id を書く
  client_secret: client_secret を書く

save_credentials: True
save_credentials_backend: file
save_credentials_file: credentials.json

get_refresh_token: True

oauth_scope:
  - https://www.googleapis.com/auth/drive.file
  - https://www.googleapis.com/auth/drive.install

----------- upload.py -------------

#!/usr/bin/python3
# -*- coding: utf-8 -*-

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive

gauth = GoogleAuth()
gauth.LocalWebserverAuth()

drive = GoogleDrive(gauth)

fname = "a.txt"

f_src = open(fname, "r")
list = f_src.readlines()
f_src.close()

contents = ""
for i in range(0, len(list)):
    contents = contents + list[i]

print(contents)

f_dist = drive.CreateFile({'title': fname})
f_dist.SetContentString(contents)
f_dist.Upload()

◆◆ Google Drive へバイナリファイをアップロード ◆◆


参考
https://raspi.ryo.sc/python-upload-google-drive/

gauth = GoogleAuth()
gauth.CommandLineAuth()
drive = GoogleDrive(gauth)

fname = "capture.jpg"

f = drive.CreateFile({'title': fname,
                      'mimeType': 'image/jpg'})
#                      'parents': [{'drive#fileLink', 'id': FOLDER_ID}]})
f.SetContentFile(fname)
f.Upload()

url = 'http://drive.google.com/uc?export=view&id=' + f['id']
print(url)


◆◆ gas の実行をリモートから指示 (python) ◆◆


デプロイし、その url を get して実行する。

import urllib.request

url = 'https://script.google.com/macros/s/各自のプログラム/exec'

req = urllib.request.urlopen(url)
page = req.read().decode()
lines = page.split('\n')

n = len(lines)
for i in range(0, n):
    print(lines[i].rstrip())


GAS のプログラムを変更した場合、
「△実行」を押して実行するなら、変更は反映される。
しかし、url を GET して実行する方法は、
デプロイし直さないと変更が反映されない。

デプロイし直すと url が変更されるので、
厄介である。

2 つのファイルに分けて開発し、doGet() があるファイルを
デプロイし、push する内容を決めるファイルが別ファイルに
あってもダメ。


◆◆ 同一名のファイル ◆◆


Google ドライブにファイル a.txt をアップロードし、
再度 a.txt をアップロードすると、a.txt というファイルが
2 つ存在する状態になる。

Windows 風に解釈すると、表示されるのはショートカット名であり、
ファイル名は異なるが、ショートカット名は同一となる。
100 個に達すると古いものは削除されるらしい。

リンクを生成すると、そのリンクは最新版を指すらしい。

◆◆ 講座 ◆◆


いいかも?
https://www.udemy.com/course/web-api-scraping-gas-linebot-tsfcm/

◆◆ ファイルの ID を得る ◆◆


原典
https://tetsuooo.net/gas/42/

//  const folder_id = 'xxxxxxx';

//  xxxxx は右クリックしてリンクを取得したときの
//  ...drive/folders/xxxxxxx?usp=sharing  の部分

//  const folder_obj = DriveApp.getFolderById(folder_id);
  const folder_obj = DriveApp.getRootFolder();
  const files = folder_obj.getFiles();

  while(files.hasNext()){
    let file = files.next();
    let fname = file.getName();
    let file_id = file.getId();
    let fileURL = file.getUrl();
    Logger.log(fname + "  " + file_id + "  " + fileURL);
  }