root/platform/tdiary/plugin/profile.rb @ 35301

Revision 35301, 6.4 kB (checked in by machu, 5 years ago)

added Profile::Service::Wassr and added Profile::Service::Hatena

Line 
1#
2# profile.rb: profile plugin for tDiary
3#
4# usage:
5#   profile(id[, service = :twitter])
6#   - id: user ID for profile service
7#   - service: profile service (default is :twitter)
8#     Choose from :github, :twitter, :friendfeed, :iddy
9#
10# Copyright (C) 2009 by MATSUOKA Kohei < http://www.machu.jp/ >
11# Distributed under the GPL.
12#
13require 'timeout'
14require 'rexml/document'
15require 'open-uri'
16require 'digest/md5'
17#require 'yaml/store'
18require 'pstore'
19
20module ::Profile
21  module Service
22    # base class for profile services
23    class Base
24      # default attributes
25      attr_reader :id
26      attr_reader :image
27      attr_reader :name
28      attr_reader :mail
29      attr_reader :description
30      attr_reader :link
31
32      # class instance variables
33      class << self
34        attr_reader :properties
35        attr_reader :endpoint_proc
36      end
37
38      # set property and xpath pair for parse XML document
39      def self.property(property, path)
40        @properties ||= {}
41        @properties[property] = path
42      end
43
44      # set endpoint proc (this proc is called by initialize method with id)
45      def self.endpoint(&block)
46        @endpoint_proc = block
47      end
48
49      def initialize(id, options = {})
50        @id = id
51        @options = options
52
53        if self.class.endpoint_proc
54          endpoint = self.class.endpoint_proc.call(id)
55          doc = fetch(endpoint)
56          parse(doc)
57        end
58      end
59
60      # get a XML document from endpoint and create REXML::Document instance
61      def fetch(endpoint)
62        timeout(5) do
63          open(endpoint) do |f|
64            doc = REXML::Document.new(f)
65          end
66        end
67      end
68
69      # parse XML document with properties
70      def parse(doc)
71        self.class.properties.each do |property, path|
72          if doc.elements[path]
73            value = doc.elements[path].text
74            instance_variable_set("@#{property}", value)
75          end
76        end
77      end
78    end
79
80    # github.com
81    class GitHub < Base
82      property :name, '//user/name'
83      property :mail, '//user/email'
84      endpoint {|id| "http://github.com/api/v2/xml/user/show/#{id}" }
85
86      def image
87        Gravatar.new(@mail, @options).image
88        # "http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(@mail)}.jpg"
89      end
90
91      def link
92        "http://github.com/#{@id}"
93      end
94    end
95
96    # twitter.com
97    class Twitter < Base
98      property :name, '//user/name'
99      property :image, '//user/profile_image_url'
100      property :description, '//user/description'
101      endpoint {|id| "http://twitter.com/users/show/#{id}.xml" }
102
103      def link
104        "http://twitter.com/#{@id}"
105      end
106    end
107
108    # friendfeed.com
109    class FriendFeed < Base
110      property :name, '//feed/name'
111      property :description, '//feed/description'
112      endpoint {|id| "http://friendfeed-api.com/v2/feed/#{id}?format=xml&num=0" }
113
114      def image
115        "http://friendfeed-api.com/v2/picture/#{id}"
116      end
117
118      def link
119        "http://friendfeed.com/#{@id}"
120      end
121    end
122
123    # iddy.jp
124    # this class is based on iddy.rb
125    class Iddy < Base
126      ######################################################################
127      # If you will modify or release another version of this code,
128      # please get your own application key from iddy.jp and replace below.
129      ######################################################################
130      API_KEY = '9262ea8ffba962aabb4f1a1d3f1cfa953b11aa23'
131
132      property :name, '//response/users/user/accountname'
133      property :image, '//response/users/user/imageurl'
134      property :description, '/response/users/user/profile'
135      endpoint {|id| "http://iddy.jp/api/user/?apikey=#{API_KEY}&accountname=#{id}" }
136
137      def link
138        "http://iddy.jp/profile/#{@id}/"
139      end
140    end
141
142    # gravatar.com
143    class Gravatar < Base
144      def image
145        size = @options[:size] ? "?s=#{@options[:size]}" : ""
146        "http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(@id.downcase)}.jpg#{size}"
147      end
148    end
149
150    class Wassr < Base
151      property :image, '//statuses/status/user/profile_image_url'
152      endpoint {|id| "http://api.wassr.jp/statuses/show.xml?id=#{id}" }
153
154      def link
155        "http://wassr.jp/user/#{id}"
156      end
157    end
158
159    class Hatena < Base
160      def image
161        prefix = id[0..1]
162        "http://www.hatena.ne.jp/users/#{prefix}/#{id}/profile.gif"
163      end
164
165      def link
166        "http://www.hatena.ne.jp/#{id}/"
167      end
168    end
169  end
170end
171
172PROFILE_VERSION = '20090909'
173def profile(id, service = :twitter, options = {})
174  # FIXME: move to user stylesheet
175  html = <<-EOS
176  <style type="text/css">
177    div.profile {
178      margin: 1em;
179      text-align: center;
180      border: solid 1px #999;
181    }
182    div.profile img {
183      border: none;
184    }
185    div.profile span {
186      font-size: 0.9em;
187      display: block;
188    }
189  </style>
190  EOS
191
192  service_class = {
193    :twitter => Profile::Service::Twitter,
194    :github => Profile::Service::GitHub,
195    :friendfeed => Profile::Service::FriendFeed,
196    :iddy => Profile::Service::Iddy,
197    :gravatar => Profile::Service::Gravatar,
198    :wassr => Profile::Service::Wassr,
199    :hatena => Profile::Service::Hatena,
200  }[service.to_s.downcase.to_sym]
201
202  # TODO: create cache manager class
203
204  # cache = "#{@cache_path}/profile.yaml"
205  cache = "#{@cache_path}/profile.pstore"
206  profile = nil
207  # db = YAML::Store.new(cache)
208  db = PStore.new(cache)
209  db.transaction do
210    key = service_class.name
211    db[key] ||= {} # initialize db
212    updated = db[key][:updated]
213    if updated && (Time::now < updated + 60 * 60) && db[key][:version] == PROFILE_VERSION
214      # use cache
215      profile = db[key][:profile]
216    else
217      # get latest date and update cache
218      begin
219        profile = service_class.new(id, options)
220      rescue # Timeout::Error
221        return html << %Q{ <div class="profile">no profile</div> }
222      end
223      db[key][:updated] = Time::now
224      db[key][:profile] = profile
225      db[key][:version] = PROFILE_VERSION
226    end
227  end
228
229  html << %Q{ <div class="profile"><a href="#{CGI.escapeHTML profile.link}"> }
230  html << %Q{ <span class="profile-image"><img src="#{CGI.escapeHTML profile.image}"></span> } if profile.image
231  html << %Q{ <span class="profile-name">#{CGI.escapeHTML profile.name}</span> } if profile.name
232  html << %Q{ <span class="profile-mail">#{CGI.escapeHTML profile.mail}</span> } if profile.mail
233  html << %Q{ <span class="profile-description">#{CGI.escapeHTML profile.description}</span> } if profile.description
234  html << %Q{ </a></div> }
235end
236
Note: See TracBrowser for help on using the browser.