記事一覧

YAYAでBase64エンコード

09/07/27 追記

YAYA/Tc535-2で追加されたFREADENCODEを使うことで簡単にBase64エンコードが可能となりました。
ありがとうございます。

sys.fnc.base64encode
{
  _filename = _argv[0]
  FCHARSET('binary')
  if !FOPEN(_filename, 'rb') {
    return
  }
  _base64 = FREADENCODE(_filename, 0, 'base64')
  FCLOSE(_filename)

  _base64
}

以下で紹介するやり方では容量の大きいデータを扱うことはできないようです。
100kB程度でフリーズします。

追記ここまで

さまざまなアプローチがある

YAYAにはBase64エンコードする関数が既に用意されています。

単純なテキストデータならこれでエンコードできるのですが、画像ファイルなどのバイナリデータとなるとそうもいきません。

上記関数の注意書きにある通り、YAYAではバイト値 0x00を文字列に含むことができないのです。
素直にSAORI等の外部プラグインモジュールを作成するのが良さそうですが、残念なことに私はC/C++がわかりません。

悩んでいたところ、「それ100% Pure YAYAでできるよ」といったアドバイスを受け、YAYAで書いてみることにしました。

バイト値 0x00問題をどう解決したものか思い付きませんでしたが、 @lre 氏に、「FREADBIN の引数charを変えて二回読み込んで差分をとる」というアイディアを頂きました。その発想は無かった。多謝。

ソース

以下のようなコードで動作しました。Base64デコードすると画像ファイルもちゃんと復元できました。
対象となる画像ファイルをあらかじめコピーしておいて2つを同時に読み込んで比較します。冗長なやり方になりますが100% Pure YAYAなのが最大の魅力です。

sys.fnc.base64encode
{
  _filename1 = _argv[0]
  _filename2 = _argv[1]
  _outputfilename = _argv[2]
  _ret = ''
  _bin = ''
  _chr1 = CHR(0x01)
  _chr2 = CHR(0x02)
  _base64table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  FCHARSET('binary')
  if !FOPEN(_filename1, 'rb') {
    return
  }
  FCHARSET('binary')
  if !FOPEN(_filename2, 'rb') {
    FCLOSE(_filename1)
    return
  }
  _buffer = 1
  while 1 {
    _line1 = FREADBIN(_filename1, _buffer, _chr1)
    _line2 = FREADBIN(_filename2, _buffer, _chr2)
    if (_line1 == -1 || _line1 == '' || _line2 == -1 || _line2 == '') {
      break;
    }
    if _line1 != _line2 {
      _bin += '00000000'
    }
    else {
      _bin += sys.fnc.size8(TOBINSTR(CHRCODE(_line1)))
    }
  }
  FCLOSE(_filename1)
  FCLOSE(_filename2)
  for _i = 0; _i < (STRLEN(_bin) % 6); _i++ {
    _bin += '0'
  }
  for _i = 0; _i < (STRLEN(_bin) / 6); _i++ {
    _str = SUBSTR(_bin, _i * 6, 6)
    _int = BINSTRTOI(_str)
    _chr = SUBSTR(_base64table, _int, 1)
    _ret += _chr
  }
  for _i = 0; _i < (STRLEN(_ret) % 4); _i++ {
    _ret += '='
  }
  // 第三引数が無ければ文字列をそのまま返す
  if _outputfilename == '' {
    _ret
    return
  }
  // 第三引数にファイルパスがあればそれに書き出す
  _filename = _outputfilename
  FCHARSET('UTF-8')
  if !FOPEN(_filename, 'w') {
    return
  }
  for _i = 0; _i <= (STRLEN(_ret) / 76); _i++ {
    _str = SUBSTR(_ret, _i * 76, 76)
    FWRITE(_filename, _str)
  }
  FCLOSE(_filename)
}

sys.fnc.size8
{
  _str = _argv[0]
  _ret = ''
  if STRLEN(_str) > 8 {
    _ret = SUBSTR(_str, -8, 8)
  }
  else {
    _ret = _str
    for _i = 0; _i < (8 - STRLEN(_str)); _i++ {
      _ret = '0' + _ret
    }
  }
  _ret
}

1バイトずつ読み込んでいるのでCHRCODEの戻り値は0x??になると思っていたのですが、何故か0xFF??という0xFF00がくっついたデータも存在しました。何ですかこれは。
関数sys.fnc.size8で、なんとなく0xFF00を取り除いてみたところ正しくエンコードできたのですが、きちんと理解できずに何となく動くからそうする、というのは気持ち悪いですね。
YAYAのソースを覗いてみたところintegerにキャストしてるっぽいところまではわかりましたがどうして0xFF00がくっついてくるのかはわかりません。どなたかわかる方いらっしゃいましたら教えて頂けるとありがたいです。

「伺か」でデータベースを扱う

SAORIでSQLite

中文偽春菜後援會論壇でsqlori.dllというSAORIがリリースされています。

早速試してみた

スクリーンショットを折りたたみ

続きを読む

SakuraScriptの括弧内で特殊文字を使う

\![...]の中に特殊文字を使う

確認したbasewareはSSP/2.01.84

「]」を使う

「]」の前に「\」を入れてエスケープする

\![raise,OnTest,\0\s[0\]こんにちは。\e]\e

最後の「]」の前に「\」を使いたい場合は「\」を「\」でエスケープする

\![raise,OnTest,円マーク→\\]\e

引数全体を「"」で囲む(SSP限定?)

\![raise,OnTest,"\0\s[0]こんにちは。\e"]\e
「,」を使う

引数全体を「"」で囲む(SSP限定?)

\![call,ghost,"媛儀prototype,re:try"]\e
「"」を使う

引数全体を「"」で囲んだ後、「"」を2個続けて記述したところが「"」として扱われる(SSP限定?)

\![call,ghost,"the ""MobileMaster"""]\e

SHIORIでマウスジェスチャー

SSPのマウスジェスチャーが使いづらい件

SSP/2.01.80-81あたりで追加されたマウスジェスチャーの仕様が不評のようです

そもそもSHIORI側でできそうなことをあえてベースウェアで実装するに至ったのには何か深い理由があるであろうことは想像に難くないですがこれではちょっとあんまりですね。

YAYAで書いてみた

もうちょっと使いやすく。SHIORI側で柔軟に制御することを目標とする。

#define TOLERANCE 10

OnMouseDown
{
  // 右クリック
  if reference[5] == 1 {
    this._isMousedown = 1
    this._startGuesture(reference)
  }
}

OnMouseMove
{
  if this._isMousedown {
    this._progressGesture(reference)
  }
}

OnMouseUp
{
  if this._isMousedown {
    ERASEVAR('this._isMousedown')
    this._stopGuesture(reference)
    return
  }
}

this._startGuesture
{
  this._lastX = _argv[0]
  this._lastY = _argv[1]
  this._directionChain = ''
  this.startX = _argv[0]
  this.startY = _argv[1]
  this._scope = _argv[3]
  this._collision = _argv[4]
}

this._progressGesture
{
  _x = _argv[0]
  _y = _argv[1]
  _dx = _x - this._lastX; if (_dx < 0) { _dx *= -1 }
  _dy = _y - this._lastY; if (_dy < 0) { _dy *= -1 }
  if (_dx < TOLERANCE) && (_dy < TOLERANCE) { return }
  _direction = ''
  if (_dx > _dy) {
    if _x < this._lastX {
      _direction = 'L'
    } else {
      _direction = 'R'
    }
  } else {
    if _y < this._lastY {
      _direction = 'U'
    } else {
      _direction = 'D'
    }
  }
  _lastDirection = SUBSTR(this._directionChain, -1, 1)
  if (_direction != _lastDirection) {
    this._directionChain += _direction
  }
  this._lastX = _x
  this._lastY = _y
}

this._stopGuesture
{
  if this._directionChain != '' {
    "\![raise,OnMouseGestureEx/
      ,%(this._scope)/
      ,%(this._lastX)%(CHR(1))%(this._lastY)/
      ,%(_argv[4])/
      ,%(this.startX)%(CHR(1))%(this.startY)/
      ,%(this._collision)/
      ,%(this._directionChain)]\e"
  }
  ERASEVAR('this._scope')
  ERASEVAR('this._collision')
  ERASEVAR('this._lastX')
  ERASEVAR('this._lastY')
  ERASEVAR('this.startX')
  ERASEVAR('this.startY')
  ERASEVAR('this._directionChain')
}

OnMouseGestureEx
{
  _action = ''
  if reference[0] == 0 {
    case reference[5] {
    when 'L' { _action = '戻る' }
    when 'R' { _action = '進む' }
    when 'D' { _action = '新窓' }
    when 'DR' { _action = '閉じる' }
    when 'UD' { _action = '更新' }
    when 'UDUDRLRLDLUR' { _action = 'エターナルフォースブリザード' }
    others { _action = '未定義' }
    }
    '\0\_q' + reference[5] + ' : ' + _action + '\_q\e'
  }
}

参考にしたもの

ここまで書いて何故か動かなかった。
よく調べてみたらSSPではOnMouseDownの後はOnMouseMoveが通知されないらしい(´・ω・`)

大人しくOnMouseGesture使えってことですね。

09/06/29 追記

SSP/2.01.82でOnMouseMoveが通知されるようになりましたね。
昨日書いたコードに間違いがあったので修正して試したところ、エターナルフォースブリザードが打てるようになりました。
ありがとうございます。

OnMouseGestureを使わない上のコードの使い方

  • TOLERANCEという所を変えることで感度を調整できます。
  • OnMouseGestureExという独自イベントを実装していますが、OnMouseGestureとの違いは最後に一度しか通知されない点、reference[5]に今までの軌跡がまとめて通知される点です。

華和梨で書いてみた

YAYAで書いたものと機能は全く一緒です。

TOLERANCE : 10

event.OnMouseDown : $(
  if $[ $(.get System.Request.Reference5[0]) == 1 ] $(
    .setstr this._isMousedown 1;
    .entry this._startGuesture;
  );
)

event.OnMouseMove : $(
  if $[ ${this._isMousedown} == 1 ] $(
    .entry this._progressGesture;
  );
)

event.OnMouseUp : $(
  if $[ ${this._isMousedown} == 1 ] $(
    .clear this._isMousedown;
    .entry this._stopGuesture;
  );
)

this._startGuesture : $(
  .setstr this._lastX $(.get System.Request.Reference0[0]);
  .setstr this._lastY $(.get System.Request.Reference1[0]);
  .clear this._directionChain;
  .setstr this.startX $(.get System.Request.Reference0[0]);
  .setstr this.startY $(.get System.Request.Reference1[0]);
  .setstr this._scope $(.get System.Request.Reference3[0]);
  .setstr this._collision $(.get System.Request.Reference4[0]);
)

this._progressGesture : $(
  .setstr @x $(.get System.Request.Reference0[0]);
  .setstr @y $(.get System.Request.Reference1[0]);
  .setstr @dx $[ ${@x} - ${this._lastX} ];
  .setstr @dy $[ ${@y} - ${this._lastY} ];
  if $[ ${@dx} < 0 ] $(
    .setstr @dx $[ ${@dx} * (-1) ];
  );
  if $[ ${@dy} < 0 ] $(
    .setstr @dy $[ ${@dy} * (-1) ];
  );
  if $[ !((${@dx} < ${TOLERANCE}) && (${@dy} < ${TOLERANCE})) ] $(
    .clear @direction;
    if $[ ${@dx} > ${@dy} ] $(
      if $[ ${@x} < ${this._lastX} ] $(
        .setstr @direction L;
      ) else $(
        .setstr @direction R;
      );
    ) else $(
      if $[ ${@y} < ${this._lastY} ] $(
        .setstr @direction U;
      ) else $(
        .setstr @direction D;
      );
    );
    .setstr @lastDirection $(.substr ${this._directionChain} -1 1);
    if $[ ${@direction} != ${@lastDirection} ] $(
      .setstr this._directionChain ${this._directionChain}${@direction};
    );
    .setstr this._lastX ${@x};
    .setstr this._lastY ${@y};
  );
)

this._stopGuesture : $(
  if $[ $(.length ${this._directionChain}) > 0 ] $(
    .echo \![raise,OnMouseGestureEx,${this._scope},${this._lastX}$(.chr 1)${this._lastY},$(.get System.Request.Reference4[0]),${this.startX}$(.chr 1)${this.startY},${this._collision},${this._directionChain}]\e;
  );
  .clear this._scope;
  .clear this._collision;
  .clear this._lastX;
  .clear this._lastY;
  .clear this.startX;
  .clear this.startY;
  .clear this._directionChain;
)

event.OnMouseGestureEx : $(
  .clear @action;
  if $[ $(.get System.Request.Reference0[0]) == 0 ] $(
    .setstr @action $(.entry gesture.$(.get System.Request.Reference5[0]) 未定義);
  );
  .echo \0\_q$(.get System.Request.Reference5[0])" ":" "${@action}\_q\e;
)

gesture.L            : 戻る
gesture.R            : 進む
gesture.D            : 新窓
gesture.DR           : 閉じる
gesture.UD           : 更新
gesture.UDUDRLRLDLUR : エターナルフォースブリザード

09/06/30 追記

里々で書く場合はOnMouseMoveで重い処理を行わないように注意が必要です。
イベント通知間隔がOnSecondChangeの比ではないので、ssu.dllなどのSAORI呼び出しも使わないようにしましょう。

里々で書いてみた

YAYAで書いたものと機能は全く一緒です。
ssu.dllを一切使わず高速です。
Mc145-1以降のバージョンの里々で動作します。

@TOLERANCE
10

@OnMouseDown
(
【タブ】when
【タブ】,(R5)==1
【タブ】,(
【タブ】【タブ】set
【タブ】【タブ】,this._isMousedown
【タブ】【タブ】,1
【タブ】)(
【タブ】【タブ】call
【タブ】【タブ】,this._startGuesture
【タブ】)
)

*OnMouseMove
>this._progressGesture【タブ】(call,this._isMousedown)==1

@OnMouseUp
(
【タブ】when
【タブ】,(call,this._isMousedown)==1
【タブ】,(
【タブ】【タブ】set
【タブ】【タブ】,this._isMousedown
【タブ】【タブ】,
【タブ】)(
【タブ】【タブ】call
【タブ】【タブ】,this._stopGuesture
【タブ】)
)

@this._startGuesture
(set,this._lastX,(R0))φ
(set,this._lastY,(R1))φ
(set,this._directionChain,)φ
(set,this.startX,(R0))φ
(set,this.startY,(R1))φ
(set,this._scope,(R3))φ
(set,this._collision,(R4))

*this._progressGesture
$x【タブ】(R0)
$y【タブ】(R1)
$dx=(x)-(when,(変数「this._lastX」の存在),(this._lastX),0)
$abs_dx=(dx)*(-1)
$dx【タブ】(when,(dx)<0,(abs_dx),(dx))
$dy=(y)-(when,(変数「this._lastY」の存在),(this._lastY),0)
$abs_dy=(dy)*(-1)
$dy【タブ】(when,(dy)<0,(abs_dy),(dy))
$dummy【タブ】(when,(dx)<(TOLERANCE)&&(dy)<(TOLERANCE),,(call,this._progressGestureNext))

@this._progressGestureNext
(
【タブ】when
【タブ】,(dx)>(dy)
【タブ】,(
【タブ】【タブ】when
【タブ】【タブ】,(x)<(this._lastX)
【タブ】【タブ】,(set,direction,L)
【タブ】【タブ】,(set,direction,R)
【タブ】),(
【タブ】【タブ】when
【タブ】【タブ】,(y)<(this._lastY)
【タブ】【タブ】,(set,direction,U)
【タブ】【タブ】,(set,direction,D)
【タブ】)
)(
【タブ】when
【タブ】,(direction)!=(call,lastDirection)
【タブ】,(set,this._directionChain,(call,this._directionChain)(direction))
)(
【タブ】set
【タブ】,lastDirection
【タブ】,(direction)
)(
【タブ】set
【タブ】,this._lastX
【タブ】,(x)
)(
【タブ】set
【タブ】,this._lastY
【タブ】,(y)
)

@this._stopGuesture
(
【タブ】when
【タブ】,(変数「this._directionChain」の存在)
【タブ】,(call,sendOnMouseGestureEx)\e
)(
【タブ】call
【タブ】,clearMouseGestureParameter
)
@sendOnMouseGestureEx
\![raise,OnMouseGestureEx,(call,this._scope),(call,this._lastX)(バイト値,1)(call,this._lastY),(R4),(call,this.startX)(バイト値,1)(call,this.startY),(call,this._collision),(call,this._directionChain)]
@clearMouseGestureParameter
(set,this._scope,)φ
(set,this._collision,)φ
(set,this._lastX,)φ
(set,this._lastY,)φ
(set,this.startX,)φ
(set,this.startY,)φ
(set,this._directionChain,)φ
(set,x,)φ
(set,y,)φ
(set,dx,)φ
(set,dy,)φ
(set,abs_dx,)φ
(set,abs_dy,)φ
(set,direction,)φ
(set,lastDirection,)

@OnMouseGestureEx
(
【タブ】when
【タブ】,(R0)==0
【タブ】,(
【タブ】【タブ】when
【タブ】【タブ】,(単語群「gesture.(R5)」の存在)
【タブ】【タブ】,(
【タブ】【タブ】【タブ】set
【タブ】【タブ】【タブ】,action
【タブ】【タブ】【タブ】,(call,gesture.(R5))
【タブ】【タブ】),(
【タブ】【タブ】【タブ】set
【タブ】【タブ】【タブ】,action
【タブ】【タブ】【タブ】,未定義
【タブ】【タブ】)
【タブ】)\0\_q(R5) : (action)\_q\e(set,action,)
)
@gesture.L
戻る
@gesture.R
進む
@gesture.D
新窓
@gesture.DR
閉じる
@gesture.UD
更新
@gesture.UDUDRLRLDLUR
エターナルフォースブリザード

【タブ】は適宜Tabに置き換えて使用してください。

ブラウザにキャラクタを常駐させるUserScript

ゴースト「うじゅら&うじゅう」

narではなくてグリモンです。

Firefox, Opera, Google Chromeで動作を確認しております。

参考にさせて頂いたサイト様

茶。様作javascript製SHIORI「梳莉」を機能拡張して使用させて頂いております。

09/06/22 追記

Sleipnir + SeaHorse で文字化けしていたのでSleipnir用に書き直したバージョンを。

文字化けは直ったけどgoogleのトップで位置ずれを起こしている…。
IEのCSSの仕様ってどうなってるのか良く分かってないのですぐには直せそうにありません…、勉強不足ですみません。

IE + Trixie も確認しようと思ったのですが自分のところの環境では何故かTrixieが動作しませんでした。
2005年で開発が止まっているのであまり期待できないのかもしれないですね…。

YAYAで集合演算

華和梨の集合演算式というものの存在を初めて知ったので

06/01は集合演算記念日

YAYAで書いてみた

// 和集合演算関数
entry.plus
{
  _entry1 = EVAL(_argv[0])
  _entry2 = EVAL(_argv[1])
  _ret    = _entry1
  foreach _entry2; _e {
    if !(ASEARCH(_e, _ret) >= 0) {
      _ret ,= _e
    }
  }
  _ret
}
// 差集合演算関数
entry.minus
{
  _entry1 = EVAL(_argv[0])
  _entry2 = EVAL(_argv[1])
  _ret    = IARRAY()
  foreach _entry1; _e {
    if !(ASEARCH(_e, _entry2) >= 0) {
      _ret ,= _e
    }
  }
  _ret
}
// 積集合演算関数
entry.product
{
  _entry1 = EVAL(_argv[0])
  _entry2 = EVAL(_argv[1])
  _ret    = IARRAY()
  foreach _entry1; _e {
    if ASEARCH(_e, _entry2) >= 0 {
      _ret ,= _e
    }
  }
  _ret
}
/**
 * 和集合を使った例
 */
foo
{
  _plus = entry.plus('野菜', '果物')
  _ret  = ''
  foreach _plus; _p {
    _ret += _p + ', '
  }
  '\0\_q' + _ret + '\e'  // => \0\_qきゅうり, なす, スイカ, トマト, りんご, みかん, なし, \e
}

野菜 : array
{
  'きゅうり'
  'なす'
  'スイカ'
  'トマト'
}

果物 : array
{
  'りんご'
  'みかん'
  'なし'
  'スイカ'
  'トマト'
}

あまりスマートじゃないですね。
EVALは邪道。

通称「GENOウイルス」に関するご注意

ゴースト"シャープとフラット","毒電波「基地外警報発令中」"等の作者さんである「真夜中の散歩人」さんのサイト「真夜中の散歩道」が、「JSRedir-R」通称「GENOウイルス」に感染したとの情報をご本人より頂きました。
「GENOウイルス」に関してご存じない方はこちらを参照してください。

現在、適切かつ迅速な対処によりPCの復旧中であるご様子です。
2009/05/30 20:40現在「真夜中の散歩道」はウイルスにより悪意のあるコードを含むサイトに改修されてるようなので、絶対にアクセスしないようにしてください。
また、このウイルスに関して初めて知った方や、まだ対策を講じていない方は速やかに上記リンクのサイトに掲載されている情報を頼りに対策を施しておくことを強く推奨します。

なお、このエントリの執筆は「真夜中の散歩人」さんご本人により承諾を頂いております。

09/05/31 15:00 追記

PCの復旧作業が終わり、クリーンな環境でland.toサーバの方にサイトを移転された模様です。

移転先URL:

私の方で(JavaScriptを無効化した状態で)ブラウザでアクセスしてHTMLソースを確認してみましたが、怪しいコードは見受けられませんでした。

移転前の旧サイトは悪意のあるコードが含まれたページ全てを削除し空っぽの状態であるとのことですが、GENOウイルス関連の情報を見る限り、FTPのパスを抜かれている可能性があるので、再び外部より悪意のあるコードを含むページがアップロードされる可能性は充分にあり得ると思います。

「真夜中の散歩道」へリンクを張られているサイト運営者様は、旧サイトのURLを移転後の方へと変更して頂きますようお願い致します。

また、この件に限らず普段からセキュリティソフトのアップデートを心掛け、ウイルスの拡散を未然に防ぐようご協力頂きたくお願い申し上げます。

参考URL:通称「GENOウイルス」・同人サイト向け対策まとめ - 感染前の対策

最低限以下の項目は実践しておきましょう。

  • WindowsUpdate(IE6,IE7をお使いの場合はIE8にアップグレード)
  • ファイアウォールの設定で有害なIPの遮断
  • AcrobatReader,FlashPlayerのアップデート
  • Adobe Acrobat Readerの JavaScript 機能OFF
  • ブラウザのjavascriptを無効にする

「ブラウザのjavascriptを無効にする」の項目にGoogle Chromeが入ってなくて涙目なGoogle Chromeユーザの私ですが、起動時の引数に「--disable-javascript」とすることでJavaScriptを無効化できます。
面倒なので、どうしたらいいかわからない場合はこの際Firefoxに乗り換えて、「NoScript」というアドオンをインストールするのが一番手軽でおすすめです。

GHOSTのネットワーク更新状況を巡回して出力してみた

GHOSTのネットワーク更新状況を巡回して出力する何か

ゴーストの更新を捕捉するRSSが欲しくなったので作ってみました。
ついでなので色々な形式で出力してみました。

RSSはSSPのエクスプローラの一括更新で参照されるRSSと互換性があります。

巡回先リストを追加するための何か

起動したGHOSTの情報を吸い上げて巡回先リストに追加(既に存在している場合は「上書き更新」)するPLUGINです。

もちろん更新URLを持たないGHOSTは追加できません。
また、巡回先が404だったり、何らかの原因で更新時刻を取得できなかった場合は自動的に巡回リストから削除されます。

課題

バグ潰し

探せば20~30個は出てきます。決して大袈裟な数字ではなく。

更新時刻が取得できないのを何とか取得する

難易度高そうですが取得できないGHOSTさんがわりといるのでどうにかしたい。
登録完了通知を受けても反映されない場合は多分更新時刻の取得に失敗してリストから削除されたものと思われます。

負荷軽減・高速化

出力されたものは全て静的なファイルなので高速なのですが。
巡回が一件一件順番に回っているので100件とかになったら多分パンクする気がする。
マルチスレッドで処理できるような方法を探ってみる。

shell, plugin, headlineも追加できるようにする

理論上は難しいことではないのですが。
GHOSTに混ぜて出力すると見づらいだろうし、別に出力するほど更新に対応している数は少ないだろうし。
SSPで一括更新とかに利用できるなら便利かもしれない。
バグ潰しに比べて優先順位は落ちると思います。

ゴーストセンターのウカニバへのリンクをなんか@東側に変えるUserScript

Firefox, Opera, Google Chromeの最新版で動作を確認しております。

irc.gimite.net / #伺か開発 監視ツール

Lingrのサービス終了に伴い後継の候補と見られるチャットが試験的に運用されているようです。

IRCベースとのことで入室しないことにはROMもできないようですが、リアルタイムでログを外部に出力しているので閲覧は可能のようです。
ただブラウザで常駐するのは厳しいですしLingrRaderのような監視ツールが個人的に欲しいなと思ったのでやっつけ仕事でPLUGINで作ってみました。
例によって自作のPLUGINの使いまわしです。

TL get interval を 1 min に設定しておけばほぼリアルタイムの監視ツールとして代用が利くのではないかと思います。

注意事項

試験運用中な上、まだこのチャットで正式に決まったわけではない模様です。
PLUGINは09/05/07現在のレイアウトと仕様に合わせて作成していますが明日には使えなくなるかもしれません。
余計な負荷がかかってご迷惑をお掛けすると判断した場合には即座に公開を停止致します。

09/05/24 追記

こちらで正式に確定した模様です。