2016/04/30

GoogleSiteページをRubyで更新する

AppEngineやHerokuの無料枠ではオープンなサイトを立てることになる。もちろん、そこはプログラマブルなので認証を自分で組み立てればプライベート化は可能だ。
でも、Googleサイトならば設定でプライベートサイトをいくつも建てられる。自分で組まなくていい。

ここでの目的は、DDNSを使わずにいかに快適にプライベートサーバーへアクセスできるか?で、サイトページを自動更新できれば、どの端末からでも活用しやすいだろうと。

Goolge Sites API を使えば簡単にサイトの更新をプログラムから行える。っていう話ですが、説明やサンプルが充実しているのは、JavaとPythonだけ。じゃあrubyは?

rubyにも'gdata'gemがあるのだけど、残念ながら目的のSitesAPIはまだ?サポートされていないようですね。

それに認証手順のページもrubyに関しては雑というか古い?ような気がする。OAuth2.0での完全な流れが読み取りづらい。

素直にPythonで書いたほうが絶対楽ですが、ここは敢えてRubyでやってみたという内容です。

google-api-client
$ gem install google-api-client
で入れる。googleauth ・ jwt ・ oauth2 と芋づるで入る。

Using OAuth 2.0 for Web Server Applications
当たりを読みます。Google Cloud Platformのコンソールから適当なプロジェクトでOAuth2.0の認証クライアントIDを作る。
そこからダウンロードできる認証ファイル(JSONファイル)をそのまま使います。

このページにもRubyでの流れが書いてあるのですが、どういうわけか実用的な流れが書いてありません。Pythonガイドのように一度認証手順を踏んだら後はrefresh_tokenでaccess_token取得する流れを示して欲しかった。

OAuth2.0はサービスアカウント認証に比べ、どうしてもこの手順が必要なのが面倒ですが、Google的にはこれを推奨している。認証コードを受け取るサイト(redirect先)を自分で用意する事ができれば全てを自動化することも不可能ではないだろう。

refresh_tokenを記録するための最初の手順スクリプト
require 'google/api_client/client_secrets'
require 'google/api_client/auth/storage'
require 'google/api_client/auth/storages/file_store'

CLIENT_SECRETS_FILE = 'client_secrets.json'
CREDENTIAL_STORE_FILE = 'credential_store.json'

client_secrets = Google::APIClient::ClientSecrets.load(CLIENT_SECRETS_FILE)
auth_client = client_secrets.to_authorization
auth_client.scope = 'https://sites.google.com/feeds/'
puts auth_client.authorization_uri.to_s

puts 'code='
auth_client.code = gets
auth_client.fetch_access_token!

storage = Google::APIClient::Storage.new(
  Google::APIClient::FileStore.new(CREDENTIAL_STORE_FILE))

storage.write_credentials(auth_client)

ググッても中々この手順をシンプルに示してくれている記事を見つけることが出来なかった。

CLIENT_SECRETS_FILE=ダウンロードしたjsonファイル
CREDENTIAL_STORE_FILE=保存したいファイル名
を指定して実行すると、ブラウザでアクセスするためのURLが表示される。
puts auth_client.authorization_uri.to_s の部分ですね。

スクリプトは認証コード入力状態で待機。

そのURLブラウザでアクセス>Googleデフォルトのコード表示ページ(urn:ietf:wg:oauth:2.0:oob)に認証コードが表示されるので、それをコピペ。

auth_client.fetch_access_token! で認証され、auth_clientには次回用のrefresh_tokenも取得される。

後はこれをファイルにでも記録して再利用すればいい。そのAPIも用意されているので使ってみたのが最後のstrage部分。

Site API Protocol Guide
Sites APIをラップしてくれているRubyGemがなかったので、ここを頑張って読む。

gdataAPIとかで何とかならないのか?と調べたけれど、ラップされていないサービスAPIは結局Protocol Guideに書いてある手順と変わらない。ならば、ややこしい物を使うより素直にhttp gemを使うことにした。

認証済みファイルからaccess_token取得してサイトページ内容を更新するスクリプト
# coding: utf-8
require 'google/api_client/client_secrets'
require 'google/api_client/auth/storage'
require 'google/api_client/auth/storages/file_store'
require 'net/http'
require 'oga'

CREDENTIAL_STORE_FILE = 'credential_store.json'

# ファイルを読んで認証
credential = Google::APIClient::Storage.new(
  Google::APIClient::FileStore.new(CREDENTIAL_STORE_FILE))

auth_client = credential.authorize

authorization = "access_token=#{auth_client.access_token}"

feed = "https://sites.google.com/feeds/content/site/自分のサイト名"
feed += "?path=/home" # path=で更新したいページをパスで指定
feed += "&#{authorization}" # 認証access_tokenを付ける

# GET
uri = URI.parse(feed)
result = Net::HTTP.get URI.parse(feed)
xml = Oga.parse_xml result
entry = xml.at_xpath('feed/entry')
title = entry.at_xpath('title').text
etag = entry.attribute('gd:etag').value
url = entry.at_xpath('id').text

# ページ内容をファイルから読んで加工
xml = Oga.parse_xml File.open('entry.xml').read
entry = xml.at_xpath('entry')
entry.attribute('gd:etag').value = etag
entry.at_xpath('title').inner_text = title
body = entry.to_xml

# PUT
uri = URI.parse("#{url}?#{authorization}")
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
  request = Net::HTTP::Put.new uri
  request.set_content_type('application/atom+xml')
  request.body = body
  http.request request
end
p response

ページ内容テンプレートファイルは Updating an entry に記載されているものから若干修正する程度のものを使う。contentタグの中に変更内容を記述しておく。

PUT先のURLはfeedレスポンスのにあるhrefアトリビュートにあると書かれているが、エレメントが他にたくさんあるのでogaで探すのが面倒です。見た限り<id>が同じ内容なので、そっちを使うようにした。
gd:etagをちゃんとページ固有のETagにする必要があるので、オリジナルを取得してテンプレート側へセットする。

titleもセットしておく必要がある。違うtitleにしてしまうとfeedをGETするときのpath=のパス名も変更することになる。この辺はあまり汎用性を確保できない。

こうやって実際にやってみると、難しくないがリファレンスドキュメントだけでは理解しきれないのも事実。チュートリアルがもう少し優しければよかったなぁ。




titleとsites:pageName
titleとpath指定が対応していると書いてしまったが、間違いだったので訂正しておく。
pathに対応しているタグ(Element)は「sites:pageName」だった。これを省略するとtitleと同じ内容がpageNameに設定される仕組みだった。
titleはページの表示名で、ページのpathに相当するのがsites:pageNameということ。

タイトルに日本語を使用したい場合などは両方共指定する必要がある。
よってテンプレートのentryアトリビュートに
xmlns:sites='http://schemas.google.com/sites/2008'
を追加して、entry内に
<sites:pageName>home</sites:pageName>
  <title>ホーム</title>
の2つを設定する必要があった。

0 件のコメント:

コメントを投稿