HTMLメールに画像を埋め込んで、sendmailで送信する

HTMLに画像を埋め込む時はimgタグにBase64エンコードした画像を書くだけ。

<img border="0" src="data:image/png;base64,(Base64 Image)" />

しかし、HTMLメールをRubyで自動生成し、同じ方法で画像を埋めこもうとしたら上手くいかなかった。

メール送信に使っているsendmailに1行あたりの文字数制限があったらしく、画像の途中で勝手に改行が入ってしまっていたのが原因だったようだ。

とはいえ改行付きのBase64をimgタグに挿入することも出来ず、仕方なしに方法を探していたところ、以下のページを見つけた。

stackoverflow.com

どうもContent-Type: multipart/related;指定で本文と画像を別個に記述できるらしい。

色々試したところ、下のような感じでメールに画像を埋め込むことができた。

To: foo@bar.com
Subject: Test
Mime-Version: 1.0
Content-Type: multipart/related; boundary="boundary-test"; type="text/html"

--boundary-test
Content-Type: text/html;

(メール本文)
(画像はContent-IDで指定した名前を``cid:``の後に続けて入力する)
(例: <img src="cid:test.png" />)

--boundary-test
Content-Type: image/png; name="test.png"
Content-Disposition: inline; filename="test.png"
Content-Transfer-Encoding: base64
Content-ID: <test.png>
Content-Location: test.png

(Base64エンコードされた画像)

--boundary-test--

これをsendmailしてあげた所上手くいってくれた。

cat test | /usr/lib/sendmail -t

参考情報

Content-Type: image/png;はじまりのboundaryをいくつか作れば、画像を複数埋め込むこともできる。

(--boundary-test--はboundaryの終端のため、1度しか現れないことに注意)

テンプレートエンジンJQuery TemplatesをJadeから利用する

特に、パラメータの参照が似ていてよく間違える。
JQueryっぽい方は「$」を使う、と覚えよう。

二つのテンプレートエンジンの違いについて

JadeもJQuery Templatesもテンプレートエンジンには違いないが、
Node.jsのWebフレームワークexpressにも搭載されているJadeと、
言わずとしれたJQueryの名を冠する(?) JQuery Templatesとでは役割が随分違う。

JadeはサーバサイドでテンプレートからHTMLを生成するのに対して、
JQuery Templateはクライアントサイドからこれを行う。

もちろん相互利用することも可能だが、それぞれのシンタックスが似ておりやや混乱してしまう。

Jade

インデントでHTMLの入れ子構造を表現する。

- var Image = "hello.png"
html
  head
  body
    img(src="#{Image}")

このテンプレートから以下のHTMLが生成される。

<html>
  <head></head>
  <body><img src="hello.png"/></body>
</html>

この例ではJade側で直接JavaScriptの変数を定義しているが、Node.jsで書かれたコード上の変数も普通に使えるので便利。

JQuery Templates

JQuery TemplatesをJade上から利用する時は以下のような感じになると思う。
鋳型ともいえるテンプレートにオブジェクトを流し込む。
もちろん、リストを流し込めばその分だけ反復してくれたりもする。

html
  head
    script(type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js")
    script(type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.js")
  body
    #example

script(id="example-tmpl" type="text/x-jquery-tmpl")
  img(src="${Image}")

script(type="text/javascript")
  |$('#example-tmpl').tmpl([{Image: 'Image1.png'}, {Image: 'Image2.png'}])
  |  .appendTo('#example')
<html>
  <head>
    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js"></script>
    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script>
  </head>
  <body>
    <div id="example"><img src="Image1.png"><img src="Image2.png"></div>
    <script id="example-tmpl" type="text/x-jquery-tmpl"><img src="${Image}"/></script>
    <script type="text/javascript">
      $('#example-tmpl').tmpl([{Image: 'Image1.png'}, {Image: 'Image2.png'}])
      .appendTo('#example')
    </script>
  </body>
</html>

困った点

とにかく変数の参照方法が似ていてややこしい。

#{Var}  Jade
${Var}  JQuery Template

JQueryっぽい方は「$」を使う、と覚えよう(再掲)。

TypeScript + Node.js + expressでGyazoサーバを作った

f:id:atdxfe:20140122145909p:plain
いい感じにスクリーンショットが撮れる

Gyazoは、スクリーンショットを共有するためのツール。
Gyazo - Gyazoへようこそ : スクリーンショットの瞬間共有
撮影したスクリーンショットは自動的にgyazo.com上へアップロードされるけど、URLは128bitのハッシュ値(MD5)で公開されるから、URLを知っている人にしかまず見えませんよ、ということになっている。

このGyazo、ソースが公開されており、サーバを設置してプライベートGyazoサーバを構築することが出来る。
せっかくなので、今回は最近使っているTypeScript(0.9.5) + Node.js + expressの構成で自作してみることにした。

shidasan/gyazo-server-typescript · GitHub

以下、ソース。

///<reference path='./d.ts/node/node.d.ts'/>
///<reference path='./d.ts/express/express.d.ts'/>

import express = require('express');
import http = require('http');
import fs = require('fs');
import path = require('path');
import crypto = require('crypto');

var CONFIG = require('config');

POSTで受け取ったファイルのハッシュをURLに用いる仕様はそのままとしたので、cryptoを使っている。

var app = express();
app.configure(function () {
    app.set('port', process.env.PORT || 3000);
    app.use(express.static(path.join(__dirname, CONFIG.Files)));
    app.use(express.bodyParser({ uploadDir: CONFIG.Files }));
});

アップロードされたファイルは/config以下のyamlに書かれたパスに保存される。

app.post('/upload', function(req, res) {
    var sourcepath = req.files.imagedata.path;
    var md5 = crypto.createHash('md5');
    
    var s = fs.createReadStream('./' + sourcepath);
    s.on('data', function(d) {
        md5.update(d);
    });
    
    s.on('end', function() {
        var digest = md5.digest('hex');
        var targetpath = CONFIG.Files + digest + '.png';
        fs.rename(sourcepath, targetpath, function(err?) {
          if (err) {
            throw err;
          }
          res.send('http://' + CONFIG.Host + '/' + path.basename(targetpath));
        });
    });
});

express.bodyParserを使用すると、アップロードされたファイルのステータスをreq.filesから取得することができる。
ファイルのハッシュをMD5で計算し、req.files.imagedata.pathで定義された古いファイル名から、ハッシュ値にリネームをする。
レスポンスにはURLを返せば、Gyazo Clientが自動的にブラウザで開いてくれる。

効果

アップロードされたスクリーンショットを直接DropBoxなどで管理することも出来るようになった

2014.1.22追記

クライアントは本家そのままです。その際は、ホスト等の設定を変更する必要があるので注意が必要。
gyazo/Gyazo · GitHub

2014.1.22さらに追記

上記の例はGitHubへのInitial Commit時のコード*1
これ以降、Twitter Cards対応のためのviewの定義など行っている。

*1:ac3b47ec6b46f3713f1c26540cba813affc6415e

印象に残っている授業

印象に残っている授業がある。
その授業は「総合的な学習の時間」の中で行われていた気がする。

パズルやクイズの中から規則性を見つけ出して、パズルのタネ、
すなわちアルゴリズムを見つけ出すというものだった。

毎週違ったパズルが出てきて、そのタネを見つけだすのがとても楽しかった。

クイズ:数字当てゲームのタネを見つけ出す

  • 回答者は、0から99までの数値を想像する
  • 出題者は、0から99までの50個の数字が書かれた問題用紙を7枚出してくる
  • 回答者は、想像した数値が問題用紙中にあった場合、問題用紙の1つめの数値を足す
  • これを全ての問題用紙に対して行うと、それが想像した数値となる

確か問題用紙は一番下に挙げた通りだったと思う。

回答:当時を振り返って

それぞれの用紙の最初の数値が明らかに怪しい。
何せ1, 2..64と等倍に増えていく。

次に、それぞれの紙に書いてある数値を並び替えると

  • 1枚目は1, 3, ..
  • 2枚目は2, 3, .., 6, ..
  • 3枚目は4, 5, 6, 7, .., 12, ..

何やらパターンが見えてきた。
最後の紙に至っては普通に数字が並んでいるだけだ。
N枚目の紙では数値が2^(N-1)から始まっており、
2^(N-1)個置きに数字が並んだり飛んでいたりする。

ただし、ノーヒントでわかったのはここまでだった気がする。

その後、2進数というものを先生から教わった。
2進数では桁数と共に1, 2, 4, .., と等倍に増えていき、
しかも0か1のどちらかしかない。

ここまできて、N枚目に書いてある数値は、2進数表記にした際のN-1桁目が1となる数値の集合だったことがわかった。

検証用のJavaScirptコード

N+1毎目の問題用紙を生成し、該当する数値が存在する場合は先頭の要素を足す。
配列のシャッフルは省いたが、先頭要素は同じなので、回答への影響は無い。
正しく動いているようなので、当時の回答も正しいことがわかる。

function make_array(n) {
    var res = []; 
    for (var i = 0; i < 100; i++) 
        if (i & (1 << n)) res.push(i);
    return res;
}

function check(n) {
    var res = 0;
    if (make_array(0).indexOf(n) != -1) res += make_array(0)[0];
    if (make_array(1).indexOf(n) != -1) res += make_array(1)[0];
    if (make_array(2).indexOf(n) != -1) res += make_array(2)[0];
    if (make_array(3).indexOf(n) != -1) res += make_array(3)[0];
    if (make_array(4).indexOf(n) != -1) res += make_array(4)[0];
    if (make_array(5).indexOf(n) != -1) res += make_array(5)[0];
    if (make_array(6).indexOf(n) != -1) res += make_array(6)[0];
    console.log(res == n); 
}

for (var i = 0; i < 100; i++) check(i);

問題用紙

大体こんな感じだっただろうか。
先頭要素以外がシャッフルされている。

  • 1枚目

1, 33, 91, 57, 23, 83, 19, 37, 3, 87, 49, 39, 13, 79, 21, 51, 47, 41, 15, 75, 89, 85, 81, 77, 65, 99, 59, 63, 27, 17, 93, 45, 71, 61, 25, 31, 7, 9, 35, 29, 97, 73, 5, 95, 43, 11, 67, 69, 53, 55

  • 2枚目

2, 42, 95, 19, 26, 87, 27, 35, 10, 59, 86, 71, 7, 79, 43, 75, 55, 15, 94, 62, 90, 18, 54, 3, 91, 31, 39, 50, 23, 74, 51, 63, 11, 78, 14, 67, 47, 99, 22, 58, 6, 82, 98, 66, 70, 34, 83, 38, 30, 46

  • 3枚目

4, 12, 60, 47, 95, 20, 14, 86, 94, 44, 23, 13, 30, 39, 28, 6, 93, 45, 21, 71, 84, 62, 68, 85, 61, 53, 37, 52, 31, 36, 70, 63, 78, 46, 7, 22, 55, 79, 38, 76, 87, 54, 77, 15, 69, 5, 29, 92

  • 4枚目

8, 14, 77, 76, 75, 61, 12, 88, 9, 40, 41, 63, 26, 74, 24, 73, 31, 45, 10, 91, 72, 93, 57, 43, 47, 28, 92, 27, 56, 44, 11, 60, 25, 94, 78, 58, 42, 95, 89, 59, 30, 13, 29, 62, 46, 15, 90, 79

  • 5枚目

16, 52, 88, 80, 56, 17, 62, 58, 51, 83, 63, 18, 57, 27, 84, 29, 59, 92, 48, 82, 54, 89, 86, 60, 95, 30, 49, 26, 87, 20, 81, 22, 21, 85, 94, 23, 55, 61, 31, 19, 90, 93, 53, 91, 24, 50, 25, 28

  • 6枚目

32, 98, 60, 56, 43, 57, 46, 62, 37, 35, 97, 39, 63, 40, 61, 52, 34, 99, 50, 53, 96, 49, 45, 59, 47, 55, 54, 58, 51, 41, 33, 38, 36, 48, 42, 44

  • 7枚目

64, 75, 82, 87, 70, 68, 97, 77, 95, 91, 92, 78, 71, 65, 69, 93, 89, 85, 90, 98, 67, 76, 99, 72, 79, 84, 66, 96, 88, 86, 80, 94, 73, 81, 74, 83

DIY Gamer Kit (Arduino同梱版)を購入した

Technology Will Save Usというイギリスのベンチャー(?)
が発売しているDIY GamerKitを購入した

DIY Gamer - Technology Will Save Us - Technology Will Save Us

8x8のLEDの画面を使って好きにゲームを作ることが出来る
少し興味があったので購入ページを見てみると、これも欲しいと思っていたArduinoの同梱版があることがわかったのが決定打だったと思う
全部込みで10000円ほど、高い買い物だった
送料15GBP(購入時点で約2500円)が痛い

*1

はんだ付けなどが必要でまだ組み立ててはいないが、それは随分先になるかもしれない

このキットに飽きたら、Arduinoを再利用して、カメラモジュールとSDカードのシールドを買って定点観測カメラを作りたいと思う
1分毎にSDカードにビットマップを書き込むようなやつ

*1:http://technologywillsaveus.org/az/wp-content/uploads/2013/10/DIY-Gamer-2-web.jpg

tmuxがCygwin上で動作するようになったのでインストール手順をまとめてみた

CygwinWindows上で動作するUNIXライクなプログラミング環境で、個人的にはよく使っている。コンパイラなどのインストールも、Cygwin自身のインストールの際に用いるsetup.exeを呼び出すだけで出来てしまう。自分は使ってはいないが、aptライクなパッケージ管理ツールも人気のようだ。
tmuxは言わずもがな、ターミナル分割に使われるソフトウェアで一つのターミナルを疑似的に複数のターミナルに分割してくれる。
ただしこのtmux、長い間Cygwin上では動作しなかった。ソースコードを入手してビルドを通すことは出来ても、コマンドを入力した瞬間に落ちてしまっていた。これは残念。

今年の5月辺りからCygwin対応が進んでいた

しかし、個人的には最大の不満点だった、この「tmuxがCygwin上で使えない!」という問題に関して今年の5月位からMLにて議論が進んでいた。
patch for tmux on cygwin

このパッチは現在tmuxのレポジトリに適用されているようで、メンテナがtmuxの時期バージョンであるtmux1.9でのCygwin版のサポートを(ほぼ公式に?)宣言している。
Re: tmux on Cygwin

というわけで、Cygwin上にtmuxをインストールするためのマニュアルを簡単に作ってみる。
基本的に以下のページを参考にしており、自分がインストールする際の手順を纏めただけのものなので注意されたし。
Steps to install tmux in Cygwin

以下、適当なディレクトリ(自分の場合は$HOME/work)で作業。

tmuxのインストール手順

依存パッケージのインストール

依存するlibeventとncursesもダウンロード。別途、setup.exeを使ってautomake, gcc, git, pkg-configをインストールしておくこと。

wget https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
wget http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz

依存パッケージのビルド

Cygwinなだけあって時間かかりますね。

tar xvf libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
./configure --prefix=/usr
make && make install
cd ..

tar xvf ncurses-5.9.tar.gz
cd ncurses-5.9
./configure --prefix=/usr
make && make install
cd ..

ここまでは上手くいくだろう。さて、tmuxは入るだろうか...

tmuxのインストール

git clone http://git.code.sf.net/p/tmux/tmux-code tmux-tmux-code
cd tmux-tmux-code
./autogen.sh
CFLAGS="-I/usr/include/ncurses" ./configure --prefix=/usr
make && make install

しかしビルドに失敗する...

tty-term.o:tty-term.c:(.rdata$.refptr.cur_term[.refptr.cur_term]+0x0): undefined reference to `cur_term'
collect2: error: ld returned 1 exit status

やむなく以下の変更を行う。とりあえずコメントアウトして再度make install。(こんなんで良いのか...)
2014.2.25追記 tmux1.9以降では不要な操作となりました。

diff --git a/tty-term.c b/tty-term.c
index a3292eb..50af6e9 100644
--- a/tty-term.c
+++ b/tty-term.c
@@ -389,10 +389,10 @@ tty_term_find(char *name, int fd, const char *overrides, char **cause)
 	tty_term_override(term, overrides);
 
 	/* Delete curses data. */
-#if !defined(NCURSES_VERSION_MAJOR) || NCURSES_VERSION_MAJOR > 5 || \
-    (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 6)
-	del_curterm(cur_term);
-#endif
+//#if !defined(NCURSES_VERSION_MAJOR) || NCURSES_VERSION_MAJOR > 5 || \
+//    (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 6)
+//	del_curterm(cur_term);
+//#endif
 
 	/* These are always required. */
 	if (!tty_term_has(term, TTYC_CLEAR)) {

結果、動きました

何はともあれtmuxが無事動いたようなので何より。
今回は若干手間取りましたが、今後tmux 1.9リリースに向けて種々の問題は解消されていくことでしょう。*1
雑なパッチによりどんな不具合があるか想像出来ないため、ビルドし直したいところ。

2014.1.9追記


とのことです。
ありがとうございました。

2014.2.25追記

tmux1.9のリリースに伴いCygwinが正式にサポートされました。
tmux1.9のコードは以下のURLから入手可能です。
http://tmux.sourceforge.net/

*1:レポジトリは2013.11.27時点の最新版3e498cdb49c4ef9fcc5a4bf742407768561e795a