Exemple #1
0
    def serialize(self, expires=None):
        """Serialize the secure cookie into a string.

        If expires is provided, the session will be automatically invalidated
        after expiration when you unseralize it. This provides better
        protection against session cookie theft.

        :param expires: an optional expiration date for the cookie (a
                        :class:`datetime.datetime` object)
        """
        if self.secret_key is None:
            raise RuntimeError('no secret key defined')
        if expires:
            self['_expires'] = _date_to_unix(expires)
        result = []
        mac = hmac(self.secret_key, None, self.hash_method)
        for key, value in sorted(self.items()):
            result.append('%s=%s' % (
                url_quote_plus(key),
                self.quote(value)
            ))
            mac.update('|' + result[-1])
        return '%s?%s' % (
            mac.digest().encode('base64').strip(),
            '&'.join(result)
        )
Exemple #2
0
def url_for(endpoint, _external=False, _anchor=None, **values):
    url = local.adapter.build(endpoint, values, force_external=_external)
    if _anchor is not None:
        url += '#' + url_quote_plus(_anchor)
    if url.startswith(u'/') or url.startswith(u'http'):
        return url
    return u'/' + url
    def serialize(self, expires=None):
        """Serialize the secure cookie into a string.

        If expires is provided, the session will be automatically invalidated
        after expiration when you unseralize it. This provides better
        protection against session cookie theft.

        :param expires: an optional expiration date for the cookie (a
                        :class:`datetime.datetime` object)
        """
        if self.secret_key is None:
            raise RuntimeError('no secret key defined')
        if expires:
            self['_expires'] = _date_to_unix(expires)
        result = []
        mac = hmac(self.secret_key, None, self.hash_method)
        for key, value in sorted(self.items()):
            result.append('%s=%s' % (
                url_quote_plus(key),
                self.quote(value)
            ))
            mac.update('|' + result[-1])
        return '%s?%s' % (
            mac.digest().encode('base64').strip(),
            '&'.join(result)
        )
    def serialize(self, expires=None):
        """Serialize the secure cookie into a string.

        If expires is provided, the session will be automatically invalidated
        after expiration when you unseralize it. This provides better
        protection against session cookie theft.
        """
        if self.secret_key is None:
            raise RuntimeError('no secret key defined')
        if expires:
            if isinstance(expires, datetime):
                expires = expires.utctimetuple()
            elif isinstance(expires, (int, long, float)):
                expires = gmtime(expires)
            self['_expires'] = int(mktime(expires))
        result = []
        mac = hmac(self.secret_key, None, self.hash_method)
        for key, value in self.iteritems():
            result.append('%s=%s' % (
                url_quote_plus(key),
                pickle_quote(value)
            ))
            mac.update('|' + result[-1])
        return '%s?%s' % (
            mac.digest().encode('base64').strip(),
            '&'.join(result)
        )
 def _rewrite_session_url(self, url, sess):
     return '%s%s%s=%s' % (
         url,
         '?' in url and '&' or '?',
         self.session_url_key,
         url_quote_plus(sess)
     )
Exemple #6
0
 def generate_google_map_url(self, street, city, city_zip, country_name):
     url = "http://maps.googleapis.com/maps/api/staticmap?" \
           "center=%s&sensor=false&zoom=8&size=298x298" % \
           werkzeug.url_quote_plus(
               '%s, %s %s, %s' % (street, city,
                                  city_zip, country_name))
     return url
Exemple #7
0
 def mail_action_view(self, **kwargs):
     _logger.info('>>> %s'%request.httprequest.url)
     _logger.info('>>>mail_action_view %s'%kwargs)
     if not request.session.uid:
         # X2Z0eXBlPXdv
         return werkzeug.utils.redirect('/web/login?_fm=X2Z0eXBlPXdv&redirect=%s'%werkzeug.url_quote_plus(request.httprequest.url), 303)
     res = super(MailControllerExt, self).mail_action_view(**kwargs)
     return res
Exemple #8
0
    def _get_url_query_kvk_api(self, kvk):
        if self._get_config('service') != 'openkvk':
            return super()._get_url_query_kvk_api(kvk)

        params = url_encode({url_quote_plus('fields[]'): 'dossiernummer'})
        url = 'https://api.overheid.io/suggest/openkvk/{0}?'
        url = url.format(kvk)
        return url + params
Exemple #9
0
    def _get_url_query_name_api(self, name):
        if self._get_config('service') != 'openkvk':
            return super()._get_url_query_name_api(name)

        params = url_encode({url_quote_plus('fields[]'): 'handelsnaam'})
        url = 'https://api.overheid.io/suggest/openkvk/{0}?'
        url = url.format(name)
        return url + params
Exemple #10
0
def test_quoting():
    """URL quoting"""
    assert url_quote(u'\xf6\xe4\xfc') == '%C3%B6%C3%A4%C3%BC'
    assert url_unquote(url_quote(u'#%="\xf6')) == u'#%="\xf6'
    assert url_quote_plus('foo bar') == 'foo+bar'
    assert url_unquote_plus('foo+bar') == 'foo bar'
    assert url_encode({'a': None, 'b': 'foo bar'}) == 'b=foo+bar'
    assert url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffsklärung)') == \
           'http://de.wikipedia.org/wiki/Elf%20%28Begriffskl%C3%A4rung%29'
Exemple #11
0
def test_quoting():
    """URL quoting"""
    assert url_quote(u'\xf6\xe4\xfc') == '%C3%B6%C3%A4%C3%BC'
    assert url_unquote(url_quote(u'#%="\xf6')) == u'#%="\xf6'
    assert url_quote_plus('foo bar') == 'foo+bar'
    assert url_unquote_plus('foo+bar') == 'foo bar'
    assert url_encode({'a': None, 'b': 'foo bar'}) == 'b=foo+bar'
    assert url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffsklärung)') == \
           'http://de.wikipedia.org/wiki/Elf%20%28Begriffskl%C3%A4rung%29'
 def _deal_state_r(self, state):
     _logger.info('>>> get_state %s' % request.httprequest.url)
     _fm = request.params.get('_fm', None)
     if _fm:
         fragment = base64.urlsafe_b64decode(
             _fm.encode('utf-8')).decode('utf-8')
         r = werkzeug.url_unquote_plus(state.get('r', ''))
         state['r'] = werkzeug.url_quote_plus('%s#%s' % (r, fragment))
     return state
Exemple #13
0
    def serialize(self, expires = None):
        if self.secret_key is None:
            raise RuntimeError('no secret key defined')
        if expires:
            self['_expires'] = _date_to_unix(expires)
        result = []
        mac = hmac(self.secret_key, None, self.hash_method)
        for key, value in sorted(self.items()):
            result.append('%s=%s' % (url_quote_plus(key), self.quote(value)))
            mac.update('|' + result[-1])

        return '%s?%s' % (mac.digest().encode('base64').strip(), '&'.join(result))
Exemple #14
0
def handle_login(request,
                 userobj=None,
                 username=None,
                 password=None,
                 attended=True,
                 openid_identifier=None,
                 stage=None):
    """
    Process a 'login' request by going through the configured authentication
    methods in turn. The passable keyword arguments are explained in more
    detail at the top of this file.
    """
    params = {
        'username': username,
        'password': password,
        'attended': attended,
        'openid_identifier': openid_identifier,
        'multistage': (stage and True) or None
    }
    for authmethod in request.cfg.auth:
        #logging.info('CURRENT STAGE: %s, %s' % (params, authmethod.name))
        if stage and authmethod.name != stage:
            continue
        if openid_identifier and authmethod.name != 'openidqw':
            continue
        ret = authmethod.login(request, userobj, **params)

        userobj = ret.user_obj
        cont = ret.continue_flag
        if stage:
            stage = None
            del params['multistage']

        if ret.multistage:
            request._login_multistage = ret.multistage
            request._login_multistage_name = authmethod.name
            return userobj

        if ret.redirect_to:
            nextstage = get_multistage_continuation_url(
                request, authmethod.name)
            url = ret.redirect_to
            url = url.replace('%return_form', url_quote_plus(nextstage))
            url = url.replace('%return', url_quote(nextstage))
            abort(redirect(url))
        msg = ret.message
        if msg and not msg in request._login_messages:
            request._login_messages.append(msg)

        if not cont:
            break

    return userobj
Exemple #15
0
    def serialize(self, expires=None):
        if self.secret_key is None:
            raise RuntimeError('no secret key defined')
        if expires:
            self['_expires'] = _date_to_unix(expires)
        result = []
        mac = hmac(self.secret_key, None, self.hash_method)
        for key, value in sorted(self.items()):
            result.append('%s=%s' % (url_quote_plus(key), self.quote(value)))
            mac.update('|' + result[-1])

        return '%s?%s' % (mac.digest().encode('base64').strip(),
                          '&'.join(result))
def anchor_name_from_text(text):
    '''
    Generate an anchor name from the given text.
    This function generates valid HTML IDs matching: [A-Za-z][A-Za-z0-9:_.-]*
    Note: this transformation has a special feature: when you feed it with a
          valid ID/name, it will return it without modification (identity
          transformation).
    '''
    quoted = werkzeug.url_quote_plus(text, charset='utf-7', safe=':')
    res = quoted.replace('%', '.').replace('+', '_')
    if not res[:1].isalpha():
        return 'A%s' % res
    return res
Exemple #17
0
    def query(self, target, query):
        fields = 'affix|class|type|notes|cll|url|components'

        if query == 'help!':
            self.msg(target, '<query http://tiny.cc/query-format > '
                     '[(%s)]' % fields)
            return

        field = 'definition'
        match = re.search(r'\s\((?P<field>%s)\)$' % fields, query)
        if match:
            field = match.group('field')
            query = re.sub(r'\s\(.+?\)$', '', query)

        url = 'http://vlasisku.lojban.org/%s' % url_quote_plus(query)
        results = database.root.query(query)

        entry = results['entry']
        if not entry and len(results['matches']) == 1:
            entry = results['matches'].pop()

        if entry or field == 'components':
            case = lambda x: field == x
            if case('definition'):
                data = entry.textdefinition.encode('utf-8')
            elif case('affix'):
                data = ', '.join('-%s-' % i for i in entry.affixes)
            elif case('class'):
                data = entry.grammarclass
            elif case('type'):
                data = entry.type
            elif case('notes'):
                data = entry.textnotes.encode('utf-8')
            elif case('cll'):
                data = '  '.join(link for (chap, link) in entry.cll)
            elif case('url'):
                data = url
            elif case('components'):
                entry = query
                data = ' '.join(e.word for a in compound2affixes(query)
                                if len(a) != 1
                                for e in database.root.entries.itervalues()
                                if a in e.searchaffixes)

            data = data or '(none)'
            if field == 'definition':
                format = '%s = %s'
                self.msg(target, format % (entry, data))
            else:
                format = '%s (%s) = %s'
                self.msg(target, format % (entry, field, data))
Exemple #18
0
def anchor_name_from_text(text):
    """
    Generate an anchor name from the given text.
    This function generates valid HTML IDs matching: [A-Za-z][A-Za-z0-9:_.-]*

    Note: this transformation has a special feature: when you feed it with a
    valid ID/name, it will return it without modification (identity
    transformation).
    """
    quoted = werkzeug.url_quote_plus(text, charset='utf-7', safe=':')
    res = quoted.replace('%', '.').replace('+', '_')
    if not res[:1].isalpha():
        return 'A{0}'.format(res)
    return res
def handle_login(userobj, **kw):
    """
    Process a 'login' request by going through the configured authentication
    methods in turn. The passable keyword arguments are explained in more
    detail at the top of this file.
    """

    stage = kw.get('stage')
    params = {
        'username': kw.get('login_username'),
        'password': kw.get('login_password'),
        'openid': kw.get('login_openid'),
        'multistage': (stage and True) or None,
        'attended': True
    }
    # add the other parameters from the form
    for param in kw.keys():
        params[param] = kw.get(param)

    for authmethod in app.cfg.auth:
        if stage and authmethod.name != stage:
            continue
        ret = authmethod.login(userobj, **params)

        userobj = ret.user_obj
        cont = ret.continue_flag
        if stage:
            stage = None
            del params['multistage']

        if ret.multistage:
            flaskg._login_multistage = ret.multistage
            flaskg._login_multistage_name = authmethod.name
            return userobj

        if ret.redirect_to:
            nextstage = get_multistage_continuation_url(authmethod.name)
            url = ret.redirect_to
            url = url.replace('%return_form', url_quote_plus(nextstage))
            url = url.replace('%return', url_quote(nextstage))
            abort(redirect(url))
        msg = ret.message
        if msg and not msg in flaskg._login_messages:
            flaskg._login_messages.append(msg)

        if not cont:
            break

    return userobj
Exemple #20
0
def handle_login(userobj, **kw):
    """
    Process a 'login' request by going through the configured authentication
    methods in turn. The passable keyword arguments are explained in more
    detail at the top of this file.
    """

    stage = kw.get('stage')
    params = {'username': kw.get('login_username'),
              'password': kw.get('login_password'),
              'openid': kw.get('login_openid'),
              'multistage': (stage and True) or None,
              'attended': True
             }
    # add the other parameters from the form
    for param in kw.keys():
        params[param] = kw.get(param)

    for authmethod in app.cfg.auth:
        if stage and authmethod.name != stage:
            continue
        ret = authmethod.login(userobj, **params)

        userobj = ret.user_obj
        cont = ret.continue_flag
        if stage:
            stage = None
            del params['multistage']

        if ret.multistage:
            flaskg._login_multistage = ret.multistage
            flaskg._login_multistage_name = authmethod.name
            return userobj

        if ret.redirect_to:
            nextstage = get_multistage_continuation_url(authmethod.name)
            url = ret.redirect_to
            url = url.replace('%return_form', url_quote_plus(nextstage))
            url = url.replace('%return', url_quote(nextstage))
            abort(redirect(url))
        msg = ret.message
        if msg and not msg in flaskg._login_messages:
            flaskg._login_messages.append(msg)

        if not cont:
            break

    return userobj
def handle_login(request, userobj=None, username=None, password=None,
                 attended=True, openid_identifier=None, stage=None):
    """
    Process a 'login' request by going through the configured authentication
    methods in turn. The passable keyword arguments are explained in more
    detail at the top of this file.
    """
    params = {
        'username': username,
        'password': password,
        'attended': attended,
        'openid_identifier': openid_identifier,
        'multistage': (stage and True) or None
    }
    for authmethod in request.cfg.auth:
        if stage and authmethod.name != stage:
            continue
        ret = authmethod.login(request, userobj, **params)

        userobj = ret.user_obj
        cont = ret.continue_flag
        if stage:
            stage = None
            del params['multistage']

        if ret.multistage:
            request._login_multistage = ret.multistage
            request._login_multistage_name = authmethod.name
            return userobj

        if ret.redirect_to:
            nextstage = get_multistage_continuation_url(request, authmethod.name)
            url = ret.redirect_to
            url = url.replace('%return_form', url_quote_plus(nextstage))
            url = url.replace('%return', url_quote(nextstage))
            abort(redirect(url))
        msg = ret.message
        if msg and not msg in request._login_messages:
            request._login_messages.append(msg)

        if not cont:
            break

    return userobj
Exemple #22
0
class WordBot(BotBase):

    nickname = 'valsi'

    def query(self, target, query):
        fields = 'affix|class|type|notes|cll|url|components|lujvo'

        if query == 'help!':
            self.msg(target, '<query http://tiny.cc/query-format > '
                             '[(%s)]' % fields)
            return
        elif query == 'update!':
            def do_update():
                from contextlib import closing
                import urllib2
                import xml.etree.cElementTree as etree
                import os

                self.msg(target, 'downloading...')
                opener = urllib2.build_opener()
                opener.addheaders.append(('Cookie', 'jbovlastesessionid=%s' % self.factory.app.config['BOT_KEY']))
                url = 'http://jbovlaste.lojban.org/export/xml-export.html?lang=en'
                with closing(opener.open(url)) as data:
                    xml = etree.parse(data)
                    assert xml.getroot().tag == 'dictionary'
                    with open('vlasisku/data/jbovlaste.xml', 'w') as file:
                        xml.write(file, 'utf-8')
                self.msg(target, 'updating...')
                database.init_app(database.app, True)
                self.msg(target, 'done!')

            import threading
            threading.Thread(target=do_update).start()
            return

        field = 'definition'
        match = re.search(r'\s\((?P<field>%s)\)$' % fields, query)
        if match:
            field = match.group('field')
            query = re.sub(r'\s\(.+?\)$', '', query)

        if field == 'lujvo':
            try:
                lujvos = jvocuhadju(query)
                tanru = query
                query = lujvos[0]
            except ValueError, e:
                self.msg(target, 'error: %s' % e)
                return

        url = 'http://%s/%s' % (self.factory.app.config['WEBSITE'], url_quote_plus(query))
        results = database.root.query(query)

        entry = results['entry']
        if not entry and len(results['matches']) == 1:
            entry = results['matches'].pop()

        if entry or field in ['components', 'lujvo']:
            case = lambda x: field == x
            if case('definition'):
                data = entry.textdefinition.encode('utf-8')
            elif case('affix'):
                data = ', '.join('-%s-' % i for i in entry.affixes)
            elif case('class'):
                data = entry.grammarclass
            elif case('type'):
                data = entry.type
            elif case('notes'):
                data = entry.textnotes.encode('utf-8')
            elif case('cll'):
                data = '  '.join(link for (chap, link) in entry.cll)
            elif case('url'):
                data = url
            elif case('components'):
                entry = query
                data = ' '.join(e.word for a in compound2affixes(query)
                                if len(a) != 1
                                for e in database.root.entries.itervalues()
                                if a in e.searchaffixes)
            elif case('lujvo'):
                data = ', '.join(lujvos)
                if entry:
                    data += ' (defined as %s = %s)' % (query, entry.textdefinition.encode('utf-8'))
                entry = tanru

            data = data or '(none)'
            if field == 'definition':
                format = '%s = %s'
                self.msg(target, format % (entry, data))
            else:
                format = '%s (%s) = %s'
                self.msg(target, format % (entry, field, data))
Exemple #23
0
    def _get_transifex_url(self):
        """ Construct transifex URL based on the module on configuration """
        # e.g. 'https://www.transifex.com/odoo/'
        base_url = self.env['ir.config_parameter'].sudo().get_param('transifex.project_url')

        tx_config_file = ConfigParser()
        tx_sections = []
        for addon_path in ad_paths:
            tx_path = opj(addon_path, '.tx', 'config')
            if os.path.isfile(tx_path):
                tx_config_file.read(tx_path)
                # first section is [main], after [odoo-11.sale]
                tx_sections.extend(tx_config_file.sections()[1:])

            # parent directory ad .tx/config is root directory in odoo/odoo
            tx_path = opj(addon_path, os.pardir, '.tx', 'config')
            if os.path.isfile(tx_path):
                tx_config_file.read(tx_path)
                tx_sections.extend(tx_config_file.sections()[1:])

        if not base_url or not tx_sections:
            self.update({'transifex_url': False})
        else:
            base_url = base_url.rstrip('/')

            # will probably be the same for all terms, avoid multiple searches
            translation_languages = list(set(self.mapped('lang')))
            languages = self.env['res.lang'].with_context(active_test=False).search(
                [('code', 'in', translation_languages)])

            language_codes = dict((l.code, l.iso_code) for l in languages)

            # .tx/config files contains the project reference
            # using ini files like '[odoo-master.website_sale]'
            translation_modules = set(self.mapped('module'))
            project_modules = {}
            for module in translation_modules:
                for section in tx_sections:
                    tx_project, tx_mod = section.split('.')
                    if tx_mod == module:
                        project_modules[module] = tx_project

            for translation in self:
                if not translation.module or not translation.source or translation.lang == 'en_US':
                    # custom or source term
                    translation.transifex_url = False
                    continue

                lang_code = language_codes.get(translation.lang)
                if not lang_code:
                    translation.transifex_url = False
                    continue

                project = project_modules.get(translation.module)
                if not project:
                    translation.transifex_url = False
                    continue

                # e.g. 'https://www.transifex.com/odoo/odoo-10/translate/#fr/sale/42?q=Sale+Order'
                translation.transifex_url = "%(url)s/%(project)s/translate/#%(lang)s/%(module)s/42?q=%(src)s" % {
                    'url': base_url,
                    'project': project,
                    'lang': lang_code,
                    'module': translation.module,
                    'src': werkzeug.url_quote_plus(translation.source[:50]),
                }
Exemple #24
0
    def _get_transifex_url(self):
        """ Construct transifex URL based on the module on configuration """
        # e.g. 'https://www.transifex.com/odoo/'
        base_url = self.env['ir.config_parameter'].sudo().get_param(
            'transifex.project_url')

        tx_config_file = ConfigParser()
        tx_sections = []
        for addon_path in ad_paths:
            tx_path = opj(addon_path, '.tx', 'config')
            if os.path.isfile(tx_path):
                tx_config_file.read(tx_path)
                # first section is [main], after [odoo-11.sale]
                tx_sections.extend(tx_config_file.sections()[1:])

            # parent directory ad .tx/config is root directory in odoo/odoo
            tx_path = opj(addon_path, os.pardir, '.tx', 'config')
            if os.path.isfile(tx_path):
                tx_config_file.read(tx_path)
                tx_sections.extend(tx_config_file.sections()[1:])

        if not base_url or not tx_sections:
            self.update({'transifex_url': False})
        else:
            base_url = base_url.rstrip('/')

            # will probably be the same for all terms, avoid multiple searches
            translation_languages = list(set(self.mapped('lang')))
            languages = self.env['res.lang'].with_context(
                active_test=False).search([('code', 'in',
                                            translation_languages)])

            language_codes = dict((l.code, l.iso_code) for l in languages)

            # .tx/config files contains the project reference
            # using ini files like '[odoo-master.website_sale]'
            translation_modules = set(self.mapped('module'))
            project_modules = {}
            for module in translation_modules:
                for section in tx_sections:
                    tx_project, tx_mod = section.split('.')
                    if tx_mod == module:
                        project_modules[module] = tx_project

            for translation in self:
                if not translation.module or not translation.source or translation.lang == 'en_US':
                    # custom or source term
                    translation.transifex_url = False
                    continue

                lang_code = language_codes.get(translation.lang)
                if not lang_code:
                    translation.transifex_url = False
                    continue

                project = project_modules.get(translation.module)
                if not project:
                    translation.transifex_url = False
                    continue

                # e.g. https://www.transifex.com/odoo/odoo-10/translate/#fr/sale/42?q=text'Sale+Order'
                translation.transifex_url = "%(url)s/%(project)s/translate/#%(lang)s/%(module)s/42?q=%(src)s" % {
                    'url':
                    base_url,
                    'project':
                    project,
                    'lang':
                    lang_code,
                    'module':
                    translation.module,
                    'src':
                    werkzeug.url_quote_plus(
                        "text:'" +
                        translation.source[:50].replace("'", "\\'") + "'"),
                }
Exemple #25
0
def escape_u(url):
    return escape(url_quote_plus(url))
Exemple #26
0
 def generate_google_map_url(self, street, city, city_zip, country_name):
     url = "http://maps.googleapis.com/maps/api/staticmap?center=%s&sensor=false&zoom=8&size=298x298" % werkzeug.url_quote_plus(
         '%s, %s %s, %s' % (street, city, city_zip, country_name)
     )
     return url
Exemple #27
0
    def query(self, target, query):
        fields = 'affix|class|type|notes|cll|url|components'

        if query == 'help!':
            self.msg(target, '<query http://tiny.cc/query-format > '
                             '[(%s)]' % fields)
            return

        field = 'definition'
        match = re.search(r'\s\((?P<field>%s)\)$' % fields, query)
        if match:
            field = match.group('field')
            query = re.sub(r'\s\(.+?\)$', '', query)

        url = 'http://vlasisku.lojban.org/%s' % url_quote_plus(query)
        results = database.root.query(query)

        entry = results['entry']
        if not entry and len(results['matches']) == 1:
            entry = results['matches'].pop()

        if entry or field == 'components':
            case = lambda x: field == x
            if case('definition'):
                data = entry.textdefinition.encode('utf-8')
            elif case('affix'):
                data = ', '.join('-%s-' % i for i in entry.affixes)
            elif case('class'):
                data = entry.grammarclass
            elif case('type'):
                data = entry.type
            elif case('notes'):
                data = entry.textnotes.encode('utf-8')
            elif case('cll'):
                data = '  '.join(link for (chap, link) in entry.cll)
            elif case('url'):
                data = url
            elif case('components'):
                entry = query
                data = ' '.join(e.word for a in compound2affixes(query)
                                if len(a) != 1
                                for e in database.root.entries.itervalues()
                                if a in e.searchaffixes)

            data = data or '(none)'
            if field == 'definition':
                format = '%s = %s'
                self.msg(target, format % (entry, data))
            else:
                format = '%s (%s) = %s'
                self.msg(target, format % (entry, field, data))

        elif results['matches']:
            format = '%d result%s: %s'
            matches = (results['words']
                      +results['glosses']
                      +results['affix']
                      +results['classes']
                      +results['types']
                      +results['definitions']
                      +results['notes'])
            data = ', '.join(map(str, matches[:10]))
            if len(results['matches']) > 10:
                data += '…'
            self.msg(target, format % (len(results['matches']),
                                       's' if len(results['matches']) != 1
                                           else '',
                                       data))
        else:
            self.msg(target, 'no results. %s' % url)