net-irc2 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env ruby
2
+ # vim:encoding=UTF-8:
3
+ =begin
4
+
5
+ ## Licence
6
+
7
+ Ruby's by cho45
8
+
9
+ =end
10
+
11
+ $LOAD_PATH << "lib"
12
+ $LOAD_PATH << "../lib"
13
+
14
+ $KCODE = "u" unless defined? ::Encoding # json use this
15
+
16
+ require "rubygems"
17
+ require "json"
18
+ require "net/http"
19
+ require "net/irc"
20
+ require "sdbm"
21
+ require "tmpdir"
22
+ require "nkf"
23
+ require 'mechanize'
24
+ require 'nokogiri'
25
+
26
+ class HatenaStarStream < Net::IRC::Server::Session
27
+ def server_name
28
+ "hatenastarstream"
29
+ end
30
+
31
+ def server_version
32
+ "0.0.0"
33
+ end
34
+
35
+ def main_channel
36
+ "#star"
37
+ end
38
+
39
+ def initialize(*a)
40
+ super
41
+ @ua = WWW::Mechanize.new
42
+ @ua.max_history = 1
43
+ end
44
+
45
+ def on_user(m)
46
+ super
47
+ post @prefix, JOIN, main_channel
48
+ post server_name, MODE, main_channel, "+o", @prefix.nick
49
+
50
+ @real, *@opts = @real.split(/\s+/)
51
+ @opts = @opts.inject({}) {|r,i|
52
+ key, value = i.split("=")
53
+ r.update(key => value)
54
+ }
55
+
56
+ @uri = URI("http://s.hatena.ne.jp/#{@real}/report.json?api_key=#{@pass}")
57
+ start_observer
58
+ end
59
+
60
+ def on_disconnected
61
+ @observer.kill rescue nil
62
+ end
63
+
64
+ def on_privmsg(m)
65
+ end
66
+
67
+ def on_ctcp(target, message)
68
+ end
69
+
70
+ def on_whois(m)
71
+ end
72
+
73
+ def on_who(m)
74
+ end
75
+
76
+ def on_join(m)
77
+ end
78
+
79
+ def on_part(m)
80
+ end
81
+
82
+ private
83
+ def start_observer
84
+ @observer = Thread.start do
85
+ Thread.abort_on_exception = true
86
+ loop do
87
+ begin
88
+ @log.info "getting report..."
89
+ @log.debug @uri.to_s
90
+ data = JSON.parse(Net::HTTP.get(@uri.host, @uri.request_uri, @uri.port))
91
+
92
+ db = SDBM.open("#{Dir.tmpdir}/#{@real}.db", 0666)
93
+ data['entries'].reverse_each do |entry|
94
+ stars = ((entry['colored_stars'] || []) + [ entry ]).inject([]) {|r,i|
95
+ r.concat i['stars'].map {|s| Star.new(s, i['color'] || 'normal') }
96
+ }
97
+
98
+ indexes = Hash.new(1)
99
+ s = stars.select {|star|
100
+ id = "#{entry['uri']}::#{indexes[star.color]}"
101
+ indexes[star.color] += 1
102
+ if db.include?(id)
103
+ false
104
+ else
105
+ db[id] = "1"
106
+ true
107
+ end
108
+ }.inject([]) {|r,i|
109
+ if r.last == i
110
+ r.last.count += 1
111
+ else
112
+ r << i
113
+ end
114
+ r
115
+ }
116
+
117
+ if s.length > 0
118
+ post server_name, NOTICE, main_channel, "#{entry['uri']} #{title(entry['uri'])}"
119
+ if @opts.key?("metadata")
120
+ post "metadata", NOTICE, main_channel, JSON.generate({ "uri" => entry['uri'] })
121
+ end
122
+ end
123
+
124
+ s.each do |star|
125
+ post server_name, NOTICE, main_channel, "id:%s \x03%d%s%s\x030 %s" % [
126
+ star.name,
127
+ Star::Colors[star.color],
128
+ ((star.color == "normal") ? "☆" : "★") * ([star.count, 10].min),
129
+ (star.count > 10) ? "(...#{star.count})" : "",
130
+ star.quote
131
+ ]
132
+ end
133
+ end
134
+
135
+ rescue Exception => e
136
+ @log.error e.inspect
137
+ @log.error e.backtrace
138
+ ensure
139
+ db.close rescue nil
140
+ end
141
+ sleep 60 * 5
142
+ end
143
+ end
144
+ end
145
+
146
+ def title(url)
147
+ uri = URI(url)
148
+ @ua.get(uri)
149
+
150
+ text = ""
151
+ case
152
+ when uri.fragment
153
+ fragment = @ua.page.root.at("//*[@name = '#{uri.fragment}']") || @ua.page.root.at("//*[@id = '#{uri.fragment}']")
154
+
155
+ text = fragment.inner_text
156
+ while fragment.respond_to? :parent
157
+ text += (fragment.next && fragment.next.text.to_s).to_s
158
+ fragment = fragment.parent
159
+ end
160
+ when uri.to_s =~ %r|^http://h.hatena.ne.jp/[^/]+/\d+|
161
+ text = @ua.page.root.at("#main .entries .entry .list-body div.body").inner_text
162
+ else
163
+ text = @ua.page.root.at("//title").inner_text
164
+ end
165
+ text.gsub!(/\s+/, " ")
166
+ text.strip!
167
+ NKF.nkf("-w", text).split(//)[0..60].join
168
+ rescue Exception => e
169
+ @log.debug ["title:", e.inspect]
170
+ ""
171
+ end
172
+
173
+ class Star < OpenStruct
174
+ Colors = {
175
+ "purple" => 6,
176
+ "blue" => 2,
177
+ "green" => 3,
178
+ "red" => 4,
179
+ "normal" => 8,
180
+ }
181
+
182
+ def initialize(obj, col="normal")
183
+ super(obj)
184
+ self.count = obj["count"].to_i + 1
185
+ self.color = col
186
+ end
187
+
188
+ def ==(other)
189
+ self.color == other.color &&
190
+ self.name == other.name
191
+ end
192
+ end
193
+ end
194
+
195
+ if __FILE__ == $0
196
+ require "optparse"
197
+
198
+ opts = {
199
+ :port => 16702,
200
+ :host => "localhost",
201
+ :log => nil,
202
+ :debug => false,
203
+ :foreground => false,
204
+ }
205
+
206
+ OptionParser.new do |parser|
207
+ parser.instance_eval do
208
+ self.banner = <<-EOB.gsub(/^\t+/, "")
209
+ Usage: #{$0} [opts]
210
+
211
+ EOB
212
+
213
+ separator ""
214
+
215
+ separator "Options:"
216
+ on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
217
+ opts[:port] = port
218
+ end
219
+
220
+ on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
221
+ opts[:host] = host
222
+ end
223
+
224
+ on("-l", "--log LOG", "log file") do |log|
225
+ opts[:log] = log
226
+ end
227
+
228
+ on("--debug", "Enable debug mode") do |debug|
229
+ opts[:log] = $stdout
230
+ opts[:debug] = true
231
+ end
232
+
233
+ on("-f", "--foreground", "run foreground") do |foreground|
234
+ opts[:log] = $stdout
235
+ opts[:foreground] = true
236
+ end
237
+
238
+ parse!(ARGV)
239
+ end
240
+ end
241
+
242
+ opts[:logger] = Logger.new(opts[:log], "daily")
243
+ opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
244
+
245
+ def daemonize(foreground=false)
246
+ trap("SIGINT") { exit! 0 }
247
+ trap("SIGTERM") { exit! 0 }
248
+ trap("SIGHUP") { exit! 0 }
249
+ return yield if $DEBUG || foreground
250
+ Process.fork do
251
+ Process.setsid
252
+ Dir.chdir "/"
253
+ File.open("/dev/null") {|f|
254
+ STDIN.reopen f
255
+ STDOUT.reopen f
256
+ STDERR.reopen f
257
+ }
258
+ yield
259
+ end
260
+ exit! 0
261
+ end
262
+
263
+ daemonize(opts[:debug] || opts[:foreground]) do
264
+ Net::IRC::Server.new(opts[:host], opts[:port], HatenaStarStream, opts).start
265
+ end
266
+ end
267
+
268
+ # Local Variables:
269
+ # coding: utf-8
270
+ # End:
data/examples/hcig.rb ADDED
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env ruby
2
+ # vim:encoding=utf-8:
3
+
4
+ $LOAD_PATH << "lib"
5
+ $LOAD_PATH << "../lib"
6
+ $LOAD_PATH.concat Dir.glob("/usr/local/ruby1.9/lib/ruby/gems/1.9.1/gems/*/lib")
7
+
8
+ $KCODE = "u" if RUBY_VERSION < "1.9" # json use this
9
+
10
+ require "rubygems"
11
+ require "net/irc"
12
+ require "logger"
13
+ require "pathname"
14
+ require "yaml"
15
+ require 'uri'
16
+ require 'net/http'
17
+ require 'nkf'
18
+ require 'stringio'
19
+ require 'zlib'
20
+ require 'mechanize'
21
+ require 'digest/sha1'
22
+
23
+
24
+ Net::HTTP.version_1_2
25
+ Thread.abort_on_exception = true
26
+
27
+ class HatenaCounterIrcGateway < Net::IRC::Server::Session
28
+ COLORS = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
29
+
30
+ def server_name
31
+ "hcig"
32
+ end
33
+
34
+ def server_version
35
+ "0.0.0"
36
+ end
37
+
38
+
39
+ def initialize(*args)
40
+ super
41
+ @channels = {}
42
+ @ua = Mechanize.new
43
+ end
44
+
45
+ def on_disconnected
46
+ @channels.each do |chan, info|
47
+ begin
48
+ info[:observer].kill if info[:observer]
49
+ rescue
50
+ end
51
+ end
52
+ end
53
+
54
+ def on_user(m)
55
+ super
56
+ @real, *@opts = @real.split(/\s+/)
57
+ @opts ||= []
58
+ end
59
+
60
+ def on_pass(m)
61
+ super
62
+ self.rk = @pass
63
+ end
64
+
65
+ def on_join(m)
66
+ channels = m.params.first.split(/,/)
67
+ channels.each do |channel|
68
+ @channels[channel] = {
69
+ :topic => "",
70
+ :time => Time.at(0),
71
+ :interval => 60,
72
+ :observer => nil,
73
+ } unless @channels.key?(channel)
74
+ create_observer(channel)
75
+ post @prefix, JOIN, channel
76
+ post nil, RPL_NAMREPLY, @prefix.nick, "=", channel, "@#{@prefix.nick}"
77
+ post nil, RPL_ENDOFNAMES, @prefix.nick, channel, "End of NAMES list"
78
+ end
79
+ end
80
+
81
+ def on_part(m)
82
+ channel = m.params[0]
83
+ if @channels.key?(channel)
84
+ info = @channels.delete(channel)
85
+ info[:observer].kill if info[:observer]
86
+ post @prefix, PART, channel
87
+ end
88
+ end
89
+
90
+ def on_privmsg(m)
91
+ target, mesg = *m.params
92
+ m.ctcps.each {|ctcp| on_ctcp(target, ctcp) } if m.ctcp?
93
+ end
94
+
95
+ def on_ctcp(target, mesg)
96
+ type, mesg = mesg.split(" ", 2)
97
+ method = "on_ctcp_#{type.downcase}".to_sym
98
+ send(method, target, mesg) if respond_to? method, true
99
+ end
100
+
101
+ def on_ctcp_action(target, mesg)
102
+ command, *args = mesg.split(" ")
103
+ command.downcase!
104
+
105
+ case command
106
+ when 'rk'
107
+ self.rk = args[0]
108
+ end
109
+ rescue Exception => e
110
+ @log.error e.inspect
111
+ e.backtrace.each do |l|
112
+ @log.error "\t#{l}"
113
+ end
114
+ end
115
+
116
+ def create_observer(channel)
117
+ info = @channels[channel]
118
+ info[:observer].kill if info[:observer]
119
+
120
+ @log.debug "create_observer %s, interval %d" % [channel, info[:interval]]
121
+ info[:observer] = Thread.start(info, channel) do |info, channel|
122
+ Thread.pass
123
+ pre, name, cid = *channel.split(/-/)
124
+
125
+ if !name || !cid
126
+ post @prefix, PART, channel, "You must join to #counter-[username]-[counter id]"
127
+ info[:observer].kill
128
+ next
129
+ end
130
+
131
+ loop do
132
+ begin
133
+ uri = "http://counter.hatena.ne.jp/#{name}/log?cid=#{cid}&date=&type="
134
+ @log.debug "Retriving... #{uri}"
135
+ ret = @ua.get(uri) do |page|
136
+ page.search('#log_table tr').reverse_each do |tr|
137
+ access = Access.new(*tr.search('td').map {|i| i.text.gsub("\302\240", ' ').gsub(/^\s+|\s+$/, '') })
138
+ next unless access.time
139
+ next if access.time < info[:time]
140
+
141
+ diff = Time.now - access.time
142
+ time = nil
143
+ case
144
+ when diff < 90
145
+ time = ''
146
+ when Time.now.strftime('%Y%m%d') == access.time.strftime('%Y%m%d')
147
+ time = access.time.strftime('%H:%M')
148
+ when Time.now.strftime('%Y') == access.time.strftime('%Y')
149
+ time = access.time.strftime('%m/%d %H:%M')
150
+ else
151
+ time = access.time.strftime('%Y/%m/%d %H:%M')
152
+ end
153
+
154
+ post access.ua_id, PRIVMSG, channel, "%s%s \003%.2d%s\017" % [
155
+ time.empty?? "" : "#{time} ",
156
+ access.request,
157
+ COLORS[access.ua_id(COLORS.size)],
158
+ access.host.gsub(/(\.\d+)+\./, '..').sub(/^[^.]+/, '')
159
+ ]
160
+ info[:time] = access.time
161
+ end
162
+ info[:time] += 1
163
+ end
164
+ unless ret.code.to_i == 200 && ret.uri.to_s == uri
165
+ @log.error "Server returned [#{ret.code}] #{ret.uri}"
166
+ post nil, NOTICE, channel, "Server returned [#{ret.code}] #{ret.uri}. Please refresh rk by /me rk [new rk]"
167
+ end
168
+ rescue Exception => e
169
+ @log.error "Error: #{e.inspect}"
170
+ e.backtrace.each do |l|
171
+ @log.error "\t#{l}"
172
+ end
173
+ end
174
+ @log.debug "#{channel}: sleep #{info[:interval]}"
175
+ sleep info[:interval]
176
+ end
177
+ end
178
+ end
179
+
180
+ def rk=(rk)
181
+ uri = URI.parse('http://www.hatena.ne.jp/')
182
+ @ua.cookie_jar.add(
183
+ uri,
184
+ Mechanize::Cookie.parse(uri, "rk=#{rk}; domain=.hatena.ne.jp").first
185
+ )
186
+ end
187
+
188
+ Access = Struct.new(:time_raw, :request, :ua, :lang, :screen, :host, :referrer) do
189
+ require 'time'
190
+
191
+ def time
192
+ time_raw ? Time.parse(time_raw) : nil
193
+ end
194
+
195
+ def digest
196
+ hostc = host.gsub(/\.[^.]+\.jp$|\.com$/, '')[/[^.]+$/]
197
+ @digest ||= Digest::SHA1.digest([ua, lang, screen, hostc].join("\n"))
198
+ end
199
+
200
+ def ua_id(num=nil)
201
+ if num
202
+ digest.unpack("N*")[0] % num
203
+ else
204
+ @ua_id ||= [ digest ].pack('m')[0, 7]
205
+ end
206
+ end
207
+ end
208
+
209
+ end
210
+
211
+ if __FILE__ == $0
212
+ require "optparse"
213
+
214
+ opts = {
215
+ :port => 16801,
216
+ :host => "localhost",
217
+ :log => nil,
218
+ :debug => false,
219
+ :foreground => false,
220
+ }
221
+
222
+ OptionParser.new do |parser|
223
+ parser.instance_eval do
224
+ self.banner = <<-EOB.gsub(/^\t+/, "")
225
+ Usage: #{$0} [opts]
226
+
227
+ EOB
228
+
229
+ separator ""
230
+
231
+ separator "Options:"
232
+ on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
233
+ opts[:port] = port
234
+ end
235
+
236
+ on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
237
+ opts[:host] = host
238
+ end
239
+
240
+ on("-l", "--log LOG", "log file") do |log|
241
+ opts[:log] = log
242
+ end
243
+
244
+ on("--debug", "Enable debug mode") do |debug|
245
+ opts[:log] = $stdout
246
+ opts[:debug] = true
247
+ end
248
+
249
+ on("-f", "--foreground", "run foreground") do |foreground|
250
+ opts[:log] = $stdout
251
+ opts[:foreground] = true
252
+ end
253
+
254
+ parse!(ARGV)
255
+ end
256
+ end
257
+
258
+ opts[:logger] = Logger.new(opts[:log], "daily")
259
+ opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
260
+
261
+ def daemonize(foreground=false)
262
+ trap("SIGINT") { exit! 0 }
263
+ trap("SIGTERM") { exit! 0 }
264
+ trap("SIGHUP") { exit! 0 }
265
+ return yield if $DEBUG || foreground
266
+ Process.fork do
267
+ Process.setsid
268
+ Dir.chdir "/"
269
+ File.open("/dev/null") {|f|
270
+ STDIN.reopen f
271
+ STDOUT.reopen f
272
+ STDERR.reopen f
273
+ }
274
+ yield
275
+ end
276
+ exit! 0
277
+ end
278
+
279
+ daemonize(opts[:debug] || opts[:foreground]) do
280
+ Net::IRC::Server.new(opts[:host], opts[:port], HatenaCounterIrcGateway, opts).start
281
+ end
282
+ end
283
+
284
+
285
+