2017-10-05

東京都(府中周辺)から福島駅への行き方

福島駅へ行くのにどのどのような方法があるのだろう。パッと思いつくのは次の方法だ。

  • 新幹線
  • 普通列車
  • 高速バス

新幹線

  • 府中本町5:16→5:46武蔵浦和5:52→6:03大宮6:30→7:39福島
  • 乗り換え2回
  • 2時間23分
  • 8220円(運賃4750円, 自由席特急券3470円)

※NAVITIME調べ(2017年10月 平日)

普通列車

  • 府中本町05:16→05:46武蔵浦和05:52→06:03大宮06:12→07:29宇都宮07:40→08:31黒磯08:54郡山10:08→10:54福島
  • 乗り換え5回
  • 5時間38分(新幹線+3時間15分)
  • 4750円(新幹線-3470円)

※NAVITIME調べ(2017年10月 平日)

高速バス

さくら観光の東京-福島間の高速バスが最安値で3000円らしい。これは相当安いケースで普通なら5000円くらいは見ておいた方が良いが、とりあえずこれにしてみる。

高速バス・夜行バス 東京・東京駅周辺発 福島(中通り・会津・浜通り)・福島駅周辺行 空席検索 | さくら観光

  • 分倍河原15:27→15:51新宿15:56→16:11東京駅16:11→16:19鍛冶橋駐車場16:30→22:15福島駅
  • 乗り換え2回
  • 6時間48分(新幹線+4時間25分)
  • 4180円(電車480円+高速バス3000円)(新幹線-4040円)

正直鍛冶橋駐車場まで行くのはつらいのでバスタ新宿くらいにしたいけれど少し料金が上がってしまう。

まとめ

  自由席新幹線 普通列車(対新幹線差) 最安時高速バス(対新幹線差)
料金 8220円 4750円 (- 3470円) 4180円 (- 4040円)
所要時間 2:23 5:38 (+ 3:15) 6:48 (+ 4:25)
乗り換え回数 2回 5回 2回(ただし徒歩距離あり)

考察

まぁ、普通は新幹線を選ぶよね。

とはいえ車内で時間を潰すネタがあるとか列車の旅を楽しむ心の余裕があるとかであれば普通列車は十分魅力的だと思う。浮いたお金で美味しいものが食べられる。

普通列車が高速バスに対して健闘している。最安値が選べなければ普通列車の方が安く済む場合もある。時間帯を柔軟に選べるのも良い。

高速バスのメリットは料金の割に乗り換え回数が少ないことくらいだろうか。

2017-09-30 ,

org-modeの見出しの色を統一する

org-modeの見出しの色が気に入らなかったので調整しました。

見出しの色は大分前に変更したのですが、使っているうちに常々見づらいような気がしていたのです。

変更前のスクリーンショット:

2017-09-30-orgmode-color-before1.png 2017-09-30-orgmode-color-before2.png

なぜ見づらいのだろうと考えたのですが、見出しの色(色相)が階層のレベルによってバラバラだからなのではないかと思い至りました。特に見出しが青になるとき。現在の設定では本文に青系の色が多用されています。リンク、タイムスタンプ、テーブル、etc……。それに加えて見出しまで青だと青色の意味がなんだかよく分からなくなる気がするのです。レベルの深さはインデントで分かるので色で表現する必要はない気がします。

というわけで、次のようなコードを書きながら調整して試行錯誤した後、第一レベルの色である黄色で統一してみました。(最終的にはcustomize-faceで設定)

(progn
  (set-face-attribute 'org-level-1 nil :foreground "#ffd000" :bold t :height 1.0)
  (set-face-attribute 'org-level-2 nil :foreground "#ffd000" :bold nil :height 1.0)
  (set-face-attribute 'org-level-3 nil :foreground "#ffd000" :bold nil :height 1.0)
  (set-face-attribute 'org-level-4 nil :foreground "#ffd000" :bold nil :height 1.0)
  (set-face-attribute 'org-level-5 nil :foreground "#ffd000" :bold nil :height 1.0)
  (set-face-attribute 'org-level-6 nil :foreground "#ffd000" :bold nil :height 1.0)
  (set-face-attribute 'org-level-7 nil :foreground "#ffd000" :bold nil :height 1.0)
  (set-face-attribute 'org-level-8 nil :foreground "#ffd000" :bold nil :height 1.0))

2017-09-30-orgmode-color-after1.png 2017-09-30-orgmode-color-after2.png

どうでしょうか。黄色は他で使っていない色なので「黄色なら見出し!」とはっきり分かります。

文字の大きさも試しに大きくしてみたのですが、折りたたんだときに文字は小さい方が良いのでやめました。

しばらくこれで使ってみようと思います。

ついでにorg-bulletsも導入してみました。「***」を他の記号文字に変えてくれます。MELPAにパッケージがあるのでそれでインストールしました。デフォルト設定の記号はあまり綺麗に表示されなかったのでUNICODEの中からMEDIUM CIRCLEやMEDIUM SQUAREあたりを設定してみました。

2017-09-29 ,

日本その日その日

最近「日本その日その日」というのを読んでいます。長いしなんだか読みづらいのでまだまだ途中までしか読んでいないのですが。

日本その日その日(JAPAN DAY BY DAY)/エドワード・シルヴェスター・モース(Edward Sylvester Morse),石川欣一訳

書かれていることは、明治初頭にアメリカからやってきた動物学者から見た当時の日本です。当時の人々の暮らし方、文化、風習などが見られます。

私は戦国武将なんかに代表されるような歴史にはあまり興味が湧かないのですが、昔の普通の人々がどう暮らしていたのかというのには少し興味があります。 現代に住んでいる我々はとても豊かで便利なくらしをしていると思うんですよ。 でもその便利さを司る物はここ数十年で急速に発展したものが多く、それが無かった時代にはどうしていたのか私には分からないわけです。

郷土資料館に行くと昔の人々のくらしの一部を垣間見ることが出来て面白いことがあります。 例えば、近所のこの辺りは将軍様の鷹狩りの場所に指定されていて住民たちは次のようなことが禁止されていた……。 なんて書かれていると、「ああ、身分制度の無い平等な世の中に生まれて良かったな~」と思うわけです。 (機械だけでなく制度などもここ数十年で大きく発展したものに含まれますね)

そういえば去年末に立川シネマシティで「この世界の片隅に」というアニメ映画を見に行きました。 私が好きそうな日常物(日々の暮らしの中にあるちょっとした個人的な喜びや悲しみを扱った作品)でとてもよい作品でしたが、そこで目に付いたのも昔の人々のくらしでした。 舞台は80~70年くらい前の広島県ですが、真っ先に目に付くのは服装ですよね。 ご婦人や子供が着物を着てる!! 私など生まれてこの方まともに和服を着たことがないんじゃないでしょうか(七五三? 温泉旅館の簡易浴衣?)。 でも当時はそれが普段着だったんですよね。 思えば母方の曾祖母はいつも着物を着ていた気がします。 いつから日本人は着物を着なくなったんですかね。 明治維新以降、男性の比較的近代的な職業から洋装化が進み、最終的に終戦後一気に洋装化が進んだようですが。 井戸で水をくんでかまどで料理するんですよ。全自動洗濯機も掃除機もないんですよ。どれだけ便利になったのかと。 一方で引き継がれているもの、形を少し変えて残っている物もありますが。

私は昔の日本が(全体的に見て)良かったとか戻りたいとか全く思いませんし、もっともっと発展させようぜと思いますが、それでも失った物はあるよなぁとも思います。

そんな今よりちょっと昔を知ることが出来る作品。

ちなみに、この本を見つけたのは比島投降記 ある新聞記者の見た敗戦/石川欣一を読んで同じ作者の本を探してのことでした。

2017-09-26 ,

ごみ出しのプロセス

ごみ箱の価値はごみを入れられるところにあるのであって、ごみを入れられないごみ箱に価値なんかないんだよ。

これを捨てたいな、と思ったときにごみ箱が一杯だとついつい机の上に放置しがち。後でごちゃごちゃになった机から物を分別して捨てるのは面倒くさい。手に持っていて捨てたいなと思ったときにごみ箱に入れられるようになっていないといけない。

なので、ごみを入れづらくなりそうだなと感じたらすぐに袋を部屋の外へ移して新しい袋へ交換する。詰めればまだ入りそうだなんて思ってはいけない。それは後でも出来る。適切に分類されたごみ袋なら後から一つにまとめるのは簡単だ。ごみの日までにその辺りに余っているごみを詰めて閉じておけばよい。

仕分け→袋の交換→まとめて閉じて出せるようにする→出す

各手順は自然な流れの中になければならない。それぞれの手順にきっかけ(条件、動機、必要性、欲求)と作業を阻害しない工夫が必要だ。

2017-09-26

Emacs 25.2以降でASCIIと日本語のフォントを別々に設定する(一部の記号のフォントが変更できない場合)

最近Emacs 25.3へ変えたら一部の記号(○や※等)が正しく表示されなくなったのですが、結論から言うと use-default-font-for-symbols を使えということでした。

以前次のような設定で解決したと思ったのですが、最近Emacs 25.3へ変えたら一部の記号だけASCII用のフォントで表示されてしまいました。

(progn
  (set-face-attribute 'default nil :family "Inconsolata" :height 120)
  (set-fontset-font nil 'cp932 (font-spec :family "MS Gothic")))

emacs-25ブランチへの最近の変更を見てみましたがそれらしい変更点は見つけられず。元々以前の時も何が原因なのか分からずじまいだったので、たまたまうまく行っていただけだったのかもしれません。

不思議なのは一部の記号(○や※)がASCIIのみに対するフォント設定に影響されてしまうことです。上の設定をした後で各文字でどのフォントが選ばれるか試してみました。

(face-font 'default nil ?○);;=>"-outline-Inconsolata-normal-normal-normal-mono-16-*-*-*-p-*-iso8859-1"
(face-font 'default nil ?×);;=>"-outline-MS ゴシック-normal-normal-normal-mono-16-*-*-*-c-*-iso8859-1"
(face-font 'default nil ?あ);;=>"-outline-MS ゴシック-normal-normal-normal-mono-16-*-*-*-c-*-iso8859-1"

「○△□☆…∵→」あたりの記号でASCII用のフォントが使われてしまいます。describe-charで各文字を確認してみましたが、おかしくなるものに共通するのは「script: Symbol」であることくらいでしょうか。

「○」のフォントを次のようにして強制的に設定してみました。

(set-fontset-font "fontset-default" ?○ (font-spec :family "MS Gothic") nil 'prepend)

describe-fontsetで確認するとちゃんと#x25cbのフォントはMS Gothicになっています。でもなぜかMS Gothicが選ばれません。

仕方ないのでソースコードを追うことに。

  1. face-font 関数はxfaces.cにある。 FACE_FOR_CHAR がcharacterに応じてfaceを割り出している部分っぽい。
  2. FACE_FOR_CHAR はdispextern.hにあって HAVE_WINDOW_SYSTEM ならそのまま face_for_char 関数を呼び出す。
  3. face_for_char 関数はfontset.cにある。んん? 冒頭に use_default_font_for_symbols なんてものがある。同ファイルの上の方で use-default-font-for-symbols という変数を定義している。

use-default-font-for-symbols の説明を読む。

use-default-font-for-symbols is a variable defined in ‘C source code’.

Its value is t

Documentation:

If non-nil, use the default face’s font for symbols and punctuation.

By default, Emacs will try to use the default face’s font for displaying symbol and punctuation characters, disregarding the fontsets, if the default font can display the character. Set this to nil to make Emacs honor the fontsets instead.

「non-nilのとき記号や区切り文字にfaceのデフォルトフォントを使用します」……だって!?

試しに (setq use-default-font-for-symbols nil) にしたら全てが解決しました。なんてこった……。

というわけで最終的に次のようにしたらうまくいきました。

;; デフォルトはASCII用のフォントでなければダメっぽい。
(set-face-attribute 'default nil :family "Inconsolata" :height 120)
;; ASCII以外のUnicodeコードポイント全部を一括で設定する。他国語を使用する人は細かく指定した方が良いかも。
(set-fontset-font nil '(#x80 . #x10ffff) (font-spec :family "MS Gothic"))
;; 記号をデフォルトのフォントにしない。(for Emacs 25.2)
(setq use-default-font-for-symbols nil)

なんでこんなことになってるんでしょうね。一貫性があったfontsetの挙動に対して、一部の文字種を特別扱いする筋の悪いやり方に見えますが。

Emacs25でSymbolをデフォルトフォントで表示するように変更されたみたいなのですが、Emacs 25.2になって従来(Emacs24)と同じ挙動が出来るように use-default-font-for-symbols が追加されたみたいです。

Improve font selection for punctuation and other symbols · emacs-mirror/emacs@e070728
Emacs25でフォントの選択が 改良 されたコミット。
Use another font for some characters
set-fontset-fontが効かなくて困るよという話。
bug#24644: 26.0.50; Emacs 25: set-fontset-font does not take effect with
set-fontset-fontが効かなくて困るよという話。
Allow selection of font for symbols as in Emacs 24.x · emacs-mirror/emacs@4ff4b66
use-default-font-for-symbolsを追加したコミット。
「GNU Emacs 25.2」がリリース | OSDN Magazine
Emacs 25.2の変更点として use-default-font-for-symbols のことに触れている。

大人しく プログラミングのストレス軽減!日本語が使えるコーディングに最適なフォント7選 みたいなプログラム用フォントと日本語フォントを合成したフォントを使った方が良いのかもしれません。

(2021-07-29追記:自分でFontForgeで合成しました)

2017-09-25

ZenFone3 MAXで星空を(万座編)

ちょっと草津白根山に遊びに行ったついでに万座のホテル近くから空を撮影してみました。ZenFone3 MAXをミニ三脚で固定して撮影。ほぼ全てシャッタースピード32秒、ISO1600の設定。後からPhotoshopでレベル補正等あり。

わし座(中央やや上の斜め三つ星が目印)~いて座(中央一番下の南斗六星が目印)方面で天の川がギリギリ映っている気がします。

2017-09-25-aquila-sagittarius.jpg

夏の大三角形(左がアルタイル、右がベガ、上がデネブ)。

2017-09-25-summer-triangle.jpg

カシオペア座(おなじみのW形)。

2017-09-25-cassiopeia.jpg

下にオリオン座、上におうし座(V字を左に倒したようなのが顔)で向かい合っています。左にぎょしゃ座。上の方(おうしの背中)にはすばることプレアデス星団が映ってます。

2017-09-25-orion-taurus-auriga.jpg

秋の四辺形でおなじみのペガスス座。左カドからAの形でアンドロメダ座。

2017-09-25-pegasus.jpg

うお座。上半分くらいの領域に、Vの字を右90度回転したような形がお分かりでしょうか。

2017-09-25-pisces-cetus.jpg

こぐま座(木の少し上に北斗七星を小さくしたようなひしゃく形)、ケフェウス座、りゅう座。

2017-09-25-ursaminor-cepheus-draco.jpg

ホテルの方を撮影するとこんな感じになりました。

2017-09-25-hotel1.jpg 2017-09-25-hotel2.jpg

破線になっているのはおそらく点滅しながら飛んでいた飛行機か何かだと思います。

2017-09-25-airplane.jpg

自分で撮影するとそれが何を映したものなのか知りたくなるので星座を覚えることにつながりますね。

星座を覚えて何の意味があるんですかね。まぁ、意味なんて他の物事との関連性の中にあるものなので組み合わせ次第で無限にあって私たちに分かるのなんてその極々一部なんでしょうけれど。とりあえず星座は星の位置を特定する住所になっているそうですね。全天は星座を元にエリア分けされていて、全ての星はそのどれかに所属することになるのだとか。星の位置なんてコンピュータで扱うなら何らかの座標で表した方が楽なんでしょうけど、人間は星の並びを形で覚えているわけで何座の近くにあると言ってもらえた方が分かりやすいわけです。世界地図上の緯度経度で言われるよりもどの島(大陸)のどの国の近くと言ってもらえた方が分かりやすいのと同じです。要するに地球から見た宇宙の地図を形作る元になっているというわけですね。星座を覚えると宇宙の中での方向感覚が身につきます。公転したり自転したりしている地球の上にいる私たちが、宇宙の中で今どの方向を向いているのかイメージできるようになります。とはいえこの地図には方向はありますが距離がありません。あくまで地球から見た宇宙の図というのがポイントですね。いつの日か人類が恒星間を行き来できる日が来たならば、もっと違う地図が必要になるかもしれません。

スマホのカメラでも30秒開ければ星はそれなりに映ることが分かりました。もっと綺麗に撮れればと思いますが、これ以上は明らかに沼なのでやめておきます。

2017-09-13

ZenFone3 MAXで星空を

少し前にNexus5Xが壊れたのでZenFone3 MAXに変えたのですが、こんな記事を見かけたので試しに撮ってみました。

ZenFone 3のマニュアル撮影、32秒の長時間露光で星空を撮影する方法 : ASUS好きのZenBlog(ゼンブログ)

20170913-zenfone3-star-1.jpg 20170913-zenfone3-star-2.jpg

近所で撮影。シャッター速度32秒、ISOは明るすぎないように適当に調整。5秒のセルフタイマーにして、自転車のスマホホルダーに逆さまに置いて撮影しました。最後に見やすいようにPhotoshopでレベル補正だけ。MAXのカメラはDeluxeほど良くないので一部の設定は適用できません。

案外映りますね。というか目で見るよりよっぽど見えるや(泣)。

両方ともはくちょう座が見えてます。

周囲が少し明るかったので、またどこか山にでも行ったときに試してみたい感じ。

2017-09-10

アナザーエデン(アナデン)

人に勧められてアナザーエデンやってます。つ、つれぇよ……。

コテコテの日本のRPGです。

  • 中二的な恥ずかしいシナリオ
  • ボスの前に延々レベル上げ
  • 単調なコマンド選択式シミュレーション戦闘(オートモードはどこ~)
  • 作業的お使いクエスト

それにガチャの要素が組み合わさって

  • ガチャを無料で引くための石集め

が加わります。

なんでも「コンシューマゲームとスマホゲームの差異を解消する」というのが製作時の主要なテーマだった1ということですから、実際良く作り込まれています。決して面白くないとは思いません。コテコテな日本のRPGなのもきっと意図的なのでしょう。

まぁ、でもつらいね……。

思えばRPGなんて何年ぶりでしょう。久しくプレイしていないと思います。少しずつ気が向いたときに進めていくべきですね。無料で6時間ごとに追加されるアナザーダンジョンのカードキーを消費しようなんて思わない方が良さそう。

2017-09-10

Headless Chromeを試してみる(Node.jsとchrome-remote-interfaceでGoogleログイン)

Chrome 59からヘッドレスモードが標準搭載されたそうです。ヘッドレスモードは、 --headless オプション(現在は --disable-gpu も?)を付けてchromeを起動すると画面無しで起動するというものです。

画面が無いのでどうやって操作するんだという話になるのですが、開発用のリモートAPI(Chrome DevTools Protocol)があるので、それを通して操作することになります。

node.js用のモジュールとして chrome-remote-interface というものがあります。これを使用するとnode.js上のコードからこのリモートAPIを経由してChromeを操作できます。

chrome-remote-interface を使う前にヘッドレスモードでChromeを起動する必要があるのですが、これは "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --headless --disable-gpu --remote-debugging-port=9222 https://www.google.com/ ようにコマンドラインから起動しても良いのですが、 chrome-launcher というnode.jsモジュールもあります。それを使うと次のコードでheadless chromeが起動できます。

const chromeLauncher = require('chrome-launcher');

chromeLauncher.launch({
  startingUrl: "https://google.com",
  chromeFlags: ["--headless", "--disable-gpu"]
}).then(chrome => {
  console.log(`Chrome debugging port running on ${chrome.port}`);
});

chrome-launcher はchromeへのパスを補ってくれるだけでは無く、クリーンなユーザープロファイルを用意するなどの処理も行ってくれます。これは自動テストの時などに便利な場合がありますが、普段使っているプロファイルを元に自動処理をしたい場合には不便かもしれません。一応userDataDirオプションでディレクトリを指定できるようになっています(chromeFlagsに–user-data-dirを直接指定するのは良くないと思われる)。

というわけで、試しにGmailの未読件数を求めるコードを書いてみました。 chrome-launcher でChromeを起動し、 chrome-remote-interface を使用してChromeへアクセスしてGoogleにログインしGmailの未読件数を求めます。GmailなんてAPIを通せば済むと思いますが、何かログインが絡む手頃な例が思いつかなかったので。

//
// chrome-remote-interfaceを使用してGmailの未読件数を求める。
// 2017-09-10
//

const chromeLauncher = require('chrome-launcher');
const CDP = require('chrome-remote-interface');

// 標準入力から文字列を受け付ける。
function prompt(question) {
    return new Promise((resolve, reject) => {
        process.stdout.write(question);
        process.stdin.resume();
        process.stdin.once("data", (data) => {
            resolve(data.toString().trim());
            process.stdin.unref(); //call Socket.unref()
        });
    });
}

// Chromeを起動してdevtoolsのドメインを有効にしてurlを開く。
function launchChrome(url){
    return new Promise((resolve, reject)=>{
        chromeLauncher.launch({
            //startingUrl: "https://google.com",
            //chromeFlags: ["--headless", "--disable-gpu"], // <= ヘッドレスモードにするなら指定する。なぜかuserDataDirが効かない? なぜかcloseChrome()が効かない?
            //userDataDir: "c:\\home\\k-aki\\tmp\\chromeuser" // <= 毎回ログインしたくないなら指定する。
        }).then(chrome => {
            console.log(`Chrome pid=${chrome.pid} port=${chrome.port}`);

            CDP({port: chrome.port}, (devtools) => {
                // ドメインを有効にする
                Promise.all([
                    devtools.Page.enable(),
                    devtools.Runtime.enable()
                ]).then(() => {
                    // ページを移動する。
                    devtools.Page.navigate({url: url});
                    devtools.Page.loadEventFired(() => {
                        resolve({chrome: chrome, devtools:devtools});
                    });
                })
            }).on('error', (err) => {
                console.error('Cannot connect to browser:', err);
                chrome.kill();
                reject();
            });
        });
    });
}

// 全てのタブを閉じます。
// chrome-launcherのkill()だとクッキー等が保存されない場合があるので。
function closeChrome(port){
    CDP.List({port:port}, (err, targets)=>{
        if(!err){
            console.log(targets);
            targets.forEach((target)=>{
                CDP.Close({port:port, id:target.id});
            });
        }
    });
}


// Gmailの未読件数を取得する。
launchChrome("https://mail.google.com/").then((env)=>{
    const {chrome, devtools} = env;

    // devtools
    function eval(expr){
        return devtools.Runtime.evaluate({expression: expr});
    }
    function check(cond){
        return eval("!!(" + cond + ")");
    }
    function dqs(selector){
        return ("document.querySelector(\"" + selector + "\")");
    }
    function waitFor(cond){
        return new Promise((resolve, reject)=>{
            function checkCond(){
                check(cond).then((result)=>{
                    if(result.result.value) {
                        resolve();
                    }
                    else {
                        setTimeout(checkCond, 500);
                    }
                });
            }
            checkCond();
        });
    }

    // Googleログイン(2017-09-10)
    //   ID
    const LOGIN_ID_INPUT = "input#identifierId";
    const LOGIN_ID_NEXT = "#identifierNext";
    //   Password
    const LOGIN_PASS_INPUT = "input[name=password]";
    const LOGIN_PASS_NEXT = "#passwordNext";
    //   Time-based One-time Password
    const LOGIN_TOTP_INPUT = "input#totpPin";
    const LOGIN_TOTP_NEXT = "#totpNext";

    function sendLoginEntry(inputSelector, nextButtonSelector, value){
        const js =
            '(()=>{'+
            '    const input = ' + dqs(inputSelector) + ';'+
            '    if(input){'+
            '        input.value = "' + value + '";'+
            '        ' + dqs(nextButtonSelector) + '.click();'+
            '    }'+
            '})()';
        return devtools.Runtime.evaluate({expression: js});
    }
    function sendUserId(id){
        return sendLoginEntry(LOGIN_ID_INPUT, LOGIN_ID_NEXT, id);
    }
    function sendPassword(password){
        return sendLoginEntry(LOGIN_PASS_INPUT, LOGIN_PASS_NEXT, password);
    }
    function sendTOTP(totp){
        return sendLoginEntry(LOGIN_TOTP_INPUT, LOGIN_TOTP_NEXT, totp);
    }
    function loginGoogle(){
        return new Promise((resolve, reject)=>{
            check(dqs(LOGIN_ID_INPUT)).then((result)=>{
                if(result.result.value){
                    prompt("User ID:")
                        .then((id)=>sendUserId(id))
                        .then(()=>waitFor(dqs(LOGIN_PASS_INPUT)))
                        .then(()=>prompt("Password:"))
                        .then((password)=>sendPassword(password))
                        .then(()=>prompt("One-time Password:")) ///@todo TOTPを要求しない場合でもTOTPを受け付けて送信している。
                        .then((totp)=>sendTOTP(totp))
                        .then(()=>{ resolve();});
                }
                else{
                    //already logged in.
                    resolve();
                }
            });
        });
    }

    // for Gmail
    function waitForGmailOpen(){
        return waitFor('document.title != "Gmail"');
    }
    function getUnreadCount(){
        return new Promise((resolve, reject)=>{
            eval("document.title").then((result)=>{
                const title = result.result.value;
                resolve(/\(([0-9]+)\)/.exec(title)[1]);
            });
        });
    }

    loginGoogle()
        .then(()=>waitForGmailOpen())
        .then(()=>getUnreadCount())
        .then((result)=>{
            // 結果を出力する。
            console.log("unread=" + result);
            // 閉じる。
            devtools.close();
            //chrome.kill();
            closeChrome(chrome.port);
        });

});

ページを開いたときにGoogleのログインフォームが検出されたらログイン処理をします。ユーザー名、パスワード、二段階認証コードをコンソールから受け付けてフォームへ書き込み「次へ」ボタンをクリックしてログインを完了させます。

ログインが済んでいる(済んだ)ならGmailが開くのを待ちます。title要素に未読件数などが表示されるので、これが出るまで一定時間間隔でチェックするようにしました。出たら未読件数の数字部分を取り出して出力します。

Chromeを閉じる処理は最初 chrome-launcher のkill()を使用してみたのですが、2回目以降の起動時にChromeが前回正常に終了しなかったと言われてしまう(クッキーも保存されていない)ので chrome-remote-interface を通して閉じる方法も試しました。

--headless を指定していないときはこれでうまく動いたのですが、指定したら二つ問題がありました。一つは毎回ログインを要求されること。どうもユーザープロファイルをちゃんと読み込んでくれていないようです。ヘッドレスモードはデフォルトでシークレットモードになるらしいことが関係しているのかもしれません。二つ目はChromeが終了しないこと。 chrome-remote-interface を通して全てのタブを閉じるコード(closeChrome)ではなぜか終了しないみたいです。

参考URL:

2017-09-04

この一年で行ったところ

9月になって涼しい日が続いていますね。今年の夏は去年行けなかった分あちこち行こうと思ったのですが、天気の悪い日が多くてそれほど行けませんでした。このまま秋になってしまうのかな。

この1年、去年の秋くらいから行ったところ:

2017-09-04-IMG_20161020_104522.jpg 2017-09-04-IMG_20161031_112229.jpg 2017-09-04-IMG_20170425_110937_0.jpg 2017-09-04-IMG_20170508_114708.jpg 2017-09-04-P_20170605_145354.jpg 2017-09-04-P_20170721_090344.jpg 2017-09-04-P_20170803_120408_HDR.jpg 2017-09-04-P_20170826_122422_HDR.jpg

まだまだ行きたいところがいっぱいあるなぁ。