LoginSignup
18

More than 3 years have passed since last update.

【Google Photo & Slack 写真Bot】GooglePhotoにある写真を取得し、Slackに送信するBotを作った話

Last updated at Posted at 2017-08-11

前書き

  • 大学2年生の秋から開発エンジニアになりたいと思い、プログラミングの独学を開始。
  • 2017年8月サーバーエンジニアとして入社しました。Railsで開発しております。
  • 初めて、Pythonでの開発をしました。
  • プログラミング初心者のときから、Qiitaを読んでいて、わかりやすい記事を書きたいなっていつかずっと思ったので、わかりやすく書こうと思います。(その分、長くなりますが、ご理解いただければ幸いです。)
  • ここでは、使っているAPIやモジュールについて、書こうと思います。
  • 全てのコードは、 githubに載せていて、urlを貼ってあるので、コードをみたい方は下まで移動していただければ、幸いです。

記事を書こうと思った理由

  • ある日、2017年の7月までPHP開発のインターン生として働いている会社のslackのrandomチャンネルにて
    • CEO 「思ったけど、google photoの写真から1日1枚ランダムにひっぱってきて#randomに流してくれるbotとかあったらもりあがりそうだなー @CTO @CTO @CTO @CTO @CTO @CTO けど、採用で忙しいよね?
    • CTO 「CSが作るしかないね笑」
    • そのslackを見ていた自分 「あの...、自分が作ってもよろしいでしょうか?」
    • CEO & CTO 「おおっ!任せた!」

こうして、写真Botを作ることになったが、難しい...と思ったのは、このとき、まだまだ先の話...。 詰まったことが多いので、記事にしてみることにしました。(Pythonで書く理由は、個人的に書いたことない言語だったので、勉強したいと思ったとの、CTOがPythonが好きだからです笑)

ゴール

下の写真のように、galleryというbotを呼び出し、galleryと入力したあと、写真がslackに流れるようにすればおkです!

一つ一つ処理を見ていきましょう。

手順

  1. Slackで、botユーザーを作成
  2. Pythonで実装されたBotライブラリをインストール
  3. gdataのmoduleをインストール
  4. Google Auth認証(Google Photoから写真を取得)
  5. RtmbotとSlack APIを使って、Slackに文章と写真を投稿。

開発環境

  • Python 2.7 (pyenvで開発環境を整えましょう)
  • Amazon EC2

1. SlackでBotユーザーを作成

まずは、Slack Appsのページで、Botユーザーを作成をします。
赤い長方形で書いたものは、botのトークンなので、どこか別の場所にコピーしましょう。
スクリーンショット 2019-06-22 12.44.19.png

2. Pythonで実装されたBotライブラリをインストール

オープンソースのBot管理ライブラリをpip経由で、インストール(https://github.com/slackapi/python-rtmbot)

$ pip install rtmbot 

そのあと、rtmbot.confを作成し、下のように編集していきます。

rtmbot.conf
# Add the following to rtmbot.conf
 DEBUG: True # make this False in production

 # 1.のときに作成したBotのトークンをここに書いてください。
 SLACK_TOKEN: "xoxb-11111111111-222222222222222"

  # ここには、Botのパスを書いてください。(ディレクトリの名前の間には、/ではなく、 . を書きましょう。)
 ACTIVE_PLUGINS:
     - plugins.google_photo_to_slack.GooglePhotoToSlackBot

3. gdataのmoduleをインストール

$ pip install gdata

4. Google OAuth認証(Google Photoから写真を取得)

ここでは、Google OAuth2.0認証のやり方を説明します。

Google Cloud PlatformDashboard->Use Google APIs->Credentialsの順番で、OAuth2.0 Client
ID を作成し、そのsecret json ファイルをダウンロードします。タイプは、「その他」を選択してください。

スクリーンショット 2017-08-11 20.19.37.png

いろいろと、ファイルが増えてきたなかで、ディレクトリ構造は下のようになります。
credentials.datの中身は今のところ、何も書かなくて大丈夫です。

rtmbot
├── photo-gallery.json
├── credentials.dat
├── plugins
│   ├── __init__.py
│   └── google_photo_to_slack.py
├── rtmbot.conf
└── rtmbot.log

最初は、ログインを確かめるために、下のように書いてみましょう。
下のファイルに書かれてあるモジュールで、インストールしていないものがあれば、インストールしましょう。

google_photo_to_slack.py
#!/usr/bin/python2.7

# module ----  
import os
import webbrowser
from datetime import datetime, timedelta
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
import gdata.photos.service
import gdata.media
import gdata.geo
import httplib2
import json
import urllib2

# Google Authetication
def OAuth2Login(client_secrets, credential_store, email):
    scope='https://picasaweb.google.com/data/'
    user_agent='picasawebuploader'
    storage = Storage(credential_store)
    credentials = storage.get()

    if credentials is None or credentials.invalid:
        flow = flow_from_clientsecrets(client_secrets, scope=scope, redirect_uri='urn:ietf:wg:oauth:2.0:oob')
        uri = flow.step1_get_authorize_url()
        webbrowser.open(uri)
        code = raw_input('Enter the authentication code: ').strip()
        credentials = flow.step2_exchange(code)
        storage.put(credentials)

    if (credentials.token_expiry - datetime.utcnow()) < timedelta(minutes=5):
        http = httplib2.Http()
        http = credentials.authorize(http)
        credentials.refresh(http)

    gd_client = gdata.photos.service.PhotosService(source=user_agent,
                                               email=email,
                                               additional_headers={'Authorization' : 'Bearer %s' % credentials.access_token})
    return gd_client


# main ----- 
if __name__ == '__main__':
    email = os.environ['EMAIL']
    confDir = os.path.abspath(os.path.dirname(__file__))
    client_secrets = os.path.join(confDir, 'photo-gallery.json') 
    credential_store = os.path.join(confDir, 'credentials.dat') 
    gd_client = OAuth2Login(client_secrets, credential_store, email)

    albums = gd_client.GetUserFeed()
    for album in albums.entry:
        print 'Album: %s (%s)' % (album.title.text, album.numphotos.text)

        photos = gd_client.GetFeed('/data/feed/api/user/default/albumid/%s?kind=photo' % (album.gphoto_id.text))
        for photo in photos.entry:
            print(photo.title.text)
            f = open(photo.title.text, 'w')
            f.write(urllib2.urlopen(photo.content.src).read())
            f.close()

そして、上のファイルを実行してみると、

$ python google_photo_to_slack.py

下のように、ブラウザには、写真のページが表示され、コンソールには、ブラウザに表示されているauthentication codeを入力せよとのものが出てきます。

Enter the authentication code: 

ブラウザに表示されるauthentication codeを入力すれば、ログイン完了です。

もう一度、

$ python google_photo_to_slack.py 

を実行すれば、google photoにある写真が全て、ダウンロードされていると思います。

5. RtmbotとSlack APIを使って、Slackに文章と写真を投稿。

上のGooglePhotoToSlackファイルを下のように編集しましょう。

GooglePhotoToSlackBot.py
class GooglePhotoToSlackBot (Plugin):
    MEDIA_ARR = []
    RANDOM_NUMBER = 0
    EMAIL = os.version['EMAIL']
    CHANNEL_POST = ''
    SLACK_BOT_TOKEN = os.version['SLACK_BOT_TOKEN']

    PLUGIN_CHILD_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
    PLUGIN_DIRECTORY = os.path.abspath(
        os.path.join(PLUGIN_CHILD_DIRECTORY, os.pardir)
    )
    RTMBOT_DIRECTORY = os.path.abspath(
        os.path.join(PLUGIN_DIRECTORY, os.pardir)
    )
    CLIENT_SECRETS = os.path.join(RTMBOT_DIRECTORY, os.version['SECRET_JSON'])
    CREDENTIAL_STORE = os.path.join(
        RTMBOT_DIRECTORY, os.version['CREDENTIAL_DAT']
    )

    def process_message(self, data):
        feedback_pattern = re.compile(
            # ここには、slack users list apiに表示されるUから始まるbotのidを入れてください
            r'.*<@UAAAAAAA.*(gallery).*', re.DOTALL | re.IGNORECASE
        )

        if not (re.match(feedback_pattern, data['text'])):
            return

        self.CHANNEL_POST = data['channel']

        message = u"本日の画像/映像をダウンロードしています!少しお待ち下さい! "
        message += "動画の場合は、ダウンロードに時間がかかる場合があります。"
        response = self.slack_client.api_call(
            "chat.postMessage",
            channel=self.CHANNEL_POST,
            text=message,
            link_names=1,
            as_user=True
        )

        self.fetch_all_media()
        self.post_random_media()

    def oauth_login(self, client_secrets, credential_store, email):
        scope = 'https://picasaweb.google.com/data/'
        user_agent = 'picasawebuploader'
        storage = Storage(credential_store)
        credentials = storage.get()
        now_time = datetime.utcnow()

        if credentials is None or credentials.invalid:
            flow = flow_from_clientsecrets(
                client_secrets,
                scope=scope,
                redirect_uri='urn:ietf:wg:oauth:2.0:oob'
            )
            uri = flow.step1_get_authorize_url()
            webbrowser.open(uri)
            code = raw_input('Enter the authentication code: ').strip()
            credentials = flow.step2_exchange(code)
            storage.put(credentials)

        if (credentials.token_expiry - now_time) < timedelta(minutes=5):
            http = httplib2.Http()
            http = credentials.authorize(http)
            credentials.refresh(http)

        gd_client = gdata.photos.service.PhotosService(
            source=user_agent,
            email=email,
            additional_headers={
                'Authorization': 'Bearer %s' % credentials.access_token
            }
        )

        return gd_client

    def fetch_all_media(self):
        gd_client = self.oauth_login(
            self.CLIENT_SECRETS,
            self.CREDENTIAL_STORE,
            self.EMAIL
        )

        albums = gd_client.GetUserFeed()
        for album in albums.entry:
            medias = gd_client.GetFeed(
                '/data/feed/api/user/default/albumid/%s' %
                (album.gphoto_id.text)
            )
            for media in medias.entry:
                self.MEDIA_ARR.append(media)

    def get_random_number_in_array(self, arr):
        max_length = len(arr)
        return random.randint(0, max_length)

    def post_random_media(self):
        self.RANDOM_NUMBER = self.get_random_number_in_array(self.MEDIA_ARR)
        media_object = self.MEDIA_ARR[self.RANDOM_NUMBER]
        media_file = open(media_object.title.text, 'wb')
        media_file.write(response.content)
        media_file.close()
        media_path = self.RTMBOT_DIRECTORY + "/" + media_object.title.text

        with open(media_path, 'rb') as f:
            param = {
                'token': self.SLACK_BOT_TOKEN,
                'channels': self.CHANNEL_POST,
                'title': u'Today\'s ' + media
            }
            r = requests.post(
                "https://slack.com/api/files.upload",
                params = param,
                files = {'file': f}
            )

そして、rtmbotディクレトリで、rtmbotコマンドを入力し、rtmbotサーバーを立ち上げます。
そのあと、スラックで、@galleryと入力しましょう。(どのチャンネルでも大丈夫です。)

$ rtmbot

上のコードにある本日の画像/映像をダウンロードしています!少しお待ち下さい! と写真がスラックに流れれば、写真botの完了です!!

全コード

いろいろと機能(ダウンロードした写真を溜めないように、写真を削除するなど)を加えたものをgithubにあげたので、興味がある方はみていただけれると幸いです。
https://github.com/romukey/PythonAlgorithm/blob/master/google_gallery/plugins/google_photo_to_slack.py

参考にしたもの

Picasa Web API の Documentation
https://developers.google.com/picasa-web/docs/1.0/developers_guide_python

Google OAuthのLoginのやり方
https://stackoverflow.com/questions/30474269/using-google-picasa-api-with-python

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
18