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

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

まさくに

まさくに

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

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

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

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

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

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

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

 

開発環境

  • 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 ライフを!

 

LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。

Webサイト制作の実績・料金を見る

この記事のシェア数

まさくに
まさくに バックエンドエンジニア / 伊藤 正訓

漢字で書くと正訓。バックエンドのエンジニアです。静岡と石川に住んだことがあり、現在は千葉に住んでいます。誰かが作ったシステムに対しては、正常系だけを通るように並列処理やデッドロックが起きそうな処理を避けて操作する職業病があります。好きな色は紫、好きなキーボードの位置は「i」、好きなご当地ヒーローはセッシャー1です。

このメンバーの記事をもっと読む
とにかく動け!世界のAPIからこんにちは。 | 7 articles