The old Twitter API V1.0 is just retired. The new API V1.1 requires OAuth authentication for each Twitter request. Our SharePoint Twitter WebPart is no longer working since this API update. I created a SharePoint layout page as a proxy connecting to Twitter using the new API so the Twitter services are available in our Intranet without the troublesome OAuth authentication. Also the proxy page caches the result locally for performance consideration and avoids exceeding the new Twitter request limit:
<%@ Page Language="C#" %> <%@ Import Namespace="System.Web.UI" %> <%@ Import Namespace="System.Collections.Generic" %> <%@ Import Namespace="System.Globalization" %> <%@ Import Namespace="System.Security.Cryptography" %> <%@ Import Namespace="System.Net.Security" %> <%@ Import Namespace="System.Net" %> <%@ Import Namespace="System.IO" %> <%@ Import Namespace="System.Text" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>A proxy page for Twitter API V1.1</title> <meta name="ROBOTS" content="NOINDEX, NOFOLLOW"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-language" content="en"> <script runat="server"> // oauth application keys string consumerKey = "xxxxxxxx"; string consumerSecret = "xxxxxxxx"; string accessToken = "xxxxxxxx"; string accessTokenSecret = "xxxxxxxx"; int cacheTime = 5; // Cache time in minutes string baseUrl = "https://api.twitter.com/1.1/"; void Page_Load(object sender, System.EventArgs e) { // handle parameters bool noCache = Request.QueryString["nocache"] == "true" ? true : false; string endpoint= string.IsNullOrEmpty(Request.QueryString["endpoint"]) ? "statuses/user_timeline.json" : Request.QueryString["endpoint"]; string[] resStrings = new string[] { "endpoint", "nocache", "callback", "_" }; List<string> reservedParameters = new List<string>(resStrings); Dictionary<string, string> parameters = new Dictionary<string, string>(); StringBuilder keys = new StringBuilder(); foreach (String key in Request.QueryString.AllKeys) { if (!reservedParameters.Contains(key) && !parameters.ContainsKey(key)) { parameters.Add(key, Request.QueryString[key]); keys.Append(key + "=" + Request.QueryString[key] + "|"); } } string cacheKey = keys.ToString(); if (string.IsNullOrEmpty(cacheKey)) // simply return if no parameter provided { lblInfo.Text = "Invalid parameters"; return; } string tweets = Convert.ToString(Cache[cacheKey]); if (noCache || string.IsNullOrEmpty(tweets)) // check if cache exsits { string requestUrl = baseUrl + endpoint; try { tweets = GetTweets(requestUrl, parameters); // Update cache Cache.Insert(cacheKey.ToString(), tweets, null, DateTime.Now.AddMinutes(cacheTime), System.Web.Caching.Cache.NoSlidingExpiration); } catch (Exception ex) { lblInfo.Text = "Error occur: " + ex.Message; return; } } // prepare for writting data to Response Response.Clear(); Response.ContentType = "application/json; charset=utf-8"; Response.ContentEncoding = System.Text.Encoding.UTF8; if (!string.IsNullOrEmpty(Request.QueryString["callback"])) // wrap data for JSONP Response.Write(string.Format("{0}({1})", Request.QueryString["callback"], tweets)); else Response.Write(tweets); Response.End(); } // Reference: https://dev.twitter.com/discussions/15206 string GetTweets(string url, Dictionary<string, string> parameters) { string responseString = string.Empty; StringBuilder queryStrings = new StringBuilder(); // OAuth setting string oauthSignatureMethod = "HMAC-SHA1"; string oauthVersion = "1.0"; string oauthNonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString())); TimeSpan timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); string oauthTimestamp = Convert.ToInt64(timeSpan.TotalSeconds).ToString(); string compositeKey = Uri.EscapeDataString(consumerSecret) + "&" + Uri.EscapeDataString(accessTokenSecret); // OAauth signature: https://dev.twitter.com/docs/auth/creating-signature string oauthSignature; SortedList<string, string> authSigBaseValues = new SortedList<string, string>(); authSigBaseValues.Add("oauth_consumer_key", consumerKey); authSigBaseValues.Add("oauth_nonce", oauthNonce); authSigBaseValues.Add("oauth_signature_method", oauthSignatureMethod); authSigBaseValues.Add("oauth_timestamp", oauthTimestamp); authSigBaseValues.Add("oauth_token", accessToken); authSigBaseValues.Add("oauth_version", oauthVersion); foreach (string key in parameters.Keys) { string escapedKey = Uri.EscapeDataString(key); string escapedValue = Uri.EscapeDataString(parameters[key]); authSigBaseValues.Add(escapedKey, escapedValue); queryStrings.Append("&" + escapedKey + "=" + escapedValue); } // build signagure base string StringBuilder oauthSigSB = new StringBuilder(); foreach (KeyValuePair<string, string> item in authSigBaseValues) { oauthSigSB.Append("&" + item.Key + "=" + item.Value); } string signatureBaseString = "GET&" + Uri.EscapeDataString(url) + "&" + Uri.EscapeDataString(oauthSigSB.ToString().Remove(0, 1)); // create OAuth signature using (HMACSHA1 hasher = new HMACSHA1(ASCIIEncoding.ASCII.GetBytes(compositeKey))) { oauthSignature = Convert.ToBase64String(hasher.ComputeHash(ASCIIEncoding.ASCII.GetBytes(signatureBaseString))); } // create the request header string headerFormat = "OAuth oauth_nonce=\"{0}\", oauth_signature_method=\"{1}\", oauth_timestamp=\"{2}\", " + "oauth_consumer_key=\"{3}\", oauth_token=\"{4}\", oauth_signature=\"{5}\", oauth_version=\"{6}\""; string authHeader = string.Format(headerFormat, Uri.EscapeDataString(oauthNonce), Uri.EscapeDataString(oauthSignatureMethod), Uri.EscapeDataString(oauthTimestamp), Uri.EscapeDataString(consumerKey), Uri.EscapeDataString(accessToken), Uri.EscapeDataString(oauthSignature), Uri.EscapeDataString(oauthVersion) ); if (queryStrings.Length > 0) url = url + "?" + queryStrings.ToString().Remove(0, 1); ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); ServicePointManager.Expect100Continue = false; // create request HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Headers.Add("Authorization", authHeader); request.Method = "GET"; request.ContentType = "application/x-www-form-urlencoded"; try { WebResponse response = request.GetResponse(); responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); } catch (Exception ex) { throw ex; } return responseString; } </script> </head> <body> <form id="form1" runat="server"> <div> <p> <asp:Label ID="lblInfo" runat="server"></asp:Label> </p> </div> </form> </body> </html>
The proxy page can work as a regular ASP.NET page inside IIS. It can also run as a layout page in SharePoint: simply copy the file to the layouts folder under SharePoint 12/14 hive. and it will just work.
The proxy accepts different endpoint and parameter where the endpoint is the twitter service endpoint such as "statuses/user_timeline.json" or "search/tweets.json", and parameter is the query strings you pass to twitter such as "screen_name=mytwittername". The easiest way to consume the proxy Twitter servcie is using JavaScript which can be inserted into SharePoint OOB content editor WebPart:
<script type="text/javascript"> <!-- $(document).ready(function() { // twitter proxy URL var url = 'http://twitterproxy/twitter.aspx?endpoint=statuses/user_timeline.json&screen_name=myname&count=10'; // http://twitterproxy/twitter.aspx?endpoint=search/tweets.json&q=from:myname&result_type=recent $.ajax({url : url, cache: false, crossDomain: true, dataType: 'jsonp'}).done(function(data) { $('#tweeters').html(""); $.each(data, function(i, tweet) { if(tweet.text) { var date = parseDate(tweet.created_at); var tweet_html = '<div><div class="tweetText">' + tweet.text + '</div>'; tweet_html += '<div class="tweetDate">'+ date.toString().substring(0, 24) +'</div></div>'; $('#tweeters').append(tweet_html); } }); }); }); // Fix IE DateTime format issue function parseDate(str) { var v=str.split(' '); return new Date(Date.parse(v[1]+" "+v[2]+", "+v[5]+" "+v[3]+" UTC")); } // --> </script>