12.31.2012

Scala: How to Create Countdown Twitter Bot

Scala: カウントダウンを行う Twitter ボットの作成方法

2ヶ月ほど前に、予備知識ゼロの状態から bot を作ってみた。その時のメモ。

 

使用したテクノロジー、サービス

 

1. TwitterAPI の設定

まずは、Twitter 側の環境セットアップから。

 

1.1 アカウント開設

bot 用の新規アカウントを作成する。

ユニークなメールアドレスの登録が必要だが、gmail のアカウントを持っているなら
ドットの位置を変えたり、エイリアスを付けることで同じメールアカウントを共有できるので便利だ。 

https://support.google.com/mail/bin/answer.py?hl=en&answer=10313
http://support.google.com/mail/bin/answer.py?hl=en&answer=12096

 

1.2 認証用トークンの取得

TwitterAPIに接続するためには、OAuth認証を行う必要がある。そのための準備。

  • 以下のURLを開く
    https://dev.twitter.com/apps/new
  • 先ほど作成したアカウントでサインイン
  • 必要な情報を入力して Create your Twitter application を押下Create an application | Twitter Developers 2
    • Application Details -> Name: ユニークなアプリケーション名 (32文字以内)
          ツイートの「○○から」(via ○○) の部分に使用される。日本語可。 
    • Application Details -> Description:  アプリケーションの説明 (10文字〜200文字)
    • Application Details -> Website:  WebサイトのURL
          ツイートの 「○○から」(via ○○) のリンクとして使用される。
    • Application Details -> Callback URL:  コールバックURL
          ブラウザアプリケーションの場合は必要だが、今回は
          クライアントアプリケーションなので ブランクのままとする。
    • Developer Rules of the Road: (規約を読んでから) Yes, I agree にチェックする
    • CAPTCHA: 画像にかかれた文字を入力
  • アプリケーションの登録に成功すると、以下のような画面が表示される。
     m
  • OAuth アクセス権限の変更
    デフォルトでは Read only となっているため、このままではツイートの作成ができない。 
    Settings タブ -> Application Type -> Access を Read and Write に変更し、
    Update this Twitter application's settings ボタンを押下。
  • アクセストークンの取得
    Detail タブに戻り、Your access token -> Create my access token ボタンを押下。
    Access level が Read and Write と表示されていることを確認。
  • 以下の4種類のパラメータを、後ほどアプリケーションから指定することとなる。
    • OAuth settings -> Consumer key
    • OAuth settings -> Consumer secret
    • Your access token -> Access token
    • Your access token -> Access token secret

 

2. 開発環境の準備

このチュートリアルに従って、環境のセットアップを行う。
https://devcenter.heroku.com/articles/scala 

 

2.1 Scala のインストール

公式サイト、パッケージ管理システムなどからパッケージをダウンロードし、インストール。
詳細は割愛する。
http://www.scala-lang.org/downloads

 

2.2 sbt のインストール

こちらも公式サイト、パッケージ管理システムなどからパッケージをダウンロードし、インストールする。

ただし、必ず heroku でサポートされているバージョンを導入すること。
https://devcenter.heroku.com/articles/scala-support#build-behavior 

手動インストールを行う場合は、jar ファイルと起動スクリプトを設置すればよい。
http://www.scala-sbt.org/release/docs/Getting-Started/Setup.html 

 

2.3 JDKの準備

OpenJDK バージョン6 上で動作させること。

 

2.4 heroku アカウントの作成

下記のページからメールアドレスを登録するだけで heroku を利用できる。
https://api.heroku.com/signup

随所に見られる日本へのリスペクトを感じさせてくれるデザインが嬉しい。
 Heroku | Login

 2.5 heroku toolbelt のインストール

下記のURLから heroku toolbelt をインストールする。
https://toolbelt.heroku.com/

heroku toolbelt とは、以下の3つのツールが同梱されたパッケージである。

  • Heroku client: heroku 管理用のCLIツール
  • Foreman: Procfile ベースのプロセス管理ツール
  • Git

 

3. sbtプロジェクトの作成

sbt 用のディレクトリおよび設定ファイルの作成を行う。

 

3.1 ディレクトリの作成

以下のような構成でディレクトリを作成する。

プロジェクトディレクトリ
├── project
└── src
    └── main
        └── scala

 

3.2 設定ファイルの作成

 

4. プログラム作成

いよいよプログラム本体の作成に入る。

 

4.1 ソースコードの作成

プロジェクトディレクトリ/src/main/scala 配下に Scalaプログラムのソースを作成する。

色々と改善の余地はあると思うが、できたものはこんな感じ。
https://github.com/mogproject/scalaconfjp-countdown/blob/master/src/main/scala/ScalaConfJPCountDown.scala

仕様

  • OAuth認証のパラメータは全て環境変数に設定することとし、tweet() という関数を定義。
  • java の Calendar クラスを利用して現在の日時を取得。
    カウントダウンの目的の日との日数の差を求め、その値によって異なるメッセージをツイートする仕様。 

注意点

  • heroku の日時は UTC で得られるので、それを前提に実行時間の計算ロジックを作ること
  • TwitterAPI の制約に注意
    • 同一のメッセージは "Status is a duplicate." エラーとなり、受け付けられない
    • レート制限 (TwitterAPI v1.0 では、OAuth接続の場合 350コール/時)
      https://dev.twitter.com/docs/rate-limiting 

 

4.2 ローカル環境変数の設定

プログラム中で参照するOAuth認証のための環境変数をローカルマシン上で設定する。 

  • CONSUMER_KEY
  • CONSUMER_SECRET
  • ACCESS_TOKEN
  • ACCESS_TOKEN_SECRET

 

4.3 sbt 実行テスト

この段階でローカルで sbt からプログラムを実行できるようになる。

プロジェクトディレクトリにて、以下のコマンドを実行する。

  • ビルド
    初回はダウンロードなどが行われるので時間がかかる。
    $ sbt clean compile stage
  • 実行
    $ sbt run

 

5. foreman の設定

プロジェクトディレクトリ直下に Procfile というファイルを作成する。

本来はWebサービスなどのプロセスを指定するのだが、今回の用途はバッチ処理のみなので
行頭に # を付けて内容をコメントアウトする。

(例) https://github.com/mogproject/scalaconfjp-countdown/blob/master/Procfile 

 

6. git リポジトリの作成

heroku を利用する前に、ローカルに git リポジトリを作成する。

 

6.1 .gitignore ファイルの作成

プロジェクトディレクトリ直下に .gitignore ファイルを作成し、git の管理対象としないファイルの定義を行う。

ひとまずこんな感じで指定。(おそらく管理対象外とすべきファイルは他にもあると思われる)
https://github.com/mogproject/scalaconfjp-countdown/blob/master/.gitignore 

最終的に、ファイル構成はこのようになった。

プロジェクトディレクトリ
├── .gitignore
├── Procfile
├── build.sbt
├── project
│   ├── build.properties
│   └── plugins.sbt
└── src
    └── main
        └── scala
            └── (クラス名).scala

 

6.2 リポジトリの作成

プロジェクトディレクトリ直下で以下のコマンドを実行し、最初のコミットを行う。

$ git init
$ git add .
$ git commit -m "init"

 

7. heroku アプリケーションの作成

プログラムを heroku へデプロイし、スケジュール実行の設定を行う。

 

7.1 アプリケーションの新規作成

プロジェクトディレクトリ直下で以下のコマンドを実行する。

$ heroku login
Enter your Heroku credentials.
Email:
Password (typing will be hidden):
Authentication successful.
$ heroku create

アプリケーションの名前は一意のものがランダムに設定される。(後で変更可能)

 

7.2 資源のデプロイ

リモートリポジトリ heroku は自動的に設定されているため、以下のコマンドを行うだけでよい。

$ git push heroku master

 

7.3 heroku 環境変数の設定

heroku 上での環境変数を設定するため、heroku config:add コマンドを実行する。

(実行例)

$ git config:add CONSUMER_KEY=xxxxxxxxxxxxxxxxxxxxxx
$ git config:add CONSUMER_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ git config:add ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ git config:add ACCESS_TOKEN_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

7.4 手動実行テスト

ここで、heroku 上でのプログラム実行テストが可能となる。
heroku run コマンドを行えばよい。

$ heroku run 'target/start (クラス名)'

 

7.5 スケジュール設定

heroku のアドオンである Heroku Scheduler を利用する。

heroku dashboard (heroku のサイトへログインした後の画面) から、Add-ons をクリックすれば
アドオンの選択画面に入る。

Utilities -> Heroku Scheduler を選択し、登録を行う。 

Heroku | Add ons

Heroku Scheduler 自体は無料のアドオンであるが、この時クレジットカードの登録を要求される。

登録が終わったら heroku dashboard に戻り
Apps -> 今回作成したアプリケーション -> Resources -> Add-ons -> Heroku Scheduler Standard と選択していく。

Add job… ボタンを押下し、
コマンド(target/start (クラス名))、頻度(10分おき/1時間おき/1日おき)、実行時間
を選択して Save すればスケジュール設定完了。

時間の指定は UTC となる。

 

8. heroku の運用

運用中の heroku コマンド。実行時にはログインが必要である。 
プロジェクトディレクトリ直下でコマンドを実行すれば、アプリケーションの指定(--app)を省略できる。

 

8.1 アプリケーション一覧の参照
$ heroku apps
=== My Apps
xxx
yyy
zzz

 

8.2 ログの参照
$ heroku logs 

 

8.3 プロセス状態の参照
$ heroku ps 

今回のプログラムに関しては、Webサービスではないので常駐プロセスは存在しない。

 

8.4 コンソールの操作

bash

$ heroku run bash

sbt コンソール

$ heroku run sbt console

 

 

References

作ったもの
https://twitter.com/scalaconfjp_cd

ソース置き場
https://github.com/mogproject/scalaconfjp-countdown 

12.26.2012

JScript: Run External Commands Asynchroniously

JScript: 外部コマンドを並列実行する

コマンド(文字列)の配列をパラメータに取り、指定されたコマンドを並列実行し、
各コマンドのリターンコードを配列として返す関数。

コマンドが見つからない場合は –1 を返す。

var executeAsyncCommands = function(commands) {
  var n = commands.length;
  var processes = new Array(n);
  var returns = new Array(n);
  var success = new Array(n);
  var shell = new ActiveXObject("WScript.Shell");

  for (var i = 0; i < n; ++i) {
    try {
      processes[i] = shell.Exec(commands[i]);
      success[i] = true;
    }
    catch(e) {
      success[i] = false;
    }
  }

  L: while (true) {
    WScript.Sleep(100);
    for (i = 0; i < n; ++i) {
      if (success[i] && processes[i].Status == 0) continue L;
    }
    break;
  }

  for (var i = 0; i < n; ++i) {
    returns[i] = success[i] ? processes[i].ExitCode : -1;
  }
  return returns;
};

12.24.2012

Eclipse: Convert Line Delimiters

Eclipse: 改行コードの変換

対象ファイルを選択した状態で、メニューから File -> Convert Line Delimiters To を選べばよい。

以下3種類から選択可能。

  • Windows (CRLF)
  • UNIX (LF)
  • MaxOS 9 (CR)

12.20.2012

Timeit in JavaScript

JavaScript で時間計測処理を実装する

関数をパラメータに取り、その所要時間(秒数)を出力する関数を定義する。
print はユーザ定義のメソッド。

var Timer = function() {};

Timer.timeit = function(description) {
  return function(func) {
    var start = (new Date).getTime();
    func();
    var elapse = ((new Date).getTime() - start) / 1000;
    print(description + ": " + elapse + " sec");
  };
};

以下は JScript(WSH) で500ミリ秒のスリープを行う例

// test code for WSH
var print = function(msg) { WScript.echo(msg); };

Timer.timeit("test code")(function() { WScript.sleep(500); });

出力例

test code: 0.5 sec

インスタンスメソッドで時間測定をしたい場合は、組み込み Function オブジェクトに
このような bind メソッドを定義すると便利だ。 (prototype.js 等を参照)

Function.prototype.bind = function(context) {
  var method = this;
  return function() { return method.apply(context, arguments); };
};

呼び出し例

// test code for WSH
var Klass = function(x) { this.x = x; }; Klass.prototype.f = function() { Timer.timeit("Klass.f")(function() { WScript.sleep(this.x); }.bind(this)); }; var klass = new Klass(1000); klass.f();

出力例

Klass.f: 1 sec

12.14.2012

Updated: HtaMarkdownViewer

HTA版 マークダウン ビューアの更新

マークダウン記法で書かれたテキストファイルの内容をHTA上でレンダリングするツール。

ファイルオープンの際のエラー処理を追加。

GitHub
https://github.com/mogproject/mogproject/blob/master/script/HtaMarkdownViewer.hta 

 

Related posts

HtaMarkdownViewer
http://mogproject.blogspot.jp/2012/11/htamarkdownviewer.html

12.05.2012

C++: Join the Strings, then Split into Integers

C++: 文字列を結合し、区切って数値へ変換する

動機

次のような数字と空白で構成された文字列(string)の vector がある。

{ "10", "2 3", "456", " 789 ", "0" }

これらを先頭から順に結合し、空白の位置で再度分割し、数値(int)の vector へ変換したい。

1. 文字列の結合

Scalaの心地よさに触発されたので、foldLeft関数を実装してみた。
自作のfoldLeftに対してSTL標準のplusファンクタ(Scalaで言う_+_)を適用することで結合を実現する。

template <typename Iterator, typename Tp, typename Func>
Tp foldLeft(Iterator first, Iterator last, Tp seed, Func f) {
  for (Iterator it = first; it != last; ++it) seed = f(seed, *it);
  return seed;
}

template <typename Container>
typename Container::value_type join(Container const& container) {
  typedef typename Container::value_type Tp;
  return foldLeft(container.begin(), container.end(), Tp(), std::plus<Tp>());
}

join関数の中で使っている Container::value_type は、typename キーワードを付けないとコンパイルが通らない。
テンプレート変数が型なのか値なのか、今回のようなケースではコンパイラは推論することができないためだ。

2. 文字列の分割

下記URL参照。stringstream と getline を利用した実装。

std::vector<std::string> split(std::string const& str, char delimiter = ' ') {
  std::istringstream iss(str);
  std::string buf;
  std::vector<std::string> result;
  while (std::getline(iss, buf, delimiter)) result.push_back(buf);
  return result;
}

3. 文字列から数値へ変換

こちらも Functional Programming ライクな map 関数を作成した。
std::map との名前の競合を避けるため fmap と命名したのは不本意であるが詮方無しか。

現状では std::vector から std::vector への変換しかできないが、他のコンテナへも適用可能にするなど
改善の余地はありそうだ。

テンプレート変数の Func は、argument_type および result_type という型名の定義を必要とする仕様とした。
通常は std::unary_function を継承すればよい。

template <typename Func>
std::vector<typename Func::result_type> fmap(
    std::vector<typename Func::argument_type> const& src) {
  std::vector<typename Func::result_type> result(src.size());
  std::transform(src.begin(), src.end(), result.begin(), Func());
  return result;
};

template <typename Arg, typename Result>
class toNumeric : public std::unary_function<Arg, Result> {
public:
  Result operator()(Arg const& str) const {
    Result result;
    std::istringstream iss(str);
    iss >> result;
    return result;
  }
};
typedef toNumeric<std::string, int> toInt;
typedef toNumeric<std::string, long long int> toLong;

個々の文字列から数値への変換は、toNumeric という独自のファンクタを作成した。
この処理でも stringstream を活用している。
toInt が int 型への変換、toLong が long long int 型への変換処理である。

コード全体

#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <functional>

// join
template <typename Iterator, typename Tp, typename Func>
Tp foldLeft(Iterator first, Iterator last, Tp seed, Func f) {
  for (Iterator it = first; it != last; ++it) seed = f(seed, *it);
  return seed;
}

template <typename Container>
typename Container::value_type join(Container const& container) {
  typedef typename Container::value_type Tp;
  return foldLeft(container.begin(), container.end(), Tp(), std::plus<Tp>());
}

// split
std::vector<std::string> split(std::string const& str, char delimiter = ' ') {
  std::istringstream iss(str);
  std::string buf;
  std::vector<std::string> result;
  while (std::getline(iss, buf, delimiter)) result.push_back(buf);
  return result;
}

// map
template <typename Func>
std::vector<typename Func::result_type> fmap(
    std::vector<typename Func::argument_type> const& src) {
  std::vector<typename Func::result_type> result(src.size());
  std::transform(src.begin(), src.end(), result.begin(), Func());
  return result;
};

template <typename Arg, typename Result>
class toNumeric : public std::unary_function<Arg, Result> {
public:
  Result operator()(Arg const& str) const {
    Result result;
    std::istringstream iss(str);
    iss >> result;
    return result;
  }
};
typedef toNumeric<std::string, int> toInt;
typedef toNumeric<std::string, long long int> toLong;

#define each(i,c) for (typeof((c).begin()) i=(c).begin(); i!=(c).end(); ++i)

using namespace std;

int main(int argc, char **argv) {
  string s[] = { "10", "2 3", "456", " 789 ", "0" };
  vector<string> vs(s, s + sizeof(s) / sizeof(s[0]));

  vector<int> vi = fmap<toInt>(split(join(vs)));
  each(i,vi) cout << *i << endl;
}

実行例

102
3456
789
0

 

References

Story of Your Life » Blog Archive » C++で文字列のsplit:
http://shnya.jp/blog/?p=195