How to growl IRC messages over ssh

My environment

I'm using weechat on remote server's screen.
The big problem is I can't perceive chatting on IRC without "polling" terminal by myself. Because I have no way to get alert over screen and ssh.

I was thinking to solve this problem and just found the solution.

The flow is:

  1. Use weechat's logging function
  2. Connect to remote server over ssh, tail -f your logs
  3. Receive the multiple lines then process it with perl on your local machine and growl!

Preparation

  • Enable weechat's logging function and find the log files.
  • Confirm that the following command works well (rewrite the path to logs)
tail -f $HOME/.weechat/logs/*/*/*.weechatlog
  • Install `growlnotify`

Setup scripts

Shell script that connecting to the server (irc_growl.sh)

You may have to rewrite ssh options and path to the log. Be careful with `'` (single quotation mark) to guaranteeing the home dir path is interpolated with remote's.

ssh example.com tail -n 1 -f '$HOME/.weechat/logs/*/*/*.weechatlog' | perl irc_growl.pl &
Perl script that processes received lines (irc_growl.pl)

You can rewrite system()'s args as you want. `man growlnotify` will help you.

while (<STDIN>) {
    chomp;
    next unless $_;
    next if /^==>/; # ignore tail's output

    my $attr = parse($_);
    if ($attr->{type} =~ /(:?NOTICE|PRIVMSG)/) {
        system("growlnotify", "-m", $attr->{content}, "-t", $attr->{user}, "--appIcon", "LimeChat");
    }
}

sub parse {
    my ($line) = @_;

    my ($time, $cmd, @contents) = split /\t/, $line;
    my $content = join "\t", @contents;

    my $user;
    if (not $cmd) {           # critical error
        $cmd = 'ERROR';
    } elsif ($cmd eq '-->') { # join
        $cmd = 'JOIN';
    } elsif ($cmd eq '<--') { # part
        $cmd = 'PART';
    } elsif ($cmd eq '--') {  # messages from server
        $cmd = 'SERVER';
    } elsif ($cmd eq '*') {   # notice
        $user = $cmd;
        $cmd = 'NOTICE';
    } else {                  # privmsg
        $user = $cmd;
        $cmd = 'PRIVMSG';
    }

    return +{
        type    => $cmd,
        time    => $time,
        content => $content,
        user    => $user,
    };
}

Run!

./irc_growl.sh

100 fav されたので MacBook Air 11インチ を買ったら spell がなくてxt/podspell.t がコケた

Mac に戻ってくるのはもう6~7年ぶりなのか、当時は Intel Mac なんて無くて PowerPC でした。全体的な UI は昔からさほど変わった感じはしないですね。

さっそく環境を整えていたら github から持ってきたモジュールのテストがコケる。
こんな感じのテストを使っていたんですが、 spell は OSX には無いらしく、今まで Linux でしか作業してなかったのでだいぶ嵌りました。

podspell.t

use Test::More;
eval q{ use Test::Spelling };
plan skip_all => "Test::Spelling is not installed." if $@;
add_stopwords(map { split /[\s\:\-]/ } <DATA>);
$ENV{LANG} = 'C';
all_pod_files_spelling_ok('lib');
__DATA__

spell がないと怒られます。

sh: spell: command not found

はてどうしたものかと CPAN モジュールを漁って見ると、各々思い思いの方法で podspell.t を書いているようでした。

そこで参考にしたのは、いろいろな環境の開発者がいて xt/ が多く走っていそうな Plack のテストを参考にしてみました。aspell が存在する場合には aspell を使い、ない場合には spell を使う方法が良さそうです。

Plack-0.9973/xt/podspell.t

use Test::More;
eval q{ use Test::Spelling };
plan skip_all => "Test::Spelling is not installed." if $@;
add_stopwords(map { split /[\s\:\-]+/ } <DATA>);
$ENV{LANG} = 'C';
set_spell_cmd("aspell -l en list") if `which aspell`;
all_pod_files_spelling_ok('lib');

__DATA__

GmailでOCNのSMTPを指定すると Remote server does not support TLS code(500) とエラーが出る件

ヘルプフォーラムから2chまで探してようやく解決したので2011年1月14日現在のメモ

2010年9月1日以前にOCN会員登録証が届いたお客さま smtp.vcの後にメールアドレスの@の右側

OCN設定サポート | NTT Com お客さまサポート の表記通りに従っても、smtp.vc*.ocn.ne.jp はTLS対応してないようなので延々と下のエラーメッセージが表示される。

Remote server does not support TLS code(500)

2010年9月1日以降にOCN会員登録証が届いたお客さま(SSL方式)

この項目は一見すると昔からのユーザは使えない様に見えるが、この通り設定することで受信できる。なんだそれ。

送信メールサーバ(SMTP smtp.ocn.ne.jp
ポート番号 465
SSL 利用する
認証 使用する
アカウント名(ユーザ名) メールアドレスをすべて入力
パスワード メールパスワードを入力

Imager で Twitter アイコンを低品質 jpeg で返す

Twitter のアイコンには主に gif / jpeg / png が使われており、 png を表示できないガラケーがある。
Cache::Memcached::Fast でキャッシュしつつ、 Imager では一時ファイルを作らず。思っていたよりも楽にかけた

use LWP::UserAgent;
use Imager;
use Cache::Memcached::Fast;

use Plack::Request;
use Plack::Builder;

my $memd = Cache::Memcached::Fast->new({
    ...
});


builder {
    mount '/tw_thumbnail' => sub {
        my $env = shift;
        my $req = Plack::Request->new($env);

        if ($req->param("url") =~ m{^(http://a\d\.twimg\.com/.+)}) {
            my $url = $1;

            if (my $result = $memd->get($url)) {
                return [200, ['Content-Type' => 'image/jpeg'], [$result]];

            } else {
                my $img = LWP::UserAgent->new->get($url)->decoded_content;
                my $image = Imager->new;

                $image->read(data => $img)
                    or return [500, [], [$image->errstr]];

                $image->write(data => \my $out, jpegquality => 30, type => 'jpeg')
                    or return [500, [], [$image->errstr]];

                $memd->set($url, $out, 1 * 60 * 60 * 24);

                return [200, ['Content-Type' => 'image/jpeg'], [$out]];
            }

        } else {
            return [404, [], ['Not Found']];
        }
    };
};

practical というプラグマ書いた

GitHub - punytan/practical: practical pragma

use practical;

use strict;
use warnings;
use utf8;
use feature qw(switch say state);

と同じ。
ほとんどの人はこの4行は書いているはず(?)

類似のプラグマ

common::sense
  • strict 'refs' はオフ
  • warnings のオプション覚えきれないよ!
    • 下のコードで "Wide character in print at" がでない
perl -Mcommon::sense -e 'print "いろは\n";'
Modern::Perl
  • use utf8 がない
  • 将来、モジュールが追加される可能性がある

Tatsumaki ライクな micro WAF "Lanky" を作ってみた

Twiggy を使う場合は Tatsumaki があって簡単に書けるけれども Starman / Starlet 使うときにも似たようにかけたらいいなぁ、ということで似たように書けるものを作ってみました。
モデルは実装してないので(どの ORM を使うかは TIMTOWTDI でどれ採用すべきか迷ったので)正確には WAF ではないと思うのだけれど、とりあえず Tatsumaki と似たことはできるようにしたつもりです。なにか意見いただけると嬉しいです

https://github.com/punytan/Lanky

Tatsumaki のように全部ひとつのファイルに突っ込むことも出来るし、別々のファイルに分割して書けるようにもしてあります。

t/02_app をみてもらえるとすぐわかると思うんですが、下記のように書けるようになっております。

ひとつのファイルで書く場合

errordoc, htdocs, template ディレクトリを準備して、

$ tree 
.
|-- errordoc
|   `-- 404
|-- htdocs
|   |-- favicon.ico
|   |-- foo.htlm
|   `-- static
|       `-- index.html
|-- template
|   `-- foo.xt
`-- united.t
united.t

こんな風に書いたり、

use strict;

package RootHandler;
use parent 'Lanky::Handler';
use Data::Dumper;

sub get {
    my ($self) = @_;
    my $body = $self->render('foo.xt', {foo => 'testing'});

    my $res = $self->request->new_response(200);
    $res->content_type('text/html');
    $res->body($body);
    $res->finalize;
}

1;

package LoginHandler;
use parent 'Lanky::Handler';
use Data::Dumper;

sub get {
    my ($self) = @_;
    my $body = $self->render('foo.xt', {foo => 'testing'});

    my $res = $self->request->new_response(200);
    $res->content_type('text/html');
    $res->body($body);
    $res->finalize;
}

1;

package main;
use Test::More;
use Plack::Test;
use HTTP::Request::Common;

use File::Spec;
use File::Basename;

use Lanky;
use Plack::Builder;

my $basedir = File::Basename::dirname(__FILE__);

my $lanky = Lanky->new(
    application => [
        '/' => 'RootHandler',
        '/login' => 'LoginHandler',
    ],
    template => [
        path => ["$basedir/template"],
        cache => 1,
        cache_dir => File::Spec->tmpdir,
    ],
    errordoc_path => "$basedir/errordoc",
    render_encoding => 'utf8',
);

my $app = builder {
    enable "Plack::Middleware::Static",
        path => sub { s{^/(?:(favicon\.ico)|static/)}{$1||''}e },
        root => "$basedir/htdocs/";
    $lanky->to_app;
};

test_psgi $app, sub {
    my $cb = shift;
    my $res = $cb->(GET "/");
    is $res->content, "testingいろはにほへと\n";

    $res = $cb->(GET "/login");
    is $res->content, "testingいろはにほへと\n";

    $res = $cb->(GET "/xxx");
    is $res->content, "みつからないよ!\n";
    is $res->code, 404;
};

done_testing;

別々のファイルに書く場合

lib ディレクトリを作って @INC に ./lib を突っ込んで、

$ tree 
.
|-- errordoc
|   `-- 404
|-- htdocs
|   |-- favicon.ico
|   |-- foo.htlm
|   `-- static
|       `-- index.html
|-- lib
|   `-- MyApp
|       `-- C
|           |-- Login.pm
|           `-- Root.pm
|-- template
|   `-- foo.xt
`-- divided.t
divided.t

こんな風に書いて、 MyApp::C::Root で上と同じように use parent 'Lanky::Handler'; してやれば、 $self から諸々呼べるようになっています

use strict;
use Test::More;
use Plack::Test;
use HTTP::Request::Common;

use File::Spec;
use File::Basename;
use lib File::Basename::dirname(__FILE__) . "/lib";

use Lanky;
use Plack::Builder;

my $basedir = File::Basename::dirname(__FILE__);

my $lanky = Lanky->new(
    application => [
        '/' => 'MyApp::C::Root',
        '/login' => 'MyApp::C::Login',
    ],
    template => [
        path => ["$basedir/template"],
        cache => 1,
        cache_dir => File::Spec->tmpdir,
    ],
    errordoc_path => "$basedir/errordoc",
    render_encoding => 'utf8',
);

my $app = builder {
    enable "Plack::Middleware::Static",
        path => sub { s{^/(?:(favicon\.ico)|static/)}{$1||''}e },
        root => "$basedir/htdocs/";
    $lanky->to_app;
};

test_psgi $app, sub {
    my $cb = shift;
    my $res = $cb->(GET "/");
    is $res->content, "testingいろはにほへと\n";

    $res = $cb->(GET "/login");
    is $res->content, "testingいろはにほへと\n";

    $res = $cb->(GET "/xxx");
    is $res->content, "みつからないよ!\n";
    is $res->code, 404;
};

done_testing;