2014年12月13日星期六

Re: [PerlChina] PerlChina Advent 14: Mojolicious+socket.io+Angular.JS

贴个源码 git 吧 好clone直接测试

On 2014年12月14日周日 上午10:53 chenlin rao <rao.chenlin@gmail.com> wrote:
# Mojolicious + SocketIO + AngularJS

今天本来是准备随便写点跟 Web 开发相关的话题,毕竟今年 YAPC 上最大的声音就是 SaywerX 的["CGI.pm must DIE!"](www.youtube.com/watch?v=tu6_3fZbWYw)。正在犹豫是介绍 [Mojolicious](https://metacpan.org/pod/Mojolicious) 还是 [PocketIO](https://metacpan.org/pod/PocketIO) 模块的时候,偶然在 gist 上看到一个单文件程序,结合了 Mojolicious、socket.io 和 angular.js 三大框架,简直就是任性。那么好,今天就拿这个做例子说一说好了:

## Mojolicious::Lite 的 DSL

因为是单文件程序,所以用的是 Mojolicious::Lite,这个提供了一些很方便的类 sinatra 的 DSL。常见的情况是 `get '/' => sub($c) { $c->render('index') }` 这样。如果一个 url 路径同时可能有 GET 或者 POST 请求,那么就用 `any` 指令。这里就是:

    use Mojolicious::Lite;
    use Protocol::SocketIO::Message;
    use Protocol::SocketIO::Handshake;
    use Class::Date;

    any '/' => 'index';
    any '/view1' => 'index';
    any '/view2' => 'index';

    any '/partials/:name' => sub {
        shift->render( $self->param('name') );
    };

在 url 路径这里可以做捕获,方便在控制器函数里处理。这里用到了 `/socket.io/` 这个路径。这是 socket.io 协议规定的固定路径。

    any '/socket.io/:id/' => sub {

渲染方法支持很多种方式。默认写法的话,会自动去找同名的 `.html.ep` 模板来渲染 —— 这种情况更简写的方式就是前面已经看到的 `any '/' => 'index'`,其实就是去找 `index.html.ep` 文件。

如果写 API ,很多时候返回的并不是 HTML 内容,Mojolicious 也支持渲染其他格式的响应。最常见的是 `->render(json => $ref)` 这样。不过这里 socket.io 因为有单独的协议,所以是用 Protocol::SocketIO(这个包就是出自原先我打算讲的 PocketIO 模块) 来单独生成响应。

        shift->render( text=> Protocol::SocketIO::Handshake->new(
            session_id        => 1234567890,
            heartbeat_timeout => 10,
            close_timeout     => 15,
            transports        => [qw/websocket xhr-polling/]
        ) );
    };

普通的 web 方法在 Mojolicious 里大致就是这样。下面进入更高级的异步交互环节了!

我们这里示例的是一个每秒自动更新时间的页面。所以 Mojolicious 要定时执行。`Mojo::IOLoop->delay` 是个完全值得单独讲一次的好东西:

    my $clients = {};

    my $delay = Mojo::IOLoop->delay;
    Mojo::IOLoop->recurring( 1 => sub {
        for my $id( keys %$clients ) {
            # send name
            $clients->{$id}->send( Protocol::SocketIO::Message->new( type => 'event', data=>{ name=>'send:name', args=>[{ name=>'Jamie '.$id }] }) );
            # send time
            $clients->{$id}->send( Protocol::SocketIO::Message->new( type => 'event', data=>{ name=>'send:time', args=>[{ time=>''.Class::Date->now }] }) );
        }
    });

Mojolicious 本身直接指示 websocket 协议。所以用起来 DSL 跟做 GET/POST 是一样的。而控制器函数里用法也跟 socket.io 的写法有些类似。采用 `$self->on(message => sub {})`。

在控制器里,Mojolicious 允许直接操作整个请求响应的事务主体,也就是代码中的 `$self->tx` 。在异步处理的时候,肯定会需要直接操作 tx,这里作为一个纯粹的定时器,就只需要 send 了:

    websocket '/socket.io/:id/websocket/:oid' => sub {
        my $self = shift;

        app->log->debug(sprintf 'Client connected: %s', $self->tx);
        my $id = sprintf "%s", $self->tx;
        $clients->{$id} = $self->tx;

        $self->tx->send( Protocol::SocketIO::Message->new( type => 'connect') );
        $self->tx->send( Protocol::SocketIO::Message->new( type => 'event', data=>{ name=>'send:name', args=>[{ name=>'Jamie starting...' }] }) );
        $self->on(message => sub {
            my ($self, $msg) = @_;
            # no messages are being sent for now
        });
        $self->on(finish => sub {
            app->log->debug('Client disconnected');
            delete $clients->{$id};
        });
    };

最后,调用 start 方法开始运行服务器:

    app->start;

## AngularJS 示例

Mojolicious::Lite 支持在 Perl 的 `__DATA__` 里直接写页面内容,每个页面以 @@开头命名即可:

    __DATA__

    @@index.html.ep

### angular 的模板和变量绑定

angular 深度的改造了 HTML 的样式和书写方式,在前端的层面上提供 MVC 功能。在使用 `ng-app` 标记整个页面归属的具体 angular 应用后,可以利用 `ng-controller` 指令将一个 div 关联到一个 angular 的控制器上。在这个 div 内,可以通过 `{{ }}` 语法加载控制器函数里的变量,可以渲染带有 `ng-view` 指令的 div 作为 HTML 内容展示。

    <!DOCTYPE html>
    <html ng-app="myApp">
    <head>
    <meta charset="utf8">
    <base href="/">
    <title>Angular Socket.io Seed App</title>
    <link rel="stylesheet" href="app.css">
    </head>
    <body>
        <div ng-controller="AppCtrl">
            <h2>Helloo {{name}}</h2>
            <ul class="menu">
                <li><a href="view1">view1</a></li>
                <li><a href="view2">view2</a></li>
            </ul>
            <div ng-view></div>
        <div>
            Angular Socket.io seed app: v<span app-version></span></div>
        </div>

        <script src="socket.js"> </script>
        <script src="app.js"> </script>
    </body>
    </html>

    @@partial1.html.ep
    <p>This is the partial for view 1.</p><p>The current time is {{time}}</p>

    @@partial2.html.ep
    <p>This is the partial for view 2.</p><p>Showing of 'interpolate' filter:
    {{ 'Current version is v%VERSION%.' | interpolate }}</p>

### angular 的应用模块

上面页面部分就完成了。下面就开始在 js 中完成这个 angular 应用。我们前面已经看到,这个页面归属的应用名字叫 **myApp**。下面是应用的代码:

    @@app.js
    'use strict';

    // Declare app level module which depends on filters, and services
    var app = angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives']).
      config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
        $routeProvider.when('/view1', {templateUrl: 'partials/partial1', controller: MyCtrl1});
        $routeProvider.when('/view2', {templateUrl: 'partials/partial2', controller: MyCtrl2});
        $routeProvider.otherwise({redirectTo: '/view1'});
        $locationProvider.html5Mode(true);
      }]);

非常清晰,实现这个 myApp,加载了对应的 filters、services、directives 模块,然后定义了两个路由规则,分别指向两个不同的模板和控制器。

### angular 的控制器

angular 的控制器,最主要的作用就是处理应用作用域 `$scope` 与其他各种数据的关联。在这个示例里,就是把前面模板里的变量,跟 socket.io 从服务器拿到的数据关联在一起:

    function AppCtrl($scope, socket) {
      socket.on('send:name', function (data) {
        $scope.name = data.name;
      });
    }

    function MyCtrl1($scope, socket) {
      socket.on('send:time', function (data) {
        $scope.time = data.time;
      });
    }
    MyCtrl1.$inject = ['$scope', 'socket'];

    function MyCtrl2() {
    }
    MyCtrl2.$inject = [];

### angular 的指令

angular 的指令的作用,就是实际操作、修改变更应用中的数据,包括可能页面元素的变化等等。一般的 Web 开发中,这个事情是交给 jQuery 来操作 DOM 的。而在 angular 里。编写成指令,可以直接写成 HTML 元素的属性,看起来非常清爽。比如这个示例中就是生成了一个 `appVersion` 指令,在前面的 HTML 里,用在了一个 span 元素上。

    angular.module('myApp.directives', []).
      directive('appVersion', ['version', function(version) {
        return function(scope, elm, attrs) {
          elm.text(version);
        };
      }]);

### angular 的过滤器

angular 的过滤器,可以利用管道的方式帮助模板中的变量达到更好的渲染效果,默认提供有 date、json、limitTo、orderBy、number、lowercase、uppercase 等几个过滤器。也可以自己写新的过滤器。过滤器函数很简单,传一个参数返回一个结果即可:

    angular.module('myApp.filters', []).
      filter('interpolate', ['version', function(version) {
        return function(text) {
          return String(text).replace(/\%VERSION\%/mg, version);
        }
      }]);

### angular 的服务和工厂

augular 利用服务(service)或者工厂(factory)的方式来提供整个应用里,不同路由或者说控制器之间共用的单例变量。这二者的不同在于:service 其实就是一种不导入其他变量的简单 factory 的简写。比如下面这段代码是原程序中用 factory 写的,虽然名叫 myApp.services:

    // Demonstrate how to register services
    // In this case it is a simple value service.
    angular.module('myApp.services', []).
      value('version', '0.1').
      factory('socket', function ($rootScope) {
        var socket = io.connect();
        return {
          on: function (eventName, callback) {
            socket.on(eventName, function () {
              var args = arguments;
              $rootScope.$apply(function () {
                callback.apply(socket, args);
              });
            });
          },
          emit: function (eventName, data, callback) {
            socket.emit(eventName, data, function () {
              var args = arguments;
              $rootScope.$apply(function () {
                if (callback) {
                  callback.apply(socket, args);
                }
              });
            })
          }
        };
      });

可以看到就没有导入其他东西,所以可以写成服务,代码应该是下面这样:

    angular.module('myApp.services', []).
      value('version', '0.1').
      service('socket', function ($rootScope) {
        var socket = io.connect();
        this.on = function (eventName, callback) {
          ...
        };
        this.emit = function (eventName, data, callback) {
          ...
        };
      });

## socket.io 接口

上面 angular 服务里,就演示了 socket.io 的客户端用法。可以看到,其实客户端的接口跟服务器端是一一对应的。

示例程序再往下就是纯粹的js和css文件,这就不再继续了。完整代码源地址见:<https://gist.github.com/rodrigolive/5546320>

## 警告

socket.io 的客户端 js 在本例中还是用的去年的 0.90 的版本。最近半年 socket.io 项目发生重大改变,从 1.0 开始,有了自己专门的协议分析库 engine.io,整个 url 路径和 handshake 编解码方式都不太一样了。在 Perl 方面,PocketIO 作者没时间跟进这个变化,所以 Protocol::SocketIO 还是只能支持 0.90 的版本。

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

[PerlChina] PerlChina Advent 14: Mojolicious+socket.io+Angular.JS

# Mojolicious + SocketIO + AngularJS

今天本来是准备随便写点跟 Web 开发相关的话题,毕竟今年 YAPC 上最大的声音就是 SaywerX 的["CGI.pm must DIE!"](www.youtube.com/watch?v=tu6_3fZbWYw)。正在犹豫是介绍 [Mojolicious](https://metacpan.org/pod/Mojolicious) 还是 [PocketIO](https://metacpan.org/pod/PocketIO) 模块的时候,偶然在 gist 上看到一个单文件程序,结合了 Mojolicious、socket.io 和 angular.js 三大框架,简直就是任性。那么好,今天就拿这个做例子说一说好了:

## Mojolicious::Lite 的 DSL

因为是单文件程序,所以用的是 Mojolicious::Lite,这个提供了一些很方便的类 sinatra 的 DSL。常见的情况是 `get '/' => sub($c) { $c->render('index') }` 这样。如果一个 url 路径同时可能有 GET 或者 POST 请求,那么就用 `any` 指令。这里就是:

    use Mojolicious::Lite;
    use Protocol::SocketIO::Message;
    use Protocol::SocketIO::Handshake;
    use Class::Date;

    any '/' => 'index';
    any '/view1' => 'index';
    any '/view2' => 'index';

    any '/partials/:name' => sub {
        shift->render( $self->param('name') );
    };

在 url 路径这里可以做捕获,方便在控制器函数里处理。这里用到了 `/socket.io/` 这个路径。这是 socket.io 协议规定的固定路径。

    any '/socket.io/:id/' => sub {

渲染方法支持很多种方式。默认写法的话,会自动去找同名的 `.html.ep` 模板来渲染 —— 这种情况更简写的方式就是前面已经看到的 `any '/' => 'index'`,其实就是去找 `index.html.ep` 文件。

如果写 API ,很多时候返回的并不是 HTML 内容,Mojolicious 也支持渲染其他格式的响应。最常见的是 `->render(json => $ref)` 这样。不过这里 socket.io 因为有单独的协议,所以是用 Protocol::SocketIO(这个包就是出自原先我打算讲的 PocketIO 模块) 来单独生成响应。

        shift->render( text=> Protocol::SocketIO::Handshake->new(
            session_id        => 1234567890,
            heartbeat_timeout => 10,
            close_timeout     => 15,
            transports        => [qw/websocket xhr-polling/]
        ) );
    };

普通的 web 方法在 Mojolicious 里大致就是这样。下面进入更高级的异步交互环节了!

我们这里示例的是一个每秒自动更新时间的页面。所以 Mojolicious 要定时执行。`Mojo::IOLoop->delay` 是个完全值得单独讲一次的好东西:

    my $clients = {};

    my $delay = Mojo::IOLoop->delay;
    Mojo::IOLoop->recurring( 1 => sub {
        for my $id( keys %$clients ) {
            # send name
            $clients->{$id}->send( Protocol::SocketIO::Message->new( type => 'event', data=>{ name=>'send:name', args=>[{ name=>'Jamie '.$id }] }) );
            # send time
            $clients->{$id}->send( Protocol::SocketIO::Message->new( type => 'event', data=>{ name=>'send:time', args=>[{ time=>''.Class::Date->now }] }) );
        }
    });

Mojolicious 本身直接指示 websocket 协议。所以用起来 DSL 跟做 GET/POST 是一样的。而控制器函数里用法也跟 socket.io 的写法有些类似。采用 `$self->on(message => sub {})`。

在控制器里,Mojolicious 允许直接操作整个请求响应的事务主体,也就是代码中的 `$self->tx` 。在异步处理的时候,肯定会需要直接操作 tx,这里作为一个纯粹的定时器,就只需要 send 了:

    websocket '/socket.io/:id/websocket/:oid' => sub {
        my $self = shift;

        app->log->debug(sprintf 'Client connected: %s', $self->tx);
        my $id = sprintf "%s", $self->tx;
        $clients->{$id} = $self->tx;

        $self->tx->send( Protocol::SocketIO::Message->new( type => 'connect') );
        $self->tx->send( Protocol::SocketIO::Message->new( type => 'event', data=>{ name=>'send:name', args=>[{ name=>'Jamie starting...' }] }) );
        $self->on(message => sub {
            my ($self, $msg) = @_;
            # no messages are being sent for now
        });
        $self->on(finish => sub {
            app->log->debug('Client disconnected');
            delete $clients->{$id};
        });
    };

最后,调用 start 方法开始运行服务器:

    app->start;

## AngularJS 示例

Mojolicious::Lite 支持在 Perl 的 `__DATA__` 里直接写页面内容,每个页面以 @@开头命名即可:

    __DATA__

    @@index.html.ep

### angular 的模板和变量绑定

angular 深度的改造了 HTML 的样式和书写方式,在前端的层面上提供 MVC 功能。在使用 `ng-app` 标记整个页面归属的具体 angular 应用后,可以利用 `ng-controller` 指令将一个 div 关联到一个 angular 的控制器上。在这个 div 内,可以通过 `{{ }}` 语法加载控制器函数里的变量,可以渲染带有 `ng-view` 指令的 div 作为 HTML 内容展示。

    <!DOCTYPE html>
    <html ng-app="myApp">
    <head>
    <meta charset="utf8">
    <base href="/">
    <title>Angular Socket.io Seed App</title>
    <link rel="stylesheet" href="app.css">
    </head>
    <body>
        <div ng-controller="AppCtrl">
            <h2>Helloo {{name}}</h2>
            <ul class="menu">
                <li><a href="view1">view1</a></li>
                <li><a href="view2">view2</a></li>
            </ul>
            <div ng-view></div>
        <div>
            Angular Socket.io seed app: v<span app-version></span></div>
        </div>

        <script src="socket.js"> </script>
        <script src="app.js"> </script>
    </body>
    </html>

    @@partial1.html.ep
    <p>This is the partial for view 1.</p><p>The current time is {{time}}</p>

    @@partial2.html.ep
    <p>This is the partial for view 2.</p><p>Showing of 'interpolate' filter:
    {{ 'Current version is v%VERSION%.' | interpolate }}</p>

### angular 的应用模块

上面页面部分就完成了。下面就开始在 js 中完成这个 angular 应用。我们前面已经看到,这个页面归属的应用名字叫 **myApp**。下面是应用的代码:

    @@app.js
    'use strict';

    // Declare app level module which depends on filters, and services
    var app = angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives']).
      config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
        $routeProvider.when('/view1', {templateUrl: 'partials/partial1', controller: MyCtrl1});
        $routeProvider.when('/view2', {templateUrl: 'partials/partial2', controller: MyCtrl2});
        $routeProvider.otherwise({redirectTo: '/view1'});
        $locationProvider.html5Mode(true);
      }]);

非常清晰,实现这个 myApp,加载了对应的 filters、services、directives 模块,然后定义了两个路由规则,分别指向两个不同的模板和控制器。

### angular 的控制器

angular 的控制器,最主要的作用就是处理应用作用域 `$scope` 与其他各种数据的关联。在这个示例里,就是把前面模板里的变量,跟 socket.io 从服务器拿到的数据关联在一起:

    function AppCtrl($scope, socket) {
      socket.on('send:name', function (data) {
        $scope.name = data.name;
      });
    }

    function MyCtrl1($scope, socket) {
      socket.on('send:time', function (data) {
        $scope.time = data.time;
      });
    }
    MyCtrl1.$inject = ['$scope', 'socket'];

    function MyCtrl2() {
    }
    MyCtrl2.$inject = [];

### angular 的指令

angular 的指令的作用,就是实际操作、修改变更应用中的数据,包括可能页面元素的变化等等。一般的 Web 开发中,这个事情是交给 jQuery 来操作 DOM 的。而在 angular 里。编写成指令,可以直接写成 HTML 元素的属性,看起来非常清爽。比如这个示例中就是生成了一个 `appVersion` 指令,在前面的 HTML 里,用在了一个 span 元素上。

    angular.module('myApp.directives', []).
      directive('appVersion', ['version', function(version) {
        return function(scope, elm, attrs) {
          elm.text(version);
        };
      }]);

### angular 的过滤器

angular 的过滤器,可以利用管道的方式帮助模板中的变量达到更好的渲染效果,默认提供有 date、json、limitTo、orderBy、number、lowercase、uppercase 等几个过滤器。也可以自己写新的过滤器。过滤器函数很简单,传一个参数返回一个结果即可:

    angular.module('myApp.filters', []).
      filter('interpolate', ['version', function(version) {
        return function(text) {
          return String(text).replace(/\%VERSION\%/mg, version);
        }
      }]);

### angular 的服务和工厂

augular 利用服务(service)或者工厂(factory)的方式来提供整个应用里,不同路由或者说控制器之间共用的单例变量。这二者的不同在于:service 其实就是一种不导入其他变量的简单 factory 的简写。比如下面这段代码是原程序中用 factory 写的,虽然名叫 myApp.services:

    // Demonstrate how to register services
    // In this case it is a simple value service.
    angular.module('myApp.services', []).
      value('version', '0.1').
      factory('socket', function ($rootScope) {
        var socket = io.connect();
        return {
          on: function (eventName, callback) {
            socket.on(eventName, function () {
              var args = arguments;
              $rootScope.$apply(function () {
                callback.apply(socket, args);
              });
            });
          },
          emit: function (eventName, data, callback) {
            socket.emit(eventName, data, function () {
              var args = arguments;
              $rootScope.$apply(function () {
                if (callback) {
                  callback.apply(socket, args);
                }
              });
            })
          }
        };
      });

可以看到就没有导入其他东西,所以可以写成服务,代码应该是下面这样:

    angular.module('myApp.services', []).
      value('version', '0.1').
      service('socket', function ($rootScope) {
        var socket = io.connect();
        this.on = function (eventName, callback) {
          ...
        };
        this.emit = function (eventName, data, callback) {
          ...
        };
      });

## socket.io 接口

上面 angular 服务里,就演示了 socket.io 的客户端用法。可以看到,其实客户端的接口跟服务器端是一一对应的。

示例程序再往下就是纯粹的js和css文件,这就不再继续了。完整代码源地址见:<https://gist.github.com/rodrigolive/5546320>

## 警告

socket.io 的客户端 js 在本例中还是用的去年的 0.90 的版本。最近半年 socket.io 项目发生重大改变,从 1.0 开始,有了自己专门的协议分析库 engine.io,整个 url 路径和 handshake 编解码方式都不太一样了。在 Perl 方面,PocketIO 作者没时间跟进这个变化,所以 Protocol::SocketIO 还是只能支持 0.90 的版本。

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

[PerlChina] PerlChina Advent 13: Tie::IxHash

# Tie::IxHash

众所周知,哈希 Hash 是无序的。但是有时候我们可能也需要一个有序的 Hash,这时候 [Tie::IxHash](https://metacpan.org/pod/Tie::IxHash) 就可以帮上忙了。

## 有序的 JSON 输出

当你使用 REST JSON 输出时,有时候你的老板可能要求你,比如把 id 放输出的 JSON 最前面。

比如你的原始代码如下:

    use JSON;
    my %r = (id => 1, name => 'Fayland', gender => 'male', bio => 'Just Another Perl Programmer');
    print encode_json(\%r);

然后输出可能不尽如人意:

    {"name":"Fayland","id":1,"bio":"Just Another Perl Programmer","gender":"male"}
    # {"bio":"Just Another Perl Programmer","gender":"male","id":1,"name":"Fayland"}
    # {"bio":"Just Another Perl Programmer","name":"Fayland","gender":"male","id":1}

任何一种可能都有。看起来不怎么好而且不容易 DEBUG (尤其是输出一个有很多 key 的 hash 时)

改动其实非常简单:

    use JSON;
    use Tie::IxHash;
    tie my %r, 'Tie::IxHash';
    %r = (id => 1, name => 'Fayland', gender => 'male', bio => 'Just Another Perl Programmer');
    print encode_json(\%r);

这样输出就永远都是

    {"id":1,"name":"Fayland","gender":"male","bio":"Just Another Perl Programmer"}

很简单,但是很管用。

## Tie::Hash::Indexed

[Tie::Hash::Indexed](https://metacpan.org/pod/Tie::Hash::Indexed) 是一个使用 XS 加速的 Tie::IxHash 替代品。但是目前测试不通过而且很久没有维护了。

## 作者
[Fayland Lam](http://fayland.me/)

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

2014年12月12日星期五

[PerlChina] PerlChina Advent 12: 交互式命令行reply

# Reply

交互式命令行是很多编程语言都提供的一个便捷方式。不过 Perl5 正巧就没提供==!前些年的 advent 中,曾经介绍过 [Devel::REPL](/2009/12/REPL/),今天这里介绍另一个模块,叫 [Reply](https://metacpan.org/pod/Reply)。

Reply 的依赖模块没有 Devel::REPL 那么多,所以安装起来更简单快速一些。此外,Devel::REPL 的 rcfile 是直接 Perl 语法,而 Reply 用的是 INI 格式的配置文件。

默认情况下,运行 reply 命令会自动生成 `~/.replyrc` 如下:

    script_line1 = use strict
    script_line2 = use warnings
    script_line3 = use 5.016003
    [Interrupt]
    [FancyPrompt]
    [DataDumper]
    [Colors]
    [ReadLine]
    [Hints]
    [Packages]
    [LexicalPersistence]
    [ResultCache]
    [Autocomplete::Packages]
    [Autocomplete::Lexicals]
    [Autocomplete::Functions]
    [Autocomplete::Globals]
    [Autocomplete::Methods]
    [Autocomplete::Commands]

这里面的 Colors 只是用来区分运行成功和失败时候的颜色,如果想要完整的高亮效果,可以安装 [Reply::Plugin::DataDumpColor](https://metacpan.org/pod/Reply::Plugin::DataDumpColor) 模块,然后把 `~/.replyrc` 里 `[DataDumper][Colors]` 两行替换成 `[DataDumpColor]` 即可。

而 Devel::REPL 自带的 `re.pl` 命令运行的时候并不会自动生成配置,也不会默认加载插件。要达到类似效果的话,需要自己创建 `~/.re.pl/repl.rc`,然后添加内容:

    use strict;
    use warnings;
    use 5.014;
    load_plugin 'Colors';
    load_plugin 'Packages';
    load_plugin 'MultiLine::PPI';
    load_plugin 'CompletionDriver::Globals';
    load_plugin 'CompletionDriver::Methods';
    load_plugin 'CompletionDriver::Keywords';

二者的 Globals 插件实现效果也有差别。比如说,在 re.pl 里,你写一个 "M",然后敲 tab 键,re.pl 是真的把你安装了的所有模块,以 M 开头的,都给你列出来,而且连模块的方法属性也单独列了;而在 reply 里,只会列出来被你显式 `use` 加载了的模块里匹配上的,就是说你 `use Mojo::UserAgent` 过,就只会列出 Mojolicious 的那些类,Moose 是不会列出的。

另一个区别,re.pl 有 multiline 插件,支持你逐行输入一个函数,他可以确定你括号结束了再执行。目前没有发现 reply 有这个功能,也算一个缺憾吧。

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

2014年12月11日星期四

[PerlChina] PerlChina Advent 11: autobox

# autobox

早在[几年前的 PerlChina Advent](/calendar/2010/06/) 上,fayland 曾经介绍过 [perl5i](https://metacpan.org/pod/perl5i) 项目。其中提到了 perl5i 里一个特性,就是 autobox。

    "12.34"->is_number;
    1->upto(5);
    "10, 20, 30, 40"->split(qr{, ?})->elements;
    (1,2,3,4,5,"a","b")->grep(sub{ $_->is_number })->sum->say;
    my %hash = (foo=>123, bar => 321);
    %hash->each(sub{
      my ($k, $v) = @_;
    });

喜欢链式调用风格的人可能会非常喜欢这种写法。那么问题来了。这是怎么做的,如果某个类型没现成的方法想自己实现要怎么办呢?
[autobox 模块](https://metacpan.org/pod/autobox) 是一个 XS 模块,利用 MAGIC 特性给 Perl 原生数据类型扩展出来添加方法的接口。其本身并没有实现具体的方法函数。任何人都是利用 autobox 模块生成自己的 autobox::\*。perl5i 里使用的,就是最常见的[autobox::Core](https://metacpan.org/pod/autobox::Core)。

autobox::Core 的实现很简单。比如其字符串相关部分就是这样:

    package autobox::Core;
    use base 'autobox';
    sub import {
        shift->SUPER::import(DEFAULT => 'autobox::Core::', @_);
    }
    package autobox::Core::SCALAR;
    sub chomp      { CORE::chomp($_[0]); }

完整情况下, autobox 一共可以扩展下面这些数据类型:

* UNDEF
* INTEGER
* FLOAT
* NUMBER
* STRING
* SCALAR
* ARRAY
* HASH
* CODE
* UNIVERSAL

而定义 DEFAULT 的作用就是表示各数据类型自动查找 DEFAULT 定义的这个名字空间下的同名类。相当于字符串类型就是:

    shift->SUPER::import(SCALAR => 'autobox::Core::SCALAR');

键值对还支持传递数组引用。这样就能对单个数据类型绑定多个类的函数。而这也是实现我们前面期望扩展个别方法的地方。比如实现一个类似 Perl6 中字符串的 `->words()` 方法:

    {
        package autobox::Core::SCALAR::Extends;
        sub words {
            CORE::split(/\s+/, $_[0]);
        }
    }
    use autobox::Core SCALAR => ['autobox::Core::SCALAR', 'autobox::Core::SCALAR::Extends'];
    my @array = "hello world"->words;
    @array->each(sub {
        $_[0]->say;
    });

传一个数组给 autobox::Core,这样既保持了 autobox::Core 原有的方法,又添加了自己想要的效果。脚本运行效果如下:

    hello
    world

btw: 代码中用了 `CORE::`,目前 Perl5 是尽量把内置函数都转移到这个 CORE 名下了。


--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

Re: Re: [PerlChina] PerlChina Advent 09: Webqq::Client

 
搞个微信群还好啊,perl,
2014-12-11
galaxy2004

发件人:"arrow.wang@gmail.com" <arrow.wang@gmail.com>
发送时间:2014-12-11 19:16
主题:Re: [PerlChina] PerlChina Advent 09: Webqq::Client
收件人:"perlchina"<perlchina@googlegroups.com>
抄送:
 
問下,Webqq::Client能不能接收圖片或文件?


On 2014年12月09日 23:39, chenlin rao wrote:
鹅厂应该是关闭webqq保留smartqq吧?完全放弃网页版不现实啊,连微信都有网页版。

在 2014年12月9日 下午10:31,arrow.wang@gmail.com <arrow.wang@gmail.com>写 道:
讚!但是聽說 webqq近期要關閉,不知道還能用多長時間。

==============================================
9月30日消息,近日登录腾讯WebQQ的用户会看到"WebQQ告别会 相聚有时 后会无期"的公告页面。预示着 WebQQ即将停止服务,但公 告页面并没有给出停止服务的具体时间,目前WebQQ及SmartQQ也仍然能正常使用。

据了解,WebQQ于2009年9月15日正式上线,是腾讯公司推出的使用网页方式上QQ的服务,特点是无需下载和安 装QQ软件,只要能打 开 WebQQ的网站就可以登录QQ与好友保持联系。具有Web产品固有的便利性,同时在Web上最大限度的保持了客户端软件的操作习惯。去年9 月,WebQQ更名为SmartQQ全新发布,但仍保留了原WebQQ版本。

此外,腾讯"我的QQ中心"的"好友"功能已于2014年7月15日停止服务。据悉,此次WebQQ是继此之后的又一 业务调整。



On 2014年12月09日 21:02, chenlin rao wrote:
=encoding utf8

=for advent_year 2014

=for advent_title Webqq::Client模块介绍

=for advent_author Perfi Wang


=head2 Webqq::Client 背景来源

最早的时候,加入了一个Python的群,看到群里有个QQ机器人可以自动帮大家查询天气

群里贴出来的url会自动去获取下html的 <title> 内容,感觉蛮不错的,就想着也搞一个玩一下

这个Python群的群主也很热心,搞了个开源的项目放到了github上:L<https://github.com/coldnight/twqq>

源码下载下来安装的过程很艰辛,因为一些语法特性要求必须python2.7以上版本并且依赖的 pycurl包也非常难编译安 装

折腾了很久总算也跑起来了,但毕竟不是Perl写的,不是很熟悉,用起来也觉得不爽

在github上搜索了下 "webqq",得到很多搜索结果,有各种语言的实现,但活跃的项目不多

尤其是基于Perl开发的项目少之又少,反而Python的倒一大堆,看不惯这种情况,决定自己 亲自用Perl写一个

在这个过程中也了解到了,目前存在的各自版本的qq机器人都基本上是基于腾讯的webqq协议来 实现的

因此要自己从头开发一个难度并不大,另外,自己有管理几个Perl的QQ群

群里经常会有一些新手不知道该去哪里找模块,去哪里查文档,讨论一些代码问题的时候

也总是不方便,因此也萌生了用Perl写一个智能化的QQ机器人,能够在群里协助大家学习 Perl语言

例如在QQ群里发一个perldoc -f open,机器人就会自动把相关的文档贴出来,聊天过程中提到了某个模块的名字

机器人也会自动的把模块相关的介绍、作者、用法自动贴出来,很贴心有木有?

当然一个机器人能做的事情远远不止这些,Webqq::Client提供的只是一个客户端的框 架,剩下的任凭你的想象力

=head2 原理说明

模块采用AnyEvent的异步框架,尽可能的减少依赖模块,以便于安装和使用,提供面向对象的 使用方式

如果你对AnyEvent有一定了解,那么相信不需要花费太多时间就很容易掌握模块的使用方法, 如果你并不了解 AnyEvent

也没有关系,你要做的仅仅是三件事:

1、登录

2、设置感兴趣的回调函数,在回调函数中对消息进行处理

3、运行

=head2 模块用法简介

    use Webqq::Client;
    use Digest::MD5 qw(md5_hex);
    my $qq = 12345678;

    #你的qq密码请使用md5加密后再传递给Webqq::Client
    #我可不想被怀疑有盗号行为
    my $pwd = md5_hex('your password');

    #通过new来初始化一个客户端对象
    #debug=>1来打印debug信息方便调试
    my $client = Webqq::Client->new(debug=>0);

    #通过login进行登录
    $client->login( qq=> $qq, pwd => $pwd);

    #客户端加载ShowMsg插件,用于打印消息内容
    $client->load("ShowMsg");

    #登录成功后设置客户端的发送消息回调函数
    $client->on_send_message=sub{
        #当发送完消息后,传递给回调函数的是三个参数
        my $msg = shift;   #发送的原始消息
        my $is_success = shift; #发送消息状态,True为成功,False为失败
        my $status = shift; #发送消息状态,UTF8编码的中文,"发送成功" 或者 "发送失败"

        #客户端执行插件,这个插件的作用是直接打印消息到屏幕
        $client->call("ShowMsg",$msg);
    };

    #登录成功后设置客户端的接收消息回调函数
    $client->on_receive_message = sub{
        #当收到消息后,传递给回调函数的唯一参数是原始消息的一个hash引用
        my $msg = shift;

        #客户端执行插件,这个插件的作用是直接打印消息到屏幕
        $client->call("ShowMsg",$msg);

        #你可以对收到的消息进行任意其他的处理
        #你也可以使用Data::Dumper这样的模块来查看消息的结构,比如
        #use Data::Dumper;
        #print Dumper $msg;

        ...;
    };
    #客户端进入事件循环,正式开始运行
    $client->run();

更多使用方法和介绍请参见文档

=head2 关于回调函数

目前提供了几种回调函数,可以满足大多是使用场景,请参见模块的文档介绍


但有一点你需要注意,客户端是单线程的,你不应该在回调函数中长时间阻塞,这样会导致整个客户端 阻塞,什么事都不做

=head2 关于插件

客户端提供了一个基本的插件管理的机制,来方便你编写和运行插件,我们举个具体的例子说明下什么 是插件,怎么写一个插件

比如我想实现一个插件,作用就是当收到群消息的时候就回复一个hello world到这个群上

第一步:写一个插件模块

    package Webqq::Client::Plugin::HelloWorld;
    #模块中定义一个call函数
    sub call{
        #记住,call函数第一个参数永远是客户端对象
        my $client = shift;

        #这个插件还需要额外传入一个客户端收到的群消息才能进行后续的处理
        my $msg = shift;

        #我们只对群消息感兴趣
        return if $msg->{type} ne 'group_message';

        #使用客户端的reply_message()方法进行消息回复
        $client->reply_message($msg,"hello world");
    }
    1;

第二步:加载插件

    #使用load方法进行加载,会自动查找Webqq::Client::Plugin::HelloWorld模块
    $client->load("HelloWorld");

第三部:使用插件

    #使用call()方法调用插件
    $client->on_receieve_message = sub{
        my $msg = shift;
        $client->call("HelloWorld",$msg);
    };
    $client->run();


=head2 模块从哪里获取

目前模块已经发布到了github和cpan上



github更新会比较频繁,建议使用github随时保持和最新版本同步

=head2 补充

腾讯的新版本是smartqq,老版本是webqq,这里统一都称之为webqq,webqq功 能本身受限并不能向PC端那样 做很多事情

另外,模块还处于不断开发完善中,存在bug和很多不完善之处是必然的,有什么问题或想法欢迎跟 作者反馈

如果你是新手,希望有一个一起学习交流Perl的地方,欢迎加入我所在的QQ群: PERL学习交流 群号:144539789

如果你是高手,又乐于助人,对新手有足够的耐心,也非常欢迎加入

=head2 作者

Perfi, E<lt>sjdy521@163.comE<gt>

=cut


感谢灰灰的投稿,欢迎更多 Perler 参与活动。本文网页地址见: http://advent.perl-china.com/calendar/2014/09/
--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

Re: [PerlChina] PerlChina Advent 09: Webqq::Client

問下,Webqq::Client能不能接收圖片或文件?


On 2014年12月09日 23:39, chenlin rao wrote:
鹅厂应该是关闭webqq保留smartqq吧?完全放弃网页版不现实啊,连微信都有网页版。

在 2014年12月9日 下午10:31,arrow.wang@gmail.com <arrow.wang@gmail.com>写 道:
讚!但是聽說 webqq近期要關閉,不知道還能用多長時間。

==============================================
9月30日消息,近日登录腾讯WebQQ的用户会看到"WebQQ告别会 相聚有时 后会无期"的公告页面。预示着 WebQQ即将停止服务,但公 告页面并没有给出停止服务的具体时间,目前WebQQ及SmartQQ也仍然能正常使用。

据了解,WebQQ于2009年9月15日正式上线,是腾讯公司推出的使用网页方式上QQ的服务,特点是无需下载和安 装QQ软件,只要能打 开 WebQQ的网站就可以登录QQ与好友保持联系。具有Web产品固有的便利性,同时在Web上最大限度的保持了客户端软件的操作习惯。去年9 月,WebQQ更名为SmartQQ全新发布,但仍保留了原WebQQ版本。

此外,腾讯"我的QQ中心"的"好友"功能已于2014年7月15日停止服务。据悉,此次WebQQ是继此之后的又一 业务调整。



On 2014年12月09日 21:02, chenlin rao wrote:
=encoding utf8

=for advent_year 2014

=for advent_title Webqq::Client模块介绍

=for advent_author Perfi Wang


=head2 Webqq::Client 背景来源

最早的时候,加入了一个Python的群,看到群里有个QQ机器人可以自动帮大家查询天气

群里贴出来的url会自动去获取下html的 <title> 内容,感觉蛮不错的,就想着也搞一个玩一下

这个Python群的群主也很热心,搞了个开源的项目放到了github上:L<https://github.com/coldnight/twqq>

源码下载下来安装的过程很艰辛,因为一些语法特性要求必须python2.7以上版本并且依赖的 pycurl包也非常难编译安 装

折腾了很久总算也跑起来了,但毕竟不是Perl写的,不是很熟悉,用起来也觉得不爽

在github上搜索了下 "webqq",得到很多搜索结果,有各种语言的实现,但活跃的项目不多

尤其是基于Perl开发的项目少之又少,反而Python的倒一大堆,看不惯这种情况,决定自己 亲自用Perl写一个

在这个过程中也了解到了,目前存在的各自版本的qq机器人都基本上是基于腾讯的webqq协议来 实现的

因此要自己从头开发一个难度并不大,另外,自己有管理几个Perl的QQ群

群里经常会有一些新手不知道该去哪里找模块,去哪里查文档,讨论一些代码问题的时候

也总是不方便,因此也萌生了用Perl写一个智能化的QQ机器人,能够在群里协助大家学习 Perl语言

例如在QQ群里发一个perldoc -f open,机器人就会自动把相关的文档贴出来,聊天过程中提到了某个模块的名字

机器人也会自动的把模块相关的介绍、作者、用法自动贴出来,很贴心有木有?

当然一个机器人能做的事情远远不止这些,Webqq::Client提供的只是一个客户端的框 架,剩下的任凭你的想象力

=head2 原理说明

模块采用AnyEvent的异步框架,尽可能的减少依赖模块,以便于安装和使用,提供面向对象的 使用方式

如果你对AnyEvent有一定了解,那么相信不需要花费太多时间就很容易掌握模块的使用方法, 如果你并不了解 AnyEvent

也没有关系,你要做的仅仅是三件事:

1、登录

2、设置感兴趣的回调函数,在回调函数中对消息进行处理

3、运行

=head2 模块用法简介

    use Webqq::Client;
    use Digest::MD5 qw(md5_hex);
    my $qq = 12345678;

    #你的qq密码请使用md5加密后再传递给Webqq::Client
    #我可不想被怀疑有盗号行为
    my $pwd = md5_hex('your password');

    #通过new来初始化一个客户端对象
    #debug=>1来打印debug信息方便调试
    my $client = Webqq::Client->new(debug=>0);

    #通过login进行登录
    $client->login( qq=> $qq, pwd => $pwd);

    #客户端加载ShowMsg插件,用于打印消息内容
    $client->load("ShowMsg");

    #登录成功后设置客户端的发送消息回调函数
    $client->on_send_message=sub{
        #当发送完消息后,传递给回调函数的是三个参数
        my $msg = shift;   #发送的原始消息
        my $is_success = shift; #发送消息状态,True为成功,False为失败
        my $status = shift; #发送消息状态,UTF8编码的中文,"发送成功" 或者 "发送失败"

        #客户端执行插件,这个插件的作用是直接打印消息到屏幕
        $client->call("ShowMsg",$msg);
    };

    #登录成功后设置客户端的接收消息回调函数
    $client->on_receive_message = sub{
        #当收到消息后,传递给回调函数的唯一参数是原始消息的一个hash引用
        my $msg = shift;

        #客户端执行插件,这个插件的作用是直接打印消息到屏幕
        $client->call("ShowMsg",$msg);

        #你可以对收到的消息进行任意其他的处理
        #你也可以使用Data::Dumper这样的模块来查看消息的结构,比如
        #use Data::Dumper;
        #print Dumper $msg;

        ...;
    };
    #客户端进入事件循环,正式开始运行
    $client->run();

更多使用方法和介绍请参见文档

=head2 关于回调函数

目前提供了几种回调函数,可以满足大多是使用场景,请参见模块的文档介绍


但有一点你需要注意,客户端是单线程的,你不应该在回调函数中长时间阻塞,这样会导致整个客户端 阻塞,什么事都不做

=head2 关于插件

客户端提供了一个基本的插件管理的机制,来方便你编写和运行插件,我们举个具体的例子说明下什么 是插件,怎么写一个插件

比如我想实现一个插件,作用就是当收到群消息的时候就回复一个hello world到这个群上

第一步:写一个插件模块

    package Webqq::Client::Plugin::HelloWorld;
    #模块中定义一个call函数
    sub call{
        #记住,call函数第一个参数永远是客户端对象
        my $client = shift;

        #这个插件还需要额外传入一个客户端收到的群消息才能进行后续的处理
        my $msg = shift;

        #我们只对群消息感兴趣
        return if $msg->{type} ne 'group_message';

        #使用客户端的reply_message()方法进行消息回复
        $client->reply_message($msg,"hello world");
    }
    1;

第二步:加载插件

    #使用load方法进行加载,会自动查找Webqq::Client::Plugin::HelloWorld模块
    $client->load("HelloWorld");

第三部:使用插件

    #使用call()方法调用插件
    $client->on_receieve_message = sub{
        my $msg = shift;
        $client->call("HelloWorld",$msg);
    };
    $client->run();


=head2 模块从哪里获取

目前模块已经发布到了github和cpan上



github更新会比较频繁,建议使用github随时保持和最新版本同步

=head2 补充

腾讯的新版本是smartqq,老版本是webqq,这里统一都称之为webqq,webqq功 能本身受限并不能向PC端那样 做很多事情

另外,模块还处于不断开发完善中,存在bug和很多不完善之处是必然的,有什么问题或想法欢迎跟 作者反馈

如果你是新手,希望有一个一起学习交流Perl的地方,欢迎加入我所在的QQ群: PERL学习交流 群号:144539789

如果你是高手,又乐于助人,对新手有足够的耐心,也非常欢迎加入

=head2 作者

Perfi, E<lt>sjdy521@163.comE<gt>

=cut


感谢灰灰的投稿,欢迎更多 Perler 参与活动。本文网页地址见: http://advent.perl-china.com/calendar/2014/09/
--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

Re: [PerlChina] PerlChina Advent 09: Webqq::Client

微信現在用的人很多啊,周圍好多人都用微信,感覺微信、QQ用的人一半一半吧。


On 2014年12月09日 23:39, chenlin rao wrote:
鹅厂应该是关闭webqq保留smartqq吧?完全放弃网页版不现实啊,连微信都有网页版。

在 2014年12月9日 下午10:31,arrow.wang@gmail.com <arrow.wang@gmail.com>写 道:
讚!但是聽說 webqq近期要關閉,不知道還能用多長時間。

==============================================
9月30日消息,近日登录腾讯WebQQ的用户会看到"WebQQ告别会 相聚有时 后会无期"的公告页面。预示着 WebQQ即将停止服务,但公 告页面并没有给出停止服务的具体时间,目前WebQQ及SmartQQ也仍然能正常使用。

据了解,WebQQ于2009年9月15日正式上线,是腾讯公司推出的使用网页方式上QQ的服务,特点是无需下载和安 装QQ软件,只要能打 开 WebQQ的网站就可以登录QQ与好友保持联系。具有Web产品固有的便利性,同时在Web上最大限度的保持了客户端软件的操作习惯。去年9 月,WebQQ更名为SmartQQ全新发布,但仍保留了原WebQQ版本。

此外,腾讯"我的QQ中心"的"好友"功能已于2014年7月15日停止服务。据悉,此次WebQQ是继此之后的又一 业务调整。



On 2014年12月09日 21:02, chenlin rao wrote:
=encoding utf8

=for advent_year 2014

=for advent_title Webqq::Client模块介绍

=for advent_author Perfi Wang


=head2 Webqq::Client 背景来源

最早的时候,加入了一个Python的群,看到群里有个QQ机器人可以自动帮大家查询天气

群里贴出来的url会自动去获取下html的 <title> 内容,感觉蛮不错的,就想着也搞一个玩一下

这个Python群的群主也很热心,搞了个开源的项目放到了github上:L<https://github.com/coldnight/twqq>

源码下载下来安装的过程很艰辛,因为一些语法特性要求必须python2.7以上版本并且依赖的 pycurl包也非常难编译安 装

折腾了很久总算也跑起来了,但毕竟不是Perl写的,不是很熟悉,用起来也觉得不爽

在github上搜索了下 "webqq",得到很多搜索结果,有各种语言的实现,但活跃的项目不多

尤其是基于Perl开发的项目少之又少,反而Python的倒一大堆,看不惯这种情况,决定自己 亲自用Perl写一个

在这个过程中也了解到了,目前存在的各自版本的qq机器人都基本上是基于腾讯的webqq协议来 实现的

因此要自己从头开发一个难度并不大,另外,自己有管理几个Perl的QQ群

群里经常会有一些新手不知道该去哪里找模块,去哪里查文档,讨论一些代码问题的时候

也总是不方便,因此也萌生了用Perl写一个智能化的QQ机器人,能够在群里协助大家学习 Perl语言

例如在QQ群里发一个perldoc -f open,机器人就会自动把相关的文档贴出来,聊天过程中提到了某个模块的名字

机器人也会自动的把模块相关的介绍、作者、用法自动贴出来,很贴心有木有?

当然一个机器人能做的事情远远不止这些,Webqq::Client提供的只是一个客户端的框 架,剩下的任凭你的想象力

=head2 原理说明

模块采用AnyEvent的异步框架,尽可能的减少依赖模块,以便于安装和使用,提供面向对象的 使用方式

如果你对AnyEvent有一定了解,那么相信不需要花费太多时间就很容易掌握模块的使用方法, 如果你并不了解 AnyEvent

也没有关系,你要做的仅仅是三件事:

1、登录

2、设置感兴趣的回调函数,在回调函数中对消息进行处理

3、运行

=head2 模块用法简介

    use Webqq::Client;
    use Digest::MD5 qw(md5_hex);
    my $qq = 12345678;

    #你的qq密码请使用md5加密后再传递给Webqq::Client
    #我可不想被怀疑有盗号行为
    my $pwd = md5_hex('your password');

    #通过new来初始化一个客户端对象
    #debug=>1来打印debug信息方便调试
    my $client = Webqq::Client->new(debug=>0);

    #通过login进行登录
    $client->login( qq=> $qq, pwd => $pwd);

    #客户端加载ShowMsg插件,用于打印消息内容
    $client->load("ShowMsg");

    #登录成功后设置客户端的发送消息回调函数
    $client->on_send_message=sub{
        #当发送完消息后,传递给回调函数的是三个参数
        my $msg = shift;   #发送的原始消息
        my $is_success = shift; #发送消息状态,True为成功,False为失败
        my $status = shift; #发送消息状态,UTF8编码的中文,"发送成功" 或者 "发送失败"

        #客户端执行插件,这个插件的作用是直接打印消息到屏幕
        $client->call("ShowMsg",$msg);
    };

    #登录成功后设置客户端的接收消息回调函数
    $client->on_receive_message = sub{
        #当收到消息后,传递给回调函数的唯一参数是原始消息的一个hash引用
        my $msg = shift;

        #客户端执行插件,这个插件的作用是直接打印消息到屏幕
        $client->call("ShowMsg",$msg);

        #你可以对收到的消息进行任意其他的处理
        #你也可以使用Data::Dumper这样的模块来查看消息的结构,比如
        #use Data::Dumper;
        #print Dumper $msg;

        ...;
    };
    #客户端进入事件循环,正式开始运行
    $client->run();

更多使用方法和介绍请参见文档

=head2 关于回调函数

目前提供了几种回调函数,可以满足大多是使用场景,请参见模块的文档介绍


但有一点你需要注意,客户端是单线程的,你不应该在回调函数中长时间阻塞,这样会导致整个客户端 阻塞,什么事都不做

=head2 关于插件

客户端提供了一个基本的插件管理的机制,来方便你编写和运行插件,我们举个具体的例子说明下什么 是插件,怎么写一个插件

比如我想实现一个插件,作用就是当收到群消息的时候就回复一个hello world到这个群上

第一步:写一个插件模块

    package Webqq::Client::Plugin::HelloWorld;
    #模块中定义一个call函数
    sub call{
        #记住,call函数第一个参数永远是客户端对象
        my $client = shift;

        #这个插件还需要额外传入一个客户端收到的群消息才能进行后续的处理
        my $msg = shift;

        #我们只对群消息感兴趣
        return if $msg->{type} ne 'group_message';

        #使用客户端的reply_message()方法进行消息回复
        $client->reply_message($msg,"hello world");
    }
    1;

第二步:加载插件

    #使用load方法进行加载,会自动查找Webqq::Client::Plugin::HelloWorld模块
    $client->load("HelloWorld");

第三部:使用插件

    #使用call()方法调用插件
    $client->on_receieve_message = sub{
        my $msg = shift;
        $client->call("HelloWorld",$msg);
    };
    $client->run();


=head2 模块从哪里获取

目前模块已经发布到了github和cpan上



github更新会比较频繁,建议使用github随时保持和最新版本同步

=head2 补充

腾讯的新版本是smartqq,老版本是webqq,这里统一都称之为webqq,webqq功 能本身受限并不能向PC端那样 做很多事情

另外,模块还处于不断开发完善中,存在bug和很多不完善之处是必然的,有什么问题或想法欢迎跟 作者反馈

如果你是新手,希望有一个一起学习交流Perl的地方,欢迎加入我所在的QQ群: PERL学习交流 群号:144539789

如果你是高手,又乐于助人,对新手有足够的耐心,也非常欢迎加入

=head2 作者

Perfi, E<lt>sjdy521@163.comE<gt>

=cut


感谢灰灰的投稿,欢迎更多 Perler 参与活动。本文网页地址见: http://advent.perl-china.com/calendar/2014/09/
--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

2014年12月10日星期三

[PerlChina] PerlChina Advent 10: Keyczar

# Keyczar

在不同语言之间做加密解密,有时候不得不说是个噩梦。CBC/Blowfish 或者 RSA 不同的参数,有时候哪怕把头发都抓没了,也找不到解决方案。

幸运的是 [keyczar](http://www.keyczar.org/) 是一个很不错的选择。

## 应用场景

一个普通的应用场景是,如果您做一个手机项目,里面有个登陆窗口。你希望在发送验证请求的时候给密码加密,这样整个 app 会显得更加安全。这里我们将尝试用 keyczar 通过 RSA 来做这个应用。

### 安装

首先您需要安装 cpanm [Crypt::Keyczar](https://metacpan.org/pod/Crypt::Keyczar)

其次您需要安装 java,然后从 [https://code.google.com/p/keyczar/downloads/list](https://code.google.com/p/keyczar/downloads/list) 下载所需的 keyczar jar

### key 的建立

    $ java -jar KeyczarTool-0.71g-090613.jar create --location=crypt-rsa --purpose=crypt --asymmetric=rsa
    $ java -jar KeyczarTool-0.71g-090613.jar addkey --location=crypt-rsa --status=primary
    $ java -jar KeyczarTool-0.71g-090613.jar pubkey --location=crypt-rsa --destination=crypt-rsa-pub

_KeyczarTool-0.71g-090613.jar 文件可能会改变名字_

### java 加密

简单的代码如下:

    import org.keyczar.*;
    public class demo {
        public static void main(String[] args) throws Exception {
            KeyczarFileReader reader = new KeyczarFileReader("./crypt-rsa-pub");
            try {
                Encrypter crypter = new Encrypter(reader);
                System.out.print(crypter.encrypt("hello") + "\n");

            } catch (org.keyczar.exceptions.KeyczarException e) {
                System.out.print("ng\n");
            }
        }
    }

测试如下:

    $ javac demo.java
    $ java demo
    AGi_FoJaaJNH52pvBgkMP94uyh7nTYXA5_OQARB5X900PLgHKPrnlDHG65OVPeYPHMLHaosqUFFTdAq_ECKrLG1qtPmp8ai7xpZycqWYfKaGezIe-ANjo1_nutwhbWEK5ixV2CRX7tsEZQ_zilkXH9KUpxZKB4j_xfL5n5q4Op6CA7FmsS--OLtHiWpvCGiw0JfCSJMjnefUVVM8apTU6vR-T-Sb--jAj4UlT_Tn7NlsVwgEAtLJZ9Qhw-4SqLhQwY-9SvzENSZ9gFWpogfzS622820dcbBTRJ-Pu37mIrBen2CuESQI2tpm08Xa45nnA2zhZZoy4xrWKwkkAQOI31Tg05cV2I3mpAEPbLpy0CcppHvyPOyxVsPw7-slgtASDYqUf_S3UNmO8yi9EOvgjmdi0WUEm41aSlr2UizMnGYZONE2RSK1PHAQlxm0-03-X-quBiE7MT5C75FlWz6iYa2LOmKwVPaydjpHur2bMXfn_pVdgYnoPmjHIfKPLuBq4lH_9qqbK9hk83GxJLQeTQ92cdOcgir9-dd6v3OE15Xf8viLCOcgao5iot7B3y76KTY2I42mcrzP8rKWokvoE3xOelkyeaZSgFKQq4wLxf7L7pbQl5s4rl8pdkXyysvWM5lUmLxduc2VRiKVXEDC55Y81CnkJmiULw9XLAUyz1chuLTKog

### perl 解密

简单代码如下:

    use v5.10;
    use FindBin qw/$Bin/;
    use Crypt::Keyczar::Crypter;

    my $msg = shift @ARGV or die "message is required.\n";

    my $c = Crypt::Keyczar::Crypter->new("$Bin/crypt-rsa");
    say $c->decrypt(Crypt::Keyczar::Util::decode($msg));

运行:

    $ perl decrypt.pl AGi_FoJaaJNH52pvBgkMP94uyh7nTYXA5_OQARB5X900PLgHKPrnlDHG65OVPeYPHMLHaosqUFFTdAq_ECKrLG1qtPmp8ai7xpZycqWYfKaGezIe-ANjo1_nutwhbWEK5ixV2CRX7tsEZQ_zilkXH9KUpxZKB4j_xfL5n5q4Op6CA7FmsS--OLtHiWpvCGiw0JfCSJMjnefUVVM8apTU6vR-T-Sb--jAj4UlT_Tn7NlsVwgEAtLJZ9Qhw-4SqLhQwY-9SvzENSZ9gFWpogfzS622820dcbBTRJ-Pu37mIrBen2CuESQI2tpm08Xa45nnA2zhZZoy4xrWKwkkAQOI31Tg05cV2I3mpAEPbLpy0CcppHvyPOyxVsPw7-slgtASDYqUf_S3UNmO8yi9EOvgjmdi0WUEm41aSlr2UizMnGYZONE2RSK1PHAQlxm0-03-X-quBiE7MT5C75FlWz6iYa2LOmKwVPaydjpHur2bMXfn_pVdgYnoPmjHIfKPLuBq4lH_9qqbK9hk83GxJLQeTQ92cdOcgir9-dd6v3OE15Xf8viLCOcgao5iot7B3y76KTY2I42mcrzP8rKWokvoE3xOelkyeaZSgFKQq4wLxf7L7pbQl5s4rl8pdkXyysvWM5lUmLxduc2VRiKVXEDC55Y81CnkJmiULw9XLAUyz1chuLTKog
    hello

## 总结


## 作者

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

2014年12月9日星期二

[PerlChina] 关于OTRS开发

大家好!
我们这里有专业做OTRS开发或服务的吗?

--
您收到此邮件是因为您订阅了 Google 网上论坛的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要向此群组发帖,请发送电子邮件至 perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问 https://groups.google.com/d/optout

Re: [PerlChina] PerlChina Advent 09: Webqq::Client

鹅厂应该是关闭webqq保留smartqq吧?完全放弃网页版不现实啊,连微信都有网页版。

在 2014年12月9日 下午10:31,arrow.wang@gmail.com <arrow.wang@gmail.com>写道:
讚!但是聽說 webqq近期要關閉,不知道還能用多長時間。

==============================================
9月30日消息,近日登录腾讯WebQQ的用户会看到"WebQQ告别会 相聚有时 后会无期"的公告页面。预示着WebQQ即将停止服务,但公 告页面并没有给出停止服务的具体时间,目前WebQQ及SmartQQ也仍然能正常使用。

据了解,WebQQ于2009年9月15日正式上线,是腾讯公司推出的使用网页方式上QQ的服务,特点是无需下载和安装QQ软件,只要能打 开 WebQQ的网站就可以登录QQ与好友保持联系。具有Web产品固有的便利性,同时在Web上最大限度的保持了客户端软件的操作习惯。去年9 月,WebQQ更名为SmartQQ全新发布,但仍保留了原WebQQ版本。

此外,腾讯"我的QQ中心"的"好友"功能已于2014年7月15日停止服务。据悉,此次WebQQ是继此之后的又一业务调整。



On 2014年12月09日 21:02, chenlin rao wrote:
=encoding utf8

=for advent_year 2014

=for advent_title Webqq::Client模块介绍

=for advent_author Perfi Wang


=head2 Webqq::Client 背景来源

最早的时候,加入了一个Python的群,看到群里有个QQ机器人可以自动帮大家查询天气

群里贴出来的url会自动去获取下html的 <title> 内容,感觉蛮不错的,就想着也搞一个玩一下

这个Python群的群主也很热心,搞了个开源的项目放到了github上:L<https://github.com/coldnight/twqq>

源码下载下来安装的过程很艰辛,因为一些语法特性要求必须python2.7以上版本并且依赖的pycurl包也非常难编译安 装

折腾了很久总算也跑起来了,但毕竟不是Perl写的,不是很熟悉,用起来也觉得不爽

在github上搜索了下 "webqq",得到很多搜索结果,有各种语言的实现,但活跃的项目不多

尤其是基于Perl开发的项目少之又少,反而Python的倒一大堆,看不惯这种情况,决定自己亲自用Perl写一个

在这个过程中也了解到了,目前存在的各自版本的qq机器人都基本上是基于腾讯的webqq协议来实现的

因此要自己从头开发一个难度并不大,另外,自己有管理几个Perl的QQ群

群里经常会有一些新手不知道该去哪里找模块,去哪里查文档,讨论一些代码问题的时候

也总是不方便,因此也萌生了用Perl写一个智能化的QQ机器人,能够在群里协助大家学习Perl语言

例如在QQ群里发一个perldoc -f open,机器人就会自动把相关的文档贴出来,聊天过程中提到了某个模块的名字

机器人也会自动的把模块相关的介绍、作者、用法自动贴出来,很贴心有木有?

当然一个机器人能做的事情远远不止这些,Webqq::Client提供的只是一个客户端的框架,剩下的任凭你的想象力

=head2 原理说明

模块采用AnyEvent的异步框架,尽可能的减少依赖模块,以便于安装和使用,提供面向对象的使用方式

如果你对AnyEvent有一定了解,那么相信不需要花费太多时间就很容易掌握模块的使用方法,如果你并不了解 AnyEvent

也没有关系,你要做的仅仅是三件事:

1、登录

2、设置感兴趣的回调函数,在回调函数中对消息进行处理

3、运行

=head2 模块用法简介

    use Webqq::Client;
    use Digest::MD5 qw(md5_hex);
    my $qq = 12345678;

    #你的qq密码请使用md5加密后再传递给Webqq::Client
    #我可不想被怀疑有盗号行为
    my $pwd = md5_hex('your password');

    #通过new来初始化一个客户端对象
    #debug=>1来打印debug信息方便调试
    my $client = Webqq::Client->new(debug=>0);

    #通过login进行登录
    $client->login( qq=> $qq, pwd => $pwd);

    #客户端加载ShowMsg插件,用于打印消息内容
    $client->load("ShowMsg");

    #登录成功后设置客户端的发送消息回调函数
    $client->on_send_message=sub{
        #当发送完消息后,传递给回调函数的是三个参数
        my $msg = shift;   #发送的原始消息
        my $is_success = shift; #发送消息状态,True为成功,False为失败
        my $status = shift; #发送消息状态,UTF8编码的中文,"发送成功" 或者 "发送失败"

        #客户端执行插件,这个插件的作用是直接打印消息到屏幕
        $client->call("ShowMsg",$msg);
    };

    #登录成功后设置客户端的接收消息回调函数
    $client->on_receive_message = sub{
        #当收到消息后,传递给回调函数的唯一参数是原始消息的一个hash引用
        my $msg = shift;

        #客户端执行插件,这个插件的作用是直接打印消息到屏幕
        $client->call("ShowMsg",$msg);

        #你可以对收到的消息进行任意其他的处理
        #你也可以使用Data::Dumper这样的模块来查看消息的结构,比如
        #use Data::Dumper;
        #print Dumper $msg;

        ...;
    };
    #客户端进入事件循环,正式开始运行
    $client->run();

更多使用方法和介绍请参见文档

=head2 关于回调函数

目前提供了几种回调函数,可以满足大多是使用场景,请参见模块的文档介绍


但有一点你需要注意,客户端是单线程的,你不应该在回调函数中长时间阻塞,这样会导致整个客户端阻塞,什么事都不做

=head2 关于插件

客户端提供了一个基本的插件管理的机制,来方便你编写和运行插件,我们举个具体的例子说明下什么是插件,怎么写一个插件

比如我想实现一个插件,作用就是当收到群消息的时候就回复一个hello world到这个群上

第一步:写一个插件模块

    package Webqq::Client::Plugin::HelloWorld;
    #模块中定义一个call函数
    sub call{
        #记住,call函数第一个参数永远是客户端对象
        my $client = shift;

        #这个插件还需要额外传入一个客户端收到的群消息才能进行后续的处理
        my $msg = shift;

        #我们只对群消息感兴趣
        return if $msg->{type} ne 'group_message';

        #使用客户端的reply_message()方法进行消息回复
        $client->reply_message($msg,"hello world");
    }
    1;

第二步:加载插件

    #使用load方法进行加载,会自动查找Webqq::Client::Plugin::HelloWorld模块
    $client->load("HelloWorld");

第三部:使用插件

    #使用call()方法调用插件
    $client->on_receieve_message = sub{
        my $msg = shift;
        $client->call("HelloWorld",$msg);
    };
    $client->run();


=head2 模块从哪里获取

目前模块已经发布到了github和cpan上



github更新会比较频繁,建议使用github随时保持和最新版本同步

=head2 补充

腾讯的新版本是smartqq,老版本是webqq,这里统一都称之为webqq,webqq功能本身受限并不能向PC端那样 做很多事情

另外,模块还处于不断开发完善中,存在bug和很多不完善之处是必然的,有什么问题或想法欢迎跟作者反馈

如果你是新手,希望有一个一起学习交流Perl的地方,欢迎加入我所在的QQ群: PERL学习交流 群号:144539789

如果你是高手,又乐于助人,对新手有足够的耐心,也非常欢迎加入

=head2 作者

Perfi, E<lt>sjdy521@163.comE<gt>

=cut


感谢灰灰的投稿,欢迎更多 Perler 参与活动。本文网页地址见: http://advent.perl-china.com/calendar/2014/09/
--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout

--
您收到此邮件是因为您订阅了Google网上论坛上的"PerlChina Mongers 讨论组"群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到perlchina+unsubscribe@googlegroups.com
要发帖到此群组,请发送电子邮件至perlchina@googlegroups.com
访问此群组:http://groups.google.com/group/perlchina
要查看更多选项,请访问https://groups.google.com/d/optout