narou 2.8.3.1 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of narou might be problematic. Click here for more details.

Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +77 -0
  3. data/Gemfile +2 -1
  4. data/README.md +69 -86
  5. data/lib/command/convert.rb +10 -11
  6. data/lib/command/init.rb +2 -2
  7. data/lib/command/list.rb +12 -3
  8. data/lib/command/setting.rb +20 -5
  9. data/lib/command/update.rb +0 -3
  10. data/lib/command/version.rb +2 -3
  11. data/lib/command/web.rb +58 -7
  12. data/lib/converterbase.rb +7 -24
  13. data/lib/database.rb +7 -6
  14. data/lib/device.rb +15 -1
  15. data/lib/device/ibunko.rb +1 -2
  16. data/lib/device/library/mac.rb +7 -0
  17. data/lib/downloader.rb +44 -21
  18. data/lib/extension.rb +25 -0
  19. data/lib/helper.rb +4 -3
  20. data/lib/html.rb +18 -2
  21. data/lib/narou.rb +20 -0
  22. data/lib/novelconverter.rb +11 -35
  23. data/lib/novelinfo.rb +19 -8
  24. data/lib/novelsetting.rb +0 -6
  25. data/lib/version.rb +1 -1
  26. data/lib/web/appserver.rb +134 -15
  27. data/lib/web/public/favicon.ico +0 -0
  28. data/lib/web/public/resources/common.ui.js +2 -2
  29. data/lib/web/public/resources/narou.library.js +657 -91
  30. data/lib/web/public/resources/narou.queue.js +1 -1
  31. data/lib/web/public/resources/narou.ui.js +253 -102
  32. data/lib/web/public/resources/sprintf.js +245 -0
  33. data/lib/web/pushserver.rb +14 -3
  34. data/lib/web/views/_about.haml +188 -0
  35. data/lib/web/views/{diff_list.haml → _diff_list.haml} +0 -0
  36. data/lib/web/views/{edit_replace_txt.haml → _edit_replace_txt.haml} +4 -4
  37. data/lib/web/views/_rebooting.haml +18 -0
  38. data/lib/web/views/bookmarklet/download.js.erb +2 -2
  39. data/lib/web/views/bookmarklet/insert_button.js.erb +1 -1
  40. data/lib/web/views/edit_menu.haml +223 -0
  41. data/lib/web/views/help.haml +29 -12
  42. data/lib/web/views/index.haml +99 -88
  43. data/lib/web/views/layout.haml +5 -3
  44. data/lib/web/views/notepad.haml +39 -0
  45. data/lib/web/views/novels/setting.haml +15 -5
  46. data/lib/web/views/settings.haml +2 -2
  47. data/lib/web/views/style.scss +72 -21
  48. data/lib/web/views/widget/download.haml +3 -2
  49. data/lib/web/views/widget/drag_and_drop.haml +3 -2
  50. data/lib/web/views/widget/notepad.haml +44 -0
  51. data/narou.gemspec +75 -6
  52. data/narou.rb +8 -14
  53. data/preset/ncode.syosetu.com/n5115cq/converter.rb +30 -0
  54. data/preset/ncode.syosetu.com/n7594ct/converter.rb +37 -0
  55. data/preset/ncode.syosetu.com/n8725k/converter.rb +1 -1
  56. data/preset/vertical_font.css +0 -10
  57. data/spec/generator/convert_spec_gen.rb +2 -0
  58. data/template/novel.txt.erb +3 -0
  59. data/webnovel/https.syosetu.org.yaml +1 -1
  60. data/webnovel/kakuyomu.jp.yaml +82 -0
  61. data/webnovel/ncode.syosetu.com.yaml +1 -0
  62. data/webnovel/syosetu.org.yaml +1 -1
  63. metadata +89 -12
  64. data/lib/web/views/about.haml +0 -85
  65. data/preset/DMincho.ttf +0 -0
@@ -14,9 +14,8 @@ module Command
14
14
  end
15
15
 
16
16
  def self.create_version_string
17
- cv_path = File.expand_path("commitversion", Narou.get_script_dir)
18
- commitversion = File.exist?(cv_path) ? File.read(cv_path) : `git describe --always`.strip + "(develop)"
19
- "#{::Version} build #{commitversion}"
17
+ postfix = (Narou.commit_version ? "" : " (develop)")
18
+ "#{::Version}#{postfix}"
20
19
  end
21
20
  end
22
21
  end
@@ -69,7 +69,35 @@ module Command
69
69
  end
70
70
 
71
71
  def execute(argv)
72
- super
72
+ if argv.delete("--boot")
73
+ @rebooted = !!argv.delete("--reboot")
74
+ super
75
+ boot
76
+ else
77
+ argv << "--backtrace" if $display_backtrace
78
+ argv << "--no-color" if $disable_color
79
+ argv << "--boot"
80
+ argv_copy = argv.dup
81
+ begin
82
+ loop do
83
+ if $development
84
+ system(RbConfig.ruby, "-x", $0, "web", *argv)
85
+ else
86
+ system("narou", "web", *argv)
87
+ end
88
+ break unless $?.exitstatus == Narou::EXIT_REQUEST_REBOOT
89
+ argv = argv_copy.dup
90
+ argv.push("--no-browser", "--reboot")
91
+ end
92
+ rescue Interrupt => e
93
+ # 中断されてコンソールへの入力が可能になってから、WEBrick が終了するまで
94
+ # タイムラグがあって表示がごちゃまぜになるので、終わるのを少し待つ
95
+ sleep 3
96
+ end
97
+ end
98
+ end
99
+
100
+ def boot
73
101
  require_relative "../web/all"
74
102
  confirm_of_first
75
103
  params = Narou::AppServer.create_address(@options["port"])
@@ -83,18 +111,18 @@ module Command
83
111
  puts
84
112
 
85
113
  push_server.run
86
- unless @options["no-browser"]
87
- Thread.new do
88
- sleep 0.2 until Narou::AppServer.running?
89
- Helper.open_browser(address)
90
- end
91
- end
114
+ open_browser_when_server_boot(address)
115
+ send_rebooted_event_when_connection_recover(push_server)
116
+
92
117
  $stdout = Narou::StreamingLogger.new(push_server)
93
118
  ProgressBar.push_server = push_server
94
119
  Narou::AppServer.push_server = push_server
95
120
  Narou::Worker.instance.start
96
121
  Narou::AppServer.run!
97
122
  push_server.quit
123
+ if Narou::AppServer.request_reboot?
124
+ exit Narou::EXIT_REQUEST_REBOOT
125
+ end
98
126
  rescue Errno::EADDRINUSE => e
99
127
  Helper.open_browser(address) unless @options["no-browser"]
100
128
  STDOUT.puts <<-EOS
@@ -107,6 +135,29 @@ module Command
107
135
  EOS
108
136
  exit Narou::EXIT_ERROR_CODE
109
137
  end
138
+
139
+ def open_browser_when_server_boot(address)
140
+ return if @options["no-browser"]
141
+ Thread.new do
142
+ sleep 0.2 until Narou::AppServer.running?
143
+ Helper.open_browser(address)
144
+ end
145
+ end
146
+
147
+ def send_rebooted_event_when_connection_recover(push_server)
148
+ return unless @rebooted
149
+ Thread.new do |th|
150
+ timeout = Time.now + 20
151
+ # WebSocketのコネクションが回復するまで待つ
152
+ until push_server.connections.count != 0
153
+ sleep 0.2
154
+ th.kill if Time.now > timeout
155
+ end
156
+ puts "<yellow>再起動が完了しました。</yellow>".termcolor
157
+ push_server.send_all(:"server.rebooted")
158
+ end
159
+ end
160
+
110
161
  end
111
162
  end
112
163
 
@@ -17,8 +17,7 @@ class ConverterBase
17
17
  ENGLISH_SENTENCES_CHARACTERS = /[\w.,!?'" &:;_-]+/
18
18
  ENGLISH_SENTENCES_MIN_LENGTH = 8 # この文字数以上アルファベットが続くと半角のまま
19
19
 
20
- attr_reader :use_dakuten_font
21
- attr_accessor :output_text_dir, :subtitles
20
+ attr_accessor :output_text_dir, :subtitles, :data_type
22
21
  attr_accessor :current_index # 現在処理してる subtitles 内でのインデックス
23
22
 
24
23
  def before(io, text_type)
@@ -39,9 +38,9 @@ class ConverterBase
39
38
  @setting = setting
40
39
  @inspector = inspector
41
40
  @illustration = illustration
42
- @use_dakuten_font = false
43
41
  @output_text_dir = nil
44
42
  @subtitles = nil
43
+ @data_type = "text"
45
44
  @current_index = 0
46
45
  reset_member_values
47
46
  end
@@ -452,23 +451,6 @@ class ConverterBase
452
451
  data.gsub!("※※", "※[#米印、1-2-8]")
453
452
  end
454
453
 
455
- #
456
- # 濁点のついてない文字に濁点をつける表現を対応
457
- #
458
- # 濁点つきフォントに部分的に切り替える
459
- #
460
- def convert_dakuten_char_to_font(data)
461
- data.gsub!(/(.)[゛゙]/) do
462
- m1 = $1
463
- if m1 =~ /[ぁ-んァ-ヶι]/ && @setting.enable_dakuten_font
464
- @use_dakuten_font = true
465
- "[#濁点]#{m1}[#濁点終わり]"
466
- else
467
- tcy(m1 + "゛")
468
- end
469
- end
470
- end
471
-
472
454
  #
473
455
  # 小説のルールに沿うように変換
474
456
  #
@@ -869,9 +851,11 @@ class ConverterBase
869
851
  data.gsub!(/(.+?)≪([^≪]+?)≫/) do |match|
870
852
  to_ruby(match, $1, $2, ["≪", "≫"])
871
853
  end
872
- # ()なルビの対処
873
- data.gsub!(/(.+?)(#{AUTO_RUBY_CHARACTERS})/) do |match|
874
- to_ruby(match, $1, $2, ["(", ")"])
854
+ if @data_type == "text"
855
+ # ()なルビの対処
856
+ data.gsub!(/(.+?)(#{AUTO_RUBY_CHARACTERS})/) do |match|
857
+ to_ruby(match, $1, $2, ["(", ")"])
858
+ end
875
859
  end
876
860
  end
877
861
  data.replace(replace_tatesen(data))
@@ -1147,7 +1131,6 @@ class ConverterBase
1147
1131
  convert_special_characters(data)
1148
1132
  convert_fraction_and_date(data)
1149
1133
  modify_kana_ni_to_kanji_ni(data)
1150
- convert_dakuten_char_to_font(data)
1151
1134
  end
1152
1135
 
1153
1136
  def before_convert(io)
@@ -106,11 +106,12 @@ class Database
106
106
  id
107
107
  end
108
108
 
109
- #
110
- # last_update で更新順にソート
111
- #
112
- def sort_by_last_update
113
- values = @database.values.sort_by { |v| v["last_update"] }.reverse
114
- values
109
+ def sort_by(key, reverse: true)
110
+ values = @database.values.sort_by { |v| v[key] }
111
+ if reverse
112
+ values.reverse
113
+ else
114
+ values
115
+ end
115
116
  end
116
117
  end
@@ -85,7 +85,21 @@ class Device
85
85
  end
86
86
 
87
87
  def connecting?
88
- !!get_documents_path
88
+ physical_support? && !!get_documents_path
89
+ end
90
+
91
+ def eject
92
+ if ejectable?
93
+ Device.eject(@device_module::VOLUME_NAME)
94
+ end
95
+ end
96
+
97
+ def self.support_eject?
98
+ respond_to?(:eject)
99
+ end
100
+
101
+ def ejectable?
102
+ Device.support_eject? && connecting?
89
103
  end
90
104
 
91
105
  def find_documents_directory(device_root_dir)
@@ -12,8 +12,7 @@ module Device::Ibunko
12
12
  DISPLAY_NAME = "i文庫"
13
13
 
14
14
  RELATED_VARIABLES = {
15
- "default.enable_half_indent_bracket" => false,
16
- "default.enable_dakuten_font" => false
15
+ "default.enable_half_indent_bracket" => false
17
16
  }
18
17
 
19
18
  #
@@ -14,5 +14,12 @@ module Device::Library
14
14
  end
15
15
  nil
16
16
  end
17
+
18
+ def eject(volume_name)
19
+ status, stdio, stderr = systemu("diskutil eject #{volume_name}")
20
+ unless status.success?
21
+ raise DontConneting, stderr.strip
22
+ end
23
+ end
17
24
  end
18
25
  end
@@ -695,6 +695,8 @@ class Downloader
695
695
  end
696
696
  end
697
697
 
698
+ class DownloaderForceRedirect < StandardError; end
699
+
698
700
  #
699
701
  # HTMLの中から小説が削除されたか非公開なことを示すメッセージを検出する
700
702
  #
@@ -707,22 +709,38 @@ class Downloader
707
709
  def get_toc_source
708
710
  toc_url = @setting["toc_url"]
709
711
  return nil unless toc_url
712
+ max_retry = 5
710
713
  toc_source = ""
711
714
  cookie = @setting["cookie"] || ""
712
715
  open_uri_options = make_open_uri_options("Cookie" => cookie, allow_redirections: :safe)
713
- open(toc_url, open_uri_options) do |toc_fp|
714
- if toc_fp.base_uri.to_s != toc_url
715
- # リダイレクトされた場合。
716
- # ノクターン・ムーンライトのNコードを ncode.syosetu.com に渡すと、novel18.syosetu.com に飛ばされる
717
- # 目次の定義が微妙に ncode.syosetu.com と違うので、設定を取得し直す
718
- s = Downloader.get_sitesetting_by_target(toc_fp.base_uri.to_s)
719
- raise DownloaderNotFoundError unless s # 非公開や削除等でトップページへリダイレクトされる場合がある
720
- @setting.clear # 今まで使っていたのは一旦クリア
721
- @setting = s
722
- toc_url = @setting["toc_url"]
716
+ begin
717
+ open(toc_url, open_uri_options) do |toc_fp|
718
+ if toc_fp.base_uri.to_s != toc_url
719
+ # リダイレクトされた場合。
720
+ # ノクターン・ムーンライトのNコードを ncode.syosetu.com に渡すと、年齢認証のクッションページに飛ばされる
721
+ # 転送先を取得し再度ページを取得し直す
722
+ uri = URI.parse(toc_fp.base_uri.to_s)
723
+ if uri.host == "nl.syosetu.com"
724
+ decode = Hash[URI.decode_www_form(uri.query)]
725
+ toc_url = decode["url"] # 年齢認証確認ページからの転送先
726
+ raise DownloaderForceRedirect
727
+ end
728
+ s = Downloader.get_sitesetting_by_target(toc_fp.base_uri.to_s)
729
+ raise DownloaderNotFoundError unless s # 非公開や削除等でトップページへリダイレクトされる場合がある
730
+ @setting.clear # 今まで使っていたのは一旦クリア
731
+ @setting = s
732
+ toc_url = @setting["toc_url"]
733
+ end
734
+ toc_source = Helper.restor_entity(Helper.pretreatment_source(toc_fp.read, @setting["encoding"]))
735
+ raise DownloaderNotFoundError if Downloader.detect_error_message(@setting, toc_source)
736
+ end
737
+ rescue DownloaderForceRedirect
738
+ max_retry -= 1
739
+ if max_retry >= 0
740
+ retry
741
+ else
742
+ raise
723
743
  end
724
- toc_source = Helper.restor_entity(Helper.pretreatment_source(toc_fp.read, @setting["encoding"]))
725
- raise DownloaderNotFoundError if Downloader.detect_error_message(@setting, toc_source)
726
744
  end
727
745
  toc_source
728
746
  end
@@ -739,7 +757,7 @@ class Downloader
739
757
  # なろうAPIの出力がおかしいので直るまで使用中止
740
758
  info = Narou::API.new(@setting, "t-s-gf-gl-nu-w")
741
759
  else
742
- info = NovelInfo.load(@setting)
760
+ info = NovelInfo.load(@setting, toc_source)
743
761
  end
744
762
  if info
745
763
  raise DownloaderNotFoundError unless info["title"]
@@ -782,7 +800,7 @@ class Downloader
782
800
  $stdout.silence do
783
801
  Command::Tag.execute!(%W(#{@id} --add 404 --color white --no-overwrite-color))
784
802
  end
785
- Command::Freeze.execute!([@id])
803
+ Command::Freeze.execute!([@id, "--on"])
786
804
  end
787
805
  else
788
806
  @stream.error "何らかの理由により目次が取得できませんでした(#{e.message})"
@@ -920,7 +938,8 @@ class Downloader
920
938
  subtitles << {
921
939
  "index" => @setting["index"],
922
940
  "href" => @setting["href"],
923
- "chapter" => @setting["chapter"],
941
+ "chapter" => @setting["chapter"].to_s,
942
+ "subchapter" => @setting["subchapter"].to_s,
924
943
  "subtitle" => @setting["subtitle"].gsub("\n", ""),
925
944
  "file_subtitle" => title_to_filename(@setting["subtitle"]),
926
945
  "subdate" => subdate,
@@ -957,15 +976,16 @@ class Downloader
957
976
  @stream.puts "<bold><green>#{"ID:#{@id} #{get_title}".escape} のDL開始</green></bold>".termcolor
958
977
  save_least_one = false
959
978
  subtitles.each_with_index do |subtitle_info, i|
960
- index, subtitle, file_subtitle, chapter = %w(index subtitle file_subtitle chapter).map { |k|
961
- subtitle_info[k]
962
- }
979
+ index, subtitle, file_subtitle, chapter, subchapter =
980
+ %w(index subtitle file_subtitle chapter subchapter).map { |k|
981
+ subtitle_info[k]
982
+ }
963
983
  info = subtitle_info.dup
964
984
  info["element"] = a_section_download(subtitle_info)
965
985
 
966
- unless chapter.empty?
967
- @stream.puts "#{chapter}"
968
- end
986
+ @stream.puts "#{chapter}" unless chapter.to_s.empty?
987
+ @stream.puts "#{subchapter}" unless subchapter.to_s.empty?
988
+
969
989
  if get_novel_type == NOVEL_TYPE_SERIES
970
990
  if index.to_s.length <= DISPLAY_LIMIT_DIGITS
971
991
  # indexの数字がでかいと見た目がみっともないので特定の桁以内だけ表示する
@@ -1112,6 +1132,9 @@ class Downloader
1112
1132
  end
1113
1133
  sleep(WAITING_TIME_FOR_503)
1114
1134
  retry
1135
+ elsif e.message =~ /^404/
1136
+ @stream.error "#{url} がダウンロード出来ませんでした。時間をおいて再度試してみてください"
1137
+ exit Narou::EXIT_ERROR_CODE
1115
1138
  else
1116
1139
  raise
1117
1140
  end
@@ -0,0 +1,25 @@
1
+
2
+ require "open-uri"
3
+
4
+ # open-uri で http → https へのリダイレクトを有効にする
5
+ require "open_uri_redirections"
6
+
7
+ # open-uri に渡すオプションを生成(必要に応じて extensions/*.rb でオーバーライドする)
8
+ def make_open_uri_options(add)
9
+ add
10
+ end
11
+
12
+ #
13
+ # 安全なファイルの書き込み
14
+ #
15
+ # ファイルに直接上書きしないで、一旦別名で作成してからファイル名変更をすることで、
16
+ # ファイル書き込み中のPCクラッシュ等でデータが飛ばない様にする
17
+ #
18
+ require "securerandom"
19
+
20
+ def File.write(path, string, *options)
21
+ dirname = File.dirname(path)
22
+ temp_path = File.join(dirname, SecureRandom.hex(15))
23
+ super(temp_path, string, *options)
24
+ File.rename(temp_path, path)
25
+ end
@@ -108,11 +108,12 @@ module Helper
108
108
  end
109
109
 
110
110
  #
111
- # ダウンロードしてきたデータを使いやすいように処理
111
+ # ダウンロードした文字列をエンコード及び不正な文字列除去、改行コード統一
112
112
  #
113
113
  def pretreatment_source(src, encoding = Encoding::UTF_8)
114
- src.force_encoding(encoding).gsub("\r", "")
115
- .encode("UTF-16BE", encoding, :invalid => :replace, :undef => :replace, :replace => "?").encode("UTF-8")
114
+ src.force_encoding(encoding)
115
+ .scrub("?")
116
+ .gsub("\r", "")
116
117
  end
117
118
 
118
119
  ENTITIES = { quot: '"', amp: "&", nbsp: " ", lt: "<", gt: ">", copy: "(c)", "#39" => "'" }
@@ -7,15 +7,20 @@ require "uri"
7
7
  require_relative "helper"
8
8
 
9
9
  class HTML
10
- attr_accessor :string, :strip_decoration_tag
10
+ attr_reader :string
11
+ attr_accessor :strip_decoration_tag
11
12
 
12
13
  def initialize(string = "")
13
- @string = string
14
+ self.string = string
14
15
  @illust_current_url = nil
15
16
  @illust_grep_pattern = /<img.+?src=\"(?<src>.+?)\".*?>/i
16
17
  @strip_decoration_tag = false
17
18
  end
18
19
 
20
+ def string=(str)
21
+ @string = str.to_s
22
+ end
23
+
19
24
  #
20
25
  # 挿絵を置換するための設定を変更する
21
26
  #
@@ -34,6 +39,7 @@ class HTML
34
39
  #
35
40
  def to_aozora
36
41
  @string = br_to_aozora
42
+ @string = p_to_aozora
37
43
  @string = ruby_to_aozora
38
44
  unless @strip_decoration_tag
39
45
  @string = b_to_aozora
@@ -41,6 +47,7 @@ class HTML
41
47
  @string = s_to_aozora
42
48
  end
43
49
  @string = img_to_aozora
50
+ @string = em_to_sesame
44
51
  @string = delete_tag
45
52
  @string = Helper.restor_entity(@string)
46
53
  @string
@@ -54,6 +61,11 @@ class HTML
54
61
  text.gsub(/[\r\n]+/, "").gsub(/<br.*?>/i, "\n")
55
62
  end
56
63
 
64
+ # p タグで段落を作ってる場合(brタグが無い場合)に改行されるように
65
+ def p_to_aozora(text = @string)
66
+ text.gsub(/\n?<\/p>/i, "\n")
67
+ end
68
+
57
69
  def ruby_to_aozora(text = @string)
58
70
  text.tr("《》", "≪≫")
59
71
  .gsub(/<ruby>(.+?)<\/ruby>/i) do
@@ -87,4 +99,8 @@ class HTML
87
99
  text
88
100
  end
89
101
  end
102
+
103
+ def em_to_sesame(text = @string)
104
+ text.gsub(%r!<em class="emphasisDots">(.+?)</em>!, "[#傍点]\\1[#傍点終わり]")
105
+ end
90
106
  end