Help us understand the problem. What is going on with this article?

流行らなそうなWebアプリを無料で運営する:Firebaseで動画付きツイートを投稿

はじめに

Firebase触ってみたいな〜と思い、誰も得しないようなWebアプリを作ってみました。

作ったサービス「マジサッチャー」

なんか錯覚を使った動画を自分でも作りたいなぁ...と思ったときにサッチャー錯視を使った動画が即座にできるアプリ。気持ち悪い錯覚体験を友達と共有だ!
https://maji-thatcher.tokyo
zjd7v-6409o.gif

こんな感じで、無性にサッチャー錯視を見たいときにでも使ってみてください。
そんなことないとは思いますが…
ログインしなくても動画作って楽しめるのでぜひ遊んでみてください。

https://maji-thatcher.tokyo

サッチャー錯視とは

サッチャー錯視は、顔を180度回転させた逆さまの状態では、顔のちょっとした変化に気づきにくくなるという現象です。
result.jpg

イギリスの首相マーガレット・サッチャー氏の顔写真を使用して、サッチャー錯視の存在を証明したことにちなんで、サッチャー錯視と名付けられました。
このサッチャー氏の顔写真もこの状態ではそれほど不自然じゃないですが、ひっくり返すと…

あまりインパクトのない錯視らしく、説明しないとなかなかわからないようです!

全体構成

ページにほぼ動的な部分がないので、NuxtでGenerateして、Hostingで配信しています。
FunctionsだけPythonを使いたかったのでCloudFunctionsを使っています。

Paper.Work.1.png

Twitter投稿の流れ

普通のサーバーならすぐ終わるようなことでも、意外と回りくどいです。
今回の場合、もしかしたらFireStoreはいらなかったかもしれません…
Paper.Work.1 (3).png

Twitterでログイン

ここら辺は公式ドキュメント見ればすぐにできちゃうのでスキップ
JavaScript で Twitter を使用して認証する

自分でプラグイン入れたり、callbackページ作ったりしなくていいなんてなんて便利なんでしょ
Firebaseバンザイ!

AccessToken・Secretの取得

クライアントサイドでTwitterログインするとToken・Secretもらえるので取り出します。

const result = await firebase.auth().signInWithPopup(provider)
const token = result.credential.accessToken;
const secret = result.credential.secret;

AccessToken・Secretの保存

Twitterへの投稿はCloudFunctions使ってサーバーサイドで行うので、FireStoreに保存しておきます。
ユーザーのログイン後にしか使わないようなら、VueのStoreに入れとくだけでも良いかもしれません。

const db = firebase.firestore()
await db
  .collection('users')
  .doc(uid)
  .set(
    {
      twitterCredentials: {
        secret: secret,
        accessToken: token
      }
    },
    {
      merge: true
    }
  )

FireStoreルールの設定

他人には見られたくないものなので、FireStoreのルールも追加します。
自分のユーザーIDのドキュメントには自分しか参照、変更できないように設定。
これで、他の人から見られたり変更される心配はありません!

firestore.rules
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userID} {
      allow read, write: if request.auth.uid == userID;
    }
  }
}

Functionsの設定

今回はPythonランタイムを使いたかったので、CloudFunctions使いました。
まだベータ版のため、コンパイルが必要なライブラリとかだとインストールできないこともあります。
とりあえずインストールできること確認してから使ったほうがいいかもしれません。

次のモジュールをインストールします。

  • python-twitter: Twitter投稿用の便利ライブラリ
  • firebase-admin: Firebaseの機能にアクセスするために必要

Python CloudFunctionsはPipenvに対応しているので、Pipenvでインストールすればオッケーです。
Pyenvを使う場合は、requirements.txtを書き出してスクリプトと同じディレクトリに置いておけばオッケー。

$ pipenv install firebase-admin,python-twitter

インポートして初期化
firebase_adminは、認証情報を設定しなければ、自動でプロジェクトの認証情報で初期化されます。
便利ー

main.py
import firebase_admin
from firebase_admin import firestore
from firebase_admin import auth

# FireBaseのプロジェクトにデプロイすれば勝手にプロジェクトの認証情報で初期化される
firebase_admin.initialize_app()
client = firestore.client()

Functionsでユーザー認証

Firebaseが発行するJWTを使ってユーザーを認証します。
JWTの有効期限は1時間と短いので、期限が切れたら、再発行して使う必要があります。
ID トークンを確認する

headerにJWTを付けてPOST

const token = await firebase.auth().currentUser.getIdToken()
const config = {
    headers: { Authorization: token }
}
const response = await this.$axios.post(
    apiUrl,
    body,
    config
)

CloudFunctionsをHttp経由で呼び出した場合Flaskとおんなじなので、こんな感じで。

受け取ったJWTを検証して、ユーザーを認証します。
JWTにはユーザーID情報が含まれているので、ユーザーIDからTwitter認証情報を取得。

main.py
def my_function(request):
    # JWTをデコードする、ここでTokenが正しいかどうかも検証される
    decoded_token = auth.verify_id_token(request.headers['Authorization'])

    # デコードされたDictの”sub”がユーザーID
    uid = decoded_token['sub']

    # FireStoreからTwitter認証情報をとってくる
    users_ref = client.collection('users')
    user_doc_ref = users_ref.document(uid)
    user_doc = user_doc_ref.get().to_dict()

動画を投稿

Twitterを投稿するには、アプリケーションのトークンと、ユーザーのトークン両方が必要になります。
APIキーは環境変数へということなので、CloudFunctionsの環境変数にアプリケーションのトークンを設定します。
環境の構成

Deployの際のコマンドから設定する方法もあるのですが、いちいち大変なので、YAMLで書いて、Deployの際に環境変数に設定します。
.gitignoreへの設定もお忘れなく

$ echo .env.yaml >> .gitignore
.env.yaml
TWITTER_CONSUMER_KEY: xxxxxxxxxxxxxx
TWITTER_CONSUMER_SECRET: xxxxxxxxxxxxxxx

デプロイする際に環境変数を書いたYAMLを指定します

deploy.sh
GCF_NAME="my_function"
GCF_REGION="asia-northeast1"

gcloud functions deploy $GCF_NAME \
  --runtime python37 \
  --region $GCF_REGION \
  --trigger-http \
  --env-vars-file .env.yaml

動画付きツイートを投稿する

あとはToken, Secretを使って投稿するだけ

最初に動画を投稿すると、media_idが発行されるので、それを投稿につけて投稿すれば、動画付きツイートの完成です。

main.py
# base64でエンコードされた動画情報ならデコードしてioに入れる
decoded = base64.b64decode(movie_base64)
movie_io = io.BytesIO(decoded)
# 本来はopen(PATH_TO_VIDEO, 'rb)を渡すためこのパラメータも設定しておく
movie_io.mode = 'rb'
movie_io.name = 'movie.mp4'

# FireBaseから取ってきた認証情報とアプリの認証情報を使って初期化
api = twitter.Api(
        consumer_key=CK,
        consumer_secret=CS,
        access_token_key=ATK,
        access_token_secret=ATS
    )
# 先に動画を投稿
media_id = api.UploadMediaChunked(
    media=movie_io, media_category="tweet_video")
# すぐにアクセスするとTwitter側のDBに動画が登録されていないことがあるので、少し時間をおいてからアクセスする
time.sleep(1)
#mediaと一緒に本文も投稿
api.PostUpdate(status=tweet_text, media=media_id)

まとめ

FireBaseはじめて触ったのですが、とっても簡単でびっくりしました。これからもりもりFirebase使っていらないものを作っていこうと思います!
ぜひ皆さんもFirebaseとNuxtで楽しいコーディングライフを〜

終わりに

本記事をここまでお読みいただきありがとうございました。それ違うんじゃね?とか多分満載だと思うので、よかったらぜひTwitter(@Pnyompen)で絡んでくださいね。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away