GAE/Pでフォロワーの増減確認

前回の記事(フォロワーの増減を確認する - void*)は、shellで動かすことを目的として作ったもので、驚くことに、id:tyru 氏がcronで実行できる(Check Twitter's follower (via http://d.hatena.ne.jp/ykot/20110228/1298879383) · GitHub)ようにしてくれていますので、実際にお使いになる場合は、そちらを使用したほうが効率的かと思います。スクリーンネームでの表示になっていますしね。素敵です:)

今回、少し調子に乗って同じような仕組みでGAEで動かせるようにしてみました。いまさら、GAEの説明は不要だとは思いますので、わからない方は適当にググッてみてください。GAEは、自前マシンを必要としないですし、何よりマシンメンテナンスが必要ない、という点において優れています。この程度のスクリプトなら、無料の範囲なのもすばらしいです。とか、偉そうなことを書いていますが使い始めたばかりで、おもちゃ代わりに遊んでいるだけで、全然解ってないです。最低限動くよって状態ですので、ご指摘などありましたら、ぜひ。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from google.appengine.ext import webapp,db
from google.appengine.ext.webapp import util
from google.appengine.api import urlfetch
from google.appengine.api import mail

from django.utils import simplejson

class Follower(db.Model):
    uid=db.StringProperty()
    followers=db.TextProperty()

class FollowerHistory(db.Model):
    uid=db.StringProperty()
    followers=db.TextProperty()
    entry_dt=db.DateTimeProperty(auto_now_add=True)

class MainHandler(webapp.RequestHandler):
    def get(self):
        for follow in Follower.all():
            self.response.out.write( u"<a href=./%s>%s<br>" % (follow.uid, follow.uid ))

class FollowerHandler(webapp.RequestHandler):
    def get(self, uid):
        url = "http://api.twitter.com/1/followers/ids/" + uid + ".json"
        # retry 10回
        for r in range(10):
            result = urlfetch.fetch(url)
            if result.status_code != 200:
                self.response.out.write( u"%s twitter api error(%d)" % (uid, result.status_code) )
            else:
                break
        if result.status_code != 200:
            return 

        self.response.out.write( u"%s follower check<br>" % uid)
        # JSON結果から、list生成
        newlist=simplejson.loads(result.content)

        # datastoreから、JSONデータを取得
        follow = Follower.gql('WHERE uid = :1', uid).get()
        if follow is None:
            # データストアに存在しない=新規.比較なし
            Follower(uid=uid,followers=result.content).put()
            FollowerHistory(uid=uid,followers=result.content).put()
            return

        # 比較
        oldlist=simplejson.loads(follow.followers)
        newset=set(newlist)
        oldset=set(oldlist)
        follow_list=""
        for i in newset - oldset:
            follow_list += "follow:%s\n" % i
        for i in oldset - newset:
            follow_list += "remove:%s\n" % i

        # データ設定
        follow.followers = result.content
        follow.put()

        b = u"...%s の現在のフォロワーの数:%d" % (uid, len(newlist))
        self.response.out.write( u"%s<br>%s" % (follow_list, b) )

        if follow_list != "":
            # 差分あり
            FollowerHistory(uid=uid,followers=follow.followers).put
            # 差分があった時、メールを送信
            body_text = follow_list + b
            mail.send_mail(sender = "foo@example.com",
                           to = "bar@example.com",
                           subject = "%s followers check" % uid,
                           body = body_text.encode('utf-8'),
                          )

def main():
    application = webapp.WSGIApplication([('/', MainHandler),
                                          ('/([-\w]+)', FollowerHandler)],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()

response.out.writeとかは、cron使うと必要ないのですが、デバッグとして埋め込んでいます。
http://[appname].appspot.com/[twitter uid]で、差分があった場合、メールで通知する仕組みになっています。一応、http://[appname].appspot.com/ で、一覧が出るようになっています。履歴をとっているのは、後ほど利用するためです。
色々いじっていてわかったのですが、apiは、頻繁にエラーを返すので、retryをするようにしています。

また、下記のメール設定を変更してやれば、メール送信できるようになります。foo@example.com、bar@example.comを適当に変更してご使用ください。

            mail.send_mail(sender = "foo@example.com",
                           to = "bar@example.com",
                           subject = "%s followers check" % uid,
                           body = body_text.encode('utf-8'),
                          )

cron.yamlで、cron設定しておけば自動的に実行されます。5分おきとかなら、こんな感じです。差分があった場合に、メールで通知してくれます。

cron:
- description: follower chenge check job
  url: /ykots
  schedule: every 5 minutes

簡単ですね ;)

ちなみに、既知のバグに、フォロワーの数が多いとエラーになるというのがあります。これは、Datastoreの容量制限に引っかかってしまっているためです。それほど試していないのですが、感覚としては、10000から20000ぐらいのフォロワーがいるとエラーになるみたいです。しばらくしたら、そのあたりも対応していく予定です。

フォロワーの増減を確認する

いまさらながら、Twitterアプリを作るためにいくつかのAPIテストしている最中です。で、副産物としてリムーブ通知ができました。cronとかに設定してメール送信でもすれば、コマンドをたたく必要がないです。スクリプト自体は、idで識別しているので、誰なのかを確認するには、別途処理が必要です。

色々確認している最中だけど、この、followers/idsは、100件とかの制限はなさそうなので、こういった簡易的なリストを作るのには向いているかも。

#!/bin/sh

uid=ykots
ymd=`date '+%Y%m%d%H%M%S'`

# TWITTER API
TWITTER_FOLLOWERS_API="http://api.twitter.com/1/followers/ids/$uid.json"

# フォローワー一覧リスト取得
status=`curl --silent --connect-timeout 10 -O $TWITTER_FOLLOWERS_API -w "%{http_code}"`
rtn=$?
if [ $rtn -ne 0 ]; then
  echo "curl error!!($rtn)"
  exit 1
fi

#HTTPステータスコード
if [ $status -ne 200 ]; then
  echo "twitter API error.($r)"
  cat $uid.json
  exit 1
fi

# リスト作成
if [ -e new_$uid.list ] ;then
  rm new_$uid.list
fi
for id in `cat $uid.json |sed -e "s/\[//" | sed -e "s/\]//" | sed -e "s/,/ /g"`; do
  echo $id >> new_$uid.list
done
rm $uid.json

# 過去比較
if [ -e $uid.list ]; then
  d=`diff new_$uid.list $uid.list | grep "^[<|>]" | sed -e "s/ /:/g"`
  if [ -z $d ]; then
    echo "変わってないよ"
    exit 2
  fi

  for id in $d; do
    f=`echo $id | awk 'BEGIN {FS=":"}{print $1}'`
    if [ $f = "<" ]; then
      echo "あたらしくフォローされたみたい。 :D"
    else
      echo "ありゃ、リムーブされちゃった。 :("
    fi
    echo $id | awk 'BEGIN {FS=":"}{print $2}'
  done
fi

# 移動およびバックアップ
cp new_$uid.list $uid.list.$ymd
mv new_$uid.list $uid.list

まあ、利用する人はいないかと思いますが、利用上の注意を書いておきます。比較のために、過去リストを保存しています。また、履歴も残していますので、適当なタイミングで消す必要があります。

追記

使う人いないと思っていたので、適当に書いていたんですが、星なんかを貰っちゃっていたので、もうすこし、注意点を。このシェルは、使ってみてもらうとわかりますが、ログインとか必要ないです。API的にOAuthとか必要ないんです。実際に使うときには、3行目のuidを変更することで自分用に変更できます。

uid=ykots <-ここを自分のidに変更する

で、ってことは、全然赤の他人のフォロワー履歴も採取することができます。さらにAPIを変更すれば、フォロー履歴も監視できてしまいます。他人を監視って、ちょっと気持ち悪い気もしますが。API的には問題ないのですが、そんな事やっていいのかわかりません。*1そのあたり、自己責任で使ってください。

*1:実際できるのだから、大丈夫だとは思いますけど...

mod_multicast(XEP-0033)

ejabberd 2.1.6は、XEP-0033を対応した、mod_multicastが同胞されていないようです。モジュールだけ入れればいいようなので、簡単な手続きで、使えるようになるようです。

今回は、old code といわれる手順で入れてみました。環境は、CentOS release 5.5 (Final)で、ejabberdのインストールは、epelのrepoからバイナリで入れています。

  1. svnを使ってモジュールをダウンロード
  2. インストールするモジュールのtrunkディレクトリへ移動
    • cd ejabberd-modules/mod_multicast/trunk
  3. README.txtをよんでみる。
    • cat README.txt
  4. コンパイル
    • ./build.sh
  5. ファイルのコピー
    • sudo cp ebin/mod_multicast.beam /usr/lib64/ejabberd/ebin/
  6. README.txt に従いconfiguration fileを変更
    • 「{access, multicast, [{allow, all}]}.」を追加
    • 「{mod_multicast, []},」を追加
  7. ejabberdのリスタート
    • sudo /etc/init.d/ejabberd restart

これらの手順だけでXEP-0033の対応が出来ます。実際に出来るかどうかは、PsiのXMLコンソールから、試してみました。入力したXMLは、こんな感じです。

<message to='multicast.SERVER'>
   <addresses xmlns='http://jabber.org/protocol/address'>
       <address type='to' jid='ykot@SERVER'/>
       <address type='cc' jid='test@SERVER'/>
   </addresses>
   <body>Hello, world!</body>
</message>

SERVERの部分は、適当に読み替えてください。ちなみにmod_multicastは、multicastのホスト名のデフォルトが、multicast.SERVERとなっています。これを変更したい場合は、{host, "multicast.example.org"},の部分で対応できるようです。*1

簡単ですね。:D

*1:試してはいないので、定かではないですが...

Pythonで、expect。pexpectがいい感じ。

とあるインストーラの自動化をしたいがために、expectを使いたい状況になったのですが、expectが使えない環境(インストールされていないし、権限もない)だったので、変わりに何かあるかなと探していたところ、expectのPython版があることを知りました。pexpect.py さえあれば動作可能なので、非常に使い勝手がいいです。
ちなみに、pexpect.py以外にも色々あるようで、このあたりを読むとイメージ出来るかと思います。
Pexpect - a Pure Python Expect-like module
うん、これは便利です。;-P

shellで日付比較

shellばかりいじっているから、最近、shellネタばかりです。shell上での日付比較は、秒に置き換えてやると、手っ取り早いです。

# 0 : 同一日時
# 1 : $1 の方が新しい日付
# 2 : $2 の方が新しい日付
function datecheck()
{
  dt1=`date -d "$1" '+%s'`
  dt2=`date -d "$2" '+%s'`

  if [ $dt1 -eq $dt2 ]; then
    return 0
  elif [ $dt1 -gt $dt2 ]; then
    return 1
  elif [ $dt1 -lt $dt2 ]; then
    return 2
  fi
}

結構、便利に使えます。

kill の「終了しました」のメッセージが邪魔

kill コマンドを発行する時の「終了しました」が非常に邪魔だったので、消す方法がないか調べていたら、これで簡単に消せた。

pid=`sh -c 'hoge & echo $!'`
kill $pid