エンジニアは、何も書かないのが正解
書き出し
お世話になっております。
ご返信ありがとうございます。
突然のメールで失礼いたします。御社のウェブサイトを拝見し、はじめてメールを差し上げます。
~様からの紹介でメールを差し上げます。
日頃から~をご利用いただき、誠にありがとうございます。
平素は格別のお引き立てをいただき、ありがとうございます。
ご無沙汰しております。
その後、いかがおすごしでしょうか。
立て続けのご連絡で失礼いたします。
書き終わり
どうぞ、よろしくお願い申し上げます。
何卒、よろしくお願い申し上げます。
引き続き、よろしくお願い申し上げます。
今後とも、お力添えのほど、よろしくお願い申し上げます。
勝手なお願いで大変恐縮ですが、よろしくお願い申し上げます。
ご確認のほどよろしくお願いいたします。
お手数ですが、ご返事いただければ幸いでございます。
ご検討のほど、よろしくお願い申し上げます。
お近くにお越しの際はぜひお立ち寄りください。
お会いできることを楽しみにしています。
ご参加をお待ちしております。
取り急ぎ、よろしくお願いいたします。
取り急ぎ、お礼申し上げます。
ご不明な際は、お気軽におっしゃってください。
今から楽しみでございます。
お礼を兼ねて失礼いたします。
なろう作品をKindleで読めるePub形式に変換
つかいかた
1. この記事の下部にあるRubyスクリプトを、任意のディレクトリに"narou2epub.rb"というファイル名で保存する。
2. 以下のようにコマンドラインで実行すると、epubファイルができる。[ncode]には、なろう作品のものを指定する。
$ ruby narou2epub.rb [ncode]
注記:未インストールのときは、事前に一度だけNokogiriとRubyzipのインストールが必要。
$ gem install nokogiri $ gem install rubyzip
3. できたファイルをメールに添付し、Kindle端末のメールアドレスに送信する。
Rubyスクリプト
require 'open-uri' require 'nokogiri' require 'zip' def scrape(url) opt = {} opt['Cookie'] = "over18=no" charset = nil html = open(url, opt) do |io| charset = io.charset io.read end return Nokogiri::HTML.parse(html, nil, charset) end def replace(text) text.gsub!(/<p id="L?\d+">/, '<p>') text.gsub!('<br>', '<br />') text.gsub!(/<img .*?>/, '(画像削除)') text.gsub!(/<a .*?>/, '') text.gsub!("</a>", '') return text end def puts_novel(io, doc) results = doc.xpath('//div[@id="novel_p"]') unless results.length == 0 io.puts replace(results.inner_html) io.puts '<hr />' end results = doc.xpath('//div[@id="novel_honbun"]') io.puts replace(results.inner_html) results = doc.xpath('//div[@id="novel_a"]') unless results.length == 0 io.puts '<hr />' io.puts replace(results.inner_html) end end ncode = (ARGV[0].nil?) ? 'n6316bn' : ARGV[0] doc = scrape('https://ncode.syosetu.com/'+ncode+'/') title = doc.xpath('//p[@class="novel_title"]').inner_text creator = doc.xpath('//div[@class="novel_writername"]').inner_text.gsub(/[\r\n]/,"").gsub(/作者:/,"") chapters= [] episodes = [] results = doc.xpath('//dl[@class="novel_sublist2"]') results.each do |r| chapter_title = r.xpath('preceding-sibling::div[@class="chapter_title"][1]').inner_text subtitle = r.xpath('descendant::a').inner_text link = r.xpath('descendant::a/@href').inner_text chapters.push chapter_title unless chapters.last == chapter_title episodes.push [chapters.length-1, subtitle, link] end io = Zip::OutputStream.open(File.basename(title)+'.epub') io.put_next_entry('mimetype') io.puts 'application/epub+zip' io.put_next_entry('META-INF/container.xml') io.puts <<"EOS" <?xml version="1.0" encoding="UTF-8"?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> <rootfile full-path="content.opf" media-type="application/oebps-package+xml" /> </rootfiles> </container> EOS io.put_next_entry('content.opf') io.puts <<"EOS" <?xml version="1.0" encoding="UTF-8"?> <package version="3.0" xmlns="http://www.idpf.org/2007/opf" xml:lang="ja"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/"> <dc:title>#{title}</dc:title> <dc:creator>#{creator}</dc:creator> <dc:language>ja</dc:language> </metadata> <manifest> <item id="toc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav" /> <item id="title" href="title.xhtml" media-type="application/xhtml+xml" /> <item id="main" href="main.xhtml" media-type="application/xhtml+xml" /> <item id="style" href="style.css" media-type="text/css" /> </manifest> <spine page-progression-direction="rtl"> <itemref idref="title" /> <itemref idref="toc" /> <itemref idref="main" /> </spine> </package> EOS io.put_next_entry('toc.xhtml') io.puts <<"EOS" <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja"> <head> <title>目次</title> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> <nav epub:type="toc"> EOS if chapters.length > 1 io.puts ' <h2>目次</h2>' io.puts ' <ol>' chapters.each_index {|i| io.puts ' <li><a href="main.xhtml#Ch_'+i.to_s+'">'+chapters[i]+'</a></li>'} io.puts ' </ol>' elsif episodes.length > 1 io.puts ' <h2>目次</h2>' io.puts ' <ol>' episodes.each {|e| io.puts ' <li><a href="main.xhtml#Ep'+e[2].gsub("/", "_")+'">'+e[1]+'</a></li>'} io.puts ' </ol>' else io.puts ' <ol hidden="hidden">' io.puts ' <li><a href="main.xhtml">本文</a></li>' io.puts ' </ol>' end io.puts <<"EOS" </nav> </body> </html> EOS io.put_next_entry('style.css') io.puts <<"EOS" html { -webkit-writing-mode: vertical-rl; -epub-writing-mode: vertical-rl; writing-mode: vertical-rl; } nav ol { list-style: none; } EOS io.put_next_entry('title.xhtml') io.puts <<"EOS" <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja"> <head> <title>表題</title> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> <h1>#{title}</h1> #{creator} </body> </html> EOS io.put_next_entry('main.xhtml') io.puts <<"EOS" <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja"> <head> <title>本文</title> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> EOS if episodes.length == 0 io.puts '<section>' puts_novel(io, doc) io.puts '</section>' else temp= nil episodes.each do |e| STDERR.puts e.join(' ') io.puts '<section>' unless temp == e[0] io.puts '<h2 id="Ch_'+e[0].to_s+'">'+chapters[e[0]]+'</h2>' temp = e[0] end io.puts '<h3 id="Ep'+e[2].gsub('/', '_')+'">'+e[1]+'</h3>' puts_novel(io, scrape('https://ncode.syosetu.com'+e[2])) io.puts '</section>' sleep 3 end end io.puts '</body>' io.puts '</html>' io.close
今後の課題
- 画像は表示できない。
キースイッチで、そろそろ動物園ができる。
動物園にしては、パンダが多めである。個人としては、Rhinoceros🦏が登場するのが待ちどおしい。
Linear
- | Name | Actuation | Bottom-out |
---|---|---|---|
🐬 | Durock Silent Linear Dolphin | - | 62g |
🦛 | Kinetic Labs Hippo | - | 63.5g |
🐕 | Kinetic Labs Husky | - | 63.5g |
🐼 | Invyr Panda | - | 67g |
🦙 | Alpaca | - | 62g |
🦙 | Silent Alpaca | - | 62g |
🪼 | Kailh Jellyfish "Y" | 50±10g | 60g |
🐸 | MOMOKA Frog | 54g | 62g |
🦩 | MOMOKA Flamingo | - | 67g |
🐍 | JWK Moyu Green Snake | - | 65g |
🐈 | Glorious Lynx | - | 60g |
🦦 | Gateron Baby Raccoon | 55±8g | - |
🦄 | Kailh Pink Unicorn | 50g | 63.5g |
🐅 | TTC Tiger | 45g | 55g |
Tactile
- | Name | Actuation | Bottom-out |
---|---|---|---|
🐼 | Drop + Invyr Holy Panda | - | 67g |
🐼 | Drop Holy Panda X | - | 60g |
🐼 | Glorious Panda | - | 67g |
🐼 | FEKER Like Holy Panda | 67g | - |
🐼 | TECSEE Purple Panda | 55g | 67g |
🐼 | Outemu Panda | 50g | 65g |
🐼 | TTC Holy Panda | 45±10g | 55±10g |
🐧 | Kinetic Labs Penguin | - | 63.5g |
🐟 | Kinetic Labs Salmon | - | 63.5g |
🦘 | Gateron Baby Kangaroo | - | 59g |
🦐 | Durock Shrimp Silent T1 | - | 67g |
🦈 | MOMOKA Shark | - | - |
🐝 | GamaKay Bumblebee | 45±10g | 65g |
🔥 | GamaKay Phoenix | 40±10g | 45g |
Clicky
- | Name | Actuation | Bottom-out |
---|---|---|---|
🦊 | Chosfox x Kailh Arctic Fox | 52g | 53g |
🪼 | Kailh Jellyfish "X" | 47±15g | - |
🦉 | Kailh White Owl | 46g | 70g |
キーボードのハードウェアマクロでExcelの文字を赤色にする
背景
Excelの利点は、マクロ/関数/入力制限/条件付き書式などの機能が利用できることです。これらは技術分野との相性がよいです。一方で、Wordと違って校閲機能がないため、変更点が可視化しにくいです。
Excelで変更点を記録するには、赤字や取り消し線で記録することになります。この操作はマウスを使うのが一般的ですが、キーボードから手が離れるため、作業効率が落ちます。
ショートカットキーもあるのですが、長いため、現実的ではありません。
赤字 | Alt,H,F,1,↓,↓,↓,↓,↓,↓,↓,←,←,←,←,ENTER |
黒字 | Alt,H,F,1,↓,←,←,←,←,ENTER |
ソフトウェアでマクロを登録するなどの対策も考えられますが、会社支給パソコンへの任意ソフトウェアのインストールは、禁止されています。
対策
Keychron Q2を購入し、ハードウェアマクロを登録することにしました。VIAではなく、Remapをつかっています。
会社支給パソコンは、キーボードが日本語配列であるため、選択肢が限られます。Keychron Q2は数少ない解決策といえます。
65%日本語配列キーボード
なろう作品をSend to Kindleする(ツール不要)
背景
「小説家になろう」の作品をKindleの電子インクペーパーで読みたい。以前は便利なWebサービスがあったが、つかえなくなって久しい。代替ツールはあるが、インストールに手間がかかるようだ。
Kindleは、mobiやepubのような電子書籍専用のファイル形式のほかに、textやhtmlにも対応している。よって、複数話をひとつのhtmlファイルに結合するだけで、Kindleにテキストを表示することはできる。この際、縦書きではなくてもよかろう!挿絵もいらない。
やったこと
$ ruby narou2zip.rb [ncode]
のようにコマンドラインで実行すると、zipファイルができる。このファイルを手動でSend to Kindleすればよい。
ただし、もし未インストールのときは、一度だけ事前にNokogiriとRubyzipのインストールが必要。
$ gem install nokogiri
$ gem install rubyzip
require 'open-uri' require 'nokogiri' require 'zip' def scrape(url) opt = {} opt['Cookie'] = "over18=no" html = open(url, opt) do |io| charset = io.charset io.read end return Nokogiri::HTML.parse(html, nil, charset) end # def puts_novel(io, doc) results = doc.xpath('//div[@id="novel_p"]') unless results.length == 0 io.puts results.inner_html.gsub(/<p id="L?\d+">/, '<p>') io.puts '<hr>' end results = doc.xpath('//div[@id="novel_honbun"]') io.puts results.inner_html.gsub(/<p id="L?\d+">/, '<p>') results = doc.xpath('//div[@id="novel_a"]') unless results.length == 0 io.puts '<hr>' io.puts results.inner_html.gsub(/<p id="L?\d+">/, '<p>') end end # ncode = (ARGV[0].nil?) ? 'n6316bn' : ARGV[0] doc = scrape('https://ncode.syosetu.com/'+ncode+'/') title = doc.xpath('//p[@class="novel_title"]').inner_text writer = doc.xpath('//div[@class="novel_writername"]').inner_text.gsub(/[\r\n]/,"") chapters= [] episodes = [] results = doc.xpath('//dl[@class="novel_sublist2"]') results.each do |r| chapter_title = r.xpath('preceding-sibling::div[@class="chapter_title"][1]').inner_text subtitle = r.xpath('descendant::a').inner_text link = r.xpath('descendant::a/@href').inner_text chapters.push chapter_title unless chapters.last == chapter_title episodes.push [chapters.length-1, subtitle, link] end #io = File.new(File.basename(title) + '.html', 'w') io = Zip::OutputStream.open(ncode+'.zip') io.put_next_entry(File.basename(title) + '.html') io.puts '<!DOCTYPE html>' io.puts '<html>' io.puts '<head>' io.puts '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' io.puts '<title>'+title+'</title>' io.puts '</head>' io.puts '<body>' io.puts '<h1>'+title+'</h1>' io.puts writer if chapters.length > 1 io.puts '<h2>目次</h2>' chapters.each_index {|i| io.puts '<a href="#Ch_'+i.to_s+'">'+chapters[i]+'</a><br>'} elsif episodes.length > 1 io.puts '<h2>目次</h2>' episodes.each {|e| io.puts '<a href="#Ep'+e[2].gsub("/", "_")+'">'+e[1]+'</a><br>'} else io.puts '<hr>' end if episodes.length == 0 puts_novel(io, doc) else temp= nil episodes.each do |e| STDERR.puts e.join(' ') unless temp == e[0] io.puts '<h2 id="Ch_'+e[0].to_s+'">'+chapters[e[0]]+'</h2>' temp = e[0] end io.puts '<h3 id="Ep'+e[2].gsub('/', '_')+'">'+e[1]+'</h3>' puts_novel(io, scrape('https://ncode.syosetu.com'+e[2])) sleep 3 end end io.puts '</body>' io.puts '</html>' io.close
きづいたこと
- 拡張子を".html"に変更するだけでは、html形式として識別されない。「<!DOCTYPE html>」や「<html></html>」などの最低限の構成要素を含めるべき。
- ページ内リンク(<a href="#sample">...</a>と<h2 id="sample">...</h2>のセット)がつかえるので、目次もかんたんに作れる。name属性も可。
- ルビタグ(<ruby>...</ruby>)はつかえる。
- アイテム名には、ファイル名が表示される。titleタグ(<title>...</title>)やdc:titleでは表示されない。
- 著者名は、metaタグ(<meta name="Author" content="...">)やdc:creatorタグでは表示されない。
- 改行コードは、macOS/UNIX(LF)でもOK。
- CSSによる縦書き表示(writing-mode: vertical-rl;や-webkit-writing-mode: vertical-rl;)はできない。
- 挿絵の画像は、表示できない。
- MacOSでは、ファイル名を右クリックして圧縮したものをSend to Kindleすると、Amazonからエラーメールが返ってくる。zipコマンドで圧縮しないとだめ。
- 短編の場合とそうでない場合との両方に対応する必要がある。
- R18作品のときは、Cookieに"over18=yes"を指定する。
つぎやること
著者名が自動で入力できるようにしたい。