4 # :title: YouTube plugin for rbot
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
8 # Copyright:: (C) 2008 Giuseppe Bilotta
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}"
15 Config.register Config::IntegerValue.new('youtube.hits',
17 :desc => "Number of hits to return from YouTube searches")
18 Config.register Config::IntegerValue.new('youtube.descs',
20 :desc => "When set to n > 0, the bot will return the description of the first n videos found")
23 loc = Utils.check_location(s, /youtube\.com/)
25 if s[:text].include? '<div id="vidTitle">'
26 video_info = @bot.filter(:"youtube.video", s)
28 elsif s[:text].include? '<!-- start search results -->'
29 vids = @bot.filter(:"youtube.search", s)[:videos]
34 # otherwise, just grab the proper div
36 content = (Hpricot(s[:text])/"#mainContent").to_html.ircify_html
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}
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
48 # mg = e.elements["media:group"]
49 # :title => mg["media:title"].text
50 # fails because "media:title" is not an Integer. Bah
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)
63 vid[:desc].gsub!(/\s+/m, " ")
65 if secs = vid[:seconds]
66 mins, secs = secs.divmod 60
67 hours, mins = mins.divmod 60
69 vid[:duration] = "%s:%s:%s" % [hours, mins, secs]
71 vid[:duration] = "%s'%s\"" % [mins, secs]
73 vid[:duration] = "%ss" % [secs]
76 vid[:duration] = _("unknown duration")
82 def youtube_apisearch_filter(s)
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)
95 return {:title => title, :vids => vids}
98 def youtube_search_filter(s)
100 # hits = s[:hits] || @bot.config['youtube.hits']
105 def youtube_video_filter(s)
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) }
119 movie = params[:movie]
122 when %r{youtube.com/watch\?v=(.*?)(&.*)?$}
124 when %r{youtube.com/v/(.*)$}
126 when /^[A-Za-z0-9]+$/
130 m.reply _("What movie was that, again?")
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})
140 debug "filtering XML"
143 vid = @bot.filter(:"youtube.apivideo", DataStream.new(xml, params))
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))
151 m.reply(_("couldn't retrieve infos on video code %{code}") % {:code => code})
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})
164 debug "filtering XML"
165 vids = @bot.filter(:"youtube.apisearch", DataStream.new(xml, params))[:vids][0, @bot.config['youtube.hits']]
169 m.reply _("no videos found for %{what}") % {:what => what}
172 show = "%{title} (%{duration}) [%{desc}] @ %{url}" % vids.first
173 m.reply _("One video found for %{what}: %{show}") % {:what => what, :show => show}
176 shorts = vids.inject([]) { |list, el|
178 list << ("#{idx}. %{bold}%{title}%{bold} (%{duration}) @ %{url}" % {:bold => Bold}.merge(el))
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)
192 plugin = YouTubePlugin.new
194 plugin.map "youtube info :movie", :action => 'info', :threaded => true
195 plugin.map "youtube [search] *words", :action => 'search', :threaded => true