├── .circleci └── config.yml ├── .gitattributes ├── .gitignore ├── .haml-lint.yml ├── .reek ├── .rspec ├── .rubocop.yml ├── .scss-lint.yml ├── ChangeLog.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── narou ├── lib ├── backtracer.rb ├── color.rb ├── command.rb ├── command │ ├── alias.rb │ ├── backup.rb │ ├── browser.rb │ ├── clean.rb │ ├── console.rb │ ├── convert.rb │ ├── csv.rb │ ├── diff.rb │ ├── download.rb │ ├── folder.rb │ ├── freeze.rb │ ├── help.rb │ ├── init.rb │ ├── inspect.rb │ ├── list.rb │ ├── list │ │ └── novel_decorator.rb │ ├── log.rb │ ├── log │ │ └── tail.rb │ ├── mail.rb │ ├── remove.rb │ ├── restore346.rb │ ├── send.rb │ ├── setting.rb │ ├── tag.rb │ ├── trace.rb │ ├── update.rb │ ├── update │ │ ├── general_lastup_updater.rb │ │ ├── hotentry_manager.rb │ │ └── interval.rb │ ├── version.rb │ └── web.rb ├── commandbase.rb ├── commandline.rb ├── converterbase.rb ├── database.rb ├── device.rb ├── device │ ├── epub.rb │ ├── ibooks.rb │ ├── ibunko.rb │ ├── kindle.rb │ ├── kobo.rb │ ├── library │ │ ├── cygwin.rb │ │ ├── linux.rb │ │ ├── mac.rb │ │ ├── windows.rb │ │ └── windows │ │ │ └── eject.rb │ └── reader.rb ├── diffviewer.rb ├── downloader.rb ├── eventable.rb ├── extension.rb ├── extensions │ ├── jruby.rb │ ├── monkey_patches.rb │ ├── monkey_patches │ │ └── pathname.rb │ ├── windows.rb │ └── windows_write_color.rb ├── helper.rb ├── html.rb ├── illustration.rb ├── ini.rb ├── input.rb ├── inspector.rb ├── inventory.rb ├── kindlestrip.rb ├── loadconverter.rb ├── mailer.rb ├── mixin │ ├── all.rb │ ├── locker.rb │ └── output_error.rb ├── narou.rb ├── narou │ └── api.rb ├── narou_logger.rb ├── novelconverter.rb ├── novelinfo.rb ├── novelsetting.rb ├── progressbar.rb ├── sitesetting.rb ├── sitesettinghandler.rb ├── template.rb ├── version.rb ├── web │ ├── all.rb │ ├── appserver.rb │ ├── helper4web.rb │ ├── progressbar4web.rb │ ├── public │ │ ├── favicon.ico │ │ ├── resources │ │ │ ├── bootbox.min.js │ │ │ ├── common.ui.js │ │ │ ├── dataTables.colVis.js │ │ │ ├── default-style.css │ │ │ ├── help │ │ │ │ ├── bookmarklet1.png │ │ │ │ ├── bookmarklet2.png │ │ │ │ ├── rect_select.png │ │ │ │ ├── ssmain.png │ │ │ │ └── tag.png │ │ │ ├── images │ │ │ │ ├── dl_button0.gif │ │ │ │ ├── dl_button1.gif │ │ │ │ └── dl_button2.gif │ │ │ ├── jquery.moveto.js │ │ │ ├── jquery.outerclick.js │ │ │ ├── jquery.slidenavbar.js │ │ │ ├── narou.library.js │ │ │ ├── narou.queue.js │ │ │ ├── narou.ui.js │ │ │ ├── perfect-scrollbar.min.css │ │ │ ├── perfect-scrollbar.min.js │ │ │ ├── shortcut.js │ │ │ ├── sort_asc.png │ │ │ ├── sort_desc.png │ │ │ ├── sprintf.js │ │ │ └── toggle-switch.css │ │ ├── robots.txt │ │ ├── test │ │ │ └── jquery.outerclick.html │ │ └── theme │ │ │ ├── Cerulean │ │ │ ├── bootstrap.min.css │ │ │ └── style.css │ │ │ ├── Darkly │ │ │ ├── bootstrap.min.css │ │ │ └── style.css │ │ │ ├── Readable │ │ │ ├── bootstrap.min.css │ │ │ └── style.css │ │ │ ├── Slate │ │ │ ├── bootstrap.min.css │ │ │ └── style.css │ │ │ ├── Superhero │ │ │ ├── bootstrap.min.css │ │ │ └── style.css │ │ │ ├── United │ │ │ ├── bootstrap.min.css │ │ │ └── style.css │ │ │ └── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ ├── pushserver.rb │ ├── server_helpers.rb │ ├── settingmessages.rb │ ├── streaminginput.rb │ ├── streaminglogger.rb │ ├── views │ │ ├── _about.haml │ │ ├── _diff_list.haml │ │ ├── _edit_replace_txt.haml │ │ ├── _header.haml │ │ ├── _move_to_top.haml │ │ ├── _queue.haml │ │ ├── _rebooting.haml │ │ ├── bookmarklet │ │ │ ├── download.js.erb │ │ │ └── insert_button.js.erb │ │ ├── edit_menu.haml │ │ ├── help.haml │ │ ├── index.haml │ │ ├── layout.haml │ │ ├── notepad.haml │ │ ├── novels │ │ │ ├── author_comments.haml │ │ │ └── setting.haml │ │ ├── partial │ │ │ ├── csv_import.haml │ │ │ └── download_form.haml │ │ ├── settings.haml │ │ ├── style.scss │ │ └── widget │ │ │ ├── download.haml │ │ │ ├── drag_and_drop.haml │ │ │ └── notepad.haml │ ├── web-socket-ruby │ │ ├── .gitignore │ │ ├── README.txt │ │ ├── lib │ │ │ └── web_socket.rb │ │ └── samples │ │ │ ├── chat_server.rb │ │ │ ├── echo_server.rb │ │ │ └── stdio_client.rb │ └── web_worker.rb └── worker.rb ├── narou.bat ├── narou.gemspec ├── narou.rb ├── preset ├── AozoraEpub3.ini ├── DMincho.ttf ├── bordersymbols.txt ├── custom_chuki_tag.txt ├── kakuyomu.jp │ ├── 1177354054883808252 │ │ └── setting.ini │ └── 1177354054885835084 │ │ └── setting.ini ├── mail_setting.yaml ├── ncode.syosetu.com │ ├── n1839bd │ │ ├── converter.rb │ │ └── setting.ini │ ├── n2267be │ │ └── converter.rb │ ├── n2525bs │ │ ├── converter.rb │ │ └── setting.ini │ ├── n3730bn │ │ └── converter.rb │ ├── n4259s │ │ ├── converter.rb │ │ └── setting.ini │ ├── n4955ee │ │ └── setting.ini │ ├── n5530cf │ │ └── converter.rb │ ├── n7594ct │ │ └── converter.rb │ ├── n7975cr │ │ └── setting.ini │ ├── n8611bv │ │ └── converter.rb │ ├── n8725k │ │ └── converter.rb │ ├── n9078bd │ │ └── converter.rb │ ├── n9463br │ │ ├── converter.rb │ │ └── setting.ini │ ├── n9669bk │ │ ├── converter.rb │ │ └── setting.ini │ └── n9902bn │ │ └── converter.rb ├── vertical_font.css └── vertical_font_with_dakuten.css ├── spec ├── command │ ├── list │ │ └── novel_decorator_spec.rb │ ├── list_spec.rb │ └── update_spec.rb ├── commandbase_spec.rb ├── convert_spec.rb ├── converterbase_spec.rb ├── data │ ├── convert_test │ │ ├── auto_indent │ │ │ ├── correct_test_auto_indent.txt │ │ │ └── test_auto_indent.txt │ │ ├── auto_join_bracket │ │ │ ├── correct_test_auto_join_bracket.txt │ │ │ ├── setting.ini │ │ │ └── test_auto_join_bracket.txt │ │ ├── auto_join_line │ │ │ ├── correct_test_auto_join_line.txt │ │ │ └── test_auto_join_line.txt │ │ ├── convert_numbers │ │ │ ├── correct_test_convert_numbers.txt │ │ │ └── test_convert_numbers.txt │ │ ├── convert_page_break │ │ │ ├── correct_test_convert_page_break.txt │ │ │ ├── setting.ini │ │ │ └── test_convert_page_break.txt │ │ ├── convert_prolonged_sound_mark_to_dash │ │ │ ├── correct_test_convert_prolonged_sound_mark_to_dash.txt │ │ │ ├── setting.ini │ │ │ └── test_convert_prolonged_sound_mark_to_dash.txt │ │ ├── disable_alphabet_word_to_zenkaku │ │ │ ├── correct_test_disable_alphabet_word_to_zenkaku.txt │ │ │ ├── setting.ini │ │ │ └── test_disable_alphabet_word_to_zenkaku.txt │ │ ├── english │ │ │ ├── correct_test_english.txt │ │ │ └── test_english.txt │ │ ├── force_indent_special_chapter │ │ │ ├── correct_test_force_indent_special_chapter.txt │ │ │ └── test_force_indent_special_chapter.txt │ │ ├── horizontal_ellipsis │ │ │ ├── correct_test_horizontal_ellipsis.txt │ │ │ └── test_horizontal_ellipsis.txt │ │ ├── insert_separator │ │ │ ├── correct_test_insert_separator.txt │ │ │ ├── setting.ini │ │ │ └── test_insert_separator.txt │ │ ├── insert_separator_and_replace_txt │ │ │ ├── correct_test_insert_separator_and_replace_txt.txt │ │ │ ├── replace.txt │ │ │ ├── setting.ini │ │ │ └── test_insert_separator_and_replace_txt.txt │ │ ├── kanji_num │ │ │ ├── correct_test_kanji_num.txt │ │ │ └── test_kanji_num.txt │ │ ├── nonokagi │ │ │ ├── correct_test_nonokagi.txt │ │ │ └── test_nonokagi.txt │ │ ├── replace │ │ │ ├── correct_test_replace.txt │ │ │ ├── replace.txt │ │ │ └── test_replace.txt │ │ ├── rome_num │ │ │ ├── correct_test_rome_num.txt │ │ │ └── test_rome_num.txt │ │ ├── ruby │ │ │ ├── correct_test_ruby.txt │ │ │ └── test_ruby.txt │ │ ├── ruby_youon │ │ │ ├── correct_test_ruby_youon.txt │ │ │ ├── setting.ini │ │ │ └── test_ruby_youon.txt │ │ ├── sesame │ │ │ ├── correct_test_sesame.txt │ │ │ └── test_sesame.txt │ │ └── to_odd_leader │ │ │ ├── correct_test_to_odd_leader.txt │ │ │ └── test_to_odd_leader.txt │ ├── html_test.html │ ├── html_test.txt │ └── test.ini ├── device_spec.rb ├── downloader_spec.rb ├── eventable_spec.rb ├── exit_code_spec.rb ├── fixtures │ ├── .test_dot_narou │ │ ├── README.md │ │ ├── alias.yaml │ │ ├── database.yaml │ │ ├── fixture_version.txt │ │ ├── freeze.yaml │ │ ├── latest_convert.yaml │ │ ├── local_setting.yaml │ │ └── tag_colors.yaml │ └── .test_novel_data │ │ ├── Arcadia │ │ └── 27656 もう一度ナデシコへ │ │ │ └── toc.yaml │ │ ├── カクヨム │ │ └── 4852201425154905871 │ │ │ └── toc.yaml │ │ ├── ノクターン・ムーンライト │ │ └── n1610bw 私、結婚しました! │ │ │ └── toc.yaml │ │ ├── ハーメルン │ │ ├── 38552 孤独と共に歩む者 │ │ │ └── toc.yaml │ │ └── 80040 Asmody Story │ │ │ └── toc.yaml │ │ └── 小説家になろう │ │ ├── n6811ck その無限の先へ │ │ └── toc.yaml │ │ ├── n7298cu 聖女の回復魔法がどう見ても俺の劣化版な件について。 │ │ └── toc.yaml │ │ ├── n7352ct 異世界でアイテムコレクター ~アイテムマスターですが魔王をしながら勇者の従者をやっています~ │ │ └── toc.yaml │ │ └── n7975cr 蜘蛛ですが、なにか? │ │ └── toc.yaml ├── generator │ ├── convert_spec_gen.rb │ └── num_to_kanji_test_gen.rb ├── helper_spec.rb ├── html_spec.rb ├── ini_spec.rb ├── input_spec.rb ├── logger_spec.rb ├── narou_spec.rb ├── novelinfo_spec.rb ├── novelsetting_spec.rb ├── num_to_kanji_spec.rb ├── spec_helper.rb ├── support │ └── init_fixtures.rb └── web_worker_spec.rb ├── template ├── converter.rb.erb ├── diff.txt.erb ├── hotentry.txt.erb ├── ibunko_novel.txt.erb ├── novel.txt.erb ├── replace.txt.erb └── setting.ini.erb └── webnovel ├── kakuyomu.jp.yaml ├── ncode.syosetu.com.yaml ├── novel18.syosetu.com.yaml ├── syosetu.org.yaml ├── www.akatsuki-novels.com.yaml └── www.mai-net.net.yaml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/circleci-narou 5 | docker: 6 | - image: circleci/ruby:3.0 7 | environment: 8 | BUNDLE_JOBS: 1 9 | BUNDLE_RETRY: 3 10 | BUNDLE_PATH: vendor/bundle 11 | steps: 12 | - checkout 13 | 14 | - run: 15 | name: Install bundler 2.1.4 16 | command: gem install bundler:2.1.4 17 | 18 | # Restore bundle cache 19 | - restore_cache: 20 | keys: 21 | - narou-bundle-{{ checksum "Gemfile.lock" }} 22 | - narou-bundle- 23 | 24 | - run: 25 | name: Bundle Install 26 | command: bundle check || bundle install 27 | 28 | # Store bundle cache 29 | - save_cache: 30 | key: narou-bundle-{{ checksum "Gemfile.lock" }} 31 | paths: 32 | - vendor/bundle 33 | 34 | - run: 35 | name: Run rspec 36 | command: | 37 | bundle exec rspec \ 38 | --format RspecJunitFormatter \ 39 | --out test_results/rspec/results.xml \ 40 | --format progress \ 41 | $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) 42 | 43 | # Save test results for timing analysis 44 | - store_test_results: 45 | path: test_results 46 | 47 | - store_artifacts: 48 | path: ./coverage 49 | destination: artifact-file 50 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # My Project (Narou.rb) 3 | # 4 | .backup/ 5 | 小説データ/ 6 | 小説データ/ 7 | kindlestrip.py 8 | kindlegen.exe 9 | t.rb 10 | 資料 11 | AozoraEpub3* 12 | TODO.txt 13 | MEMO.txt 14 | .narou/ 15 | .narousetting/ 16 | *.zip 17 | debug 18 | .DS_Store 19 | *.swp 20 | *.swo 21 | *.un~ 22 | *.orig 23 | *.patch 24 | trash/ 25 | .ruby-version 26 | .sass-cache/ 27 | log/**/* 28 | misc/ 29 | hotentry/ 30 | replace.txt 31 | tags 32 | trace_dump.txt 33 | \#*\# 34 | archive 35 | commitversion 36 | .test_dot_narou/ 37 | .test_novel_data/ 38 | mail_setting.yaml 39 | .byebug_history 40 | 41 | # 42 | # for gem 43 | # 44 | *.gem 45 | *.rbc 46 | .bundle 47 | .config 48 | .yardoc 49 | InstalledFiles 50 | _yardoc 51 | coverage 52 | doc/ 53 | lib/bundler/man 54 | pkg 55 | rdoc 56 | spec/reports 57 | test/tmp 58 | test/version_tmp 59 | tmp 60 | document 61 | 62 | -------------------------------------------------------------------------------- /.haml-lint.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | LineLength: 3 | max: 140 4 | HtmlAttributes: 5 | enabled: false 6 | ClassesBeforeIds: 7 | EnforcedStyle: 'id' 8 | -------------------------------------------------------------------------------- /.reek: -------------------------------------------------------------------------------- 1 | # vim: set ft=yaml : 2 | --- 3 | # クラスコメントはなくてもいい 4 | IrresponsibleModule: 5 | enabled: false 6 | 7 | # メソッドの複雑さは rubocop でチェックする 8 | TooManyStatements: 9 | enabled: false 10 | 11 | DuplicateMethodCall: 12 | max_calls: 2 13 | 14 | NestedIterators: 15 | max_allowed_nesting: 3 16 | 17 | TooManyMethods: 18 | enabled: false 19 | 20 | # Narou.rb ではクラス変数は使用許可 21 | ClassVariable: 22 | enabled: false 23 | 24 | UtilityFunction: 25 | enabled: false 26 | 27 | FeatureEnvy: 28 | enabled: false 29 | 30 | UncommunicativeVariableName: 31 | accept: 32 | - _ 33 | - e 34 | - i 35 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | -cr spec_helper 2 | -------------------------------------------------------------------------------- /.scss-lint.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | SelectorFormat: 3 | enabled: false 4 | 5 | EmptyRule: 6 | enabled: false 7 | 8 | PropertySortOrder: 9 | enabled: false 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in narou.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Narou.rb - 小説家になろうのダウンローダ&縦書き整形&管理アプリ。Kindle(などの電子書籍端末)でなろうを読む場合に超便利です! 2 | =================================================================================== 3 | 4 | [![Gem Version](https://badge.fury.io/rb/narou.svg)](http://badge.fury.io/rb/narou) 5 | [![Join the chat at https://gitter.im/whiteleaf7/narou](https://badges.gitter.im/whiteleaf7/narou.svg)](https://gitter.im/whiteleaf7/narou?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | 概要 - Summary 8 | -------------- 9 | このアプリは[小説家になろう](http://syosetu.com/)などで公開されている小説の管理、 10 | 及び電子書籍データへの変換を支援します。縦書き用に特化されており、 11 | 横書きに最適化されたWEB小説を違和感なく縦書きで読むことが出来るようになります。 12 | また、校正機能もありますので、小説としての一般的な整形ルールに矯正します。(例:感嘆符のあとにはスペースが必ずくる) 13 | 14 | 小説家になろうを含めて、下記のサイトに対応しています。 15 | + 小説家になろう http://syosetu.com/ 16 | + ノクターンノベルズ http://noc.syosetu.com/ 17 | + ムーンライトノベルズ http://mnlt.syosetu.com/ 18 | + ミッドナイトノベルズ http://mid.syosetu.com/ 19 | + ハーメルン https://syosetu.org/ 20 | + Arcadia http://www.mai-net.net/ 21 | + 暁 http://www.akatsuki-novels.com/ (※300話以上ある作品は未対応) 22 | + カクヨム https://kakuyomu.jp/ 23 | 24 | コンソールで操作するアプリケーションですが、ブラウザを使って直感的に操作することができる WEB UI も搭載!([デモページ](http://whiteleaf7.github.io/narou/demo/)) 25 | 26 | 主な機能は小説家になろうの小説のダウンロード、更新管理、テキスト整形、AozoraEpub3・kindlegen連携によるEPUB/MOBI出力です。 27 | その他にも変換したデータを直接電子書籍端末へ送信する機能は、メールで送信する機能などもあります。 28 | 29 | 詳細な説明やインストール方法は **[Narou.rb 説明書](https://github.com/whiteleaf7/narou/wiki)** を御覧ください。 30 | 31 | ![WEB UI ScreenCapture](https://raw.github.com/wiki/whiteleaf7/narou/images/webui_cap.png) 32 | ![Console ScreenCapture](https://raw.github.com/wiki/whiteleaf7/narou/images/narou_cap.gif) 33 | 34 | 更新履歴 - ChangeLog 35 | -------------------- 36 | 37 | 3.9.1: 2024-09-19 38 | ----------------- 39 | #### 修正内容 40 | - 小説家になろうの目次修正に対応 #432 @etg-lt 41 | 42 | ---- 43 | 44 | 「小説家になろう」は株式会社ヒナプロジェクトの登録商標です 45 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # -*- coding: utf-8 -*- 3 | 4 | task :default => [:spec] 5 | begin 6 | require "rspec/core/rake_task" 7 | RSpec::Core::RakeTask.new(:spec) do |spec| 8 | spec.pattern = 'spec/**/*_spec.rb' 9 | spec.rspec_opts = ['-cr spec_helper'] 10 | end 11 | rescue LoadError 12 | end 13 | -------------------------------------------------------------------------------- /bin/narou: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # -*- mode: ruby -*- 3 | # -*- coding: utf-8 -*- 4 | # 5 | # Narou.rb ― 小説家になろうダウンロード&整形スクリプト 6 | # 7 | # Copyright 2013 whiteleaf. All rights reserved. 8 | # 9 | 10 | if File.exist?("narou.rb") 11 | require File.join(Dir.pwd, "narou.rb") 12 | else 13 | require_relative "../narou.rb" 14 | end 15 | -------------------------------------------------------------------------------- /lib/backtracer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Narou 8 | module Backtracer 9 | module_function 10 | 11 | LOG_NAME = "trace_dump.txt" 12 | 13 | def capture(&block) 14 | raise "need a block" unless block 15 | rescue_level = $debug ? Exception : StandardError 16 | yield 17 | rescue SystemExit => e 18 | exit e.status 19 | rescue SyntaxError => e 20 | warn e 21 | exit Narou::EXIT_ERROR_CODE 22 | rescue rescue_level => e 23 | traces = build_traces(e) 24 | if $display_backtrace 25 | warn traces 26 | else 27 | warn traces.lines[0..2] 28 | warn "" 29 | warn " エラーが発生したため終了しました。" 30 | warn " 詳細なエラーログは narou trace で表示出来ます。もしくは --backtrace オプションを付けて再度実行して下さい。" 31 | end 32 | save_log(traces) 33 | exit Narou::EXIT_ERROR_CODE 34 | end 35 | 36 | def argv=(argv) 37 | @argv = argv.map(&:dup) 38 | end 39 | 40 | def argv 41 | @argv || ARGV 42 | end 43 | 44 | def log_path 45 | if Narou.root_dir 46 | File.join(Narou.root_dir, LOG_NAME) 47 | else 48 | LOG_NAME 49 | end 50 | end 51 | 52 | def build_traces(exception) 53 | backtrace = exception.backtrace 54 | head = "#{backtrace.shift}: #{exception.message.encode(Encoding::UTF_8)} (#{exception.class})" 55 | buffer = StringIO.new 56 | buffer.puts head 57 | backtrace.each do |b| 58 | buffer.puts " from #{b}" 59 | end 60 | buffer.string 61 | end 62 | 63 | def build_command 64 | "#{$0} #{argv.join(' ')}" 65 | end 66 | 67 | def save_log(traces) 68 | File.open(log_path, "w:UTF-8") do |fp| 69 | fp.puts "--- #{Time.now.strftime("%Y/%m/%d %H:%M:%S")} ---" 70 | fp.puts build_command 71 | fp.puts 72 | fp.puts traces 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/color.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | def write_color(str, console = STDOUT) 8 | console.write str 9 | end 10 | 11 | if RbConfig::CONFIG["host_os"] =~ /mswin(?!ce)|mingw|bccwin/i 12 | require_relative "extensions/windows" 13 | require_relative "extensions/windows_write_color" if $color_parser == "self" 14 | end 15 | -------------------------------------------------------------------------------- /lib/command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "commandbase" 8 | Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), "command", "*.rb"))) do |path| 9 | require path 10 | end 11 | 12 | module Command 13 | # 14 | # コマンド一覧を取得 15 | # 16 | def self.get_list 17 | COMMAND_LIST 18 | end 19 | 20 | # この順番がヘルプに表示される順番 21 | COMMAND_LIST = { 22 | "download" => Download, 23 | "update" => Update, 24 | "list" => List, 25 | "convert" => Convert, 26 | "diff" => Diff, 27 | "setting" => Setting, 28 | "alias" => Alias, 29 | "inspect"=> Inspect, 30 | "send" => Send, 31 | "folder" => Folder, 32 | "browser" => Browser, 33 | "remove" => Remove, 34 | "freeze" => Freeze, 35 | "tag" => Tag, 36 | "web" => Web, 37 | "mail" => Mail, 38 | "backup" => Backup, 39 | "csv" => Csv, 40 | "clean" => Clean, 41 | "log" => Log, 42 | "trace" => Trace, 43 | "help" => Help, 44 | "version" => Version, 45 | "init" => Init, 46 | "restore346" => Restore346 47 | } 48 | 49 | if $development 50 | COMMAND_LIST["console"] = Console 51 | end 52 | 53 | # ショートカット定義 54 | # COMMAND_LIST の上から順に優先度が高い 55 | Shortcuts = Hash[*get_list.keys.reverse.map { |s| 56 | [s[0], s, s[0..1], s] 57 | }.flatten] 58 | end 59 | -------------------------------------------------------------------------------- /lib/command/alias.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "../inventory" 8 | 9 | module Command 10 | class Alias < CommandBase 11 | # 禁止ワード 12 | BAN_WORD = %w(hotentry) 13 | 14 | def self.oneline_help 15 | "小説のIDに紐付けた別名を作成します" 16 | end 17 | 18 | def initialize 19 | super("[= ...] [options]") 20 | @opt.separator <<-EOS 21 | 22 | ・小説のIDに紐付けた好きな別名を作ることが出来ます。IDやNコード等を覚える必要がなくなります。 23 | ・にはアルファベット及び数字、アンダースコアが使用出来ます。 24 | ・は他のコマンドで指定出来るものがそのまま使えますが、すでにダウンロード済みである必要があります。 25 | 26 | Examples: 27 | narou alias --list 28 | narou alias musyoku=n9669bk 29 | narou alias harem=1 30 | narou convert harem # 他のコマンドで別名が使えるようになる 31 | narou alias harem= # 右辺に何も書かないとその別名を解除できる 32 | 33 | Options: 34 | EOS 35 | @opt.on("-l", "--list", "現在の割り当て一覧を表示する") { 36 | output_aliases_list 37 | exit 0 38 | } 39 | end 40 | 41 | def output_aliases_list 42 | aliases = Inventory.load("alias") 43 | database = Database.instance 44 | aliases.each do |name, id| 45 | title = database[id]["title"] rescue "(すでに削除されています)" 46 | puts "#{name}=#{title}".termcolor 47 | end 48 | end 49 | 50 | def execute(argv) 51 | super 52 | display_help! if argv.empty? 53 | aliases = Inventory.load("alias") 54 | argv.each_with_index do |arg, i| 55 | Helper.print_horizontal_rule if i > 0 56 | alias_name, target = arg.split("=", 2) 57 | if BAN_WORD.include?(alias_name) 58 | error "#{alias_name} は使用禁止ワードです" 59 | next 60 | end 61 | unless alias_name =~ /^\w+$/ 62 | error "別名にはアルファベット・数字・アンダースコアしか使えません" 63 | next 64 | end 65 | if target.nil? 66 | error "書式が間違っています。#{alias_name}=別名 のように書いて下さい" 67 | next 68 | end 69 | if target == "" 70 | aliases.delete(alias_name) 71 | puts "#{alias_name} を解除しました" 72 | next 73 | end 74 | unless Downloader.novel_exists?(target) 75 | error "#{target} は存在しません" 76 | next 77 | end 78 | data = Downloader.get_data_by_target(target) 79 | id = data["id"] 80 | title = data["title"] 81 | aliases[alias_name] = id 82 | puts "#{alias_name} を #{title} の別名に設定しました" 83 | end 84 | aliases.save 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/command/browser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "../inventory" 8 | 9 | module Command 10 | class Browser < CommandBase 11 | def self.oneline_help 12 | "小説の掲載ページをブラウザで開きます" 13 | end 14 | 15 | def initialize 16 | super(" [ ...] [options]") 17 | @opt.separator <<-EOS 18 | 19 | ・指定した小説の掲載ページをブラウザで開きます。 20 | 21 | Examples: 22 | narou browser n9669bk 23 | narou browser musyoku -v 24 | narou b 0 25 | 26 | Options: 27 | EOS 28 | 29 | @opt.on("-v", "--vote", "小説の投票・感想を投稿するページを表示する(なろうのみ)") { 30 | @options["vote"] = true 31 | } 32 | end 33 | 34 | def execute(argv) 35 | super 36 | display_help! if argv.empty? 37 | tagname_to_ids(argv) 38 | argv.each do |target| 39 | data = Downloader.get_data_by_target(target) 40 | unless data 41 | error "#{target} は存在しません" 42 | next 43 | end 44 | toc_url = data["toc_url"] 45 | if @options["vote"] 46 | # TODO: 最新話の場所をAPIで取得する 47 | data_dir = Downloader.get_novel_data_dir_by_target(data["id"]) 48 | latest_index = YAML.unsafe_load_file(File.join(data_dir, Downloader::TOC_FILE_NAME))["subtitles"].last["index"] 49 | open_url = "#{toc_url + latest_index}/#my_novelpoint" 50 | else 51 | open_url = toc_url 52 | end 53 | Helper.open_browser(open_url) 54 | puts open_url 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/command/console.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | if $development 8 | module Command 9 | class Console < CommandBase 10 | def self.oneline_help 11 | "開発用コンソール" 12 | end 13 | 14 | def initialize 15 | super() 16 | @opt.separator <<~HELP 17 | 18 | ・開発時のみ有効になるコンソール。pry のインストール必須 19 | HELP 20 | end 21 | 22 | def execute(argv) 23 | $stdout = STDOUT 24 | super 25 | unless defined?(Pry) 26 | error "gem install pry をが必要です" 27 | exit Narou::EXIT_ERROR_CODE 28 | end 29 | Pry.start(TOPLEVEL_BINDING) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/command/folder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "../helper" 8 | 9 | module Command 10 | class Folder < CommandBase 11 | def self.oneline_help 12 | "小説の保存フォルダを開きます" 13 | end 14 | 15 | def initialize 16 | super(" [ ...]") 17 | @opt.separator <<-EOS 18 | 19 | ・指定した小説の保存フォルダを開きます。 20 | 21 | Examples: 22 | narou folder n9669bk 23 | narou folder musyoku 24 | narou folder 0 25 | narou f 0 26 | 27 | Options: 28 | EOS 29 | @opt.on("-n", "--no-open", "フォルダを開かずにパスだけ表示する") { 30 | @options["no-open"] = true 31 | } 32 | end 33 | 34 | def execute(argv) 35 | super 36 | display_help! if argv.empty? 37 | tagname_to_ids(argv) 38 | argv.each do |target| 39 | dir = Downloader.get_novel_data_dir_by_target(target) 40 | if dir 41 | Helper.open_directory(dir) unless @options["no-open"] 42 | puts dir 43 | else 44 | error "#{target} は存在しません" 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/command/freeze.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "../database" 8 | require_relative "../downloader" 9 | require_relative "../inventory" 10 | 11 | module Command 12 | class Freeze < CommandBase 13 | def self.oneline_help 14 | "小説の凍結設定を行います" 15 | end 16 | 17 | def initialize 18 | super(" [ ...] [options]") 19 | @opt.separator <<-EOS 20 | 21 | ・指定した小説を凍結し、変更不可属性を付与します。 22 | ・凍結することでダウンロード、アップデート及び削除が出来なくなります。 23 | ・凍結済みの小説を指定した場合、凍結が解除されます。 24 | 25 | Examples: 26 | narou freeze --list 27 | narou freeze n9669bk 28 | narou freeze 0 1 musyoku 29 | 30 | Options: 31 | EOS 32 | @opt.on("-l", "--list", "凍結中小説の一覧を表示") { 33 | output_freeze_list 34 | exit 0 35 | } 36 | @opt.on("--on", "現在の状態にかかわらず凍結する") { 37 | @options["on"] = true 38 | } 39 | @opt.on("--off", "現在の状態にかかわらず解除する") { 40 | @options["off"] = true 41 | } 42 | end 43 | 44 | def output_freeze_list 45 | List.execute!("--filter", "frozen") 46 | end 47 | 48 | def execute(argv) 49 | super 50 | display_help! if argv.empty? 51 | tagname_to_ids(argv) 52 | frozen_list = Inventory.load("freeze") 53 | argv.each do |target| 54 | data = Downloader.get_data_by_target(target) 55 | unless data 56 | puts "#{target} は存在しません" 57 | next 58 | end 59 | id, title = data["id"], data["title"] 60 | flag = !frozen_list.include?(id) 61 | flag = true if @options["on"] 62 | flag = false if @options["off"] 63 | if flag 64 | frozen_list[id] = true 65 | puts "#{title} を凍結しました" 66 | else 67 | frozen_list.delete(id) 68 | puts "#{title} の凍結を解除しました" 69 | next 70 | end 71 | end 72 | frozen_list.save 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/command/help.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Command 8 | class Help < CommandBase 9 | HEADER = "Narou.rb ― 小説家になろうダウンローダ&縦書き用整形スクリプト" 10 | 11 | def self.oneline_help 12 | "このヘルプを表示します" 13 | end 14 | 15 | def execute(_argv) 16 | disable_logging 17 | if Narou.already_init? 18 | display_help 19 | else 20 | display_help_first_time 21 | end 22 | end 23 | 24 | def display_help 25 | stream_io.puts(<<~HELP.termcolor) 26 | #{HEADER} 27 | 28 | Usage: narou <command> [arguments...] [options...] 29 | [--no-color] [--multiple] [--time] [--backtrace] 30 | 31 | コマンドの簡単な説明: 32 | HELP 33 | cmd_list = Command.get_list 34 | cmd_list.each do |key, command| 35 | oneline = command.oneline_help.split("\n") 36 | stream_io.puts " #{key.ljust(12)} #{oneline.shift}".termcolor 37 | oneline.each do |h| 38 | stream_io.puts " " * 16 + h 39 | end 40 | end 41 | stream_io.puts(<<-HELP.termcolor) 42 | 43 | 各コマンドの詳細は narou <command> -h を参照してください。 44 | 各コマンドは先頭の一文字か二文字でも指定できます。 45 | (e.g. `narou d n4259s', `narou fr musyoku') 46 | 47 | Global Options: 48 | --no-color カラー表示を無効にする 49 | --multiple 引数の区切りにスペースの他に","も使えるようにする 50 | --time 実行時間表示 51 | --backtrace エラー発生時詳細情報を表示 52 | HELP 53 | end 54 | 55 | def display_help_first_time 56 | stream_io.puts(<<~HELP.termcolor) 57 | #{HEADER} 58 | 59 | Usage: narou init 60 | 61 | まだこのフォルダは初期化されていません。 62 | narou init コマンドを実行して初期化を行いましょう。 63 | HELP 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/command/inspect.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "fileutils" 8 | require_relative "../downloader" 9 | require_relative "../inspector" 10 | require_relative "../novelsetting" 11 | require_relative "../inventory" 12 | 13 | module Command 14 | class Inspect < CommandBase 15 | def self.oneline_help 16 | "小説状態の調査状況ログを表示します" 17 | end 18 | 19 | def initialize 20 | super("[ ...]") 21 | @opt.separator <<-EOS 22 | 23 | ・引数を指定しなかった場合は直前に変換した小説の状態調査状況ログを表示します。 24 | ・小説を指定した場合はその小説のログを表示します。 25 | ・narou setting convert.inspect=true とすれば変換時に常に表示されるようになります。 26 | 27 | Examples: 28 | narou inspect # 直前の変換時のログを表示 29 | narou inspect 6 # ログを表示したい小説を指定する 30 | EOS 31 | end 32 | 33 | def execute(argv) 34 | super 35 | if argv.empty? 36 | latest_id = Inventory.load("latest_convert")["id"] 37 | if latest_id 38 | data = Downloader.get_data_by_target(latest_id) 39 | display_log(data) 40 | end 41 | return 42 | end 43 | tagname_to_ids(argv) 44 | argv.each_with_index do |target, i| 45 | Helper.print_horizontal_rule if i > 0 46 | data = Downloader.get_data_by_target(target) 47 | unless data 48 | error "#{target} は存在しません" 49 | next 50 | end 51 | display_log(data) 52 | end 53 | end 54 | 55 | def display_log(data) 56 | puts "(#{data["title"]} の小説状態調査状況ログ)" 57 | novel_setting = NovelSetting.load(data["id"], false, false) 58 | puts Inspector.read_messages(novel_setting) || "調査ログがまだ無いようです" 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/command/log/tail.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Command 8 | class Log < CommandBase 9 | # based on https://qiita.com/ymmtmdk/items/10cf80ec2fc7962ff9fc 10 | class Tail 11 | CHUNK_SIZE = 1024 * 16 12 | 13 | attr_accessor :path, :num, :stream_io 14 | 15 | def initialize(path, num) 16 | self.path = path 17 | self.num = num 18 | raise unless num.to_i > 0 19 | end 20 | 21 | def stream 22 | execute do |file| 23 | loop do 24 | stream_io.print file.read 25 | sleep 0.1 26 | end 27 | end 28 | end 29 | 30 | def display 31 | execute do |file| 32 | stream_io.puts file.read 33 | end 34 | end 35 | 36 | private 37 | 38 | def execute 39 | File.open(path, "r:UTF-8") do |file| 40 | offset = offset_of_nth_chr_from_tail(file, num, "\n") 41 | file.seek(file.size - offset) 42 | yield file 43 | end 44 | end 45 | 46 | def reverse_chunks(file, size) 47 | n = file.size / size 48 | n -= 1 if file.size == n * size 49 | len = file.size - n * size 50 | until n < 0 51 | file.seek(n * size) 52 | yield file.read(len) 53 | n -= 1 54 | len = size 55 | end 56 | end 57 | 58 | def offset_of_nth_chr_from_tail(file, count, target) 59 | offset = 0 60 | reverse_chunks(file, CHUNK_SIZE) do |chunk| 61 | chunk.size.times do |i| 62 | chr = chunk[chunk.size - i - 1] 63 | next unless chr == target || (offset == 0 && i == 0 && chr != target) 64 | count -= 1 65 | if count < 0 66 | offset += i 67 | return offset 68 | end 69 | end 70 | offset += chunk.size 71 | end 72 | offset 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/command/restore346.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "../database" 8 | require_relative "../downloader" 9 | 10 | module Command 11 | class Restore346 < CommandBase 12 | def self.oneline_help 13 | "ver 3.4.6 で壊れた toc.yaml の復元" 14 | end 15 | 16 | def initialize 17 | super(" [ ...]") 18 | @opt.separator <<-HELP 19 | 20 | ・ver 3.4.6 で更新して壊れた toc.yaml の更新日時部分を復元します 21 | ・このコマンドを実行しなくても更新は出来ますが、改稿がある話のDLが走ってしまいます 22 | 23 | Examples: 24 | narou restore346 25 | 26 | HELP 27 | 28 | @opt.on('-f') do 29 | @options["f"] = true 30 | end 31 | end 32 | 33 | def execute(argv) 34 | super 35 | database_values = Database.instance.get_object.values 36 | database_values.each do |data| 37 | begin 38 | puts "#{data["title"].escape}".termcolor 39 | downloader = Downloader.new(data["id"]) 40 | toc = downloader.load_toc_file 41 | modified = @options["f"] ? restore_subupdate(toc) : restore(toc) 42 | downloader.save_toc_once(toc) if modified 43 | rescue StandardError 44 | next 45 | end 46 | end 47 | end 48 | 49 | def restore(toc) 50 | subtitles = toc["subtitles"] 51 | modified = false 52 | subtitles.each do |subtitle| 53 | next unless subtitle["subdate"] =~ /\A(?.+?)/ 54 | 55 | subdate = Regexp.last_match[:subdate] 56 | subupdate = Regexp.last_match[:subupdate] 57 | 58 | download_time = subtitle["download_time"] 59 | if Time.parse(subupdate) > download_time 60 | subupdate = '' 61 | end 62 | 63 | subtitle["subdate"] = subdate 64 | subtitle["subupdate"] = subupdate 65 | modified = true 66 | end 67 | 68 | if modified 69 | puts "#{toc["title"].escape} の目次データを復元しました".termcolor 70 | end 71 | 72 | modified 73 | end 74 | 75 | def restore_subupdate(toc) 76 | subtitles = toc["subtitles"] 77 | modified = false 78 | subtitles.each do |subtitle| 79 | subupdate = subtitle["subupdate"] 80 | 81 | next if subupdate.blank? 82 | 83 | download_time = subtitle["download_time"] 84 | next if Time.parse(subupdate) <= download_time 85 | 86 | subtitle["subupdate"] = '' 87 | modified = true 88 | end 89 | 90 | if modified 91 | puts "#{toc["title"].escape} の目次データを復元しました".termcolor 92 | end 93 | 94 | modified 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/command/trace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Command 8 | class Trace < CommandBase 9 | def self.oneline_help 10 | "直前のバックトレースを表示します" 11 | end 12 | 13 | def initialize 14 | super() 15 | @opt.separator <<-MSG 16 | 17 | ・エラーが発生した際に保存されたバックトレースログを表示します。 18 | ・ログテキスト自体は #{Narou::Backtracer.log_path} に保存されています。 19 | MSG 20 | end 21 | 22 | def execute(argv) 23 | super 24 | return unless File.exist?(Narou::Backtracer.log_path) 25 | puts File.read(Narou::Backtracer.log_path) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/command/update/hotentry_manager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | class HotentryManager 8 | attr_reader :hotentries 9 | 10 | def initialize 11 | inv = Inventory.load("local_setting") 12 | self.enable = inv["hotentry"] 13 | self.auto_mail = inv["hotentry.auto-mail"] 14 | self.hotentries = {} 15 | end 16 | 17 | def connect(downloader) 18 | return unless enable? 19 | downloader.on(:newarrival, &method(:store_entry)) 20 | end 21 | 22 | def enable? 23 | enable 24 | end 25 | 26 | def auto_mail? 27 | auto_mail 28 | end 29 | 30 | private 31 | 32 | attr_accessor :enable, :auto_mail 33 | attr_writer :hotentries 34 | 35 | def store_entry(hash) 36 | entry = hotentries[hash[:id]] ||= [] 37 | entry << hash[:subtitle_info] 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/command/update/interval.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Command 8 | class Update < CommandBase 9 | class Interval 10 | MIN = 2.5 # 作品間ウェイトの最低秒数(処理時間含む) 11 | FORCE_WAIT_TIME = 2.0 # 強制待機時間 12 | 13 | def initialize(interval) 14 | @time = Time.now - MIN 15 | interval = interval.to_f 16 | @interval_time = interval >= MIN ? interval : MIN 17 | end 18 | 19 | def wait 20 | wait_time = Time.now - @time 21 | sleep(@interval_time - wait_time) if wait_time < @interval_time 22 | @time = Time.now 23 | end 24 | 25 | def force_wait 26 | sleep(FORCE_WAIT_TIME) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/commandline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "narou" 8 | require_relative "command" 9 | require_relative "helper" 10 | require_relative "inventory" 11 | 12 | module CommandLine 13 | module_function 14 | 15 | def run(*argv, catch_exit: false, io: $stdout) 16 | argv.flatten! 17 | argv_for_windows(argv) 18 | cmd_name = take_command_name(argv) 19 | proc_default_arguments(argv, cmd_name) 20 | if argv.delete("--multiple") 21 | multiple_argument_extract(argv) 22 | end 23 | unless STDIN.tty? 24 | # pipeで接続された場合、標準入力からIDリストを受け取って引数に繋げる 25 | argv += (STDIN.gets || "").split 26 | end 27 | command = Command.get_list[cmd_name] 28 | if catch_exit 29 | command.execute!(argv, io: io) 30 | else 31 | cmd = command.new 32 | cmd.stream_io = io 33 | cmd.execute(argv) 34 | end 35 | ensure 36 | Command::Convert.display_sending_error_list 37 | end 38 | 39 | # 40 | # exit を捕捉して終了コードを返す 41 | # 42 | def run!(*argv, io: $stdout) 43 | run(*argv, catch_exit: true, io: io) 44 | end 45 | 46 | def load_default_arguments(cmd) 47 | default_arguments_list = Inventory.load("local_setting") 48 | (default_arguments_list["default_args.#{cmd}"] || "").split 49 | end 50 | 51 | def argv_for_windows(argv) 52 | return unless Helper.os_windows? 53 | argv.map! do |arg| 54 | arg.class == Integer ? arg : arg&.encode(Encoding::UTF_8) 55 | end 56 | end 57 | 58 | def take_command_name(argv) 59 | argv.unshift("help") if argv.empty? 60 | name = argv.shift.downcase 61 | name = Command::Shortcuts[name] || name 62 | name = case name 63 | when "-v", "--version" 64 | "version" 65 | when "-h", "--help" 66 | "help" 67 | else 68 | name 69 | end 70 | unless Narou.already_init? 71 | unless %w(help version init).include?(name) 72 | name = "help" 73 | end 74 | end 75 | unless Command.get_list.include?(name) 76 | error "不明なコマンドです。narou help を確認してください" 77 | exit Narou::EXIT_ERROR_CODE 78 | end 79 | name 80 | end 81 | 82 | def proc_default_arguments(argv, name) 83 | if argv.empty? && STDIN.tty? 84 | argv.concat(load_default_arguments(name)) 85 | end 86 | end 87 | 88 | # 89 | # 引数をスペース以外による区切り文字で展開する 90 | # 91 | def multiple_argument_extract(argv) 92 | delimiter = Inventory.load("local_setting")["multiple-delimiter"] || "," 93 | argv.map! { |arg| 94 | arg.split(delimiter) 95 | }.flatten! 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/database.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "fileutils" 8 | require "singleton" 9 | require "yaml" 10 | require "forwardable" 11 | require_relative "narou" 12 | require_relative "inventory" 13 | 14 | class Database 15 | include Singleton 16 | include Enumerable 17 | extend Forwardable 18 | 19 | ARCHIVE_ROOT_DIR_PATH = "小説データ/" 20 | DATABASE_NAME = "database" 21 | 22 | def_delegators :@database, :[], :[]=, :each, :each_key, :each_value, :delete 23 | 24 | def initialize 25 | refresh 26 | end 27 | 28 | def refresh 29 | @database = Inventory.load(DATABASE_NAME) 30 | end 31 | 32 | # 33 | # データベース初期設定 34 | # 35 | def self.init 36 | unless File.exist?(ARCHIVE_ROOT_DIR_PATH) 37 | FileUtils.mkdir(ARCHIVE_ROOT_DIR_PATH) 38 | puts ARCHIVE_ROOT_DIR_PATH + " を作成しました" 39 | end 40 | end 41 | 42 | # 43 | # 小説格納用のルートディレクトリを取得 44 | # 45 | def self.archive_root_path 46 | @archive_root_path ||= Narou.root_dir.join(ARCHIVE_ROOT_DIR_PATH).expand_path 47 | end 48 | 49 | def save_database 50 | @database.save 51 | end 52 | 53 | def get_object 54 | @database 55 | end 56 | 57 | def ids 58 | @database.keys 59 | end 60 | 61 | def novel_exists?(id) 62 | return nil if id.nil? 63 | @database.keys.include?(id.to_i) 64 | end 65 | 66 | def get_data(type, value) 67 | @database.each_value do |data| 68 | return data if data[type] == value 69 | end 70 | nil 71 | end 72 | 73 | # SiteSetting を使って toc url を正規化してマッチングする。 74 | # get_data("toc_url", url) だと、アドレスが仕様変更した場合に、 75 | # 古いままのデータとマッチングしなくなるため 76 | def get_data_by_toc_url(toc_url, site_setting) 77 | @database.each_value do |data| 78 | site_setting.multi_match_once(data["toc_url"], "url") or next 79 | return data if site_setting["toc_url"] == toc_url 80 | end 81 | nil 82 | end 83 | 84 | def create_new_id 85 | max_id = @database.keys.max 86 | id = max_id ? max_id + 1 : 0 87 | id 88 | end 89 | 90 | def sort_by(key, reverse: true) 91 | values = @database.values.sort_by { |v| v[key] } 92 | if reverse 93 | values.reverse 94 | else 95 | values 96 | end 97 | end 98 | 99 | def tag_indexies 100 | result = Hash.new { [] } 101 | @database.each do |id, data| 102 | tags = data["tags"] || [] 103 | tags.each do |tag| 104 | result[tag.to_s] |= [id] 105 | end 106 | end 107 | result 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/device/epub.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | # 8 | # multi-device で単純なEPUB変換をしたい場合用 9 | # 10 | module Device::Epub 11 | PHYSICAL_SUPPORT = false 12 | VOLUME_NAME = nil 13 | DOCUMENTS_PATH_LIST = nil 14 | EBOOK_FILE_EXT = ".epub" 15 | NAME = "EPUB" 16 | DISPLAY_NAME = "EPUB" 17 | 18 | RELATED_VARIABLES = { 19 | "default.enable_half_indent_bracket" => false, 20 | } 21 | end 22 | -------------------------------------------------------------------------------- /lib/device/ibunko.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Device::Ibunko 8 | PHYSICAL_SUPPORT = false 9 | VOLUME_NAME = nil 10 | DOCUMENTS_PATH_LIST = nil 11 | EBOOK_FILE_EXT = ".zip" 12 | NAME = "iBunko" 13 | DISPLAY_NAME = "i文庫" 14 | 15 | RELATED_VARIABLES = { 16 | "default.enable_half_indent_bracket" => false 17 | } 18 | 19 | # 20 | # i文庫用にテキストと挿絵ファイルをzipアーカイブ化する 21 | # 22 | def hook_convert_txt_to_ebook_file(&original_func) 23 | return false if @options["no-zip"] 24 | require "zip" 25 | Zip.unicode_names = true 26 | # TODO: テキストファイル変換時もsettingを取れるようにする 27 | setting = {} 28 | if @novel_data 29 | setting = NovelSetting.load(@novel_data["id"], @options["ignore-force"], @options["ignore-default"]) 30 | end 31 | dirpath = File.dirname(@converted_txt_path) 32 | translate_illust_chuki_to_img_tag 33 | zipfile_path = @converted_txt_path.sub(/.txt$/, @device.ebook_file_ext) 34 | File.delete(zipfile_path) if File.exist?(zipfile_path) 35 | Zip::File.open(zipfile_path, Zip::File::CREATE) do |zip| 36 | zip.add(File.basename(@converted_txt_path), @converted_txt_path) 37 | # 挿絵 38 | if setting["enable_illust"] 39 | illust_dirpath = File.join(dirpath, Illustration::ILLUST_DIR) 40 | if File.exist?(illust_dirpath) 41 | Dir.glob(File.join(illust_dirpath, "*")) do |img_path| 42 | zip.add(File.join(Illustration::ILLUST_DIR, File.basename(img_path)), img_path) 43 | end 44 | end 45 | end 46 | # 表紙画像 47 | cover_name = NovelConverter.get_cover_filename(dirpath) 48 | if cover_name 49 | zip.add(cover_name, File.join(dirpath, cover_name)) 50 | end 51 | end 52 | puts File.basename(zipfile_path) + " を出力しました" 53 | puts "#{@device.display_name}用ファイルを出力しました".termcolor 54 | if Narou.economy?("cleanup_temp") && @argument_target_type == :novel 55 | # 作業用ファイルを削除 56 | FileUtils.rm_f(@converted_txt_path) 57 | end 58 | zipfile_path 59 | end 60 | 61 | # 62 | # 挿絵注記をimgタグに変換する 63 | # 64 | def translate_illust_chuki_to_img_tag 65 | data = File.read(@converted_txt_path, encoding: Encoding::UTF_8) 66 | data.gsub!(/[#挿絵((.+?))入る]/, "") 67 | File.write(@converted_txt_path, data) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/device/kindle.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "fileutils" 8 | 9 | module Device::Kindle 10 | PHYSICAL_SUPPORT = true 11 | VOLUME_NAME = "Kindle" 12 | DOCUMENTS_PATH_LIST = %w(documents Documents Books) 13 | EBOOK_FILE_EXT = ".mobi" 14 | NAME = "Kindle" 15 | DISPLAY_NAME = "Kindle" 16 | 17 | RELATED_VARIABLES = { 18 | "default.enable_half_indent_bracket" => true, 19 | } 20 | 21 | include Device::BackupBookmarkUtility 22 | 23 | def backup_bookmark 24 | documents_path = get_documents_path 25 | raise Device::DontConneting unless documents_path 26 | paths = Dir.glob(File.join(documents_path, "*.sdr/*.azw3{f,r}")) 27 | Helper.copy_files(paths, get_storage_path, exception: false) 28 | paths.size 29 | end 30 | 31 | def restore_bookmark 32 | documents_path = get_documents_path 33 | raise Device::DontConneting unless documents_path 34 | paths = Dir.glob(File.join(get_storage_path, "*.sdr/*")) 35 | Helper.copy_files(paths, documents_path, exception: false) 36 | paths.size 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/device/kobo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Device::Kobo 8 | PHYSICAL_SUPPORT = true 9 | VOLUME_NAME = "KOBOeReader" 10 | DOCUMENTS_PATH_LIST = ["/"] 11 | EBOOK_FILE_EXT = ".kepub.epub" 12 | NAME = "Kobo" 13 | DISPLAY_NAME = "Kobo" 14 | 15 | RELATED_VARIABLES = { 16 | "default.enable_half_indent_bracket" => false, 17 | } 18 | end 19 | -------------------------------------------------------------------------------- /lib/device/library/cygwin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Device::Library 8 | module Cygwin 9 | def get_device_root_dir(volume_name) 10 | # cygwinのドライブマウント先のpath prefixを取得する。 11 | # 1.7.28では二行目の行頭 12 | mount_root = `mount -p`.split("\n").last.split(/\s+/).first 13 | 14 | Dir.glob("#{mount_root}/*") do |drive_path| 15 | drive = drive_path.sub(/.*\//,"") 16 | # windowsのvolコマンドを利用してがんばってvolume_nameを探す。 17 | # 一行目にボリュームラベルが含まれる。 18 | # volume_nameが "volume" とか "hoge(*)" などだとはまる。 19 | cmd = "cmd /c vol #{drive}:".encode(Encoding::Windows_31J) 20 | capture = `#{cmd}`.force_encoding(Encoding::Windows_31J).encode(Encoding::UTF_8) 21 | if capture.split("\n").first.match(/#{volume_name}/i) 22 | return drive_path 23 | end 24 | end 25 | nil 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/device/library/linux.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | # rubocop:disable Lint/AssignmentInCondition 7 | 8 | require "etc" 9 | 10 | class Device 11 | module Library 12 | module Linux 13 | username = Etc.getpwuid(Process.euid).name 14 | @@mount_roots = %w(/media /mnt) 15 | @@mount_roots << "/run/media/" + username # udisks2 による自動マウント(デフォルトの場所) 16 | @@mount_roots << "/media/" + username # udisks2 による自動マウント(Ubuntuの場合) 17 | 18 | # :reek:UtilityFunction 19 | def get_device_root_dir(volume_name) 20 | @@mount_roots.each do |mount_root| 21 | path = File.join(mount_root, volume_name) 22 | if File.directory?(path) 23 | return path 24 | end 25 | end 26 | nil 27 | end 28 | 29 | # 端末のデバイスファイル名と uhelper オプションの値を取得 30 | def device_mount_info(volume_name) 31 | device_root = get_device_root_dir(volume_name) 32 | raise Device::CantEject, "端末が接続されていません" unless device_root 33 | 34 | pattern = %r!^(/dev/[^ ]+) .* #{device_root} .*\Wuhelper=(\w+)! 35 | open("|mount") do |io| 36 | while line = io.gets 37 | if line =~ pattern 38 | return [$1, $2] 39 | end 40 | end 41 | end 42 | [nil, nil] 43 | end 44 | 45 | def ejectable?(volume_name) 46 | case device_mount_info(volume_name)[1] 47 | when "udisks", "udisks2" 48 | true 49 | else 50 | false 51 | end 52 | rescue Device::CantEject 53 | false 54 | end 55 | 56 | def eject(volume_name) 57 | device, uhelper = device_mount_info(volume_name) 58 | # Kindle第10世代への対応 59 | # https://github.com/whiteleaf7/narou/issues/314 60 | device_chopped = device.gsub(/\d+$/, "") 61 | 62 | case uhelper 63 | when "udisks" 64 | commands = ["udisks --unmount #{device}", 65 | "udisks --detach #{device_chopped}"] 66 | when "udisks2" 67 | commands = ["udisksctl unmount -b #{device} --no-user-interaction", 68 | "udisksctl power-off -b #{device_chopped} --no-user-interaction"] 69 | else 70 | raise Device::CantEject, "udisks または udisks2 によって自動マウントされたデバイスではありません。" 71 | end 72 | 73 | commands.each do |command| 74 | status, _stdout, stderr = systemu(command) 75 | unless status.success? 76 | raise Device::CantEject, "#{command}: #{stderr.strip}" 77 | end 78 | end 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/device/library/mac.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Device::Library 8 | module Mac 9 | def get_device_root_dir(volume_name) 10 | %w(/Volumes).each do |mount_root| 11 | path = File.join(mount_root, volume_name) 12 | if File.directory?(path) 13 | return path 14 | end 15 | end 16 | nil 17 | end 18 | 19 | def eject(volume_name) 20 | status, stdio, stderr = systemu("diskutil eject #{volume_name}") 21 | unless status.success? 22 | raise Device::CantEject, stderr.strip 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/device/library/windows.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | # rubocop:disable Naming/VariableName 8 | 9 | require "win32ole" 10 | require_relative "../../extensions/windows" 11 | require_relative "windows/eject" 12 | 13 | class Device 14 | module Library 15 | module Windows 16 | def get_device_root_dir(volume_name) 17 | @@FileSystemObject ||= WIN32OLE.new("Scripting.FileSystemObject") 18 | drives.each do |drive_letter| 19 | drive_info = @@FileSystemObject.GetDrive(drive_letter) 20 | vol = drive_info.VolumeName rescue "" 21 | if vol.casecmp(volume_name) == 0 22 | return File.expand_path(drive_letter) 23 | end 24 | end 25 | nil 26 | end 27 | 28 | def drives 29 | result = [] 30 | bits = WinAPI.GetLogicalDrives 31 | 26.times do |i| 32 | if bits & (1 << i) != 0 33 | result << "#{(65 + i).chr}:\\" 34 | end 35 | end 36 | result 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/device/reader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Device::Reader 8 | PHYSICAL_SUPPORT = true 9 | VOLUME_NAME = "READER" 10 | DOCUMENTS_PATH_LIST = ["Sony_Reader/media/books"] 11 | EBOOK_FILE_EXT = ".epub" 12 | NAME = "Reader" 13 | DISPLAY_NAME = "SonyReader" 14 | 15 | RELATED_VARIABLES = { 16 | "default.enable_half_indent_bracket" => false, 17 | } 18 | end 19 | -------------------------------------------------------------------------------- /lib/eventable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Narou::Eventable 8 | EVENTS_CONTAINER = {} 9 | 10 | def self.included(base) 11 | super 12 | base.extend(ClassMethods) 13 | EVENTS_CONTAINER[base] = {} 14 | end 15 | 16 | class NonBlockError < StandardError; end 17 | 18 | module ClassMethods 19 | def add_event_listener(event_name, &block) 20 | raise NonBlockError unless block_given? 21 | stack = EVENTS_CONTAINER[self][event_name] ||= [] 22 | stack.push(block) 23 | end 24 | 25 | def trigger_event(event_name, *argv) 26 | stack = EVENTS_CONTAINER[self][event_name] 27 | return unless stack 28 | stack.each do |block| 29 | block.call(*argv) 30 | end 31 | end 32 | end 33 | 34 | def add_event_listener(event_name, once = false, &block) 35 | raise NonBlockError unless block_given? 36 | @__events_container ||= {} 37 | stack = @__events_container[event_name] ||= [] 38 | stack.push([block, once]) 39 | end 40 | 41 | def remove_event_listener(event_name, &block) 42 | @__events_container ||= {} 43 | if block_given? 44 | events = @__events_container[event_name] 45 | if events 46 | events.reject! do |values| 47 | values[0] == block 48 | end 49 | end 50 | else 51 | @__events_container.delete(event_name) 52 | end 53 | end 54 | 55 | def trigger_event(event_name, *argv) 56 | @__events_container ||= {} 57 | stack = @__events_container[event_name] 58 | return unless stack 59 | stack.reject! do |block, once| 60 | block.call(*argv) 61 | once 62 | end 63 | end 64 | 65 | alias :on :add_event_listener 66 | alias :off :remove_event_listener 67 | alias :trigger :trigger_event 68 | 69 | def one(event_name, &block) 70 | add_event_listener(event_name, true, &block) 71 | end 72 | end 73 | 74 | -------------------------------------------------------------------------------- /lib/extension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "open-uri" 8 | require "openssl" 9 | require_relative "inventory" 10 | 11 | # open-uri で http → https へのリダイレクトを有効にする 12 | require "open_uri_redirections" 13 | 14 | # open-uri に渡すオプションを生成(必要に応じて extensions/*.rb でオーバーライドする) 15 | def make_open_uri_options(add) 16 | ua = Inventory.load("local_setting")["user-agent"] || "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" 17 | add.merge(ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) 18 | add.merge("User-Agent" => ua) 19 | end 20 | 21 | # 22 | # 安全なファイルの書き込み 23 | # 24 | # ファイルに直接上書きしないで、一旦別名で作成してからファイル名変更をすることで、 25 | # ファイル書き込み中のPCクラッシュ等でデータが飛ばない様にする 26 | # 27 | require "securerandom" 28 | 29 | def File.write(path, string, *options, mode: nil) 30 | return super if mode 31 | 32 | dirpath = File.dirname(path) 33 | FileUtils.makedirs(dirpath) unless Dir.exist?(dirpath) 34 | temp_path = File.join(dirpath, SecureRandom.hex(15)) 35 | if File.extname(path) == ".yaml" && File.basename(dirpath) != Downloader::SECTION_SAVE_DIR_NAME 36 | backup = "#{path}.backup" 37 | end 38 | 39 | res = super(temp_path, string, *options) 40 | if backup 41 | super(backup, string, *options) 42 | end 43 | File.rename(temp_path, path) 44 | res 45 | end 46 | -------------------------------------------------------------------------------- /lib/extensions/jruby.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | if Helper.os_windows? 8 | require "fileutils" 9 | 10 | module FileUtils 11 | # マルチバイト文字を含むパスを認識出来ないため 12 | def self.cp(src, dst, opt = nil) 13 | open(src, "rb") do |fp| 14 | File.binwrite(dst, fp.read) 15 | end 16 | end 17 | end 18 | 19 | class File 20 | # 何故かエンコーディングエラーが出るため 21 | def self.binwrite(path, data) 22 | open(path, "wb") do |fp| 23 | fp.write(data) 24 | end 25 | end 26 | 27 | # -Dfile.encoding=UTF-8 を指定するとなぜか File.mtime がマルチバイト文字を含むパスを認識出来ないため 28 | def self.mtime(path) 29 | java_path = java.nio.file.FileSystems.default.getPath(path) 30 | java_file_time = java.nio.file.Files.getLastModifiedTime(java_path) 31 | Time.parse(java_file_time.to_s).getlocal("+09:00") 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/extensions/monkey_patches.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "monkey_patches/pathname" 8 | -------------------------------------------------------------------------------- /lib/extensions/monkey_patches/pathname.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "pathname" 8 | 9 | module Narou 10 | module MonkyPatches 11 | module PathnameGlob 12 | # Pathname#glob は Ruby2.5 から実装された 13 | def glob(pattern, flags = 0, &block) 14 | Pathname.glob(self.join(pattern), flags, &block) 15 | end 16 | end 17 | end 18 | end 19 | 20 | unless Pathname.method_defined?(:glob) 21 | Pathname.prepend(Narou::MonkyPatches::PathnameGlob) 22 | end 23 | -------------------------------------------------------------------------------- /lib/extensions/windows.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module WinAPI 8 | begin 9 | require "fiddle/import" 10 | extend Fiddle::Importer 11 | rescue LoadError 12 | # Fiddle がない環境用(http://www.artonx.org/data/asr/ の1.9.3とか) 13 | require "dl/import" 14 | extend DL::Importer 15 | end 16 | 17 | begin 18 | dlload "msvcrt", "kernel32" 19 | rescue DL::DLError 20 | dlload "crtdll", "kernel32" 21 | end 22 | extern "long GetLogicalDrives()" 23 | extern "unsigned long SetConsoleTextAttribute(unsigned long, unsigned long)" 24 | extern "unsigned long GetConsoleScreenBufferInfo(unsigned long, void*)" 25 | extern "unsigned long GetStdHandle(unsigned long)" 26 | extern "long GetLastError()" 27 | extern "unsigned long _getch()" 28 | extern "unsigned long GetDriveType(void*)" 29 | extern "unsigned long DeviceIoControl(unsigned long, unsigned long, void*, unsigned long, void*, unsigned long, void*, void*)" 30 | extern "unsigned long CreateFile(void*, unsigned long, unsigned long, void*, unsigned long, unsigned long, unsigned long)" 31 | extern "unsigned long CloseHandle(unsigned long)" 32 | end 33 | 34 | require "tmpdir" 35 | 36 | # テンポラリディレクトリのパスにASCII以外が含まれていると、systemu が 37 | # エラーになる問題に対処するモンキーパッチ。 38 | # テンポラリディレクトリをユーザーディレクトリからシステムに切り替える 39 | if Dir.tmpdir !~ /\A[ -~]+\z/ 40 | def Dir.tmpdir 41 | File.expand_path(File.join(ENV["SystemRoot"], "Temp")) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/input.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "termcolorlight" 8 | 9 | module Narou 10 | module Input 11 | module_function 12 | 13 | # 14 | # 肯定か否定かの確認を入力 15 | # 16 | # default: エンターを押した場合に返ってくる値 17 | # nontty_default: pipe等から接続された場合に返ってくる値 18 | # @return: yes = true, no = false 19 | # 20 | def confirm(message, default = false, nontty_default = true) 21 | return nontty_default unless $stdin.tty? 22 | confirm_msg = "#{message} (y/n)?: " 23 | print confirm_msg 24 | while input = $stdin.getch 25 | puts input 26 | case input.downcase 27 | when "y" 28 | return true 29 | when "n" 30 | return false 31 | else 32 | return default if input.strip == "" 33 | print confirm_msg 34 | end 35 | end 36 | end 37 | 38 | # 39 | # 選択肢を表示して選択させる 40 | # 41 | # choices: { 選択肢: 説明 } のハッシュ形式で渡す。選択出来ない状況(pipe等)の場合に返す 42 | # 値は :default の要素を返す。:default がない場合は先頭の要素 43 | # @return: 選ばれた選択肢を返す 44 | # 45 | # @example: 46 | # choices = { "ja" => "日本語", "en" => "English", default: "ja" } 47 | # Narou::Input.choose("Title: Select language", "Please select a language?", choices) 48 | # 49 | def choose(title, message, choices) 50 | default = choices[:default] || choices.first[0] 51 | return default unless $stdin.tty? 52 | puts title 53 | puts message 54 | choices.each do |name, help| 55 | next if name == :default 56 | puts "#{name}: #{help}".termcolor 57 | end 58 | loop do 59 | print "> " 60 | input = $stdin.gets.strip.downcase 61 | if key = choices.keys.delete(input) 62 | return key 63 | end 64 | puts "選択肢の中にありません。もう一度入力して下さい" 65 | end 66 | end 67 | end 68 | end 69 | 70 | -------------------------------------------------------------------------------- /lib/inventory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "yaml" 8 | require "ostruct" 9 | require_relative "narou" 10 | 11 | # 12 | # Narou.rbのシステムが記録するデータ単位 13 | # 14 | # .narou ディレクトリにYAMLファイルとして保存される 15 | # scope に :global を指定するとユーザーディレクトリ/.narousetting に保存される 16 | # 17 | module Inventory 18 | def self.load(name = "local_setting", scope = :local) 19 | @@cache ||= {} 20 | return @@cache[name] if @@cache[name] 21 | {}.tap { |h| 22 | h.extend(Inventory) 23 | h.init(name, scope) 24 | @@cache[name] = h 25 | } 26 | end 27 | 28 | def self.clear 29 | @@cache = {} 30 | end 31 | 32 | def init(name, scope) 33 | dir = case scope 34 | when :local 35 | Narou.local_setting_dir 36 | when :global 37 | Narou.global_setting_dir 38 | else 39 | raise "Unknown scope" 40 | end 41 | return nil unless dir 42 | @mutex = Mutex.new 43 | @inventory_file_path = File.join(dir, name + ".yaml") 44 | return unless File.exist?(@inventory_file_path) 45 | self.merge!(Helper::CacheLoader.memo(@inventory_file_path) { |yaml| 46 | begin 47 | YAML.unsafe_load(yaml) 48 | rescue Psych::SyntaxError 49 | unless restore(@inventory_file_path) 50 | error "#{@inventory_file_path} が壊れてるっぽい" 51 | raise 52 | end 53 | YAML.unsafe_load_file(@inventory_file_path) 54 | end 55 | }) 56 | end 57 | 58 | def save 59 | unless @inventory_file_path 60 | raise "not initialized setting dir yet" 61 | end 62 | @mutex.synchronize do 63 | File.write(@inventory_file_path, YAML.dump(self)) 64 | end 65 | end 66 | 67 | def restore(path) 68 | backup_path = "#{path}.backup" 69 | return nil unless File.exist?(backup_path) 70 | FileUtils.copy(backup_path, path) 71 | true 72 | end 73 | 74 | def group(group_name) 75 | result = {} 76 | each do |name, value| 77 | next unless name =~ /^#{group_name}\.(.+)$/ 78 | child_name = $1 79 | result[child_name] = value 80 | lodashed_name = child_name.tr("-", "_") 81 | result[lodashed_name] = value if child_name != lodashed_name 82 | end 83 | OpenStruct.new(result) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/loadconverter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | require_relative "converterbase" 7 | require_relative "helper" 8 | 9 | BlankConverter = Class.new(ConverterBase) {} 10 | 11 | $latest_converter = nil 12 | 13 | def converter(_title = nil, &block) 14 | $latest_converter = Class.new(ConverterBase, &block) 15 | end 16 | 17 | # 18 | # 小説固有コンバーターのロード 19 | # 20 | # ファイル名は converter.rb 固定で、変換処理のフックを定義できる。 21 | # 基本の変換処理の実行前にはbeforeメソッド、変換処理後にafterメソッドが呼ばれる。 22 | # このファイルを必ず用意する必要はない。 23 | # converter は ConverterBase を継承したクラスを生成するものである。 24 | # converter "タイトル" {} は class CONVERTER < ConverterBase; end にほぼ等しい。 25 | # 26 | # デフォルトの動作は ConverterBase#before 及び ConverterBase#aftert に定義されている。 27 | # super を呼ばなければ基本動作を抑制出来る。 28 | # 29 | # 引数の io は StringIO のオブジェクトであり、ファイル先頭に seek(rewind) してあることが保証される。 30 | # また、返却する IO オブジェクトはファイル先頭に seek しておく必要はない。 31 | # text_type は渡されるテキストがタイトルなのか、前書きなのか等の種別を判断する文字列が渡される。 32 | # 渡される文字列と意味: 33 | # story あらすじ 34 | # chapter 章のタイトル 35 | # subtitle 節のタイトル 36 | # introduction 前書き 37 | # body 本文 38 | # postscript 後書き 39 | # textfile テキストファイル形式で変換をしようとした場合 40 | # 41 | # @setting で setting.ini で設定した値が読み込める(ex. @setting.enable_num_to_kanji) 42 | # 43 | # e.g.) 44 | # converter "書籍のタイトル" do 45 | # # 共通変換処理の前に呼ばれる 46 | # def before(io, text_type) 47 | # io.string.gsub!("\n\n", "\n") # WEB小説に多い無駄な空改行を削除する 48 | # io # 返り値もStringIOで 49 | # end 50 | # 51 | # # 共通変換処理の後に呼ばれる 52 | # def after(io, text_type) 53 | # buffer = StringIO.new 54 | # io.each do |line| 55 | # ~何らかの処理~(共通変換処理で漢数字化されたものを特定部分だけアラビア数字に戻したり) 56 | # buffer.write ~~ 57 | # end 58 | # buffer 59 | # end 60 | # end 61 | def load_converter(archive_path) 62 | converter_path = File.join(archive_path, "converter.rb") 63 | if File.exist?(converter_path) 64 | $latest_converter = nil 65 | eval(File.read(converter_path, mode: "r:BOM|UTF-8"), binding, converter_path) 66 | else 67 | return BlankConverter 68 | end 69 | 70 | conv = $latest_converter 71 | if conv 72 | return conv 73 | else 74 | error "converter.rbは見つかりましたが、`converter'で登録されていないようです。" + 75 | "変換処理は converter do ... end として登録する必要があります" 76 | return BlankConverter 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "yaml" 8 | require "singleton" 9 | require_relative "narou" 10 | 11 | class Mailer 12 | include Singleton 13 | 14 | SETTING_FILE = "mail_setting.yaml" 15 | 16 | class SettingNotFound < StandardError; end 17 | class SettingUncompleteError < StandardError; end 18 | 19 | attr_reader :error_message 20 | 21 | def self.create 22 | this = instance 23 | this.clear 24 | setting_file_path = File.join(Narou.root_dir, SETTING_FILE) 25 | if File.exist?(setting_file_path) 26 | options = YAML.unsafe_load_file(setting_file_path) 27 | unless options.delete(:complete) 28 | raise SettingUncompleteError, "設定ファイルの書き換えが終了していないようです。\n" + 29 | "設定ファイルは #{setting_file_path} にあります" 30 | end 31 | this.options = options 32 | else 33 | raise SettingNotFound 34 | end 35 | this 36 | end 37 | 38 | def initialize 39 | @options = {} 40 | @error_message = "" 41 | end 42 | 43 | def clear 44 | @options.clear 45 | end 46 | 47 | def options=(options) 48 | @options.merge!(options) 49 | end 50 | 51 | def send(id, message, attached_file_path = nil) 52 | gem "mail", "2.6.6" 53 | require "pony" 54 | @error_message = "" 55 | params = @options.dup 56 | params[:body] = message 57 | params[:charset] = "UTF-8" 58 | params[:text_part_charset] = "UTF-8" 59 | if attached_file_path 60 | basename = "#{id}#{extname(attached_file_path)}" 61 | params[:attachments] = { basename => File.binread(attached_file_path) } 62 | end 63 | begin 64 | Pony.mail(params) 65 | rescue StandardError => e 66 | @error_message = e.message 67 | return false 68 | end 69 | true 70 | end 71 | 72 | # .kepub.epub を考慮する必要がある 73 | def extname(path) 74 | if File.basename(path) =~ /\A[^.]+(.+)/ 75 | $1 76 | else 77 | "" 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/mixin/all.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "output_error" 8 | require_relative "locker" 9 | -------------------------------------------------------------------------------- /lib/mixin/locker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Narou 8 | module Mixin 9 | module Locker 10 | def locked?(target) 11 | id = Downloader.get_id_by_target(target) or return false 12 | Inventory.load("lock").include?(id) 13 | end 14 | 15 | def lock(target) 16 | id = Downloader.get_id_by_target(target) 17 | unless id 18 | yield if block_given? 19 | return 20 | end 21 | locked_list = Inventory.load("lock") 22 | locked_list[id] = Time.now 23 | locked_list.save 24 | return unless block_given? 25 | begin 26 | yield 27 | ensure 28 | unlock(target) 29 | end 30 | end 31 | 32 | def unlock(target) 33 | id = Downloader.get_id_by_target(target) or return 34 | locked_list = Inventory.load("lock") 35 | return unless locked_list.delete(id) 36 | locked_list.save 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/mixin/output_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Narou 8 | module Mixin 9 | module OutputError 10 | def output_error(io, exception) 11 | outputter = proc do 12 | io.puts "#{$@.shift}: #{exception.message} (#{exception.class})" 13 | $@.each do |b| 14 | io.puts " from #{b}" 15 | end 16 | end 17 | # MEMO: report_on_exception は Ruby 2.4.0 から実装 18 | if Thread.respond_to?(:report_on_exception) && Thread.report_on_exception 19 | # report_on_exception が有効な場合は標準出力へのエラー表示はそっちに任せる。 20 | # silence を使うことで、標準出力には表示されないがログには記録される 21 | io.silence(&outputter) 22 | else 23 | outputter.call 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/narou/api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "open-uri" 8 | require "zlib" 9 | require "yaml" 10 | require "json" 11 | require "memoist" 12 | require_relative "../novelinfo" 13 | 14 | module Narou 15 | # 16 | # 小説家になろうデベロッパーAPI操作クラス 17 | # 18 | class API 19 | # 一度に問い合わせする件数 20 | BATCH_LIMIT = 300 21 | 22 | # なろうAPIの結果の圧縮レベル(1〜5) 23 | GZIP_LEVEL = 5 24 | 25 | # リクエスト間ウェイト(sec) 26 | REQUEST_INTERVAL = 3 27 | 28 | # 29 | # なろうデベロッパーAPIから情報を取得 30 | # 31 | # api_url: なろうAPIのエンドポイントを指定。通常小説用と18禁小説用と分かれているため 32 | # ncodes: 取得したい小説のNコードを指定。文字列か配列を指定可能 33 | # of: 出力パラメータ(http://dev.syosetu.com/man/api/#of_parm 参照) 34 | # 35 | def initialize(api_url:, ncodes:, of:) 36 | @api_url = api_url 37 | @ncodes = Array(ncodes).map(&:downcase) 38 | @of = "n-#{of}" 39 | @splited_of = @of.split("-") 40 | end 41 | 42 | def [](key) 43 | @api_result[key] 44 | end 45 | 46 | def request 47 | @stores = [] 48 | @ncodes.each_slice(BATCH_LIMIT).with_index do |ncodes, index| 49 | sleep REQUEST_INTERVAL unless index.zero? 50 | request_api(ncodes) 51 | end 52 | @stores 53 | end 54 | 55 | def request_api(ncodes, limit = BATCH_LIMIT) 56 | url = "#{@api_url}?gzip=#{GZIP_LEVEL}&ncode=#{ncodes.join("-")}&of=#{@of}&lim=#{limit}&out=json" 57 | URI.open(url) do |fp| 58 | data = Zlib::GzipReader.wrap(fp).read.force_encoding(Encoding::UTF_8) 59 | results = JSON.parse(data) 60 | return if results[0]["allcount"].zero? 61 | store_results(results.drop(1)) 62 | end 63 | end 64 | 65 | def store_results(results) 66 | @stores += results.map do |result| 67 | result["ncode"].downcase! 68 | result["novel_type"] = result["noveltype"] if has_of?("nt") 69 | result["writer"] = result["writer"].to_s if has_of?("w") 70 | %w(general_firstup general_lastup novelupdated_at).each do |key| 71 | result[key] &&= Time.parse(result[key]) 72 | end 73 | stat_end = result["end"] 74 | if stat_end 75 | result["end"] = stat_end.zero? 76 | end 77 | result 78 | end 79 | end 80 | 81 | def has_of?(type) 82 | @splited_of.include?(type) 83 | end 84 | 85 | def private_novels 86 | (@ncodes - @stores.map { |st| st["ncode"] }).map do |ncode| 87 | Downloader.get_data_by_target(ncode)["id"] 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/progressbar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | class ProgressBar 8 | class OverRangeError < StandardError; end 9 | 10 | attr_reader :io 11 | 12 | def initialize(max, interval = 1, width = 50, char = "*", io: $stdout) 13 | @max = max == 0 ? 1.0 : max.to_f 14 | @interval = interval 15 | @width = width 16 | @char = char 17 | @counter = 0 18 | @io = io 19 | end 20 | 21 | def output(num) 22 | return if silent? 23 | if num > @max 24 | raise OverRangeError, "`#{num}` over `#{@max}(max)`" 25 | end 26 | @counter += 1 27 | return unless @counter % @interval == 0 28 | ratio = calc_ratio(num) 29 | now = (@width * ratio).round 30 | rest = @width - now 31 | io.stream.print format("[%s%s] %d%%\r", @char * now, " " * rest, (ratio * 100).round) 32 | end 33 | 34 | def clear 35 | return if silent? 36 | io.stream.print "\e[2K\r" # 行削除して行頭へ移動 37 | end 38 | 39 | def calc_ratio(num) 40 | num / @max 41 | end 42 | 43 | def silent? 44 | ENV["NAROU_ENV"] == "test" || !io.tty? || io.silent? 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "erb" 8 | require_relative "narou" 9 | 10 | class Template 11 | TEMPLATE_DIR = "template/" 12 | OVERWRITE = true 13 | 14 | class LoadError < StandardError; end 15 | 16 | # 17 | # テンプレートを元にファイルを作成 18 | # 19 | # src_filename 読み込みたいテンプレートファイル名(.erb は省略する) 20 | # dest_filepath 保存先ファイルパス。ディレクトリならファイル名はsrcと同じ名前で保存する 21 | # _binding 変数とか設定したいスコープの binding 変数を渡す 22 | # overwrite 上書きするか 23 | # 24 | def self.write(src_filename, dest_filepath, _binding, binary_version, overwrite = false) 25 | if File.directory?(dest_filepath) 26 | dest_filepath = File.join(dest_filepath, src_filename) 27 | end 28 | unless overwrite 29 | return if File.exist?(dest_filepath) 30 | end 31 | result = get(src_filename, _binding, binary_version) or return nil 32 | if Helper.os_windows? 33 | File.write(dest_filepath, result) 34 | else 35 | File.binwrite(dest_filepath, result.lstrip) 36 | end 37 | end 38 | 39 | # 40 | # テンプレートを元にデータを作成 41 | # 42 | # テンプレートファイルの検索順位 43 | # 1. root_dir/template 44 | # 2. script_dir/template 45 | # 46 | def self.get(src_filename, _binding, binary_version) 47 | @@binary_version = binary_version 48 | @@src_filename = src_filename 49 | [Narou.root_dir, Narou.script_dir].each do |dir| 50 | path = dir.join(TEMPLATE_DIR, src_filename + ".erb") 51 | next unless path.exist? 52 | src = Helper::CacheLoader.load(path) 53 | result = ERB.new(src, trim_mode: "-").result(_binding) 54 | return result 55 | end 56 | raise LoadError, "テンプレートファイルが見つかりません。(#{src_filename}.erb)" 57 | end 58 | 59 | def self.invalid_templace_version? 60 | @@src_version != @@binary_version 61 | end 62 | 63 | # 64 | # 書かれているテンプレートがどのバージョンのテンプレートかを設定 65 | # 66 | # テンプレート内部で使われる変数の変更があった場合に binary_version が上がる 67 | # (変数の追加ではバージョンは上がらない。現在使われている変数の中身が変わった場合は上る) 68 | # 69 | def self.target_binary_version(version) 70 | @@src_version = version 71 | if invalid_templace_version? 72 | error "テンプレートのバージョンが異なるので意図しない動作をする可能性があります\n" + 73 | "(#{@@src_filename}.erb ver #{version.to_f} != #{@@binary_version.to_f})" 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Narou 8 | VERSION = "3.9.1" 9 | end 10 | -------------------------------------------------------------------------------- /lib/web/all.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "appserver" 8 | require_relative "pushserver" 9 | require_relative "streaminglogger" 10 | require_relative "streaminginput" 11 | require_relative "progressbar4web" 12 | require_relative "helper4web" 13 | 14 | -------------------------------------------------------------------------------- /lib/web/helper4web.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "../helper" 8 | 9 | module Helper 10 | module_function 11 | 12 | HR_TAG = "
" 13 | 14 | class << self 15 | alias :original_print_horizontal_rule :print_horizontal_rule 16 | end 17 | 18 | def print_horizontal_rule(io = $stdout) 19 | # タグがキャプチャーされると困るので、キャプチャ専用の出力とWEBへの出力は分ける 20 | if $stdout.capturing 21 | $stdout.silence do 22 | original_print_horizontal_rule($stdout) 23 | end 24 | else 25 | io.append_log("#{HR_TEXT}\n") 26 | end 27 | io.push_streaming(HR_TAG) 28 | io.original_write(HR_TAG) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/web/progressbar4web.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require_relative "../progressbar" 8 | 9 | # 10 | # コンソール用のプログレスバーはWEB UIでは使えないため置き換える 11 | # 12 | class ProgressBar 13 | def self.push_server=(server) 14 | @@push_server = server 15 | end 16 | 17 | alias :original_initialize :initialize 18 | 19 | def initialize(*args, **opt) 20 | original_initialize(*args, **opt) 21 | @@push_server.send_all("progressbar.init" => { target_console: io.target_console }) 22 | end 23 | 24 | def output(num) 25 | percent = calc_ratio(num) * 100 26 | @@push_server.send_all("progressbar.step" => { 27 | percent: percent, 28 | target_console: io.target_console 29 | }) 30 | end 31 | 32 | def clear 33 | @@push_server.send_all("progressbar.clear" => { target_console: io.target_console }) 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /lib/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/favicon.ico -------------------------------------------------------------------------------- /lib/web/public/resources/default-style.css: -------------------------------------------------------------------------------- 1 | 2 | .navbar { 3 | background-color: rgba(248,248,248,0.93); 4 | } 5 | @media (max-width:767px) { 6 | .navbar .navbar-collapse { 7 | background-color: rgba(248,248,248,0.93); 8 | } 9 | } 10 | 11 | .navbar ul.dropdown-menu li.active a { 12 | background-color: inherit !important; 13 | color: inherit !important; 14 | } 15 | .navbar ul.dropdown-menu li.active a:hover { 16 | background-color: #f5f5f5 !important; 17 | color: inherit !important; 18 | } 19 | @media (max-width:767px) { 20 | .navbar ul.dropdown-menu li.active a:hover { 21 | background-color: inherit !important; 22 | } 23 | } 24 | 25 | .queue-plus { 26 | color: #d9534f; 27 | font-weight: bold; 28 | } 29 | 30 | .queue.active { 31 | color: #d9534f !important; 32 | } 33 | 34 | .queue.active .queue__sizes { 35 | background-color: #d9534f !important; 36 | } 37 | -------------------------------------------------------------------------------- /lib/web/public/resources/help/bookmarklet1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/help/bookmarklet1.png -------------------------------------------------------------------------------- /lib/web/public/resources/help/bookmarklet2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/help/bookmarklet2.png -------------------------------------------------------------------------------- /lib/web/public/resources/help/rect_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/help/rect_select.png -------------------------------------------------------------------------------- /lib/web/public/resources/help/ssmain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/help/ssmain.png -------------------------------------------------------------------------------- /lib/web/public/resources/help/tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/help/tag.png -------------------------------------------------------------------------------- /lib/web/public/resources/images/dl_button0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/images/dl_button0.gif -------------------------------------------------------------------------------- /lib/web/public/resources/images/dl_button1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/images/dl_button1.gif -------------------------------------------------------------------------------- /lib/web/public/resources/images/dl_button2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/images/dl_button2.gif -------------------------------------------------------------------------------- /lib/web/public/resources/jquery.moveto.js: -------------------------------------------------------------------------------- 1 | /* -*- coding: utf-8 -*- 2 | * 3 | * jquery.moveto.js 4 | * Copyright 2014 whiteleaf. All rights reserved. 5 | * 6 | * Usage: 7 | * $.moveTo({ 8 | * duration: 500 9 | * }); 10 | * 11 | * in your html: 12 | * Jump 13 | * ==/snip/== 14 | *

Section #2

15 | */ 16 | 17 | (function($) { 18 | "use strict"; 19 | // default options 20 | var options = { 21 | duration: "fast" 22 | }; 23 | var touchable_device = "ontouchstart" in window; 24 | var toggle_event_name = (touchable_device ? "touchstart" : "click"); 25 | var moveTo = { 26 | initialize: function() { 27 | var body_padding_top = parseInt($("body").css("padding-top")); 28 | return $("[data-move-to]").on(toggle_event_name, function(e) { 29 | e.preventDefault(); 30 | var target = $(this).data("moveTo"); 31 | var target_y = 0; 32 | if (target !== "top") { 33 | target_y = $(target).offset().top - body_padding_top; 34 | } 35 | $("html,body").animate({ scrollTop: target_y }, options.duration); 36 | }); 37 | }, 38 | }; 39 | $.moveTo = function(opts) { 40 | $.extend(options, opts); 41 | return moveTo.initialize(); 42 | }; 43 | }(jQuery)); 44 | 45 | -------------------------------------------------------------------------------- /lib/web/public/resources/jquery.outerclick.js: -------------------------------------------------------------------------------- 1 | /* -*- coding: utf-8 -*- 2 | * 3 | * jquery.outerclick.js 4 | * Copyright 2014 whiteleaf. All rights reserved. 5 | * 6 | * Usage: 7 | * $("#menu").outerClick(function() { 8 | * $(this).hide(); // do something 9 | * }); 10 | * $("#menu").outerClickOne(function() { 11 | * // once process 12 | * }); 13 | * either 14 | * $("#menu").outerClick(function(){}, "one"); 15 | */ 16 | 17 | (function($) { 18 | "use strict"; 19 | var outerClick = function(obj, callback, bind_func) { 20 | var self = this; 21 | $(document)[bind_func]("click", function(e) { 22 | var pos = self.getEventPosition(e); 23 | var element = self.elementFromPoint(pos); 24 | if (obj !== element) { 25 | if (!self.searchTargetUpstream(element, obj)) { 26 | callback.apply(obj); 27 | } 28 | } 29 | }); 30 | }; 31 | $.extend(outerClick.prototype, { 32 | elementFromPoint : function(pos) { 33 | var doc = $(document); 34 | var element = document.elementFromPoint(pos.x - doc.scrollLeft(), 35 | pos.y - doc.scrollTop()); 36 | return element; 37 | }, 38 | getEventPosition : function(event) { 39 | return { x: event.pageX, y: event.pageY }; 40 | }, 41 | searchTargetUpstream : function(element, target) { 42 | var parent = $(element).parent(); 43 | if (parent.length === 0) return null; 44 | if (parent[0] === target) return parent; 45 | return this.searchTargetUpstream(parent, target); 46 | }, 47 | }); 48 | $.fn.outerClick = function(callback, bind_func) { 49 | if (typeof bind_func === "undefined") bind_func = "on"; 50 | return this.each(function() { 51 | new outerClick(this, callback, bind_func); 52 | }); 53 | }; 54 | $.fn.outerClickOne = function(callback) { 55 | return this.each(function() { 56 | new outerClick(this, callback, "one"); 57 | }); 58 | } 59 | }(jQuery)); 60 | 61 | -------------------------------------------------------------------------------- /lib/web/public/resources/jquery.slidenavbar.js: -------------------------------------------------------------------------------- 1 | /* -*- coding: utf-8 -*- 2 | * 3 | * jquery.slideNavbar.js 4 | * Copyright 2014 whiteleaf. All rights reserved. 5 | * 6 | * Usage: 7 | * var slideNavbar = $(".navbar-collapse").slideNavbar(); 8 | * slideNavbar.slide(); // slide nav manually 9 | * slideNavbar.slide(true); // no animation 10 | * 11 | * in your css: 12 | * @media (max-width:767px) { 13 | * .navbar-collapse { 14 | * position: absolute; 15 | * right: -270px; 16 | * width: 270px; 17 | * } 18 | * } 19 | * 20 | * Require: 21 | * Bootstrap v3.2.0 or higher 22 | */ 23 | 24 | (function($) { 25 | "use strict"; 26 | // default options 27 | var options = { 28 | hiding_class: "slide-hiding-collapse", 29 | width: 270, 30 | duration: 250 31 | }; 32 | var touchable_device = "ontouchstart" in window; 33 | var toggle_event_name = (touchable_device ? "touchstart" : "click"); 34 | var slideNavbar = { 35 | initialize: function(i, obj) { 36 | var collapse = $(obj); 37 | var self = this; 38 | this.hiding_class = options.hiding_class; 39 | this.collapse = collapse; 40 | collapse.css({ 41 | "max-height": $(window).height() * 0.9, 42 | }); 43 | $(".navbar-toggle").off("click"); 44 | $(".navbar-toggle").on(toggle_event_name, function(e) { 45 | e.preventDefault(); 46 | e.stopPropagation(); 47 | self.slide(); 48 | }); 49 | collapse.addClass(self.hiding_class); 50 | collapse.addClass("in"); // enable visible collpase nav 51 | }, 52 | slide: function(force_close) { 53 | var duration_org = options.duration; 54 | if (this.collapse.hasClass(this.hiding_class) && !force_close) { 55 | this.slideOpen(); 56 | this.collapse.removeClass(this.hiding_class); 57 | } 58 | else { 59 | this.slideClose(); 60 | this.collapse.addClass(this.hiding_class); 61 | } 62 | }, 63 | slideOpen: function() { 64 | this.collapse.stop().animate({right: "0"}, options.duration); 65 | }, 66 | slideClose: function() { 67 | this.collapse.stop().animate({ 68 | right: "-" + options.width + "px" 69 | }, options.duration); 70 | }, 71 | }; 72 | var Api = function() {}; 73 | Api.prototype.slide = function(force_close) { 74 | if ($(".navbar-toggle").is(":visible")) { 75 | slideNavbar.slide(force_close); 76 | } 77 | }; 78 | Api.prototype.close = function() { 79 | slideNavbar.slide(true); 80 | }; 81 | $.fn.slideNavbar = function(opts) { 82 | $.extend(options, opts); 83 | this.each(function(i, obj) { 84 | slideNavbar.initialize(i, obj); 85 | }); 86 | return new Api(); 87 | }; 88 | }(jQuery)); 89 | 90 | -------------------------------------------------------------------------------- /lib/web/public/resources/narou.queue.js: -------------------------------------------------------------------------------- 1 | /* -*- coding: utf-8 -*- 2 | * 3 | * Copyright 2013 whiteleaf. All rights reserved. 4 | */ 5 | 6 | /* 7 | * キュー関係のコード 8 | */ 9 | $(function() { 10 | "use strict"; 11 | 12 | /* 13 | * キューに積まれた数を表示 14 | */ 15 | var notification = Narou.Notification.instance(); 16 | var $queue = $(".queue"); 17 | var $queueSizes = $(".queue__sizes"); 18 | var $queueSizeDefault = $(".queue__size--default"); 19 | var $queueSizeConvert = $(".queue__size--convert"); 20 | 21 | function highlightQueuBoxIcon(sizes) { 22 | if (sizes[0] || sizes[1]) { 23 | $queue.addClass("active"); 24 | } 25 | else { 26 | $queue.removeClass("active"); 27 | } 28 | } 29 | 30 | function setQueueSizesText(sizes) { 31 | $queueSizeDefault.text(sizes[0]); 32 | if (Narou.concurrencyIsEnabled()) { 33 | $queueSizeConvert.text(sizes[1]); 34 | } 35 | highlightQueuBoxIcon(sizes); 36 | } 37 | 38 | notification.on("notification.queue", function(sizes) { 39 | setQueueSizesText(sizes); 40 | }); 41 | 42 | $.get("/api/get_queue_size", function(sizes) { 43 | setQueueSizesText(sizes); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /lib/web/public/resources/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/sort_asc.png -------------------------------------------------------------------------------- /lib/web/public/resources/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/resources/sort_desc.png -------------------------------------------------------------------------------- /lib/web/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | 4 | -------------------------------------------------------------------------------- /lib/web/public/test/jquery.outerclick.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | test jquery.outerclick.js 11 | 12 | 13 | 17 | 18 | 39 | 40 | 41 | 42 |
43 | 46 | 47 | 58 |
59 | 60 | 61 | 62 | 63 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /lib/web/public/theme/Cerulean/style.css: -------------------------------------------------------------------------------- 1 | 2 | @media (max-width:767px) { 3 | .navbar .navbar-collapse { 4 | background-color: #2FA4E7; 5 | } 6 | } 7 | 8 | @media (min-width:768px) { 9 | .navbar ul.dropdown-menu li.active a { 10 | background-color: inherit !important; 11 | color: inherit !important; 12 | } 13 | .navbar ul.dropdown-menu li.active a:hover { 14 | background-color: #2FA4E7 !important; 15 | color: #fff !important; 16 | } 17 | } 18 | @media (max-width:767px) { 19 | .navbar ul.dropdown-menu li.active a { 20 | background-color: inherit !important; 21 | } 22 | .navbar ul.dropdown-menu li.active a:hover { 23 | color: #fff !important; 24 | background-color: #178acc !important; 25 | } 26 | } 27 | 28 | .queue { 29 | color: #fff !important; 30 | } 31 | 32 | .queue__sizes { 33 | color: #2FA4E7 !important; 34 | background-color: #fff !important; 35 | } 36 | 37 | .queue-plus { 38 | color: #d9534f; 39 | font-weight: bold; 40 | } 41 | 42 | .queue.active { 43 | color: #d9534f !important; 44 | } 45 | 46 | .queue.active .queue__sizes { 47 | color: white !important; 48 | background-color: #d9534f !important; 49 | } 50 | -------------------------------------------------------------------------------- /lib/web/public/theme/Darkly/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | padding-top: 85px !important; 4 | } 5 | 6 | @media (max-width:767px) { 7 | .navbar .navbar-collapse { 8 | background-color: #375a7f; 9 | } 10 | } 11 | 12 | @media (min-width:768px) { 13 | .navbar ul.dropdown-menu li.active a { 14 | background-color: inherit !important; 15 | color: inherit !important; 16 | } 17 | .navbar ul.dropdown-menu li.active a:hover { 18 | background-color: #375a7f !important; 19 | color: #fff !important; 20 | } 21 | } 22 | @media (max-width:767px) { 23 | .navbar ul.dropdown-menu li.active a { 24 | background-color: inherit !important; 25 | } 26 | .navbar ul.dropdown-menu li.active a:hover { 27 | color: #00bc8c !important; 28 | background-color: transparent !important; 29 | } 30 | } 31 | 32 | .queue { 33 | color: #fff !important; 34 | } 35 | 36 | .queue__sizes { 37 | color: #375a7f !important; 38 | background-color: #fff !important; 39 | } 40 | 41 | .queue-plus { 42 | color: #d9534f; 43 | font-weight: bold; 44 | } 45 | 46 | .queue.active { 47 | color: #d9534f !important; 48 | } 49 | 50 | .queue.active .queue__sizes { 51 | color: white !important; 52 | background-color: #d9534f !important; 53 | } 54 | -------------------------------------------------------------------------------- /lib/web/public/theme/Readable/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | padding-top: 85px !important; 4 | } 5 | 6 | @media (max-width:767px) { 7 | .navbar .navbar-collapse { 8 | background-color: #fff; 9 | } 10 | } 11 | 12 | @media (min-width:768px) { 13 | .navbar ul.dropdown-menu li.active a { 14 | background-color: inherit !important; 15 | color: inherit !important; 16 | } 17 | .navbar ul.dropdown-menu li.active a:hover { 18 | background-color: #4582ec !important; 19 | color: #fff !important; 20 | } 21 | } 22 | @media (max-width:767px) { 23 | .navbar ul.dropdown-menu li.active a { 24 | background-color: inherit !important; 25 | } 26 | .navbar ul.dropdown-menu li.active a:hover { 27 | color: #4582ec !important; 28 | background-color: #fff !important; 29 | } 30 | } 31 | 32 | .queue { 33 | color: #4582ec !important; 34 | } 35 | 36 | .queue__sizes { 37 | color: #fff !important; 38 | background-color: #4582ec !important; 39 | } 40 | 41 | .queue-plus { 42 | color: #d9534f; 43 | font-weight: bold; 44 | } 45 | 46 | .queue.active { 47 | color: #d9534f !important; 48 | } 49 | 50 | .queue.active .queue__sizes { 51 | color: white !important; 52 | background-color: #d9534f !important; 53 | } 54 | -------------------------------------------------------------------------------- /lib/web/public/theme/Slate/style.css: -------------------------------------------------------------------------------- 1 | 2 | @media (max-width:767px) { 3 | .navbar .navbar-collapse { 4 | background-color: #3A3F44; 5 | } 6 | } 7 | 8 | @media (min-width:768px) { 9 | .navbar ul.dropdown-menu li.active a { 10 | background-color: inherit !important; 11 | color: inherit !important; 12 | } 13 | .navbar ul.dropdown-menu li.active a:hover { 14 | background-color: #272b2e !important; 15 | color: #fff !important; 16 | } 17 | } 18 | @media (max-width:767px) { 19 | .navbar ul.dropdown-menu li.active a { 20 | background-color: inherit !important; 21 | } 22 | .navbar ul.dropdown-menu li.active a:hover { 23 | color: #fff !important; 24 | background-color: #272b2e !important; 25 | } 26 | } 27 | 28 | .queue-plus { 29 | color: #d9534f; 30 | font-weight: bold; 31 | } 32 | 33 | .queue.active { 34 | color: #d9534f !important; 35 | } 36 | 37 | .queue.active .queue__sizes { 38 | background-color: #d9534f !important; 39 | } 40 | -------------------------------------------------------------------------------- /lib/web/public/theme/Superhero/style.css: -------------------------------------------------------------------------------- 1 | 2 | @media (max-width:767px) { 3 | .navbar .navbar-collapse { 4 | background-color: #4E5D6C; 5 | } 6 | } 7 | 8 | @media (min-width:768px) { 9 | .navbar ul.dropdown-menu li.active a { 10 | background-color: inherit !important; 11 | color: inherit !important; 12 | } 13 | .navbar ul.dropdown-menu li.active a:hover { 14 | background-color: #485563 !important; 15 | color: #ebebeb !important; 16 | } 17 | } 18 | @media (max-width:767px) { 19 | .navbar ul.dropdown-menu li.active a { 20 | background-color: inherit !important; 21 | } 22 | .navbar ul.dropdown-menu li.active a:hover { 23 | color: #ebebeb !important; 24 | background-color: #485563 !important; 25 | } 26 | } 27 | 28 | .queue-plus { 29 | color: #d9534f; 30 | font-weight: bold; 31 | } 32 | 33 | .queue.active { 34 | color: #d9534f !important; 35 | } 36 | 37 | .queue.active .queue__sizes { 38 | background-color: #d9534f !important; 39 | } 40 | -------------------------------------------------------------------------------- /lib/web/public/theme/United/style.css: -------------------------------------------------------------------------------- 1 | 2 | @media (max-width:767px) { 3 | .navbar .navbar-collapse { 4 | background-color: #DD4814; 5 | } 6 | } 7 | 8 | @media (min-width:768px) { 9 | .navbar ul.dropdown-menu li.active a { 10 | background-color: inherit !important; 11 | color: inherit !important; 12 | } 13 | .navbar ul.dropdown-menu li.active a:hover { 14 | background-color: #DD4814 !important; 15 | color: #fff !important; 16 | } 17 | } 18 | @media (max-width:767px) { 19 | .navbar ul.dropdown-menu li.active a { 20 | background-color: #DD4814 !important; 21 | } 22 | .navbar ul.dropdown-menu li.active a:hover { 23 | background-color: #ae3910 !important; 24 | } 25 | } 26 | 27 | .queue { 28 | color: #fff !important; 29 | } 30 | 31 | .queue__sizes { 32 | color: #DD4814 !important; 33 | background-color: #fff !important; 34 | } 35 | 36 | .queue-plus { 37 | color: yellow; 38 | font-weight: bold; 39 | } 40 | 41 | .queue.active { 42 | color: yellow !important; 43 | } 44 | 45 | .queue.active .queue__sizes { 46 | background-color: yellow !important; 47 | } 48 | -------------------------------------------------------------------------------- /lib/web/public/theme/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/theme/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /lib/web/public/theme/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/theme/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /lib/web/public/theme/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/theme/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /lib/web/public/theme/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/lib/web/public/theme/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /lib/web/settingmessages.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | module Narou 8 | # WEB UI > 環境設定画面で表示する各項目の説明 9 | # ここになければ元々の説明が表示される 10 | SETTING_VARIABLES_WEBUI_MESSAGES = { 11 | "convert.multi-device" => "複数の端末用に同時に変換する。deviceよりも優先される。\nただのEPUBを出力したい場合はepubを指定", 12 | "device" => "変換、送信対象の端末", 13 | "difftool" => "%%ORIG%%。※WEB UIでは使われません", 14 | "update.sort-by" => "アップデートを指定した項目順で行う", 15 | "default.title_date_align" => "enable_add_date_to_title で付与する日付の位置", 16 | "force.title_date_align" => "enable_add_date_to_title で付与する日付の位置", 17 | "difftool.arg" => "difftoolで使う引数(指定しなければ単純に新旧ファイルを引数に呼び出す)\n" \ 18 | "特殊な変数\n" \ 19 | "%NEW : 最新データの差分用ファイルパス\n" \ 20 | "%OLD : 古い方の差分用ファイルパス", 21 | "no-color" => "コンソールのカラー表示を無効にする\n※要サーバ再起動", 22 | "economy" => "容量節約に関する設定", 23 | "send.without-freeze" => "一括送信時に凍結された小説は対象外にする。(個別送信時は凍結済みでも送信可能)", 24 | "server-digest-auth.enable" => "%%ORIG%%\n※digest-auth関連の設定を変更した場合サーバの再起動が必要", 25 | "server-digest-auth.hashed-password" => "サーバのDigest認証のパスワードを、Realmを\"narou.rb\"としてハッシュにしたもの。\n" \ 26 | "https://tgws.plus/app/digest/ などで生成できる", 27 | "concurrency" => "%%ORIG%% ※要サーバ再起動", 28 | "logging" => "%%ORIG%%\n※要サーバ再起動", 29 | "logging.format-filename" => "%%ORIG%%\n※要サーバ再起動", 30 | "logging.format-timestamp" => "%%ORIG%%\n※要サーバ再起動", 31 | } 32 | end 33 | -------------------------------------------------------------------------------- /lib/web/streaminglogger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | class String 8 | if $disable_color 9 | def termcolor 10 | TermColorLight.strip_tag(self, false) 11 | end 12 | else 13 | require "termcolorlight/html" 14 | 15 | def termcolor 16 | TermColorLight.to_html(self) 17 | end 18 | end 19 | end 20 | 21 | # 22 | # 標準出力をフックする 23 | # 24 | # $stdout を介した出力は echo イベントとして全てのクライアントへ送信される 25 | # 26 | module Narou 27 | class StreamingLogger < StringIO 28 | include Narou::LoggerModule 29 | 30 | attr_reader :push_server, :target_console 31 | 32 | def initialize(push_server, original_stream = $stdout, target_console: "stdout") 33 | super() 34 | @push_server = push_server 35 | @target_console = target_console 36 | self.log_postfix = original_stream.log_postfix 37 | original_stream.string.clear 38 | end 39 | 40 | def tty? 41 | false 42 | end 43 | 44 | def copy_instance 45 | self.class.new(@push_server).tap do |obj| 46 | obj.silent = silent? 47 | end 48 | end 49 | 50 | def strip_color(str) 51 | if $disable_color 52 | str 53 | else 54 | str.gsub(%r!!, "") 55 | end 56 | end 57 | 58 | def push_streaming(str, no_history: false) 59 | return if silent? 60 | @push_server&.send_all(echo: build_echo(str, no_history)) 61 | end 62 | 63 | def build_echo(str, no_history) 64 | { 65 | target_console: target_console, 66 | body: str, 67 | no_history: no_history 68 | } 69 | end 70 | 71 | def write(str) 72 | str = str.to_s 73 | if str.encoding == Encoding::ASCII_8BIT 74 | str.force_encoding(Encoding::UTF_8) 75 | end 76 | super(str) 77 | push_streaming(str) 78 | append_log(str) 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/web/views/_diff_list.haml: -------------------------------------------------------------------------------- 1 | .diff-list-container 2 | .title #{@list[:title]} 3 | .list{ "data-diff-target" => @list[:id] } 4 | - if @list[:list].empty? 5 | %i 6 | (差分がまだ存在しません) 7 | - else 8 | - @list[:list].each do |data| 9 | - next if data[:objects].empty? 10 | .item{ "data-diff-item-number" => data[:number] } 11 | .date #{data[:time].strftime("%Y/%m/%d %H:%M")} 12 | %ul.subtitles 13 | - data[:objects].each do |object| 14 | %li 15 | 第#{object[:index]}部分 #{object[:subtitle]} 16 | -------------------------------------------------------------------------------- /lib/web/views/_edit_replace_txt.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | local_initialize_functions.push(function($) { 3 | var tooltip_opt = { 4 | animation: false, 5 | container: "body", 6 | }; 7 | // replace.txt 設定パネル 8 | $(".btn-replace-remove").on("click", function(e) { 9 | e.preventDefault(); 10 | if ($(".replace-pattern-tr").size() === 1) { 11 | // 入力欄のtrが全部なくなると追加出来なくなるので、消す前に1個追加しておく 12 | $("#btn-replace-add").trigger("click"); 13 | } 14 | $(this).tooltip("destroy").closest("tr").remove(); 15 | }); 16 | $blank_tr = $(".replace-pattern-tr:last").clone(true); // 読み込み直後の末尾は空なので流用 17 | $("#btn-replace-add").on("click", function(e) { 18 | e.preventDefault(); 19 | var $clone = $blank_tr.clone(true); 20 | $clone.find("button").tooltip(tooltip_opt); 21 | $(".replace-pattern-tr:last").after($clone); 22 | $(this).tooltip("hide"); 23 | }); 24 | $("[data-toggle=remove-tooltip]").tooltip(tooltip_opt); 25 | }); 26 | 27 | #replace-panel.panel.panel-settings{ class: replace_panel_var[:panel_class] } 28 | .panel-heading 29 | = replace_panel_var[:title] 30 | .panel-body{ class: replace_panel_var[:body_class] } 31 | = replace_panel_var[:body].join("
") 32 | .list-group 33 | .list-group-item.form-group 34 | .list-group-item-text 35 | %table#replace-txt-table 36 | %thead 37 | %tr 38 | %td 対象文字列 39 | %td   40 | %td 置き換える文字列 41 | %td   42 | %tbody 43 | -# 末尾に新規入力用の空要素を追加 44 | - (@replace_pattern + [["",""]]).each do |pattern| 45 | %tr.replace-pattern-tr 46 | %td 47 | %input{type:"text", name:"replace_pattern[][left]", value:pattern[0], class:"form-control"} 48 | %td 49 | %span.glyphicon.glyphicon-chevron-right 50 | %td 51 | %input{type:"text", name:"replace_pattern[][right]", value:pattern[1], class:"form-control"} 52 | %td 53 | %button.btn-replace-remove.btn.btn-default(data-toggle="remove-tooltip" title="削除" data-placement="right") 54 | %span.glyphicon.glyphicon-remove 55 | %tr 56 | %td(colspan=4) 57 | %button#btn-replace-add.btn.btn-default(data-toggle="tooltip" title="1行追加" data-placement="right") 58 | %span.glyphicon.glyphicon-plus 59 | 60 | -------------------------------------------------------------------------------- /lib/web/views/_header.haml: -------------------------------------------------------------------------------- 1 | :ruby 2 | enable_save_settings ||= false 3 | nav ||= '' 4 | 5 | .navbar.navbar-default.navbar-fixed-top(role="navigation") 6 | .container 7 | .navbar-header 8 | %button.navbar-toggle(type="button") 9 | %span.sr-only Toggle navigation 10 | %span.icon-bar 11 | %span.icon-bar 12 | %span.icon-bar 13 | %a.navbar-brand(href="#" data-move-to="top") Narou.rb WEB UI 14 | .collapse.navbar-collapse 15 | %ul.nav.navbar-nav 16 | %li{ class: ("active" if nav == "settings") } 17 | %a(href="/settings") 環境設定 18 | %li{ class: ("active" if nav == "help") } 19 | %a(href="/help") ヘルプ 20 | %li 21 | %a(href="/") 戻る 22 | - if enable_save_settings 23 | .navbar-form.navbar-left 24 | %input.btn.btn-info(type="submit" value="設定を保存" style="margin-left: 50px") 25 | %button#btn-reboot.btn.btn-default サーバを再起動 26 | = partial :queue 27 | -------------------------------------------------------------------------------- /lib/web/views/_move_to_top.haml: -------------------------------------------------------------------------------- 1 | .move-to-top{ "data-move-to": "top" } 2 | %i.glyphicon.glyphicon-arrow-up.icon 3 | -------------------------------------------------------------------------------- /lib/web/views/_queue.haml: -------------------------------------------------------------------------------- 1 | %p.queue.navbar-text.navbar-right(data-toggle="tooltip" title="キューに積まれた処理の数" data-placement="bottom") 2 | %span.glyphicon.glyphicon-inbox 3 | %span.queue__sizes 4 | %span.queue__size.queue__size--default> 0 5 | - if Narou.concurrency_enabled? 6 | %span.queue__size.queue__size--divider 7 | %span.queue__size.queue__size--convert> 0 8 | -------------------------------------------------------------------------------- /lib/web/views/_rebooting.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | var busy = $("#rebooting-busy"); 3 | busy.text("."); 4 | setInterval(function() { 5 | busy.text(busy.text() + "."); 6 | if (busy.text().length == 6) { 7 | busy.text("."); 8 | } 9 | }, 500); 10 | 11 | %p 12 | サーバを再起動しています 13 | %p 14 | 再起動が完了したら自動的にページがリロードされます。 15 | %p 16 | しばらく待ってもリロードされない場合は 17 | %a(href="/") こちら 18 | をクリックするかブラウザのリロードボタンを押してみてください。 19 | -------------------------------------------------------------------------------- /lib/web/views/bookmarklet/download.js.erb: -------------------------------------------------------------------------------- 1 | <%# vim: set ft=javascript : -%> 2 | (function() { 3 | function merge(target, object) { 4 | for (var key in object) { 5 | target[key] = object[key]; 6 | } 7 | } 8 | 9 | // オフライン表記を先に消しておく 10 | var offline = document.getElementById("narourb-offline"); 11 | offline.parentNode.removeChild(offline); 12 | 13 | var ALLOW_HOSTS = <%= ALLOW_HOSTS.to_s %>; 14 | var guard = true; 15 | if (typeof ALLOW_HOSTS.indexOf === "function") { 16 | guard = ALLOW_HOSTS.indexOf(location.hostname) === -1; 17 | } 18 | else { 19 | for (var i in ALLOW_HOSTS) { 20 | if (ALLOW_HOSTS[i] === location.hostname) { 21 | guard = false; 22 | break; 23 | } 24 | } 25 | } 26 | if (guard) { 27 | alert("対応ページではありません"); 28 | return; 29 | } 30 | if (document.getElementById("narourb-console")) return; 31 | 32 | var url, div, iframe, btn_close; 33 | 34 | url = '<%= "#{request.scheme}://#{env["SERVER_NAME"]}:#{env["SERVER_PORT"]}" %>' + 35 | "/widget/download?from=" + location.hostname + "&mail=<%= @params['mail'].empty? ? '' : '1' %>" + 36 | "&target=" + encodeURIComponent(location.href) + "&t=" + Date.now(); 37 | 38 | // IE の場合、iframe は親のドキュメントモードが汚染して動かない可能性があるので対処 39 | var docmode = document.documentMode || 10; 40 | if (docmode < 10) { 41 | window.open(url, "dl_window", "width=400,height=250,menubar=no,toolbar=no,scrollbars=no,resizable=yes"); 42 | return; 43 | } 44 | 45 | div = document.createElement("div"); 46 | merge(div.style, { 47 | position: "fixed", 48 | top: 0, left: 0, 49 | opacity: 0.85, 50 | zIndex: 9999 51 | }); 52 | 53 | iframe = document.createElement("iframe"); 54 | iframe.src = url; 55 | iframe.id = "narourb-console"; 56 | iframe.width = 400; 57 | iframe.height = 250; 58 | 59 | btn_close = document.createElement("span"); 60 | btn_close.textContent = "×"; 61 | merge(btn_close.style, { 62 | position: "absolute", 63 | fontSize: "20px", 64 | fontWeight: "bold", 65 | background: "black", 66 | top: 0, 67 | color: "white", 68 | cursor: "pointer", 69 | opacity: 0.85 70 | }); 71 | btn_close.onclick = function() { 72 | div.parentNode.removeChild(div); 73 | }; 74 | 75 | div.appendChild(iframe); 76 | div.appendChild(btn_close); 77 | document.body.appendChild(div); 78 | })(); 79 | -------------------------------------------------------------------------------- /lib/web/views/notepad.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | local_initialize_functions.push(function($) { 3 | $("[data-toggle=tooltip]").tooltip({ 4 | animation: false, 5 | container: "body", 6 | }); 7 | 8 | Narou.Notepad.replace("notepad"); 9 | }); 10 | 11 | = partial :header 12 | 13 | .container.theme-showcase(role="main") 14 | %h1 メモ帳 15 | %ul.text-muted 16 | %li 編集したら自動でサーバに保存されます 17 | %li 別のブラウザで開いていても内容をリアルタイムに同期します 18 | .row 19 | .col-sm-9 20 | #notepad 21 | -------------------------------------------------------------------------------- /lib/web/views/novels/author_comments.haml: -------------------------------------------------------------------------------- 1 | :ruby 2 | html = HTML.new 3 | 4 | :css 5 | .indent { 6 | margin-left: 2em; 7 | margin-right: 2em; 8 | } 9 | 10 | = partial :header 11 | 12 | .container 13 | %h1= @data["title"] 14 | %ul 15 | %li 16 | まえがき率 17 | = @introductions_ratio 18 | \% 19 | %li 20 | あとがき率 21 | = @postscripts_ratio 22 | \% 23 | %li 24 | %a{ href: "./setting#enable_author_comments" } 25 | まえがき・あとがき削除設定へ飛ぶ 26 | 27 | %table.table.table-striped 28 | - @comments.each do |comment| 29 | %tr 30 | %td 31 | %dl 32 | %dd= comment[:sub]["subtitle"] 33 | 34 | %dt まえがき 35 | %dd.indent= html.ln_to_br(comment[:introduction]) 36 | 37 | %dt あとがき 38 | %dd.indent= html.ln_to_br(comment[:postscript]) 39 | -------------------------------------------------------------------------------- /lib/web/views/partial/csv_import.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | var $here = $("#csv-drop-here"); 3 | var form_data = new FormData; 4 | 5 | var appendFiles = function(files) { 6 | $.each(files, function(i, file) { 7 | form_data.append("files[]", file); 8 | $("#csv-file-list").append($("
" + file.name + "
")); 9 | }); 10 | }; 11 | 12 | $("#csv-drop-cover").hide(); 13 | $here.on("dragenter", function(e) { 14 | $("#csv-drop-cover").show(); 15 | }); 16 | 17 | $("#csv-drop-cover") 18 | .on("dragenter", function(e) { 19 | $here.addClass("dragover"); 20 | }) 21 | .on("dragover", function(e) { 22 | e.preventDefault(); 23 | e.dataTransfer.dropEffect = "copy"; 24 | return false; 25 | }) 26 | .on("dragleave", function(e) { 27 | $here.removeClass("dragover"); 28 | $(this).hide(); 29 | }) 30 | .on("drop", function(e) { 31 | e.preventDefault(); 32 | $here.removeClass("dragover"); 33 | $(this).hide(); 34 | appendFiles(e.dataTransfer.files); 35 | }); 36 | 37 | $("#csv-select-file-button").on("click", function(e) { 38 | $("#csv-select-file-input").click(); 39 | }); 40 | 41 | $("#csv-select-file-input").on("change", function(e) { 42 | appendFiles(this.files); 43 | }); 44 | 45 | $("#csv-import-files").on("click", function(e) { 46 | $.ajax({ 47 | url: "/api/csv/import", 48 | type: "post", 49 | data: form_data, 50 | processData: false, 51 | contentType: false 52 | }); 53 | }); 54 | 55 | #csv-drop-here 56 | .inner 57 | %div 58 | %div ここにCSVファイルをドラッグ&ドロップ 59 | %div.else もしくは… 60 | %div 61 | %input#csv-select-file-input.hidden(type="file" multiple="multiple") 62 | %button#csv-select-file-button.btn.btn-warning.btn-xs 63 | ファイルを選択 64 | #csv-drop-cover.drop-cover(effectAllowed="all") 65 | #csv-file-list 66 | 67 | -# インポートダイアログの「インポート」ボタンから処理を移譲されてくる 68 | #csv-import-files.hidden 69 | -------------------------------------------------------------------------------- /lib/web/views/widget/download.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html{:lang => "ja"} 3 | %head 4 | %meta{:charset => "utf-8"}/ 5 | %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ 6 | %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ 7 | %meta{:content => "whiteleaf", :name => "author"}/ 8 | %meta{:content => "none", :name => "robots"}/ 9 | %title Narou.rb WEB UI Widget 10 | %link{:href => "//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css", :rel => "stylesheet"}/ 11 | %link{:href => "/resources/perfect-scrollbar.min.css", :rel => "stylesheet"}/ 12 | %link{:href => "/style.css?_=#{Narou::VERSION}", :rel => "stylesheet"}/ 13 | %link{:href => "/resources/default-style.css?_=#{Narou::VERSION}", :rel => "stylesheet"}/ 14 | / HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries 15 | /[if lt IE 9] 16 | 17 | 18 | %body 19 | :css 20 | html, body, .console.fullscreen { 21 | height: 100%; 22 | margin: 0; 23 | padding: 0; 24 | } 25 | .queue { 26 | position: absolute; 27 | top: 3px; 28 | right: 3px; 29 | color: white; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | = embed_concurrency_enabled 35 | 36 | #console.console.fullscreen 37 | .queue.navbar-text.navbar-right 38 | %span.glyphicon.glyphicon-inbox 39 | %span.queue__sizes 40 | %span.queue__size.queue__size--default> 0 41 | - if Narou.concurrency_enabled? 42 | %span.queue__size.queue__size--divider 43 | %span.queue__size.queue__size--convert> 0 44 | 45 | %script{:src => "//code.jquery.com/jquery-1.11.1.min.js"} 46 | %script{:src => "//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"} 47 | %script{:src => "//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.6.1/lodash.min.js"} 48 | %script{:src => "/resources/sprintf.js"} 49 | %script{:src => "/resources/perfect-scrollbar.min.js"} 50 | %script{:src => "/resources/narou.library.js?_=#{Narou::VERSION}"} 51 | %script{:src => "/resources/narou.queue.js?_=#{Narou::VERSION}"} 52 | :javascript 53 | $(document).ready(function($) { 54 | var stream_console = new Narou.Console({ 55 | restore: false, buttons: false 56 | }); 57 | }); 58 | 59 | -------------------------------------------------------------------------------- /lib/web/views/widget/notepad.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html{:lang => "ja"} 3 | %head 4 | %meta{:charset => "utf-8"}/ 5 | %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ 6 | %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ 7 | %meta{:content => "whiteleaf", :name => "author"}/ 8 | %meta{:content => "none", :name => "robots"}/ 9 | %title Narou.rb メモ帳 10 | %link{:href => "//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css", :rel => "stylesheet"}/ 11 | %link{:href => "/resources/perfect-scrollbar.min.css", :rel => "stylesheet"}/ 12 | %link{:href => "/style.css?_=#{Narou::VERSION}", :rel => "stylesheet"}/ 13 | %link{:href => "/resources/default-style.css?_=#{Narou::VERSION}", :rel => "stylesheet"}/ 14 | / HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries 15 | /[if lt IE 9] 16 | 17 | 18 | %body 19 | :scss 20 | html, body { 21 | height: 100%; 22 | margin: 0; 23 | padding: 0; 24 | } 25 | 26 | #notepad.fullscreen { 27 | &, div, textarea { 28 | box-sizing: border-box; 29 | height: 100%; 30 | } 31 | } 32 | 33 | #notepad.fullscreen 34 | 35 | %script{:src => "//code.jquery.com/jquery-1.11.1.min.js"} 36 | %script{:src => "//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"} 37 | %script{:src => "//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.6.1/lodash.min.js"} 38 | %script{:src => "/resources/sprintf.js"} 39 | %script{:src => "/resources/narou.library.js?_=#{Narou::VERSION}"} 40 | :javascript 41 | $(document).ready(function($) { 42 | Narou.Notepad.replace("notepad"); 43 | }); 44 | 45 | -------------------------------------------------------------------------------- /lib/web/web-socket-ruby/.gitignore: -------------------------------------------------------------------------------- 1 | exp 2 | -------------------------------------------------------------------------------- /lib/web/web-socket-ruby/README.txt: -------------------------------------------------------------------------------- 1 | HTML5 Web Socket server/client implementation in Ruby. 2 | 3 | For server, em-websocket ( https://github.com/igrigorik/em-websocket ) may be a better choice, especially if you want to use EventMachine. 4 | 5 | 6 | * How to run sample 7 | 8 | - Run sample Web Socket server (echo server) with: 9 | $ ruby samples/echo_server.rb localhost 10081 10 | 11 | - Run sample Web Socket client and type something: 12 | $ ruby samples/stdio_client.rb ws://localhost:10081 13 | Ready 14 | hoge 15 | Sent: "hoge" 16 | Received: "hoge" 17 | 18 | 19 | * Usage example 20 | 21 | Server: 22 | 23 | # Runs the server at port 10081. It allows connections whose origin is example.com. 24 | server = WebSocketServer.new(:port => 10081, :accepted_domains => ["example.com"]) 25 | server.run() do |ws| 26 | # The block is called for each connection. 27 | # Checks requested path. 28 | if ws.path == "/" 29 | # Call ws.handshake() without argument first. 30 | ws.handshake() 31 | # Receives one message from the client as String. 32 | while data = ws.receive() 33 | puts(data) 34 | # Sends the message to the client. 35 | ws.send(data) 36 | end 37 | else 38 | # You can call ws.handshake() with argument to return error status. 39 | ws.handshake("404 Not Found") 40 | end 41 | end 42 | 43 | Client: 44 | 45 | # Connects to Web Socket server at host example.com port 10081. 46 | client = WebSocket.new("ws://example.com:10081/") 47 | # Sends a message to the server. 48 | client.send("Hello") 49 | # Receives a message from the server. 50 | data = client.receive() 51 | puts(data) 52 | 53 | 54 | * Supported WebSocket protocol versions 55 | 56 | WebSocketServer class (server) accepts version hixie-75, hixie-76, hybi-07, hybi-10. 57 | WebSocket class (client) speaks version hixie-76. 58 | 59 | 60 | * Tips: JavaScript client implementation 61 | 62 | Google Chrome Dev Channel natively supports Web Socket. For other browsers, you can use an implementation using Flash: 63 | http://github.com/gimite/web-socket-js/tree/master 64 | 65 | 66 | * WebSocket protocol versions 67 | 68 | The server supports the protocol defined in RFC 6455, draft versions hixie-75 and hixie-76. 69 | 70 | The client speaks draft version hixie-76. 71 | 72 | 73 | * License 74 | 75 | New BSD License. 76 | -------------------------------------------------------------------------------- /lib/web/web-socket-ruby/samples/chat_server.rb: -------------------------------------------------------------------------------- 1 | # Copyright: Hiroshi Ichikawa 2 | # Lincense: New BSD Lincense 3 | 4 | $LOAD_PATH << File.dirname(__FILE__) + "/../lib" 5 | require "web_socket" 6 | require "thread" 7 | 8 | Thread.abort_on_exception = true 9 | 10 | if ARGV.size != 2 11 | $stderr.puts("Usage: ruby sample/chat_server.rb ACCEPTED_DOMAIN PORT") 12 | exit(1) 13 | end 14 | 15 | server = WebSocketServer.new( 16 | :accepted_domains => [ARGV[0]], 17 | :port => ARGV[1].to_i()) 18 | puts("Server is running at port %d" % server.port) 19 | connections = [] 20 | history = [nil] * 20 21 | 22 | server.run() do |ws| 23 | begin 24 | 25 | puts("Connection accepted") 26 | ws.handshake() 27 | que = Queue.new() 28 | connections.push(que) 29 | 30 | for message in history 31 | next if !message 32 | ws.send(message) 33 | puts("Sent: #{message}") 34 | end 35 | 36 | thread = Thread.new() do 37 | while true 38 | message = que.pop() 39 | ws.send(message) 40 | puts("Sent: #{message}") 41 | end 42 | end 43 | 44 | while data = ws.receive() 45 | puts("Received: #{data}") 46 | for conn in connections 47 | conn.push(data) 48 | end 49 | history.push(data) 50 | history.shift() 51 | end 52 | 53 | ensure 54 | connections.delete(que) 55 | thread.terminate() if thread 56 | puts("Connection closed") 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/web/web-socket-ruby/samples/echo_server.rb: -------------------------------------------------------------------------------- 1 | # Copyright: Hiroshi Ichikawa 2 | # Lincense: New BSD Lincense 3 | 4 | $LOAD_PATH << File.dirname(__FILE__) + "/../lib" 5 | require "web_socket" 6 | 7 | Thread.abort_on_exception = true 8 | # WebSocket.debug = true 9 | 10 | if ARGV.size != 2 11 | $stderr.puts("Usage: ruby sample/echo_server.rb ACCEPTED_DOMAIN PORT") 12 | exit(1) 13 | end 14 | 15 | server = WebSocketServer.new( 16 | :accepted_domains => [ARGV[0]], 17 | :port => ARGV[1].to_i()) 18 | puts("Server is running at port %d" % server.port) 19 | server.run() do |ws| 20 | puts("Connection accepted") 21 | puts("Path: #{ws.path}, Origin: #{ws.origin}") 22 | if ws.path == "/" 23 | ws.handshake() 24 | while data = ws.receive() 25 | printf("Received: %p\n", data) 26 | ws.send(data) 27 | printf("Sent: %p\n", data) 28 | end 29 | else 30 | ws.handshake("404 Not Found") 31 | end 32 | puts("Connection closed") 33 | end 34 | -------------------------------------------------------------------------------- /lib/web/web-socket-ruby/samples/stdio_client.rb: -------------------------------------------------------------------------------- 1 | # Copyright: Hiroshi Ichikawa 2 | # Lincense: New BSD Lincense 3 | 4 | $LOAD_PATH << File.dirname(__FILE__) + "/../lib" 5 | require "web_socket" 6 | 7 | if ARGV.size != 1 8 | $stderr.puts("Usage: ruby samples/stdio_client.rb ws://HOST:PORT/") 9 | exit(1) 10 | end 11 | 12 | client = WebSocket.new(ARGV[0]) 13 | puts("Connected") 14 | Thread.new() do 15 | while data = client.receive() 16 | printf("Received: %p\n", data) 17 | end 18 | exit() 19 | end 20 | $stdin.each_line() do |line| 21 | data = line.chomp() 22 | client.send(data) 23 | printf("Sent: %p\n", data) 24 | end 25 | client.close() 26 | -------------------------------------------------------------------------------- /narou.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM -*- mode: ruby -*- 3 | @if not "%~d0" == "~d0" goto WinNT 4 | ruby -x narou.bat %1 %2 %3 %4 %5 %6 %7 %8 %9 5 | @goto endofruby 6 | :WinNT 7 | ruby -x "%~f0" %* 8 | @goto endofruby 9 | #! ruby 10 | 11 | require_relative "narou.rb" 12 | 13 | __END__ 14 | :endofruby 15 | -------------------------------------------------------------------------------- /narou.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # Narou.rb ― 小説家になろうダウンロード&整形スクリプト 6 | # 7 | # Copyright 2013 whiteleaf. All rights reserved. 8 | # 9 | 10 | require_relative "lib/extension" 11 | require_relative "lib/extensions/monkey_patches" 12 | require_relative "lib/backtracer" 13 | 14 | script_dir = File.expand_path(File.dirname(__FILE__)) 15 | $debug = File.exist?(File.join(script_dir, "debug")) 16 | 17 | Encoding.default_external = Encoding::UTF_8 18 | Narou::Backtracer.argv = ARGV 19 | 20 | if ARGV.delete("--time") 21 | now = Time.now 22 | at_exit do 23 | puts "実行時間 #{Time.now - now}秒" 24 | end 25 | end 26 | 27 | require_relative "lib/inventory" 28 | 29 | $development = Narou.commit_version.! 30 | if $development 31 | begin 32 | require "pry" 33 | require "awesome_print" 34 | rescue LoadError 35 | end 36 | end 37 | 38 | global = Inventory.load("global_setting", :global) 39 | $display_backtrace = ARGV.delete("--backtrace") 40 | $display_backtrace ||= $debug 41 | $disable_color = ARGV.delete("--no-color") 42 | $disable_color ||= global["no-color"] 43 | $color_parser ||= global["color-parser"] || "system" 44 | 45 | require_relative "lib/narou_logger" 46 | require_relative "lib/version" 47 | require_relative "lib/commandline" 48 | 49 | exit Narou::Backtracer.capture { 50 | CommandLine.run!(ARGV.map(&:dup)) 51 | } 52 | -------------------------------------------------------------------------------- /preset/AozoraEpub3.ini: -------------------------------------------------------------------------------- 1 | #AozoraEpub3 Parameters 2 | #Mon Feb 18 12:29:25 JST 2013 3 | GammaValue=1.0 4 | CoverHistory= 5 | AutoYokoNum3=1 6 | MaxEmptyLine=0 7 | ChkConfirm=1 8 | AutoYokoNum1=1 9 | ProfileList= 10 | PageBreakEmptySize=300 11 | ImageFloat= 12 | ChapterNumTitle= 13 | PageBreakEmpty= 14 | ImageFloatW=600 15 | UseFileName= 16 | WebInterval=0.5 17 | LastDir= 18 | PageBreak= 19 | AutoMarginNombreSize=3.0 20 | ImageFloatH=400 21 | TitleType=0 22 | ChapterPattern= 23 | ReplaceCover= 24 | MaxChapterNameLength=64 25 | CoverPage=1 26 | AutoMarginLimitV=15 27 | Vertical=1 28 | CommentPrint= 29 | AutoYokoEQ1= 30 | DividerLocation=230 31 | TitlePage=2 32 | ChapterH3=1 33 | ChapterH2=1 34 | ChapterH1=1 35 | AutoMarginLimitH=15 36 | ProfileFileName= 37 | SizeW=528.0 38 | JpegQuality=85 39 | TocPage=1 40 | AutoMargin= 41 | TitlePageWrite=1 42 | PosY=106.0 43 | ChapterName= 44 | PosX=627.0 45 | NoIllust= 46 | PageBreakChapterSize=200 47 | ResizeW= 48 | RemoveEmptyLine=0 49 | SizeH=455.0 50 | ChapterNumParenTitle= 51 | NcxNest=1 52 | Gamma= 53 | AutoYoko=1 54 | SinglePageSizeW=400 55 | CoverW=658 56 | ChapterExclude=1 57 | ResizeH= 58 | NavNest=1 59 | Cover= 60 | RotateImage=2 61 | ForceIndent= 62 | TocVertical= 63 | SinglePageSizeH=600 64 | Gaiji32=1 65 | TitleToc=1 66 | DstPath= 67 | CoverH=905 68 | AutoFileName=1 69 | DispW=658 70 | CoverPageToc= 71 | PageBreakChapter= 72 | ChapterNumOnly= 73 | CommentConvert= 74 | ChapterH= 75 | Ext=.mobi 76 | AutoMarginWhiteLevel=80 77 | DispH=905 78 | ImageFloatType=0 79 | SpaceHyphenation=1 80 | OverWrite=1 81 | EncType=0 82 | ChapterUseNextLine= 83 | ChapterNumParen= 84 | FitImage=1 85 | PageBreakEmptyLine=2 86 | MaxCoverLine=10 87 | ResizeNumW=2048 88 | ChapterSection= 89 | ChukiRuby=0 90 | ChapterPatternText= 91 | MarkId= 92 | AutoMarginNombre=0 93 | AutoMarginPadding=1.0 94 | SinglePageWidth=600 95 | ResizeNumH=2048 96 | PageBreakSize=400 97 | DakutenType=2 98 | -------------------------------------------------------------------------------- /preset/DMincho.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteleaf7/narou/27fb741fd561e2f26aba9fdf9d9b7b4384792be4/preset/DMincho.ttf -------------------------------------------------------------------------------- /preset/bordersymbols.txt: -------------------------------------------------------------------------------- 1 | ■□◆◇○◎●★☆\**※  ♡♥❤∽♤♠§♯ -------------------------------------------------------------------------------- /preset/custom_chuki_tag.txt: -------------------------------------------------------------------------------- 1 | ### Narou.rb embedded custom chuki ### 2 | 3 | ####カスタム注記 4 | 二分アキ 5 | 一字下げ
1 6 | 二字下げ
1 7 | 三字下げ
1 8 | ここからパラメーター
1 9 | ここでパラメーター終わり
1 10 | ここから前書き
1 11 | ここで前書き終わり
1 12 | ここから後書き
1 13 | ここで後書き終わり
1 14 | 濁点 15 | 濁点終わり 16 | ここから柱
1 17 | ここで柱終わり
1 18 | zws ​ 19 | 20 | ### Narou.rb embedded custom chuki ### 21 | -------------------------------------------------------------------------------- /preset/mail_setting.yaml: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # メールの送信設定 3 | # 4 | # Gmail で Send to Kindle を使うためのサンプルです。 5 | # ご自分の環境に適宜書き換えて下さい。 6 | # ------------------------------------------------------------ 7 | # Send to Kindle で使う場合の注意点 8 | # 送ったファイルがどんどんパーソナル・ドキュメントに保存されていくので、 9 | # hotentry を有効にした場合などは特に大量に溜まって管理が大変になるので、 10 | # パーソナル・ドキュメントの保存は無効にしたほうがよいでしょう。 11 | # 12 | # コンテンツと端末の管理>設定 13 | # https://www.amazon.co.jp/mn/dcw/myx.html/ref=kinw_myk_redirect#/home/settings/payment 14 | # > パーソナル・ドキュメント設定 > パーソナル・ドキュメントの保存 15 | # > 保存設定を編集 16 | # 17 | # 上記で無効に出来ます(デフォルトでは有効になっています) 18 | # 19 | # また、同じファイル名で送信したとしても Kindle には別の本として受信されます。 20 | # 21 | 22 | # toのアドレスは 23 | # https://www.amazon.co.jp/mn/dcw/myx.html/ref=kinw_myk_redirect#/home/settings/payment 24 | # コンテンツと端末の管理 > パーソナル・ドキュメント設定 > Send-to-Kindle Eメールアドレスの設定 25 | # に書いてあります。 26 | # 自分のメールアドレス(from)が承認済みEメールアドレス一覧にあることを確認して下さい。 27 | :from : youraccount@gmail.com 28 | :to : youraccount@kindle.com 29 | 30 | # 件名を「変換」にすると Kindleフォーマット(.azw) に変換される 31 | :subject : "" 32 | 33 | :via : smtp 34 | :via_options : 35 | :address : smtp.gmail.com 36 | :port : 587 37 | :enable_starttls_auto : true 38 | :user_name : your_gmail_account 39 | :password : password 40 | # 2段階認証の場合はアプリケーション固有のパスワード 41 | # 参照 https://support.google.com/accounts/answer/185833?hl=ja 42 | :authentication : :plain 43 | :domain : gmail.com 44 | 45 | # 設定が終わったらこの項目を true にして下さい 46 | :complete : false 47 | 48 | 49 | # 下記は Yahoo! メールで送るための参考設定です。 50 | # 51 | # # Yahoo! メールはFromを正しく設定しないと送信出来ない 52 | # :from : ヤフーID@yahoo.co.jp 53 | # :to : hogehoge@kindle.com 54 | # 55 | # :subject : "" 56 | # 57 | # :via : smtp 58 | # :via_options : 59 | # :address : smtp.mail.yahoo.co.jp 60 | # :port : 587 61 | # :enable_starttls_auto : true 62 | # :user_name : ヤフーID 63 | # :password : パスワード 64 | # :authentication : :plain 65 | # :domain : yahoo.co.jp 66 | # 67 | # # 設定が終わったらこの項目を true にして下さい 68 | # :complete : false 69 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n1839bd/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: オーバーロード:後編 6 | # 作者: 丸山くがね 7 | # URL: http://ncode.syosetu.com/n1839bd/ 8 | # 9 | converter "n1839bd オーバーロード:後編" do 10 | def before(io, text_type) 11 | super 12 | end 13 | 14 | def after(io, text_type) 15 | if io.string.scan(/[<<〈((]([ぁ-んァ-ヴーゞ・A-Za-zA-Za-z]+?)[>>〉))]/).length > 0 16 | @inspector.warning("オバロっぽいルビを検出:#{$1}") 17 | end 18 | io.string.gsub!(/[≪『](.+?)/(.+?)[≫』]/) do 19 | spell_ruby, spell_name = $1, $2 20 | if spell_name =~ /^([ぁ-んァ-ヴーゞ・A-Za-zA-Za-z]+)$/ 21 | spell_name = spell_ruby 22 | spell_ruby = $& 23 | end 24 | option = "" 25 | if spell_ruby =~ /^――(.+)/ 26 | spell_ruby = $1 27 | option = "――" 28 | end 29 | "#{option}≪|#{spell_name}《#{spell_ruby}》≫" 30 | end 31 | io 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n1839bd/setting.ini: -------------------------------------------------------------------------------- 1 | ; 数字の漢数字変換を有効に 2 | enable_convert_num_to_kanji = true 3 | 4 | ; 漢数字変換した場合、千・万などに変換するか 5 | enable_kanji_num_with_units = true 6 | 7 | ; 〇(ゼロ)が最低この数字以上付いてないと千・万などをつける対象にしない 8 | kanji_num_with_units_lower_digit_zero = 2 9 | 10 | ; アルファベットを強制的に全角にする。falseの場合英文は半角、それ以外は全角になる 11 | enable_alphabet_force_zenkaku = false 12 | 13 | ; 行頭かぎ括弧に二分アキを挿入する 14 | enable_half_indent_bracket = true 15 | 16 | ; 自動行頭字下げ機能。行頭字下げが行われているかを判断し、適切に行頭字下げをする 17 | enable_auto_indent = false 18 | 19 | ; かぎ括弧内自動連結を有効に 20 | ; 例) 21 | ; 「~~~! 22 | ;  ***?」 → 「~~~! ***?」 23 | enable_auto_join_in_brackets = false 24 | 25 | ; かぎ括弧内のとじ開きが正しくされているかどうか調査する 26 | enable_inspect_invalid_openclose_brackets = true 27 | 28 | ; 行末が読点で終わっている部分を出来るだけ連結する 29 | enable_auto_join_line = false 30 | 31 | ; [#改ページ]直後の行に中見出しを付与する(テキストファイルを直接変換する場合のみの設定) 32 | enable_enchant_midashi = true 33 | 34 | ; 作者コメントを検出するか(テキストファイルを直接変換する場合のみの設定) 35 | enable_author_comments = true 36 | 37 | ; 前書きを削除するか 38 | enable_erase_introduction = false 39 | 40 | ; 後書きを削除するか 41 | enable_erase_postscript = false 42 | 43 | ; ルビ処理対策を有効に 44 | enable_ruby = false 45 | 46 | ; 小説家になろうの挿絵タグを有効にする(false なら削除) 47 | enable_narou_illust = true 48 | 49 | ; ○/×表記を×分の○表記に変換する。日付表記(10/23)と誤爆しやすいので注意 50 | enable_transform_fraction = true 51 | 52 | ; 日付表記(20yy/mm/dd)を任意の形式(date_formatで指定)に変換する 53 | enable_transform_date = false 54 | 55 | ; 書式は http://goo.gl/gvJ5u を参考 56 | date_format = %Y年%m月%d日 57 | 58 | ; 中黒(・)を並べて三点リーダーもどきにしているのを三点リーダーに変換する 59 | enable_convert_horizontal_ellipsis = false 60 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n2267be/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: Re:ゼロから始める異世界生活 6 | # 作者: 鼠色猫 7 | # URL: http://ncode.syosetu.com/n2267be/ 8 | # 9 | converter "n2267be Re:ゼロから始める異世界生活" do 10 | def before(io, text_type) 11 | super 12 | io.string.gsub!(/^[  \t]*[※  \t]+$/, "※ ※ ※") 13 | io 14 | end 15 | 16 | def after(io, text_type) 17 | super 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n2525bs/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: 魔剣ゾルディの女主人公とっかえひっかえ成長記録 6 | # 作者: 木原ゆう 7 | # URL: http://ncode.syosetu.com/n2525bs/ 8 | # 9 | converter "n2525bs 魔剣ゾルディの女主人公とっかえひっかえ成長記録" do 10 | def before(io, text_type) 11 | super 12 | io.string.gsub!(/(\/{2,})/) do 13 | if $1.length == 3 14 | "❤" 15 | else 16 | "❤❤" 17 | end 18 | end 19 | io 20 | end 21 | 22 | def is_parameter_block?(line) 23 | !!(line =~ /={2,}/) 24 | end 25 | 26 | def in_parameter_block?(line) 27 | unless @in_parameter_block 28 | if is_parameter_block?(line) 29 | @in_parameter_block = true 30 | return true 31 | end 32 | end 33 | return false 34 | end 35 | 36 | def out_parameter_block?(line) 37 | if @in_parameter_block 38 | if is_parameter_block?(line) 39 | @in_parameter_block = false 40 | return true 41 | end 42 | end 43 | return false 44 | end 45 | 46 | def after(io, text_type) 47 | return io unless text_type == "body" 48 | write_fp = StringIO.new 49 | non_close_parameter = false 50 | @in_parameter_block = false 51 | io.each do |line| 52 | line.rstrip! 53 | if in_parameter_block?(line) 54 | non_close_parameter = true 55 | write_fp.puts("[#ここからパラメーター]") 56 | next 57 | end 58 | if out_parameter_block?(line) 59 | non_close_parameter = false 60 | write_fp.puts("[#ここでパラメーター終わり]") 61 | next 62 | end 63 | write_fp.puts(line) 64 | end 65 | if non_close_parameter 66 | write_fp.puts("[#ここでパラメーター終わり]") 67 | end 68 | data = write_fp.string 69 | data.lstrip! 70 | data.gsub!(/\n\n([#ここからパラメーター])/m, "\n\\1") 71 | data.gsub!(/([#ここでパラメーター終わり]\n\n)/m, "[#ここでパラメーター終わり]\n") 72 | data.gsub!(" ❤", "❤") 73 | write_fp 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n2525bs/setting.ini: -------------------------------------------------------------------------------- 1 | ; 数字の漢数字変換を有効に 2 | enable_convert_num_to_kanji = false 3 | 4 | ; 漢数字変換した場合、千・万などに変換するか 5 | enable_kanji_num_with_units = true 6 | 7 | ; 〇(ゼロ)が最低この数字以上付いてないと千・万などをつける対象にしない 8 | kanji_num_with_units_lower_digit_zero = 3 9 | 10 | ; アルファベットを強制的に全角にする。falseの場合英文は半角、それ以外は全角になる 11 | enable_alphabet_force_zenkaku = true 12 | 13 | ; 行頭かぎ括弧に二分アキを挿入する 14 | enable_half_indent_bracket = true 15 | 16 | ; 自動行頭字下げ機能。行頭字下げが行われているかを判断し、適切に行頭字下げをする 17 | enable_auto_indent = true 18 | 19 | ; かぎ括弧内自動連結を有効に 20 | ; 例) 21 | ; 「~~~! 22 | ;  ***?」 → 「~~~! ***?」 23 | enable_auto_join_in_brackets = true 24 | 25 | ; かぎ括弧内のとじ開きが正しくされているかどうか調査する 26 | enable_inspect_invalid_openclose_brackets = false 27 | 28 | ; 行末が読点で終わっている部分を出来るだけ連結する 29 | enable_auto_join_line = true 30 | 31 | ; [#改ページ]直後の行に中見出しを付与する(テキストファイルを直接変換する場合のみの設定) 32 | enable_enchant_midashi = true 33 | 34 | ; 作者コメントを検出するか(テキストファイルを直接変換する場合のみの設定) 35 | enable_author_comments = true 36 | 37 | ; 前書きを削除するか 38 | enable_erase_introduction = false 39 | 40 | ; 後書きを削除するか 41 | enable_erase_postscript = false 42 | 43 | ; ルビ処理を有効に 44 | enable_ruby = true 45 | 46 | ; 小説家になろうの挿絵タグを有効にする(false なら削除) 47 | enable_narou_illust = true 48 | 49 | ; ○/×表記を×分の○表記に変換する。日付表記(10/23)と誤爆しやすいので注意 50 | enable_transform_fraction = false 51 | 52 | ; 日付表記(20yy/mm/dd)を任意の形式(date_formatで指定)に変換する 53 | enable_transform_date = true 54 | 55 | ; 書式は http://goo.gl/gvJ5u を参考 56 | date_format = %Y年%m月%d日 57 | 58 | ; 中黒(・)を並べて三点リーダーもどきにしているのを三点リーダーに変換する 59 | enable_convert_horizontal_ellipsis = true 60 | 61 | ; `to_page_break_threshold` で設定した個数以上連続する空行を改ページに変換する 62 | enable_convert_page_break = false 63 | 64 | ; ここで設定した値が `enable_convert_page_break` に反映される 65 | to_page_break_threshold = 10 66 | 67 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n3730bn/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: まのわ ~魔物倒す・能力奪う・私強くなる~ 6 | # 作者: 紫炎 7 | # URL: http://ncode.syosetu.com/n3730bn/ 8 | # 9 | converter "n3730bn まのわ ~魔物倒す・能力奪う・私強くなる~" do 10 | def before(io, text_type) 11 | if text_type == "postscript" 12 | @__manowa_setting_num2kanji |= @setting.enable_convert_num_to_kanji 13 | @setting.enable_convert_num_to_kanji = false 14 | else 15 | super 16 | end 17 | io 18 | end 19 | 20 | def after(io, text_type) 21 | buffer = StringIO.new 22 | io.each do |line| 23 | if line =~ /^◎/ 24 | buffer.puts "[#ゴシック体]#{line.rstrip}[#ゴシック体終わり]" 25 | buffer.puts 26 | else 27 | buffer.puts line 28 | end 29 | end 30 | if text_type == "postscript" 31 | @setting.enable_convert_num_to_kanji = @__manowa_setting_num2kanji 32 | end 33 | buffer 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n4259s/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: 異世界迷宮で奴隷ハーレムを 6 | # 作者: 蘇我捨恥 7 | # URL: http://ncode.syosetu.com/n4259s/ 8 | # 9 | converter "n4259s 異世界迷宮で奴隷ハーレムを" do 10 | def before(io, text_type) 11 | data = io.read 12 | data.gsub!("\n\n", "\n") 13 | StringIO.new(data) 14 | end 15 | 16 | def level_convert!(line) 17 | line.gsub!(/Lv/, "Lv[#「Lv」は縦中横]") 18 | line.gsub!(/Lv([0-90-9]+)/, "Lv[#「Lv」は縦中横]\\1[#「\\1」は縦中横]") 19 | end 20 | 21 | def is_parameter_block?(line) 22 | if line =~ /^[^「『\((【〈《≪[  ―]/ 23 | return true 24 | else 25 | return false 26 | end 27 | end 28 | 29 | def in_parameter_block?(line) 30 | unless @in_parameter_block 31 | if is_parameter_block?(line) 32 | @in_parameter_block = true 33 | return true 34 | end 35 | end 36 | return false 37 | end 38 | 39 | def out_parameter_block?(line) 40 | if @in_parameter_block 41 | unless is_parameter_block?(line) || line =~ /^[\**]+/ 42 | @in_parameter_block = false 43 | return true 44 | end 45 | end 46 | return false 47 | end 48 | 49 | def after(io, text_type) 50 | return io unless text_type == "body" 51 | write_fp = StringIO.new 52 | non_close_parameter = false 53 | @in_parameter_block = false 54 | io.each do |line| 55 | line.rstrip! 56 | unless comments_block?(line) 57 | level_convert!(line) 58 | if in_parameter_block?(line) 59 | non_close_parameter = true 60 | write_fp.puts("[#ここからパラメーター]") 61 | end 62 | if out_parameter_block?(line) 63 | non_close_parameter = false 64 | write_fp.puts("[#ここでパラメーター終わり]") 65 | end 66 | end 67 | write_fp.puts(line) 68 | end 69 | if non_close_parameter 70 | write_fp.puts("[#ここでパラメーター終わり]") 71 | end 72 | data = write_fp.string 73 | data.lstrip! 74 | data.gsub!(/\n\n([#ここからパラメーター])/m, "\n\\1") 75 | data.gsub!(/([#ここでパラメーター終わり]\n\n)/m, "[#ここでパラメーター終わり]\n") 76 | write_fp 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n4259s/setting.ini: -------------------------------------------------------------------------------- 1 | ; 数字の漢数字変換を有効に 2 | enable_convert_num_to_kanji = false 3 | 4 | ; 漢数字変換した場合、千・万などに変換するか 5 | enable_kanji_num_with_units = false 6 | 7 | ; 〇(ゼロ)が最低この数字以上付いてないと千・万などをつける対象にしない 8 | kanji_num_with_units_lower_digit_zero = 2 9 | 10 | ; アルファベットを強制的に全角にする。falseの場合英文は半角、それ以外は全角になる 11 | enable_alphabet_force_zenkaku = false 12 | 13 | ; 行頭かぎ括弧に二分アキを挿入する 14 | enable_half_indent_bracket = true 15 | 16 | ; 自動行頭字下げ機能。行頭字下げが行われているかを判断し、適切に行頭字下げをする 17 | enable_auto_indent = false 18 | 19 | ; かぎ括弧内自動連結を有効に 20 | ; 例) 21 | ; 「~~~! 22 | ;  ***?」 → 「~~~! ***?」 23 | enable_auto_join_in_brackets = false 24 | 25 | ; かぎ括弧内のとじ開きが正しくされているかどうか調査する 26 | enable_inspect_invalid_openclose_brackets = true 27 | 28 | ; 行末が読点で終わっている部分を出来るだけ連結する 29 | enable_auto_join_line = true 30 | 31 | ; [#改ページ]直後の行に中見出しを付与する(テキストファイルを直接変換する場合のみの設定) 32 | enable_enchant_midashi = true 33 | 34 | ; 作者コメントを検出するか(テキストファイルを直接変換する場合のみの設定) 35 | enable_author_comments = true 36 | 37 | ; 前書きを削除するか 38 | enable_erase_introduction = false 39 | 40 | ; 後書きを削除するか 41 | enable_erase_postscript = false 42 | 43 | ; ルビ処理対策を有効に 44 | enable_ruby = true 45 | 46 | ; 小説家になろうの挿絵タグを有効にする(false なら削除) 47 | enable_narou_illust = true 48 | 49 | ; ○/×表記を×分の○表記に変換する。日付表記(10/23)と誤爆しやすいので注意 50 | enable_transform_fraction = false 51 | 52 | ; 日付表記(20yy/mm/dd)を任意の形式(date_formatで指定)に変換する 53 | enable_transform_date = true 54 | 55 | ; 書式は http://goo.gl/gvJ5u を参考 56 | date_format = %Y年%m月%d日 57 | 58 | ; 中黒(・)を並べて三点リーダーもどきにしているのを三点リーダーに変換する 59 | enable_convert_horizontal_ellipsis = false 60 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n5530cf/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: うちの娘の為ならば、俺はもしかしたら魔王も倒せるかもしれない 。 6 | # 作者: CHIROLU 7 | # URL: http://ncode.syosetu.com/n5530cf/ 8 | # 9 | # 詳細については http://bit.ly/1vTEH04 を参照して下さい 10 | # 11 | converter "n5530cf うちの娘の為ならば、俺はもしかしたら魔王も倒せるかもしれない 。" do 12 | # 各種変換処理がされる「前」の生データに対しての変換処理を記述 13 | def before(io, text_type) 14 | io.string.gsub!(/[-ー][-―]/, "――") 15 | io.string.gsub!(/\( ([前後]) \)/, "(\\1)") if text_type == "subtitle" 16 | if text_type =~ /body|postscript/ 17 | io.string.gsub!(/([っッ♪…]) +([^」』\)])/, "\\1 \\2") 18 | io.string.gsub!(/ +/, "") 19 | end 20 | 21 | case (@current_index + 1) 22 | when 78 23 | io.string.gsub!("どうして無事だっ たのですか", "どうして無事だったのですか") 24 | end 25 | 26 | super 27 | io 28 | end 29 | 30 | # 各種変換処理がされた「後」のデータに対しての変換処理を記述 31 | def after(io, text_type) 32 | super 33 | io 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n7594ct/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: 10年ごしの引きニートを辞めて外出したら自宅ごと異世界に転移してた 6 | # 作者: 坂東太郎 7 | # URL: http://ncode.syosetu.com/n7594ct/ 8 | # 9 | # 詳細については http://bit.ly/1vTEH04 を参照して下さい 10 | # 11 | converter "n7594ct 10年ごしの引きニートを辞めて外出したら自宅ごと異世界に転移してた" do 12 | # 各種変換処理がされる「前」の生データに対しての変換処理を記述 13 | def before(io, text_type) 14 | if text_type == "subtitle" 15 | @bbs_mode = io.string.include?("掲示板回") 16 | super 17 | end 18 | if text_type == "body" && @bbs_mode 19 | @setting.enable_convert_num_to_kanji = false 20 | else 21 | super 22 | end 23 | io 24 | end 25 | 26 | # 各種変換処理がされた「後」のデータに対しての変換処理を記述 27 | def after(io, text_type) 28 | if text_type == "body" && @bbs_mode 29 | @setting.enable_convert_num_to_kanji = true 30 | @bbs_mode = false 31 | else 32 | super 33 | end 34 | 35 | io 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n7975cr/setting.ini: -------------------------------------------------------------------------------- 1 | ; 対象小説情報 2 | ; タイトル: 蜘蛛ですが、なにか? 3 | ; 作者: 馬場翁 4 | ; URL: http://ncode.syosetu.com/n7975cr/ 5 | 6 | enable_convert_num_to_kanji = false 7 | enable_auto_join_in_brackets = false 8 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n8611bv/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: ありふれた職業で世界最強 6 | # 作者: 厨二好き/白米良 7 | # URL: http://ncode.syosetu.com/n8611bv/ 8 | # 9 | # 詳細については http://bit.ly/1vTEH04 を参照して下さい 10 | # 11 | converter "n8611bv ありふれた職業で世界最強" do 12 | def before(io, text_type) 13 | super 14 | io 15 | end 16 | 17 | def is_parameter?(line) 18 | line =~ /^ ?={5,}$/ 19 | end 20 | 21 | def after(io, text_type) 22 | super 23 | return io unless text_type == "body" 24 | write_fp = StringIO.new 25 | parameter_opened = false 26 | io.each do |line| 27 | if is_parameter?(line) 28 | if parameter_opened 29 | write_fp.puts "===============================" 30 | write_fp.puts "[#ここで太字終わり]" 31 | else 32 | write_fp.puts "[#ここから太字]" 33 | write_fp.puts "===============================" 34 | end 35 | parameter_opened ^= true 36 | else 37 | write_fp.puts(line) 38 | end 39 | end 40 | if parameter_opened 41 | write_fp.puts "===============================" 42 | write_fp.puts "[#ここで太字終わり]" 43 | end 44 | 45 | write_fp.string.gsub!(/^~{5,}/, "    * * *") 46 | write_fp 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n8725k/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: ログ・ホライズン 6 | # 作者: 橙乃ままれ 7 | # URL: http://ncode.syosetu.com/n8725k/ 8 | # 9 | converter "n8725k ログ・ホライズン" do 10 | def before(io, element_type) 11 | super 12 | io.string.gsub!(/ルビ[::]/, "") 13 | io 14 | end 15 | 16 | def after(io, element_type) 17 | data = io.string 18 | if element_type == "body" 19 | if data =~ /[||]([^《]+)《(、+)》/ 20 | if $1.length == $2.length 21 | data.gsub!(/[||]([^《]+)《(、+)》/) do 22 | "[#傍点]" + $1 + "[#傍点終わり]" 23 | end 24 | end 25 | end 26 | data.gsub!("ロエ二", "ロエ2") 27 | end 28 | io 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n9078bd/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: この世界がゲームだと俺だけが知っている 6 | # 作者: ウスバー 7 | # URL: http://ncode.syosetu.com/n9078bd/ 8 | # 9 | converter "n9078bd この世界がゲームだと俺だけが知っている" do 10 | def before(io, text_type) 11 | super 12 | end 13 | 14 | def after(io, text_type) 15 | io.string.gsub!("+ ※[#縦線]L","[#ここから横書き]+ ※[#縦線]L[#ここで横書き終わり]") 16 | io.string.gsub!("+ ±","[#ここから横書き]+ ±[#ここで横書き終わり]") 17 | io.string.gsub!("+ -","[#ここから横書き]+ -[#ここで横書き終わり]") 18 | io.string.gsub!(" -"," [#ここから横書き]-[#ここで横書き終わり]") 19 | io.string.gsub!("『-』","『[#ここから横書き]-[#ここで横書き終わり]』") 20 | io.string.gsub!("Ξ","[#ここから横書き]≡[#ここで横書き終わり]") 21 | io.string.gsub!("+L","[#ここから横書き]+L[#ここで横書き終わり]") 22 | io.string.gsub!("※[#縦線]L","[#ここから横書き]※[#縦線]L[#ここで横書き終わり]") 23 | io.string.gsub!(" ="," [#ここから横書き]=[#ここで横書き終わり]") 24 | io 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n9463br/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: 僕と彼女と実弾兵器(アンティーク) 6 | # 作者: Gibson 7 | # URL: http://ncode.syosetu.com/n9463br/ 8 | # 9 | # 詳細については http://bit.ly/1vTEH04 を参照して下さい 10 | # 11 | converter "n9463br 僕と彼女と実弾兵器(アンティーク)" do 12 | # 各種変換処理がされる「前」の生データに対しての変換処理を記述 13 | def before(io, text_type) 14 | #super 15 | io.string.gsub!("\n\n", "\n") 16 | io 17 | end 18 | 19 | # 各種変換処理がされた「後」のデータに対しての変換処理を記述 20 | def after(io, text_type) 21 | super 22 | if text_type == "body" 23 | io.string.gsub!(/^( ――.+?――)$/, "[#太字]\\1[#太字終わり]") 24 | io.string.gsub!(/「〝(.+?)〟」/, "『\\1』") 25 | io.string.gsub!(/「〝([^〟]+?)」$/, "『\\1』") 26 | end 27 | io 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n9669bk/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: 無職転生 - 異世界行ったら本気だす - 6 | # 作者: 理不尽な孫の手 7 | # URL: http://ncode.syosetu.com/n9669bk/ 8 | # 9 | converter "n9669bk 無職転生 - 異世界行ったら本気だす -" do 10 | def before(io, type) 11 | super 12 | end 13 | 14 | def is_parameter_block?(line) 15 | line =~ /^-{24,}$/ || line =~ /^={10,}$/ 16 | end 17 | 18 | def in_parameter_block?(line) 19 | unless @in_parameter_block 20 | if is_parameter_block?(line) 21 | @in_parameter_block = true 22 | return true 23 | end 24 | end 25 | return false 26 | end 27 | 28 | def out_parameter_block?(line) 29 | if @in_parameter_block 30 | if is_parameter_block?(line) 31 | @in_parameter_block = false 32 | return true 33 | end 34 | end 35 | return false 36 | end 37 | 38 | def conver_dash!(line) 39 | line.gsub!(/-{2,}/) do |match| 40 | match.tr("-", "―") 41 | end 42 | line.gsub!(/^(―――)$/) do 43 | result = "       #{$1}" 44 | unless blank_line?(@before_line) 45 | result = "\n" + result 46 | end 47 | @request_insert_blank_next_line = true 48 | result 49 | end 50 | line.gsub!(/^(――)/, " \\1") 51 | end 52 | 53 | def convert_shiten!(line) 54 | line.gsub!(/^ *―――?([^―]+?)―――?$/, 55 | "\n [#1段階小さな文字][#ゴシック体][#斜体]――\\1――" + \ 56 | "[#斜体終わり][#ゴシック体終わり][#小さな文字終わり]\n\n") 57 | end 58 | 59 | def after(io, type) 60 | write_fp = StringIO.new 61 | @before_line = "" 62 | @request_insert_blank_next_line = false 63 | @in_parameter_block = false 64 | io.each do |line| 65 | if @request_insert_blank_next_line 66 | write_fp.puts unless blank_line?(line) 67 | @request_insert_blank_next_line = false 68 | end 69 | zenkaku_rstrip(line) 70 | if in_parameter_block?(line) 71 | write_fp.puts("[#ここからパラメーター]") 72 | next 73 | elsif out_parameter_block?(line) 74 | write_fp.puts("[#ここでパラメーター終わり]") 75 | next 76 | end 77 | conver_dash!(line) 78 | convert_shiten!(line) 79 | write_fp.puts(line) 80 | @before_line = line.dup 81 | end 82 | data = write_fp.string 83 | data.gsub!(/\n\n([#ここからパラメーター])/m, "\n\\1") 84 | data.gsub!(/([#ここでパラメーター終わり]\n\n)/m, "[#ここでパラメーター終わり]\n") 85 | data.gsub!("それぞれ、人の世界、人神。", "それぞれ、\n 人の世界、人神。") 86 | data.rstrip! 87 | write_fp 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n9669bk/setting.ini: -------------------------------------------------------------------------------- 1 | ; 数字の漢数字変換を有効に 2 | enable_convert_num_to_kanji = true 3 | 4 | ; 漢数字変換した場合、千・万などに変換するか 5 | enable_kanji_num_with_units = true 6 | 7 | ; 〇(ゼロ)が最低この数字以上付いてないと千・万などをつける対象にしない 8 | kanji_num_with_units_lower_digit_zero = 2 9 | 10 | ; アルファベットを強制的に全角にする。falseの場合英文は半角、それ以外は全角になる 11 | enable_alphabet_force_zenkaku = false 12 | 13 | ; 行頭かぎ括弧に二分アキを挿入する 14 | enable_half_indent_bracket = true 15 | 16 | ; 自動行頭字下げ機能。行頭字下げが行われているかを判断し、適切に行頭字下げをする 17 | enable_auto_indent = false 18 | 19 | ; かぎ括弧内自動連結を有効に 20 | ; 例) 21 | ; 「~~~! 22 | ;  ***?」 → 「~~~! ***?」 23 | enable_auto_join_in_brackets = true 24 | 25 | ; かぎ括弧内のとじ開きが正しくされているかどうか調査する 26 | enable_inspect_invalid_openclose_brackets = true 27 | 28 | ; 行末が読点で終わっている部分を出来るだけ連結する 29 | enable_auto_join_line = true 30 | 31 | ; [#改ページ]直後の行に中見出しを付与する(テキストファイルを直接変換する場合のみの設定) 32 | enable_enchant_midashi = true 33 | 34 | ; 作者コメントを検出するか(テキストファイルを直接変換する場合のみの設定) 35 | enable_author_comments = true 36 | 37 | ; 前書きを削除するか 38 | enable_erase_introduction = false 39 | 40 | ; 後書きを削除するか 41 | enable_erase_postscript = false 42 | 43 | ; ルビ処理対策を有効に 44 | enable_ruby = true 45 | 46 | ; 小説家になろうの挿絵タグを有効にする(false なら削除) 47 | enable_narou_illust = true 48 | 49 | ; ○/×表記を×分の○表記に変換する。日付表記(10/23)と誤爆しやすいので注意 50 | enable_transform_fraction = false 51 | 52 | ; 日付表記(20yy/mm/dd)を任意の形式(date_formatで指定)に変換する 53 | enable_transform_date = true 54 | 55 | ; 書式は http://goo.gl/gvJ5u を参考 56 | date_format = %Y年%m月%d日 57 | 58 | ; 中黒(・)を並べて三点リーダーもどきにしているのを三点リーダーに変換する 59 | enable_convert_horizontal_ellipsis = false 60 | -------------------------------------------------------------------------------- /preset/ncode.syosetu.com/n9902bn/converter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 対象小説情報 5 | # タイトル: デスマーチからはじまる異世界狂想曲 6 | # 作者: 愛七ひろ 7 | # URL: http://ncode.syosetu.com/n9902bn/ 8 | # 9 | converter "n9902bn デスマーチからはじまる異世界狂想曲( web版 )" do 10 | def before(io, text_type) 11 | super 12 | end 13 | 14 | def announce_line?(line) 15 | line =~ /^〉/ 16 | end 17 | 18 | def after(io, text_type) 19 | buffer = StringIO.new 20 | just_before_line_is_announce = false 21 | io.each do |line| 22 | if announce_line?(line) 23 | unless just_before_line_is_announce 24 | if line.strip != "" 25 | buffer.puts 26 | end 27 | end 28 | just_before_line_is_announce = true 29 | buffer.puts "[#ゴシック体][#太字]#{line.rstrip}[#太字終わり][#ゴシック体終わり]" 30 | else 31 | if just_before_line_is_announce 32 | buffer.puts 33 | end 34 | just_before_line_is_announce = false 35 | buffer.puts line 36 | end 37 | end 38 | buffer 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /preset/vertical_font.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @namespace "http://www.w3.org/1999/xhtml"; 3 | 4 | body { 5 | font-family: "@MS 明朝", "@MS Mincho", "ヒラギノ明朝 ProN W3", "HiraMinProN-W3", serif, sans-serif; 6 | line-height: <%= line_height %>em !important; 7 | } 8 | 9 | .gtc, .b { 10 | font-family: '@MS ゴシック','@MS Gothic',sans-serif !important; 11 | } 12 | 13 | .b { font-weight: bold; } 14 | .i { font-style: italic; } 15 | 16 | rt { font-size: 0.6em; } 17 | 18 | /* カスタム注記用 */ 19 | 20 | /* 柱(もどき) */ 21 | .running_head { 22 | position: absolute !important; 23 | top: 15px; 24 | left: 10px; 25 | font-size: 0.8em; 26 | } 27 | 28 | /* 二分アキ */ 29 | .half_em_space { padding-top: 0.5em; } 30 | 31 | /* パラメーター(折り返しあり) */ 32 | .custom_parameter_block { 33 | font-size: 100%; 34 | line-height: 1.2em !important; 35 | border: 2px solid #000; 36 | border-radius: 4px; 37 | margin: 1em 0.5em 1em 0.5em; 38 | padding: 1em 0.2em 1em 0.2em; 39 | display: inline-block; 40 | font-family: sans-serif !important; 41 | font-weight: bold; 42 | box-shadow: 3px 3px 3px #bbb; 43 | -webkit-box-shadow: 3px 3px 3px #bbb; 44 | } 45 | .jzm .custom_parameter_block { 46 | display: block; 47 | } 48 | .jzm .p .custom_parameter_block { 49 | display: inline-block; 50 | } 51 | 52 | /* 前書き */ 53 | .introduction { 54 | float: right; 55 | font-size: 83%; 56 | line-height: 1.5em !important; 57 | border-top: 3px solid #aaa; 58 | color: #555; 59 | margin: 0.25em; 60 | margin-right: 1em; 61 | padding: 1em 0.5em 1em 0.5em; 62 | display: inline-block; 63 | font-family: sans-serif !important; 64 | text-align: left !important; 65 | height: 70%; 66 | } 67 | .jzm .introduction { 68 | display: block; 69 | } 70 | .jzm .p .introduction { 71 | display: inline-block; 72 | } 73 | 74 | /* 後書き */ 75 | .postscript { 76 | float: right; 77 | font-size: 83%; 78 | line-height: 1.5em !important; 79 | border-top: 3px solid #888; 80 | color: #222; 81 | margin: 0.25em; 82 | margin-right: 2em; 83 | padding: 1em 0.5em 1em 0.5em; 84 | display: inline-block; 85 | font-family: sans-serif !important; 86 | text-align: left !important; 87 | height: 70%; 88 | } 89 | .jzm .postscript { 90 | display: block; 91 | } 92 | .jzm .p .postscript { 93 | display: inline-block; 94 | } 95 | 96 | div.clear { 97 | clear: both; 98 | } 99 | -------------------------------------------------------------------------------- /preset/vertical_font_with_dakuten.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @namespace "http://www.w3.org/1999/xhtml"; 3 | 4 | body { 5 | font-family: "@MS 明朝", "@MS Mincho", "ヒラギノ明朝 ProN W3", "HiraMinProN-W3", serif, sans-serif; 6 | line-height: <%= line_height %>em !important; 7 | } 8 | 9 | .gtc, .b { 10 | font-family: '@MS ゴシック','@MS Gothic',sans-serif !important; 11 | } 12 | 13 | .b { font-weight: bold; } 14 | .i { font-style: italic; } 15 | 16 | rt { font-size: 0.6em; } 17 | 18 | /* カスタム注記用 */ 19 | 20 | /* 柱(もどき) */ 21 | .running_head { 22 | position: absolute !important; 23 | top: 15px; 24 | left: 10px; 25 | font-size: 0.8em; 26 | } 27 | 28 | /* 濁点フォント */ 29 | @font-face { 30 | font-family: "DakutenAokinMincho"; 31 | src: url(../fonts/DMincho.ttf) format("truetype"); 32 | } 33 | 34 | .dakuten { 35 | font-family: "DakutenAokinMincho" !important; 36 | } 37 | 38 | /* 二分アキ */ 39 | .half_em_space { padding-top: 0.5em; } 40 | 41 | /* パラメーター(折り返しあり) */ 42 | .custom_parameter_block { 43 | font-size: 100%; 44 | line-height: 1.2em !important; 45 | border: 2px solid #000; 46 | border-radius: 4px; 47 | margin: 1em 0.5em 1em 0.5em; 48 | padding: 1em 0.2em 1em 0.2em; 49 | display: inline-block; 50 | font-family: sans-serif !important; 51 | font-weight: bold; 52 | box-shadow: 3px 3px 3px #bbb; 53 | -webkit-box-shadow: 3px 3px 3px #bbb; 54 | } 55 | .jzm .custom_parameter_block { 56 | display: block; 57 | } 58 | .jzm .p .custom_parameter_block { 59 | display: inline-block; 60 | } 61 | 62 | /* 前書き */ 63 | .introduction { 64 | float: right; 65 | font-size: 83%; 66 | line-height: 1.5em !important; 67 | border-top: 3px solid #aaa; 68 | color: #555; 69 | margin: 0.25em; 70 | margin-right: 1em; 71 | padding: 1em 0.5em 1em 0.5em; 72 | display: inline-block; 73 | font-family: sans-serif !important; 74 | text-align: left !important; 75 | height: 70%; 76 | } 77 | .jzm .introduction { 78 | display: block; 79 | } 80 | .jzm .p .introduction { 81 | display: inline-block; 82 | } 83 | 84 | /* 後書き */ 85 | .postscript { 86 | float: right; 87 | font-size: 83%; 88 | line-height: 1.5em !important; 89 | border-top: 3px solid #888; 90 | color: #222; 91 | margin: 0.25em; 92 | margin-right: 2em; 93 | padding: 1em 0.5em 1em 0.5em; 94 | display: inline-block; 95 | font-family: sans-serif !important; 96 | text-align: left !important; 97 | height: 70%; 98 | } 99 | .jzm .postscript { 100 | display: block; 101 | } 102 | .jzm .p .postscript { 103 | display: inline-block; 104 | } 105 | 106 | div.clear { 107 | clear: both; 108 | } 109 | -------------------------------------------------------------------------------- /spec/command/list_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | require "time" 7 | require "timecop" 8 | require "commandline" 9 | 10 | describe Command::List do 11 | let(:novels) { Database.instance.get_object.values } 12 | let(:list) do 13 | Command::List.new.tap do |this| 14 | this.instance_variable_set(:@options, options) 15 | end 16 | end 17 | let(:options) { {} } 18 | let(:max) { novels.size } 19 | 20 | define :be_size_of do |expected| 21 | match do |actual| 22 | actual.size == expected 23 | end 24 | end 25 | 26 | describe "#output_list" do 27 | def expect_receive_output_lines_with_be_size_of(size) 28 | expect(list).to receive(:output_lines).with(be_size_of(size)) 29 | end 30 | 31 | it do 32 | expect_receive_output_lines_with_be_size_of 5 33 | list.output_list(novels, 5) 34 | end 35 | 36 | it do 37 | expect_receive_output_lines_with_be_size_of max 38 | list.output_list(novels, max) 39 | end 40 | 41 | it do 42 | expect_receive_output_lines_with_be_size_of max 43 | list.output_list(novels, max + 1) 44 | end 45 | 46 | context "--filter" do 47 | context "series" do 48 | let(:options) { { "filters" => %w(series) } } 49 | it do 50 | expect_receive_output_lines_with_be_size_of 9 51 | list.output_list(novels, max) 52 | end 53 | end 54 | 55 | context "ss" do 56 | let(:options) { { "filters" => %w(ss) } } 57 | it do 58 | expect_receive_output_lines_with_be_size_of 1 59 | list.output_list(novels, max) 60 | end 61 | end 62 | 63 | context "frozen" do 64 | let(:options) { { "filters" => %w(frozen) } } 65 | it do 66 | expect_receive_output_lines_with_be_size_of 3 67 | list.output_list(novels, max) 68 | end 69 | end 70 | 71 | context "nonfrozen" do 72 | let(:options) { { "filters" => %w(nonfrozen) } } 73 | it do 74 | expect_receive_output_lines_with_be_size_of 7 75 | list.output_list(novels, max) 76 | end 77 | end 78 | 79 | context "series nonfrozen" do 80 | let(:options) { { "filters" => %w(series nonfrozen) } } 81 | it do 82 | expect_receive_output_lines_with_be_size_of 6 83 | list.output_list(novels, max) 84 | end 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/command/update_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | # auto generated at 2015-08-07 22:44:33 +0900 6 | 7 | require "commandline" 8 | require "narou_logger" 9 | 10 | describe Command::Update do 11 | describe "--ignore-all" do 12 | it "should be blank" do 13 | cap = $stdout.capture { 14 | CommandLine.run!(%w(update --ignore-all)) 15 | }.strip 16 | expect(cap).to eq "" 17 | end 18 | 19 | it "should not be blank" do 20 | cap = $stdout.capture(quiet: true) { 21 | CommandLine.run!(%w(update --ignore-all 22)) 22 | }.strip 23 | expect(cap).to eq "ID:22 もう一度ナデシコへ は凍結中です" 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/data/convert_test/auto_indent/correct_test_auto_indent.txt: -------------------------------------------------------------------------------- 1 | test_auto_indent 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]オートインデントテスト[#中見出し終わり] 7 | 8 | 9 | [#二分アキ]「おはようございます、先輩♪」 10 |  朝から元気に挨拶をしてきたのは、一つしたの後輩、光坂瑞穂である。 11 |  こいつは昔から近所同士の付き合いをしてきた、いわゆる幼馴染という奴だ。 12 |  ……というのは嘘だ。 13 |  ・・これはインデントされる 14 | 15 | ・提案一 16 |  ああしろ、こうしろ 17 | 18 | ・提案二 19 |  どうしろ、さあしろ 20 | 21 | ・英文のインデント 22 |  Deus ex machina 23 | 24 | ・半角スペースを全角スペースで字下げ 25 |  半角スペースで字下げしたつもりの行 26 |  半角スペースで字下げしたつもりの行 27 |   半角スペース四個分は全角二個 28 |    奇数の場合は二で割って切り上 29 | -------------------------------------------------------------------------------- /spec/data/convert_test/auto_indent/test_auto_indent.txt: -------------------------------------------------------------------------------- 1 | test_auto_indent 2 | whiteleaf 3 | 4 | [#改ページ] 5 | オートインデントテスト 6 | 7 | 8 | 「おはようございます、先輩♪」 9 | 朝から元気に挨拶をしてきたのは、一つしたの後輩、光坂瑞穂である。 10 | こいつは昔から近所同士の付き合いをしてきた、いわゆる幼馴染という奴だ。 11 | ・・・というのは嘘だ。 12 | 13 | ・・これはインデントされる 14 | 15 | 16 | ・提案1 17 | ああしろ、こうしろ 18 | 19 | 20 | ・提案2 21 | どうしろ、さあしろ 22 | 23 | 24 | ・英文のインデント 25 | Deus ex machina 26 | 27 | 28 | ・半角スペースを全角スペースで字下げ 29 | 半角スペースで字下げしたつもりの行 30 | 半角スペースで字下げしたつもりの行 31 | 半角スペース4個分は全角2個 32 | 奇数の場合は2で割って切り上 33 | -------------------------------------------------------------------------------- /spec/data/convert_test/auto_join_bracket/correct_test_auto_join_bracket.txt: -------------------------------------------------------------------------------- 1 | test_auto_join_bracket 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]カギ括弧連結テスト[#中見出し終わり] 7 | 8 | 9 | [#二分アキ]「あいうえお? 二かきくけこ! 三さしすせそ[#縦中横]!?[#縦中横終わり]」たちつてと。 10 | 11 | [#二分アキ]「そういえば、それはミツキも同じだろ? ミツキは大丈夫なのか?」 12 | [#二分アキ]「私ですか? 一週間程度なら、連戦も問題ありません」 13 | [#二分アキ]「そ、そうか……」 14 | 15 | [#二分アキ]「改行の 16 |  多い 17 |  鍵カッコの 18 |  文章は 19 |  手紙形式の 20 |  可能性が 21 |  あるため 22 |  連結をしないで 23 |  そのままに 24 |  しておく!」 25 | -------------------------------------------------------------------------------- /spec/data/convert_test/auto_join_bracket/setting.ini: -------------------------------------------------------------------------------- 1 | enable_auto_join_in_brackets = true 2 | -------------------------------------------------------------------------------- /spec/data/convert_test/auto_join_bracket/test_auto_join_bracket.txt: -------------------------------------------------------------------------------- 1 | test_auto_join_bracket 2 | whiteleaf 3 | 4 | [#改ページ] 5 | カギ括弧連結テスト 6 | 7 | 「あいうえお? 8 |  二かきくけこ! 9 |  三さしすせそ!?」たちつてと。 10 | 11 | 12 | 「そういえば、それはミツキも同じだろ? 13 |  ミツキは大丈夫なのか?」 14 | 「私ですか? 15 |  一週間程度なら、連戦も問題ありません」 16 | 「そ、そうか……」 17 | 18 | 19 | 「改行の 20 |  多い 21 |  鍵カッコの 22 |  文章は 23 |  手紙形式の 24 |  可能性が 25 |  あるため 26 |  連結をしないで 27 |  そのままに 28 |  しておく!」 29 | -------------------------------------------------------------------------------- /spec/data/convert_test/auto_join_line/correct_test_auto_join_line.txt: -------------------------------------------------------------------------------- 1 | test_auto_join_line 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]作者による折り返しを強制連結テスト[#中見出し終わり] 7 | 8 | 9 | ○前後の文がつながって欲しい表現 10 |  悲痛な叫び声が背中に突き刺さっていたが、明日香は無視して走り続けた。 11 | 12 |  悲痛な叫び声が背中に突き刺さっていたが、明日香は無視して走り続けた。 13 | 14 | 15 | ×前後の文はつながって欲しくない表現 16 | 悲痛な叫び声が背中に突き刺さっていたが、 17 | 明日香は無視して走り続けた。 18 | 19 |  悲痛な叫び声が背中に突き刺さっていたが、 20 | 21 |  明日香は無視して走り続けた。 22 | 23 |  次の行が字下げなしの場合は、箇条書きの説明等の可能性があるため連結して欲しくない。 24 |  悲痛な叫び声が背中に突き刺さっていたが、 25 | 明日香は無視して走り続けた。 26 | 27 |  悲痛な叫び声が背中に突き刺さっていたが、 28 |  ……明日香は無視して走り続けた。 29 | 30 |  悲痛な叫び声が背中に突き刺さっていたが、 31 |  ――明日香は無視して走り続けた。 32 | 33 |  悲痛な叫び声が背中に突き刺さっていたが、 34 | [#二分アキ]「私が行かなければ彼の犠牲は無駄になる……!」 35 | 36 |  悲痛な叫び声が背中に突き刺さっていたが、 37 | [#二分アキ]「私が行かなければ彼の犠牲は無駄になる……!」 38 | 39 |  悲痛な叫び声が背中に突き刺さっていたが、 40 | [#二分アキ](私が行かなければ彼の犠牲は無駄になる……!) 41 | 42 |       一度鑑定するか、倒した魔物、アイテムの情報を蓄積し、 43 |       後に閲覧できる。 44 | 45 |  箇条書きの文章と想定される文章は連結しない、 46 | ・項目一 47 | ・項目二 48 | 49 |  箇条書きの文章と想定される文章は連結しない、 50 | ■項目一 51 | ■項目二 52 | 53 |  箇条書きの文章と想定される文章は連結しない(行頭字下げパターン)、 54 |  ・項目一 55 |  ・項目二 56 | 57 |  箇条書きの文章と想定される文章は連結しない(行頭字下げパターン)、 58 |  ■項目一 59 |  ■項目二 60 | 61 |  箇条書きの文章と想定される文章は連結しない(数字パターン)、 62 |  一、項目一 63 |  二、項目二 64 | -------------------------------------------------------------------------------- /spec/data/convert_test/auto_join_line/test_auto_join_line.txt: -------------------------------------------------------------------------------- 1 | test_auto_join_line 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 作者による折り返しを強制連結テスト 6 | 7 | 8 | ○前後の文がつながって欲しい表現 9 | 10 |  悲痛な叫び声が背中に突き刺さっていたが、 11 |  明日香は無視して走り続けた。 12 | 13 | 14 |  悲痛な叫び声が背中に突き刺さっていたが、 15 | 16 |  明日香は無視して走り続けた。 17 | 18 | 19 | 20 | 21 | ×前後の文はつながって欲しくない表現 22 | 23 | 悲痛な叫び声が背中に突き刺さっていたが、 24 | 明日香は無視して走り続けた。 25 | 26 | 27 |  悲痛な叫び声が背中に突き刺さっていたが、 28 | 29 | 30 |  明日香は無視して走り続けた。 31 | 32 | 33 |  次の行が字下げなしの場合は、箇条書きの説明等の可能性があるため連結して欲しくない。 34 |  悲痛な叫び声が背中に突き刺さっていたが、 35 | 明日香は無視して走り続けた。 36 | 37 | 38 |  悲痛な叫び声が背中に突き刺さっていたが、 39 |  ……明日香は無視して走り続けた。 40 | 41 | 42 |  悲痛な叫び声が背中に突き刺さっていたが、 43 |  ――明日香は無視して走り続けた。 44 | 45 | 46 |  悲痛な叫び声が背中に突き刺さっていたが、 47 | 「私が行かなければ彼の犠牲は無駄になる……!」 48 | 49 | 50 |  悲痛な叫び声が背中に突き刺さっていたが、 51 |  「私が行かなければ彼の犠牲は無駄になる……!」 52 | 53 | 54 |  悲痛な叫び声が背中に突き刺さっていたが、 55 | (私が行かなければ彼の犠牲は無駄になる……!) 56 | 57 | 58 |       一度鑑定するか、倒した魔物、アイテムの情報を蓄積し、 59 |       後に閲覧できる。 60 | 61 | 62 |  箇条書きの文章と想定される文章は連結しない、 63 | ・項目1 64 | ・項目2 65 | 66 | 67 |  箇条書きの文章と想定される文章は連結しない、 68 | ■項目1 69 | ■項目2 70 | 71 | 72 |  箇条書きの文章と想定される文章は連結しない(行頭字下げパターン)、 73 |  ・項目1 74 |  ・項目2 75 | 76 | 77 |  箇条書きの文章と想定される文章は連結しない(行頭字下げパターン)、 78 |  ■項目1 79 |  ■項目2 80 | 81 | 82 |  箇条書きの文章と想定される文章は連結しない(数字パターン)、 83 |  1、項目1 84 |  2、項目2 85 | -------------------------------------------------------------------------------- /spec/data/convert_test/convert_numbers/correct_test_convert_numbers.txt: -------------------------------------------------------------------------------- 1 | test_convert_numbers 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]数字変換テスト[#中見出し終わり] 7 | 8 | 9 |  三・一四 10 |  三・一四 11 |  一.一石二鳥 12 | -------------------------------------------------------------------------------- /spec/data/convert_test/convert_numbers/test_convert_numbers.txt: -------------------------------------------------------------------------------- 1 | test_convert_numbers 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 数字変換テスト 6 | 7 | 8 | 3.14 9 | 三.一四 10 | 1.一石二鳥 11 | -------------------------------------------------------------------------------- /spec/data/convert_test/convert_page_break/correct_test_convert_page_break.txt: -------------------------------------------------------------------------------- 1 | test_convert_page_break 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]改ページ変換テスト[#中見出し終わり] 7 | 8 | 9 |  一般的な空行一(改ページに変換されない) 10 | 11 |  一般的な空行二(改ページに変換されない) 12 | 13 | 14 |  空行九個(改ページに変換されない) 15 | 16 | 17 | 18 |  空行一〇個(改ページに変換される) 19 | [#改頁] 20 |  一〇個よりも多い空行(改ページに変換される) 21 | [#改頁] 22 |  終わり 23 | -------------------------------------------------------------------------------- /spec/data/convert_test/convert_page_break/setting.ini: -------------------------------------------------------------------------------- 1 | ; `to_page_break_threshold` で設定した個数以上連続する空行を改ページに変換する 2 | enable_convert_page_break = true 3 | 4 | ; ここで設定した値が `enable_convert_page_break` に反映される 5 | to_page_break_threshold = 10 6 | -------------------------------------------------------------------------------- /spec/data/convert_test/convert_page_break/test_convert_page_break.txt: -------------------------------------------------------------------------------- 1 | test_convert_page_break 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 改ページ変換テスト 6 | 7 |  一般的な空行1(改ページに変換されない) 8 | 9 | 10 |  一般的な空行2(改ページに変換されない) 11 | 12 | 13 | 14 | 15 |  空行9個(改ページに変換されない) 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |  空行10個(改ページに変換される) 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |  10個よりも多い空行(改ページに変換される) 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |  終わり 61 | -------------------------------------------------------------------------------- /spec/data/convert_test/convert_prolonged_sound_mark_to_dash/correct_test_convert_prolonged_sound_mark_to_dash.txt: -------------------------------------------------------------------------------- 1 | test_convert_prolonged_sound_mark_to_dash 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]二つ以上の長音記号を全角ダッシュに置換[#中見出し終わり] 7 | 8 | 9 |  ヨシダ――――[#縦中横]!![#縦中横終わり] 10 | 11 |  ヨッシー 12 | -------------------------------------------------------------------------------- /spec/data/convert_test/convert_prolonged_sound_mark_to_dash/setting.ini: -------------------------------------------------------------------------------- 1 | enable_prolonged_sound_mark_to_dash = true 2 | -------------------------------------------------------------------------------- /spec/data/convert_test/convert_prolonged_sound_mark_to_dash/test_convert_prolonged_sound_mark_to_dash.txt: -------------------------------------------------------------------------------- 1 | test_convert_prolonged_sound_mark_to_dash 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 2つ以上の長音記号を全角ダッシュに置換 6 | 7 |  ヨシダーーーー!! 8 | 9 | 10 |  ヨッシー 11 | -------------------------------------------------------------------------------- /spec/data/convert_test/disable_alphabet_word_to_zenkaku/correct_test_disable_alphabet_word_to_zenkaku.txt: -------------------------------------------------------------------------------- 1 | test_disable_alphabet_word_to_zenkaku 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]英単語全角化抑制テスト[#中見出し終わり] 7 | 8 | 9 |  Salarua, |Xalijasti《シャリヤスティ》! Edixa |mi《私》 klie dea do! 10 | 11 |  私はHTMLを書いている。 12 |  私はHTMLを書いている。 13 | -------------------------------------------------------------------------------- /spec/data/convert_test/disable_alphabet_word_to_zenkaku/setting.ini: -------------------------------------------------------------------------------- 1 | disable_alphabet_word_to_zenkaku = true 2 | -------------------------------------------------------------------------------- /spec/data/convert_test/disable_alphabet_word_to_zenkaku/test_disable_alphabet_word_to_zenkaku.txt: -------------------------------------------------------------------------------- 1 | test_disable_alphabet_word_to_zenkaku 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 英単語全角化抑制テスト 6 | 7 | Salarua, |Xalijasti《シャリヤスティ》! Edixa |mi《私》 klie dea do! 8 | 9 | 10 | 私はHTMLを書いている。 11 | 私はHTMLを書いている。 12 | -------------------------------------------------------------------------------- /spec/data/convert_test/english/correct_test_english.txt: -------------------------------------------------------------------------------- 1 | test_english 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]アルファベット全角化&英文テスト[#中見出し終わり] 7 | 8 | 9 |  ■次の文中のアルファベットは全角化される 10 |  モンスターのHPが高すぎる。 11 |  MPが足りなくて魔法を使うことが出来ない。 12 |  Randomは全角になる。 13 |  ABC:DEF 14 | 15 |  ■次の文中のアルファベットは全角化されない(英文判定) 16 |  This is a pen. 17 |  This novel was written by whiteleaf7. 18 |  What happen!? 19 |  Ken's delivery shopping. 20 |  幼女戦記Tuez-les tous, Dieu reconnaitra les siens. 21 |  He said: "I’m dead beat, but I have work after this." 22 |  maid by H&M 23 |  background-color: black; 24 |  Alphabetは長いので半角のまま。 25 |  ABC_DEF_GHI 26 | [#二分アキ]「Deus ex machina!」 27 | [#二分アキ]「Deus ex machina!」 28 | -------------------------------------------------------------------------------- /spec/data/convert_test/english/test_english.txt: -------------------------------------------------------------------------------- 1 | test_english 2 | whiteleaf 3 | 4 | [#改ページ] 5 | アルファベット全角化&英文テスト 6 | 7 | 8 |  ■次の文中のアルファベットは全角化される 9 |  モンスターのHPが高すぎる。 10 |  MPが足りなくて魔法を使うことが出来ない。 11 |  Randomは全角になる。 12 |  ABC:DEF 13 | 14 | 15 |  ■次の文中のアルファベットは全角化されない(英文判定) 16 |  This is a pen. 17 |  This novel was written by whiteleaf7. 18 |  What happen!? 19 |  Ken's delivery shopping. 20 |  幼女戦記Tuez-les tous, Dieu reconnaitra les siens. 21 |  He said: "I’m dead beat, but I have work after this." 22 |  maid by H&M 23 |  background-color: black; 24 |  Alphabetは長いので半角のまま。 25 |  ABC_DEF_GHI 26 | 「Deus ex machina!」 27 | 「Deus ex machina!」 28 | -------------------------------------------------------------------------------- /spec/data/convert_test/force_indent_special_chapter/correct_test_force_indent_special_chapter.txt: -------------------------------------------------------------------------------- 1 | test_force_indent_special_chapter 2 | whiteleaf 3 | 4 |  数字章見出しの自動インデント 5 | 6 |    ― [#縦中横]10[#縦中横終わり] ― 7 | 8 |  ほげほげ 9 | 10 |    ― 1 ― 11 | 12 |  ほげほげ 13 | 14 |    〈[#縦中横]23[#縦中横終わり]〉 15 | 16 |  ほげほげ 17 | 18 |    ― 200 ― 19 | 20 |    ― 100 ― 21 | 22 |    〈350〉 23 | 24 |    〈[#縦中横]31[#縦中横終わり]〉 25 | 26 |    5 27 | 28 |    [#縦中横]40[#縦中横終わり] 29 | 30 |    100 31 | 32 | 四桁以上は対象外 33 | 千 34 | 二万 35 | 36 |    五 37 | 38 |    三五 39 | 40 |    一〇〇 41 | 42 | 六七八九 43 | 三百 44 | 45 |  第一〇章 46 |  これは変換してほしくない 47 | [#改頁] 48 | 49 |    1 50 | 51 |  窓の外から小鳥のさえずる声が聞こえる。窓から差し込む淡い光が部屋の中に広がっている。 52 |  また今日も新しい朝が来た。 53 | 54 |    2 55 | 56 |  仕事支度を整えた私は、時間を気にしつつもゆっくりとした気持ちで玄関の扉を開けた。 57 |  朝もやの漂う空気は静謐さを感じさせる。 58 |  まだ周囲には人はまばらにしかいない。 59 |  それも当然だろう。現在の時刻は午前六時。出勤するというにはまだ若干早いといえる時間だ。 60 | 61 |    3 62 | 63 |  私は出勤したらまずデスク周りの掃除からはじめる。 64 |  ゴミひとつないキレイな環境で、今日もまた気持ちのいい心で仕事に励めるだろう。 65 | -------------------------------------------------------------------------------- /spec/data/convert_test/force_indent_special_chapter/test_force_indent_special_chapter.txt: -------------------------------------------------------------------------------- 1 | test_force_indent_special_chapter 2 | whiteleaf 3 | 4 |  数字章見出しの自動インデント 5 | 6 | 7 |  ―10― 8 | 9 |  ほげほげ 10 | 11 | 12 |  -1- 13 |  ほげほげ 14 | 15 | 16 |  <23> 17 | 18 |  ほげほげ 19 | 20 | 21 | -200 22 | 23 | 24 | -100- 25 | <350> 26 | <31> 27 | 5 28 | 40 29 | 100 30 | 31 | 四桁以上は対象外 32 | 1000 33 | 20000 34 | 五 35 | 三五 36 | 一〇〇 37 | 六七八九 38 | 三百 39 | 40 | 41 |  第10章 42 | 43 |  これは変換してほしくない 44 | [#改頁] 45 |  1 46 | 47 |  窓の外から小鳥のさえずる声が聞こえる。窓から差し込む淡い光が部屋の中に広がっている。 48 |  また今日も新しい朝が来た。 49 | 50 | 51 |  2 52 | 53 |  仕事支度を整えた私は、時間を気にしつつもゆっくりとした気持ちで玄関の扉を開けた。 54 |  朝もやの漂う空気は静謐さを感じさせる。 55 |  まだ周囲には人はまばらにしかいない。 56 |  それも当然だろう。現在の時刻は午前六時。出勤するというにはまだ若干早いといえる時間だ。 57 | 58 |  3 59 | 60 |  私は出勤したらまずデスク周りの掃除からはじめる。 61 |  ゴミひとつないキレイな環境で、今日もまた気持ちのいい心で仕事に励めるだろう。 62 | -------------------------------------------------------------------------------- /spec/data/convert_test/horizontal_ellipsis/correct_test_horizontal_ellipsis.txt: -------------------------------------------------------------------------------- 1 | test_horizontal_ellipsis 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]三点リーダー化テスト[#中見出し終わり] 7 | 8 | 9 |  。 10 |  、 11 | 12 |  …… 13 |  …… 14 |  …… 15 |  …… 16 |  ………… 17 |  ………… 18 |  ………… 19 |  ………… 20 |  ………… 21 |  ………… 22 |  ……………… 23 | 24 |  …… 25 |  …… 26 |  …… 27 |  …… 28 |  ………… 29 |  ………… 30 |  ………… 31 |  ………… 32 |  ………… 33 |  ………… 34 |  ……………… 35 | 36 |  俺はこう尋ねた、 37 | [#二分アキ]「それは違うんじゃないか?」 38 |  俺としては、もっと溌溂とした姿を見てみたい、 39 |  ……だが。 40 | 41 |  …… 42 |  …… 43 |  …… 44 |  …… 45 |  ………… 46 |  ………… 47 |  ………… 48 |  ………… 49 |  ………… 50 |  ………… 51 |  ……………… 52 | 53 |  そうだな…… 54 | -------------------------------------------------------------------------------- /spec/data/convert_test/horizontal_ellipsis/test_horizontal_ellipsis.txt: -------------------------------------------------------------------------------- 1 | test_horizontal_ellipsis 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 三点リーダー化テスト 6 | 7 |  。。 8 |  、、 9 | 10 | 11 |  ・・・ 12 |  ・・・・ 13 |  ・・・・・ 14 |  ・・・・・・ 15 |  ・・・・・・・ 16 |  ・・・・・・・・ 17 |  ・・・・・・・・・ 18 |  ・・・・・・・・・・ 19 |  ・・・・・・・・・・・ 20 |  ・・・・・・・・・・・・ 21 |  ・・・・・・・・・・・・・ 22 | 23 | 24 |  、、、 25 |  、、、、 26 |  、、、、、 27 |  、、、、、、 28 |  、、、、、、、 29 |  、、、、、、、、 30 |  、、、、、、、、、 31 |  、、、、、、、、、、 32 |  、、、、、、、、、、、 33 |  、、、、、、、、、、、、 34 |  、、、、、、、、、、、、、 35 | 36 | 37 |  俺はこう尋ねた、 38 | 「それは違うんじゃないか?」 39 |  俺としては、 40 |  もっと溌溂とした姿を見てみたい、 41 |  ……だが。 42 | 43 | 44 |  。。。 45 |  。。。。 46 |  。。。。。 47 |  。。。。。。 48 |  。。。。。。。 49 |  。。。。。。。。 50 |  。。。。。。。。。 51 |  。。。。。。。。。。 52 |  。。。。。。。。。。。 53 |  。。。。。。。。。。。。 54 |  。。。。。。。。。。。。。 55 | 56 | 57 |  そうだな...... 58 | -------------------------------------------------------------------------------- /spec/data/convert_test/insert_separator/correct_test_insert_separator.txt: -------------------------------------------------------------------------------- 1 | test_insert_separator 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し][#zws]単語分割[#zws]の[#zws]テスト[#zws][#中見出し終わり] 7 | 8 | 9 |  [#zws]単語分割[#zws]の[#zws]テスト[#zws]。 10 | [#二分アキ]「隣[#zws]の[#zws]客[#zws]はよく[#zws]柿食[#zws]う[#zws]客[#zws]だ[#zws]」 11 | 12 | [#zws]http[#zws]://[#zws]www[#zws].[#zws]google[#zws].[#zws]com[#zws]/ 13 | 14 | -------------------------------------------------------------------------------- /spec/data/convert_test/insert_separator/setting.ini: -------------------------------------------------------------------------------- 1 | ; 単語選択がしやすいように単語単位の区切りデータを挿入する(Kindle専用) 2 | enable_insert_word_separator = true 3 | 4 | -------------------------------------------------------------------------------- /spec/data/convert_test/insert_separator/test_insert_separator.txt: -------------------------------------------------------------------------------- 1 | test_insert_separator 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 単語分割のテスト 6 | 7 |  単語分割のテスト。 8 | 9 | 「隣の客はよく柿食う客だ」 10 | 11 | 12 | http://www.google.com/ 13 | -------------------------------------------------------------------------------- /spec/data/convert_test/insert_separator_and_replace_txt/correct_test_insert_separator_and_replace_txt.txt: -------------------------------------------------------------------------------- 1 | test_insert_separator_and_replace_txt 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し][#zws]単語分割[#zws]をしつつ[#zws]置換[#zws]する[#zws]テスト[#zws][#中見出し終わり] 7 | 8 | 9 |  [#zws]隣[#zws]の[#zws]客[#zws]はよく[#zws]カキ[#zws]食[#zws]う[#zws]客[#zws]だ[#zws]。 10 |  [#zws]さんぞうほうしは[#zws]天竺[#zws]を[#zws]目指[#zws]した[#zws]。[#zws]仲間[#zws]としてそんごくう[#zws]、[#zws]さごじょう[#zws]、[#zws]チョハッカイ[#zws]という[#zws]妖怪[#zws]を[#zws]従[#zws]えていた[#zws]。 11 | 12 |  [#zws]すでに[#zws]置換済[#zws]みの[#zws]文章[#zws] 13 | -------------------------------------------------------------------------------- /spec/data/convert_test/insert_separator_and_replace_txt/replace.txt: -------------------------------------------------------------------------------- 1 | 柿 カキ 2 | 3 | 三蔵法師 さんぞうほうし 4 | 孫悟空 そんごくう 5 | 沙悟浄 さごじょう 6 | 猪八戒 チョハッカイ 7 | 8 | 色々まとめて置換 すでに置換済みの文章 9 | -------------------------------------------------------------------------------- /spec/data/convert_test/insert_separator_and_replace_txt/setting.ini: -------------------------------------------------------------------------------- 1 | ; 単語選択がしやすいように単語単位の区切りデータを挿入する(Kindle専用) 2 | enable_insert_word_separator = true 3 | 4 | -------------------------------------------------------------------------------- /spec/data/convert_test/insert_separator_and_replace_txt/test_insert_separator_and_replace_txt.txt: -------------------------------------------------------------------------------- 1 | test_insert_separator_and_replace_txt 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 単語分割をしつつ置換するテスト 6 | 7 |  隣の客はよく柿食う客だ。 8 |  三蔵法師は天竺を目指した。仲間として孫悟空、沙悟浄、猪八戒という妖怪を従えていた。 9 | 10 | 11 |  色々まとめて置換 12 | -------------------------------------------------------------------------------- /spec/data/convert_test/kanji_num/correct_test_kanji_num.txt: -------------------------------------------------------------------------------- 1 | test_kanji_num 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]漢数字化テスト[#中見出し終わり] 7 | 8 | 9 |  八百万一千 10 |  八〇〇万一〇〇 11 |  八〇〇万一〇 12 |  二千三百万 13 | 14 |  もともと漢数字なのは弄らない 15 |  八百万一千 16 |  八百万の神 17 |  こちらは二千三百万円になります。 18 |  五十歩百歩 19 |  三億円 20 | 21 |  三億一千万 22 |  三万一千 23 |  三十万 24 |  三百万 25 |  三千万 26 |  三億 27 |  三十億 28 |  三百億 29 |  三千億 30 |  三兆一千 31 |  三十兆一千三百三十三億四千二百三十四万 32 |  一千兆 33 |  一京 34 |  十京 35 |  百京 36 |  一千京 37 |  二千三百万円 38 |  億千万 39 | 40 |  次の数字は対応桁数以上なのでそのまま 41 |  一〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇 42 |  一〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇 43 | 44 |  次の数字はアラビア数字のままである 45 |  Ver1・01 46 |  100m 47 |  秒速5cm 48 |  出力120%! 49 |  ものさしの長さは30㎝ 50 |  100cm×100cm=1㎡ 51 | -------------------------------------------------------------------------------- /spec/data/convert_test/kanji_num/test_kanji_num.txt: -------------------------------------------------------------------------------- 1 | test_kanji_num 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 漢数字化テスト 6 | 7 | 8 | 800万1000 9 | 800万100 10 | 800万10 11 | 2千3百万 12 | 13 | 14 | もともと漢数字なのは弄らない 15 | 八百万一千 16 | 八百万の神 17 | こちらは二千三百万円になります。 18 | 五十歩百歩 19 | 三億円 20 | 21 | 22 | 310000000 23 | 31000 24 | 300000 25 | 3000000 26 | 30000000 27 | 300000000 28 | 3000000000 29 | 30000000000 30 | 300000000000 31 | 3000000001000 32 | 30133342340000 33 | 1000000000000000 34 | 10000000000000000 35 | 100000000000000000 36 | 1000000000000000000 37 | 10000000000000000000 38 | 23000000円 39 | 億千万 40 | 41 | 42 | 次の数字は対応桁数以上なのでそのまま 43 | 44 | 100000000000000000000 45 | 1000000000000000000000 46 | 47 | 48 | 次の数字はアラビア数字のままである 49 | 50 | Ver1.01 51 | 100m 52 | 秒速5cm 53 | 出力120%! 54 | ものさしの長さは30㎝ 55 | 100cm×100cm=1㎡ 56 | 57 | -------------------------------------------------------------------------------- /spec/data/convert_test/nonokagi/correct_test_nonokagi.txt: -------------------------------------------------------------------------------- 1 | test_nonokagi 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]ノノカギテスト[#中見出し終わり] 7 | 8 | 9 | [#二分アキ]〝行頭ノノカギ〟 10 | [#二分アキ]〝行頭ノノカギ〟 11 | [#二分アキ]〝行頭ノノカギ〟 12 | [#二分アキ]〝行頭ノノカギ〟 13 | [#二分アキ]〝行頭ノノカギ〟 14 | [#二分アキ]〝行頭ノノカギ〟 15 | 16 |  文中〝ノノカギ〟テスト 17 | 18 | [#二分アキ]〝改行テスト 19 |  ほげほげ〟 20 | 21 | “改行テスト。 22 |  これは変換されないでそのままのはずである。 23 |  改行を挟んだノノカギの変換には対応しない。 24 |  閉じ忘れた場合、入れ子対応しない限り後ろのほうまで影響が出る為” 25 | 26 | [#二分アキ]〝ノノカギ閉じ忘れテスト 27 |  そう、私は言った、的な。 28 | 29 | [#二分アキ]〝ノノカギのとじ忘れ以降の文章〟 30 |  マミコはそう言った。 31 | -------------------------------------------------------------------------------- /spec/data/convert_test/nonokagi/test_nonokagi.txt: -------------------------------------------------------------------------------- 1 | test_nonokagi 2 | whiteleaf 3 | 4 | [#改ページ] 5 | ノノカギテスト 6 | 7 | ”行頭ノノカギ” 8 | “行頭ノノカギ” 9 | "行頭ノノカギ" 10 | '行頭ノノカギ' 11 | ‘行頭ノノカギ’ 12 | 〝行頭ノノカギ〟 13 | 14 | 15 |  文中”ノノカギ”テスト 16 | 17 | 18 | 〝改行テスト 19 |  ほげほげ〟 20 | 21 | 22 | “改行テスト。 23 |  これは変換されないでそのままのはずである。 24 |  改行を挟んだノノカギの変換には対応しない。 25 |  閉じ忘れた場合、入れ子対応しない限り後ろのほうまで影響が出る為” 26 | 27 | 28 | 〝ノノカギ閉じ忘れテスト 29 |  そう、私は言った、的な。 30 | 31 | 32 | 〝ノノカギのとじ忘れ以降の文章〟 33 |  マミコはそう言った。 34 | 35 | -------------------------------------------------------------------------------- /spec/data/convert_test/replace/correct_test_replace.txt: -------------------------------------------------------------------------------- 1 | test_replace 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]replace.txtテスト[#中見出し終わり] 7 | 8 | 9 |  |東京特許許可局《とうきょうとっきょきょかきょく》 10 | 11 |  HOGEHOGE 12 |  BぷよでやるPUYOPUYOは楽しいよ 13 |  はげはげ 14 | -------------------------------------------------------------------------------- /spec/data/convert_test/replace/replace.txt: -------------------------------------------------------------------------------- 1 | ルビ: 2 | 3 | 置換 ちかん 4 | 5 | ほげ HOGE 6 | 7 | ぷよぷよ PUYOPUYO -------------------------------------------------------------------------------- /spec/data/convert_test/replace/test_replace.txt: -------------------------------------------------------------------------------- 1 | test_replace 2 | whiteleaf 3 | 4 | [#改ページ] 5 | replace.txtテスト 6 | 7 | |東京特許許可局《ルビ:とうきょうとっきょきょかきょく》 8 | 9 | 10 | ほげほげ 11 | 12 | Bぷよでやるぷよぷよは楽しいよ 13 | 14 | はげはげ 15 | -------------------------------------------------------------------------------- /spec/data/convert_test/rome_num/correct_test_rome_num.txt: -------------------------------------------------------------------------------- 1 | test_rome_num 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]ローマ数字テスト[#中見出し終わり] 7 | 8 | 9 |  Ⅱ Ⅲ Ⅳ Ⅵ Ⅶ Ⅷ Ⅸ ⅱ ⅲ ⅳ ⅵ ⅶ ⅷ ⅸ 10 | -------------------------------------------------------------------------------- /spec/data/convert_test/rome_num/test_rome_num.txt: -------------------------------------------------------------------------------- 1 | test_rome_num 2 | whiteleaf 3 | 4 | [#改ページ] 5 | ローマ数字テスト 6 | 7 | 8 | II III IV VI VII VIII IX ii iii iv vi vii viii ix 9 | -------------------------------------------------------------------------------- /spec/data/convert_test/ruby_youon/correct_test_ruby_youon.txt: -------------------------------------------------------------------------------- 1 | test_ruby_youon 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]拗音巨大化テスト[#中見出し終わり] 7 | 8 | 9 |  僕の好きな言葉に|一日千秋《いちじつせんしゆう》という言葉がある。 10 | 11 |  |東京特許許可局《とうきようとつきよきよかきよく》 12 | 13 |  |東京特許許可局《トウキヨウトツキヨキヨカキヨク》 14 | -------------------------------------------------------------------------------- /spec/data/convert_test/ruby_youon/setting.ini: -------------------------------------------------------------------------------- 1 | ; ルビの拗音(ぁ、ぃ等)を商業書籍のように大きくするかどうか 2 | enable_ruby_youon_to_big = true 3 | -------------------------------------------------------------------------------- /spec/data/convert_test/ruby_youon/test_ruby_youon.txt: -------------------------------------------------------------------------------- 1 | test_ruby_youon 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 拗音巨大化テスト 6 | 7 | 8 | 僕の好きな言葉に一日千秋《いちじつせんしゅう》という言葉がある。 9 | 10 | 11 | 東京特許許可局《とうきょうとっきょきょかきょく》 12 | 13 | 14 | 東京特許許可局《トウキョウトッキョキョカキョク》 15 | -------------------------------------------------------------------------------- /spec/data/convert_test/sesame/correct_test_sesame.txt: -------------------------------------------------------------------------------- 1 | test_sesame 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]傍点テスト[#中見出し終わり] 7 | 8 | 9 | 傍点にならないパターン 10 | 11 | [#二分アキ](………………) 12 | [#二分アキ](………………) 13 | [#二分アキ](………………) 14 | [#二分アキ](…………) 15 |  なんということだ(…………) 16 | 17 |  漢字※[#始め二重山括弧]……※[#終わり二重山括弧] 18 | 19 | 20 | 傍点になるパターン 21 | 22 |  次の漢字には[#傍点]圏点[#傍点終わり] 次の漢字には[#傍点]圏点[#傍点終わり] 23 |  次の漢字には[#傍点]圏点[#傍点終わり]次の漢字には[#傍点]圏点[#傍点終わり] 24 | 25 |  [#傍点]この文章には圏点[#傍点終わり] [#傍点]この文章には圏点[#傍点終わり] 26 |  [#傍点]この文章には圏点[#傍点終わり][#傍点]この文章には圏点[#傍点終わり] 27 | 28 |  中黒の文字数が合っていない場合の[#傍点]圏点化[#傍点終わり] 29 |  中黒の文字数が合っていない場合の[#傍点]圏点化[#傍点終わり] 30 |  中黒の文字数が[#傍点]合っていない場合の圏点化[#傍点終わり] 31 | 32 | [#二分アキ]※[#始め二重山括弧]二重山括弧の中での[#傍点]圏点化[#傍点終わり]です※[#終わり二重山括弧] 33 | [#二分アキ](括弧の中での[#傍点]圏点化[#傍点終わり]です) 34 | 35 |  読点で圏点(、)  NG 36 |  読点で※[#縦線]圏点(、)  NG 37 |  ※[#縦線]読点で圏点(、)  NG 38 | 39 |  読点で[#傍点]圏点[#傍点終わり] 40 |  読点で[#傍点]圏点[#傍点終わり] 41 |  [#傍点]読点で圏点[#傍点終わり] 42 | -------------------------------------------------------------------------------- /spec/data/convert_test/sesame/test_sesame.txt: -------------------------------------------------------------------------------- 1 | test_sesame 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 傍点テスト 6 | 7 | 8 | 傍点にならないパターン 9 | 10 | 11 | (・・・・・・・・・・・・・・) 12 | 13 | (………………) 14 | 15 |  (……………) 16 |  (・・・・・・・・・・・) 17 |  なんということだ(・・・・・・・・) 18 | 19 | 20 |  漢字|《・・・》 21 | 22 | 23 | 24 | 25 | 傍点になるパターン 26 | 27 | 28 |  次の漢字には圏点《・・・・・・》 次の漢字には圏点(・・・・・・) 29 |  次の漢字には圏点《・・・・・・》次の漢字には圏点(・・・・・・) 30 | 31 | 32 |  |この文章には圏点《・・・・・・》 |この文章には圏点《・・・・・・》 33 |  |この文章には圏点《・・・・・・》|この文章には圏点《・・・・・・》 34 | 35 | 36 |  中黒の文字数が合っていない場合の圏点化《・・・・・・・》 37 |  中黒の文字数が合っていない場合の|圏点化《・・・・・・・》 38 |  中黒の文字数が|合っていない場合の圏点化《・・・》 39 | 40 | 41 | 《二重山括弧の中での|圏点化《・・・》です》 42 | (括弧の中での|圏点化《・・・》です) 43 | 44 | 45 |  読点で圏点(、、)  NG 46 |  読点で|圏点(、、)  NG 47 |  |読点で圏点(、、)  NG 48 | 49 | 50 |  読点で圏点《、、》 51 |  読点で|圏点《、、》 52 |  |読点で圏点《、、》 53 | -------------------------------------------------------------------------------- /spec/data/convert_test/to_odd_leader/correct_test_to_odd_leader.txt: -------------------------------------------------------------------------------- 1 | test_to_odd_leader 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 6 | [#3字下げ][#中見出し]三点リーダー偶数化テスト[#中見出し終わり] 7 | 8 | 9 |  …… 10 |  …… 11 |  ………… 12 |  ………… 13 |  ……………… 14 |  ……………… 15 | 16 |  ‥‥ 17 |  ‥‥ 18 |  ‥‥‥‥ 19 |  ‥‥‥‥ 20 |  ‥‥‥‥‥‥ 21 |  ‥‥‥‥‥‥ 22 | -------------------------------------------------------------------------------- /spec/data/convert_test/to_odd_leader/test_to_odd_leader.txt: -------------------------------------------------------------------------------- 1 | test_to_odd_leader 2 | whiteleaf 3 | 4 | [#改ページ] 5 | 三点リーダー偶数化テスト 6 | 7 | … 8 | …… 9 | ……… 10 | ………… 11 | …………… 12 | ……………… 13 | 14 | 15 | ‥ 16 | ‥‥ 17 | ‥‥‥ 18 | ‥‥‥‥ 19 | ‥‥‥‥‥ 20 | ‥‥‥‥‥‥ 21 | 22 | -------------------------------------------------------------------------------- /spec/data/html_test.html: -------------------------------------------------------------------------------- 1 | 《今日の晩御飯》
2 | 昨日の晩御飯(ばんごはん)はとても美味しかった。
3 | でも今日のご飯はもっと美味しいに違いない美味しくなかった。
4 | 挿絵: 5 | -------------------------------------------------------------------------------- /spec/data/html_test.txt: -------------------------------------------------------------------------------- 1 | ≪今日の晩御飯≫ 2 | 昨日の|晩御飯《ばんごはん》はとても美味しかった。 3 | [#太字]でも[#太字終わり]、[#斜体]今日のご飯[#斜体終わり]はもっと[#取消線]美味しいに違いない[#取消線終わり]美味しくなかった。 4 | 挿絵:[#挿絵(./images/100.jpg)入る] -------------------------------------------------------------------------------- /spec/data/test.ini: -------------------------------------------------------------------------------- 1 | ; comments 2 | global_string = 文字列 3 | 4 | [sub_section] 5 | true_value = true 6 | false_value = false 7 | nil_value_1 = nil 8 | nil_value_2 = 9 | number = 100 10 | quote_string = "囲まれた文字列" 11 | -------------------------------------------------------------------------------- /spec/device_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- Encoding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | require_relative "../lib/device" 7 | 8 | if Helper.os_windows? 9 | describe "Windowsの場合" do 10 | describe Device::Library::Windows do 11 | it "Windows7_OS または Windows なボリュームネームのドライブは C:/" do 12 | module Dummy2 13 | extend Device::Library::Windows 14 | end 15 | device_root_dir = Dummy2.get_device_root_dir("Windows7_OS") 16 | device_root_dir ||= Dummy2.get_device_root_dir("Windows10_OS") 17 | device_root_dir ||= Dummy2.get_device_root_dir("Windows") 18 | expect(device_root_dir).to eq("C:/") 19 | end 20 | end 21 | 22 | describe "Kindle" do 23 | before do 24 | @device = Device.create("kindle") 25 | end 26 | 27 | it "は存在するべき" do 28 | expect(Device.exists?("kindle")).to be_truthy 29 | end 30 | end 31 | 32 | describe "Kobo" do 33 | before do 34 | @device = Device.create("kobo") 35 | end 36 | 37 | it "は存在するべき" do 38 | expect(Device.exists?("kobo")).to be_truthy 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/downloader_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- Encoding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | require_relative "../lib/downloader" 7 | 8 | describe Downloader do 9 | describe ".create_subdirecotry_name" do 10 | context "小説家になろうのタイトルが渡された場合" do 11 | it do 12 | expect(Downloader.create_subdirecotry_name("n9669bk 無職転生 - 異世界行ったら本気だす -")).to eq "96" 13 | end 14 | 15 | it do 16 | expect(Downloader.create_subdirecotry_name("n8725k ログ・ホライズン")).to eq "87" 17 | end 18 | end 19 | 20 | context "なろう以外のタイトルが渡された場合" do 21 | it do 22 | expect(Downloader.create_subdirecotry_name("15041 とある能力の代償")).to eq "15" 23 | end 24 | 25 | it do 26 | expect(Downloader.create_subdirecotry_name("40151 異界渡りの魔法使い")).to eq "40" 27 | end 28 | end 29 | 30 | context "1桁のID+タイトルを渡された場合" do 31 | it do 32 | expect(Downloader.create_subdirecotry_name("5 魔王の友を持つ魔王")).to eq "5" 33 | end 34 | 35 | it do 36 | expect(Downloader.create_subdirecotry_name("10 ペルソナ4~覚醒のゼロの力~")).to eq "10" 37 | end 38 | end 39 | 40 | context "1文字のタイトルが渡された場合" do 41 | it { expect(Downloader.create_subdirecotry_name("n")).to eq "" } 42 | it { expect(Downloader.create_subdirecotry_name("1")).to eq "1" } 43 | it { expect(Downloader.create_subdirecotry_name("a")).to eq "a" } 44 | end 45 | end 46 | end 47 | 48 | -------------------------------------------------------------------------------- /spec/exit_code_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- Encoding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | require "commandline" 7 | require "narou_logger" 8 | require "database" 9 | require "downloader" 10 | 11 | describe "exit code" do 12 | before do 13 | $stdout.silent = true 14 | end 15 | 16 | after do 17 | $stdout.silent = false 18 | end 19 | 20 | let(:frozen_ids) do 21 | Database.instance.get_object.keys.select { |id| Narou.novel_frozen?(id) } 22 | end 23 | 24 | let(:nonfrozen_ids) do 25 | Database.instance.get_object.keys.reject { |id| Narou.novel_frozen?(id) } 26 | end 27 | 28 | describe "download command" do 29 | describe "return mistook count" do 30 | context "when novel is nothing" do 31 | it { expect(CommandLine.run!(%w(download foo))).to eq 1 } 32 | it { expect(CommandLine.run!(%w(download foo bar))).to eq 2 } 33 | it { expect(CommandLine.run!(%w(download foo bar baz))).to eq 3 } 34 | end 35 | 36 | context "when novel is alrady existed" do 37 | # 事前に凍結されていない小説を3つ用意しておく 38 | it "got 3" do 39 | expect(CommandLine.run!(["download"] + nonfrozen_ids[0, 3])).to eq 3 40 | end 41 | end 42 | 43 | context "when novel is alrady frozen" do 44 | # 事前に凍結済み小説を2つ用意しておく 45 | it "got 2" do 46 | expect(CommandLine.run!(["download"] + frozen_ids[0, 2])).to eq 2 47 | end 48 | end 49 | end 50 | end 51 | 52 | describe "update command" do 53 | describe "return mistook count" do 54 | context "when novel is nothing" do 55 | it { expect(CommandLine.run!(%w(update foo))).to eq 1 } 56 | it { expect(CommandLine.run!(%w(update foo bar))).to eq 2 } 57 | it { expect(CommandLine.run!(%w(update foo bar baz))).to eq 3 } 58 | end 59 | 60 | context "when novel is alrady frozen" do 61 | it "got 2" do 62 | expect(CommandLine.run!(["update"] + frozen_ids[0, 2])).to eq 2 63 | end 64 | end 65 | end 66 | end 67 | end 68 | 69 | -------------------------------------------------------------------------------- /spec/fixtures/.test_dot_narou/README.md: -------------------------------------------------------------------------------- 1 | # テストデータのバージョン管理 2 | テストデータを更新したら fixture_version.txt の中のタイムスタンプを更新する。 3 | -------------------------------------------------------------------------------- /spec/fixtures/.test_dot_narou/alias.yaml: -------------------------------------------------------------------------------- 1 | --- {} 2 | -------------------------------------------------------------------------------- /spec/fixtures/.test_dot_narou/fixture_version.txt: -------------------------------------------------------------------------------- 1 | 2019-08-30 19:34:00 +0900 2 | -------------------------------------------------------------------------------- /spec/fixtures/.test_dot_narou/freeze.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 22: true 3 | 29: true 4 | 118: true 5 | -------------------------------------------------------------------------------- /spec/fixtures/.test_dot_narou/latest_convert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | id: 107 3 | -------------------------------------------------------------------------------- /spec/fixtures/.test_dot_narou/local_setting.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | convert.no-open: true 3 | convert.no-epub: false 4 | default.enable_half_indent_bracket: true 5 | convert.copy-to-grouping: true 6 | hotentry.auto-mail: false 7 | update.strong: true 8 | update.logging: false 9 | update.convert-only-new-arrival: false 10 | convert.no-mobi: false 11 | convert.no-strip: false 12 | convert.no-zip: false 13 | convert.inspect: false 14 | download.use-subdirectory: false 15 | send.without-freeze: false 16 | send.backup-bookmark: false 17 | default.enable_double_dash_to_image: true 18 | guard-spoiler: false 19 | download.wait-steps: 30 20 | convert.filename-to-ncode: false 21 | default.author_comment_style: simple 22 | device: kindle 23 | update.interval: 2.5 24 | download.interval: 0.5 25 | filename-length-limit: 100 26 | -------------------------------------------------------------------------------- /spec/fixtures/.test_dot_narou/tag_colors.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | end: red 3 | -------------------------------------------------------------------------------- /spec/fixtures/.test_novel_data/ノクターン・ムーンライト/n1610bw 私、結婚しました!/toc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | title: "私、結婚しました!" 3 | author: "椙下 裕" 4 | toc_url: http://novel18.syosetu.com/n1610bw/ 5 | story: |- 6 | お見合い結婚しました。ひゃっほう旦那様イケメン!素敵過ぎて生きるのつらい!そんな主人公の新婚生活 7 |   ※26/5/17 書籍化の話が進行中の為本編部分を削除しました。現在は番外編置き場と成り果てています。 8 | subtitles: 9 | - index: '1' 10 | href: "/n1610bw/1/" 11 | chapter: '' 12 | subchapter: '' 13 | subtitle: "報告" 14 | file_subtitle: "報告" 15 | subdate: 2013年 11月 17日 16 | subupdate: 2014年 06月 02日 17 | download_time: 2016-07-01 12:50:55.941354000 +09:00 18 | - index: '2' 19 | href: "/n1610bw/2/" 20 | chapter: '' 21 | subchapter: '' 22 | subtitle: "【番外編】家族で楽しく" 23 | file_subtitle: "【番外編】家族で楽しく" 24 | subdate: 2014年 03月 30日 25 | subupdate: 2014年 05月 17日 26 | download_time: 2016-07-01 12:50:56.724788000 +09:00 27 | - index: '3' 28 | href: "/n1610bw/3/" 29 | chapter: '' 30 | subchapter: '' 31 | subtitle: "2" 32 | file_subtitle: "2" 33 | subdate: 2014年 04月 02日 34 | subupdate: 2014年 05月 17日 35 | download_time: 2016-07-01 12:50:57.509449000 +09:00 36 | - index: '4' 37 | href: "/n1610bw/4/" 38 | chapter: '' 39 | subchapter: '' 40 | subtitle: "3" 41 | file_subtitle: "3" 42 | subdate: 2014年 04月 20日 43 | subupdate: 2014年 05月 17日 44 | download_time: 2016-07-01 12:50:58.107160000 +09:00 45 | - index: '5' 46 | href: "/n1610bw/5/" 47 | chapter: '' 48 | subchapter: '' 49 | subtitle: "人生ゲーム" 50 | file_subtitle: "人生ゲーム" 51 | subdate: 2014年 06月 17日 52 | subupdate: 2014年 06月 18日 53 | download_time: 2016-07-01 12:50:58.698568000 +09:00 54 | - index: '6' 55 | href: "/n1610bw/6/" 56 | chapter: '' 57 | subchapter: '' 58 | subtitle: "オレ、結婚します!" 59 | file_subtitle: "オレ、結婚します!" 60 | subdate: 2015年 07月 19日 61 | subupdate: '' 62 | download_time: 2016-07-01 12:50:59.299159000 +09:00 63 | - index: '7' 64 | href: "/n1610bw/7/" 65 | chapter: '' 66 | subchapter: '' 67 | subtitle: "旅行のすすめ" 68 | file_subtitle: "旅行のすすめ" 69 | subdate: 2016年 02月 25日 70 | subupdate: '' 71 | download_time: 2016-07-01 12:50:59.902437000 +09:00 72 | -------------------------------------------------------------------------------- /spec/generator/num_to_kanji_test_gen.rb: -------------------------------------------------------------------------------- 1 | # -*- Encoding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | #require "pry" 7 | 8 | require_relative "../../lib/converterbase" 9 | 10 | converter = ConverterBase.new(nil, nil, nil) 11 | 12 | 11111.times do |i| 13 | str = i.to_s 14 | converter.num_to_kanji(str) 15 | converter.convert_kanji_num_with_unit(str) 16 | puts 17 | puts " it \"#{str}\" do".encode("sjis") 18 | puts " compare_kanji_and_integer(\"#{str}\", #{i})".encode("sjis") 19 | puts " end" 20 | end 21 | 22 | __END__ 23 | 24 | サンプル 25 | 26 | it "百十" do 27 | compare_kanji_and_integer("百十", 110) 28 | end 29 | 30 | it "二百十" do 31 | compare_kanji_and_integer("二百十", 210) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/input_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- Encoding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | require "input" 7 | require "narou_logger" 8 | 9 | describe Narou::Input do 10 | before :all do 11 | $stdout.silent = true 12 | end 13 | 14 | after :all do 15 | $stdout.silent = false 16 | end 17 | 18 | describe ".confirm" do 19 | it "y の時 true を返すべき" do 20 | $stdin = double("$stdin yes", getch: "y", tty?: true) 21 | expect(Narou::Input.confirm("")).to eq true 22 | end 23 | 24 | it "Y の時 true を返すべき" do 25 | $stdin = double("$stdin yes", getch: "Y", tty?: true) 26 | expect(Narou::Input.confirm("")).to eq true 27 | end 28 | 29 | it "n の時 false を返すべき" do 30 | $stdin = double("$stdin no", getch: "n", tty?: true) 31 | expect(Narou::Input.confirm("")).to eq false 32 | end 33 | 34 | it "N の時 fale を返すべき" do 35 | $stdin = double("$stdin no", getch: "N", tty?: true) 36 | expect(Narou::Input.confirm("")).to eq false 37 | end 38 | 39 | it "enter をおした時 false を返すべき" do 40 | $stdin = double("$stdin enter", getch: "\n", tty?: true) 41 | expect(Narou::Input.confirm("")).to eq false 42 | end 43 | 44 | it "pipe で接続された時 true を返すべき" do 45 | $stdin = double("$stdin nontty", tty?: false) 46 | expect(Narou::Input.confirm("")).to eq true 47 | end 48 | end 49 | 50 | describe ".choose" do 51 | before do 52 | @choices = { "japanese" => "日本語", "english" => "English", default: "japanese" } 53 | end 54 | 55 | it "japanese を入力された時 japanese を返すべき" do 56 | $stdin = double("$stdin japanese", gets: "japanese\n", tty?: true) 57 | expect(Narou::Input.choose("", "", @choices)).to eq "japanese" 58 | end 59 | 60 | it "JAPANESE を入力された時 japanese を返すべき" do 61 | $stdin = double("$stdin JAPANESE", gets: "JAPANESE\n", tty?: true) 62 | expect(Narou::Input.choose("", "", @choices)).to eq "japanese" 63 | end 64 | 65 | it "English を入力された時 english を返すべき" do 66 | $stdin = double("$stdin English", gets: "English\n", tty?: true) 67 | expect(Narou::Input.choose("", "", @choices)).to eq "english" 68 | end 69 | 70 | it "pipe で接続された時 japanese を返すべき" do 71 | $stdin = double("$stdin nontty", tty?: false) 72 | expect(Narou::Input.choose("", "", @choices)).to eq "japanese" 73 | end 74 | end 75 | end 76 | 77 | -------------------------------------------------------------------------------- /spec/narou_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | require "narou" 8 | 9 | describe Narou do 10 | before :each do 11 | Narou.flush_cache 12 | end 13 | 14 | describe ".last_commit_year" do 15 | it "should be commited year" do 16 | expect(Narou.last_commit_year).to eq Time.now.year 17 | end 18 | end 19 | 20 | describe ".global_setting_dir" do 21 | before :all do 22 | @original_name = Narou::GLOBAL_SETTING_DIR_NAME 23 | Narou.const_replace :GLOBAL_SETTING_DIR_NAME, ".narousetting_dummy" 24 | @global_dir_in_root = Pathname(".narousetting_dummy").expand_path(Narou.root_dir) 25 | end 26 | 27 | after :all do 28 | Narou.const_replace :GLOBAL_SETTING_DIR_NAME, @original_name 29 | end 30 | 31 | after do 32 | Dir.rmdir(Narou.global_setting_dir) 33 | end 34 | 35 | context ".narou があるディレクトリにはない場合" do 36 | it "ユーザーディレクトリにあるべき" do 37 | expect(Narou.global_setting_dir).to eq Pathname(".narousetting_dummy").expand_path("~") 38 | end 39 | end 40 | 41 | context ".narou があるディレクトリと同じ場所にある場合" do 42 | before do 43 | FileUtils.mkdir(@global_dir_in_root) 44 | end 45 | 46 | it "同じディレクトリにあるほうが優先されるべき" do 47 | expect(Narou.global_setting_dir).to eq @global_dir_in_root 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/novelinfo_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- Encoding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | require_relative "../lib/novelinfo" 7 | 8 | xdescribe NovelInfo do 9 | before do 10 | setting = { 11 | "name" => "dummy", "ncode" => "10000" 12 | } 13 | @novelinfo = NovelInfo.new(setting) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/novelsetting_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- Encoding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | require "tmpdir" 7 | require_relative "../lib/inventory" 8 | require_relative "../lib/novelsetting" 9 | 10 | describe NovelSetting do 11 | context "setting.ini 書き出し関係" do 12 | before do 13 | @tmpdir = Dir.mktmpdir 14 | @novel_setting = NovelSetting.new(@tmpdir, true, true) 15 | @inipath = File.join(@tmpdir, NovelSetting::INI_NAME) 16 | end 17 | 18 | after do 19 | FileUtils.remove_entry_secure @tmpdir 20 | end 21 | 22 | it "読み込んだ設定を setting.ini に書き出せるか" do 23 | @novel_setting.save_settings 24 | expect(File.exist?(@inipath)).to be_truthy 25 | end 26 | 27 | it "オリジナル設定も setting.ini に書きだされるか" do 28 | @novel_setting["original"] = "hoge" 29 | @novel_setting.save_settings 30 | lines = File.read(@inipath).split("\n") 31 | expect(lines.last).to eq 'original = "hoge"' 32 | end 33 | end 34 | end 35 | 36 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "pry" 3 | require "simplecov" 4 | SimpleCov.start do 5 | add_filter "/spec/" 6 | end 7 | 8 | Encoding.default_external = Encoding::UTF_8 9 | ENV["NAROU_ENV"] ||= "test" 10 | # 環境に依存しないようにタイムゾーンを固定してテストする 11 | ENV["TZ"] = "Asia/Tokyo" 12 | 13 | $LOAD_PATH.unshift File.expand_path("lib", __dir__) 14 | 15 | Dir[File.expand_path("support/**/*.rb", __dir__)].each do |f| 16 | require f 17 | end 18 | 19 | RSpec.configure do |config| 20 | config.expect_with :rspec do |expectations| 21 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 22 | end 23 | end 24 | 25 | class String 26 | def inspect 27 | self.to_s 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/support/init_fixtures.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright 2013 whiteleaf. All rights reserved. 5 | # 6 | 7 | # 8 | # テスト用にデータベース等を設定する 9 | # 10 | 11 | require "time" 12 | require "narou" 13 | require "database" 14 | 15 | class Module 16 | def const_replace(name, value) 17 | remove_const(name) 18 | const_set(name, value) 19 | end 20 | end 21 | 22 | Narou.const_replace :LOCAL_SETTING_DIR_NAME, ".test_dot_narou" 23 | Narou.const_replace :GLOBAL_SETTING_DIR_NAME, ".test_dot_narousetting" 24 | Database.const_replace :ARCHIVE_ROOT_DIR_PATH, ".test_novel_data/" 25 | 26 | Narou.flush_cache 27 | 28 | def install_fixtures 29 | root_dir = File.expand_path("../..", __dir__) 30 | local_dir = File.join(root_dir, ".test_dot_narou") 31 | _global_dir = File.join(root_dir, ".test_dot_narousetting") 32 | fixture_narou = File.expand_path("../fixtures/.test_dot_narou", __dir__) 33 | fixture_novel_data = File.expand_path("../fixtures/.test_novel_data", __dir__) 34 | novel_data_dir = File.join(root_dir, ".test_novel_data") 35 | 36 | if File.directory?(local_dir) 37 | version = Time.parse(File.read(File.join(local_dir, "fixture_version.txt"))) 38 | fixture_version = Time.parse(File.read(File.join(fixture_narou, "fixture_version.txt"))) 39 | return if version == fixture_version 40 | FileUtils.rm_r(local_dir, force: true) 41 | FileUtils.rm_r(novel_data_dir, force: true) 42 | end 43 | FileUtils.cp_r(fixture_narou, root_dir) 44 | FileUtils.cp_r(fixture_novel_data, root_dir) 45 | puts "== Copied fixtures version #{fixture_version}" 46 | end 47 | 48 | install_fixtures 49 | -------------------------------------------------------------------------------- /spec/web_worker_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- Encoding: utf-8 -*- 2 | # 3 | # Copyright 2013 whiteleaf. All rights reserved. 4 | # 5 | 6 | module Narou end 7 | require_relative "../lib/web/web_worker" 8 | 9 | describe Narou::WebWorker do 10 | before :all do 11 | @worker = Narou::WebWorker.instance 12 | end 13 | 14 | it "instance is a singleton object" do 15 | expect(@worker).to be Narou::WebWorker.instance 16 | end 17 | 18 | context "not running yet" do 19 | it do 20 | expect(@worker.running?).to be false 21 | end 22 | end 23 | 24 | context "already running" do 25 | before do 26 | @worker.start 27 | end 28 | 29 | it do 30 | expect(@worker.running?).to be true 31 | end 32 | 33 | xcontext "when pushing blocks" do 34 | before do 35 | @result1 = false 36 | @result2 = false 37 | block1 = -> { @result1 = true } 38 | block2 = -> { @result2 = true } 39 | @worker.push(&block1) 40 | @worker.push(&block2) 41 | end 42 | 43 | it "should call blocks", retry: 5, retry_wait: 5 do 44 | # 確実にWebWorkerスレッドが実行されるように 45 | Thread.pass 46 | sleep 0.001 47 | expect(@result1).to be true 48 | expect(@result2).to be true 49 | end 50 | end 51 | 52 | xit "canceling", retry: 5, retry_wait: 5 do 53 | @worker.stop 54 | expect(@worker.canceled?).to be_falsey 55 | _execute = false 56 | expect(@worker.size).to be 0 57 | @worker.push(&->{ _execute = true }) 58 | expect(@worker.size).to be 1 59 | @worker.cancel 60 | expect(@worker.canceled?).to be_truthy 61 | @worker.start 62 | Thread.pass 63 | sleep 0.1 64 | expect(@worker.running?).to be_truthy 65 | expect(@worker.canceled?).to be_falsey 66 | expect(_execute).to be false 67 | expect(@worker.size).to be 0 68 | end 69 | 70 | it "stoping" do 71 | @worker.stop 72 | expect(@worker.running?).to be false 73 | end 74 | end 75 | end 76 | 77 | -------------------------------------------------------------------------------- /template/converter.rb.erb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | <% Template.target_binary_version(1.0) -%> 3 | 4 | # 詳細については http://bit.ly/1vTEH04 を参照して下さい 5 | converter do 6 | # 各種変換処理がされる「前」の生データに対しての変換処理を記述 7 | def before(io, text_type) 8 | super 9 | io 10 | end 11 | 12 | # 各種変換処理がされた「後」のデータに対しての変換処理を記述 13 | def after(io, text_type) 14 | super 15 | 16 | # サンプル1:ダブルダッシュで囲まれた部分を太字にする 17 | # io.string.gsub!(/^( ――.+?――)$/, "[#太字]\\1[#太字終わり]") 18 | 19 | # サンプル2:漢数字化したけど、特定の部分はアラビア数字にしたい場合 20 | # io.string.gsub!(/([〇一二三四五六七八九]+)メートル/) do |match| 21 | # match.tr("〇一二三四五六七八九", "0123456789") 22 | # end 23 | 24 | # サンプル3:最新話以外の挿絵を削除する 25 | # if @current_index < @subtitles.size - 1 26 | # io.string.gsub!(/[#挿絵(.+?)入る]/, "") 27 | # end 28 | 29 | io 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /template/diff.txt.erb: -------------------------------------------------------------------------------- 1 | <%# -*- coding: utf-8 -*- -%> 2 | <% Template.target_binary_version(1.0) -%> 3 | <%= novel_info["title"] %> 4 | 5 | (この差分データに含まれる話数一覧) 6 | <% sections.each_with_index do |section, i| -%> 7 | <% if section["chapter"] != "" -%> 8 | <%= section["chapter"] %> 9 | <% end -%> 10 | ・<%= section["subtitle"].rstrip %> <% if !section["subupdate"] || section["subupdate"] == "" -%> 11 | <%= section["subdate"] %> 12 | <% else -%> 13 | <%= section["subupdate"] %> 14 | <% end -%> 15 | <% end -%> 16 | ――――――――――――――――――――――――――――― 17 | 18 | <% sections.each do |section| -%> 19 |    <%= section["subtitle"].rstrip %> 20 | <% if section["element"]["introduction"] != "" -%> 21 | (前書き) 22 | <%= section["element"]["introduction"] %> 23 | ********************************************************** 24 | <% end -%> 25 | 26 | <%= section["element"]["body"] %> 27 | <% if section["element"]["postscript"] != "" -%> 28 | ********************************************************** 29 | (後書き) 30 | <%= section["element"]["postscript"] %> 31 | <% end -%> 32 | ――――――――――――――――――――――――――――― 33 | 34 | <% end -%> 35 | -------------------------------------------------------------------------------- /template/hotentry.txt.erb: -------------------------------------------------------------------------------- 1 | <%# -*- coding: utf-8 -*- -%> 2 | <% Template.target_binary_version(1.0) -%> 3 | <%= hotentry_title %> 4 | Narou.rb 5 | 6 | [#改ページ] 7 | このデータは<%= Time.now.strftime("%y年%m月%d日 %H時%M分") %>頃作成されました。 8 | 9 | ■収録作品一覧 10 | <% converted_text_array.each do |entry| -%> 11 | [#1字下げ]<%= entry[:setting].title %> 12 | <% end -%> 13 | 14 | <% converted_text_array.each do |entry| -%> 15 | <%- setting = entry[:setting] -%> 16 | [#改ページ] 17 | [#ページの左右中央] 18 | [#1字下げ][#大見出し]<%= setting.title %>[#大見出し終わり] 19 | [#ここから地付き]<%= setting.author %>[#ここで地付き終わり] 20 | <%= entry[:text] %> 21 | <% end -%> 22 | 23 | [#ここから地付き][#小書き](本を読み終わりました)[#小書き終わり][#ここで地付き終わり] 24 | -------------------------------------------------------------------------------- /template/ibunko_novel.txt.erb: -------------------------------------------------------------------------------- 1 | <%# -*- coding: utf-8 -*- -%> 2 | <% Template.target_binary_version(1.1) -%> 3 | <% unless is_hotentry %> 4 | <%= processed_title %> 5 | <%= toc["author"] %> 6 | <%= cover_chuki %> 7 | ―――――――――――――――――――――――― 8 | あらすじ: 9 | <%= toc["story"] %> 10 | 11 | 掲載ページ: 12 | <%= toc["toc_url"] %> 13 | ―――――――――――――――――――――――― 14 | 15 | <%- end -%> 16 | <% sections.each_with_index do |section, i| -%> 17 | [#改ページ] 18 | <% if section["chapter"] != "" -%> 19 | [#ページの左右中央] 20 | 21 | 22 | [#3字下げ][#大見出し]<%= section["chapter"] %>[#大見出し終わり] 23 | 24 | 25 | [#改ページ] 26 | <% end -%> 27 | <% if section["subchapter"].to_s != "" -%> 28 | [#1字下げ][#1段階大きな文字]<%= section["subchapter"].rstrip %>[#大きな文字終わり] 29 | <% end -%> 30 | 31 | <% if @setting.enable_yokogaki -%> 32 | [#1字下げ]<% else -%> 33 | [#3字下げ]<% end -%> 34 | [#中見出し]<%= section["subtitle"].rstrip %>[#中見出し終わり] 35 | 36 | <% if section["element"]["introduction"] != "" -%> 37 | [#ここから6字下げ] 38 | <%= section["element"]["introduction"] %> 39 | [#ここで字下げ終わり] 40 | <% end -%> 41 | 42 | 43 | <%= section["element"]["body"] %> 44 | 45 | <% if section["element"]["postscript"] != "" -%> 46 | [#ここから6字下げ] 47 | <%= section["element"]["postscript"] %> 48 | [#ここで字下げ終わり] 49 | <% end -%> 50 | <% end -%> 51 | <% if setting.enable_display_end_of_book && !is_hotentry -%> 52 | [#ここから地付き][#小書き](本を読み終わりました)[#小書き終わり][#ここで地付き終わり] 53 | <% end -%> 54 | -------------------------------------------------------------------------------- /template/novel.txt.erb: -------------------------------------------------------------------------------- 1 | <%# -*- coding: utf-8 -*- -%> 2 | <% Template.target_binary_version(1.1) -%> 3 | <% unless is_hotentry -%> 4 | <%= processed_title %> 5 | <%= toc["author"] %> 6 | <%= cover_chuki %> 7 | [#区切り線] 8 | <% if toc["story"] != "" -%> 9 | あらすじ: 10 | <%= toc["story"] %> 11 | 12 | <% end -%> 13 | 掲載ページ: 14 | "><%= toc["toc_url"] %> 15 | [#区切り線] 16 | 17 | <%- end -%> 18 | <% sections.each_with_index do |section, i| -%> 19 | [#改ページ] 20 | <% if section["chapter"] != "" -%> 21 | [#ページの左右中央] 22 | [#ここから柱]<%= toc["title"] %>[#ここで柱終わり] 23 | <% if device && device.ibooks? -%> 24 | <%# ibooks だと左右中央が機能しないため -%> 25 | <%= "\n" * 6 -%> 26 | <% end -%> 27 | [#3字下げ][#大見出し]<%= section["chapter"] %>[#大見出し終わり] 28 | [#改ページ] 29 | <% end -%> 30 | <% if section["subchapter"].to_s != "" -%> 31 | [#1字下げ][#1段階大きな文字]<%= section["subchapter"].rstrip %>[#大きな文字終わり] 32 | <% end -%> 33 | 34 | <% if @setting.enable_yokogaki -%> 35 | [#1字下げ]<% else -%> 36 | [#3字下げ]<% end -%> 37 | [#中見出し]<%= section["subtitle"].rstrip %>[#中見出し終わり] 38 | <% if section["element"]["introduction"] != "" -%> 39 | <% introduction, illust_chuki_array = Helper.extract_illust_chuki(section["element"]["introduction"]) -%> 40 | <% if setting.author_comment_style == "simple" -%> 41 | 42 | [#ここから8字下げ] 43 | [#ここから2段階小さな文字] 44 | <%= introduction %> 45 | [#ここで小さな文字終わり] 46 | [#ここで字下げ終わり] 47 | <% elsif setting.author_comment_style == "plain" -%> 48 | 49 | 50 | <%= introduction %> 51 | 52 | [#区切り線] 53 | 54 | <% else -%> 55 | [#ここから前書き] 56 | <%= introduction %> 57 | [#ここで前書き終わり] 58 | <% end -%> 59 | <% if illust_chuki_array.count > 0 -%> 60 | <%= illust_chuki_array.join("\n") %> 61 | <% end -%> 62 | <% end -%> 63 | 64 | 65 | <%= section["element"]["body"] %> 66 | <% if section["element"]["postscript"] != "" -%> 67 | <% postscript, illust_chuki_array = Helper.extract_illust_chuki(section["element"]["postscript"]) -%> 68 | <% if setting.author_comment_style == "simple" -%> 69 | 70 | [#ここから8字下げ] 71 | [#ここから2段階小さな文字] 72 | <%= postscript %> 73 | [#ここで小さな文字終わり] 74 | [#ここで字下げ終わり] 75 | <% elsif setting.author_comment_style == "plain" -%> 76 | 77 | [#区切り線] 78 | 79 | <%= postscript %> 80 | <% else -%> 81 | [#ここから後書き] 82 | <%= postscript %> 83 | [#ここで後書き終わり] 84 | <% end -%> 85 | <% if illust_chuki_array.count > 0 -%> 86 | <%= illust_chuki_array.join("\n") %> 87 | <% end -%> 88 | <% end -%> 89 | <% end -%> 90 | <% if setting.enable_display_end_of_book && !is_hotentry -%> 91 | 92 | [#ここから地付き][#小書き](本を読み終わりました)[#小書き終わり][#ここで地付き終わり] 93 | <% end -%> 94 | -------------------------------------------------------------------------------- /template/replace.txt.erb: -------------------------------------------------------------------------------- 1 | <%# -*- coding: utf-8 -*- -%> 2 | <% Template.target_binary_version(1.0) -%> 3 | ; 単純置換用ファイル 4 | ; 5 | ; 対象小説情報 6 | ; タイトル: <%= @setting["title"] %> 7 | ; 作者: <%= @setting["author"] %> 8 | ; URL: <%= @setting["toc_url"] %> 9 | ; 10 | ; 書式 11 | ; 置換対象置換文字 12 | ; 13 | ; サンプル 14 | ; 一〇歳 十歳 15 | ; 第一章 [#ゴシック体]第一章[#ゴシック体終わり] 16 | ; 17 | ; 正規表現での置換などは converter.rb で対応して下さい 18 | 19 | -------------------------------------------------------------------------------- /template/setting.ini.erb: -------------------------------------------------------------------------------- 1 | <%# -*- coding: utf-8 -*- -%> 2 | <% Template.target_binary_version(1.2) -%> 3 | ; 小説の変換時の挙動を変更出来ます。 4 | ; 行頭がセミコロンの行はコメントです。 5 | ; 6 | ; 変更したい設定値の行頭のセミコロンを削除し、値を設定して下さい。 7 | ; コメントになったままの行は、各種操作によって設定した値によって最終決定されます。 8 | ; 設定値が決定されるフローは以下の通りです。 9 | ; 10 | ; 1. force.* が設定されているか 11 | ; 2. setting.ini の項目が有効になっているか 12 | ; 3. default.* が設定されているか 13 | ; 4. 上記いずれかにも該当しない場合、Narou.rb のオリジナル設定 14 | 15 | <% original_settings.each do |element| -%> 16 | <% element[:help].split("\n").each do |help_line| -%> 17 | ; <%= help_line %> 18 | <% end -%> 19 | <% name = element[:name] -%> 20 | <% novel_setting_value_is_nil = novel_setting[name].nil? -%> 21 | <% if novel_setting_value_is_nil -%>; <% end -%> 22 | <% if !novel_setting_value_is_nil -%> 23 | <% value = novel_setting[name] -%> 24 | <% elsif default_settings.include?(name) -%> 25 | <% value = default_settings[name] -%> 26 | <% else -%> 27 | <% value = element[:value] -%> 28 | <% end -%> 29 | <%= name %> = <%= element[:value].kind_of?(String) ? %!"#{value}"! : value %> 30 | 31 | <% end -%> 32 | <%# 独自設定の書き込み -%> 33 | <% ini = novel_setting.load_setting_ini["global"].merge(novel_setting.settings) -%> 34 | <% own_setting_keys = ini.keys - original_settings.map { |s| s[:name] } -%> 35 | <% own_setting_keys.each do |key| -%> 36 | <%= key %> = <%= ini[key].kind_of?(String) ? %!"#{ini[key]}"! : ini[key] %> 37 | <% end -%> 38 | -------------------------------------------------------------------------------- /webnovel/syosetu.org.yaml: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # 小説サイト定義 3 | name: &name ハーメルン 4 | domain: syosetu.org 5 | top_url: https?://\\k 6 | url: 7 | - \\k/novel/(?\d+) 8 | - https?://novel.syosetu.org/(?\d+) 9 | encoding: UTF-8 10 | confirm_over18: no 11 | append_title_to_folder_name: yes 12 | title_strip_pattern: null 13 | cookie: over18=off 14 | sitename: *name 15 | version: 1.0 16 | 17 | # ------------------------------------------------------------ 18 | # 目次取得設定 19 | toc_url: https://syosetu.org/novel/\\k/ 20 | subtitles: |- 21 | (?:(?.+?))?  (?.+?)(?.+?)(?:\(\))? 22 | 23 | href: \\k.html 24 | 25 | error_message: 投稿者が削除、もしくは間違ったアドレスを指定しています|この作品は投稿者によって削除されました|この作品は完全非公開設定です|この小説は非公開設定になっています 26 | 27 | # ------------------------------------------------------------ 28 | # 本文取得設定 29 | 30 | body_pattern: |- 31 | .+?

32 | (?.+?) 33 | (?:



(?.+?)
)? 34 | 35 | introduction_pattern:
(?.+)


< 36 | postscript_pattern: null 37 | 38 | illust_current_url: null 39 | illust_grep_pattern: 【挿絵表示】 40 | 41 | # ------------------------------------------------------------ 42 | # 小説情報からパースするための設定 43 | novel_info_url: https://syosetu.org/?mode=ss_detail&nid=\\k 44 | 45 | # タイトル 46 | t: タイトル(?.+?)</a> 47 | 48 | # novel_type 小説種別 49 | nt: 話数</td><td.*?>(?<novel_type>.+?) .+?話</td> 50 | novel_type_string: 51 | 連載(連載中): 1 52 | 連載(未完): 1 53 | 連載(完結): 3 54 | 短編: 2 55 | 56 | # general_all_no 掲載話数 57 | ga: 話数</td><td.*?>.+? (?<general_all_no>\d+)話</td> 58 | 59 | # story あらすじ 60 | s: "あらすじ</td><td.*?>(?<story>.+?)</td>" 61 | 62 | # general_firstup 初回掲載日 63 | gf: 掲載開始</td><td.*?>(?<general_firstup>.+?)</td> 64 | 65 | # novelupdated_at 小説の更新時刻 66 | nu: null 67 | 68 | # general_lastup 最終掲載日 69 | gl: 最新投稿</td><td.*?>(?<general_lastup>.+?)</td> 70 | 71 | # writer 作者名 72 | w: 作者</td><td.*?>(?:<a href=.+?>)?(?<writer>.+?)(?:</a>)?</td> 73 | 74 | # length 文字数 75 | l: 合計文字数</td><td.*?>(?<length>.+?)文字 76 | -------------------------------------------------------------------------------- /webnovel/www.akatsuki-novels.com.yaml: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # 小説サイト定義 3 | name: &name 暁 4 | domain: www.akatsuki-novels.com 5 | top_url: https://\\k<domain> 6 | url: https?://\\k<domain>/stories/index/novel_id\~(?<ncode>\d+) 7 | encoding: UTF-8 8 | confirm_over18: no 9 | append_title_to_folder_name: yes 10 | title_strip_pattern: null 11 | cookie: CakeCookie[ALLOWED_ADULT_NOVEL]=on 12 | sitename: *name 13 | version: 1.0 14 | 15 | # ------------------------------------------------------------ 16 | # 書籍情報取得設定 17 | title: id="LookNovel">(?<title>.+?)</a> 18 | author: 作者:<a href="/users/view/\d+">(?<author>.+?)</a> 19 | story: |- 20 | <div class=" body-x1 body-normal body-w\d+">.+?<div>(?<story>.+?)</div> 21 | 22 | # ------------------------------------------------------------ 23 | # 目次取得設定 24 | toc_url: \\k<top_url>/stories/index/novel_id~\\k<ncode> 25 | subtitles: |- 26 | (?:<tr><td style="border: 0; padding: 0;word-break:break-all;" colspan=\\"2\\"><b>(?<chapter>.+?)</b></td></tr>)*<tr><td>( )?<a href="(?<href>/stories/view/(?<index>\d+)/novel_id\~\d+)">(?<subtitle>.+?)</a> </td><td class="font-s">(?<subupdate>.+?) </td></tr> 27 | 28 | # subdate(初投稿日)がない場合、一番最初のsubupdateで代用する 29 | subdate: no 30 | 31 | error_message: この作品は作者の諸事情により非公開にしています 32 | 33 | # ------------------------------------------------------------ 34 | # 本文取得設定 35 | body_pattern: |- 36 | </h2>(?:<div> </div><div><b>前書き</b></div><div class="body-novel">(?<introduction>.+?) </div><hr width="100%"><div> </div>)?<div class="body-novel">(?<body>.+?) </div>(?:<div> </div><hr width="100%"><div> </div><div><b>後書き</b></div><div class="body-novel">(?<postscript>.+?) </div>)? 37 | 38 | introduction_pattern: null 39 | postscript_pattern: null 40 | 41 | illust_current_url: null 42 | illust_grep_pattern: <img src="(?<src>.+?)" /> 43 | 44 | # ------------------------------------------------------------ 45 | # 小説情報からパースするための設定 46 | novel_info_url: null 47 | -------------------------------------------------------------------------------- /webnovel/www.mai-net.net.yaml: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # 小説サイト定義 3 | name: &name Arcadia 4 | domain: www.mai-net.net 5 | top_url: http://\\k<domain> 6 | category: '' # カテゴリ無しの場合を考慮する 7 | url: 8 | - https?://\\k<domain>/bbs/sst/sst.php\?act=dump&cate=(?<category>.+?)&all=(?<ncode>\d+) 9 | - https?://\\k<domain>/bbs/sst/sst.php\?act=dump&cate=&all=(?<ncode>\d+) 10 | encoding: UTF-8 11 | confirm_over18: no 12 | append_title_to_folder_name: yes 13 | title_strip_pattern: (【.+?】|\(.+?\)|(.+?)) 14 | sitename: *name 15 | version: 1.0 16 | 17 | # ------------------------------------------------------------ 18 | # 書籍情報取得設定 19 | title: <font size=4 color=4444aa>(?<title>.+?)</font> 20 | author: "<tt>Name: (?:(?<author>.+?)◆.+</tt>|(?<author>.+?) </tt>)" 21 | story: "" 22 | 23 | # ------------------------------------------------------------ 24 | # 目次取得設定 25 | toc_url: \\k<top_url>/bbs/sst/sst.php?act=dump&cate=\\k<category>&all=\\k<ncode>&n=0&count=1 26 | chapter: "" 27 | subtitles: |- 28 | <td width="0%" style="font-size:60%">\[(?<index>\d+?)\]</td><td width="0%" style="font-size:60%"><b> 29 | <a href="(?<href>.+?)#kiji">(?<subtitle>.+?)</a></b></td><td width="0%" style="font-size:60%">\[(.+?)\]</td><td width="0%" style="font-size:60%">\((?<subupdate>.+?)\)</td> 30 | 31 | # subdate(初投稿日)がない場合、一番最初のsubupdateで代用する 32 | subdate: no 33 | 34 | error_message: エラー!恐らく存在しないスレッドを指定しています 35 | 36 | # ------------------------------------------------------------ 37 | # 本文取得設定 38 | 39 | body_pattern: <blockquote><div style="line-height:1.5">(?<body>.+?)</div></blockquote> 40 | introduction_pattern: null 41 | postscript_pattern: null 42 | 43 | # ------------------------------------------------------------ 44 | # 小説情報からパースするための設定 45 | novel_info_url: null 46 | --------------------------------------------------------------------------------