MATLAB タグ付きの新着投稿をお知らせする Twitter bot (Powered by ThingSpeak)


やったこと

MATLAB の新着記事無いかな~と Qiita をチェックする回数を減らすために、MATLAB タグのついた記事を自動でお知らせする bot を作ったのでコード公開します。

アカウントはこちら -> @Qiita_MATLAB

本投稿のポイント

タイムゾーンを加味した処理が楽な datetime 型変数の TimeZone プロパティが見どころです。

簡単な例だと、

t1 = datetime();
t1.TimeZone = 'Asia/Shanghai'
t2 = t1;
t2.TimeZone = 'Europe/London'

と実行すると

t1 = 
  datetime
   2019/10/26 07:00:14
t2 = 
  datetime
   2019/10/26 00:00:14

表示は異なりますが、以下のように時間情報としては同値です。

>> isequal(t1,t2)
ans =
  logical
   1

関連記事、ページ

ThingSpeak 自体や、Twitter で呟くのに必要な設定やコードは以下で紹介していますので、良ければ参考にしてください。

Qiita: ThingSpeak x MATLAB で Twitter bot 実装

実際の Channel は こちら で確認できますが、一応以下の2つの情報もトラックしています。

  • total_items: MATLAB タグが付いた記事の合計数
  • total_followers: MATLAB タグのフォロワー数

本題の MATLAB Analysis

コード全文は最後に纏めて再掲しますので、ここでは要所だけ解説します。ThingSpeak の Time Control 設定によって1時間に1回実行され、

  • 新着チェック
  • 新着情報を Tweet

する仕組みとしています。

まず、Qiita API を使って最新20件のリストを作ります。


% ここから新着記事のチェック(最新20個取ればOKでしょう・・)
url = "https://qiita.com/api/v2/tags/matlab/items?page=1&per_page=20";
tmp = webread(url,opts);

% 投稿時刻をチェック(TimeZone は日本時間に設定)
created_at = datetime(vertcat(tmp.created_at),...
    'InputFormat', "uuuu-MM-dd'T'HH:mm:ss'+09:00",'TimeZone','Asia/Tokyo');
titles = string({tmp.title}');
urls = string({tmp.url}');
item_list = timetable(titles, urls, 'RowTimes', created_at,...
    'VariableNames',{'titles', 'urls'})

この時、時間情報は TimeZone, Asia/Tokyo と設定して timetable 型変数:item_list としてまとめます。

現時刻から1時間以内に投稿されていればそれは新着記事。現時刻から過去1時間の間に投稿されたものだけを、item_list から抜き出します。

% 新着かどうかのチェック
% このスクリプトは 1時間に1回実行する設定にします。(ThingSpeak の Time Control 設定)
interval = duration(1,0,0);
tnow = datetime;
% ThingSpeak が動いているところでは TimeZone が UTC であるところに注意
tnow.TimeZone = 'UTC'; 
trange = timerange(tnow-interval,tnow) % 過去一時間以内の投稿だけを抽出
newitem_list = item_list(trange,:);

trange 変数につかう時間情報は TimeZone, UTC に設定しています。

まとめ

ThingSpeak を使って @Qiita_MATLAB bot を作りました。
ThingSpeak 上の時間設定は UTC ですが、Qiita API からとれる投稿時間は JST ということで時間情報に時差がありましたが、datetime 型変数の TimeZone プロパティで対処しています。

MATLAB タグのついた投稿頻度はまだ高くないので、bot としては少し寂しいですね。。こんなこと呟いてみたら?など提案ありましたら何なりと。

MATLAB コード全文

各 APIKey だけ設定すればそのまま機能するはず。

% アクセストークン使用
% accessToken = 'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
% opts = weboptions('HeaderFields',{'Authorization',accessToken});
% アクセストークン使用しない場合
opts = weboptions;


% ThingSpeak ログ用:記事合計数、フォロワー数の取得
url = "https://qiita.com/api/v2/tags/matlab";
tmp = webread(url,opts);
items_count = tmp.items_count;
followers_count = tmp.followers_count;

channelID = 890134;
writeAPIKey = 'XXXXXXXXXXXXXXXX'; % それぞれの Channel に固有の APIKey
readAPIKey = 'XXXXXXXXXXXXXXXX'; % それぞれの Channel に固有の APIKey
% Fields:
%       1: total_items
%       2: total_followers

% Channel に情報送信
data = {items_count, followers_count};
thingSpeakWrite(channelID, data , 'WriteKey', writeAPIKey);


% ここから新着記事のチェック(最新20個取ればOKでしょう・・)
url = "https://qiita.com/api/v2/tags/matlab/items?page=1&per_page=20";
tmp = webread(url,opts);

% 投稿時刻をチェック(TimeZone は日本時間に設定)
created_at = datetime(vertcat(tmp.created_at),...
    'InputFormat', "uuuu-MM-dd'T'HH:mm:ss'+09:00",'TimeZone','Asia/Tokyo');
titles = string({tmp.title}');
urls = string({tmp.url}');
item_list = timetable(titles, urls, 'RowTimes', created_at,...
    'VariableNames',{'titles', 'urls'})

% 新着かどうかのチェック
% このスクリプトは 1時間に1回実行する設定にします。(ThingSpeak の Time Control 設定)
% なので、、現時刻から1時間以内に投稿されていればそれは新着記事とします。
interval = duration(1,0,0);
tnow = datetime;
% ThingSpeak が動いているところでは TimeZone が UTC であるところに注意
tnow.TimeZone = 'UTC'; 
trange = timerange(tnow-interval,tnow) % 過去一時間以内の投稿だけを抽出
newitem_list = item_list(trange,:);


% ThingTweet 設定
tturl='https://api.thingspeak.com/apps/thingtweet/1/statuses/update';
api_key = 'XXXXXXXXXXXXXX'; % Twitterをリンクして APIKey を取得してください。
options = weboptions('MediaType','application/x-www-form-urlencoded');
options.Timeout = 10;

tweetFlag = true;
% 新着の数だけ呟きます(無ければ呟かない)
for ii=1:height(newitem_list)

    % 投稿文
    status = "#Qiita #MATLAB" + newline;
    status = status + newitem_list.titles(ii);
    status = status + newitem_list.urls(ii);
    disp(status);

    if tweetFlag
        try
            webwrite(tturl, 'api_key', api_key, 'status', status, options);
        catch ME
            disp(ME)
        end
    end
end