緊急地震速報の生電文を受信しよう

日本は地震が多いですね。その割には情報を入手する手段が少ない気がします。
テレビでは震度5弱以上予想時のみ提供されますが、震度4以下の場合には事後の提供のみです。
提供してもらえないなら自分で入手しましょう。
（毎回 Qiita 記事の頭の文章が適当になってしまう）

なお、記事の最後に `わざわざ自分で電文を受信して解析する必要がない方法` も紹介します。

# 免責事項
**筆者は現在この記事で紹介する方法は `使用していません` 。今後使えなくなっても責任は負いかねます。
参考にする場合は自己責任でお願いします。**

# 環境
- 安定したネットワーク環境 (1Mbps 以下でも接続を維持できればOK)
- C# 開発環境
- Windows 10
- WeatherNews.jp 有料会員

# 流れをざっくりと説明
```
気象庁 -> WeatherNews.jp -> 利用者
```

WeatherNews.jp -> 利用者 の間は TCPSocket を使用します。

# プログラム
簡単にサンプルで作成したプログラムを載せます。自由に改変や修正を加えてください。
途中の正規表現は即席なのでもっと良いパターンがあればコメントでお願いします。
また、 WNI が緊急地震速報を送信するサーバーの IP アドレスは [http://lst10s-sp.wni.co.jp/server_list.txt](http://lst10s-sp.wni.co.jp/server_list.txt) に記述されていますが、どれでもOKです。
選択するサーバーによっては遅延がかなり酷く、平気で1分以上遅れたりする場合があります。
~~なのでサーバーガチャで勝利してください。~~
「ここ速かったよ！」とかありましたらコメントでお願いします。（使っていないけど）

あ、死活管理は適宜実装してください。

```csharp:WniClient.cs
public class WniClient : IDisposable {
    private Socket _socket;
    private const int BufferSize = 1024;
    private readonly byte[] _buffer = new byte[BufferSize];

    public async Task ConnectAsync(string mailAddress, string passWord)
    {
        // パスワードを MD5 に変換
        var passWordByte = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(passWord));

        // MD5 に変換すると byte[] になるため string に変換
        var passWordStr = BitConverter.ToString(passWordByte).ToLower().Replace("-", "");

        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _socket.Connect(new IPEndPoint(IPAddress.Parse(await GetServerIpAsync()), 80));
        // 認証情報を送信
        _socket.Send(new UTF8Encoding().GetBytes($"GET /login HTTP/1.1\r\nX-WNI-Account: {mailAddress}\r\nX-WNI-Password: {passWordStr}\r\n\r\n"));
        // データ受信時にイベント発火
        _socket.BeginReceive(_buffer, 0, BufferSize, SocketFlags.None, ReceiveCallback, _socket);
    }

    private static async Task<string> GetServerIpAsync()
    {
        using var c = new HttpClient();
        var servers = (await c.GetStringAsync("http://lst10s-sp.wni.co.jp/server_list.txt").ConfigureAwait(false)).Split('\n');
        var rdm = new Random();
        var server = servers[rdm.Next(0, servers.Length - 1)];
        return server.Split(':')[0];
    }

    public void Disconnect() => _socket.Disconnect(false);

    public void Dispose() => _socket.Close();

    private void ReceiveCallback(IAsyncResult asyncResult)
    {
        var socket = (Socket)asyncResult.AsyncState;
        if (socket == null) return;

        int byteSize;
        try
        {
            byteSize = socket.EndReceive(asyncResult);
        }
        catch (Exception ex)
        {
            OnException?.Invoke(ex);
            return;
        }

        if (byteSize < 1) return;
        var res = Encoding.UTF8.GetString(_buffer, 0, byteSize);

        if (res.Contains("X-WNI-ID: Data"))
        {
            res = new string(res.Replace("\n", " ").Replace("\r", " ").Replace("  ", " ").Where(x => !char.IsControl(x)).ToArray());
            var mc = new Regex("3([0-9/]{1}) ([0-9/]{2}) ([0-9/]{2}) ([0-9/]{12}) C([0-9/]{2}) ([0-9/]{12}) ND([0-9/]{14}) NCN([0-9A-Z/]{3}) JD([0-9/]{14}) JN([0-9/]{3}) ([0-9/]{3}) ([N0-9/]{4}) ([E0-9/]{5}) ([0-9/]{3}) ([0-9/]{2}) ([0-9/+-]{2}) RK([0-9/]{5}) RT([0-9/]{5}) RC([0-9/]{5}) (.*?)9999=", RegexOptions.None).Matches(res);
            foreach (Match m in mc) OnReceiveData?.Invoke(m.Value.Trim());
        }

        socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, socket);
    }

    public delegate void ReceiveEventHandler(string receiveData);

    public event ReceiveEventHandler OnReceiveData;

    public delegate void OnExceptionEventHandler(Exception exception);

    public event OnExceptionEventHandler OnException;
}
```

```csharp:Main.cs
private static async Task Main()
{
    using var client = new WniClient();
    // 受信したら OnReceiveData が発火し、電文が渡される
    client.OnReceiveData += Console.WriteLine;
    await client.ConnectAsync("[メールアドレス]", "[パスワード]");
    Console.ReadLine();
    client.Disconnect();
}
```

# まとめ
`The Last 10-Second` で使用されている緊急地震速報配信サーバーを利用する方法を紹介しました。
とにかく遅延が酷いです。調子がいい時は3秒遅れ程度や遅延無しで配信されます。
速いサーバーを見つけた方は共有しましょう。

# 楽な方法
手前味噌ですが [https://api.iedred7584.com/eew/](https://api.iedred7584.com/eew/) を使ってください。（宣伝）