はじめに
ツイートに際してはユーザー別のアクセストークンとアクセストークンシークレットが必要となります。
取得方法については、以下のページのソースを参考にしてください。
Rubyでgemを使わずに、twitterのアクセストークンを取得
ソースコード
※あくまで動かすようのサンプルです。ステータスチェックはきちんとやってくださいね。
※今回は行ってませんが、投稿の際、文字長のチェックが本来必要です。
※twitterの仕様で連続して同じ内容の投稿はできません。(403が帰ってくるんじゃなかったかな?)
def post_tweet
#ユーザー別のアクセストークン
oauth_token = "194485223--oauth_token --ynck19"
#ユーザー別のアクセストークンシークレット
oauth_token_secret = "BopAEWj--oauth_token_secret--DKy2iei"
res = post_time_line(oauth_token, oauth_token_secret)
#画面に投稿文が表示される
render text: res["text"]
end
def post_time_line(oauth_token, oauth_token_secret)
api_key = 'hogehogehogehoge'
api_secret = 'fugofugofugofugo'
post_params = {
## チルダはエスケープしない方がよいらしい
##ERB::Utilは~をエスケープする→RFC3986だとしないのが正しい
"status" => escape("API_TEST ~+ ちるだプラス入れたろ!").gsub("%7E", "~")
}
oauth_params = {
"oauth_consumer_key" => URI.escape(api_key),
"oauth_nonce" => URI.escape(SecureRandom.uuid),
"oauth_signature_method" => URI.escape('HMAC-SHA1'),
"oauth_timestamp" => URI.escape(Time.now.to_i.to_s),
"oauth_token" => oauth_token,
"oauth_version" => URI.escape('1.0')
}
params = post_params.merge(oauth_params)
signature_key = "#{URI.escape(api_secret)}&#{URI.escape(oauth_token_secret)}"
method = 'POST'
request_url = "https://api.twitter.com/1.1/statuses/update.json"
request_params = sort_and_concat(params, "&")
## チルダはエスケープしない方がよいらしい
signature_data = "#{URI.escape(method)}&#{escape(request_url)}&#{escape(request_params).gsub("%7E", "~")}"
hash = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, signature_key, signature_data)
signature = escape(Base64.urlsafe_encode64(hash))
params["oauth_signature"] = signature
header_params = sort_and_concat(params, ',')
twitter_uri = URI.parse(request_url)
https = Net::HTTP.new(twitter_uri.host, twitter_uri.port)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
req = Net::HTTP::Post.new(twitter_uri)
req["Authorization"] = "OAuth #{header_params}"
https.set_debug_output $stderr
res = https.request(req, body = sort_and_concat(post_params, '&'))
json = JSON.parse(res.body)
return json
end
def sort_and_concat(params, delimiter)
params = params.sort
str = params.collect{|k, v| "#{k}=#{v}"}.join(delimiter)
return str
end
def escape(str)
return ERB::Util.url_encode(str)
end
はまったこと
投稿する文章によって、エラーが発生する。
当初、投稿内容を
"status" => "API_TEST ~+ ちるだプラス入れたろ!"
こんな感じにしていたところ、
+&=~ を投稿文に入れる
→401エラー
{"errors":[{"code":32,"message":"Could not authenticate you."}]}
% を投稿文に入れる
→:400エラー
といった具合にうまく動かない。
記号周りだからエスケープの問題だろということで
"status" => escape("API_TEST ~+ ちるだプラス入れたろ!").gsub("%7E", "~")
こんな感じにしました。
チルダ(~)以外、問題なく動くが、チルダだけはエラー
401エラー
{"errors":[{"code":32,"message":"Could not authenticate you."}]}
調べた結果、チルダのエスケープの問題らしく、以下に落ち着く
"status" => escape("API_TEST ~+ ちるだプラス入れたろ!").gsub("%7E", "~")
今回、エスケープはERB::Util.url_encodeを利用したのですが、
この子「a~z、A~Z、0~9、_(アンダースコア) -(ハイフン).(ピリオド)」以外はエスケープするらしい。
RFC3986によると
URIs that differ in the replacement of an unreserved character with
its corresponding percent-encoded US-ASCII octet are equivalent: they
identify the same resource. However, URI comparison implementations
do not always perform normalization prior to comparison (see Section
6). For consistency, percent-encoded octets in the ranges of ALPHA
(%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period (%2E),
underscore (%5F), or tilde (%7E) should not be created by URI
producers and, when found in a URI, should be decoded to their
corresponding unreserved characters by URI normalizers.
どうも「a~z、A~Z、0~9、_(アンダースコア) -(ハイフン).(ピリオド) ~(チルダ)」はエスケープすべきじゃないよと言ってるのかな?
というわけで、上記記述で問題はなさそう。
ここに書いて思ったけど、escapeメソッドに切り出してるんだから、そっちにgsubつけた方がよかったのかな…