net-irc2 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+