128
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

フロムスクラッチAdvent Calendar 2017

Day 25

Google Apps ScriptのExecution REST APIで他のアプリと接続方法

Last updated at Posted at 2017-12-24

#Google Apps Scriptとは何?
G suiteの中にあるDoc、Spreadsheet、Formの内部処理を自動化するために使用するものです。MicrosoftのOffice suiteの中にあるExcelやWordの内部処理を自動化するために組むvba scriptと同じ役割をもっています。

##script editorを開く

  1. Standalone
    google app script standalone.png

  2. DocやSpreadsheetの中から
    not_standalone.png

##Execution REST APIについて
Google Apps ScriptのRESTインタフェース経由で別のアプリがscriptの関数を呼び出すことができます。具体的な例としては別のアプリから下記の操作が実行できます。

  • Google DocやGoogle Spreadsheetのデータの取得
  • Google Doc、Google Spreadsheet、Google Driveのファイルの基本操作(生成、更新、削除など)

##REST I/Fを使ってapps script+pythonでプログラムを組んでみましょう
###プログラムの目的
Google Drive上フォルダサイズが表示されないことはみんな知っているかと思います。フォルダの管理のためにプログラムを組んで自分のGoogle Driveにあるフォルダのstorageサイズを降順で出力させましょう

##流れ
下記URLを参照してください。この記事で説明が不足している点をクリアにしていきたいと思います。
https://developers.google.com/apps-script/guides/rest/quickstart/python

###環境

  • python2.6(pip)以上やpython3(pip3)
  • 有効なGoogleアカウントとGoogle drive
  • アプリと連携のためにtarget Apps Script

###target apps scriptをeditorから作成(ファイル名は任意)



/**
 * The function in this script will be called by the Apps Script Execution API.
 */
/**
 * Return the set of folder names contained in the user's root folder as an
 * object (folder set along with folder size).
 * @return {Object} A set of folder names,size keyed by folder ID.
 */
function getFoldersUnderRoot() {
  var root = DriveApp.getRootFolder();
  var folders = root.getFolders();
  var folderSet = {};
  while (folders.hasNext()) {
    var folder = folders.next();
    // send list
    folderSet[folder.getId()] = [folder.getName(), getTotalSizeOfCurrentFolder(folder)];
  }
  return folderSet;
}

function getTotalSizeOfCurrentFolder(folder) {
  var files = folder.getFiles();
  var totalSize = 0;
  while (files.hasNext()) {
    var file = files.next();
    totalSize = totalSize + file.getSize();    
  }
  Logger.log(totalSize);
  return totalSize;
}

###target apps scriptの配置
「公開」メニューから「実行可能APIとして導入」を選ぶ
Deploy as API executable .png

下記のようにtarget設定を行う、そのあと配置ボタンを押す
deploy.png

表示されるAPI IDをメモーしておいて画面を閉じる
API ID.png

##Step1
###target apps scriptの設定
1.「リソース」メニューから「Cloud Platformプロジェクト」を選ぶ
Cloud Platformproject.png

  1. ダイアログの青いリンクのProject IDを押す
    Project ID.png

  2. APIs & servicesからLibraryを選ぶ
    Choose Library.png

  3. 検索のGoogle APIsタブから"Google Apps Script API"を探してクリックする
    Search.png
    Google Apps Script API.png

  4. Enable APIを押す(一回有効になったら次からManage APIが表示される
    Google Apps Script API Manage_Enable.png

6. 左側にあるCredentialsタブを選択する
Create Credentialsの中からOAuth client IDを選択する
認証IDとして使える
Create Credentials.png

7. Application typeでOtherを選んでCreateボタンを押す
Other.png

8. 次表示されるダイアログをOKで閉じる
9. ID情報のJSONをダウンロードする(Download JSON)
10. このファイルをそのままPythonのアプリのパスに保存しておく
(記事にclient_secret.jsonとして保存と書いてあるが、認証情報が他のtarget apps scriptとかぶらないため名前の変更をしないまま使う)

##Step2
###Pythonのアプリ側の準備

  • 下記のlibraryのpip install
    • sudo pip/pip3 install httplib2
    • sudo pip/pip3 install --upgrade google-api-python-client
      (上記のインストール失敗の場合)
    • sudo pip/pip3 install --upgrade google-api-python-client --ignore-installed six

##Step3
###Pythonでscriptを呼び出すアプリを作成(ファイル名は任意)


from __future__ import print_function
import httplib2
import os
import fnmatch

from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage

from apiclient import errors

try:
    import argparse
    flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
    flags = None

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/script-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/drive'
for file in os.listdir('.'):
 if fnmatch.fnmatch(file,'client*'):
  CLIENT_SECRET_FILE = file
APPLICATION_NAME = 'Google Apps Script Execution API Python Quickstart'


def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_json_file = os
    credential_path = os.path.join(credential_dir, CLIENT_SECRET_FILE)

    store = Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else: # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials

def main():
    """Shows basic usage of the Apps Script Execution API.

    Creates a Apps Script Execution API service object and uses it to call an
    Apps Script function to print out a list of folders in the user's root
    directory.
    """
    SCRIPT_ID = 'ENTER_YOUR_SCRIPT_ID_HERE'

    # Authorize and create a service object.
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    service = discovery.build('script', 'v1', http=http)

    # Create an execution request object.
    request = {"function": "getFoldersUnderRoot"}

    try:
        # Make the API request.
        response = service.scripts().run(body=request,
                scriptId=SCRIPT_ID).execute()

        if 'error' in response:
            # The API executed, but the script returned an error.

            # Extract the first (and only) set of error details. The values of
            # this object are the script's 'errorMessage' and 'errorType', and
            # an list of stack trace elements.
            error = response['error']['details'][0]
            print("Script error message: {0}".format(error['errorMessage']))

            if 'scriptStackTraceElements' in error:
                # There may not be a stacktrace if the script didn't start
                # executing.
                print("Script error stacktrace:")
                for trace in error['scriptStackTraceElements']:
                    print("\t{0}: {1}".format(trace['function'],
                        trace['lineNumber']))
        else:
            # The structure of the result will depend upon what the Apps Script
            # function returns. Here, the function returns an Apps Script Object
            # with String keys and values, and so the result is treated as a
            # Python dictionary (folderSet).
            folderSet = response['response'].get('result', {})
            if not folderSet:
                print('No folders returned!')
            else:
                folderSet = SortFolderIDAccToSize(folderSet)
                print('Folders under your root folder:')
                for (folderId, folder) in folderSet:
                    print("\t{0} {1}".format(folder[0], GetHumanReadable(folder[1])))
    except errors.HttpError as e:
        # The API encountered a problem before the script started executing.
        print(e.content)

# return sorted folderSet as list
def SortFolderIDAccToSize(folderSet):
    return sorted(folderSet.items(), key=lambda x: x[1][1], reverse=True)

# Size format
def GetHumanReadable(size,precision=2):
    suffixes=['B','KB','MB','GB','TB']
    suffixIndex = 0
    while size > 1024 and suffixIndex < 4:
        suffixIndex += 1 #increment the index of the suffix
        size = size/1024.0 #apply the division
    return "%.*f%s"%(precision,size,suffixes[suffixIndex])

if __name__ == '__main__':
    main()

上記の*SCRIPT_ID = 'ENTER_YOUR_SCRIPT_ID_HERE'*にメモーしていたAPI IDを入力してソース修正を行う

##Step4
###プログラムを実行する

  • $ python3 quickstart.py
  • 実行されたあとに開いているブラウザーから認証の要求が行われる
  • 許可をあげたら自動的にアプリ側のコンソールに出力が表示される
  • 一回許可あげたあとに認証情報が~/.credentialsのフォルダに保存され、次から聞かれない
  • 出力結果例
    Folders under your root folder:
    • Import 1.69MB
    • Spark 538.07KB
    • 社内勉強会リソース 190.42KB
    • open edX 5.85KB
    • backup 0.00B
      (自分のGoogle Driveにあるフォルダが表示されています):relaxed:

##注意点

  • Python 2.7を使っている場合下記を注意しましょう
    • pipで必要なlibraryをインストールする
    • $ python quickstart.pyでアプリ実行
    • メソッド
      • iterItems()が2.7用、items()が3.0用
  • 本scriptがフォルダの中にあるフォルダ(nested folder)のサイズを計算してくれない
    • フォルダの中にファイルがある前提でscriptが正しく動く
    • nested folderの場合、scriptの改善が少しい必要
  • script側の変更修正が必要な場合、新しいバージョンで再配置を行う
    re-deploy.png
    更新の効果が出なかった場合、~/.credentialsの中に該当するOAuthのjsonファイルを削除する

#参考文献

128
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
128
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?