l'essentiel est invisible pour les yeux

Showing posts with label Ruby. Show all posts
Showing posts with label Ruby. Show all posts

Tuesday, April 29, 2008

Edge Rails: Describes Gem Dependencies

So far today, Gem dependencies might be written with Capistrano, but we can write Gem dependencies in config/environemt.rb with Edge Rails, and it will be installed with Rake task.

# in config/enviroment.rb


config.gem 'hpricot'
config.gem 'aws/s3', '>= 0.4.0'
config.gem 'aws-s3', :lib => 'aws/s3', :version => '>= 0.4.0', :source => "http://code.whytheluckystiff.net"


Install all required Gems.
rake gems:install

Cool

Wednesday, April 23, 2008

DRY routing with form generated by Rails helper methods, shouldn't write routings directly in Javascript code.

The routing solution in Rails is pretty powerful, so we can modify routing easiliy. This facility may become cause of bugs. If we wanna submit data asynchronously with Javascript, then we have to write some routing paths in source of Javascript directly. It's not DRY and it doesn't work together with Rails routing solution.

So we think example code which post a comment to the article. We have two resources, articles, comments.


map.resources :articles, :has_many => [:comments]


In this case, I guess most people use helper methods which built in rails.

<% # The generated path (aka action) is same as "/articles/:article_id/comments" %>
<% remote_for_for([@article, Comment.new]) do |f| %>
<%= f.text_field :body %>
<%= submit_tag 'Post' %>
<% end %>


It's just simple, not a problem.

Imagine create a basket like shopping card, it supports drag and drop user interface. We can drag products or image into basket. This example has routings is as follows:

map.resource :basket, :has_many => [:items]


And we have to implement drag and drop user interface, following code is sample implementation with jQuery. It has URL where submits item data. If someone will change Rails routings, this code will be sucks.


$(function(){
// FIXME: It's not DRY
var ADD_ITEMS_TO_BASKET_PATH = '/basket/items';

$('img.draggableItem').draggable();
$('#basket').droppable({
drop: function(e) {
$.ajax({
url: ADD_ITEM_TO_BASKET_PATH,
type: 'post',
success: function(){}
});
}
});
});

So I guess we should use forms generated by Rails helper methods, and It set form's visibility is hidden. You set a required data into fields, and submit the form.

In Rails view:

<% remote_form_for([@basket, Item.new], :html => {:id => 'add_to_basket', :style => 'display:none'}) do |f| %>
<%= f.hidden_field :id %>
<%= submit_tag 'Add an item' %>
<% end %>

In Javascript:

$(function(){
$('img.draggableItem').draggable();
$('#basket').droppable({
drop: function(e, ui) {
var itemId = itemId(e.target);
var form = $('#add_to_basket');
form.find('> input[name="item[id]"]').val(itemId);
$('#add_to_basket > input[type="submit"]').click();
}
});
});


In this case, we don't need to write any routings directly in Javascript, so It's DRY.

Monday, April 21, 2008

rubyonrails.org isn't available now?

It might forget to update a domain name, so we can't see rubyonrails.org.


% whois rubyonrails.org
Last Updated On:20-Apr-2008 18:08:51 UTC
Expiration Date:19-Apr-2009 22:40:29 UTC


We can access rubyonrails.com, .net instead of .org.

See also
http://www.ruby-forum.com/topic/150274

Monday, March 24, 2008

[ruby] Why does StoreRequest of OpenID Attribute Exchange (AX) attract our heart? (and DEMO of AX StoreRequest with ruby-openid)

AX StoreRequest message is defined in OpenID Attribute Exchange 1.0. It can store personal identity information to the OpenID Provider from RP. It's exciting specification because we use many services on WWW, and input variety of many personal identity in sites. We can aggregate our personal identity automatically with AX StoreRequest message! Additionally, updated personal identity is synced AX's update_uri property across the all services!

Demo that sent AX StoreRequest message, and dispatch AX StoreResponse message with ruby-openid as follows.

At first, we need to extend ruby-openid to handle AX StoreRequest and StoreResponse usefully.
openid_ax_ext.rb


require 'rubygems'
require 'openid/extension'
require 'openid/message'

module OpenID
module AX
class StoreRequest
# Extract a StoreRequest from an OpenID message
def self.from_openid_request(oid_req)
message = oid_req.message
ax_args = message.get_args(NS_URI)
return nil if ax_args.empty?
req = new
req.parse_extension_args(ax_args)
req
end
end

class StoreResponse
def self.from_success_response(success_response)
resp = nil
ax_args = success_response.message.get_args(NS_URI)
resp = ax_args.key?('error') ? new(false, ax_args['error']) : new
end
end
end
end


app/controller/consumer_controller.rb

# in ConsumerController#start
# AX StoreRequest message
if params[:use_ax_store]
ax_store_req = OpenID::AX::StoreRequest.new
ax_store_req.set_values('http://axschema.org/contact/email', %w([email protected] [email protected]))
ax_store_req.set_values('http://axschema.org/birthDate', %w(1985-03-12))
ax_store_req.set_values('http://axschema.org/contact/IM/Skype', %w(rfurutani))
oidreq.add_extension(ax_store_req)
oidreq.return_to_args['did_ax_store'] = 'y'
end


Next, OP handles the request included StoreRequest message.
app/controller/server_controller.rb

# in ServerController#start
# Check the AX StoreRequest
@ax_store_req = OpenID::AX::StoreRequest.from_openid_request(oidreq)


And OP must save personal identity sent from RP.

# Extract Personal Identity from request and save it.
# Check the StoreaRequest for AX
ax_store_resp = nil
ax_store_req = OpenID::AX::StoreRequest.from_openid_request(oidreq)
if ax_store_req
begin
# I think all user's attribute should be saved, but table structure of RDBMS VERY statically.
# So we need to respond StoreResponse mode is 'store_response_failure'.
# In a nut shell, we may use XMLDB by now!
ax_store_req.data.each do |type_uri, values|
case type_uri
when 'http://axschema.org/email'
# XXX: This is dummy
user.emails.concat(values)
when 'http://axschema.org/birthDate'
# XXX: This is dummy
user.birth_date = Date.parse(values.first)
when 'http://axschema.org/contact/IM/Skype'
# XXX: This is dummy
# Just one
user.skype_id = values.first
else
ax_store_resp = OpenID::AX::StoreResponse.new(false, "#{type_uri} isn't supported Type URI in this service.")
end
end
user.save
rescue Error => e
ax_store_resp = OpenID::AX::StoreResponse.new(false, e.message)
end
end
oidresp.add_extension(ax_store_resp || OpenID::AX::StoreResponse.new)


This is the sample view for AX StoreRequest added the checkbox enable it.

<% if flash[:ax_store_results] %>
<div class='error'>
<%= flash[:ax_store_results] %>
</div>
<% elsif params[:did_ax_store] -%>
<div class='alert'>
Your Personal identity has saved.
</div>
<% end %>
<div id="verify-form">
<form method="get" accept-charset="UTF-8"
action='<%= url_for :action => 'start' %>'>
Identifier:
<input type="text" class="openid" name="openid_identifier" />
<input type="submit" value="Verify" /><br />
<input type="checkbox" name="immediate" id="immediate" /><label for="immediate">Use immediate mode</label><br/>
<input type="checkbox" name="use_sreg" id="use_sreg" /><label for="use_sreg">Request registration data</label><br/>
<input type="checkbox" name="use_pape" id="use_pape" /><label for="use_pape">Request phishing-resistent auth policy (PAPE)</label><br/>
<input type="checkbox" name="force_post" id="force_post" /><label for="force_post">Force the transaction to use POST by adding 2K of extra d
ata</label><br/>
<input type="checkbox" name="use_ax" id="use_ax" /><label for="use_ax">Request registration data with AX</label><br/>
<input type="checkbox" name="use_ax_store" id="use_ax_store" /><label for="use_ax_store">Request store attributes with AX</label>
</form>
</div>


Confirmation of stored personal identities.
app/views/server/decide.rhtml

<% # XXX: Part of axschema -%>
<% axschema_map = {
'http://axschema.org/birthDate' => 'Birthdate',
'http://axschema.org/contact/email' => 'Email',
'http://axschema.org/contact/IM/Skype' => 'Skype'
} -%>
<% # Is StoreRequest included in request? -%>
<% if @store_req -%>
<div class="attention">
<p>And do you accept following data sent from RP?</p>
<dl>
<% @store_req.data.each do |k,v| -%>
<dt><%= axschema_map[k] -%>:</dt>
<dd><%= h v.join(', ') -%></dd>
<% end -%>
</dl>
</div>
<% end -%>


Finally, request is coming back to RP, so We need to handle success response.
app/controllers/consumer_controller.rb

# in ConsumerController#complete
# for AX store
if params[:did_ax_store]
ax_store_resp = OpenID::AX::StoreResponse.from_success_response(oidresp)
flash[:ax_store_results] = ax_store_resp.error_message unless ax_store_resp.succeeded?
end


Screenshot
These screenshot are capture output of above code.


Confirmation of stored personal identities.


Conclusion
This future is pretty interesting, but it isn't suppoted even myOpenID now. In the future, I think many OP supports AX StoreRequest. I wish!

Saturday, March 22, 2008

[ruby] myOpenID and OpenID Attributes Exchange, myOpenID isn't supported Type URI described in http://axschema.org/

JanRain's myOpenID is popular OpenID provider, supports SSL, SREG, some persona, authentication with Information Card, OpenID Attributes Exchange (but only some attributes), Pavatar and so on. JanRain also offers ruby-openid that implements OpenID 2.0 with Ruby.

myOpenID supports only some attributes that can be exchange with OpenID Attribute Exchange, but you should care about Type URI, because myOpenID doesn't support Type URI described on "http://www.axschema.org/types/". You should specify Type URI with "http://schema.openid.net/" instead.

Sample code that fetch some attributes from myOpenID is as follows. At first, you need to initialize your persona. My persona for work is "Rakuto Furutani".

ConsumerController#start in /path/to/rails_openid/app/controller/consumer_controller.rb


# AX
if params[:use_ax]
# Builds Fetch Request of Attributes Exchange
ax_req = OpenID::AX::FetchRequest.new
requested_attrs = [ ['http://schema.openid.net/namePerson', 'name', true],
['http://schema.openid.net/namePerson/friendly', 'nickname'],
['http://schema.openid.net/contact/email', 'email', true],
['http://schema.openid.net/contact/web/default', 'web_default'],
['http://schema.openid.net/contact/postalCode/home', 'postal_code'],
['http://schema.openid.net/person/gender', 'gender'],
['http://schema.openid.net/birthDate', 'birthDate'],
['http://schema.openid.net/contact/country/home', 'country'],
['http://schema.openid.net/pref/language', 'language']]

requested_attrs.each {|a| ax_req.add(OpenID::AX::AttrInfo.new(a[0], a[1], a[2] || false))}
oidreq.add_extension(ax_req)
oidreq.return_to_args['did_ax'] = 'y'
pp oidreq
end


Customize a view
/path/to/rails_openid/app/views/consumer/index.rhtml

<% if flash[:ax_results]%>
<div class='alert'>
<%= flash[:ax_results] %>
</div>
<% end %>
<div id="verify-form">
<form method="get" accept-charset="UTF-8"
action='<%= url_for :action => 'start' %>'>
Identifier:
<input type="text" class="openid" name="openid_identifier" />
<input type="submit" value="Verify" /><br />
<input type="checkbox" name="immediate" id="immediate" /><label for="immediate">Use immediate mode</label><br/>
<input type="checkbox" name="use_sreg" id="use_sreg" /><label for="use_sreg">Request registration data</label><br/>
<input type="checkbox" name="use_pape" id="use_pape" /><label for="use_pape">Request phishing-resistent auth policy (PAPE)</label><br/>
<input type="checkbox" name="force_post" id="force_post" /><label for="force_post">Force the transaction to use POST by adding 2K of extra data</label><br/>
<input type="checkbox" name="use_ax" id="use_ax" /><label for="use_ax">Request registration data with AX (Attribute Exchange)</label>
</form>
</div>


ConsumerController#complete in /path/to/rails_openid/app/controllers/consumer_controller.rb

if params[:did_ax]
ax_resp = OpenID::AX::FetchResponse.from_success_response(oidresp)
pp ax_resp
end


Screenshot


We can fetch some attribtues from myOpenID, above code's output is as follows:

OpenID::Consumer::CheckIDRequest instance

#<OpenID::Consumer::CheckIDRequest:0x33543fc
@anonymous=false,
@assoc=
#<OpenID::Association:0x3354500
@assoc_type="HMAC-SHA1",
@handle="{HMAC-SHA1}{47dd30ef}{WTRsAg==}",
@issued=Sun Mar 16 14:38:35 +0000 2008,
@lifetime=1209600,
@secret="\005F6\246W\032\220\233]\002V$j\034��j�!1">,
@endpoint=
#<OpenID::OpenIDServiceEndpoint:0x33602b0
@canonical_id=nil,
@claimed_id="http://rakuto.myopenid.com/",
@display_identifier=nil,
@local_id="http://rakuto.myopenid.com/",
@server_url="http://www.myopenid.com/server",
@type_uris=
["http://specs.openid.net/auth/2.0/signon",
"http://openid.net/sreg/1.0",
"http://openid.net/extensions/sreg/1.1",
"http://schemas.openid.net/pape/policies/2007/06/phishing-resistant",
"http://openid.net/srv/ax/1.0"],
@used_yadis=true>,
@message=
#<OpenID::Message:0x3354334
@args=
{["http://openid.net/srv/ax/1.0", "type.gender"]=>
"http://schema.openid.net/person/gender",
["http://openid.net/srv/ax/1.0", "if_available"]=>
"country,web_default,language,birthDate,postal_code,nickname,gender",
["http://openid.net/srv/ax/1.0", "type.language"]=>
"http://schema.openid.net/pref/language",
["http://openid.net/srv/ax/1.0", "type.postal_code"]=>
"http://schema.openid.net/contact/postalCode/home",
["http://openid.net/srv/ax/1.0", "mode"]=>"fetch_request",
["http://openid.net/srv/ax/1.0", "type.birthDate"]=>
"http://schema.openid.net/birthDate",
["http://openid.net/srv/ax/1.0", "type.country"]=>
"http://schema.openid.net/contact/country/home",
["http://openid.net/srv/ax/1.0", "type.nickname"]=>
"http://schema.openid.net/namePerson/friendly",
["http://openid.net/srv/ax/1.0", "type.name"]=>
"http://schema.openid.net/namePerson",
["http://openid.net/srv/ax/1.0", "required"]=>"name,email",
["http://openid.net/srv/ax/1.0", "type.web_default"]=>
"http://schema.openid.net/contact/web/default",
["http://openid.net/srv/ax/1.0", "type.email"]=>
"http://schema.openid.net/contact/email"},
@namespaces=
#<OpenID::NamespaceMap:0x33542d0
@alias_to_namespace=
{"ax"=>"http://openid.net/srv/ax/1.0",
:null_namespace=>"http://specs.openid.net/auth/2.0"},
@namespace_to_alias=
{"http://specs.openid.net/auth/2.0"=>:null_namespace,
"http://openid.net/srv/ax/1.0"=>"ax"}>,
@openid_ns_uri="http://specs.openid.net/auth/2.0">,
@return_to_args={"did_ax"=>"y"}>


OpenID::Consumer::SuccessResponse instance

#<OpenID::Consumer::SuccessResponse:0x32800c0
@endpoint=
#<OpenID::OpenIDServiceEndpoint:0x32bea28
@canonical_id=nil,
@claimed_id="http://rakuto.myopenid.com/",
@display_identifier=nil,
@local_id="http://rakuto.myopenid.com/",
@server_url="http://www.myopenid.com/server",
@type_uris=
["http://specs.openid.net/auth/2.0/signon",
"http://openid.net/sreg/1.0",
"http://openid.net/extensions/sreg/1.1",
"http://schemas.openid.net/pape/policies/2007/06/phishing-resistant",
"http://openid.net/srv/ax/1.0"],
@used_yadis=true>,
@identity_url="http://rakuto.myopenid.com/",
@message=
#<OpenID::Message:0x3296f78
@args=
{["http://openid.net/srv/ax/1.0", "type.gender"]=>
"http://schema.openid.net/person/gender",
["http://specs.openid.net/auth/2.0", "sig"]=>
"5aziAIn3yMlbxq8XFaF8hKG4GCk=",
[:bare_namespace, "did_ax"]=>"y",
["http://openid.net/srv/ax/1.0", "type.language"]=>
"http://schema.openid.net/pref/language",
["http://openid.net/srv/ax/1.0", "count.gender"]=>"1",
["http://specs.openid.net/auth/2.0", "mode"]=>"id_res",
["http://openid.net/srv/ax/1.0", "mode"]=>"fetch_response",
["http://openid.net/srv/ax/1.0", "type.postal_code"]=>
"http://schema.openid.net/contact/postalCode/home",
["http://openid.net/srv/ax/1.0", "value.gender.1"]=>"M",
["http://specs.openid.net/auth/2.0", "op_endpoint"]=>
"http://www.myopenid.com/server",
["http://specs.openid.net/auth/2.0", "response_nonce"]=>
"2008-03-22T04:51:54ZT0280h",
["http://openid.net/srv/ax/1.0", "type.birthDate"]=>
"http://schema.openid.net/birthDate",
["http://openid.net/srv/ax/1.0", "value.name.1"]=>"Rakuto Furutani",
["http://openid.net/srv/ax/1.0", "count.postal_code"]=>"0",
["http://openid.net/srv/ax/1.0", "value.nickname.1"]=>"rakuto",
["http://openid.net/srv/ax/1.0", "type.country"]=>
"http://schema.openid.net/contact/country/home",
["http://openid.net/srv/ax/1.0", "count.name"]=>"1",
["http://openid.net/srv/ax/1.0", "count.web_default"]=>"1",
["http://openid.net/srv/ax/1.0", "type.nickname"]=>
"http://schema.openid.net/namePerson/friendly",
["http://specs.openid.net/auth/2.0", "return_to"]=>
"http://localhost:3001/consumer/complete?did_ax=y",
["http://specs.openid.net/auth/2.0", "assoc_handle"]=>
"{HMAC-SHA1}{47dd30ef}{WTRsAg==}",
["http://openid.net/srv/ax/1.0", "count.email"]=>"0",
["http://openid.net/srv/ax/1.0", "type.name"]=>
"http://schema.openid.net/namePerson",
["http://openid.net/srv/ax/1.0", "count.nickname"]=>"1",
["http://openid.net/srv/ax/1.0", "count.country"]=>"1",
["http://specs.openid.net/auth/2.0", "identity"]=>
"http://rakuto.myopenid.com/",
["http://specs.openid.net/auth/2.0", "signed"]=>
"assoc_handle,ax.count.birthDate,ax.count.country,ax.count.email,ax.count.gender,ax.count.language,ax.count.name,ax.count.nickname,ax.count.postal_code,ax.count.web_default,ax.mode,ax.type.birthDate,ax.type.country,ax.type.email,ax.type.gender,ax.type.language,ax.type.name,ax.type.nickname,ax.type.postal_code,ax.type.web_default,ax.value.birthDate.1,ax.value.country.1,ax.value.gender.1,ax.value.language.1,ax.value.name.1,ax.value.nickname.1,ax.value.web_default.1,claimed_id,identity,mode,ns,ns.ax,op_endpoint,response_nonce,return_to,signed",
["http://openid.net/srv/ax/1.0", "count.language"]=>"1",
["http://openid.net/srv/ax/1.0", "value.language.1"]=>"JA",
["http://openid.net/srv/ax/1.0", "value.web_default.1"]=>
"http://raku.to/",
["http://openid.net/srv/ax/1.0", "value.birthDate.1"]=>"1985-03-12",
["http://openid.net/srv/ax/1.0", "type.web_default"]=>
"http://schema.openid.net/contact/web/default",
["http://openid.net/srv/ax/1.0", "type.email"]=>
"http://schema.openid.net/contact/email",
["http://openid.net/srv/ax/1.0", "value.country.1"]=>"JP",
["http://specs.openid.net/auth/2.0", "claimed_id"]=>
"http://rakuto.myopenid.com/",
["http://openid.net/srv/ax/1.0", "count.birthDate"]=>"1"},
@namespaces=
#<OpenID::NamespaceMap:0x329680c
@alias_to_namespace=
{"ax"=>"http://openid.net/srv/ax/1.0",
:null_namespace=>"http://specs.openid.net/auth/2.0"},
@namespace_to_alias=
{"http://specs.openid.net/auth/2.0"=>:null_namespace,
"http://openid.net/srv/ax/1.0"=>"ax"}>,
@openid_ns_uri="http://specs.openid.net/auth/2.0">,
@signed_fields=
[
"openid.assoc_handle",
"openid.ax.count.birthDate",
"openid.ax.count.country",
"openid.ax.count.email",
"openid.ax.count.gender",
"openid.ax.count.language",
"openid.ax.count.name",
"openid.ax.count.nickname",
"openid.ax.count.postal_code",
"openid.ax.count.web_default",
"openid.ax.mode",
"openid.ax.type.birthDate",
"openid.ax.type.country",
"openid.ax.type.email",
"openid.ax.type.gender",
"openid.ax.type.language",
"openid.ax.type.name",
"openid.ax.type.nickname",
"openid.ax.type.postal_code",
"openid.ax.type.web_default",
"openid.ax.value.birthDate.1",
"openid.ax.value.country.1",
"openid.ax.value.gender.1",
"openid.ax.value.language.1",
"openid.ax.value.name.1",
"openid.ax.value.nickname.1",
"openid.ax.value.web_default.1",
"openid.claimed_id",
"openid.identity",
"openid.mode",
"openid.ns",
"openid.ns.ax",
"openid.op_endpoint",
"openid.response_nonce",
"openid.return_to",
"openid.signed"]>


Extracted attributes from response - OpenID::AX::FetchResponse instance

#<OpenID::AX::FetchResponse:0x3254a4c
@data=
{"http://schema.openid.net/namePerson"=>["Rakuto Furutani"],
"http://schema.openid.net/contact/country/home"=>["JP"],
"http://schema.openid.net/contact/web/default"=>["http://raku.to/"],
"http://schema.openid.net/pref/language"=>["JA"],
"http://schema.openid.net/contact/email"=>[],
"http://schema.openid.net/birthDate"=>["1985-03-12"],
"http://schema.openid.net/contact/postalCode/home"=>[],
"http://schema.openid.net/namePerson/friendly"=>["rakuto"],
"http://schema.openid.net/person/gender"=>["M"]},
@mode="fetch_response",
@ns_alias="ax",
@ns_uri="http://openid.net/srv/ax/1.0",
@update_url=nil>

Conclusion about myOpenID
  • Supports only some attributes that can be exchanged with AX.
  • Doesn't support openid.ax.update_url yet.
  • Doesn't support Type URI described in axschema, you should specify Type URI with "http://schema.openid.net/" namespace.

Monday, March 17, 2008

[ruby] released jrails_auto_complete

Programming using jQuery is more fun than protoype.js, but Ruby on Rails depend on Prototype.js and script.aculo.us strongly. jRails is solution that use jQuery on RoR platform but jRails doesn't have some plugins, auto_complete, in_place_editor and so on. So I release jrails_auto_complete rails plugin.

INSTALL


% cd /path/to/rails_app
% ./script/plugin install http://svn.raku.to/throwaway/jrails_auto_complete/


USAGE
Basically, usage of jrails_auto_complete is same as auto_complete plugin. But some options isn't supported yet. For details, see jrails_auto_complete/lib/auto_complete_macro_helper.rb.

DEPENDENCIES
This programm is depended on jrails. First, you need to install jrails plugin.

NOTE
I can't test it enough. If you found the bugs, let me know.
Thanks

[ruby] Fetch and Store some attributes with OpenID Attributes Exchange 1.0

We have many profiles about ourselves in web applications, communities, organization, and so on. My profile in mixi and my profile in Facebook are different, my Blogger's profile and my HP's profile are different too. This problem more and more annoy us.

OpenID Atrributes Exchange 1.0
is an OpenID service extension for exchanging identity information between endpoint(Relying Party and OpenID Provider). We can think some scenarios. For example, we want to disclose about name to all services, but we think want to only disclose email or credit card number to the specified service.

As you know, OpenID has protocol for light-weight profile exchange. This protocols is known as OpenID Simple Registration Extension 1.1 (SREG), but SREG is obsolete now. The downside of SREG is that it only supported limited number of attributes. You should use OpenID Attributes Exchange for exchanging identity.

What can we do with OpenID Attribute Exchange?

  • Fetch some values -- (use fetch_request)
  • Store some values -- (use store_request)


What kind of attributes do we use with OpenID Attribute Exchange?
You can see exchanging identity in OpenID Attribute Exchange | Attribute Type. If the parameter you want doesn't exist, then you define new parameter. For details, see How do I define new attributes?. The attributes specified with Type URI unlike SREG.


http://axschema.org/namePerson/friendly => Alias/Username
http://axschema.org/contact/email => Email
http://axschema.org/contact/IM/Skype => Skype IM

Sample code with Attribute Exchange implementaion with ruby-openid as follows:

requirements:
ruby-openid 2.0.4

The side of Relying Party
This example requires "nickname", "email" are required and "birth_date" and "phone" is obtained if available it. The code is beggining of OpenID Authentication. (ConsumerController#begin etc)


# NOTE: in your controller that start login with OpenID Authentication
consumer = OpenID::Consumer.new(session, store)
oid_req = consumer.begin(params[:openid_identifier])

# An attribute exchange 'fetch_request' message
ax_req = OpenID::AX::FetchRequest.new

# OpenID::AX::AttrInfo.new(type_uri, extension_alias=nil, required=nil)
# Specify required attributes with Attribute Exchange
attr_nickname = OpenID::AX::AttrInfo.new('http://axschema.org/namePerson/friendly', 'nick', true)
attr_email = OpenID::AX::AttrInfo.new('http://axschema.org/contact/email', 'email', true)
attr_birth_date = OpenID::AX::AttrInfo.new('http://axschema.org/birthDate', 'birth_date', false)
attr_phone = OpenID::AX::AttrInfo.new('http://axschema.org/contact/phone/default', 'phone', false)
[attr_nickname, attr_email, attr_birth_date, attr_phone].each {|a| ax_req.add(a)}

# add extension with Attribute Exchange object
oid_req.add_extension(ax_req)


The side of OpenID Provider
You can extract extension fields from request with OpenID::AX::FetchRequest.from_openid_request method, and add some values with OpenID::AX::FetchResponse#parse_extension_args method. You can specify values with array. (value.ALIAS_NAME.1, value.ALIAS_NAME.2 etc).


oid_resp = oid.answer(true, nil, identity)

# XXX: In a real application, these attributes are user-specific
# and the use should be asked for permission to release these attributes
ax_req = OpenID::AX::FetchRequest.from_openid_request(oidreq)
ax_args = { 'mode' => 'fetch_response',
'update_url' => url_for(:controller => 'user', :action => 'update', :id => '1'),
'type.nick' => 'http://axschema.org/namePerson/friendly',
'value.nick' => 'rakuto',
'type.email' => 'http://axschema.org/contact/email',
'value.email.1' => '[email protected]'.sub('nospam.', ''),
'value.email.2' => '[email protected]',
'type.birth_date' => 'http://axschema.org/birthDate',
'value.birth_date' => '1985-03-12',
'count.email' => 2}
ax_resp = OpenID::AX::FetchResponse.new
ax_resp.parse_extension_args(ax_args)
ax_args = ax_resp.get_extension_args(ax_req) # for validation
ax_resp.parse_extension_args(ax_args)

# pp ax_resp.get_extension_args(ax_req)
# {"count.email"=>"2",
# "type.email"=>"http://axschema.org/contact/email",
# "value.email.1"=>"[email protected]",
# "count.birth_date"=>"1",
# "count.nick"=>"1",
# "value.email.2"=>"[email protected]",
# "value.birth_date.1"=>"1985-03-12",
# "mode"=>"fetch_response",
# "type.nick"=>"http://axschema.org/namePerson/friendly",
# "type.birth_date"=>"http://axschema.org/birthDate",
# "value.nick.1"=>"rakuto",
# "update_url"=>"http://localhost:3000/user/update/1",
# "type.phone"=>"http://axschema.org/contact/phone/default",
# "count.phone"=>"0"}

oid_resp.add_extension(ax_res)



The side of Relying Party
Last come, the request returned to Relying Party. This code is part of end of OpenID authentication. (ConsumerController#complete etc)


consumer = OpenID::Consumer.new(session, store)
current_url = url_for(:action => 'complete', :only_path => false)
parameters = params.reject{|k,v|request.path_parameters[k]}
oid_resp = consumer.complete(parameters, current_url)

# Dump the exchanged attributes
# pp OpenID::AX::FetchResponse.from_success_response(oid_resp)
#<OpenID::AX::FetchResponse:0x33527a0
# @data=
# {"http://axschema.org/contact/email"=>
# ["[email protected]", "rakuto@examhttp://www.blogger.com/img/gl.link.gifple.com"],
# "http://axschema.org/birthDate"=>["1985-03-12"],
# "http://axschema.org/namePerson/friendly"=>["rakuto"]},
# @mode="fetch_response",
# @ns_alias="ax",
# @ns_uri="http://openid.net/srv/ax/1.0",
# @update_url="http://localhost:3000/user/update/1">

OpenID Attributes Exchange is simple, but pretty interesting. Next time, I'm going to introduce "store_request" mode for storing value if value dont't exists.

Saturday, March 15, 2008

[ruby] mixi2foaf.rb generates FOAF with friends network in mixi.

mixi is the biggest social network site in Japan. There are more users than ten million. Many people have friends networks in mixi, and mixi announced about respond OpenID and OpenSocial API, but they are very slow!! I heard that mixi is going to release OpenID API about Sep. It's too late compared to Facebook.

The Friend of a Friend(FOAF) is creating a Web of machine-readable pages describing people, the links between them and the things they create and do. I have created program to extract the friend links from mixi.

Usage:

% svn co http://svn.raku.to/throwaway/mixi2foaf.rb
% ruby mixi2foaf.rb email password > foaf.rdf


NOTE: Customize your profile
You need to customize profile about yourself, for details see mixi2foaf.rb.


# FIXME: Edit your profile
mixi.me = FOAF::Person.new({
:rdf_id => 'rakuto',
:name => 'Rakuto Furutani',
:nick => 'rakuto',
:openid => '=rakuto',
:mbox_sha1sum => '[email protected]'.sub('nospam.', ''),
:jabberID => '[email protected]'.sub('nospam.', ''),
:msnChatID => '[email protected]'
})




Example:
% ruby mixi2foaf.rb [email protected] ********* > foaf.rdf
[Traverse] http://mixi.jp/show_friend.pl?id=4588051
[Traverse] http://mixi.jp/show_friend.pl?id=3805395
...
%


foaf.rdf

<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:wot="http://xmlns.com/wot/0.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:lang="ja" xml:lang="ja" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:dcterms="http://purl.org/dc/terms/" xmlns="http://www.w3.org/foaf/0.1/">
<foaf:Person rdf:ID="rakuto">
<foaf:nick>rakuto</foaf:nick>
<foaf:jabberID>[email protected]</foaf:jabberID>
<foaf:mbox_sha1sum>bf8892066afd2ea3eef4f3c1fd834eed96ba1c39</foaf:mbox_sha1sum>
<foaf:openid>=rakuto</foaf:openid>
<foaf:msnChatID>[email protected]</foaf:msnChatID>

<foaf:rdf_id>rakuto</foaf:rdf_id>
</foaf:Person>
<knows>
<Person>
<name>&#12422;&#12426;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/80/51/4588051_99567334.jpg</img>
<nick>&#12422;&#12426;&#12373;&#12435;</nick>
<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#38899;&#27005;&#37969;&#36062;, &#26009;&#29702;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#35501;&#26360;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;, &#12506;&#12483;&#12488;</topic_interest>

<based_near>&#20140;&#37117;&#24220;&#20140;&#37117;&#24066;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12358;&#12376;&#12402;&#12373;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/53/95/3805395_1723386446.jpg</img>
<nick>&#12358;&#12376;&#12402;&#12373;&#12373;&#12435;</nick>

<topic_interest>&#12450;&#12540;&#12488;, &#35486;&#23398;, &#12506;&#12483;&#12488;</topic_interest>
</Person>
</knows>
<knows>
<Person>
<name>&#12373;&#12387;&#12385;&#12355;&#12540;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/40/38/3064038_2463714862.jpg</img>

<nick>&#12373;&#12387;&#12385;&#12355;&#12540;&#12373;&#12435;</nick>
<topic_interest>&#12473;&#12509;&#12540;&#12484;, &#12473;&#12509;&#12540;&#12484;&#35251;&#25126;, &#12362;&#37202;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#12450;&#12454;&#12488;&#12489;&#12450;, &#12489;&#12521;&#12452;&#12502;, &#26053;&#34892;, &#35501;&#26360;, &#12466;&#12540;&#12512;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>
<based_near>&#20140;&#37117;&#24220;&#20140;&#37117;&#24066;</based_near>
</Person>

</knows>
<knows>
<Person>
<name>genki&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/5/69/2560569_1803584202.jpg</img>
<nick>genki&#12373;&#12435;</nick>
<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#35501;&#26360;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>

<based_near>&#26481;&#20140;&#37117;&#19990;&#30000;&#35895;&#21306;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12356;&#12387;&#12410;&#12356;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/65/15/2276515_15823768.jpg</img>
<nick>&#12356;&#12387;&#12410;&#12356;&#12373;&#12435;</nick>

<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#12473;&#12509;&#12540;&#12484;&#35251;&#25126;, &#12450;&#12454;&#12488;&#12489;&#12450;, &#12489;&#12521;&#12452;&#12502;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>
<based_near>&#28363;&#36032;&#30476;&#29356;&#19978;&#37089;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12414;&#12385;&#12419;&#12365;&#12373;&#12435;</name>

<img>http://member.img.mixi.jp/photo/member/12/57/2231257_4177973050.jpg</img>
<nick>&#12414;&#12385;&#12419;&#12365;&#12373;&#12435;</nick>
<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#12473;&#12509;&#12540;&#12484;, &#12473;&#12509;&#12540;&#12484;&#35251;&#25126;, &#38899;&#27005;&#37969;&#36062;, &#12362;&#37202;, &#12510;&#12531;&#12460;, &#12466;&#12540;&#12512;</topic_interest>
<based_near>&#20853;&#24235;&#30476;&#23019;&#36335;&#24066;</based_near>
</Person>

</knows>
<knows>
<Person>
<name>&#12403;&#12387;&#12367;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/74/81/2227481_2218119710.jpg</img>
<nick>&#12403;&#12387;&#12367;&#12373;&#12435;</nick>
<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#12473;&#12509;&#12540;&#12484;, &#12473;&#12509;&#12540;&#12484;&#35251;&#25126;, &#26009;&#29702;, &#12450;&#12454;&#12488;&#12489;&#12450;, &#12489;&#12521;&#12452;&#12502;, &#26053;&#34892;, &#12486;&#12524;&#12499;, &#12506;&#12483;&#12488;, &#32654;&#23481;&#12539;&#12480;&#12452;&#12456;&#12483;&#12488;</topic_interest>

<based_near>&#26481;&#20140;&#37117;&#27743;&#25144;&#24029;&#21306;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12405;&#12387;&#12385;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/12/13/2221213_1175337526.jpg</img>
<nick>&#12405;&#12387;&#12385;&#12373;&#12435;</nick>

<topic_interest>&#38899;&#27005;&#37969;&#36062;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#12486;&#12524;&#12499;</topic_interest>
</Person>
</knows>
<knows>
<Person>
<name>&#12383;&#12397;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/27/88/2112788_3718693077.jpg</img>

<nick>&#12383;&#12397;&#12373;&#12435;</nick>
<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#12473;&#12509;&#12540;&#12484;, &#12473;&#12509;&#12540;&#12484;&#35251;&#25126;, &#38899;&#27005;&#37969;&#36062;, &#12489;&#12521;&#12452;&#12502;</topic_interest>
<based_near>&#20140;&#37117;&#24220;&#20140;&#37117;&#24066;</based_near>
</Person>
</knows>
<knows>
<Person>

<name>&#12365;&#12426;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/54/23/2025423_3963467882.jpg</img>
<nick>&#12365;&#12426;&#12373;&#12435;</nick>
<topic_interest>&#12473;&#12509;&#12540;&#12484;, &#12473;&#12509;&#12540;&#12484;&#35251;&#25126;, &#38899;&#27005;&#37969;&#36062;, &#12464;&#12523;&#12513;</topic_interest>
<based_near>&#20140;&#37117;&#24220;&#20140;&#37117;&#24066;</based_near>
</Person>
</knows>

<knows>
<Person>
<name>&#12402;&#12391;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/17/46/2001746_3424328848.jpg</img>
<nick>&#12402;&#12391;&#12373;&#12435;</nick>
<topic_interest>&#12473;&#12509;&#12540;&#12484;, &#12473;&#12509;&#12540;&#12484;&#35251;&#25126;, &#38899;&#27005;&#37969;&#36062;, &#26009;&#29702;, &#12464;&#12523;&#12513;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#12450;&#12454;&#12488;&#12489;&#12450;, &#12489;&#12521;&#12452;&#12502;, &#26053;&#34892;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>

<based_near>&#20140;&#37117;&#24220;&#20140;&#37117;&#24066;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12373;&#12392;&#12375;&#12355;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/5/38/1410538_2487809269.jpg</img>
<nick>&#12373;&#12392;&#12375;&#12355;&#12373;&#12435;</nick>

<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#12473;&#12509;&#12540;&#12484;, &#38899;&#27005;&#37969;&#36062;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#35486;&#23398;, &#35501;&#26360;, &#12510;&#12531;&#12460;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;, &#12462;&#12515;&#12531;&#12502;&#12523;</topic_interest>
<based_near>&#28363;&#36032;&#30476;</based_near>
</Person>
</knows>
<knows>

<Person>
<name>&#12394;&#12362;&#9734;&#12444;+.&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/96/1/1339601_3143320574.jpg</img>
<nick>&#12394;&#12362;&#9734;&#12444;+.&#12373;&#12435;</nick>
<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#38899;&#27005;&#37969;&#36062;, &#12464;&#12523;&#12513;, &#12362;&#37202;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#12489;&#12521;&#12452;&#12502;, &#26053;&#34892;, &#12506;&#12483;&#12488;</topic_interest>

<based_near>&#20140;&#37117;&#24220;&#20140;&#37117;&#24066;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12354;&#12451; ? &#12422;&#12453; &#27096;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/37/71/883771_3362183383.jpg</img>

<nick>&#12354;&#12451; ? &#12422;&#12453; &#27096;&#12373;&#12435;</nick>
<topic_interest>&#12473;&#12509;&#12540;&#12484;, &#12464;&#12523;&#12513;, &#12362;&#37202;, &#32654;&#23481;&#12539;&#12480;&#12452;&#12456;&#12483;&#12488;</topic_interest>
<based_near>&#20140;&#37117;&#24220;&#20140;&#37117;&#24066;</based_near>
</Person>
</knows>
<knows>

<Person>
<name>&#12407;&#12426;&#12435;&#12373;&#12435;</name>
<img>http://img.mixi.jp/img/basic/common/noimage_member180.gif</img>
<nick>&#12407;&#12426;&#12435;&#12373;&#12435;</nick>
</Person>
</knows>
<knows>
<Person>

<name>cuzic&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/66/22/516622_1304637750.jpg</img>
<nick>cuzic&#12373;&#12435;</nick>
<topic_interest>&#35486;&#23398;, &#35501;&#26360;, &#12486;&#12524;&#12499;</topic_interest>
<based_near>&#22823;&#38442;&#24220;&#22823;&#38442;&#24066;</based_near>
</Person>

</knows>
<knows>
<Person>
<name>&#12490;&#12288;&#12458;&#12479;&#12465;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/43/66/344366_692701929.jpg</img>
<nick>&#12490;&#12288;&#12458;&#12479;&#12465;&#12373;&#12435;</nick>
<topic_interest>&#12473;&#12509;&#12540;&#12484;, &#38899;&#27005;&#37969;&#36062;, &#12450;&#12454;&#12488;&#12489;&#12450;, &#12450;&#12540;&#12488;, &#35501;&#26360;, &#12466;&#12540;&#12512;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>

<based_near>&#38263;&#23822;&#30476;&#20304;&#19990;&#20445;&#24066;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>ankoparty&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/29/2/292902_3069630518.jpg</img>
<nick>ankoparty&#12373;&#12435;</nick>

<topic_interest>&#38899;&#27005;&#37969;&#36062;, &#12464;&#12523;&#12513;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#26053;&#34892;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;, &#12506;&#12483;&#12488;</topic_interest>
<based_near>&#22524;&#29577;&#30476;</based_near>
</Person>
</knows>
<knows>
<Person>

<name>hiroko&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/73/65/47365_319217593.jpg</img>
<nick>hiroko&#12373;&#12435;</nick>
<topic_interest>&#12473;&#12509;&#12540;&#12484;, &#12473;&#12509;&#12540;&#12484;&#35251;&#25126;, &#38899;&#27005;&#37969;&#36062;, &#26009;&#29702;, &#12464;&#12523;&#12513;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#26053;&#34892;</topic_interest>

<based_near>&#28363;&#36032;&#30476;&#22823;&#27941;&#24066;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12356;&#12373;&#12416;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/33/41/3341_118115164.jpg</img>
<nick>&#12356;&#12373;&#12416;&#12373;&#12435;</nick>

<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#38899;&#27005;&#37969;&#36062;, &#12464;&#12523;&#12513;, &#12362;&#37202;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#12489;&#12521;&#12452;&#12502;, &#26053;&#34892;, &#35501;&#26360;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;, &#12506;&#12483;&#12488;</topic_interest>
</Person>
</knows>
<knows>

<Person>
<name>&#65312;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/32/37/3237_54519266.jpg</img>
<nick>&#65312;&#12373;&#12435;</nick>
<topic_interest>&#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>
<based_near>&#28363;&#36032;&#30476;&#26647;&#26481;&#24066;</based_near>
</Person>
</knows>

<knows>
<Person>
<name>&#12354;&#12420;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/22/88/2288_1047374038.jpg</img>
<nick>&#12354;&#12420;&#12373;&#12435;</nick>
<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#12473;&#12509;&#12540;&#12484;, &#38899;&#27005;&#37969;&#36062;, &#26009;&#29702;, &#12464;&#12523;&#12513;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#35501;&#26360;</topic_interest>

<based_near>&#39321;&#24029;&#30476;&#26481;&#12363;&#12364;&#12431;&#24066;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12402;&#12391;&#12392;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/7/44/744_785912264.jpg</img>
<nick>&#12402;&#12391;&#12392;&#12373;&#12435;</nick>

<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#38899;&#27005;&#37969;&#36062;, &#12464;&#12523;&#12513;, &#12362;&#37202;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#12489;&#12521;&#12452;&#12502;, &#26053;&#34892;, &#35501;&#26360;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>
<based_near>&#20140;&#37117;&#24220;&#20140;&#37117;&#24066;</based_near>
</Person>
</knows>
<knows>

<Person>
<name>&#12402;&#12391;&#12405;&#12415;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/5/2/502_1466901064.jpg</img>
<nick>&#12402;&#12391;&#12405;&#12415;&#12373;&#12435;</nick>
<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#12473;&#12509;&#12540;&#12484;, &#38899;&#27005;&#37969;&#36062;, &#12362;&#37202;, &#12450;&#12540;&#12488;, &#35501;&#26360;</topic_interest>
<based_near>&#32676;&#39340;&#30476;&#26704;&#29983;&#24066;</based_near>

</Person>
</knows>
<knows>
<Person>
<name>&#12371;&#12400;&#12370;&#12435;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/4/74/474_3993761079.jpg</img>
<nick>&#12371;&#12400;&#12370;&#12435;&#12373;&#12435;</nick>
<topic_interest>&#38899;&#27005;&#37969;&#36062;, &#35501;&#26360;, &#12510;&#12531;&#12460;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>

</Person>
</knows>
<knows>
<Person>
<name>&#21281;&#24535;&#12373;&#12435;</name>
<img>http://img.mixi.jp/img/basic/common/noimage_member180.gif</img>
<nick>&#21281;&#24535;&#12373;&#12435;</nick>
<topic_interest>&#26053;&#34892;, &#35501;&#26360;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>

<based_near>&#22823;&#38442;&#24220;&#23500;&#30000;&#26519;&#24066;</based_near>
</Person>
</knows>
<knows>
<Person>
<name>&#12422;&#12358;&#12365;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/1/12/112_3714846406.jpg</img>
<nick>&#12422;&#12358;&#12365;&#12373;&#12435;</nick>

<topic_interest>&#26144;&#30011;&#37969;&#36062;, &#26009;&#29702;, &#12362;&#37202;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#35501;&#26360;, &#12510;&#12531;&#12460;, &#12452;&#12531;&#12479;&#12540;&#12493;&#12483;&#12488;</topic_interest>
<based_near>&#26481;&#20140;&#37117;&#27743;&#25144;&#24029;&#21306;</based_near>
</Person>
</knows>
<knows>

<Person>
<name>&#12358;&#12425;&#12383;&#12373;&#12435;</name>
<img>http://member.img.mixi.jp/photo/member/0/6/6_814100255.jpg</img>
<nick>&#12358;&#12425;&#12383;&#12373;&#12435;</nick>
<topic_interest>&#38899;&#27005;&#37969;&#36062;, &#12459;&#12521;&#12458;&#12465;&#12539;&#12496;&#12531;&#12489;, &#26009;&#29702;, &#12464;&#12523;&#12513;, &#12471;&#12519;&#12483;&#12500;&#12531;&#12464;, &#12501;&#12449;&#12483;&#12471;&#12519;&#12531;, &#12450;&#12540;&#12488;, &#12510;&#12531;&#12460;</topic_interest>

<based_near>&#23500;&#23665;&#30476;&#28369;&#24029;&#24066;</based_near>
</Person>
</knows>
</rdf:RDF>



#
# Generate FOAF (Friend Of A Friend) from mixi <http://mixi.jp/>.
# mixi is the biggest social network site in Japan.
#
# == FOAF
# FOAF Vocabulary Specification 0.91 <http://xmlns.com/foaf/spec/>
#
# Author: Rakuto Furutani <http://raku.to/>
#
require 'rubygems'
require 'mechanize'
require 'active_support' # For builder.rb
require 'uri'
require 'logger'
require 'iconv'
require 'cgi'
require 'digest/sha1'

$KCODE = 'u'
$logger = Logger.new(STDERR)

module FOAF
class Person
attr_accessor :rdf_id, :name, :nick, :jabberID, :aimChatID, :msnChatID, :mbox, :description,
:mbox_sha1sum, :openid, :dateOfBirth, :jabberID, :aimChatID, :msnChatID

def initialize(attrs)
attrs.each do |k, v|
instance_eval %Q{self.#{k} = '#{v}'}
end
end
end

class Base
attr_accessor :me

XML_NS = {
'xml:lang' => 'en',
'xmlns' => 'http://www.w3.org/foaf/0.1/',
'xmlns:foaf' => 'http://xmlns.com/foaf/0.1/',
'xmlns:rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
'xmlns:rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
'xmlns:dcterms' => 'http://purl.org/dc/terms/',
'xmlns:wot' => 'http://xmlns.com/wot/0.1/'
}

def initialize(username, password)
@username = username
@password = password
@agent = WWW::Mechanize.new
end

def login
end

def to_foaf
end

end

class Mixi < Base
def initialize(username, password)
@base_uri = URI.parse('http://mixi.jp/')
super
end

def login!
top = @agent.get(@base_uri)
login_form = top.forms.first
login_form.fields.name('email').value = @username
login_form.fields.name('password').value = @password
raise Exception, 'login failed' if login_form.submit.meta.empty?
end

def to_foaf(xmlns={})
login!
@friends = extract_friends!
xm = Builder::XmlMarkup.new(:indent => 2)
xm.instruct!

foaf = xm.rdf :RDF, XML_NS.merge(xmlns) do
# About me
xm.foaf :Person, 'rdf:ID' => @me.rdf_id do
(@me.public_methods - Object.public_methods).find_all {|meth| meth =~ /[^=]$/}.each do |meth|
if v = @me.__send__(meth)
case meth
when 'mbox_sha1sum'
xm.foaf meth.to_sym, Digest::SHA1.hexdigest(v)
else
xm.foaf meth.to_sym, v
end
end
end
end if @me

# Extract friends information
@friends.each do |f|
xm.knows do
xm.Person do
xm.name f[:name] if f.key?(:name)
xm.gender f[:gender] if f.key?(:gender)
xm.img f[:image] if f.key?(:image)
xm.homepage f[:homepage] if f.key?(:url)
xm.nick f[:name] if f.key?(:name)
xm.topic_interest f[:hobby].join(', ') if f.key?(:hobby)
xm.based_near f[:home_town] if f.key?(:home_town)
end
end
end
end
end

private

def extract_friends!
# Obtain my friend list
friend_list_page = @agent.get(@base_uri + 'list_friend.pl')
while true
friends = (friend_list_page.parser/'div.iconList03 ul li div').inject([]) do |ret, div|
returning ret do
begin
ret << {
:name => (div/'span').inner_text.strip.toutf8.match(/(.+?)\(\d+\)/)[1],
:url => @base_uri + (div/'div.iconListImage a')[0]['href']
} if div['class'] =~ /^iconState/ && div.inner_html.strip != '&nbsp;'
rescue => e
# Just ignor it
end
end
end
# find anchor to next page
if (anchor = friend_list_page.parser/'div[@class="pageList02"]//ul//li[2]//a[1]').empty?
break
else
friend_list_page = @agent.get(@base_uri + anchor[0]['href'])
end
end

# Obtain details of friends
#friends = [friends[0]]
friends.map! do |f|
$logger.info "[Traverse] #{f[:url]}"
friend_page = @agent.get(f[:url])

# an image
f[:image] = (friend_page/'div.contents01 img')[0]['src']

# a profile
(friend_page.parser/'#profile ul li').each do |li|
dt = li/'dl dt'
dd = li/'dl dd'
value = dd.inner_text.toutf8.strip
case CGI.escape(dt.inner_text.toutf8.strip)
when '%E6%80%A7%E5%88%A5' # gender
f[:sex] = CGI.escape(value) == '%E5%A5%B3%E6%80%A7'? 'female' : 'female'
when '%E7%8F%BE%E4%BD%8F%E6%89%80' # address
f[:address] = value
when '%E5%B9%B4%E9%BD%A2' # age
f[:age] = value
when '%E8%AA%95%E7%94%9F%E6%97%A5' # birthday
f[:birthday] = value
when '%E8%A1%80%E6%B6%B2%E5%9E%8B' # type of blood
f[:type_of_blood] = value
when '%E5%87%BA%E8%BA%AB%E5%9C%B0' # home town
f[:home_town] = value
when '%E8%B6%A3%E5%91%B3' # hobby
f[:hobby] = dd.inner_text.toutf8.strip.split(',')
when '%E8%81%B7%E6%A5%AD' # job
f[:job] = value
when '%E6%89%80%E5%B1%9E' # organization
f[:org] = value
when '%E8%87%AA%E5%B7%B1%E7%B4%B9%E4%BB%8B' # self introduction
f[:introduction] = value
end
end
f
end
end
end
end

if $0 == __FILE__
# USAGE: ruby mixi2foaf.rb USERNAME PASSWORD
mixi = FOAF::Mixi.new(ARGV[0], ARGV[1])

# FIXME: Edit your profile
mixi.me = FOAF::Person.new({
:rdf_id => 'rakuto',
:name => 'Rakuto Furutani',
:nick => 'rakuto',
:openid => '=rakuto',
:mbox_sha1sum => '[email protected]'.sub('nospam.', ''),
:jabberID => '[email protected]'.sub('nospam.', ''),
:msnChatID => '[email protected]'
})
puts mixi.to_foaf('xmlns:lang' => 'ja', 'xml:lang' => 'ja')
end