nadoka 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,4 @@
3
3
  Gemfile.lock
4
4
  log/
5
5
  pkg/*
6
+ nadoka_fatal_error
@@ -353,11 +353,13 @@ module Nadoka
353
353
  context.cert = OpenSSL::X509::Certificate.new(File.read(@config.client_server_ssl_cert_file))
354
354
  context.key = OpenSSL::PKey::RSA.new(File.read(@config.client_server_ssl_key_file))
355
355
  @cserver = OpenSSL::SSL::SSLServer.new(@cserver, context)
356
+ @cserver.start_immediately = false
356
357
  end
357
358
 
358
359
  while true
359
360
  # wait for client connections
360
361
  Thread.start(@cserver.accept){|cc|
362
+ cc.accept if OpenSSL::SSL::SSLSocket === cc rescue NameError
361
363
  client = nil
362
364
  begin
363
365
  if !@config.acl_object || @config.acl_object.allow_socket?(cc)
@@ -11,7 +11,7 @@
11
11
  #
12
12
 
13
13
  module Nadoka
14
- VERSION = '0.8.3'
14
+ VERSION = '0.8.4'
15
15
  NDK_Version = VERSION.dup
16
16
  NDK_Created = Time.now
17
17
 
@@ -0,0 +1,141 @@
1
+ # -*-ruby; coding: utf-8 -*- vim:set ft=ruby:
2
+ # Copyright (C) 2013 Kazuhiro NISHIYAMA
3
+ #
4
+ # This program is free software with ABSOLUTELY NO WARRANTY.
5
+ # You can re-distribute and/or modify this program under
6
+ # the same terms of the Ruby's license.
7
+ #
8
+
9
+ =begin
10
+
11
+ == Configuration:
12
+
13
+ BotConfig << {
14
+ :name => :GithubIssuesBot,
15
+ :bot_name => 'gh',
16
+ :ch => '#nadoka_check',
17
+ :tm => 30, # min
18
+ #:nkf => "--oc=CP50221 --ic=UTF-8 --fb-xml",
19
+ :owner => "nadoka",
20
+ :repo => "nadoka",
21
+ }
22
+
23
+ =end
24
+ require 'open-uri'
25
+ require 'time'
26
+ begin
27
+ require 'json'
28
+ rescue LoadError
29
+ require 'rubygems'
30
+ require 'json'
31
+ end
32
+
33
+ module GithubIssues
34
+ module_function
35
+
36
+ def issues(owner, repo, since=nil, state='open')
37
+ since = since.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
38
+ uri = URI("https://api.github.com/repos/#{owner}/#{repo}/issues?since=#{since}&state=#{state}")
39
+ issues = JSON.parse(uri.read)
40
+ issues.each do |issue|
41
+ comments_uri = URI("#{issue['comments_url']}?since=#{since}")
42
+ comments = JSON.parse(comments_uri.read)
43
+ if comments.empty?
44
+ yield [:issue, issue, issue_to_s(issue)]
45
+ else
46
+ comments.each do |comment|
47
+ yield [:comment, comment, comment_to_s(comment, issue)]
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def user_to_s(user)
54
+ return unless user
55
+ "@#{user['login']} "
56
+ end
57
+
58
+ def time_to_s(time)
59
+ return unless time
60
+ time = Time.parse(time)
61
+ time.localtime.strftime("%H:%M ")
62
+ end
63
+
64
+ def issue_to_s(issue)
65
+ return unless issue
66
+ "#{issue['html_url']} [#{issue['state']}] #{time_to_s(issue['updated_at'])}#{user_to_s(issue['user'])}#{issue['title']}"
67
+ end
68
+
69
+ def comment_to_s(comment, issue=nil)
70
+ return unless comment
71
+ if issue
72
+ info = "[#{issue['state']}] "
73
+ else
74
+ end
75
+ "#{comment['html_url']} #{info}#{time_to_s(comment['updated_at'])}#{user_to_s(comment['user'])}#{comment['body']}"
76
+ end
77
+ end
78
+
79
+ if __FILE__ == $0
80
+ owner = "rubima"
81
+ repo = "rubima"
82
+ since = Time.now - 60*60*3
83
+ GithubIssues.issues(owner, repo, since) do |_, _, s|
84
+ puts s.gsub(/\s+/, ' ')
85
+ end
86
+ GithubIssues.issues(owner, repo, since, 'close') do |_, _, s|
87
+ puts s.gsub(/\s+/, ' ')
88
+ end
89
+ exit
90
+ end
91
+
92
+ require 'nkf'
93
+
94
+ class GithubIssuesBot < Nadoka::NDK_Bot
95
+ def bot_initialize
96
+ @ch = @bot_config.fetch(:ch, '#nadoka_check')
97
+ @tm = @bot_config.fetch(:tm, 30) # min
98
+ @prevtm = Time.now
99
+ @nkf_options = @bot_config.fetch(:nkf, "--oc=CP50221 --ic=UTF-8 --fb-xml")
100
+ @owner = @bot_config.fetch(:owner, "nadoka")
101
+ @repo = @bot_config.fetch(:repo, "nadoka")
102
+ end
103
+
104
+ def bot_state
105
+ nt = Time.at(@prevtm.to_i + @tm * 60)
106
+ "<#{self.class}: next check at #{nt.asctime}@#{@ch}>"
107
+ end
108
+
109
+ def send_notice(ch, msg)
110
+ msg = msg.gsub(/\s+/, ' ')
111
+ if @nkf_options
112
+ msg = NKF.nkf(@nkf_options, msg)
113
+ end
114
+ super(ch, msg)
115
+ end
116
+
117
+ def on_timer tm
118
+ check
119
+ end
120
+
121
+ def check
122
+ tm = Time.now
123
+ if tm.to_i - @tm * 60 > @prevtm.to_i
124
+ make_notice tm
125
+ end
126
+ end
127
+
128
+ def make_notice tm
129
+ since = @prevtm
130
+ @prevtm = tm
131
+ GithubIssues.issues(@owner, @repo, since) do |_, _, s|
132
+ send_notice @ch, s
133
+ end
134
+ GithubIssues.issues(@owner, @repo, since, 'close') do |_, _, s|
135
+ send_notice @ch, s
136
+ end
137
+ rescue Exception => e
138
+ send_notice(@ch, "rss bot error: #{e}")
139
+ @manager.ndk_error e
140
+ end
141
+ end
@@ -182,7 +182,16 @@ class GoogleBot < Nadoka::NDK_Bot
182
182
  result.gsub!(/<sup>(.+?)<\/sup>/u) { "^(#{$1})" }
183
183
  result.gsub!(/<.+?>/u, '')
184
184
  result.gsub!(/&\#215;/u, "\303\227")
185
- result
185
+ return result
186
+ elsif /<[^<>]+ id="cwos"[^<>]*>([^<>]+)</u =~ html
187
+ result = $1
188
+ if /<[^<>]+ id="cwles"[^<>]*>([^<>]+)</u =~ html
189
+ result = "#{$1}#{result}"
190
+ end
191
+ #@logger.slog("google_calc>#{result.dump}")
192
+ result.gsub!(/&nbsp;/u, " ")
193
+ result.gsub!(/\s+/, " ")
194
+ return result
186
195
  else
187
196
  "response error"
188
197
  end
@@ -13,18 +13,15 @@
13
13
 
14
14
  Answer weather information using "Livedoor Weather Web Service / LWWS".
15
15
 
16
- LWWS: http://weather.livedoor.com/weather_hacks/webservice.html
16
+ LWWS: http://weather.livedoor.com/weather_hacks/webservice
17
17
 
18
18
 
19
19
  == Usage
20
20
 
21
21
  tenki> [CITY]
22
- tenki:[today|tomorrow|dayaftertomorrow]> [CITY]
23
22
 
24
23
  [CITY] should be city name in Kanji listed on following table.
25
- http://weather.livedoor.com/forecast/rss/forecastmap.xml
26
-
27
- If timing is not specified, show today's information.
24
+ http://weather.livedoor.com/forecast/rss/primary_area.xml
28
25
 
29
26
 
30
27
  == Configuration
@@ -32,7 +29,7 @@ LWWS: http://weather.livedoor.com/weather_hacks/webservice.html
32
29
  BotConfig = [
33
30
  {
34
31
  :name => :TenkiBot,
35
- :ch => /nadoka/, # default: /.*/
32
+ :ch => /nadoka/, # default: //
36
33
  }
37
34
  ]
38
35
 
@@ -42,62 +39,65 @@ BotConfig = [
42
39
  require 'open-uri'
43
40
  require 'pp'
44
41
  require 'kconv'
45
- require 'rexml/document'
46
42
  require 'date'
43
+ begin
44
+ require 'json'
45
+ rescue LoadError
46
+ require 'rubygems'
47
+ require 'json'
48
+ end
47
49
 
48
50
  module Tenki
49
51
  CityIDs = {}
50
52
 
51
53
  def init_tenki
52
- open('http://weather.livedoor.com/forecast/rss/forecastmap.xml'){|f|
53
- f.each{|line|
54
+ open('http://weather.livedoor.com/forecast/rss/primary_area.xml') do |f|
55
+ f.each_line do |line|
54
56
  if /city title="(.+?)" id="(\d+)"/ =~ line
55
- CityIDs[$1.toutf8] = $2.to_i
57
+ CityIDs[$1.toutf8] = $2
56
58
  end
57
- }
58
- }
59
+ end
60
+ end
59
61
  end
60
62
 
61
- def tenki city, timing
62
- doc = open(
63
- "http://weather.livedoor.com/forecast/webservice/rest/v1?" \
64
- "city=#{CityIDs.fetch(city)}&day=#{timing}"){|f|
65
- REXML::Document.new f.read
66
- }
67
-
68
- title = doc.elements['/lwws/title/'].text.toutf8
69
- telop = doc.elements['/lwws/telop/'].text.toutf8
70
- link = doc.elements['/lwws/link/'].text.toutf8
71
- desc = doc.elements['/lwws/description/'].text.toutf8
72
- max = doc.elements['/lwws/temperature/max/celsius/'].text
73
- min = doc.elements['/lwws/temperature/min/celsius/'].text
74
- date = Date.parse(doc.elements['/lwws/forecastdate/'].text)
75
- datestr = date.strftime('%m/%d')
76
-
77
- desc.sub!(/\.\.\..*/m, '...')
78
-
79
- celsius = []
80
- celsius << "max: #{max}" if max
81
- celsius << "min: #{min}" if min
82
- unless celsius.empty?
83
- celsius = "(#{celsius.join(', ')}) "
63
+ def tenki(city)
64
+ unless city_id = CityIDs[city]
65
+ return "Unknown city. Check city title on http://weather.livedoor.com/forecast/rss/primary_area.xml"
84
66
  end
85
- "#{title} (#{datestr}): #{telop} - #{celsius}#{desc} - #{link}"
67
+ json = open("http://weather.livedoor.com/forecast/webservice/json/v1?city=#{city_id}") do |f|
68
+ JSON.parse f.read
69
+ end
70
+
71
+ tenki = "#{json['title']}: "
72
+ tenki << json['forecasts'].map do |forecast|
73
+ max = forecast['temperature']['max']
74
+ min = forecast['temperature']['min']
75
+ celsius = []
76
+ celsius << "min:#{min['celsius']}" if min
77
+ celsius << "max:#{max['celsius']}" if max
78
+ unless celsius.empty?
79
+ temperature = "(#{celsius.join(',')})"
80
+ end
81
+ "#{forecast['dateLabel']}:#{forecast['telop']}#{temperature}"
82
+ end.join(', ')
83
+ desc = json['description']
84
+ text, = desc['text'].split(/\n\n/, 2)
85
+ text.gsub!(/\n/, '')
86
+ tenki << " - #{text}(#{desc['publicTime']})"
87
+ tenki << " - #{json['link']}"
88
+
89
+ tenki
86
90
  end
87
91
  end
88
92
 
89
93
  if __FILE__ == $0
90
94
  include Tenki
91
- city = ARGV.shift
92
- timing = ARGV.shift || 'today'
93
- if city.nil?
94
- puts "#$0 city [today|tomorrow|dayaftertomorrow]"
95
+ if ARGV.empty?
96
+ puts "#$0 city"
95
97
  else
96
98
  init_tenki
97
- begin
98
- puts tenki(city, timing)
99
- rescue IndexError
100
- puts "Unknown city. Check city title on http://weather.livedoor.com/forecast/rss/forecastmap.xml"
99
+ ARGV.each do |city|
100
+ puts tenki(city)
101
101
  end
102
102
  end
103
103
  exit
@@ -116,17 +116,14 @@ class TenkiBot < Nadoka::NDK_Bot
116
116
  return unless @available_channel === ch
117
117
  return if same_bot?(ch)
118
118
  msg = NKF.nkf('-w', msg)
119
- if /\Atenki(|:(today|tomorrow|dayaftertomorrow))>(.+)/ =~ msg
120
- city = $3.strip.toutf8
121
- timing = ($2 || 'today').strip
119
+ if /\Atenki>/ =~ msg
120
+ city = $'.strip.toutf8
122
121
  begin
123
- result = tenki(city, timing).gsub(/\n/, ' ')
124
- rescue IndexError
125
- result = "Unknown city. Check city title on http://weather.livedoor.com/forecast/rss/forecastmap.xml"
122
+ result = tenki(city)
126
123
  rescue => e
127
124
  result = "#{e}"
128
125
  end
129
- send_notice ch, NKF.nkf(@nkf, "tenki bot: #{result}")
126
+ send_notice ch, NKF.nkf(@nkf, "tenki bot: #{result}".gsub(/\s+/, ' '))
130
127
  end
131
128
  end
132
129
  end
@@ -1,4 +1,4 @@
1
- # -*-ruby-*-
1
+ # -*-ruby; coding: utf-8 -*-
2
2
  # vim:set ft=ruby:
3
3
  #
4
4
  # Copyright (c) 2009, 2011, 2012 Kazuhiro NISHIYAMA
@@ -33,6 +33,12 @@ require 'open-uri'
33
33
  require 'timeout'
34
34
  require 'tmpdir'
35
35
 
36
+ begin
37
+ require 'cgi/util'
38
+ rescue LoadError
39
+ require 'cgi'
40
+ end
41
+
36
42
  begin
37
43
  require 'rubygems'
38
44
  require 'nokogiri'
@@ -92,10 +98,10 @@ module URL2Title
92
98
  when /euc-jp/i # euc-jp, x-euc-jp
93
99
  charset = "eucjp-ms"
94
100
  end
95
- if /\Autf-8\z/i =~ charset
101
+ if /\A(?:utf-8|eucjp-ms)\z/i =~ charset
96
102
  # avoid #<ArgumentError: invalid byte sequence in UTF-8>
97
103
  # or Iconv::IllegalSequence
98
- body = NKF.nkf("-Wwm0x", body)
104
+ body = NKF.nkf("-wm0x --ic=#{charset}", body)
99
105
  elsif charset
100
106
  charset.sub!(/\Ax-?/i, '')
101
107
  begin
@@ -127,20 +133,33 @@ module URL2Title
127
133
  title = tweet unless tweet.empty?
128
134
  end
129
135
  end
130
- when /\A(?:www\.so-net\.ne\.jp)\z/
136
+ else
131
137
  if %r"<title\b(?>[^<>]*)>(.*?)</title(?>[^<>]*)>"miu =~ body
132
138
  title = $1
133
139
  end
134
- if %r!<dt id="ttl">(.*?)</dt>!miu =~ body
140
+ if defined?(::Nokogiri)
141
+ doc ||= Nokogiri::HTML(body, uri.to_s, 'utf-8')
142
+ og_title = doc.xpath("//meta[@property='og:title'][1]/@content")
143
+ unless og_title.empty?
144
+ # title is escaped string when get by regexp
145
+ title = CGI.escapeHTML(og_title.text)
146
+ end
147
+ elsif /<meta property="og:title" content="(.+?)">/ =~ body
135
148
  title = $1
136
149
  end
137
- else
138
- if %r"<title\b(?>[^<>]*)>(.*?)</title(?>[^<>]*)>"miu =~ body
150
+ if defined?(::Nokogiri)
151
+ doc ||= Nokogiri::HTML(body, uri.to_s, 'utf-8')
152
+ og_title = doc.xpath("//meta[@property='og:title']/@content")
153
+ unless og_title.empty?
154
+ # title is escaped string when get by regexp
155
+ title = CGI.escapeHTML(og_title.text)
156
+ end
157
+ elsif /<meta property="og:title" content="(.+?)">/ =~ body
139
158
  title = $1
140
159
  end
141
160
  if uri.fragment && defined?(::Nokogiri)
142
161
  begin
143
- doc = Nokogiri::HTML(body, uri.to_s, 'utf-8')
162
+ doc ||= Nokogiri::HTML(body, uri.to_s, 'utf-8')
144
163
  xpath = "//*[@id='#{uri.fragment}' or @name='#{uri.fragment}']"
145
164
  fragment_element = doc.xpath(xpath)
146
165
  # tDiary style
@@ -153,6 +172,19 @@ module URL2Title
153
172
  end
154
173
  end
155
174
  end
175
+ if defined?(::Nokogiri)
176
+ doc ||= Nokogiri::HTML(body, uri.to_s, 'utf-8')
177
+ canonical_uri = doc.xpath("//link[@rel='canonical'][1]/@href")
178
+ unless canonical_uri.empty?
179
+ info[:uri] = URI(canonical_uri.text)
180
+ end
181
+ elsif /<link rel="canonical" href="(.+?)"/i =~ body
182
+ info[:uri] = URI(CGI.unescapeHTML($1))
183
+ end
184
+ if title
185
+ title = CGI.unescapeHTML(title)
186
+ title.gsub!(/\xE3\x80\x9C/u, "\xEF\xBD\x9E") # WAVE DASH -> FULLWIDTH TILDE
187
+ end
156
188
  info[:title] = title || body
157
189
  return info
158
190
  when /\Aimage\//
@@ -211,7 +243,7 @@ if __FILE__ == $0
211
243
  ARGV.each do |url|
212
244
  info = u2t(url)
213
245
  p info
214
- puts url
246
+ puts info[:uri]
215
247
  puts info[:title]
216
248
  end
217
249
  end
@@ -232,7 +264,7 @@ class TitleBot < Nadoka::NDK_Bot
232
264
  end
233
265
 
234
266
  @same_bot = @bot_config.fetch(:same_bot, /(?!)/)
235
- @nkf_options = @bot_config.fetch(:nkf, "--oc=CP50221 --ic=UTF-8 --numchar-input --fb-xml")
267
+ @nkf_options = @bot_config.fetch(:nkf, "--oc=CP50221 --ic=UTF-8 --fb-xml")
236
268
  @timeout = @bot_config.fetch(:timeout, 10)
237
269
  @fragment_size_range = @bot_config.fetch(:fragment_size_range, 5..100)
238
270
  @headers = @bot_config.fetch(:headers, {})
@@ -95,39 +95,50 @@ class TwitterBot < Nadoka::NDK_Bot
95
95
  end
96
96
 
97
97
  @streamer = Thread.new do
98
- UserStream.client.user do |status|
99
- case # https://dev.twitter.com/docs/streaming-apis/messages
100
- when status[:delete]
101
- when status[:scrub_geo]
102
- when status[:limit]
103
- when status[:status_withheld]
104
- when status[:user_withheld]
105
- when status[:friends]
106
- when status[:event]
107
- when status[:for_user]
108
- when status[:control]
109
- when status[:warning]
110
- screen_name = status[:code]
111
- time = Time.parse(status[:created_at])
112
- send_notice @ch, "#{time.strftime('%H:%M')} #{screen_name}: #{status.text}"
113
- else
98
+ loop do
99
+ UserStream.client.user do |status|
114
100
  begin
115
- screen_name = status[:user][:screen_name]
116
- if status[:retweeted_status]
117
- status = status[:retweeted_status]
118
- screen_name << ":"
119
- screen_name << status[:user][:screen_name]
101
+ case # https://dev.twitter.com/docs/streaming-apis/messages
102
+ #when status[:delete]
103
+ #when status[:scrub_geo]
104
+ when status[:limit]
105
+ when status[:status_withheld]
106
+ when status[:user_withheld]
107
+ when status[:friends]
108
+ when status[:event]
109
+ when status[:for_user]
110
+ when status[:control]
111
+ when status[:warning]
112
+ screen_name = status[:code]
113
+ time = Time.parse(status[:created_at])
114
+ send_notice @ch, "#{time.strftime('%H:%M')} #{screen_name}: #{status.text}"
115
+ when status[:user]
116
+ screen_name = status[:user][:screen_name]
117
+ if status[:retweeted_status]
118
+ status = status[:retweeted_status]
119
+ screen_name << ":"
120
+ screen_name << status[:user][:screen_name]
121
+ end
122
+ time = Time.parse(status[:created_at])
123
+ text = status.text
124
+ text.tr!("\r\n", ' ')
125
+ text.gsub!(/&lt;/, '<')
126
+ text.gsub!(/&gt;/, '>')
127
+ text.gsub!(/&quot;/, '"')
128
+ text = NKF.nkf('--numchar-input --ic=UTF-8 --oc=' + @nkf_encoding, text) if @nkf_encoding
129
+ text.gsub!(/&amp;/, '&')
130
+ send_notice @ch, "#{time.strftime('%H:%M')} #{screen_name}: #{text}"
131
+ else
132
+ send_notice @ch, status.inspect
120
133
  end
121
- time = Time.parse(status[:created_at])
122
- text = status.text.tr("\r\n", ' ')
123
- text = NKF.nkf('--ic=UTF-8 --oc=' + @nkf_encoding, text) if @nkf_encoding
124
- send_notice @ch, "#{time.strftime('%H:%M')} #{screen_name}: #{text}"
125
134
  rescue => e
135
+ @logger.slog e.inspect
126
136
  slog e.backtrace
127
137
  slog e.inspect
128
138
  slog status.inspect
129
139
  end
130
140
  end
141
+ @logger.slog "user stream finished...and restart"
131
142
  end
132
143
  end
133
144
  end
@@ -300,7 +300,7 @@ module RICE
300
300
 
301
301
  rescue Closed
302
302
  begin
303
- @main_th.run if @main_th.alive?
303
+ @main_th.run if alive?
304
304
  rescue Closed
305
305
  end
306
306
  retry
@@ -317,7 +317,7 @@ module RICE
317
317
  def close(restart = false)
318
318
  begin
319
319
  unless restart
320
- @main_th.exit if @main_th.alive?
320
+ @main_th.exit if alive?
321
321
  @read_th.exit if @read_th.alive?
322
322
  end
323
323
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nadoka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.3
4
+ version: 0.8.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-09 00:00:00.000000000 Z
13
+ date: 2013-04-29 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: Nadoka is a tool for monitoring and logging IRC conversations and responding
16
16
  to specially formatted requests. You define and customize these responses in Ruby.
@@ -52,6 +52,7 @@ files:
52
52
  - plugins/drbcl.rb
53
53
  - plugins/drbot.nb
54
54
  - plugins/evalbot.nb
55
+ - plugins/githubissuesbot.nb
55
56
  - plugins/gonzuibot.nb
56
57
  - plugins/googlebot.nb
57
58
  - plugins/identifynickserv.nb