1.21 jigowatts

Great Scott!

C# Twitterの自動リプライ機能を作ってみた

概要

一年ほど前に定時に天気をツイートするBOTを作成しましたが、自動で返信してくれる機能が欲しくなったので作ってみました。ただ今回は想定してた以上に難しかった…。サンプルコードを検索しては試してみるの連続で未だに理解はしてません。きっと詳しい人が教えてくれるに違いない(チラチラ

環境

Microsoft Visual Studio Community 2015

ライブラリのインストール

パッケージマネージャよりReactive ExtensionsとCoreTweetをインストールします。
最初Reactive Extensionsがなんだかよくわかってなくて(今でも)、ライブラリをインストールしていなかったせいでサンプルが動かないっていうね。

PM> Install-Package CoreTweet
PM> Install-Package System.Reactive.Linq

実装

コンソールアプリです。前回と一緒でAPIキーはexe実行時の引数で渡してますがこの辺はどうでもいいですね。

■Program.cs

using CoreTweet;
using System;

namespace AutoReplyBot
{
    class Program
    {
        static void Main(string[] args)
        {
            var APIKey = args[0];
            var APISecret = args[1];
            var AccessToken = args[2];
            var AccessTokenSecret = args[3];
            try
            {
                var t = Tokens.Create(APIKey
                    , APISecret
                    , AccessToken
                    , AccessTokenSecret);
                var replyer = new Replyer(t);
                replyer.Observe();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex}");
                throw;
            }

            Console.WriteLine($"[{DateTime.Now.ToString()}] 処理開始");
            Console.ReadKey(true);
        }
    }
}

ストリーミングAPI

やりたいことは、タイムラインに流れてくるツイートたちを受信します。そんで、自分宛のリプライに反応して後はオシャレな一言を返すだけ。
素晴らしいサンプルコードからふんいきだけを感じ取りました。一応動きます。ストリーミングAPIによってツイートを監視してて、データがやってきたらNextメソッドに渡してリプライのパターンマッチングを行う。やってることは単純ですね。
絶えずツイートの監視状態となってますが、コンソールはReadKeyの状態となっているので何かキーを叩くとアプリは終了となります。
■Replyer.cs

using CoreTweet;
using CoreTweet.Streaming;
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Text.RegularExpressions;

namespace AutoReplyBot
{
    public class Replyer
    {
        public Tokens Tokens { get; private set; }
        public Replyer(Tokens tokens)
        {
            Tokens = tokens;
        }

        public void Observe()
        {
            var observable = Tokens.Streaming.UserAsObservable();

            observable.Catch(
                    observable.DelaySubscription(
                        TimeSpan.FromSeconds(10)
                        ).Retry()
                )
                .Repeat()
                .Where((StreamingMessage m) => m.Type == MessageType.Create)
                .Cast<StatusMessage>()
                .Select((StatusMessage m) => m.Status)
                .Subscribe(
                    Next,
                    (Exception ex) => Console.WriteLine(ex),
                    () => Console.WriteLine("終点")
                );
        }

        public void Next(Status status)
        {
            var createdAt = status.CreatedAt.LocalDateTime;
            var screenName = status.User.ScreenName;
            var text = status.Text;
            var id = status.Id;

            Console.WriteLine($"[{createdAt}] {screenName}: {text}");

            var user = "USER_NAME";
            // どんなキーワードに反応するか決める
            var pattern = $@"@{user}\s.*Gigawatts.*";

            if (Regex.IsMatch(text, pattern))
            {
                // 素敵な一言を自動生成するのが本番
                var reply = $@"@{screenName} Jigowatts!";

                Console.WriteLine($"{reply}");
                // りぷ
                Tokens.Statuses.Update(
                    new
                    {
                        status = reply,
                        in_reply_to_status_id = id
                    });
            }
        }
    }
}

所感

とにかくReactive Extensions何それスゴイわからない!得体のしれない、なにかトンデモナイものに触れてしまったんでしょう。何がどうなっているのかはこれから少しずつ調べて理解していこう。そうしよう。
がんばれ、明日のジブン。