]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/url.rb
f9e64efbc55e42e85104881459a21b88dd3d0c6b
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / url.rb
1 require 'uri'
2
3 Url = Struct.new("Url", :channel, :nick, :time, :url)
4 TITLE_RE = /<\s*?title\s*?>(.+?)<\s*?\/title\s*?>/im
5
6 class UrlPlugin < Plugin
7   BotConfig.register BotConfigIntegerValue.new('url.max_urls',
8     :default => 100, :validate => Proc.new{|v| v > 0},
9     :desc => "Maximum number of urls to store. New urls replace oldest ones.")
10   BotConfig.register BotConfigBooleanValue.new('url.display_link_info',
11     :default => false,
12     :desc => "Get the title of any links pasted to the channel and display it (also tells if the link is broken or the site is down)")
13   BotConfig.register BotConfigBooleanValue.new('url.titles_only',
14     :default => false,
15     :desc => "Only show info for links that have <title> tags (in other words, don't display info for jpegs, mpegs, etc.)")
16
17   def initialize
18     super
19     @registry.set_default(Array.new)
20   end
21
22   def help(plugin, topic="")
23     "urls [<max>=4] => list <max> last urls mentioned in current channel, urls search [<max>=4] <regexp> => search for matching urls. In a private message, you must specify the channel to query, eg. urls <channel> [max], urls search <channel> [max] <regexp>"
24   end
25
26   def get_title_from_html(pagedata)
27     return unless TITLE_RE.match(pagedata)
28     title = $1.strip.gsub(/\s*\n+\s*/, " ")
29     title = Utils.decode_html_entities title
30     "title: #{title}"
31   end
32
33   def get_title_for_url(uri_str)
34
35     url = uri_str.kind_of?(URI) ? uri_str : URI.parse(uri_str)
36     return if url.scheme !~ /https?/
37
38     title = nil
39
40     begin
41       @bot.httputil.get_response(url) { |response|
42         case response
43         when Net::HTTPSuccess
44           if response['content-type'] =~ /^text\//
45             # since the content is 'text/*' and is small enough to
46             # be a webpage, retrieve the title from the page
47             debug "+ getting #{url.request_uri}"
48
49             # we look for the title in the first 4k bytes
50             response.partial_body(@bot.config['http.info_bytes']) { |part|
51               title = get_title_from_html(part)
52               return title if title
53             }
54             # if nothing was found, provide more basic info
55           end
56           debug response.to_hash.inspect
57           unless @bot.config['url.titles_only']
58             # content doesn't have title, just display info.
59             size = response['content-length'].gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2') rescue nil
60             size = size ? ", size: #{size} bytes" : ""
61             return "type: #{response['content-type']}#{size}"
62           end
63         when Net::HTTPResponse
64           return "Error getting link (#{response.code} - #{response.message})"
65         else
66           raise response
67         end
68       }
69     rescue Object => e
70       if e.class <= StandardError
71         error e.inspect
72         debug e.backtrace.join("\n")
73       end
74
75       msg = e.respond_to?(:message) ? e.message : e.to_s
76       return "Error connecting to site (#{e.message})"
77     end
78   end
79
80   def listen(m)
81     return unless m.kind_of?(PrivMessage)
82     return if m.address?
83     # TODO support multiple urls in one line
84     if m.message =~ /(f|ht)tps?:\/\//
85       if m.message =~ /((f|ht)tps?:\/\/.*?)(?:\s+|$)/
86         urlstr = $1
87         list = @registry[m.target]
88
89         if @bot.config['url.display_link_info']
90           Thread.start do
91             debug "Getting title for #{urlstr}..."
92             begin
93               title = get_title_for_url urlstr
94               if title
95                 m.reply "[Link Info] #{title}"
96                 debug "Title found!"
97               else
98                 debug "Title not found!"
99               end
100             rescue => e
101               debug "Failed: #{e}"
102             end
103           end
104         end
105
106         # check to see if this url is already listed
107         return if list.find {|u| u.url == urlstr }
108
109         url = Url.new(m.target, m.sourcenick, Time.new, urlstr)
110         debug "#{list.length} urls so far"
111         if list.length > @bot.config['url.max_urls']
112           list.pop
113         end
114         debug "storing url #{url.url}"
115         list.unshift url
116         debug "#{list.length} urls now"
117         @registry[m.target] = list
118       end
119     end
120   end
121
122   def urls(m, params)
123     channel = params[:channel] ? params[:channel] : m.target
124     max = params[:limit].to_i
125     max = 10 if max > 10
126     max = 1 if max < 1
127     list = @registry[channel]
128     if list.empty?
129       m.reply "no urls seen yet for channel #{channel}"
130     else
131       list[0..(max-1)].each do |url|
132         m.reply "[#{url.time.strftime('%Y/%m/%d %H:%M:%S')}] <#{url.nick}> #{url.url}"
133       end
134     end
135   end
136
137   def search(m, params)
138     channel = params[:channel] ? params[:channel] : m.target
139     max = params[:limit].to_i
140     string = params[:string]
141     max = 10 if max > 10
142     max = 1 if max < 1
143     regex = Regexp.new(string, Regexp::IGNORECASE)
144     list = @registry[channel].find_all {|url|
145       regex.match(url.url) || regex.match(url.nick)
146     }
147     if list.empty?
148       m.reply "no matches for channel #{channel}"
149     else
150       list[0..(max-1)].each do |url|
151         m.reply "[#{url.time.strftime('%Y/%m/%d %H:%M:%S')}] <#{url.nick}> #{url.url}"
152       end
153     end
154   end
155 end
156 plugin = UrlPlugin.new
157 plugin.map 'urls search :channel :limit :string', :action => 'search',
158                           :defaults => {:limit => 4},
159                           :requirements => {:limit => /^\d+$/},
160                           :public => false
161 plugin.map 'urls search :limit :string', :action => 'search',
162                           :defaults => {:limit => 4},
163                           :requirements => {:limit => /^\d+$/},
164                           :private => false
165 plugin.map 'urls :channel :limit', :defaults => {:limit => 4},
166                           :requirements => {:limit => /^\d+$/},
167                           :public => false
168 plugin.map 'urls :limit', :defaults => {:limit => 4},
169                           :requirements => {:limit => /^\d+$/},
170                           :private => false