问题
I'm uploading photos to TwitPic, using OAuth, from a .NET app written in C#.
The oAuth stuff is a little tricky. I found two bits of .NET code to handle it, but wasn't satisfied with either. DotNetOpenAuth seemed to be pretty heavy, more than I need. (Just wanna do oAuth signatures and token requests). The OAuthBase.cs code seemed confused and inelegant to me. I had to pass 6 string parameters to methods, and if I got any out of order, woe be unto me.
So I wrote some code to do it myself, it's fairly small and it seems to work. It works to acquire "request tokens". It works to pop the authorize web page and to acquire "access tokens". It also works to upload photos to TwitPic.
All the HTTP responses come back as 200 or 201.
The upload HTTP message looks like this:
POST http://api.twitpic.com/2/upload.json HTTP/1.1
Content-Type: multipart/form-data; boundary=48cb9a6d-1f1d-432d-b6e3-307e32e8228a
X-Auth-Service-Provider: https://api.twitter.com/1/account/verify_credentials.json
X-Verify-Credentials-Authorization: OAuth realm="http://api.twitter.com/",
oauth_consumer_key="Dv1er93yKzEMn74hZfPmJA",
oauth_nonce="51fi305k",
oauth_signature="4oWcmZcd%2F%2F81JslJ70xFXFm8%2BQs%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1292277715",
oauth_token="59152613-z8EP4GoYS1Mgo3E29JfIqBnyTRlruAJs8Bkvs3q0T",
oauth_version="1.0"
Host: api.twitpic.com
Content-Length: 14605
Expect: 100-continue
Connection: Keep-Alive
--48cb9a6d-1f1d-432d-b6e3-307e32e8228a
Content-Disposition: file; name="media"; filename="CropperCapture[10].jpg"
Content-Type: image/jpeg
....
--48cb9a6d-1f1d-432d-b6e3-307e32e8228a
Content-Disposition: form-data; name="key"
twitpic-api-key-here
--48cb9a6d-1f1d-432d-b6e3-307e32e8228a
Content-Disposition: form-data; name="message"
uploaded from Yappy. (at 12/13/2010 5:01:55 PM)
--48cb9a6d-1f1d-432d-b6e3-307e32e8228a--
The json I get back from the upload is like this:
{"id":"3f0jeiw5",
"text":"uploaded from Yappy. (at 12\/13\/2010 5:01:55 PM)",
"url":"http:\\/twitpic.com\/3f0jeiw5",
"width":257,
"height":184,
"size":14156,
"type":"jpg",
"timestamp":"Mon, 13 Dec 2010 22:02:06 +0000",
"user":{
"id":54591561,"screen_name":"bfavre"}
}
But the problem is, after I upload the image to Twitpic, the image is available on TwitPic, but the associated message never appears on Twitter.
What gives?
I read in a random blog post that using TwitPic+oAuth requires me to post the tweet message in a separate HTTP transaction, direct to Twitter. huh? I thought the mail purpose of oAuth was to allow consumers to do things on my behalf - like allowing TwitPic to post a tweet for me.
Any hints?
EDIT
I'm learning a little more here. This blog post from May 2010 suggests to me that using a value for X-Auth-Service-Provider
of https://api.twitter.com/1/account/verify_credentials.json
tells TwitPic to call "verify_credentials.json" on twitter.com when it gets my request. If it really IS just verifying my credentials, this would explain why no Tweet is posted.
The post also suggests that swapping that out and replacing it with https://api.twitter.com/1/status/update.json
should allow me to update Twitter via TwitPic with delegation. But it is a forward-looking post - it says that getting this capability requires work on Twitter's part.
I haven't found an example HTTP message that does this yet. Anyone?
UPDATE
After converting the verify URL to https://api.twitter.com/1/status/update.json
and using POST for the signature, I get a 401 response code:
{"errors":
[{"code":401,
"message":"Could not authenticate you (header rejected by twitter)."}]
}
This is basically the same problem as described here, in the twitter dev forum. The suggestion at the end of that thread was that the signature-computation algorithm is wrong, but I think that's incorrect, as my sig algorithm works with all other requests.
回答1:
I have been working on using Twitpic API to generate the oAuth integration and found that I can get the image posted and Twitter comment sent with the image from Twitpic. This posts an image to the twitter user account of our Eplixo (http://eplixo.com/m/) video chat and twitter client
However I can't seem to get the response. Now I can live with it posting and uploading but it would be useful to figure out how to get the response data for other parts of the application.
Here is what I have. It is a variation on the Twipli API wrapper
protected void Button1_Click(object sender, EventArgs e)
{
string ct = img.PostedFile.ContentType.ToString();
string usertoken = Session["usrToken"].ToString();
string userSecret = Session["usrSecret"].ToString();
string conkey = Session["ConsumerKey"].ToString();
string consecret = Session["ConsumerSecret"].ToString();
string twitkey = Session["twitpickey"].ToString();
string _m = m.Text; // This takes the Tweet to be posted
HttpPostedFile myFile = img.PostedFile;
string fileName = myFile.FileName.ToString();
int nFileLen = myFile.ContentLength;
byte[] myData = new byte[nFileLen];
myFile.InputStream.Read(myData, 0, nFileLen);
TwitPic tw = new TwitPic();
upres.Text = tw.UploadPhoto(myData, ct, _m, fileName, twitkey, usertoken, userSecret, conkey, consecret).ToString();
Response.Redirect("twittercb.aspx?oauth_verifier=none");
}
public class TwitPic
{
private const string TWITPIC_UPLADO_API_URL = "http://api.twitpic.com/2/upload";
private const string TWITPIC_UPLOAD_AND_POST_API_URL = "http://api.twitpic.com/1/uploadAndPost";
///
/// Uploads the photo and sends a new Tweet
///
/// <param name="binaryImageData">The binary image data.
/// <param name="tweetMessage">The tweet message.
/// <param name="filename">The filename.
/// Return true, if the operation was succeded.
public string UploadPhoto(byte[] binaryImageData, string ContentType, string tweetMessage, string filename, string tpkey, string usrtoken, string usrsecret, string contoken, string consecret)
{
string boundary = Guid.NewGuid().ToString();
string requestUrl = String.IsNullOrEmpty(tweetMessage) ? TWITPIC_UPLADO_API_URL : TWITPIC_UPLOAD_AND_POST_API_URL;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
string encoding = "iso-8859-1";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
request.Method = "POST";
string header = string.Format("--{0}", boundary);
string footer = string.Format("--{0}--", boundary);
StringBuilder contents = new StringBuilder();
contents.AppendLine(header);
string fileContentType = ContentType;
string fileHeader = String.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", "media", filename);
string fileData = Encoding.GetEncoding(encoding).GetString(binaryImageData);
contents.AppendLine(fileHeader);
contents.AppendLine(String.Format("Content-Type: {0}", fileContentType));
contents.AppendLine();
contents.AppendLine(fileData);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "key"));
contents.AppendLine();
contents.AppendLine(tpkey);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "consumer_token"));
contents.AppendLine();
contents.AppendLine(contoken);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "consumer_secret"));
contents.AppendLine();
contents.AppendLine(consecret);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "oauth_token"));
contents.AppendLine();
contents.AppendLine(usrtoken);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "oauth_secret"));
contents.AppendLine();
contents.AppendLine(usrsecret);
if (!String.IsNullOrEmpty(tweetMessage))
{
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "message"));
contents.AppendLine();
contents.AppendLine(tweetMessage);
}
contents.AppendLine(footer);
byte[] bytes = Encoding.GetEncoding(encoding).GetBytes(contents.ToString());
request.ContentLength = bytes.Length;
string mediaurl = "";
try
{
using (Stream requestStream = request.GetRequestStream()) // this is where the bug is due to not being able to seek.
{
requestStream.Write(bytes, 0, bytes.Length); // No problem the image is posted and tweet is posted
requestStream.Close();
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) // here I can't get the response
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string result = reader.ReadToEnd();
XDocument doc = XDocument.Parse(result); // this shows no root elements and fails here
XElement rsp = doc.Element("rsp");
string status = rsp.Attribute(XName.Get("status")) != null ? rsp.Attribute(XName.Get("status")).Value : rsp.Attribute(XName.Get("stat")).Value;
mediaurl = rsp.Element("mediaurl").Value;
return mediaurl;
}
}
}
}
catch (Exception ex)
{
ex.ToString();
}
return mediaurl;
}
}
回答2:
I don't know the final answer, but I think that the changes that were described as coming "Real Soon Now" in Raffi's blog post of May 2010, have not actually been made, for whatever reason.
So in fact, using OAuth, you do have to post separately to TwitPic and to Twitter.
It's not hard to do this in Twitter, though. Just do a POST on the statuses/update.xml URL.
private void Tweet(string message)
{
var twitterUpdateUrlBase = "http://api.twitter.com/1/statuses/update.xml?status=";
var url = twitterUpdateUrlBase + UrlEncode(message);
var authzHeader = oauth.GenerateAuthzHeader(url, "POST");
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);
using (var response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode != HttpStatusCode.OK)
MessageBox.Show("There's been a problem trying to tweet:" +
Environment.NewLine +
response.StatusDescription +
Environment.NewLine +
Environment.NewLine +
"You will have to tweet manually." +
Environment.NewLine);
}
}
There are two tricky parts to that code: First is the UrlEncode() call. OAuth specifies that the urlencoding must use uppercase letters. The built-in .NET routine uses lowercase. So be sure to upcase.
The second tricky part is is getting the OAuth Authorization Header. If you use an OAuth library package it should be pretty simple. For those who want a simple one, you can get one here: OAuth.cs. (Get the OAuthManager downloaD)
来源:https://stackoverflow.com/questions/4434576/using-twitpic-oauth-to-upload-a-photo-tweet-to-twitter-net-c-why-no-tw