2019年12月19日木曜日

Ruby で Facebook の Graph API をコールしてみた

概要

Facebook の Graph API を Ruby からコールしてみました
Facebook Graph API をコールするために OAuth によって取得できたアクセストークの取得が必要になるので Web アプリとして作成しています

環境

  • macOS 10.15.2
  • Ruby 2.6.2p47
  • koala 3.0.0

Facebook アプリの作成

過去に Facebook Developer のページでアプリの作成方法を紹介しているのでそちらを参考にして事前に作成しておいてください

Facebook OAuth の実装

Facebook の OAuth は JavaScript 側で行います
Facebook の OAuth の方法も過去の記事で紹介しているのでそれを参考にします

JavaScript 側で取得したアクセストークンを Web アプリ側に渡して Facebook Graph API をコールしていきます

サンプルアプリの作成

冒頭紹介した通り Web アプリ (Sinatra) として作成します
JavaScript 側で取得したセッショントークンを使ってコールします
また Facebook Graph API は koala というライブラリを使います

  • vim Gemfile
gem "koala"
  • bundle install --path vendor
  • vim app.rb
require 'sinatra/base'
require 'sinatra/cookies'
require 'koala'

class App < Sinatra::Base
  helpers Sinatra::Cookies

  get '/' do
    if cookies.has_key?(:fb_access_token)
      graph = Koala::Facebook::API.new(cookies[:fb_access_token])
      profile = graph.get_object("me")
      @id = profile['id']
    end
    erb :index
  end
end

cookies にセッショントークンがある場合は Graph API をコールします
今回はユーザ ID を取得しているだけです

ビューの作成

Facebook Graph API をコールして取得できた情報を HTML として表示するビューを作成します

  • vim views/index.erb
<html>
<head>
  <title></title>
</head>
<body>
  <script>
    window.fbAsyncInit = function() {
      FB.init({
        appId      : 'xxxxxxxxxxxxxxx',
        cookie     : true,
        xfbml      : true,
        version    : 'v5.0'
      });
      FB.AppEvents.logPageView();   
      FB.getLoginStatus(function(response) {
        console.log(response);
        if (response.status === 'connected') {
          console.log(response.authResponse.accessToken);
          document.cookie = 'fb_access_token=' + response.authResponse.accessToken;
        }
      });
    };
    (function(d, s, id){
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) {return;}
      js = d.createElement(s); js.id = id;
      js.src = "https://connect.facebook.net/en_US/sdk.js";
      fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));
    function checkLoginState() {
      FB.getLoginStatus(function(response) {
        console.log(response);
        if (response.status === 'connected') {
          console.log(response.authResponse.accessToken);
          document.cookie = 'fb_access_token=' + response.authResponse.accessToken;
        }
      });
    }
  </script>
  <fb:login-button 
    scope="public_profile,email"
    onlogin="checkLoginState();">
  </fb:login-button>
  <div><%= @id %></div>
</body>
</html>

ログインしている状態でページにアクセスするとユーザ ID が表示されます
ログインしていない場合はログインボタンを押してログインするとユーザ ID が表示されるようになります

動作確認

  • bundle exec rackup config.ru -o 0.0.0.0

localhost:9292 にアクセスしてログインして動作確認しましょう

Tips: Instagram の Graph API をコールするには

どうやら Instagram の Graph API も同じエンドポイントでコールできるようです
が、Instagram ビジネスアカウントの作成と Facebook ページの作成が必須なので少し勝手が違うようです
当然ですが API のスコープも違います (instagram_basic, pages_show_list が必須?)
詳しくはチュートリアルの手順を御覧ください

また Instagram API は廃止になり Instagram Graph API を使うようになっているので後者を使いましょう

Instagram Graph API を追加する

Facebook アプリはコールされる API をプロダクトとして追加します
左メニューにあるプロダクトから「Instagram」を追加しましょう (スクリーンショットは追加後の状態)

参考サイト

2019年12月18日水曜日

Sinatra で JavaScript でセットした cookies を参照する方法

概要

Sinatra のエクステンション Sinatra::Contrib に含まれる Sinatra::Cookies を使います

環境

  • macOS 10.15.2
  • Ruby 2.6.2p47
  • Sinatra 2.0.7

sinatra-contrib インストール

  • vim Gemfile
gem "sinatra"
gem "sinatra-contrib"
  • bundle install --path vendor

Sinatra::Cookies をヘルパーとして使う

require 'sinatra/cookies' をし helpers として Sinatra::Cookies を追加すれば OK です
あとは cookies というハッシュが参照できます

  • vim app.rb
require 'sinatra/base'
require 'sinatra/cookies'

class App < Sinatra::Base
  helpers Sinatra::Cookies

  get '/' do
    if cookies.has_key?(:fb_access_token)
      puts cookies[:fb_access_token]
    end
    erb :index
  end
end

cookies をセットするクライアントサンプル

JavaScript 側は document.cookie = という感じで代入するだけで cookies に好きな値をセットできます
セットする値は文字列ですが「名前=値」で指定します

以下は Facebook ログインで成功したセッショントークンを cookies にセットするサンプルです
ちょっと長いですが cookies に値をセットしている箇所は document.cookie = 'fb_access_token=' + response.authResponse.accessToken; の部分だけです

  • vim views/index.erb
<html>
<head>
  <title></title>
</head>
<body>
  <script>
    window.fbAsyncInit = function() {
      FB.init({
        appId      : '2613442028942274',
        cookie     : true,
        xfbml      : true,
        version    : 'v5.0'
      });
      FB.AppEvents.logPageView();   
      FB.getLoginStatus(function(response) {
        console.log(response);
        if (response.status === 'connected') {
          console.log(response.authResponse.accessToken);
          document.cookie = 'fb_access_token=' + response.authResponse.accessToken;
        }
      });
    };
    (function(d, s, id){
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) {return;}
      js = d.createElement(s); js.id = id;
      js.src = "https://connect.facebook.net/en_US/sdk.js";
      fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));
    function checkLoginState() {
      FB.getLoginStatus(function(response) {
        console.log(response);
        if (response.status === 'connected') {
          console.log(response.authResponse.accessToken);
          document.cookie = 'fb_access_token=' + response.authResponse.accessToken;
        }
      });
    }
  </script>
  <fb:login-button 
    scope="public_profile,email"
    onlogin="checkLoginState();">
  </fb:login-button>
</body>
</html>

動作確認

  • bundle exec rackup config.ru -o 0.0.0.0

で localhost:9292 にアクセスしましょう
ログインが成功して cookies に値がセットされた状態で再度ページにアクセスすると Sinatra 側でも cookies[:fb_access_token] が取得できているのが確認できると思います

最後に

Sinatra で JavaScript がセットした cookies を参照する方法を紹介しました
sinatra-contrib の Sinatra::Cookies を使えば OK です

JavaScript 側でも document.cookie という感じで簡単に cookies に値をセットすることができます
取得する場合は少し面倒なのでその場合は js-cookie/js-cookie を使うと簡単に扱えます

参考サイト

2019年12月17日火曜日

JavaScript で Facebook の OAuth 認証を試してみた

概要

Facebook の OAuth を JavaScript で試してみました
Facebook の JavaScript SDK があるのでそれを使っています

環境

  • macOS 10.15.2
  • Chrome 78.0.3904.108
  • Facebook OAuth API v5.1

Facebook アプリの作成

ここから作成します
まだデベロッパー登録していない場合はページにアクセスしてログインすれば登録しアプリが作成できるようになります

新規アプリを作成しましょう
適当なアプリの名前を設定します

キャプチャが表示されるので入力しましょう

アプリが作成されてダッシュボードに移動します

アプリにプロダクトを追加する

今回は Facebook ログインを使うので Facebook ログインの「設定」を選択します

今回は Web アプリなので「ウェブ」を選択します

あとはアプリを動作させる予定の URL を設定します
localhost だけでテストするのであれば適当な URL を入力すれば OK です
アクセスできない URL でも大丈夫でした

サンプルアプリの作成

今回は Ruby で作成します
HTML + JavaScript だけなのでサーバサイドアプリじゃなくても OK です

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • mkdir views
  • vim app.rb
require 'sinatra/base'

class App < Sinatra::Base
  get '/' do
    erb :index
  end
end
  • vim config.ru
require './app'
run App
  • touch views/index.erb

OAuth の実装

JavaScript SDK を使ってログインを実装します
基本は Facebook アプリの設定に出てくるクイックスタートを実装した内容になります

  • vim views/index.erb
<html>
<head>
  <title></title>
</head>
<body>
  <script>
    window.fbAsyncInit = function() {
      FB.init({
        appId      : '2613442028942274',
        cookie     : true,
        xfbml      : true,
        version    : 'v5.0'
      });
      FB.AppEvents.logPageView();   
      FB.getLoginStatus(function(response) {
        console.log(response);
        if (response.status === 'connected') {
          console.log(response.authResponse.accessToken);
        }
      });
    };
    (function(d, s, id){
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) {return;}
      js = d.createElement(s); js.id = id;
      js.src = "https://connect.facebook.net/en_US/sdk.js";
      fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));
    function checkLoginState() {
      FB.getLoginStatus(function(response) {
        console.log(response);
        if (response.status === 'connected') {
          console.log(response.authResponse.accessToken);
        }
      });
    }
  </script>
  <fb:login-button 
    scope="public_profile,email"
    onlogin="checkLoginState();">
  </fb:login-button>
</body>
</html>

FB.init で Facebook アプリの初期化を行います
作成した Facebook アプリの ID が必要になります
ID はアプリのダッシュボード上部で確認できます
また appId の部分は各自で作成した Facebook アプリの ID に変更してください

その下の function は Facebook JavaScript SDK の動的な読み込みを行っています
ページがロードされたあとで確認するとちゃんと SDK の読み込みやボタンの生成に必要なスタイルのタグが定義されていると思います

ボタンを押したときとページがロードされたときに FB.getLoginStatus が呼ばれます
これはログインの状態を確認する関数です
connected の場合はすでにログインの状態になります
その場合は他の API をコールするためのアクセストークンが取得できます

動作確認

  • bundle exec rackup config.ru

で localhost:9292 にアクセスしてみましょう
Facebook のログインボタンが表示されるので押して見ると別ウィンドウが表示され認証画面になることが確認できると思います
今回のサンプルアプリでは認証後に取得できるアクセストークンをコンソールに表示して終了です

ただページを再度読み込むとなぜかログアウトした状態になってしまいます
理由は不明なのですが localhost でテストしているのが原因かもしれないので https 付きの Heroku 上でも動作確認してみました

Heroku で試してみる

本来は https が動作しているサーバ上でホスティングし動作させる必要があります
試しに Heroku にデプロイして試してみました

Heroku Container で動作させています

  • vim Dockerfile
FROM ruby

RUN gem install bundler

ADD . /app
WORKDIR /app

RUN bundle install
CMD bundle exec rackup config.ru -o 0.0.0.0 -p $PORT
  • docker build -t registry.heroku.com/randomgram/web .
  • heroku create -a randomgram
  • heroku container:login
  • docker push registry.heroku.com/randomgram/web
  • heroku container:release web -a randomgram

これで Heroku 上で動作確認するとページをリロードしてもちゃんとログイン状態になっていました
やはり http 上 (もしくは localhost) だとログイン状態は毎回解除されるようです
おそらく開発中のテストなのでそのような挙動になっているのだと思います

Tips

The method FB.getLoginStatus can no longer be called from http pages というエラーがコンソールで表示されますが localhost でテストした場合でも出るようです
localhost でのテストの場合は問題ないですがドメインありのサイトで http の場合は動作しないので注意しましょう

The Login Button plugin no longer works on http pages. Please update your site to use https for Facebook Login. このエラーも同様に http 上での動作だと出るので注意しましょう

最後に

Facebook JavaScript SDK を使って HTML + JavaScript で Facebook の OAuth を試してみました
このあとは取得できたセッショントークンを使って各種 API をコールする感じになります
そのあたりも別途紹介できたらなと思います

また今回は JavaScript ですべて完結しました
トークン情報をサーバサイドで使いたい場合もあるかなと思いますがその場合はトークン情報をサーバサイドに渡す工夫が必要になります

参考サイト

2019年12月16日月曜日

MacOS (Mac Book Air) で hostapd の用にアクセスポイントを作成する方法

概要

hostapd は Linux でよく使われるアクセスポイントを作成する仕組みです
MacOS の場合は標準で搭載されているインターネット共有機能を使えば簡単にアクセスポイントを作成することができます

環境

  • macOS 10.15.2

環境設定を開く

まずは「環境設定」->「共有」を選択します

Bluetooth を使って共有する設定

そして「インターネット共有」を選択し「Bluetooth PAN」のチェックを ON にします
こうすることで Mac 側の Bluetooth を使ってスマホに接続しインターネット共有できるようにします
もちろんここで Ethernet ポートなどを選択すればアクセスポイントを作成することもできます
普通の MacBook の場合 Ethernet ポートはすでに使われているケースが多いので Bluetooth をおすすめします

インターネット共有を有効にする

これで左メニューの「インターネット共有」にチェックを入れられる状態になるのでオンにします
確認ダイアログが表示されるので開始を選択します

ステータスが緑になれば OK です

iPhone から Bluetooth を使って接続

iPhone の設定から Bluetooth デバイスの一覧を開きます
すると先程オンにした Mac が見えるようになっているので接続します

iPhone からの接続要求があると Mac 側に接続確認のダイアログが表示されるので問題ない端末であれば接続許可しましょう

動作確認

あとは接続した iPhone 側でインターネットに接続できるか確認しましょう
動作確認として LTE 回線と Wifi をオフにすると良いと思います

最後に

MacOS でインターネット共有を試してみました
Bluetooth の場合 hostapd のようには使えませんが Ethernet ポートを使えばちゃんとアクセスポイントを作成してパスワードを設定することができます

目的としてはどちらも達成できると思うのでお好きな方を使えば良いと思います
個人的には Bluetooth のほうが余計な設定をする必要がないのでオススメです

2019年12月15日日曜日

Github の API は Personal Access Token を使ってコールしましょう

概要

Github の API をコールするのにパスワードを使った認証は廃止になります
メールで Github から注意が来たので対応してみました
基本的に PAT (Personal Access Token) を作成してパスワードから PAT に変更するだけです

環境

  • Ruby 2.6.2p47
  • octkit 4.9.0

Personal Access Token の作成

ここから作成します
「Generate new token」を選択します

トークンの名前とトークンが使用するスコープにチェックを入れます

以下のように作成されれば OK です
トークンはあとで使用するのコピーしておきます

コード修正

octkit を使ったコードを修正しましょう
基本的にはコンストラクタで指定するパラメータを password から access_token に変更するだけです

Octokit::Client.new(:login => 'github_username', :password => 'github_password')
Octokit::Client.new(:login => 'github_username', :access_token => 'f542xxxxxxxxxxxxxxxxxxxxxxxxxxx')

参考サイト

2019年12月14日土曜日

Catalina で MP3 などの音楽ファイルを iPhone に転送する方法

概要

自作の音源ファイルなどを iPhone に転送する方法を紹介します
よく専用のアプリを使って転送する方法が紹介されていますがデフォルトのアプリだけで転送可能です
また Catalina からは iTunes アプリが廃止されたので Finder を使って転送することができます

環境

  • macOS 10.15.1
  • ミュージックアプリ

MP3 ファイルを用意する

これは何でも OK です
Youtube のフリー音源でも良いですし GarageBand で作成した自作音楽でも OK です
拡張子も基本は何でも OK ですが mp3 や mp4 が無難です

Audacity で編集した MP3 ファイルなどでも OK です

ミュージック.app を開いて音源を読み込む

Mac でミュージック.app を開きましょう
そして作成した音楽ファイルをドラッグアンドドロップなどで読み込みます
自作音楽なので楽曲名やアーティスト名、アルバム名などを適当に決めましょう
決めなくても OK ですが今後追加する場合は自作だとわかるようにしたほうが良いと思います

iPhone と Mac を Lightning ケーブルで接続する

接続しましょう

Finder を開いて iPhone のミュージックを同期する

Finder の左メニューに iPhone が表示されるので選択します
そして選択した iPhone のミュージックメニューを開きましょう

するとミュージック.appに取り込んだ音源と iPhone を同期させることができるので同期します
同期することで iPhone 側に音楽と転送することができます

同期はミュージック.app にあるファイルをすべて同期することもできますし、指定したファイルのみを同期することもできます
今回は自作音源一つなので「ミュージックライブラリ全体」にチェックして同期を開始しました

確認

iPhone に音源を転送できれば普通にミュージックとして参照することができます
ファイルとして出はなくミュージックとしてちゃんと転送しているため例えばアラームの音楽として選択することもできます

最後に

MacOS Catalina で音源ファイルを iPhone に転送する方法を紹介しました
デフォルトのミュージックアプリと Finder を使えば簡単にできます

一度ミュージックアプリに取り込んでそこから同期する手順が少し面倒ですが Mac 側でも使うことができるので便利です
手順も慣れればそこまで難しくはありません

よく iPhone にデータ転送するアプリがありますが有料だったり最新の MacOS に対応していないケースもあるのでデフォルトのアプリを使う手順になれたほうが無難だと思います

2019年12月13日金曜日

docker-slim 使ってみた

概要

docker-silm はビルドした docker のイメージのサイズを小さくすることができるツールです
イメージは無駄に大きくなることが多いのでこれを使うことでレジストリのディスク容量を節約することができます

環境

  • macOS 10.15.2
  • docker-slic 1.26.1

インストール

執筆時点では Homebrew ではインストールできないようです
公式のホームページからバイナリをダウンロードして PATH 配下に配置すれば OK です

  • unzip dist_mac.zip
  • mv dist_mac/docker-slim* /usr/local/bin
  • docker-slim --version

試してみる

よく使ってる Ruby のイメージがどれくらい小さくなるか試してみました

  • docker images ruby
REPOSITORY TAG IMAGE ID CREATED SIZE ruby latest d529acb9f124 5 months ago 840MB
  • docker-slim build --http-probe ruby
  • docker images ruby
REPOSITORY TAG IMAGE ID CREATED SIZE ruby latest d529acb9f124 5 months ago 840MB

あれ小さくなっていないぞ、、
どうやらポートを EXPOSE しているようなイメージでないとダメなようです
要するに Web アプリのイメージであれば良いようです

イメージを作成する

簡単な Web アプリのイメージを作成してみましょう
元のイメージは Ruby を使っています

  • bundle init
  • vim Gemfile
gem "sinatra"
  • vim app.rb
require 'sinatra'

get '/' do
  'ok'
end
  • vim Dockerfile
FROM ruby

ADD . /home
WORKDIR /home
RUN bundle install

EXPOSE 4567
CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0"]
  • docker build -t my_app .
  • docker images my_app
REPOSITORY TAG IMAGE ID CREATED SIZE my_app latest 09c12a3382ec About a minute ago 872MB

必要なライブラリをインストールした分少し大きくなっているのがわかります

小さくしてみる

ではこれを小さくしてみましょう

  • docker-slim build --http-probe my_app

作成したイメージからコンテナが起動し EXPOSE したポートに対してアクセスします
うまくアクセスできればスリム化が始まります

「docker-slim[build]: info=results status='MINIFIED BY 35.26X [872106607 (872 MB) => 24732141 (25 MB)]'」こんな感じのログが表示されれば成功です
スリム化したイメージはイメージ名の最後に .slim が付与されているようです

  • docker images my_app.slim
REPOSITORY TAG IMAGE ID CREATED SIZE my_app.slim latest 3a67521db855 About a minute ago 24.7MB

872MB -> 25MB にまで縮小されました
かなり小さくなっているのがわかります

イメージの動作確認

念の為 Web アプリが起動するか確認してみましょう

  • docker run -d -p 4567:4567 --name my_app my_app.slim

あとは 4567 ポートにアクセスして OK が返ってくるか確認しましょう

  • curl localhost:4567

動作確認できたら停止しておきましょう

  • docker stop my_app
  • docker rm my_app

Web アプリでないとダメなのか

単純に EXPOSE されているイメージであればいけるのかなと思いやってみました

  • vim Dockerfile
FROM ruby

EXPOSE 4567
  • docker build -t my_app2 .
  • docker-slim build --http-probe my_app2

結果はダメでした
docker-slim の途中で EXPOSE したポートにアクセスするときにレスポンスが返ってこないためエラーになりました
「docker-slim[build]: info=http.probe.call status=error method=GET target=http://127.0.0.1:32780/ attempt=1 error='Get http://127.0.0.1:32780/: EOF' time=2019-12-11T23:36:21Z」

やはりスリム化するためには Web アプリのように何かしらのプロセスでコンテナが LISTEN している必要があるようです
ただ、そこまで仕組みなどを調査していないので他に方法があるかもしれません
サンプル もいろいろとあるので興味があれば見てください

最後に

docker-slim を試してみました
すでに Web アプリがある場合は導入が簡単なのですぐにでも適用できると思います
スリム化後は別のイメージとしてできあがるのでコンテナを起動する際は小さくなったイメージからコンテナを生成するようにしましょう
そうしないと大きいサイズのイメージを使い続けることになるのでせっかくイメージを小さくしたのに大きいサイズのイメージを削除することができません

現状は Web アプリ限定な感じがあるのでポートを EXPOSE しないようなイメージの場合は適用できないかなと思います
小さくするためだけに無理やり EXPOSE するのも微妙かなと思います

参考サイト