WeRoBot

WeRoBot 是一个微信机器人框架,采用 MIT 协议发布。

如果你在使用 WeRoBot 的过程中有什么建议或者疑惑,欢迎去 https://github.com/whtsky/WeRoBot/issues 提 Issue 或者给我发邮件: whtsky [at] gmail.com

入门

Hello World

最简单的Hello World, 会给收到的每一条信息回复 Hello World

import werobot

robot = werobot.WeRoBot(token='tokenhere')

@robot.handler
def echo(message):
    return 'Hello World!'

robot.run()

Handlers

WeRoBot会将合法的请求发送给 handlers 依次执行。

如果某一个 Handler 返回了非空值, WeRoBot 就会根据这个值创建回复,后面的 handlers 将不会被执行。

你可以通过两种方式添加 handler

import werobot

robot = werobot.WeRoBot(token='tokenhere')

# 通过修饰符添加handler
@robot.handler
def echo(message):
    return 'Hello World!'

# 通过`add_handler`添加handler
def echo(message):
    return 'Hello World!'
robot.add_handler(echo)

类型过滤

在大多数情况下, 一个 Handler 并不能处理所有类型的消息。幸运的是, WeRoBot 可以帮你过滤收到的消息。

只想处理被新用户关注的消息?:

import werobot

robot = werobot.WeRoBot(token='tokenhere')

@robot.subscribe
def subscribe(message):
    return 'Hello My Friend!'

robot.run()

或者,你的 handler 只能处理文本?

import werobot

robot = werobot.WeRoBot(token='tokenhere')

@robot.text
def echo(message):
    return message.content

robot.run()
修饰符 类型
robot.text 文本
robot.image 图像
robot.location 位置
robot.link 链接
robot.subscribe 被关注
robot.unsubscribe 被取消关注
robot.click 自定义菜单时间
robot.voice 语音
robot.unknown 未知类型

额,这个 handler 想处理文本信息和地理位置信息?

import werobot

robot = werobot.WeRoBot(token='tokenhere')

@robot.text
@robot.location
def handler(message):
    # Do what you love to do
    pass

robot.run()

当然,你也可以用 add_handler 函数添加handler,就像这样:

import werobot

robot = werobot.WeRoBot(token='tokenhere')

def handler(message):
    # Do what you love to do
    pass

robot.add_handler(handler, types=['text', 'location'])

robot.run()

Note

通过 robot.handler 添加的 handler 将收到所有信息;只有在其他 handler 没有给出返回值的情况下, 通过 robot.handler 添加的 handler 才会被调用。

robot.key_click —— 回应自定义菜单

@robot.key_click 是对 @robot.click 修饰符的改进。

如果你在自定义菜单中定义了一个 Key 为 abort 的菜单,响应这个菜单的 handler 可以写成这样

@robot.key_click("abort")
def abort():
    return "I'm a robot"

当然,如果你不喜欢用 @robot.key_click ,也可以写成这样

@robot.click
def abort(message):
    if message.key == "abort":
        return "I'm a robot"

两者是等价的。

robot.filter —— 回应有指定文本的消息

@robot.filter 是对 @robot.text 修饰符的改进。

现在你可以写这样的代码

@robot.filter("a")
def a():
    return "正文为 a "

import re


@robot.filter(re.compile(".*?bb.*?"))
def b():
    return "正文中含有 b "

@robot.filter(re.compile(".*?c.*?"), "d")
def c():
    return "正文中含有 c 或正文为 d"

这段代码等价于

@robot.text
def a(message):
    if message.content == "a":
        return "正文为 a "
import re


@robot.text
def b():
    if re.compile(".*?bb.*?").match(message.content):
        return "正文中含有 b "

@robot.text
def c():
    if re.compile(".*?c.*?").match(message.content) or message.content == "d":
        return "正文中含有 c 或正文为 d"

消息

公共属性

除了 UnknownMessage, 每一种 Message 都包括以下属性:

name value
id 消息id,64位整型 [2]
target 开发者账号( OpenID )
source 发送方账号( OpenID )
time 信息发送的时间,一个UNIX时间戳。
raw 信息的原始 XML 格式

TextMessage

TextMessage的属性:

name value
type ‘text’
content 信息的内容

ImageMessage

ImageMessage的属性:

name value
type ‘image’
img 图片网址。你可以从这个网址下到图片

LinkMessage

name value
type ‘link’
title 消息标题
description 消息描述
url 消息链接

LocationMessage

LocationMessage的属性:

name value
type ‘location’
location 一个元组。(纬度, 经度)
scale 地图缩放大小
label 地理位置信息

EventMessage

EventMessage的属性:

name value
type ‘subscribe’ ‘unsubscribe’ ‘click’ 或 ‘location’ [1]
key 事件 key 值。当 type = ‘click’ 时存在。
Latitude 地理位置纬度。当 type = ‘location’ 时存在。
Longitude 地理位置经度。当 type = ‘location’ 时存在。
Precision 地理位置精度。当 type = ‘location’ 时存在。

VoiceMessage

VoiceMessage的属性:

name value
type ‘voice’
media_id 消息媒体 ID
format 声音格式
recognition 语音识别结果

VideoMessage

VideoMessage:

UnknownMessage

UnknownMessage的属性:

name value
type ‘unknown’
raw 请求的正文部分。标准的XML格式。

Note

如果你不为 WeRoBot 贡献代码,你完全可以无视掉 UnknownMessage 。在正常的使用中,WeRoBot应该不会收到 UnknownMessage ——除非 WeRoBot 停止开发。

[1]当你被用户关注时,会收到 type=’subscribe’ 的事件; 被取消关注时是 type=’unsubscribe’ 。当用户点击自定义菜单按钮时 type 为 click ,用户上报地理位置的数据包 type 为 location
[2]截至目前( 2013.03.16 ),微信机器人所收到的消息中都不包含 MsgID.

回复

目前WeRoBot共有四种Reply: TextReplyArticlesReplyMusicReplyTransferCustomerServiceReply 。他们都继承自 WeChatReply

TextReply

TextReply 是简单的文本消息,构造函数的参数如下:

name value
content 信息正文。
target 信息的目标用户。通常是机器人用户。
source 信息的来源用户。通常是发送信息的用户。
time 信息发送的时间,一个UNIX时间戳。默认情况下会使用当前时间。
flag 如果是True, WeRoBot会对这条消息进行星标。你可以在公众平台后台看到所有的星标消息。

你可以在构建Reply时传入一个合法的 Message 对象来自动生成 sourcetarget

reply = TextReply(message=message, content='Hello!')

Note

如果你的handler返回了一个字符串, WeRoBot会自动将其转化为一个文本消息。

ArticlesReply

ArticlesReply 是图文消息,构造函数的参数如下:

name value
content 信息正文。可为空
target 信息的目标用户。通常是机器人用户。
source 信息的来源用户。通常是发送信息的用户。
time 信息发送的时间,一个UNIX时间戳。默认情况下会使用当前时间。
flag 如果是True, WeRoBot会对这条消息进行星标。你可以在公众平台后台看到所有的星标消息。

你需要给 ArticlesReply 添加 Article 来增加图文。 Article 类位于 werobot.reply.Article

Article 的构造函数的参数如下:

name value
title 标题
description 描述
img 图片链接
url 点击图片后跳转链接

注意,微信公众平台对图片链接有特殊的要求,详情可以在 消息接口使用指南 里看到。

在构造完一个 Article 后, 你需要通过 ArticlesReplyadd_article 参数把它添加进去。就像这样:

from werobot.reply import ArticlesReply, Article
reply = ArticlesReply(message=message)
article = Article(
    title="WeRoBot",
    description="WeRoBot是一个微信机器人框架",
    img="https://github.com/apple-touch-icon-144.png",
    url="https://github.com/whtsky/WeRoBot"
)
reply.add_article(article)

Note

每个ArticlesReply中 最多添加10个Article

你也可以让你的 handler 返回一个列表, 里面每一个元素都是一个长度为四的列表,

WeRoBot 会将其自动转为 ArticlesReply 。就像这样:

import werobot

robot = werobot.WeRoBot(token='tokenhere')

@robot.text
def articles(message):
    return [
        [
            "title",
            "description",
            "img",
            "url"
        ],
        [
            "whtsky",
            "I wrote WeRoBot",
            "https://secure.gravatar.com/avatar/0024710771815ef9b74881ab21ba4173?s=420",
            "http://whouz.com/"
        ]
    ]

robot.run()

MusicReply

MusicReply 是音乐消息,构造函数的参数如下:

name value
target 信息的目标用户。通常是机器人用户。
source 信息的来源用户。通常是发送信息的用户。
time 信息发送的时间,一个UNIX时间戳。默认情况下会使用当前时间。
title 标题
description 描述
url 音乐链接
hq_url 高质量音乐链接,WIFI环境优先使用该链接播放音乐。可为空 [3]
flag 如果是True, WeRoBot会对这条消息进行星标。你可以在公众平台后台看到所有的星标消息。
你也可以让你的 handler 返回一个长度为三或四的列表, [3]

WeRoBot 会将其自动转为 MusicReply 。就像这样:

import werobot

robot = werobot.WeRoBot(token='tokenhere')

@robot.text
def music(message):
    return [
        "title",
        "description",
        "music_url",
        "hq_music_url"
        ]

@robot.text
def music2(message):
    return [
        "微信你不懂爱",
        "龚琳娜最新力作",
        "http://weixin.com/budongai.mp3",
        ]

robot.run()
[3](1, 2) 如果你省略了高质量音乐链接的地址, WeRoBot 会自动将音乐链接的地址用于高质量音乐链接。

TransferCustomerServiceReply

将消息转发到多客服

Session

WeRoBot 0.4.0 中增加了功能强大的 Session 系统,你可以通过 Session 轻松实现用户状态的记录,享受如同 Web 开发般的便捷。

一个简单的使用 Session 的 Demo

robot = werobot.WeRoBot(token=werobot.utils.generate_token(),
                        enable_session=True)

@robot.text
def first(message, session):
    if 'last' in session:
        return
    session['last'] = message.content
    return message.content

robot.run()

开启 Session

想要开启 Session ,在实例化 WeRoBot 的时候需要传入 enable_sessionsession_storage 两个参数:

修改 Handler 以使用 Session

没有打开 Session 的时候,一个标准的 WeRoBot Handler 应该是这样的

@robot.text
def hello(message):
    return "Hello!"

而在打开 Session 之后, 这个 Handler 需要修改为接受第二个参数: session

@robot.text
def hello(message, session):
    count = session.get("count", 0) + 1
    session["count"] = count
    return "Hello! You have sent %s messages to me" % count

传入的 session 参数是一个标准的 Python 字典。

在修改完 Handler 之后, 你的微信机器人就拥有 Session 系统了 :)

可用的 Session Storage

class werobot.session.filestorage.FileStorage(filename='werobot_session')

FileStorage 会把你的 Session 数据以 dbm 形式储存在文件中。

Parameters:filename – 文件名, 默认为 werobot_session
class werobot.session.mongodbstorage.MongoDBStorage(collection)

MongoDBStorage 会把你的 Session 数据储存在一个 MongoDB Collection 中

import pymongo
import werobot
from werobot.session.mongodbstorage import MongoDBStorage

collection = pymongo.MongoClient()["wechat"]["session"]
session_storage = MongoDBStorage(collection)
robot = werobot.WeRoBot(token="token", enable_session=True,
                        session_storage=session_storage)

你需要安装 pymongo 才能使用 MongoDBStorage 。

Parameters:collection – 一个 MongoDB Collection。
class werobot.session.redisstorage.RedisStorage(redis, prefix='ws_')

RedisStorage 会把你的 Session 数据储存在 Redis 中

import redis
import werobot
from werobot.session.redisstorage import RedisStorage

db = redis.Redis()
session_storage = RedisStorage(db, prefix="my_prefix_")
robot = werobot.WeRoBot(token="token", enable_session=True,
                        session_storage=session_storage)

你需要安装 redis 才能使用 RedisStorage 。

Parameters:
  • redis – 一个 Redis Client。
  • prefix – Reids 中 Session 数据 key 的 prefix 。默认为 ws_
class werobot.session.saekvstorage.SaeKVDBStorage(prefix='ws_')

SaeKVDBStorage 使用SAE 的 KVDB 来保存你的session

import werobot
from werobot.session.saekvstorage import SaeKVDBStorage

session_storage = SaeKVDBStorage()
robot = werobot.WeRoBot(token="token", enable_session=True,
                        session_storage=session_storage)

需要先在后台开启 KVDB 支持

Parameters:prefix – KVDB 中 Session 数据 key 的 prefix 。默认为 ws_

WeRoBot.Client —— 微信 API 操作类

class werobot.client.Client(appid, appsecret)

微信 API 操作类 通过这个类可以方便的通过微信 API 进行一系列操作,比如主动发送消息、创建自定义菜单等

create_group(name)

创建分组 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=分组管理接口

Parameters:name – 分组名字(30个字符以内)
Returns:返回的 JSON 数据包
create_menu(menu_data)

创建自定义菜单

client = Client("id", "secret")
client.create_menu({
    "button":[
        {
            "type":"click",
            "name":"今日歌曲",
            "key":"V1001_TODAY_MUSIC"
        },
        {
            "type":"click",
            "name":"歌手简介",
            "key":"V1001_TODAY_SINGER"
        },
        {
            "name":"菜单",
            "sub_button":[
                {
                    "type":"view",
                    "name":"搜索",
                    "url":"http://www.soso.com/"
                },
                {
                    "type":"view",
                    "name":"视频",
                    "url":"http://v.qq.com/"
                },
                {
                    "type":"click",
                    "name":"赞一下我们",
                    "key":"V1001_GOOD"
                }
            ]
        }
    ]})

详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口

Parameters:menu_data – Python 字典
Returns:返回的 JSON 数据包
create_qrcode(**data)

创建二维码 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=生成带参数的二维码

Parameters:data – 你要发送的参数 dict
Returns:返回的 JSON 数据包
delete_menu()

删除自定义菜单。 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口

Returns:返回的 JSON 数据包
download_media(media_id)

下载多媒体文件。 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件

Parameters:media_id – 媒体文件 ID
Returns:requests 的 Response 实例
get_followers(first_user_id=None)

获取关注者列表 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=获取关注者列表

Parameters:first_user_id – 可选。第一个拉取的OPENID,不填默认从头开始拉取
Returns:返回的 JSON 数据包
get_group_by_id(openid)

查询用户所在分组 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=分组管理接口

Parameters:openid – 用户的OpenID
Returns:返回的 JSON 数据包
get_groups()

查询所有分组 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=分组管理接口

Returns:返回的 JSON 数据包
get_menu()

查询自定义菜单。 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口

Returns:返回的 JSON 数据包
get_user_info(user_id, lang='zh_CN')

获取用户基本信息 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=获取用户基本信息

Parameters:
  • user_id – 用户 ID 。 就是你收到的 Message 的 source
  • lang – 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
Returns:

返回的 JSON 数据包

grant_token()

获取 Access Token 。 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=通用接口文档

Returns:返回的 JSON 数据包
move_user(user_id, group_id)

移动用户分组 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=分组管理接口

Parameters:
  • user_id – 用户 ID 。 就是你收到的 Message 的 source
  • group_id – 分组 ID
Returns:

返回的 JSON 数据包

send_article_message(user_id, articles)

发送图文消息 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息

Parameters:
  • user_id – 用户 ID 。 就是你收到的 Message 的 source
  • articles – 一个包含至多10个 Article 实例的数组
Returns:

返回的 JSON 数据包

send_image_message(user_id, media_id)

发送图片消息 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息

Parameters:
  • user_id – 用户 ID 。 就是你收到的 Message 的 source
  • media_id – 图片的媒体ID。 可以通过 upload_media() 上传。
Returns:

返回的 JSON 数据包

send_music_message(user_id, url, hq_url, thumb_media_id, title=None, description=None)

发送音乐消息 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息

Parameters:
  • user_id – 用户 ID 。 就是你收到的 Message 的 source
  • url – 音乐链接
  • hq_url – 高品质音乐链接,wifi环境优先使用该链接播放音乐
  • thumb_media_id – 缩略图的媒体ID。 可以通过 upload_media() 上传。
  • title – 音乐标题
  • description – 音乐描述
Returns:

返回的 JSON 数据包

send_text_message(user_id, content)

发送文本消息 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息

Parameters:
  • user_id – 用户 ID 。 就是你收到的 Message 的 source
  • content – 消息正文
Returns:

返回的 JSON 数据包

send_video_message(user_id, media_id, title=None, description=None)

发送视频消息 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息

Parameters:
  • user_id – 用户 ID 。 就是你收到的 Message 的 source
  • media_id – 发送的视频的媒体ID。 可以通过 upload_media() 上传。
  • title – 视频消息的标题
  • description – 视频消息的描述
Returns:

返回的 JSON 数据包

send_voice_message(user_id, media_id)

发送语音消息 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息

Parameters:
  • user_id – 用户 ID 。 就是你收到的 Message 的 source
  • media_id – 发送的语音的媒体ID。 可以通过 upload_media() 上传。
Returns:

返回的 JSON 数据包

show_qrcode(ticket)

通过ticket换取二维码 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=生成带参数的二维码

Parameters:ticket – 二维码 ticket 。可以通过 create_qrcode() 获取到
Returns:返回的 Request 对象
update_group(group_id, name)

修改分组名 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=分组管理接口

Parameters:
  • group_id – 分组id,由微信分配
  • name – 分组名字(30个字符以内)
Returns:

返回的 JSON 数据包

upload_media(media_type, media_file)

上传多媒体文件。 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件

Parameters:media_type – 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)

:param media_file:要上传的文件,一个 File-object

Returns:返回的 JSON 数据包

WeRoBot.pay.WeixinPayClient —— 微信支付 API 操作类

class werobot.pay.WeixinPayClient(appid, pay_sign_key, pay_partner_id, pay_partner_key)

简化微信支付API操作

create_js_edit_address_param(accesstoken, **params)

alpha 暂时不建议使用 这个接口使用起来十分不友好 而且会引起巨大的误解

url 需要带上 code 和 state (url?code=xxx&state=1) code 和state 是 oauth 时候回来的

token 要传用户的 token

这尼玛 你能相信这些支付接口都是腾讯出的?

create_js_pay_package(**package)

签名 pay package 需要的参数 详情请参考 支付开发文档

Parameters:package – 需要签名的的参数
Returns:可以使用的packagestr
create_js_pay_params(**package)

签名 js 需要的参数 详情请参考 支付开发文档

wxclient.create_js_pay_params(
    body=标题, out_trade_no=本地订单号, total_fee=价格单位分,
    notify_url=通知url,
    spbill_create_ip=建议为支付人ip,
)
Parameters:package – 需要签名的的参数
Returns:支付需要的对象
create_native_pay_url(productid)

创建 native pay url 详情请参考 支付开发文档

Parameters:productid – 本地商品ID
Returns:返回URL
pay_deliver_notify(**deliver_info)

通知 腾讯发货

一般形式 ::
wxclient.pay_delivernotify(
openid=openid, transid=transaction_id, out_trade_no=本地订单号, deliver_timestamp=int(time.time()), deliver_status=”1”, deliver_msg=”ok”

)

:param 需要签名的的参数 :return: 支付需要的对象

pay_order_query(out_trade_no)

查询订单状态 一般用于无法确定 订单状态时候补偿

Parameters:out_trade_no – 本地订单号
Returns:订单信息dict

部署

在独立服务器上部署

当你运行 werobot.run 的时候,你可以通过传递 server 参数来手动指定使用的服务器

import werobot

robot = werobot.WeRoBot(token='tokenhere')

@robot.handler
def echo(message):
    return 'Hello World!'

robot.run(server='gevent', port=12233)

server 支持以下几种:

  • cgi
  • flup
  • wsgiref
  • waitress
  • cherrypy
  • paste
  • fapws3
  • tornado
  • gae
  • twisted
  • diesel
  • meinheld
  • gunicorn
  • eventlet
  • gevent
  • rocket
  • bjoern
  • auto

当 server 为 auto 时, WeRoBot 会自动依次尝试以下几种服务器:

  • Waitress
  • Paste
  • Twisted
  • CherryPy
  • WSGIRef

所以,只要你安装了相应的服务器软件,就可以使用 werobot.run 直接跑在生产环境下。

Note

server 的默认值为 auto

使用 Supervisor 管理守护进程

请注意, werobot.run 是跑在 非守护进程模式下 的——也就是说,一旦你关闭终端,进程就会自动退出。

我们建议您使用 Supervisor 来管理 WeRoBot 的进程。

配置文件样例:

[program:wechat_robot]
command = python /home/whtsky/robot.py
user = whtsky
redirect_stderr = true
stdout_logfile = /home/whtsky/logs/robot.log

使用 Nginx 进行反向代理

微信服务器只支持80端口的机器人——显然,你的服务器上不会只跑着一个微信机器人。对于这种情况,我们建议您使用 Nginx 来进行反向代理。

配置文件样例:

server {
    server_name example.com;
    listen 80;

    location / {
        proxy_pass_header Server;
        proxy_redirect off;
        proxy_pass http://127.0.0.1:12233;
    }
}

Note

在这个例子中, WeRoBot 的端口号为 12233。你应该在微信管理后台中将服务器地址设为 http://example.com

在SAE上部署

参考: 示例仓库

如果你需要使用 Session ,最好使用 werobot.session.saekvstorage

小工具

Token 生成器

WeRoBot帮你准备了一个Token生成器:

import werobot.utils

print(werobot.utils.generate_token())

Changelog

Version 0.6.1

  • Fix wrong URL in upload_media
  • Add VideoMessage

Version 0.6.0

Version 0.5.3

  • Fix: can’t handle request for root path

Version 0.5.2

  • Fix Python 3 support

Version 0.5.1

  • Fix typo

Version 0.5.0

  • Add werobot.client
  • Add werobot.config
  • Add werobot.logger
  • Add @werobot.key_click (Thanks @tg123)
  • Support Location Event
  • Use smart args
  • Friendly 403 page
  • Improved server support
  • Enable session by default.
  • Drop werobot.testing.make_text_message
  • Drop werobot.testing.make_image_message
  • Drop werobot.testing.make_location_message
  • Drop werobot.testing.make_voice_message
  • Drop werobot.testing.WeTest.send
  • Rewrite werobot.message
  • Rewrite testing case

Version 0.4.1

  • Add VoiceMessage
  • Add message.raw: Raw XML of message
  • Rename UnknownMessage.content to UnknownMessage.raw
  • Fix a bug when signature is invalid.
  • Ignore session when receive UnknownMessage

Version 0.4.0

  • Add session support
  • Add logging support
  • Rename werobot.test to werobot.testing
  • Handlers added by @robot.handler will have the lowest priority.

Version 0.3.5

  • Bug fix: Make BaseRoBot importable

Version 0.3.4

  • Rename WeRoBot.app to WeRoBot.wsgi
  • Add BaseRoBot class. It’s useful for creating extensions.
  • Reorganized documents.

Version 0.3.3

  • Add host param in werobot.run
  • Update EventMessage
  • Add LinkMessage

Version 0.3.2

  • Convert all arguments to unicode in Python 2 ( See issue #1 )

Version 0.3.1

  • Add server param in werobot.run

Version 0.3.0

  • Add new messages and replies support for WeChat 4.5