ついっぷるフォトに画像をアップロードする (OAuth Echo認証APIを利用) - C#

注意
この記事で紹介している方法は 2010年時点の方法です。現在はついっぷるフォトはサービス終了のため動作しませんので、ご注意ください。
OAuth Echoの仕組みを利用して ついっぷるフォトに画像をアップロードしてみます。
OAuthを使ってTwitterにツイートを投稿する際に利用したライブラリ(oAuthBase.csなど)を使います。

Twitter へのアプリケーション登録とアクセスキーの取得

Twitterへのアプリケーション登録とアクセスキーを取得する必要があります。手順はデスクトップアプリケーションの場合はこちら、ASP.NETなどのwebアプリケーションの場合はこちらです。

ついっぷるフォトにAPIを使って画像をアップロードする手順の概要

以下の手順でついっぷるフォトに画像をアップロードします。基本はこちらのYFrogの古い認証方式APIを使ったアップロード方法とほぼ同じになります。
  1. TwitterのOAuthのシグネチャーを取得します。(OAuthBase.csのGenerateSignature()を使います。) 生成したシグネチャーとTimeStamp,nonceの3つをverify_urlの値として使います。
  2. ついっぷるフォトへ画像を投稿(ポスト)するためのHttpWebRequestを準備します。これをwebRequestとします
  3. 本文(content)を作成します。
    1. authパラメータの作成 (authパラメータは省略できることになっていますが、今回はパラメータを入力します。)
      1. バウンダリのヘッダを挿入します。ヘッダの形式は "--(バウンダリ文字列)" となります。バウンダリ文字列はユニークな文字列にするため、Guid.NewGuid()メソッドで作成します。
      2. Content-Disposition: form-data; name="auth" ヘッダを挿入します。
      3. Content-Type: text/plain; charset=US-ASCII ヘッダを挿入します。
      4. Content-Transfer-Encoding: 8bit ヘッダを挿入します。(ついっぷるフォトではJavaのHttpClient,FilePartの実装にそろえることが好ましいため、Content-Type: Content-Transfer-Encoding:のヘッダを挿入します。)
      5. 空行を挿入しヘッダを終了します
      6. 文字列"oauth"を挿入します。
    2. verify_urlの作成
      1. バウンダリのヘッダを挿入します。
      2. Content-Disposition: form-data; name="verify_url" ヘッダを挿入します。
      3. Content-Type: text/plain; charset=US-ASCII ヘッダを挿入します。
      4. Content-Transfer-Encoding: 8bit ヘッダを挿入します。(ついっぷるフォトではJavaのHttpClient,FilePartの実装にそろえることが好ましいため、Content-Type: Content-Transfer-Encoding:のヘッダを挿入します。)
      5. 空行を挿入しヘッダを終了します
      6. verify_urlを挿入します。verify_urlについては後述します。
    3. 画像(media)の作成
      1. バウンダリのヘッダを挿入します。
      2. Content-Disposition: form-data; name="media"; filename="ファイル名" ヘッダを追加します。mediaはAPIで指定されているパラメータ名です。(ついっぷるフォトではJavaのHttpClient,FilePartの実装にそろえることが好ましいため、Content-Disposition: file; ではなくContent-Disposition: form-data;としたほうが良いようです。)
      3. Content-Type: image/jpeg のContent-Typeヘッダを追加します。アップロードする画像の形式に合わせます。
      4. Content-Type: application/octet-stream; charset=ISO-8859-1 ヘッダを追加します。
      5. Content-Transfer-Encoding: binary ヘッダを追加します。
      6. 空行を挿入しヘッダを終了します
      7. 画像データを挿入します。
    4. バウンダリのフッタを追加します。フッタの形式は "--(バウンダリ文字列)--" となります。
  4. 本文を"iso-8859-1"でエンコードします。
  5. webRequest.GetRequestStream();メソッドを使い、書き込み用のストリームを取得します。
  6. エンコードした本文を書き込みます。
  7. レスポンスを取得し正常に書き込めたかをチェックします。

verify_urlについて

verify_urlの値は以下の通りです。
https://api.twitter.com/1/account/verify_credentials.xml?oauth_version=1.0&oauth_nonce=(シグネチャー生成時のnonce)&oauth_timestamp=(シグネチャー生成時のタイムスタンプ)&oauth_consumer_key=(TwitterのConsumerKey)&oauth_token=(TwitterのAcessToken)&oauth_signature_method=HMAC-SHA1&oauth_signature=(シグネチャー)
となります。
  • (TwitterのConsumerKey),(TwitterのAcessToken)はTwitterから取得したキーを設定します。
  • (シグネチャー生成時のタイムスタンプ),(シグネチャー生成時のnonce),(シグネチャー)はOAuthのシグネチャー生成時のものを使います。
  • oauth_signatureパラメータに設定するシグネチャーはURLエンコードしません。

oauth_signatureパラメータはURLエンコードしない処理について

前節で「oauth_signatureパラメータに設定するシグネチャーはURLエンコードしません。」と記載してありますが、その後情報提供があり、URLエンコードした結果の「%3d」のdを大文字に変更すると動作するとの指摘がありました。
実際に試してみたところ、%3dを%3Dに置換するとアップロードできます。

oauth_signatureの値が
Z5AVfg33RphjJHf99HJUaGEforJd=
のときは正しくアップロードでき

Z5AVfg33RphjJHf99HJUaGEforJd%3d
のときは失敗し(エラーコード1001)

Z5AVfg33RphjJHf99HJUaGEforJd%3D
のときは正しくアップロードできるようです。

"%2b"の処理

その後さらに指摘があり、Base64エンコード文字に含まれる「+」の文字も同様の可能性があるため%2bを%2Bに置換する必要もあるのではないかとのことです。

プログラムの実装

Oauthを使ってTwitterのAPIを呼び出すライブラリのコードに以下を実装します。

oAuthBase.csへの実装

変更はありません。

oAuthTwitter.csへの実装

public string oAuthEchoWebRequestForTwipplePhoto(
  Method method, string url, string auth_provider_url, 
  byte[] image, string imageName, Encoding ContentEncoding, string FileContentType)
{
  HttpWebRequest webRequest = null;
  string responseData = "";

  webRequest = System.Net.WebRequest.Create(url) as HttpWebRequest;
  webRequest.Method = method.ToString();
  webRequest.ServicePoint.Expect100Continue = false;

  string boundary = Guid.NewGuid().ToString();
  webRequest.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);

  //Generate Signature
  string outUrl = "";
  string querystring = "";
  string ret = "";

  Uri uri = new Uri(auth_provider_url);
  string nonce = this.GenerateNonce();
  string timeStamp = this.GenerateTimeStamp();
  string sig = this.GenerateSignature(uri,
      this.ConsumerKey,
      this.ConsumerSecret,
      this.Token,
      this.TokenSecret,
      "GET",
      timeStamp,
      nonce,
      out outUrl,
      out querystring);

  string credentialPattern = "oauth_version=1.0&oauth_nonce={0:s}&oauth_timestamp={1:s}"
    + "&oauth_consumer_key={2:s}&oauth_token={3:s}"
    + "&oauth_signature_method=HMAC-SHA1&oauth_signature={4:s}";

  //oauth_signatureをURLエンコードし、%3dを%3Dに、%2bを%2B置換して処理する場合。
  string UrlEncodeSig=HttpUtility.UrlEncode(sig);
  UrlEncodeSig = UrlEncodeSig.Replace("%3d", "%3D");
  UrlEncodeSig = UrlEncodeSig.Replace("%2b", "%2B");
  string credential = string.Format(credentialPattern, nonce, timeStamp,
    this.ConsumerKey, this.Token, UrlEncodeSig);
  
  /*
  //oauth_signatureをURLエンコードしない場合
  string credential = string.Format(credentialPattern,
    nonce, timeStamp, this.ConsumerKey, this.Token, sig);
  */
  string CredentialURL = auth_provider_url + "?" + credential;

  /*
  string credentialPattern = "oauth_consumer_key={0:s}&&oauth_signature_method=HMAC-SHA1"
    + "&oauth_timestamp={1:s}&oauth_nonce={2:s}&oauth_version=1.0&oauth_token={3:s}"
    + "&oauth_signature={4:s}";
  string credential = string.Format(
    credentialPattern, this.ConsumerKey, timeStamp, nonce, this.Token, sig);
  string CredentialURL = auth_provider_url + "?" + credential;
  */

  if (method == Method.POST) {
    string header = string.Format("--{0}", boundary);
    string footer = string.Format("--{0}--", boundary);

    StringBuilder contents = new StringBuilder();

    //string fileHeader = string.Format(
    //  "Content-Disposition: file; name=\"{0:s}\"; filename=\"{1:s}\"", "media", imageName);

    contents.AppendLine(header);
    contents.AppendLine(
      string.Format("Content-Disposition: form-data; name=\"{0:s}\"", "auth"));
    contents.AppendLine("Content-Type: text/plain; charset=US-ASCII");
    contents.AppendLine("Content-Transfer-Encoding: 8bit");
    contents.AppendLine();
    contents.AppendLine("oauth");

    contents.AppendLine(header);
    contents.AppendLine(
      string.Format("Content-Disposition: form-data; name=\"{0:s}\"", "verify_url"));
    contents.AppendLine("Content-Type: text/plain; charset=US-ASCII");
    contents.AppendLine("Content-Transfer-Encoding: 8bit");
    contents.AppendLine();
    contents.AppendLine(CredentialURL);

    /*
    contents.AppendLine(header);
    contents.AppendLine(
      string.Format("Content-Disposition: form-data; name=\"{0:s}\"", "username"));
    contents.AppendLine("Content-Type: text/plain; charset=US-ASCII");
    contents.AppendLine("Content-Transfer-Encoding: 8bit");
    contents.AppendLine();
    contents.AppendLine("");

    contents.AppendLine(header);
    contents.AppendLine(
      string.Format("Content-Disposition: form-data; name=\"{0:s}\"", "password"));
    contents.AppendLine("Content-Type: text/plain; charset=US-ASCII");
    contents.AppendLine("Content-Transfer-Encoding: 8bit");
    contents.AppendLine();
    contents.AppendLine("");
    */

    string fileHeader = string.Format(
      "Content-Disposition: form-data; name=\"{0:s}\"; filename=\"{1:s}\"", "media", imageName);
    string fileData = ContentEncoding.GetString(image);
            
    contents.AppendLine(header);
    contents.AppendLine(fileHeader);
    contents.AppendLine(
      string.Format("Content-Type: {0:s}; charset=ISO-8859-1", FileContentType));
    contents.AppendLine("Content-Transfer-Encoding: binary");
    contents.AppendLine();
    contents.AppendLine(fileData);

    contents.AppendLine(footer);

    byte[] bytes = ContentEncoding.GetBytes(contents.ToString());
    webRequest.ContentLength = bytes.Length;

    Stream requestWriter = webRequest.GetRequestStream();
    try {
      requestWriter.Write(bytes, 0, bytes.Length);
    }
    catch {
      throw;
    }
    finally {
      requestWriter.Close();
      requestWriter = null;
    }
  }

  responseData = WebResponseGet(webRequest);
  webRequest = null;
  return responseData;
}

TwitterUtils.csへの実装

public void PostPictureToTwipplePhoto(byte[] image, string imageName,
  string ConsumerKey, string ConsumerSecret, string AccessToken, string AccessTokenSecret,
  string PictureContentType)
{
  string twipple_photo_api_url = "http://p.twipple.jp/api/upload";
  string auth_provider_url = "https://api.twitter.com/1/account/verify_credentials.xml";

  string contentEncodingStr = "iso-8859-1";
  Encoding contentEncoding = Encoding.GetEncoding(contentEncodingStr);

  oAuth.ConsumerKey = ConsumerKey;
  oAuth.ConsumerSecret = ConsumerSecret;
  oAuth.Token = AccessToken;
  oAuth.TokenSecret = AccessTokenSecret;

  oAuth.oAuthEchoWebRequestForTwipplePhoto(oAuthTwitter.Method.POST, 
    twipple_photo_api_url, auth_provider_url, image, imageName, comment,
    TwitpicKey, contentEncoding, PictureContentType);
}

呼び出し側アプリケーションの実装

private void button2_Click(object sender, EventArgs e)
{
  string PictureContentType="application/octet-stream";
  //string PictureContentType="image/jpeg"; //または "image/png"

  string apppath = Path.GetDirectoryName(Application.ExecutablePath);
  FileStream  fs = new FileStream(apppath + @"\img\pic.jpg", FileMode.Open);
  byte[] img = new byte[fs.Length];
  int r = fs.Read(img, 0, (int)fs.Length);

  TwitterUtils tu = new TwitterUtils();
  tu.PostPictureToTwipplePhoto(img, "pic.jpg", 
    ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, PictureContentType);
}
ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecretには取得したキーを代入しておきます。
アプリケーションを実行し、ボタンをクリックすると実行ファイルのあるフォルダの中のimgフォルダに置いてあるpic.jpgをついっぷるフォトにアップロードします。

送信するデータ形式

HTTP Header

Content-Length:30839
Content-Type:multipart/form-data; boundary=4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx
Host:192.168.0.1

HTTP Content

--4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx
Content-Disposition: form-data; name="auth"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

oauth
--4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx
Content-Disposition: form-data; name="verify_url"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

https://api.twitter.com/1/account/verify_credentials.xml?oauth_consumer_key=m2TZPL0l1d0opS9I2z9G7g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1234567890&oauth_nonce=1234567890&oauth_version=1.0&oauth_token=1234567890-ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMN&oauth_signature=ABCDEFGHIJKLMNOPQRSTUVWXYZ=
--4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx
Content-Disposition: form-data; name="media"; filename="pic.jpg"
Content-Type: application/octet-stream; charset=ISO-8859-1
Content-Transfer-Encoding: binary

(画像データ)
--4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx--

HTTP Content (oauth_signatureをURLエンコードし%3dを%3Dに置換した場合)

--4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx
Content-Disposition: form-data; name="auth"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

oauth
--4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx
Content-Disposition: form-data; name="verify_url"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

https://api.twitter.com/1/account/verify_credentials.xml?oauth_consumer_key=m2TZPL0l1d0opS9I2z9G7g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1234567890&oauth_nonce=1234567890&oauth_version=1.0&oauth_token=1234567890-ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMN&oauth_signature=ABCDEFGHIJKLMNOPQRSTUVWXYZ%3D
--4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx
Content-Disposition: form-data; name="media"; filename="pic.jpg"
Content-Type: application/octet-stream; charset=ISO-8859-1
Content-Transfer-Encoding: binary

(画像データ)
--4pRqQiqHK9EkpLjr6TpVIwirh3HL2z2ywkVHKGx--

著者
iPentecのプログラマー、最近はAIの積極的な活用にも取り組み中。
とっても恥ずかしがり。
最終更新日: 2024-01-07
作成日: 2010-10-20
iPentec all rights reserverd.