gotin blog

Whatever gotin wanna write

lingrクライアントをGreasemonkeyで作ってみた

lingrクライアントをGreasemonkeyで作ってみました。
↓こちらです
lingr.user.js

と言っても、全然機能はショボショボです。roomを選ぶことすらできません^^;
gotin部屋専用になっちゃってます。
※うまく動作しないこともあるかもしれませんがあしからず><

Greasemonkeyで作ったlingrクライアントをちょっとだけ改造中に最新版があります。だいぶマシになったと思います。gotin部屋以外のあらゆる部屋に行けるようになってます。

使い方:

  1. インストールしたら適当なページを読み込みます(今のこのページをreloadでよいです)
  2. Shift-Lを押す
  3. プロンプトが出てくるのでそこに適当なニックネームを入力する
  4. チャット用画面が出てくる
  5. しばらくするとgotin部屋に入った旨が表示される
  6. 何か発言する(上の方の入力フィールドが発言用のものです)

当たり前ですが、lingrのサービスが動いてないと(アクセスできない状態だと)まともに動きません。念のため。

今後作りたい機能:

  • room選び機能
  • サインイン・アウト機能
  • 「amazon javascript」なんて発言したら"javascript"をキーワードにamazon検索した結果を発言する機能
  • amazon以外もいろいろ

どうでもいいけど今lingrってこみこみなんでしょうか?
今現在(2007/01/31 23:09)全然アクセスできなくなっちゃってますがそれは私だけかな。。
→ほんとに私だけだったかも。。><

「今後作りたい機能」に書いたことをどんどこ作りたいんだけどナ。。
とりあえずamazon検索結果をいじくるコードの素振りでもして待ってます。。。
あ、それよかgoogle mapsの検索結果を無理矢理チャットに押し込んだりするほうが面白いかしら。ってかそんなのできる?できたらいいなー(笑)

あれ。。。そういえばAPI KEYそのまんまコードに入っちゃってるけどいいんだろうか。まずいのかな。。



ちょっとこまごまと不具合があったっぽいので修正できるかもしれないコードを取り急ぎここにメモっておく(全然汚いし、きっともっとスマートな書き方はあるだろうと思えるところがたくさんありますのであしからず)。

// ==UserScript==
// @name         lingr
// @namespace    http://gomaxfire.dnsdojo.com/
// @description  a lingr client
// @include      *
// @version      0.016
// ==/UserScript==

(function(){

  function $(id){
    return document.getElementById(id);
  }

  function mktag(tagName, option){
    var tag = document.createElement(tagName);
    if(option){
      for(a in option){
        if(option.hasOwnProperty(a)){
          tag[a] = option[a];
        }
      }
    }
    return tag;
  }


  // debug
  function debug(s){
    if(unsafeWindow.hasOwnProperty("console") && unsafeWindow.console.debug){
      unsafeWindow.console.debug(s);
    }
  }

  function debugByJSON(object){
    if(unsafeWindow.hasOwnProperty("console") && unsafeWindow.console.debug){
      unsafeWindow.console.debug(objectToJSON(object));
    }
  }


  var jsEncode =function(str){
    return str.replace(/\\/ig,"\\\\")
    .replace(/\f/ig,"\\f")
    .replace(/\n/ig,"\\n")
    .replace(/\r/ig,"\\r")
    .replace(/\t/ig,"\\t")
    .replace(/\'/ig,"\\'")
    .replace(/\"/ig,"\\\"");
  }

  var objectToJSON = function(object){
    var rtn;
    if(object==null){
      rtn = "null";
    }else{
      var callee = arguments.callee;
      switch(object.constructor){
      case Boolean:
        rtn = object ? "true" : "false";
        break;
      case Number:
        rtn = isNaN(object) || !isFinite(object)? "null" : object.toString(10);
        break;
      case String:
        rtn = ["\"", jsEncode(object), "\""].join("");
        break;
      case Array:
        var buf = [];
        for(var i=0;i<object.length;i++){
          buf.push(callee(object[i]));
        }
        rtn = ["[", buf.join(","), "]"].join("");
        break;
      case Object:
        var buf = [];
        for(var key in object){
          if(object.hasOwnProperty(key)){
            buf.push(callee(key)+":"+callee(object[key]));
          }
        }
        rtn = ["{", buf.join(","), "}"].join("");
        break;
      default:
        rtn = "null";
        break;
      }
    }
    return rtn;
  };

// Date/W3CDTF.js -- W3C Date and Time Formats
Date.W3CDTF = function ( dtf ) {
    var dd = new Date();
    dd.setW3CDTF = Date.W3CDTF.prototype.setW3CDTF;
    dd.getW3CDTF = Date.W3CDTF.prototype.getW3CDTF;
    if ( dtf ) this.setW3CDTF( dtf );
    return dd;
};

Date.W3CDTF.VERSION = "0.04";

Date.W3CDTF.prototype.setW3CDTF = function( dtf ) {
    var sp = dtf.split( /[^0-9]/ );

    // invalid format
    if ( sp.length < 6 || sp.length > 8 ) return;

    // invalid time zone
    if ( sp.length == 7 ) {
        if ( dtf.charAt( dtf.length-1 ) != "Z" ) return;
    }

    // to numeric
    for( var i=0; i<sp.length; i++ ) sp[i] = sp[i]-0;

    // invalid range
    if ( sp[0] < 1970 ||                // year
         sp[1] < 1 || sp[1] > 12 ||     // month
         sp[2] < 1 || sp[2] > 31 ||     // day
         sp[3] < 0 || sp[3] > 23 ||     // hour
         sp[4] < 0 || sp[4] > 59 ||     // min
         sp[5] < 0 || sp[5] > 60 ) {    // sec
        return;                         // invalid date 
    }

    // get UTC milli-second
    var msec = Date.UTC( sp[0], sp[1]-1, sp[2], sp[3], sp[4], sp[5] );

    // time zene offset
    if ( sp.length == 8 ) {
        if ( dtf.indexOf("+") < 0 ) sp[6] *= -1;
        if ( sp[6] < -12 || sp[6] > 13 ) return;    // time zone offset hour
        if ( sp[7] < 0 || sp[7] > 59 ) return;      // time zone offset min
        msec -= (sp[6]*60+sp[7]) * 60000;
    }

    // set by milli-second;
    return this.setTime( msec );
};

Date.W3CDTF.prototype.getW3CDTF = function() {
    var year = this.getFullYear();
    var mon  = this.getMonth()+1;
    var day  = this.getDate();
    var hour = this.getHours();
    var min  = this.getMinutes();
    var sec  = this.getSeconds();

    // time zone
    var tzos = this.getTimezoneOffset();
    var tzpm = ( tzos > 0 ) ? "-" : "+";
    if ( tzos < 0 ) tzos *= -1;
    var tzhour = tzos / 60;
    var tzmin  = tzos % 60;

    // sprintf( "%02d", ... )
    if ( mon  < 10 ) mon  = "0"+mon;
    if ( day  < 10 ) day  = "0"+day;
    if ( hour < 10 ) hour = "0"+hour;
    if ( min  < 10 ) min  = "0"+min;
    if ( sec  < 10 ) sec  = "0"+sec;
    if ( tzhour < 10 ) tzhour = "0"+tzhour;
    if ( tzmin  < 10 ) tzmin  = "0"+tzmin;
    var dtf = year+"-"+mon+"-"+day+"T"+hour+":"+min+":"+sec+tzpm+tzhour+":"+tzmin;
    return dtf;
};


  var XHR  = {
    count:0,
    post:function(url, params, func){
      XHR.count++;
      debug("try post:"+XHR.count);
      var array = [];
      for(a in params){
        if(params.hasOwnProperty(a)){
          array.push([encodeURIComponent(a),
                      "=",
                      encodeURIComponent(params[a])].join(""));
        }
      }
      var data = array.join("&");
      GM_xmlhttpRequest({url:url, 
            method:"POST",
            headers:{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
            data:data,
            onload:function(xhr){
            XHR.count--;
            debug("posted:"+XHR.count);
            func(xhr)},
            onerror:function(xhr){debug("error:"+url);debugByJSON(xhr);}});
    },
    get:function(url, params, func, headers){
      XHR.count++;
      debug("try get:"+XHR.count);
      var array = [];
      for(a in params){
        if(params.hasOwnProperty(a)){
          array.push([encodeURIComponent(a),
                      "=",
                      encodeURIComponent(params[a])].join(""));
        }
      }
      var data = array.join("&");
      if(typeof headers != "object"){
        headers = {};
      }
      GM_xmlhttpRequest({url:[url,data].join("?"), 
                         method:"GET",
                         headers:headers,
                         onload:function(xhr){
            XHR.count--;
            debug("got:"+XHR.count);
            func(xhr)},
                         onerror:function(xhr){debug("error:"+url);debugByJSON(xhr);}});
    }
  }

  function parseJSON(json){
    var obj = {};
    if(json){
      obj = eval(["(", json, ")"].join(""));
    }
    return obj;
  }

  var Lingr = function(nickname, apiKey, format){
    this.createCounter = Lingr.MAX_SESSION_TRY;

    this.nickname = nickname;
    this.baseURL = "http://www.lingr.com/api";
    this.apiKey = apiKey;
    this.format = format ? format : "";
  };
  
  Lingr.MAX_SESSION_TRY = 5;

  Lingr.prototype = {
    session_create:function(func){
      var self = this;
      var params = {"api_key":this.apiKey, 
                    "client_type":"human",
                    format:this.format};
      debug("try session_create....");
      XHR.post(this.baseURL + "/session/create",
               params,
               function(xhr){
                 debug("session_create:" + xhr.responseText);
                 var response = parseJSON(xhr.responseText);
                 if(response.status == "ok"){
                   self.session = response.session;
                   self.createCounter = Lingr.MAX_SESSION_TRY;

                   if(typeof func == "function") func(response);
                 } else if(self.createCounter > 0){
                   self.createCounter--;
                   debug(self.createCounter);
                   self.session_create(func);
                 }
               });
    },
    room_enter : function(roomId, func){
      //debug(this.session);
      var self = this;
      if(!this.session || !roomId)return;
      var params = {"session":this.session,
                    nickname:this.nickname, 
                    id:roomId,
                    format:this.format};
      debug("try room_enter....");
      XHR.post(this.baseURL + "/room/enter",
               params,
               function(xhr){
                 debug("room_enter:" + xhr.responseText);
                 //var response = eval(["(", xhr.responseText, ")"].join(""));
                 var response = parseJSON(xhr.responseText);
                 if(response.status != "ok") return;
                 self.ticket = response.ticket;
                 self.room = response.room;
//               self.counter = response.room.counter;
                 self.counter = 1;
                 if(typeof func == "function") func(response);
               });
    },
    room_say:function(message, func){
      if(!this.session || !this.ticket)return;
      var self = this;
      var params = {session:this.session, 
                    ticket:this.ticket, 
                    message:message, 
                    format:this.format};
      debug("try room_say....");
      XHR.post(this.baseURL + "/room/say",
               params,
               function(xhr){
                 debug("room_say:" + xhr.responseText);
                 //var response = eval(["(", xhr.responseText, ")"].join(""));
                 var response = parseJSON(xhr.responseText);
                 if(typeof func == "function") func(response);
               });
    },
    room_observe:function(func){
      if(!this.session || !this.ticket || !this.counter)return;
      var self = this;
      var params = {session:this.session, 
                    ticket:this.ticket,
                    format:this.format,
                    counter:this.counter};
      var headers = {Connection:"close"};
      debug("try room_observe....");
      //      XHR.get(this.baseURL + "/room/observe/",
      XHR.get("http://gotin.client" + 
              new Date().getTime() +
              Math.random() + 
              ".www.lingr.com/api/room/observe/",
              params,
              function(xhr){
                debug("room_observe:" + xhr.responseText);
                var response = eval(["(", xhr.responseText, ")"].join(""));
                if(response.status == "ok" && 
                   response.hasOwnProperty("counter")) 
                    self.counter = response.counter;
                if(typeof func == "function" && 
                   self.ticket && 
                   self.session) func(response);
              });
    },

    room_exit:function(func){
      if(!this.session || !this.ticket) return;
      var params = {session:this.session, 
                    ticket:this.ticket,
                    format:this.format};
      debug("try room_exit....");
      var self = this;
      XHR.post(this.baseURL + "/room/exit/",
              params,
              function(xhr){
                 debug("room_exit:" + xhr.responseText);
                 self.ticket = null;
                 if(typeof func == "function") func();
              });
      //this.ticket = null;
     
    },

    session_destroy:function(func){
      if(!this.session) return;
      var params = {session:this.session, 
                    format:this.format};
      debug("try session_destroy....");
      var self = this;
      XHR.post(this.baseURL + "/session/destroy/",
               params,
               function(xhr){
                 debug(xhr.status);
                 debug("session_destroy:" + xhr.responseText);
                 self.session = null;
                 var response = parseJSON(xhr.responseText);
                 debug(response.status);
                 if(typeof func == "function") func();
               });
     
    }

  };

  var lingr = null;
  var Editor = {
    init:function(){
      var div = mktag("div", {id:"__lingr__"});
      var messages = mktag("div", {id:"__messages__"});
      var header = mktag("div", {id:"__header__"});
      var footer = mktag("div", {id:"__footer__"});
      var form = mktag("form");
      var input = mktag("input", {id:"__input__",size:100});
      var submit = mktag("input", {type:"submit", value:"say"});
      div.appendChild(header);
      form.appendChild(input);
      form.appendChild(submit);
      footer.appendChild(form);
      div.appendChild(footer);
      div.appendChild(messages);
      document.body.appendChild(div);
      input.addEventListener("keydown", 
              function(event){KeyProcessor.code(event) == "esc" ? Editor.hide() : null;}, true);
      window.addEventListener("resize", Editor.resize, true);
      Editor.resize();
      Editor.makeStyle();
      form.addEventListener("submit", Editor.say, true);
    },

    say:function(event){
      if(!lingr)return;
      event.preventDefault();
      lingr.room_say($("__input__").value);
      $("__input__").value = ""
      return false;
    },
		
    resize:function(e){
      var div = $("__lingr__");
      var messages = $("__messages__");
      var header = $("__header__");
      var footer = $("__footer__");
      div.innerWidth = window.innerWidth;
      div.innerHeight = window.innerHeight;
      messages.style.width = (window.innerWidth - 35) + "px";
      messages.style.height = (window.innerHeight - 70) + "px";
      header.style.width = (window.innerWidth - 35) +"px";
      footer.style.width = (window.innerWidth - 35) +"px";
    },

    makeStyle:function(){
      var s= [];
      s.push("#__lingr__ {");
      s.push(" background-color:black;");
      s.push(" position:fixed;");
      s.push(" top:0;");
      s.push(" left:0;");
      s.push(" width:100%;");
      s.push(" height:100%;");
      s.push(" padding:10px;");
      s.push(" margin:0;");
      s.push(" opacity:0.90;");
      s.push(" z-index:999998;");
      s.push(" display:none;");
      s.push("}");
      s.push("#__header__ {");
      s.push(" background-color:gray;");
      s.push(" color:white;");
      s.push(" text-align:left;");
      s.push(" height:20px;");
      s.push(" padding:0;");
      s.push(" margin:0;");
      s.push(" margin-top:2px;");
      s.push("}");
      s.push("#__messages__ {");
      s.push(" background-color:white;");
      s.push(" padding:0;");
      s.push(" margin:0;");
      s.push(" overflow:scroll;");
      s.push(" text-align:left;");
      s.push("}");
      s.push("#__messages__ *{");
      s.push(" color:black;");
      s.push(" text-align:left;");
      s.push(" font-size:medium;");
      s.push("}");
      s.push("#__messages__ div.userType{");
      s.push(" font-size:small;");
      s.push(" color:green;");
      s.push("}");

      s.push("#__messages__ div.userType *{");
      s.push(" font-size:small;");
      s.push(" color:green;");
      s.push("}");

      s.push("#__messages__ div.userType span.__timestamp__ {");
      s.push(" margin:5px;");
      s.push(" color:blue;");
      s.push("}");

      s.push("#__messages__ div.systemType{");
      s.push(" margin-left:100px;");
      s.push(" font-size:small;");
      s.push(" color:orange;");
      s.push("}");

      s.push("#__messages__ div.systemType span.__timestamp__ {");
      s.push(" font-size:small;");
      s.push(" margin:5px;");
      s.push(" color:blue;");
      s.push("}");

      s.push("#__messages__ span.userText{");
      s.push(" margin-left:10px;");
      s.push("}");
      s.push("#__messages__ span.myselfText{");
      s.push(" color:red;");
      s.push(" margin-left:10px;");
      s.push("}");

      s.push("#__messages__ span a");
      s.push(" text-decoration:underline;");
      s.push(" color:blue;");
      s.push("}");

      s.push("#__messages__ span.systemText{");
      s.push(" font-size:small;");
      s.push(" margin-left:120px;");
      s.push(" color:orange;");
      s.push("}");
      s.push("#__footer__ {");
      s.push(" background-color:gray;");
      s.push(" color:white;");
      s.push(" text-align:left;");
      s.push(" height:20px;");
      s.push(" padding:0;");
      s.push(" margin:0;");
      s.push(" margin-top:2px;");
      s.push("}");
      GM_addStyle(s.join("\n"));
    },

    toggleView:function(){
      var div = $("__lingr__");
      if(div.style.display == "block"){
        Editor.hide();
      } else {
        Editor.show();
      }
    },
	
    show:function(){
      if(!lingr){
        var nickname = prompt("input your nickname", "foo");
        if(nickname){
          lingr = new Lingr(nickname, "4eace1b549534060808f65215dd1ecfb", "json");
          var showMessages = function(response){
            lingr.room_observe(showMessages);
            if(!response || response.status != "ok")return;
            var messageDiv = $("__messages__");
            var messages = response.messages;
            if(!messages)return;
            messages.forEach(function(message){
                var id = message.id;
                var iconURL = message.icon_url;
                var nickname = message.nickname;
                var myself = (nickname == lingr.nickname);
                var date = new Date.W3CDTF();
                date.setW3CDTF(message.timestamp);
                var time = date.toLocaleString();
                var text = message.text;
                var type = message.type;
                var cls = type == "user" ? "user" : "system";
                var user_div = mktag("div", 
                                     {className: cls + "Type"});
                if(iconURL){
                  var img = mktag("img", {src:iconURL});
                  with(img.style){
                    width="32px";
                    height = "32px";
                  }
                  user_div.appendChild(img);
                }
                if(type == "user"){
                  var name_span = mktag("span", {textContent:nickname});
                  user_div.appendChild(name_span);
                }
                var time_span = mktag("span", {textContent:time,className:"__timestamp__"});
                user_div.appendChild(time_span);
                if(cls == "user" && myself) cls = "myself";
                if(text.match(/(http:\/\/\S*\.(jpg|jpeg|png|gif))/)){
                  text = text.replace(/(http:\/\/\S*\.(jpg|jpeg|png|gif))/, 
                                      "<img src=\"" + RegExp.$1 +"\" />");
                } else if(text.match(/http:\/\/www\.youtube\.com\/watch\?v=(\S*)/)){
                  var vid = RegExp.$1;
                  text = text.replace(/http:\/\/www\.youtube\.com\/watch\?v=(\S*)/, 
                                      "<embed width=\"450\" height=\"370\" wmode=\"transparent\" type=\"application/x-shockwave-flash\" src=\"http://www.youtube.com/v/" + vid + "\"/>");
                } else if(text.match(/(http:\/\/\S*)/)){
                    var url = RegExp.$1;
                    text = text.replace(/(http:\/\/\S*)/, 
                                      "<a href=\"" + url +"\" >" + url + "</a>");
                } 
                
                var text_span = mktag("span",
                                      {innerHTML:text, 
                                       className:cls + "Text"});
                messageDiv.insertBefore(text_span, messageDiv.childNodes[0]);
                messageDiv.insertBefore(user_div, text_span);
              });
          };

          var setRoom = function(response){
            var header = $("__header__");
            header.textContent = [lingr.nickname, 
                                  " in ",
                                  response.room.name].join("");
            $("__lingr__").style.display = "block";
            $("__input__").focus();
            lingr.room_observe(showMessages);
          };

          var roomEnter = function(){
            lingr.room_enter("bKyYmUlUCTh", setRoom);
          };

          lingr.session_create(roomEnter);
          
          window.addEventListener("unload", 
                                  function(event){
                                    Editor.exit();
                                  }, true);
        } else {
          return;
        }
      } else {
        $("__lingr__").style.display = "block";
        $("__input__").focus();
      }
    },
    
    exit:function(){
      if(lingr && lingr.ticket && lingr.session){
        lingr.room_exit(function(){Editor.destroy();});
      }
    },
    
    destroy:function(){
      if(lingr && lingr.session){
        lingr.session_destroy(function(){       
            delete lingr;
            lingr = null;
          });
      }
    },

    hide:function(){
      var div = $("__lingr__").style.display = "none";
      Editor.exit();
    },

    isShown:function(){
      return $("__lingr__").style.display == "block";
    },
	
  };



  var KeyProcessor = {
    shortCutsWhenDisable:[],
    shortCuts:[],
    toggleShortCut:"",
    keyList:[],
    keyListWhenDisable:[],
    init:function(){
      document.addEventListener("keydown", KeyProcessor.process, true);
    },
	
    addShortCut:function(phrase, command, enable){
      enable = (enable != false) ? true: false;
      if(typeof command == "string" && command.match(/^\//)){
        KeyProcessor.addShortCutExec(phrase, command, enable);
      } else {
        if(enable){
          KeyProcessor.addShortCutWhenEnable(phrase, command);
        } else {
          KeyProcessor.addShortCutWhenDisable(phrase, command);
        }
      }
    },
    addShortCutWhenEnable:function(phrase, command){
      KeyProcessor.addShortCutAux(phrase, command, KeyProcessor.shortCuts);
    },
	
    addShortCutWhenDisable:function(phrase, command){
      KeyProcessor.addShortCutAux(phrase, command, KeyProcessor.shortCutsWhenDisable);
    },
    addShortCutExec:function(phrase, path, enable){
      if(enable != false){
        KeyProcessor.addShortCutExecWhenEnable(phrase, path);
      } else {
        KeyProcessor.addShortCutExecWhenDisable(phrase, path);
      }
    },
    addShortCutExecWhenEnable:function(phrase, path){
      KeyProcessor.addShortCutWhenEnable(phrase, KeyProcessor.makeExecFunc(path));
    },
    addShortCutExecWhenDisable:function(phrase, path){
      KeyProcessor.addShortCutWhenDisable(phrase, KeyProcessor.makeExecFunc(path));
    },

    addShortCutAux:function(phrase, command, shortCuts){
      if(typeof command == "function"){
        shortCuts.unshift({phrase:phrase, func:command});
      } else if(typeof command == "string"){
        shortCuts.unshift({phrase:phrase, func:function(){Commander.execute(command);}});
      }
    },
    makeExecFunc:function(path){
      var func = function(){Executer.execute(path);}
      if(path instanceof Array){
        func = function(){
          path.forEach(function(p){
              Executer.execute(p);
            });
        };
      }
      return func;
    },

    setToggleShortCut:function(phrase){
      KeyProcessor.addShortCutWhenDisable(phrase, 
                                          function(){
                                            Editor.toggleView();
                                          });
      KeyProcessor.addShortCut(phrase, 
                               function(){
                                 Editor.toggleView();
                               });
    },
	
    process:function(event){
      var tagName = event.target.tagName;
      if(tagName == "INPUT" || tagName == "TEXTAREA")return;
      if(Editor.isShown()){
        KeyProcessor.processAux(event,
                                KeyProcessor.keyList, 
                                KeyProcessor.shortCuts);
        KeyProcessor.keyListWhenDisable = [];
      } else {
        KeyProcessor.processAux(event, 
                                KeyProcessor.keyListWhenDisable, 
                                KeyProcessor.shortCutsWhenDisable);
        KeyProcessor.keyList = [];
      }
    },

    processAux:function(event, keyList, shortCuts){
      keyList.push(KeyProcessor.code(event));
      var mode = keyList == KeyProcessor.keyList ? "enable" : "disable";
      var phrase = keyList.join(" ");
      //    Editor.setCommand(phrase);
      var inStroke = false;
      var length = shortCuts.length;
      for(var i=0;i<length;i++){
        var shortCut = shortCuts[i];
        if(phrase == shortCut.phrase){
          shortCut.func();
          keyList.length = 0;
          inStroke = false;
          event.preventDefault();
          break;
        } else if(shortCut.phrase.indexOf(phrase) == 0
                  && phrase.length < shortCut.phrase.length){
          inStroke = true;
          event.preventDefault();
        }
      }
      if(!inStroke){
        keyList.length = 0;
        //      Editor.setCommand("");
      }
    },

    code:function(event){
      var code = [];
      if(event.shiftKey){
        code.push("S");
      } else if(event.ctrlKey){
        code.push("C");
      } else if(event.altKey){
        code.push("M");
      }
      code.push(KeyProcessor.kc2char(event.keyCode));
      return code.join("-");
    },
    kc2char:function(kc){
      var between = function(a,b){
        return a <= kc && kc <= b;
      };
		
      var _32_40 = "space pageup pagedown end home left up right down".split(" ");
      var kt = {
        8  : "back",
        9  : "tab"  ,
        13 : "enter",
        16 : "shift",
        17 : "ctrl",
        27 : "esc",
        46 : "delete",
			
      };
      return (
              between(65,90)  ? String.fromCharCode(kc+32) : // a-z
              between(48,57)  ? String.fromCharCode(kc) :    // 0-9
              between(96,105) ? String.fromCharCode(kc-48) : // num 0-9
              between(32,40)  ? _32_40[kc-32] :
              kt.hasOwnProperty(kc) ? kt[kc] : 
              kc
              );
    }
	
  };

  var Commander = {
    commands :{},
	
    addCommand:function(name, func, args, beforeFunc){
      Commander.commands[name] = {func:func, args:args, before:beforeFunc};
    },
    execute:function(name){
      var command = Commander.commands[name];
      if(command){
        var before = command.before;
        if(typeof before == "function"){
          before();
        }
        var func = command.func;
        var argInfos = command.args;
        var args = [];
        var cancel = false;
        if(argInfos && argInfos.length > 0){
          for(var i=0;i<argInfos.length;i++){
            var message = ["[" + name + "]"];
            var argInfo = argInfos[i];
            var length = args.length;
            for(var j=0;j<length;j++){
              message.push(argInfos[j].name + ":" + args[j]);
            }
            message.push(argInfo.name +":");
            var defaultValue = argInfo.defaultValue;
            if(typeof defaultValue == "function"){
              defaultValue = defaultValue();
            }
            var value = prompt(message.join("\n"), defaultValue);
            if(value){
              args.push(value);
            } else {
              cancel = true;
              break;
            }
          }
        }
        if(!cancel){
          func.apply(window, args);
        }
      }
    },
  };


  function init(){
    Editor.init();
    KeyProcessor.init();
    KeyProcessor.setToggleShortCut("S-l");
  }

  if(parent.document == document){
    init();
  }

})();




既知の問題:
ver 0.012
実行させていたページを閉じたりリロードしたりすると、次からアクセスできなくなり、www.lingr.comにもつながらなくなる。。
その状態になったらwww.lingr.comにリロード連打するとなぜかつながって、lingr.user.jsからもつながるようになる。
なぜこういう状態になるのか、誰か教えてくださーい、助けてーっ><

↑助けていただきました☆
gotinの日記 - amazon検索するlingrボットをGreasemonkeyで作ってみたlingrボットも作ってみたのですが、それでも同じ問題が起こっていました。そちらの修正が先にできたのですが、中身は同じなので同じように修正しました。

In Browser Implementation Notes in Lingr Developer Wikiに載っているんですが、ひとつのHTTPクライアントは2つ以上同じ場所にコネクションをつなぐべきでないというHTTPの仕様(HTTP 1.1 RFC)からFirefoxもそのような実装になっているようで、 それがひとつの原因になっておりました。発言待ち処理でkeep-aliveになってしまっていたためにそのコネクションが残ってしまい、↑のバグのような症状が出てしまったのだと思われます。

ということで発言待ち処理をkeep-aliveにせず、かつ別www.lingr.comの代わりにgotin.bot.www.lingr.comにアクセスするようにして上記問題を回避しています。それでも複数つなごうとすればやはりバグが発生すると思います。その回避策もつくればなんとかなると思いますが、まぁそんなに一度に使わないでねってことで修正は気が向いたらやることとします。

↑この問題についてはLegendes.jpの方(であってますよね?^^;)にLingr 関連の開発(at Lingr)でいろいろと教えていただきました。ありがとうございました!



更新履歴:

2007/01/31 ver 0.01 公開
2007/02/01 ver 0.011 日時表示機能実装
ver 0.012 発言入力欄でescしたら画面を閉じる機能実装
ver 0.013 色合いをちょこっと変更
2007/02/04 ver 0.014 ver0.01から発生していたコネクション関連のバグを修正
2007/02/07 ver 0.015 observe時のURLを時刻と乱数を追加して重なる可能性を低くした
2007/02/08 ver 0.016 imgなURLの場合画像表示を、youTubeなURLの場合動画表示をするようにした(未リンク。ソースだけ公開中)