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.
- checksums.yaml +5 -13
- data/.gitignore +1 -0
- data/ChangeLog.md +35 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +100 -0
- data/README.md +28 -39
- data/lib/color.rb +0 -2
- data/lib/command.rb +1 -0
- data/lib/command/convert.rb +33 -4
- data/lib/command/diff.rb +5 -4
- data/lib/command/download.rb +9 -1
- data/lib/command/flag.rb +2 -2
- data/lib/command/list.rb +1 -1
- data/lib/command/mail.rb +3 -3
- data/lib/command/remove.rb +2 -1
- data/lib/command/send.rb +7 -6
- data/lib/command/setting.rb +39 -95
- data/lib/command/tag.rb +25 -13
- data/lib/command/update.rb +6 -1
- data/lib/command/version.rb +5 -1
- data/lib/command/web.rb +111 -0
- data/lib/commandbase.rb +5 -2
- data/lib/commandline.rb +16 -0
- data/lib/converterbase.rb +20 -14
- data/lib/device.rb +5 -4
- data/lib/downloader.rb +68 -39
- data/lib/eventable.rb +72 -0
- data/lib/helper.rb +105 -37
- data/lib/ini.rb +2 -1
- data/lib/input.rb +68 -0
- data/lib/inventory.rb +4 -0
- data/lib/kindlestrip.rb +2 -2
- data/lib/logger.rb +41 -19
- data/lib/narou.rb +10 -0
- data/lib/narou/api.rb +1 -1
- data/lib/novelconverter.rb +8 -21
- data/lib/novelsetting.rb +79 -4
- data/lib/version.rb +1 -1
- data/lib/web/all.rb +12 -0
- data/lib/web/appserver.rb +612 -0
- data/lib/web/helper4web.rb +15 -0
- data/lib/web/progressbar4web.rb +32 -0
- data/lib/web/public/favicon.ico +0 -0
- data/lib/web/public/resources/bootbox.min.js +6 -0
- data/lib/web/public/resources/common.ui.js +143 -0
- data/lib/web/public/resources/dataTables.colVis.js +1113 -0
- data/lib/web/public/resources/help/rect_select.png +0 -0
- data/lib/web/public/resources/help/ssmain.png +0 -0
- data/lib/web/public/resources/help/tag.png +0 -0
- data/lib/web/public/resources/jquery.moveto.js +44 -0
- data/lib/web/public/resources/jquery.outerclick.js +60 -0
- data/lib/web/public/resources/jquery.slidenavbar.js +89 -0
- data/lib/web/public/resources/narou.library.js +815 -0
- data/lib/web/public/resources/narou.ui.js +993 -0
- data/lib/web/public/resources/perfect-scrollbar.min.css +5 -0
- data/lib/web/public/resources/perfect-scrollbar.min.js +4 -0
- data/lib/web/public/resources/shortcut.js +223 -0
- data/lib/web/public/resources/sort_asc.png +0 -0
- data/lib/web/public/resources/sort_desc.png +0 -0
- data/lib/web/public/resources/toggle-switch.css +322 -0
- data/lib/web/public/robots.txt +3 -0
- data/lib/web/public/test/jquery.outerclick.html +72 -0
- data/lib/web/pushserver.rb +110 -0
- data/lib/web/settingmessages.rb +14 -0
- data/lib/web/streaminginput.rb +103 -0
- data/lib/web/streaminglogger.rb +52 -0
- data/lib/web/views/about.haml +11 -0
- data/lib/web/views/help.haml +105 -0
- data/lib/web/views/index.haml +245 -0
- data/lib/web/views/js/widget.erb +74 -0
- data/lib/web/views/layout.haml +49 -0
- data/lib/web/views/novels/setting.haml +177 -0
- data/lib/web/views/settings.haml +115 -0
- data/lib/web/views/style.scss +737 -0
- data/lib/web/views/widget.haml +39 -0
- data/lib/web/web-socket-ruby/.gitignore +1 -0
- data/lib/web/web-socket-ruby/README.txt +75 -0
- data/lib/web/web-socket-ruby/lib/web_socket.rb +601 -0
- data/lib/web/web-socket-ruby/samples/chat_server.rb +58 -0
- data/lib/web/web-socket-ruby/samples/echo_server.rb +33 -0
- data/lib/web/web-socket-ruby/samples/stdio_client.rb +25 -0
- data/lib/web/worker.rb +87 -0
- data/narou.gemspec +36 -3
- data/narou.rb +8 -6
- data/preset/ncode.syosetu.com/n8725k/converter.rb +2 -1
- data/spec/data/convert_test/replace/correct_test_replace.txt +1 -1
- data/spec/data/convert_test/replace/test_replace.txt +1 -1
- data/spec/data/convert_test/ruby/correct_test_ruby.txt +18 -1
- data/spec/data/convert_test/ruby/test_ruby.txt +18 -0
- data/spec/downloader_spec.rb +37 -0
- data/spec/eventable_spec.rb +172 -0
- data/spec/exit_code_spec.rb +67 -0
- data/spec/helper_spec.rb +72 -0
- data/spec/input_spec.rb +76 -0
- data/spec/logger_spec.rb +53 -0
- data/spec/novelsetting_spec.rb +35 -0
- data/spec/worker_spec.rb +56 -0
- data/template/ibunko_novel.txt.erb +2 -2
- data/template/novel.txt.erb +2 -2
- 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
data/lib/novelconverter.rb
CHANGED
@@ -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
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) + ' <span class="tag label label-white" data-tag="" data-toggle="tooltip" title="タグ検索を解除"> </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
|
+
|