net-irc 0.0.3 → 0.0.4
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.
- 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:
|