def __init__(self, token=None, logger=None, enable_session=True, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, **kwargs): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session") ) self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key ) self.use_encryption = False for k, v in kwargs.items(): self.config[k.upper()] = v
def test_from_object(): config = Config() config.from_pyfile(os.path.join(basedir, "test_config.py")) class ConfigObject: TOKEN = "456" config.from_object(ConfigObject()) assert config["TOKEN"] == "456"
def __init__( self, token=None, logger=None, enable_session=None, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, config=None, **kwargs ): self._handlers = {k: [] for k in self.message_types} self._handlers['all'] = [] self.make_error_page = make_error_page if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if config is None: self.config = Config(_DEFAULT_CONFIG) self.config.update( TOKEN=token, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key ) for k, v in kwargs.items(): self.config[k.upper()] = v if enable_session is not None: warnings.warn( "enable_session is deprecated." "set SESSION_STORAGE to False if you want to disable Session", DeprecationWarning, stacklevel=2 ) if not enable_session: self.config["SESSION_STORAGE"] = False if session_storage: self.config["SESSION_STORAGE"] = session_storage else: self.config = config self.use_encryption = False
def __init__(self, token=None, logger=None, enable_session=True, session_storage=None): self.config = Config(_DEFAULT_CONFIG) # 存放各类型处理函数的字典,键为类型,值为函数(用户通过handler添加) # 这里把各类型都作为键,并将值初始化为空数组 self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] # 新建记录器 if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger # 如果允许session,启用之,并将之存进名为werobot_session的文件中 if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session") ) # 更新配置 self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, )
def __init__(self, token=None, logger=None, enable_session=True, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, **kwargs): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] self.make_error_page = make_error_page if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.sqlitestorage import SQLiteStorage session_storage = SQLiteStorage() self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key ) self.use_encryption = False for k, v in kwargs.items(): self.config[k.upper()] = v
def test_config_ignore(): from werobot.config import Config config = Config( TOKEN="token from config" ) robot = WeRoBot( config=config, token="token2333" ) assert robot.token == "token from config"
def __init__(self, token=None, logger=None, enable_session=True, session_storage=None): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session") ) self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, )
def test_from_object(): config = Config() config.from_pyfile(os.path.join(basedir, "test_config.py")) class ConfigObject(): TOKEN = "456" config.from_object(ConfigObject()) assert config["TOKEN"] == "456"
def __init__(self, token=None, logger=None, enable_session=True, session_storage=None): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers["all"] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage(filename=os.path.abspath("werobot_session")) self.config.update(TOKEN=token, SESSION_STORAGE=session_storage)
def __init__(self, token=None, logger=None, enable_session=True, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, corp_id=None): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session") ) self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key, CORP_ID=corp_id )
class BaseRoBot(object): message_types = ['subscribe', 'unsubscribe', 'click', 'view', 'LOCATION', 'scancode_push', 'scancode_waitmsg', 'pic_sysphoto', 'pic_photo_or_album', 'pic_weixin', 'location_select', 'enter_agent', 'batch_job_result', # event 'text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=True, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, corp_id=None): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session") ) self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key, CORP_ID=corp_id ) def handler(self, f): """ Decorator to add a handler function for every messages """ self.add_handler(f, type='all') return f def text(self, f): """ Decorator to add a handler function for ``text`` messages """ self.add_handler(f, type='text') return f def filter(self, *args): """ Shortcut for ``text`` messages ``@filter("xxx")``, ``@filter(re.compile("xxx"))`` or ``@filter("xxx", "xxx2")`` to handle message with special content """ content_is_list = False if len(args) > 1: content_is_list = True else: target_content = args[0] if isinstance(target_content, six.string_types): target_content = to_text(target_content) def _check_content(message): return message.content == target_content elif hasattr(target_content, "match") and callable(target_content.match): # 正则表达式什么的 def _check_content(message): return target_content.match(message.content) else: raise TypeError("%s is not a valid target_content" % target_content) def wraps(f): if content_is_list: for x in args: self.filter(x)(f) return f argc = len(inspect.getargspec(f).args) @self.text def _f(message, session=None): if _check_content(message): return f(*[message, session][:argc]) return f return wraps def image(self, f): """ Decorator to add a handler function for ``image`` messages """ self.add_handler(f, type='image') return f def voice(self, f): """ Decorator to add a handler function for ``voice`` messages """ self.add_handler(f, type='voice') return f def location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='location') return f def link(self, f): """ Decorator to add a handler function for ``link`` messages """ self.add_handler(f, type='link') return f def subscribe(self, f): """ Decorator to add a handler function for ``subscribe event`` messages """ self.add_handler(f, type='subscribe') return f def unsubscribe(self, f): """ Decorator to add a handler function for ``unsubscribe event`` messages """ self.add_handler(f, type='unsubscribe') return f def Location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='LOCATION') return f def click(self, f): """ Decorator to add a handler function for ``click`` messages """ self.add_handler(f, type='click') return f def key_click(self, key): """ Shortcut for ``click`` messages @key_click('KEYNAME') for special key on click event """ def wraps(f): argc = len(inspect.getargspec(f).args) @self.click def onclick(message, session=None): if message.key == key: return f(*[message, session][:argc]) return f return wraps def view(self, f): """ Decorator to add a handler function for ``view event`` messages """ self.add_handler(f, type='view') return f def location_select(self, f): """ Decorator to add a handler function for ``location_select`` messages """ self.add_handler(f, type='location_select') return f def add_handler(self, func, type='all'): """ Add a handler function for messages of given type. """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append((func, len(inspect.getargspec(func).args))) def get_handlers(self, type): return self._handlers[type] + self._handlers['all'] def get_reply(self, message): """ Return the raw xml reply for the given message. """ session_storage = self.config["SESSION_STORAGE"] id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return reply except: self.logger.warning("Catch an exception", exc_info=True) def check_signature(self, timestamp, nonce, signature): sign = [self.config["TOKEN"], timestamp, nonce] sign.sort() sign = to_binary(''.join(sign)) sign = hashlib.sha1(sign).hexdigest() return sign == signature
class BaseRoBot(object): message_types = ['subscribe', 'unsubscribe', 'click', # event 'text', 'image', 'link', 'location', 'voice'] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=True, session_storage=None): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session") ) self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, ) def handler(self, f): """ Decorator to add a handler function for every messages """ self.add_handler(f, type='all') return f def text(self, f): """ Decorator to add a handler function for ``text`` messages """ self.add_handler(f, type='text') return f def image(self, f): """ Decorator to add a handler function for ``image`` messages """ self.add_handler(f, type='image') return f def location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='location') return f def link(self, f): """ Decorator to add a handler function for ``link`` messages """ self.add_handler(f, type='link') return f def voice(self, f): """ Decorator to add a handler function for ``voice`` messages """ self.add_handler(f, type='voice') return f def subscribe(self, f): """ Decorator to add a handler function for ``subscribe event`` messages """ self.add_handler(f, type='subscribe') return f def unsubscribe(self, f): """ Decorator to add a handler function for ``unsubscribe event`` messages """ self.add_handler(f, type='unsubscribe') return f def click(self, f): """ Decorator to add a handler function for ``click`` messages """ self.add_handler(f, type='click') return f def key_click(self, key): """ Shortcut for ``click`` messages @key_click('KEYNAME') for special key on click event """ def wraps(f): argc = len(inspect.getargspec(f).args) @self.click def onclick(message, session): if message.key == key: return f(*[message, session][:argc]) return f return wraps def add_handler(self, func, type='all'): """ Add a handler function for messages of given type. """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append((func, len(inspect.getargspec(func).args))) def get_handlers(self, type): return self._handlers[type] + self._handlers['all'] def get_reply(self, message): """ Return the raw xml reply for the given message. """ session_storage = self.config["SESSION_STORAGE"] id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return reply except: self.logger.warning("Catch an exception", exc_info=True) def check_signature(self, timestamp, nonce, signature): sign = [self.config["TOKEN"], timestamp, nonce] sign.sort() sign = to_binary(''.join(sign)) sign = hashlib.sha1(sign).hexdigest() return sign == signature
from werobot import WeRoBot from werobot.config import Config config = Config( TOKEN='tangxin', SERVER="auto", HOST="0.0.0.0", PORT="80", SESSION_STORAGE=None, APP_ID='wx98e9f23f0ba108c6', APP_SECRET=None, ENCODING_AES_KEY='36HbTSXwX3yU0NAbkvII2a0d6DLMK7TCm8e0bWQpZ3h ' ) robot = WeRoBot(config=config) @robot.handler def hello_world(message): return 'hello world' @try: pass except expression as identifier: pass else: pass @robot.text def test(message): return message.content
# -*- coding: utf-8 -*- import os from werobot.config import Config env_dist = os.environ config = Config( SERVER="auto", HOST="127.0.0.1", PORT="8888", SESSION_STORAGE=None, APP_ID=env_dist.get('APP_ID'), APP_SECRET=env_dist.get('APP_SECRET'), TOKEN=env_dist.get('TOKEN'), ENCODING_AES_KEY=env_dist.get('ENCODING_AES_KEY') )
class BaseRoBot(object): message_types = ['subscribe_event', 'unsubscribe_event', 'click_event', 'view_event', 'scan_event', 'location_event', 'unknown_event', # event 'text', 'image', 'link', 'location', 'voice', 'unknown'] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=True, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, **kwargs): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session") ) self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key ) self.use_encryption = False for k, v in kwargs.items(): self.config[k.upper()] = v @property def crypto(self): if hasattr(self, "_crypto"): return self._crypto app_id = self.config.get("APP_ID", None) if not app_id: raise ConfigError( "You need to provide app_id to encrypt/decrypt messages" ) encoding_aes_key = self.config.get("ENCODING_AES_KEY", None) if not encoding_aes_key: raise ConfigError( "You need to provide encoding_aes_key " "to encrypt/decrypt messages" ) from .crypto import MessageCrypt self._crypto = MessageCrypt( token=self.config["TOKEN"], encoding_aes_key=encoding_aes_key, app_id=app_id ) self.use_encryption = True return self._crypto def handler(self, f): """ Decorator to add a handler function for every messages """ self.add_handler(f, type='all') return f def text(self, f): """ Decorator to add a handler function for ``text`` messages """ self.add_handler(f, type='text') return f def image(self, f): """ Decorator to add a handler function for ``image`` messages """ self.add_handler(f, type='image') return f def location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='location') return f def link(self, f): """ Decorator to add a handler function for ``link`` messages """ self.add_handler(f, type='link') return f def voice(self, f): """ Decorator to add a handler function for ``voice`` messages """ self.add_handler(f, type='voice') return f def unknown(self, f): """ Decorator to add a handler function for ``unknown`` messages """ self.add_handler(f, type='unknown') return f def subscribe(self, f): """ Decorator to add a handler function for ``subscribe`` event """ self.add_handler(f, type='subscribe_event') return f def unsubscribe(self, f): """ Decorator to add a handler function for ``unsubscribe`` event """ self.add_handler(f, type='unsubscribe_event') return f def click(self, f): """ Decorator to add a handler function for ``click`` event """ self.add_handler(f, type='click_event') return f def scan(self, f): """ Decorator to add a handler function for ``scan`` event """ self.add_handler(f, type='scan_event') return f def location_event(self, f): """ Decorator to add a handler function for ``location`` event """ self.add_handler(f, type='location_event') return f def view(self, f): """ Decorator to add a handler function for ``view`` event """ self.add_handler(f, type='view_event') return f def unknown_event(self, f): """ Decorator to add a handler function for ``unknown`` event """ self.add_handler(f, type='unknown_event') return f def key_click(self, key): """ Shortcut for ``click`` messages @key_click('KEYNAME') for special key on click event """ def wraps(f): argc = len(inspect.getargspec(f).args) @self.click def onclick(message, session=None): if message.key == key: return f(*[message, session][:argc]) return f return wraps def filter(self, *args): """ Shortcut for ``text`` messages ``@filter("xxx")``, ``@filter(re.compile("xxx"))`` or ``@filter("xxx", "xxx2")`` to handle message with special content """ content_is_list = False if len(args) > 1: content_is_list = True else: target_content = args[0] if isinstance(target_content, six.string_types): target_content = to_text(target_content) def _check_content(message): return message.content == target_content elif hasattr(target_content, "match") \ and callable(target_content.match): # 正则表达式什么的 def _check_content(message): return target_content.match(message.content) else: raise TypeError( "%s is not a valid target_content" % target_content ) def wraps(f): if content_is_list: for x in args: self.filter(x)(f) return f argc = len(inspect.getargspec(f).args) @self.text def _f(message, session=None): if _check_content(message): return f(*[message, session][:argc]) return f return wraps def add_handler(self, func, type='all'): """ Add a handler function for messages of given type. """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append((func, len(inspect.getargspec(func).args))) def get_handlers(self, type): return self._handlers.get(type, []) + self._handlers['all'] def get_reply(self, message): """ Return the Reply Object for the given message. """ session_storage = self.config["SESSION_STORAGE"] id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return process_function_reply(reply, message=message) except: self.logger.warning("Catch an exception", exc_info=True) def check_signature(self, timestamp, nonce, signature): return check_signature( self.config["TOKEN"], timestamp, nonce, signature )
def client(self): config = Config() config.from_pyfile(os.path.join(BASE_DIR, "client_config.py")) return Client(config)
def test_from_pyfile(): config = Config() assert "TOKEN" not in config config.from_pyfile(os.path.join(basedir, "test_config.py")) assert config["TOKEN"] == "123"
class BaseRoBot(object): message_types = [ 'subscribe', 'unsubscribe', 'click', 'view', 'LOCATION', 'scancode_push', 'scancode_waitmsg', 'pic_sysphoto', 'pic_photo_or_album', 'pic_weixin', 'location_select', 'enter_agent', 'batch_job_result', # event 'text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link' ] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=True, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, corp_id=None): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session")) self.config.update(TOKEN=token, SESSION_STORAGE=session_storage, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key, CORP_ID=corp_id) def handler(self, f): """ Decorator to add a handler function for every messages """ self.add_handler(f, type='all') return f def text(self, f): """ Decorator to add a handler function for ``text`` messages """ self.add_handler(f, type='text') return f def filter(self, *args): """ Shortcut for ``text`` messages ``@filter("xxx")``, ``@filter(re.compile("xxx"))`` or ``@filter("xxx", "xxx2")`` to handle message with special content """ content_is_list = False if len(args) > 1: content_is_list = True else: target_content = args[0] if isinstance(target_content, six.string_types): target_content = to_text(target_content) def _check_content(message): return message.content == target_content elif hasattr(target_content, "match") and callable( target_content.match): # 正则表达式什么的 def _check_content(message): return target_content.match(message.content) else: raise TypeError("%s is not a valid target_content" % target_content) def wraps(f): if content_is_list: for x in args: self.filter(x)(f) return f argc = len(inspect.getargspec(f).args) @self.text def _f(message, session=None): if _check_content(message): return f(*[message, session][:argc]) return f return wraps def image(self, f): """ Decorator to add a handler function for ``image`` messages """ self.add_handler(f, type='image') return f def voice(self, f): """ Decorator to add a handler function for ``voice`` messages """ self.add_handler(f, type='voice') return f def location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='location') return f def link(self, f): """ Decorator to add a handler function for ``link`` messages """ self.add_handler(f, type='link') return f def subscribe(self, f): """ Decorator to add a handler function for ``subscribe event`` messages """ self.add_handler(f, type='subscribe') return f def unsubscribe(self, f): """ Decorator to add a handler function for ``unsubscribe event`` messages """ self.add_handler(f, type='unsubscribe') return f def Location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='LOCATION') return f def click(self, f): """ Decorator to add a handler function for ``click`` messages """ self.add_handler(f, type='click') return f def key_click(self, key): """ Shortcut for ``click`` messages @key_click('KEYNAME') for special key on click event """ def wraps(f): argc = len(inspect.getargspec(f).args) @self.click def onclick(message, session=None): if message.key == key: return f(*[message, session][:argc]) return f return wraps def view(self, f): """ Decorator to add a handler function for ``view event`` messages """ self.add_handler(f, type='view') return f def location_select(self, f): """ Decorator to add a handler function for ``location_select`` messages """ self.add_handler(f, type='location_select') return f def add_handler(self, func, type='all'): """ Add a handler function for messages of given type. """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append((func, len(inspect.getargspec(func).args))) def get_handlers(self, type): return self._handlers[type] + self._handlers['all'] def get_reply(self, message): """ Return the raw xml reply for the given message. """ session_storage = self.config["SESSION_STORAGE"] id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return reply except: self.logger.warning("Catch an exception", exc_info=True) def check_signature(self, timestamp, nonce, signature): sign = [self.config["TOKEN"], timestamp, nonce] sign.sort() sign = to_binary(''.join(sign)) sign = hashlib.sha1(sign).hexdigest() return sign == signature
from werobot import WeRoBot from werobot.config import Config from .config import ROBOT_HOST, ROBOT_PORT, APP_ID, APP_SECRET, TOKEN, ENCODING_AES_KEY, DATABASE from .utils.log import Logger from .utils.db_connect import RedisConnect, SQLiteConnect from .utils.robot_msg import is_subscribe_msg, is_unsubscribe_msg logger = Logger('robot') app_config = Config( HOST=ROBOT_HOST, PORT=ROBOT_PORT, APP_ID=APP_ID, APP_SECRET=APP_SECRET, TOKEN=TOKEN, ENCODING_AES_KEY=ENCODING_AES_KEY, SERVER='auto', SESSION_STORAGE=None, ) app = WeRoBot(config=app_config) # logger=logger logger.info('初始化 WeRoBot 完成') logger.info('数据库类型: {}'.format(DATABASE)) if DATABASE == 'redis': db = RedisConnect() elif DATABASE == 'sqlite': db = SQLiteConnect() else: logger.critical('未知数据库类型,无法初始化') exit()
class BaseRoBot(object): """ BaseRoBot 是整个应用的核心对象,负责提供 handler 的维护,消息和事件的处理等核心功能。 :param logger: 用来输出 log 的 logger,如果是 ``None``,将使用 werobot.logger :param config: 用来设置的 :class:`werobot.config.Config` 对象 \\ .. note:: 对于下面的参数推荐使用 :class:`~werobot.config.Config` 进行设置,\ 并且以下参数均已 **deprecated**。 :param token: 微信公众号设置的 token **(deprecated)** :param enable_session: 是否开启 session **(deprecated)** :param session_storage: 用来储存 session 的对象,如果为 ``None``,\ 将使用 werobot.session.sqlitestorage.SQLiteStorage **(deprecated)** :param app_id: 微信公众号设置的 app id **(deprecated)** :param app_secret: 微信公众号设置的 app secret **(deprecated)** :param encoding_aes_key: 用来加解密消息的 aes key **(deprecated)** """ message_types = [ 'subscribe_event', 'unsubscribe_event', 'click_event', 'view_event', 'scan_event', 'scancode_waitmsg_event', 'scancode_push_event', 'pic_sysphoto_event', 'pic_photo_or_album_event', 'pic_weixin_event', 'location_select_event', 'location_event', 'unknown_event', 'user_scan_product_event', 'user_scan_product_enter_session_event', 'user_scan_product_async_event', 'user_scan_product_verify_action_event', 'card_pass_check_event', 'card_not_pass_check_event', 'user_get_card_event', 'user_gifting_card_event', 'user_del_card_event', 'user_consume_card_event', 'user_pay_from_pay_cell_event', 'user_view_card_event', 'user_enter_session_from_card_event', 'update_member_card_event', 'card_sku_remind_event', 'card_pay_order_event', 'submit_membercard_user_info_event', # event 'text', 'image', 'link', 'location', 'voice', 'unknown', 'video', 'shortvideo' ] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__( self, token=None, logger=None, enable_session=None, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, config=None, **kwargs ): self._handlers = {k: [] for k in self.message_types} self._handlers['all'] = [] self.make_error_page = make_error_page if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if config is None: self.config = Config(_DEFAULT_CONFIG) self.config.update( TOKEN=token, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key ) for k, v in kwargs.items(): self.config[k.upper()] = v if enable_session is not None: warnings.warn( "enable_session is deprecated." "set SESSION_STORAGE to False if you want to disable Session", DeprecationWarning, stacklevel=2 ) if not enable_session: self.config["SESSION_STORAGE"] = False if session_storage: self.config["SESSION_STORAGE"] = session_storage else: self.config = config self.use_encryption = False @cached_property def crypto(self): app_id = self.config.get("APP_ID", None) if not app_id: raise ConfigError( "You need to provide app_id to encrypt/decrypt messages" ) encoding_aes_key = self.config.get("ENCODING_AES_KEY", None) if not encoding_aes_key: raise ConfigError( "You need to provide encoding_aes_key " "to encrypt/decrypt messages" ) self.use_encryption = True from .crypto import MessageCrypt return MessageCrypt( token=self.config["TOKEN"], encoding_aes_key=encoding_aes_key, app_id=app_id ) @cached_property def client(self): return Client(self.config) @cached_property def session_storage(self): if self.config["SESSION_STORAGE"] is False: return None if not self.config["SESSION_STORAGE"]: from .session.sqlitestorage import SQLiteStorage self.config["SESSION_STORAGE"] = SQLiteStorage() return self.config["SESSION_STORAGE"] @session_storage.setter def session_storage(self, value): warnings.warn( "You should set session storage in config", DeprecationWarning, stacklevel=2 ) self.config["SESSION_STORAGE"] = value def handler(self, f): """ 为每一条消息或事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='all') return f def text(self, f): """ 为文本 ``(text)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='text') return f def image(self, f): """ 为图像 ``(image)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='image') return f def location(self, f): """ 为位置 ``(location)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='location') return f def link(self, f): """ 为链接 ``(link)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='link') return f def voice(self, f): """ 为语音 ``(voice)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='voice') return f def video(self, f): """ 为视频 ``(video)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='video') return f def shortvideo(self, f): """ 为小视频 ``(shortvideo)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='shortvideo') return f def unknown(self, f): """ 为未知类型 ``(unknown)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='unknown') return f def subscribe(self, f): """ 为被关注 ``(subscribe)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='subscribe_event') return f def unsubscribe(self, f): """ 为被取消关注 ``(unsubscribe)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='unsubscribe_event') return f def click(self, f): """ 为自定义菜单事件 ``(click)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='click_event') return f def scan(self, f): """ 为扫描推送 ``(scan)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='scan_event') return f def scancode_push(self, f): """ 为扫描推送 ``(scancode_push)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='scancode_push_event') return f def scancode_waitmsg(self, f): """ 为扫描弹消息 ``(scancode_waitmsg)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='scancode_waitmsg_event') return f def pic_sysphoto(self, f): """ 为弹出系统拍照发图的事件推送 ``(pic_sysphoto_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='pic_sysphoto_event') return f def pic_photo_or_album(self, f): """ 为弹出拍照或者相册发图的事件推送 ``(pic_photo_or_album_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='pic_photo_or_album_event') return f def pic_weixin(self, f): """ 为弹出微信相册发图器的事件推送 ``(pic_weixin_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='pic_weixin_event') return f def location_select(self, f): """ 为弹出地理位置选择器的事件推送 ``(location_select_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='location_select_event') return f def location_event(self, f): """ 为上报位置 ``(location_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='location_event') return f def view(self, f): """ 为链接 ``(view)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='view_event') return f def user_scan_product(self, f): """ 为打开商品主页事件推送 ``(user_scan_product_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_scan_product_event') return f def user_scan_product_enter_session(self, f): """ 为进入公众号事件推送 ``(user_scan_product_enter_session_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_scan_product_enter_session_event') return f def user_scan_product_async(self, f): """ 为地理位置信息异步推送 ``(user_scan_product_async_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_scan_product_async_event') return f def user_scan_product_verify_action(self, f): """ 为商品审核结果推送 ``(user_scan_product_verify_action_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_scan_product_verify_action_event') return f def card_pass_check(self, f): """ 为生成的卡券通过审核 ``(card_pass_check_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='card_pass_check_event') return f def card_not_pass_check(self, f): """ 为生成的卡券未通过审核 ``(card_not_pass_check_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='card_not_pass_check_event') return f def user_get_card(self, f): """ 为用户领取卡券 ``(user_get_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_get_card_event') return f def user_gifting_card(self, f): """ 为用户转赠卡券 ``(user_gifting_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_gifting_card_event') return f def user_del_card(self, f): """ 为用户删除卡券 ``(user_del_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_del_card_event') return f def user_consume_card(self, f): """ 为卡券被核销 ``(user_consume_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_consume_card_event') return f def user_pay_from_pay_cell(self, f): """ 为微信买单完成 ``(user_pay_from_pay_cell_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_pay_from_pay_cell_event') return f def user_view_card(self, f): """ 为用户进入会员卡 ``(user_view_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_view_card_event') return f def user_enter_session_from_card(self, f): """ 为用户卡券里点击查看公众号进入会话 ``(user_enter_session_from_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_enter_session_from_card_event') return f def update_member_card(self, f): """ 为用户的会员卡积分余额发生变动 ``(update_member_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='update_member_card_event') return f def card_sku_remind(self, f): """ 为某个card_id的初始库存数大于200且当前库存小于等于100 ``(card_sku_remind_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='card_sku_remind_event') return f def card_pay_order(self, f): """ 为券点发生变动 ``(card_pay_order_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='card_pay_order_event') return f def submit_membercard_user_info(self, f): """ 为用户通过一键激活的方式提交信息并点击激活或者用户修改会员卡信息 ``(submit_membercard_user_info_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='submit_membercard_user_info_event') return f def unknown_event(self, f): """ 为未知类型 ``(unknown_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='unknown_event') return f def key_click(self, key): """ 为自定义菜单 ``(click)`` 事件添加 handler 的简便方法。 **@key_click('KEYNAME')** 用来为特定 key 的点击事件添加 handler 方法。 """ def wraps(f): argc = len(signature(f).parameters.keys()) @self.click def onclick(message, session=None): if message.key == key: return f(*[message, session][:argc]) return f return wraps def filter(self, *args): """ 为文本 ``(text)`` 消息添加 handler 的简便方法。 使用 ``@filter("xxx")``, ``@filter(re.compile("xxx"))`` 或 ``@filter("xxx", "xxx2")`` 的形式为特定内容添加 handler。 """ def wraps(f): self.add_filter(func=f, rules=list(args)) return f return wraps def add_handler(self, func, type='all'): """ 为 BaseRoBot 实例添加一个 handler。 :param func: 要作为 handler 的方法。 :param type: handler 的种类。 :return: None """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append( (func, len(signature(func).parameters.keys())) ) def get_handlers(self, type): return self._handlers.get(type, []) + self._handlers['all'] def add_filter(self, func, rules): """ 为 BaseRoBot 添加一个 ``filter handler``。 :param func: 如果 rules 通过,则处理该消息的 handler。 :param rules: 一个 list,包含要匹配的字符串或者正则表达式。 :return: None """ if not callable(func): raise ValueError("{} is not callable".format(func)) if not isinstance(rules, list): raise ValueError("{} is not list".format(rules)) if len(rules) > 1: for x in rules: self.add_filter(func, [x]) else: target_content = rules[0] if isinstance(target_content, six.string_types): target_content = to_text(target_content) def _check_content(message): return message.content == target_content elif is_regex(target_content): def _check_content(message): return target_content.match(message.content) else: raise TypeError("%s is not a valid rule" % target_content) argc = len(signature(func).parameters.keys()) @self.text def _f(message, session=None): _check_result = _check_content(message) if _check_result: if isinstance(_check_result, bool): _check_result = None return func(*[message, session, _check_result][:argc]) def parse_message( self, body, timestamp=None, nonce=None, msg_signature=None ): """ 解析获取到的 Raw XML ,如果需要的话进行解密,返回 WeRoBot Message。 :param body: 微信服务器发来的请求中的 Body。 :return: WeRoBot Message """ message_dict = parse_xml(body) if "Encrypt" in message_dict: xml = self.crypto.decrypt_message( timestamp=timestamp, nonce=nonce, msg_signature=msg_signature, encrypt_msg=message_dict["Encrypt"] ) message_dict = parse_xml(xml) return process_message(message_dict) def get_reply(self, message): """ 根据 message 的内容获取 Reply 对象。 :param message: 要处理的 message :return: 获取的 Reply 对象 """ session_storage = self.session_storage id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return process_function_reply(reply, message=message) except: self.logger.exception("Catch an exception") def get_encrypted_reply(self, message): """ 对一个指定的 WeRoBot Message ,获取 handlers 处理后得到的 Reply。 如果可能,对该 Reply 进行加密。 返回 Reply Render 后的文本。 :param message: 一个 WeRoBot Message 实例。 :return: reply (纯文本) """ reply = self.get_reply(message) if not reply: self.logger.warning("No handler responded message %s" % message) return '' if self.use_encryption: return self.crypto.encrypt_message(reply) else: return reply.render() def check_signature(self, timestamp, nonce, signature): """ 根据时间戳和生成签名的字符串 (nonce) 检查签名。 :param timestamp: 时间戳 :param nonce: 生成签名的随机字符串 :param signature: 要检查的签名 :return: 如果签名合法将返回 ``True``,不合法将返回 ``False`` """ return check_signature( self.config["TOKEN"], timestamp, nonce, signature ) def error_page(self, f): """ 为 robot 指定 Signature 验证不通过时显示的错误页面。 Usage:: @robot.error_page def make_error_page(url): return "<h1>喵喵喵 %s 不是给麻瓜访问的快走开</h1>" % url """ self.make_error_page = f return f
class BaseRoBot(object): message_types = [ 'subscribe', 'unsubscribe', 'click', 'event', 'text', 'image', 'link', 'location', 'voice', 'view' ] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=True, session_storage=None): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session")) self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, ) def handler(self, f): """ Decorator to add a handler function for every messages """ self.add_handler(f, type='all') return f def text(self, f): """ Decorator to add a handler function for ``text`` messages """ self.add_handler(f, type='text') return f def image(self, f): """ Decorator to add a handler function for ``image`` messages """ self.add_handler(f, type='image') return f def location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='location') return f def link(self, f): """ Decorator to add a handler function for ``link`` messages """ self.add_handler(f, type='link') return f def voice(self, f): """ Decorator to add a handler function for ``voice`` messages """ self.add_handler(f, type='voice') return f def subscribe(self, f): """ Decorator to add a handler function for ``subscribe event`` messages """ self.add_handler(f, type='subscribe') return f def unsubscribe(self, f): """ Decorator to add a handler function for ``unsubscribe event`` messages """ self.add_handler(f, type='unsubscribe') return f def click(self, f): """ Decorator to add a handler function for ``click`` messages """ self.add_handler(f, type='click') return f def view(self, f): """ Decorator to add a handler function for ``click`` messages """ self.add_handler(f, type='view') return f def key_click(self, key): """ Shortcut for ``click`` messages @key_click('KEYNAME') for special key on click event """ def wraps(f): argc = len(inspect.getargspec(f).args) @self.click def onclick(message, session): if message.key == key: return f(*[message, session][:argc]) return f return wraps def add_handler(self, func, type='all'): """ Add a handler function for messages of given type. """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append((func, len(inspect.getargspec(func).args))) def get_handlers(self, type): return self._handlers[type] + self._handlers['all'] def get_reply(self, message): """ Return the raw xml reply for the given message. """ session_storage = self.config["SESSION_STORAGE"] id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return reply except: self.logger.warning("Catch an exception", exc_info=True) def check_signature(self, timestamp, nonce, signature): sign = [self.config["TOKEN"], timestamp, nonce] sign.sort() sign = to_binary(''.join(sign)) sign = hashlib.sha1(sign).hexdigest() return sign == signature
class BaseRoBot(object): """ BaseRoBot 是整个应用的核心对象,负责提供 handler 的维护,消息和事件的处理等核心功能。 :param logger: 用来输出 log 的 logger,如果是 ``None``,将使用 werobot.logger :param config: 用来设置的 :class:`werobot.config.Config` 对象 \\ .. note:: 对于下面的参数推荐使用 :class:`~werobot.config.Config` 进行设置,\ 并且以下参数均已 **deprecated**。 :param token: 微信公众号设置的 token **(deprecated)** :param enable_session: 是否开启 session **(deprecated)** :param session_storage: 用来储存 session 的对象,如果为 ``None``,\ 将使用 werobot.session.sqlitestorage.SQLiteStorage **(deprecated)** :param app_id: 微信公众号设置的 app id **(deprecated)** :param app_secret: 微信公众号设置的 app secret **(deprecated)** :param encoding_aes_key: 用来加解密消息的 aes key **(deprecated)** """ message_types = [ 'subscribe_event', 'unsubscribe_event', 'click_event', 'view_event', 'scan_event', 'scancode_waitmsg_event', 'scancode_push_event', 'pic_sysphoto_event', 'pic_photo_or_album_event', 'pic_weixin_event', 'location_select_event', 'location_event', 'unknown_event', 'user_scan_product_event', 'user_scan_product_enter_session_event', 'user_scan_product_async_event', 'user_scan_product_verify_action_event', 'card_pass_check_event', 'card_not_pass_check_event', 'user_get_card_event', 'user_gifting_card_event', 'user_del_card_event', 'user_consume_card_event', 'user_pay_from_pay_cell_event', 'user_view_card_event', 'user_enter_session_from_card_event', 'update_member_card_event', 'card_sku_remind_event', 'card_pay_order_event', 'submit_membercard_user_info_event', # event 'text', 'image', 'link', 'location', 'voice', 'unknown', 'video', 'shortvideo' ] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=None, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, config=None, **kwargs): self._handlers = {k: [] for k in self.message_types} self._handlers['all'] = [] self.make_error_page = make_error_page if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if config is None: self.config = Config(_DEFAULT_CONFIG) self.config.update(TOKEN=token, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key) for k, v in kwargs.items(): self.config[k.upper()] = v if enable_session is not None: warnings.warn( "enable_session is deprecated." "set SESSION_STORAGE to False if you want to disable Session", DeprecationWarning, stacklevel=2) if not enable_session: self.config["SESSION_STORAGE"] = False if session_storage: self.config["SESSION_STORAGE"] = session_storage else: self.config = config self.use_encryption = False @cached_property def crypto(self): app_id = self.config.get("APP_ID", None) if not app_id: raise ConfigError( "You need to provide app_id to encrypt/decrypt messages") encoding_aes_key = self.config.get("ENCODING_AES_KEY", None) if not encoding_aes_key: raise ConfigError("You need to provide encoding_aes_key " "to encrypt/decrypt messages") self.use_encryption = True from .crypto import MessageCrypt return MessageCrypt(token=self.config["TOKEN"], encoding_aes_key=encoding_aes_key, app_id=app_id) @cached_property def client(self): return Client(self.config) @cached_property def session_storage(self): if self.config["SESSION_STORAGE"] is False: return None if not self.config["SESSION_STORAGE"]: from .session.sqlitestorage import SQLiteStorage self.config["SESSION_STORAGE"] = SQLiteStorage() return self.config["SESSION_STORAGE"] @session_storage.setter def session_storage(self, value): warnings.warn("You should set session storage in config", DeprecationWarning, stacklevel=2) self.config["SESSION_STORAGE"] = value def handler(self, f): """ 为每一条消息或事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='all') return f def text(self, f): """ 为文本 ``(text)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='text') return f def image(self, f): """ 为图像 ``(image)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='image') return f def location(self, f): """ 为位置 ``(location)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='location') return f def link(self, f): """ 为链接 ``(link)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='link') return f def voice(self, f): """ 为语音 ``(voice)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='voice') return f def video(self, f): """ 为视频 ``(video)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='video') return f def shortvideo(self, f): """ 为小视频 ``(shortvideo)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='shortvideo') return f def unknown(self, f): """ 为未知类型 ``(unknown)`` 消息添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='unknown') return f def subscribe(self, f): """ 为被关注 ``(subscribe)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='subscribe_event') return f def unsubscribe(self, f): """ 为被取消关注 ``(unsubscribe)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='unsubscribe_event') return f def click(self, f): """ 为自定义菜单事件 ``(click)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='click_event') return f def scan(self, f): """ 为扫描推送 ``(scan)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='scan_event') return f def scancode_push(self, f): """ 为扫描推送 ``(scancode_push)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='scancode_push_event') return f def scancode_waitmsg(self, f): """ 为扫描弹消息 ``(scancode_waitmsg)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='scancode_waitmsg_event') return f def pic_sysphoto(self, f): """ 为弹出系统拍照发图的事件推送 ``(pic_sysphoto_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='pic_sysphoto_event') return f def pic_photo_or_album(self, f): """ 为弹出拍照或者相册发图的事件推送 ``(pic_photo_or_album_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='pic_photo_or_album_event') return f def pic_weixin(self, f): """ 为弹出微信相册发图器的事件推送 ``(pic_weixin_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='pic_weixin_event') return f def location_select(self, f): """ 为弹出地理位置选择器的事件推送 ``(location_select_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='location_select_event') return f def location_event(self, f): """ 为上报位置 ``(location_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='location_event') return f def view(self, f): """ 为链接 ``(view)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='view_event') return f def user_scan_product(self, f): """ 为打开商品主页事件推送 ``(user_scan_product_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_scan_product_event') return f def user_scan_product_enter_session(self, f): """ 为进入公众号事件推送 ``(user_scan_product_enter_session_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_scan_product_enter_session_event') return f def user_scan_product_async(self, f): """ 为地理位置信息异步推送 ``(user_scan_product_async_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_scan_product_async_event') return f def user_scan_product_verify_action(self, f): """ 为商品审核结果推送 ``(user_scan_product_verify_action_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_scan_product_verify_action_event') return f def card_pass_check(self, f): """ 为生成的卡券通过审核 ``(card_pass_check_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='card_pass_check_event') return f def card_not_pass_check(self, f): """ 为生成的卡券未通过审核 ``(card_not_pass_check_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='card_not_pass_check_event') return f def user_get_card(self, f): """ 为用户领取卡券 ``(user_get_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_get_card_event') return f def user_gifting_card(self, f): """ 为用户转赠卡券 ``(user_gifting_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_gifting_card_event') return f def user_del_card(self, f): """ 为用户删除卡券 ``(user_del_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_del_card_event') return f def user_consume_card(self, f): """ 为卡券被核销 ``(user_consume_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_consume_card_event') return f def user_pay_from_pay_cell(self, f): """ 为微信买单完成 ``(user_pay_from_pay_cell_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_pay_from_pay_cell_event') return f def user_view_card(self, f): """ 为用户进入会员卡 ``(user_view_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_view_card_event') return f def user_enter_session_from_card(self, f): """ 为用户卡券里点击查看公众号进入会话 ``(user_enter_session_from_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='user_enter_session_from_card_event') return f def update_member_card(self, f): """ 为用户的会员卡积分余额发生变动 ``(update_member_card_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='update_member_card_event') return f def card_sku_remind(self, f): """ 为某个card_id的初始库存数大于200且当前库存小于等于100 ``(card_sku_remind_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='card_sku_remind_event') return f def card_pay_order(self, f): """ 为券点发生变动 ``(card_pay_order_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='card_pay_order_event') return f def submit_membercard_user_info(self, f): """ 为用户通过一键激活的方式提交信息并点击激活或者用户修改会员卡信息 ``(submit_membercard_user_info_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='submit_membercard_user_info_event') return f def unknown_event(self, f): """ 为未知类型 ``(unknown_event)`` 事件添加一个 handler 方法的装饰器。 """ self.add_handler(f, type='unknown_event') return f def key_click(self, key): """ 为自定义菜单 ``(click)`` 事件添加 handler 的简便方法。 **@key_click('KEYNAME')** 用来为特定 key 的点击事件添加 handler 方法。 """ def wraps(f): argc = len(signature(f).parameters.keys()) @self.click def onclick(message, session=None): if message.key == key: return f(*[message, session][:argc]) return f return wraps def filter(self, *args): """ 为文本 ``(text)`` 消息添加 handler 的简便方法。 使用 ``@filter("xxx")``, ``@filter(re.compile("xxx"))`` 或 ``@filter("xxx", "xxx2")`` 的形式为特定内容添加 handler。 """ def wraps(f): self.add_filter(func=f, rules=list(args)) return f return wraps def add_handler(self, func, type='all'): """ 为 BaseRoBot 实例添加一个 handler。 :param func: 要作为 handler 的方法。 :param type: handler 的种类。 :return: None """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append( (func, len(signature(func).parameters.keys()))) def get_handlers(self, type): return self._handlers.get(type, []) + self._handlers['all'] def add_filter(self, func, rules): """ 为 BaseRoBot 添加一个 ``filter handler``。 :param func: 如果 rules 通过,则处理该消息的 handler。 :param rules: 一个 list,包含要匹配的字符串或者正则表达式。 :return: None """ if not callable(func): raise ValueError("{} is not callable".format(func)) if not isinstance(rules, list): raise ValueError("{} is not list".format(rules)) if len(rules) > 1: for x in rules: self.add_filter(func, [x]) else: target_content = rules[0] if isinstance(target_content, six.string_types): target_content = to_text(target_content) def _check_content(message): return message.content == target_content elif is_regex(target_content): def _check_content(message): return target_content.match(message.content) else: raise TypeError("%s is not a valid rule" % target_content) argc = len(signature(func).parameters.keys()) @self.text def _f(message, session=None): _check_result = _check_content(message) if _check_result: if isinstance(_check_result, bool): _check_result = None return func(*[message, session, _check_result][:argc]) def parse_message(self, body, timestamp=None, nonce=None, msg_signature=None): """ 解析获取到的 Raw XML ,如果需要的话进行解密,返回 WeRoBot Message。 :param body: 微信服务器发来的请求中的 Body。 :return: WeRoBot Message """ message_dict = parse_xml(body) if "Encrypt" in message_dict: xml = self.crypto.decrypt_message( timestamp=timestamp, nonce=nonce, msg_signature=msg_signature, encrypt_msg=message_dict["Encrypt"]) message_dict = parse_xml(xml) return process_message(message_dict) def get_reply(self, message): """ 根据 message 的内容获取 Reply 对象。 :param message: 要处理的 message :return: 获取的 Reply 对象 """ session_storage = self.session_storage id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return process_function_reply(reply, message=message) except: self.logger.exception("Catch an exception") def get_encrypted_reply(self, message): """ 对一个指定的 WeRoBot Message ,获取 handlers 处理后得到的 Reply。 如果可能,对该 Reply 进行加密。 返回 Reply Render 后的文本。 :param message: 一个 WeRoBot Message 实例。 :return: reply (纯文本) """ reply = self.get_reply(message) if not reply: self.logger.warning("No handler responded message %s" % message) return '' if self.use_encryption: return self.crypto.encrypt_message(reply) else: return reply.render() def check_signature(self, timestamp, nonce, signature): """ 根据时间戳和生成签名的字符串 (nonce) 检查签名。 :param timestamp: 时间戳 :param nonce: 生成签名的随机字符串 :param signature: 要检查的签名 :return: 如果签名合法将返回 ``True``,不合法将返回 ``False`` """ return check_signature(self.config["TOKEN"], timestamp, nonce, signature) def error_page(self, f): """ 为 robot 指定 Signature 验证不通过时显示的错误页面。 Usage:: @robot.error_page def make_error_page(url): return "<h1>喵喵喵 %s 不是给麻瓜访问的快走开</h1>" % url """ self.make_error_page = f return f
Rule('/ics', endpoint='ics'), ] api = Api(app) api.add_resource(App, '/api/applications') for rule in urlpatterns: app.url_map.add(rule) #### router #### #### wechat #### import werobot robot = werobot.WeRoBot(token='69b3f633cd9e4136bfdd8be812a34e28') config = Config() basedir = os.path.dirname(os.path.abspath(__file__)) config.from_pyfile(os.path.join(basedir, "client_config.py")) client = Client(config=config) from werkzeug.local import LocalProxy def get_client(): return client tulingbot = Tuling() ###client.get_menu ''' client.create_menu({
from werobot.config import Config cfg = Config(SERVER='auto', HOST='0.0.0.0', PORT=80, SESSION_STORAGE=None, TOKEN='pXCtMZZ66J', APP_ID='wx8c53a82f87f6fe0d', APP_SECRET='dc9235aa8e7da02002221f1be221c7c6')
def test_id_and_secret(): config = Config() config.from_pyfile(os.path.join(basedir, "test_client.py")) client = Client(config) assert client.appid == "123" assert client.appsecret == "321"
class BaseRoBot(object): message_types = ['subscribe_event', 'unsubscribe_event', 'click_event', 'view_event', 'scan_event', 'location_event', 'unknown_event', # event 'text', 'image', 'link', 'location', 'voice', 'unknown'] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=True, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, **kwargs): self.config = Config(_DEFAULT_CONFIG) self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] self.make_error_page = make_error_page if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if enable_session and session_storage is None: from .session.sqlitestorage import SQLiteStorage session_storage = SQLiteStorage() self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key ) self.use_encryption = False for k, v in kwargs.items(): self.config[k.upper()] = v @property def crypto(self): if hasattr(self, "_crypto"): return self._crypto app_id = self.config.get("APP_ID", None) if not app_id: raise ConfigError( "You need to provide app_id to encrypt/decrypt messages" ) encoding_aes_key = self.config.get("ENCODING_AES_KEY", None) if not encoding_aes_key: raise ConfigError( "You need to provide encoding_aes_key " "to encrypt/decrypt messages" ) from .crypto import MessageCrypt self._crypto = MessageCrypt( token=self.config["TOKEN"], encoding_aes_key=encoding_aes_key, app_id=app_id ) self.use_encryption = True return self._crypto def handler(self, f): """ Decorator to add a handler function for every messages """ self.add_handler(f, type='all') return f def text(self, f): """ Decorator to add a handler function for ``text`` messages """ self.add_handler(f, type='text') return f def image(self, f): """ Decorator to add a handler function for ``image`` messages """ self.add_handler(f, type='image') return f def location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='location') return f def link(self, f): """ Decorator to add a handler function for ``link`` messages """ self.add_handler(f, type='link') return f def voice(self, f): """ Decorator to add a handler function for ``voice`` messages """ self.add_handler(f, type='voice') return f def unknown(self, f): """ Decorator to add a handler function for ``unknown`` messages """ self.add_handler(f, type='unknown') return f def subscribe(self, f): """ Decorator to add a handler function for ``subscribe`` event """ self.add_handler(f, type='subscribe_event') return f def unsubscribe(self, f): """ Decorator to add a handler function for ``unsubscribe`` event """ self.add_handler(f, type='unsubscribe_event') return f def click(self, f): """ Decorator to add a handler function for ``click`` event """ self.add_handler(f, type='click_event') return f def scan(self, f): """ Decorator to add a handler function for ``scan`` event """ self.add_handler(f, type='scan_event') return f def location_event(self, f): """ Decorator to add a handler function for ``location`` event """ self.add_handler(f, type='location_event') return f def view(self, f): """ Decorator to add a handler function for ``view`` event """ self.add_handler(f, type='view_event') return f def unknown_event(self, f): """ Decorator to add a handler function for ``unknown`` event """ self.add_handler(f, type='unknown_event') return f def key_click(self, key): """ Shortcut for ``click`` messages @key_click('KEYNAME') for special key on click event """ def wraps(f): argc = len(signature(f).parameters.keys()) @self.click def onclick(message, session=None): if message.key == key: return f(*[message, session][:argc]) return f return wraps def filter(self, *args): """ Shortcut for ``text`` messages ``@filter("xxx")``, ``@filter(re.compile("xxx"))`` or ``@filter("xxx", "xxx2")`` to handle message with special content """ content_is_list = False if len(args) > 1: content_is_list = True else: target_content = args[0] if isinstance(target_content, six.string_types): target_content = to_text(target_content) def _check_content(message): return message.content == target_content elif hasattr(target_content, "match") \ and callable(target_content.match): # 正则表达式什么的 def _check_content(message): return target_content.match(message.content) else: raise TypeError( "%s is not a valid target_content" % target_content ) def wraps(f): if content_is_list: for x in args: self.filter(x)(f) return f argc = len(signature(f).parameters.keys()) @self.text def _f(message, session=None): if _check_content(message): return f(*[message, session][:argc]) return f return wraps def add_handler(self, func, type='all'): """ Add a handler function for messages of given type. """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append((func, len(signature(func).parameters.keys()))) def get_handlers(self, type): return self._handlers.get(type, []) + self._handlers['all'] def parse_message(self, body, timestamp=None, nonce=None, msg_signature=None): """ 解析获取到的 Raw XML ,如果需要的话进行解密,返回 WeRoBot Message。 :param body: 微信服务器发来的请求中的 Body。 :return: WeRoBot Message """ message_dict = parse_xml(body) if "Encrypt" in message_dict: xml = self.crypto.decrypt_message( timestamp=timestamp, nonce=nonce, msg_signature=msg_signature, encrypt_msg=message_dict["Encrypt"] ) message_dict = parse_xml(xml) return process_message(message_dict) def get_reply(self, message): """ Return the Reply Object for the given message. """ session_storage = self.config["SESSION_STORAGE"] id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return process_function_reply(reply, message=message) except: self.logger.warning("Catch an exception", exc_info=True) def get_encrypted_reply(self, message): """ 对一个指定的 WeRoBot Message ,获取 handlers 处理后得到的 Reply。 如果可能,对该 Reply 进行加密。 返回 Reply Render 后的文本。 :param message: 一个 WeRoBot Message 实例。 :return: reply (纯文本) """ reply = self.get_reply(message) if not reply: self.logger.warning("No handler responded message %s" % message) return '' if self.use_encryption: return self.crypto.encrypt_message(reply) else: return reply.render() def check_signature(self, timestamp, nonce, signature): return check_signature( self.config["TOKEN"], timestamp, nonce, signature ) def error_page(self, f): """ 为 robot 指定 Signature 验证不通过时显示的错误页面。 Usage :: @robot.error_page def make_error_page(url): return "<h1>喵喵喵 %s 不是给麻瓜访问的快走开</h1>" % url """ self.make_error_page = f return f
class BaseRoBot(object): message_types = [ 'subscribe_event', 'unsubscribe_event', 'click_event', 'view_event', 'scan_event', 'location_event', 'unknown_event', # event 'text', 'image', 'link', 'location', 'voice', 'unknown' ] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=None, session_storage=None, app_id=None, app_secret=None, encoding_aes_key=None, config=None, **kwargs): self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] self.make_error_page = make_error_page if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger if config is None: self.config = Config(_DEFAULT_CONFIG) self.config.update(TOKEN=token, APP_ID=app_id, APP_SECRET=app_secret, ENCODING_AES_KEY=encoding_aes_key) for k, v in kwargs.items(): self.config[k.upper()] = v if enable_session is not None: warnings.warn( "enable_session is deprecated." "set SESSION_STORAGE to False if you want to disable Session", DeprecationWarning, stacklevel=2) if not enable_session: self.config["SESSION_STORAGE"] = False if session_storage: self.config["SESSION_STORAGE"] = session_storage else: self.config = config self.use_encryption = False @cached_property def crypto(self): app_id = self.config.get("APP_ID", None) if not app_id: raise ConfigError( "You need to provide app_id to encrypt/decrypt messages") encoding_aes_key = self.config.get("ENCODING_AES_KEY", None) if not encoding_aes_key: raise ConfigError("You need to provide encoding_aes_key " "to encrypt/decrypt messages") self.use_encryption = True from .crypto import MessageCrypt return MessageCrypt(token=self.config["TOKEN"], encoding_aes_key=encoding_aes_key, app_id=app_id) @cached_property def client(self): return Client(self.config) @cached_property def session_storage(self): if self.config["SESSION_STORAGE"] is False: return None if not self.config["SESSION_STORAGE"]: from .session.sqlitestorage import SQLiteStorage self.config["SESSION_STORAGE"] = SQLiteStorage() return self.config["SESSION_STORAGE"] @session_storage.setter def session_storage(self, value): warnings.warn("You should set session storage in config", DeprecationWarning, stacklevel=2) self.config["SESSION_STORAGE"] = value def handler(self, f): """ Decorator to add a handler function for every messages """ self.add_handler(f, type='all') return f def text(self, f): """ Decorator to add a handler function for ``text`` messages """ self.add_handler(f, type='text') return f def image(self, f): """ Decorator to add a handler function for ``image`` messages """ self.add_handler(f, type='image') return f def location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='location') return f def link(self, f): """ Decorator to add a handler function for ``link`` messages """ self.add_handler(f, type='link') return f def voice(self, f): """ Decorator to add a handler function for ``voice`` messages """ self.add_handler(f, type='voice') return f def unknown(self, f): """ Decorator to add a handler function for ``unknown`` messages """ self.add_handler(f, type='unknown') return f def subscribe(self, f): """ Decorator to add a handler function for ``subscribe`` event """ self.add_handler(f, type='subscribe_event') return f def unsubscribe(self, f): """ Decorator to add a handler function for ``unsubscribe`` event """ self.add_handler(f, type='unsubscribe_event') return f def click(self, f): """ Decorator to add a handler function for ``click`` event """ self.add_handler(f, type='click_event') return f def scan(self, f): """ Decorator to add a handler function for ``scan`` event """ self.add_handler(f, type='scan_event') return f def location_event(self, f): """ Decorator to add a handler function for ``location`` event """ self.add_handler(f, type='location_event') return f def view(self, f): """ Decorator to add a handler function for ``view`` event """ self.add_handler(f, type='view_event') return f def unknown_event(self, f): """ Decorator to add a handler function for ``unknown`` event """ self.add_handler(f, type='unknown_event') return f def key_click(self, key): """ Shortcut for ``click`` messages @key_click('KEYNAME') for special key on click event """ def wraps(f): argc = len(signature(f).parameters.keys()) @self.click def onclick(message, session=None): if message.key == key: return f(*[message, session][:argc]) return f return wraps def filter(self, *args): """ Shortcut for ``text`` messages ``@filter("xxx")``, ``@filter(re.compile("xxx"))`` or ``@filter("xxx", "xxx2")`` to handle message with special content """ content_is_list = False if len(args) > 1: content_is_list = True else: target_content = args[0] if isinstance(target_content, six.string_types): target_content = to_text(target_content) def _check_content(message): return message.content == target_content elif hasattr(target_content, "match") \ and callable(target_content.match): # 正则表达式什么的 def _check_content(message): return target_content.match(message.content) else: raise TypeError("%s is not a valid target_content" % target_content) def wraps(f): if content_is_list: for x in args: self.filter(x)(f) return f argc = len(signature(f).parameters.keys()) @self.text def _f(message, session=None): if _check_content(message): return f(*[message, session][:argc]) return f return wraps def add_handler(self, func, type='all'): """ Add a handler function for messages of given type. """ if not callable(func): raise ValueError("{} is not callable".format(func)) self._handlers[type].append( (func, len(signature(func).parameters.keys()))) def get_handlers(self, type): return self._handlers.get(type, []) + self._handlers['all'] def parse_message(self, body, timestamp=None, nonce=None, msg_signature=None): """ 解析获取到的 Raw XML ,如果需要的话进行解密,返回 WeRoBot Message。 :param body: 微信服务器发来的请求中的 Body。 :return: WeRoBot Message """ message_dict = parse_xml(body) if "Encrypt" in message_dict: xml = self.crypto.decrypt_message( timestamp=timestamp, nonce=nonce, msg_signature=msg_signature, encrypt_msg=message_dict["Encrypt"]) message_dict = parse_xml(xml) return process_message(message_dict) def get_reply(self, message): """ Return the Reply Object for the given message. """ session_storage = self.session_storage id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return process_function_reply(reply, message=message) except: self.logger.warning("Catch an exception", exc_info=True) def get_encrypted_reply(self, message): """ 对一个指定的 WeRoBot Message ,获取 handlers 处理后得到的 Reply。 如果可能,对该 Reply 进行加密。 返回 Reply Render 后的文本。 :param message: 一个 WeRoBot Message 实例。 :return: reply (纯文本) """ reply = self.get_reply(message) if not reply: self.logger.warning("No handler responded message %s" % message) return '' if self.use_encryption: return self.crypto.encrypt_message(reply) else: return reply.render() def check_signature(self, timestamp, nonce, signature): return check_signature(self.config["TOKEN"], timestamp, nonce, signature) def error_page(self, f): """ 为 robot 指定 Signature 验证不通过时显示的错误页面。 Usage :: @robot.error_page def make_error_page(url): return "<h1>喵喵喵 %s 不是给麻瓜访问的快走开</h1>" % url """ self.make_error_page = f return f
class BaseRoBot(object): message_types = ['subscribe', 'unsubscribe', 'click', # event 'text', 'image', 'link', 'location', 'voice'] token = ConfigAttribute("TOKEN") session_storage = ConfigAttribute("SESSION_STORAGE") def __init__(self, token=None, logger=None, enable_session=True, session_storage=None): self.config = Config(_DEFAULT_CONFIG) # 存放各类型处理函数的字典,键为类型,值为函数(用户通过handler添加) # 这里把各类型都作为键,并将值初始化为空数组 self._handlers = dict((k, []) for k in self.message_types) self._handlers['all'] = [] # 新建记录器 if logger is None: import werobot.logger logger = werobot.logger.logger self.logger = logger # 如果允许session,启用之,并将之存进名为werobot_session的文件中 if enable_session and session_storage is None: from .session.filestorage import FileStorage session_storage = FileStorage( filename=os.path.abspath("werobot_session") ) # 更新配置 self.config.update( TOKEN=token, SESSION_STORAGE=session_storage, ) # 修饰符函数,指定修饰类型,用于指定需要处理消息类型 # 处理所有类型消息 def handler(self, f): """ Decorator to add a handler function for every messages """ self.add_handler(f, type='all') return f # 只处理文本消息 def text(self, f): """ Decorator to add a handler function for ``text`` messages """ self.add_handler(f, type='text') return f # 只处理图像消息 def image(self, f): """ Decorator to add a handler function for ``image`` messages """ self.add_handler(f, type='image') return f # 只处理位置消息 def location(self, f): """ Decorator to add a handler function for ``location`` messages """ self.add_handler(f, type='location') return f # 只处理链接消息 def link(self, f): """ Decorator to add a handler function for ``link`` messages """ self.add_handler(f, type='link') return f # 只处理语音消息 def voice(self, f): """ Decorator to add a handler function for ``voice`` messages """ self.add_handler(f, type='voice') return f # 只处理被关注消息 def subscribe(self, f): """ Decorator to add a handler function for ``subscribe event`` messages """ self.add_handler(f, type='subscribe') return f # 只处理被取消关注消息 def unsubscribe(self, f): """ Decorator to add a handler function for ``unsubscribe event`` messages """ self.add_handler(f, type='unsubscribe') return f # 只处理菜单点击消息 def click(self, f): """ Decorator to add a handler function for ``click`` messages """ self.add_handler(f, type='click') return f # 只处理菜单点击消息,可指定菜单名(key) def key_click(self, key): """ Shortcut for ``click`` messages @key_click('KEYNAME') for special key on click event """ def wraps(f): # inspect.getargspec(func) return:(args, varargs, keywords, defaults) # args is a list of the argument names (it may contain nested lists). # varargs and keywords are the names of the * and ** arguments or None. # defaults is a tuple of default argument values or None if there are no default arguments. # 获取函数f的参数数目 argc = len(inspect.getargspec(f).args) @self.click def onclick(message, session): if message.key == key: return f(*[message, session][:argc]) return f return wraps # 添加handler def add_handler(self, func, type='all'): """ Add a handler function for messages of given type. """ if not callable(func): raise ValueError("{} is not callable".format(func)) # 把函数和其参数长度加到对应类型的key值列表中 self._handlers[type].append((func, len(inspect.getargspec(func).args))) # 获取处理该类型的handler def get_handlers(self, type): return self._handlers[type] + self._handlers['all'] # 获取返回的xml def get_reply(self, message): """ Return the raw xml reply for the given message. """ # 读取session session_storage = self.config["SESSION_STORAGE"] id = None session = None if session_storage and hasattr(message, "source"): id = to_binary(message.source) session = session_storage[id] # 获取处理该信息的所有handler handlers = self.get_handlers(message.type) try: for handler, args_count in handlers: # 从list[...]中取args_count个构成新list args = [message, session][:args_count] reply = handler(*args) if session_storage and id: session_storage[id] = session if reply: return reply except: self.logger.warning("Catch an exception", exc_info=True) # 校验签名 # 加密/校验流程如下: # 1. 将token、timestamp、nonce三个参数进行字典序排序 # 2. 将三个参数字符串拼接成一个字符串进行sha1加密 # 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 def check_signature(self, timestamp, nonce, signature): sign = [self.config["TOKEN"], timestamp, nonce] sign.sort() sign = to_binary(''.join(sign)) sign = hashlib.sha1(sign).hexdigest() return sign == signature