Perlの最近のブログ記事

Niigatapmtechtalk012012年4月28日に新潟市で開催された Niigata.pm Tech Talk #1 [atnd.org] に参加してきました。

時間が鬼のようにあったので、せっかく近い日時で開催するということで、もともとDTP関連の勉強会で使おうと思って作っておいたプレゼンテーションのシステム(s6.js+Websocket)を使い、急遽Perlネタのプレゼンテーションとして仕立て上げました。

その前に僕のPerlでのスタンス:

  • Perlはグルー、ダクトテープとしての利用
  • 必要なアプリケーションがあるのならばCPANモジュールを探して作ることができる
  • 業務で必要なプログラムを書くので、ほかの人が手を入れることを前提としたシステム構成(マニアックにしない)
  • CGI、mod_perlどまり。永続化させてリバースプロキシでサービスは出来るけれどもやらない(マニアックにしない)
  • DBはMySQLなどの他のプロダクトを使わず、SQLiteを使う。SQLiteで間に合う程度に規模を抑える(メンテナンス性の確保)
  • 開発環境は、Linuxのvimで、入力支援などは一切入れない。(マニアックさを排除)

など、徹底してメンテナンス時に専門知識がいらないようにしていました。それは、僕がコードメンテナンスができなくなったあと、残った人がコードに手を入れる必要が生じたときに、ウェブで情報が検索できる範囲内でなんとか達成できる、という想定をしていたからです。

そのような決して上級者ではないけれども初心者でもない、きっと何者にもなれない僕がNiigata.pmに参加して感じたのは、「マニアックの領域に踏み込んだ人たちの面白さ」と、「プログラマの能力に依存することを許す状況を作り出せるならばHappy」ということでした。

本日をもちまして、会社からもろもろの書類をいただき、無職になりました(正確には明日からです)。

次の職が早く決まるように、自分の意識を高める目的で、無職になってから何日目になるかというのをTwitterプロフィールの名前に設定するスクリプトです。

毎日cronで実行します。

Filename: updateprofile.pl

#!/usr/bin/perl
 
use strict;
use warnings;
use Config::Pit;
use Net::Twitter;
use Time::Piece ();
use utf8;
use YAML;
 
my $p = pit_get( 'twitter.com@CLCLCL' );
my $consumer = YAML::LoadFile('./consumer_keys.yaml');
my $nt = Net::Twitter->new(
      traits          => [qw/API::REST OAuth/],
      consumer_key    => $consumer->{consumer_key},
      consumer_secret => $consumer->{consumer_key_secret},
      ssl => 1,
  );
$nt->access_token       ($p->{access_token});
$nt->access_token_secret($p->{access_token_secret});
 
die "not preset account data in Pit." if !%$p;
 
my $start = Time::Piece->localtime( Time::Piece->strptime('2012-04-20T00:00:00', '%Y-%m-%dT%H:%M:%S') );
my $now   = Time::Piece->localtime();
my $delta = $now - $start;
my $delta_days = int( $delta->days );
 
my $profile;
$profile->{name} = "CL無職${delta_days}日目";
my $res = $nt->update_profile($profile);
print Dump($res);
 
exit;
 
__END__

特に解説するまでもないですけれども、$startが退職日です。Access TokenとかConsumer Keyとかは、あらかじめ取得しておき、前者なら~/.pit/default.yaml、後者なら同じディレクトリのconsumer_keys.yamlとして用意しておくことが必要です。

enjoy!

とある冊子にケータイサイトのURLをQRコードで載せようと思いリンク許可を取ったのは良いけれどもそのサイトはなんとPC版とiモードとezWebとソフトバンクの入口がそれぞれ別URLになっていて4つのQRコードを紙面に乗せるわけにはいかない、どーしよーていう状況をイメージしてこんなのを書いてみました。つまりアクセスしている端末によって表示するリンクを変えてしまうという寸法です。

4年前に同様の問題をやっていることを思い出しコードを掘り起こしてきたのでさほど問題はなかったです。


Filename: index.html

#!/usr/bin/perl
 
use strict;
use warnings;
use utf8;
use CGI;
use Encode;
use Encode::JP::Mobile qw(:props);
use HTML::Template;
use HTTP::MobileAgent;
use HTTP::MobileAgent::Plugin::Charset;
 
my $emoticons = {
  'E63E' => 'sun',
  'E63F' => 'cloud',
  'E640' => 'rain',
  'E641' => 'snow',
  'E642' => 'thunder',
  'E643' => 'typhoon',
  'E644' => 'mist',
  'E645' => 'sprinkle',
  'E646' => 'aries',
  'E647' => 'taurus',
  'E648' => 'gemini',
  'E649' => 'cancer',
  'E64A' => 'leo',
  'E64B' => 'virgo',
  'E64C' => 'libra',
  'E64D' => 'scorpius',
  'E64E' => 'sagittarius',
  'E64F' => 'capricornus',
 
  'E650' => 'aquarius',
  'E651' => 'pisces',
  'E652' => 'sports',
  'E653' => 'baseball',
  'E654' => 'golf',
  'E655' => 'tennis',
  'E656' => 'soccer',
  'E657' => 'ski',
  'E658' => 'basketball',
  'E659' => 'motorsports',
  'E65A' => 'pocketbell',
  'E65B' => 'train',
  'E65C' => 'subway',
  'E65D' => 'bullettrain',
  'E65E' => 'car',
  'E65F' => 'rvcar',
 
  'E660' => 'bus',
  'E661' => 'ship',
  'E662' => 'airplane',
  'E663' => 'house',
  'E664' => 'building',
  'E665' => 'postoffice',
  'E666' => 'hospital',
  'E667' => 'bank',
  'E668' => 'atm',
  'E669' => 'hotel',
  'E66A' => '24hours',
  'E66B' => 'gasstation',
  'E66C' => 'parking',
  'E66D' => 'signaler',
  'E66E' => 'toilet',
  'E66F' => 'restaurant',
 
  'E670' => 'cafe',
  'E671' => 'bar',
  'E672' => 'beer',
  'E673' => 'fastfood',
  'E674' => 'boutique',
  'E675' => 'hairsalon',
  'E676' => 'karaoke',
  'E677' => 'movie',
  'E678' => 'upwardright',
  'E679' => 'carouselpony',
  'E67A' => 'music',
  'E67B' => 'art',
  'E67C' => 'drama',
  'E67D' => 'event',
  'E67E' => 'ticket',
  'E67F' => 'smoking',
 
  'E680' => 'nosmoking',
  'E681' => 'camera',
  'E682' => 'bag',
  'E683' => 'book',
  'E684' => 'ribbon',
  'E685' => 'present',
  'E686' => 'birthday',
  'E687' => 'telephone',
  'E688' => 'mobilephone',
  'E689' => 'memo',
  'E68A' => 'tv',
  'E68B' => 'game',
  'E68C' => 'cd',
  'E68D' => 'heart',
  'E68E' => 'spade',
  'E68F' => 'diamond',
 
  'E690' => 'club',
  'E691' => 'eye',
  'E692' => 'ear',
  'E693' => 'rock',
  'E694' => 'scissors',
  'E695' => 'paper',
  'E696' => 'downwardright',
  'E697' => 'upwardleft',
  'E698' => 'foot',
  'E699' => 'shoe',
  'E69A' => 'eyeglass',
  'E69B' => 'wheelchair',
  'E69C' => 'newmoon',
  'E69D' => 'moon1',
  'E69E' => 'moon2',
  'E69F' => 'moon3',
 
  'E6A0' => 'fullmoon',
  'E6A1' => 'dog',
  'E6A2' => 'cat',
  'E6A3' => 'yacht',
  'E6A4' => 'xmas',
  'E6A5' => 'downwardleft',
 
  'E6AC' => 'slate',
  'E6AD' => 'pouch',
  'E6AE' => 'pen',
 
  'E6B1' => 'shadow',
  'E6B2' => 'chair',
  'E6B3' => 'night',
 
  'E6B7' => 'soon',
  'E6B8' => 'on',
  'E6B9' => 'end',
  'E6BA' => 'clock',
 
  'E6CE' => 'phoneto',
  'E6CF' => 'mailto',
 
  'E6D0' => 'faxto',
  'E6D1' => 'info01',
  'E6D2' => 'info02',
  'E6D3' => 'mail',
  'E6D4' => 'by-d',
  'E6D5' => 'd-point',
  'E6D6' => 'yen',
  'E6D7' => 'free',
  'E6D8' => 'id',
  'E6D9' => 'key',
  'E6DA' => 'enter',
  'E6DB' => 'clear',
  'E6DC' => 'search',
  'E6DD' => 'new',
  'E6DE' => 'flag',
  'E6DF' => 'freedial',
 
  'E6E0' => 'sharp',
  'E6E1' => 'mobaq',
  'E6E2' => 'one',
  'E6E3' => 'two',
  'E6E4' => 'three',
  'E6E5' => 'four',
  'E6E6' => 'five',
  'E6E7' => 'six',
  'E6E8' => 'seven',
  'E6E9' => 'eight',
  'E6EA' => 'nine',
  'E6EB' => 'zero',
  'E6EC' => 'heart01',
  'E6ED' => 'heart02',
  'E6EE' => 'heart03',
  'E6EF' => 'heart04',
 
  'E6F0' => 'happy01',
  'E6F1' => 'angry',
  'E6F2' => 'despair',
  'E6F3' => 'sad',
  'E6F4' => 'wobbly',
  'E6F5' => 'up',
  'E6F6' => 'note',
  'E6F7' => 'spa',
  'E6F8' => 'cute',
  'E6F9' => 'kissmark',
  'E6FA' => 'shine',
  'E6FB' => 'flair',
  'E6FC' => 'annoy',
  'E6FD' => 'punch',
  'E6FE' => 'bomb',
  'E6FF' => 'notes',
 
  'E700' => 'down',
  'E701' => 'sleepy',
  'E702' => 'sign01',
  'E703' => 'sign02',
  'E704' => 'sign03',
  'E705' => 'impact',
  'E706' => 'sweat01',
  'E707' => 'sweat02',
  'E708' => 'dash',
  'E709' => 'sign04',
  'E70A' => 'sign05',
  'E70B' => 'ok',
  'E70C' => 'appli01',
  'E70D' => 'appli02',
  'E70E' => 't-shirt',
  'E70F' => 'moneybag',
 
  'E710' => 'rouge',
  'E711' => 'denim',
  'E712' => 'snowboard',
  'E713' => 'bell',
  'E714' => 'door',
  'E715' => 'dollar',
  'E716' => 'pc',
  'E717' => 'loveletter',
  'E718' => 'wrench',
  'E719' => 'pencil',
  'E71A' => 'crown',
  'E71B' => 'ring',
  'E71C' => 'sandclock',
  'E71D' => 'bicycle',
  'E71E' => 'japanesetea',
  'E71F' => 'watch',
 
  'E720' => 'think',
  'E721' => 'confident',
  'E722' => 'coldsweats01',
  'E723' => 'coldsweats02',
  'E724' => 'pout',
  'E725' => 'gawk',
  'E726' => 'lovely',
  'E727' => 'good',
  'E728' => 'bleah',
  'E729' => 'wink',
  'E72A' => 'happy02',
  'E72B' => 'bearing',
  'E72C' => 'catface',
  'E72D' => 'crying',
  'E72E' => 'weep',
  'E72F' => 'ng',
 
  'E730' => 'clip',
  'E731' => 'copyright',
  'E732' => 'tm',
  'E733' => 'run',
  'E734' => 'secret',
  'E735' => 'recycle',
  'E736' => 'r-mark',
  'E737' => 'danger',
  'E738' => 'ban',
  'E739' => 'empty',
  'E73A' => 'pass',
  'E73B' => 'full',
  'E73C' => 'leftright',
  'E73D' => 'updown',
  'E73E' => 'school',
  'E73F' => 'wave',
 
  'E740' => 'fuji',
  'E741' => 'clover',
  'E742' => 'cherry',
  'E743' => 'tulip',
  'E744' => 'banana',
  'E745' => 'apple',
  'E746' => 'bud',
  'E747' => 'maple',
  'E748' => 'cherryblossom',
  'E749' => 'riceball',
  'E74A' => 'cake',
  'E74B' => 'bottle',
  'E74C' => 'noodle',
  'E74D' => 'bread',
  'E74E' => 'snail',
  'E74F' => 'chick',
 
  'E750' => 'penguin',
  'E751' => 'fish',
  'E752' => 'delicious',
  'E753' => 'smile',
  'E754' => 'horse',
  'E755' => 'pig',
  'E756' => 'wine',
  'E757' => 'shock',
};
 
my $q = CGI->new();
my $agent = HTTP::MobileAgent->new;
 
# テンプレートファイル読み込み
my $template = sub {
  open my $fh, '<', shift or die $!;
  local $/ = undef; my $s = <$fh>;
  close $fh;
  return $s;
}->('index.tmpl');
$template = decode_utf8( $template );
$template =~s|\\x{(.+?)}|chr(hex($1))|eg; # 絵文字記述をUnicode文字列に
 
# キャリア別処理
my $params;  # TemplateにUserAgent(キャリア名)をまとめて渡す
my $carrier; # Templateに渡すキャリア名
if      ( $agent->is_docomo   ) { $carrier  = 'docomo';
} elsif ( $agent->is_ezweb    ) { $carrier  = 'AU';
} elsif ( $agent->is_vodafone ) { $carrier  = 'SoftBank';
} else {
  $carrier  = 'パソコン/スマートフォン';
  $params->{pc} = $carrier;
  $template =~ s{(\p{InMobileJPPictograms})}{
    my $char = Encode::JP::Mobile::Character->from_unicode(ord $1);
    sprintf '<img src="emoticons/%s.gif" />', $emoticons->{$char->unicode_hex};
  }ge;
}
my $encoding = $agent->encoding; # Encode用のキャリアごとのエンコーディング
my $charset  = $encoding =~ /sjis/ ? 'Shift_JIS' : 'UTF-8'; # HTML用のcharset
$params->{ carrier} = $carrier; # <tmpl_var name="carrier">でdocomoとか表示できる
$params->{$carrier} = $carrier; # <tmpl_if name="docomo">でdocomo用処理振り分けできる
$params->{ charset} = $charset; # <meta~charset="<tmpl_var name="charset">" />
# テンプレート処理
my $t = HTML::Template->new(
  scalarref => \$template,
  die_on_bad_params => 0
);
$t->param($params);
my $html = $t->output;
 
# 出力
print $q->header( -type => 'text/html', charset => $charset );
print encode( $encoding, $html );
 
exit;
 
__END__

Filename: index.tmpl

<!DOCTYPE HTML PUBLIC "-//W3C//DTD Compact HTML 1.0 Draft//EN">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset="<tmpl_var name="charset">" />
    <title>エロいリンク</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes," />
  </head>
  <body link="#003399" alink="#ffa500" vlink="#003399">
    <div bgcolor="#7fb5e5">
      <h1 size="4">エロいページ</h1>
      <h2>ケータイ版リンク集</h2>
    </div>
    <hr />
    <div>
      <dl>
        <dt>
          <font color="#0077ff">\x{e6e2}</font
          ><a accesskey="1"
            href="<tmpl_if name="pc"
            >http://example.com/<tmpl_else><tmpl_if name="docomo"
            >http://example.com/i/</tmpl_if><tmpl_if name="au"
            >http://example.com/e/</tmpl_if><tmpl_if name="softbank"
            >http://example.com/j/</tmpl_if></tmpl_if
            >">お世話になったVHS</a>(<tmpl_var name="carrier">)</dt>
        <dd>
          ツメオリのVHS情報がご覧になれます。
        </dd>
    </div>
    <hr />
    <div>
      &copy;DTPWiki 2012
    </div>
  </body>
</html>

こーやって事前に作り置きして備えるんだけれども、使われることってほぼ無いんだよね。

新潟運輸の荷物配達状況を確認するPerlモジュールWebService::NiigataUnyu 0.06の動作がおかしくなりました。原因は、スクレーピング結果のお問い合わせ番号の後ろに空白が付くようになってしまったことにあります。

こういうのはプログラムの世界ではトリム(Trim)とかいって、入力値などの前後の空白を除去する処理をするのですが、やっていなかったのでした。

実際に稼働しているシステムで不具合が出たのですが、当方Perlのパッケージのビルド方法なんて毎回忘れてしまう故、ちょっとパッケージを作ってCPANに上げるだけの余裕がありませんので、取り急ぎパッチを用意しました。

$ diff -c NiigataUnyu.pm.old NiigataUnyu.pm
*** NiigataUnyu.pm.old  2011-04-06 00:31:51.000000000 +0900
--- NiigataUnyu.pm      2012-03-13 23:13:26.000000000 +0900
***************
*** 73,79 ****
      process '//div[3]/div/div/div[2]/div/table',
      'results[]' => scraper {
        process q{//tr/th/font[text() =~ /お問合せ番号/]/../../td},
!       number => 'TEXT',
        process '//tr/th/font[text() =~ /日付/ and @size = 4]/../../td',
        date => [ 'TEXT', sub { s/\s//g; return $_; } ],
        process '//tr/th/font[text() =~ /時間/ and @size = 4]/../../td',
--- 73,79 ----
      process '//div[3]/div/div/div[2]/div/table',
      'results[]' => scraper {
        process q{//tr/th/font[text() =~ /お問合せ番号/]/../../td},
!       number => [ 'TEXT', sub { s/^\s*//; s/\s*$//; return $_; } ],
        process '//tr/th/font[text() =~ /日付/ and @size = 4]/../../td',
        date => [ 'TEXT', sub { s/\s//g; return $_; } ],
        process '//tr/th/font[text() =~ /時間/ and @size = 4]/../../td',

このパッチを有効に使っていただける人がどれだけいるかは謎です。

MySQLのtimestamp形式の日付文字列をPerlで日付として取り扱いたい。いままではDateTimeを使っていたけれども、今後の事を考えるとPerl 5.10で標準となっているTime::Pieceを使うのがよいだろう。ということでテストしてみたが。

テストしたのは、MySQLのtimestamp形式 "2012-03-01 00:00:00"(GMT) を、日本の時刻として読めるISO 8601形式の日付文字列(JST+09:00)として表示させるというもの。

FileName: datetime-timepiece.pl

use strict;
use warnings;
use Time::Piece::MySQL;
use DateTime::Format::MySQL;
 
my $datestrings = '2012-03-01 00:00:00';
 
my $dt = DateTime::Format::MySQL
          ->parse_datetime( $datestrings )
          ->set_time_zone('GMT')
          ->set_time_zone('Asia/Tokyo');
# DateTime::Format::MySQLを使わない場合は、
#use DateTime::Format::Strptime;
#my $strp = DateTime::Format::Strptime->new(pattern => '%F %T',time_zone => 'GMT');
#my $dt = $strp->parse_datetime( $datestrings )->set_time_zone('Asia/Tokyo');
 
my $tp = localtime Time::Piece->from_mysql_timestamp( $datestrings )->epoch ;
# Time::Piece::MySQLを使わない場合は、
#my $tp = localtime Time::Piece->strptime( $datestrings, '%Y-%m-%d %H:%M:%S')->epoch ;
 
print "DateTime   > $dt\n";
print "Time::Piece> ".$tp->datetime."\n";
 
__END___

実行結果:

$ perl datetime-timepiece.pl
DateTime   > 2012-03-01T09:00:00
Time::Piece> 2012-03-01T09:00:00
$

勝手知ったるDateTimeは、日付文字列をparseした瞬間は、タイムゾーンなどないまっさら状態なので、1回目のset_time_zone('GMT')で、"2012-03-01 00:00:00" はGMTにおける時刻であると設定したうえで、2回目のset_time_zone('Asia/Tokyo')で、日本のタイムゾーンを設定しました。set_time_zoneが2つあるというのが違和感ありますが、だいたいこんなもんです。

一方、Time::Pieceでは、parseして得られた時刻はGMTになっているから、一回epochに戻してからlocaltimeの引数として指定することで、タイムゾーンを設定しています。epochにまで戻す必要があるというのが違和感ありますが、そんなもののようです。

こんな感じで、タイムゾーンの指定は難しいよ、というお話でした。

Mojoliciousのテンプレートシステムのepなんですけれども、titleヘルパーでページタイトルが与えられていない場合(すなわちトップページ)をlayout側で判断して、titleの中身を変えたいという時。

たとえば、サイト名が「ちんぽこりん」だとして、

/ にアクセスした場合は<title>ちんぽこりん</title>
/new にアクセスした時は <title>更新情報 - ちんぽこりん</title>

みたいにしたいとき。stashの中をのぞいてifで判別するといいのかな。

Filename: common.html.ep

    <title><% if ( defined stash('title') ) { %><%= title %> - <% } %><%= $site_name %></title>

スマートじゃねえような気がしますが……

昨日書いた、

HTML::AccountAutoDiscoveryがはてなブログbetaに対応していない件

で、HTML::AccountAutoDiscoveryを使わなければよくね? 的なアプローチではてなブックマークWebHook用投げ銭スクリプトがこちらとなります。

Filename: throw.cgi

#!/usr/bin/perl
 
# throw.cgi ブックマークしたら投げ銭します。
# 説明:http://blog.dtpwiki.jp/dtp/2009/06/web-hook-645b.html
# 2009-06-07 ver 0.0.1 ファーストポスト
# 2009-10-03 ver 0.0.2 最新のMechでも動くように
# 2012-01-26 ver 0.0.3 はてなブログ対応
 
 
use strict;
use warnings;
use CGI;
use Config::Pit;
use Encode;
use HTML::AccountAutoDiscovery;
use utf8;
use WWW::Mechanize;
 
# 初期設定
my $url_sendpoint = 'https://www.hatena.ne.jp/sendpoint';
my $send_point = 10; # 送信するポイント(はてな手数料別)
my $login;
my $config = pit_get('hatena.ne.jp');
die "not preset account data in Pit." if !%$config;
my $my_id    = $config->{id      } or die 'id not found.';
my $password = $config->{password} or die 'password not found.';
my $auth_key = $config->{auth_key} or die 'auth_key not found.';
 
my $q = CGI->new;
my $mech = WWW::Mechanize->new;
$mech->agent_alias('Windows IE 6');
 
{ # メインルーチン
  # 認証
  if ( $q->param('key') ne $auth_key ) {
      die "Authentication failed";
  }
  # メソッド確認
  if ( $q->param('status') eq 'add' ) {
    # エントリーの情報
    my $req = $q->Vars();
    nagesen( $req );
  }
  # はてなブックマークWeb Hook用リザルト
  print $q->header('text/plain');
  print 'ok';
}
exit;
 
sub nagesen {
  my $req = shift;
  my $url = $req->{url};
  my @account = HTML::AccountAutoDiscovery->find( $url );
  unless( @account ) { @account = find_hatenablog( $url ); }
   
  sub find_hatenablog {
    my $url = shift;
    use LWP::Simple;
    my @r;
    my $c = get( $url );
    $c =~ s/.+(<html.+?>).+/$1/so;
    if ( $c =~ m{data-admin-domain="http://blog.hatena.ne.jp"} ) {
      if ( $c =~ m{data-author="(.+)"} ) {
        push @r,{account => $1, service => 'http://blog.hatena.ne.jp' };
      }
    }
    return  @r;
  }
  
  foreach my $item ( @account ) {
    my $send_id = $item->{account}; # account name
    send_hatenapoint( $req, $send_id );
    last; # HTMLに複数のIDを埋め込んでいた場合最初の人の分
          # だけ対応(同じ人がID埋め込みまくるとポイント
          # 送信しまくるのを防ぐ)
  }
  return;
}
 
sub login_hatenapoint {
# はてなにログインします
  $mech->get( $url_sendpoint );
  # ログインを促す画面に遷移済
  $mech->follow_link( text => mech_encode('ログイン') );
  # ログイン画面に遷移済
  $mech->set_visible( $my_id, $password );
  $mech->submit();
  # ログイン済み画面に遷移済
  $mech->follow_link( text => mech_encode('こちら') );
  # 投げ銭画面に遷移済
  $login = 1;
  return;
}
 
sub send_hatenapoint {
# はてなポイント送信をします
  my $req     = shift;
  my $send_id = shift;
  unless ( $login ) {
    login_hatenapoint();
  }
  # ログイン済みの状態
  $mech->get( $url_sendpoint );
  # はてなポイント送信のページに遷移済
  # ポイント送信メッセージ組み立て
  my $send_message = decode('utf8', $req->{title})
    ."($req->{url}) をブックマークしました。投げ銭いたします。"
    .'投げ銭スクリプト:http://svn.coderepos.org/share/lang/'
    .'perl/misc/hatenabookmark_webhook_nagesen/';
  # ポイント送信用パラメータ入力
  $mech->set_visible(
    $password, $send_id, $send_point,
    undef, # 匿名にしたい場合は1
    mech_encode( $send_message )
  );
  $mech->submit();
  # confirm画面に遷移済
  return unless $mech->title() eq mech_encode(
    'はてな ポイント付きメッセージ送信確認'); # 送信確認ページ?
  $mech->click_button( value => mech_encode('送信する') );
  #open my $fh, '>', 'log.html';
  #print $fh $mech->content();
  #close $fh;
  return;
}
 
sub mech_encode {
# WWW::Mechanize 1.21_01以降の挙動に対応
  my $str = shift;
  if ( ( $WWW::Mechanize::VERSION ) < 1.21 ) {
    $str = encode( 'utf8', $str );
  }
  return $str;
}
 
__END__

2009年10月に一度改定していて、WWW::Mechanizeのバージョンによる差異を吸収していたんだけれども、

M.C.P.C.: はてな自動投げ銭スクリプトを最新WWW::Mechanizeに対応させたが、Mechの動作が変わる境目のバージョンがわからない

その分はCodeReposにコミットするの忘れていたので今回はちゃんとコミットしたよ。

http://svn.coderepos.org/share/lang/perl/misc/hatenabookmark_webhook_nagesen/

とある密林に出店している中古屋さんから中古DVDを購入したのですが、パッケージと別のDVD板が入っているという事案が発生。中古屋さんに密林メールで連絡とったら、着払いで送り返してねということになりました。

んで、近くのサークルKに行ったれば、いつの間にか宅配業者の取り扱いが日本郵便になっていました。

んで、僕は、送り状伝票に記載されている番号を入れると配達状況を取得できるPerlモジュール、WebService::KuronekoYamatoと、WebService::SagawaKyubinというPerlモジュールをCPANで公開しているのですけれども、日本郵便は、(仕事では使ったことがないので)ノーマークでした……

というわけで、日本郵便用の荷物確認できるPerlモジュールを作るわけですけれども、名前をどうするかという問題が発生。

ヤマト運輸の荷物確認用Perlモジュールを、WebService::KuronekoYamatoとしてしまった以上、日本郵便のゆうパックは、WebService::YouPackとするしかないような希ガス。

そもそも、ゆうパックのローマ字表記って、“YouPack”なのですね! 知らんかったよ。

はたして、WebService::YouPackはちゃんとできるのでしょうか。予想としては、CPAN配布用ビルドの仕方をすっかり忘れてしまっているんじゃないかなあ。

先日から、Mojolicious 2.20にしたら、AnyEvent::Twitter::Streamが使われているとWebSocketが動かない問題に悩まされ、とりあえず本稼働が優先だったのでMojolicious 2.19で開発をしていた案件がとりあえず終わり、原因究明ということで、とりあえずMojoliciousをアップデートしたら、2.36まで進んでいた。進みすぎ!

そんで、いくらバージョンの進みが早すぎるからといっても、ドキュメントに書いてあるサンプルスクリプトが動かなくなるようなことはあるめえ、ということで、

http://search.cpan.org/~sri/Mojolicious-2.36/lib/Mojolicious/Guides/Cookbook.pod

を見て、WebSocketの場所に書いてあるプログラム

use Mojolicious::Lite;
 
# Template with browser-side code
get '/' => 'index';
 
# WebSocket echo service
websocket '/echo' => sub {
  my $self = shift;
  
  # Connected
  $self->app->log->debug('WebSocket connected.');
  
  # Incoming message
  $self->on(message => sub {
    my ($self, $message) = @_;
    $self->send_message("echo: $message");
  });
  
  # Disconnected
  $self->on(finish => sub {
    my $self = shift;
    $self->app->log->debug('WebSocket disconnected.');
  });
};
 
app->start;
__DATA__
 
@@ index.html.ep
<!DOCTYPE html>
<html>
  <head><title>Echo</title></head>
  <body>
    <script>
      var ws = new WebSocket('<%= url_for('echo')->to_abs %>');
      
      // Incoming messages
      ws.onmessage = function(event) {
        document.body.innerHTML += event.data + '<br/>';
      };
      
      // Outgoing messages
      window.setInterval(function() {
        ws.send('Hello Mojo!');
      }, 1000);
    </script>
  </body>
</html>

を実行してみると、これは動いた。

これの先頭に、

use AnyEvent::Twitter::Stream;

を入れるだけで、WebSocket接続が勝手にcloseされてしまう。

AnyEvent::Twitter::Streamの再接続の件をずうっと考えていたのですが、当初参考にしていた、

hidekiy blog: [perl] AnyEvent::Twitter::Streamで自動再接続処理 [blog.hidekiy.com]

のとおりにすると、通常のAnyEventでの組み方の時はよさそうなのですが、Mojoliciousと同時に使用するなど、イベントループを共有する場合は、while(1){} のせいで、Mojoliciousのイベントループ開始までプログラムが進行できず、かといって、AnyEvent::Twitter::Streamの再接続の処理をMojoliciousに担当させるというのは難しそうなので、ここは、「Twitterのウォッチャー」と、「〔Twitteのウォッチャー〕のウォッチャー」の二つのウォッチャーを作り、お互いにウォッチャーの再生成をさせることで、結果的にAnyEvent::Twitter::Streamの再接続が実現できるようにしてみました。

月別 アーカイブ

ウェブページ

OpenID対応しています OpenIDについて
Powered by Movable Type 5.2.7

このアーカイブについて

このページには、過去に書かれたブログ記事のうちPerlカテゴリに属しているものが含まれています。

前のカテゴリはPageMakerです。

次のカテゴリはPressです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。