はじめに
初投稿です。もし何か気づきやご意見がありましたら、ぜひフィードバックをお願いします。
Lineだけでの家族内コミュニケーションに限界を感じています。
Slackの無料版は90日しか履歴が残らないので、Discordが第一候補に挙がりました。
Discordを理解する手段の1つとしてBot作成にトライしました。
個人の備忘録も兼ねて手順を投稿します。
1.Photo LibraryAPIの有効化
以下の解説記事の[Photo Library APIの有効化]セクションを参照してください。
参考:PythonからGooglePhotoに画像や動画をアップロード
2. 認証情報の作成
以下の解説記事の[認証情報の作成]セクションを参照してください。
取得した認証情報のjsonファイルは後ほど使用します。
参考:PythonからGoogleDriveにファイルをアップロード
3. DiscordBotのアカウント作成
- 以下のURLにアクセスします。
Developer portal - [NewApplication]ボタンをクリックします。
- アプリケーション名を入力しチェックボックスにチェックを入れたら[Create]ボタンをクリックします。
- 作成したBotの設定画面に遷移します。必要に応じてICON、NAMEなどを任意に設定してください。
- 左のメニューから[Bot]を選び、[MESSAGE CONTENT INTENT]にチェックを入れます。
これによってBotがDiscordで投稿された画像にアクセスできるようになります。
- [Bot]画面上の[ResetToken]をクリックしてTokenを取得します。このTokenは後で使うのでメモしておいてください。
- 左のメニューから[URL Generator]を選び、[Bot]にチェックを入れます。
するとBotに与える権限を選択するチェックボックスが表示されますので、適切な権限を選択します。(今回は[Administrator]を選択しました。)
- さきほどのチェックボックス下部にある[GENERATED URL]欄のURLにアクセスします。Botを追加するサーバを選択したら[はい]をクリックします。他に指示が続くようであれば、画面の指示に従ってください。
以上でDiscordサーバにBotを作成できます。
4. 必要ライブラリのインストール
以下のライブラリをインストールしてください。
- discord.py
- google-api-python-client
- google-auth-oauthlib
5. 実装その1
GooglePhotoに画像を保存するクラスGooglePhotoFacade.py
を作成します。なお、このコードも先ほどと同じ記事からの引用です。
参考:PythonからGooglePhotoに画像や動画をアップロード
import pickle
from pathlib import Path
import requests
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
# 各URLやスコープ
API_SERVICE_NAME = "photoslibrary"
API_VERSION = "v1"
SCOPES = ["https://www.googleapis.com/auth/photoslibrary.appendonly"]
class GooglePhotoFacade:
# ログインしてセッションオブジェクトを返す
def __init__(
self,
credential_path: str,
token_path: str = "",
):
with build(
API_SERVICE_NAME,
API_VERSION,
credentials=self._login(credential_path, token_path),
static_discovery=False,
) as service:
self.service = service
print("Google OAuth is Complete.")
self.credential_path = credential_path
self.token_path = token_path
def _login(self, credential_path: str, token_path: str) -> any:
"""Googleの認証を行う
Args:
credential_path (str): GCPから取得したclient_secret.jsonのパス
token_path (str): Oauth2認証によって得られたトークンを保存するパス。
Returns:
googleapiclient.discovery.Resource: _description_
"""
if Path(token_path).exists():
# TOKENファイルを読み込み
with open(token_path, "rb") as token:
credential = pickle.load(token)
if credential.valid:
print("トークンが有効です.")
return credential
if credential and credential.expired and credential.refresh_token:
print("トークンの期限切れのため、リフレッシュします.")
# TOKENをリフレッシュ
credential.refresh(Request())
else:
print("トークンが存在しないため、作成します.")
credential = InstalledAppFlow.from_client_secrets_file(
credential_path, SCOPES
).run_local_server()
# CredentialをTOKENファイルとして保存
with open(token_path, "wb") as token:
pickle.dump(credential, token)
return credential
def upload(
self,
local_file_path: str,
):
self._login(self.credential_path, self.token_path) # トークンの期限を確認
save_file_name: str = Path(local_file_path).name
with open(str(local_file_path), "rb") as image_data:
url = "https://photoslibrary.googleapis.com/v1/uploads"
headers = {
"Authorization": "Bearer " + self.service._http.credentials.token,
"Content-Type": "application/octet-stream",
"X-Goog-Upload-File-Name": save_file_name.encode(),
"X-Goog-Upload-Protocol": "raw",
}
response = requests.post(url, data=image_data.raw, headers=headers)
upload_token = response.content.decode("utf-8")
print("Google Photoへのアップロードが完了しました。")
body = {"newMediaItems": [{"simpleMediaItem": {"uploadToken": upload_token}}]}
upload_response = self.service.mediaItems().batchCreate(body=body).execute()
print("Google Photoへのアップロードした動画の登録に成功しました。")
# uploadしたURLを返す
return upload_response["newMediaItemResults"][0]["mediaItem"]
6.実装その2
Discord側の処理をmain.py
として実装します。
-
client_secrets.json
は手順2で取得したjsonファイルです。同じファイル名でmain.py
と同じライブラリに置けばこのままでも動作します。 -
YOUR_TOKEN
の部分は手順3で取得したTOKENに置き換えてください。 -
YOUR_CHANNEL_ID
の部分はBotを追加したいチャンネルのIDで置き換えてください。チャンネルのIDはURLから確認できます。
https://discord.com/channels/サーバーのID/チャンネルのID
import discord
import tempfile
from GooglePhotoFacade import GooglePhotoFacade
# ボットのトークン
TOKEN = "YOUR_TOKEN"
# Discord Intentsの設定
intents = discord.Intents.default()
intents.message_content = True
# Google Photoへのアップロードに使用するクラス
google_photo = GooglePhotoFacade(
credential_path="client_secrets.json", token_path="token.pkl"
)
# Discordクライアントを作成
client = discord.Client(intents=intents)
@client.event
async def on_ready():
print(f"Logged in as {client.user.name}")
@client.event
async def on_message(message):
# メッセージがボット自身のものであれば無視
if message.author == client.user:
return
# 特定のチャンネルID
target_channel_id = YOUR_CHANNEL_ID
# 投稿されたメッセージが特定のチャンネルからのものかチェック
if message.channel.id == target_channel_id:
# 画像が添付されているかチェック
if len(message.attachments) > 0:
total_images = len(message.attachments)
for index, attachment in enumerate(message.attachments, start=1):
if attachment.content_type.startswith("image"):
# 画像のダウンロード
image_data = await attachment.read()
# 一時ファイルとして保存
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
temp_file.write(image_data)
save_file_path = temp_file.name
# 画像をGoogle Photoにアップロード
uploaded_media = google_photo.upload(local_file_path=save_file_path)
# アップロードの進捗と画像のURLを表示
upload_message = f"{total_images}枚中の{index}枚目の画像をアップロードが完了しました。\n{uploaded_media['productUrl']}"
await message.reply(upload_message)
if __name__ == "__main__":
# ボットを起動
client.run(TOKEN)
7.動作確認
main.py
を起動させた状態でDiscordに画像を投稿するとBotが動作します。無事アップロードが完了するとBotから画像のような返信がきます。同時に複数枚投稿しても問題ありません。
GooglePhotoを開くと無事に画像が保存されています。
参考サイト
文中に載せた参考サイトを改めて列記します。ありがとうございます。