text: # -*-coding: utf-8 -*-
  # !/usr/bin/env python
  
  """
  File:   client.py
  Author: goodspeed
  Email:  cacique1103@gmail.com
  Github: https://github.com/gusibi
  Date:   2015-02-11
  Description: Weixin helpers
  """
  
  import sys
  import datetime
  from hashlib import sha1
  from decimal import Decimal
  
  import six
  from six.moves import html_parser
  
  PY2 = sys.version_info[0] == 2
  
  _always_safe = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-+"
  
  safe_char = _always_safe
  
  error_dict = {"AppID 参数错误": {"errcode": 40013, "errmsg": "invalid appid"}}
  
  
  if PY2:
      text_type = unicode
      iteritems = lambda d, *args, **kwargs: d.iteritems(*args, **kwargs)
  
      def to_native(x, charset=sys.getdefaultencoding(), errors="strict"):
          if x is None or isinstance(x, str):
              return x
          return x.encode(charset, errors)
  
  
  else:
      text_type = str
      iteritems = lambda d, *args, **kwargs: iter(d.items(*args, **kwargs))
  
      def to_native(x, charset=sys.getdefaultencoding(), errors="strict"):
          if x is None or isinstance(x, str):
              return x
          return x.decode(charset, errors)
  
  
  """
  The md5 and sha modules are deprecated since Python 2.5, replaced by the
  hashlib module containing both hash algorithms. Here, we provide a common
  interface to the md5 and sha constructors, preferring the hashlib module when
  available.
  """
  
  try:
      import hashlib
  
      md5_constructor = hashlib.md5
      md5_hmac = md5_constructor
      sha_constructor = hashlib.sha1
      sha_hmac = sha_constructor
  except ImportError:
      import md5
  
      md5_constructor = md5.new
      md5_hmac = md5
      import sha
  
      sha_constructor = sha.new
      sha_hmac = sha
  
  
  class Promise(object):
      """
      This is just a base class for the proxy class created in
      the closure of the lazy function. It can be used to recognize
      promises in code.
      """
  
      pass
  
  
  class _UnicodeDecodeError(UnicodeDecodeError):
      def __init__(self, obj, *args):
          self.obj = obj
          UnicodeDecodeError.__init__(self, *args)
  
      def __str__(self):
          original = UnicodeDecodeError.__str__(self)
          return "%s. You passed in %r (%s)" % (original, self.obj, type(self.obj))
  
  
  def smart_text(s, encoding="utf-8", strings_only=False, errors="strict"):
      """
      Returns a text object representing 's' -- unicode on Python 2 and str on
      Python 3. Treats bytestrings using the 'encoding' codec.
      If strings_only is True, don't convert (some) non-string-like objects.
      """
      if isinstance(s, Promise):
          # The input is the result of a gettext_lazy() call.
          return s
      return force_text(s, encoding, strings_only, errors)
  
  
  _PROTECTED_TYPES = six.integer_types + (
      type(None),
      float,
      Decimal,
      datetime.datetime,
      datetime.date,
      datetime.time,
  )
  
  
  def is_protected_type(obj):
      """Determine if the object instance is of a protected type.
      Objects of protected types are preserved as-is when passed to
      force_text(strings_only=True).
      """
      return isinstance(obj, _PROTECTED_TYPES)
  
  
  def force_text(s, encoding="utf-8", strings_only=False, errors="strict"):
      """
      Similar to smart_text, except that lazy instances are resolved to
      strings, rather than kept as lazy objects.
      If strings_only is True, don't convert (some) non-string-like objects.
      """
      # Handle the common case first for performance reasons.
      if issubclass(type(s), six.text_type):
          return s
      if strings_only and is_protected_type(s):
          return s
      try:
          if not issubclass(type(s), six.string_types):
              if six.PY3:
                  if isinstance(s, bytes):
                      s = six.text_type(s, encoding, errors)
                  else:
                      s = six.text_type(s)
              elif hasattr(s, "__unicode__"):
                  s = six.text_type(s)
              else:
                  s = six.text_type(bytes(s), encoding, errors)
          else:
              # Note: We use .decode() here, instead of six.text_type(s, encoding,
              # errors), so that if s is a SafeBytes, it ends up being a
              # SafeText at the end.
              s = s.decode(encoding, errors)
      except UnicodeDecodeError as e:
          if not isinstance(s, Exception):
              raise _UnicodeDecodeError(s, *e.args)
          else:
              # If we get to here, the caller has passed in an Exception
              # subclass populated with non-ASCII bytestring data without a
              # working unicode method. Try to handle this without raising a
              # further exception by individually forcing the exception args
              # to unicode.
              s = " ".join(force_text(arg, encoding, strings_only, errors) for arg in s)
      return s
  
  
  def smart_bytes(s, encoding="utf-8", strings_only=False, errors="strict"):
      """
      Returns a bytestring version of 's', encoded as specified in 'encoding'.
      If strings_only is True, don't convert (some) non-string-like objects.
      """
      if isinstance(s, Promise):
          # The input is the result of a gettext_lazy() call.
          return s
      return force_bytes(s, encoding, strings_only, errors)
  
  
  def force_bytes(s, encoding="utf-8", strings_only=False, errors="strict"):
      """
      Similar to smart_bytes, except that lazy instances are resolved to
      strings, rather than kept as lazy objects.
      If strings_only is True, don't convert (some) non-string-like objects.
      """
      # Handle the common case first for performance reasons.
      if isinstance(s, bytes):
          if encoding == "utf-8":
              return s
          else:
              return s.decode("utf-8", errors).encode(encoding, errors)
      if strings_only and is_protected_type(s):
          return s
      if isinstance(s, Promise):
          return six.text_type(s).encode(encoding, errors)
      if not isinstance(s, six.string_types):
          try:
              if six.PY3:
                  return six.text_type(s).encode(encoding)
              else:
                  return bytes(s)
          except UnicodeEncodeError:
              if isinstance(s, Exception):
                  # An Exception subclass containing non-ASCII data that doesn't
                  # know how to print itself properly. We shouldn't raise a
                  # further exception.
                  return b" ".join(
                      force_bytes(arg, encoding, strings_only, errors) for arg in s
                  )
              return six.text_type(s).encode(encoding, errors)
      else:
          return s.encode(encoding, errors)
  
  
  if six.PY3:
      smart_str = smart_text
      force_str = force_text
      smart_unicode = smart_text
  else:
      smart_str = smart_bytes
      force_str = force_bytes
      # backwards compatibility for Python 2
      smart_unicode = smart_text
      force_unicode = force_text
  
  smart_str.__doc__ = """
  Apply smart_text in Python 3 and smart_bytes in Python 2.
  This is suitable for writing to sys.stdout (for instance).
  """
  
  force_str.__doc__ = """
  Apply force_text in Python 3 and force_bytes in Python 2.
  """
  
  
  def genarate_js_signature(params):
      keys = list(params.keys())
      keys.sort()
      params_str = ""
      for key in keys:
          params_str += "%s=%s&" % (smart_str(key), smart_str(params[key]))
      params_str = smart_bytes(params_str[:-1])
      return sha1(params_str).hexdigest()
  
  
  def genarate_signature(params):
      sorted_params = sorted([v for k, v in params.items()])
      params_str = smart_str("".join(sorted_params))
      return sha1(str(params_str).encode("utf-8")).hexdigest()
  
  
  def get_encoding(html=None, headers=None):
      try:
          import chardet
  
          if html:
              encoding = chardet.detect(html).get("encoding")
              return encoding
      except ImportError:
          pass
      if headers:
          content_type = headers.get("content-type")
          try:
              encoding = content_type.split(" ")[1].split("=")[1]
              return encoding
          except IndexError:
              pass
  
  
  def iter_multi_items(mapping):
      """
      Iterates over the items of a mapping yielding keys and values
      without dropping any from more complex structures.
      """
      if isinstance(mapping, dict):
          for key, value in iteritems(mapping):
              if isinstance(value, (tuple, list)):
                  for value in value:
                      yield key, value
              else:
                  yield key, value
      else:
          for item in mapping:
              yield item
  
  
  def url_quote(string, charset="utf-8", errors="strict", safe="/:", unsafe=""):
      """
      URL encode a single string with a given encoding.
  
      :param s: the string to quote.
      :param charset: the charset to be used.
      :param safe: an optional sequence of safe characters.
      :param unsafe: an optional sequence of unsafe characters.
  
      .. versionadded:: 0.9.2
      The `unsafe` parameter was added.
      """
      if not isinstance(string, (text_type, bytes, bytearray)):
          string = text_type(string)
      if isinstance(string, text_type):
          string = string.encode(charset, errors)
      if isinstance(safe, text_type):
          safe = safe.encode(charset, errors)
      if isinstance(unsafe, text_type):
          unsafe = unsafe.encode(charset, errors)
      always_safe = _always_safe.encode(charset, errors)
      safe = frozenset(bytearray(safe) + always_safe) - frozenset(bytearray(unsafe))
      rv = bytearray()
      for char in bytearray(string):
          if char in safe:
              rv.append(char)
          else:
              rv.extend(("%%%02X" % char).encode("ascii"))
      return to_native(bytes(rv))
  
  
  def url_quote_plus(string, charset="utf-8", errors="strict", safe=""):
      return url_quote(string, charset, errors, safe + " ", "+").replace(" ", "+")
  
  
  def _url_encode_impl(obj, charset, encode_keys, sort, key):
      iterable = iter_multi_items(obj)
      if sort:
          iterable = sorted(iterable, key=key)
      for key, value in iterable:
          if value is None:
              continue
          if not isinstance(key, bytes):
              key = text_type(key).encode(charset)
          if not isinstance(value, bytes):
              value = text_type(value).encode(charset)
          yield url_quote_plus(key) + "=" + url_quote_plus(value)
  
  
  def url_encode(
      obj, charset="utf-8", encode_keys=False, sort=False, key=None, separator=b"&"
  ):
      separator = to_native(separator, "ascii")
      return separator.join(_url_encode_impl(obj, charset, encode_keys, sort, key))
  
  
  class WeixiErrorParser(html_parser.HTMLParser):
      def __init__(self):
          html_parser.HTMLParser.__init__(self)
          self.recording = 0
          self.data = []
  
      def handle_starttag(self, tag, attrs):
          if tag != "h4":
              return
          if self.recording:
              self.recording += 1
          self.recording = 1
  
      def handle_endtag(self, tag):
          if tag == "h4" and self.recording:
              self.recording -= 1
  
      def handle_data(self, data):
          if self.recording:
              self.data.append(data)
  
  
  def error_parser(error_html, encoding="gbk"):
      html = text_type(error_html, encoding or "gbk")
      error_parser = WeixiErrorParser()
      error_parser.feed(html)
      if error_parser.data:
          return error_dict.get(error_parser.data[0], None)
  