Showing posts with label microformats. Show all posts
Showing posts with label microformats. Show all posts

Saturday, April 12, 2008

Operator user script: Add to Google Contacts (GData API)

I wanted to export a hcard to my google contacts with Operator, but there was no such thing.

I went off and tinkered around with the google contacts API, and operator.

I did this all while experimenting with Flock 1.1, (by the way, Flock still sucks, but this time because it's only got a handful of sites it integrates with); so if it breaks in your fancy new firefox, don't look at me!

To use it;
Copy and Paste, save as add-google-contact.js
Install Operator
Tools, Options, Operator
User scripts tab
Find add-google-contact.js
Find a site with hcards (like this one), and export away to your Google account!

var add_google_contact_login_details = {email: false, password: false, auth_token: false};

/**
* A helper method to send http requests to the google services.
*/
function add_google_contact_send_request(method, url, content, auth, content_type) {
request = new XMLHttpRequest();

request.open(method, url, false);

if (auth) {
request.setRequestHeader("Authorization", "GoogleLogin auth=" + auth);
}

if (content_type) {
request.setRequestHeader("Content-type", content_type);
}

try {
request.send(content);

if (request.status == 200 || request.status == 201 || request.status == 409) {
return request.responseText;
}
dump(request.status);
dump(request.responseText);
} catch (ex) {
dump(ex);
}

return null;
}

/**
* Extracts information out from a hCard semantic object
* and returns a google-friendly XML representation.
*/
function add_google_contact_create_xml_from_vcard(hcard) {
var i;
var full_address;
var email;
var tel;
var url;
var xml = "";

xml += "<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'>" + "\n";
xml += " <atom:category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact' />" + "\n";

//Parse name
xml += " <atom:title type='text'>" + hcard.fn + "</atom:title> " + "\n";

xml += " <atom:content type='text'>Notes</atom:content>" + "\n";

if (hcard.email) {
for (i = 0; i < hcard.email.length; i++) {
email = hcard.email[i];

type = 'home';
if (email.type && email.type[0] == 'work') {
type = 'work';
}

xml += " <gd:email rel='http://schemas.google.com/g/2005#" + type + "' address='" + email.value + "' />" + "\n";
}
}


if (hcard.tel) {
for (i = 0; i < hcard.tel.length; i++) {
tel = hcard.tel[i];

type = 'home';
if (tel.type && tel.type[0] == 'work') {
type = 'work';
}
xml += " <gd:phoneNumber rel='http://schemas.google.com/g/2005#" + type + "'>" + tel.value + "</gd:phoneNumber>" + "\n";
}
}


if (hcard.adr) {
for (i = 0; i < hcard.adr.length; i++) {
adr = hcard.adr[i];
full_address = "";
if (adr["street-address"]) {
full_address += adr["street-address"] + " ";
}

if (adr["locality"]) {
full_address += adr["locality"] + " ";
}

if (adr["region"]) {
full_address += adr["region"] + " ";
}

if (adr["postal-code"]) {
full_address += adr["postal-code"] + " ";
}

if (adr["country-name"]) {
full_address += adr["country-name"] + " ";
}

if (full_address != "") {
xml += " <gd:postalAddress rel='http://schemas.google.com/g/2005#work'>" + full_address + "</gd:postalAddress>" + "\n";
}
}
}


xml += "</atom:entry>" + "\n";

return xml;
}

/**
* Send a create contact request for the email & auth_token provided.
*
* The contact is described in xml.
*
* @see add_google_contact_create_xml_from_vcard()
*/
function add_google_contact_create_contact(email_address, auth_token, xml) {
url = 'http://www.google.com/m8/feeds/contacts/' + escape(email_address) + "/base";

return add_google_contact_send_request("POST", url, xml, auth_token, "application/atom+xml");
}

/**
* Fetch an authorisation token for a given
* username and password
*
* @return An authorisation token string
*/
function add_google_contact_login(username, password) {
var url = 'https://www.google.com/accounts/ClientLogin';
var content = "";

content += "accountType=HOSTED_OR_GOOGLE";
content += "&Email=" + username;
content += "&Passwd=" + password;
content += "&service=cp";
content += "&source=NoCompany-Operator-0.1";


response = add_google_contact_send_request("POST", url, content, null, "application/x-www-form-urlencoded");


// Sample response
/*
HTTP/1.0 200 OK
Server: GFE/1.3
Content-Type: text/plain

SID=DQAAAGgA...7Zg8CTN
LSID=DQAAAGsA...lk8BBbG
Auth=DQAAAGgA...dk3fA5N
*/
if (response) {
parts = response.split("\n");
return parts[2].substring(5);
}

return null;
}

function add_google_contact_get_login_details() {
var passwordManager = Components.classes["@mozilla.org/passwordmanager;1"]
.getService(Components.interfaces.nsIPasswordManager);

var e = passwordManager.enumerator;

//Ask the existing password manager for google account details
var queryString = 'https://www.google.com';

while (e.hasMoreElements()) {
try {
var pass = e.getNext().QueryInterface(Components.interfaces.nsIPassword);

if (pass.host == queryString) {
email_address = pass.user;
password = pass.password;

//TODO: Check if the email_address is valid (I store my username without the @ details)
return {email: email_address, password: password, auth_token: false};
}
} catch (ex) {
dump(ex);
}
}

//We didn't find the details. Oh dear.
//Better ask nicely.

var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);


email_address = {value: ""};
password = {value: ""};
check = {value: true};

var result = prompts.promptUsernameAndPassword(null, "", "Enter email and password for your Google Account:",
email_address, password, "Remember password", check);


if (check.value) {
try {
passwordManager.addUser(queryString, email_address.value, password.value);
} catch (ex) {
dump(ex);
}
}

return {email: email_address, password: password, auth_token: false};
}


var add_google_contact = {
description: "Add to Google Contacts",
shortDescription: "Add Google Contact",
scope: {
semantic: {
"hCard" : "fn"
}
},
doAction: function(semanticObject, semanticObjectType, propertyIndex) {
//Do we have login details?
if (add_google_contact_login_details.email == false) {
add_google_contact_login_details = add_google_contact_get_login_details();

//If the user cancelled finding them...
if (add_google_contact_login_details.email == false) {
return false
}
}

if (!add_google_contact_login_details.auth_token) {
add_google_contact_login_details.auth_token = add_google_contact_login(add_google_contact_login_details.email,
add_google_contact_login_details.password);
}

xml = add_google_contact_create_xml_from_vcard(semanticObject);

result = add_google_contact_create_contact(add_google_contact_login_details.email, add_google_contact_login_details.auth_token, xml);
}
};

SemanticActions.add("add_google_contact", add_google_contact);

Sunday, March 23, 2008

How to make OpenID really rock (user signup process)

Robby finds himself asking why come I have 75 openids, and why can't I just transfer my details from site to site?

Well Robby, this is for you.


First, you need to grab XML_GRDDL. It's fairly stable at the moment, and a PEPR proposal.

For now, do a

pear install http://xmlgrddl.googlecode.com/files/XML_GRDDL-0.0.4.tgz

... but if it gets through PEPR, this will be easier.


Be warned, you'll need version of PHP 5.2.5+ unless you can compile php with a decent version of libxml / libxslt.

You also need the XSL extension.

Open up your favourite editor.

Paste in:

require_once 'XML/GRDDL.php';

/**
* Example: Fetch multiple URLs about a specific user
* and get useful information out.
*/
$urls = array();
$urls[0] = 'http://flickr.com/people/clockwerx/';
$urls[1] = 'http://www.linkedin.com/in/clockwerx';
$urls[2] = 'http://www.last.fm/user/CloCkWeRX/';
$urls[3] = 'http://clockwerx.blogspot.com/';

//For each URL, pretend it has these urls in <head profile="foo" />
//These look for hcard, hcalendar, etc.
$profiles[$urls[0]][] = 'http://www.w3.org/2002/12/cal/cardcaletc';
$profiles[$urls[1]][] = 'http://microformats.org/wiki/hresume-profile';
$profiles[$urls[1]][] = 'http://www.w3.org/2002/12/cal/cardcaletc';
$profiles[$urls[2]][] = 'http://www.w3.org/2002/12/cal/cardcaletc';
$profiles[$urls[3]][] = 'http://www.w3.org/2002/12/cal/cardcaletc';

//Set what kind of transformations we're interested in.


$options = XML_GRDDL::getDefaultOptions();
$options['quiet'] = true;

$grddl = XML_GRDDL::factory('xsl', $options);
$results = array();
foreach ($urls as $n => $url) {
$data = $grddl->fetch($url);

$data = $grddl->prettify($data);

$modified_data = $grddl->appendProfiles($data, $profiles[$url]);

$stylesheets = $grddl->inspect($modified_data, $url);

$rdf_xml = array();
foreach ($stylesheets as $stylesheet) {
$rdf_xml[] = $grddl->transform($stylesheet, $modified_data);
}

$results[$url] = array_reduce($rdf_xml, array($grddl, 'merge'));
}

print "We scuttered " . count($urls) . " urls and found these results\n";
foreach ($results as $url => $rdf_xml) {
print $url . "\n";

$sxe = simplexml_load_string($rdf_xml);
$sxe->registerXPathNamespace('vcard', 'http://www.w3.org/2006/vcard/ns#');
$sxe->registerXPathNamespace('ical', 'http://www.w3.org/2002/12/cal/icaltzd#');

print "We found the following pieces of information, choose which are yours:\n";
$xpaths = array();
$xpaths["Formatted name"] = '//vcard:fn';
$xpaths["First name"] = '//vcard:givenName';
$xpaths["Last name"] = '//vcard:familyName';
$xpaths["Email"] = '//vcard:email';
$xpaths["Homepage or URl"] = '//vcard:url';
$xpaths["Workplace name"] = '//vcard:organization-name';
$xpaths["Photo URL"] = '//vcard:photo';
$xpaths["Locality"] = '//vcard:locality';
$xpaths["Position/Title"] = '//vcard:title';

foreach ($xpaths as $name => $xpath) {
$results = $sxe->xpath($xpath);
if (empty($results)) {
continue;
}

print $name . ": ";
foreach ($results as $node) {
print trim((string)$node);
$attributes = $node->attributes(XML_GRDDL::RDF_NS);
if (!empty($attributes['resource'])) {
print trim((string)$attributes['resource']);
}
print "\n";
}
}
//print $rdf_xml . "\n\n";
print "\n";
}


Save it, run it.

You *should get*:

---------- PHP ----------
We scuttered 4 urls and found these results
http://flickr.com/people/clockwerx/
We found the following pieces of information, choose which are yours:
Formatted name: DanielO'Connor
First name: Daniel
Last name: O'Connor
Homepage or URl: http://clockwerx.blogspot.com/
Locality: Klemzig
Position/Title: Web Developer

http://www.linkedin.com/in/clockwerx
We found the following pieces of information, choose which are yours:
Formatted name: Daniel
O'Connor
Adelaide Institude of TAFE
PEAR member
LIXI Members member
First name: Daniel
Last name: O'Connor
Homepage or URl: http://http;//clockwerx.blogspot.com
http://www.valuationexchange.com.au
Workplace name: PEAR
Valuation Exchange
Fresh FM
Self-employed
Adelaide Institude of TAFE
PEAR member
LIXI Members member
Locality: Adelaide Area, Australia
Position/Title: Developer at Valuation Exchange
Contributer
Software Developer
Web Developer
Freelancer

http://www.last.fm/user/CloCkWeRX/
We found the following pieces of information, choose which are yours:
Formatted name: Daniel O'Connor
NoBloodForOil
Homepage or URl: http://clockwerx.blogspot.com
Photo URL: http://userserve-ak.last.fm/serve/160/682792.jpg
http://userserve-ak.last.fm/serve/50/690470.gif

http://clockwerx.blogspot.com/
We found the following pieces of information, choose which are yours:
Formatted name: Daniel O'Connor
Email: mailto:daniel.oconnor@gmail.com
Homepage or URL: http://clockwerx.blogspot.com/
xmpp:daniel.oconnor@gmail.com
Workplace name: Valuation Exchange


Output completed (30 sec consumed)


Now, how neat is that. You can grab any url which publishes microformats, grab out the hcards from it, grab the information from those, and viola! A pre-populated signup form.


Why is this neat?
* If an OpenID url has Microformats, bam! You can read it.
* If you are a bit more hardcore, you can hook up xOperator and a triplestore to this information.
* Or you could use it in Drupal.

There you have it, ladies and gents: semantic web in a box, with practical applications for user signup.

Friday, October 05, 2007

e-government, SOA and Microformats

The Australian Government appears to get e-government. I'm still waiting on an email reply, to a whole bunch of question I had though, so maybe the AGIMO isn't the most responsive government department...

Anyway, this leads me to ask:
  • What government sites would benefit from basic microformats the most? (vcard, vevent, addresses)
  • What raw data would benefit you the most?
For me, when it comes to work, it's getting hooked up to a title/deed search webservice - it's currently a complete pain and splintered between many different offices.

Personally, I like what the National Archive of Australia has done - rdf for all with AGLS!

Also useful for business is the ABRRegister SOAP services - exposing simple functionality to lookup ABNs.

So readers, what would make you as a private citizen quite pleased to have?

Monday, September 17, 2007

Operator: Find people in whitepages.com.au

Here's an Operator user action to make people searchable on whitepages.com.au

You require a Surname and State/Region, and obviously, them being Australian helps.

Use cases include finding alternative (public) contact details; or confirming an address...

To install: Copy and paste this to a file, whitepages.js, use the Options menu, User Scripts, and load in whitepages.js.


var whitepages_search = {
description: "Find on Whitepages.com.au",
shortDescription: "Whitepages",
icon: "http://whitepages.com.au/wp/favicon.ico",
scope: {
semantic: {
"hCard" : "hCard",
}
},

doAction: function(semanticObject, semanticObjectType) {
var hcard, adr, url;

if (semanticObjectType == "hCard") {
hcard = semanticObject;

if (hcard.adr) {
adr = hcard.adr[0];
}

url = 'http://whitepages.com.au/wp/resSearch.do?';

url += 'subscriberName=' + encodeURIComponent(hcard.n["family-name"]);

url += '&givenName=' + encodeURIComponent(hcard.n['given-name'].substr(0, 1));

if (adr) {
if (adr['postal-code']) {
url += '&suburb=' + encodeURIComponent(adr['postal-code']);
} elseif (adr['locality']) {
url += '&locality=' + encodeURIComponent(adr['locality']);
}

if (adr['region']) {
url += '&state=' + encodeURIComponent(adr['region']);
}
}

url += '&textOnly=true';

return url;

}
}
};

SemanticActions.add("whitepages_search", whitepages_search);

Wednesday, August 01, 2007

Google Maps to have microformats

Microformats in Google Maps.

Pretty neat. If you don't know what this means, I'll tell you.

You would:
  • Search for pizza in collinswood
  • Find your favourite pizza place
  • Click a button
  • Add it magically to your address book
Want to see how? Go and install Operator, and then do the above steps.