PythonでGoogleDrive上の画像を使いTwitterのプロフィール画像を更新する

動作環境

・Python 3.6.8
・Windows 10
・PyDrive 1.3.1
・requests_oauthlib 1.2.0

0. 前置き

僕はTwitter等のSNSのアイコンを数日に1回という頻度でぽんぽん変えているため、何かしらで簡略化もとい自動化しようと思いコードに起こした。
変える理由は新しい絵を描いたからだとか、ただ単に気分だったり。
本当はTwitter以外にも変えたいサービスがいっぱいあるけど、ひとまずTwitterの頻度が一番高いため。
GoogleDriveから画像を参照出来るようにしたのは、サーバーに置いて自動化させた際に、サーバーに新しい画像ファイルを置きにいくのが面倒なため。

1. 必要なもの

・GoogleDrive API関係のアクセスキー
・Twitter API関係のアクセスキー
・PyDriveやrequests_oauthlib等の各種ライブラリ

GoogleDrive APIのセットアップに関しては PythonでGoogleドライブに画像をアップロード が分かりやすいためそちらを参照。
Twitter APIのセットアップに関しては Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ が分かりやすいためそちらを参照。

2. コード

コードを実行すればGoogleDriveの指定フォルダ内の画像ファイルから無作為に選んでTwitterへアップロードしてくれるため、後はcronで定期実行するなりメイン部分にループ処理を追加するなりすれば定期的に自動でアイコンを変えてくれる。
ただし、cronで定期実行する場合は今の相対パスのままだと多分動かない(未確認)。絶対パスに書き換えることを推奨。
今後の拡張性を考慮し、ファイルは役割毎に分けてある。

IconChanger.py:IconChangerのメインクラス
modules/TwitterDriver.py:Twitter関係のクラス
modules/GoogleDriveDriver.py:GoogleDrive関係のクラス
modules/__init__.py:modulesの初期化クラス
settings.yaml:アクセスキー等の設定ファイル
client_secret_***.apps.googleusercontent.com.json:GoogleDriveのキーが入ったjsonファイル
credentials.json:GoogleDrive APIの認証済証明(?)jsonファイル、最初の一度だけブラウザによる認証があり、承認すると生成される。

IconChanger.py

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

"""
IconChangerメインクラス
ログ関係の初期化やアイコンチェンジの基底部分を処理する。
また、おまけでGoogleDriveへのファイルアップロード機能も付いてる。
"""
import os
import glob
import yaml
import logging
import datetime
import time
import modules

class SetLogger:
   '''
   Loggerクラス
   Logのセットアップを行う。
   '''
   def __init__(self):
       self.logfh = None
       self.logger = logging.getLogger('LoggingTest')
       self.logfmt = logging.Formatter('%(asctime)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S")
       self.log_filename = 'info.log'
       self.log_folder = './'

   def _init_log(self):
   	self.logger.setLevel(logging.INFO)
   	sh = logging.StreamHandler()
   	sh.setFormatter(self.logfmt)
   	self.logger.addHandler(sh)
   	self._refresh_log()
   
   def _refresh_log(self):
   	self.logfh = logging.FileHandler(self.log_folder+self.log_filename)
   	self.logger.addHandler(self.logfh)
   	self.logfh.setFormatter(self.logfmt)

class IconChanger:
   '''
   IconChangerクラス
   アイコンチェンジの基底部分を処理する。
   '''
   def __init__(self):
       # 設定ファイル読み込み
       f = open("settings.yaml", "r+")
       yamldata = yaml.load(f)
       f.close()
       # Driver読み込み
       self.Twitter = modules.TwitterDriver.TwitterDriver(yamldata)
       self.GoogleDrive = modules.GoogleDriveDriver.GoogleDriveDriver(yamldata)
   
   def _delete_file(self,filename,delFile):
       """
       一時的にダウンロードしておいたイメージファイルを削除する。
       :params filename:イメージファイル名
       :params delFile: 画像ファイルを削除するかどうか。
       """
       if delFile:
           os.remove(filename)

   def _check_status(self,status):
       """
       各API接続後のステータスチェックを行う。
       :params status: ステータス。0以外は異常コード
       :return:  msg: 異常時のメッセージ
       """
       msg = None
       if status == 0:
           return msg
       elif status == 9:
           msg = "GoogleDriveDriver Error End."
           return msg
       elif status == 8:
           msg = "TwitterDriver Error End."
           return msg
           
   def _iconchanger_main(self,logger,delFile=True):
       """
       アイコンチェンジャーのメイン部分。
       :params logger: ログ
       :params delFile: 終了後にダウンロードした画像ファイルを削除するかどうか。未指定の場合は削除する。
       """
       msg = None
       # ランダムで選択されたイメージファイルをローカルにダウンロードする。
       status,filename = self.GoogleDrive._pick_image()
       result = filename
       msg = self._check_status(status)
       if status != 0:
           logger.info(msg)
           logger.info(result)
           return 
       # Twitterのアイコンを更新する。
       status,result = self.Twitter._upload_profile_image(filename)
       msg = self._check_status(status)
       if status != 0:
           logger.info(msg)
           logger.info(result)
       # イメージファイルを削除する。
       self._delete_file(filename,delFile)
       logger.info(f"Icon Changer is Done. {filename}")
       return 

def main():
   """
   アイコンチェンジャーメイン部
   """
   # 開始時間の記録
   start_time = time.time()
   # ログセットアップ
   Log = SetLogger()
   Log._init_log()
   Log.logger.info("Icon changer started!")
   # アイコンチェンジャーセットアップ
   iconChanger = IconChanger()
   # 実行
   iconChanger._iconchanger_main(Log.logger)
   # 処理時間をログに記載
   Log.logger.info("Processing time: %.6f[sec]" % (time.time() - start_time))

def upload():
   """
   GoogleDriveにイメージファイルをアップロードする。
   アップロード対象はuploadsフォルダの中にあるpngファイルのみ。
   """
   # 開始時間の記録
   start_time = time.time()
   # ログセットアップ
   Log = SetLogger()
   Log._init_log()
   Log.logger.info("Image upload started!")
   try:
       iconChanger = IconChanger()
       filelist = glob.glob("./uploads/*.png")
       for file in filelist:
           iconChanger.GoogleDrive._upload(file)
           Log.logger.info(f"upload is done. {os.path.basename(file)}")
   except Exception as e:
       import traceback
       msg = f"GoogleDriveDriver._upload Error!\n{traceback.format_exc()}"
       Log.logger.info(f"Error!\n{msg}")
   Log.logger.info("Processing time: %.6f[sec]" % (time.time() - start_time))

if __name__ == "__main__":
   #upload()
   main()
   

modules/TwitterDriver.py

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

import yaml
import json
import base64
from requests_oauthlib import OAuth1Session

class TwitterDriver:
   """
   Twitterクラス
   Twitterへアイコンをアップロードし、プロフィールを更新する。
   """
   def __init__ (self,yamldata):
       CK = yamldata.get('TWITTER_CONSUMER_KEY')
       CS = yamldata.get('TWITTER_CONSUMER_SECRET')
       AT = yamldata.get('TWITTER_ACCESS_TOKEN')
       ATS = yamldata.get('TWITTER_ACCESS_TOKEN_SECRET')
       self.twitter = OAuth1Session(CK, CS, AT, ATS)
       self.profile_url = 'https://api.twitter.com/1.1/account/update_profile_image.json'
   
   def _upload_profile_image(self,filename):
       """
       アイコンをアップロードし、更新する。
       :params filename: アップロードするイメージファイル名
       :return1: status: 0でないならエラー
       :return2:    msg: 終了メッセージ
       """
       b64 = base64.encodestring(open(filename, 'rb').read())
       params = {'image': b64}
       r = self.twitter.post(self.profile_url, data=params)
       if r.status_code == 200:
           return 0,"TwitterDriver_upload_profile_image is Done."
       else:
           return 9,"TwitterDriver_upload_profile_image is Failed."

modules/GoogleDriveDriver.py

#!/usr/bin/python3
# coding: utf-8
import yaml
import os
import random
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive

class GoogleDriveDriver:
   '''
   GoogleDriveクラス
   GoogleDriveからアイコンとなるイメージファイルをダウンロードする。
   また、アップロードを行うことも出来る。
   '''
   def __init__ (self,yamldata):
       self.gauth = GoogleAuth()
       self.gauth.LocalWebserverAuth()
       self.drive = GoogleDrive(self.gauth)
       self.folder_id = yamldata.get('folder_id')
       self.max_results = 100
       self.query = f"'{self.folder_id}' in parents and trashed=false"
   
   def _get_mimeType(self,filename):
       """
       アップロードする際のmimeTypeの判別をする。イメージファイル専用
       :params filename: アップロードファイル名
       :return: jpg or png
       """
       extension = os.path.splitext(filename)[1][1:]
       if extension == 'jpg' or extension == 'jpeg':
           r = 'image/jpeg'
       elif extension == 'png':
           r = 'image/png'
       return r

   def _upload(self,filename):
       """
       ファイルのアップロードを行う。
       :params filename: アップロードファイル名
       """
       # folder_idの指定がない場合はhomeに
       if self.folder_id is None:
           f = self.drive.CreateFile({'title': os.path.basename(filename)
                                   , 'mimeType': self._get_mimeType(filename)})
       # folder_idの指定がある場合はそのフォルダに
       else:
           f = self.drive.CreateFile({'title': os.path.basename(filename)
                                   , 'mimeType': self._get_mimeType(filename)
                                   , 'parents': [{'kind': 'drive#fileLink', 'id':self.folder_id}]})
       f.SetContentFile(filename)
       f.Upload()

   def _get_filelist(self):
       """
       GoogleDriveのファイルリストを作成する。
       :return: GoogleDriveのファイル辞書データリスト
       """
       l = []
       for file_list in self.drive.ListFile({'q': self.query, 'maxResults': self.max_results}):
           for file in file_list:
               l.append(file)
       return l
   
   def _download(self,file,downloadpath="./"):
       """
       ファイルをローカルにダウンロードする。
       :params downloadpath: ダウンロード先フォルダ。未指定の場合は実行ファイルと同階層。
       """
       file.GetContentFile(os.path.join(downloadpath, file['title']))
   
   def _pick_file(self,filelist):
       """
       GoogleDriveのファイルリストから1つを無作為に選ぶ。
       :params filelist: GoogleDriveのファイル辞書データリスト
       :return: GoogleDriveのファイル辞書データ
       """
       return random.choice(filelist)
   
   def _pick_image(self,downloadpath="./"):
       """
       アイコンにするイメージを1枚選択し、ローカルにダウンロードする。
       :params downloadpath: ダウンロード先フォルダ。未指定の場合は実行ファイルと同階層。
       :return1: status: 0でないならエラー
       :return2:    msg: ファイル名。エラーの場合はエラーメッセージ
       """
       try:
           l = self._get_filelist()
           file = self._pick_file(l)
           self._download(file,downloadpath)
           return 0,file['title']

       except Exception as e:
           import traceback
           msg = f"GoogleDriveDriver._pick_image Error!\n{traceback.format_exc()}"
           return 9,msg

modules/__init__.py

#!/usr/bin/python3
# coding: utf-8
from . import TwitterDriver
from . import GoogleDriveDriver

settings.yaml
※ yamlファイルは文字列でも''や""で囲う必要は無し
folder_idはGoogleDriveの「https://drive.google.com/drive/u/0/folders/hoge」のhoge部分

client_config_backend: settings
client_config:
 client_id: ***
 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

folder_id: ***

TWITTER_CONSUMER_KEY: ***
TWITTER_CONSUMER_SECRET: ***
TWITTER_ACCESS_TOKEN: ***
TWITTER_ACCESS_TOKEN_SECRET: ***

3. まとめ

今回のコード
SNS_IconChanger - akaness1git GitHub

参考
PythonでGoogleドライブに画像をアップロード
Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ
PyDriveでGoogle Driveの特定フォルダ配下のファイルをすべてダウンロードする
POST account/update_profile_image

この記事が気に入ったらサポートをしてみませんか?