0

Twitterの地名なうbotを全blockするOAuthアプリ

http://shokai.mag.keio.ac.jp/block_nowbots/から使える。
(8月15日:URL変更しました

とりあえずSinatraとOAuthの組み合わせを試してみたかったので、DBは使わないものを作りたかった。取得したOAuthのtokenなどはその場で捨てている。blockコマンドを送る権限だけを一時的に委譲してもらう。


以下技術的なことのメモ。
■OAuthアプリの登録
まずhttp://twitter.com/oauth_clientsでアプリを登録し、consumer keyとconsumer secretを取得する。
で、下のコード中のCONSUMER_KEY, CONSUMER_SECRETを書き換える。


■必要なライブラリ
必要なgemをインストールする。最新版にした。

sudo gem install oauth twitter sinatra
それぞれ0.3.5, 0.6.13, 0.9.4がインストールされた。
twitterは内部でoauthに依存していて、oauthはバージョン毎に関数がけっこう変わっている。このバージョンの組み合わせなら動く。


■動かす
そして起動。
ruby block-nowbots.rb -p 2692 -s mongrel
thinだと複数クライアントから同時にアクセスした時1クライアントずつしか対応してくれなかったんだけど、mongrelは全クライアントに同時に応答してくれた。mongrelにいつのまにかそういう機能がついたのか、sinatraのバグでthinがthread処理されないのかはよくわからない。
sinatraもrailsと同じくデプロイまわりを工夫した方がよさそう。passenger使うのがいいのかな?


参考:


block-nowbots.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'
require 'oauth'
require 'twitter'

HOST_AND_PORT = "localhost:2692"

CONSUMER_KEY = "your-consumer-key"
CONSUMER_SECRET = "your-consumer-secret"

BOTS = 'nishinipporinow,nippori_now,kanda_now,tabata_now,sugamo_now,komagome_now,shinokubo_now,mejiro_now,takadanobabanow,okachimachi_now,uguisudani_now,ikebukuro_now,otsuka_now,akiba_now,tokyo_now,harajuku_now,shibuya_now,shibuya_now,yoyogi_now,shinjuku_now,ebisu_now'

set :sessions, true
def consumer
  OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET,
                      :site => "http://twitter.com")
end

template :layout do
  '<html>
     <body>
       <h1>地名なうbotを全blockするOAuthアプリ</h1>
       <%= yield %>
       <hr style="margin-top: 100px" />
       <a href="http://shokai.org">http://shokai.org</a><br /><a href="http://shokai.org/blog/archives/4698">解説</a>
     </body>
   </html>'
end

get '/' do
  @request_token = consumer.get_request_token(:oauth_callback => "http://#{HOST_AND_PORT}/auth")
  session[:request_token] = @request_token.token
  session[:request_token_secret] = @request_token.secret
  erb %{
    <p><%= BOTS %><br />をblock設定します</p>
    <p>OAuth認証してください</p>
    <a href="<%= @request_token.authorize_url %>">認証する!</a>
  }
end

get '/auth' do

  @request_token = OAuth::RequestToken.new(consumer,
                                          session[:request_token],
                                          session[:request_token_secret])
  @access_token = @request_token.get_access_token({},
                                                  :oauth_token => params[:oauth_token],
                                                  :oauth_verifier => params[:oauth_verifier])
  session[:access_token] = @access_token.token
  session[:access_token_secret] = @access_token.secret

  erb %{
    <p>認証成功</p>
    <p><a href="/block_now_bots">地名なうbotをblockする</a>(全部で<%= BOTS.split(",").size*3 %>秒くらいかかる)</p>
    <a href='/'>戻る</a>
  }
end

get '/block_now_bots' do
  oauth = Twitter::OAuth.new(CONSUMER_KEY, CONSUMER_SECRET)
  oauth.authorize_from_access(session[:access_token], session[:access_token_secret])
  twit = Twitter::Base.new(oauth)
  BOTS.split(",").each{|bot|
    twit.block(bot)
    puts "block #{bot}"
    sleep 3
  }
  erb %{
    <p>たぶんblockしました</p>
    <p><a href="http://twitter.com/yamanote_now/following">確認</a></p>
  }
end

0

Twib数をLDRに表示するGM

インストール → LDR-with-twib.user.js


表示例。右下の4tweetってやつ。
f03f6b6dee2f9d7bc4afb06695e5392f.png

参考

0

google-code-prettifyをインストール

google-code-prettify – Project Hosting on Google Code

wget http://google-code-prettify.googlecode.com/files/prettify-21-May-2009.zip
unzip prettify-21-May-2009.zip

srcとtestsのディレクトリが解凍されたので、httpで見える場所に置く
mkdir ~/www/shokai.org/js
cp -R src/ ~/www/shokai.org/js/pre

inoveテーマを使っているので、 wp-content/themes/inove/header.php を編集
headの中に
  <script type="text/javascript" src="http://shokai.org/js/pre/prettify.js"></script>
<link rel="stylesheet" type="text/css" href="http://shokai.org/js/pre/prettify.css" />

bodyタグを修正
  <body onload="prettyPrint()">

これでpreやcodeタグでclass=”prettyprint”すれば色が付く

さすがに>や<は&gt;と&lt;に置換しないとならんかった
line-heightが広くなりすぎたのでcssで修正

c++テスト
#include "cv.h"
#include "highgui.h"
#include <boost/program_options.hpp>
#include <iostream>
using namespace boost;
using namespace std;

int main(int argc, char* argv[]) {
program_options::options_description opts("options");
opts.add_options()
("help,h", "ヘルプを表示")
("cascade,c", program_options::value<string>(), "haarcascade設定ファイル")
("input,i", program_options::value<string>(), "入力画像ファイル名")
("output,o", program_options::value<string>(), "出力ファイル名")
("preview,p", "プレビュー表示");
program_options::variables_map argmap;
program_options::store(parse_command_line(argc, argv, opts), argmap);
program_options::notify(argmap);
if (argmap.count("help") || !argmap.count("cascade") || !argmap.count("input")) {
cerr << "cascadeとinputが必要です" << endl;
cerr << opts << endl;
return 1;
}

CvHaarClassifierCascade *cascade;
cascade = (CvHaarClassifierCascade*)cvLoad(argmap["cascade"].as<string>().c_str(), 0, 0, 0);
if(!cascade){
cerr << "error! Cascade not Found" << endl;
return -1;
}

IplImage *image = cvLoadImage(argmap["input"].as<string>().c_str());
if(!image){
cerr << "error! Image File not Found" << endl;
return -11;
}

CvMemStorage *storage = 0;
storage = cvCreateMemStorage(0);
CvSeq* faces = cvHaarDetectObjects(image, cascade, storage,
1.1, 2, CV_HAAR_DO_CANNY_PRUNING,
cvSize(30, 30));

bool isOutput = (argmap.count("preview")||argmap.count("output"));
for(int i = 0; i < faces->total; i++){
CvRect *rect = (CvRect*)cvGetSeqElem(faces, i);
cout << "x:" << rect->x << ", y:" << rect->y
<< ", width:" << rect->width << ", height:" << rect->height << endl;
if(isOutput){
CvPoint center;
center.x = rect->x + rect->width/2.0;
center.y = rect->y + rect->height/2.0;
int r = (rect->width + rect->height)/4.0;
cvCircle(image, center, r, CV_RGB(255, 0, 0), 2, CV_AA, 0);
}
}

if(argmap.count("output")){
string out_filename = argmap["output"].as<string>();
cout << "save! " << out_filename << endl;
cvSaveImage(out_filename.c_str(), image);
}

if(argmap.count("preview")){
char winName[] = "haarcascade test";
cvNamedWindow(winName, CV_WINDOW_AUTOSIZE);
cvShowImage(winName, image);
while (1) {
if (cvWaitKey(1) == 'q') break;
}
cvDestroyWindow(winName);
}

cvReleaseImage(&image);
return 0;
}


rubyテスト
#!/usr/bin/env ruby
require 'rubygems'
require 'active_record'
require 'feed-normalizer'
require 'open-uri'
require 'kconv'
require 'yaml'
require File.dirname(__FILE__) + "/model_post.rb"

config = YAML::load open(File.dirname(__FILE__)+'/config.yaml')

user = config["usernum"] # twitterID
if user == nil
puts 'Error!: usernum not Found on config.yaml'
exit(1)
end

ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
#:dbfile => ':memory:',
:dbfile => File.dirname(__FILE__) + '/db',
:timeout => 30000
)

last = 10
last = ARGV[0].to_i if ARGV[0].to_i > 0
first = 1
first = ARGV[1].to_i if ARGV[1].to_i > 0

errors = Array.new
for page in first..last
uri = "http://twitter.com/statuses/user_timeline/#{user}.atom?page=#{page}"
begin
if(config["user"] != nil && config["pass"] != nil)
feed = FeedNormalizer::FeedNormalizer.parse open(uri, :http_basic_authentication => [config["user"], config["pass"]])
else
feed = FeedNormalizer::FeedNormalizer.parse open(uri)
end

rescue
puts "feed fetch error! page:#{page}"
errors.push(page)
sleep 10
next
end

puts uri
feed.entries.each{ |e|
if Post.find_by_uri(e.url) == nil
post = Post.create(:uri => e.url,
:message => e.content.gsub(/&#(?:(\d*?)|(?:[xX]([0-9a-fA-F]{4})));/) { [$1.nil? ? $2.to_i(16) : $1.to_i].pack('U') },
:time => e.last_updated
)
puts post
end
}
puts "-----page:#{page} (#{first}-#{last}) finished-----"
sleep 10 if page < last
end

if errors.size > 0
print "feed fetch error at page:"
puts errors.join(' ')
end


jsテスト
var radio = new Object();
radio.id = 1;
radio.channel = 15;

var led = new Object();
led.pin = 7;
led.state = false;
pinMode(led.pin, true); // 出力設定

nodes = new Array(16);
for(i = 1; i < nodes.length; i++){
nodes[i] = new Node(i);
}

radioInit(0xDEAD, radio.id, radio.channel, 31);
serialInit(9600);

for(i = 0; i < 6; i++){
pinMode(i, true); // pull-up
}


function Node(id){ // Node型
this.id = id;
this.led = true;
}

function loop() {
for(i = 0; i < 6; i++){
if(serialAvailable()){
recv = serialRead();
serial_parse(recv);
}
if(digitalRead(i)){
id = i+6;
node = nodes[id];
digitalWrite(led.pin, led.state = !led.state);
request = "id:" + node.id + ",led:";
if(node.led) request += "1";
else request += "0";
radioSend(0xFFFF, request);
serialSend(request+"\r\n");
sleep(50);
}
}
}

function serial_parse(data){
for(i = 0; i < data.length; i++){
c = data.charAt(i);
switch(c){
case "a":
nodes[6].led = false;
break;
case "b":
nodes[7].led = false;
break;
case "c":
nodes[8].led = false;
break;
case "d":
nodes[9].led = false;
break;
case "e":
nodes[10].led = false;
break;
case "f":
nodes[11].led = false;
break;
case "A":
nodes[6].led = true;
break;
case "B":
nodes[7].led = true;
break;
case "C":
nodes[8].led = true;
break;
case "D":
nodes[9].led = true;
break;
case "E":
nodes[10].led = true;
break;
case "F":
nodes[11].led = true;
break;
}
}
}

function sleep(count){
for(var i = 0; i < count*10; i++){
}
}

while(true){
loop();
}

function pinMode(pin_id, mode){
}

function digitalWrite(pin_id, state){
}

function digitalRead(pin_id){
}

function analogWrite(pin_id){
}

function analogRead(pin_id){
}

function soundWrite(value){
}

function radioInit(panID, deviceID, channel){
}

function radioConnect(){
}

function radioSend(){
}


function radioClose(){
}

function serialInit(baudRate){
}

function serialAvailable(){
}

function serialRead(){
}

function serialSend(message){
}

function debug(message){
}

0

BeagleBoard(Ubuntu9.04)とArduinoやmoxaを接続し、シリアル通信でやりとりする

BeagleBoardはTIの作ったオープンソースハードウェア。こっそり春頃からいじっていたんだけど書いてなかった。
スペックに難はあるが、ディスプレイとサウンド出力つきの組み込み環境が俺でも作れるのは魅力。作品のレベルをプロダクトに近い所まで引き上げられる。

最近CRESTでのプロジェクト用にARM版Ubuntu9.04をbuildしてインストールして、Arduinoと接続する所までやったので書いておこう。

今はApache2とruby1.8.7(いずれもaptでインストールした)を使って、外付けしたArduinoとmoxaからデータを読み取っている。とりあえず大学院棟の明るさをCdSで取得してwebに公開している → http://shokai-b.mag.keio.ac.jp/light


R0012112


■準備
BeagleBoardと、インストール時にパソコンとBeagleBoard間でシリアルコンソールを使うのに必要なBeagleケーブルはdigikeyで購入できる。他の部品は国内で全て入手可能。
BeargleBoard 周辺機器


■Ubuntu9.04をBeagleBoardにインストール
OSはSDカードにインストールする。
最初デスクトップ版をインストールしてみたけど重すぎた。AngstromやHandheld Mojoならそれなりに動くけど、フルのUbuntuデスクトップはウィンドウマネージャをtwmにしても無理だった。
今回の用途にはCUIがあれば十分だったので、最低限のbuildツールを含んだARM用イメージを作って起動させた。

なお作業はext3のファイルシステムが読み書きできる必要があるので全て別のUbuntu Desktopで行った。

  1. BeagleBoardにUbuntu Desktop版インストール – 橋本詳解
  2. BeagleBoardにUbuntuセットアップ(GUIなし)、sshdを入れてリモートログインしてみる – 橋本詳解
  3. いろいろインストールして環境を整える – 橋本詳解
  4. Rubyまわりをインストール – 橋本詳解

これで、ApacheやRubyやsshdはもちろん、emacsと各種elisp、gemとeasy_install、rails2やsinatra、mongrel_clusterとapache2でmod_proxy_balancerなどがaptとgemでさくっとインストールできた。
このへんのサーバサイドアプリ開発環境のセットアップの容易さはubuntuさすが。

処理速度は体感でDebian化した白箱より速い。SDカードなのであまりR/Wしたくないがたぶん外付けUSB-HDDも認識できるんじゃないだろうか。そうすると、クローラ作ったり簡単なbotを動かす程度の俺は自宅サーバ環境はBeagleBoardで十分という可能性もある。



処理速度とストレージ読み書きに不安はあるが小型かつバッテリーでも動かせるUbuntu Linux環境ができた。


■Arduino / moxaとの通信
BeagleBoard基板上のシリアルコンソールに使ったポートをArduinoやmoxaとの通信を使った。

本当はUSBで接続したかったけどできなかった。
FTDIチップのドライバはaptでインストールできるが、USBに接続しても認識されない(/dev/ttyUSB0に現れない)
同じバージョンのUbuntuデスクトップを別のAMD64マシンで用意して、同じようにセットアップをしたがこちらは認識した。ARM版Ubuntuが何かおかしいのかもしれない。

64bitAMDマシンではlsmodした時にFTDIチップを監視してるプロセスがいるんだけど、BeagleBoardではlsmodしてもいない。
誰かLinux詳しい人教えてくれるとうれしいです・・・


しかたないのでセットアップに使ったBeagleケーブルのオス版を作って通信に使う。
シリアルポートの9つのピンにはよく見ると番号が振ってあり、それをBeagleBoardの基板上の1~9ピン(10は無視する)と接続する。
シリアルポート側は

12345
6789

のようになるが、BeagleBoard上のコネクタは

13579
2468

となっているのでとても捻れる。気合いではんだづけする。基板上のシルク印刷をよく見てね。


R0012102
R0012104
R0012106
R0012107


これで、Arduinoの外側にあるRX / TXピンにMAX232(ADM3202)を接続すればBeagleBoardとシリアル通信できる。
R0012110

ArduinoにCdS(明るさのセンサ)なんかを接続して数値をBeagleBoardに送り続ければいい。


/etc/event.d/ttyS2 を削除して再起動すると、/dev/ttyS2がシリアルコンソールではなくシリアルポートとして開ける。

screen /dev/ttyS2

でモニタできる。
UNIX系なので、シリアルポートもFileとして簡単に読み出せた。
Arduino + BeagleBoard(Ubuntu) シリアルポートを読んでたまにファイルに書き出す – 橋本詳解

通信できる手段が整えばあとは好きにできる。

0

buzztterでtimelineを作るYahoo Pipes

buzztter.comでtwitter上の最近の頻出単語の統計を出してくれている。
頻出語の上位5単語で検索してタイムラインを作るYahoo Pipesを作った。

Pipes: buzztter timeline

1週間ぐらい見ているが、だいたいみんなテレビとかゲームとか選挙とか芸能人の話題、眠気や月曜日出勤したくないなどの体調の問題を喋っている。ワイドショー的。
上位3語や10語も試したけど、5語ぐらいが話題がバラけすぎもせず偏りすぎもせずちょうどよかった。


前作「twitter上で親しい人のタイムラインを動的に生成するYahoo Pipes」は自分に近い人がでてくるけど、今度のはtwitterの中心近くで騒いでいる人たちが見える。