Fork me on GitHub

2010/01/28

はてな
長きに渡り活躍したWIDEプロジェクトのIRCサーバが役割を終えようとしています。そして惜しまれる中、livedoorがIRCサーバの提供をかって出てくれました。
livedoor ラボ「EDGE」 開発日誌 : livedoorからIRCnetへIRCサーバを提供します - livedoor Blog(ブログ)

WIDEプロジェクトIRCワーキンググループによるIRCサーバ運用終了について
という発表がありましたが IPv6を応援し、IRCをこよなく愛するlivedoorは、IRCnetへIRCサーバを提供することを決定しました。
※IPv4とIPv6で利用可能なデュアルスタック環境を用意する予定
※接続数などの仕様は現行と同じです

現在、関係各所と調整を行っているところですが近日中に接続先情報などはお知らせ出来ると思いますので、お待ちください。

宜しくお願いいたします。

http://blog.livedoor.jp/edge_labs/archives/1092902.html
すばらしいですね。これを期にIRCユーザがまた増え出すといいですね。
ぜんぜん関係ないですが、今日はvimからIRC出来るスクリプトを書いてみました。IRCプロトコルを喋るのではなく、freenode.netのウェブインタフェースを叩いています。
curlコマンドを使っているのでお使いのvimがperl拡張(if_perl)やpython拡張(if_python)、ruby拡張(if_ruby)でコンパイルされている必要はありません。Windowsでも動きます。
生のIRCと違い、プロキシさえ通れば防火壁内の人でもvimからIRC出来ます。
一つ問題があるとすれば、クライアントサーバをサポートしたvimである事と起動方法が難しい事。クライアントサーバはUNIX系OSであればgvimで、Windowsであればコマンドライン版でもOKです。クライアントサーバをサポートしているかどうかは、一つvimを起動しておいて
# vim --serverlist
とする事で確認出来ます。vimは内部で非同期に生成したプロセスとやりとりするのが苦手なので、vimを2つ使っています。
以下起動手順。
  1. 新しく端末を開きvimを起動する
  2. さらに新しく端末を開きもう一つvimを起動する
  3. 片方のvimで「:Irc」と入力します。
  4. もう片方のvimで「:IrcServer #mychannel mynick」と入力します。
場合により上記vimをgvimで置き換えて下さい。
これで「IrcServer」と入力した側がIRCサーバとなり、「Irc」と入力した側がIRCクライアントになります。内部でremote_expr()による通信をしています。
実際にはサーバがremote_expr()でコンテキスト、発言者、メッセージを飛ばし、受け側であるクライアント側がバッファに追加しています。
Linux版で試した所、若干とろい気もしますが十分楽しめます。(ネタとして)

pureirc-vim
一人で寂しいよー。
どうぞ遊んでやって下さい。
mattn's pureirc-vim at master - GitHub

vimscript for IRC. This make possible to access freenode.net from vim via HTTP protocol. it don't require if_xxx. using 'curl'

http://github.com/mattn/pureirc-vim
Posted at 01:39 in ソフトウェア::vim | WriteBacks (0)
Tagged as: freenode, irc, vim
Bookmarks: このエントリーのtweets add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip | add to buzzurl add to buzzurl | add to fc2bookmark add to fc2bookmark | add to Yahoo Bookmark add to Yahoo Bookmark | add to Pookmark add to Pookmark

2009/11/23

はてな
昨日書いたGoのIRCボットに手を入れ、mecabで要素解析する様にした。色々弄ってたら、某所で有名な「悪く言うなbot」になってた。
goによるmecab wrapperの実装がある事をtokuhiromさんに教えてもらったので、さっそく使わせて頂いた。
package main

import (
  "fmt";
  "http";
  "bytes";
  "json";
  "strings";
  "io";
  "mecab";
)

func get_json(url string, data map[string]string) json.Json {
  q := "";
  for key, val := range data {
    if (len(q) > 0) { q += "&" }
    q += fmt.Sprintf("%s=%s", key, http.URLEscape(val));
  }
  bb := &bytes.Buffer{};
  bb.Write(strings.Bytes(q));
  r, err := http.Post(url + "?" + q, "application/x-www-form-urlencoded", nil);
  if err == nil {
    b, _ := io.ReadAll(r.Body);
    r.Body.Close();
    j, _, _ := json.StringToJson(string(b));
    return j
  }
  return nil
}

func get_sid(nick string) string {
  j := get_json("http://webchat.freenode.net/e/n", map[string]string{"nick":nick});
  return j.Elem(1).String();
}

func login(nick *string) string {
  sid := get_sid(*nick);
  for {
    j := get_json("http://webchat.freenode.net/e/s", map[string]string{"s":sid});
    if j == nil {
      break;
    }
    for i := 0; i < j.Len(); i++ {
      data := j.Elem(i);
      println(data.String());
      if (data.Elem(0).String() != "c") {
        continue;
      }
      if (data.Elem(1).String() == "433") {
        *nick += "_";
        sid = get_sid(*nick);
      }
      if (data.Elem(1).String() == "376") {
        return sid;
      }
    }
  }
  return "";
}

func join(sid string, room string) bool {
  j := get_json("http://webchat.freenode.net/e/p", map[string]string{"s":sid, "c":"JOIN " + room});
  if j == nil {
    return false;
  }
  return true;
}

func say(sid string, room string, msg string) bool {
  j := get_json("http://webchat.freenode.net/e/p", map[string]string{"s":sid, "c":"NOTICE " + room + " :" + msg});
  if j == nil {
    return false;
  }
  return true;
}

func listen(sid string, nick string, room string) {
  for {
    j := get_json("http://webchat.freenode.net/e/s", map[string]string{"s":sid});
    if j == nil {
      continue;
    }
    for i := 0; i < j.Len(); i++ {
      data := j.Elem(i);
      println(data.String());
      if (data.Elem(0).String() != "c") {
        continue;
      }
      if (data.Elem(1).String() != "PRIVMSG") {
        continue;
      }
      line := data.Elem(3);
      if (line.Elem(0).String() == nick) {
        continue;
      }
      s := line.Elem(1).String();
      m := mecab.New2("");
      r := mecab.SparseToStr(m, s);
      ss := strings.Split(r, "\n", 999);
      for l := 0; l < len(ss); l++ {
        st := strings.Split(ss[l], "\t", 2);
        if (len(st) < 2) {
          continue;
        }
        if (strings.Index(st[1], "固有名詞") != -1 ||
            strings.Index(st[1], "人名") != -1 ||
            strings.Index(st[1], "組織") != -1) {
          say(sid, room, st[0] + "は良い奴だ。悪く言うな。俺が許さん。");
        }
      }
    }
  }
}

func main() {
  nick := "go-japan";
  room := "#mattn";
  sid := login(&nick);
  if (len(sid) > 0) {
    if (join(sid, room)) {
      listen(sid, nick, room);
    }
  }
}
channelに固有名詞や人名等が現れると「XXXは良い奴だ。悪く言うな。俺が許さん。」と発言します。
mecabライブラリについては、やっぱりfeatureとかsurfaceを扱いたいのでこれからに期待したいと思います。まぁ、現状でも上記の様にstrings.Split使えば出来なくないんですけどね。

なんとなくだけど、だんだん「C++書きの為のGo言語」と言われてる理由が分かって来た気がする。
Posted at 23:19 in ソフトウェア::lang::go | WriteBacks (0)
Tagged as: freenode, go, golang, irc
Bookmarks: このエントリーのtweets add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip | add to buzzurl add to buzzurl | add to fc2bookmark add to fc2bookmark | add to Yahoo Bookmark add to Yahoo Bookmark | add to Pookmark add to Pookmark

はてな
とは言っても、freenodeのWebインタフェース使ってJSONやり取りして、発言するだけの物。以前C++で作った全裸botの簡易版といった所か。
動かすと、freenodeの"golang-daisuki"(仮)にJOINして「郷です!ジャパーーーン!」と発言した後に死亡します。
嫌がらせには持って来いですね。

package main

import (
  "fmt";
  "http";
  "bytes";
  "json";
  "strings";
  "io";
)

func get_json(url string, data map[string]string) json.Json {
  q := "";
  for key, val := range data {
    if (len(q) > 0) { q += "&" }
    q += fmt.Sprintf("%s=%s", key, http.URLEscape(val));
  }
  bb := &bytes.Buffer{};
  bb.Write(strings.Bytes(q));
  // freenodeはPOSTメソッドでGETクエリをおくらなきゃ駄目
  r, err := http.Post(url + "?" + q, "application/x-www-form-urlencoded", nil);
  if err == nil {
    b, _ := io.ReadAll(r.Body);
    r.Body.Close();
    j, _, _ := json.StringToJson(string(b));
    return j
  }
  return nil
}

func get_sid(nick string) string {
  j := get_json("http://webchat.freenode.net/e/n", map[string]string{"nick":nick});
  return j.Elem(1).String();
}

func login(nick string) (string, string) {
  sid := get_sid(nick);
  for {
    j := get_json("http://webchat.freenode.net/e/s", map[string]string{"s":sid});
    if j == nil {
      break;
    }
    for i := 0; i < j.Len(); i++ {
      data := j.Elem(i);
      println(data.String());
      if (data.Elem(0).String() != "c") {
        continue;
      }
      if (data.Elem(1).String() == "433") {
        nick += "_";
        sid = get_sid(nick);
      }
      if (data.Elem(1).String() == "376") {
        return sid, nick;
      }
    }
  }
  return "", "";
}

func join(sid string, room string) bool {
  j := get_json("http://webchat.freenode.net/e/p", map[string]string{"s":sid, "c":"JOIN " + room});
  if j == nil {
    return false;
  }
  return true;
}

func say(sid string, room string, msg string) bool {
  j := get_json("http://webchat.freenode.net/e/p", map[string]string{"s":sid, "c":"NOTICE " + room + " :" + msg});
  if j == nil {
    return false;
  }
  return true;
}

func main() {
  nick := "go-japan";
  sid, _ := login(nick);
  if (len(sid) > 0) {
    if (join(sid, "#golang-daisuki")) {
      say(sid, "#golang-daisuki", "郷です!ジャパーーーン!");
    }
  }
}
作ってみた感想としては、httpまわりにもう少しPOSTに便利なメソッドが欲しいのと、Postメソッドのデフォルトがcheckedになっている(Postメソッドでは変えれない)ので古くさいサーバで動かないんじゃないかと思ったり。

ただC++版に比べて幾らか短く書けたし、今回のコードでは例外等処理してないけど、例外処理を書きたくなった時にも簡単に実装が出来るのも分かった。

ところでpublicメソッドはメソッド名を先頭大文字にするってルールはいいけど、少しだけオモチャっぽく感じてしまうのは私だけだろうか。
Posted at 01:36 in ソフトウェア::lang::go | WriteBacks (0)
Tagged as: bot, go, irc
Bookmarks: このエントリーのtweets add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip | add to buzzurl add to buzzurl | add to fc2bookmark add to fc2bookmark | add to Yahoo Bookmark add to Yahoo Bookmark | add to Pookmark add to Pookmark

2009/10/09

はてな
tokuhiromさんがnanowwwという、C++から簡単にHTTPが扱えるクライアントライブラリを書いてくれたので、botを書いてみた。
tokuhirom's nanowww at master - GitHub

C++ lightweight, fast, portable HTTP client library

http://github.com/tokuhirom/nanowww
picojsonは以前ご紹介した通り、kazuhoさんが書いたC++からSTLと親和性の高いJSONパーサです。
今回はtokuhiromさんが、C++で、もちろんSTLと親和性の高いHTTPクライアントライブラリを書いてくれました。
これを使えば、例えばhttp://example.com/fooというURLにPOSTで"name=hasegawa"を送信するとした場合
  nanowww::Client client;
  nanowww::Request req("POST", "http://example.com/foo", "name=hasegawa");
  nanowww::Response res;
  req.set_header("Content-Type", "application/x-www-form-urlencoded");
  if (client.send_request(req, &res))
      std::cout << res.content() << std::endl;
  else
      std::cout << client.errstr() << std::endl;
これだけのコードでHTTP通信出来てしまいます。
C++らしくていいですね。CURLよりはインタフェースが優れていると思います。ただ現状はPROXYに対応していなかったり、Windowsで使えなかったりしますので、用途を絞られますが、今後に期待したいと思います。
さて今日はこれを使って、IRCに常駐し、指定のチャネルに入り、指定の単語が現れると「それXXX関係ないだろ!jk」と発言する、「関係ないbot」を書きました。実際にIRCプロトコルを喋っている訳でなく、webchat.freenode.netのAjax通信を、nanowwwとpicojsonによる擬似Ajax通信を行って実現しています。
以下、少々長いですが全ソースコード。
#include "picojson.h"
#include "nanowww.h"

using namespace std;
using namespace picojson;
using namespace nanowww;

#ifdef _WIN32
static char* utf8_to_str_alloc(const char* utf8) {
    size_t in_len = strlen(utf8);
    wchar_t* wcsdata;
    char* mbsdata;
    size_t mbssize, wcssize;

    wcssize = MultiByteToWideChar(CP_UTF8, 0, utf8, in_len,  NULL, 0);
    wcsdata = (wchar_t*) malloc((wcssize + 1) * sizeof(wchar_t));
    wcssize = MultiByteToWideChar(CP_UTF8, 0, utf8, in_len, wcsdata, wcssize + 1);
    wcsdata[wcssize] = 0;

    mbssize = WideCharToMultiByte(GetACP(), 0, (LPCWSTR) wcsdata, -1, NULL, 0, NULL, NULL);
    mbsdata = (char*) malloc((mbssize + 1));
    mbssize = WideCharToMultiByte(GetACP(), 0, (LPCWSTR) wcsdata, -1, mbsdata, mbssize, NULL, NULL);
    mbsdata[mbssize] = 0;
    free(wcsdata);
    return mbsdata;
}
#endif

value get_json(string url, map<string, string>& postdata) {
  value v;
  char error[256];
  string data;
  map<string, string>::iterator it;

  for (it = postdata.begin(); it != postdata.end(); it++) {
      if (data.size()) data += "&";
      data += it->first + "=";
      data += it->second;
  }

  Client client;
  Request req("POST", url.c_str(), data.c_str());
  Response res;
  req.set_header("Content-Type", "application/x-www-form-urlencoded");
  if (client.send_request(req, &res)) {
    const char* ptr = res.content().c_str();
    string err = parse(v, ptr, ptr + strlen(ptr));
    if (!err.empty()) cerr << err << endl;
  }
  return v;
}

int
main(int argc, char* argv[]) {
  map<string, string> postdata;
  value v;
  bool loop;

  if (argc != 4) {
      std::cerr << "usage: kankeinaibot [nick] [channel] [word]" << std::endl;
      return -1;
  }
  string nick = argv[1];
  string channel = argv[2];
  string word = argv[3];

  postdata.clear();
  postdata["nick"] = nick;
  v = get_json("http://webchat.freenode.net/e/n", postdata);
  string sid = v.get<array>()[1].get<string>();

  loop = true;
  postdata.clear();
  postdata["s"] = sid;
  while (loop) {
    v = get_json("http://webchat.freenode.net/e/s", postdata);
    array a = v.get<array>();
    for (array::iterator it = a.begin(); it != a.end(); it++) {
      if (!it->is<array>()) continue;
      array line = it->get<array>();
      if (line[0].to_str() == "c") {
        if (line[1].to_str() == "443") {
            postdata["nick"] += "_";
            v = get_json("http://webchat.freenode.net/e/n", postdata);
            sid = v.get<array>()[1].get<string>();
            break;
        }
        if (line[1].to_str() == "376") {
            loop = false;
            break;
        }
      }
    }
  }

  loop = true;
  postdata.clear();
  postdata["c"] = "JOIN #";
  postdata["c"] += channel;
  postdata["s"] = sid;
  v = get_json("http://webchat.freenode.net/e/p", postdata);
  while (loop) {
    v = get_json("http://webchat.freenode.net/e/s", postdata);
    array a = v.get<array>();
    for (array::iterator n = a.begin(); n != a.end(); n++) {
      array line = n->get<array>();
      if (line[0].to_str() == "c") {
        array aa = line[3].get<array>();
        if (aa.size() > 1 && aa[0].to_str() != nick) {
          cout << aa[0].serialize() << endl;
          cout << aa[1].serialize() << endl;
          if (!strncmp(aa[1].to_str().c_str(), word.c_str(), word.size())) {
              map<string, string> saydata;
              saydata["s"] = sid;
              std::stringstream s;
              s << "PRIVMSG #" << channel << " :" <<
                  "それ" << word << "関係ないだろ!jk";
              saydata["c"] = s.str();
              cout << saydata["c"] << endl;
              v = get_json("http://webchat.freenode.net/e/p", saydata);
          }
        }
        string s = line[3].serialize();
#ifdef _WIN32
        char* p = utf8_to_str_alloc(s.c_str());
        cout << p;
        cout << " ";
        free((void*)p);
        cout << endl;
#else
        cout << s << endl;
#endif
      }
    }
  }
  
  return 0;
}
CURLを使った場合のレスポンスwriterを書かなくてもいいので、かなりスッキリしましたね。

ソースはgithubにも置いてあるのでよろしければ試してみて下さい。
mattn's kankeinai-bot at master - GitHub

関係ないbot

http://github.com/mattn/kankeinai-bot

えっ?全然botじゃない?

bot関係ないだろ!jk

C++の設計と進化 C++の設計と進化
ビョーン ストラウストラップ
ソフトバンククリエイティブ / ¥ 4,200 (2005-01-19)
 
発送可能時間:在庫あり。

Posted at 00:43 in ソフトウェア::lang::c | WriteBacks (1)
Tagged as: c, c++, irc, nanowww, picojson
Bookmarks: このエントリーのtweets add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip | add to buzzurl add to buzzurl | add to fc2bookmark add to fc2bookmark | add to Yahoo Bookmark add to Yahoo Bookmark | add to Pookmark add to Pookmark