nadoka 0.9.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 499bbabcf8dd4f2b5c8850550daee031996821c3106f63795972f843b22fc45b
4
- data.tar.gz: 1b151a20ec41485b4a0e368d62746591be4710c82b089e82314b9a969f499d3a
3
+ metadata.gz: 8794eb0d0b61ad6529c1f96b4b55fadd8eec443f0a36e8b284e6eaf21b9ad3c8
4
+ data.tar.gz: 43242ba299e9acc927cdce8a1049d8ca5160cb1b8ff4d7ddc478e8cdc0c3152a
5
5
  SHA512:
6
- metadata.gz: 2cc2ed86d0496fdc7bbad02524df8c50efb784e559d15b3590c3dbdb8f1125f74738a5ccb574e682efd211e47b979a111e1dea3b7a341b58e8bef6715de99ed5
7
- data.tar.gz: 71b721ff820b48fbdc39601e578cdbc795088fa6df223027a4c6e801d1eb8089de6dbb3024d3c6c2f467c5922afe0d796485b8fb50b1eb3eba58cf75ddd1763b
6
+ metadata.gz: 643b75b33ebb71275c0c1c71a437c3dee2f2b1a313bdc0c7164f6db66371197206cb3f12cef9a2d034fa0a86d7c48158ee53e7c268994bbb3aa2b29d3bd010c8
7
+ data.tar.gz: 9ea081ef58fdfdc708d0cf92d37db5fc725a0d95028fa391339dc9408648489b8dbcbd2a693c6acc06b09d39978d55428e479805f4122353c56aa4492a8f5cd9
@@ -1,26 +1,18 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2
4
- - 2.1
5
- - 2.0.0
6
- - 1.9.3
7
- - jruby-18mode # JRuby in 1.8 mode
8
- - jruby-19mode # JRuby in 1.9 mode
9
- - rbx-18mode
10
- - rbx-19mode
3
+ - 2.7
4
+ - 2.6
5
+ - 2.5
6
+ - 2.4
7
+ - 2.3
11
8
  - ruby-head
12
9
  - jruby-head
13
- - 1.8.7
14
- - ree
15
10
  matrix:
16
11
  allow_failures:
17
12
  - rvm: jruby-18mode
18
13
  - rvm: jruby-19mode
19
- - rvm: rbx-18mode
20
- - rvm: rbx-19mode
21
14
  - rvm: ruby-head
22
15
  - rvm: jruby-head
23
- - rvm: ree
24
16
  before_install:
25
17
  - gem update bundler
26
18
  script: ruby check-syntax.rb
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The MIT License (MIT)
4
+ #
5
+ # Copyright (c) 2019 Kazuhiro NISHIYAMA
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+
25
+ # see https://www.freedesktop.org/software/systemd/man/sd_notify.html
26
+ class NotifySocket
27
+ def initialize(path=ENV['NOTIFY_SOCKET'])
28
+ @notify_socket = nil
29
+ return unless path
30
+ @notify_socket = Addrinfo.unix(path, :DGRAM).connect
31
+ end
32
+
33
+ def []=(key, value)
34
+ return unless @notify_socket
35
+ @notify_socket.puts "#{key}=#{value}"
36
+ end
37
+
38
+ def ready!
39
+ self['READY'] = 1
40
+ end
41
+
42
+ def reloading!
43
+ self['RELOADING'] = 1
44
+ end
45
+
46
+ def stopping!
47
+ self['STOPPING'] = 1
48
+ end
49
+
50
+ # notify_socket.status = 'Completed 66% of file system check…'
51
+ def status=(state)
52
+ self['STATUS'] = state
53
+ end
54
+
55
+ # notify_socket.errno = 2
56
+ def errno=(error_code)
57
+ self['ERRNO'] = error_code
58
+ end
59
+
60
+ # notify_socket.buserror = 'org.freedesktop.DBus.Error.TimedOut'
61
+ def buserror=(error_code)
62
+ self['BUSERROR'] = error_code
63
+ end
64
+
65
+ # notify_socket.mainpid = 4711
66
+ def mainpid=(pid)
67
+ self['MAINPID'] = pid
68
+ end
69
+
70
+ def watchdog!
71
+ self['WATCHDOG'] = 1
72
+ end
73
+
74
+ # notify_socket.watchdog_usec = 20_000_000
75
+ def watchdog_usec=(usec)
76
+ self['WATCHDOG_USEC'] = usec
77
+ end
78
+
79
+ def extend_timeout_usec=(usec)
80
+ self['EXTEND_TIMEOUT_USEC'] = usec
81
+ end
82
+ end
data/nadoka.rb CHANGED
@@ -29,6 +29,16 @@ require 'ndk/bot'
29
29
  $stdout.sync=true
30
30
  $NDK_Debug = false
31
31
 
32
+ if ENV.key?('NOTIFY_SOCKET')
33
+ require 'lib/notify_socket'
34
+ $NDK_NOTIFY_SOCKET = NotifySocket.new
35
+ else
36
+ $NDK_NOTIFY_SOCKET = Object.new
37
+ def $NDK_NOTIFY_SOCKET.method_missing(*)
38
+ # ignore
39
+ end
40
+ end
41
+
32
42
  unless defined? Process.daemon
33
43
  def Process.daemon(nochdir = nil, noclose = nil)
34
44
  exit!(0) if fork
@@ -107,12 +117,14 @@ begin
107
117
  GC.start
108
118
  Nadoka::NDK_Server.new(rcfile).start
109
119
  rescue Nadoka::NDK_QuitProgram
110
- #
120
+ $NDK_NOTIFY_SOCKET.stopping!
111
121
  rescue Nadoka::NDK_RestartProgram
122
+ $NDK_NOTIFY_SOCKET.reloading!
112
123
  GC.start
113
124
  ObjectSpace.each_object(Socket) {|sock| sock.close}
114
125
  retry
115
126
  rescue Exception => e
127
+ $NDK_NOTIFY_SOCKET.stopping!
116
128
  open('nadoka_fatal_error', 'w'){|f|
117
129
  f.puts e
118
130
  f.puts e.backtrace.join("\n")
data/nadokarc CHANGED
@@ -68,12 +68,12 @@ class NADOKA_Config < Nadoka::NDK_ConfigBase
68
68
  :port => 6667, # default: 6667
69
69
  :pass => nil, # default: nil
70
70
  },
71
- { :host => 'irc.media.kyoto-u.ac.jp',
72
- :port => (6660 .. 6669),
73
- },
74
- { :host => 'irc.huie.hokudai.ac.jp',
71
+ # { :host => 'irc.media.kyoto-u.ac.jp',
72
+ # :port => (6660 .. 6669),
73
+ # },
74
+ # { :host => 'irc.huie.hokudai.ac.jp',
75
75
  # without :port, use 6667 as default port
76
- },
76
+ # },
77
77
  # IPv6 Sample
78
78
  # {
79
79
  # :host => 'irc6.ircnet.ne.jp',
@@ -214,7 +214,7 @@ module Nadoka
214
214
  :nostamp => nostamp,
215
215
  :ch => ch,
216
216
  }
217
-
217
+ $NDK_NOTIFY_SOCKET.status = msgobj.inspect.tr("\n", ' ')
218
218
  msgobj
219
219
  end
220
220
 
@@ -219,6 +219,7 @@ module Nadoka
219
219
  end
220
220
 
221
221
  invoke_event :invoke_bot, :server_connected
222
+ $NDK_NOTIFY_SOCKET.ready!
222
223
 
223
224
  # loop
224
225
  while q = recv_from_server
@@ -413,6 +414,7 @@ module Nadoka
413
414
  if @connected
414
415
  if @pong_recieved
415
416
  @pong_fail_count = 0
417
+ $NDK_NOTIFY_SOCKET.watchdog!
416
418
  else
417
419
  # fail
418
420
  @pong_fail_count += 1
@@ -11,7 +11,7 @@
11
11
  #
12
12
 
13
13
  module Nadoka
14
- VERSION = '0.9.2'
14
+ VERSION = '0.10.0'
15
15
  NDK_Version = VERSION.dup
16
16
  NDK_Created = Time.now
17
17
 
@@ -78,7 +78,7 @@ if __FILE__ == $0
78
78
  def initialize
79
79
  @bot_config = Hash.new
80
80
  @bot_config[:headers] = {
81
- "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:27.0) Gecko/20100101 Firefox/27.0",
81
+ "User-Agent" => "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) snap Chromium/80.0.3987.132 Chrome/80.0.3987.132 Safari/537.36",
82
82
  }
83
83
  bot_initialize
84
84
  end
@@ -256,7 +256,13 @@ class GoogleBot < Nadoka::NDK_Bot
256
256
  result = $1
257
257
  result.sub!(/<a.*/u, '')
258
258
  result.gsub!(/<.+?>/u, '')
259
+ return result
260
+ elsif /<div class="RJn8N xXEKkb ellip[^"]*">= ([^<>]+)</ =~ html
261
+ result = $1
262
+ #@logger.slog("google_calc>#{result.dump}")
263
+ return result
259
264
  else
265
+ #IO.write('g.html', html) if STDOUT.tty?
260
266
  "response error"
261
267
  end
262
268
  rescue Exception
@@ -1,6 +1,7 @@
1
- # -*-ruby; coding: utf-8 -*- vim:set ft=ruby:
1
+ # -*- coding: utf-8 -*-
2
+ # -*-ruby-*-
2
3
  #
3
- # Copyright (c) 2004-2006 SASADA Koichi <ko1 at atdot.net>
4
+ # Copyright (c) 2020 Kazuhiro NISHIYAMA
4
5
  #
5
6
  # This program is free software with ABSOLUTELY NO WARRANTY.
6
7
  # You can re-distribute and/or modify this program under
@@ -11,93 +12,152 @@
11
12
 
12
13
  == Abstract
13
14
 
14
- Answer weather information using "Livedoor Weather Web Service / LWWS".
15
-
16
- LWWS: http://weather.livedoor.com/weather_hacks/webservice
17
-
15
+ Answer weather information using "https://www.jma.go.jp/"
18
16
 
19
17
  == Usage
20
18
 
21
- tenki> [CITY]
22
-
23
- [CITY] should be city name in Kanji listed on following table.
24
- http://weather.livedoor.com/forecast/rss/primary_area.xml
19
+ tenki> [AREA]
25
20
 
21
+ [AREA] should be an area name in Kanji listed on following table.
22
+ https://www.jma.go.jp/jp/yoho/
26
23
 
27
24
  == Configuration
28
25
 
29
- BotConfig = [
30
- {
31
- :name => :TenkiBot,
32
- :ch => /nadoka/, # default: //
33
- }
34
- ]
26
+ BotConfig << {
27
+ :name => :TenkiBot,
28
+ :ch => //,
29
+ :timeout => 10,
30
+ }
35
31
 
36
32
 
37
33
  =end
38
34
 
35
+ require 'nokogiri'
39
36
  require 'open-uri'
40
- require 'pp'
41
- require 'kconv'
42
- require 'date'
43
- begin
44
- require 'json'
45
- rescue LoadError
46
- require 'rubygems'
47
- require 'json'
48
- end
37
+ require 'tmpdir'
49
38
 
50
39
  module Tenki
51
- CityIDs = {}
40
+ AREA = {}
41
+
42
+ TMPDIR = Dir.mktmpdir('tenkibot')
43
+ EN_CACHE_HTML = File.join(TMPDIR, 'en.html~')
44
+ EN_URI = URI('https://www.jma.go.jp/en/yoho/')
45
+ JP_CACHE_HTML = File.join(TMPDIR, 'jp.html~')
46
+ JP_URI = URI('https://www.jma.go.jp/jp/yoho/')
47
+
48
+ def get(uri, cache=nil)
49
+ raise Errno::ENOENT unless cache
50
+ File.read(cache)
51
+ rescue Errno::ENOENT
52
+ html = uri.read
53
+ File.write(cache, html) if cache
54
+ html
55
+ end
52
56
 
53
57
  def init_tenki
54
- open('http://weather.livedoor.com/forecast/rss/primary_area.xml') do |f|
55
- f.each_line do |line|
56
- if /city title="(.+?)" id="(\d+)"/ =~ line
57
- CityIDs[$1.toutf8] = $2
58
- end
59
- end
58
+ jp = get(JP_URI, JP_CACHE_HTML)
59
+ doc = Nokogiri::HTML(jp)
60
+ doc.xpath('//noscript//a').each do |e|
61
+ AREA[e.text] = JP_URI + e[:href]
60
62
  end
61
- end
62
63
 
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"
64
+ en = get(EN_URI, EN_CACHE_HTML)
65
+ doc = Nokogiri::HTML(en)
66
+ doc.xpath('//select[@name="elfukenlist"]/option').each do |e|
67
+ AREA[e.text] = JP_URI + "/jp/yoho/#{e[:value]}.html" if e[:value]
66
68
  end
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
+ def tenki(area)
72
+ begin
73
+ uri = AREA.fetch(area)
74
+ rescue KeyError => e
75
+ if e.respond_to?(:corrections) && !e.corrections.empty?
76
+ raise "もしかして:#{e.corrections.join(' or ')}"
77
+ else
78
+ raise "例: #{AREA.keys.sample(10).join(', ')}"
79
+ end
69
80
  end
70
81
 
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(',')})"
82
+ html = get(uri)
83
+ doc = Nokogiri::HTML(html)
84
+
85
+ forecast = doc.css('table.forecast')
86
+ tenki = {}
87
+ area = nil
88
+ forecast.xpath('./tr').each do |forecast_tr|
89
+ th_area = forecast_tr.css('.th-area')
90
+ unless th_area.empty?
91
+ # 見出し行
92
+ area = th_area.text
93
+ tenki[area] = []
94
+ next
80
95
  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
96
+ weather = {}
97
+ th_weather = forecast_tr.css('th.weather')
98
+ weather[:when] = th_weather.text.strip
99
+ weather[:title] = th_weather.xpath('img/@title').to_s.strip
100
+ forecast_tr.css('table.rain').each do |rain_table|
101
+ weather[:rain] = []
102
+ rain_table.css('tr').map do |tr|
103
+ time_range = tr.css('td[align=left]').text.strip
104
+ percent = tr.css('td[align=right]').text.strip
105
+ if /\d/ =~ percent
106
+ weather[:rain].push [time_range, percent]
107
+ end
108
+ end
109
+ end
110
+ forecast_tr.css('table.temp').each do |temp_table|
111
+ weather[:temp] = []
112
+ temp_table.css('tr').map do |tr|
113
+ city = tr.css('td.city').text.strip
114
+ min = tr.css('td.min').text.strip
115
+ max = tr.css('td.max').text.strip
116
+ if /./ =~ city
117
+ weather[:temp].push(city: city, min: min, max: max)
118
+ end
119
+ end
120
+ end
121
+ tenki[area].push weather
122
+ end
123
+
124
+ textframe = doc.css('pre.textframe')
125
+ tenki_time = textframe.children[0]&.text&.lines&.[](1)&.strip
126
+ tenki_text = textframe.children[2]&.text&.strip&.[](/.+/)&.gsub(/\s+/, ' ')
127
+
128
+ if $DEBUG
129
+ p tenki, tenki_time, tenki_text
130
+ end
131
+
132
+ texts = []
133
+ tenki.each do |area, weathers|
134
+ text = "#{area}: "
135
+ text += weathers.map do |weather|
136
+ if weather[:rain]&.any?
137
+ rain_text = weather[:rain]&.map{|time_range,percent|"#{time_range}:#{percent}"}.join(';')
138
+ rain_text = "(#{rain_text})"
139
+ end
140
+ [
141
+ "#{weather[:when]}:#{weather[:title]}",
142
+ weather[:temp]&.map{|temp|"(#{temp[:city]}:#{temp[:min]}-#{temp[:max]})"}&.join(''),
143
+ rain_text,
144
+ ].compact.join('')
145
+ end.join(', ')
146
+ texts << text
147
+ end
148
+ texts << "#{tenki_text} (#{tenki_time})"
149
+ return texts
90
150
  end
91
151
  end
92
152
 
93
153
  if __FILE__ == $0
94
154
  include Tenki
95
155
  if ARGV.empty?
96
- puts "#$0 city"
156
+ puts "#$0 area"
97
157
  else
98
158
  init_tenki
99
- ARGV.each do |city|
100
- puts tenki(city)
159
+ ARGV.each do |area|
160
+ puts tenki(area)
101
161
  end
102
162
  end
103
163
  exit
@@ -109,21 +169,25 @@ class TenkiBot < Nadoka::NDK_Bot
109
169
  def bot_initialize
110
170
  bot_init_utils
111
171
  init_tenki
112
- @nkf = @bot_config[:nkf] || "-Wj"
172
+ @nkf = @bot_config.fetch(:nkf, "-Wj")
113
173
  end
114
174
 
115
175
  def on_privmsg prefix, ch, msg
116
176
  return unless @available_channel === ch
117
177
  return if same_bot?(ch)
118
- msg = NKF.nkf('-w', msg)
178
+ msg = NKF.nkf('-w', msg) if @nkf
119
179
  if /\Atenki>/ =~ msg
120
- city = $'.strip.toutf8
180
+ area = $'.strip.toutf8
121
181
  begin
122
- result = tenki(city)
182
+ results = tenki(area)
123
183
  rescue => e
124
- result = "#{e}"
184
+ results = ["#{e}"]
185
+ end
186
+ results.each do |result|
187
+ msg = "tenki bot: #{result}".gsub(/\s+/, ' ')
188
+ msg = NKF.nkf(@nkf, msg) if @nkf
189
+ send_notice ch, msg
125
190
  end
126
- send_notice ch, NKF.nkf(@nkf, "tenki bot: #{result}".gsub(/\s+/, ' '))
127
191
  end
128
192
  end
129
193
  end
@@ -67,6 +67,12 @@ module URL2Title
67
67
  /\A169\.254\./,
68
68
  ]
69
69
 
70
+ if defined?(::Nadoka::VERSION)
71
+ DEFAULT_USER_AGENT = "Ruby/#{RUBY_VERSION} NadokaTitleBot/#{Nadoka::VERSION}"
72
+ else
73
+ DEFAULT_USER_AGENT = "Ruby/#{RUBY_VERSION}"
74
+ end
75
+
70
76
  def get_title(url, headers)
71
77
  uri = URI(url)
72
78
  info = { :uri => uri }
@@ -75,13 +81,11 @@ module URL2Title
75
81
  when *BLACKLIST_HOST
76
82
  info[:title] = "(ignored)"
77
83
  return info
78
- when /\A(?:www\.nicovideo\.jp)\z/
79
- if /\A\/watch\/(sm\d+)\z/ =~ uri.path
80
- uri = URI("http://d.hatena.ne.jp/video/niconico/#{$1}")
81
- end
82
- when /\A(?:mobile\.twitter\.com)\z/
83
- uri = URI(url.sub(/mobile\.twitter\.com/, 'twitter.com'))
84
+ when /\A(?:twitter\.com)\z/
85
+ # Twitter response depends on User-Agent
86
+ headers = headers.merge!('User-Agent' => DEFAULT_USER_AGENT)
84
87
  end
88
+ headers['User-Agent'] ||= DEFAULT_USER_AGENT
85
89
  headers = {
86
90
  :content_length_proc => proc{|x| raise Errno::EFBIG if x && x > 1048576},
87
91
  }.merge!(headers)
@@ -155,7 +159,12 @@ module URL2Title
155
159
  when /\A(?:twitter\.com)\z/
156
160
  if defined?(::Nokogiri)
157
161
  doc = Nokogiri::HTML(body, uri.to_s, 'utf-8')
158
- tweet, = doc.css('.permalink-tweet > .tweet-text')
162
+ title = doc.xpath("//meta[@property='og:description'][1]/@content").text
163
+ end
164
+ when /\A(?:mobile\.twitter\.com)\z/
165
+ if defined?(::Nokogiri)
166
+ doc = Nokogiri::HTML(body, uri.to_s, 'utf-8')
167
+ tweet, = doc.css('.main-tweet .tweet-text')
159
168
  if tweet
160
169
  tweet = tweet.inner_html
161
170
  tweet.gsub!(/<.*?>/, '')
@@ -199,10 +208,10 @@ module URL2Title
199
208
  doc ||= Nokogiri::HTML(body, uri.to_s, 'utf-8')
200
209
  canonical_uri = doc.xpath("//link[@rel='canonical'][1]/@href")
201
210
  unless canonical_uri.empty?
202
- info[:uri] = URI(shorten_url(canonical_uri.text))
211
+ info[:uri] = shorten_url(canonical_uri.text)
203
212
  end
204
213
  elsif /<link rel="canonical" href="(.+?)"/i =~ body
205
- info[:uri] = URI(shorten_url(CGI.unescapeHTML($1)))
214
+ info[:uri] = shorten_url(CGI.unescapeHTML($1))
206
215
  end
207
216
  if title
208
217
  title = CGI.unescapeHTML(title)
@@ -250,12 +259,14 @@ module URL2Title
250
259
 
251
260
  def shorten_url(url)
252
261
  case url
253
- when %r!\Ahttp://www\.amazon\.co\.jp/.*(/dp/.+)\z!
254
- "http://amazon.jp#{$1}"
262
+ when %r!\Ahttps?://www\.amazon\.co\.jp/.*(/dp/[^/]+)!
263
+ URI("https://amazon.jp#{$1}")
255
264
  else
256
265
  # default: do nothing
257
- url
266
+ URI(url)
258
267
  end
268
+ rescue URI::InvalidURIError
269
+ url
259
270
  end
260
271
 
261
272
  def cleanup(title)
@@ -280,7 +291,10 @@ if __FILE__ == $0
280
291
  }
281
292
  URL2Title.url2title(url, headers)
282
293
  rescue
283
- $!.inspect
294
+ puts $!.backtrace
295
+ info = { :uri => url }
296
+ info[:title] = $!.inspect
297
+ info
284
298
  end
285
299
  URL2Title::BLACKLIST_HOST << /\.test\z/
286
300
  if ARGV.empty?
@@ -311,7 +325,7 @@ class TitleBot < Nadoka::NDK_Bot
311
325
 
312
326
  @same_bot = @bot_config.fetch(:same_bot, /(?!)/)
313
327
  @nkf_options = @bot_config.fetch(:nkf, "--oc=CP50221 --ic=UTF-8 --fb-xml")
314
- @timeout = @bot_config.fetch(:timeout, 10)
328
+ @timeout = @bot_config.fetch(:timeout, 60)
315
329
  @skip_nkf_msg = @bot_config.fetch(:skip_nkf_msg, false)
316
330
  @fragment_size_range = @bot_config.fetch(:fragment_size_range, 5..100)
317
331
  @headers = @bot_config.fetch(:headers, {})
@@ -330,30 +344,32 @@ class TitleBot < Nadoka::NDK_Bot
330
344
 
331
345
  msg = NKF.nkf('-w', msg) unless @skip_nkf_msg
332
346
  case msg
333
- when /^\S+>/
347
+ when /^[^<>]+>/
334
348
  # ignore messages to other bots
335
349
  when /https?:/
336
350
  return if @state.channel_users(ccn(ch)).find{|x| @same_bot =~ x }
337
351
 
338
- url, = URI.extract(msg, ["http", "https"])
339
- info = Timeout.timeout(@timeout) do
340
- url2title(url, @headers)
341
- end
342
- return unless info[:title]
343
- if url != info[:uri].to_s
344
- send_notice(ch, "title bot: #{info[:title]} - #{info[:uri]}")
345
- else
346
- send_notice(ch, "title bot: #{info[:title]}")
347
- end
348
- if info[:fragment_text]
349
- # ignore when fragment_text is too long or too short
350
- if @fragment_size_range === info[:fragment_text].size
351
- send_notice(ch, "title bot:: #{info[:fragment_text]}")
352
+ msg.scan(%r<(?<l>\()?(?<url>https?://(?(<l>)(?<ascii_without_paren>[!-~&&[^()]]+|\(\g<ascii_without_paren>\))+|[!-~]+))>) do
353
+ url = $~[:url]
354
+ info = Timeout.timeout(@timeout) do
355
+ url2title(url, @headers)
356
+ end
357
+ return unless info[:title]
358
+ if url != info[:uri].to_s
359
+ send_notice(ch, "title bot: #{info[:title]} - #{info[:uri]}")
360
+ else
361
+ send_notice(ch, "title bot: #{info[:title]}")
362
+ end
363
+ if info[:fragment_text]
364
+ # ignore when fragment_text is too long or too short
365
+ if @fragment_size_range === info[:fragment_text].size
366
+ send_notice(ch, "title bot:: #{info[:fragment_text]}")
367
+ end
368
+ end
369
+ info[:errors].each do |e|
370
+ @manager.ndk_error e
371
+ send_notice(ch, "title bot error: #{e}")
352
372
  end
353
- end
354
- info[:errors].each do |e|
355
- @manager.ndk_error e
356
- send_notice(ch, "title bot error: #{e}")
357
373
  end
358
374
  end
359
375
  rescue Exception => e
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nadoka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuhiro NISHIYAMA
8
8
  - SASADA Koichi
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-09-29 00:00:00.000000000 Z
12
+ date: 2020-07-29 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Nadoka is a tool for monitoring and logging IRC conversations and responding
15
15
  to specially formatted requests. You define and customize these responses in Ruby.
@@ -29,6 +29,7 @@ files:
29
29
  - bin/nadoka
30
30
  - check-syntax.rb
31
31
  - doc/ChangeLog.old
32
+ - lib/notify_socket.rb
32
33
  - lib/rss_check.rb
33
34
  - lib/tagparts.rb
34
35
  - nadoka.gemspec
@@ -81,7 +82,7 @@ files:
81
82
  homepage: https://github.com/nadoka/nadoka
82
83
  licenses: []
83
84
  metadata: {}
84
- post_install_message:
85
+ post_install_message:
85
86
  rdoc_options: []
86
87
  require_paths:
87
88
  - lib
@@ -96,9 +97,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
97
  - !ruby/object:Gem::Version
97
98
  version: '0'
98
99
  requirements: []
99
- rubyforge_project:
100
- rubygems_version: 2.7.6
101
- signing_key:
100
+ rubygems_version: 3.0.3
101
+ signing_key:
102
102
  specification_version: 4
103
103
  summary: IRC logger, monitor and proxy program ("bot")
104
104
  test_files: []