AtCoder頑張りたい

AtCoder頑張ってますか?私は頑張っていますがその努力は実っていません。

AtCoderはすばらしい競技プログラミングですが、開催が不定期なのが唯一の不満です。不定期だと次の予定がいつなのか忘れてしまうんです。でも毎回問題を考えるのはとても大変でしょうから、しょうがないですよね。

こちらの努力でなんとかしましょう。具体的にはAtCoderの開催予定はTwitterで定型文を使って告知されるので、ここから情報収集して勝手にGoogleカレンダーに登録すればよさそうです。
調べたらTwitterで検索した結果を自動でLINEに投稿している方がいました。

定期的に Twitter で特定の内容を検索して通知してくれる Bot を作った話(前編)

なるほどるほど。Google Apps Scriptを使うと実現できるみたいです。あとは以下のようにカスタマイズすればよさそうですね。

  • 検索は特定のアカウント(@atcoder)のみを対象にする
  • 検索ワードは"AtCoder Beginner Contest"と"【コンテスト開催のお知らせ】"とする(ABC以外無理だから)
  • 検索期間は7日(ツイッターAPIのデフォルト)
  • みつかったツイートの日時とABCの回を正規表現を利用して取得し、それがGoogleカレンダーに既に登録されていなければ登録する(開始時間は21時で固定とする。面倒なので)
  • 以上を1日1回実行する

そういうことで書いていきます。前提として、Twitter APIの各種キーの取得と、CalendarApp.getDefaultCalendar()で自分の登録したいGoogleカレンダーがgetできるようにしておきます。


function getToken() {
  const apiKey = "XXX";
  const apiSecretKey = "XXX";
  const credential = Utilities.base64Encode(apiKey + ':' + apiSecretKey);
  const options = {
    "method": "post",
    "contentType": "application/x-www-form-urlencoded;charset=UTF-8",
    "headers": { "Authorization" : "Basic " + credential },
    "payload": { "grant_type": "client_credentials" }
  };
  const uri = "https://api.twitter.com/oauth2/token";
  const res = JSON.parse(UrlFetchApp.fetch(uri, options));
  return res.access_token;
}

function searchTwitter(){
  const options = {
    "method": "get",
    "headers": { "Authorization" : "Bearer " + getToken()}
  }
  const q = encodeURIComponent('from:atcoder "AtCoder Beginner Contest" "【コンテスト開催のお知らせ】"');
  const uri = "https://api.twitter.com/1.1/search/tweets.json?q=" + q + "&result_type=recent&tweet_mode=extended";
  const res = JSON.parse(UrlFetchApp.fetch(uri, options));
  var texts = [];
  res.statuses.forEach(function(status) {
    texts.push(status.full_text);
  });
  return texts;
}

function extractTwitter(text){
  const abc_date = text.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/gi)[0];
  const abc_name = text.match(/AtCoder Beginner Contest [0-9]{3}/gi)[0];
  const abc_year = abc_date.split("-")[0];
  const abc_month = abc_date.split("-")[1];
  const abc_day = abc_date.split("-")[2];
  const abc_data = {
          "name": abc_name,
          "year":abc_year,
          "month":abc_month,
          "day":abc_day};
  return abc_data;
}

function check_calendar(abc_data, calendar, startTime) {
  var events = calendar.getEventsForDay(startTime);
  var titlelist = events.map(value => value.getTitle());
  var ans = titlelist.some(value => value==abc_data.name);
  return ans
}

function regist_to_calendar(abc_data) {
  if(Object.keys(abc_data).length){
    const calendar = CalendarApp.getDefaultCalendar();
    const title = abc_data.name;
    const startTime = new Date(abc_data.year, abc_data.month-1, abc_data.day, 21, 0, 0);
    const endTime = new Date(abc_data.year, abc_data.month-1, abc_data.day, 22, 0, 0);
    // monthは0-index
    if (!check_calendar(abc_data, calendar, startTime)){
      calendar.createEvent(title, startTime, endTime);
    }
  }
}

function main(){
  getToken()
  const texts = searchTwitter()
  if (texts.length) {
    texts.map(function(text){
      abc_data = extractTwitter(text);
      regist_to_calendar(abc_data);
      })
  }
}

このままだとタイムゾーンがずれていてめちゃくちゃになってしまうので、プロジェクトの設定から『「appsscript.json」マニフェスト ファイルをエディタで表示する』にチェックを入れて、現れたappsscript.jsonを以下のように変更します(参考:Google Apps Script タイムゾーンの設定
)。


{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

あとはmain関数をトリガーで実行します。

Twitterに投稿があった翌日、こんな感じでイベントが登録されました。

file

できた!通知もデフォルトでつきます。
私は無事ゲームに集中しすぎて作った翌週の回に参加し忘れましたが、これで皆さんはAtCoderを忘れることはありませんね。


Header photo by Estée Janssens on Unsplash