だるろぐ

明日できることは、今日しない。

はてなブログのドメインを daruyanagi.jp から blog.daruyanagi.jp へ引越しした

f:id:daruyanagi:20170325190700p:plain

あまり気付かれてない気がしますが(こっそりやったので!)、はてなブログのドメインをお引越ししました。はてなブログを独自ドメインにする話はよく見かけますが、独自ドメインで運用していたはてなブログを別のドメインへ移動させた例はあまりないかもしれないので、メモがてら残しておきます。

とはいえ、心配したようなトラブルもとくに起こりませんでした。1時間ちょっと はてなブログ がダウンしたのと、AdSense がちょっと減ったような気がする程度です。

以前の状態と解決すべき課題

以前は

  • daruyanagi.net:メインのブログ(WordPress)
  • daruyanagi.jp:はてなブログ

という運用をしていたのですが(こういうちょっと謎な構造になっていたのは、もっぱら歴史的な事情によります)、WordPress よりも はてなブログの方が管理が楽だったこともあり、いつしか daruyanagi.jp がメインに。持て余していた daruyanagi.net をポートフォリオサイト(というか自己紹介サイト)っぽい感じにして使っていたのですが、とある日、ドメインの延長手続きを失念してしまい、daruyanagi.net を失ってしまいました。

f:id:daruyanagi:20170326230159p:plain

現在は水素水を布教するサイトになっています。

そんなわけで、

  • daruyanagi.jp:はてなブログ
  • about.daruyanagi.jp:自己紹介サイト

というよくわからない構造になってしまい、ちょっと困っていました。できればこれを

  • blog.daruyanagi.jp:はてなブログ
  • daruyanagi.jp:自己紹介サイト

という感じに整理したかった。あと、トップドメインで はてなブログ を運用するのはあまりよくないので直したかった、というのもあります。

※はてなブログでは、サブドメインを使用しないexample.comでブログを運用すると、ドメイン名の仕組み上、トラブルの原因になることがあります。取得したドメインの前に、必ずサブドメイン(wwwやblogなど)を付加してください。

はてなブログを独自ドメインで利用する - はてなブログ ヘルプ

Twitter で煽られるのも悲しいですしね!


WebMatrix × ASP.NET Web Pages で自己紹介サイトを構築し、Azure へ配置する

というわけで、サイトのお引越し処理です。やらなければいけないのは以下の2つになります。

  1. はてなブログを daruyanagi.jp から blog.daruyanagi.jp へ移動
    1. 独自ドメインの割り当てを解除し、daruyanagi.hatenablog.com に戻す
    2. DNS設定を行う
    3. はてなブログに新しい独自ドメインを割り当てる
  2. 自己紹介サイトを daruyanagi.jp にする
    1. DNS設定を行う
    2. Azure のカスタムドメイン機能で daruyanagi.jp を割り当てる

うちは20年近くバリュードメインですので、そこで DNS の設定を書き換えます。

f:id:daruyanagi:20170326233254p:plain

これが反映されるのを待って、はてなブログ と Azure 側でもドメインの設定をしました。

はてなブログのドメイン移動は、反映までに少し時間がかかりましたが(1時間ちょい)、問題なく行えました。あまり期待していなかったんですが、はてなブックマークなどもちゃんと受け継がれたようです。さすが!*1

しかし、以前に外部からいただいた はてなブログ へのリンクが、移転に伴い 404 になってしまうのはあまりいただけません。というわけで、自己紹介サイトを拡張し、blog.daruyanagi.jp へリダイレクトを行うことにしました。幸い、自己紹介サイトは WebMatrix × ASP.NET Web Pages で作ってあったので、そんなに難しくはなさそう。

しかも幸運なことに、はてなブログ は記事を配信するディレクトリを一段深く掘ってあります(ドメイン直下ではない)。

※ディレクトリとして、はてなブログで使用しているaboutやarchiveなどを設定することはできません。

記事を配信するディレクトリを変更する - はてなブログ ヘルプ

というわけで、daruyanagi.jp/entrydaruyanagi.jp/archivedaruyanagi.jp/about へのリクエストをそのまま blog.daruyanagi.jp の各ディレクトリへリダイレクトすればよさそう。

f:id:daruyanagi:20170326232734p:plain

# ~/Entry.cs、 ~/Archive.cs、 ~/About.cs

@{
    var url = App.Blog + Request.Url.PathAndQuery;

    Response.RedirectPermanent(url);
}

App.Blog には、blog.daruyanagi.jp が格納されています*2HttpResponse.RedirectPermanent メソッド (System.Web) は 301 リダイレクトを行ってくれるらしいです。楽ちんですね! WebMatrix/ASP.NET Web Pages を使ってる人は少ないと思いますが、他の言語やフレームワーク、サーバーでも似たような感じの運用で対処できそうな気がします。

フィードもリダイレクトしようかなと思いましたが、それはなんとなくやめました。ごめんなさい。購読してくれてる方は、URL の登録をやり直してください。*3

Azure のカスタムドメイン割り当ては、はてなブログの独自ドメイン設定よりも早く、30分弱で反映されました。うまくいってるみたいでよかったです。Google はすぐに古い URL を参照しなくなりましたが(RedirectPermanent のおかげでしょうか?)、まだ古い URL からくるトラフィックもあるみたいで、残念な思いをさせなくてよかったと思います(ブログを読んでがっかりするのは防げませんが!)。

f:id:daruyanagi:20170326233705p:plain

丸一日様子を見てから、daruyanagi.jp -> Azure も cname ではなく a レコードで引くようにしてみました。これで今回のミッションは完了! お疲れさまでした。WebMatrix は今年いっぱいでサポート終了ですけど、最後まで役に立ってくれました。僕のようなアマチュアにはピッタリなツールだったのですが、やっぱり Visual Studio Code などと比べてエディターが弱いので仕方がないかな。ともあれ、ありがとうと言いたいです。

追記

*1:スターはダメなんだっけ? よくわかんないけどいいや

*2:開発中は daruyanagi.jp にしていろいろテストしていました

*3:daruyanagi.jp/feed を登録している場合は、はてなブログと僕のもう一つのブログ(コラムのみ)の新着情報の両方を受け取れます

WebMatrix とおさらばして、Visual Studio 2015 で ASP.NET Web Pages をはじめる

f:id:daruyanagi:20170212020533p:plain

長年愛用してきた「WebMatrix」ですが、昨年10月に2017年11月1日 でのサポート終了がアナウンスされました。

Hi everyone

After a long and successful run, Microsoft has decided to end formal support of WebMatrix. Formal support will end on November 1st, 2017. Community support will continue on the WebMatrix forums

Please take a look at Visual Studio Code, our new, free, open source, multi-platform editor! VS Code support git integration, extensions and a whole bunch of other great features!

Thanks!

The WebMatrix team

WebMatrix formal support ends November 1st, 2017 : The Official Microsoft IIS Forums

f:id:daruyanagi:20170212020959p:plain

すでにテンプレートをダウンロードする際に 404 が発生することが多くなったほか、拡張機能のダウンロード提供が終了しており、第一線で使うにはつらい状況になっています。そろそろ乗り換えを検討すべきでしょう。

Microsoft は後継ソフトとして「Visual Studio Code」を推奨していますが、これは統合ターミナルでバリバリとコマンドを打つ感じなので、GUI に甘やかされた僕にはしんどい感じ(最近「Express」を少し触っているのですが、そっちの文化にあわせるなら割と使いやすいですけどねー)。いずれ慣れないといけないなーとは思うんですが、APS.NET Web Pages を使うならば、当面の間は「Visual Studio 2015」が一番楽かなーって感じです。

1. 「Visual Studio 2015」で ASP.NET プロジェクトを作成

f:id:daruyanagi:20170212021454p:plain

まず「Visual Studio 2015」で ASP.NET プロジェクトを作成します。

f:id:daruyanagi:20170212021543p:plain

プロジェクトタイプは“Empty”でよいです。というか、「WebMatrix」でいうところのスターターテンプレートの類は「Visual Studio」に用意されていないので、“Empty”が無難な気がします。

2. プロジェクトに Razor ページを追加

f:id:daruyanagi:20170212021735p:plain

このテンプレートは本当にほぼ空なので、プロジェクトのコンテキストメニューなどから Web ページ(Razor)を追加する必要があります。

f:id:daruyanagi:20170212021905p:plain

“Razor”などのキーワードで探すと簡単。いろいろあるけど、わかんなかった“Web ページ(Razor v3)”でいいと思います。

f:id:daruyanagi:20170212022148p:plain

最初のページを追加すると、ASP.NET Web Pages に必要な参照が勝手に追加されます。すごーい!

3. [F5]キーを押して実行

とりあえずなんか書いて実行してみましょう。

<!DOCTYPE html>
@{ 
    var message = "Hello! World";
}
<html>
    <head>
        <title></title>
    </head>
    <body>
        <p>@message</p>
    </body>
</html>

[F5]キーを押すとサーバー&ブラウザーが立ち上がります。

f:id:daruyanagi:20170212022322p:plain

message を変えてブラウザーをリロードすると、即座に反映されまっする。なんとなく気軽で「WebMatrix」を使い続けてきましたが、機能面では「Visual Studio」のほうがずっと強力(ブレークポイントとかもしかけられるやで。入力補完はちょっと緩いところあるけど)です。……で、「Visual Studio Code」ではこの手順をどうやるんだ? 

最後に「WebMatrix」さん、長いことありがとうございました。

WebMatrix:フィードを読み込むときに System.Xml.XmlException が発生する

ASP.NET Web Pages 製の BOT が、ある日を境に突然、System.Xml.XmlException エラーを吐くようになった。

The element with name 'RDF' and namespace 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' is not an allowed feed format.

某サイトのリニューアルで、RSS 2.0 が RSS 1.0 になったせいでフィードが読めなくなったらしい/(^o^)\

Classes That Model Syndication Feeds and Items

  • SyndicationFeed - represents a syndication feed. Has properties like Title, Description, Links, and Items. The Items property represents the collection of content items expressed in the feed.
  • SyndicationItem - represents a specific syndication feed item and includes properties like Title, Summary, PublishDate, Authors, and so on.

Classes That Transform Syndication Feeds To/From XML

  • Rss20FeedFormatter - can take a SyndicationFeed object and turn it into XML that conforms to the RSS 2.0 specification. Also, can be used to consume a properly-formatted RSS 2.0 feed, turning the XML into a SyndicationFeed object with its properties set based on the data in the consumed XML.
  • Atom10FeedFormatter - same as the Rss20FeedFormatter, but uses the Atom 1.0 standard.
Syndicating and Consuming RSS 1.0 (RDF) Feeds in ASP.NET 3.5 - 4GuysFromRolla.com

Rss10FeedFormatter は標準で用意されていないみたいなので、自分で実装する必要がある。

幸い、コードが GitHub に転がっていたので拝借(見覚えのあるアイコンの人だなぁ……)。おおきにさんきゅーやで!

f:id:daruyanagi:20160603190721p:plain

これを以下のように呼び出してやれば、解決。めでたしめでたし。

try
{
    feed = SyndicationFeed.Load(reader);
}
catch
{
    // エラーがでたら RSS 1.0 ってことにしておく
    var formatter = new Rss10FeedFormatter();
    formatter.ReadFrom(reader);
    feed = formatter.Feed;
}

WebMatrix:改修工廠の早見表がほしかったので作ってみた

f:id:daruyanagi:20150919201617p:plain

今週は超忙しい&体調がよろしくなかった“ので”、合間を見つけて前々からほしかった改修工廠の早見表を作ってみた。接続詞が間違っているというツッコミが入りそうだけど、こういうのって、そういうときにこそやりたくならない?

最初に作ったヤツ

f:id:daruyanagi:20150919201737p:plain

ASP.NET Web Pages + SQL Server Compact で作ってみたけど、だいぶ遅かった。遅いのは多分、自分が SQL わかってないせいだと思う(SELECT でごっそりとってきたデータを LINQ で加工するとかいうわけのわからないことをやっていた)。SQL が分かんないのは一朝一夕に改善できないので、とりあえずデータベースへの問い合わせ結果をキャッシュしまくってみたけれど、スタートアップの遅さは改善できない。Web サイトがスタンバイ(スリープ? なに?)したら、キャッシュを一から再構築しなきゃいけないのも困った。

――というわけで、廃棄する予定。

二番目に作ったヤツ

f:id:daruyanagi:20150919202146p:plain

で、ちょっと考えてみたんだけど、こういうデータっていうのは稀にあるサーバーのメンテナンスのときにしか更新されないわけだ。だったら、なにもデータベースに入れる必要はなかった(CURD のうち R しかやらへんやん?)。JSON か何かでデータを用意しておいて、サーバーのスタートアップ時に読み込めばいい。

俺ってアホだなーと思いながら、ぐちゃぐちゃっと今日半日で作ったら(ロジック組むより、データ打ち込む方がよっぽど時間かかった)、最初に作ったヤツよりだいぶ早くて満足。コードを整理しながらロジック見直せばもうちょっと早くなりそう。なんせ今のは cshtml に foreach が腐るほど埋まってるという正真正銘のクソコードだし……ちょうどいいから今度 GitHub にあげて、GitHub Flow ってのを勉強するネタにしようと思う。

ほんとうはこれを Windows Phone に移植しておでコンに出したかったけれど、いつの間にか締め切り過ぎてたみたい。残念。艦これ Android 版が出るらしいし、Android アプリにするのもアリかなぁ? 

WebMatrix: URL にドットを含めたい

f:id:daruyanagi:20150913032211p:plain

Wiki エンジンなんかを作るときなど、「ドット(.)」を URL に含めたい場合は、Web.config に一行、以下のように加えるといいみたい。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
    </system.webServer>
</configuration>

静的リソースまでアプリケーションで処理するようになる(?)ので、効率が悪そうだけど。もっとちゃんと対策したければ、以下の URL が参考になりそう。

お前ら喜べ! Windows 10 でも WebMatrix が使えるぞ!!

f:id:daruyanagi:20150707204808p:plain

@shibayan からもたらされた極秘情報によると、6月30日に WebMatrix 3.1 がリリースされていたらしい。


変更点が相変わらずよくわからなかったので、その日はそのまま寝て、このこともすっかり忘れていたのだけど、ふと思い立って Windows 10 にインストールしてみたところ……

f:id:daruyanagi:20150707205443p:plainf:id:daruyanagi:20150707205450p:plainf:id:daruyanagi:20150707205447p:plainf:id:daruyanagi:20150707205454p:plainf:id:daruyanagi:20150707205502p:plain

キタ━(゚∀゚)━(∀゚ )━(゚  )━(  )━(  )━(  ゚)━( ゚∀)━(゚∀゚)━!!

ちょっと端っこが欠けてるような気がするけど、きっと気にしたら負けだ!! ちゃんと息をしてくれているだけで、おじさんは満足です。これでいちいちわざわざゴニャゴニャしなくても、Windows 10 でちゃんと WebMatrix がインストールできる!

みんな大好き Visual Basic 6 ともども、Windows 10 でも WebMatrix をよろしくお願いいたします(^v^)ノ

おまけ

f:id:daruyanagi:20150707205943p:plain

もしお手すきでしたらこれも直していただければ幸いです(`・ω・́)ゝ

WebMatrix:フィードを読み込んでデータベースへつっこむときに System.Data.SqlServerCe.SqlCeException が発生する。

だいぶ悩んで、Visual Studio まで立ち上げていろいろデバッグしていたのだけど、原因は簡単だった。

datetime

300 分の 1 秒、つまり 3.33 ミリ秒の精度で、1753 年 1 月 1 日から 9999 年 12 月 31 日までの日付と時刻のデータを格納するデータ型です。値は .000、.003、または .007 ミリ秒の単位になるように丸められます。

2 つの 4 バイト整数として格納されます。最初の 4 バイトは、base date である 1900 年 1 月 1 日からの日数、またはこの日までの日数を格納します。基準日とは、システムが参照する日付です。datetime 型の値には、1753 年 1 月 1 日より前の日付を使用できません。もう一方の 4 バイトは、午前 0 時から数えた時間をミリ秒単位で格納します。秒の有効範囲は 0 から 59 までです。

データ型

f:id:daruyanagi:20150625045042p:plain

一方、C# の default(DateTime)0001/01/01 0:00:00。つまり、SyndicationFeed オブジェクトの LastUpdateTime プロパティあたりにちゃんと値がセットされてなくて(そういうフィードを配信しているサイトは割とある)、default(DateTime) を返してくるとき、それをそのまま SQL CE データベースに格納しようとするとエラーになる。

f:id:daruyanagi:20150625050249p:plain

SQL CE の datetime 型を扱う場合は、事前に値の範囲に収まるかチェックして、ダメな場合はハネておかないといけないんだね。

var min_date = new DateTime(1753, 1, 1); // ほんとは readonly でどこかに
var updated_at = item.LastUpdatedTime.UtcDateTime < min_date 
    ? new DateTimeOffset(DateTime.Now).UtcDateTime 
    : item.LastUpdatedTime.UtcDateTime;

// Database.Execute();

教訓

ちゃんとマニュアルは読もう。

先生助けてっ! WebMatrix ちゃんが息をしてないのっっ!!

f:id:daruyanagi:20150428022417p:plain

WebMatrix 3 does not support IIS Express 10. You will not be able to run WebMatrix 3 on the same machine with any version of Visual Studio 2015 that includes IIS Express 10

とうとうこの日が来たかーって感じですね! 一応、IIS Express 10 に対応した WebMatrix(WebMatrix 4)がリリースされるという可能性もなきにしもあらずですが、あんまり期待はもてなさそう。また、Visual Studio 2015 を使わないという手もありますが……WebMatrix と Visual Studio だったら Visual Studio 選びますわな。

まぁ、でも、WebMatrix のおかげでいろいろ楽しかったです。Visual Studio がジェット戦闘機ならば、WebMatrix はレシプロ複葉機みたいなもんですが、自分みたいな趣味として片手間で触るような人間にはぴったりでした。入門向けとしてはなかなかいいツールではないかと思うのですが、あんまり普及しなくて残念です。

というわけで、完全に死んだ頃を見計らってお葬式イベントでもやりたいと思います。

追伸

これで WebMatrix とかを完全に捨てられますね!

Monaco上のファイルを一括ダウンロードする - xin9le.net

ぐぬぬ(^v^)

WebMatrix: 伊予鉄も止まったので、早速、遅延情報をゲットしてみる。

f:id:daruyanagi:20150126102626p:plain

WebMatrix: JR四国が止まったので、運行情報の取得プログラム作るのが捗った。 - だるろぐ で喜んでたら、今朝は伊予鉄にも遅れが出た。これで、伊予鉄の運行情報もとれるぞ!

伊予鉄の運行情報ページの構造は、

<p class="about">電車・バス 現在通常通り運行しております。</p>
<p class="date">2015/01/26(月) 10:26</p>

<!-- 遅延がある場合だけ↓ -->
<table class=default>
:
:
</table>

こんな感じになってるみたい。ということは、このテーブルがある前提で遅延情報を解析・出力、途中で例外が発生すれば正常運行とみなすという方針でよさそうだ。

public static List<DelayInfo> GetIyotestu()
{
    const string name = "伊予鉄";
    const string url = "http://www.iyotetsu.co.jp/kinkyu/";

    return GetDelayInfo(name, url, Encoding.UTF8, _ => 
    {
        var doc = new HtmlAgilityPack.HtmlDocument();

        doc.LoadHtml(_);

        try
        {
            // 遅延情報のテーブルを取得。発見できなければ例外 → 正常運行
            var rows = doc
                .DocumentNode
                // table.default のなかにある tr タグを取得
                .SelectNodes(@"//table[@class=""default""]//tr")
                // テキストノードなどは読み飛ばす
                .Where(n => n.NodeType == HtmlNodeType.Element);

            var result = new List<DelayInfo>();

            foreach (var row in rows)
            {
                var td = row.ChildNodes
                    // テキストノードなどは読み飛ばす
                    .Where(n => n.NodeType == HtmlNodeType.Element)
                    // セルの結合があるので、ほしい情報のインデックスが列によって違う!
                    // → 後ろから読む
                    .Reverse() 
                    .ToList();

                result.Add(
                    new DelayInfo()
                    {
                        Line = td[1].InnerHtml,
                        Status = td[0].InnerHtml.IndexOf("通常運行") < 0
                            ? "運休・遅延あり"
                            : "正常運行",
                        Message = td[0].InnerHtml,
                    }
                );
            }

            return result;
        }
        catch
        {
            // p.about の内容を解析して、正常運行というデータを返す    
            var text = doc.DocumentNode
                .SelectSingleNode(@"//p[@class=""about""]")
                .InnerText;
            var line = text.Split(' ')[0];
            var message = text.Split(' ')[1];
            return new List<DelayInfo>()
            {
                new DelayInfo()
                {
                    Line = line,
                    Status = "正常運行",
                    Message = message,
                },
            };
        }
    });
}

GetDelayInfo(name, url, encodsing, processor) は、指定した URL からソースコードの取得を取得するもので、キャッシュを Get/Set も行う(定型処理なので分離した)。HTML の解釈を string processor(string src) に委譲しているので、今回はその中身だけを書けばよい。

とくに難しいことはないのだけど、ただ一点、テーブルのセルがところどころ結合されている関係で、列によって取得したい情報のセルのインデックスがずれる。しかし、後ろから数えた場合のインデックスは変わらないので、Reverse() してから読んでやればいい。

f:id:daruyanagi:20150126104125p:plain

これで数日様子を見てみる。

WebMatrix: JR四国が止まったので、運行情報の取得プログラム作るのが捗った。

f:id:daruyanagi:20150125141900p:plain

密かに待ってました。人身事故ってのがちょっと胸痛むけれど。すみません。でも、これで運行に遅延が発生した時にどんなコードが吐かれるのかわかったやで。

遅れがないとき。

<div ID="delay_info">
    <div class="no_delay">◇現在、遅れ等の情報はありません。</div>
</div>

遅れがあるとき。

<div ID="delay_info">
    <p>◇現在、以下のエリアで影響が出ています(*:**更新)。</p>

    <h4>徳島線</h4>
    <h5>徳島線(牛島駅構内) 運転再開</h5>
    <p>徳島線では……<br>※高徳線の列車は……</p>
</div>

同じ路線で複数の遅延情報があるケースも考えられるだろうので、タグはこんな感じになるんだろう(と思う)。

p

h4
    h5
    p
    
    h5
    p
    :
h4
    h5
    p
    
    h5
    p
    :

というわけで、これをいい感じにデータオブジェクトに変換していくわけれだけれど、どうしよう。

とりあえず HtmlAgilityPack でやってみた。

public class DelayInfo
{
    public string Line { get; set; }
    public string Status { get; set; }
    public string Message { get; set; }
}

public static List<DelayInfo> GetJRShikoku()
{
    const string key = "jrshikoku";
    const string url = "http://www.jr-shikoku.co.jp/info/";

    // キャッシュを探す
    var result = WebCache.Get(key) as List<DelayInfo>;

    // キャッシュがヒットしなければ、情報の取得
    if (result == null)
    {
        using (var client = new WebClient())
        {
            // !!
            client.Encoding = Encoding.GetEncoding("Shift_JIS");

            var doc = new HtmlAgilityPack.HtmlDocument();
            var src = client.DownloadString(url);

            doc.LoadHtml(src);

            var nodes = doc.DocumentNode
                // div#delay_info タグを探す
                .SelectSingleNode(@"//div[@id=""delay_info""]")
                // 直下のノードを列挙
                .ChildNodes
                // 改行・コメントなどのノードは読み飛ばす
                .Where(_ => _.NodeType == HtmlNodeType.Element)
                // 最初の div#no_delay や p は読み飛ばす
                .Skip(1); 

            var line = string.Empty;
            var status = string.Empty;

            result = new List<DelayInfo>();

            foreach (var node in nodes)
            {
                switch (node.Name)
                {
                    case "h4": // 線名を記憶
                        line = node.InnerText;
                        continue;
                    case "h5": // "徳島線(牛島駅構内) 運転再開"を記憶
                        status = node.InnerText;
                        continue;
                    case "p":  // p タグを見つけたらデータをプッシュ
                        result.Add(new DelayInfo()
                        {
                            Line = line,
                            Status = status,
                            Message = node.InnerText,
                        }); 
                        break;
                    default:
                        // Do Nothing
                        break;
                }
            }
            
            // 遅延情報が見つからなかった場合、全線正常のデータをプッシュ
            if (result.Count == 0)
            {
                result.Add(new DelayInfo()
                {
                    Line = "全線",
                    Status = "正常運行",
                    Message = "現在通常通り運行しております。",
                });
            }
        }
    }

    return result;
}

f:id:daruyanagi:20150125143551p:plain

ビューでこのデータを適当にレンダリング。まぁ、まぁ、いい感じかもしれない。もっといろんなケースを見てみたかったけれど、とりあえずこれが正しいとして、Twitter BOT でも実装してみるかな。