narou 2.5.2 → 2.6.0
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 +4 -4
- data/.gitignore +1 -0
- data/ChangeLog.md +54 -0
- data/LICENSE.txt +26 -0
- data/README.md +70 -84
- data/lib/command/alias.rb +7 -0
- data/lib/command/convert.rb +40 -61
- data/lib/command/download.rb +9 -1
- data/lib/command/mail.rb +24 -13
- data/lib/command/send.rb +18 -2
- data/lib/command/setting.rb +152 -34
- data/lib/command/tag.rb +5 -0
- data/lib/command/update.rb +137 -4
- data/lib/converterbase.rb +12 -6
- data/lib/database.rb +1 -1
- data/lib/device/ibooks.rb +6 -5
- data/lib/downloader.rb +85 -83
- data/lib/helper.rb +3 -2
- data/lib/html.rb +3 -0
- data/lib/logger.rb +20 -12
- data/lib/narou.rb +16 -0
- data/lib/novelconverter.rb +209 -75
- data/lib/novelinfo.rb +1 -1
- data/lib/novelsetting.rb +53 -29
- data/lib/template.rb +4 -3
- data/lib/version.rb +1 -1
- data/lib/web/appserver.rb +46 -32
- data/lib/web/public/resources/common.ui.js +2 -4
- data/lib/web/public/resources/default-style.css +37 -0
- data/lib/web/public/resources/narou.library.js +48 -57
- data/lib/web/public/resources/narou.ui.js +71 -39
- data/lib/web/public/theme/Cerulean/bootstrap.min.css +11 -0
- data/lib/web/public/theme/Cerulean/style.css +49 -0
- data/lib/web/public/theme/Darkly/bootstrap.min.css +11 -0
- data/lib/web/public/theme/Darkly/style.css +53 -0
- data/lib/web/public/theme/Readable/bootstrap.min.css +11 -0
- data/lib/web/public/theme/Readable/style.css +53 -0
- data/lib/web/public/theme/Slate/bootstrap.min.css +11 -0
- data/lib/web/public/theme/Slate/style.css +40 -0
- data/lib/web/public/theme/Superhero/bootstrap.min.css +11 -0
- data/lib/web/public/theme/Superhero/style.css +40 -0
- data/lib/web/public/theme/United/bootstrap.min.css +11 -0
- data/lib/web/public/theme/United/style.css +47 -0
- data/lib/web/public/theme/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/web/public/theme/fonts/glyphicons-halflings-regular.svg +288 -0
- data/lib/web/public/theme/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/web/public/theme/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/web/public/theme/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/lib/web/settingmessages.rb +5 -0
- data/lib/web/views/index.haml +15 -19
- data/lib/web/views/layout.haml +6 -1
- data/lib/web/views/novels/setting.haml +76 -75
- data/lib/web/views/settings.haml +111 -107
- data/lib/web/views/style.scss +6 -21
- data/narou.gemspec +49 -6
- data/narou.rb +3 -1
- data/preset/ncode.syosetu.com/n7975cr/setting.ini +7 -0
- data/spec/README.txt +4 -0
- data/spec/data/convert_test/ruby/correct_test_ruby.txt +4 -4
- data/template/hotentry.txt.erb +28 -0
- data/template/ibunko_novel.txt.erb +4 -2
- data/template/novel.txt.erb +6 -3
- data/template/setting.ini.erb +18 -2
- metadata +73 -8
data/lib/command/tag.rb
CHANGED
|
@@ -12,6 +12,8 @@ module Command
|
|
|
12
12
|
COLORS = %w(green yellow blue magenta cyan red white)
|
|
13
13
|
# 禁止文字
|
|
14
14
|
BAN_CHAR = /[:;"'><$@&^\\\|%'\/`]/
|
|
15
|
+
# 禁止ワード
|
|
16
|
+
BAN_WORD = %w(hotentry)
|
|
15
17
|
|
|
16
18
|
def self.oneline_help
|
|
17
19
|
"各小説にタグを設定及び閲覧が出来ます"
|
|
@@ -57,6 +59,9 @@ module Command
|
|
|
57
59
|
if tag =~ BAN_CHAR
|
|
58
60
|
error "#{tag} に使用禁止記号が含まれています"
|
|
59
61
|
exit Narou::EXIT_ERROR_CODE
|
|
62
|
+
elsif BAN_WORD.include?(tag)
|
|
63
|
+
error "#{tag} は使用禁止ワードです"
|
|
64
|
+
exit Narou::EXIT_ERROR_CODE
|
|
60
65
|
end
|
|
61
66
|
end
|
|
62
67
|
}
|
data/lib/command/update.rb
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
# Copyright 2013 whiteleaf. All rights reserved.
|
|
4
4
|
#
|
|
5
5
|
|
|
6
|
+
require "fileutils"
|
|
6
7
|
require "memoist"
|
|
7
8
|
require_relative "../database"
|
|
8
9
|
require_relative "../downloader"
|
|
10
|
+
require_relative "../template"
|
|
11
|
+
require_relative "../novelconverter"
|
|
9
12
|
|
|
10
13
|
module Command
|
|
11
14
|
class Update < CommandBase
|
|
@@ -15,6 +18,11 @@ module Command
|
|
|
15
18
|
LOG_NUM_LIMIT = 30 # ログの保存する上限数
|
|
16
19
|
LOG_FILENAME_FORMAT = "update_log_%s.txt"
|
|
17
20
|
|
|
21
|
+
HOTENTRY_DIR_NAME = "hotentry"
|
|
22
|
+
HOTENTRY_TEMPLATE_NAME = "hotentry.txt"
|
|
23
|
+
HOTENTRY_TITLE_PATTERN = "hotentry %y/%m/%d %H:%M"
|
|
24
|
+
HOTENTRY_FILE_PATTERN = "hotentry_%y-%m-%d_%H%M.txt"
|
|
25
|
+
|
|
18
26
|
def self.oneline_help
|
|
19
27
|
"小説を更新します"
|
|
20
28
|
end
|
|
@@ -56,6 +64,9 @@ module Command
|
|
|
56
64
|
update_general_lastup
|
|
57
65
|
exit 0
|
|
58
66
|
}
|
|
67
|
+
@opt.on("-f", "--force", "凍結済みも更新する") {
|
|
68
|
+
@options["force"] = true
|
|
69
|
+
}
|
|
59
70
|
@opt.on("-s", "--sort-by KEY", "アップデートする順番を変更する\n#{Narou.update_sort_key_summaries}") { |key|
|
|
60
71
|
@options["sort-by"] = key
|
|
61
72
|
}
|
|
@@ -123,13 +134,18 @@ module Command
|
|
|
123
134
|
end
|
|
124
135
|
flush_cache # memoist のキャッシュ削除
|
|
125
136
|
|
|
137
|
+
inv = Inventory.load("local_setting", :local)
|
|
138
|
+
@options["hotentry"] = inv["hotentry"]
|
|
139
|
+
@options["hotentry.auto-mail"] = inv["hotentry.auto-mail"]
|
|
140
|
+
hotentry = {}
|
|
141
|
+
|
|
126
142
|
update_log = $stdout.capture(quiet: false) do
|
|
127
143
|
sort_by_key(sort_key, update_target_list).each_with_index do |target, i|
|
|
128
144
|
display_message = nil
|
|
129
145
|
data = Downloader.get_data_by_target(target)
|
|
130
146
|
if !data
|
|
131
147
|
display_message = "<bold><red>[ERROR]</red></bold> #{target} は管理小説の中に存在しません".termcolor
|
|
132
|
-
elsif Narou.novel_frozen?(target)
|
|
148
|
+
elsif Narou.novel_frozen?(target) && !@options["force"]
|
|
133
149
|
if argv.length > 0
|
|
134
150
|
display_message = "ID:#{data["id"]} #{data["title"]} は凍結中です"
|
|
135
151
|
else
|
|
@@ -142,7 +158,16 @@ module Command
|
|
|
142
158
|
mistook_count += 1
|
|
143
159
|
next
|
|
144
160
|
end
|
|
145
|
-
|
|
161
|
+
downloader = Downloader.new(target)
|
|
162
|
+
|
|
163
|
+
if @options["hotentry"]
|
|
164
|
+
downloader.on(:newarrival) do |hash|
|
|
165
|
+
entry = hotentry[hash[:id]] ||= []
|
|
166
|
+
entry << hash[:subtitle_info]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
result = downloader.start_download
|
|
146
171
|
case result.status
|
|
147
172
|
when :ok
|
|
148
173
|
unless @options["no-convert"] ||
|
|
@@ -162,7 +187,10 @@ module Command
|
|
|
162
187
|
end
|
|
163
188
|
end
|
|
164
189
|
end
|
|
190
|
+
|
|
191
|
+
process_hotentry(hotentry)
|
|
165
192
|
save_log(update_log)
|
|
193
|
+
|
|
166
194
|
exit mistook_count if mistook_count > 0
|
|
167
195
|
rescue Interrupt
|
|
168
196
|
puts "アップデートを中断しました"
|
|
@@ -253,8 +281,8 @@ module Command
|
|
|
253
281
|
|
|
254
282
|
# オンラインの目次からgeneral_lastupを取得する
|
|
255
283
|
# ただし、toc.yaml に最新話が存在し、かつsubdateが設定されていたらそれを使う
|
|
256
|
-
def get_latest_dates(
|
|
257
|
-
downloader = Downloader.new(
|
|
284
|
+
def get_latest_dates(target)
|
|
285
|
+
downloader = Downloader.new(target)
|
|
258
286
|
old_toc = downloader.load_toc_file
|
|
259
287
|
latest_toc = downloader.get_latest_table_of_contents(old_toc, through_error: true)
|
|
260
288
|
{
|
|
@@ -262,5 +290,110 @@ module Command
|
|
|
262
290
|
"general_lastup" => downloader.get_general_lastup
|
|
263
291
|
}
|
|
264
292
|
end
|
|
293
|
+
|
|
294
|
+
#
|
|
295
|
+
# 新着話をまとめたデータの作成に関する処理
|
|
296
|
+
#
|
|
297
|
+
def process_hotentry(hotentry)
|
|
298
|
+
return if hotentry.empty?
|
|
299
|
+
cmd_convert = Command::Convert.new
|
|
300
|
+
cmd_convert.load_local_settings
|
|
301
|
+
cmd_convert.device = Narou.get_device
|
|
302
|
+
|
|
303
|
+
ebook_path = convert_hotentry(hotentry, cmd_convert)
|
|
304
|
+
copy_hotentry(ebook_path, cmd_convert)
|
|
305
|
+
send_hotentry(ebook_path, cmd_convert)
|
|
306
|
+
mail_hotentry
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def convert_hotentry(hotentry, cmd_convert)
|
|
310
|
+
output_filename = nil
|
|
311
|
+
display_inspector = false
|
|
312
|
+
ignore_force = false
|
|
313
|
+
ignore_default = false
|
|
314
|
+
|
|
315
|
+
converted_text_array = []
|
|
316
|
+
use_dakuten_font = false
|
|
317
|
+
|
|
318
|
+
Helper.print_horizontal_rule
|
|
319
|
+
puts "hotentry の変換を開始"
|
|
320
|
+
|
|
321
|
+
subtitles_size = hotentry.inject(0) { |sum, (_, subtitles)| subtitles.size + sum }
|
|
322
|
+
progressbar = ProgressBar.new(subtitles_size)
|
|
323
|
+
total_progress = 0
|
|
324
|
+
|
|
325
|
+
hotentry.each do |id, subtitles|
|
|
326
|
+
setting = NovelSetting.load(id, ignore_force, ignore_default)
|
|
327
|
+
novel_converter = NovelConverter.new(setting, output_filename, display_inspector)
|
|
328
|
+
last_num = 0
|
|
329
|
+
novel_converter.on(:"convert_main.loop") do |i|
|
|
330
|
+
progressbar.output(total_progress + i)
|
|
331
|
+
last_num = i
|
|
332
|
+
end
|
|
333
|
+
converted_text_array << {
|
|
334
|
+
setting: setting,
|
|
335
|
+
text: novel_converter.convert_main_for_novel(subtitles, true)
|
|
336
|
+
}
|
|
337
|
+
use_dakuten_font |= novel_converter.use_dakuten_font
|
|
338
|
+
|
|
339
|
+
total_progress += last_num + 1
|
|
340
|
+
end
|
|
341
|
+
progressbar.clear
|
|
342
|
+
puts "縦書用の変換が終了しました"
|
|
343
|
+
|
|
344
|
+
device = Narou.get_device
|
|
345
|
+
now = Time.now
|
|
346
|
+
# テキストの生成
|
|
347
|
+
hotentry_title = now.strftime(HOTENTRY_TITLE_PATTERN)
|
|
348
|
+
hotentry_text = Template.get(HOTENTRY_TEMPLATE_NAME, binding, 1.0)
|
|
349
|
+
# 生成したテキストファイルの保存
|
|
350
|
+
txt_output_path = File.join(Update.hotentry_dirname, now.strftime(HOTENTRY_FILE_PATTERN))
|
|
351
|
+
create_inclusive_directory(txt_output_path)
|
|
352
|
+
File.write(txt_output_path, hotentry_text)
|
|
353
|
+
# テキストを書籍データに変換
|
|
354
|
+
relay_proc = -> {
|
|
355
|
+
NovelConverter.convert_txt_to_ebook_file(txt_output_path, {
|
|
356
|
+
use_dakuten_font: use_dakuten_font,
|
|
357
|
+
device: device
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
if device
|
|
361
|
+
cmd_convert.extend(device.get_hook_module)
|
|
362
|
+
cmd_convert.converted_txt_path = txt_output_path
|
|
363
|
+
cmd_convert.hook_call(:change_settings)
|
|
364
|
+
end
|
|
365
|
+
if cmd_convert.respond_to?(:hook_convert_txt_to_ebook_file)
|
|
366
|
+
ebook_path = cmd_convert.hook_convert_txt_to_ebook_file(&relay_proc)
|
|
367
|
+
else
|
|
368
|
+
ebook_path = relay_proc.call
|
|
369
|
+
end
|
|
370
|
+
ebook_path
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def create_inclusive_directory(path)
|
|
374
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def copy_hotentry(ebook_path, cmd_convert)
|
|
378
|
+
cmd_convert.copy_to_converted_file(ebook_path)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def send_hotentry(ebook_path, cmd_convert)
|
|
382
|
+
cmd_convert.send_file_to_device(ebook_path)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def mail_hotentry
|
|
386
|
+
return unless @options["hotentry.auto-mail"]
|
|
387
|
+
Mail.execute!(["hotentry"])
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def self.hotentry_dirname
|
|
391
|
+
@@__hotentry_dirname ||= File.join(Narou.get_root_dir, HOTENTRY_DIR_NAME)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def self.get_newest_hotentry_file_path(device)
|
|
395
|
+
pattern = File.join(Update.hotentry_dirname, "hotentry_*#{device.ebook_file_ext}")
|
|
396
|
+
Dir.glob(pattern).sort.last
|
|
397
|
+
end
|
|
265
398
|
end
|
|
266
399
|
end
|
data/lib/converterbase.rb
CHANGED
|
@@ -284,7 +284,7 @@ class ConverterBase
|
|
|
284
284
|
data.gsub!(/([!?!?]+)([^!?!?])/) do
|
|
285
285
|
m1, m2 = $1, $2
|
|
286
286
|
m2 = " " if m2 == " "
|
|
287
|
-
if m2 =~ /[
|
|
287
|
+
if m2 =~ /[^」]}\]\}』】〉》〕>>≫))"”’〟 ☆★♪[―]/
|
|
288
288
|
"#{m1} #{m2}"
|
|
289
289
|
else
|
|
290
290
|
"#{m1}#{m2}"
|
|
@@ -344,7 +344,7 @@ class ConverterBase
|
|
|
344
344
|
data.gsub!(/[#{SINGLE_MINUTE_FAMILY}]([^"\n]+?)[#{SINGLE_MINUTE_FAMILY}]/, "〝\\1〟")
|
|
345
345
|
# MEMO: シングルミュートを表示出来るフォントはほとんど無いためダブルにする
|
|
346
346
|
data.gsub!(/[#{DOUBLE_MINUTE_FAMILY}]([^"\n]+?)[#{DOUBLE_MINUTE_FAMILY}]/, "〝\\1〟")
|
|
347
|
-
data.tr!("-=+/*《》'\"%$#&!?<><>()|‐,._
|
|
347
|
+
data.tr!("-=+/*《》'\"%$#&!?<><>()|‐,._;:\[\]{}",
|
|
348
348
|
"-=+/*≪≫’”%$#&!?〈〉〈〉()|-,._;:[]")
|
|
349
349
|
data.gsub!("\\", "¥")
|
|
350
350
|
data
|
|
@@ -798,7 +798,7 @@ class ConverterBase
|
|
|
798
798
|
# かぎ括弧内自動連結
|
|
799
799
|
#
|
|
800
800
|
def auto_join_in_brackets(data)
|
|
801
|
-
if !@setting.enable_auto_join_in_brackets && !@setting.
|
|
801
|
+
if !@setting.enable_auto_join_in_brackets && !@setting.enable_inspect
|
|
802
802
|
return
|
|
803
803
|
end
|
|
804
804
|
OPENCLOSE_REGEXPS.each_with_index do |openclose, i|
|
|
@@ -813,7 +813,7 @@ class ConverterBase
|
|
|
813
813
|
end
|
|
814
814
|
"[#かぎ括弧=#{j}]"
|
|
815
815
|
end
|
|
816
|
-
if @setting.
|
|
816
|
+
if @setting.enable_inspect
|
|
817
817
|
# 正しく閉じてないかぎ括弧だけが data に残ってる
|
|
818
818
|
@inspector.inspect_invalid_openclose_brackets(data, BRACKETS[i], stack)
|
|
819
819
|
end
|
|
@@ -836,7 +836,7 @@ class ConverterBase
|
|
|
836
836
|
end
|
|
837
837
|
|
|
838
838
|
CHARACTER_OF_RUBY = "一-龠A-Za-zA-Za-z"
|
|
839
|
-
AUTO_RUBY_CHARACTERS = "([
|
|
839
|
+
AUTO_RUBY_CHARACTERS = "([ぁ-んァ-ヶーゝゞ・ ]{,20})"
|
|
840
840
|
|
|
841
841
|
#
|
|
842
842
|
# 小説家になろうのルビ対策
|
|
@@ -1225,7 +1225,13 @@ class ConverterBase
|
|
|
1225
1225
|
symbol = false
|
|
1226
1226
|
case char
|
|
1227
1227
|
when "|"
|
|
1228
|
-
|
|
1228
|
+
buffer << char
|
|
1229
|
+
if ss.scan(/.+?》/)
|
|
1230
|
+
buffer << "#{ss.matched}"
|
|
1231
|
+
else
|
|
1232
|
+
before_symbol = false
|
|
1233
|
+
end
|
|
1234
|
+
next
|
|
1229
1235
|
when "["
|
|
1230
1236
|
buffer << char
|
|
1231
1237
|
if ss.scan(/^#.+?]/)
|
data/lib/database.rb
CHANGED
data/lib/device/ibooks.rb
CHANGED
|
@@ -36,19 +36,20 @@ module Device::Ibooks
|
|
|
36
36
|
ebook_file_path = original_func.call
|
|
37
37
|
return ebook_file_path unless ebook_file_path
|
|
38
38
|
return ebook_file_path unless @@__ibooks_container_dir
|
|
39
|
+
epubdir_path = nil
|
|
39
40
|
if @argument_target_type == :file
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
@toc_url = nil
|
|
42
|
+
else
|
|
43
|
+
@toc_url = @novel_data["toc_url"]
|
|
44
|
+
epubdir_path = get_epubdir_path_in_ibooks_container
|
|
42
45
|
end
|
|
43
|
-
@toc_url = @novel_data["toc_url"]
|
|
44
|
-
epubdir_path = get_epubdir_path_in_ibooks_container
|
|
45
46
|
if epubdir_path && File.exist?(epubdir_path)
|
|
46
47
|
extract_epub(ebook_file_path, epubdir_path)
|
|
47
48
|
puts "iBooksに登録してあるEPUBを更新しました"
|
|
48
49
|
else
|
|
49
50
|
epubdir_path = watch_ibooks_container(ebook_file_path)
|
|
50
51
|
if epubdir_path
|
|
51
|
-
regist_epubdir_path_to_setting(epubdir_path)
|
|
52
|
+
regist_epubdir_path_to_setting(epubdir_path) if @toc_url
|
|
52
53
|
puts "iBooksへの登録を確認しました"
|
|
53
54
|
else
|
|
54
55
|
error "EPUBの展開後のフォルダが見つかりませんでした。" \
|
data/lib/downloader.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative "sitesetting"
|
|
|
12
12
|
require_relative "template"
|
|
13
13
|
require_relative "database"
|
|
14
14
|
require_relative "inventory"
|
|
15
|
+
require_relative "eventable"
|
|
15
16
|
require_relative "narou/api"
|
|
16
17
|
require_relative "html"
|
|
17
18
|
require_relative "input"
|
|
@@ -20,6 +21,8 @@ require_relative "input"
|
|
|
20
21
|
# 小説サイトからのダウンロード
|
|
21
22
|
#
|
|
22
23
|
class Downloader
|
|
24
|
+
include Narou::Eventable
|
|
25
|
+
|
|
23
26
|
NOVEL_SITE_SETTING_DIR = "webnovel/"
|
|
24
27
|
SECTION_SAVE_DIR_NAME = "本文" # 本文を保存するディレクトリ名
|
|
25
28
|
CACHE_SAVE_DIR_NAME = "cache" # 差分用キャッシュ保存用ディレクトリ名
|
|
@@ -34,44 +37,28 @@ class Downloader
|
|
|
34
37
|
|
|
35
38
|
attr_reader :id
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
data = @@database[target.to_i]
|
|
56
|
-
unless data
|
|
57
|
-
error "指定のID(#{target})は存在しません"
|
|
58
|
-
return fail_status
|
|
59
|
-
end
|
|
60
|
-
setting = get_sitesetting_by_sitename(data["sitename"])
|
|
61
|
-
setting.multi_match(data["toc_url"], "url")
|
|
62
|
-
when :other
|
|
63
|
-
data = @@database.get_data("title", target)
|
|
64
|
-
if data
|
|
65
|
-
setting = get_sitesetting_by_sitename(data["sitename"])
|
|
66
|
-
setting.multi_match(data["toc_url"], "url")
|
|
67
|
-
else
|
|
68
|
-
error "指定の小説(#{target})は存在しません"
|
|
69
|
-
return fail_status
|
|
40
|
+
class InvalidTarget < StandardError; end
|
|
41
|
+
|
|
42
|
+
def initialize(target, options = {})
|
|
43
|
+
id = Downloader.get_id_by_target(target)
|
|
44
|
+
options = {
|
|
45
|
+
force: false, from_download: false,
|
|
46
|
+
stream: $stdout
|
|
47
|
+
}.merge(options)
|
|
48
|
+
setting = Downloader.get_sitesetting_by_target(target)
|
|
49
|
+
|
|
50
|
+
unless setting
|
|
51
|
+
case type = Downloader.get_target_type(target)
|
|
52
|
+
when :url, :ncode
|
|
53
|
+
raise InvalidTarget, "対応外の#{type}です(#{target})"
|
|
54
|
+
when :id
|
|
55
|
+
raise InvalidTarget, "指定のID(#{target})は存在しません"
|
|
56
|
+
when :other
|
|
57
|
+
raise InvalidTarget, "指定の小説(#{target})は存在しません"
|
|
70
58
|
end
|
|
71
59
|
end
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
result
|
|
60
|
+
|
|
61
|
+
initialize_variables(id, setting, options)
|
|
75
62
|
end
|
|
76
63
|
|
|
77
64
|
#
|
|
@@ -139,8 +126,8 @@ class Downloader
|
|
|
139
126
|
else
|
|
140
127
|
@@database.delete(id)
|
|
141
128
|
@@database.save_database
|
|
142
|
-
error "#{path}
|
|
143
|
-
|
|
129
|
+
error "#{path} が見つかりません。\n" \
|
|
130
|
+
"保存フォルダが消去されていたため、データベースのインデックスを削除しました。"
|
|
144
131
|
return nil
|
|
145
132
|
end
|
|
146
133
|
end
|
|
@@ -286,37 +273,43 @@ class Downloader
|
|
|
286
273
|
end
|
|
287
274
|
|
|
288
275
|
#
|
|
289
|
-
#
|
|
276
|
+
# 変数初期化
|
|
290
277
|
#
|
|
291
|
-
def
|
|
278
|
+
def initialize_variables(id, setting, options)
|
|
292
279
|
@title = nil
|
|
293
280
|
@file_title = nil
|
|
294
|
-
@setting =
|
|
295
|
-
@force = force
|
|
296
|
-
@from_download = from_download
|
|
281
|
+
@setting = setting
|
|
282
|
+
@force = options[:force]
|
|
283
|
+
@from_download = options[:from_download]
|
|
284
|
+
@stream = options[:stream]
|
|
297
285
|
@cache_dir = nil
|
|
298
286
|
@new_arrivals = false
|
|
299
287
|
@novel_data_dir = nil
|
|
300
288
|
@novel_status = nil
|
|
301
|
-
@id =
|
|
289
|
+
@id = id || @@database.create_new_id
|
|
302
290
|
@new_novel = @@database[@id].!
|
|
303
291
|
@section_download_cache = {}
|
|
292
|
+
@download_wait_steps = Inventory.load("local_setting", :local)["download.wait-steps"] || 0
|
|
293
|
+
@download_use_subdirectory = use_subdirectory?
|
|
294
|
+
if @setting["is_narou"] && (@download_wait_steps > 10 || @download_wait_steps == 0)
|
|
295
|
+
@download_wait_steps = 10
|
|
296
|
+
end
|
|
297
|
+
initialize_wait_counter
|
|
298
|
+
end
|
|
304
299
|
|
|
305
|
-
|
|
300
|
+
#
|
|
301
|
+
# ウェイト関係初期化
|
|
302
|
+
#
|
|
303
|
+
def initialize_wait_counter
|
|
306
304
|
@@__run_once ||= false
|
|
307
305
|
unless @@__run_once
|
|
308
306
|
@@__run_once = true
|
|
309
307
|
@@__wait_counter = 0
|
|
310
308
|
@@__last_download_time = Time.now - 20
|
|
311
|
-
@@interval_sleep_time = Inventory.load("local_setting", :local)["download.interval"] || 0
|
|
312
|
-
@@interval_sleep_time = 0 if @@interval_sleep_time < 0
|
|
313
|
-
@@max_steps_wait_time = [STEPS_WAIT_TIME, @@interval_sleep_time].max
|
|
314
|
-
end
|
|
315
|
-
@download_wait_steps = Inventory.load("local_setting", :local)["download.wait-steps"] || 0
|
|
316
|
-
@download_use_subdirectory = use_subdirectory?
|
|
317
|
-
if @setting["is_narou"] && (@download_wait_steps > 10 || @download_wait_steps == 0)
|
|
318
|
-
@download_wait_steps = 10
|
|
319
309
|
end
|
|
310
|
+
@@interval_sleep_time = Inventory.load("local_setting", :local)["download.interval"] || 0
|
|
311
|
+
@@interval_sleep_time = 0 if @@interval_sleep_time < 0
|
|
312
|
+
@@max_steps_wait_time = [STEPS_WAIT_TIME, @@interval_sleep_time].max
|
|
320
313
|
end
|
|
321
314
|
|
|
322
315
|
#
|
|
@@ -372,12 +365,12 @@ class Downloader
|
|
|
372
365
|
old_toc = @new_novel ? nil : load_toc_file
|
|
373
366
|
latest_toc = get_latest_table_of_contents(old_toc)
|
|
374
367
|
unless latest_toc
|
|
375
|
-
error @setting["toc_url"] + " の目次データが取得出来ませんでした"
|
|
368
|
+
@stream.error @setting["toc_url"] + " の目次データが取得出来ませんでした"
|
|
376
369
|
return :failed
|
|
377
370
|
end
|
|
378
371
|
if @setting["confirm_over18"]
|
|
379
372
|
unless confirm_over18?
|
|
380
|
-
puts "18歳以上のみ閲覧出来る小説です。ダウンロードを中止しました"
|
|
373
|
+
@stream.puts "18歳以上のみ閲覧出来る小説です。ダウンロードを中止しました"
|
|
381
374
|
return :canceled
|
|
382
375
|
end
|
|
383
376
|
end
|
|
@@ -394,7 +387,7 @@ class Downloader
|
|
|
394
387
|
end
|
|
395
388
|
|
|
396
389
|
if old_toc.empty? && update_subtitles.count == 0
|
|
397
|
-
error "#{@setting['title']} の目次がありません"
|
|
390
|
+
@stream.error "#{@setting['title']} の目次がありません"
|
|
398
391
|
return :failed
|
|
399
392
|
end
|
|
400
393
|
|
|
@@ -417,23 +410,23 @@ class Downloader
|
|
|
417
410
|
end
|
|
418
411
|
rescue Interrupt
|
|
419
412
|
remove_cache_dir
|
|
420
|
-
puts "ダウンロードを中断しました"
|
|
413
|
+
@stream.puts "ダウンロードを中断しました"
|
|
421
414
|
exit Narou::EXIT_ERROR_CODE
|
|
422
415
|
end
|
|
423
416
|
update_database
|
|
424
417
|
:ok
|
|
425
418
|
when old_toc["subtitles"].size > latest_toc["subtitles"].size
|
|
426
419
|
# 削除された節がある(かつ更新がない)場合
|
|
427
|
-
puts "#{id_and_title} は一部の話が削除されています"
|
|
420
|
+
@stream.puts "#{id_and_title} は一部の話が削除されています"
|
|
428
421
|
:ok
|
|
429
422
|
when old_toc["title"] != latest_toc["title"]
|
|
430
423
|
# タイトルが更新されている場合
|
|
431
|
-
puts "#{id_and_title} のタイトルが更新されています"
|
|
424
|
+
@stream.puts "#{id_and_title} のタイトルが更新されています"
|
|
432
425
|
update_database
|
|
433
426
|
:ok
|
|
434
427
|
when old_toc["story"] != latest_toc["story"]
|
|
435
428
|
# あらすじが更新されている場合
|
|
436
|
-
puts "#{id_and_title} のあらすじが更新されています"
|
|
429
|
+
@stream.puts "#{id_and_title} のあらすじが更新されています"
|
|
437
430
|
:ok
|
|
438
431
|
else
|
|
439
432
|
:none
|
|
@@ -447,7 +440,7 @@ class Downloader
|
|
|
447
440
|
Command::Tag.execute!([@id, "--add", "end", "--color", "white"])
|
|
448
441
|
end
|
|
449
442
|
msg = old_toc.empty? ? "完結しているようです" : "完結したようです"
|
|
450
|
-
puts "<cyan>#{id_and_title.escape} は#{msg}</cyan>".termcolor
|
|
443
|
+
@stream.puts "<cyan>#{id_and_title.escape} は#{msg}</cyan>".termcolor
|
|
451
444
|
return_status = :ok
|
|
452
445
|
end
|
|
453
446
|
else
|
|
@@ -456,7 +449,7 @@ class Downloader
|
|
|
456
449
|
$stdout.silence do
|
|
457
450
|
Command::Tag.execute!([@id, "--delete", "end"])
|
|
458
451
|
end
|
|
459
|
-
puts "<cyan>#{id_and_title.escape} は連載を再開したようです</cyan>".termcolor
|
|
452
|
+
@stream.puts "<cyan>#{id_and_title.escape} は連載を再開したようです</cyan>".termcolor
|
|
460
453
|
return_status = :ok
|
|
461
454
|
end
|
|
462
455
|
end
|
|
@@ -708,7 +701,7 @@ class Downloader
|
|
|
708
701
|
@setting = s
|
|
709
702
|
toc_url = @setting["toc_url"]
|
|
710
703
|
end
|
|
711
|
-
toc_source = Helper.pretreatment_source(toc_fp.read, @setting["encoding"])
|
|
704
|
+
toc_source = Helper.restor_entity(Helper.pretreatment_source(toc_fp.read, @setting["encoding"]))
|
|
712
705
|
raise DownloaderNotFoundError if Downloader.detect_error_message(@setting, toc_source)
|
|
713
706
|
end
|
|
714
707
|
toc_source
|
|
@@ -762,7 +755,7 @@ class Downloader
|
|
|
762
755
|
rescue OpenURI::HTTPError, Errno::ECONNRESET => e
|
|
763
756
|
raise if through_error # エラー処理はしなくていいからそのまま例外を受け取りたい時用
|
|
764
757
|
if e.message.include?("404")
|
|
765
|
-
error "小説が削除されているか非公開な可能性があります"
|
|
758
|
+
@stream.error "小説が削除されているか非公開な可能性があります"
|
|
766
759
|
if @@database.novel_exists?(@id)
|
|
767
760
|
$stdout.silence do
|
|
768
761
|
Command::Tag.execute!(%W(#{@id} --add 404 --color white))
|
|
@@ -770,7 +763,7 @@ class Downloader
|
|
|
770
763
|
Command::Freeze.execute!([@id])
|
|
771
764
|
end
|
|
772
765
|
else
|
|
773
|
-
error "何らかの理由により目次が取得できませんでした(#{e.message})"
|
|
766
|
+
@stream.error "何らかの理由により目次が取得できませんでした(#{e.message})"
|
|
774
767
|
end
|
|
775
768
|
false
|
|
776
769
|
end
|
|
@@ -930,7 +923,7 @@ class Downloader
|
|
|
930
923
|
def sections_download_and_save(subtitles)
|
|
931
924
|
max = subtitles.size
|
|
932
925
|
return if max == 0
|
|
933
|
-
puts "<bold><green>#{"ID:#{@id} #{get_title}".escape} のDL開始</green></bold>".termcolor
|
|
926
|
+
@stream.puts "<bold><green>#{"ID:#{@id} #{get_title}".escape} のDL開始</green></bold>".termcolor
|
|
934
927
|
save_least_one = false
|
|
935
928
|
subtitles.each_with_index do |subtitle_info, i|
|
|
936
929
|
index, subtitle, file_subtitle, chapter = %w(index subtitle file_subtitle chapter).map { |k|
|
|
@@ -940,24 +933,25 @@ class Downloader
|
|
|
940
933
|
info["element"] = a_section_download(subtitle_info)
|
|
941
934
|
|
|
942
935
|
unless chapter.empty?
|
|
943
|
-
puts "#{chapter}"
|
|
936
|
+
@stream.puts "#{chapter}"
|
|
944
937
|
end
|
|
945
938
|
if get_novel_type == NOVEL_TYPE_SERIES
|
|
946
939
|
if index.to_s.length <= DISPLAY_LIMIT_DIGITS
|
|
947
940
|
# indexの数字がでかいと見た目がみっともないので特定の桁以内だけ表示する
|
|
948
|
-
print "第#{index}部分 "
|
|
941
|
+
@stream.print "第#{index}部分 "
|
|
949
942
|
end
|
|
950
943
|
else
|
|
951
|
-
print "短編 "
|
|
944
|
+
@stream.print "短編 "
|
|
952
945
|
end
|
|
953
|
-
print "#{subtitle} (#{i+1}/#{max})"
|
|
946
|
+
@stream.print "#{subtitle} (#{i+1}/#{max})"
|
|
954
947
|
|
|
955
948
|
section_file_name = "#{index} #{file_subtitle}.yaml"
|
|
956
949
|
section_file_relative_path = File.join(SECTION_SAVE_DIR_NAME, section_file_name)
|
|
957
|
-
|
|
950
|
+
section_file_full_path = File.join(get_novel_data_dir, section_file_relative_path)
|
|
951
|
+
if File.exist?(section_file_full_path)
|
|
958
952
|
if @force
|
|
959
953
|
if different_section?(section_file_relative_path, info)
|
|
960
|
-
print " (更新あり)"
|
|
954
|
+
@stream.print " (更新あり)"
|
|
961
955
|
move_to_cache_dir(section_file_relative_path)
|
|
962
956
|
end
|
|
963
957
|
else
|
|
@@ -965,13 +959,17 @@ class Downloader
|
|
|
965
959
|
end
|
|
966
960
|
else
|
|
967
961
|
if !@from_download || (@from_download && @force)
|
|
968
|
-
print " <bold><magenta>(新着)</magenta></bold>".termcolor
|
|
962
|
+
@stream.print " <bold><magenta>(新着)</magenta></bold>".termcolor
|
|
963
|
+
trigger(:newarrival, {
|
|
964
|
+
id: @id,
|
|
965
|
+
subtitle_info: subtitle_info
|
|
966
|
+
})
|
|
969
967
|
end
|
|
970
968
|
@new_arrivals = true
|
|
971
969
|
end
|
|
972
970
|
save_novel_data(section_file_relative_path, info)
|
|
973
971
|
save_least_one = true
|
|
974
|
-
puts
|
|
972
|
+
@stream.puts
|
|
975
973
|
end
|
|
976
974
|
remove_cache_dir unless save_least_one
|
|
977
975
|
end
|
|
@@ -1033,6 +1031,7 @@ class Downloader
|
|
|
1033
1031
|
end
|
|
1034
1032
|
raw = download_raw_data(subtitle_url)
|
|
1035
1033
|
if @setting["is_narou"]
|
|
1034
|
+
raw = Helper.restor_entity(raw)
|
|
1036
1035
|
save_raw_data(raw, subtitle_info)
|
|
1037
1036
|
element = extract_elements_in_section(raw, subtitle_info["subtitle"])
|
|
1038
1037
|
element["data_type"] = "text"
|
|
@@ -1055,24 +1054,25 @@ class Downloader
|
|
|
1055
1054
|
def download_raw_data(url)
|
|
1056
1055
|
raw = nil
|
|
1057
1056
|
retry_count = RETRY_MAX_FOR_503
|
|
1057
|
+
cookie = @setting["cookie"] || ""
|
|
1058
1058
|
begin
|
|
1059
|
-
open(url) do |fp|
|
|
1059
|
+
open(url, "Cookie" => cookie) do |fp|
|
|
1060
1060
|
raw = Helper.pretreatment_source(fp.read, @setting["encoding"])
|
|
1061
1061
|
end
|
|
1062
1062
|
rescue OpenURI::HTTPError => e
|
|
1063
1063
|
if e.message =~ /^503/
|
|
1064
1064
|
if retry_count == 0
|
|
1065
|
-
error "上限までリトライしましたがファイルがダウンロード出来ませんでした"
|
|
1065
|
+
@stream.error "上限までリトライしましたがファイルがダウンロード出来ませんでした"
|
|
1066
1066
|
exit Narou::EXIT_ERROR_CODE
|
|
1067
1067
|
end
|
|
1068
1068
|
retry_count -= 1
|
|
1069
|
-
puts
|
|
1070
|
-
warn "server message: #{e.message}"
|
|
1071
|
-
warn "リトライ待機中……"
|
|
1069
|
+
@stream.puts
|
|
1070
|
+
@stream.warn "server message: #{e.message}"
|
|
1071
|
+
@stream.warn "リトライ待機中……"
|
|
1072
1072
|
@@display_hint_once ||= false
|
|
1073
1073
|
unless @@display_hint_once
|
|
1074
|
-
warn "ヒント: narou s download.wait-steps=10 とすることで、" \
|
|
1075
|
-
|
|
1074
|
+
@stream.warn "ヒント: narou s download.wait-steps=10 とすることで、" \
|
|
1075
|
+
"10話ごとにウェイトをいれられます"
|
|
1076
1076
|
@@display_hint_once = true
|
|
1077
1077
|
end
|
|
1078
1078
|
sleep(WAITING_TIME_FOR_503)
|
|
@@ -1193,11 +1193,13 @@ class Downloader
|
|
|
1193
1193
|
novel_dir_path = get_novel_data_dir
|
|
1194
1194
|
file_title = File.basename(novel_dir_path)
|
|
1195
1195
|
FileUtils.mkdir_p(novel_dir_path) unless File.exist?(novel_dir_path)
|
|
1196
|
-
original_settings = NovelSetting
|
|
1196
|
+
original_settings = NovelSetting.get_original_settings
|
|
1197
|
+
default_settings = NovelSetting.load_default_settings
|
|
1198
|
+
novel_setting = NovelSetting.new(@id, true, true)
|
|
1197
1199
|
special_preset_dir = File.join(Narou.get_preset_dir, @setting["domain"], @setting["ncode"])
|
|
1198
1200
|
exists_special_preset_dir = File.exist?(special_preset_dir)
|
|
1199
1201
|
templates = [
|
|
1200
|
-
[NovelSetting::INI_NAME,
|
|
1202
|
+
[NovelSetting::INI_NAME, NovelSetting::INI_ERB_BINARY_VERSION],
|
|
1201
1203
|
["converter.rb", 1.0],
|
|
1202
1204
|
[NovelSetting::REPLACE_NAME, 1.0]
|
|
1203
1205
|
]
|