仙石浩明の日記

2011年2月14日

Google Voice で電話する 「V字発信」 Perl スクリプトを書いてみた 〜 日本へは 2円/分、米国内は年内無料

2009年に米国内でサービス開始した Google Voice は、 2010年6月から以前のような 「招待制」 ではなくなり、 米国内からであれば設定するだけですぐ利用できるようになった。 近いうちに米国外へも展開すると思われるが、 日本は後回しになりそうな気がする (Android 版 Skype が使えるようになったのも世界中で一番最後だったし)。 米国内限定といっても、 米国内に IP アドレスと電話番号があれば利用できるようなので試しに使ってみた。

米国内 IP アドレスは Linode VPS (Virtual Private Server, 仮想専有サーバ) を契約しているので既に持っている。 この VPS 上で PPTP (Point to Point Tunneling Protocol) デーモンを立ち上げておけば、 Windows 標準機能の 「仮想プライベート ネットワーク」 (VPN) で接続して、 米国内からのアクセスを装うことができる。 WWW アクセスだけなら proxy server を立ち上げておくだけで充分かと思ったが、 proxy 経由のアクセスかどうか Google サーバ側で見ているらしく、 proxy 経由では Google Voice は利用できない。

なお、 いったん Google Voice の設定を済ませて Google Number を確保してしまえば、 米国内国外を問わず任意のマシンから Google Voice を利用可能。 つまり Google Number を確保するときのみ、 米国内の IP アドレスからアクセスする必要がある、 ということ。

米国内の電話番号は、 無料で電話番号を割当ててくれるサービスがあるのでそれを利用する。 私は IPKall を利用した。 こちらも米国内の IP アドレスからアクセスする必要があるが、 Google Voice とは異なり米国内の proxy server を経由するだけで登録できる。 SIP フォン (VoIP 電話) のアドレスと、 メールアドレスを登録すると、 ワシントン州の電話番号を一つ割当て、 それをメールで教えてくれる。 割当てられた電話番号に着呼すると、 登録した SIPフォンへ転送してくれる仕掛け。 ただ、 米国は電話代が安すぎるためかイタ電が多いのがちょっと困りもの。 いたずら電話に困っている人がよほど多いのか、 WhoCallsMe.com, Who Called Us, PhoneOwner.info, white pages, Location Lookup など、 かかってきた電話番号を調べるサービスがいくつもある。

維持費用無しで米国の電話番号を持てるのは大変ありがたいが、 30日間サービスを利用していないと登録が無効になってしまう (IPKall の場合)。

IPKall Signup
**ABSOLUTELY FREE**
Washington state phone number to your IP phone.
...(中略)...
Accounts that are inactive for 30 days will be terminated for non-use.
IPKall Signup から引用

「inactive」 というのが、 (1)通話実績がないことを意味するのか、 (2)着呼だけでも 「active」 と見做されるのか、 あるいは (3)登録情報を更新するだけでもよいのかは不明。

仮に (1) だとすると、 毎月日本から国際電話をかけて通話実績を作らなければならず、 とても面倒だしお金もかかる。 そこで Google Voice を使って通話実績を作ることを考えた。 自動で発呼できれば手間もかからず番号を維持できる。 しかも 2011年中 Google Voice は米国内宛の通話が無料らしいので発呼の実験し放題。

JavaPython などから Google Voice をアクセスする 非公式な Google Voice API が発表されているが、 公式ではないので Google が仕様変更すると使えなくなる恐れあり。 「非公式 API」 のコードを見てみると、 内容的にはとても単純であるように見える。 この程度なら自分で一から書いた方が Google の仕様変更への追随も即座にできるし、 Java や Python よりは Perl で書きたかったというのもあって、 さくっと書いてみた。 わずか 58行、 しかも後述するように follow_meta_redirect のバグ対策で 21行ほど要してるので、 実質だとわずか 27行。

Google Voice Call

Google Voice で通話するには、 いわゆる 「V字発信」 を行なう。 つまり電話をかけようとする自分の側と通話先である相手側との両方に対して Google から電話をかけ、 両者を繋いで通話する。 自分の側の電話はあらかじめ登録してあるものから選ぶ。

Google Number を取得済の場合は、 Google Voice ページにアクセスすると左上のロゴの下に 「Call」 ボタンがあって、 押すと
← のようなウィンドウが現れる。

「Number to call」 に通話先の電話番号、 「Phone to call with」 に登録してある自分の電話のうちどれを選ぶかを指定し、 「Connect」 ボタンを押す。 自分の電話の電話番号の代わりに、 (登録済であれば) Google TalkGizmo5 のアカウントを指定することもできる。

すると、 まず自分の電話 (あるいは Google Talk や Gizmo5) に対して Google から発呼が行なわれる。 電話に出ると、 次に Google は通話先の番号に対して発呼を行なう。 呼び出し中は呼び出し音が自分の電話から聞こえ、 相手が出ればそのまま通話できる。

以上を、 ブラウザで操作する代わりに、 Perl スクリプトから行なえばよい。
まず Google Voice にログインする:

my $mech = WWW::Mechanize->new();
$mech->get("https://www.google.com/voice");
$mech->success || die "Fail to access\n";
my $res = $mech->submit_form(
    form_id => "gaia_loginform",
    fields => {
	Email => $user,
	Passwd => $pass,
    }
    );
$res->is_success || die "Fail to login\n";

「WWW::Mechanize」 は、 ブラウザでの操作を簡単にスクリプト化できる便利な Perl モジュール。 「ページを開く」 「ボタンを押す」 「リンクをたどる」 等々、 ブラウザ操作をずらずら書くだけで自動化が実現できてしまう。 混雑時は何度もリロードしないとアクセスできないチケット販売サイト等で、 どうしてもチケットを購入したいときに威力を発揮する(うそ)。

最初に "https://www.google.com/voice" をアクセスすると、 Google アカウントのログインページが表示される。 HTML ソースを見ると、

<form id="gaia_loginform"
      action="https://www.google.com/accounts/ServiceLoginAuth" method="post"
      onsubmit="return(gaia_onLoginSubmit());" >
	...
<input type="text" name="Email" id="Email"
       size="18" value="" class='gaia le val' />
	...
<input type="password" name="Passwd" id="Passwd"
       size="18" class="gaia le val" />
	...
<input type="submit" class="gaia le button" name="signIn" id="signIn"
       value="Sign in" />
	...
</form>

などとなっているので、 ID が "gaia_loginform" である form について、 「Email」 フィールドに gmail アドレス、 「Passwd」 フィールドにパスワードを入力して、 「Sign in」 ボタンを押せばよいことが分かる。 この操作をそのままスクリプトで記述して、

my $res = $mech->submit_form(
    form_id => "gaia_loginform",
    fields => {
	Email => $user,
	Passwd => $pass,
    }
    );

などと書けばよい。 もちろん変数 「$user」 「$pass」 には、 あらかじめそれぞれ 「gmail アドレス」 「パスワード」 を代入しておく。

大変便利な WWW::Mechanize モジュールだが、 画竜点睛を欠くことに meta refresh に対応していない。 すなわち、 「Sign in」 ボタンを押すと、 Google サーバから次のようなレスポンスが返ってくるが、

<html><head><title>Redirecting</title>
	...
<meta http-equiv="refresh" content="0; url=&#39;http://www.google.co.jp/accounts/SetSID?ssdc=1&amp;sidt=...(中略)...&#39;"></head>
<body ... >
	...
</body></html>

「<meta http-equiv="refresh" content="0; url=...」 タグがあるので、 「url=」 で示された URL へ遷移する必要がある。 ブラウザは自動でこのページ遷移を行なうが、 WWW::Mechanize は遷移してくれないので、 WWW::Mechanize::Plugin::FollowMetaRedirect モジュールの follow_meta_redirect を呼び出す必要がある。

ところが残念なことに follow_meta_redirect にはバグ? があり、 点睛を入れても龍は空へ飛んでいかない。 上記のように URL が 「&#39;」 (「'」 single quote) で囲われていると、 「'」 を含めて URL として扱ってしまう (つまり不正な URL になる) ために、 サーバ側に拒絶される。

仕方ないので、 follow_meta_redirect と同等のことを行なう関数 follow_refresh を、 follow_meta_redirect を真似して書いた:

sub follow_refresh {
    my ($mech) = @_;
    my $content = $mech->content;
    my $p = HTML::TokeParser->new(\$content) || return $content;
    while (my $token = $p->get_token) {
	if ($token->[0] eq 'S' && $token->[1] eq 'meta') {
	    if (defined $token->[2] && ref $token->[2] eq 'HASH') {
		if (defined $token->[2]->{'http-equiv'}
		    && $token->[2]->{'http-equiv'} =~ /^refresh$/io) {
		    if (defined $token->[2]->{'content'}
			&& $token->[2]->{'content'}
			=~ m|^(([0-9]+);\s*)*url\=(\'?)(.+)\3$|io) {
			$mech->get($4);
			return $mech->content;
		    }
		}
	    }
	}
    }
    return $content;
}

URL が 「'」 で囲われているときは、 その 「'」 を取り除いて $mech->get するだけ。 この関数を呼び出すことにより、 上記 meta タグで指定された URL へ遷移できる。

こうして遷移したページには、 以下のような部分がある:

<form action="#" id="gc-search-form" method="post" onsubmit="return false;">
<input name="_rnr_se" type="hidden" value="3W/C7p9ElNW6H+LdP0ndD9EIek4="/>

Google Voice では、 このように隠しフィールド (type="hidden") を使って 「_rnr_se」 の値を引き回しているようだ。 この値は Google Voice で電話をかける際にも必要なので、 保存しておく:

my $content = &follow_refresh($mech);
$content =~ m/\<input\s+name=\"_rnr_se\"[^\>]*\svalue=\"(.*)\"/
    || die "Fail to get _rnr_se\n";
my $rnr_se = $1;

以上で Google Voice へのログインが完了。

次に、 Google Voice ページの 「Connect」 ボタン (前述) を押したとき、 どのようなリクエストが Google サーバへ送られるか調べる。 ブラウザの挙動を模倣する Perl スクリプトを書けば、 ブラウザで操作する代わりに Perl スクリプトで発呼を行なわせることができるはず。

どんなリクエストがサーバへ送られるか調べるには、 Live HTTP headers アドオンが便利 (FireFox の場合)。 実際に調べてみるとこんな感じ:

POST /voice/call/connect/ HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded;charset=utf-8
Referer: https://www.google.com/voice?pli=1
Content-Length: 146
Cookie: (省略)
Pragma: no-cache
Cache-Control: no-cache

outgoingNumber=360469zzzz&forwardingNumber=253802yyyy&subscriberNumber=undefined&phoneType=2&remember=0&_rnr_se=3W%2FC7p9ElNW6H%2BLdP0ndD9EIek4%3D

POST リクエストの本文 (Content) に、 自分の電話番号 「253802yyyy」 (一部伏字、以下同様) と 通話先電話番号 「360469zzzz」 が指定されていることが分かる。 前述した 「_rnr_se」 の値も指定されている。

あとは同じリクエストを送信する Perl スクリプトを書くだけ:

$mech->post("https://www.google.com/voice/call/connect/",
	    Referer => "https://www.google.com/voice/",
	    Content => {
		outgoingNumber   => $call,
		forwardingNumber => $home,
		subscriberNumber => "undefined",
		phoneType        => 2,	# 7:gizmo, 9:gtalk
		remember         => 0,
		_rnr_se          => $rnr_se
	    });
$mech->success || die "Fail to call\n";
Filed under: プログラミングと開発環境 — hiroaki_sengoku @ 09:27

9件のコメント »

  1. とおりすがりの者です。
    V字発信をしなくても、日本で普通に google voice を使っています。
    表示を英語にしているから使えるのかもしれませんが。

    コメント by とおりすがり — 2011年2月15日 @ 08:11

  2. 1のとおりすがりの者です。
    この方によるとやはり英語表示にすると使えるようですね。 http://hardware.blog.so-net.ne.jp/2010-10-16
    ちなみに、番号は自動的に割り振られたようです。

    コメント by とおりすがり — 2011年2月15日 @ 08:17

  3. おっしゃるとおり、Google Number を取得しなくても (英語表示の) Gmail ページで発信はできるのですが、当然ながら着信はできないわけで、「Google Voice を使える」 と言っていいのか微妙であるような…? まあ、(Skypeに比べ)通話料が格安なのでメリットはありますね。

    ちなみに、PCの場合はブラウザに Google Talk Plugin を組み込むことによって発信を実現しているわけですが、Android 端末だと +1-760-537-0537 へ発信しようとするので、日本からだと国際電話料金がかかってしまいます。

    米国以外からモバイルで発信したいとなると、(少なくとも現時点では) V字コールしか方法がないのではないかと思います。

    また、Google Voice の魅力は格安で発信できること以外にも、Google Number にかかってきた電話の転送先を自由に設定できたり、留守電の録音をWebやAndroidアプリでどこからでも聞くことができたり、音声だけでなくSMSも使えるなど、いろいろあります。

    コメント by hiroaki_sengoku — 2011年2月15日 @ 10:17

  4. 仙石様、
    初めまして、

    私もGoogle voiceの番号を取得しようと思い、いろいろと先人のされたことを試してみました。しかし、結果として、いまだ取得できずにいます。

    実は今年、2011年になって、初めてGoogle voiceの存在を知りました。そして、試しに http://www.google.com/voice のURLを開いてみると、以下のような忠告が表示されました。

    Google Voice is not available in your country.

    Thanks for visiting Google Voice. We’re not yet open for users outside the US, but are planning to expand our service to additional countries in the future.

    何回かこのGoogle voiceのアドレスを開いていると、Google voiceのページが開くようになりました!それのページの左上にGoogle voiceの文字、ロゴマークはあります。Call, Text の表示はありません。それは本来ならinboxがメイン(最初の画面)に表示されると思うのですが、Call historyがメインに表示されます。おそらく、GmailからCall phoneを使っての発信の記録だと思いましたし、実際にGmailからCall phoneを使って米国に発信した記録が残るようになりました。

    先日、Google voiceの番号を取得された先人達のように、IPアドレスをHotspot shieldを使って、米国のものとしてGoogle voiceのページを開くと、左側に”Get New Number” の表示が現れました。そして、電話番号取得のプロセスを順に行っていったのですが、最終段階の米国の電話番号を使っての確認の際に私の使っていたIPkall とEkiga phoneの組み合わせた電話がうまく作動せず、結局、このプロセスは失敗に終わったと思慮されます。しかし、私のGmailには以下の内容のメールが届きました。

    おそらく、仙石様もGoogle voice番号を取得された時に同じような内容のメールを受信されたと思います。

    Hi there,

    Thanks for signing up for Google Voice! Your new Google number is (707) 4xx-xxxx.

    (中略)

    You can see all these videos at youtube.com/googlevoice. And for the latest news, check out our blog.

    - The Google Voice Team

    後刻、再度、Google voice番号を取得しようと思い、IPアドレスを操作して、Google voiceのアドレスを開いても、以前のような “Get New Number” の表示も表れなくなってしまいました。

    これは以前のプロセスで番号取得に失敗しているにもかかわらず、番号を与えたとして記録が残っているために、表示が表れないのでしょうか?それとも、ほかに何か要因があるのでしょうか?

    現在、www.Google.com/voiceのURLを開いてみると、やはり、以前と同じCall historyがメインの画面が表示されます。

    ちなみに、私はもう一つ、別のGmailアドレスを持っていますので、試しにこのアドレスでサインインすると私が最初に拒否された

    Google Voice is not available in your country.

    の表示が表れます。IPアドレスを操作しても同様です。

    これはパソコンの問題なのか、それとも何か別の問題なのかも、わかりません。

    何回、試してみても同様です。

    説明が下手で申し訳ありません。

    このようなことを相談できる方がおらずに、悶々としておりました。

    もし、仙石様が何か思いついたり、気がついたことがありましたら、大変申し訳ありませんが、お時間のある時にでも、教えていただけませんでしょうか?

    初めてコメントをするのに、長文の質問となってしまい、大変失礼をしてしまいました。どうか、無礼をお許しください。

    ありがとうございます。

    コメント by Yutaka Shibano — 2011年2月23日 @ 12:28

  5. Google Number の取得に失敗したときも、「Thanks for signing up for Google Voice!」メールは届きますので、Shibano 様の側には特に問題はないと思います。

    「Get New Number」の表示が現れたり、現れなかったり、あるいは 「Google Voice is not available in your country.」と表示されたり、などの症状は私のところでも確認しております。

    試しに Google アカウントを米国 IPアドレスで新規に作り、Google Voice を Sign up しようとしたところ、「Google Voice is not available in your country.」と表示されてしまいました。
    もしかすると、VPN などを利用した米国外からのアクセスを検知するように Google 側で変更が行われたのかもしれません。

    Google がどうやって国判別を行っているか不明なので、なんとも手の打ちようがないのが現状です(データセンタ等に割り当てられているIPアドレスを排除するようになったのかも?)。かくなるうえは米国で Sign up してみるしかないと思っていたところでした (4月末に訪米を予定しています)。

    コメント by hiroaki_sengoku — 2011年2月23日 @ 13:10

  6. 仙石様、

    さっそくのご返信、そして、私の質問にお答えいただき、ありがとうございます。

    そうですか、仙石様のところでも、うまくいかないときがあるとのことで、少し安心しました。

    私がGoogle voice の番号を取得しようとして、最初に挑戦したのは本年1月の21日でした。そのときは”Get a New Number”の表示はありました。が、私のように米国外でGoogle voice番号を取得する者が増えたことにより、おっしゃるようにGoogle側で国判別の方法が変更になったかもしれませんね。

    私もさらに挑戦してみようと思います。また、米国在住の古くからの知人に、アカウント名とパスワードを知らせて依頼することも可能ですが、これはちょっと不安でもありますので、最後の手段にしておきます。仙石様と同じように米国に行けたらよいのですが、先立つものも、時間もないのが現実です。

    もし、私がGoogle voice 番号を取得できたときはご報告させていただきます。

    本日はお忙しい中、ご返信をいただいたことを大変うれしく思っております。

    ありがとうございます。

    コメント by Yutaka Shibano — 2011年2月23日 @ 14:54

  7. GV Dialer というソフトウェアをマーケットからインストールすると、Android上で V字発信できます。海外旅行などでアメリカを離れていてWiFiからGV経由で電話するときに利用しています。(受信はGizmo5経由でsipdroid)
    https://market.android.com/details?id=net.cpedia.gvdialer

    コメント by Tatsuhiko Miyagawa — 2011年3月1日 @ 03:46

  8. Google Voice Dialer は使ったことがないのですが(有料ですし ^^;)、
    Google Voice Callback FREE でもV字発信できるようです。
    Gizmo5 へのコールバックで米国宛の通話が可能でした。Gingerbread から SIPが Android 標準になってますます便利になりました(WiFi のみですが)。

    Google Voice Dialer にできて Google Voice Callback にはできないことがありましたら、教えていただけると幸いです。

    それにしても、もうちょっと本家 Google Voice アプリにも頑張っていただきたいところです。Google がケータイから SIP を使った公衆網への発信を(キャリアに遠慮して)自粛しているような気配を感じてしまう今日この頃…

    Voice アプリの機能向上以外にも、例えば Gizmo5 の New user signup を再開するとか、Google Voice に他 SIP アドレスの登録ができるようにするとか、Google Voice の利便性を向上させる方法はいくつもあるのに、あえてそれをしないというのは、おそらく理由があるのでしょう。

    コメント by hiroaki_sengoku — 2011年3月1日 @ 18:41

  9. 悪い予感は的中というか、Gizmo5がシャットダウンするようです。
    http://www.androidpolice.com/2011/03/04/gizmo5-going-offline-april-3/

    Sipgate とか localphone とかで退避先を探す必要がありそう。何段も転送するよりもSkypeIn で妥協してしまおうかという気にもなってきます。Google Talk のVoIPクライアントを Android でだしてくれれば何の問題もないのですが。。

    コメント by Tatsuhiko Miyagawa — 2011年3月5日 @ 09:43

この投稿へのコメントの RSS フィード。

コメントする