Browsing the archives for the Web category.


Twitter Etiquette

Community, Networking, Web

First rule of Twitter etiquette: there is no such thing.  In my opinion, if you don’t like something that someone is doing, you can unfollow them anytime you want.

Having said that, what are some of the new themes in Twitter etiquette that are worth commenting on?  Let’s take a tour.

#hashtags: Hashtags are those words prefixed by the pound sign (“#”, also known a hash sign) that you see in tweetstreams.  They supposedly provide context and metadata to tweets, but in practice, I see them used mostly for emphasis, and that’s how I use them.  It’s slightly painful to see well-meaning twitterati try to coordinate hashtags for an event when everyone has already started using different ones.

Blog posts: Normally people are pretty good about noting that they are linking to one of their own posts by prefixing their tweet with [blog] or [post] or something similar.  Bloggers who repeatedly spam their own posts get blocked, period.  Twitter in my mind is a lifestreaming app, not a one-way push mechanism.

Retweets: Twitter recently fucked up the retweet function on their website, and not only don’t allow you to edit tweets for length, or add your own prefix comment.  Luckily most other tools still allow you retweet and  keep your sanity.  A couple tricky issues with retweets:

  • How much editing is too much?
  • If an item has been retweeted before it got to you, do you have to keep the whole chain of retweeters?

I have no hard-and-fast expectation for either.  If you want to edit the retweet, go for it.  If you want to chop out multiple retweeters, go for it.  Try your best to keep original attribution, but don’t go all haywire applying severe rules to a fluid medium.

Grammar: A lot of twitter grammarians have already checked themselves in to inpatient psych wards, so this problem is less of an issue than it used to be.  Twitter grammar sucks.  People type quickly, they type while driving, they type while roller skating backwards and locking lips with their significant others.  Bad grammar happens.  Even if bad grammar chafes (as it does me), you can learn to deal with it.  People are messy. So is Twitter.

Rickrolling:  Rickrolling is like heavy drinking; every now and then it can be a fun break.  Do it every day and people start to shun you.

Protected Updates: My favorite phrase on this question is “protected updates make baby Jesus cry.”  Don’t protect your updates and still pretend that you’re “doing” Twitter.  Get over your fear of the Wayback Machine, live your life authentically, and don’t worry what some future boss/friend/spouse might think about something you tweeted back in 2008.

Inanity: Sometimes tweets make no sense.  In my opinion, that’s part of the fun of Twitter; you get to see people as they really are, without a lot of filtering and polishing and pre-planning and run-this-by-my-publicist checking.  Tweeps who are too polished are boring, IMHO.  On the other hand, if you like the  corporate flavors, feel free to unfollow someone who tweets about the junk that their cat just threw up.

Anything to add?  Things you love or hate about Twitter?  Write it in the comments!

2 Comments

Widgets, Widgets, Widgets

Software, Web

I’m going to start looking closely at widget technology.  Not widget as in “doodad: something unspecified whose name is either forgotten or not known”, but widget as in “a live update on a website, webpage, or desktop.”  I have been thinking for some time about using widgets as part of a distribution strategy for Crowdify, and a recent phone call convinced me to jump on the ball and get the tech done.

So, what’s a widget, really?  It’s something that:

a) publishers (using that term VERY broadly) can get;

b) runs on a webpage;

c) dynamically pulls content at render-time from a source location;

d) (optionally) uses JavaScript to do snazzy UI effects;

e) (optionally) allows website visitors to interact with them

The user views the widget contents, interacts with the widget by clicking or what-have-you, and hopefully does whatever the widget provider wanted them to do in the first place.  For Crowdify, that will mean clicking on terms and submitting them.

You can think of banner ads as a widget, using definitions (a) through (c), above.  I’m more interested in examples of fully-interactive widgets, since that’s what I’ll need for Crowdify.

So what sorts of architectures can one use for widgets?  (I’m talking the display of the widget on the publisher’s site, not the getting of the widget code in the first place).

  • Well, <IFRAME>s are deprecated all to hell, and for good reason.  Next.
  • I see a lot of <script> tags that pull HTML content back from a dynamic source, i.e. PHP or ASPX code.  This is the “server proxy” approach.
  • I also see lots of widgets that use a <script> tag that pulls from a remote .js script.  Similar deal as the above, but makes heavier use of JavaScript to render results.  This is the “script tag” approach.
  • You can make Flash widgets that load from a remote host.
  • I’m sure, without checking, that I could do a Silverlight widget.
  • What is this JSONP business?  It’s a way to do Ajax calls without getting drilled by XSS restrictions.  I’m not sure what advantages this has over the server proxy approach, other than the “cool factor”.  Maybe performance?  Agility/more flexibility for API callers?  I need to look into this a bit more.

Enough for now.  More widget research later.

No Comments

Google Real-Time Search Results

Web

Google’s real-time search results are starting to be shown at the top of the search engine results pages.  Here’s a search I did today for “balsamiq”.

image

I like it.  I hope that the interface doesn’t get too cluttered, and of course I REALLY hope that the relevance stays high.  But it’s a natural, necessary next step to incorporate the Real Time Web into the results.

What does it all mean?  I’ll leave that discussion for people who have time to think deeply on the subject, or perhaps I’ll carve out some time for a Seattle 2.0 blog post on the subject.

No Comments

Twilio Demo #3: Appointment Reminder using the REST API

Software, Web

My last Twilio demo showed how you could call Twilio and retrieve information from a web application you’ve built.  This demo shows the reverse scenario: you use a web application that calls out to Twilio, which in turn calls out to a given phone number.

This will be a pretty straightforward translation of Twilio’s existing Appointment Reminder demo, which is written in Ruby.

WARNING: Use this power only for good.  For example, you wouldn’t dream of rickrolling anyone, would you? :)

Let’s get started.  Our web app will be written in ASP.NET/C#, and, unlike the previous example, has no database backend, for simplicity’s sake.  In a real-life application, you would pull these reminders from a database on a schedule; but here we’ll bypass that part.

Before you get started, you’ll need to download the C# REST helper library from the Twilio website. This contains some code that wraps the messy network communications.

Add the file twiliorest.cs to your web application project.

You’ll need two ASPX pages: a page that captures the phone number you want Twilio to call, and a page that Twilio uses to decide what to do based on the callee’s responses.

Here’s the web form to capture input. Put this in your <form> tag in a new web form page called default.aspx:

<h1>Twilio phone reminder demo</h1>
<h3>Enter your phone number to receive an automated reminder</h3>

Here’s the code-behind in default.aspx.cs:

using System;
using System.Collections;
using TwilioRest;
 
namespace Twilio.Web.appointment
{
    public partial class _default : System.Web.UI.Page
    {
	protected const string ACCOUNT_SID = "SSSSSSSSSSSSSSSSSSSSSSSSSSSSS";
        protected const string AUTH_TOKEN = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
        protected const string API_VERSION = "2008-08-01";
        protected const string CALLER_ID = "NNN-NNN-NNNN";
 
        protected void SubmitButton_Click(object sender, EventArgs e)
        {
            string phoneNumber = Server.HtmlEncode(this.PhoneNumberText.Text);
            this.MakeTwilioCall(phoneNumber);
        }
 
        protected virtual void MakeTwilioCall(string phoneNumber)
        {
            TwilioRest.Account account = new Account(ACCOUNT_SID, AUTH_TOKEN);
 
            // create the URL at Twilio to post to
            string postUrl = "/{0}/Accounts/{1}/Calls";
            postUrl = string.Format(postUrl, API_VERSION, ACCOUNT_SID);
 
            // create the variables to pass in to the POST
            string reminderUrl = Page.Request.Url.GetLeftPart(UriPartial.Authority) + "/appointment/reminder.aspx";
            Hashtable vars = new Hashtable(3);
            vars.Add("Caller", CALLER_ID);
            vars.Add("Called", phoneNumber);
            vars.Add("Url", reminderUrl);
            try
            {
                string response = account.request(postUrl, "POST", vars);
            }
            catch (Exception exc)
            {
                this.MessageElement.InnerText = exc.Message;
            }
        }
    }
}

What we’re doing here is simply handling the button click event and then using the Twilio helper library to make the RESTful call to the Twilio servers. Of course you need to put your own values in for ACCOUNT_SID, AUTH_TOKEN, and CALLER_ID, which you can get from your Account page on the Twilio website.

Twilio will call the phone number you enter in the text box, and use a new page /appointment/reminder.aspx (which you have to create) as the “controller” for the outbound call.

Your reminder.aspx page should be blank (since you will be writing XML output, not HTML). Here’s the code-behind for reminder.aspx:

using System;
using System.Collections;
using System.Xml;
 
namespace Twilio.Web.appointment
{
    public partial class reminder : System.Web.UI.Page
    {
        protected string Url = null;
 
        protected void Page_Load(object sender, EventArgs e)
        {
            Page.Response.ContentType = "text/xml";
            this.Url = Page.Request.Url.GetLeftPart(UriPartial.Authority) + "/appointment/reminder.aspx";
 
            if (Request["Digits"] != null)
            {
                DoAction(int.Parse(Request["Digits"].ToString()));
            }
            else
            {
                this.WriteReminder();
            }
        }
 
        protected virtual void DoAction(int digit)
        {
            switch (digit)
            {
                case 3:
                    {
                        WriteGoodbye();
                        break;
                    }
                case 2:
                    {
                        WriteDirections();
                        break;
                    }
                default:
                    {
                        WriteReminder();
                        break;
                    }
            }
        }
 
        protected virtual void WriteGoodbye()
        {
            this.WriteXmlToResponse("goodbye.xml", null);
        }
 
        protected virtual void WriteDirections()
        {
            this.WriteXmlToResponse("directions.xml", new string[1] { this.Url });
        }
 
        protected virtual void WriteReminder()
        {
            this.WriteXmlToResponse("reminder.xml", new string[1] { this.Url });
 
        }
 
        /// 
        /// XML helper method
        /// 
        protected virtual void WriteXmlToResponse(string fileName, string[] values)
        {
            System.Xml.XmlDocument doc = new XmlDocument();
            string path = Server.MapPath(Page.Request.ApplicationPath) + @"\appointment\" + fileName;
            doc.Load(path);
            string xml = doc.OuterXml;
            xml = string.Format(xml, values);
            Response.Write(xml);
        }
    }
}

We’re using the same WriteXmlToResponse() helper method we created in our last demo. There are three output functions: WriteReminder(), WriteDirections(), and WriteGoodbye(), which correspond to the three choices the user can make when they receive the phone call.

Also note that we set the MIME content-type to “text/xml” in the first line of Page_Load().

Not much to it, is there?

To make this demo work, you’ll also need three XML files that contain the content to read to the user:

reminder.xml

 
			Hello this is a call from Crowdify.  You have an appointment tomorrow at 9 AM.
 
			Please press 1 to repeat this menu. Press 2 for directions.	Or press 3 if you are done.

directions.xml

 
	Your appointment is located on the top of Mount Everest.
	{0}

goodbye.xml

 
	Good bye.

Note that in the first two XML files we are using the same String.Format() placeholders as in our previous demo.

That’s it – an end-to-end demo of using the Twilio REST API. The helper library that Twilio provides makes it really easy to get going in no time at all.

UPDATE: I’ve zipped the source (which includes code for all three recent Twilio demos) and saved it at http://thepursuitofalife.com/wp-content/uploads/2009/01/twilio-demo.zip.

Technorati Tags: ,,

7 Comments

Twilio Company Directory Example

Software, Web

Moving on from the very simple “Hello, World” sample I discussed in my last post, I’ll now describe how to create a simple company directory application that uses Twilio in ASP.NET, C#, LINQ, and SQL Server.

This sample is translated pretty directly from the PHP example found on the Twilio demo page.

Unlike the last sample, this one actually uses some C# code-behind and has a backing database to look up and retrieve information.

OK, let’s begin with the database code. Open SQL Server Management Studio and create a new table called directory. You can do this manually via the GUI, or you can run this script:

1
2
3
4
5
CREATE TABLE directory
(
	extension varchar(12) NOT NULL PRIMARY KEY CLUSTERED,
	phone_number varchar(30)
)

Insert data into your new table using the following script:

1
2
INSERT INTO directory (extension, phone_number) VALUES (“0”, “206-555-1212”)
INSERT INTO directory (extension, phone_number) VALUES (1234,206-555-3434)

Using  Visual Studio, create a new C# web application project.

Add a new LINQ to SQL file called twilio_demo.dbml.  This will contain the LINQ mappings between your ASP.NET application and your database.

In the Server Explorer, add a new data connection by right-clicking the Data Connections folder and selecting Add Connection.

image

This will show you the Add Connection dialog:

clip_image001

Open the DBML file in the editor.

In the Server explorer, drill down through your new data connection to the Tables node.  You should see a single table called directory.

Using your mouse, drag the table to your DBML editor pane.  You’ll see a visual representation in the editor like so:

image

Next, you need to set up your content.  I opted to create separate XML files for this purpose, rather than writing the XML inline.  To do this, create three XML files as follows:

initial_greeting.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0"; encoding="utf-8" ?>
<response>
	<gather>
		<say>Thank you for calling Crowdify.</say>
		<say>
			If you know your party's extension, please enter it now,
			followed by the pound sign.  Otherwise, stay on the line for the receptionist.
		</say>
	</gather>
	<say>Connecting you to the operator, please stay on the line</say>
	<dial>
		{0}
	</dial>
</response>

dialing.xml

1
2
3
4
5
6
7
<?xml version="1.0"; encoding="utf-8" ?>
<response>
	<say>Thank you, dialing now</say>
	<dial>
		{0}
	</dial>
</response>

not_found.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0"; encoding="utf-8" ?>
<response>
	<gather>
		<say>I'm sorry, we couldn't find the extension {0}.</say>
		<say>
			If you know your party's extension, please enter it now,
			followed by the pound sign.  Otherwise, stay on the line for the receptionist.
		</say>
	</gather>
	<say>Connecting you to the operator, please stay on the line</say>
	<dial>
		{1}
	</dial>
</response>

You’ll notice that these XML files have string placeholders of the format {n}, into which we’ll put the dynamically-retrieved information from the database.  You could do this in a more type-safe way by using XSLT and passing parameters, but we’ll stay simple for now.

Now you’re ready to code your page that will handle the incoming Twilio requests.  Add a new ASPX page called default.aspx.

Open the file in the HTML editor and delete everything but the <%@ Page %> directive.  This is because this file will be displaying XML and we don’t want any of the default HTML hanging around.

To set up your file for displaying the correct MIME content-type, add this line to your Page_Load() method:

Page.ContentType = “text/xml”;

Now for the rest of the code.  For brevity, I’ll display the whole code of the default.aspx.cs file below, and comment afterward.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml;
using System.Xml.Linq;
 
namespace Twilio.Web.directory_simple
{
    public partial class _default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Page.Response.ContentType = &quot;text/xml&quot;;
            string extension = this.GetExtensionFromRequest();
            string receptionistNumber = this.GetReceptionistNumber();
 
            if (extension == null)
            {
                this.WriteInitialGreeting(receptionistNumber);
            }
            else
            {
                string number = this.GetPhoneNumberByExtension(extension);
                if (number != null)
                {
                    this.WriteDialing(number);
                }
                else
                {
                    this.WriteNotFound(extension, receptionistNumber);
                }
            }
        }
 
        /// <summary>
        /// Page POST variable retreival
        /// </summary>
        protected virtual string GetExtensionFromRequest()
        {
            string extension = null;
            object digits = Request[&quot;Digits&quot;];
            if (digits != null)
            {
                extension = digits.ToString();
            }
            return extension;
        }
 
        /// <summary>
        /// XML helper method
        /// </summary>
        protected virtual void WriteXmlToResponse(string fileName, string[] values)
        {
            System.Xml.XmlDocument doc = new XmlDocument();
            string path = Server.MapPath(Page.Request.ApplicationPath) + @"\directory_simple\" + fileName;
            doc.Load(path);
            string xml = doc.OuterXml;
            xml = string.Format(xml, values);
            Response.Write(xml);
        }
 
        #region Database Methods
 
        protected virtual string GetPhoneNumberByExtension(string extension)
        {
            string phone = null;
            string connString = ConfigurationManager.ConnectionStrings["twilio_demoConnectionString"].ConnectionString;
            using (twilio_directoryDataContext tdc = new twilio_directoryDataContext(connString))
            {
                var item = tdc.directories.SingleOrDefault(x => x.extension == extension);
                if (item != null)
                {
                    phone = item.phone_number;
                }
            }
 
            return phone;
        }
 
        protected virtual string GetReceptionistNumber()
        {
            return this.GetPhoneNumberByExtension("0");
        }
 
        #endregion
 
        #region Write Methods
 
        protected virtual void WriteInitialGreeting(string receptionistNumber)
        {
            this.WriteXmlToResponse("initial_response.xml", new string[1] { receptionistNumber });
        }
 
        protected virtual void WriteDialing(string number)
        {
            this.WriteXmlToResponse("dialing.xml", new string[1] { number });
        }
 
        protected virtual void WriteNotFound(string number, string receptionistNumber)
        {
            this.WriteXmlToResponse("not_found.xml", new string[2] { number, receptionistNumber });
        }
 
        #endregion
 
    }
}

Comments about this code:

In order to use the ConfigurationManager class, you need to add a reference to System.Configuration, version 2.0.50727, via the Add References dialog.

You’ll see the method GetPhoneNumberFromExtension uses the LINQ data context that was created automatically when you created your .DBML file. LINQ makes data access incredibly easy.

The Page_Load() method contains some simple logic to write out the appropriate XML content based on whether or not we’ve gotten a Digits parameter via the Post variable. I intentionally left it open to retrieving QueryString variables as well, for debugging purposes.

The XML writing helper method is self-explanatory. Note that the code for this example lives in a /directory_simple/ subdirectory of my web application.

The Connection String information was generated for you when you created your twilio_demo DBML file. It’s placed by default in the web.config file:

1
2
3
<connectionStrings>
		<add name="twilio_demoConnectionString" connectionString="Data Source=ANTHONYS-M1330\SQL2005DEV;Initial Catalog=twilio_demo;Integrated Security=True" providerName="System.Data.SqlClient"/>
	</connectionStrings>

Once you compile and deploy your web application (including the XML files), you can dial your Twilio number and work your way through the possible workflows:

  • Initial greeting; enter an extension
  • Initial greeting; wait for receptionist
  • Extension not found
  • Extension found; dialing

Again, this is a fairly simple example that doesn’t begin to tap the full power of Twilio, but shows you how to get going with a simple database-driven telephony app.

Technorati Tags:

2 Comments

Twilio Simple “Hello, World!” Demo

Software, Web

I’ve been fooling around with a very powerful telephony-enabling technology called Twilio and did a couple demos in ASP.NET and C# today.

The first one is a simple “Hello, World” demo that shows how you can tie your Twilio phone number to a simple recording that is served up by your website.

Here are the steps:

  • In Visual Studio 2008, create a new Web Application Project called “Twilio.Web”.
  • Include an new XML file called helloworld.xml.
  • The contents of helloworld.xml should be:
1
2
3
4
<?xml version="1.0" encoding="utf-8" ?>
<Response>
    <Say>Hello world.</Say>
</Response>

  • Compile and deploy your web application
  • Go to twilio.com and in your Account settings, point the "Uses Url" url to your XML file:

image

When you dial your Twilio phone number and enter your pin, the automated voice should say “Hello world.” to you.

Piece of cake.

Technorati Tags: ,,

No Comments

Great Greasemonkey Script Extends Bio Information For Twitter Friends and Followers

Twitter, Web

Hat tip to Marjolein Hoekstra for this link to Twitter Friends’ Bio at a Glance, a greasemonkey script that adds additional information to your friend and follower lists at twitter.com.

Even if you don’t use twitter.com for tweets, it’s a handy place to review who follows you and whom you follow.  With this extra script, it becomes even handier!

Screenie:

image

Technorati Tags: ,

3 Comments

Just Tweet It Fail

Software, Web

This morning, when trying to register myself in the Just Tweet It! directory (think the old-school Yahoo directory, but for Twitter users), I get this:

image

Bummer.  After just reading a great post by Om Malik on mediocrity, this is a disappointment.

Technorati Tags:

2 Comments

The Intercepter Greasemonkey Script

Software, Twitter, Web

Intercepter 1.2 is a greasemonkey script for Firefox that replaces TinyURL links embedded in a web page with the “real” domains so that you know roughly where you’re going before you click.

Before example:

After example:

A couple notes:

  1. The script only works on TinyURL – not other url-shortening services like Snurl.
  2. It only shows the top-level domain, not the final URL – which is probably enough to give you an idea of where you’re being redirected to.

No Comments

Liverpool Beats Manchester United in Googlefight!

Humor, Web

You should check out Googlefight.  Here’s my favorite so far:

It’s not even close! :)

No Comments
« Older Posts