]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/youtube.rb
2f009f11994aeed6bb7555bb4c17b32d45202370
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / youtube.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: YouTube plugin for rbot
5 #
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
7 #
8 # Copyright:: (C) 2008 Giuseppe Bilotta
9
10
11 class YouTubePlugin < Plugin
12   YOUTUBE_SEARCH = "http://gdata.youtube.com/feeds/api/videos?vq=%{words}&orderby=relevance"
13   YOUTUBE_VIDEO = "http://gdata.youtube.com/feeds/api/videos/%{code}"
14
15   Config.register Config::IntegerValue.new('youtube.hits',
16     :default => 3,
17     :desc => "Number of hits to return from YouTube searches")
18   Config.register Config::IntegerValue.new('youtube.descs',
19     :default => 3,
20     :desc => "When set to n > 0, the bot will return the description of the first n videos found")
21
22   def youtube_filter(s)
23     loc = Utils.check_location(s, /youtube\.com/)
24     return nil unless loc
25     if s[:text].include? '<div id="vidTitle">'
26       video_info = @bot.filter(:"youtube.video", s)
27       return nil # TODO
28     elsif s[:text].include? '<!-- start search results -->'
29       vids = @bot.filter(:"youtube.search", s)[:videos]
30       if !vids.empty?
31         return nil # TODO
32       end
33     end
34     # otherwise, just grab the proper div
35     if defined? Hpricot
36       content = (Hpricot(s[:text])/"#mainContent").to_html.ircify_html
37     end
38     # suboptimal, but still better than the default HTML info extractor
39     content ||= /<div id="mainContent"[^>]*>/.match(s[:text]).post_match.ircify_html
40     return {:title => s[:text].ircify_html_title, :content => content}
41   end
42
43   def youtube_apivideo_filter(s)
44     # This filter can be used either
45     e = s[:rexml] || REXML::Document.new(s[:text]).elements["entry"]
46     # TODO precomputing mg doesn't work on my REXML, despite what the doc
47     # says?
48     #   mg = e.elements["media:group"]
49     #   :title => mg["media:title"].text
50     # fails because "media:title" is not an Integer. Bah
51     vid = {
52       :author => (e.elements["author/name"].text rescue nil),
53       :title =>  (e.elements["media:group/media:title"].text rescue nil),
54       :desc =>   (e.elements["media:group/media:description"].text rescue nil),
55       :cat => (e.elements["media:group/media:category"].text rescue nil),
56       :seconds => (e.elements["media:group/yt:duration/@seconds"].value.to_i rescue nil),
57       :url => (e.elements["media:group/media:player/@url"].value rescue nil),
58       :rating => (("%s/%s" % [e.elements["gd:rating/@average"].value, e.elements["gd:rating/@max"].value]) rescue nil),
59       :views => (e.elements["yt:statistics/@viewCount"].value rescue nil),
60       :faves => (e.elements["yt:statistics/@favoriteCount"].value rescue nil)
61     }
62     if vid[:desc]
63       vid[:desc].gsub!(/\s+/m, " ")
64     end
65     if secs = vid[:seconds]
66       mins, secs = secs.divmod 60
67       hours, mins = mins.divmod 60
68       if hours > 0
69         vid[:duration] = "%s:%s:%s" % [hours, mins, secs]
70       elsif mins > 0
71         vid[:duration] = "%s'%s\"" % [mins, secs]
72       else
73         vid[:duration] = "%ss" % [secs]
74       end
75     else
76       vid[:duration] = _("unknown duration")
77     end
78     debug vid
79     return vid
80   end
81
82   def youtube_apisearch_filter(s)
83     vids = []
84     title = nil
85     begin
86       doc = REXML::Document.new(s[:text])
87       title = doc.elements["feed/title"].text
88       doc.elements.each("*/entry") { |e|
89         vids << @bot.filter(:"youtube.apivideo", :rexml => e)
90       }
91       debug vids
92     rescue => e
93       debug e
94     end
95     return {:title => title, :vids => vids}
96   end
97
98   def youtube_search_filter(s)
99     # TODO
100     # hits = s[:hits] || @bot.config['youtube.hits']
101     # scrap the videos
102     return []
103   end
104
105   def youtube_video_filter(s)
106     # TODO
107   end
108
109   def initialize
110     super
111     @bot.register_filter(:youtube, :htmlinfo) { |s| youtube_filter(s) }
112     @bot.register_filter(:apisearch, :youtube) { |s| youtube_apisearch_filter(s) }
113     @bot.register_filter(:apivideo, :youtube) { |s| youtube_apivideo_filter(s) }
114     @bot.register_filter(:search, :youtube) { |s| youtube_search_filter(s) }
115     @bot.register_filter(:video, :youtube) { |s| youtube_video_filter(s) }
116   end
117
118   def info(m, params)
119     movie = params[:movie]
120     code = ""
121     case movie
122     when %r{youtube.com/watch\?v=(.*?)(&.*)?$}
123       code = $1.dup
124     when %r{youtube.com/v/(.*)$}
125       code = $1.dup
126     when /^[A-Za-z0-9]+$/
127       code = movie.dup
128     end
129     if code.empty?
130       m.reply _("What movie was that, again?")
131       return
132     end
133
134     url = YOUTUBE_VIDEO % {:code => code}
135     resp, xml = @bot.httputil.get_response(url)
136     unless Net::HTTPSuccess === resp
137       m.reply(_("error looking for movie %{code} on youtube: %{e}") % {:code => code, :e => xml})
138       return
139     end
140     debug "filtering XML"
141     debug xml
142     begin
143       vid = @bot.filter(:"youtube.apivideo", DataStream.new(xml, params))
144     rescue => e
145       debug e
146     end
147     if vid
148       m.reply(_("Video: %{bold}%{title}%{bold} [%{cat}] %{rating} @ %{url} by %{author} (%{duration}). %{views} views, faved %{faves} times.\nDescription: %{desc}") %
149               {:bold => Bold}.merge(vid))
150     else
151       m.reply(_("couldn't retrieve infos on video code %{code}") % {:code => code})
152     end
153   end
154
155   def search(m, params)
156     what = params[:words].to_s
157     searchfor = CGI.escape what
158     url = YOUTUBE_SEARCH % {:words => searchfor}
159     resp, xml = @bot.httputil.get_response(url)
160     unless Net::HTTPSuccess === resp
161       m.reply(_("error looking for %{what} on youtube: %{e}") % {:what => what, :e => xml})
162       return
163     end
164     debug "filtering XML"
165     vids = @bot.filter(:"youtube.apisearch", DataStream.new(xml, params))[:vids][0, @bot.config['youtube.hits']]
166     debug vids
167     case vids.length
168     when 0
169       m.reply _("no videos found for %{what}") % {:what => what}
170       return
171     when 1
172       show = "%{title} (%{duration}) [%{desc}] @ %{url}" % vids.first
173       m.reply _("One video found for %{what}: %{show}") % {:what => what, :show => show}
174     else
175       idx = 0
176       shorts = vids.inject([]) { |list, el|
177         idx += 1
178         list << ("#{idx}. %{bold}%{title}%{bold} (%{duration}) @ %{url}" % {:bold => Bold}.merge(el))
179       }.join(" | ")
180       m.reply(_("Videos for %{what}: %{shorts}") % {:what =>what, :shorts => shorts},
181               :split_at => /\s+\|\s+/)
182       if (descs = @bot.config['youtube.descs']) > 0
183         vids[0, descs].each_with_index { |v, i|
184           m.reply("[#{i+1}] %{title} (%{duration}): %{desc}" % v, :overlong => :truncate)
185         }
186       end
187     end
188   end
189
190 end
191
192 plugin = YouTubePlugin.new
193
194 plugin.map "youtube info :movie", :action => 'info', :threaded => true
195 plugin.map "youtube [search] *words", :action => 'search', :threaded => true