Twitter Reply EMailer

Posted in Software on Thursday, Thursday, July 10, 2008 by Anthony Burns

My addiction to Twitter has grown exponentially over the last month or so. I've gone from "I just don't get it" to developing a Windows Mobile application - Quakk - so I can check my Tweets on the go.

Twitter does me the favour of sending an email when when I receive a direct message, but doesn't bother when I recieve a reply - I'd prefer it the other way around. After browsing the list of available applications at the Twitter Fan Wiki I tried a few online services that claimed to monitor your replies and send notification emails, but was satisfied by none of them. The first one I tried wouldn't let me sign up due to database connection problems, and the second one sent my notifications sporadically. So I decided I could easily write my own monitor using the parts I've already developed for Quakk.

I've made the entire code for this project available to download at the bottom of this article, but if you're not a coder and you just want a copy of the application to use yourself, then that is available for download below too.

There are four classes from Quakk I'll be using: Configuration, TwitterApi, ReplyTimeline and Tweet. I've stripped out any code that's irrelevant to this project and detailed the public interfaces of these classes below, with the main code for the project below that. Please keep in mind that Quakk is still in the beta stage, so some of this code is a bit scruffy.

Configuration

public class Configuration
{
    public string LastID
    public string Username
    public string Password
    public string EmailAddress
    public string SmtpServer
    public static Configuration LoadConfiguration()
    public void Save()
}

The Configuration class deals with loading and saving your Twitter login credentials, email details and the ID of the last received tweet to an xml file. The static method LoadConfiguration() loads and parses the Xml file and returns an instance of the Configuration class populated with the details from the Xml file. These are made available as public properties, and a Save() method enables you to save any changes you make back to the Xml file.

TwitterApi

public class TwitterApi
{
    public string Username
    public string Password
    public string GetXml(string url)
}

The TwitterApi class takes care of connecting to Twitter, authenticating your user details and returning the response as a string. Create an instance of the TwitterApi, populate the Username and Password properties from your Configuration object and then call GetXml() on the desired url from the Twitter API.

Tweet

public class Tweet
{
    public string ID
    public string DisplayName
    public DateTime TweetDateTime
    public string Message
    public Tweet(XmlNode tweetXml)
}

The Tweet class takes an XmlNode as part of its constuctor which it parses and populates the public Properties from the data provided.

ReplyTimeline

public class ReplyTimeline
{
    public List<Tweet> Tweets
    protected ReplyTimeline(TwitterApi twitterApi)
    public void Update()
}

You pass in an instance of the TwitterApi to the ReplyTimeline class when you instantiate it which gives the ReplyTimeline the ability to connect to Twitter. When you call the Update method, it obtains the Xml for the 20 most recent replies from your Twitter account, parses it and creates a List of parsed Tweet objects which it makes available from its public Tweets property.

Putting it all Together

As you can see, by making use of these objects you can get a collection of Tweets live from Twitter in very little code.

// Load Configuration from Xml file, quitting if it doesn't exist
Configuration config = Configuration.LoadConfiguration();
if(config == null) return;

TwitterApi twitter = new TwitterApi
                         {
                             Username = config.Username,
                             Password = config.Password
                         };

// Update the Reply Timeline
ReplyTimeline timeline;
try
{
    timeline = new Timeline(twitter);
    timeline.Update();
}
catch(TwitterException)
{
    // We could implement some sort of logging here,
    // but since Twitter is down more than it's up 
    // I've opted to just quit the application here
    return;
}

List<Tweet> tweets = timeline.Tweets;

At this point we have a generic List of Tweet objects from our Reply Timeline.

string emailBody = "";

// Iterate through each tweet returned from the timeline 
// until we hit a tweet we've already seen
foreach(Tweet tweet in tweets)
{
    if(tweet.ID == config.LastID)
    {
        break;
    }

    // Append the current tweet's details to our email body
    emailBody += tweet.DisplayName + " replied:\r\n" +
                 tweet.Message + "\r\n\r\nAt: " +
                 tweet.TweetDateTime + "\r\n\r\n";
}

We iterate through the Tweet list building up an email containing the tweets we haven't seen yet.

// If we have tweet notifications to send then compose the email
if(emailBody != "")
{

    MailMessage mail = new MailMessage(config.EmailAddress,
                                       config.EmailAddress,
                                       "New Reply Tweet", 
                                       emailBody);

    // Send the email using the SMTP server specified in the config
    try
    {
        SmtpClient client = new SmtpClient(config.SmtpServer);
        client.Send(mail);
    }
    catch(SmtpException)
    {
        return;
    }

    // Update the ID of the last tweet in the config so we
    // can identify if we've already seen it next time
    config.LastID = tweets[0].ID;
    config.Save();
}

Then we send out the email using the details from the config file, and update the config file with the ID of the last tweet.

You also need the following Configuration.xml file in the same directory as the application.

<?xml version="1.0"?>
<Configuration>
  <LastID></LastID>
  <Username>Your Twitter Username</Username>
  <Password>Your Twitter Password</Password>
  <EmailAddress>billy@billy.com</EmailAddress>
  <SmtpServer>smtp.mysmtpserver.com</SmtpServer>
</Configuration>

How to use the Software

You can download the source in a VS2008 project, or if you're only interested in the application itself then download the app.

The application simply connects to Twitter, downloads your Replies, compiles an email of any new Replies and sends that email to you. In order to make use of it you should set it up as a scheduled task in Windows.

Tagged as: .net, csharp, twitter,

One-Click File Upload for IE in ASP.NET

Posted in Development on Wednesday, Wednesday, May 21, 2008 by Anthony Burns

DISCLAIMER: I'm in the privileged position of being able to dictate which browser our users use to access our extranet at work - I insist that IE must be used, not because I think it's a better browser, but because everyone has it installed by default and they know how to use it. By restricting to only one browser, I can speed up development times by not having to test our pages across multiple browsers. Because of that, this article is for IE only, and hasn't even been tested outside of IE7. If it proves interesting/helpful to people, then I might do an update later that works cross-browser.

I developed a webform for a project at work recently, that had a lot of form elements and that needed a file upload component adding.

The form was already too busy from a UI perspective and there wasn't a great location to plop down a file input element and submit button to upload the file. I decided I'd prefer it if I could just have an "Upload File" link, that when clicked prompted the user to select a file and caused the form to submit. I knew this was possible because Hotmail use a similar concept for attaching files to an email.

After a quick Google I discovered that the file input element in IE had a Javascript click() method - this I could use to open a file select dialog and I could then use form.submit() to upload the file. Simple.

I knocked up a quick prototype html file with the following code:

   1:  <script type="text/javascript">
   2:   
   3:  function UploadFile()
   4:  {
   5:      var form = document.UploadForm;
   6:      form.FileUpload.click();
   7:      form.SubmitButton.click();
   8:  }
   9:   
  10:  </script>
  11:   
  12:  <form id="UploadForm" name="UploadForm">
  13:    <input type="file" id="FileUpload" name="FileUpload" />
  14:    <input type="submit" value="Upload" id="SubmitButton" name="SubmitButton" />
  15:  </form>

When the link was clicked IE prompted me to select a file, but after I'd selected a file, IE gave me a Javascript "Access is denied" error. Bollocks.

After a long time spent on Google, I discovered that IE doesn't allow you to programmatically click the file input control and submit the form. Why on earth you'd want to click the control but not submit the form is beyond me. Even more bizarre is the fact that IE WILL allow you to do this, if the form is contained inside an IFrame. Bizarre, but a solution nonetheless.

So I constructed an UploadFile.aspx file containing the code below.

   1:  <form id="UploadForm" runat="server" enctype="multipart/form-data">
   2:      <asp:FileUpload ID="FileUpload" runat="server" />
   3:      <asp:HiddenField ID="UploadPath" runat="server" />
   4:      <asp:HiddenField ID="UploadLinkId" runat="server" />
   5:      <asp:Button UseSubmitBehavior="true" ID="SubmitButton" runat="server" />
   6:  </form>
   7:   
   8:  <a href="javascript:document.UploadForm.FileUpload.click();document.UploadForm.submit();">Upload File</a>

The form contains a file upload element, a submit button and two hidden fields. I plan on using javascript to set these hidden fields, one to enable me to specify a subfolder to upload the file to, and one to keep a reference to the "Upload File" link.

I then created a TestForm.aspx file containing a link and a hidden IFrame to contain my UploadFile.asp page:

   1:  <head>
   2:  <title>One-Click Upload</title>
   3:  <script type="text/javascript" src="Javascript/Upload.js"></script>
   4:  </head>
   5:  <body>
   6:   
   7:    <p><a href="#" id="UploadLink" onclick="return UploadFile(this, '');">Upload File</a></p>
   8:   
   9:    <iframe src="UploadFile.aspx" frameborder="0" id="UploadFrame" name="UploadFrame" style="display: none">
  10:    </iframe>
  11:   
  12:  </body>
  13:  </html>

In order to tie the "Upload File" link and the upload form within the IFrame together, I wrote the following Javascript function:

   1:  function UploadFile(link, uploadPath) {
   2:   
   3:    var uploadStatus = '<b>Uploading, please wait...</b>';
   4:   
   5:    // Check to see if we are already uploading a file
   6:    if(link.innerHTML.toLowerCase() == uploadStatus.toLowerCase()) {
   7:      alert('A file is currently being uploaded, please wait for it to finish before uploading another.');
   8:    } else {
   9:    
  10:      // Set our hidden fields and cause IE to prompt for a file
  11:      var form = UploadFrame.document.UploadForm;
  12:      form.UploadPath.value = uploadPath;
  13:      form.UploadLinkId.value = link.id;
  14:      form.FileUpload.click();
  15:   
  16:      // If the user has selected a file then submit the form
  17:      if(form.FileUpload.value != '') {
  18:        form.SubmitButton.click();
  19:        
  20:        // Make a copy of the link text and change it to show that the file is being uploaded
  21:        link.rel = link.innerHTML;
  22:        link.innerHTML = uploadStatus;
  23:      }
  24:    }
  25:    
  26:    return false;
  27:  }

It takes a reference to the link, and a subfolder to upload the file to as parameters. When called, it sets the ID of the link (to be utilised later on) and the upload path in the hidden form and then causes IE to prompt for a file. If a file is selected, it then submits the form and changes the text of the link to indicate that the upload is in progress.

On a subsequent click, it detects that the link contains the new text and explains to the user that an upload is already in progress.

So clicking the "Upload File" link prompts the user to select a file and if a file is selected, it submits the form upload the file.

The code behind the UploadFile.aspx file handles the uploading of the file:

   1:          protected void Page_Load(object sender, EventArgs e)
   2:          {
   3:              if (IsPostBack)
   4:              {
   5:                  string filePath = ConfigurationManager.AppSettings["OneClickUploadFilePath"];
   6:                  if (UploadPath.Value != "")
   7:                  {
   8:                      filePath = Path.Combine(filePath, UploadPath.Value);
   9:                  }
  10:   
  11:                  if (!Directory.Exists(filePath))
  12:                  {
  13:                      Directory.CreateDirectory(filePath);
  14:                  }
  15:   
  16:                  FileUpload.SaveAs(Path.Combine(filePath, FileUpload.FileName));
  17:   
  18:                  Body.Attributes["onload"] = "UploadFinished('" + UploadLinkId.Value + "');";
  19:              }
  20:          }

First we get the upload path from the Application Settings of the web.config file (obviously you set this yourself in your own web.config. This is then combined with any subfolder passed through the form, the directory is created if it doesn't already exist and the uploaded file is saved to it.

The final line sets the onload attribute to call a Javascript function called UploadFinished with the ID of the link received from the form. That function can be used to notify the user that the file has finished uploading. In my case I also wanted to offer the option of refreshing the page, as my pages contain a list of the uploaded files:

   1:  function UploadFinished(linkId)
   2:  {
   3:    if(confirm('The file has finished uploading, would you like to refresh the page?')) {
   4:      parent.location.reload(true);
   5:    } else {
   6:      var link = parent.document.getElementById(linkId);
   7:      if(link != null) {
   8:        link.innerHTML = link.rel;
   9:      }
  10:    }
  11:  }

First it asks the user if they would like to refresh the page and if they click "Yes" then the page reloads. If they click "No", then the function uses the link ID - that has been passed through the form - to set the link text back to its original text, thereby enabling the user to upload an additional file.

So there we have it: upload a file using a single link rather than a pair of ugly form controls.

Tagged as: asp.net, javascript, csharp,