Rubyでgemを使わずに、twitterにツイート(ポスト)する

#はじめに
ツイートに際してはユーザー別のアクセストークンとアクセストークンシークレットが必要となります。
取得方法については、以下のページのソースを参考にしてください。

[Rubyでgemを使わずに、twitterのアクセストークンを取得](http://qiita.com/take2isk/items/95f44c107e32c892ac9b)

#ソースコード
※あくまで動かすようのサンプルです。ステータスチェックはきちんとやってくださいね。
※今回は行ってませんが、投稿の際、文字長のチェックが本来必要です。
※twitterの仕様で連続して同じ内容の投稿はできません。（403が帰ってくるんじゃなかったかな？）

```rb:post_tweet.rb

  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](https://www.ietf.org/rfc/rfc3986.txt)によると

>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つけた方がよかったのかな…