インスタンスからGoogleドライブにファイルを転送するツールを作ってみた。

[前回](https://qiita.com/katsuki_ono/items/c702a68d93ec3f0ad44f)、LINE botを用いてテキストと音声をデータとして、インスタンスに保存するツールを作成しました。

しかしながら、インスタンスにデータが有っても利便性が悪いので、データをGoogleドライブに転送するツールを作成しました。

今回もインスタンスイメージがubuntuのインスタンスを使用しています。

手順は以下です。
1. Google Drive APIの有効化、及び、認証JSONをインスタンスに入れる
2. Google APIの認証を行ってから、プログラムを実行中にする

# 1. Google Drive APIの有効化、及び、認証JSONをインスタンスに入れる
## 1.1. Google Drive APIの有効化
Google Drive APIの有効化の手順は以下です。
1. [Google Cloud Console](https://console.cloud.google.com/) にアクセスし、プロジェクトを作成または選択します。
2. [Google Drive API ページ](https://console.cloud.google.com/apis/library/drive.googleapis.com) に移動し、「有効にする」をクリックして API を有効化します。
3. 必要に応じて同意画面を設定します。テストユーザーには、ファイルを転送したいフォルダのあるGoogleアカウントを設定してください。
4. 画面左側のメニューから「認証情報」をクリックし、認証情報を作成 を選択して「OAuth クライアントID」をクリックします。
5. 「デスクトップアプリ」を選択してクライアントIDを作成します。作成したら認証用のJSONをダウンロードします。

## 1.2. 認証JSONをインスタンスに入れる
1.1.で作成した認証JSONをインスタンスに入れます。手順は以下です。(Termiusの場合)
1. Termiusのフォルダに認証JSONを移します。
2. Termiusを開き、メニュー画面の左側のメニューからSFTPを選択します。
3. 認証JSONを移したいインスタンスを選択してから、ローカルの認証JSONを選択して、コピーを押します。これでインスタンスのホームディレクトリに認証JSONがコピーされます。
4. 任意で認証JSON用のディレクトリを作成して、認証JSONをそのディレクトリに移して下さい。

JSONをディレクトリに移動
```
mv /path/to/file /path/to/destination/directory/
```

# 2. Google APIの認証を行ってから、プログラムを実行中にする　
## 2.1. インストール
まず、インスタンスにSSH接続します。

インスタンスのコンソールが開いたら以下をインストールします。

aptのアップデート
```
sudo apt update
```

pipのインストール
```
sudo apt install python3-pip
```

Google API関連のインストール
```
sudo pip install --upgrade google-api-python-client 
```
```
sudo pip install --upgrade google-auth-httplib2 
```
```
sudo pip install --upgrade google-auth-oauthlib 
```

## 2.2. プログラムのファイルを作成
まず、無ければ、pythonのファイルを保存するディレクトリを作成します。
```
mkdir ディレクトリ名
```

pythonのファイルを保存するディレクトリに移動します。
```
cd ディレクトリ名
```

### 2.2.1. Google APIの認証プログラム
作成したディレクトリでGoogle APIの認証プログラムのファイルを作成します。
```
nano ファイル名.py
```
作成したファイルの中に下記のコードを入れて、ctrl oでファイルを保存して、ctrl xでファイルを閉じます。
※先程作成した認証JSONのパスとアクセストークンを保存するパスをそれぞれコード内で指定してください。
```python
import os.path
import pickle
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# 認証jsonのパス
credentials_path = '/path/to/your/directory/credentials.json'

# アクセストークンの保存先のパス
token_path = '/path/to/your/directory/token.pickle'

# 認証するスコープを設定
SCOPES = ['https://www.googleapis.com/auth/drive.file']

# リダイレクトURLを設定
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'

def main():
    # クライアント認証情報を読み込み
    flow = InstalledAppFlow.from_client_secrets_file(credentials_path, scopes=SCOPES, redirect_uri=REDIRECT_URI)

    # 認証用のURLを取得し、ユーザに認証を求める
    auth_url, _ = flow.authorization_url(prompt='consent')
    print(f"Please visit this URL to authorize the application: {auth_url}")

    # ユーザから認証コードを取得し、アクセストークンを取得するために使用する
    auth_code = input("Enter the authorization code: ")
    flow.fetch_token(code=auth_code)

    # アクセストークンを保存
    creds = flow.credentials
    with open(token_path, 'wb') as token:
        pickle.dump(creds, token)

    print("Authentication succeeded, token.pickle saved.")

if __name__ == '__main__':
    main()
```

### 2.2.2. Googleドライブにファイルを転送するプログラム
作成したディレクトリでGoogleドライブにファイルを転送するプログラムのファイルを作成します。
```
nano ファイル名.py
```

作成したファイルの中に下記のコードを入れて、ctrl oでファイルを保存して、ctrl xでファイルを閉じます。
※先程指定したアクセストークンを保存するパス、転送するファイルのあるディレクトリのパス、転送先のGoogleドライブのフォルダのIDをそれぞれコード内で指定してください。 (GoogleドライブのフォルダのIDは、フォルダのURLのdrive.google.com/drive/folders/の後の部分です。)
※このプログラムでは２つのディレクトリからファイルを転送していますが、用途に合わせて修正してください。
```python
import pickle
import os.path

import google
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaFileUpload


token_path = '/path/to/your/directory/token.pickle'

file_dir_text = "/path/to/your/directory"
folder_id_text = "your_folder_id"

file_dir_audio = "/path/to/your/directory"
folder_id_audio = "your_folder_id"

def get_credentials():
    creds = None
    if os.path.exists(token_path):
        with open(token_path, 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(creds_file_path, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(token_path, 'wb') as token:
            pickle.dump(creds, token)
    return creds

def upload_to_google_drive(file_path, folder_id=None):
    creds = get_credentials()

    drive_service = build('drive', 'v3', credentials=creds)

    file_metadata = {
        'name': os.path.basename(file_path),
    }

    if folder_id:
        file_metadata['parents'] = [folder_id]

    media = MediaFileUpload(file_path, resumable=True)
    file = drive_service.files().create(body=file_metadata, media_body=media, fields='id').execute()
    print(f"File ID: {file.get('id')}")

def main():
    for file_name in os.listdir(file_dir_text):
        file_path = os.path.join(file_dir_text, file_name)
        upload_to_google_drive(file_path, folder_id_text)
        os.remove(file_path)

    for file_name in os.listdir(file_dir_audio):
        file_path = os.path.join(file_dir_audio, file_name)
        upload_to_google_drive(file_path, folder_id_audio)
        os.remove(file_path)

if __name__ == '__main__':
    main()
```

### 2.2.3. Googleドライブに午前3時毎にファイルを転送するプログラム
作成したディレクトリでGoogleドライブに午前3時毎にファイルを転送するプログラムのファイルを作成します。
```
nano ファイル名.py
```

作成したファイルの中に下記のコードを入れて、ctrl oでファイルを保存して、ctrl xでファイルを閉じます。
※先程指定したアクセストークンを保存するパス、転送するファイルのあるディレクトリのパス、転送先のGoogleドライブのフォルダのIDをそれぞれコード内で指定してください。(GoogleドライブのフォルダのIDは、フォルダのURLのdrive.google.com/drive/folders/の後の部分です。)
※このプログラムでは２つのディレクトリからファイルを転送していますが、用途に合わせて修正してください。
```python
import pickle
import os.path
import datetime

import google
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaFileUpload


token_path = '/path/to/your/directory/token.pickle'

file_dir_text = "/path/to/your/directory"
folder_id_text = "your_folder_id"

file_dir_audio = "/path/to/your/directory"
folder_id_audio = "your_folder_id"

def get_credentials():
    creds = None
    if os.path.exists(token_path):
        with open(token_path, 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(creds_file_path, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(token_path, 'wb') as token:
            pickle.dump(creds, token)
    return creds

def upload_to_google_drive(file_path, folder_id=None):
    creds = get_credentials()

    drive_service = build('drive', 'v3', credentials=creds)

    file_metadata = {
        'name': os.path.basename(file_path),
    }

    if folder_id:
        file_metadata['parents'] = [folder_id]

    media = MediaFileUpload(file_path, resumable=True)
    file = drive_service.files().create(body=file_metadata, media_body=media, fields='id').execute()
    print(f"File ID: {file.get('id')}")

def main():
    for file_name in os.listdir(file_dir_text):
        file_path = os.path.join(file_dir_text, file_name)
        upload_to_google_drive(file_path, folder_id_text)
        os.remove(file_path)

    for file_name in os.listdir(file_dir_audio):
        file_path = os.path.join(file_dir_audio, file_name)
        upload_to_google_drive(file_path, folder_id_audio)
        os.remove(file_path)

def dataset_send():
    while True:
        # 現在の時刻を取得
        now = datetime.datetime.now()

        # 毎日午前3時に実行するように設定
        if now.hour == 3:
            main()

        # 次の日の3時まで待機
        tomorrow = now + datetime.timedelta(days=1)
        tomorrow_morning = datetime.datetime(year=tomorrow.year, month=tomorrow.month, day=tomorrow.day, hour=3)
        time.sleep((tomorrow_morning - now).seconds)

if __name__ == '__main__':
    dataset_send()
```

## 2.3. プログラムを実行する
まず、2.2.1.の認証プログラムを実行します。
```
python3 ファイル名.py
```

生成されたURLをローカルマシンで開き、テストユーザーに登録してある転送先のフォルダがあるGoogleアカウントを選択して、諸々同意します。
同意後に表示されるコードをコピーして、インスタンスのコンソールにペーストします。
これで認証が完了してアクセストークンなどが含まれるファイルが生成されます。

次にファイルを転送するディレクトリにファイルがある状態で、2.2.2.のファイルを転送するコードを実行します。
```
python3 ファイル名.py
```

Googleドライブにファイルが転送されていたら成功です。

最後に、午前3時毎にファイルを自動で転送するようにします。
まず、screenを作成します。
```
screen -S スクリーン名
```

screen内で2.2.3.のファイルを実行します。
```
python3 ファイル名.py
```

午前3時毎にGoogleドライブにファイルが転送されていたら成功です。

# 最後に
インスタンスからファイルを簡単に取り出せると便利ですね。　
また何か作ってみます。