NTTドコモ様_dカーシェア
NTTドコモ様_dカーシェア
2017.08.25
#5
とにかく動け!世界のAPIからこんにちは。

Pocketに記事ためすぎてない?どうせ後で読めないからPocket APIでSlackに定量通知しちゃう。【コード掲載】

まさくに

大人になったら、もう勉強しなくてもいいと思ってた。

これが公開されるころは夏休みも佳境というところでしょうか(執筆時は 7 月後半)。あの 8 月末日、友人宅でひたすら宿題を「処理」していたときのひぐらしの鳴く声は未だに悪夢で見ます。こんにちは。バックエンドエンジニアのまさくにです。

さて大人になるとペンとノートに向かうこと自体は減りましたが、日常的に様々な記事を読んで頭に入れておく必要が出てきました。読まなきゃ追いついていけない。そこで皆様も後で読む記事をストックしておけるように Pocket を使用されているのではないでしょうか。

でも気がついたら Pocket にも記事がいっぱい……。そもそも これポイポイ気軽に Pocket に入れているけれど、よく考えてみるとそもそもそんな重要なものってあったっけ? 何か定期的に一定数 Slack に URL 投げてくれれば、毎日少しずつ読むのになー。

それ作りましょう。API で。

名付けて、リボルビング読み。

目次

  1. 開発環境
  2. Pocketの管理画面でアプリケーションを登録する
  3. アクセストークンを取得する流れ
  4. アクセストークンを取得するコードを書く
  5. アクセストークンを取得する
  6. Pocket内の記事を取得する
  7. 取得した記事をSlackに投げる
  8. 実行
  9. まとめ

本稿のために作成したコードは こちら

 

開発環境

  • Sierra: 10.12.5
  • 今回の言語は ruby を使用
  • iTerm2 で実行
$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin15]

 

Pocketの管理画面でアプリケーションを登録する

Pocket の API を使用するものは Pocket で認められたアプリケーションである必要があります。そのため、Pocket の管理画面からアプリケーションの登録をしましょう。

ここ からアプリケーションの登録をします。

スクリーンショット 2017-07-26 16.04.45

「 CREATE NEW APP 」でアプリケーションを作成します。

項目を埋めましょう。Permissions はこのアプリケーションに Pocket の何を操作する権限を与えるかという設定です。すべてチェックしておきます。自分だけが使う想定ですし。

スクリーンショット 2017-07-25 15.43.14

アプリケーションが作成されました。

スクリーンショット 2017-07-25 15.44.05

ここに記載されている Web の CONSUMER_KEY がアプリケーションの ID になります。控えておいてください。

あれ? 何か Platforms は Web だけを選んだのに、全部のアプリケーションが作成されてますね? すみません、よく分かりません。

 

アクセストークンを取得する流れ

Pocket も最近の Web サービスと同様に API の認可に OAuth 2.0 を使用しています。そのため、API を使用するのに取得しなければならないのはアクセストークンです。先ほどの CONSUMER_KEY を物々交換することによって取得しなければなりません。

今回は自分の Mac 上に Web サーバーを立てます。Pocket からアクセストークンを取得する際は、下記のような流れになります。

pocket_authorize

 

アクセストークンを取得するコードを書く

今回は自分だけが使用するので、Twitter のようにどこかでアクセストークンくれないかなと思っていたのですが、見当たりませんでした。自分のアクセストークンも自分で GET しなければならないのですね。仕方ない。

再開発気味ですが、ローカルで Web サーバー( Webrick )を立てて、Pocket から正式にアクセストークンを取得したいと思います。

書いたのは下記のスクリプト。

▼server.rb:ビルトインサーバーの起動

require 'webrick'

system("export CONSUMER_KEY=#{ENV['CONSUMER_KEY']}")

srv = WEBrick::HTTPServer.new({
  :DocumentRoot => './',
  :BindAddress => '127.0.0.1',
  :Port => 8000,
  :CGIInterpreter => `which ruby`.strip!,
  :Logger => WEBrick::Log::new(STDOUT, WEBrick::Log::DEBUG),
})

srv.mount('/', WEBrick::HTTPServlet::CGIHandler, 'request.rb')
srv.mount('/authorize', WEBrick::HTTPServlet::CGIHandler, 'authorize.rb')

trap("INT"){ srv.shutdown }
srv.start

▼request.rb:リクエストトークンの取得とリダイレクト

require 'json'
require 'net/https'
require 'cgi'
require 'cgi/session'

cgi =  CGI.new

consumer_key = nil
if cgi.params.has_key?('consumer_key')
  consumer_key = cgi.params['consumer_key'].first.to_s
end

# get request_token
headers = {
  'Content-Type' =>'application/json; charset=UTF-8',
  'X-Accept' => 'application/json'
}

params = {:consumer_key => consumer_key, :redirect_uri => 'http://localhost:8000/authorize'}
data = params.map { |k, v| [k, v.to_s.encode('utf-8')] }.to_h

http = Net::HTTP.new('getpocket.com', 443)
http.use_ssl = true

req = Net::HTTP::Post.new('/v3/oauth/request', initheader = headers)
req.set_form_data(data)

res = http.request(req)

raise "error: cannot get response." unless res.is_a?(Net::HTTPOK)

# get code
res_json = JSON.parse(res.body)

# save code in session
session = CGI::Session.new(cgi)
session['consumer_key'] = consumer_key
session['code'] = res_json['code']

# redirect to pocket authorization
link = URI.escape("https://getpocket.com/auth/authorize?request_token=#{res_json['code']}&redirect_uri=http://localhost:8000/authorize")
print cgi.header({ 
  "status"     => "REDIRECT",
  "Location"   => link
})

▼authorize.rb:アクセストークンの表示

require 'json'
require 'net/https'
require 'cgi'
require 'cgi/session'

cgi = CGI.new
session = CGI::Session.new(cgi)

headers = {
  'Content-Type' =>'application/json; charset=UTF-8',
  'X-Accept' => 'application/json'
}

params = {:consumer_key => session['consumer_key'], :code => session['code']}
data = params.map { |k, v| [k, v.to_s.encode('utf-8')] }.to_h

http = Net::HTTP.new('getpocket.com', 443)
http.use_ssl = true

req = Net::HTTP::Post.new('/v3/oauth/authorize', initheader = headers)
req.set_form_data(data)

res = http.request(req)

raise "error: cannot get response." unless res.is_a?(Net::HTTPOK)

# show acces_code
res_json = JSON.parse(res.body)
cgi.out(:type => 'text/plain', :charset => 'UTF-8') {
  "access_token: " << res_json['access_token']
}

 

アクセストークンを取得する

下記で Web サーバーが起動します。

$ ruby server.rb
[2017-07-26 18:14:28] INFO  WEBrick 1.3.1
[2017-07-26 18:14:28] INFO  ruby 2.3.3 (2016-11-21) [x86_64-darwin15]
[2017-07-26 18:14:28] DEBUG WEBrick::HTTPServlet::FileHandler is mounted on /.
[2017-07-26 18:14:28] DEBUG WEBrick::HTTPServlet::CGIHandler is mounted on /.
[2017-07-26 18:14:28] DEBUG WEBrick::HTTPServlet::CGIHandler is mounted on /authorize.
[2017-07-26 18:14:28] INFO  WEBrick::HTTPServer#start: pid=21846 port=8000

Web サーバーが起動したら、下記 URL の YOUR_CONSUMER_KEY の部分を、先ほど取得した CONSUMER_KEY に書き換えて、ブラウザで開いてください。

http://localhost:8000/?consumer_key=YOUR_CONSUMER_KEY

スクリーンショット 2017-07-26 15.44.04

上記のように Pocket へログインしていれば、認可画面が表示されるかと思います。アイコンは設定しないと表示されないのかな……?

「認可」をクリックすると localhost に戻り、アクセストークンが表示されればひとまず成功です。表示されているアクセストークンは後で使用するので、これも控えてください。Web サーバが動いていると思うので、Ctrl + C で終了してください。

スクリーンショット 2017-07-26 16.25.51

毎回ここまでが長いんだよな……。

 

Pocket内の記事を取得する

先ほど取得した CONSUMER_KEY とアクセストークンを使用すると、自分が Pocket してある記事を取得できます。

▼get_articles.rb

require 'json'
require 'net/https'

CONSUMER_KEY=ENV['POCKET_CONSUMER_KEY']
POCKET_ACCESS_TOKEN=ENV['POCKET_ACCESS_TOKEN']

headers = {
  'Content-Type' =>'application/json; charset=UTF-8',
  'X-Accept' => 'application/json'
}

params = {:consumer_key => CONSUMER_KEY, :access_token => POCKET_ACCESS_TOKEN, :sort => "newest", :count => 2}
data = params.map { |k, v| [k, v.to_s.encode('utf-8')] }.to_h

http = Net::HTTP.new('getpocket.com', 443)
http.use_ssl = true

req = Net::HTTP::Post.new('/v3/get', initheader = headers)
req.set_form_data(data)

res = http.request(req)

raise "error: cannot get response." unless res.is_a?(Net::HTTPOK)

res_json = JSON.parse(res.body)

puts JSON.pretty_generate(res_json)

上記のスクリプトをコマンドラインで実行してみましょう。YOUR_CONSUMER_KEY に加え、YOUR_ACCESS_TOKEN は先ほどブラウザ上で取得したアクセストークンに書き換えてください。

$ POCKET_ACCESS_TOKEN='YOUR_ACCESS_TOKEN' POCKET_CUSTOMER_KEY='YOUR_CONSUMER_KEY' ruby get_articles.rb
{
  "status": 1,
  "complete": 1,
  "list": {
    "1825534674": {
      "item_id": "1825534674",
      "resolved_id": "1825534674",
      "given_url": "https://liginc.co.jp/362231",
      "given_title": "",
      "favorite": "0",
      "status": "0",
      "time_added": "1501046770",
      "time_updated": "1501046770",
      "time_read": "0",
      "time_favorited": "0",
      "sort_id": 0,
      "resolved_title": "それはぜんぶ夏のせい。怖かった体験を英語で話す #TOEIC350の英語力 #英会話 | スタートアップイングリッシュ",
      "resolved_url": "https://liginc.co.jp/362231",
      "excerpt": "いや……この暑さで英語とか……。2017、夏、暑すぎません? これ南国のフィリピンより暑いと思うから、先生たち絶対 “Japanese summer crazy” とか思うだろ。それから、まだ固着してない僕の英語脳が溶ける。阿呆の融点",
      "is_article": "1",
      "is_index": "0",
      "has_video": "0",
      "has_image": "1",
      "word_count": "527"
    },
・
・
(中略)
・
・
  },
  "error": null,
  "search_meta": {
    "search_type": "normal"
  },
  "since": 1501051487
}

 

取得した記事をSlackに投げる

これまでで取得できた記事を 2 つ、Slack へ投げるように機能追加します。

また、Slack に投げたものは自動的に Archive 化します。Slack への投稿は Gem を使わせてください。あと、Slack の使い方は割愛させていただきます。

▼Gemfile

source 'https://rubygems.org'

gem 'slack-api'

▼revolve_articles.rb

require 'json'
require 'net/https'
require 'slack'

CONSUMER_KEY=ENV['POCKET_CONSUMER_KEY']
POCKET_ACCESS_TOKEN=ENV['POCKET_ACCESS_TOKEN']

Slack.configure do |config|
  config.token = ENV['SLACK_API_TOKEN']
end

def request(host, path, params = {}, headers = {})
  data = params.map { |k, v| [k, v.to_s.encode('utf-8')] }.to_h
  
  http = Net::HTTP.new(host, 443)
  http.use_ssl = true
  
  req = Net::HTTP::Post.new(path, initheader = headers)
  req.set_form_data(data)
  
  res = http.request(req)
end

headers = {
  'Content-Type' =>'application/json; charset=UTF-8',
  'X-Accept' => 'application/json'
}

params = {:consumer_key => CONSUMER_KEY, :access_token => POCKET_ACCESS_TOKEN, :sort => "newest", :count => 2}
res = request('getpocket.com', '/v3/get', params, headers)

raise "error: cannot get response." unless res.is_a?(Net::HTTPOK)

res_json = JSON.parse(res.body)

unless res_json.has_key?('list')
  exit
end

res_json['list'].each do |id, article|

  # post to slack
  text = "Don't forget this biscuit in Pocket\n" << article['given_url']
  Slack.chat_postMessage(text: text, channel: '#news', as_user: true)

  # archive
  actions = JSON.generate([{ "action" => "archive", "item_id" => id.to_s }])
  params = {:consumer_key => CONSUMER_KEY, :access_token => POCKET_ACCESS_TOKEN, :actions => actions.to_s}
  res = request('getpocket.com', '/v3/send', params, headers)

  sleep(1) # pray
end

 

実行

それでは実行します。実行方法がとても頭悪い感じになっているので、本当に使おうと思ったら 適宜環境変数の管理をお願いします。

$ bundle install --path ./vendor # install
$ POCKET_ACCESS_TOKEN='YOUR_ACCESS_TOKEN' POCKET_CONSUMER_KEY='YOUR_CONSUMER_KEY' SLACK_API_TOKEN='YOUR_SLACK_API_KEY' bundle exec ruby revolve_articles.rb

▼Slackの指定されたチャンネル

スクリーンショット 2017-07-26 18.57.06

できたーーー!

実用的にはこれを cron などで回して、自分の隙間時間を見計らって日々通知してしまいましょう。毎日少しずつ読めば、無理なく「後で読む」を消化できるはずです。夏休みの宿題と同じですね。また Slack の有料会員になると、全ログ検索できますし、幸せかもしれません。

 

まとめ

ここまで書いてアレですけど、似たようなことなら PocketRocket でもできるようです。あと IFTTT 使っても同じようなことはできそうですよね。何となく初めから分かってはいました。

それでは車輪の再開発はやめて、良い API ライフを!