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.
- checksums.yaml +7 -0
- data/AUTHORS.txt +33 -0
- data/ChangeLog +95 -0
- data/README +91 -0
- data/Rakefile +69 -0
- data/examples/2ch.rb +225 -0
- data/examples/2ig.rb +267 -0
- data/examples/client.rb +23 -0
- data/examples/echo_bot.rb +31 -0
- data/examples/echo_bot_celluloid.rb +33 -0
- data/examples/gig.rb +192 -0
- data/examples/gmail.rb +202 -0
- data/examples/gtig.rb +420 -0
- data/examples/hatena-star-stream.rb +270 -0
- data/examples/hcig.rb +285 -0
- data/examples/hig.rb +771 -0
- data/examples/iig.rb +819 -0
- data/examples/ircd.rb +358 -0
- data/examples/lig.rb +551 -0
- data/examples/lingr.rb +327 -0
- data/examples/mixi.rb +252 -0
- data/examples/sig.rb +188 -0
- data/examples/tig.rb +2712 -0
- data/lib/net/irc/client/channel_manager.rb +144 -0
- data/lib/net/irc/client.rb +117 -0
- data/lib/net/irc/constants.rb +214 -0
- data/lib/net/irc/message/modeparser.rb +85 -0
- data/lib/net/irc/message/serverconfig.rb +30 -0
- data/lib/net/irc/message.rb +109 -0
- data/lib/net/irc/pattern.rb +68 -0
- data/lib/net/irc/server.rb +186 -0
- data/lib/net/irc.rb +77 -0
- data/spec/channel_manager_spec.rb +184 -0
- data/spec/modeparser_spec.rb +165 -0
- data/spec/net-irc_spec.rb +337 -0
- data/spec/spec.opts +1 -0
- metadata +91 -0
data/examples/gmail.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim:encoding=UTF-8:
|
3
|
+
|
4
|
+
$LOAD_PATH << "lib"
|
5
|
+
$LOAD_PATH << "../lib"
|
6
|
+
|
7
|
+
$KCODE = "u" unless defined? ::Encoding
|
8
|
+
|
9
|
+
require "rubygems"
|
10
|
+
require "net/irc"
|
11
|
+
require "sdbm"
|
12
|
+
require "tmpdir"
|
13
|
+
require "uri"
|
14
|
+
require "mechanize"
|
15
|
+
require "rexml/document"
|
16
|
+
|
17
|
+
class GmailNotifier < Net::IRC::Server::Session
|
18
|
+
def server_name
|
19
|
+
"gmail"
|
20
|
+
end
|
21
|
+
|
22
|
+
def server_version
|
23
|
+
"0.0.0"
|
24
|
+
end
|
25
|
+
|
26
|
+
def main_channel
|
27
|
+
"#gmail"
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(*args)
|
31
|
+
super
|
32
|
+
@agent = WWW::Mechanize.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_user(m)
|
36
|
+
super
|
37
|
+
post @prefix, JOIN, main_channel
|
38
|
+
post server_name, MODE, main_channel, "+o", @prefix.nick
|
39
|
+
|
40
|
+
@real, *@opts = @opts.name || @real.split(/\s+/)
|
41
|
+
@opts ||= []
|
42
|
+
|
43
|
+
start_observer
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_disconnected
|
47
|
+
@observer.kill rescue nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_privmsg(m)
|
51
|
+
super
|
52
|
+
case m[1]
|
53
|
+
when 'list'
|
54
|
+
check_mail
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_ctcp(target, message)
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_whois(m)
|
62
|
+
end
|
63
|
+
|
64
|
+
def on_who(m)
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_join(m)
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_part(m)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def start_observer
|
75
|
+
@observer = Thread.start do
|
76
|
+
Thread.abort_on_exception = true
|
77
|
+
loop do
|
78
|
+
begin
|
79
|
+
@agent.auth(@real, @pass)
|
80
|
+
page = @agent.get(URI.parse("https://gmail.google.com/gmail/feed/atom"))
|
81
|
+
feed = REXML::Document.new page.body
|
82
|
+
db = SDBM.open("#{Dir.tmpdir}/#{@real}.db", 0666)
|
83
|
+
feed.get_elements('/feed/entry').reverse.each do |item|
|
84
|
+
id = item.text('id')
|
85
|
+
if db.include?(id)
|
86
|
+
next
|
87
|
+
else
|
88
|
+
db[id] = "1"
|
89
|
+
end
|
90
|
+
post server_name, PRIVMSG, main_channel, "Subject: #{item.text('title')} From: #{item.text('author/name')}"
|
91
|
+
post server_name, PRIVMSG, main_channel, "#{item.text('summary')}"
|
92
|
+
end
|
93
|
+
rescue Exception => e
|
94
|
+
@log.error e.inspect
|
95
|
+
ensure
|
96
|
+
db.close rescue nil
|
97
|
+
end
|
98
|
+
sleep 60 * 5
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_mail
|
104
|
+
begin
|
105
|
+
@agent.auth(@real, @pass)
|
106
|
+
page = @agent.get(URI.parse("https://gmail.google.com/gmail/feed/atom"))
|
107
|
+
feed = REXML::Document.new page.body
|
108
|
+
db = SDBM.open("#{Dir.tmpdir}/#{@real}.db", 0666)
|
109
|
+
feed.get_elements('/feed/entry').reverse.each do |item|
|
110
|
+
id = item.text('id')
|
111
|
+
if db.include?(id)
|
112
|
+
#next
|
113
|
+
else
|
114
|
+
db[id] = "1"
|
115
|
+
end
|
116
|
+
post server_name, PRIVMSG, main_channel, "Subject: #{item.text('title')} From: #{item.text('author/name')}"
|
117
|
+
post server_name, PRIVMSG, main_channel, "#{item.text('summary')}"
|
118
|
+
end
|
119
|
+
rescue Exception => e
|
120
|
+
@log.error e.inspect
|
121
|
+
ensure
|
122
|
+
db.close rescue nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
if __FILE__ == $0
|
128
|
+
require "optparse"
|
129
|
+
|
130
|
+
opts = {
|
131
|
+
:port => 16800,
|
132
|
+
:host => "localhost",
|
133
|
+
:log => nil,
|
134
|
+
:debug => false,
|
135
|
+
:foreground => false,
|
136
|
+
}
|
137
|
+
|
138
|
+
OptionParser.new do |parser|
|
139
|
+
parser.instance_eval do
|
140
|
+
self.banner = <<-EOB.gsub(/^\t+/, "")
|
141
|
+
Usage: #{$0} [opts]
|
142
|
+
|
143
|
+
EOB
|
144
|
+
|
145
|
+
separator ""
|
146
|
+
|
147
|
+
separator "Options:"
|
148
|
+
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
|
149
|
+
opts[:port] = port
|
150
|
+
end
|
151
|
+
|
152
|
+
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
|
153
|
+
opts[:host] = host
|
154
|
+
end
|
155
|
+
|
156
|
+
on("-l", "--log LOG", "log file") do |log|
|
157
|
+
opts[:log] = log
|
158
|
+
end
|
159
|
+
|
160
|
+
on("--debug", "Enable debug mode") do |debug|
|
161
|
+
opts[:log] = $stdout
|
162
|
+
opts[:debug] = true
|
163
|
+
end
|
164
|
+
|
165
|
+
on("-f", "--foreground", "run foreground") do |foreground|
|
166
|
+
opts[:log] = $stdout
|
167
|
+
opts[:foreground] = true
|
168
|
+
end
|
169
|
+
|
170
|
+
parse!(ARGV)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
opts[:logger] = Logger.new(opts[:log], "daily")
|
175
|
+
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
|
176
|
+
|
177
|
+
def daemonize(foreground=false)
|
178
|
+
trap("SIGINT") { exit! 0 }
|
179
|
+
trap("SIGTERM") { exit! 0 }
|
180
|
+
trap("SIGHUP") { exit! 0 }
|
181
|
+
return yield if $DEBUG || foreground
|
182
|
+
Process.fork do
|
183
|
+
Process.setsid
|
184
|
+
Dir.chdir "/"
|
185
|
+
File.open("/dev/null") {|f|
|
186
|
+
STDIN.reopen f
|
187
|
+
STDOUT.reopen f
|
188
|
+
STDERR.reopen f
|
189
|
+
}
|
190
|
+
yield
|
191
|
+
end
|
192
|
+
exit! 0
|
193
|
+
end
|
194
|
+
|
195
|
+
daemonize(opts[:debug] || opts[:foreground]) do
|
196
|
+
Net::IRC::Server.new(opts[:host], opts[:port], GmailNotifier, opts).start
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Local Variables:
|
201
|
+
# coding: utf-8
|
202
|
+
# End:
|
data/examples/gtig.rb
ADDED
@@ -0,0 +1,420 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
=begin
|
3
|
+
|
4
|
+
タスク:
|
5
|
+
10:30 21:00 にタスクを表示
|
6
|
+
|
7
|
+
カレンダー:
|
8
|
+
予定の10分前に予定を表示
|
9
|
+
07:30 今日のタスクを表示
|
10
|
+
23:30 明日のタスクを表示
|
11
|
+
|
12
|
+
=end
|
13
|
+
|
14
|
+
$LOAD_PATH << "lib"
|
15
|
+
$LOAD_PATH << "../lib"
|
16
|
+
|
17
|
+
$KCODE = "u" unless defined? ::Encoding
|
18
|
+
|
19
|
+
require "pp"
|
20
|
+
require "rubygems"
|
21
|
+
require "net/irc"
|
22
|
+
require "logger"
|
23
|
+
require "pathname"
|
24
|
+
require "yaml"
|
25
|
+
require 'pit'
|
26
|
+
require 'google/api_client'
|
27
|
+
|
28
|
+
class GoogleTasksIrcGateway < Net::IRC::Server::Session
|
29
|
+
CONFIG = Pit.get('google tasks', :require => {
|
30
|
+
'CLIENT_ID' => 'OAuth Client ID',
|
31
|
+
'CLIENT_SECRET' => 'OAuth Client Secret',
|
32
|
+
})
|
33
|
+
|
34
|
+
CONFIG_DIR = Pathname.new("~/.gtig.rb").expand_path
|
35
|
+
|
36
|
+
@@ctcp_action_commands = []
|
37
|
+
|
38
|
+
class << self
|
39
|
+
def ctcp_action(*commands, &block)
|
40
|
+
name = "+ctcp_action_#{commands.inspect}"
|
41
|
+
define_method(name, block)
|
42
|
+
commands.each do |command|
|
43
|
+
@@ctcp_action_commands << [command, name]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def server_name
|
49
|
+
"tasks"
|
50
|
+
end
|
51
|
+
|
52
|
+
def server_version
|
53
|
+
"0.0.0"
|
54
|
+
end
|
55
|
+
|
56
|
+
def main_channel
|
57
|
+
"#tasks"
|
58
|
+
end
|
59
|
+
|
60
|
+
def config(&block)
|
61
|
+
# merge local (user) config and global config
|
62
|
+
merged = {}
|
63
|
+
global = {}
|
64
|
+
local = {}
|
65
|
+
|
66
|
+
global_config = CONFIG_DIR + "config"
|
67
|
+
begin
|
68
|
+
global = eval(global_config.read) || {}
|
69
|
+
rescue Errno::ENOENT
|
70
|
+
end
|
71
|
+
|
72
|
+
local_config = @real ? CONFIG_DIR + "#{@real}/config" : nil
|
73
|
+
if local_config
|
74
|
+
begin
|
75
|
+
local = eval(local_config.read) || {}
|
76
|
+
rescue Errno::ENOENT
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
merged.update(global)
|
81
|
+
merged.update(local)
|
82
|
+
|
83
|
+
if block
|
84
|
+
merged.instance_eval(&block)
|
85
|
+
merged.each do |k, v|
|
86
|
+
unless global[k] == v
|
87
|
+
local[k] = v
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if local_config
|
92
|
+
local_config.parent.mkpath
|
93
|
+
local_config.open('w') do |f|
|
94
|
+
PP.pp(local, f)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
merged
|
100
|
+
end
|
101
|
+
|
102
|
+
COLORS = {
|
103
|
+
"navy" => 2,
|
104
|
+
"aqua" => 11,
|
105
|
+
"teal" => 10,
|
106
|
+
"blue" => 2,
|
107
|
+
"olive" => 7,
|
108
|
+
"purple" => 6,
|
109
|
+
"lightcyan" => 11,
|
110
|
+
"grey" => 14,
|
111
|
+
"royal" => 12,
|
112
|
+
"white" => 0,
|
113
|
+
"red" => 4,
|
114
|
+
"orange" => 7,
|
115
|
+
"lightpurple" => 13,
|
116
|
+
"pink" => 13,
|
117
|
+
"yellow" => 8,
|
118
|
+
"black" => 1,
|
119
|
+
"cyan" => 11,
|
120
|
+
"maroon" => 5,
|
121
|
+
"silver" => 15,
|
122
|
+
"lime" => 9,
|
123
|
+
"lightgreen" => 9,
|
124
|
+
"fuchsia" => 13,
|
125
|
+
"lightblue" => 12,
|
126
|
+
"lightgrey" => 15,
|
127
|
+
"green" => 3,
|
128
|
+
"brown" => 5
|
129
|
+
}
|
130
|
+
|
131
|
+
def color(color, string)
|
132
|
+
"\003%.2d%s\017" % [COLORS[color.to_s], string]
|
133
|
+
end
|
134
|
+
|
135
|
+
def initialize(*args)
|
136
|
+
super
|
137
|
+
@channels = {}
|
138
|
+
end
|
139
|
+
|
140
|
+
def on_disconnected
|
141
|
+
end
|
142
|
+
|
143
|
+
def on_user(m)
|
144
|
+
super
|
145
|
+
@real, *@opts = @real.split(/\s+/)
|
146
|
+
@opts ||= []
|
147
|
+
post @prefix, JOIN, main_channel
|
148
|
+
init_channel(main_channel)
|
149
|
+
end
|
150
|
+
|
151
|
+
def on_join(m)
|
152
|
+
channels = m.params[0].split(/ *, */)
|
153
|
+
channels.each do |channel|
|
154
|
+
channel = channel.split(" ", 2).first
|
155
|
+
init_channel(channel)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def on_part(m)
|
160
|
+
channel = m.params[0]
|
161
|
+
destroy_channel(channel)
|
162
|
+
end
|
163
|
+
|
164
|
+
def on_privmsg(m)
|
165
|
+
target, mesg = *m.params
|
166
|
+
m.ctcps.each {|ctcp| on_ctcp(target, ctcp) } if m.ctcp?
|
167
|
+
return if mesg.empty?
|
168
|
+
return on_ctcp_action(target, mesg) if mesg.sub!(/\A +/, "")
|
169
|
+
end
|
170
|
+
|
171
|
+
def on_ctcp(target, mesg)
|
172
|
+
type, mesg = mesg.split(" ", 2)
|
173
|
+
method = "on_ctcp_#{type.downcase}".to_sym
|
174
|
+
send(method, target, mesg) if respond_to? method, true
|
175
|
+
end
|
176
|
+
|
177
|
+
def on_ctcp_action(target, mesg)
|
178
|
+
command, *args = mesg.split(" ")
|
179
|
+
if command
|
180
|
+
command.downcase!
|
181
|
+
|
182
|
+
@@ctcp_action_commands.each do |define, name|
|
183
|
+
if define === command
|
184
|
+
send(name, target, mesg, Regexp.last_match || command, args)
|
185
|
+
break
|
186
|
+
end
|
187
|
+
end
|
188
|
+
else
|
189
|
+
commands = @@ctcp_action_commands.map {|define, name|
|
190
|
+
define
|
191
|
+
}.select {|define|
|
192
|
+
define.is_a? String
|
193
|
+
}
|
194
|
+
|
195
|
+
commands.each_slice(5) do |c|
|
196
|
+
post server_name, NOTICE, target, c.join(" ")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
rescue Exception => e
|
201
|
+
post server_name, NOTICE, target, e.inspect
|
202
|
+
e.backtrace.each do |l|
|
203
|
+
@log.error "\t#{l}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
ctcp_action "oauth" do |target, mesg, command, args|
|
208
|
+
if args.length == 1
|
209
|
+
auth_channel(target, args.first)
|
210
|
+
else
|
211
|
+
init_channel(target)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def init_channel(channel)
|
216
|
+
destroy_channel(channel) if @channels[channel]
|
217
|
+
|
218
|
+
client = Google::APIClient.new
|
219
|
+
client.authorization.update_token!(Marshal.load(config["token_#{channel}"])) if config["token_#{channel}"]
|
220
|
+
client.authorization.client_id = CONFIG['CLIENT_ID']
|
221
|
+
client.authorization.client_secret = CONFIG['CLIENT_SECRET']
|
222
|
+
client.authorization.scope = 'https://www.googleapis.com/auth/tasks.readonly https://www.googleapis.com/auth/calendar.readonly'
|
223
|
+
client.authorization.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
|
224
|
+
|
225
|
+
@channels[channel] = {
|
226
|
+
:client => client,
|
227
|
+
}
|
228
|
+
|
229
|
+
if client.authorization.access_token
|
230
|
+
auth_channel(channel)
|
231
|
+
else
|
232
|
+
post server_name, NOTICE, channel, 'Access following URL: %s' % client.authorization.authorization_uri.to_s
|
233
|
+
post server_name, NOTICE, channel, 'and send /me oauth <CODE>'
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def auth_channel(channel, code=nil)
|
238
|
+
post server_name, NOTICE, channel, 'Authenticating...'
|
239
|
+
client = @channels[channel][:client]
|
240
|
+
|
241
|
+
client.authorization.code = code if code
|
242
|
+
client.authorization.fetch_access_token!
|
243
|
+
|
244
|
+
post server_name, NOTICE, channel, 'Authenticating... done'
|
245
|
+
|
246
|
+
config {
|
247
|
+
self["token_#{channel}"] = Marshal.dump({
|
248
|
+
'refresh_token' => client.authorization.refresh_token,
|
249
|
+
'access_token' => client.authorization.access_token,
|
250
|
+
'expires_in' => client.authorization.expires_in
|
251
|
+
})
|
252
|
+
}
|
253
|
+
|
254
|
+
@channels[channel] = {
|
255
|
+
:client => client,
|
256
|
+
:tasks => client.discovered_api('tasks'),
|
257
|
+
:calendar => client.discovered_api('calendar', 'v3'),
|
258
|
+
}
|
259
|
+
|
260
|
+
observe_channel(channel)
|
261
|
+
end
|
262
|
+
|
263
|
+
def observe_channel(channel)
|
264
|
+
check_tasks
|
265
|
+
check_calendars
|
266
|
+
|
267
|
+
@channels[channel][:thread] = Thread.start(channel) do |channel|
|
268
|
+
loop do
|
269
|
+
@log.info :loop
|
270
|
+
|
271
|
+
now = Time.now.strftime("%H:%M")
|
272
|
+
|
273
|
+
case now
|
274
|
+
when "07:30"
|
275
|
+
check_calendars(60 * 60 * 24)
|
276
|
+
when "10:30"
|
277
|
+
check_tasks
|
278
|
+
check_calendars
|
279
|
+
when "21:00"
|
280
|
+
check_tasks
|
281
|
+
check_calendars
|
282
|
+
when "23:30"
|
283
|
+
check_calendars(60 * 60 * 24)
|
284
|
+
when /..:.0/
|
285
|
+
check_calendars
|
286
|
+
end
|
287
|
+
|
288
|
+
sleep 60
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def destroy_channel(channel)
|
294
|
+
@channels[channel][:thread].kill rescue nil
|
295
|
+
@channels.delete(channel)
|
296
|
+
end
|
297
|
+
|
298
|
+
def check_tasks
|
299
|
+
@channels.each do |channel, info|
|
300
|
+
@log.info "check_tasks[#{channel}]"
|
301
|
+
client = info[:client]
|
302
|
+
client.authorization.fetch_access_token! if client.authorization.refresh_token && client.authorization.expired?
|
303
|
+
|
304
|
+
result = client.execute( info[:tasks].tasks.list, { 'tasklist' => '@default' })
|
305
|
+
|
306
|
+
now = Time.now
|
307
|
+
result.data.items.sort_by {|i| i.due ? -i.due.to_i : -(1/0.0) }.each do |task|
|
308
|
+
next if task.status == 'completed'
|
309
|
+
if task.due
|
310
|
+
diff = task.due - now
|
311
|
+
due = diff < 0 ? color(:red, "overdue") : color(:green, "#{(diff / (60 * 60 * 24)).floor} days")
|
312
|
+
post server_name, NOTICE, channel, "%s %s" % [due, task.title]
|
313
|
+
else
|
314
|
+
post server_name, NOTICE, channel, task.title
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def check_calendars(range=60*10)
|
321
|
+
@channels.each do |channel, info|
|
322
|
+
@log.info "check_calendars[#{channel}]"
|
323
|
+
client = info[:client]
|
324
|
+
client.authorization.fetch_access_token! if client.authorization.refresh_token && client.authorization.expired?
|
325
|
+
|
326
|
+
result = client.execute( info[:calendar].calendar_list.list, {})
|
327
|
+
calendars = result.data.items.select {|i| i.accessRole == 'owner' }
|
328
|
+
|
329
|
+
calendars.each do |calendar|
|
330
|
+
calendar_color = COLORS.invert[ calendar.color_id.to_i % COLORS.size ]
|
331
|
+
result = client.execute( info[:calendar].events.list, {
|
332
|
+
'calendarId' => calendar.id,
|
333
|
+
'maxResults' => 100,
|
334
|
+
'timeMin' => Time.now.xmlschema,
|
335
|
+
'timeMax' => (Time.now + range).xmlschema,
|
336
|
+
'singleEvents' => 'true',
|
337
|
+
'orderBy' => 'startTime',
|
338
|
+
'fields' => 'items(description,end,etag,iCalUID,id,kind,location,originalStartTime,reminders,start,status,summary,transparency,updated),nextPageToken,summary',
|
339
|
+
})
|
340
|
+
result.data.items.each do |item|
|
341
|
+
post server_name, NOTICE, channel, "%s: %s~ %s" % [ color(calendar_color, calendar.summary), item.start.date || item.start.date_time.strftime('%m/%d %H:%M'), item.summary ]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
if __FILE__ == $0
|
349
|
+
require "optparse"
|
350
|
+
|
351
|
+
opts = {
|
352
|
+
:port => 16720,
|
353
|
+
:host => "localhost",
|
354
|
+
:log => $stdout,
|
355
|
+
:debug => true,
|
356
|
+
:foreground => true,
|
357
|
+
}
|
358
|
+
|
359
|
+
OptionParser.new do |parser|
|
360
|
+
parser.instance_eval do
|
361
|
+
self.banner = <<-EOB.gsub(/^\t+/, "")
|
362
|
+
Usage: #{$0} [opts]
|
363
|
+
|
364
|
+
EOB
|
365
|
+
|
366
|
+
separator ""
|
367
|
+
|
368
|
+
separator "Options:"
|
369
|
+
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
|
370
|
+
opts[:port] = port
|
371
|
+
end
|
372
|
+
|
373
|
+
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
|
374
|
+
opts[:host] = host
|
375
|
+
end
|
376
|
+
|
377
|
+
on("-l", "--log LOG", "log file") do |log|
|
378
|
+
opts[:log] = log
|
379
|
+
end
|
380
|
+
|
381
|
+
on("--debug", "Enable debug mode") do |debug|
|
382
|
+
opts[:log] = $stdout
|
383
|
+
opts[:debug] = true
|
384
|
+
end
|
385
|
+
|
386
|
+
on("-f", "--foreground", "run foreground") do |foreground|
|
387
|
+
opts[:log] = $stdout
|
388
|
+
opts[:foreground] = true
|
389
|
+
end
|
390
|
+
|
391
|
+
parse!(ARGV)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
opts[:logger] = Logger.new(opts[:log], "daily")
|
396
|
+
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
|
397
|
+
|
398
|
+
def daemonize(foreground=false)
|
399
|
+
trap("SIGINT") { exit! 0 }
|
400
|
+
trap("SIGTERM") { exit! 0 }
|
401
|
+
trap("SIGHUP") { exit! 0 }
|
402
|
+
return yield if $DEBUG || foreground
|
403
|
+
Process.fork do
|
404
|
+
Process.setsid
|
405
|
+
Dir.chdir "/"
|
406
|
+
File.open("/dev/null") {|f|
|
407
|
+
STDIN.reopen f
|
408
|
+
STDOUT.reopen f
|
409
|
+
STDERR.reopen f
|
410
|
+
}
|
411
|
+
yield
|
412
|
+
end
|
413
|
+
exit! 0
|
414
|
+
end
|
415
|
+
|
416
|
+
daemonize(opts[:debug] || opts[:foreground]) do
|
417
|
+
Net::IRC::Server.new(opts[:host], opts[:port], GoogleTasksIrcGateway, opts).start
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|