はじめに
探偵小説作家夢野久作の 瓶詰地獄(青空文庫リンク) は、昭和3年に書かれた書簡体形式の掌編です。
「三個とも封瓶のまま」打ち上げられた手紙を、読み進めていくと、最後に劇的な読後感に襲われる仕掛けになっています。
作中手紙の ”書かれた順番”
”書かれた順番” については、
この作品における3篇の手紙が、第3、第2、第1の瓶の内容の順番で書かれたとする(Wikipedia )
解釈が一般的なものです。
が、読者が読む順番(情報の与えられる順番) がそもそも違ったら、”書かれた順番”について、どんな印象を持てるでしょうか…?
#作中手紙を ”読む順番”
”読む順番” が違うとどんな気持ちになるか、気になったので、タイトル要素で実装しました!(本題)
画面イメージは下記です。(”瓶詰地獄”ネタバレ防止のため、本文一部が表示されてしまう操作動画アニメGIFは本稿末尾に配置しました…)
操作の流れ
1. BOTにテキストを投げる
2. 浜辺の絵と、カルーセルに載った瓶がBOTから返ってくる
3. カルーセルの中にはランダムな順番で、作中の3つの手紙が詰まっている
4. 「瓶を開ける」ボタンをクリックすると、手紙本文画像が開く
「封を開けて読む」読書体験をしたかったので「瓶画像をクリックしたら、手紙が開く」2段階UIにしました。
環境(2017年4月現在)
UI: Line-BOT
静的コンテンツ(画像)配信: Azure BLOB ストレージ
Webhook: Azure Functions
UI:LineBOT
Line Messaging API は、去年の春(2016年春)のトライアル提供から、秋の正式提供への経緯を経ています。
その影響か各種仕様変更が多々あり、Line側準備は公式サイトの最新情報を見るのが一番だと思います。
ここ から ここ など。
#静的コンテンツ(画像)配信: Azure BLOB ストレージ
前述のLineBotの細かい仕様変更のひとつに、Line へ reply/push できるコンテンツは https に限定 するというものがあります。
(去年はたしか http でよかったような…?)
自前 https 環境設定は面倒なので、Azure BLOB ストレージ に画像ファイルを置いて、静的配信することにしました(参考)。
Webhook: Azure Functions
去年リリースされた(参考 [techcrunch] MicrosoftがAWSのLambdaに続いてサーバー不要のイベント駆動型クラウドサービスAzure Functionsをローンチ) Azure Functions は 従来あった webJobs を lamda ライクに拵えたサービスです。
今回は、node.js で下記のように書きました。
(httpリクエストトリガー: リクエストをキューに溜める)
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);
context.log(req);
context.bindings.outputQueueItem = req.body;
res = { body : "" };
context.done();
};
(キュートリガー: キュー起動で処理実行)
var https = require("https");
var url = require("url");
// 送出
function send_line(postData, context, mtd){
var parse_url = url.parse('https://api.line.me/v2/bot/message/'+ mtd);
var options = {
host: parse_url.host,
path: parse_url.path,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer {(キー)}'
}
};
var req = https.request(options, function(res) {
context.log('-- send_line : STATUS: ' + res.statusCode);
context.log('-- send_line : HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function (chunk) {
context.log('-- send_line : BODY: ' + chunk);
});
});
req.on('error', function(e) {
context.log('-- send_line : problem with request: ' + e.message);
});
req.write(postData);
req.end();
}
// 手紙シャッフル
function shuffle(array) {
var n = array.length, t, i;
while (n) {
i = Math.floor(Math.random() * n--);
t = array[n];
array[n] = array[i];
array[i] = t;
}
return array;
}
// 瓶詰
function push_bottles(context, event) {
shuffled_lettersNum = shuffle(["1", "2", "3"]);
var postData = JSON.stringify(
{'to' : event.source.userId,
'messages' : [
{
"type": "template",
"altText": "『瓶画像と本文へのリンク』です。",
"template": {
"type": "carousel",
"columns": [
{
"thumbnailImageUrl": "https://(アカウント).blob.core.windows.net/bins/btl_typeB.png",
"text": "よほど以前に漂着致したる封瓶",
"actions": [
{
"type": "uri",
"label": "瓶を開ける",
"uri": "https://(アカウント).blob.core.windows.net/bins/" + shuffled_lettersNum[0] + ".jpg"
}
]
},
{
"thumbnailImageUrl": "https://(アカウント).blob.core.windows.net/bins/btl_typeA.png",
"text": "よほど以前に漂着致したる封瓶",
"actions": [
{
"type": "uri",
"label": "瓶を開ける",
"uri": "https://(アカウント).blob.core.windows.net/bins/" + shuffled_lettersNum[1] + ".jpg"
}
]
},
{
"thumbnailImageUrl": "https://(アカウント).blob.core.windows.net/bins/btl_typeB.png",
"text": "よほど以前に漂着致したる封瓶",
"actions": [
{
"type": "uri",
"label": "瓶を開ける",
"uri": "https://(アカウント).blob.core.windows.net/bins/" + shuffled_lettersNum[2] + ".jpg"
}
]
}
]
}
}
]}
);
send_line(postData, context, "push");
};
// 浜辺
function reply_beach(context, event) {
var postData = JSON.stringify({
'replyToken' : event.replyToken,
'messages' : [
{
"type": "image",
"originalContentUrl": "https://(アカウント).blob.core.windows.net/bins/beach.jpg",
"previewImageUrl": "https://(アカウント).blob.core.windows.net/bins/beach.jpg"
}
]
});
send_line(postData, context, "reply");
};
function post_message(context, event) {
if (event.type == 'message'){
var messageType = event.message.type;
if (messageType == 'text') {
reply_beach(context, event);
push_bottles(context, event);
} else
reply_beach(context, event);
};
}
module.exports = function (context, myQueueItem) {
context.log('------------ JavaScript queue trigger function', myQueueItem);
myQueueItem.events.forEach(event => post_message(context, event));
context.done();
};
BOT内画像&BOT操作動画
画像は下記で
- 浜辺と瓶の画像は、安定と信頼の(瓶詰手紙画像が2種類もあって嬉しかった!) いらすとや さんから
- 手紙の画像は下記方法で
- Chrome + Full Page Screen Capture で、青空文庫 - 瓶詰地獄 をページ全体キャプチャ
- 幕末古写真ジェネレータ + gimp で加工
準備しました。
また、操作動画は、PC版LINEだと今回使ったカルーセルテンプレートは表示されない(下記画像)ので、
スマホアプリ版LINEの動画を撮る必要があり、
- Androidアプリ「Az Screen Recorder」
を使いました。(無償録画機能+課金サブセット(トリミング、画面切り取り、GIF変換など、オールインワンで便利でした))
さいごに
満足いく読書(?)体験ができました!
操作動画アニメGIF(※1アクション(ループ再生))