Archive for the ‘未分類’ Category

iPhone OSにマルチタスク付けろとか付くとかいろいろうるさいですが、正直まともな議論は少ないと感じてます。

まず現状。電話かけながらSafariいじったり、音楽聞きながらアプリ立ち上げたり出来るのは使ってる人なら知ってると思いますが、当然これはマルチタスクです。なのでOSが対応してないというのは嘘。

次に、どんなアプリがマルチタスクして欲しいかという問題があります。マルチタスクの利点は主に、「起動時間の短縮」「前回の作業状態を保持」「バックグラウンドプロセス」あたりがあると思うのですが、起動時間は問題にならないだろうし、作業状態の保持もアプリで出来ること。バックグラウンドプロセスだけは無理ですが。

で、マルチタスクにしようとすると二つ大きな問題があると思います。UIとハードウェア。

まずUIですが、マルチタスクにすると確実に覚えることが増えます。まずマルチタスクの概念。次にタスク切り替えの操作。次にアプリの終了の操作。終了したつもりがしてなかったというトラブルが起きること間違いなし。無理とまでは言わなくてもよほど注意深くやらないと使えない人が出てくるでしょう。

次にハードウェアの制限。もちろんバッテリーもありますが、注目すべきはメモリ。最初に買ったiPod touchは128MBでした。このうちOSとバックグラウンドで動くiPodのプロセスが40MBあったとして、自由に使えるのは90MBほど。これを例えば2つのプロセスで使うと45MB、3つだと30MB。例えばあるアプリが60MB使うとすると、マルチタスク化によって動かないものが出てきます。

ここでパソコン脳の人は「swap使えばいいじゃん」と言うかもしれませんが、これは下の下の策。まずスワップという概念を理解出来る人は半数にも満たないはず。で、swap領域の設定という分かりにくい設定が増える。さらに、動かしている間にswapすると動きが遅くなるのが容易に想像付きます。MRAM採用してメインメモリとストレージの区別なしという大技でもない限り。

じゃあどうするよ?という訳ですが、ほとんどの要求はバックグラウンドで動くAPIがあれば解決するので、これを提供するのはありかなと。今でもpush機能があるので、この延長線上で。あと考えられるとしたら、メモリが512MBあれば2つまで同時起動可能にするとか。UIは知らん。

とりあえずPOSTする箇所とかは除いて、コアの部分だけ。
質問はコメントにください。

#!/home/ikemo/local/bin/ruby -Ku

# -*- encoding: utf-8 -*-

require 'net/http'
require 'rexml/document'
require 'timeout'
require 'time'
require 'dbm'
require 'uri'

include REXML

$yoruho = URI.encode("よるほ")
$yoruho_pittari = URI.encode("さんよるほーピッタリ賞です。おめでとうございます。")

user = 'yoruho'
pass = 'password'

def get_yoruho_buzztter(max_id)
  rss = ""
  begin
    res = nil
    now = Time::now
    yoru = Time::local(now.year, now.month, now.day, 0, 0, 0)

    timeout(60, IOError) {
      http = Net::HTTP.new("buzztter.com")
      if max_id then
        res = http.get("/ja/rss/#{$yoruho}?since=#{yoru.to_i}&max_id=#{max_id}",
                      {'User-Agent' => "Ruby/#{RUBY_VERSION}"})
      else
        res = http.get("/ja/rss/#{$yoruho}?since=#{yoru.to_i}",
                      {'User-Agent' => "Ruby/#{RUBY_VERSION}"})
      end
      rss = res.body
    }

    raise IOError if res.code != "200"
  end

  id = nil
  success = []
  doc = Document.new(rss)
  doc.elements.each("/rss/channel/item") {|item|
    title = item.elements["title"].text
    title_regexp = %r|([^:]+): .*|
    title_regexp.match(title)
    name = $1
    time = Time::parse(item.elements["pubDate"].text).localtime
    link = item.elements["link"].text
    link_regexp = %r|http://twitter.com/[^/]+/statuses/(.*)|
    link_regexp.match(link)
    id = $1
    if time.day == Time::now.day && time.hour == 0 && time.min == 0 && time.sec == 0 then
      success.push(name)
    elsif time.day != Time::now.day then
      return success, true, id
    end
  }

  return success, false, id
end

def get_yoruho_official(i)
  rss = ""
  begin
    res = nil

    timeout(60, IOError) {
      http = Net::HTTP.new("search.twitter.com")
      res = http.get("/search.atom?q=#{$yoruho}&page=#{i}&rpp=50",
                    {'User-Agent' => "Ruby/#{RUBY_VERSION}"})
      rss = res.body
    }

    raise IOError if res.code != "200"
  end

  success = []
  doc = Document.new(rss)
  doc.elements.each("//feed/entry") {|entry|
    url = entry.elements["author/uri"].text
    name_regexp = %r|^http://twitter.com/(.*)$|
    name_regexp.match(url)
    name = $1
    time = Time::parse(entry.elements["updated"].text).localtime
    if time.day == Time::now.day && time.hour == 0 && time.min == 0 && time.sec == 0 then
      success.push(name)
    elsif time.day != Time::now.day then
      return success, true
    end
  }

  return success, false
end

def get_yoruho(i)
  rss = ""
  begin
    res = nil

    timeout(60, IOError) {
      http = Net::HTTP.new("pcod.no-ip.org")
      res = http.get("/yats/search?query=#{$yoruho}&page=#{i}&rss",
                    {'User-Agent' => "Ruby/#{RUBY_VERSION}"})
      rss = res.body
    }

    raise IOError if res.code != "200"
  end

  success = []
  doc = Document.new(rss)
  doc.elements.each("//feed/entry") {|entry|
    name = entry.elements["author/name"].text
    time = Time::parse(entry.elements["updated"].text).localtime
    if time.day == Time::now.day && time.hour == 0 && time.min == 0 && time.sec == 0 then
      success.push(name)
    elsif time.day != Time::now.day then
      return success, true
    end
  }

  return success, false
end

def get_yoruho_timeline(max_id)
  xml = ""
  begin
    res = nil

    timeout(60, IOError) {
      path = if max_id != nil then
               "/statuses/friends_timeline.xml?count=200&max_id=#{max_id}"
             else
               "/statuses/friends_timeline.xml?count=200"
             end
      req = Net::HTTP::Get.new(path)
      req.basic_auth "yoruho", "password"

      Net::HTTP.start("twitter.com", 80) {|http|
        res = http.request(req)

        xml = res.body
      }
    }

    raise IOError if res.code != "200"
  end

  success = []
  id = nil
  doc = Document.new(xml)
  doc.elements.each("/statuses/status") {|status|
    screen_name = status.elements["user/screen_name"].text
    time = Time::parse(status.elements["created_at"].text).localtime
    text = status.elements["text"].text
    id = status.elements["id"].text

    if text.include?("よるほ") && time.day == Time::now.day && time.hour == 0 && time.min == 0 && time.sec == 0 then
      success.push(screen_name)
    elsif time.day != Time::now.day then
      return success, true, id
    end
  }

  return success, false, id
end

def post(name, status)
  # ここでTwitterにPOSTする
end

#
# kokokara
#
success_list_timeline = []
max_id = nil
(1..50).each {|i|
  count = 0

  begin
    (success, end_flag, max_id) = get_yoruho_timeline(max_id)
    success_list_timeline += success
    if end_flag == true then
      break
    end
  rescue
    count += 1
    retry if count <= 0
  end
}

success_list_buzztter = []
max_id = nil
[1].each {|i|
  count = 0

  begin
    (success, end_flag, max_id) = get_yoruho_buzztter(max_id)
    success_list_buzztter += success
    if end_flag == true then
      break
    end
  rescue
    count += 1
    retry if count <= 0
  end
}

success_list_pcod = []
(1..0).each {|i|
  count = 0

  begin
    (success, end_flag) = get_yoruho(i)
    success_list_pcod += success
    if end_flag == true then
      break
    end
  rescue
    count += 1
    retry if count <= 0
  end
}

success_list_official = []
(1..50).each {|i|
  count = 0

  begin
    (success, end_flag) = get_yoruho_official(i)
    success_list_official += success
    if end_flag == true then
      break
    end
  rescue
    count += 1
    retry if count <= 0
  end
}

success_list = success_list_timeline | success_list_pcod | success_list_official | success_list_buzztter

db = DBM::open("yoruho")
success_list.each {|name|
  if db[name] == nil then
    db[name] = "1"
  else
    db[name] = (db[name].to_i + 1).to_s
  end
}

success_list.each {|name|
  status = 'status=@' + name + "%20" + $yoruho_pittari + "%20" + db[name] + "%E5%9B%9E%E7%9B%AE%E3%80%82"
  post(name, status)

  # ここで段級の判定

  post(name, words)
}

# ここで人数を出力

(2010/06/13) FAQ作りました(`・ω・´)

またTwitter Botヽ(´ー`)ノ

こんどはよるほーbotっての作りました。 00:00:00に「よるほ」とつぶやくと、祝福してくれます。10回ごとに段級と副賞がもらえます。

(2009/12/27追記) 2009/12/16から@yoruhoのTLを拾うように変更してます。確実に捕捉されたい人は@yoruhoをフォローしてください。

(2010/03/02追記) 2010/02/14から@yoruho2のTLも拾うように変更してます。@yoruhoはフォロー返し停止してますので、@yoruho2をフォローしてください。

(2010/04/12追記) cronが00:05でなく00:00なのを修正。@yoruho2のTLを追加

(2010/05/25追記) 2010/05/21から@yoruho3のTLも拾うように変更しています。@yoruho2はフォロー返し停止してますので、@yoruho3をフォローしてください。

仕様は次のような感じです。

  • 毎日0時0分にbotが起動(cron)
  • @yoruhoのTL、@yoruho2のTL、@yoruho3のTL、buzztterTwitter検索(yats)(今は止めてます)、Twitter検索(公式)の順で検索して、00:00:00ぴったりに「よるほ」をつぶやいた人をリストにする。
  • ピッタリの人に対して@を送る
  • 最後に、今日ピッタリ賞だった人の人数をつぶやいて終わり。

ソースは今度整理して掲載する予定です。→整理してないけど公開しました

トラブルシューティング

  • ピッタリ賞が届かない場合
    • まず、本当にピッタリかどうかをチェックしてください。twilogを使うのが簡単です。
    • いろんなサブアカウントから来るので、Mentionsをリロードして確認してください。
    • それでもこない場合は@ikemoまでお知らせください。
  • ピッタリ賞が1日2回来る場合
    • おそらく@yoruho, @YORUHOのように、大文字小文字がばらばらになっていると思います。@ikemoまでお知らせください。

余談その1:Twitter検索の使える度

  • Twitter検索(yats):反映が遅いので速報にはあまり向いてませんが、数時間〜数ヶ月前あたりの検索はにはこれが一番です。あと、検索されない人も作者さんに言えば追加してくれるので安心。
  • Twitter検索(公式):速報性は高いですが、日本語の解釈がうまくないので、検索語によっては何もヒットしないことがあります。「たろっとさん」とかダメ。あと、ユーザー名の大文字小文字が違ってしまう問題があります。
  • buzztter:速報性は高いですが、RSSだと20件までしか取ってこれないので、よるほbotみたいにヒット数が多くなると使えません。Webからの検索で速報性を求めるのならおすすめ。
  • TLから検索:一番確実だけどフォローしてないとダメです。

よるほーbotでは公式検索から引っ張ってきてるのが一番多いです。次にTLから。

余談その2:よるほの起源

いくつか調べてみたところによると、

これだけしか分かりませんでした\(^o^)/。これより古い情報を持っている人いたらお願いします。

たろっとさんの人気が増すにつれて引けないという話が大きくなってきたので、副アカウントを作ってみました。名前はたろっとさんさんさんさん。引く箇所は変えてないんですが、規制対策のためいろいろやってます。

まず、たろっとさんをフォローしている人だけに応答を返すようにしました。最初は誰でも構わず応答を返すようにしてたんですが、すぐにspam扱いされてsuspendにorz。これでアカウントが2つ死にました(´・ω・`)。なのでフォローしている人だけ応答するようにしてます。フォローしてなくても送ってしまうbotは珍しくないと思うのですが、知らない人に送る頻度が多かったのと、URLをくっつけてしまっているのがまずいのかなと。なので知っている人に変えよーということで。

ソースコード1がfollowersを取得してファイルに保存している箇所、ソースコード2がtarot3333のロジックを組み込んだソースコード。

ソースコードその1

#!/usr/local/bin/ruby

require 'net/http'
require 'rss'
require 'rexml/document'

include REXML

req = Net::HTTP::Get.new("/statuses/followers.xml?screen_name=tarot3333")
req.basic_auth "tarot3333", "password"

followers_from_xml = []
Net::HTTP.start("twitter.com", 80) {|http|
  res = http.request(req)
  doc = Document.new(res.body)
  doc.elements.each("*/user") {|user|
    followers_from_xml.push(user.elements["screen_name"].text)
  }
}

followers_from_db = open("tarot3333.db").readlines.each {|line| line.chop! }

followers_towrite = followers_from_xml | followers_from_db
followers_towrite.uniq!
open("tarot3333.db", "w") {|f|
  f.puts followers_towrite.join("\n")
}

ソースコードその2

#!/usr/local/bin/ruby

require 'net/http'
require 'rss'
require 'rexml/document'
require 'time'

include REXML

def post(name, status)
  req = Net::HTTP::Post.new("/statuses/update.json")
  req.body = status

  Net::HTTP.start("twitter.com", 80) {|http|
    req.basic_auth "tarot3", "password"
    res = http.request(req)

    $log.puts(name + "," + res.inspect + "," + Time::now.to_i.to_s)

    if res.code == "200" then
      return
    end

    tarot3333 = open("tarot3333.db").readlines.each {|line| line.chop! }
    if tarot3333.include?(name) then
      req.basic_auth "tarot3333", "password"
      res = http.request(req)

      $log.puts(name + "," + res.inspect + "," + Time::now.to_i.to_s + ",tarot3333")
    end
  }
end

req = Net::HTTP::Get.new("/statuses/mentions.xml")
req.basic_auth "tarot3", "password"

names = []
Net::HTTP.start("twitter.com", 80) {|http|
  res = http.request(req)
  doc = Document.new(res.body)
  doc.elements.each("*/status") {|item|
    name = item.elements["user/screen_name"].text
    text = item.elements["text"].text
    time = Time::parse(item.elements["created_at"].text)

    now = Time::now
    start_time = Time::local(now.year, now.month, now.day, now.hour, now.min, 0, 0) - 120
    end_time = start_time + 119

    if time >= start_time && time <= end_time && !text.include?("RT") then
      names.push(name)
    end
  }
}

tarot = open("tarot.csv")
$log = open("log.txt", "a")
lines = tarot.readlines

names.each {|name|
index = rand(lines.size)

  status = 'status=@' + name + "%20%E3%81%AE%E3%82%AB%E3%83%BC%E3%83%89%E3%81%AF" + URI.encode(lines[index])
  post(name, status)
}

ほとんど使ってないThinkPad X60 Tabletを活用しようかと思ってせっかくなのでWindows 7を買ってみることにしました。Vistaからのアップグレードなので32ビット版にすればデータ保存したまま移行できるのですが、今回はCore 2 Duoということで64ビット版を入れてみました。値段はVista Business→Professionalなので18000円程度。

2時間ほど触ってみましたが、Vistaアップグレード版という感じですねぇ。非互換性ある大きな変更、例えばユーザのフォルダがC:¥Users以下になってるとか、はVistaでやっちゃったので、今回はあまり変わったという感じがしないです。一番変わったのはタスクバーかな。タスクが多くなると二段になってしまうと言う難点があるけど、17個のアプリケーションまでは大丈夫です。でもDockだと自動的にアイコン縮小してくれるんですよね…。

Snow Leopardみたいに安ければともかく、この値段だと無理に乗り換えるほどじゃないよなぁ。ただ新規に買うならXPやVistaを選ぶ必要はなさそうですね。

しかし個人的に一番期待してたタブレット機能は試せず(´・ω・`)。ドライバ待ちかな…。

iPhoneを買ってもうすぐ一ヶ月経つんですが、すっかり手に馴染んでしまいました。電池は使い方次第だけど、自分の場合は一日終わると40%くらいまで経る。アプリや通信を使う頻度はかなり高く、動画を見る機会は少ない。まぁこんなものかなと。電波は前に使ってたauに比べると入らないけどそれほど不満はない。オフ会行ったときに会場の飲み屋が圏外だったことくらいかな。ただ普通に電波が入るはずの場所で圏外と表示されることがある。バグがあるのかも?

主に使ってるアプリケーションまとめ。

BB2C
2chブラウザ
OmniFocus
GTD用
MindNode
マインドマップ
全力案内!ナビ
ナビアプリ
iXpenseIt
買い物記録アプリ。
weathernews
天気アプリ
EchoFon
Twitterアプリ。
LDR touch
RSSリーダー。普段はデスクトップなので。
Baloo!
2chの実況板を流し読み。
大辞林
辞書。あまり使ってないけど…
GNReader
Google Newsリーダー。

この前のTwitterオフ会でも話題になったんですが、Twitterを始めてからブログを書かなくなったという人が結構いるようです。ここは元々あまり更新してないんですが、確かにブログの地位が低下してるなぁと思うことがあります。というのは、RSSリーダーの購読数の低下です。さっき調べてみたら購読数は166。一時期は500越えてた事を考えるとかなり減ってます。

RSSで購読しているサイトを分類すると、

  • ニュースサイト:ここは健在。個人運営でもニュースサイト系は強い。
  • リアルの友人:ここも堅調。Twitterだと追い切れないので。人によってはTwitterのRSSそのものを購読してます。
  • アニメ、漫画の感想サイト:ここは衰退気味。購読してるけどほとんど見てないことが多い。
  • Sockal Bookmark:ここは数は少ないけど堅調。ニュースサイトに近いからかも。
  • アルファブロガー:ここは壊滅的。両手で数えるほどしかない。

くらいでしょうか。特に「アルファブロガー」のRSSを直接購読することが少なくなりました。一番の理由はTwitter…じゃなくてSocial Bookmarkです。ツッコミを入れたくなるような記事が多い人だと、コメントを見てる方が楽しい。じゃあ速報性いらんよね。じゃあはてブとかで話題になってからでいいよね。という感じで。

まぁTwitter Bot Makerでも作れるんですが、たろっとさんはいろいろと実験したいので別管理にしてます。

ポイントはRSSの解析の箇所ですね。例えば23:18:03にcronでこのスクリプトが呼ばれたとすると、23:16:00〜23:17:59までの@がリプライの対象になります。以前はnow – 120とかやってたんですが、誤差が生じるのでこっちの方法にしました。

追記。たろっとさんさんさんさん作りました

#!/usr/local/bin/ruby

require 'net/http'
require 'rss'
require 'rexml/document'
require 'time'

include REXML

user = 'tarot3'
pass = 'password'

req = Net::HTTP::Get.new("/statuses/mentions.xml")
req.basic_auth user, pass

names = []
Net::HTTP.start("twitter.com", 80) {|http|
  res = http.request(req)
  doc = Document.new(res.body)
  doc.elements.each("*/status") {|item|
    name = item.elements["user/screen_name"].text
    text = item.elements["text"].text
    time = Time::parse(item.elements["created_at"].text)

    now = Time::now
    start_time = Time::local(now.year, now.month, now.day, now.hour, now.min, 0, 0) - 120
    end_time = start_time + 119

    if time >= start_time && time <= end_time && !text.include?("RT") then
      names.push(name)
    end
  }
}

tarot = open("tarot.csv")
lines = tarot.readlines

names.each {|name|
index = rand(lines.size)

  req = Net::HTTP::Post.new("/statuses/update.json")
  req.basic_auth user, pass
  req.body = 'status=@' + name + "%20" + URI.encode("のカードは") + URI.encode(lines[index])

  Net::HTTP.start("twitter.com", 80) {|http|
    res = http.request(req)
  }
}

たろっとさんのよくある質問(気が向いたら更新)

カードを引いてくれないんですが
上のアルゴリズムを見れば分かるんですが、応答を保存してるわけではないです。なので、運悪く(最近は頻繁に…)投稿規制に引っかかったときはいつまで経っても返事が来ないということが起きます。その時はリトライしてください。
たろっとさんさんさんさんが出来ました!@tarot3333をフォローして15分待って、@tarot3に話しかけるとよいです。
引いてくるカードがツンなんですが
正位置と逆位置両方ともあまり良くないカードがあるので(The Towerとか)そう見えるのかもしれません。

記事が多くなったんでMTよりWPかなぁと思ってたんですが、今日やっと移行しました。とりあえず過去記事は移転してますが、画像は一部を除いて移転出来てません><

nuko.jpg

Continue reading ‘Movable TypeからWordPressへ移行しました’ »

Twitter Bot Maker

仮称のつもりだったんですが、どうやら正式名称になりそうな感じw

元ネタはphaさんの記事です。

ドメイン名を見れば分かるようにGoogle App Engineを使ってますが、GAEで動くのはデータの登録だけで、ボットを動かすプログラムは別のサーバに置いてあります。場所はセキュリティ上秘密です(アクセス制限かけてるけどね)。なのでソースコードもしばらくは公開しません。興味がある人はTwitter宛にメッセージください。もしくはメール。

データの登録方法は略。ボットを呼び出すところですが、秘密のURLにアクセスすると(アクセス制限あり)、キーとなるボットのID、パスワードの一覧が取得されます。そこからボットのIDとパスワードをキーとしてアクセスすると、「にょろーん」といった文が表示されます。後はそれを投稿するだけ。このスクリプトを15分ごと、@もらうと反応するボットは1分ごとにcronで動かしてます。

@もらうと動く仕組みは簡単で、mentions.xmlにアクセスして、1分以内の@のみ引っ張ってくるだけです。過去に反応したかどうかのチェックは全く行ってません。なのでまれに取りこぼす可能性があります。特に報告はあがってないようですが。

ちなみに@tarot3もGAEは使ってませんが、投稿の仕組みは同じです。