]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/url.rb
Whoops! I renamed the config setting in one place and not another...
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / url.rb
1 require 'net/http'
2 require 'uri'
3 require 'cgi'
4
5 Url = Struct.new("Url", :channel, :nick, :time, :url)
6 TITLE_RE = /<\s*?title\s*?>(.+?)<\s*?\/title\s*?>/im
7
8 class UrlPlugin < Plugin
9   BotConfig.register BotConfigIntegerValue.new('url.max_urls',
10     :default => 100, :validate => Proc.new{|v| v > 0},
11     :desc => "Maximum number of urls to store. New urls replace oldest ones.")
12   BotConfig.register BotConfigBooleanValue.new('url.display_link_info',
13     :default => true, 
14     :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)")
15   
16   def initialize
17     super
18     @registry.set_default(Array.new)
19   end
20
21   def help(plugin, topic="")
22     "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>"
23   end
24
25   def get_title_from_html(pagedata)
26     return unless TITLE_RE.match(pagedata)
27     title = $1.strip.gsub(/\s*\n+\s*/, " ")
28     title = CGI::unescapeHTML title
29     title = title[0..255] if title.length > 255
30     "[Link Info] title: #{title}"
31   end
32
33   def get_title_for_url(uri_str)
34     # This god-awful mess is what the ruby http library has reduced me to.
35     # Python's HTTP lib is so much nicer. :~(
36     
37     puts "+ Getting #{uri_str}"
38     url = URI.parse(uri_str)
39     return if url.scheme !~ /https?/
40     
41     puts "+ connecting to #{url.host}:#{url.port}"
42     http = @bot.httputil.get_proxy(url) 
43     title = http.start do |http|
44       url.path = '/' if url.path == ''
45       head = http.request_head(url.path)
46       case head
47         when Net::HTTPRedirection then
48           # call self recursively if this is a redirect
49           redirect_to = head['location']
50           puts "+ redirect location: #{redirect_to}"
51           url = URI.join url.to_s, redirect_to
52           puts "+ whee, redirecting to #{url.to_s}!"
53           title = get_title_for_url(url.to_s)
54         when Net::HTTPSuccess then
55           if head['content-type'] =~ /^text\//
56             # content is 'text/*'
57             # retrieve the title from the page
58             puts "+ getting #{url.request_uri}"
59             response = http.request_get(url.request_uri)
60             return get_title_from_html(response.body)
61           else
62             # content isn't 'text/*'... display info about the file.
63             size = head['content-length'].gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
64             #lastmod = head['last-modified']
65             return "[Link Info] type: #{head['content-type']}#{size ? ", size: #{size} bytes" : ""}"
66           end
67         when Net::HTTPClientError then
68           return "[Link Info] Error getting link (#{head.code} - #{head.message})"
69         when Net::HTTPServerError then
70           return "[Link Info] Error getting link (#{head.code} - #{head.message})"
71       end
72     end
73   rescue SocketError => e
74     return "[Link Info] Error connecting to site (#{e.message})"
75   end
76
77   def listen(m)
78     return unless m.kind_of?(PrivMessage)
79     return if m.address?
80     # TODO support multiple urls in one line
81     if m.message =~ /(f|ht)tps?:\/\//
82       if m.message =~ /((f|ht)tps?:\/\/.*?)(?:\s+|$)/
83         urlstr = $1
84         list = @registry[m.target]
85
86         if @bot.config['url.display_link_info']
87           debug "Getting title for #{urlstr}..."
88           title = get_title_for_url urlstr
89           if title
90             m.reply title
91             debug "Title found!"
92           else
93             debug "Title not found!"
94           end        
95         end
96     
97         # check to see if this url is already listed
98         return if list.find {|u| u.url == urlstr }
99         
100         url = Url.new(m.target, m.sourcenick, Time.new, urlstr)
101         debug "#{list.length} urls so far"
102         if list.length > @bot.config['url.max_urls']
103           list.pop
104         end
105         debug "storing url #{url.url}"
106         list.unshift url
107         debug "#{list.length} urls now"
108         @registry[m.target] = list
109       end
110     end
111   end
112
113   def urls(m, params)
114     channel = params[:channel] ? params[:channel] : m.target
115     max = params[:limit].to_i
116     max = 10 if max > 10
117     max = 1 if max < 1
118     list = @registry[channel]
119     if list.empty?
120       m.reply "no urls seen yet for channel #{channel}"
121     else
122       list[0..(max-1)].each do |url|
123         m.reply "[#{url.time.strftime('%Y/%m/%d %H:%M:%S')}] <#{url.nick}> #{url.url}"
124       end
125     end
126   end
127
128   def search(m, params)
129     channel = params[:channel] ? params[:channel] : m.target
130     max = params[:limit].to_i
131     string = params[:string]
132     max = 10 if max > 10
133     max = 1 if max < 1
134     regex = Regexp.new(string)
135     list = @registry[channel].find_all {|url|
136       regex.match(url.url) || regex.match(url.nick)
137     }
138     if list.empty?
139       m.reply "no matches for channel #{channel}"
140     else
141       list[0..(max-1)].each do |url|
142         m.reply "[#{url.time.strftime('%Y/%m/%d %H:%M:%S')}] <#{url.nick}> #{url.url}"
143       end
144     end
145   end
146 end
147 plugin = UrlPlugin.new
148 plugin.map 'urls search :channel :limit :string', :action => 'search',
149                           :defaults => {:limit => 4},
150                           :requirements => {:limit => /^\d+$/},
151                           :public => false
152 plugin.map 'urls search :limit :string', :action => 'search',
153                           :defaults => {:limit => 4},
154                           :requirements => {:limit => /^\d+$/},
155                           :private => false
156 plugin.map 'urls :channel :limit', :defaults => {:limit => 4},
157                           :requirements => {:limit => /^\d+$/},
158                           :public => false
159 plugin.map 'urls :limit', :defaults => {:limit => 4},
160                           :requirements => {:limit => /^\d+$/},
161                           :private => false