『3月のライオン(1)~(12)』を読んだ

BUMP OF CHICKEN が『ファイター』でタイアップして、『3月のライオン』のことを知った。 そして、『アンサー』で再びタイアップしたので、これをいい機会に 1巻から12巻までを Kindle でまとめ買いして読んだ。

読む前は失礼ながら、競技が違うけど『ヒカルの碁』みたいなマンガかなと思っていた。実際に読んでみると、まったく違った。将棋マンガではあるが、むしろ将棋を題材にした人間ドラマを描いたマンガに思えた。

主人公の桐山零は物語開始時点ですでに、中学でプロ入りした天才棋士という周りの評価。ただメンタルに難があり、そのせいで伸び悩んでいる状態。物語内で明かされていく彼の半生からすると、それも仕方ないことではある。 なので、将棋の技量的な成長よりも、人間的な成長に重きを置いて物語は進んでいく。

零の人間的な成長に深く関わってくるのが川本家。3姉妹と祖父だけというワケありの家族なのだが、皆まっすぐで、そしてすごく温かい。 子供がいる身としては、特にひなたやモモみたいに健やかに育って欲しいと、かなり思った。

最近の零は、ひなたに(心を?)救われてからというもの、その成長は目を見張るものがある。 このマンガに悪役ってほとんどいないんだけど、数少ない悪役・捨男(仮名)と舌戦を繰り広げる姿は頼もしい限りで、ここまでに成長するとは。

個人的なお気に入りは二階堂。 めっちゃ熱くていいヤツだった。 ライバルというより強敵とかいてトモと呼ぶタイプ。むしろ親友。 モモに並ぶ癒しキャラでもあると思ってる。 モモとのコンビは見ててホッコリする。

将棋の知識が無くても楽しめるマンガになっていた。 ヤングアニマルで連載中なので、連載の方も読んでみようかな。 ただ、ヤングアニマルはちと購入のハードルが高めの雑誌だ。 モーニングやアフタヌーンだったらなぁ。 13巻が待ち遠しい。

S3 からファイルをフォルダごとダウンロードする方法のメモ

S3 Management Console では、フォルダごとダウンロードできなかった。 2017/04/10 現在でも、まだ AWS CLI を使うしかない。

aws s3 cp --region ap-northeast-1 s3://バケット名/パス ダウンロード先 --recursive

指定した URL に付けられたすべてのブックマークを取得する

はてなブックマークネタ第3弾。 最後は、指定した URL に付けられたすべてのブックマークを取得してみた。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace HatenaBookmarkSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var bookmarks = GetAllBookmarks("http://hatenablog.com")
                .GetAwaiter().GetResult();

            foreach(var bookmark in bookmarks.Bookmarks)
            {
                Console.WriteLine($"{bookmark.User} : {bookmark.Comment}");
            }

            Console.WriteLine("Enter で終了します。");
            Console.ReadLine();
        }

        static async Task<EntryBookmarks> GetPopularBookmarks(string url)
        {
            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders
                .Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // User-Agent を設定しないと nginx にはじかれる
            httpClient.DefaultRequestHeaders
                .UserAgent.Add(new ProductInfoHeaderValue("HatenaClient", "0.0.1"));

            var requestUri = $"http://b.hatena.ne.jp/api/viewer.popular_bookmarks?url={url}";

            var json = await httpClient.GetStringAsync(requestUri);

            var bookmarks = JsonConvert.DeserializeObject<EntryBookmarks>(json);

            return bookmarks;
        }

        static async Task<EntryBookmarks> GetAllBookmarks(string url)
        {
            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders
                .Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // User-Agent を設定しないと nginx にはじかれる
            httpClient.DefaultRequestHeaders
                .UserAgent.Add(new ProductInfoHeaderValue("HatenaClient", "0.0.1"));

            var requestUri = $"http://b.hatena.ne.jp/entry/jsonlite/?url={url}";

            var json = await httpClient.GetStringAsync(requestUri);

            var bookmarks = JsonConvert.DeserializeObject<EntryBookmarks>(json);

            return bookmarks;
        }
    }

    [JsonObject]
    public class EntryBookmark
    {
        /// <summary>
        /// ブックマーク ID を取得または設定します。
        /// </summary>
        [JsonProperty("eid")]
        public string Id { get; set; }

        /// <summary>
        /// タイトルを取得または設定します。
        /// </summary>
        [JsonProperty("title")]
        public string Title { get; set; }

        /// <summary>
        /// 記事の URL を取得または設定します。
        /// </summary>
        [JsonProperty("url")]
        public string Url { get; set; }

        /// <summary>
        /// ブックマークしたユーザーを取得または設定します。
        /// </summary>
        [JsonProperty("user")]
        public string User { get; set; }

        /// <summary>
        /// ブックマークコメントを取得または設定します。
        /// </summary>
        [JsonProperty("comment")]
        public string Comment { get; set; }

        /// <summary>
        /// ブックマークした時間を取得または設定します。
        /// </summary>
        [JsonProperty("timestamp")]
        public string Timestamp { get; set; }
    }

    [JsonObject]
    public class EntryBookmarks
    {
        /// <summary>
        /// ブックマーク件数を取得または設定します。
        /// </summary>
        [JsonProperty("count")]
        public int Count { get; set; }

        /// <summary>
        /// エントリの ID を取得または設定します。
        /// </summary>
        [JsonProperty("eid")]
        public string EntryId { get; set; }

        /// <summary>
        /// エントリのタイトルを取得または設定します。
        /// </summary>
        [JsonProperty("title")]
        public string Title { get; set; }

        /// <summary>
        /// エントリの URL を取得または設定します。
        /// </summary>
        [JsonProperty("entry_url")]
        public string EntryUrl { get; set; }

        /// <summary>
        /// ブックマークの URL を取得または設定します。
        /// </summary>
        [JsonProperty("url")]
        public string Url { get; set; }

        /// <summary>
        /// ブックマーク一覧を取得または設定します。
        /// </summary>
        [JsonProperty("bookmarks")]
        public List<EntryBookmark> Bookmarks { get; set; } = new List<EntryBookmark>();
    }
}

実行結果はこちら。

f:id:griefworker:20170405172557p:plain

はてなブックマークが提供する API は、REST APIAtom API や件数取得 API などなど、 たくさんあって分かりにくい。 REST API に一本化して、機能を充実してほしいところだ。

指定した URL の人気ブックマーク一覧を取得する

はてなブックマークネタ第2弾。 今度は、指定した URL に付けられたブックマークのうち、 人気のヤツを取得してみた。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace HatenaBookmarkSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var bookmarks = GetPopularBookmarks("http://hatenablog.com")
                .GetAwaiter().GetResult();

            foreach(var bookmark in bookmarks.Bookmarks)
            {
                Console.WriteLine($"{bookmark.User} : {bookmark.Comment}");
            }

            Console.WriteLine("Enter で終了します。");
            Console.ReadLine();
        }

        static async Task<EntryBookmarks> GetPopularBookmarks(string url)
        {
            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders
                .Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // User-Agent を設定しないと nginx にはじかれる
            httpClient.DefaultRequestHeaders
                .UserAgent.Add(new ProductInfoHeaderValue("HatenaClient", "0.0.1"));

            var requestUri = $"http://b.hatena.ne.jp/api/viewer.popular_bookmarks?url={url}";

            var json = await httpClient.GetStringAsync(requestUri);

            var bookmarks = JsonConvert.DeserializeObject<EntryBookmarks>(json);

            return bookmarks;
        }
    }

    [JsonObject]
    public class EntryBookmark
    {
        /// <summary>
        /// ブックマーク ID を取得または設定します。
        /// </summary>
        [JsonProperty("eid")]
        public string Id { get; set; }

        /// <summary>
        /// タイトルを取得または設定します。
        /// </summary>
        [JsonProperty("title")]
        public string Title { get; set; }

        /// <summary>
        /// 記事の URL を取得または設定します。
        /// </summary>
        [JsonProperty("url")]
        public string Url { get; set; }

        /// <summary>
        /// ブックマークしたユーザーを取得または設定します。
        /// </summary>
        [JsonProperty("user")]
        public string User { get; set; }

        /// <summary>
        /// ブックマークコメントを取得または設定します。
        /// </summary>
        [JsonProperty("comment")]
        public string Comment { get; set; }

        /// <summary>
        /// ブックマークした時間を取得または設定します。
        /// </summary>
        [JsonProperty("timestamp")]
        public string Timestamp { get; set; }
    }

    [JsonObject]
    public class EntryBookmarks
    {
        /// <summary>
        /// ブックマーク件数を取得または設定します。
        /// </summary>
        [JsonProperty("count")]
        public int Count { get; set; }

        /// <summary>
        /// エントリの ID を取得または設定します。
        /// </summary>
        [JsonProperty("eid")]
        public string EntryId { get; set; }

        /// <summary>
        /// エントリのタイトルを取得または設定します。
        /// </summary>
        [JsonProperty("title")]
        public string Title { get; set; }

        /// <summary>
        /// エントリの URL を取得または設定します。
        /// </summary>
        [JsonProperty("entry_url")]
        public string EntryUrl { get; set; }

        /// <summary>
        /// ブックマークの URL を取得または設定します。
        /// </summary>
        [JsonProperty("url")]
        public string Url { get; set; }

        /// <summary>
        /// ブックマーク一覧を取得または設定します。
        /// </summary>
        [JsonProperty("bookmarks")]
        public List<EntryBookmark> Bookmarks { get; set; } = new List<EntryBookmark>();
    }
}

実行結果はこちら。

f:id:griefworker:20170405164319p:plain

指定したユーザーのはてなブックマーク一覧を取得する

いいかげん、そろそろプログラミングネタを書かねば。 ってことで、C# で「指定したユーザーのはてなブックマーク一覧を取得する」サンプルを書いてみた。

はてなブックマークに専用の API が見当たらないので、RSS フィードを取得してパースしている。 オフセットも指定できるから、これはこれで XML を返す Web API と見れなくもない。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace HatenaBookmarkSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var bookmarks = GetUserBookmarks("griefworker", 100)
                .GetAwaiter().GetResult();

            foreach(var b in bookmarks)
            {
                Console.WriteLine(b.Title);
                Console.WriteLine(b.Url);
                Console.WriteLine(b.Comment);
                Console.WriteLine();
            }

            Console.WriteLine("Enter で終了します。");
            Console.ReadLine();
        }

        static async Task<IEnumerable<Bookmark>> GetUserBookmarks(string userId, int offset = 0)
        {
            var httpClient = new HttpClient();

            // RSS フィードなのでレスポンスは XML
            httpClient.DefaultRequestHeaders
                .Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));

            // User-Agent を設定しないと nginx にはじかれる
            httpClient.DefaultRequestHeaders
                .UserAgent.Add(new ProductInfoHeaderValue("HatenaClient", "0.0.1"));

            var requestUri = $"http://b.hatena.ne.jp/{userId}/rss";
            if (0 < offset)
            {
                requestUri += $"?of={offset}";
            }

            var rss = await httpClient.GetStringAsync(requestUri);

            var document = XDocument.Parse(rss);

            var bookmarks = document.Descendants()
                .Where(e => e.Name.LocalName == "item")
                .Select(ToBookmark)
                .ToList();

            return bookmarks;
        }

        static Bookmark ToBookmark(XElement element)
        {
            var bookmark = new Bookmark();

            foreach(var e in element.Descendants())
            {
                switch (e.Name.LocalName)
                {
                    case "title":
                        bookmark.Title = e.Value;
                        break;
                    case "link":
                        bookmark.Url = e.Value;
                        break;
                    case "description":
                        bookmark.Comment = e.Value;
                        break;
                }
            }

            return bookmark;
        }
    }

    class Bookmark
    {
        public string Title { get; set; }

        public string Url { get; set; }

        public string Comment { get; set; }
    }
}

実行結果は次の通り。

f:id:griefworker:20170405153751p:plain

はてなブックマークREST API で用意してくれたらいいんだけどな。

うえすたん 木の葉モール店

警固に本店がある『うえすたん』の木の葉モール店がオープンしていたので行ってみた。

注文したのはサイコロステーキとハンバーグのコンボ。サイコロステーキが一番人気だけど、でもハンバーグも気になる、そんな自分にぴったり。

サイコロステーキは予想以上の柔らかさで、ハンバーグを食べたかと間違うほどだった。こいつは旨いな。コンビの80グラムではとても食べ足りない。ハンバーグはしっかりと練られていて口当たりが良かった。でも、ビフテキ屋なんだから、もっと肉肉しくていいのに。

サイコロステーキ気に入った。今度からコンビではなく、サイコロステーキ 200 グラムを注文することにしよう。

うえすたん 木の葉モール店

食べログうえすたん 木の葉モール店

『からかい上手の高木さん(1)〜(5)』を読んだ

ネットでたびたび話題になった、『ゲッサン』で連載中の『からかい上手の高木さん』をKindleでまとめ買いして読んだ。

西片くんが主人公なのかな。その西片くんが、となりの席の高木さんにひたすらからかわれ続ける。簡単にいうとそんなマンガ。やられっぱなしではいられないと、西片くんは何度も仕返ししようとするが、高木さんの方が何枚も上手で結局からかわれてしまう。好きな女子にちょっかいを出す男子というのはあるけど、このマンガはその逆を行っている。そんな高木さんに大人は悶えてしまう。

西片くんが高木さんに勝つ日は訪れるのだろうか。最新の5巻では西片くんのクリティカルがあった。ああいうのがもう少しあってもいいと思う。ただ、西片くんは勝利よりもからかわれることを読者に望まれてるだろうから、最後まで勝利は難しいかもしれない。

このマンガをラブコメに分類することに違和感ある。2人が結ばれるかどうかは気にならない。むしろ、今の距離感の2人の話をずっと読んでいたい。まぁ、5巻の最初、大人になった高木さんが出てくる衝撃回で、未来の2人がどうなっているのかについて、一応の正史がわかったからというのもあるが。まさか読者すらもからかうとは。恐れ入った。

今後も高木さんに目が離せそうにない。