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
|
+
|