粗大メモ置き場

個人用,たまーに来訪者を意識する雑記メモ

【備忘録】結婚式でフォトコンテストをした時の設定事項メモ(LINE APIとGooglePhoto API使用)

最近全くブログを書くモチベがないですが,昔やった/できたことを完全に忘れるのは忍びないので備忘録だけでも放流しておきます。
基本的には技術的なメモを書くので運営的な反省については書かない予定。

フォトコンテストをする際に参考にしたもの

下記のブログを参考にLINE botとGooglePhotoの連携を試した。

qiita.com

ts-engine.net

Heroku上にサーバーを立ててそこからLINE APIとGooglePhotoAPIを叩いてLINE BOTとGooglePhotoをつなぐ形式である。

元記事のソースコードはいくつか挙動が合わなかったので改変したVerを自分のGithubにて管理している。

github.com

Heroku側の設定

Heroku上でのConfig設定

アクセストークンなどの値は直接サーバーにあげずにconfig値として送ると多少セキュアらしい。

heroku config:set <NAME>=<VAL>

.envファイルでまとめて入れても良い。 https://blog.44uk.net/2019/03/16/heroku-config-multiple-set-and-remove/

その場合は下記のコマンドでセットできる(bash限定)

heroku config:set $(cat .env)

DOSコマンドプロンプトからは下記のどちらかを叩いた気がする。

FOR /F "usebackq" %i IN (`type env_heroku.txt`) DO heroku config:set %i
FOR /F "usebackq" %i IN (`type .env_heroku`) DO set %i

Heroku上でのコミット設定

以下を参照

qiita.com

データベースURIってなんだっけ?

Flask初めてだったので上記の疑問がメモに残っていた。

データベースの記述フォーマットのことで dialect+driver://username:password@host:port/database名みたいな形式で書くらしい。

mycodingjp.blogspot.com

ここでは面倒だったので

DATABASE_URI=sqlite:////tmp/photocontest.db

とした。

db/photocontest.sqliteでも良かったかも。

ngrokでデバッグ

heroku上でデバッグするのはちょっとしんどかったのでローカル上でデバッグするためにngrokというのを使った。

ローカルからwebhookのURLを獲得することができる。いろいろ初期設定したら下記を叩くだけだった。

ngrok http 5000

GooglePhotoやGoogleの認証まわり

Googleの認証周りはとにかく面倒だった。正直今でも何もわかっていない。

Credentialsを取得するPythonスクリプト

Googleの管理画面でアプリ用のIDとSECRETを取得した後,実際のAPIを叩くためにはCredentialsというものを取得しなければならない。

これが意外と面倒なのでスクリプトで一気にできるようにした。下記を適当なファイルで保存して実行すればOK。

  • input: 同じフォルダにclient_id_secret.jsonが必要
  • output: credentials.json ができる
# google_photos.py
import google.oauth2.credentials
import google_auth_oauthlib.flow
import json
import os
from datetime import datetime, date

from matplotlib.pyplot import get
#from googleapiclient.discovery import build
from verify_reflesh_token import verify_credentials, get_accesstoken_from_refreshtoken

SCOPES = ['https://www.googleapis.com/auth/photoslibrary']
API_SERVICE_NAME = 'photoslibrary'
API_VERSION = 'v1'
CLIENT_SECRET_FILE = 'client_id_secret.json'
CREDENTIAL_FILE = 'credentials.json'


def support_datetime_default(o):
    if isinstance(o, datetime):
        return o.isoformat()
    raise TypeError(repr(o) + " is not JSON serializable")


def getNewCredentials():
    flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(
        CLIENT_SECRET_FILE, scopes=SCOPES)

    credentials = flow.run_console()
    with open(CREDENTIAL_FILE, mode='w') as f_credential_w:
        f_credential_w.write(json.dumps(
            vars(credentials), default=support_datetime_default, sort_keys=True))
    return credentials


def getCredentials():
    if os.path.exists(CREDENTIAL_FILE):
        with open(CREDENTIAL_FILE) as f_credential_r:
            credentials_json = json.loads(f_credential_r.read())
            credentials = google.oauth2.credentials.Credentials(
                credentials_json['token'],
                refresh_token=credentials_json['_refresh_token'],
                token_uri=credentials_json['_token_uri'],
                client_id=credentials_json['_client_id'],
                client_secret=credentials_json['_client_secret']
            )
        valid = get_accesstoken_from_refreshtoken(
            credentials_json['_client_id'], credentials_json['_client_secret'], credentials_json['_refresh_token'])
        print("Credentials is ", credentials.valid, valid)
        if not valid:
            print("Credential is Old!")
            credentials = getNewCredentials()
    else:
        print("Credential is not found!")
        getNewCredentials()
    return credentials


def main():
    credentials = getCredentials()
    # service = build(
    #    API_SERVICE_NAME,
    #    API_VERSION,
    #    credentials=credentials
    # )


if __name__ == "__main__":
    main()

Credentialsが有効か確かめるスクリプト

上記で取得したCredentialsはすぐ有効期限が切れるのでこれが有効かどうか手っ取り早く試す作業もスクリプト化した。

  • input : credentials.json
  • output: Credentialsが有効化どうか文が出力される
# google_photos.py
import requests
import json
import google

CREDENTIAL_FILE = "credentials.json"


def load_credential_file():
    with open(CREDENTIAL_FILE) as f_credential_r:
        credentials_json = json.loads(f_credential_r.read())
        credentials_dict = {
            "client_id": credentials_json["_client_id"],
            "client_secret": credentials_json["_client_secret"],
            "refresh_token": credentials_json["_refresh_token"]
        }
        return credentials_dict


def verify_credentials(credentials):
    client_id = credentials["client_id"]
    client_secret = credentials["client_secret"]
    refresh_token = credentials["refresh_token"]

    token = get_accesstoken_from_refreshtoken(
        client_id, client_secret, refresh_token)
    if token:
        print("Credentials is valid. Gained access token is:", token)
        return True
    else:
        print("Credentials is not valid!")
        return False


def get_accesstoken_from_refreshtoken(client_id, client_secret, refresh_token):
    params = {
        "grant_type": "refresh_token",
        "client_id": client_id,
        "client_secret": client_secret,
        "refresh_token": refresh_token
    }

    authorization_url = "https://oauth2.googleapis.com/token"
    authorization_url = 'https://www.googleapis.com/oauth2/v4/token'

    r = requests.post(authorization_url, data=params)

    if r.ok:
        return r.json()['access_token']
    else:
        # 失敗したときにRequestを表示
        print("Failed to get Access token!", r)
        return None


def main():
    credentials = load_credential_file()
    verify_credentials(credentials)


if __name__ == "__main__":
    main()

アップロードできない!?

後に気づいたことだがどうもAPIで作ったアルバムじゃないとAPIからアップロードできない仕様になっているっぽい。 ここがGooglePhotoを結婚式のフォトコンテストに一番勧めにくい点である。私のケースではCredentialsが万が一にも切れないよう,当日の朝にHerokuにPushして運用した。

https://www.sukerou.com/2020/10/gapino-permission-to-add-media-items-to.html

https://stackoverflow.com/questions/58414706/why-cant-i-add-a-existing-mediaitem-to-an-existing-album-using-the-online-api-e

https://stackoverflow.com/questions/58738766/google-photos-api-adding-photos-to-existing-album-is-broken/58740231#58740231

LINE まわり

本当に何もメモが残っていないので多分苦戦しなかったものと思われる。 多分なにかに使ったコードがあるのでメモする。

  • なんか秘密鍵・公開鍵を作るときに使ったプログラム
from jwcrypto import jwk
import json
key = jwk.JWK.generate(kty='RSA', alg='RS256', use='sig', size=2048)

private_key = key.export_private()
public_key = key.export_public()

print("=== private key ===\n"+json.dumps(json.loads(private_key),indent=2))
print("=== public key ===\n"+json.dumps(json.loads(public_key),indent=2))
  • private_keyを読んでJWTを出すプログラム。何に使ったっけ?
import jwt
from jwt.algorithms import RSAAlgorithm
import time

import json
with open("privatekey.txt", 'r') as json_open:
    privateKey = json.load(json_open)

headers = {
    "alg": "RS256",
    "typ": "JWT",
    "kid": "d173551d-2f74-4faa-81af-5ae3eb545c0a"  # your key
}

payload = {
    "iss": "1657287856",  # channel ID
    "sub": "1657287856",  # channel ID
    "aud": "https://api.line.me/",
    "exp": int(time.time())+(60 * 30),
    "token_exp": 60 * 60 * 24 * 30  # token valid sec
}

key = RSAAlgorithm.from_jwk(privateKey)

JWT = jwt.encode(payload, key, algorithm="RS256",
                 headers=headers, json_encoder=None)
print(JWT)