net-irc 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +7 -0
- data/examples/hatena-star-stream.rb +265 -0
- data/examples/lig.rb +3 -0
- data/examples/mixi.rb +236 -0
- data/examples/tig.rb +68 -21
- data/lib/net/irc.rb +6 -811
- data/lib/net/irc/client.rb +227 -0
- data/lib/net/irc/constants.rb +214 -0
- data/lib/net/irc/message.rb +100 -0
- data/lib/net/irc/message/modeparser.rb +44 -0
- data/lib/net/irc/message/modeparser/hyperion.rb +88 -0
- data/lib/net/irc/message/modeparser/rfc1459.rb +21 -0
- data/lib/net/irc/pattern.rb +68 -0
- data/lib/net/irc/server.rb +188 -0
- data/spec/modeparser_spec.rb +43 -0
- data/spec/net-irc_spec.rb +7 -7
- metadata +17 -3
data/ChangeLog
CHANGED
@@ -0,0 +1,265 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
=begin
|
3
|
+
|
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" # json use this
|
15
|
+
|
16
|
+
require "rubygems"
|
17
|
+
require "json"
|
18
|
+
require "net/irc"
|
19
|
+
require "mechanize"
|
20
|
+
require "sdbm"
|
21
|
+
require "tmpdir"
|
22
|
+
require "nkf"
|
23
|
+
|
24
|
+
class HatenaStarStream < Net::IRC::Server::Session
|
25
|
+
def server_name
|
26
|
+
"hatenastarstream"
|
27
|
+
end
|
28
|
+
|
29
|
+
def server_version
|
30
|
+
"0.0.0"
|
31
|
+
end
|
32
|
+
|
33
|
+
def main_channel
|
34
|
+
"#star"
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(*args)
|
38
|
+
super
|
39
|
+
@ua = WWW::Mechanize.new
|
40
|
+
@ua.max_history = 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_user(m)
|
44
|
+
super
|
45
|
+
post @prefix, JOIN, main_channel
|
46
|
+
post server_name, MODE, main_channel, "+o", @prefix.nick
|
47
|
+
|
48
|
+
@real, *@opts = @opts.name || @real.split(/\s+/)
|
49
|
+
@opts ||= []
|
50
|
+
|
51
|
+
start_observer
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_disconnected
|
55
|
+
@observer.kill rescue nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_privmsg(m)
|
59
|
+
@ua.instance_eval do
|
60
|
+
get "http://h.hatena.ne.jp/"
|
61
|
+
form = page.forms.find {|f| f.action == "/entry" }
|
62
|
+
form["body"] = m[1]
|
63
|
+
submit form
|
64
|
+
end
|
65
|
+
post server_name, NOTICE, main_channel, "posted"
|
66
|
+
rescue Exception => e
|
67
|
+
log e.inspect
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_ctcp(target, message)
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_whois(m)
|
74
|
+
end
|
75
|
+
|
76
|
+
def on_who(m)
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_join(m)
|
80
|
+
end
|
81
|
+
|
82
|
+
def on_part(m)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def start_observer
|
87
|
+
@observer = Thread.start do
|
88
|
+
Thread.abort_on_exception = true
|
89
|
+
loop do
|
90
|
+
begin
|
91
|
+
login
|
92
|
+
@log.info "getting report..."
|
93
|
+
@ua.get("http://s.hatena.ne.jp/#{@real}/report")
|
94
|
+
entries = @ua.page.root.search("#main span.entry-title a").map {|a|
|
95
|
+
a[:href]
|
96
|
+
}
|
97
|
+
|
98
|
+
@log.info "getting stars... #{entries.length}"
|
99
|
+
stars = retrive_stars(entries)
|
100
|
+
|
101
|
+
db = SDBM.open("#{Dir.tmpdir}/#{@real}.db", 0666)
|
102
|
+
entries.reverse_each do |entry|
|
103
|
+
next if stars[entry].empty?
|
104
|
+
s, quoted = stars[entry].select {|star|
|
105
|
+
id = "#{entry}::#{star.values_at("name", "quote").inspect}"
|
106
|
+
if db.include?(id)
|
107
|
+
false
|
108
|
+
else
|
109
|
+
db[id] = "1"
|
110
|
+
true
|
111
|
+
end
|
112
|
+
}.partition {|star| star["quote"].empty? }
|
113
|
+
post server_name, NOTICE, main_channel, "#{entry} #{title(entry)}" if s.length + quoted.length > 0
|
114
|
+
post server_name, NOTICE, main_channel, s.map {|star| "id:#{star["name"]}" }.join(" ") unless s.empty?
|
115
|
+
|
116
|
+
quoted.each do |star|
|
117
|
+
post server_name, NOTICE, main_channel, "id:#{star["name"]} '#{star["quote"]}'"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
rescue Exception => e
|
122
|
+
@log.error e.inspect
|
123
|
+
ensure
|
124
|
+
db.close rescue nil
|
125
|
+
end
|
126
|
+
sleep 60 * 5
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def retrive_stars(entries, n=0)
|
132
|
+
uri = "http://s.hatena.ne.jp/entries.json?"
|
133
|
+
while uri.length < 1800 and n < entries.length
|
134
|
+
uri << "uri=#{URI.escape(entries[n], /[^-.!~*'()\w]/n)}&"
|
135
|
+
n += 1
|
136
|
+
end
|
137
|
+
ret = JSON.load(@ua.get(uri).body)["entries"].inject({}) {|r,i|
|
138
|
+
if i["stars"].any? {|star| star.kind_of? Numeric }
|
139
|
+
i = JSON.load(@ua.get("http://s.hatena.ne.jp/entry.json?uri=#{URI.escape(i["uri"])}").body)["entries"].first
|
140
|
+
end
|
141
|
+
r.update(i["uri"] => i["stars"])
|
142
|
+
}
|
143
|
+
if n < entries.length
|
144
|
+
ret.update retrive_stars(entries, n)
|
145
|
+
end
|
146
|
+
ret
|
147
|
+
end
|
148
|
+
|
149
|
+
def title(url)
|
150
|
+
uri = URI(url)
|
151
|
+
@ua.get(uri)
|
152
|
+
|
153
|
+
text = ""
|
154
|
+
case
|
155
|
+
when uri.fragment
|
156
|
+
fragment = @ua.page.root.at("//*[@name = '#{uri.fragment}']") || @ua.page.root.at("//*[@id = '#{uri.fragment}']")
|
157
|
+
|
158
|
+
text = fragment.inner_text + fragment.following.text + fragment.parent.following.text
|
159
|
+
when uri.to_s =~ %r|^http://h.hatena.ne.jp/[^/]+/\d+|
|
160
|
+
text = @ua.page.root.at("#main .entries .entry .list-body div.body").inner_text
|
161
|
+
else
|
162
|
+
text = @ua.page.root.at("//title").inner_text
|
163
|
+
end
|
164
|
+
text.gsub!(/\s+/, " ")
|
165
|
+
text.strip!
|
166
|
+
NKF.nkf("-w", text).split(//)[0..30].join
|
167
|
+
rescue Exception => e
|
168
|
+
@log.debug ["title:", e.inspect]
|
169
|
+
""
|
170
|
+
end
|
171
|
+
|
172
|
+
def login
|
173
|
+
@log.info "logging in as #{@real}"
|
174
|
+
@ua.get "https://www.hatena.ne.jp/login?backurl=http%3A%2F%2Fd.hatena.ne.jp%2F"
|
175
|
+
return if @ua.page.forms.empty?
|
176
|
+
|
177
|
+
form = @ua.page.forms.first
|
178
|
+
form["name"] = @real
|
179
|
+
form["password"] = @pass
|
180
|
+
|
181
|
+
@ua.submit(form)
|
182
|
+
|
183
|
+
unless @ua.page.forms.empty?
|
184
|
+
post server_name, ERR_PASSWDMISMATCH, ":Password incorrect"
|
185
|
+
finish
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
if __FILE__ == $0
|
191
|
+
require "optparse"
|
192
|
+
|
193
|
+
opts = {
|
194
|
+
:port => 16700,
|
195
|
+
:host => "localhost",
|
196
|
+
:log => nil,
|
197
|
+
:debug => false,
|
198
|
+
:foreground => false,
|
199
|
+
}
|
200
|
+
|
201
|
+
OptionParser.new do |parser|
|
202
|
+
parser.instance_eval do
|
203
|
+
self.banner = <<-EOB.gsub(/^\t+/, "")
|
204
|
+
Usage: #{$0} [opts]
|
205
|
+
|
206
|
+
EOB
|
207
|
+
|
208
|
+
separator ""
|
209
|
+
|
210
|
+
separator "Options:"
|
211
|
+
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
|
212
|
+
opts[:port] = port
|
213
|
+
end
|
214
|
+
|
215
|
+
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
|
216
|
+
opts[:host] = host
|
217
|
+
end
|
218
|
+
|
219
|
+
on("-l", "--log LOG", "log file") do |log|
|
220
|
+
opts[:log] = log
|
221
|
+
end
|
222
|
+
|
223
|
+
on("--debug", "Enable debug mode") do |debug|
|
224
|
+
opts[:log] = $stdout
|
225
|
+
opts[:debug] = true
|
226
|
+
end
|
227
|
+
|
228
|
+
on("-f", "--foreground", "run foreground") do |foreground|
|
229
|
+
opts[:log] = $stdout
|
230
|
+
opts[:foreground] = true
|
231
|
+
end
|
232
|
+
|
233
|
+
parse!(ARGV)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
opts[:logger] = Logger.new(opts[:log], "daily")
|
238
|
+
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
|
239
|
+
|
240
|
+
def daemonize(foreground=false)
|
241
|
+
trap("SIGINT") { exit! 0 }
|
242
|
+
trap("SIGTERM") { exit! 0 }
|
243
|
+
trap("SIGHUP") { exit! 0 }
|
244
|
+
return yield if $DEBUG || foreground
|
245
|
+
Process.fork do
|
246
|
+
Process.setsid
|
247
|
+
Dir.chdir "/"
|
248
|
+
File.open("/dev/null") {|f|
|
249
|
+
STDIN.reopen f
|
250
|
+
STDOUT.reopen f
|
251
|
+
STDERR.reopen f
|
252
|
+
}
|
253
|
+
yield
|
254
|
+
end
|
255
|
+
exit! 0
|
256
|
+
end
|
257
|
+
|
258
|
+
daemonize(opts[:debug] || opts[:foreground]) do
|
259
|
+
Net::IRC::Server.new(opts[:host], opts[:port], HatenaStarStream, opts).start
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Local Variables:
|
264
|
+
# coding: utf-8
|
265
|
+
# End:
|
data/examples/lig.rb
CHANGED
@@ -266,6 +266,9 @@ class LingrIrcGateway < Net::IRC::Server::Session
|
|
266
266
|
unless e.code == 102
|
267
267
|
log "Error: #{e.code}: #{e.message}"
|
268
268
|
log "Coundn't say to #{target}."
|
269
|
+
|
270
|
+
@channels.delete(channel.downcase)
|
271
|
+
post prefix, PART, channel, "Parted"
|
269
272
|
end
|
270
273
|
end
|
271
274
|
|
data/examples/mixi.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
=begin
|
3
|
+
|
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" # json use this
|
15
|
+
|
16
|
+
require "rubygems"
|
17
|
+
require "json"
|
18
|
+
require "net/irc"
|
19
|
+
require "mechanize"
|
20
|
+
|
21
|
+
# Mixi from mixi.vim by ujihisa!
|
22
|
+
class Mixi
|
23
|
+
def initialize(email, password, mixi_premium = false, image_dir = '~/.vim/mixi_images')
|
24
|
+
require 'kconv'
|
25
|
+
require 'rubygems'
|
26
|
+
require 'mechanize'
|
27
|
+
|
28
|
+
@image_dir = File.expand_path image_dir
|
29
|
+
@email, @password, @mixi_premium =
|
30
|
+
email, password, mixi_premium
|
31
|
+
end
|
32
|
+
|
33
|
+
def post(title, body, images)
|
34
|
+
@agent = WWW::Mechanize.new
|
35
|
+
@agent.user_agent_alias = 'Mac Safari'
|
36
|
+
page = @agent.get 'http://mixi.jp/home.pl'
|
37
|
+
form = page.forms[0]
|
38
|
+
form.email = @email
|
39
|
+
form.password = @password
|
40
|
+
@agent.submit form
|
41
|
+
|
42
|
+
page = @agent.get "http://mixi.jp/home.pl"
|
43
|
+
page = @agent.get page.links[18].uri
|
44
|
+
form = page.forms[(@mixi_premium ? 1 : 0)]
|
45
|
+
form.diary_title = title
|
46
|
+
form.diary_body = self.class.magic_body(body)
|
47
|
+
get_image images
|
48
|
+
images[0, 3].each_with_index do |img, i|
|
49
|
+
if /darwin/ =~ RUBY_PLATFORM && /\.png$/i =~ img
|
50
|
+
imgjpg = '/tmp/mixi-vim-' << File.basename(img).sub(/\.png$/i, '.jpg')
|
51
|
+
system "sips -s format jpeg --out #{imgjpg} #{img} > /dev/null 2>&1"
|
52
|
+
img = imgjpg
|
53
|
+
end
|
54
|
+
form.file_uploads[i].file_name = img
|
55
|
+
end
|
56
|
+
page = @agent.submit form
|
57
|
+
page = @agent.submit page.forms[0]
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_latest
|
61
|
+
page = @agent.get 'http://mixi.jp/list_diary.pl'
|
62
|
+
["http://mixi.jp/" << page.links[37].uri.to_s.toutf8,
|
63
|
+
page.links[37].text.toutf8]
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.magic_body(body)
|
67
|
+
body.gsub(/^( )+/) {|i| ' '.toeuc * (i.length/2) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_image(images)
|
71
|
+
images.each_with_index do |img, i|
|
72
|
+
if img =~ %r{^http://}
|
73
|
+
path =
|
74
|
+
File.join @image_dir, i.to_s + File.extname(img)
|
75
|
+
unless File.exist? @image_dir
|
76
|
+
Dir.mkdir @image_dir
|
77
|
+
else
|
78
|
+
Dir.chdir(@image_dir) do
|
79
|
+
Dir.entries(@image_dir).
|
80
|
+
each {|f| File.unlink f if File.file? f }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
system "wget -O #{path} #{img} > /dev/null 2>&1"
|
84
|
+
if File.exist? path and !File.zero? path
|
85
|
+
images[i] = path
|
86
|
+
else
|
87
|
+
images.delete_at i
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class MixiDiary < Net::IRC::Server::Session
|
95
|
+
def server_name
|
96
|
+
"mixi"
|
97
|
+
end
|
98
|
+
|
99
|
+
def server_version
|
100
|
+
"0.0.0"
|
101
|
+
end
|
102
|
+
|
103
|
+
def main_channel
|
104
|
+
"#mixi"
|
105
|
+
end
|
106
|
+
|
107
|
+
def initialize(*args)
|
108
|
+
super
|
109
|
+
@ua = WWW::Mechanize.new
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_user(m)
|
113
|
+
super
|
114
|
+
post @prefix, JOIN, main_channel
|
115
|
+
post server_name, MODE, main_channel, "+o", @prefix.nick
|
116
|
+
|
117
|
+
@real, *@opts = @opts.name || @real.split(/\s+/)
|
118
|
+
@opts ||= []
|
119
|
+
|
120
|
+
@mixi = Mixi.new(@real, @pass)
|
121
|
+
@cont = []
|
122
|
+
end
|
123
|
+
|
124
|
+
def on_disconnected
|
125
|
+
@observer.kill rescue nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def on_privmsg(m)
|
129
|
+
super
|
130
|
+
|
131
|
+
case m[1]
|
132
|
+
when "."
|
133
|
+
title, body = *@cont
|
134
|
+
@mixi.post ">_<× < #{title}".toeuc, body.toeuc, []
|
135
|
+
@mixi.get_latest.each do |line|
|
136
|
+
post server_name, NOTICE, main_channel, line.chomp
|
137
|
+
end
|
138
|
+
when " "
|
139
|
+
@cont.clear
|
140
|
+
else
|
141
|
+
@cont << m[1]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def on_ctcp(target, message)
|
146
|
+
end
|
147
|
+
|
148
|
+
def on_whois(m)
|
149
|
+
end
|
150
|
+
|
151
|
+
def on_who(m)
|
152
|
+
end
|
153
|
+
|
154
|
+
def on_join(m)
|
155
|
+
end
|
156
|
+
|
157
|
+
def on_part(m)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
if __FILE__ == $0
|
162
|
+
require "optparse"
|
163
|
+
|
164
|
+
opts = {
|
165
|
+
:port => 16701,
|
166
|
+
:host => "localhost",
|
167
|
+
:log => nil,
|
168
|
+
:debug => false,
|
169
|
+
:foreground => false,
|
170
|
+
}
|
171
|
+
|
172
|
+
OptionParser.new do |parser|
|
173
|
+
parser.instance_eval do
|
174
|
+
self.banner = <<-EOB.gsub(/^\t+/, "")
|
175
|
+
Usage: #{$0} [opts]
|
176
|
+
|
177
|
+
EOB
|
178
|
+
|
179
|
+
separator ""
|
180
|
+
|
181
|
+
separator "Options:"
|
182
|
+
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
|
183
|
+
opts[:port] = port
|
184
|
+
end
|
185
|
+
|
186
|
+
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
|
187
|
+
opts[:host] = host
|
188
|
+
end
|
189
|
+
|
190
|
+
on("-l", "--log LOG", "log file") do |log|
|
191
|
+
opts[:log] = log
|
192
|
+
end
|
193
|
+
|
194
|
+
on("--debug", "Enable debug mode") do |debug|
|
195
|
+
opts[:log] = $stdout
|
196
|
+
opts[:debug] = true
|
197
|
+
end
|
198
|
+
|
199
|
+
on("-f", "--foreground", "run foreground") do |foreground|
|
200
|
+
opts[:log] = $stdout
|
201
|
+
opts[:foreground] = true
|
202
|
+
end
|
203
|
+
|
204
|
+
parse!(ARGV)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
opts[:logger] = Logger.new(opts[:log], "daily")
|
209
|
+
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
|
210
|
+
|
211
|
+
def daemonize(foreground=false)
|
212
|
+
trap("SIGINT") { exit! 0 }
|
213
|
+
trap("SIGTERM") { exit! 0 }
|
214
|
+
trap("SIGHUP") { exit! 0 }
|
215
|
+
return yield if $DEBUG || foreground
|
216
|
+
Process.fork do
|
217
|
+
Process.setsid
|
218
|
+
Dir.chdir "/"
|
219
|
+
File.open("/dev/null") {|f|
|
220
|
+
STDIN.reopen f
|
221
|
+
STDOUT.reopen f
|
222
|
+
STDERR.reopen f
|
223
|
+
}
|
224
|
+
yield
|
225
|
+
end
|
226
|
+
exit! 0
|
227
|
+
end
|
228
|
+
|
229
|
+
daemonize(opts[:debug] || opts[:foreground]) do
|
230
|
+
Net::IRC::Server.new(opts[:host], opts[:port], MixiDiary, opts).start
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Local Variables:
|
235
|
+
# coding: utf-8
|
236
|
+
# End:
|