M.C.P.C.

―むり・くり―プラスコミュニケーション(更新終了)


| トップページ |

2011年9月25日 23:08

Google ChromeウェブブラウザにMojolicious::LiteサーバからWebSocketで接続しリアルタイムでTwitterのストリームをニコニコ動画風に表示する

このエントリーをはてなブックマークに追加 mixiチェック

Twitter Streaming APIからの出力をリアルタイムにWebブラウザに表示する実装を作るにあたり、Mojolicious::LiteサーバとGoogle ChromeブラウザをWebSocketで接続すればいいと考え、実際にできるかどうか試したのは昨日のエントリでやりましたが、表示のさせ方を変えてみました。

声の高さやアクセントをちょっといじってみた。

どう見てもニコニコ動画です。本当にありがとうございました

コメントの当り判定がすごい甘いのでコメント同士が重なってしまっているのです。

これは少しずつ直していかなくてはならないなあ。

#!/usr/bin/perl
 
use AnyEvent::Twitter::Stream;
use Config::Pit;
use DateTime;
use Encode;
use EV;
use File::Spec;
use FindBin::Real;
use HTML::Entities;
use Mojolicious::Lite;
use Mojo::JSON;
use URI::Escape;
use utf8;
use YAML;
 
my $keys = YAML::LoadFile(
  File::Spec->catdir( FindBin::Real::Bin(), '..', 'consumer_keys.yaml' )
);
my $pit = pit_get( 'twitter.com@CLCLCL' );
 
my $clients = {};
my $done = AnyEvent->condvar;
 
my $listener = AnyEvent::Twitter::Stream->new(
  consumer_key    => $keys->{consumer_key},
  consumer_secret => $keys->{consumer_key_secret},
  token           => $pit->{access_token},
  token_secret    => $pit->{access_token_secret},
  method => 'sample',
  #method => 'filter',
  #track  => 'dtp',
  on_tweet => sub {
    my $tweet = shift;
    my $user = $tweet->{user}{screen_name};
    my $text = $tweet->{text} || '';
    my $lang = $tweet->{user}{lang} || '';
    return if $lang ne 'ja';
    return unless $user && $text;
    $user =~ s/./*/g;
    $text =~ s/\@[^\s:]+/@****/g;
    $text =~ s/[^\s]+さん/****ちゃん/g;
    $text =~ s/[^\s]+ちゃん/****さん/g;
    $text =~ s/[^\s]+くん/****クン/g;
    $text =~ s{http://t.co/[\w\-]+}{http://t.co/******}g;
    print $user." : ".encode_utf8($text)."\n";
    my $send_datetime;
    $send_datetime = sub {
      my $json = Mojo::JSON->new;
      my $dt   = DateTime->now( time_zone => 'Asia/Tokyo');
      my $str = $json->encode({
        hms  => $dt->hms,
        user => $user,
        text => $text,
      });
      utf8::decode( $str );
      for (keys %$clients) {
        $clients->{$_}->send_message( $str );
      }
    };
    $send_datetime->();
  },
  on_error => sub {
    my $error = shift;
    warn "ERROR: $error";
    $done->send;
  },
  on_eof   => sub {
    $done->send;
  },
);
 
get '/' => 'index';
 
 
websocket '/echo' => sub {
  my $self = shift;
 
  app->log->debug(sprintf 'Client connected: %s', $self->tx);
  my $id = sprintf "%s", $self->tx;
  $clients->{$id} = $self->tx;
 
  $self->on_message();
 
  $self->on_finish(
    sub {
      app->log->debug('Client disconnected');
      delete $clients->{$id};
    }
  );
};
 
app->start;
 
__DATA__
@@ index.html.ep
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Mojolicious + WebSocket + Twitter Streaming API</title>
    %= javascript '/js/jquery.js';
    <script type="text/javascript">
    var Ji = function( msg ) {
      this.msg = msg;
      this.screen = {};
      this.screen.width = $('#stage').width() || 100;
      this.position = {};
      this.position.x = this.screen.width;
      this.position.y = 0;
      this.id = 0;
      this.create();
      this.speed = 0;
      this.start();
      this.number = 0;
    };
    Ji.objects = [];
    Ji.prototype = {
      slice: 66,
      time: 5000,
      height: 40,
      move : function() {
        // console.log( this.position.x );
        this.position.x -= this.speed;
        this.show();
        if ( this.position.x < this.width * -1 ) {
          clearInterval( this.id );
          this.destroy();
        }
      },
      start : function() {
        var self = this;
        this.speed = ( this.screen.width + this.width )  / (this.time / this.slice );
        this.id = setInterval( function(){ self.move(); }, this.slice);
      },
      create : function() {
        this.object = $('<span>').html(this.msg).css('visiblity','hidden').appendTo('#stage');
        if (Ji.objects.length) {
          var item = Ji.objects[Ji.objects.length - 1];
          if (item.position.x + item.width > this.screen.width ) {
            this.position.y = item.position.y + this.height;
          }
        }
        this.width = this.object.width() + 1;
        this.object.css({
          visiblity: 'visible',
          display  : 'block',
          width    : this.width + 'px',
          left     : this.position.x + 'px',
          top      : this.position.y + 'px'
        });
        Ji.objects.push(this);
      },
      show: function() {
        $(this.object).css('left', this.position.x + 'px');
      },
      destroy : function() {
        $(this.object).remove();
        Ji.objects.pop();
      }
    };
    $(function () {
      var show = function (text) {
        var sel = 'textarea';
        $(sel).val( text + "\n" + $(sel).val() );
      };
      var ws = new WebSocket('ws://' + location.host + '/echo');
      ws.onopen = function () {
        console.log('Connection opened');
      };
      ws.onmessage = function (msg) {
        var res = JSON.parse(msg.data);
        var d = new Ji(res.text); 
      };
    });
    </script>
    <style type="text/css">
      html, body { height: 98%; }
      h1 { font-size: 14px; }
      span {
        position: absolute;
        font-size: 36px;
        font-family: 'A-OTF 新ゴ Pro M';
      }
      body {
        overflow: hidden;
      }
    </style>
  </head>
  
  <body>
    <header>
      <h1>Mojolicious + WebSocket + Twitter Streaming API</h1>
    </header>
    <article id="stage">
    </article>
  </body>
</html>

実は、横に流れるオブジェクト一つ一つがタイマーを持っているという無駄な作りでありまして、すごい重いので、スタックに積んでおいて、タイマー1つでスタックにあるオブジェクトを一つ一つ動かす、など言う実装のほうがいいのかもしれません。


(2011-10-10 15:50追記)

このままだと、素の状態から構築したときEVモジュールが入っていないと動かないことが判明したので、use EV;を追加しました。

投稿 大野 義貴 [Web] | |

トラックバック(0)

トラックバックURL: http://blog.dtpwiki.jp/MTOS/mt-tb.cgi/3720

コメントする