narou 1.7.2 → 2.0.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 (100) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +1 -0
  3. data/ChangeLog.md +35 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.txt +100 -0
  6. data/README.md +28 -39
  7. data/lib/color.rb +0 -2
  8. data/lib/command.rb +1 -0
  9. data/lib/command/convert.rb +33 -4
  10. data/lib/command/diff.rb +5 -4
  11. data/lib/command/download.rb +9 -1
  12. data/lib/command/flag.rb +2 -2
  13. data/lib/command/list.rb +1 -1
  14. data/lib/command/mail.rb +3 -3
  15. data/lib/command/remove.rb +2 -1
  16. data/lib/command/send.rb +7 -6
  17. data/lib/command/setting.rb +39 -95
  18. data/lib/command/tag.rb +25 -13
  19. data/lib/command/update.rb +6 -1
  20. data/lib/command/version.rb +5 -1
  21. data/lib/command/web.rb +111 -0
  22. data/lib/commandbase.rb +5 -2
  23. data/lib/commandline.rb +16 -0
  24. data/lib/converterbase.rb +20 -14
  25. data/lib/device.rb +5 -4
  26. data/lib/downloader.rb +68 -39
  27. data/lib/eventable.rb +72 -0
  28. data/lib/helper.rb +105 -37
  29. data/lib/ini.rb +2 -1
  30. data/lib/input.rb +68 -0
  31. data/lib/inventory.rb +4 -0
  32. data/lib/kindlestrip.rb +2 -2
  33. data/lib/logger.rb +41 -19
  34. data/lib/narou.rb +10 -0
  35. data/lib/narou/api.rb +1 -1
  36. data/lib/novelconverter.rb +8 -21
  37. data/lib/novelsetting.rb +79 -4
  38. data/lib/version.rb +1 -1
  39. data/lib/web/all.rb +12 -0
  40. data/lib/web/appserver.rb +612 -0
  41. data/lib/web/helper4web.rb +15 -0
  42. data/lib/web/progressbar4web.rb +32 -0
  43. data/lib/web/public/favicon.ico +0 -0
  44. data/lib/web/public/resources/bootbox.min.js +6 -0
  45. data/lib/web/public/resources/common.ui.js +143 -0
  46. data/lib/web/public/resources/dataTables.colVis.js +1113 -0
  47. data/lib/web/public/resources/help/rect_select.png +0 -0
  48. data/lib/web/public/resources/help/ssmain.png +0 -0
  49. data/lib/web/public/resources/help/tag.png +0 -0
  50. data/lib/web/public/resources/jquery.moveto.js +44 -0
  51. data/lib/web/public/resources/jquery.outerclick.js +60 -0
  52. data/lib/web/public/resources/jquery.slidenavbar.js +89 -0
  53. data/lib/web/public/resources/narou.library.js +815 -0
  54. data/lib/web/public/resources/narou.ui.js +993 -0
  55. data/lib/web/public/resources/perfect-scrollbar.min.css +5 -0
  56. data/lib/web/public/resources/perfect-scrollbar.min.js +4 -0
  57. data/lib/web/public/resources/shortcut.js +223 -0
  58. data/lib/web/public/resources/sort_asc.png +0 -0
  59. data/lib/web/public/resources/sort_desc.png +0 -0
  60. data/lib/web/public/resources/toggle-switch.css +322 -0
  61. data/lib/web/public/robots.txt +3 -0
  62. data/lib/web/public/test/jquery.outerclick.html +72 -0
  63. data/lib/web/pushserver.rb +110 -0
  64. data/lib/web/settingmessages.rb +14 -0
  65. data/lib/web/streaminginput.rb +103 -0
  66. data/lib/web/streaminglogger.rb +52 -0
  67. data/lib/web/views/about.haml +11 -0
  68. data/lib/web/views/help.haml +105 -0
  69. data/lib/web/views/index.haml +245 -0
  70. data/lib/web/views/js/widget.erb +74 -0
  71. data/lib/web/views/layout.haml +49 -0
  72. data/lib/web/views/novels/setting.haml +177 -0
  73. data/lib/web/views/settings.haml +115 -0
  74. data/lib/web/views/style.scss +737 -0
  75. data/lib/web/views/widget.haml +39 -0
  76. data/lib/web/web-socket-ruby/.gitignore +1 -0
  77. data/lib/web/web-socket-ruby/README.txt +75 -0
  78. data/lib/web/web-socket-ruby/lib/web_socket.rb +601 -0
  79. data/lib/web/web-socket-ruby/samples/chat_server.rb +58 -0
  80. data/lib/web/web-socket-ruby/samples/echo_server.rb +33 -0
  81. data/lib/web/web-socket-ruby/samples/stdio_client.rb +25 -0
  82. data/lib/web/worker.rb +87 -0
  83. data/narou.gemspec +36 -3
  84. data/narou.rb +8 -6
  85. data/preset/ncode.syosetu.com/n8725k/converter.rb +2 -1
  86. data/spec/data/convert_test/replace/correct_test_replace.txt +1 -1
  87. data/spec/data/convert_test/replace/test_replace.txt +1 -1
  88. data/spec/data/convert_test/ruby/correct_test_ruby.txt +18 -1
  89. data/spec/data/convert_test/ruby/test_ruby.txt +18 -0
  90. data/spec/downloader_spec.rb +37 -0
  91. data/spec/eventable_spec.rb +172 -0
  92. data/spec/exit_code_spec.rb +67 -0
  93. data/spec/helper_spec.rb +72 -0
  94. data/spec/input_spec.rb +76 -0
  95. data/spec/logger_spec.rb +53 -0
  96. data/spec/novelsetting_spec.rb +35 -0
  97. data/spec/worker_spec.rb +56 -0
  98. data/template/ibunko_novel.txt.erb +2 -2
  99. data/template/novel.txt.erb +2 -2
  100. metadata +213 -29
data/lib/narou.rb CHANGED
@@ -18,12 +18,14 @@ module Narou
18
18
  AOZORAEPUB3_JAR_NAME = "AozoraEpub3.jar"
19
19
  AOZORAEPUB3_DIR = "./AozoraEpub3"
20
20
  PRESET_DIR = "./preset"
21
+ EXIT_ERROR_CODE = 127
21
22
 
22
23
  @@root_dir = nil
23
24
  @@local_setting_dir = nil
24
25
  @@global_setting_dir = nil
25
26
  @@aozora_jar_path = nil
26
27
  @@preset_dir = nil
28
+ @@is_web = false
27
29
 
28
30
  def get_root_dir
29
31
  return @@root_dir if @@root_dir
@@ -157,4 +159,12 @@ module Narou
157
159
  end
158
160
  nil
159
161
  end
162
+
163
+ def web=(bool)
164
+ @@is_web = bool
165
+ end
166
+
167
+ def web?
168
+ @@is_web
169
+ end
160
170
  end
data/lib/narou/api.rb CHANGED
@@ -52,7 +52,7 @@ module Narou
52
52
  result = NovelInfo.load(@setting)
53
53
  unless result
54
54
  error "小説家になろうからデータを取得出来ませんでした"
55
- exit
55
+ exit Narou::EXIT_ERROR_CODE
56
56
  end
57
57
  @api_result = result
58
58
  end
@@ -155,6 +155,14 @@ class NovelConverter
155
155
  Dir.chdir(pwd)
156
156
  end
157
157
 
158
+ # AozoraEpub3はエラーだとしてもexitコードは0なので、
159
+ # 失敗した場合はjavaが実行できない場合と確定できる
160
+ unless res[2].success?
161
+ puts
162
+ error "Javaがインストールされていない可能性があります。EPUBを作成出来ませんでした"
163
+ return :error
164
+ end
165
+
158
166
  stdout_capture = res[0]
159
167
 
160
168
  # Javaの実行環境に由来するであろうエラー
@@ -377,7 +385,6 @@ class NovelConverter
377
385
  @use_dakuten_font = conv.use_dakuten_font
378
386
  conv.replace_by_replace_txt(result)
379
387
 
380
- midashi_save(result)
381
388
  inspect_novel(result)
382
389
 
383
390
  if @output_filename
@@ -410,12 +417,6 @@ class NovelConverter
410
417
  { "title" => title, "author" => author }
411
418
  end
412
419
 
413
- def midashi_save(text)
414
- midashi_list = listup_midashi(text)
415
- path = File.join(@setting.archive_path, "見出しリスト.txt")
416
- File.write(path, midashi_list)
417
- end
418
-
419
420
  def inspect_novel(text)
420
421
  # 行末読点の現在状況を調査する
421
422
  @inspector.inspect_end_touten_conditions(text)
@@ -444,20 +445,6 @@ class NovelConverter
444
445
  @inspector.save
445
446
  end
446
447
 
447
- #
448
- # 見出しのリストを取得する
449
- #
450
- def listup_midashi(data)
451
- list = ""
452
- data.each_line.with_index(1) do |line, no|
453
- if line =~ /[#ここから(?<scale>[大中小])見出し](?<title>.+?)[#ここで\g<scale>見出し終わり]/ ||
454
- line =~ /[#「(?<title>.+?)は(?<scale>[大中小])見出し]/
455
- list << "#{$~[:scale]}:#{no}:#{$~[:title]}\n"
456
- end
457
- end
458
- list
459
- end
460
-
461
448
  #
462
449
  # 最近変換した小説IDを記録更新
463
450
  #
data/lib/novelsetting.rb CHANGED
@@ -29,6 +29,7 @@ class NovelSetting
29
29
  def initialize(archive_path, ignore_force)
30
30
  @archive_path = File.expand_path(archive_path)
31
31
  @ignore_force = ignore_force
32
+ @replace_pattern = []
32
33
  load_settings
33
34
  set_attribute
34
35
  load_replace_pattern
@@ -47,8 +48,8 @@ class NovelSetting
47
48
  ini_path = File.join(@archive_path, INI_NAME)
48
49
  ini = Ini.load_file(ini_path) rescue Ini.load("")
49
50
  force_settings = {}
50
- # 設定値を強制的に上書きするデータの読込
51
51
  unless @ignore_force
52
+ # 設定値を強制的に上書きするデータの読込
52
53
  Inventory.load("local_setting", :local).each { |name, value|
53
54
  if name =~ /^force\.(.+)$/
54
55
  force_settings[$1] = value
@@ -56,10 +57,10 @@ class NovelSetting
56
57
  }
57
58
  end
58
59
  DEFAULT_SETTINGS.each do |element|
59
- name, value = element[:name], element[:value]
60
+ name, value, type = element[:name], element[:value], element[:type]
60
61
  if force_settings.include?(name)
61
62
  @setting[name] = force_settings[name]
62
- elsif ini["global"].include?(name)
63
+ elsif ini["global"].include?(name) && type == Helper.type_of_value(ini["global"][name])
63
64
  @setting[name] = ini["global"][name]
64
65
  else
65
66
  @setting[name] = value
@@ -73,6 +74,28 @@ class NovelSetting
73
74
  end
74
75
  end
75
76
 
77
+ #
78
+ # 設定を保存
79
+ #
80
+ def save_settings
81
+ ini = Ini.new
82
+ ini.filename = File.join(@archive_path, INI_NAME)
83
+ ini.object["global"].merge!(@setting)
84
+ ini.save
85
+ end
86
+
87
+ #
88
+ # 指定された設定の型チェック
89
+ #
90
+ def check_value_of_type(name, value)
91
+ index = DEFAULT_SETTINGS.index { |v| v[:name] == name }
92
+ return unless index
93
+ default = DEFAULT_SETTINGS[index]
94
+ if default && default[:type] != Helper.type_of_value(value)
95
+ raise Helper::InvalidVariableType, default[:type]
96
+ end
97
+ end
98
+
76
99
  #
77
100
  # 設定データ用アクセサ定義
78
101
  #
@@ -84,17 +107,30 @@ class NovelSetting
84
107
  end
85
108
 
86
109
  def #{key}=(value)
110
+ check_value_of_type("#{key}", value)
87
111
  @setting["#{key}"] = value
88
112
  end
89
113
  EOS
90
114
  end
91
115
  end
92
116
 
117
+ #
118
+ # 配列風のアクセサ定義
119
+ #
120
+ def [](name)
121
+ @setting[name]
122
+ end
123
+
124
+ def []=(name, value)
125
+ check_value_of_type(name, value)
126
+ @setting[name] = value
127
+ end
128
+
93
129
  #
94
130
  # replace.txt による置換定義を読み込む
95
131
  #
96
132
  def load_replace_pattern
97
- @replace_pattern = []
133
+ @replace_pattern.clear
98
134
  replace_txt_path = File.join(@archive_path, REPLACE_NAME)
99
135
  if File.exist?(replace_txt_path)
100
136
  open(replace_txt_path, "r:BOM|UTF-8") do |fp|
@@ -108,149 +144,188 @@ class NovelSetting
108
144
  end
109
145
  end
110
146
  end
147
+ @replace_pattern
148
+ end
149
+
150
+ #
151
+ # replace.txt に設定を書き戻す
152
+ #
153
+ def save_replace_pattern
154
+ replace_txt_path = File.join(@archive_path, REPLACE_NAME)
155
+ buff = @replace_pattern.each_with_object("\t").map(&:join).join("\n")
156
+ File.write(replace_txt_path, buff)
111
157
  end
112
158
 
113
159
  DEFAULT_SETTINGS = [
114
160
  # name: 変数名
161
+ # type: 変数の型
115
162
  # value: 初期値
116
163
  # help: 説明(setting.ini に書き出される)
117
164
  {
118
165
  name: "enable_convert_num_to_kanji",
166
+ type: :boolean,
119
167
  value: true,
120
168
  help: "数字の漢数字変換を有効に"
121
169
  },
122
170
  {
123
171
  name: "enable_kanji_num_with_units",
172
+ type: :boolean,
124
173
  value: true,
125
174
  help: "漢数字変換した場合、千・万などに変換するか"
126
175
  },
127
176
  {
128
177
  name: "kanji_num_with_units_lower_digit_zero",
178
+ type: :integer,
129
179
  value: 3,
130
180
  help: "〇(ゼロ)が最低この数字以上付いてないと千・万などをつける対象にしない"
131
181
  },
132
182
  {
133
183
  name: "enable_alphabet_force_zenkaku",
184
+ type: :boolean,
134
185
  value: false,
135
186
  help: "アルファベットを強制的に全角にする。falseの場合英文は半角、それ以外は全角になる"
136
187
  },
137
188
  {
138
189
  name: "enable_half_indent_bracket",
190
+ type: :boolean,
139
191
  value: true,
140
192
  help: "行頭かぎ括弧に二分アキを挿入する"
141
193
  },
142
194
  {
143
195
  name: "enable_auto_indent",
196
+ type: :boolean,
144
197
  value: true,
145
198
  help: "自動行頭字下げ機能。行頭字下げが行われているかを判断し、適切に行頭字下げをする"
146
199
  },
147
200
  {
148
201
  name: "enable_auto_join_in_brackets",
202
+ type: :boolean,
149
203
  value: true,
150
204
  help: "かぎ括弧内自動連結を有効に\n例)\n「~~~!\n ***?」 → 「~~~! ***?」"
151
205
  },
152
206
  {
153
207
  name: "enable_inspect_invalid_openclose_brackets",
208
+ type: :boolean,
154
209
  value: false,
155
210
  help: "かぎ括弧内のとじ開きが正しくされているかどうか調査する"
156
211
  },
157
212
  {
158
213
  name: "enable_auto_join_line",
214
+ type: :boolean,
159
215
  value: true,
160
216
  help: "行末が読点で終わっている部分を出来るだけ連結する"
161
217
  },
162
218
  {
163
219
  name: "enable_enchant_midashi",
220
+ type: :boolean,
164
221
  value: true,
165
222
  help: "[#改ページ]直後の行に中見出しを付与する(テキストファイルを直接変換する場合のみの設定)"
166
223
  },
167
224
  {
168
225
  name: "enable_author_comments",
226
+ type: :boolean,
169
227
  value: true,
170
228
  help: "作者コメントを検出するか(テキストファイルを直接変換する場合のみの設定)"
171
229
  },
172
230
  {
173
231
  name: "enable_erase_introduction",
232
+ type: :boolean,
174
233
  value: false,
175
234
  help: "前書きを削除するか"
176
235
  },
177
236
  {
178
237
  name: "enable_erase_postscript",
238
+ type: :boolean,
179
239
  value: false,
180
240
  help: "後書きを削除するか"
181
241
  },
182
242
  {
183
243
  name: "enable_ruby",
244
+ type: :boolean,
184
245
  value: true,
185
246
  help: "ルビ処理を有効に"
186
247
  },
187
248
  {
188
249
  name: "enable_illust",
250
+ type: :boolean,
189
251
  value: true,
190
252
  help: "挿絵タグを有効にする(false なら削除)"
191
253
  },
192
254
  {
193
255
  name: "enable_transform_fraction",
256
+ type: :boolean,
194
257
  value: false,
195
258
  help: "○/×表記を×分の○表記に変換する。日付表記(10/23)と誤爆しやすいので注意"
196
259
  },
197
260
  {
198
261
  name: "enable_transform_date",
262
+ type: :boolean,
199
263
  value: false,
200
264
  help: "日付表記(20yy/mm/dd)を任意の形式(date_formatで指定)に変換する"
201
265
  },
202
266
  {
203
267
  name: "date_format",
268
+ type: :string,
204
269
  value: "%Y年%m月%d日",
205
270
  help: "書式は http://bit.ly/1m5e3w7 を参考"
206
271
  },
207
272
  {
208
273
  name: "enable_convert_horizontal_ellipsis",
274
+ type: :boolean,
209
275
  value: true,
210
276
  help: "中黒(・)を並べて三点リーダーもどきにしているのを三点リーダーに変換する"
211
277
  },
212
278
  {
213
279
  name: "enable_convert_page_break",
280
+ type: :boolean,
214
281
  value: false,
215
282
  help: "`to_page_break_threshold` で設定した個数以上連続する空行を改ページに変換する"
216
283
  },
217
284
  {
218
285
  name: "to_page_break_threshold",
286
+ type: :integer,
219
287
  value: 10,
220
288
  help: "ここで設定した値が `enable_convert_page_break` に反映される"
221
289
  },
222
290
  {
223
291
  name: "enable_dakuten_font",
292
+ type: :boolean,
224
293
  value: true,
225
294
  help: "濁点フォントを使用するか。false の場合は縦中横による擬似表現を使用する"
226
295
  },
227
296
  {
228
297
  name: "enable_display_end_of_book",
298
+ type: :boolean,
229
299
  value: true,
230
300
  help: "小説の最後に本を読み終わった表示をするかどうか"
231
301
  },
232
302
  {
233
303
  name: "enable_add_date_to_title",
304
+ type: :boolean,
234
305
  value: false,
235
306
  help: "変換後の小説のタイトルに更新日の日付を付加するかどうか"
236
307
  },
237
308
  {
238
309
  name: "title_date_format",
310
+ type: :string,
239
311
  value: "(%-m/%-d)",
240
312
  help: "enable_add_date_to_title で付与する日付のフォーマット。書式は http://bit.ly/1m5e3w7 を参照"
241
313
  },
242
314
  {
243
315
  name: "title_date_align",
316
+ type: :string,
244
317
  value: "right",
245
318
  help: "enable_add_date_to_title で付与する日付の位置。left か right"
246
319
  },
247
320
  {
248
321
  name: "enable_ruby_youon_to_big",
322
+ type: :boolean,
249
323
  value: false,
250
324
  help: "ルビの拗音(ぁ、ぃ等)を商業書籍のように大きくするかどうか"
251
325
  },
252
326
  {
253
327
  name: "enable_pack_blank_line",
328
+ type: :boolean,
254
329
  value: true,
255
330
  help: "縦書きで読みやすいように空行を減らすかどうか"
256
331
  },
data/lib/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  # Copyright 2013 whiteleaf. All rights reserved.
4
4
  #
5
5
 
6
- Version = "1.7.2"
6
+ Version = "2.0.0"
7
7
 
data/lib/web/all.rb ADDED
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2013 whiteleaf. All rights reserved.
4
+ #
5
+
6
+ require_relative "appserver"
7
+ require_relative "pushserver"
8
+ require_relative "streaminglogger"
9
+ require_relative "streaminginput"
10
+ require_relative "progressbar4web"
11
+ require_relative "helper4web"
12
+
@@ -0,0 +1,612 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2013 whiteleaf. All rights reserved.
4
+ #
5
+
6
+ require "socket"
7
+ require "sinatra/base"
8
+ require "sinatra/json"
9
+ if $debug
10
+ require "sinatra/reloader"
11
+ require "pry"
12
+ end
13
+ require "haml"
14
+ require "sass"
15
+ require_relative "../logger"
16
+ require_relative "../CommandLine"
17
+ require_relative "../inventory"
18
+ require_relative "worker"
19
+ require_relative "pushserver"
20
+ require_relative "settingmessages"
21
+
22
+ module Narou::ServerHelpers
23
+ #
24
+ # タグをHTMLで装飾する
25
+ #
26
+ def decorate_tags(tags)
27
+ tags.map do |tag|
28
+ %!<span class="tag label label-#{Command::Tag.get_color(tag)}" data-tag="#{escape_html(tag)}">#{escape_html(tag)}</span>!
29
+ end.join(" ")
30
+ end
31
+
32
+ #
33
+ # Rubyバージョンを構築
34
+ #
35
+ def build_ruby_version
36
+ begin
37
+ `"#{RbConfig.ruby}" -v`.strip
38
+ rescue
39
+ config = RbConfig::CONFIG
40
+ "ruby #{RUBY_VERSION}p#{config["PATCHLEVEL"]} [#{RUBY_PLATFORM}]"
41
+ end
42
+ end
43
+
44
+ #
45
+ # 有効な novel ID だけの配列を生成する
46
+ # ID が指定されなかったか、1件も存在しない場合は nil を返す
47
+ #
48
+ def select_valid_novel_ids(ids)
49
+ return nil unless ids.kind_of?(Array)
50
+ result = ids.select do |id|
51
+ id =~ /^\d+$/
52
+ end
53
+ result.empty? ? nil : result
54
+ end
55
+
56
+ #
57
+ # フォーム情報の真偽値データを実際のデータに変換
58
+ #
59
+ def convert_on_off_to_boolean(str)
60
+ case str
61
+ when "on"
62
+ true
63
+ when "off"
64
+ false
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ #
71
+ # nil true false を nil on off という文字列に変換
72
+ #
73
+ def convert_boolean_to_on_off(bool)
74
+ case bool
75
+ when TrueClass
76
+ "on"
77
+ when FalseClass
78
+ "off"
79
+ else
80
+ "nil"
81
+ end
82
+ end
83
+
84
+ #
85
+ # HTMLエスケープヘルパー
86
+ #
87
+ def h(text)
88
+ Rack::Utils.escape_html(text)
89
+ end
90
+
91
+ #
92
+ # 与えられたデータが真偽値だった場合、設定画面用に「する」「しない」に変換する
93
+ # 真偽値ではなかった場合、そのまま返す
94
+ #
95
+ def value_to_msg(value)
96
+ case value
97
+ when TrueClass
98
+ "する"
99
+ when FalseClass
100
+ "しない"
101
+ else
102
+ value
103
+ end
104
+ end
105
+ end
106
+
107
+ class Narou::AppServer < Sinatra::Base
108
+ register Sinatra::Reloader if $debug
109
+ helpers Narou::ServerHelpers
110
+
111
+ configure do
112
+ set :app_file, __FILE__
113
+ set :erb, trim: "-"
114
+ enable :protection
115
+ enable :sessions
116
+
117
+ set(:version) do
118
+ Command::Version.create_version_string
119
+ end
120
+
121
+ set :environment, :production unless $debug
122
+ end
123
+
124
+ def self.push_server=(server)
125
+ @@push_server = server
126
+ end
127
+
128
+ #
129
+ # サーバのアドレスを生成
130
+ #
131
+ # portは初回起動時にランダムで設定する。次回からは同じ設定を引き継ぐ。
132
+ # bindは自分で設定する場合は narou s server-bind=address で行う。
133
+ # bindは設定しなかった場合は起動したPCのプライベートIPアドレスが設定される。
134
+ # この場合はLAN内からアクセス出来る。
135
+ # bindがlocalhostの場合は実際には127.0.0.1で処理される。(起動したPCでしか
136
+ # アクセス出来ない)
137
+ # 0.0.0.0 を指定した場合はアクセスに制限がかからない(外部からアクセス可能)
138
+ # セキュリティ上オススメ出来ない。
139
+ #
140
+ def self.create_address(user_port = nil)
141
+ global_setting = Inventory.load("global_setting", :global)
142
+ port, bind = global_setting["server-port"], global_setting["server-bind"]
143
+ port = user_port if user_port
144
+ ipaddress = my_ipaddress
145
+ unless port
146
+ port = rand(4000..65000)
147
+ global_setting["server-port"] = port
148
+ global_setting.save
149
+ end
150
+ bind = "127.0.0.1" if bind == "localhost"
151
+ host = bind ? bind : ipaddress
152
+ set :port, port
153
+ set :bind, host
154
+ {
155
+ host: host,
156
+ port: port
157
+ }
158
+ end
159
+
160
+ #
161
+ # 自分のIPアドレス取得
162
+ #
163
+ # 参考:http://qiita.com/saltheads/items/cc49fcf2af37cb277c4f
164
+ #
165
+ def self.my_ipaddress
166
+ @@__ipaddress ||= -> {
167
+ udp = UDPSocket.new
168
+ udp.connect("128.0.0.0", 7)
169
+ adrs = Socket.unpack_sockaddr_in(udp.getsockname)[1]
170
+ udp.close
171
+ adrs
172
+ }.call
173
+ end
174
+
175
+ # ===================================================================
176
+ # ルーティング
177
+ # ===================================================================
178
+
179
+ before do
180
+ Narou::Worker.push_as_system_worker do
181
+ Inventory.clear
182
+ end
183
+ end
184
+
185
+ get "/" do
186
+ setting = Inventory.load("server_setting", :global)
187
+ @is_first_access = !setting["already-accessed"]
188
+ if @is_first_access
189
+ setting["already-accessed"] = true
190
+ setting.save
191
+ end
192
+ haml :index, layout: true
193
+ end
194
+
195
+ get "/style.css" do
196
+ scss :style
197
+ end
198
+
199
+ before "/settings" do
200
+ @title = "環境設定"
201
+ @view_invisible = params["view_invisible"] == "1"
202
+ @setting_variables = Command::Setting.get_setting_variables
203
+ @error_list = {}
204
+ end
205
+
206
+ post "/settings" do
207
+ built_arguments = []
208
+ output = ""
209
+ device = params.delete("device")
210
+ [:local, :global].each do |scope|
211
+ @setting_variables[scope].each do |name, info|
212
+ param_data = params[name]
213
+ argument = ""
214
+ if info[0] == :boolean
215
+ # :boolean 用のフォームデータは on, off, nil で渡される。
216
+ # ただしチェックボックスはチェックした時だけ on が渡されるので、
217
+ # 何もデータが無い=off を選択したと判断する。
218
+ # 隠しデータの場合は hidden として on, off, nil が必ず送信されるので、
219
+ # それで判断できる。
220
+ if param_data
221
+ argument = convert_on_off_to_boolean(param_data).to_s
222
+ else
223
+ argument = "false"
224
+ end
225
+ else
226
+ argument = param_data
227
+ end
228
+ built_arguments << "#{name}=#{argument}"
229
+ end
230
+ end
231
+ # device の項目だけ関連項目を変更するという挙動をするため、変更を上書き
232
+ # されないように最後にまわす
233
+ built_arguments << "device=#{device}" if device
234
+ unless built_arguments.empty?
235
+ $stdout.silence do
236
+ setting = Command::Setting.new
237
+ setting.on(:error) do |msg, name|
238
+ if name
239
+ @error_list[name] = msg
240
+ end
241
+ end
242
+ begin
243
+ setting.execute(built_arguments)
244
+ rescue SystemExit
245
+ end
246
+ end
247
+ end
248
+ if @error_list.empty?
249
+ session[:alert] = [ "保存が完了しました", "success" ]
250
+ else
251
+ session[:alert] = [ "#{@error_list.size}個の設定にエラーがありました", "danger" ]
252
+ end
253
+ haml :settings
254
+ end
255
+
256
+ get "/settings" do
257
+ haml :settings
258
+ end
259
+
260
+ get "/help" do
261
+ @title = "ヘルプ"
262
+ haml :help
263
+ end
264
+
265
+ get "/about" do
266
+ @narourb_version = settings.version
267
+ @ruby_version = build_ruby_version
268
+ haml :about, layout: false
269
+ end
270
+
271
+ post "/shutdown" do
272
+ self.class.quit!
273
+ "シャットダウンしました。再起動するまで操作は出来ません"
274
+ end
275
+
276
+ before "/novels/:id/*" do
277
+ @id = params[:id]
278
+ not_found unless @id =~ /^\d+$/
279
+ @data = Downloader.get_data_by_target(@id)
280
+ not_found unless @data
281
+ end
282
+
283
+ before "/novels/:id/setting" do
284
+ @title = "小説の変換設定"
285
+ @novel_title = @data["title"]
286
+ @setting_variables = []
287
+ @error_list = {}
288
+ @novel_setting = NovelSetting.load(@id, true)
289
+ @default_settings = NovelSetting::DEFAULT_SETTINGS
290
+ @force_settings = Inventory.load("local_setting", :local).select { |n| n.start_with?("force.") }
291
+ @replace_pattern = @novel_setting.load_replace_pattern
292
+ end
293
+
294
+ post "/novels/:id/setting" do
295
+ # 変換設定保存
296
+ @default_settings.each do |info|
297
+ name, type = info[:name], info[:type]
298
+ param_data = params[name]
299
+ value = nil
300
+ begin
301
+ case type
302
+ when :boolean
303
+ if param_data
304
+ value = convert_on_off_to_boolean(param_data)
305
+ else
306
+ value = false
307
+ end
308
+ else
309
+ value = Helper.string_cast_to_type(param_data, type)
310
+ end
311
+ @novel_setting[name] = value
312
+ rescue Helper::InvalidVariableType => e
313
+ @error_list[name] = e.message
314
+ end
315
+ end
316
+ @novel_setting.save_settings
317
+
318
+ # 置換設定保存
319
+ params_replace_pattern = params["replace_pattern"]
320
+ @novel_setting.replace_pattern.clear
321
+ if params_replace_pattern.kind_of?(Array)
322
+ params_replace_pattern.each do |pattern|
323
+ left, right = pattern["left"].strip, pattern["right"].strip
324
+ next if left == ""
325
+ @novel_setting.replace_pattern << [left, right]
326
+ end
327
+ end
328
+ @novel_setting.save_replace_pattern
329
+
330
+ if @error_list.empty?
331
+ session[:alert] = [ "保存が完了しました", "success" ]
332
+ else
333
+ session[:alert] = [ "#{@error_list.size}個の設定にエラーがありました", "danger" ]
334
+ end
335
+
336
+ haml :"novels/setting"
337
+ end
338
+
339
+ get "/novels/:id/setting" do
340
+ haml :"novels/setting"
341
+ end
342
+
343
+ get "/novels/:id/download" do
344
+ device = Narou.get_device
345
+ ext = device ? device.ebook_file_ext : ".epub"
346
+ path = Narou.get_ebook_file_path(@id, ext)
347
+ if File.exist?(path)
348
+ send_file(path, filename: File.basename(path))
349
+ else
350
+ not_found
351
+ end
352
+ end
353
+
354
+ # -------------------------------------------------------------------------------
355
+ # API's
356
+ # -------------------------------------------------------------------------------
357
+
358
+ get "/api/list" do
359
+ database_values = Database.instance.get_object.values
360
+ json_objects = {
361
+ draw: 1, recordsTotal: database_values.count, recordsFiltered: database_values.count
362
+ }
363
+ json_objects[:data] =
364
+ database_values.map do |data|
365
+ tags = data["tags"] || []
366
+ id = data["id"]
367
+ {
368
+ id: id.to_s,
369
+ last_update: data["last_update"].to_i,
370
+ title: escape_html(data["title"]),
371
+ author: escape_html(data["author"]),
372
+ sitename: data["sitename"],
373
+ toc_url: %!<a href="#{escape_html(data["toc_url"])}" target="_blank" ! +
374
+ %!data-toggle="tooltip" data-placement="top" title="#{data["toc_url"]}">! +
375
+ %!<span class="glyphicon glyphicon-link"></span></a>!,
376
+ novel_type: data["novel_type"] == 2 ? "短編" : "連載",
377
+ tags: (tags.empty? ? "" : decorate_tags(tags) + '&nbsp;<span class="tag label label-white" data-tag="" data-toggle="tooltip" title="タグ検索を解除">&nbsp;</span>'),
378
+ status: [
379
+ Narou.novel_frozen?(id) ? "凍結" : nil,
380
+ tags.include?("end") ? "完結" : nil,
381
+ tags.include?("404") ? "削除" : nil,
382
+ ].compact.join(", "),
383
+ download: %!<a href="/novels/#{id}/download"><span class="glyphicon glyphicon-book"></span></a>!,
384
+ frozen: Narou.novel_frozen?(id),
385
+ new_arrivals_date: data["new_arrivals_date"] ? data["new_arrivals_date"].to_i : nil
386
+ }
387
+ end
388
+ json json_objects
389
+ end
390
+
391
+ post "/api/convert" do
392
+ ids = select_valid_novel_ids(params["ids"]) or pass
393
+ Narou::Worker.push do
394
+ CommandLine.run!(["convert", "--no-open", ids])
395
+ end
396
+ end
397
+
398
+ post "/api/download" do
399
+ target = params["target"] or pass
400
+ Narou::Worker.push do
401
+ CommandLine.run!(["download", target])
402
+ @@push_server.send_all("table.reload" => true)
403
+ end
404
+ end
405
+
406
+ post "/api/download_force" do
407
+ ids = select_valid_novel_ids(params["ids"]) or pass
408
+ Narou::Worker.push do
409
+ CommandLine.run!(["download", "--force", ids])
410
+ @@push_server.send_all("table.reload" => true)
411
+ end
412
+ end
413
+
414
+ post "/api/update" do
415
+ Narou::Worker.push do
416
+ CommandLine.run!(["update"])
417
+ @@push_server.send_all("table.reload" => true)
418
+ end
419
+ end
420
+
421
+ post "/api/update_select" do
422
+ ids = select_valid_novel_ids(params["ids"]) or pass
423
+ Narou::Worker.push do
424
+ CommandLine.run!(["update", ids])
425
+ @@push_server.send_all("table.reload" => true)
426
+ end
427
+ end
428
+
429
+ post "/api/send" do
430
+ Narou::Worker.push do
431
+ CommandLine.run!(["send"])
432
+ end
433
+ end
434
+
435
+ post "/api/send_select" do
436
+ ids = select_valid_novel_ids(params["ids"]) or pass
437
+ Narou::Worker.push do
438
+ CommandLine.run!(["send", ids])
439
+ end
440
+ end
441
+
442
+ post "/api/freeze" do
443
+ ids = select_valid_novel_ids(params["ids"]) or pass
444
+ Narou::Worker.push do
445
+ CommandLine.run!(["freeze", ids])
446
+ @@push_server.send_all("table.reload" => true)
447
+ end
448
+ end
449
+
450
+ post "/api/freeze_on" do
451
+ ids = select_valid_novel_ids(params["ids"]) or pass
452
+ Narou::Worker.push do
453
+ CommandLine.run!(["freeze", "--on", ids])
454
+ @@push_server.send_all("table.reload" => true)
455
+ end
456
+ end
457
+
458
+ post "/api/freeze_off" do
459
+ ids = select_valid_novel_ids(params["ids"]) or pass
460
+ Narou::Worker.push do
461
+ CommandLine.run!(["freeze", "--off", ids])
462
+ @@push_server.send_all("table.reload" => true)
463
+ end
464
+ end
465
+
466
+ post "/api/remove" do
467
+ ids = select_valid_novel_ids(params["ids"]) or pass
468
+ Narou::Worker.push do
469
+ CommandLine.run!(["remove", "--yes", ids])
470
+ @@push_server.send_all("table.reload" => true)
471
+ end
472
+ end
473
+
474
+ post "/api/remove_with_file" do
475
+ ids = select_valid_novel_ids(params["ids"]) or pass
476
+ Narou::Worker.push do
477
+ CommandLine.run!(["remove", "--yes", "--with-file", ids])
478
+ @@push_server.send_all("table.reload" => true)
479
+ end
480
+ end
481
+
482
+ post "/api/diff" do
483
+ ids = select_valid_novel_ids(params["ids"]) or pass
484
+ Narou::Worker.push do
485
+ # diff コマンドは1度に一つのIDしか受け取らないので
486
+ ids.each do |id|
487
+ # セキュリティ的にWEB UIでは独自の差分表示のみ使う
488
+ CommandLine.run!(["diff", "--no-tool", id])
489
+ print "<hr>"
490
+ end
491
+ end
492
+ end
493
+
494
+ post "/api/inspect" do
495
+ ids = select_valid_novel_ids(params["ids"]) or pass
496
+ Narou::Worker.push do
497
+ CommandLine.run!(["inspect", ids])
498
+ end
499
+ end
500
+
501
+ post "/api/folder" do
502
+ ids = select_valid_novel_ids(params["ids"]) or pass
503
+ Narou::Worker.push do
504
+ CommandLine.run!(["folder", ids])
505
+ end
506
+ end
507
+
508
+ post "/api/backup" do
509
+ ids = select_valid_novel_ids(params["ids"]) or pass
510
+ Narou::Worker.push do
511
+ CommandLine.run!(["backup", ids])
512
+ end
513
+ end
514
+
515
+ post "/api/clear_history" do
516
+ Narou::PushServer.instance.clear_history
517
+ end
518
+
519
+ get "/api/tag_list" do
520
+ result = '<div><span class="tag label label-default" data-tag="">タグ検索を解除</span></div>'
521
+ tagname_list = Command::Tag.get_tag_list.keys
522
+ tagname_list.each do |tagname|
523
+ result << "<div>#{decorate_tags([tagname])}</div>"
524
+ end
525
+ result
526
+ end
527
+
528
+ post "/api/taginfo.json" do
529
+ ids = select_valid_novel_ids(params["ids"]) or pass
530
+ ids.map!(&:to_i)
531
+ database = Database.instance
532
+ tag_info = {}
533
+ database.each do |_, data|
534
+ tags = data["tags"] || []
535
+ tags.each do |tag|
536
+ tag_info[tag] ||= {
537
+ count: 0,
538
+ html: decorate_tags([tag])
539
+ }
540
+ if ids.include?(data["id"])
541
+ tag_info[tag][:count] += 1
542
+ end
543
+ end
544
+ end
545
+ json tag_info
546
+ end
547
+
548
+ post "/api/edit_tag" do
549
+ ids = select_valid_novel_ids(params["ids"]) or pass
550
+ # key と value を重複を維持したまま反転
551
+ invert_states = params["states"].inject({}) { |h,(k,v)| (h[v] ||= []) << k; h }
552
+ $stdout.silence do
553
+ invert_states.each do |state, tags|
554
+ case state.to_i
555
+ when 0
556
+ # タグを削除
557
+ CommandLine.run!(["tag", "--delete", tags.join(" "), ids])
558
+ when 1
559
+ # 現状を維持(何もしない)
560
+ when 2
561
+ # タグを追加
562
+ CommandLine.run!(["tag", "--add", tags.join(" "), ids])
563
+ end
564
+ end
565
+ end
566
+ @@push_server.send_all(:"table.reload")
567
+ @@push_server.send_all(:"tag.updateCanvas")
568
+ end
569
+
570
+ get "/api/get_queue_size" do
571
+ res = {
572
+ size: Narou::Worker.instance.size
573
+ }
574
+ json res
575
+ end
576
+
577
+ get "/js/widget.js" do
578
+ if %w(download).include?(params["mode"])
579
+ content_type :js
580
+ erb :"js/widget"
581
+ else
582
+ error("invaid mode")
583
+ end
584
+ end
585
+
586
+ ALLOW_HOSTS = [].tap do |hosts|
587
+ Downloader.load_settings.each do |s|
588
+ hosts << s["domain"]
589
+ end
590
+ hosts.freeze
591
+ end
592
+
593
+ before "/widget/*" do
594
+ from = params["from"]
595
+ if ALLOW_HOSTS.include?(from)
596
+ headers "X-Frame-Options" => "ALLOW-FROM http://#{from}/"
597
+ else
598
+ headers "X-Frame-Options" => "DENY"
599
+ halt
600
+ end
601
+ end
602
+
603
+ get "/widget/download" do
604
+ target = params["target"] or error("targetを指定して下さい")
605
+ Narou::Worker.push do
606
+ CommandLine.run!(["download", target])
607
+ @@push_server.send_all(:"table.reload")
608
+ end
609
+ haml :widget, :layout => nil
610
+ end
611
+ end
612
+