l'essentiel est invisible pour les yeux

Thursday, March 27, 2008

AWS EC2 announced that release Elastic IP which includes Static IP support and more !!

Wow!!

Feature Guide: Amazon EC2 Elastic IP Addresses

Amazon announced about Elastic IP. This solution is included static IP support and more. Many developers who use EC2 may become happy! The Elastic IP is associated with our account, not a paticular account, and Amazon EC2 enables us to engineer around problems with our instance or software by programmatically remapping your Elastic IP address to a replacement instance.

For details, see Feature Guide: Amazon EC2 Elastic IP Addresses.

Thanks AWS team!

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(rakuto@example.com rakuto@example2.com))
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' => 'rakuto@nospam.gmail.com'.sub('nospam.', ''),
'value.email.2' => 'rakuto@example.com',
'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"=>"rakuto@gmail.com",
# "count.birth_date"=>"1",
# "count.nick"=>"1",
# "value.email.2"=>"rakuto@example.com",
# "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"=>
# ["rakuto@gmail.com", "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 => 'rakuto@nospam.gmail.com'.sub('nospam.', ''),
:jabberID => 'rakuto@nospam.gmail.com'.sub('nospam.', ''),
:msnChatID => 'Strawberry_au_rait@hotmail.com'
})




Example:
% ruby mixi2foaf.rb rakuto@gmail.com ********* > 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>rakuto@gmail.com</foaf:jabberID>
<foaf:mbox_sha1sum>bf8892066afd2ea3eef4f3c1fd834eed96ba1c39</foaf:mbox_sha1sum>
<foaf:openid>=rakuto</foaf:openid>
<foaf:msnChatID>Strawberry_au_rait@hotmail.com</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 => 'rakuto@nospam.gmail.com'.sub('nospam.', ''),
:jabberID => 'rakuto@nospam.gmail.com'.sub('nospam.', ''),
:msnChatID => 'Strawberry_au_rait@hotmail.com'
})
puts mixi.to_foaf('xmlns:lang' => 'ja', 'xml:lang' => 'ja')
end