Jump to content

How to make Azure talk to Twitter

+ 2
  JonUdell's Photo
Posted Aug 10 2010 10:06 AM

Note: This series of how to articles is a companion to my elmcity series on the Radar blog, which chronicles what I've been learning as I build a calendar aggregation service on Azure.

Overview

The elmcity service doesn't (yet) require its users -- who are called curators -- to register in order to create and operate calendar hubs that aggregate calendar feeds. Instead it relies on several informal contracts involving partner services, including delicious, FriendFeed, and Twitter. Here's the Twitter contract:

The service will follow the curator at a specified Twitter account. By doing so, it enables the curator to send authentic messages to the service. The vocabulary used by those messages will initially be just a single verb: start. When the service receives the start message from a hub, it will re-aggregate the hub's list of feeds.

The code that supports this contract, Twitter.cs, only needs to do two things:

1. Follow curators' Twitter accounts from the service's Twitter account

2. Read direct messages from curators' accounts and transmit them to the service's account

Following a Twitter account

Here's the follow request:

    public static HttpResponse FollowTwitterAccount(string account)
    {
      var url = String.Format
        (
        "http://api.twitter.com/friendships/create/{0}.xml",
        account
        );
      var request = (HttpWebRequest)WebRequest.Create(new Uri(url));
      request.Method = "POST";
      var response = HttpUtils.DoAuthorizedHttpRequest
        (
        request,
        Configurator.twitter_account,
        Configurator.twitter_password,
        data: new byte[0]
        );
      return response;
    }
public static HttpResponse FollowTwitterAccount(string account) { var url = String.Format("http://twitter.com/friendships/create/{0}.xml", account); var request = (HttpWebRequest)WebRequest.Create(new Uri(url)); request.Method = "POST"; var response = HttpUtils.DoAuthorizedHttpRequest ( request, Configurator.twitter_account, Configurator.twitter_password, new byte[0] ); return response; }

Elsewhere I'll explore the HttpUtils class. It's a wrapper around the core .NET WebRequest and WebResponse classes that helps me focus on the HTTP mechanics underlying the various partner services that my service interacts with. But as you can see, this is just a simple and straightforward HTTP request for Twitter to follow the specified account on behalf of the service's own account.

Monitoring Twitter direct messages

The method that reads Twitter direct messages is more involved. Let's start with its return type, List. In C# you can make a generic List out of any type. In this case, I've defined a type called TwitterDirectMessage like so:

  public class TwitterDirectMessage
  {
    public string id { getset; }
    public string sender_screen_name { getset; }
    public string recipient_screen_name { getset; }
    public string text { getset; }

    public TwitterDirectMessage() { }

    public TwitterDirectMessage(string id, string sender_screen_name, 
      string recipient_screen_name, string text)
    {
      this.id = id;
      this.sender_screen_name = sender_screen_name;
      this.recipient_screen_name = recipient_screen_name;
      this.text = text;
    }
public class TwitterDirectMessage { public string id { get; set; } public string sender_screen_name { get; set; } public string recipient_screen_name { get; set; } public string text { get; set; } public TwitterDirectMessage() { } public TwitterDirectMessage(string id, string sender_screen_name, string recipient_screen_name, string text) { this.id = id; this.sender_screen_name = sender_screen_name; this.recipient_screen_name = recipient_screen_name; this.text = text; }

Define a custom type, or just use a dictionary?

It's worth noting that you don't have to define a type for this purpose. Originally, in fact, I didn't. When I'm working in Python I often don't define types but instead use dictionaries to model simple packages of data in a fluid way. You can do the same in C#. The first incarnation of this method was declared like so:

public static List<Dictionary<string,object>> TwitterDirectMessage(...)

In that version, the return type was also a generic List. Each item was a generic Dictionary whose keys and values were both of type String. Modeling a Twitter direct message that way avoids the overhead of declaring a new type, but makes it a little harder to keep track of things. Consider these two declarations:

      var dm_object = new TwitterDirectMessage
      (
        id: "123",
        sender_screen_name: "elmcity",
        recipient_screen_name: "judell",
        text: "hello!"
      );

      var dm_dict = new Dictionary<stringstring>() 
      {
        {"id" , "123"}, 
        {"sender_screen_name""elmcity"},
        {"recipient_screen_name""judell"},
        {"text""hello!"}
      };
var dm_object = new TwitterDirectMessage ( id: "123", sender_screen_name: "elmcity", recipient_screen_name: "judell", text: "hello!" ); var dm_dict = new Dictionary() { {"id" , "123"}, {"sender_screen_name", "elmcity"}, {"recipient_screen_name", "judell"}, {"text", "hello!"} };

They look similar when viewed in the debugger:


dm_object

{CalendarAggregator.TwitterDirectMessage}

    id: "123"

    recipient_screen_name: "judell"

    sender_screen_name: "elmcity"

    text: "hello!"



dm_dict

Count = 4

    [0]: {[id, 123]}

    [1]: {[sender_screen_name, elmcity]}

    [2]: {[recipient_screen_name, judell]}

    [3]: {[text, hello!]}

But these objects behave differently when you're writing code that uses them. If I use a dictionary-style object, I have to remember (or look up) that my convention for that dictionary is, for example, to use the key sender_screen_name rather than, say, sender_name. And if I get that wrong, I won't find out until run time. When I create a new custom type, though, Visual Studio knows the names and types of the object's properties and prompts for their values by name. I regard this static typing as optional, and sometimes I don't bother, but increasingly I've come to see it as a form of documentation that repays the overhead required to create it.

Unpacking a Twitter message using LINQ

Now let's look at how GetDirectMessagesFromTwitter unpacks the XML response from the Twitter API.

      var xdoc = XmlUtils.XdocFromXmlBytes(response.bytes);
      var messages = from message in xdoc.Descendants("direct_message")
        select new TwitterDirectMessage()
          {
          id = message.Descendants("id").First().Value,
          sender_screen_name = message.Descendants("sender_screen_name").First().Value,
          recipient_screen_name = message.Descendants("recipient_screen_name").First().Value,
          text = message.Descendants("text").First().Value
          };
      return messages.ToList();
var xdoc = XmlUtils.XdocFromXmlBytes(response.bytes); var messages = from message in xdoc.Descendants("direct_message") select new TwitterDirectMessage() { id = message.Descendants("id").First().Value, sender_screen_name = message.Descendants("sender_screen_name").First().Value, recipient_screen_name = message.Descendants("recipient_screen_name").First().Value, text = message.Descendants("text").First().Value }; return messages.ToList();

In this example, response.bytes is the XML text returned from the Twitter API, and xdoc is a System.Xml.Linq.Xdocument. I could alternatively have read that XML text into a System.Xml.XmlDocument, and used XPath to pick out elements from within it. That was always my approach in Python, it's available in C# too, and there are some cases where I use it. But here I'm instead using LINQ (language-integrated query) which is a pattern that works for many different kinds of data sources. The elmcity project uses LINQ to query a variety of sources including XML, in-memory objects, JSON, and CSV. I've come to appreciate the generality of LINQ. And when the data source is XML, I find that System.Xml.Linq.XDocument makes namespaces more tractable than System.Xml.XmlDocument does.

The type of object returned by xdoc.Descendants() is IEnumerable. That means it's an enumerable set of objects of type XElement. If you wanted to simply return an enumerable set of XElement, you could do this:

      var messages = from message in xdoc.Descendants("direct_message"
var messages = from message in xdoc.Descendants("direct_message")

It would then be the caller's responsibility to pick out the elements and construct TwitterDirectMessages from them. But instead, this method does that on the fly. Although xdoc.Descendants() is an IEnumerable, messages is an IEnumerable. The transformation occurs in the LINQ select clause, which unpacks the System.Xml.Linq.XElement and makes a TwitterDirectMessage.

You could return that messages object directly, in which case it would be the caller's responsibility to actually do the enumeration that it encapsulates. That style of deferred execution is fundamental to LINQ. But here there's no need to decouple the declaration of the query from the materialization of a view based on it. So GetDirectMessagesFromTwitter returns messages.ToList(), which does the enumeration and sends back a List as advertised in its declaration.

Moving Twitter messages to and from Azure storage

Each message sent from a curator to the elmcity service should only be handled once. So the service needs to be able to identify messages it hasn't seen before, and then dispose of them after its handling is done. To accomplish this, and also to keep an archive of all messages sent to the service, it reflects all new messages into the Azure table service, which is a scalable key/value database that stores bags of properties without requiring an explicit schema. In this case, scaling isn't a concern. At any given time, there shouldn't ever be more than a handful of Twitter messages stored in Azure. But the bags-of-properties aspect of the Azure table service is really convenient, for the same reason that hashtables and dictionaries are.

(Azure is, by the way, not restricted to the key/value table store. There's a cloud-based relational store as well. My project's storage needs are basic, so I haven't used SQL Azure yet. But I expect that I eventually will.)

The Azure SDK models the table service using a set of abstractions that work with defined types, but I wanted to preserve the flexibility to store and retrieve arbitrary key/value collections. Hence my project's alternate interface to the service. I'll explore it in more detail elsewhere. For now, just consider the Azure-oriented counterpart to GetDirectMessagesFromTwitter:

    public static List<TwitterDirectMessage> GetDirectMessagesFromAzure()
    {
      {
        var q = string.Format("$filter=(PartitionKey eq '{0}')", pk_directs);
        var qdicts = (List<Dictionary<stringobject>>)ts.QueryEntities(ts_table, q).response;
        var messages = new List<TwitterDirectMessage>();
        foreach (var qdict in qdicts)
        {
          var message = (TwitterDirectMessage)ObjectUtils.DictObjToObj
            (
            qdict, 
            new TwitterDirectMessage().GetType()
            );
          messages.Add(message);
        }
        return messages;
      }
    }
public static List GetDirectMessagesFromAzure() { var q = "$filter=(PartitionKey eq 'direct_message')"; var qdicts = (List>) ts.QueryEntities ( tablename:'twitter', query:q ).response; var messages = new List(); foreach (var qdict in qdicts) { var message = (TwitterDirectMessage)ObjectUtils.DictObjToObj ( qdict, new TwitterDirectMessage().GetType() ); messages.Add(message); } return messages; }

If it were written in English, that method would say the following.

From the Azure tabled named twitter, retrieve all entities identified by a partition key whose value is direct_messages. The result is an Atom feed whose entries look like this:

<entry m:etag="W/"datetime'2010-06-14T15%3A01%3A39.103296Z'"">
 <id>https://elmcity.table.core.windows.net/twitter(PartitionKey='direct_messages',RowKey='1003299227')id>
 <title type="text"/>
 <updated>2010-08-09T22:13:20Zupdated>
 <author><name/>author>
 <link rel="edit"title="twitter"href="twitter(PartitionKey='direct_messages',RowKey='1003299227')"/>
 <category term="elmcity.twitter"scheme="http://schemas.micro...services/scheme"/>
 <content type="application/xml">
   <m:properties>
   <d:PartitionKey>direct_messagesd:PartitionKey>
   <d:RowKey>1003299227d:RowKey>
   <d:Timestamp m:type="Edm.DateTime">2010-06-14T15:01:39.103296Zd:Timestamp>
   <d:id>1003299227d:id>
   <d:sender_screen_name>westboroughd:sender_screen_name>
   <d:recipient_screen_name>elmcity_azured:recipient_screen_name>
   <d:text>startd:text>
   m:properties>
   content>
entry>
https://elmcity.table.core.windows.net/twitter(PartitionKey='direct_messages',RowKey='1003299227') <updated>2010-08-09T22:13:20Z</updated> <author><name/></author> <link rel="edit"title="twitter"href="twitter(PartitionKey='direct_messages',RowKey='1003299227')"/> <category term="elmcity.twitter"scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/> <content type="application/xml"> <m:properties> <d:PartitionKey>direct_messages</d:PartitionKey> <d:RowKey>1003299227</d:RowKey> <d:Timestamp m:type="Edm.DateTime">2010-06-14T15:01:39.103296Z</d:Timestamp> <d:id>1003299227</d:id> <d:sender_screen_name>westborough</d:sender_screen_name> <d:recipient_screen_name>elmcity_azure</d:recipient_screen_name> <d:text>start</d:text> </m:properties> </content> </entry> </div> </pre> <p style="margin-bottom:10pt"> Convert each Atom entry into a dictionary (i.e., Dictionary<string,object>) and combine those into a list (i.e., List<Dictionary<string,object>>). Then run through the list, convert each dictionary into its corresponding TwitterDirectMessage, and return a list of those. </p> </blockquote> <p style="margin-bottom:10pt"> That conversion is enabled by a method in the <a href="http://github.com/judell/elmcity/blob/master/elmcityutils/ObjectUtils.cs">ObjectUtils</a> class, DictObjToObj. It works in a generic way for any type. Here the conversion is from Dictionary<string,string> to TwitterDirectMessage. But suppose you had a different type, say: </p> <pre style="color:black;font-family:consolas;margin-bottom:12pt">  <span style="color: blue">public</span> <span style="color: blue">class</span> <span style="color: #2b91af">ExampleType</span><br/>  {<br/>    <span style="color: blue">public</span> <span style="color: blue">int</span> id = 123;<br/>    <span style="color: blue">public</span> <span style="color: blue">string</span> name = <span style="color: #a31515">"elmcity"</span>;<br/>    <span style="color: blue">public</span> <span style="color: #2b91af">DateTime</span> timestamp = <span style="color: #2b91af">DateTime</span>.Now;<br/>    <span style="color: blue">public</span> <span style="color: blue">bool</span> validated = <span style="color: blue">true</span>;<br/>  }</pre><div style="display:none"> public class ExampleType { public int id = 123; public string name = "elmcity"; public DateTime timestamp = DateTime.Now; public bool validated = true; } </div> <p style="margin-bottom:10pt"> DictObjToObj can make an instance of that type from this dictionary: </p> <pre style="color:black;font-family:consolas;margin-bottom:12pt">    <span style="color: blue">var</span> dict = <span style="color: blue">new</span> <span style="color: #2b91af">Dictionary</span><<span style="color: blue">string</span>, <span style="color: blue">object</span>>() <br/>    {<br/>      {<span style="color: #a31515">"id"</span> , 123}, <br/>      {<span style="color: #a31515">"name"</span>, <span style="color: #a31515">"elmcity"</span>},<br/>      {<span style="color: #a31515">"timestamp"</span>, <span style="color: #2b91af">DateTime</span>.Now},<br/>      {<span style="color: #a31515">"validated"</span>, <span style="color: blue">true</span>}<br/>    };</pre><div style="display:none"> var dict = new Dictionary<string, object>() { {"id" , 123}, {"name", "elmcity"}, {"timestamp", DateTime.Now }, {"validated", true} }; </div> <p style="margin-bottom:10pt"> It does so using a C# capability called <a href="http://msdn.microsoft.com/en-us/library/ms173183%28VS.80%29.aspx">reflection</a>: </p> <blockquote style="margin-bottom:10pt;margin-left:10%;margin-right:10%"> Reflection provides objects (of type Type) that encapsulate assemblies, modules and types. You can use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties. </blockquote> <p style="margin-bottom:10pt"> Here's how DictObjToObj uses reflection: </p> <pre style="color:black;font-family:consolas;margin-bottom:12pt">    <span style="color: blue">public</span> <span style="color: blue">static</span> <span style="color: #2b91af">Object</span> DictObjToObj(<span style="color: #2b91af">Dictionary</span><<span style="color: blue">string</span>, <span style="color: blue">object</span>> dict_obj, <span style="color: #2b91af">Type</span> type)<br/>    {<br/>      <span style="color: blue">var</span> o = <span style="color: #2b91af">Activator</span>.CreateInstance(type);  <span style="color: green">// create object</span><br/> <br/>      <span style="color: blue">if</span> (type.GetProperties() == <span style="color: blue">null</span>)<br/>      {<br/>        <span style="color: #2b91af">GenUtils</span>.LogMsg(<span style="color: #a31515">"exception"</span>, <span style="color: #a31515">"DictObjToObj: "</span> + type.Name, <br/>          <span style="color: #a31515">"target type does not define properties"</span>);<br/>        <span style="color: blue">return</span> o;<br/>      }<br/> <br/>      <span style="color: blue">foreach</span> (<span style="color: blue">var</span> key <span style="color: blue">in</span> dict_obj.Keys)<br/>      {<br/>        <span style="color: blue">try</span>                                  <span style="color: green">// set properties</span><br/>        {<br/>          type.GetProperty(key).SetValue(o, dict_obj[key], index: <span style="color: blue">null</span>);<br/>        }<br/>        <span style="color: blue">catch</span> (<span style="color: #2b91af">NullReferenceException</span>)<br/>        {<br/>          <span style="color: green">// this is normal since an azure table includes PartitionKey, RowKey, </span><br/>          <span style="color: green">// and Timestamp which will not map into the object</span><br/>        }<br/>        <span style="color: blue">catch</span> (<span style="color: #2b91af">Exception</span> e)<br/>        {<br/>          <span style="color: #2b91af">GenUtils</span>.LogMsg(<span style="color: #a31515">"exception"</span>, <span style="color: #a31515">"DictObjToObj: "</span> + type.Name,<br/>            e.Message + e.StackTrace);<br/>        }<br/>      }<br/> <br/>      <span style="color: blue">return</span> o;<br/>    }</pre><div style="display:none"> public static Object DictObjToObj(Dictionary<string, object> dict_obj, Type type) { var o = Activator.CreateInstance(type); // create the object if (type.GetProperties() == null) { GenUtils.LogMsg("exception", "DictObjToObj: " + type.Name, "target type does not define properties"); return o; } foreach (var key in dict_obj.Keys) { try // set property value { type.GetProperty(key).SetValue(o, dict_obj[key], index: null); } catch (NullReferenceException) { // this is normal since an Azure table includes PartitionKey, RowKey, // and Timestamp which will not map to properties of the object. // so, do nothing } catch (Exception e) { GenUtils.LogMsg("exception", "DictObjToObj: " + type.Name, e.Message + e.StackTrace); } } return o; } </div> <p style="margin-bottom:10pt"> In English: </p> <blockquote style="margin-bottom:10pt;margin-left:10%;margin-right:10%"> Given a source dictionary and a target type, and assuming that the keys of the dictionary match properties of the object, create an object of the type and map the dictionary's key/value pairs to the object's properties. </blockquote> <p style="margin-bottom:10pt"> There's also a converse, ObjToDictObj, which reverses the transformation. It's used here to store a TwitterDirectMessage into an Azure table: </p> <pre style="color:black;font-family:consolas;margin-bottom:12pt">        <span style="color: blue">private</span> <span style="color: blue">static</span> <span style="color: #2b91af">TableStorageResponse</span> StoreDirectMessageToAzure(<span style="color: #2b91af">TwitterDirectMessage</span> message)<br/>        {<br/>            <span style="color: blue">var</span> dict = <span style="color: #2b91af">ObjectUtils</span>.ObjToDictObj(message);<br/>            <span style="color: blue">return</span> <span style="color: #2b91af">TableStorage</span>.UpdateDictToTableStore<br/>        }</pre><div style="display:none"> private static TableStorageResponse StoreDirectMessageToAzure(TwitterDirectMessage message) { var dict = ObjectUtils.ObjToDictObj(message); return TableStorage.UpdateDictToTableStore ( dict, table: ts_table, partkey: pk_directs, rowkey: message.id ); } </div> <p style="margin-bottom:10pt"> There are many ways to think about the relationship between objects in programming languages and objects in databases. I like thinking in terms of bags of properties. From the C# perspective, as we've seen, these can be defined more or less strictly, as needed. The same holds true for Azure table entities. It's true that they lack an explicit schema and are thus inherently more flexible. But you can nail down their attributes more or less strictly, depending on how strongly typed you want them to be in the database. In both the programming language and the database, you can move flexibly along a continuum of ways to represent bags of properties. </p> <h2 style="float:none">Identifying new Twitter messages</h2> <p style="margin-bottom:10pt"> Given the ability to fetch a list of TwitterDirectMessage from Twitter, and a corresponding list from Azure, all that's left is to compare the lists in order to find items from the former that are missing from the latter. These are the new messages to be handled and then deleted. </p> <p style="margin-bottom:10pt"> Here's a side-by-side comparison of similar idioms for finding the difference between two lists. On the left is how I'd do it in Python. On the right is how I'm actually doing it in C#. </p> <div width="90%"> <table style="width:90%"> <tbody><tr> <td style="text-align: center;"> Python </td> <td style="text-align: center;"> C# </td> </tr> <tr> <td style="vertical-align: top;width:50%"> <pre style="color:black;margin:10px;width:300px;line-height:.7"> fetched_messages =   GetDirectMessagesFromTwitter() stored_messages =   GetDirectMessagesFromAzure() <span style="color: blue;">diff = set(fetched_messages) -   set(stored_messages)</span> return list(diff) </pre> </td> <td style="vertical-align:top;width:50%"> <pre style="color:black;margin:10px;width:300px;line-height:.7"> var fetched_messages =   GetDirectMessagesFromTwitter(); var stored_messages =   GetDirectMessagesFromAzure(); <span style="color: blue;">var diff = fetched_messages.Except(   stored_messages);</span> return diff.ToList(); </pre> </td> </tr> </tbody> </table> </div> <p style="margin-bottom:10pt"> I'm not sure which I prefer. Python's set arithmetic is mathematically pure. But C#'s noun-verb syntax is appealing too. For me it's a toss-up, which shows that C# need not be a lot more verbose than Python. That said, the elmcity project uses Python too -- specifically IronPython. In future installments I'll discuss the pros and cons of using C# and IronPython in complementary ways on Azure. </p> <br /> </div> </div> <script type='text/javascript'> // Show multiquote for JS browsers //$('multiq_3571').show(); if( $('toggle_post_3571') ) { $('toggle_post_3571').show(); } </script> <!-- END OF FIRST POST IN LINEAR+, SHOW BUTTONS AND NEW TITLE --> <div class='topic_controls'> <hr class="dashed" /> Tags: <ul class="tagList"><li><a href='http://answers.oreilly.com/tag/elmcity'>elmcity</a></li><li> <a href='http://answers.oreilly.com/tag/azure'>azure</a></li><li> <a href='http://answers.oreilly.com/tag/twitter'>twitter</a></li><li> <a href='http://answers.oreilly.com/tag/linq'>linq</a></li><li> <a href='http://answers.oreilly.com/tag/reflection'>reflection</a></li></ul> <hr class="dashed" /> <div id="subscribe" style="float:left;width:90px;height:22px;font-weight:bold;"> 0 <a onclick="post_toggle('login-to-subscribe');"><img src="http://cdn.oreilly.com/answers/subscribe.png" alt="Subscribe" /></a> </div> <div id="email" style="float:left;width:60px;height:22px;"> <a onclick="post_toggle('login-to-email');"><img src="http://oreilly.com/images/icons/email_icon2.gif" /></a> </div> <div id="tweetmeme" style="float:left;width:118px;height:22px;padding-top:0;"> <a href="http://twitter.com/share" class="twitter-share-button" data-url="http://answers.oreilly.com/topic/1889-how-to-make-azure-talk-to-twitter/" data-text="Just read How to make Azure talk to Twitter on #oreillyanswers" data-count="horizontal" data-related="oreillyanswers">Tweet</a><script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script> </div> <div class="plus" style="float: left; height: 20px; width: 82px; padding-top: 0px;"><g:plusone size="medium" count="true" href="http://answers.oreilly.com/topic/1889-how-to-make-azure-talk-to-twitter/"></g:plusone> </div> <div id="facebook" style="float:left;width:100px;height:22px;"> <iframe src="http://www.facebook.com/plugins/like.php?href=http://answers.oreilly.com/topic/1889-how-to-make-azure-talk-to-twitter/&layout=button_count&show_faces=false&width=100&action=like&colorscheme=light" scrolling="no" frameborder="0" allowTransparency="true" style="border:none; overflow:hidden; width:100px; height:22px"></iframe> </div> <br style="clear:left;" /> <div style="display:none;" id="login-to-subscribe"><p>Please <a href="http://answers.oreilly.com/index.php?app=core&module=global§ion=login">sign in or register</a> to subscribe.</p></div> <div style="display:none;" id="login-to-email"><p>Please <a href="http://answers.oreilly.com/index.php?app=core&module=global§ion=login">sign in or register</a> to email.</p></div> <!--<li class='top hide'><a href='#top' class='top' title='Back to top'>Back to top</a></li> <li class='multiquote' id='multiq_3571' style='display: none'> <a href="http://answers.oreilly.com/index.php?app=forums&module=post&section=post&do=reply_post&f=2&t=1889&qpid=3571" title="Quote this post with Multi-Quote"><img src='http://answers.oreilly.com/public/style_images/master/comment_add.png' alt='Reply Icon' /> MultiQuote</a> </li> <li><a href="http://answers.oreilly.com/index.php?app=forums&module=post&section=post&do=reply_post&f=2&t=1889&qpid=3571" title="Reply directly to this post"><img src='http://answers.oreilly.com/public/style_images/master/comment_add.png' alt='Reply Icon' /> Reply</a></li> --> <!-- Matt: Approve / unapprove post button --> <a name="comment"></a> </div> <!-- /if --> <!-- /div --> <!-- </if> --> </div> <br class="clear" /> <h2 class="maintitle" style="margin: 8px 0 0 0 !important"> 0 Replies</h2> <div style='float:right;'> <a onclick="post_toggle('login-to-answer');"><img src="http://cdn.oreilly.com/answers/submitReply.png" /></a> <div style="display:none;" id="login-to-answer"><p>Please <a href="http://answers.oreilly.com/index.php?app=core&module=global§ion=login">sign in or register</a> to reply.</p></div> </div> <!-- <script type='text/javascript'> //<![CDATA[ if( $('watch_opts_menucontent') ){ $('watch_opts_menucontent').hide(); } //]]> </script> <ul class='sorting'> <li> <strong><a href='http://answers.oreilly.com/topic/1889-how-to-make-azure-talk-to-twitter/page__sortKey__recent__sortBy__desc'>Date Posted <img src='http://answers.oreilly.com/public/style_images/master/dropdown.png' alt='sort desc' /></a></strong> </li> <li> <a href='http://answers.oreilly.com/topic/1889-how-to-make-azure-talk-to-twitter/page__sortKey__answers__sortBy__desc'>Answers <img src='http://answers.oreilly.com/public/style_images/master/dropdown.png' alt='sort decending' /></a> </li> <li> <a href='http://answers.oreilly.com/topic/1889-how-to-make-azure-talk-to-twitter/page__sortKey__comments__sortBy__desc'>Comments <img src='http://answers.oreilly.com/public/style_images/master/dropdown.png' alt='sort decending' /></a> </li> </ul> <span class='right'>filter by: </span> </div --> <!-- MOD LINKS --> <!-- REPUTATION FILTERS --> <!--<div class='moderation_bar rounded' id='topic_search_jump'> <form method='post' action='http://answers.oreilly.com/index.php?app=core&module=search&do=quick_search&search_filter_app[forums]=1' id='context_search' class='left'> <fieldset> <input type='hidden' name='type_2' value='topic' /> <input type='hidden' name='type_id_2' value='1889' /> <label for='topic_search'>Search Topic</label> <input type='text' id='topic_search' name='search_term' class='input_text' size='20' tabindex='8' /> <input type='submit' class='input_submit alt' value='Go' /> </fieldset> </form> </div>--> <!-- THREADED MODE --> <ul class='topic_jump right clearfloat'> <li class='previous'><a href='http://answers.oreilly.com/topic/1889-how-to-make-azure-talk-to-twitter/page__view__old'>← Previous Topic</a></li> <li><strong><a href='http://answers.oreilly.com/forum/2-clever-hacks-creative-ideas-innovative-solutions-ask-answer-share/' title='Return to Clever Hacks, Creative Ideas, Innovative Solutions. Ask, Answer, Share'>O'Reilly Answers Home</a></strong></li> <li class='next'><a href='http://answers.oreilly.com/topic/1889-how-to-make-azure-talk-to-twitter/page__view__new'>Next Topic →</a></li> </ul> <!-- BOTTOM BUTTONS --> <!-- Close topic --> </div> <script type="text/javascript" src="http://answers.oreilly.com/public/js/3rd_party/prettify/prettify.js"></script> <script type='text/javascript' src='http://answers.oreilly.com/public/js/3rd_party/prettify/lang-sql.js'></script> <!-- By default we load generic code, php, css, sql and xml/html; load others here if desired --> <script type="text/javascript"> //<![CDATA[ Event.observe( window, 'load', function(e){ prettyPrint() }); //]]> </script> <br class='clear' /> <!-- __FOOTER__ --> <!-- /__FOOTER__ --> <script type='text/javascript' src='http://answers.oreilly.com/public/js/3rd_party/lightbox.js'></script> <script type='text/javascript'> //<![CDATA[ // Lightbox Configuration LightboxOptions = Object.extend({ fileLoadingImage: 'http://answers.oreilly.com/public/style_images/master/lightbox/loading.gif', fileBottomNavCloseImage: 'http://answers.oreilly.com/public/style_images/master/lightbox/closelabel.gif', overlayOpacity: 0.8, // controls transparency of shadow overlay animate: true, // toggles resizing animations resizeSpeed: 7, // controls the speed of the image resizing animations (1=slowest and 10=fastest) borderSize: 10, //if you adjust the padding in the CSS, you will need to update this variable // When grouping images this is used to write: Image # of #. // Change it for non-english localization labelImage: "Image", labelOf: "of" }, window.LightboxOptions || {}); //]]> </script> </div> <!--INVISION END--> </div> </div> </div><!-- this is in footer --> </div><!-- this is in footer --> <!-- /div> /content-09 --> <!-- start footer --> <!--footer --> </td> </tr> <tr> <td class="eightPixel"> </td> </tr> <tr valign="bottom"> <td align="center" class="navfooterbg"><!--BottomNav Start--> <table border="0" cellspacing="0" cellpadding="0" class="emailSignUpTable"> <tr> <td style="width:516px;"> <form method="GET" id="emailSignUp" action="http://post.oreilly.com/client/o/oreilly/forms/quicksignup.cgi"> <table border="0" cellspacing="0" cellpadding="0" width="500"> <tbody> <tr> <td width="275"> <div style="padding-left: 12px;">Sign up today to receive special discounts,<br /> product alerts, and news from O'Reilly.</div> </td> <td width="172"> <input type="hidden" name="client_token" value="oreilly"><input type="hidden" name="subscribe" value="optin"> <input type="hidden" name="success_url" value="http://oreilly.com/store/newsletter-thankyou.html"> <input type="hidden" name="error_url" value="http://oreilly.com/store/newsletter-signup-error.html"> <input type="hidden" name="news_oreilly" value="1"> <input type="text" name="email_addr" value='Enter Email' maxlength="100" size="12" class="NavSearchBottom"> </td> <td width="53"> <input border="0" type="image" alt="Submit" src="http://cdn.oreilly.com/images/sitewide-headers/emailsignup_btn.gif" type="submit" name="submit" value="submit" /> </td> </tr> </tbody> </table> </form> </td> <td> <a href="http://oreilly.com/oreilly/privacy.html">Privacy Policy ></a><br /> <a href="http://oreilly.com/pdf/sample-newsletter.pdf" alt="View Sample Newsletter >">View Sample Newsletter ></a> </td> <td style="text-align:right"> <ul class="utility social"> <li><a class="twitter" href="http://twitter.com/oreillymedia" target="_blank"><span class="hidden">Twitter</span></a></li> <!--<li><a class="youtube" href="http://youtube.com/oreillymedia" target="_blank"><span class="hidden">YouTube</span></a></li>--> <li><a class="youtube" href="http://www.youtube.com/subscription_center?add_user=oreillymedia" target="_blank"><span class="hidden">YouTube</span></a></li> <li><a class="slideshare" href="http://slideshare.net/oreillymedia" target="_blank"><span class="hidden">Slideshare</span></a></li> <li><a class="facebook" href="http://www.facebook.com/OReilly" target="_blank"><span class="hidden">Facebook</span></a></li> <li><a class="googleplus" href="https://plus.google.com/108442503368488643007?prsrc=3"><span class="hidden">Google Plus</span></a></li> <li><a class="rss" href="http://feeds.feedburner.com/oreilly/news" target="_blank"><span class="hidden">RSS</span></a></li> <li><a class="all-rss" href="http://oreilly.com/feeds">View All RSS Feeds ></a></li> </ul> </td> </tr> </table> <div id="multiColumnFooter"> <div id="footer-branding"> <!-- oreilly logo was here --> <p class="copyright">© 2014, O’Reilly Media, Inc.</p> <p> <span class="phone-number">(707) 827-7019</span> <span class="phone-number">(800) 889-8969</span> </p> <p class="trademarks">All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.</p> </div> <div class="contentSectionBlock"> <!-- style="width:25%;" --> <div class="contentSectionContainer" > <span id="footerColumn-1" class="contentSection"> <h3>About O'Reilly</h3> <ul> <li><a class="footer" href="http://oreilly.com/academic/">Academic Solutions</a></li> <li><a class="footer" href="http://oreilly.com/jobs/">Jobs</a></li> <li><a class="footer" href="http://oreilly.com/contact.html">Contacts</a></li> <li><a class="footer" href="http://oreilly.com/about/">Corporate Information</a></li> <li><a class="footer" href="http://press.oreilly.com/index.html">Press Room</a></li> <li><a class="footer" href="http://oreilly.com/oreilly/privacy.csp">Privacy Policy</a></li> <li><a class="footer" href="http://oreilly.com/terms/">Terms of Service</a></li> <li><a class="footer" href="http://oreilly.com/oreilly/author/intro.csp">Writing for O'Reilly</a></li> </ul> </span> </div><!--end contentSectionContainer--> <!-- style="width:25%;" --> <div class="contentSectionContainer" > <span id="footerColumn-2" class="contentSection"> <h3>Community</h3> <ul> <li><a class="footer" href="http://www.oreilly.com/authors/">Authors</a></li> <li><a class="footer" href="http://oreilly.com/webcasts/index.html">Webcasts</a></li> <li><a class="footer" href="http://oreilly.com/community/">Community & Featured Users</a></li> <li><a class="footer" href="http://forums.oreilly.com/">Forums</a></li> <li><a class="footer" href="http://elists.oreilly.com/">Newsletters</a></li> <li><a class="footer" href="http://answers.oreilly.com/">O'Reilly Answers</a></li> <li><a class="footer" href="http://oreilly.com/feeds/">RSS Feeds</a></li> <li><a class="footer" href="http://ug.oreilly.com/">User Groups</a></li> <li><a class="footer" href="http://chimera.labs.oreilly.com/">O'Reilly Atlas (beta)</a></li> </ul> </span> </div><!--end contentSectionContainer--> <!-- style="width:25%;" --> <div class="contentSectionContainer" > <span id="footerColumn-3" class="contentSection"> <h3>Partner Sites</h3> <ul> <li><a class="footer" href="http://makezine.com/">makezine.com</a></li> <li><a class="footer" href="http://makerfaire.com/">makerfaire.com</a></li> <li><a class="footer" href="http://craftzine.com/">craftzine.com</a></li> <li><a class="footer" href="http://igniteshow.com/">igniteshow.com</a></li> <li><a class="footer" href="https://developer.paypal.com/">PayPal Developer Zone</a></li> <li><a class="footer" href="http://blogs.forbes.com/oreillymedia/">O'Reilly Insights on Forbes.com</a></li> </ul> </span> </div><!--end contentSectionContainer--> <!-- style="width:24%;" --> <div class="contentSectionContainer" > <span id="footerColumn-4" class="contentSection"> <h3>Shop O'Reilly</h3> <ul> <li><a class="footer" href="http://shop.oreilly.com/category/customer-service.do">Customer Service</a></li> <li><a class="footer" href="http://shop.oreilly.com/category/customer-service.do">Contact Us</a></li> <li><a class="footer" href="http://shop.oreilly.com/category/customer-service/shipping-information.do">Shipping Information</a></li> <li><a class="footer" href="http://shop.oreilly.com/category/customer-service/ordering-payment.do">Ordering & Payment</a></li> <li><a class="footer" href="http://oreilly.com/affiliates/">Affiliate Program</a></li> <li><a class="footer" href="http://shop.oreilly.com/category/customer-service/oreilly-guarantee.do">The O'Reilly Guarantee</a></li> </ul> </span> </div> <!--end contentSectionContainer--> <div style="clear:both;"></div> </div><!--end contentSectionBlock--> </div><!--end multiColumnFooter--> </td><!--BottomNav End--> </tr> </table> </div> <!-- /page --> <script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script> <!-- autocomplete scripts - autocomplete_data.js updated 8/23/12 --> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/utilities/utilities.js"></script> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/datasource/datasource-min.js"></script> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/autocomplete/autocomplete-min.js"></script> <script type="text/javascript" src="//content.atomz.com/sp10049ed9/publish/autocomplete_data.js"></script> <!-- MyBuys Page Initialization --> <script type="text/javascript"> mybuys.initPage(); </script> <script type="text/javascript"> var _sf_async_config={uid:1632,domain:"answers.oreilly.com"}; (function(){ function loadChartbeat() { window._sf_endpt=(new Date()).getTime(); var e = document.createElement('script'); e.setAttribute('language', 'javascript'); e.setAttribute('type', 'text/javascript'); e.setAttribute('src', (("https:" == document.location.protocol) ? "https://s3.amazonaws.com/" : "http://") + "static.chartbeat.com/js/chartbeat.js"); document.body.appendChild(e); } var oldonload = window.onload; window.onload = (typeof window.onload != 'function') ? loadChartbeat : function() { oldonload(); loadChartbeat(); }; })(); </script> <script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-4591498-1']); _gaq.push(['_setDomainName', 'oreilly.com']); _gaq.push(['_addIgnoredRef', 'oreilly.com']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> </body> </html>