spam-filter/0000755000175500017550000000000012776011776013051 5ustar debacledebaclespam-filter/README.txt0000644000175500017550000000062312111117057014526 0ustar debacledebacleAbout TracSpamFilter ==================== TracSpamFilter is a plugin for Trac (http://trac.edgewall.com/) that provides an infrastructure for detecting and rejecting spam (or other forms of illegitimate/unwanted content) in submitted content. This plugin requires Trac 1.0 or later. More Information ---------------- See the website at spam-filter/COPYING0000644000175500017550000000261112634632470014075 0ustar debacledebacleCopyright (C) 2006-2015 Edgewall Software All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spam-filter/setup.cfg0000644000175500017550000000131312766707445014674 0ustar debacledebacle#[egg_info] #tag_build = dev [extract_messages] add_comments = TRANSLATOR: msgid_bugs_address = trac@dstoecker.de output_file = tracspamfilter/locale/messages.pot keywords = _ ngettext:1,2 N_ tag_ cleandoc_ Option:4 BoolOption:4 IntOption:4 ChoiceOption:4 ListOption:6 ExtensionOption:5 ConfigSection:2 width = 72 [init_catalog] input_file = tracspamfilter/locale/messages.pot output_dir = tracspamfilter/locale domain = tracspamfilter [compile_catalog] directory = tracspamfilter/locale domain = tracspamfilter [update_catalog] input_file = tracspamfilter/locale/messages.pot output_dir = tracspamfilter/locale domain = tracspamfilter [check_catalog] input_dir = tracspamfilter/locale domain = tracspamfilter spam-filter/setup.py0000755000175500017550000001057412722760003014556 0ustar debacledebacle#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2006-2015 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import sys from setuptools import setup, find_packages PACKAGE = 'TracSpamFilter' VERSION = '1.0.9' extra = {} try: from trac.util.dist import get_l10n_cmdclass cmdclass = get_l10n_cmdclass() if cmdclass: extra['cmdclass'] = cmdclass extractors = [ ('**.py', 'trac.dist:extract_python', None), ('**/templates/**.html', 'genshi', None) ] extra['message_extractors'] = { 'tracspamfilter': extractors, } except ImportError: pass setup( name=PACKAGE, version=VERSION, description='Plugin for spam filtering', author="Edgewall Software", author_email="info@edgewall.com", url='http://trac.edgewall.org/wiki/SpamFilter', download_url='http://trac.edgewall.org/wiki/SpamFilter', license='BSD', classifiers=[ 'Framework :: Trac', 'License :: OSI Approved :: BSD License', ], keywords='trac plugin', packages=find_packages(exclude=['*.tests*']), package_data={'tracspamfilter': [ 'templates/*', 'htdocs/*', 'fonts/*', 'locale/*/LC_MESSAGES/*.mo' ]}, install_requires=['Trac'], extras_require={ 'dns': ['dnspython>=1.3.5'], 'spambayes': ['spambayes'], 'pillow': ['pillow'], 'json': ['simplejson' if sys.version_info < (2, 6) else ''], 'account': ['TracAccountManager >= 0.4'], 'oauth': ['oauth2'], 'httplib2': ['httplib2'] }, entry_points=""" [trac.plugins] spamfilter = tracspamfilter.api spamfilter.filtersystem = tracspamfilter.filtersystem spamfilter.admin = tracspamfilter.admin spamfilter.adminusers = tracspamfilter.adminusers spamfilter.adminreport = tracspamfilter.adminreport spamfilter.adapters = tracspamfilter.adapters spamfilter.report = tracspamfilter.report spamfilter.accountadapter = tracspamfilter.accountadapter[account] spamfilter.registration = tracspamfilter.filters.registration[account] spamfilter.akismet = tracspamfilter.filters.akismet spamfilter.stopforumspam = tracspamfilter.filters.stopforumspam spamfilter.botscout = tracspamfilter.filters.botscout spamfilter.fspamlist = tracspamfilter.filters.fspamlist spamfilter.blogspam = tracspamfilter.filters.blogspam[json] spamfilter.mollom = tracspamfilter.filters.mollom[oauth,httplib2] spamfilter.bayes = tracspamfilter.filters.bayes[spambayes] spamfilter.extlinks = tracspamfilter.filters.extlinks spamfilter.httpbl = tracspamfilter.filters.httpbl[dns] spamfilter.ip_blacklist = tracspamfilter.filters.ip_blacklist[dns] spamfilter.url_blacklist = tracspamfilter.filters.url_blacklist[dns] spamfilter.ip_throttle = tracspamfilter.filters.ip_throttle spamfilter.regex = tracspamfilter.filters.regex spamfilter.trapfield = tracspamfilter.filters.trapfield spamfilter.ip_regex = tracspamfilter.filters.ip_regex spamfilter.session = tracspamfilter.filters.session spamfilter.captcha = tracspamfilter.captcha.api spamfilter.captcha.admin = tracspamfilter.captcha.admin spamfilter.captcha.image = tracspamfilter.captcha.image[pillow] spamfilter.captcha.expression = tracspamfilter.captcha.expression spamfilter.captcha.rand = tracspamfilter.captcha.rand spamfilter.captcha.recaptcha = tracspamfilter.captcha.recaptcha spamfilter.captcha.recaptcha2 = tracspamfilter.captcha.recaptcha2[json] spamfilter.captcha.keycaptcha = tracspamfilter.captcha.keycaptcha spamfilter.captcha.mollom = tracspamfilter.captcha.mollom[oauth,httplib2] """, test_suite='tracspamfilter.tests.suite', zip_safe=False, **extra ) spam-filter/tracspamfilter/0000755000175500017550000000000012731667632016070 5ustar debacledebaclespam-filter/tracspamfilter/accountadapter.py0000644000175500017550000000551712671220455021436 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from acct_mgr.register import IAccountRegistrationInspector from trac.core import Component, implements from trac.util.html import tag from trac.util.translation import cleandoc_ from tracspamfilter.api import _ from tracspamfilter.filters.trapfield import TrapFieldFilterStrategy from tracspamfilter.filtersystem import FilterSystem try: from tracspamfilter.filters.registration import RegistrationFilterStrategy except ImportError: RegistrationFilterStrategy = None class RegistrationFilterAdapter(Component): """Interface of spamfilter to account manager plugin to check new account registrations for spam.""" _domain = 'tracspamfilter' _description = cleandoc_( """Interface of spamfilter to account manager plugin to check new account registrations for spam. It provides an additional 'Details' input field to get more information for calculating the probability of a spam registration attempt. Knowledge gained from inspecting registration attempts is shared with all other spam filter adapters for this system. """) implements(IAccountRegistrationInspector) # IAccountRegistrationInspector methods def render_registration_fields(self, req, data): insert = tag.div(tag.label(_("Details:"), tag.input(type="text", name=TrapFieldFilterStrategy(self.env).name_register, size=60, class_="textwidget"))) if RegistrationFilterStrategy: return RegistrationFilterStrategy(self.env).render_registration_fields(req, data, dict(optional=insert)) else: return dict(optional=insert), data def validate_registration(self, req): # Add the author/reporter name if req.authname and req.authname != 'anonymous': author = req.authname else: author = req.args.get('username', req.authname) email = req.args.get('email') if email: author += " <%s>" % email changes = [] sentinel = req.args.get('sentinel') if sentinel: changes += [(None, sentinel)] name = req.args.get('name') if name: changes += [(None, name)] comment = req.args.get(TrapFieldFilterStrategy(self.env).name_register) if comment: changes += [(None, comment)] FilterSystem(self.env).test(req, author, changes) return [] spam-filter/tracspamfilter/users.py0000644000175500017550000004142112722402375017574 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2013 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from __future__ import with_statement import re try: from acct_mgr.api import AccountManager except ImportError: # not installed AccountManager = None from tracspamfilter.api import _ class UserInfo(object): @staticmethod def gettemporary(env): anons = [] for sid, in env.db_query("SELECT sid FROM session WHERE authenticated = 0"): anons.append(sid) return anons @staticmethod def getuserinfo(env, username): data = [] for time,page,version in env.db_query("SELECT time,name,version FROM wiki WHERE author=%s", (username,)): data.append((time, "/wiki/%s?version=%s" % (page,version), _("Wiki page '%(page)s' version %(version)s modified", page=page, version=version))) for filename,time,page,type in env.db_query("SELECT filename,time,id,type FROM attachment WHERE author=%s", (username,)): data.append((time, "/%s/%s#no1" % (type, page), _("Attachment '%s' added") % filename)) for author,time,field,oldvalue,newvalue,ticket in env.db_query("SELECT author,time,field,oldvalue,newvalue,ticket FROM ticket_change"): if author == username: data.append((time, "/ticket/%s" % ticket, _("Ticket %(id)s field '%(field)s' changed", id=ticket, field=field))) if field == "reporter" or field == "owner": if oldvalue == username: data.append((time, "/ticket/%s" % ticket, _("Removed from ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')", id=ticket, field=field, old=oldvalue, new=newvalue))) if newvalue == username: data.append((time, "/ticket/%s" % ticket, _("Set in ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')", id=ticket, field=field, old=oldvalue, new=newvalue))) elif field == 'cc': authors = [] for val in [oldvalue, newvalue]: authors += UserInfo.splitcc(env, val, ticket) if username in authors: data.append((time, "/ticket/%s" % ticket, _("Ticket %(id)s CC field change ('%(old)s' --> '%(new)s')", id=ticket, old=oldvalue, new=newvalue))) for time, reporter, owner, cc, id in env.db_query("SELECT time,reporter,owner,cc,id FROM ticket"): if reporter == username: data.append((time, "/ticket/%s" % id, _("Reporter of ticket %s") % id)) if owner == username: data.append((time, "/ticket/%s" % id, _("Owner of ticket %s") % id)) if username in UserInfo.splitcc(env, cc, id): data.append((time, "/ticket/%s" % id, _("In CC of ticket %(id)s ('%(cc)s')", id=id, cc=cc))) for rev,time in env.db_query("SELECT rev,time FROM revision WHERE author=%s", (username,)): data.append((time, None, _("Author of revision %s") % rev)) for name, in env.db_query("SELECT name FROM component WHERE owner=%s", (username,)): data.append((None, "/admin/ticket/components", _("Component '%s' owner") % name)) for sid, in env.db_query("SELECT DISTINCT(username) FROM permission WHERE username=%s", (username,)): data.append((None, "/admin/general/perm", _("In permissions list"))) for id, in env.db_query("SELECT id FROM report WHERE author=%s", (username,)): data.append((None, "/report/%s" %id, _("Author of report %d") % id)) # non-standard table try: for time,realm,resource in env.db_query("SELECT time,realm,resource_id FROM votes WHERE username=%s", (username,)): data.append((time, "/%s/%s" % (realm, resource), _("Voted for '%s'") % ("/%s/%s" % (realm, resource)))) except: # old style try: for resource, in env.db_query("SELECT resource FROM votes WHERE username=%s", (username,)): data.append((None, "/%s" % resource, _("Voted for '%s'") % resource)) except: pass return sorted(data, key=lambda x: x[0]) @staticmethod def splitcc(env, cc, ticket): authors = [] if cc: sepchar = re.compile("[ ;]") for ccval in cc.split(", "): if ccval != '': authors.append(ccval) if sepchar.search(ccval): env.log.warn("Strange character in CC value for " "ticket %s: '%s'", ticket, ccval) return authors @staticmethod def getinfo(env, mode='unused', minwiki=0): users = {} emails = {} emailscase = {} # arguments order: # 0: last visit time or initial link # 1: registered # 2: has settings # 3: e-mail # 4: wiki edited (1=as user, 2=as email, 3=both) # 5: wiki edit count # 6: ticket edited (1=as user, 2=as email, 3=both) # 7: ticket edit count # 8: SVN edited (only 1=as user) # 9: SVN edit count #10: component, permissions (1=as user, 2=as email, 3=both) #11: component, ... count #12: name #13: mail is double #14: is in password store mailre = re.compile("(?i)^(?:.*<)?([A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z0-9-]{2,63})(?:>.*)?$") def addemail(user, mail): finalmail = mail.lower() # sanitize Google mail m = finalmail.split("@") if len(m) == 2 and m[1] in ("gmail.com", "googlemail.com"): m = m[0].split("+") # ignore anything behind a plus m = m[0].replace(".", "") # ignore any dots finalmail = m + "@gmail.com" if not mail in emailscase: emailscase[mail] = list((user,)) elif not user in emailscase[mail]: # mail already there, but longer name not, remove mail if mail in emailscase[mail]: if users[mail][5]: users[user][5] += users[sid][5] users[user][4] |= 2 if users[mail][7]: users[user][7] += users[sid][7] users[user][6] |= 2 if users[mail][9]: users[user][9] += users[sid][9] users[user][8] |= 2 if users[mail][11]: users[user][11] += users[sid][11] users[user][10] |= 2 users.pop(mail) emailscase[mail].remove(mail) emails[finalmail].remove(mail) emailscase[mail].append(user) if not finalmail in emails or not len(emails[finalmail]): emails[finalmail] = list((user,)) elif not user in emails[finalmail]: emails[finalmail].append(user) for u in emails[finalmail]: users[u][13] = finalmail def getuser(mail): if mail in emailscase: return emailscase[mail][0] return mail def adduser(user, link, idx): if user is not None and user != "anonymous" and user != '': val = 1 res = mailre.search(user) mail = 0 if res: mail = res.group(1) if mail == user: val = 2 user = getuser(mail) if not user in users: users[user] = [link, 0, 0, mail, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] if mail: addemail(user, mail) users[user][idx] |= val users[user][idx+1] += 1 for sid,last_visit in env.db_query("SELECT sid,last_visit FROM session WHERE authenticated = 1"): users[sid] = [int(last_visit), 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] for sid,name,value in env.db_query("SELECT sid,name,value FROM session_attribute WHERE authenticated = 1"): if not sid in users: users[sid] = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] else: users[sid][2] = 1 if name == "email": users[sid][3] = value addemail(sid, value) elif name == "name": users[sid][12] = value if AccountManager: for sid in AccountManager(env).get_users(): if not sid in users: users[sid] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] else: users[sid][14] += 1 for sid,page,version in env.db_query("SELECT author,name,version FROM wiki"): adduser(sid, "/wiki/%s?version=%s" % (page,version), 4) for sid,page,type in env.db_query("SELECT author,id,type FROM attachment"): adduser(sid, "/%s/%s#no1" % (type, page), 6 if type == 'ticket' else 4) for author,field,oldvalue,newvalue,ticket in env.db_query("SELECT author,field,oldvalue,newvalue,ticket FROM ticket_change"): authors = [author] if field == "reporter" or field == "owner": if oldvalue is not None and oldvalue != '': authors.append(oldvalue) if newvalue is not None and newvalue != '': authors.append(newvalue) elif field == 'cc': for val in [oldvalue, newvalue]: authors += UserInfo.splitcc(env, val, ticket) for sid in authors: adduser(sid, "/ticket/%s" % ticket, 6) for reporter, owner, cc, id in env.db_query("SELECT reporter,owner,cc,id FROM ticket"): authors = [reporter, owner] + UserInfo.splitcc(env, cc, id) for sid in authors: adduser(sid, "/ticket/%s" % id, 6) for sid, in env.db_query("SELECT author FROM revision"): adduser(sid, 0, 8) for sid, in env.db_query("SELECT owner FROM component"): adduser(sid, 0, 10) for sid, in env.db_query("SELECT username FROM permission"): adduser(sid, 0, 10) for sid, in env.db_query("SELECT author FROM report"): adduser(sid, 0, 10) # non-standard table try: for sid, in env.db_query("SELECT username FROM votes"): adduser(sid, 0, 10) except: pass killsids = [] stats = {'numunused': 0, 'numauthorized': 0, 'numtotal': len(users)} for sid in users: if not users[sid][1]: if mode == 'authorized' or mode == 'unused': killsids.append(sid) else: stats['numauthorized'] += 1 wikicount = users[sid][5] if wikicount <= minwiki: wikicount = 0 if wikicount+users[sid][7]+users[sid][9]+users[sid][11]: if mode == 'unused': killsids.append(sid) else: stats['numunused'] += 1 for sid in killsids: del users[sid] if mode == 'overview': users = () return users,stats @staticmethod def deletetemporary(env): env.db_transaction("DELETE FROM session WHERE authenticated = 0") env.db_transaction("DELETE FROM session_attribute WHERE authenticated = 0") @staticmethod def _fixcc(cc, old, new): results = [] for entry in cc.split(", "): if entry == old: entry = new if not entry in results: results.append(entry) return ", ".join(results) @staticmethod def _callupdate(cursor, cmd, arg): cursor.execute(cmd, arg) try: # tested with postgres only return int(cursor.statusmessage[7:]) # strip "UPDATE " text except: return 0 @staticmethod def changeuser(env, old, new, authorized=False): if authorized == "forcecc": forcecc = True authorized = None else: forcecc = False # prevent changing registered users if not authorized: res = env.db_query("SELECT sid,authenticated FROM session WHERE sid = %s", (old,)) if len(res): return -1 elif authorized != 'join': # already existing user res = env.db_query("SELECT sid,authenticated FROM session WHERE sid = %s", (new,)) if len(res): return -4 if old == new or not old or not new: return -2 env.log.warn("Change username '%s' to '%s'", old, new) count = 0 with env.db_transaction as db: cursor = db.cursor() entries = [] cursor.execute("SELECT cc FROM ticket WHERE cc LIKE %s", ("%%"+old+"%%",)) for row in cursor: if not row[0] in entries: entries.append(row[0]) cursor.execute("SELECT oldvalue,newvalue FROM ticket_change WHERE field='cc' AND (oldvalue LIKE %s OR newvalue LIKE %s)", ("%%"+old+"%%","%%"+old+"%%")) for row in cursor: for entry in row: if entry is not None and entry != '' and \ entry not in entries: entries.append(entry) sepchar = re.compile("[ ,;]") for entry in entries: newcc = UserInfo._fixcc(entry, old, new) if newcc != entry: if sepchar.search(new) and not forcecc: return -3; count += UserInfo._callupdate(cursor, "UPDATE ticket SET cc = %s WHERE cc = %s", (newcc, entry)) count += UserInfo._callupdate(cursor, "UPDATE ticket_change SET oldvalue = %s WHERE oldvalue = %s AND field = 'cc'", (newcc, entry)) count += UserInfo._callupdate(cursor, "UPDATE ticket_change SET newvalue = %s WHERE newvalue = %s AND field = 'cc'", (newcc, entry)) if authorized: if authorized == 'join': count += UserInfo._callupdate(cursor, "DELETE FROM session WHERE sid = %s", (old,)) count += UserInfo._callupdate(cursor, "DELETE FROM session_attribute WHERE sid = %s", (old, )) else: count += UserInfo._callupdate(cursor, "UPDATE session SET sid = %s WHERE sid = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE session_attribute SET sid = %s WHERE sid = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE wiki SET author = %s WHERE author = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE ticket_change SET author = %s WHERE author = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE ticket_change SET oldvalue = %s WHERE oldvalue = %s AND (field = 'reporter' OR field = 'owner')", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE ticket_change SET newvalue = %s WHERE newvalue = %s AND (field = 'reporter' OR field = 'owner')", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE ticket SET reporter = %s WHERE reporter = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE ticket SET owner = %s WHERE owner = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE attachment SET author = %s WHERE author = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE report SET author = %s WHERE author = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE component SET owner = %s WHERE owner = %s", (new, old)) count += UserInfo._callupdate(cursor, "UPDATE permission SET username = %s WHERE username = %s", (new, old)) db.commit() # non-standard table try: try: cursor.execute("DELETE from votes WHERE username = %s AND (realm, resource_id) IN (SELECT realm, resource_id FROM votes WHERE username = %s)", (old, new)) except: cursor.execute("DELETE from votes WHERE username = %s AND resource IN (SELECT resource FROM votes WHERE username = %s)", (old, new)) count += UserInfo._callupdate(cursor, "UPDATE votes SET username = %s WHERE username = %s", (new, old)) db.commit() except: pass return count spam-filter/tracspamfilter/model.py0000644000175500017550000004763412722402375017547 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from __future__ import with_statement import binascii import xml.etree.ElementTree as ElementTree from datetime import datetime, timedelta from time import mktime, time from trac.db import Column, Index, Table from trac.util.text import to_unicode from tracspamfilter.api import gettext, _, get_strategy_name class LogEntry(object): table = Table('spamfilter_log', key='id')[ Column('id', auto_increment=True), Column('time', type='int'), Column('path'), Column('author'), Column('authenticated', type='int'), Column('ipnr'), Column('headers'), Column('content'), Column('rejected', type='int'), Column('karma', type='int'), Column('reasons'), Column('request') ] def __init__(self, env, time, path, author, authenticated, ipnr, headers, content, rejected, karma, reasons, request): self.id = None self.env = env self.time = time self.path = path self.author = to_unicode(author) self.authenticated = bool(authenticated) self.ipnr = ipnr self.headers = to_unicode(headers) or '' self.content = content self.rejected = bool(rejected) self.karma = karma self.reasons = reasons self.request = request def __repr__(self): date = datetime.fromtimestamp(self.time).isoformat() return '<%s %s from %s by "%s">' % (self.__class__.__name__, self.id, date, self.author) exists = property(fget=lambda self: self.id is not None, doc='Whether this log entry exists in the database') def _encode_content(cls, content): """Take a `basestring` content and return a plain text encoding.""" return to_unicode(content).encode('utf-8').encode('base64') _encode_content = classmethod(_encode_content) def _decode_content(cls, content): """Revert the encoding done by `_encode_content` and return an unicode string""" try: return to_unicode(content.decode('base64')) except (UnicodeEncodeError, binascii.Error): # cope with legacy content (stored before base64 encoding) return to_unicode(content) _decode_content = classmethod(_decode_content) def get_next(self): """Return the next log entry in reverse chronological order (i.e. the next older entry.)""" for row in self.env.db_query(""" SELECT id,time,path,author,authenticated,ipnr,headers, content,rejected,karma,reasons,request FROM spamfilter_log WHERE id<%s ORDER BY id DESC LIMIT 1 """, (self.id,)): return self.__class__._from_db(self.env, row) def get_previous(self): """Return the previous log entry in reverse chronological order (i.e. the next younger entry.)""" for row in self.env.db_query(""" SELECT id,time,path,author,authenticated,ipnr,headers, content,rejected,karma,reasons,request FROM spamfilter_log WHERE id>%s ORDER BY id LIMIT 1 """, (self.id,)): return self.__class__._from_db(self.env, row) def insert(self): """Insert a new log entry into the database.""" assert not self.exists, 'Cannot insert existing log entry' content = self._encode_content(self.content) with self.env.db_transaction as db: cursor = db.cursor() cursor.execute(""" INSERT INTO spamfilter_log (time,path,author,authenticated,ipnr,headers,content,rejected, karma,reasons,request) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) """, (int(self.time), self.path, self.author, int(bool(self.authenticated)), self.ipnr, self.headers, content, int(bool(self.rejected)), int(self.karma), self._reasons_to_xml(self.reasons), self._request_to_xml(self.request))) self.id = db.get_last_id(cursor, 'spamfilter_log') def update(self, **kwargs): """Update the log entry in the database.""" assert self.exists, 'Cannot update a non-existing log entry' for name, value in kwargs.items(): if hasattr(self, name): setattr(self, name, value) content = self._encode_content(self.content) self.env.db_transaction(""" UPDATE spamfilter_log SET time=%s,path=%s,author=%s, authenticated=%s,ipnr=%s,headers=%s,content=%s, rejected=%s,karma=%s,reasons=%s, request=%s WHERE id=%s """, (int(self.time), self.path, self.author, int(bool(self.authenticated)), self.ipnr, self.headers, content, int(bool(self.rejected)), int(self.karma), self._reasons_to_xml(self.reasons), self._request_to_xml(self.request), self.id)) def delete(cls, env, id): """Delete the log entry with the specified ID from the database.""" if isinstance(id, list): env.db_transaction( "DELETE FROM spamfilter_log WHERE id IN (%s)" % ",".join("'%s'" % each for each in id)) else: env.db_transaction( "DELETE FROM spamfilter_log WHERE id=%s", (id,)) delete = classmethod(delete) def getobvious(cls, env): """Delete the obvious log entries from the database.""" ids = [] for id, reasons in env.db_query(""" SELECT id,reasons FROM spamfilter_log WHERE authenticated=0 AND rejected=1 """): for r in cls._xml_to_reasons(reasons): if r[0] == "Bayesian" and float(r[3]) > 90: ids.append(id) return ids getobvious = classmethod(getobvious) def fetch(cls, env, id): """Retrieve an existing log entry from the database by ID.""" for row in env.db_query(""" SELECT id,time,path,author,authenticated,ipnr,headers, content,rejected,karma,reasons,request FROM spamfilter_log WHERE id=%s """, (int(id),)): return cls._from_db(env, row) fetch = classmethod(fetch) def count(cls, env): """Return the number of log entries in the database.""" for count, in env.db_query( "SELECT COUNT(*) FROM spamfilter_log "): return count count = classmethod(count) def purge(cls, env, days): """Delete log entries older than the specified number of days.""" threshold = datetime.now() - timedelta(days=days) env.db_transaction( "DELETE FROM spamfilter_log WHERE time < %s", (mktime(threshold.timetuple()),)) purge = classmethod(purge) def select(cls, env, ipnr=None, limit=None, offset=0): """Retrieve existing log entries from the database that match the specified criteria. """ params = [] where_clauses = [] if ipnr: where_clauses.append("ipnr=%s") params.append(ipnr) if where_clauses: where = "WHERE %s" % " AND ".join(where_clauses) else: where = "" return cls._doselect(env, where, limit, offset, params) select = classmethod(select) def selectrelated(cls, env, page, limit=None, offset=0): """Retrieve page related log entries""" # strip additional appended values p = page.split("#") params = [p[0]] where_clauses = ["path = %s"] if page.startswith('/ticket/'): if len(p) == 2: # if we have time, use it time = p[1] else: # otherwise hope ticket is not yet deleted time = None for t, in env.db_query("SELECT time/1000000 from ticket WHERE id=%s", (page[8:],)): time = t if time: where_clauses.append("(path = '/newticket' AND time BETWEEN %s AND %s)") params.append(time-30) params.append(time+30) where = "WHERE %s" % " OR ".join(where_clauses) return cls._doselect(env, where, limit, offset, params) selectrelated = classmethod(selectrelated) def _doselect(cls, env, where, limit, offset, params): extra_clauses = [] if limit: extra_clauses.append("LIMIT %s") params.append(limit) if offset: extra_clauses.append("OFFSET %s") params.append(offset) if extra_clauses: extra = " ".join(extra_clauses) else: extra = "" for row in env.db_query(""" SELECT id,time,path,author,authenticated,ipnr,headers, content,rejected,karma,reasons,request FROM spamfilter_log %s ORDER BY time DESC %s """ % (where, extra), params): yield cls._from_db(env, row) _doselect = classmethod(_doselect) def _from_db(cls, env, row): """Create a new LogEntry from a row from the `spamfilter_log` table.""" fields = list(row[1:]) fields[6] = cls._decode_content(fields[6]) fields[9] = cls._xml_to_reasons(fields[9]) fields[10] = cls._xml_to_request(fields[10]) obj = cls(env, *fields) obj.id = row[0] return obj _from_db = classmethod(_from_db) def _reasons_to_xml(self, reasons): root = ElementTree.Element("entries") for r in reasons: e = ElementTree.SubElement(root, "e", name = r[0], points = r[1], text = r[2]) for a in r[3:]: ElementTree.SubElement(e, "v", value=a) return ElementTree.tostring(root) def _request_to_xml(self, request): if request is None: return None try: root = ElementTree.Element("request", target=request[0]) for key, value in request[1].iteritems(): ElementTree.SubElement(root, "arg", key = key).text = value return ElementTree.tostring(root) except: return None def _xml_to_reasons(self, xml): reasons = [] root = ElementTree.fromstring(xml) for r in list(root): reasons.append([r.attrib.get("name"), r.attrib.get("points"), r.attrib.get("text")] + [a.attrib.get("value") for a in list(r)]) return reasons _xml_to_reasons = classmethod(_xml_to_reasons) def _xml_to_request(self, xml): if xml is None: return None try: root = ElementTree.fromstring(xml) request = [root.attrib.get("target"), {}] for r in list(root): request[1][r.attrib.get("key")] = r.text or "" return request except: return None _xml_to_request = classmethod(_xml_to_request) def getreasons(self): reasons = [] for r in self.reasons: if len(r) == 3: reasons.append("%s (%s): %s" % (r[0], r[1], gettext(r[2]))) else: reasons.append("%s (%s): %s" % (r[0], r[1], gettext(r[2]) % tuple(r[3:]))) return reasons def findreasonvalue(self, name): for r in self.reasons: if r[0] == name: try: return int(r[1]) except: return 0 return 0 class Bayes(object): table = Table('spamfilter_bayes', key='word')[ Column('word'), Column('nspam', type='int'), Column('nham', type='int'), Index(['word']) ] class Statistics(object): table = Table('spamfilter_statistics', key=['strategy', 'action', 'data', 'status'])[ Column('strategy'), Column('action'), Column('data'), Column('status'), Column('delay', type='double precision'), Column('delay_max', type='double precision'), Column('delay_min', type='double precision'), Column('count', type='int'), Column('external', type='int'), Column('time', type='int') ] def __init__(self, env): self.env = env def insert_or_update(self, strategy, action, data, status, delay, external): with self.env.db_transaction as db: row = db("SELECT delay,delay_max,delay_min,count FROM " "spamfilter_statistics WHERE action=%s " "AND data=%s AND status=%s AND strategy=%s", (action, data, status, strategy)) if row: count = float(row[0][3]) delay_val = float(row[0][0])*(count/(count+1.0))+delay/(count+1.0) delay_max = float(row[0][1]) if delay > delay_max: delay_max = delay delay_min = float(row[0][2]) if delay < delay_min: delay_min = delay count = int(row[0][3])+1 db("UPDATE spamfilter_statistics SET delay=%s,delay_max=%s," "delay_min=%s,count=%s WHERE action=%s AND data=%s AND " "status=%s AND strategy=%s", (delay_val, delay_max, delay_min, count, action, data, status, strategy)) else: db("INSERT INTO spamfilter_statistics VALUES (%s, %s, %s, %s, " "%s, %s, %s, 1, %s, %s)", (strategy, action, data, status, delay, delay, delay, external, int(time()))) self.env.log.debug("SPAMLOG: %s %s %s %s %.3f %s", action, data, status, strategy, delay, "external" if external else "local") def clean(self, strategy): self.env.db_transaction( "DELETE FROM spamfilter_statistics WHERE strategy=%s", (strategy,)) def cleanall(self): self.env.db_transaction("DELETE FROM spamfilter_statistics") def getstats(self): strategies = {} overall = {} overall['test'] = 0 overall['time'] = 0 overall['testspam'] = 0 overall['testham'] = 0 overall['testint'] = 0 overall['testext'] = 0 overall['testinttime'] = 0 overall['testexttime'] = 0 overall['spamerror'] = 0 overall['hamerror'] = 0 for strategy,action,data,status,delay,delay_max,delay_min,count,external,time in \ self.env.db_query("SELECT * FROM spamfilter_statistics"): if strategy: str = strategies.get(strategy, {}) str['type'] = "external" if external else "internal" str['i18type'] = _("external") if external else _("internal") if action == 'test': if data == 'empty': str['testempty'] = count elif data == 'ham' or data == 'spam': str['test%s%s' % (data,status)] = count lasttotal = str.get('testtotal', 0) str['testtotal'] = count + lasttotal if lasttotal: l = float(lasttotal) c = float(count) s = l+c str['testtime'] = str['testtime']*(l/s)+float(delay)*(c/s) else: str['testtime'] = float(delay) elif action.startswith('train') or action == 'delete': if status: key = 'train%s%s' % (data,status) str[key] = count + str.get(key, 0) key = 'train%stotal' % data str[key] = count + str.get(key, 0) str['traintotal'] = count + str.get('traintotal', 0) if action == 'trainerror' or action == 'traincond': str[action] = count + str.get(action, 0) strategies[strategy] = str else: if not overall['time'] or time < overall['time']: overall['time'] = time if (action == 'trainint' or action == 'trainext') and status == 'error': if data == 'spam': overall['spamerror'] += count else: overall['hamerror'] += count if action == 'testint' or action == 'testext': overall[action] += count overall["%stime" % action] = delay overall['test'] += count if data == 'spam': overall['testspam'] += count else: overall['testham'] += count return strategies,overall def sortbyperformance(self, entries): strategies = {} for strategy,action,data,status,delay,count in self.env.db_query(""" SELECT strategy,action,data,status,delay,count FROM spamfilter_statistics WHERE external=1 AND strategy != '' """): str = strategies.get(strategy, {'count': 0, 'delay': 0, 'ham': False, 'err': 0, 'empty': 0, 'testcount': 0}) if action == 'delete' or action.startswith('train'): if status == 'error': str['err'] += count if status: str['count'] += count elif action == 'test': if delay > str['delay']: str['delay'] = delay if data == 'empty': str['empty'] += count elif data == 'ham': str['ham'] = True str['testcount'] += count strategies[strategy] = str for strategy,str in strategies.iteritems(): # 10% if ham is reported c = 10 if str['ham'] else 0 # 40% if fast s = 40-int(str['delay']*20) c += s if s > 0 else 0 # 25% if low error rate if str['count']: e = 25-int(1000.0*str['err']/str['count']) else: e = 15 c += e if e > 0 else 0 # 25% if high answer rate if str['testcount']: a = 25-int(25.0*str['empty']/str['testcount']) else: a = 15 c += a if a > 0 else 0 strategies[strategy] = c def mysort(val): return strategies.get(get_strategy_name(val), 0) entries = sorted(entries, key=mysort, reverse=True) return entries class SpamReport(object): table = Table('spamfilter_report', key=['id'])[ Column('id', auto_increment=True), Column('entry'), Column('headers'), Column('author'), Column('authenticated', type='int'), Column('comment'), Column('time', type='int') ] schema = [Bayes.table, LogEntry.table, Statistics.table, SpamReport.table] schema_version = 4 spam-filter/tracspamfilter/templates/0000755000175500017550000000000012731667632020066 5ustar debacledebaclespam-filter/tracspamfilter/templates/usertable.html0000644000175500017550000001162212726054172022735 0ustar debacledebacle
  User name Last login Registered Setup E-Mail Wiki edits Ticket edits SVN edits Other  
  ${name} (${data[12]}) Source ${format_datetime(data[0])} - yes no yes(password) no(password) ${data[3] if data[3] else '-'} (double) user (${data[5]}) e-mail (${data[5]}) both (${data[5]}) - user (${data[7]}) e-mail (${data[7]}) both (${data[7]}) - user (${data[9]}) e-mail (${data[9]}) both (${data[9]}) - user (${data[11]}) e-mail (${data[11]}) both (${data[11]}) -  
spam-filter/tracspamfilter/templates/admin_user.html0000644000175500017550000000737012726054172023102 0ustar debacledebacle Spam User Handling

Spam Filtering: User handling (${usertype})

There are ${stats['numtotal']} different entries in the database, ${stats['numauthorized']} users are registered and ${stats['numunused']} have not been used.
Date Action of user '${username}'
${format_datetime(date) if date else "-"} ${action} ${action}

Values must be URL encoded!

spam-filter/tracspamfilter/templates/admin_spamconfig.html0000644000175500017550000001163512731667632024260 0ustar debacledebacle Spam Filter

Spam Filtering: Configuration

See wiki page for a short documentation.
Help translating this plugin at Transifex.
Karma Tuning


Content submissions are passed through a set of registered and enabled filter strategies, each of which check the submitted content and may assign karma points to it. The sum of these karma points needs to be greater than or equal to the minimum karma configured here for the submission to be accepted.

Strategy Karma points Description
${strategy.name}

${strategy.karma_help}

Logging

The spam filter plugin can optionally log every content submission so that you can monitor and tune the effectiveness of the filtering. The log can be viewed on the Monitoring page.

Authenticated

If authenticated users should not be trusted automatically, this option must be disabled. Instead of full trust the supplied karma value is used in this case.

spam-filter/tracspamfilter/templates/admin_reportentry.html0000644000175500017550000000504712726054172024520 0ustar debacledebacle Spam Report

Spam Filtering: Report

Report Entry:

Information

Time: ${pretty_dateinfo(fromtimestamp(time))}
Path: ${abs_href(path)}
Author: ${author}
Authenticated: ${authenticated and dgettext('tracspamfilter', 'yes') or dgettext('tracspamfilter', 'no')}
Comment: ${comment}

HTTP headers

${headers}

Possibly related log entries:

spam-filter/tracspamfilter/templates/admin_external.html0000644000175500017550000002503212726054172023741 0ustar debacledebacle External services

Spam Filtering: External services

An error checking supplied data occured, see below for details.


Akismet

The Akismet filter uses the free Akismet service to decide if content submissions are potential spam. You need to obtain an API key to use the service, which is freely available for personal use.

Key validation failed: ${akismeterror}
BlogSpam

The BlogSpam filter uses the free BlogSpam service to decide if content submissions are potential spam.

StopForumSpam

The StopForumSpam filter uses the StopForumSpam service to decide if content submissions are potential spam. You need to obtain an API key to report SPAM to the service, which is freely available.

BotScout

The BotScout filter uses the BotScout service to decide if content submissions are potential spam. You need to obtain an API key to use the service, which is freely available.

FSpamList

The FSpamList filter uses the FSpamList service to decide if content submissions are potential spam. You need to obtain an API key to use the service, which is freely available.

HTTP:BL

The HTTP_BL filter uses the free HTTP:BL service to decide if content submissions are potential spam. You need to obtain an API key to use the service, which is freely available for personal use.

Mollom

The Mollom filter uses the free Mollom service to decide if content submissions are potential spam. You need to obtain API keys to use the service, which are freely available for personal use.

Key validation failed: ${mollomerror}
Key validation failed: ${unknownsourceerror}
Free access blacklists
A list of DNS blacklists can be found at the RBLCheck or MultiRBL services.

You can enable or disable these filters from the “General → Plugins” panel of the web administration interface.

spam-filter/tracspamfilter/templates/admin_bayes.html0000644000175500017550000001023312726054172023217 0ustar debacledebacle Bayes

Spam Filtering: Bayes

Configuration

The bayesian filter requires training before it can effectively differentiate between spam and ham. The training database currently contains ${nspam} spam and ${nham} ham ${ratio} submissions.

The bayesian database contains currently ${dblines} lines with trained words (${dblinesspamonly}, ${dblineshamonly}, ${dblinesmixed}).

Reducing the training database can help when it got very large and tests take too long.

Any database lines with less entries is removed when reducing the database.

Resetting the training database can help when training was incorrect and is producing bad results.

The minimum number of spam and ham in the training database before the filter starts affecting the karma of submissions.

Training

While you can train the spam filter from the “Spam Filtering → Monitoring” panel in the web administration interface, you can also manually train the filter by entering samples here, or check what kind of spam probability currently gets assigned to the content.


Error: ${error} Score: ${round(score * 100, 2)}%
spam-filter/tracspamfilter/templates/admin_spamentry.html0000644000175500017550000001101712726054172024137 0ustar debacledebacle Spam Monitoring

Spam Filtering: Monitoring

Log Entry:

Information

Time: ${pretty_dateinfo(fromtimestamp(entry.time))}
Path: ${abs_href(entry.path)}
Author: ${entry.author}
Authenticated: ${entry.authenticated and dgettext('tracspamfilter', 'yes') or dgettext('tracspamfilter', 'no')}
IP address: ${entry.ipnr}
Karma: ${entry.karma} (marked as ${spam_or_ham})
  • $reason

Submitted content

${entry.content}

HTTP headers

${entry.headers}
spam-filter/tracspamfilter/templates/admin_report.html0000644000175500017550000000712412726054172023434 0ustar debacledebacle Spam Reports

Spam Filtering: Reports

Viewing entries ${offset} – ${offset + len(entries) - 1} of ${total}.

  Path Author Date/time
${shorten_line(path, 55)} ${auth_img[:-4]} ${shorten_line(author, 55) or 'anonymous'} ${format_datetime(time)}
${comment}
No data available
spam-filter/tracspamfilter/templates/verify_captcha.html0000644000175500017550000000250312726054172023734 0ustar debacledebacle

Captcha Error

${error}

Trac thinks your submission might be Spam. To prove otherwise please provide a response to the following.

Note - the captcha method is choosen randomly. Retry if the captcha does not work on your system!

${challenge}

Response:

${challenge}
spam-filter/tracspamfilter/templates/admin_statistics.html0000644000175500017550000001444012726054172024312 0ustar debacledebacle Spam Statistics

Spam Filtering: Statistics

No submission statistics yet.
${overall['test']} Submissions tested since ${format_datetime(overall['time'])}, ${"%3.1f%%" % (100.0*overall['testspam']/overall['test'])} spam and ${"%3.1f%%" % (100.0*overall['testham']/overall['test'])} ham (${"%3.1f%%" % (100.0*overall['testint']/overall['test'])} of the tests could be solved local). ${overall['spamerror']} spam and ${overall['hamerror']} ham have been retrained.
Strategy Type Test Train / Verify Errors
Spam Ham Spam Ham
Total Mean Delay No Result Match Mismatch Match Mismatch Total Right Wrong Total Right Wrong
${name}${data['i18type']} No tests yet. ${data['testtotal']} ${"%4.2f" % (data['testtime'])} s ${"%3.1f%%" % (100.0*data['testempty']/data['testtotal'])} ${"%3.1f%%" % (100.0*data['testspamok']/data['testtotal'])} ${"%3.1f%%" % (100.0*data['testspamerror']/data['testtotal'])} ${"%3.1f%%" % (100.0*data['testhamok']/data['testtotal'])} ${"%3.1f%%" % (100.0*data['testhamerror']/data['testtotal'])} No spam yet. ${data['trainspamtotal']} ${"%3.1f%%" % (100.0*data['trainspamok']/data['traintotal'])} ${"%3.1f%%" % (100.0*data['trainspamerror']/data['traintotal'])} No ham yet. ${data['trainhamtotal']} ${"%3.1f%%" % (100.0*data['trainhamok']/data['traintotal'])} ${"%3.1f%%" % (100.0*data['trainhamerror']/data['traintotal'])} ${data.get('trainerror')}
spam-filter/tracspamfilter/templates/admin_captcha.html0000644000175500017550000001504212726054172023522 0ustar debacledebacle Captcha handling

Spam Filtering: Captcha handling

Captcha type:
reCAPTCHA

The reCAPTCHA system provides a very good captcha system based on scanned books. See Google reCAPTCHA page. You need to obtain API keys to use the service, which is freely available for personal use.

Public key:
Private key:
Key validation failed: ${recaptcha_error}
KeyCaptcha

The KeyCatcha system provides a captcha system based on JavaScript functions to reassemble a picture. See KeyCaptcha page. You need to obtain an API key to use the service, which is freely available for limited use.

User ID:
Private key:
Key validation failed: ${keycaptcha_error}
Text captcha

The text captcha constructs easy text questions. They can be broken relatively easy.

Maximum value in a term:
Number of terms:
Image captcha

The image captcha constructs obstructed images using Python imaging library.

Number of letters:
Font size:
Alphabet:
Fonts:
spam-filter/tracspamfilter/templates/monitortable.html0000644000175500017550000000673612726054172023460 0ustar debacledebacle
  Path Author IP Address Karma Date/time
${shorten_line(entry.path, 40)}
${entry.path}
${auth_img[:-4]} ${shorten_line(entry.author, 40) or 'anonymous'} ${auth_img[:-4]} ${shorten_line(entry.author, 40) or 'anonymous'} ${entry.ipnr} ${entry.karma} ${format_datetime(entry.time)}
  • ${reason}
${shorten_line(entry.content)}
No data available
spam-filter/tracspamfilter/templates/admin_spammonitor.html0000644000175500017550000000573512726054172024477 0ustar debacledebacle Spam Monitoring

Spam Filtering: Monitoring

Note: Logging by the spam filter is currently disabled.

Viewing entries ${offset} – ${offset + len(entries) - 1} of ${total}.

spam-filter/tracspamfilter/__init__.py0000644000175500017550000000126012640411433020161 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2005-2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import pkg_resources from tracspamfilter.api import * from tracspamfilter.filtersystem import * from tracspamfilter.model import * pkg_resources.require('Trac >= 1.0') spam-filter/tracspamfilter/adapters.py0000644000175500017550000001070412666701205020237 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from trac.attachment import IAttachmentManipulator from trac.config import IntOption from trac.core import Component, implements from trac.mimeview import is_binary from trac.ticket import ITicketManipulator, TicketSystem from trac.util.text import to_unicode from trac.wiki.api import IWikiPageManipulator from tracspamfilter.filtersystem import FilterSystem class TicketFilterAdapter(Component): """Interface to check ticket changes for spam. """ implements(ITicketManipulator) # ITicketManipulator methods def prepare_ticket(self, req, ticket, fields, actions): pass def validate_ticket(self, req, ticket): if 'TICKET_ADMIN' in req.perm: # An administrator is allowed to spam return [] if 'preview' in req.args: # Only a preview, no need to filter the submission yet return [] changes = [] # Add the author/reporter name if req.authname and req.authname != 'anonymous': author = req.authname elif not ticket.exists: author = ticket['reporter'] else: author = req.args.get('author', req.authname) # Add any modified text fields of the ticket fields = [f['name'] for f in TicketSystem(self.env).get_ticket_fields() if f['type'] in ('textarea', 'text')] for field in fields: if field in ticket._old: changes.append((ticket._old[field], ticket[field])) if 'comment' in req.args: changes.append((None, req.args.get('comment'))) FilterSystem(self.env).test(req, author, changes) return [] class WikiFilterAdapter(Component): """Interface to check wiki changes for spam. """ implements(IWikiPageManipulator) # IWikiPageManipulator methods def prepare_wiki_page(self, req, page, fields): pass def validate_wiki_page(self, req, page): if 'WIKI_ADMIN' in req.perm: # An administrator is allowed to spam return [] if 'preview' in req.args: # Only a preview, no need to filter the submission yet return [] old_text = page.old_text text = page.text author = req.args.get('author', req.authname) comment = req.args.get('comment') # Test the actual page changes as well as the comment changes = [(old_text, text)] if comment: changes += [(None, comment)] FilterSystem(self.env).test(req, author, changes) return [] class AttachmentFilterAdapter(Component): """Interface to check attachment uploads for spam. """ implements(IAttachmentManipulator) sample_size = IntOption('spam-filter', 'attachment_sample_size', 16384, """The maximum number of bytes from an attachment to pass through the spam filters.""", doc_domain='tracspamfilter') # ITicketManipulator methods def prepare_attachment(self, req, attachment, fields): pass def validate_attachment(self, req, attachment): if 'WIKI_ADMIN' in req.perm: # An administrator is allowed to spam return [] author = req.args.get('author', req.authname) description = req.args.get('description') filename = None upload = req.args.get('attachment') content = '' if upload is not None: try: data = upload.file.read(self.sample_size) if not is_binary(data): content = to_unicode(data) finally: upload.file.seek(0) filename = upload.filename changes = [] for field in filter(None, [description, filename, content]): changes += [(None, field)] FilterSystem(self.env).test(req, author, changes) return [] spam-filter/tracspamfilter/report.py0000644000175500017550000000735512671220455017756 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2014-2016 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from time import time from trac.config import ListOption from trac.core import Component, TracError, implements from trac.util.html import tag from trac.web.api import IRequestFilter, IRequestHandler from trac.web.chrome import ( add_ctxtnav, add_notice, add_script, add_script_data) from tracspamfilter.api import _, ngettext class SpamReportAdapter(Component): """Interface to allow users to report spam.""" checkreq = ListOption('spam-filter', 'report_pages', 'wiki, attachment, ticket', doc= """List of page types to add spam report link""", doc_domain="tracspamfilter") implements(IRequestFilter, IRequestHandler) def match_request(self, req): return req.path_info == '/reportspam' def process_request(self, req): req.perm.require('SPAM_REPORT') if 'page' not in req.args: raise TracError(_("No page supplied to report as spam")) page = req.args['page'] savepage = page isauth = 1 if req.authname and req.authname != 'anonymous' else 0 headers = '\n'.join(['%s: %s' % (k[5:].replace('_', '-').title(), v) for k, v in req.environ.items() if k.startswith('HTTP_')]) if page.startswith("/ticket/"): for tim, in self.env.db_query("SELECT time/1000000 from ticket WHERE id=%s", (page[8:],)): # append creation time, so spam log entries can be seen after deleting ticket savepage = "%s#%d" % (page, tim) self.env.db_transaction(""" INSERT INTO spamfilter_report (entry, headers, author, authenticated, comment, time) VALUES (%s,%s,%s,%s,%s,%s)""", (savepage, headers, req.authname, isauth, req.args.get('comment', None), int(time()))) req.redirect(req.href(page)) def pre_process_request(self, req, handler): return handler def post_process_request(self, req, template, data, content_type): if 'SPAM_REPORT' in req.perm: i = req.path_info.find("/", 1) if (req.path_info[1:i] if i > 0 else req.path_info[1:]) in self.checkreq: isauth = 1 if req.authname and req.authname != 'anonymous' else 0 if self.env.db_query(""" SELECT id FROM spamfilter_report WHERE entry = %s AND author = %s AND authenticated = %s""", (req.path_info, req.authname, isauth)): add_ctxtnav(req, _('Reported spam')) else: add_script_data(req, {'spamreport_comment': _("Comment")}) add_script(req, 'spamfilter/reportspam.js') add_ctxtnav(req, tag.a(_('Report spam'), id='reportspam', href=req.href('reportspam', (('page', req.path_info),)))) if 'SPAM_CHECKREPORTS' in req.perm: for total, in self.env.db_query("SELECT COUNT(*) FROM spamfilter_report"): if total: add_notice(req, tag.a(ngettext('%(num)d spam report', '%(num)d spam reports', total), href=req.href.admin("spamfilter/report"))) return template, data, content_type spam-filter/tracspamfilter/fonts/0000755000175500017550000000000010524227206017204 5ustar debacledebaclespam-filter/tracspamfilter/fonts/vera.ttf0000644000175500017550000020061410524227206020663 0ustar debacledebacleOS/2_cpVPCLTъ^6cmaplXcvt 9fpgm&`gaspH glyf tA&~hdmx4!Hhead݄T6hheaEoL$hmtx Ǝ0kernRՙ-loca=maxpG:, nameټȵpostZ/prep; h::_:: dM0l   p t  &   Y &  &   c . 5 `  s 0 & {Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.Bitstream Vera SansBitstreamVeraSans-RomanRelease 1.10Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.http://www.bitstream.comCopyright (c) 2003 by Bitstream, Inc. All Rights Reserved.Bitstream Vera SansBitstreamVeraSans-RomanRelease 1.10Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.http://www.bitstream.com5fqu-J3T99NR7s`s3VV9s3D{o{RoHT3fs +b-{T#\q#H99`#fy```{w``b{{Rffw;{J/}oo5jo{-{T7fD)fs@%2%%A:B2SAS//2ݖ}ٻ֊A}G}G͖2ƅ%]%]@@%d%d%A2dA  d   A(]%]@%..%A  %d%@~}}~}}|d{T{%zyxw v utsrqponl!kjBjSih}gBfedcba:`^ ][ZYX YX WW2VUTUBTSSRQJQP ONMNMLKJKJIJI IH GFEDC-CBAK@?>=>=<=<; <@; :987876765 65 43 21 21 0/ 0 / .- .- ,2+*%+d*)*%)('%(A'%&% &% $#"!! d d BBBdB-B}d       -d@--d++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++, %Id@QX Y!-,%Id@QX Y!-,  P y PXY%%# P y PXY%-,KPX EDY!-,%E`D-,KSX%%EDY!!-,ED-ff@ /10!%!!fsr)5 @@ <2991/0K TX @ 878Y P ]%3#3#5qeM@1<20KTKT[X@878Y@0 @ P ` p ]#!#o$++`@1      91/<<<<<<<2220@   ]!! !3!!!!#!#!5!!5!T%Dh$ig8R>hggh`TifaabbNm!(/@U" '&( /)/))/B" ) *!#*- ) " & 0<<<1/299990KSX99Y"K TX0@00878YK TKT[KT[X000@878Y#.'5.546753.'>54&dijfod]SS\dtzq{---@A$*.U# jXV`OnZXhq) #'3@6$%&%&'$'B .$ &($4'!%   ! + 1 49912<0KSXY"K TK T[K T[KT[KT[K T[X4@44878Y"32654&'2#"&546"32654&%3#2#"&546WccWUccUVcbWWcd1Zܻۻa ۻۼ 0@      !         B  (('+'$ .  .'.'!!199999991/9990KSX99999999Y"2]@ " ) **&:4D ^YZ UZZY0g{ "-  ' (   2'') #**(/2; 49?2J LKFO2VZ Y UY\_2j i`2uy z 2229]]3267 >73#'#"5467.54632.#"[UԠ_I{;B h]hΆ02޸SUWDi;#QX?@Yr~YW׀c?}<$$/1oX3goB@ 10KTKT[X@878Y@ @P`p]#o+{ O@  29910KTX@878YKTX@878Y#&547{>;o @ <99103#654<:=JN@,       <2<2991<22990%#'%%73%g:r:g:PrPbybcy #@   <<1/<<0!!#!5!-Ө-Ӫ--@ 1073#ӤR@d10!!d1/073#B-@B/9910KSXY"3#m #@  10"32'2#"  P3343ssyzZ K@B  1/20KSXY"KTX  @878Y]7!5%3!!JeJsHHժJ@'B   91/20KSX9Y"KTKT[KT[X@878Y@2UVVzzvtvust]]%!!567>54&#"5>32Ls3aM_xzXE[w:mIwBC12\ps({@.    #)&  )99190KTKT[X)@))878Y@ daa d!]!"&'532654&+532654&#"5>32?^jTmǹSrsY %Đ%%12wps{$& Ѳ|d @   B    <291/<290KSXY"K TK T[X@878Y@* *HYiw+&+6NO O Vfuz ]] !33##!55^%3`du@#    190KTKT[X@878YKTX@878Y!!>32!"&'532654&#",X,$^hZkʭQTժ 10$& $X@$  "% " !%190@]]"32654&.#">32# !2 LL;kPL;y$&W]ybhc@B991/0KSXY"KTX@878Y@X9Hg]]!#!3V+ #/C@% '-'0 $*$ !0991990"32654&%&&54632#"$54632654&#"HŚV г "Əُattt$X@# %!"" %190@]]7532#"543 !"&2654&#"LK:lL>$& V\s[#@<21/073#3### %@  <2103#3#ӤR#٬@^M@*B$#29190KSXY" 5Ѧ`@ #<210!!!!^O@+B$#<9190KSXY"55//m$p@+$     &%99991/9990K TX%@%%878Yy z z ]%3##546?>54&#"5>32ſ8ZZ93lOa^gHZX/'eVY5^1YnFC98ŸLVV/5<4q L@2  L4307$7CM34( (+(I+*(I,=M<9912990K TK T[KT[KT[KT[XMMM@878Y@ NN/N?N]32654&#"#"&5463253>54&'&$#"3267#"$'&5476$32|{zy!orqp ˘s'6@   0210].# !267# !2'ffjzSb_^^_HHghG.@   2 99991/0`]3 !%! )5BhPa/w.,~ .@   21/0 ]!!!!!!9>ժF# )@ 21/0 ]!!!!#ZpPժH7s9@ 43 1990%!5!# !2&&# !26uu^opkSUmnHF_`%; ,@ 8  221/<20P ]3!3#!#"d+991/0KTX@878Y@ 0@P`]3#+f M@  9 991990KTX  @878Y@ 0 @ P ` ]3+53265M?nj @(B  291/<290KSXY"]@ ((764GFCUgvw    (+*66650 A@E@@@ b`hgwp  ,]q]q3! !#3wH1j%@ :1/0@ 0P]3!!_ժ @4  B    >  91/<290KSXY"p]@V   && & 45 i|{y   #,'( 4<VY ej vy ]]! !###-}-+3 y@B6 991/<2990KSXY" ]@068HGif FIWXeiy ]]!3!#j+s #@  310"32' ! ':xyLHH[[bb:@   ? 291/0@ ?_]32654&#%!2+#8/ϒs R@*  B     39991990KSX9Y""32#'# ! '? !#y;:xLHHab[T@5  B    ?  299991/<9990KSX9Y"@]@Bz%%%&'&&& 66FFhuuw]]#.+#! 32654&#A{>ٿJx~hb؍O'~@<    B %( "-"(9999190KSX99Y")])/)O)].#"!"&'532654&/.54$32Hs_wzj{r{i76vce+ٶ0/EF~n|-&J@@@1/20K TX@878Y@  @ p ]!!#!ժ+)K@   8A1299990KTX@878Y]332653! ˮ®u\*$h@'B91/290KSXY"P]@b*GGZ} *&&))% 833<<7HEEIIGYVfiizvvyyu)]]!3 3J+D {@I      B     91/<2290KSXY"]@  ($ >>4 0 LMB @ Yjkg ` {|      !   # $ %  <:5306 9 ? 0FFJ@E@BBB@@ D M @@XVY Pfgab```d d d wv{xwtyywpx   []]3 3 3# #D:9:9+=; ]@F      B    91/<290KSXY"K TK T[KT[X  @878Y@ '' 486 KX[fkww       &()&(' ) 54<;:;4 4 8 ? H O X _ eejjhiil l xyyx}  x   @]]3 3 # #su \Y+3{@(B@@ 91/290KSXY" ]@<5000F@@@QQQe &)78@ ghxp ]]3 3#f9\ @BB 991/0KSXY"K TK T[X @ 878Y@@ )&8HGH    / 59? GJO UYfio wx ]]!!!5!sP=g՚oXS@C210K TX@878YKTKT[X@878Y!#3!XB-@B/9910KSXY"#mo<@C<10KTKT[X@878Y!53#5oXޏ@ 91290##HHu-10!5f1@ D10K TKT[X@878Y #ofv{-{ %@'   #   E&22991/9990@n0000 0!0"?'@@@@ @!@"PPPP P!P"P'p' !"'''000 0!@@@ @!PPP P!``` `!ppp p! !]]"326=7#5#"&5463!54&#"5>32߬o?`TeZ3f{bsٴ)Lfa..'' 8@  G F221/0`]4&#"326>32#"&'#3姒:{{:/Rdaadq{?@  HE210@ ].#"3267#"!2NPƳPNM]-U5++++$$>:#qZ8@G E221/0`]3#5#"3232654&#":||ǧ^daDDaq{p@$   KE9190@)?p?????,// , ooooo ]q]!3267# 32.#" ͷjbck)^Z44*,8 Cė/p@     L<<991/22990K TX@878YKTX@878Y@P]#"!!##535463cM/ѹPhc/яNqVZ{ (J@#  &#' & G E)221/990`***]4&#"326!"&'5326=#"3253aQQR9||9=,*[cb::bcd4@  N  F21/<90`]#4&#"#3>32d||Bu\edy+@F<21/0@  @ P ` p ]3#3#`Vy D@   O  F<2991990@ @P`p]3+532653#F1iL`a( @)B F 291/<90KSXY" ]@_ ')+Vfgsw    ('(++@ h` ]q]33 ##%kǹi#y"F1/0@ @P`p]3#{"Z@&   PPF#291/<<<290@0$P$p$$$$$$$ ]>32#4&#"#4&#"#3>32)Erurw?yz|v\`gb|d{6@  N  F21/<90`]#4&#"#3>32d||Bu\`edqu{ J@  QE10@#?{{   {  {]"32654&'2#"s98V{>@ GF2210@ `]%#3>32#"&4&#"326s:{{8 daaqVZ{ >@   GE2210@ `]32654&#"#"3253#/s:||:/daDDadJ{0@    F21/90P].#"#3>32JI,:.˾`fco{'@<  S  SB %( R"E(9999190KSX99Y"']@m   . , , , ; ; ; ; $( ( *//*(() )!$'      '/)?)_))))))]]q.#"#"&'532654&/.54632NZb?ĥZlfae@f?((TT@I!*##55YQKP%$78@  F<<2991/<2990]!!;#"&5#53w{KsբN`>X`6@    NF21/290`]332653#5#"&||Cua{fc=`@'B91/290KSXY"K TX@878YKTKT[X@878Y@Hj{  &&)) 55::0FFIIFH@VVYYPffiigh`ut{{uz>]]3 3#=^^\`TV5` @IU U U U   B     91/<2290KSXY"K TKT[KT[KT[K T[X  @878YK TK T[KT[X @ 878Y@" 5 IIF @ [[U P nnf yy          %%#'!%""%' $ ! # 9669 0FHF@B@@@D D D @@VVVPQRRPS T U cdejejjjn a g ouuy}x}zzxy  { v } @/   y]]333# #V`jjj;y` Z@F      B   91/<290KSXY"K TKT[KT[KT[X  @878YKTX @ 878Y@   & =1 UWX f vzvt        )&% * :9746 9 0 IFE J @ YVYYWVYVV Y P o x  /]] # # 3 dkr))`HJq=V`@C        B     9129990KSX2Y"K TKT[X@878YKTX@878Y@     # 5 I O N Z Z j        '$$  )( % $ $ ' ** 755008 6 6 8 990A@@@@@@@@B E G II@TQQUPPVUVW W U U YYPffh ii`{xx   e]]+5326?3 3N|lLT3!;^^hzHTNlX` @B 2991/0KSXY"K TK T[X @ 878YKTX  @878Y@B&GI  + 690 @@E@@CWY_ ``f``b ]]!!!5!qjL}e`ۓ%$@4 %   !  % $  C %<<29999999199999990K TX%%%@878Y&]#"&=4&+5326=46;#"3>l==k>DV[noZVtsݓXX10#$@6%   #%#C %<2<9999999199999990K TX%@%%878YKTX%%%@878Y&]326=467.=4&+532;#"+FUZooZUF?l>>l?VWstݔ1#@  1990#"'&'&'&#"56632326ian ^Xbian ^V1OD;>MSOE<>LhN'$uhm !@T   !!  ! !!!B     !  VV!"2299999991/<9990KSXY" #]@  s P#f iu {yyv v!# ]]4&#"326!.54632#!#TY?@WX??Y!X=>sr?<҈_Z?YWA?XXN)sIsrFv)su''&-k'(u3^'1usN'2'u)N'8u{-f'DR{-f'DCR{-f'DR{-'DR{-7'DR{-'DRqu{'Fqf'Hqf'HCqf'Hq'Hof'f'C\f'F'd7'Qquf'Rsquf'RCsquf'Rsqu'Rsqu7'RsXf'X{Xf'XC{Xf'X{X'X{9; '@  YW Y <<1<203!!#!5!oo\]u=  @  Z[Z10"32654&'2#"&546PnnPPnoO@v+..ooPOmmOOp1.-rB#!Q@+     "  "<<<221<9990%&&'667#&73JDFHAMf fIX⸹)**'# 32!b`@!    <<1/2<2990K TX@878Y66].#"!!!!53#535632NL=ty-=))׏/я\= >@54&.#"#"&'532654/.5467.54632{?>?>S8alӃ\]>9̭IXW:fqր][;;ȦI.Z.L-[.K''PGZsweZ54m@''TLf{xf[1,pE3!   \ 104632#"&3~|}}||};9 %@]] 91290!###&&54$yfNݸ/@0-'!  **.  !' $'$-F099991/990@@'(     ! "&  : :!MM I!I"jj  ]]4632#"&'532654&/.5467.#"#:A9`@IPAtx;e\`Wqqs`/Q*%jd_[?T>7;[gp/8L`@6EBC?2H09JC 9 $HE301BKL?gwyVpMI`3D/IC@&=>:A$104G$ 7aD=0^* D^ J21/02#"$'&5476$"32676654&'&&&&#"3267#"&54632mmllmmmmllmm^^``^^⃄^]]^\^BB@zBCFInmmmmnnmmmmng^^^傁^^__^]⃅]^^! "'F >@!    b b cbc91<<2<<903#######5Jq7rqr/B^^sRf1@ D10K TKT[X@878Y3#fF)@dd1<20K TK T[X@878YK TK T[KT[KT[X@878YKTKT[X@878Y@````pppp]3#%3#^y'>@"     <291<2<<990!!!!!'7!5!7!}/H{};fըfӪH@9  B     <291/<0KSXY"]@gww  ]!!!!!!#!59=qժF՞f +@< +,  )&  *&& &,+,* # )#3,99999999199999990@*WZWU!je!{vu! FYVjddj(|svz( ]] 324&'.#"&5!27!"&''3>_'y=_''NOy;WfNPƀ[gX@CHp@CpDfbMKYg[KKX /@- !$'!!0 $*0999919990@     $$$   $$ $ ***///***55500055 5 :::???:::EEE@@@EE E JJJOOOJJJV !"&'()]]32654&#".#"326#"&54632>32#"&1TevYR1UfvYRF^_HDa^/XZie7XXjeߦ~᧯w .@     <2<21/<<0!!#!5!!!-Ө-}} T@.B $# <2291/90KSXY" 5!!@po V@/B$ # <<291/90KSXY"55!5AǪR@F  B     fe f e<2299991/2<2<290KSXY"K TX@878Y@(' ' ')((79  ]]!#!5!5'!5!3 3!!!c`Tþ{yT9{3{JD{3V` M@%  !   NF!2912<990"`""]3326533267#"&'#"&'#% )I#ER2bf*V H<9 NPOONNh-)b@'! '!* $$*9991990K TK T[KT[KT[KT[X*@**878Y>54&#"#"&54632#"&54324&#"32IH7$$0e՘ݢe WOmVPmmWKt,>bFأ[t}t{w; ]@    91990@0QVPZ spvupz  Z pp{ t  ]]!! !!5 7AJI3!wq@gg120!#!# }/#@1 " $ #" #h#$9999991/<229990K TX$$$@878Y@V             ##(]]#3267#"&5467!##"#>3!i/7.%7vy"Pµ)6< yJ\:1fd.xo@E}/%&@  & iji&1026732#"&'&&#"#"&546327j Pd@7*8  kOeD=!0 l9TA6?&#Hn!bSA8?Ss;)_@3(%%  * "(kl"k *22999199990!!#5#"&5463354&#"56632"32655P,]uu>DIE~bRhP{@p?Dq[[""CO@Mr`d.@  klk 9910!!2#"&546"32654&PXγгi~hi}|P{ݿܾsN@@"   mm  9991/<20%!5654#"!5!&5! Dz?1/aL"a*>w؍{o{3>@C'-%= 4%:.-*1 %?47&%7& =&-7"E?<9999912<<29990@0+0,0-0.0/00@+@,@-@.@/@0P+P,P-P.P/P0+0@@@@@@@@@??? ??0,0-0.0/@,@-@.@/P,P-P.P/ooo oo`,`-`.`/p,p-p.p/,-./]q].#">32!3267#"&'#"&5463!54&#"5>32"326=DJԄ ̷hddjMI؏`TeZ߬o0Z^Z55*,ywxx..''`f{bsٴ)H +@<+,&  )&  *&& &,+,* # #Q)E,22999999199999990@p(?-YVUV jf!{    { z{ {!"#$%{&%--&YVUZ(ifej(ztvz($$]] 32654&'.#".5327#"&'')gA\*g>}66]C_56`?`!*(Ou))Hn.Mw834OMx43N $@/  !# #%" " "!& %999919990KTKT[KT[X%%%@878Y@ ttttv]33267#"&546?>7>5#537ZZ:3mN`^gIYX0&DeWX5^1YnFC98ŸLVV/5<65 b@ <2991/0K TX @ 878YKTKT[KT[X  @878Y P ]#53#3+e^@ 10!#!^=} *@    91903##'%\sB}}`s-Pb;V#@@   B   !$  $912299990KSX29Y"K TX$$$@878Y.#"!!#"&'53267#5!>32&P,`r<::d/4a/am"?$Ɨ5dzɏ!!J;?@.9*-" *19" <-<<219999990#"'&'&'&#"56632326#"'&'&'&#"56632326ian ^Xbian ^Vgian ^Xbian ^VoNE;=LTNE;=KڲOE;=LSNE;=K`8@91/90@cmpxyvn]] !3!^DC?%# @I    B   o o n<2991<2990KSXY"55%-+#-+#RRH# @I  B   o op<<991<2990KSXY"5%5+-+-#^R^  ^R^   #@   1/<<220%3#%3#%3#hk'$uh^'$us^'2'us ;@   299991/220!!!!! !# !39OAg@AժF|pm|q{'3@1 . ("%4"1 K1 Q+E499912<2290@%?5_5p55555????? ooooo ]q].#"!3267#"&'#"32>32%"32654& H ̷jbdjQGьBN5Z44*,nmnm98olkp݇y/10!!yy/10!!ym '@   1<20#53#53ӤRӤR??m '@   1<203#%3#ӤRӤRլ@@@ 10#53ӤR?@ q103#ӤR՘?o )@ r <<103#3#!!oA#u"@91990  9%-=V'\^N'<su+@B10KSXY"3#-\^R#/@I -'! - -'!0 *$0* $ $(st*(s099999999919999999907'#"&''7&&5467'766324&#"326{r%$&(r;t=:x=q%%&&s7t@?s9q(&%%s>v:@t8s'%$|pprs#G@%Bon29190KSXY"5s-+#R#I@&Bop<9190KSXY"5+-#^R^  /J@(   L<2<2991/<22990K TX@878YKTX@878Y@0P]]#!##53546;#"3#JcM`/яNPhc/J@!    L<<991/<22990K TX@878YKTX@878Y@0P ]!#!"!!##53546JcM/ѹ{Phc/яN9;>@   Y W Y <<2<<2122220%!#!5!!5!3!!!oooo\\HF103#F@ 10%3#ӤR@m '@    1<20%3#%3#ӤRfӤR@@q L #'3?K@D$%&%&'$'B@ .(F4 :&$L%IC'1+C =  1 =I 7+ ! L9912<<2220KSXY"KTK T[K T[K T[K T[KT[XL@LL878Y"32654&'2#"&5462#"&546!3#"32654&2#"&546"32654&WddWUccUt%ZVcbWWcdWccWUccܻۻۻۼܻۻhm'$um'(uhk'$uN'(uk'(uk',/u`m',/uXN',/u;k',/usk'2'usm'2'usk'2'u)k'8u)m'8u)k'8uy` F1/0@ @P`p]3#`?f7@ u91290K TKT[X@878Y3#'#fJ7c@$   VwVv99991<<99990K TK T[X@878Y'.#"#>3232673#"&9! &$}f[&@%9! &$}f[&@Z7IR!7IRb+/10K TKT[X@878Y!!V)9H W@ VV1<0K TX@878YKTKT[KT[X@878Y332673#"&v aWV` v HKKJLDf,@ d10K TX@878Y3# _@ V xV10K TK T[X@878YK TK T[K T[X@878Y4&#"3267#"&54632X@AWWA@Xzssss?XW@AWX@sss#u@  ' 1/90!#"&'532654&'T76xv.W+"J/;<+->i0Y[ 0.W=fB@991<20K TKT[X@878Y3#3#߉fxLu @   '1/90!33267#"&546w-+76 >&Dzs5=X.. W]0i?f7@ u91<90K TKT[X@878Y373xu ?@   : y<<991/900P]3%!!'79Pw^Mo;jnH ^@  z z <<991/90KTX @ 878Y@ @ P ` sz p ]37#'7Ǹ}Lɸ{JZjXjm'6uof'V\m'=uXf']@ <210##    g@    2  y<291/220@(   ]]! )#53!!3 !iP`P5~.,qu('@^%{&%#${##{#({'(#&'('%$%(('"#" ! B('&%"! ## #)&' ! (%#" QE)999999919990KSXY"?*]@v%+("/#/$)%-&-'*(6%F%X X!` `!f"u u!u"%#%$&&&''(6$6%F$E%Z Z!b b!z{     {zzv v!x"**']].#"32654&#"5432''%'3%F2X)6 ~r4*!M!ü޼z&77kc\̑oabk'<su=Vf'\^ =@   ? 2291/0@ ?_]332+#32654&#'ђV>@ GF2210@ `]%#3>32#"&4&#"326s:{{8daa-10!!ת? @M    B   <291<290KSXY" '77w55v8vL57y5yy5 ,@   |]|| 12035733! c)t'+n^J@$}}B ~9190KSX2Y"!!56754&#"56632 "?XhU4zHM98rn81^BQ##{l0b(H@'    #)~&~ )999190#"&'532654&##532654&#"56632 \e9}F4wCmxolV^^ad_(fQI7Z`mR|yOFJLl?<:=svcE`''5 d?''5db''5 dsm'* uqVZH'JP', /uu'6ou{'Vs'k'&-uqf'Fs'm'&-uqf'Fq$J@$ "    GE%<<1/<20`&&&]!5!533##5#"3232654&#"F:||ǧN}}daDDad10!!dHF103#F1@: "+ /) 2+"!)#&  , & &*!/<29999999999122<20K TK T[K T[KT[KT[KT[X222@878Y@z  1Ti lnooooiko o!o"o#n$l%i'i-  !"#$%&'()*+,-2   USjg ]].#"!!!!3267#"#734&5465#7332[f A78 ʝf[Y`(77(6bbiZȻ{.# .{ZiHH"{/ #/{"G)@ dd1<20KTKT[X@878YKTK T[KT[X@878YKTKT[X@878YKTX@878Y@````pppp]3#%3#^ys@B10KSXY"K TX@878YKTX@878Y@ %%6FVjg //]]3#7Ju@!  VV 99991<2990K TX@878YKTX@878Y ]'.#"#4632326=3#"&9 $(}gV$=09" (}gT";9! 2-ev 3)dw @B10KSXY"K TX@878YKTX@878Y@*$$5CUU//]]#ę1w@ 91<90K TX@878YKTX@878YKTX@878Y@ //- ]3#'#Ӌ1@ 91290K TK T[K T[K T[X@878YKTX@878YKTX@878Y@ "  ]373Ӌ ? @   ] <291<290KTKT[KT[KT[K T[K T[X@878YKTKT[X@878Y@T /9IFYi       "5GK S[ e]] !33##5!55bf]my9 j@ VV120K TX@878YKTX@878YKTKT[X@878Y332673#"&v cSRav 6978w{zf103#  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~>: ~1BSax~ & 0 : !""""+"H"e%  0AR^x}  0 9 !""""+"H"`%^ChVjq_8 (Bbcdefghjikmlnoqprsutvwxzy{}|~f55q=3=dd?y}s)3s\\?uLsLsyD{={\{fqqq/q999qqJ+o#7=V;=3XyysLs{{{{{{fqqqqq9999qqqqq9\3 'sLfR#hd+/s`N{H?55=ZyyLss/q%%=V^33 / /9% qyy\\\\;LsLsLs9#LF+o{\3X3 q=55^5bb3sq\+osfqsfqqds 5?+   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~     sfthyphenperiodcenteredEuroc6459c6460c6461c6462c6463c6466c6467c6468c6469""""XO!nE~Le  R s  X : i  = z /Eu)pP@"m#{CwRw [ r !5!B!!!" ""#"0"="J"W"d"q"~"""""""""## ##'#4#A#N#[#h##$4$%3%S%&&'K''((X()_*%*\**+z+,D,,-P-..R./0A011!1P12H2z23F3p3p3}3334z44445595g55556[667C77888J999)969C9P9]9j9w99999999::{::;;^;;;<"<_<<<<<<=c>;>H>U>>>?a??@:@K@\@m@z@@@@@@@@A@AVAkBEBBC_CCDUDE*E?- x$%&')*K+-r./2934K57D9:;< =IQR&UYZ\bdg9xy&z&{&|&}&9 999 K$$$$$9$&$*$2$4$6$7a$8$9}$:$;$ # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Matthew Good from dns.ipv6 import inet_aton as ipv6_inet_aton from dns.name import from_text from dns.resolver import NXDOMAIN, NoAnswer, NoNameservers, Timeout, query from trac.config import ListOption, IntOption from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ class IPBlacklistFilterStrategy(Component): """Spam filter based on IP blacklistings. Requires the dnspython module from http://www.dnspython.org/. """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'ip_blacklist_karma', '5', """By how many points blacklisting by a single server impacts the overall karma of a submission.""", doc_domain='tracspamfilter') servers_default = 'all.s5h.net, rbl.rbldns.ru, dnsbl.dronebl.org, ' \ 'rbl.blockedservers.com' servers = ListOption('spam-filter', 'ip_blacklist_servers', servers_default, doc="Servers used for IPv4 blacklisting.", doc_domain='tracspamfilter') servers6_default = 'all.s5h.net, dnsbl.dronebl.org, ' \ 'bl.ipv6.spameatingmonkey.net' servers6 = ListOption('spam-filter', 'ip6_blacklist_servers', servers6_default, doc="Servers used for IPv6 blacklisting.", doc_domain='tracspamfilter') # IFilterStrategy implementation def is_external(self): return True def test(self, req, author, content, ip): if self.karma_points == 0: return serverlist, prefix = self._getdata(ip) if not serverlist: self.log.warning("No IP blacklist servers configured") return self.log.debug('Checking for IP blacklisting on "%s"', ip) points = 0 servers = [] for server in serverlist: self.log.debug("Checking blacklist %s for %s [%s]", server, ip, prefix) try: res = query(from_text(prefix + server.encode('utf-8')))[0].to_text() points -= abs(self.karma_points) if res == '127.0.0.1': servers.append(server) else: # strip the common part of responses if res.startswith('127.0.0.'): res = res[8:] elif res.startswith('127.'): res = res[4:] servers.append('%s [%s]' % (server, res)) except NXDOMAIN: # not blacklisted on this server continue except (Timeout, NoAnswer, NoNameservers), e: self.log.warning('Error checking IP blacklist server "%s" ' 'for IP "%s": %s', server, ip, e) if points != 0: return (points, N_("IP %s blacklisted by %s"), ip, ', '.join(servers)) def train(self, req, author, content, ip, spam=True): return 0 # Internal methods def _getdata(self, ip): if ip.find(".") < 0: encoded = reversed(list(ipv6_inet_aton(ip).encode('hex_codec'))) return self.servers6, '.'.join(encoded) + '.' return self.servers, '.'.join(reversed(ip.split('.'))) + '.' spam-filter/tracspamfilter/filters/regex.py0000644000175500017550000001247012724515466021227 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2005-2006 Edgewall Software # Copyright (C) 2005 Matthew Good # Copyright (C) 2006 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Matthew Good import re from trac.config import BoolOption, IntOption, Option from trac.core import Component, TracError, implements from trac.util.html import tag from trac.wiki.api import IWikiChangeListener, IWikiPageManipulator from trac.wiki.model import WikiPage from tracspamfilter.api import IFilterStrategy, N_, tag_ class RegexFilterStrategy(Component): """Spam filter based on regular expressions defined in BadContent page. """ implements(IFilterStrategy, IWikiChangeListener, IWikiPageManipulator) karma_points = IntOption('spam-filter', 'regex_karma', '5', """By how many points a match with a pattern on the BadContent page impacts the overall karma of a submission.""", doc_domain='tracspamfilter') badcontent_file = Option('spam-filter', 'badcontent_file', '', """Local file to be loaded to get BadContent. Can be used in addition to BadContent wiki page.""", doc_domain='tracspamfilter') show_blacklisted = BoolOption('spam-filter', 'show_blacklisted', 'true', "Show the matched bad content patterns in rejection message.", doc_domain='tracspamfilter') def __init__(self): self.patterns = [] page = WikiPage(self.env, 'BadContent') if page.exists: try: self._load_patterns(page) except TracError: pass if self.badcontent_file != '': with open(self.badcontent_file, 'r') as file: if file is None: self.log.warning("BadContent file cannot be opened") else: lines = file.read().splitlines() pat = [re.compile(p.strip()) for p in lines if p.strip()] self.log.debug("Loaded %s patterns from BadContent file", len(pat)) self.patterns += pat # IFilterStrategy implementation def is_external(self): return False def test(self, req, author, content, ip): gotcha = [] points = 0 if author is not None and author != 'anonymous': testcontent = author + '\n' + content else: testcontent = content for pattern in self.patterns: match = pattern.search(testcontent) if match: gotcha.append("'%s'" % pattern.pattern) self.log.debug('Pattern %s found in submission', pattern.pattern) points -= abs(self.karma_points) if points != 0: if self.show_blacklisted: matches = ", ".join(gotcha) return points, N_("Content contained these blacklisted " "patterns: %s"), matches else: return points, N_("Content contained %s blacklisted " "patterns"), str(len(gotcha)) def train(self, req, author, content, ip, spam=True): return 0 # IWikiPageManipulator implementation def prepare_wiki_page(self, req, page, fields): pass def validate_wiki_page(self, req, page): if page.name == 'BadContent': try: self._load_patterns(page) except TracError, e: return [(None, e)] return [] # IWikiChangeListener implementation def wiki_page_changed(self, page, *args): if page.name == 'BadContent': self._load_patterns(page) wiki_page_added = wiki_page_changed wiki_page_version_deleted = wiki_page_changed def wiki_page_deleted(self, page): if page.name == 'BadContent': self.patterns = [] # Internal methods def _load_patterns(self, page): if '{{{' in page.text and '}}}' in page.text: lines = page.text.split('{{{', 1)[1].split('}}}', 1)[0] lines = [l.strip() for l in lines.splitlines() if l.strip()] for p in lines: try: self.patterns.append(re.compile(p)) except re.error, e: self.log.debug("Error in pattern %s: %s", p, e) raise TracError(tag_("Error in pattern %(pattern)s: " "%(error)s.", pattern=tag.tt(p), error=tag.i(e))) self.log.debug("Loaded %s patterns from BadContent", len(self.patterns)) else: self.log.warning("BadContent page does not contain any patterns") self.patterns = [] spam-filter/tracspamfilter/filters/blogspam.py0000644000175500017550000001370112724350346021711 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Edgewall Software # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Dirk Stöcker from email.Utils import parseaddr from pkg_resources import get_distribution try: import json except ImportError: import simplejson as json from trac import __version__ as TRAC_VERSION from trac.config import IntOption, Option, ListOption from trac.core import Component, implements from trac.mimeview.api import is_binary from tracspamfilter.api import IFilterStrategy, N_ from tracspamfilter.timeoutserverproxy import TimeoutHTTPConnection from tracspamfilter.filters.trapfield import TrapFieldFilterStrategy class BlogSpamFilterStrategy(Component): """Spam filter using the BlogSpam service (http://blogspam.net/). """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'blogspam_karma', '5', """By how many points an BlogSpam reject impacts the overall karma of a submission.""", doc_domain='tracspamfilter') api_url = Option('spam-filter', 'blogspam_json_api_url', 'test.blogspam.net:9999', "URL of the BlogSpam service.", doc_domain='tracspamfilter') skip_tests = ListOption('spam-filter', 'blogspam_json_skip_tests', '45-wordcount.js, 60-drone.js, 80-sfs.js', doc="Comma separated list of tests to skip.", doc_domain='tracspamfilter') user_agent = 'Trac/%s | SpamFilter/%s' % ( TRAC_VERSION, get_distribution('TracSpamFilter').version ) # IFilterStrategy implementation def is_external(self): return True def test(self, req, author, content, ip): if not self._check_preconditions(req, author, content): return try: resp = self._post(req, author, content, ip) if resp[0] != 200: raise Exception("Return code %s" % resp[0]) resp = resp[1] if resp['result'] == 'SPAM': return (-abs(self.karma_points), N_("BlogSpam says content is spam (%s [%s])"), resp['reason'], resp['blocker']) except Exception, v: self.log.warning("Checking with BlogSpam failed: %s", v) def train(self, req, author, content, ip, spam=True): if not self._check_preconditions(req, author, content): return -2 try: if spam: resp = self._post(req, author, content, ip, 'spam') else: resp = self._post(req, author, content, ip, 'ok') if resp[0] != 200: raise Exception("Return code %s" % resp[0]) self.log.debug("Classifying with BlogSpam succeeded.") return 1 except Exception, v: self.log.warning("Classifying with BlogSpam failed: %s", v) except IOError, v: self.log.warning("Classifying with BlogSpam failed: %s", v) return -1 def getmethods(self): try: resp = self._call('GET', '%s/plugins' % self.api_url, None) if resp[0] != 200: raise Exception("Return code %s" % resp[0]) return resp[1] except Exception, v: self.log.warning("Getting BlogSpam methods failed: %s", v) return None # Internal methods def _check_preconditions(self, req, author, content): if self.karma_points == 0: return False if len(content) == 0: return False if is_binary(content): self.log.warning("Content is binary, BlogSpam content check " "skipped") return False return True def _post(self, req, author, content, ip, train=None): # Split up author into name and email, if possible author = author.encode('utf-8') author_name, author_email = parseaddr(author) if not author_name and not author_email: author_name = author elif not author_name and author_email.find("@") < 1: author_name = author author_email = None params = { 'ip': ip, 'name': author_name, 'comment': content.encode('utf-8'), 'agent': req.get_header('User-Agent'), 'site': req.base_url, 'version': self.user_agent } if len(self.skip_tests): params['options'] = 'exclude=%s' % \ ',exclude='.join(self.skip_tests) if author_email: params['email'] = author_email trap_link = TrapFieldFilterStrategy(self.env).getlink(req) if trap_link: params['link'] = trap_link if train is not None: params['train'] = train return self._call('POST', '%s/classify' % self.api_url, params) else: return self._call('POST', '%s/' % self.api_url, params) def _call(self, method, url, data=None): """ Do the actual HTTP request """ offs = url.find('/') api_host = url[:offs] path = url[offs:] conn = TimeoutHTTPConnection(api_host) headers = {'User-Agent': self.user_agent} if data: headers.update({'Content-type': 'application/json'}) conn.request(method, path, json.dumps(data), headers) else: conn.request(method, path, None, headers) response = conn.getresponse() body = response.read() body = json.loads(body) result = [response.status, body] conn.close() return result spam-filter/tracspamfilter/filters/bayes.py0000644000175500017550000002020712722402375021205 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2005-2006 Edgewall Software # Copyright (C) 2005 Matthew Good # Copyright (C) 2006 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Matthew Good from __future__ import with_statement from trac.config import IntOption from trac.core import Component, implements from trac.env import ISystemInfoProvider from trac.util import get_pkginfo from tracspamfilter.api import IFilterStrategy, N_ from spambayes.hammie import Hammie from spambayes.storage import SQLClassifier class BayesianFilterStrategy(Component): """Bayesian filtering strategy based on SpamBayes.""" implements(IFilterStrategy, ISystemInfoProvider) karma_points = IntOption('spam-filter', 'bayes_karma', '15', """By what factor Bayesian spam probability score affects the overall karma of a submission.""", doc_domain='tracspamfilter') min_training = IntOption('spam-filter', 'bayes_min_training', '25', """The minimum number of submissions in the training database required for the filter to start impacting the karma of submissions.""", doc_domain='tracspamfilter') min_dbcount = IntOption('spam-filter', 'bayes_min_dbcount', '5', """Entries with a count less than this value get removed from the database when calling the reduce function.""", doc_domain='tracspamfilter') # IFilterStrategy implementation def is_external(self): return False def test(self, req, author, content, ip): hammie = self._get_hammie() nspam = hammie.bayes.nspam nham = hammie.bayes.nham if author is not None: testcontent = author + '\n' + content else: testcontent = content if min(nspam, nham) < self.min_training: self.log.info("Bayes filter strategy requires more training. " "It currently has only %d words marked as ham, and " "%d marked as spam, but requires at least %d for " "each.", nham, nspam, self.min_training) return if nham - nspam > min(nham, nspam) * 2: self.log.warn("The difference between the number of ham versus " "spam submissions in the training database is " "large, results may be bad.") score = hammie.score(testcontent.encode('utf-8')) self.log.debug("SpamBayes reported spam probability of %s", score) points = -int(round(self.karma_points * (score * 2 - 1))) if points != 0: return (points, N_("SpamBayes determined spam probability of %s%%"), ("%3.2f" % (score * 100))) def train(self, req, author, content, ip, spam=True): if author is not None: testcontent = author + '\n' + content else: testcontent = content self.log.info("Training SpamBayes, marking content as %s", spam and "spam" or "ham") hammie = self._get_hammie() hammie.train(testcontent.encode('utf-8', 'ignore'), spam) hammie.store() return 1 # ISystemInfoProvider methods def get_system_info(self): import spambayes yield 'SpamBayes', get_pkginfo(spambayes)['version'] # Internal methods def _get_hammie(self): try: # 1.0 return Hammie(TracDbClassifier(self.env, self.log)) except TypeError: # 1.1 return Hammie(TracDbClassifier(self.env, self.log), 'c') def _get_numbers(self): hammie = self._get_hammie() return hammie.nspam, hammie.nham # used by admin panel def reduce(self): self.env.db_transaction(""" DELETE FROM spamfilter_bayes WHERE nspam+nham < %s AND NOT word = 'saved state' """, (self.min_dbcount,)) def dblines(self): total = self.env.db_query(""" SELECT COUNT(*) FROM spamfilter_bayes WHERE NOT word = 'saved state' """)[0][0] spam = self.env.db_query(""" SELECT COUNT(*) FROM spamfilter_bayes WHERE nham = 0 AND NOT word = 'saved state' """)[0][0] ham = self.env.db_query(""" SELECT COUNT(*) FROM spamfilter_bayes WHERE nspam = 0 AND NOT word = 'saved state' """)[0][0] reduce = self.env.db_query(""" SELECT COUNT(*) FROM spamfilter_bayes WHERE nspam+nham < %s AND NOT word = 'saved state' """, (self.min_dbcount,))[0][0] return total, spam, ham, reduce class TracDbClassifier(SQLClassifier): # FIXME: This thing is incredibly slow def __init__(self, env_db, log): self.env_db = env_db self.log = log self.nham = None self.nspam = None SQLClassifier.__init__(self, 'Trac') def load(self): if self._has_key(self.statekey): row = self._get_row(self.statekey) self.nspam = row['nspam'] self.nham = row['nham'] else: # new database self.nspam = self.nham = 0 def _sanitize(self, text): if isinstance(text, unicode): return text # Remove invalid byte sequences from utf-8 encoded text return text.decode('utf-8', 'ignore') def _get_row(self, word): word = self._sanitize(word) for row in self.env_db.db_query(""" SELECT nspam,nham FROM spamfilter_bayes WHERE word=%s """, (word,)): break else: return {} # prevent assertion - happens when there are failures in training and # the count is not updated due to an exception if word != self.statekey: if row[0] > self.nspam: self.log.warn("Reset SPAM count from %d to %d due to keyword " "'%s'.", self.nspam, row[0], word) self.nspam = row[0] self.store() if row[1] > self.nham: self.log.warn("Reset HAM count from %d to %d due to keyword " "'%s'.", self.nham, row[1], word) self.nham = row[1] self.store() return {'nspam': row[0], 'nham': row[1]} def _set_row(self, word, nspam, nham): word = self._sanitize(word) with self.env_db.db_transaction as db: if self._has_key(word): db("UPDATE spamfilter_bayes SET nspam=%s,nham=%s " "WHERE word=%s", (nspam, nham, word)) else: db("INSERT INTO spamfilter_bayes (word,nspam,nham) " "VALUES (%s,%s,%s)", (word, nspam, nham)) def _delete_row(self, word): word = self._sanitize(word) self.env_db.db_transaction(""" DELETE FROM spamfilter_bayes WHERE word=%s """, (word,)) def _has_key(self, key): key = self._sanitize(key) for count, in self.env_db.db_query(""" SELECT COUNT(*) FROM spamfilter_bayes WHERE word=%s """, (key,)): return bool(count) def _wordinfoget(self, word): row = self._get_row(word) if row: item = self.WordInfoClass() item.__setstate__((row['nspam'], row['nham'])) return item def _wordinfokeys(self): words = [] for word, in self.env_db.db_query(""" SELECT word FROM spamfilter_bayes """): words.append(word) return words spam-filter/tracspamfilter/filters/url_blacklist.py0000644000175500017550000001070112724350346022734 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Edgewall Software # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Dirk Stöcker import re from dns.name import from_text from dns.resolver import NXDOMAIN, NoAnswer, NoNameservers, Timeout, query from trac.config import IntOption, ListOption from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ class URLBlacklistFilterStrategy(Component): """Spam filter based on URL blacklistings. Requires the dnspython module from http://www.dnspython.org/. """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'url_blacklist_karma', '3', """By how many points blacklisting by a single bad URL impacts the overall karma of a submission.""", doc_domain='tracspamfilter') servers_default = 'urired.spameatingmonkey.net, multi.surbl.org, ' \ 'dbl.spamhaus.org' servers = ListOption('spam-filter', 'url_blacklist_servers', servers_default, doc="Servers used for URL blacklisting.", doc_domain='tracspamfilter') # IFilterStrategy implementation def is_external(self): return True def test(self, req, author, content, ip): if not self._check_preconditions(req, author, content, ip): return urls = self._geturls(author + "\n" + content) if not urls: return self.log.debug('Checking for URL blacklisting on "%s"', ", ".join(urls)) points = 0 servers = [] for server in self.servers: for url in sorted(urls.keys()): self.log.debug("Checking blacklist %s for %s", server, url) try: servers.append(self._query(url, server)) points -= abs(self.karma_points) except NXDOMAIN: # not blacklisted on this server #if url.startswith("www.") and not url[4:] in urls: # try: # self.log.debug("Checking blacklist %s for %s", # server, url[4:]) # servers.append("[www.]%s" % self._query(url[4:], server)) # points -= abs(self.karma_points) # except: # pass continue except (Timeout, NoAnswer, NoNameservers), e: self.log.warning('Error checking URL blacklist server ' '"%s" for URL "%s": %s', server, url, e) if points != 0: return points, N_("URL's blacklisted by %s"), ', '.join(servers) def train(self, req, author, content, ip, spam=True): return 0 # Internal methods def _query(self, url, server): res = query(from_text(url + '.' + server.encode('utf-8')))[0].to_text() if res == '127.0.0.1': return '%s (%s)' % (server, url) # strip the common part of responses if res.startswith('127.0.0.'): res = res[8:] elif res.startswith('127.'): res = res[4:] return '%s (%s[%s])' % (server, url, res) def _check_preconditions(self, req, author, content, ip): if self.karma_points == 0 or not self.servers: return False return True def _geturls(self, content): urls = {} content = content.lower() # no IDN domains, only punnycode urlstr = re.compile("^([a-z0-9][a-z0-9.-]+[a-z0-9])(.?)") while 1: pos = content.find('//') if pos < 0: break content = content[pos + 2:] res = urlstr.search(content) if res: u = res.group(1) urls[u] = urls.get(u, 0) if res.group(2) not in ('"', '\'', '/', '\n', '.', '!', '?', ',', ';', ''): self.log.warn("Strange URL '%s' found.", u) return urls spam-filter/tracspamfilter/filters/mollom.py0000644000175500017550000001562112724350346021407 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2014 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import httplib2 import oauth2 import urllib2 from email.Utils import parseaddr from pkg_resources import get_distribution from urllib import urlencode from xml.etree import ElementTree from trac import __version__ as TRAC_VERSION from trac.config import IntOption, Option from trac.core import Component, implements from trac.env import ISystemInfoProvider from trac.mimeview.api import is_binary from trac.util import get_pkginfo from tracspamfilter.api import IFilterStrategy, N_ class MollomFilterStrategy(Component): """Spam filter using the Mollom service (http://mollom.com/). """ implements(IFilterStrategy, ISystemInfoProvider) karma_points = IntOption('spam-filter', 'mollom_karma', '5', """By how many points an Mollom reject impacts the overall karma of a submission.""", doc_domain ='tracspamfilter') public_key = Option('spam-filter', 'mollom_public_key', '', "Public key required to use the Mollom API.", doc_domain ='tracspamfilter') private_key = Option('spam-filter', 'mollom_private_key', '', "Private key required to use the Mollom API.", doc_domain ='tracspamfilter') api_url = Option('spam-filter', 'mollom_api_url', 'rest.mollom.com/v1/', "URL of the Mollom service.", doc_domain ='tracspamfilter') user_agent = 'Trac/%s | SpamFilter/%s' % ( TRAC_VERSION, get_distribution('TracSpamFilter').version ) def __init__(self): self.verified_key = None # IFilterStrategy implementation def is_external(self): return True def test(self, req, author, content, ip): if not self._check_preconditions(req, author, content): return try: # Split up author into name and email, if possible author = author.encode('utf-8') author_name, author_email = parseaddr(author) if not author_name and not author_email: author_name = author elif not author_name and author_email.find('@') < 1: author_name = author author_email = None params = { 'authorIp': ip, 'postBody': content.encode('utf-8'), 'checks': 'spam', 'authorName': author_name } if author_email: params['authorMail'] = author_email resp, content = self._call('content', params) if 'spam' in content: tree = ElementTree.fromstring(content) confidence = 1 se = tree.find('./content/spamScore') if se is not None: confidence = float(se.text) else: self.log.warn("Mollom score not found") karma = abs(self.karma_points) * float(confidence) self.log.debug("Mollom says content is %s spam", confidence) return -int(karma + 0.5), N_("Mollom says content is spam") elif 'ham' in content: tree = ElementTree.fromstring(content) confidence = 1 se = tree.find('./content/spamScore') if se is not None: confidence = 1.0 - float(se.text) else: self.log.warn("Mollom score not found") karma = abs(self.karma_points) * float(confidence) self.log.debug("Mollom says content is %s ham", confidence) return int(karma + 0.5), N_("Mollom says content is ham") except urllib2.URLError, e: self.log.warn("Mollom request failed (%s)", e) def train(self, req, author, content, ip, spam=True): return 0 # ISystemInfoProvider methods def get_system_info(self): yield 'python-oauth2', get_pkginfo(oauth2)['version'] yield 'httplib2', get_pkginfo(httplib2)['version'] # Internal methods def _call(self, url, params=None, public_key=None, private_key=None, api_url=None): if not api_url: api_url = self.api_url if not public_key: public_key = self.public_key if not private_key: private_key = self.private_key headers = { 'Content-Type': 'text/plain', 'User-Agent': self.user_agent } if params: body = urlencode(params) else: body = '\n' url = 'http://' + api_url + url consumer = oauth2.Consumer(public_key, private_key) req = oauth2.Request.from_consumer_and_token( consumer, http_method='POST', http_url=url, body=body) req.sign_request(oauth2.SignatureMethod_HMAC_SHA1(), consumer, None) headers.update(req.to_header()) return httplib2.Http().request(url, method="POST", body=body, headers=headers) def _check_preconditions(self, req, author, content): if self.karma_points == 0: return False if not self.public_key or not self.private_key: self.log.warning("Mollom API keys missing") return False if is_binary(content): self.log.warning("Content is binary, Mollom content check " "skipped") return False try: if not self.verify_key(req): self.log.warning("Mollom API keys are invalid") return False return True except urllib2.URLError, e: self.log.warn("Mollom request failed (%s)", e) def verify_key(self, req, api_url=None, public_key=None, private_key=None): if api_url is None: api_url = self.api_url if private_key is None: private_key = self.private_key if public_key is None: public_key = self.public_key if public_key + private_key != self.verified_key: self.log.debug("Verifying Mollom API keys") resp, content = self._call('site/%s' % public_key, None, public_key, private_key, api_url) c = "%s" % private_key if c in content: self.log.debug("Mollom API keys are valid") self.verified = True self.verified_key = public_key + private_key return self.verified_key is not None spam-filter/tracspamfilter/filters/__init__.py0000644000175500017550000000104110417443477021643 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2005-2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from tracspamfilter.api import * spam-filter/tracspamfilter/filters/botscout.py0000644000175500017550000000644312724350346021754 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2012 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import string import urllib2 from email.Utils import parseaddr from pkg_resources import get_distribution from urllib import urlencode from trac import __version__ as TRAC_VERSION from trac.config import IntOption, Option from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ class BotScoutFilterStrategy(Component): """Spam filter using the BotScout (http://botscout.com/). """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'botscout_karma', '3', """By how many points a BotScout reject impacts the overall karma of a submission.""", doc_domain='tracspamfilter') api_key = Option('spam-filter', 'botscout_api_key', '', "API key required to use BotScout.", doc_domain='tracspamfilter') user_agent = 'Trac/%s | SpamFilter/%s' % ( TRAC_VERSION, get_distribution('TracSpamFilter').version ) # IFilterStrategy implementation def is_external(self): return True def test(self, req, author, content, ip): if not self._check_preconditions(False): return try: resp = self._send(req, author, ip) if resp.startswith('Y'): count = 0 res = string.split(resp, '|') if res[3] != '0': count += 1 if res[5] != '0': count += 1 if res[7] != '0': count += 1 return (-abs(self.karma_points) * count, N_("BotScout says this is spam (%s)"), resp) except urllib2.URLError, e: self.log.warn("BotScout request failed (%s)", e) def train(self, req, author, content, ip, spam=True): return 0 # Internal methods def _check_preconditions(self, train): if self.karma_points == 0: return False if not self.api_key: return False return True def _send(self, req, author, ip): # Split up author into name and email, if possible author = author.encode('utf-8') author_name, author_email = parseaddr(author) if not author_name and not author_email: author_name = author elif not author_name and author_email.find('@') < 1: author_name = author author_email = None if author_name == 'anonymous': author_name = None params = {'ip': ip, 'key': self.api_key} if author_name: params['name'] = author_name if author_email: params['mail'] = author_email url = 'http://botscout.com/test/?multi&' + urlencode(params) url_req = urllib2.Request(url, None, {'User-Agent': self.user_agent}) resp = urllib2.urlopen(url_req) return resp.read() spam-filter/tracspamfilter/filters/fspamlist.py0000644000175500017550000000753512725737376022134 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2012 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import string import urllib2 from email.Utils import parseaddr from pkg_resources import get_distribution from urllib import quote from xml.etree import ElementTree from trac import __version__ as TRAC_VERSION from trac.config import IntOption, Option from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ class FSpamListFilterStrategy(Component): """Spam filter using the FSpamList (http://www.fspamlist.com/). """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'fspamlist_karma', '3', """By how many points a FSpamList reject impacts the overall karma of a submission.""", doc_domain='tracspamfilter') api_key = Option('spam-filter', 'fspamlist_api_key', '', """API key required to use FSpamList.""", doc_domain='tracspamfilter') user_agent = 'Trac/%s | SpamFilter/%s' % ( TRAC_VERSION, get_distribution('TracSpamFilter').version ) # IFilterStrategy implementation def is_external(self): return True def test(self, req, author, content, ip): if not self._check_preconditions(False): return try: resp = self._send(req, author, ip) except urllib2.URLError, e: self.log.warn("FSpamList request failed (%s)", e) return try: tree = ElementTree.fromstring(resp) except ElementTree.ParseError, e: self.log.warn("Error parsing response from FSpamList: %s\n" "Response:\n%s", e, resp) return reason = [] for el in list(tree): if el.findtext('isspammer', 'false') == 'true': r = '%s [%s' % (el.findtext('spammer', '-'), el.findtext('threat', '-')) n = string.split(el.findtext('notes', '-'), 'Time taken')[0].rstrip(' ') if n != "": r += ", " + n r += "]" reason.append(r) if len(reason): return (-abs(self.karma_points) * len(reason), N_("FSpamList says this is spam (%s)"), ("; ".join(reason))) def train(self, req, author, content, ip, spam=True): return 0 # Internal methods def _check_preconditions(self, train): if self.karma_points == 0: return False if not self.api_key: return False return True def _send(self, req, author, ip): # Split up author into name and email, if possible author = author.encode('utf-8') author_name, author_email = parseaddr(author) if not author_name and not author_email: author_name = author elif not author_name and author_email.find('@') < 1: author_name = author author_email = None if author_name == 'anonymous': author_name = None request = quote(ip) if author_name: request += "," + quote(author_name) if author_email: request += "," + quote(author_email) url = 'http://www.fspamlist.com/api.php?spammer=' + request + \ '&key=' + self.api_key urlreq = urllib2.Request(url, None, {'User-Agent': self.user_agent}) resp = urllib2.urlopen(urlreq) return resp.read() spam-filter/tracspamfilter/filters/extlinks.py0000644000175500017550000000507512724350346021753 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import copy import re from trac.config import ListOption, IntOption from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ class ExternalLinksFilterStrategy(Component): """Spam filter strategy that reduces the karma of a submission if the content contains too many links to external sites. """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'extlinks_karma', '2', """By how many points too many external links in a submission impact the overall score.""", doc_domain='tracspamfilter') max_links = IntOption('spam-filter', 'max_external_links', '4', """The maximum number of external links allowed in a submission until that submission gets negative karma.""", doc_domain='tracspamfilter') allowed_domains = ListOption('spam-filter', 'extlinks_allowed_domains', 'example.com, example.org', doc="List of domains that should be allowed in external links", doc_domain='tracspamfilter') _URL_RE = re.compile('https?://([^/]+)/?', re.IGNORECASE) # IFilterStrategy methods def is_external(self): return False def test(self, req, author, content, ip): num_ext = 0 allowed = copy.copy(self.allowed_domains) allowed.append(req.get_header('Host')) for host in self._URL_RE.findall(content): if host not in allowed: self.log.debug('"%s" is not in extlink_allowed_domains', host) num_ext += 1 else: self.log.debug('"%s" is whitelisted.', host) if num_ext > self.max_links: if self.max_links > 0: return -abs(self.karma_points) * num_ext / self.max_links, \ N_("Maximum number of external links per post exceeded") else: return -abs(self.karma_points) * num_ext, \ N_("External links in post found") def train(self, req, author, content, ip, spam=True): return 0 spam-filter/tracspamfilter/filters/registration.py0000644000175500017550000001014012724350346022611 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from acct_mgr.api import IAccountRegistrationInspector from acct_mgr.register import RegistrationError from trac.config import BoolOption, IntOption from trac.core import Component, ExtensionPoint, implements from trac.util.html import tag from tracspamfilter.api import IFilterStrategy, N_ class RegistrationFilterStrategy(Component): """Spam filter strategy that calls account manager checks for account registration. """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'account_karma', '0', """By how many points a failed registration check impacts the overall score.""", doc_domain='tracspamfilter') replace_checks = BoolOption('spam-filter', 'account_replace_checks', 'false', """Replace checks in account manager totally.""", doc_domain='tracspamfilter') listeners = ExtensionPoint(IAccountRegistrationInspector) def render_registration_fields(self, req, data, fragments): self.log.debug("Adding registration check data fields") if self.replace_checks: for check in self.listeners: try: if check.__class__.__name__ != 'RegistrationFilterAdapter': self.log.debug("Add registration check data %s", check) fragment, f_data = \ check.render_registration_fields(req, data) try: fragments['optional'] = \ tag(fragments.get('optional', ''), fragment.get('optional', '')) fragments['required'] = \ tag(fragments.get('required', ''), fragment.get('required', '')) except AttributeError: if fragment is not None and fragment != '': fragments['required'] = \ tag(fragments.get('required', ''), fragment) data.update(f_data) except Exception, e: self.log.exception("Adding registration fields failed: %s", e) return fragments, data # IFilterStrategy methods def is_external(self): return False def test(self, req, author, content, ip): if req.path_info == '/register': karma = 0 checks = [] for check in self.listeners: try: if check.__class__.__name__ != 'RegistrationFilterAdapter': self.log.debug("Try registration check %s", check) check.validate_registration(req) except RegistrationError, e: karma -= abs(self.karma_points) msg = e.message.replace('\n', '') args = e.msg_args if args: msg = msg % args msg.replace('', '*').replace('', '*') self.log.debug("Registration check returned %s", msg) checks.append('%s: %s' % (check.__class__.__name__, msg)) except Exception, e: self.log.exception("Registration check %s failed: %s", check, e) if karma or checks: return karma, N_("Account registration failed (%s)"), \ ", ".join(checks) def train(self, req, author, content, ip, spam=True): return 0 spam-filter/tracspamfilter/filters/ip_regex.py0000644000175500017550000001016212724350346021705 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2005-2011 Edgewall Software # Copyright (C) 2005 Matthew Good # Copyright (C) 2006 Christopher Lenz # Copyright (C) 2011 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Dirk Stöcker , # Matthew Good import re from trac.config import BoolOption, IntOption, Option from trac.core import Component, implements from trac.wiki.api import IWikiChangeListener from trac.wiki.model import WikiPage from tracspamfilter.api import IFilterStrategy, N_ class IPRegexFilterStrategy(Component): """Spam filter for submitter's IP based on regular expressions defined in BadIP page. """ implements(IFilterStrategy, IWikiChangeListener) karma_points = IntOption('spam-filter', 'ipregex_karma', '20', """By how many points a match with a pattern on the BadIP page impacts the overall karma of a submission.""", doc_domain='tracspamfilter') badcontent_file = Option('spam-filter', 'ipbadcontent_file', '', """Local file to be loaded to get BadIP. Can be used in addition to BadIP wiki page.""", doc_domain='tracspamfilter') show_blacklisted = BoolOption('spam-filter', 'show_blacklisted_ip', 'true', "Show the matched bad IP patterns in rejection message.", doc_domain='tracspamfilter') def __init__(self): self.patterns = [] page = WikiPage(self.env, 'BadIP') if page.exists: self._load_patterns(page) if self.badcontent_file != '': file = open(self.badcontent_file, 'r') if file is None: self.log.warning("BadIP file cannot be opened") else: lines = file.read().splitlines() pat = [re.compile(p.strip()) for p in lines if p.strip()] self.log.debug("Loaded %s patterns from BadIP file", len(pat)) self.patterns += pat # IFilterStrategy implementation def is_external(self): return False def test(self, req, author, content, ip): gotcha = [] points = 0 for pattern in self.patterns: match = pattern.search(ip) if match: gotcha.append("'%s'" % pattern.pattern) self.log.debug("Pattern %s found in submission", pattern.pattern) points -= abs(self.karma_points) if points != 0: if self.show_blacklisted: matches = ", ".join(gotcha) return points, N_("IP catched by these blacklisted " "patterns: %s"), matches else: return (points, N_("IP catched by %s blacklisted patterns"), str(len(gotcha))) def train(self, req, author, content, ip, spam=True): return 0 # IWikiChangeListener implementation def wiki_page_changed(self, page, *args): if page.name == 'BadIP': self._load_patterns(page) wiki_page_added = wiki_page_changed wiki_page_version_deleted = wiki_page_changed def wiki_page_deleted(self, page): if page.name == 'BadIP': self.patterns = [] # Internal methods def _load_patterns(self, page): if '{{{' in page.text and '}}}' in page.text: lines = page.text.split('{{{', 1)[1].split('}}}', 1)[0].splitlines() self.patterns = [re.compile(p.strip()) for p in lines if p.strip()] self.log.debug("Loaded %s patterns from BadIP", len(self.patterns)) else: self.log.warning("BadIP page does not contain any patterns") self.patterns = [] spam-filter/tracspamfilter/filters/session.py0000644000175500017550000000376712671220455021601 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from email.Utils import parseaddr from trac.config import IntOption from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ class SessionFilterStrategy(Component): """This strategy grants positive karma points to users with an existing session, and extra points if they've set up their user name and password.""" implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'session_karma', '6', """By how many points an existing and configured session improves the overall karma of the submission. A third of the points is granted for having an existing session at all, the other two thirds are granted when the user has his name and/or email address set in the session, respectively.""", doc_domain="tracspamfilter") # IFilterStrategy implementation def is_external(self): return False def test(self, req, author, content, ip): points = 0 if req.session.last_visit: points += abs(self.karma_points) / 3 if req.session.get('name'): points += abs(self.karma_points) / 3 if req.session.get('email'): email = parseaddr(req.session.get('email'))[1] if email and '@' in email: points += abs(self.karma_points) / 3 return points, N_('Existing session found') def train(self, req, author, content, ip, spam=True): return 0 spam-filter/tracspamfilter/filters/tests/0000755000175500017550000000000012724472512020673 5ustar debacledebaclespam-filter/tracspamfilter/filters/tests/regex.py0000644000175500017550000001353312724472512022364 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import unittest from trac.core import TracError from trac.perm import PermissionSystem from trac.test import EnvironmentStub, Mock from trac.web.api import RequestDone from trac.wiki.model import WikiPage from trac.wiki.web_ui import WikiModule from tracspamfilter.filters import regex from tracspamfilter.filters.regex import RegexFilterStrategy from tracspamfilter.tests.compat import MockRequest class DummyWikiPage(object): def __init__(self): self.text = '' def __call__(self, env, name): self.env = env self.name = name self.exists = True return self class RegexFilterStrategyTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', RegexFilterStrategy], default_data=True) self.page = regex.WikiPage = DummyWikiPage() self.strategy = RegexFilterStrategy(self.env) def tearDown(self): self.env.reset_db() def _dispatch_request(self, req): module = WikiModule(self.env) self.assertTrue(module.match_request(req)) try: params = module.process_request(req) except RequestDone: return [] else: return params def test_no_patterns(self): retval = self.strategy.test(Mock(), 'anonymous', 'foobar', '127.0.0.1') self.assertEqual(None, retval) def test_one_matching_pattern(self): self.page.text = """{{{ foobar }}}""" self.strategy.wiki_page_changed(self.page) retval = self.strategy.test(Mock(), 'anonymous', 'foobar', '127.0.0.1') self.assertEqual((-5, "Content contained these blacklisted " "patterns: %s", '\'foobar\''), retval) def test_multiple_matching_pattern(self): self.page.text = """{{{ foobar ^foo bar$ }}}""" self.strategy.wiki_page_changed(self.page) retval = self.strategy.test(Mock(), 'anonymous', '\nfoobar', '127.0.0.1') self.assertEqual((-10, "Content contained these blacklisted " "patterns: %s", '\'foobar\', \'bar$\''), retval) def test_view_page_with_invalid_pattern(self): """Page with invalid pattern should render fine, but not allow an edit without correcting the invalid pattern. """ text = """{{{ (?i)eventbrite\.com (?i)sneaker(?:supplier|nice\.com }}}""" page = WikiPage(self.env) page.text = text page.name = 'BadContent' try: page.save('anonymous', 'Page created.') except TracError: self.assertTrue(WikiPage(self.env, 'BadContent').exists) else: self.fail("Saving page with invalid content did not " "raise a TracError.") req = MockRequest(self.env, authname='user', args={ 'action': 'view', }, path_info='/wiki/BadContent') data = self._dispatch_request(req)[1] self.assertEqual(page.text, data['text']) req = MockRequest(self.env, authname='user', args={ 'action': 'edit', 'preview': True, 'version': 1, 'text': text }, method='POST', path_info='/wiki/BadContent') self._dispatch_request(req) self.assertIn('Invalid Wiki page: Error in pattern ' '(?i)sneaker(?:supplier|nice\\.com: ' 'unbalanced parenthesis.', req.chrome['warnings']) def test_save_page_with_invalid_pattern(self): """Page cannot be saved with an invalid pattern.""" perm = PermissionSystem(self.env) perm.grant_permission('user', 'authenticated') text = """{{{ (?i)eventbrite\.com (?i)sneaker(?:supplier|nice\.com }}}""" req = MockRequest(self.env, authname='user', args={ 'action': 'edit', 'text': text, 'version': 0, }, method='POST', path_info='/wiki/BadContent') self._dispatch_request(req) self.assertIn('Invalid Wiki page: Error in pattern ' '(?i)sneaker(?:supplier|nice\\.com: ' 'unbalanced parenthesis.', req.chrome['warnings']) def test_save_page_with_valid_patterns(self): """Page with valid patterns can be saved.""" perm = PermissionSystem(self.env) perm.grant_permission('user', 'authenticated') text = """{{{ (?i)eventbrite\.com (?i)sneaker(?:supplier|nice)\.com }}}""" req = MockRequest(self.env, authname='user', args={ 'action': 'edit', 'text': text, 'version': 0, }, method='POST', path_info='/wiki/BadContent') self._dispatch_request(req) self.assertIn('Your changes have been saved in version 1.', req.chrome['notices']) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(RegexFilterStrategyTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/filters/tests/bayes.py0000644000175500017550000001003212724472512022344 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from __future__ import with_statement import unittest from trac.test import EnvironmentStub, Mock from tracspamfilter.filtersystem import FilterSystem from tracspamfilter.tests.model import drop_tables class BayesianFilterStrategyTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[BayesianFilterStrategy]) self.env.config.set('spam-filter', 'bayes_karma', '10') with self.env.db_transaction as db: FilterSystem(self.env).upgrade_environment(db) self.strategy = BayesianFilterStrategy(self.env) def tearDown(self): drop_tables(self.env) self.env.reset_db() def test_karma_calculation_unsure(self): bayes.Hammie = lambda x: Mock(score=lambda x: .5, bayes=Mock(nham=1000, nspam=1000)) req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1') self.assertEquals(None, self.strategy.test(req, 'John Doe', 'Spam', '127.0.0.1')) def test_karma_calculation_negative(self): bayes.Hammie = lambda x: Mock(score=lambda x: .75, bayes=Mock(nham=1000, nspam=1000)) req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1') points, reasons, args = \ self.strategy.test(req, 'John Doe', 'Spam', '127.0.0.1') self.assertEquals(-5, points) def test_karma_calculation_positive(self): bayes.Hammie = lambda x: Mock(score=lambda x: .25, bayes=Mock(nham=1000, nspam=1000)) req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1') points, reasons, args = \ self.strategy.test(req, 'John Doe', 'Spam', '127.0.0.1') self.assertEquals(5, points) def test_classifier_untrained(self): req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1') self.assertEqual(None, self.strategy.test(req, 'John Doe', 'Hammie', '127.0.0.1')) def test_classifier_basics(self): req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1') self.env.config.set('spam-filter', 'bayes_min_training', '1') self.strategy.train(req, 'John Doe', 'Spam spam spammie', '127.0.0.1', True) self.strategy.train(req, 'John Doe', 'Ham ham hammie', '127.0.0.1', False) points, reasons, args = \ self.strategy.test(req, 'John Doe', 'Hammie', '127.0.0.1') self.assertGreater(points, 0, 'Expected positive karma') points, reasons, args = \ self.strategy.test(req, 'John Doe', 'Spam', '127.0.0.1') self.assertLess(points, 0, 'Expected negative karma') try: from tracspamfilter.filters import bayes from tracspamfilter.filters.bayes import BayesianFilterStrategy except ImportError: # Skip tests if SpamBayes isn't installed class BayesianFilterStrategyTestCase(object): pass def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(BayesianFilterStrategyTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/filters/tests/__init__.py0000644000175500017550000000175412724472512023013 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import unittest from tracspamfilter.filters.tests import akismet, bayes, extlinks, regex, \ session def test_suite(): suite = unittest.TestSuite() suite.addTest(akismet.test_suite()) suite.addTest(bayes.test_suite()) suite.addTest(extlinks.test_suite()) suite.addTest(regex.test_suite()) suite.addTest(session.test_suite()) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/filters/tests/extlinks.py0000644000175500017550000001044612724472512023113 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import unittest from trac.test import EnvironmentStub, Mock from tracspamfilter.filters.extlinks import ExternalLinksFilterStrategy class ExternalLinksFilterStrategyTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[ExternalLinksFilterStrategy]) self.strategy = ExternalLinksFilterStrategy(self.env) def test_no_links(self): req = Mock(get_header=lambda x: {'Host': 'example.org'}.get(x)) retval = self.strategy.test(req, 'John Doe', 'Foo bar', '127.0.0.1') self.assertEqual(None, retval) def test_few_ext_links(self): req = Mock(get_header=lambda x: {'Host': 'example.org'}.get(x)) retval = self.strategy.test(req, 'John Doe', """ fakehandbags fakewatches """, '127.0.0.1') self.assertEqual(None, retval) def test_many_ext_links(self): req = Mock(get_header=lambda x: {'Host': 'example.org'}.get(x)) retval = self.strategy.test(req, 'John Doe', """ fakehandbags fakewatches fakehandbags fakewatches fakehandbags fakewatches """, '127.0.0.1') self.assertEqual( (-3, 'Maximum number of external links per post exceeded'), retval ) def test_many_ext_links_same_site(self): req = Mock(get_header=lambda x: {'Host': 'example.org'}.get(x)) retval = self.strategy.test(req, 'John Doe', """ foo bar foo bar foo bar """, '127.0.0.1') self.assertEqual(None, retval) def test_many_ext_links_raw(self): req = Mock(get_header=lambda x: {'Host': 'example.org'}.get(x)) retval = self.strategy.test(req, 'John Doe', """ http://spammers-site.com/fakehandbags http://spammers-site.com/fakewatches http://spammers-site.com/fakehandbags http://spammers-site.com/fakewatches http://spammers-site.com/fakehandbags http://spammers-site.com/fakewatches """, '127.0.0.1') self.assertEqual( (-3, 'Maximum number of external links per post exceeded'), retval ) def test_many_ext_links_bbcode(self): req = Mock(get_header=lambda x: {'Host': 'example.org'}.get(x)) retval = self.strategy.test(req, 'John Doe', """ [url=http://spammers-site.com/fakehandbags]fakehandbags[/url] [url=http://spammers-site.com/fakewatches]fakewatches[/url] [url=http://spammers-site.com/fakehandbags]fakehandbags[/url] [url=http://spammers-site.com/fakewatches]fakewatches[/url] [url=http://spammers-site.com/fakehandbags]fakehandbags[/url] [url=http://spammers-site.com/fakewatches]fakewatches[/url] """, '127.0.0.1') self.assertEqual( (-3, 'Maximum number of external links per post exceeded'), retval ) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ExternalLinksFilterStrategyTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/filters/tests/session.py0000644000175500017550000000510712724472512022733 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import unittest from trac.test import EnvironmentStub, Mock from tracspamfilter.filters.session import SessionFilterStrategy class SessionFilterStrategyTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[SessionFilterStrategy]) self.strategy = SessionFilterStrategy(self.env) def test_new_session(self): data = {} session = Mock(last_visit=42, get=data.get) rv = self.strategy.test(Mock(session=session), None, None, '127.0.0.1') self.assertEqual((2, "Existing session found"), rv) def test_session_name_set(self): data = {'name': 'joe'} session = Mock(last_visit=42, get=data.get) rv = self.strategy.test(Mock(session=session), None, None, '127.0.0.1') self.assertEqual((4, "Existing session found"), rv) def test_session_email_set(self): data = {'email': 'joe@example.org'} session = Mock(last_visit=42, get=data.get) rv = self.strategy.test(Mock(session=session), None, None, '127.0.0.1') self.assertEqual((4, "Existing session found"), rv) def test_session_email_set_but_invalid(self): data = {'email': 'joey'} session = Mock(last_visit=42, get=data.get) rv = self.strategy.test(Mock(session=session), None, None, '127.0.0.1') self.assertEqual((2, "Existing session found"), rv) def test_session_name_and_email_set(self): data = {'name': 'joe', 'email': 'joe@example.org'} session = Mock(last_visit=42, get=data.get) retval = self.strategy.test(Mock(session=session), None, None, '127.0.0.1') self.assertEqual((6, "Existing session found"), retval) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(SessionFilterStrategyTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/filters/tests/akismet.py0000644000175500017550000001422212724472512022703 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import StringIO import unittest from trac.test import EnvironmentStub, Mock from tracspamfilter.filters import akismet from tracspamfilter.filters.akismet import AkismetFilterStrategy class DummyRequest(object): def __init__(self, url, params, headers): self.url = url self.params = params self.headers = headers akismet.urllib2.Request = DummyRequest class DummyURLOpener(object): def __init__(self): self.responses = [] self.requests = [] def __call__(self, request): self.requests.append(request) return StringIO.StringIO(self.responses.pop(0)) class AkismetFilterStrategyTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[AkismetFilterStrategy]) self.strategy = AkismetFilterStrategy(self.env) self.urlopen = akismet.urllib2.urlopen = DummyURLOpener() def test_no_api_key(self): req = Mock(remote_addr='127.0.0.1') retval = self.strategy.test(req, 'anonymous', 'foobar', req.remote_addr) self.assertEqual(None, retval) def test_bad_api_key(self): req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1') self.env.config.set('spam-filter', 'akismet_api_key', 'INVALID') self.urlopen.responses = ['invalid'] retval = self.strategy.test(req, 'anonymous', 'foobar', req.remote_addr) self.assertEqual(None, retval) self.assertEqual(1, len(self.urlopen.requests)) req = self.urlopen.requests[0] self.assertEqual('http://rest.akismet.com/1.1/verify-key', req.url) self.assertEqual('blog=http%3A%2F%2Fexample.org%2F&key=INVALID', req.params) self.assertEqual(self.strategy.user_agent, req.headers['User-Agent']) def test_check_ham(self): req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1', get_header=lambda x: None, environ={}) self.env.config.set('spam-filter', 'akismet_api_key', 'mykey') self.urlopen.responses = ['valid', 'false'] retval = self.strategy.test(req, 'anonymous', 'foobar', req.remote_addr) self.assertEqual(None, retval) self.assertEqual(2, len(self.urlopen.requests)) req = self.urlopen.requests[1] self.assertEqual('http://mykey.rest.akismet.com/1.1/comment-check', req.url) self.assertEqual('user_ip=127.0.0.1&comment_type=trac&' 'referrer=unknown&blog=http%3A%2F%2Fexample.org%2F&' 'user_agent=None&comment_content=foobar&' 'comment_author=anonymous', req.params) self.assertEqual(self.strategy.user_agent, req.headers['User-Agent']) def test_check_spam(self): req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1', get_header=lambda x: None, environ={}) self.env.config.set('spam-filter', 'akismet_api_key', 'mykey') self.urlopen.responses = ['valid', 'true'] retval = self.strategy.test(req, 'anonymous', 'foobar', req.remote_addr) self.assertEqual((-10, 'Akismet says content is spam'), retval) self.assertEqual(2, len(self.urlopen.requests)) def test_submit_ham(self): req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1', get_header=lambda x: None, environ={}) self.env.config.set('spam-filter', 'akismet_api_key', 'mykey') self.urlopen.responses = ['valid', ''] self.strategy.train(req, 'anonymous', 'foobar', req.remote_addr, spam=False) req = self.urlopen.requests[1] self.assertEqual('http://mykey.rest.akismet.com/1.1/submit-ham', req.url) self.assertEqual('user_ip=127.0.0.1&comment_type=trac&' 'referrer=unknown&blog=http%3A%2F%2Fexample.org%2F&' 'user_agent=None&comment_content=foobar&' 'comment_author=anonymous', req.params) self.assertEqual(self.strategy.user_agent, req.headers['User-Agent']) def test_submit_spam(self): req = Mock(authname='anonymous', base_url='http://example.org/', remote_addr='127.0.0.1', get_header=lambda x: None, environ={}) self.env.config.set('spam-filter', 'akismet_api_key', 'mykey') self.urlopen.responses = ['valid', ''] self.strategy.train(req, 'anonymous', 'foobar', req.remote_addr, spam=True) req = self.urlopen.requests[1] self.assertEqual('http://mykey.rest.akismet.com/1.1/submit-spam', req.url) self.assertEqual('user_ip=127.0.0.1&comment_type=trac&' 'referrer=unknown&blog=http%3A%2F%2Fexample.org%2F&' 'user_agent=None&comment_content=foobar&' 'comment_author=anonymous', req.params) self.assertEqual(self.strategy.user_agent, req.headers['User-Agent']) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(AkismetFilterStrategyTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/filters/stopforumspam.py0000644000175500017550000001106112724350346023021 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2011 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import urllib2 from email.Utils import parseaddr from pkg_resources import get_distribution from urllib import urlencode from xml.etree import ElementTree from trac import __version__ as TRAC_VERSION from trac.config import IntOption, Option from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ class StopForumSpamFilterStrategy(Component): """Spam filter using the StopForumSpam service (http://stopforumspam.com/). """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'stopforumspam_karma', '4', """By how many points a StopForumSpam reject impacts the overall karma of a submission.""", doc_domain='tracspamfilter') api_key = Option('spam-filter', 'stopforumspam_api_key', '', "API key used to report SPAM.", doc_domain='tracspamfilter') user_agent = 'Trac/%s | SpamFilter/%s' % ( TRAC_VERSION, get_distribution('TracSpamFilter').version ) # IFilterStrategy implementation def is_external(self): return True def test(self, req, author, content, ip): if not self._check_preconditions(False): return try: resp = self._send(req, author, content, ip, False) tree = ElementTree.fromstring(resp) karma = 0 reason = [] for entry in ('username', 'ip', 'email'): e = tree.find('./%s/appears' % entry) if e is not None and e.text == "1": confidence = tree.find('./%s/confidence' % entry).text karma += abs(self.karma_points) * float(confidence) / 100.0 reason.append("%s [%s]" % (entry, confidence)) reason = ",".join(reason) if karma: return (-int(karma + 0.5), N_("StopForumSpam says this is spam (%s)"), reason) except IOError, e: self.log.warn("StopForumSpam request failed (%s)", e) def train(self, req, author, content, ip, spam=True): if not spam: return 0 elif not self._check_preconditions(True): return -2 try: self._send(req, author, content, ip, True) return 1 except urllib2.URLError, e: self.log.warn("StopForumSpam request failed (%s)", e) return -1 # Internal methods def _check_preconditions(self, train): if self.karma_points == 0: return False if train and not self.api_key: return False return True def _send(self, req, author, content, ip, train): # Split up author into name and email, if possible author = author.encode('utf-8') author_name, author_email = parseaddr(author) if not author_name and not author_email: author_name = author elif not author_name and author_email.find('@') < 1: author_name = author author_email = None if author_name == 'anonymous': author_name = None params = {'ip': ip} if author_name: params['username'] = author_name if author_email: params['email'] = author_email if train: if not author_name or not author_email: return params['api_key'] = self.api_key params['ip_addr'] = ip params['evidence'] = "Spam training using Trac SpamFilter " \ "(%s)\n%s" \ % (self.user_agent, content.encode('utf-8')) url = 'http://www.stopforumspam.com/add.php' urlreq = urllib2.Request(url, urlencode(params), {'User-Agent': self.user_agent}) else: params['ip'] = ip url = 'http://www.stopforumspam.com/api?confidence&f=xmldom&' + \ urlencode(params) urlreq = urllib2.Request(url, None, {'User-Agent': self.user_agent}) resp = urllib2.urlopen(urlreq) return resp.read() spam-filter/tracspamfilter/filters/akismet.py0000644000175500017550000001435712703512135021543 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2005-2006 Edgewall Software # Copyright (C) 2005-2006 Matthew Good # Copyright (C) 2006 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Matthew Good # Christopher Lenz import urllib2 from email.Utils import parseaddr from urllib import urlencode from pkg_resources import get_distribution from trac import __version__ as TRAC_VERSION from trac.config import IntOption, Option from trac.core import Component, implements from trac.mimeview.api import is_binary from tracspamfilter.api import IFilterStrategy, N_ class AkismetFilterStrategy(Component): """Spam filter using the Akismet service (http://akismet.com/). Based on the `akismet` Python module written by Michael Ford: http://www.voidspace.org.uk/python/modules.shtml#akismet """ implements(IFilterStrategy) noheaders = ['HTTP_COOKIE', 'HTTP_HOST', 'HTTP_REFERER', 'HTTP_USER_AGENT', 'HTTP_AUTHORIZATION'] karma_points = IntOption('spam-filter', 'akismet_karma', '10', """By how many points an Akismet reject impacts the overall karma of a submission.""", doc_domain='tracspamfilter') api_key = Option('spam-filter', 'akismet_api_key', '', """Wordpress key required to use the Akismet API.""", doc_domain='tracspamfilter') api_url = Option('spam-filter', 'akismet_api_url', 'rest.akismet.com/1.1/', """URL of the Akismet service.""", doc_domain='tracspamfilter') user_agent = 'Trac/%s | SpamFilter/%s' % ( TRAC_VERSION, get_distribution('TracSpamFilter').version) def __init__(self): self.verified_key = None self.verified = None # IFilterStrategy methods def is_external(self): return True def test(self, req, author, content, ip): if not self._check_preconditions(req, author, content): return try: url = 'http://%s.%scomment-check' % (self.api_key, self.api_url) self.log.debug("Checking content with Akismet service at %s", url) resp = self._post(url, req, author, content, ip) if resp.strip().lower() != 'false': self.log.debug("Akismet says content is spam") return -abs(self.karma_points), \ N_("Akismet says content is spam") except urllib2.URLError, e: self.log.warn("Akismet request failed (%s)", e) def train(self, req, author, content, ip, spam=True): if not self._check_preconditions(req, author, content): return -2 try: which = spam and 'spam' or 'ham' url = 'http://%s.%ssubmit-%s' \ % (self.api_key, self.api_url, which) self.log.debug("Submitting %s to Akismet service at %s", which, url) self._post(url, req, author, content, ip) return 1 except urllib2.URLError, e: self.log.warn("Akismet request failed (%s)", e) return -1 # Internal methods def _check_preconditions(self, req, author, content): if self.karma_points == 0: return False if not self.api_key: self.log.warning("Akismet API key is missing") return False if is_binary(content): self.log.warning("Content is binary, Akismet content check " "skipped") return False try: if not self.verify_key(req): self.log.warning("Akismet API key is invalid") return False return True except urllib2.URLError, e: self.log.warn("Akismet request failed (%s)", e) def verify_key(self, req, api_url=None, api_key=None): if api_url is None: api_url = self.api_url if api_key is None: api_key = self.api_key if api_key != self.verified_key: self.log.debug("Verifying Akismet API key") params = {'blog': req.base_url, 'key': api_key} req = urllib2.Request('http://%sverify-key' % api_url, urlencode(params), {'User-Agent': self.user_agent}) resp = urllib2.urlopen(req).read() if resp.strip().lower() == 'valid': self.log.debug("Akismet API key is valid") self.verified = True self.verified_key = api_key return self.verified_key is not None def _post(self, url, req, author, content, ip): # Split up author into name and email, if possible author = author.encode('utf-8') author_name, author_email = parseaddr(author) if not author_name and not author_email: author_name = author elif not author_name and author_email.find('@') < 1: author_name = author author_email = None params = { 'blog': req.base_url, 'user_ip': ip, 'user_agent': req.get_header('User-Agent'), 'referrer': req.get_header('Referer') or 'unknown', 'comment_author': author_name, 'comment_type': 'trac', 'comment_content': content.encode('utf-8') } if author_email: params['comment_author_email'] = author_email for k, v in req.environ.items(): if k.startswith('HTTP_') and k not in self.noheaders: params[k] = v.encode('utf-8') urlreq = urllib2.Request(url, urlencode(params), {'User-Agent': self.user_agent}) resp = urllib2.urlopen(urlreq) return resp.read() spam-filter/tracspamfilter/filters/trapfield.py0000644000175500017550000000755112724517265022072 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2012 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from genshi.filters.transform import Transformer from trac.config import IntOption, Option from trac.core import Component, implements from trac.util.html import tag from trac.util.text import javascript_quote, shorten_line from trac.web.api import ITemplateStreamFilter from tracspamfilter.api import IFilterStrategy, N_ class TrapFieldFilterStrategy(Component): """Spam filter using a hidden trap field. """ implements(IFilterStrategy, ITemplateStreamFilter) karma_points = IntOption('spam-filter', 'trap_karma', '10', """By how many points a trap reject impacts the overall karma of a submission.""", doc_domain='tracspamfilter') name = Option('spam-filter', 'trap_name', 'sfp_email', """Name of the invisible trap field, should contain some reference to e-mail for better results.""", doc_domain='tracspamfilter') name_hidden = Option('spam-filter', 'trap_name_hidden', 'sfph_mail', """Name of the hidden trap field, should contain some reference to e-mail for better results.""", doc_domain='tracspamfilter') name_register = Option('spam-filter', 'trap_name_register', 'spf_homepage', """Name of the register trap field, should contain some reference to web/homepage for better results.""", doc_domain='tracspamfilter') # IFilterStrategy implementation def is_external(self): return False def get_trap(self, req): i = req.args.getfirst(self.name) h = req.args.getfirst(self.name_hidden) if i and h and i != h: return i + "\n" + h elif h: return h return i def test(self, req, author, content, ip): i = req.args.getfirst(self.name) h = req.args.getfirst(self.name_hidden) r = self.getlink(req) if i and h: i = shorten_line(javascript_quote(i), 50) h = shorten_line(javascript_quote(h), 50) return -abs(self.karma_points), \ N_("Both trap fields says this is spam (%s, %s)"), i, h elif i: i = shorten_line(javascript_quote(i), 50) return -abs(self.karma_points), \ N_("Invisible trap field says this is spam (%s)"), i elif h: h = shorten_line(javascript_quote(h), 50) return -abs(self.karma_points), \ N_("Hidden trap field says this is spam (%s)"), h elif r: r = shorten_line(javascript_quote(r), 50) return -abs(self.karma_points), \ N_("Register trap field starts with HTTP URL (%s)"), r def getlink(self, req): r = req.args.getfirst(self.name_register) if not (r and (r.startswith('http://') or r.startswith('https://'))): return None return r def train(self, req, author, content, ip, spam=True): return 0 # ITemplateStreamFilter interface def filter_stream(self, req, method, filename, stream, data): if self.karma_points > 0: # Insert the hidden field right before the submit buttons trap = tag.div(style='display:none;')( tag.input(type='text', name=self.name, value=''), tag.input(type='hidden', name=self.name_hidden, value='')) stream = stream | \ Transformer('//div[@class="buttons"]').before(trap) return stream spam-filter/tracspamfilter/filters/ip_throttle.py0000644000175500017550000000410412724350346022437 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from datetime import datetime, timedelta from trac.config import IntOption from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ from tracspamfilter.model import LogEntry class IPThrottleFilterStrategy(Component): """Spam filter strategy that throttles multiple subsequent submissions from the same IP address. """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'ip_throttle_karma', '3', """By how many points exceeding the configured maximum number of posts per hour impacts the overall score.""", doc_domain='tracspamfilter') max_posts = IntOption('spam-filter', 'max_posts_by_ip', '10', """The maximum allowed number of submissions per hour from a single IP address. If this limit is exceeded, subsequent submissions get negative karma.""", doc_domain='tracspamfilter') # IFilterStrategy implementation def is_external(self): return False def test(self, req, author, content, ip): threshold = datetime.now() - timedelta(hours=1) num_posts = 0 for entry in LogEntry.select(self.env, ipnr=ip): if datetime.fromtimestamp(entry.time) < threshold: break num_posts += 1 if num_posts > self.max_posts: return -abs(self.karma_points) * num_posts / self.max_posts, \ N_("Maximum number of posts per hour for this IP exceeded") def train(self, req, author, content, ip, spam=True): return 0 spam-filter/tracspamfilter/filters/httpbl.py0000644000175500017550000000624112724350346021403 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Edgewall Software # Copyright (C) 2006 Matthew Good # Copyright (C) 2009 Vaclav Slavik # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Vaclav Slavik , # Matthew Good from dns.resolver import NXDOMAIN, NoAnswer, NoNameservers, Timeout, query from trac.config import Option, IntOption from trac.core import Component, implements from tracspamfilter.api import IFilterStrategy, N_ class HttpBLFilterStrategy(Component): """Spam filter based on Project Honey Pot's Http:BL blacklist. Requires the dnspython module from http://www.dnspython.org/. """ implements(IFilterStrategy) karma_points = IntOption('spam-filter', 'httpbl_spammer_karma', '6', """By how many points listing as "comment spammer" impacts the overall karma of a submission.""", doc_domain='tracspamfilter') api_key = Option('spam-filter', 'httpbl_api_key', '', "Http:BL API key required for use.", doc_domain='tracspamfilter') # IFilterStrategy implementation def is_external(self): return True def test(self, req, author, content, ip): if not self.api_key: self.log.warning("API key not configured.") return # IPV4 address? HTTP:BL does not yet support IPv6 if ip.find(".") < 0: return reverse_octal = '.'.join(reversed(ip.split('.'))) addr = '%s.%s.dnsbl.httpbl.org' % (self.api_key, reverse_octal) self.log.debug('Querying Http:BL: %s', addr) try: dns_answer = query(addr) answer = [int(i) for i in str(dns_answer[0]).split('.')] if answer[0] != 127: self.log.warning('Invalid Http:BL reply for IP "%s": %s', ip, dns_answer) return # TODO: answer[1] represents number of days since last activity # and answer[2] is treat score assigned by Project Honey # Pot. We could use both to adjust karma. is_suspicious = answer[3] & 1 is_spammer = answer[3] & 4 points = 0 if is_suspicious: points -= abs(self.karma_points) / 3 if is_spammer: points -= abs(self.karma_points) if points != 0: return points, N_("IP %s blacklisted by Http:BL"), ip except NXDOMAIN: # not blacklisted on this server return except (Timeout, NoAnswer, NoNameservers), e: self.log.warning('Error checking Http:BL for IP "%s": %s', ip, e) def train(self, req, author, content, ip, spam=True): return 0 spam-filter/tracspamfilter/adminusers.py0000644000175500017550000001332212722764512020610 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from time import time from urllib import quote, unquote from trac.admin import IAdminPanelProvider from trac.config import ChoiceOption, IntOption from trac.core import Component, TracError, implements from trac.web.chrome import add_notice, add_stylesheet from tracspamfilter.api import _, ngettext from tracspamfilter.users import UserInfo class UserAdminPageProvider(Component): """Web administration panel for spam user info.""" MIN_WIKI = IntOption('spam-filter', 'spam_user_minwiki', '0', "How many wiki edits are still an unused account.", doc_domain='tracspamfilter') MAX_AGE = IntOption('spam-filter', 'spam_user_maxage', '200', "How many days no login are considered for dead accounts.", doc_domain='tracspamfilter') modes = ['overview', 'unused', 'authorized', 'all'] DEFAULT_MODE = ChoiceOption('spam-filter', 'spam_user_defaultmode', modes, "Default mode for spam user admin panel.", doc_domain='tracspamfilter') implements(IAdminPanelProvider) # IAdminPanelProvider methods def get_admin_panels(self, req): if 'SPAM_USER' in req.perm: yield ('spamfilter', _('Spam Filtering'), 'user', _('Users')) def render_admin_panel(self, req, cat, page, path_info): req.perm.require('SPAM_USER') if req.method == 'POST': if 'cleantemp' in req.args: UserInfo.deletetemporary(self.env) elif 'changeuser' in req.args: if not 'userold' in req.args or not 'usernew' in req.args: raise TracError(_('Old or new value cannot be empty')) old = req.args['userold'] new = req.args['usernew'] # for strange usernames entering names already encoded is helpful if req.args.get('encoded', 0): old = unquote(old).decode('utf8') new = unquote(new).decode('utf8') if old == new: raise TracError(_('Old and new value cannot be equal')) res = UserInfo.changeuser(self.env, old, new, req.args.get('auth', '')) if res == -3: raise TracError(_('New name cannot be used in CC fields')) elif res < 0: raise TracError(_('Illegal user arguments passed or changing not allowed')) elif res: add_notice(req, ngettext('%(num)d entry has been updated', '%(num)d entries have been updated', res)) elif 'fixemails' in req.args: users, stats = UserInfo.getinfo(self.env, 'authorized') for name,user in sorted(users.iteritems()): if user[3] and user[3] != name and ((user[4] | user[6]) & 2): res = UserInfo.changeuser(self.env, user[3], name) if res == -3: add_notice(req, _('Username \'%s\' cannot be used in CC fields') % name) elif res < 0: add_notice(req, _('Error for e-mail change for username \'%s\'') % name) elif res: add_notice(req, ngettext('%(num)d entry has been updated for user %(user)s', '%(num)d entries have been updated for user %(user)s', res, user=name)) elif res: add_notice(req, _('E-mails for user %s updated') % name) req.redirect(req.href.admin(cat, page, mode=req.args.get('mode'))) data = {} data['curtime'] = int(time()) data['maxage'] = int(req.args.get('maxage', self.MAX_AGE))*24*60*60 data['tempcount'] = len(UserInfo.gettemporary(self.env)) data['accmgr'] = 'ACCTMGR_USER_ADMIN' in req.perm mode = req.args.get('mode', self.DEFAULT_MODE) data['mode'] = mode data['encoded'] = req.args.get('encoded','') data['auth'] = req.args.get('auth','') minwiki = int(req.args.get('minwiki', self.MIN_WIKI)) if 'user' in req.args: user = req.args['user'] data['username'] = user data['user'] = UserInfo.getuserinfo(self.env, user) data['users'] = [] data['stats'] = None else: data['username'] = None data['user'] = [] users, stats = UserInfo.getinfo(self.env, mode if mode != 'unusedmulti' else 'unused', minwiki) data['users'] = users data['stats'] = stats data['quote'] = quote if mode == 'overview': data['usertype'] = _('data overview') elif mode == 'unused' or mode == 'unusedmulti': data['usertype'] = _('unused accounts') elif mode == 'authorized': data['usertype'] = _('registered accounts') elif mode == 'user': data['usertype'] = _("detailed user information for '%s'") % data['username'] else: data['usertype'] = _('everything from accounts, wiki, tickets and svn') data['entrytext'] = ngettext('%(num)d entry', '%(num)d entries', (stats['numtotal'] if mode=='overview' else len(data['users']))) add_stylesheet(req, 'spamfilter/admin.css') return 'admin_user.html', data spam-filter/tracspamfilter/locale/0000755000175500017550000000000012731667632017327 5ustar debacledebaclespam-filter/tracspamfilter/locale/cs/0000755000175500017550000000000012725137772017734 5ustar debacledebaclespam-filter/tracspamfilter/locale/cs/LC_MESSAGES/0000755000175500017550000000000012725137772021521 5ustar debacledebaclespam-filter/tracspamfilter/locale/cs/LC_MESSAGES/tracspamfilter.po0000644000175500017550000015031012725137772025101 0ustar debacledebacle# Czech translations for TracSpamFilter. # Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the TracSpamFilter # project. # # Translators: # Martin Bulant , 2015 msgid "" msgstr "" "Project-Id-Version: Trac Plugin L10N\n" "Report-Msgid-Bugs-To: trac@dstoecker.de\n" "POT-Creation-Date: 2016-03-05 18:00-0800\n" "PO-Revision-Date: 2016-02-20 12:42+0000\n" "Last-Translator: Dirk Stöcker \n" "Language-Team: Czech (http://www.transifex.com/hasienda/Trac_Plugin-" "L10N/language/cs/)\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" #: tracspamfilter/accountadapter.py:33 msgid "" "Interface of spamfilter to account manager plugin to check new account\n" "registrations for spam.\n" "\n" "It provides an additional 'Details' input field to get more information\n" "for calculating the probability of a spam registration attempt.\n" "Knowledge gained from inspecting registration attempts is shared with all" "\n" "other spam filter adapters for this system." msgstr "" #: tracspamfilter/accountadapter.py:47 msgid "Details:" msgstr "Podrobnosti:" #: tracspamfilter/adapters.py:106 msgid "" "The maximum number of bytes from an attachment to pass through\n" "the spam filters." msgstr "" #: tracspamfilter/admin.py:75 msgid "How many monitor entries are displayed by default (between 5 and 10000)." msgstr "" #: tracspamfilter/admin.py:79 msgid "Show the buttons for training without deleting entry." msgstr "" #: tracspamfilter/admin.py:87 tracspamfilter/admin.py:90 #: tracspamfilter/admin.py:315 tracspamfilter/admin.py:500 #: tracspamfilter/admin.py:593 tracspamfilter/admin.py:641 #: tracspamfilter/adminreport.py:43 tracspamfilter/adminusers.py:48 msgid "Spam Filtering" msgstr "" #: tracspamfilter/admin.py:88 tracspamfilter/templates/admin_bayes.html:19 msgid "Configuration" msgstr "Konfigurace" #: tracspamfilter/admin.py:91 msgid "Monitoring" msgstr "" #: tracspamfilter/admin.py:113 tracspamfilter/filters/bayes.py:78 #, python-format msgid "SpamBayes determined spam probability of %s%%" msgstr "" #: tracspamfilter/admin.py:115 #, python-format msgid "Select 100.00%% entries" msgstr "" #: tracspamfilter/admin.py:116 #, python-format msgid "Select >90.00%% entries" msgstr "" #: tracspamfilter/admin.py:117 #, python-format msgid "Select <10.00%% entries" msgstr "" #: tracspamfilter/admin.py:118 #, python-format msgid "Select 0.00%% entries" msgstr "" #: tracspamfilter/admin.py:119 msgid "Select Spam entries" msgstr "" #: tracspamfilter/admin.py:120 msgid "Select Ham entries" msgstr "" #: tracspamfilter/admin.py:246 tracspamfilter/adminreport.py:110 #: tracspamfilter/templates/admin_report.html:31 #: tracspamfilter/templates/admin_report.html:32 #: tracspamfilter/templates/admin_spammonitor.html:29 #: tracspamfilter/templates/admin_spammonitor.html:30 msgid "Previous Page" msgstr "Předchozí stránka" #: tracspamfilter/admin.py:250 tracspamfilter/adminreport.py:114 #: tracspamfilter/templates/admin_report.html:37 #: tracspamfilter/templates/admin_report.html:38 #: tracspamfilter/templates/admin_spammonitor.html:35 #: tracspamfilter/templates/admin_spammonitor.html:36 msgid "Next Page" msgstr "Další stránka" #: tracspamfilter/admin.py:267 msgid "Log entry not found" msgstr "" #: tracspamfilter/admin.py:272 tracspamfilter/admin.py:277 #, python-format msgid "Log Entry %(id)s" msgstr "" #: tracspamfilter/admin.py:273 msgid "Log Entry List" msgstr "" #: tracspamfilter/admin.py:316 msgid "External" msgstr "" #: tracspamfilter/admin.py:500 tracspamfilter/templates/admin_bayes.html:10 msgid "Bayes" msgstr "" #: tracspamfilter/admin.py:552 #, python-format msgid "(ratio %.1f : 1)" msgstr "(poměr %.1f : 1)" #: tracspamfilter/admin.py:554 #, python-format msgid "(ratio 1 : %.1f)" msgstr "(poměr 1 : %.1f)" #: tracspamfilter/admin.py:566 #, python-format msgid "%(num)d spam" msgid_plural "%(num)d spam" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/admin.py:568 #, python-format msgid "%(num)d ham" msgid_plural "%(num)d ham" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/admin.py:570 #, python-format msgid "%(num)d line" msgid_plural "%(num)d lines" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/admin.py:572 #, python-format msgid "%(num)d mixed" msgid_plural "%(num)d mixed" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/admin.py:594 msgid "Statistics" msgstr "Statistika" #: tracspamfilter/admin.py:641 msgid "Captcha" msgstr "" #: tracspamfilter/admin.py:663 tracspamfilter/admin.py:697 #: tracspamfilter/admin.py:706 tracspamfilter/admin.py:720 #: tracspamfilter/admin.py:729 #, python-format msgid "Invalid value for %(key)s" msgstr "" #: tracspamfilter/admin.py:673 msgid "The keys are invalid" msgstr "" #: tracspamfilter/admin.py:685 msgid "The key or user id are invalid" msgstr "" #: tracspamfilter/adminreport.py:32 msgid "How many report entries are displayed by default (between 5 and 10000)." msgstr "" #: tracspamfilter/adminreport.py:43 msgid "Reports" msgstr "" #: tracspamfilter/adminreport.py:134 msgid "Report entry not found" msgstr "" #: tracspamfilter/adminreport.py:142 tracspamfilter/adminreport.py:149 #, python-format msgid "Report Entry %d" msgstr "" #: tracspamfilter/adminreport.py:143 msgid "Report Entry List" msgstr "" #: tracspamfilter/adminusers.py:29 msgid "How many wiki edits are still an unused account." msgstr "" #: tracspamfilter/adminusers.py:33 msgid "How many days no login are considered for dead accounts." msgstr "" #: tracspamfilter/adminusers.py:38 msgid "Default mode for spam user admin panel." msgstr "" #: tracspamfilter/adminusers.py:48 msgid "Users" msgstr "Uživatelé" #: tracspamfilter/adminusers.py:58 msgid "Old or new value cannot be empty" msgstr "" #: tracspamfilter/adminusers.py:66 msgid "Old and new value cannot be equal" msgstr "Stará a nová hodnota se nesmí shodovat" #: tracspamfilter/adminusers.py:69 msgid "New name cannot be used in CC fields" msgstr "" #: tracspamfilter/adminusers.py:71 msgid "Illegal user arguments passed or changing not allowed" msgstr "" #: tracspamfilter/adminusers.py:73 #, python-format msgid "%(num)d entry has been updated" msgid_plural "%(num)d entries have been updated" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/adminusers.py:80 #, python-format msgid "Username '%s' cannot be used in CC fields" msgstr "" #: tracspamfilter/adminusers.py:82 #, python-format msgid "Error for e-mail change for username '%s'" msgstr "" #: tracspamfilter/adminusers.py:84 #, python-format msgid "%(num)d entry has been updated for user %(user)s" msgid_plural "%(num)d entries have been updated for user %(user)s" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/adminusers.py:87 #, python-format msgid "E-mails for user %s updated" msgstr "E-maily pro uživatele %s aktualizovány" #: tracspamfilter/adminusers.py:118 msgid "data overview" msgstr "" #: tracspamfilter/adminusers.py:120 msgid "unused accounts" msgstr "nepoužívané účty" #: tracspamfilter/adminusers.py:122 msgid "registered accounts" msgstr "zaregistrované účty" #: tracspamfilter/adminusers.py:124 #, python-format msgid "detailed user information for '%s'" msgstr "" #: tracspamfilter/adminusers.py:126 msgid "everything from accounts, wiki, tickets and svn" msgstr "" #: tracspamfilter/adminusers.py:127 #, python-format msgid "%(num)d entry" msgid_plural "%(num)d entries" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/filtersystem.py:52 msgid "The minimum score required for a submission to be allowed." msgstr "" #: tracspamfilter/filtersystem.py:56 msgid "" "The karma given to authenticated users, in case\n" "`trust_authenticated` is false." msgstr "" #: tracspamfilter/filtersystem.py:60 msgid "" "Whether all content submissions and spam filtering activity should\n" "be logged to the database." msgstr "" #: tracspamfilter/filtersystem.py:64 msgid "The number of days after which log entries should be purged." msgstr "" #: tracspamfilter/filtersystem.py:68 msgid "Allow usage of external services." msgstr "" #: tracspamfilter/filtersystem.py:71 msgid "" "Skip external calls when this negative karma is already reached\n" "by internal tests." msgstr "" #: tracspamfilter/filtersystem.py:75 msgid "" "Skip external calls when this positive karma is already reached\n" "by internal tests." msgstr "" #: tracspamfilter/filtersystem.py:79 msgid "Stop external calls when this negative karma is reached." msgstr "" #: tracspamfilter/filtersystem.py:83 msgid "Stop external calls when this positive karma is reached." msgstr "" #: tracspamfilter/filtersystem.py:87 msgid "Allow training of external services." msgstr "" #: tracspamfilter/filtersystem.py:90 msgid "" "Whether content submissions by authenticated users should be trusted\n" "without checking for potential spam or other abuse." msgstr "" #: tracspamfilter/filtersystem.py:96 msgid "The karma given to attachments." msgstr "" #: tracspamfilter/filtersystem.py:99 msgid "The karma given to registrations." msgstr "" #: tracspamfilter/filtersystem.py:102 msgid "The handler used to reject content." msgstr "" #: tracspamfilter/filtersystem.py:106 msgid "Interpret X-Forwarded-For header for IP checks." msgstr "" #: tracspamfilter/filtersystem.py:110 msgid "" "This section is used to handle all configurations used by\n" "spam filter plugin." msgstr "" #: tracspamfilter/filtersystem.py:169 msgid "User is authenticated" msgstr "" #: tracspamfilter/filtersystem.py:175 msgid "Attachment weighting" msgstr "" #: tracspamfilter/filtersystem.py:180 msgid "Registration weighting" msgstr "" #: tracspamfilter/filtersystem.py:304 #, python-format msgid "Submission rejected as potential spam %(message)s" msgstr "" #: tracspamfilter/model.py:422 msgid "external" msgstr "" #: tracspamfilter/model.py:422 msgid "internal" msgstr "" #: tracspamfilter/report.py:29 msgid "List of page types to add spam report link" msgstr "" #: tracspamfilter/report.py:42 msgid "No page supplied to report as spam" msgstr "" #: tracspamfilter/report.py:74 msgid "Reported spam" msgstr "" #: tracspamfilter/report.py:76 msgid "Comment" msgstr "Komentář" #: tracspamfilter/report.py:78 msgid "Report spam" msgstr "" #: tracspamfilter/report.py:82 #, python-format msgid "%(num)d spam report" msgid_plural "%(num)d spam reports" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/users.py:38 #, python-format msgid "Wiki page '%(page)s' version %(version)s modified" msgstr "" #: tracspamfilter/users.py:41 #, python-format msgid "Attachment '%s' added" msgstr "" #: tracspamfilter/users.py:45 #, python-format msgid "Ticket %(id)s field '%(field)s' changed" msgstr "" #: tracspamfilter/users.py:48 #, python-format msgid "Removed from ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:50 #, python-format msgid "Set in ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:56 #, python-format msgid "Ticket %(id)s CC field change ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:60 #, python-format msgid "Reporter of ticket %s" msgstr "" #: tracspamfilter/users.py:62 #, python-format msgid "Owner of ticket %s" msgstr "" #: tracspamfilter/users.py:64 #, python-format msgid "In CC of ticket %(id)s ('%(cc)s')" msgstr "" #: tracspamfilter/users.py:67 #, python-format msgid "Author of revision %s" msgstr "" #: tracspamfilter/users.py:70 #, python-format msgid "Component '%s' owner" msgstr "" #: tracspamfilter/users.py:73 msgid "In permissions list" msgstr "" #: tracspamfilter/users.py:76 #, python-format msgid "Author of report %d" msgstr "" #: tracspamfilter/users.py:81 tracspamfilter/users.py:85 #, python-format msgid "Voted for '%s'" msgstr "" #: tracspamfilter/captcha/api.py:59 msgid "CAPTCHA method to use for verifying humans." msgstr "" #: tracspamfilter/captcha/api.py:64 msgid "" "By how many points a successful CAPTCHA response increases the\n" "overall score." msgstr "" #: tracspamfilter/captcha/api.py:69 msgid "By how many points a failed CAPTCHA impacts the overall score." msgstr "" #: tracspamfilter/captcha/api.py:73 msgid "" "Time in seconds that a successful CAPTCHA response increases\n" "karma." msgstr "" #: tracspamfilter/captcha/api.py:77 msgid "Time in seconds before CAPTCHA is removed." msgstr "" #: tracspamfilter/captcha/api.py:81 msgid "Time in seconds before database cleanup is called." msgstr "" #: tracspamfilter/captcha/api.py:100 #, python-format msgid "Human verified via CAPTCHA (%s)" msgstr "" #: tracspamfilter/captcha/api.py:109 #, python-format msgid "Failed CAPTCHA (%s) attempts" msgstr "" #: tracspamfilter/captcha/api.py:141 msgid "CAPTCHA failed to handle original request" msgstr "" #: tracspamfilter/captcha/api.py:143 msgid "CAPTCHA verification failed" msgstr "" #: tracspamfilter/captcha/expression.py:32 msgid "Number of terms in numeric CAPTCHA expression." msgstr "" #: tracspamfilter/captcha/expression.py:36 msgid "" "Maximum value of individual terms in numeric CAPTCHA\n" "expression." msgstr "" #: tracspamfilter/captcha/expression.py:40 msgid "multiplied by" msgstr "" #: tracspamfilter/captcha/expression.py:40 msgid "minus" msgstr "mínus" #: tracspamfilter/captcha/expression.py:41 msgid "plus" msgstr "plus" #: tracspamfilter/captcha/expression.py:43 msgid "zero" msgstr "nula" #: tracspamfilter/captcha/expression.py:43 msgid "one" msgstr "jedna" #: tracspamfilter/captcha/expression.py:43 msgid "two" msgstr "dva" #: tracspamfilter/captcha/expression.py:43 msgid "three" msgstr "tři" #: tracspamfilter/captcha/expression.py:43 msgid "four" msgstr "čtyři" #: tracspamfilter/captcha/expression.py:44 msgid "five" msgstr "pět" #: tracspamfilter/captcha/expression.py:44 msgid "six" msgstr "šest" #: tracspamfilter/captcha/expression.py:44 msgid "seven" msgstr "sedm" #: tracspamfilter/captcha/expression.py:44 msgid "eight" msgstr "osm" #: tracspamfilter/captcha/expression.py:45 msgid "nine" msgstr "devět" #: tracspamfilter/captcha/expression.py:45 msgid "ten" msgstr "deset" #: tracspamfilter/captcha/expression.py:45 msgid "eleven" msgstr "jedenáct" #: tracspamfilter/captcha/expression.py:45 msgid "twelve" msgstr "dvanáct" #: tracspamfilter/captcha/expression.py:46 msgid "thirteen" msgstr "třináct" #: tracspamfilter/captcha/expression.py:46 msgid "fourteen" msgstr "čtrnáct" #: tracspamfilter/captcha/expression.py:46 msgid "fifteen" msgstr "patnáct" #: tracspamfilter/captcha/expression.py:47 msgid "sixteen" msgstr "šestnáct" #: tracspamfilter/captcha/expression.py:47 msgid "seventeen" msgstr "sedmnáct" #: tracspamfilter/captcha/expression.py:47 msgid "eighteen" msgstr "osmnáct" #: tracspamfilter/captcha/expression.py:48 msgid "nineteen" msgstr "devatenáct" #. TRANSLATOR: if compound numbers like in english are not #. supported, simply add a "plus" command to the following #. translations! #: tracspamfilter/captcha/expression.py:53 msgid "twenty" msgstr "dvacet" #: tracspamfilter/captcha/expression.py:53 msgid "thirty" msgstr "třicet" #: tracspamfilter/captcha/expression.py:53 msgid "forty" msgstr "čtyřicet" #: tracspamfilter/captcha/expression.py:53 msgid "fifty" msgstr "padesát" #: tracspamfilter/captcha/expression.py:54 msgid "sixty" msgstr "šedesát" #: tracspamfilter/captcha/expression.py:54 msgid "seventy" msgstr "sedmdesát" #: tracspamfilter/captcha/expression.py:54 msgid "eighty" msgstr "osmdesát" #: tracspamfilter/captcha/expression.py:54 msgid "ninety" msgstr "devadesát" #: tracspamfilter/captcha/expression.py:61 msgid "Numeric captcha can not represent numbers > 100" msgstr "" #: tracspamfilter/captcha/image.py:43 msgid "Set of fonts to choose from when generating image CAPTCHA." msgstr "" #: tracspamfilter/captcha/image.py:47 msgid "Font size to use in image CAPTCHA." msgstr "" #: tracspamfilter/captcha/image.py:51 msgid "Alphabet to choose image CAPTCHA challenge from." msgstr "" #: tracspamfilter/captcha/image.py:56 msgid "Number of letters to use in image CAPTCHA challenge." msgstr "" #: tracspamfilter/captcha/keycaptcha.py:32 msgid "Private key for KeyCaptcha usage." msgstr "" #: tracspamfilter/captcha/keycaptcha.py:36 msgid "User id for KeyCaptcha usage." msgstr "" #: tracspamfilter/captcha/recaptcha.py:32 #: tracspamfilter/captcha/recaptcha2.py:34 msgid "Private key for reCaptcha usage." msgstr "" #: tracspamfilter/captcha/recaptcha.py:36 #: tracspamfilter/captcha/recaptcha2.py:38 msgid "Public key for reCaptcha usage." msgstr "" #: tracspamfilter/captcha/recaptcha.py:59 #: tracspamfilter/captcha/recaptcha2.py:50 #: tracspamfilter/templates/verify_captcha.html:24 msgid "Submit" msgstr "" #: tracspamfilter/filters/akismet.py:42 msgid "" "By how many points an Akismet reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/akismet.py:46 msgid "Wordpress key required to use the Akismet API." msgstr "" #: tracspamfilter/filters/akismet.py:50 msgid "URL of the Akismet service." msgstr "" #: tracspamfilter/filters/akismet.py:75 msgid "Akismet says content is spam" msgstr "" #: tracspamfilter/filters/bayes.py:33 msgid "" "By what factor Bayesian spam probability score affects the overall\n" "karma of a submission." msgstr "" #: tracspamfilter/filters/bayes.py:37 msgid "" "The minimum number of submissions in the training database required\n" "for the filter to start impacting the karma of submissions." msgstr "" #: tracspamfilter/filters/bayes.py:42 msgid "" "Entries with a count less than this value get removed from the\n" "database when calling the reduce function." msgstr "" #: tracspamfilter/filters/blogspam.py:35 msgid "" "By how many points an BlogSpam reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/blogspam.py:39 msgid "URL of the BlogSpam service." msgstr "" #: tracspamfilter/filters/blogspam.py:42 msgid "Comma separated list of tests to skip." msgstr "" #: tracspamfilter/filters/blogspam.py:64 #, python-format msgid "BlogSpam says content is spam (%s [%s])" msgstr "" #: tracspamfilter/filters/botscout.py:31 msgid "" "By how many points a BotScout reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/botscout.py:35 msgid "API key required to use BotScout." msgstr "" #: tracspamfilter/filters/botscout.py:61 #, python-format msgid "BotScout says this is spam (%s)" msgstr "" #: tracspamfilter/filters/extlinks.py:28 msgid "" "By how many points too many external links in a submission impact\n" "the overall score." msgstr "" #: tracspamfilter/filters/extlinks.py:32 msgid "" "The maximum number of external links allowed in a submission until\n" "that submission gets negative karma." msgstr "" #: tracspamfilter/filters/extlinks.py:36 msgid "List of domains that should be allowed in external links" msgstr "" #: tracspamfilter/filters/extlinks.py:64 msgid "Maximum number of external links per post exceeded" msgstr "" #: tracspamfilter/filters/extlinks.py:67 msgid "External links in post found" msgstr "" #: tracspamfilter/filters/fspamlist.py:32 msgid "" "By how many points a FSpamList reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/fspamlist.py:36 msgid "API key required to use FSpamList." msgstr "" #: tracspamfilter/filters/fspamlist.py:67 #, python-format msgid "FSpamList says this is spam (%s)" msgstr "" #: tracspamfilter/filters/httpbl.py:34 msgid "" "By how many points listing as \"comment spammer\" impacts the\n" "overall karma of a submission." msgstr "" #: tracspamfilter/filters/httpbl.py:38 msgid "Http:BL API key required for use." msgstr "" #: tracspamfilter/filters/httpbl.py:81 #, python-format msgid "IP %s blacklisted by Http:BL" msgstr "" #: tracspamfilter/filters/ip_blacklist.py:34 msgid "" "By how many points blacklisting by a single server impacts the\n" "overall karma of a submission." msgstr "" #: tracspamfilter/filters/ip_blacklist.py:39 msgid "Servers used for IPv4 blacklisting." msgstr "" #: tracspamfilter/filters/ip_blacklist.py:43 msgid "Servers used for IPv6 blacklisting." msgstr "" #: tracspamfilter/filters/ip_blacklist.py:87 #, python-format msgid "IP %s blacklisted by %s" msgstr "" #: tracspamfilter/filters/ip_regex.py:34 msgid "" "By how many points a match with a pattern on the BadIP page\n" "impacts the overall karma of a submission." msgstr "" #: tracspamfilter/filters/ip_regex.py:37 msgid "" "Local file to be loaded to get BadIP. Can be used in\n" "addition to BadIP wiki page." msgstr "" #: tracspamfilter/filters/ip_regex.py:40 msgid "Show the matched bad IP patterns in rejection message." msgstr "" #: tracspamfilter/filters/ip_regex.py:76 #, python-format msgid "IP catched by these blacklisted patterns: %s" msgstr "" #: tracspamfilter/filters/ip_regex.py:78 #, python-format msgid "IP catched by %s blacklisted patterns" msgstr "" #: tracspamfilter/filters/ip_throttle.py:27 msgid "" "By how many points exceeding the configured maximum number of posts\n" "per hour impacts the overall score." msgstr "" #: tracspamfilter/filters/ip_throttle.py:31 msgid "" "The maximum allowed number of submissions per hour from a single IP\n" "address. If this limit is exceeded, subsequent submissions get negative\n" "karma." msgstr "" #: tracspamfilter/filters/ip_throttle.py:52 msgid "Maximum number of posts per hour for this IP exceeded" msgstr "" #: tracspamfilter/filters/mollom.py:36 msgid "" "By how many points an Mollom reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/mollom.py:40 msgid "Public key required to use the Mollom API." msgstr "" #: tracspamfilter/filters/mollom.py:44 msgid "Private key required to use the Mollom API." msgstr "" #: tracspamfilter/filters/mollom.py:48 msgid "URL of the Mollom service." msgstr "" #: tracspamfilter/filters/mollom.py:95 msgid "Mollom says content is spam" msgstr "" #: tracspamfilter/filters/mollom.py:106 msgid "Mollom says content is ham" msgstr "" #: tracspamfilter/filters/regex.py:31 msgid "" "By how many points a match with a pattern on the BadContent page\n" "impacts the overall karma of a submission." msgstr "" #: tracspamfilter/filters/regex.py:34 msgid "" "Local file to be loaded to get BadContent. Can be used in\n" "addition to BadContent wiki page." msgstr "" #: tracspamfilter/filters/regex.py:37 msgid "Show the matched bad content patterns in rejection message." msgstr "" #: tracspamfilter/filters/regex.py:77 #, python-format msgid "Content contained these blacklisted patterns: %s" msgstr "" #: tracspamfilter/filters/regex.py:79 #, python-format msgid "Content contained %s blacklisted patterns" msgstr "" #: tracspamfilter/filters/registration.py:28 msgid "" "By how many points a failed registration check impacts\n" "the overall score." msgstr "" #: tracspamfilter/filters/registration.py:32 msgid "Replace checks in account manager totally." msgstr "" #: tracspamfilter/filters/registration.py:84 #, python-format msgid "Account registration failed (%s)" msgstr "" #: tracspamfilter/filters/session.py:26 msgid "" "By how many points an existing and configured session improves the\n" "overall karma of the submission. A third of the points is granted for\n" "having an existing session at all, the other two thirds are granted\n" "when the user has his name and/or email address set in the session,\n" "respectively." msgstr "" #: tracspamfilter/filters/session.py:48 msgid "Existing session found" msgstr "" #: tracspamfilter/filters/stopforumspam.py:30 msgid "" "By how many points a StopForumSpam reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/stopforumspam.py:34 msgid "API key used to report SPAM." msgstr "" #: tracspamfilter/filters/stopforumspam.py:62 #, python-format msgid "StopForumSpam says this is spam (%s)" msgstr "" #: tracspamfilter/filters/trapfield.py:27 msgid "" "By how many points a trap reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/trapfield.py:30 msgid "" "Name of the invisible trap field, should contain some reference\n" "to e-mail for better results." msgstr "" #: tracspamfilter/filters/trapfield.py:33 msgid "" "Name of the hidden trap field, should contain some reference\n" "to e-mail for better results." msgstr "" #: tracspamfilter/filters/trapfield.py:36 msgid "" "Name of the register trap field, should contain some reference\n" "to web/homepage for better results." msgstr "" #: tracspamfilter/filters/trapfield.py:62 #, python-format msgid "Both trap fields says this is spam (%s, %s)" msgstr "" #: tracspamfilter/filters/trapfield.py:65 #, python-format msgid "Invisible trap field says this is spam (%s)" msgstr "" #: tracspamfilter/filters/trapfield.py:68 #, python-format msgid "Hidden trap field says this is spam (%s)" msgstr "" #: tracspamfilter/filters/trapfield.py:71 #, python-format msgid "Register trap field starts with HTTP URL (%s)" msgstr "" #: tracspamfilter/filters/url_blacklist.py:33 msgid "" "By how many points blacklisting by a single bad URL impacts the\n" "overall karma of a submission." msgstr "" #: tracspamfilter/filters/url_blacklist.py:38 msgid "Servers used for URL blacklisting." msgstr "" #: tracspamfilter/filters/url_blacklist.py:81 #, python-format msgid "URL's blacklisted by %s" msgstr "" #: tracspamfilter/templates/admin_bayes.html:14 msgid "Spam Filtering: Bayes" msgstr "" #: tracspamfilter/templates/admin_bayes.html:20 #, python-format msgid "" "The bayesian filter requires training before it can effectively\n" " differentiate between spam and ham. The training database " "currently\n" " contains [1:%(numspam)s spam] and\n" " [2:%(numham)s ham] %(ratio)s submissions." msgstr "" #: tracspamfilter/templates/admin_bayes.html:25 #, python-format msgid "" "The bayesian\n" " database contains currently %(lines)s lines with trained words " "(%(spamtext)s,\n" " %(hamtext)s, %(mixedtext)s)." msgstr "" #: tracspamfilter/templates/admin_bayes.html:30 #, python-format msgid "" "[1:]\n" " Reduce training database (%(linestext)s)" msgstr "" #: tracspamfilter/templates/admin_bayes.html:34 msgid "" "Reducing the training database can help when it got very large\n" " and tests take too long." msgstr "" #: tracspamfilter/templates/admin_bayes.html:40 msgid "Minimum database count:" msgstr "" #: tracspamfilter/templates/admin_bayes.html:44 msgid "" "Any database lines with less entries is removed when reducing\n" " the database." msgstr "" #: tracspamfilter/templates/admin_bayes.html:52 msgid "Clear training database" msgstr "" #: tracspamfilter/templates/admin_bayes.html:55 msgid "" "Resetting the training database can help when training was incorrect\n" " and is producing bad results." msgstr "" #: tracspamfilter/templates/admin_bayes.html:61 msgid "Minimum training required:" msgstr "" #: tracspamfilter/templates/admin_bayes.html:65 msgid "" "The minimum number of spam and ham in the training database before\n" " the filter starts affecting the karma of submissions." msgstr "" #: tracspamfilter/templates/admin_bayes.html:71 #: tracspamfilter/templates/admin_captcha.html:173 #: tracspamfilter/templates/admin_external.html:263 #: tracspamfilter/templates/admin_spamconfig.html:123 msgid "Apply changes" msgstr "" #: tracspamfilter/templates/admin_bayes.html:76 msgid "Training" msgstr "" #: tracspamfilter/templates/admin_bayes.html:77 msgid "" "While you can train the spam filter from the “[1:Spam\n" " Filtering → Monitoring]” panel in the web\n" " administration interface, you can also manually train the " "filter by\n" " entering samples here, or check what kind of spam probability\n" " currently gets assigned to the content." msgstr "" #: tracspamfilter/templates/admin_bayes.html:85 msgid "Content:" msgstr "Obsah:" #: tracspamfilter/templates/admin_bayes.html:91 #, python-format msgid "Error: %(error)s" msgstr "" #: tracspamfilter/templates/admin_bayes.html:92 #, python-format msgid "Score: %(score)s%" msgstr "" #: tracspamfilter/templates/admin_bayes.html:95 #: tracspamfilter/templates/admin_statistics.html:36 msgid "Test" msgstr "" #: tracspamfilter/templates/admin_bayes.html:97 msgid "Train as Spam" msgstr "" #: tracspamfilter/templates/admin_bayes.html:98 msgid "Train as Ham" msgstr "" #: tracspamfilter/templates/admin_captcha.html:11 msgid "Captcha handling" msgstr "" #: tracspamfilter/templates/admin_captcha.html:15 msgid "Spam Filtering: Captcha handling" msgstr "" #: tracspamfilter/templates/admin_captcha.html:22 msgid "Enable captcha usage" msgstr "" #: tracspamfilter/templates/admin_captcha.html:29 msgid "Captcha type:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:39 msgid "Maximum captcha lifetime in seconds" msgstr "" #: tracspamfilter/templates/admin_captcha.html:47 msgid "reCAPTCHA" msgstr "" #: tracspamfilter/templates/admin_captcha.html:48 msgid "" "The reCAPTCHA system provides a very good captcha system based on\n" " scanned books. See\n" " [1:Google\n" " reCAPTCHA] page. You need to obtain\n" " API keys to use the service, which is freely available for " "personal use." msgstr "" #: tracspamfilter/templates/admin_captcha.html:58 #: tracspamfilter/templates/admin_external.html:201 msgid "Public key:" msgstr "Veřejný klíč:" #: tracspamfilter/templates/admin_captcha.html:65 #: tracspamfilter/templates/admin_captcha.html:97 msgid "Private key:" msgstr "Privátní klíč:" #: tracspamfilter/templates/admin_captcha.html:75 #: tracspamfilter/templates/admin_captcha.html:106 msgid "Key validation failed:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:80 msgid "KeyCaptcha" msgstr "" #: tracspamfilter/templates/admin_captcha.html:81 msgid "" "The KeyCatcha system provides a captcha system based on JavaScript\n" " functions to reassemble a picture. See\n" " [1:KeyCaptcha]\n" " page. You need to obtain an API key to use the service, which" " is\n" " freely available for limited use." msgstr "" #: tracspamfilter/templates/admin_captcha.html:91 msgid "User ID:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:111 msgid "Text captcha" msgstr "" #: tracspamfilter/templates/admin_captcha.html:112 msgid "" "The text captcha constructs easy text questions. They can be\n" " broken relatively easy." msgstr "" #: tracspamfilter/templates/admin_captcha.html:119 msgid "Maximum value in a term:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:125 msgid "Number of terms:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:135 msgid "Image captcha" msgstr "" #: tracspamfilter/templates/admin_captcha.html:136 msgid "" "The image captcha constructs obstructed images using Python\n" " imaging library." msgstr "" #: tracspamfilter/templates/admin_captcha.html:143 msgid "Number of letters:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:149 msgid "Font size:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:155 msgid "Alphabet:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:161 msgid "Fonts:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:174 #: tracspamfilter/templates/admin_external.html:264 msgid "Revert changes" msgstr "" #: tracspamfilter/templates/admin_external.html:10 msgid "External services" msgstr "" #: tracspamfilter/templates/admin_external.html:14 msgid "Spam Filtering: External services" msgstr "" #: tracspamfilter/templates/admin_external.html:17 msgid "An error checking supplied data occured, see below for details." msgstr "" #: tracspamfilter/templates/admin_external.html:24 msgid "Use external services" msgstr "" #: tracspamfilter/templates/admin_external.html:32 msgid "Train external services" msgstr "" #: tracspamfilter/templates/admin_external.html:36 msgid "" "Skip external services, when internal tests reach a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" #: tracspamfilter/templates/admin_external.html:46 msgid "" "Stop external services, when reached a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" #: tracspamfilter/templates/admin_external.html:58 msgid "" "The Akismet filter uses the free\n" " [1:Akismet]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" #: tracspamfilter/templates/admin_external.html:65 #: tracspamfilter/templates/admin_external.html:135 #: tracspamfilter/templates/admin_external.html:152 #: tracspamfilter/templates/admin_external.html:168 #: tracspamfilter/templates/admin_external.html:185 msgid "API key:" msgstr "" #: tracspamfilter/templates/admin_external.html:71 #: tracspamfilter/templates/admin_external.html:91 #: tracspamfilter/templates/admin_external.html:213 msgid "URL:" msgstr "" #: tracspamfilter/templates/admin_external.html:77 #: tracspamfilter/templates/admin_external.html:219 #: tracspamfilter/templates/admin_external.html:224 #, python-format msgid "[1:Key validation failed:] %(error)s" msgstr "" #: tracspamfilter/templates/admin_external.html:85 msgid "" "The BlogSpam filter uses the free\n" " [1:BlogSpam]\n" " service to decide if content submissions are potential spam." msgstr "" #: tracspamfilter/templates/admin_external.html:97 msgid "Tests to skip (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:100 msgid "Possible Values:" msgstr "" #: tracspamfilter/templates/admin_external.html:105 msgid "method" msgstr "" #: tracspamfilter/templates/admin_external.html:106 msgid "description" msgstr "" #: tracspamfilter/templates/admin_external.html:107 msgid "author" msgstr "autor" #: tracspamfilter/templates/admin_external.html:127 msgid "" "The StopForumSpam filter uses the\n" " [1:StopForumSpam]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to report SPAM to the service, which is freely " "available." msgstr "" #: tracspamfilter/templates/admin_external.html:144 msgid "" "The BotScout filter uses the\n" " [1:BotScout]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" #: tracspamfilter/templates/admin_external.html:161 msgid "" "The FSpamList filter uses the\n" " [1:FSpamList]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" #: tracspamfilter/templates/admin_external.html:177 msgid "" "The HTTP_BL filter uses the free\n" " [1:HTTP:BL]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" #: tracspamfilter/templates/admin_external.html:194 msgid "" "The Mollom filter uses the free\n" " [1:Mollom]\n" " service to decide if content submissions are potential spam. " "You need to obtain\n" " API keys to use the service, which are freely available for " "personal use." msgstr "" #: tracspamfilter/templates/admin_external.html:207 msgid "Secret key:" msgstr "" #: tracspamfilter/templates/admin_external.html:229 msgid "Free access blacklists" msgstr "" #: tracspamfilter/templates/admin_external.html:231 msgid "IPv4 Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:235 #: tracspamfilter/templates/admin_external.html:242 #: tracspamfilter/templates/admin_external.html:249 #, python-format msgid "(default: %(list)s)" msgstr "" #: tracspamfilter/templates/admin_external.html:238 msgid "IPv6 Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:245 msgid "URL Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:251 msgid "" "A list of DNS blacklists can be found at the [1:RBLCheck]\n" " or [2:MultiRBL] services." msgstr "" #: tracspamfilter/templates/admin_external.html:256 msgid "" "You can enable or disable these filters from the “[1:General →\n" " Plugins]” panel of the web administration interface." msgstr "" #: tracspamfilter/templates/admin_report.html:10 msgid "Spam Reports" msgstr "" #: tracspamfilter/templates/admin_report.html:19 msgid "Spam Filtering: Reports" msgstr "" #: tracspamfilter/templates/admin_report.html:22 #: tracspamfilter/templates/admin_spammonitor.html:20 #, python-format msgid "Viewing entries %(start)s – %(end)s of %(total)s." msgstr "" #: tracspamfilter/templates/admin_report.html:50 #: tracspamfilter/templates/monitortable.html:13 msgid "Path" msgstr "" #: tracspamfilter/templates/admin_report.html:51 #: tracspamfilter/templates/monitortable.html:14 msgid "Author" msgstr "Autor" #: tracspamfilter/templates/admin_report.html:52 #: tracspamfilter/templates/monitortable.html:17 msgid "Date/time" msgstr "Datum/čas" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:44 msgid "User was logged in" msgstr "" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:44 msgid "User was not logged in" msgstr "" #: tracspamfilter/templates/admin_report.html:79 #: tracspamfilter/templates/monitortable.html:64 msgid "No data available" msgstr "" #: tracspamfilter/templates/admin_report.html:90 #: tracspamfilter/templates/admin_spammonitor.html:60 msgid "Delete selected" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:10 msgid "Spam Report" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:20 #: tracspamfilter/templates/admin_reportentry.html:21 msgid "Previous Report Entry" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:25 #: tracspamfilter/templates/admin_spamentry.html:25 msgid "Back to List" msgstr "Zpět do seznamu" #: tracspamfilter/templates/admin_reportentry.html:30 #: tracspamfilter/templates/admin_reportentry.html:31 msgid "Next Report Entry" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:35 msgid "Spam Filtering: Report" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:39 msgid "Report Entry:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:40 #: tracspamfilter/templates/admin_spamentry.html:40 msgid "Information" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:42 #: tracspamfilter/templates/admin_spamentry.html:42 msgid "Time:" msgstr "Čas:" #: tracspamfilter/templates/admin_reportentry.html:45 #: tracspamfilter/templates/admin_spamentry.html:45 msgid "Path:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:48 #: tracspamfilter/templates/admin_spamentry.html:48 msgid "Author:" msgstr "Autor:" #: tracspamfilter/templates/admin_reportentry.html:51 #: tracspamfilter/templates/admin_spamentry.html:51 msgid "Authenticated:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:47 #: tracspamfilter/templates/usertable.html:49 msgid "yes" msgstr "ano" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:48 #: tracspamfilter/templates/usertable.html:50 msgid "no" msgstr "ne" #: tracspamfilter/templates/admin_reportentry.html:54 msgid "Comment:" msgstr "Komentář:" #: tracspamfilter/templates/admin_reportentry.html:58 #: tracspamfilter/templates/admin_spamentry.html:75 msgid "HTTP headers" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:63 #: tracspamfilter/templates/admin_spamentry.html:82 msgid "Delete" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:68 msgid "Possibly related log entries:" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:10 msgid "Spam Filter" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:14 msgid "Spam Filtering: Configuration" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:15 msgid "" "See [1:wiki page]\n" " for a short documentation." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:16 msgid "Help translating this plugin at [1:Transifex]." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:21 msgid "Karma Tuning" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:23 msgid "Minimum karma required for a successful submission:" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:29 msgid "" "Karma assigned to attachments (e.g. to allow relaxed rules for file " "uploads):" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:35 msgid "" "Karma assigned to registering users (e.g. negative value to increase " "captcha usage):" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:41 msgid "" "Content submissions are passed through a set of registered and enabled\n" " [1:filter strategies], each of which check the submitted " "content\n" " and may assign [2:karma points] to it. The sum of these karma\n" " points needs to be greater than or equal to the minimum karma\n" " configured here for the submission to be accepted." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:50 #: tracspamfilter/templates/admin_statistics.html:34 msgid "Strategy" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:51 msgid "Karma points" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:52 msgid "Description" msgstr "Popis" #: tracspamfilter/templates/admin_spamconfig.html:66 msgid "Logging" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:70 msgid "Enable" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:74 msgid "" "The spam filter plugin can optionally log every content submission so\n" " that you can monitor and tune the effectiveness of the " "filtering. The\n" " log is stored in the database, and can be viewed under “[1:Spam" "\n" " Filtering → Monitoring]” from the web administration\n" " interface." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:82 msgid "" "Purge old entries after\n" " [1:]\n" " days" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:90 #, python-format msgid "" "Number of entries in log message display\n" " (%(min)s-%(max)s)\n" " [1:]" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:100 msgid "Authenticated" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:104 msgid "Trust authenticated users" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:108 msgid "" "If authenticated users should not be trusted automatically, this\n" " option must be disabled. Instead of full trust the supplied " "karma\n" " value is used in this case." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:114 msgid "Karma of authenticated users:" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:10 #: tracspamfilter/templates/admin_spammonitor.html:10 msgid "Spam Monitoring" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:20 #: tracspamfilter/templates/admin_spamentry.html:21 msgid "Previous Log Entry" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:30 #: tracspamfilter/templates/admin_spamentry.html:31 msgid "Next Log Entry" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:35 #: tracspamfilter/templates/admin_spammonitor.html:14 msgid "Spam Filtering: Monitoring" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:39 msgid "Log Entry:" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:54 msgid "IP address:" msgstr "IP adresa:" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "spam" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "ham" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:60 msgid "Karma:" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:61 #, python-format msgid "" "[1:%(karma)s]\n" " (marked as %(spam_or_ham)s)\n" " [2:\n" " [3:%(reasons)s]\n" " ]" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:71 msgid "Submitted content" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:80 msgid "Mark as Spam" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:81 msgid "Mark as Ham" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:83 msgid "Delete as Spam" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:84 msgid "Delete as Ham" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:85 msgid "Delete (No Statistics)" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:93 msgid "Remove registered user" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:99 msgid "Search for user name" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Note:" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Logging by the spam filter is currently disabled." msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:52 msgid "Delete > 90%" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:56 msgid "Mark selected as Spam" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:58 msgid "Mark selected as Ham" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:62 msgid "Delete selected as Spam" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:64 msgid "Delete selected as Ham" msgstr "" #: tracspamfilter/templates/admin_statistics.html:10 msgid "Spam Statistics" msgstr "" #: tracspamfilter/templates/admin_statistics.html:14 msgid "Spam Filtering: Statistics" msgstr "" #: tracspamfilter/templates/admin_statistics.html:17 msgid "No submission statistics yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:21 #, python-format msgid "" "%(count)s Submissions tested since %(time)s,\n" " %(spam)s spam and\n" " %(ham)s ham\n" " (%(local)s of the tests could be solved local).\n" " %(spamerr)s spam and %(hamerr)s ham have been retrained." msgstr "" #: tracspamfilter/templates/admin_statistics.html:35 msgid "Type" msgstr "" #: tracspamfilter/templates/admin_statistics.html:37 msgid "Train / Verify" msgstr "" #: tracspamfilter/templates/admin_statistics.html:38 msgid "Errors" msgstr "" #: tracspamfilter/templates/admin_statistics.html:43 #: tracspamfilter/templates/admin_statistics.html:45 msgid "Spam" msgstr "" #: tracspamfilter/templates/admin_statistics.html:44 #: tracspamfilter/templates/admin_statistics.html:46 msgid "Ham" msgstr "" #: tracspamfilter/templates/admin_statistics.html:49 #: tracspamfilter/templates/admin_statistics.html:56 #: tracspamfilter/templates/admin_statistics.html:59 msgid "Total" msgstr "Celkem" #: tracspamfilter/templates/admin_statistics.html:50 msgid "Mean Delay" msgstr "" #: tracspamfilter/templates/admin_statistics.html:51 msgid "No Result" msgstr "" #: tracspamfilter/templates/admin_statistics.html:52 #: tracspamfilter/templates/admin_statistics.html:54 msgid "Match" msgstr "" #: tracspamfilter/templates/admin_statistics.html:53 #: tracspamfilter/templates/admin_statistics.html:55 msgid "Mismatch" msgstr "" #: tracspamfilter/templates/admin_statistics.html:57 #: tracspamfilter/templates/admin_statistics.html:60 msgid "Right" msgstr "" #: tracspamfilter/templates/admin_statistics.html:58 #: tracspamfilter/templates/admin_statistics.html:61 msgid "Wrong" msgstr "" #: tracspamfilter/templates/admin_statistics.html:69 msgid "No tests yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:72 #, python-format msgid "%(seconds)s s" msgstr "" #: tracspamfilter/templates/admin_statistics.html:101 msgid "No spam yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:119 msgid "No ham yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:141 msgid "Clear statistics" msgstr "" #: tracspamfilter/templates/admin_statistics.html:151 msgid "Clear statistics database" msgstr "" #: tracspamfilter/templates/admin_user.html:10 msgid "Spam User Handling" msgstr "" #: tracspamfilter/templates/admin_user.html:14 #, python-format msgid "" "Spam Filtering: User handling (%(type)s)\n" " [1:%(count)s]" msgstr "" #: tracspamfilter/templates/admin_user.html:20 msgid "Overview" msgstr "" #: tracspamfilter/templates/admin_user.html:21 msgid "All" msgstr "" #: tracspamfilter/templates/admin_user.html:22 #: tracspamfilter/templates/usertable.html:15 msgid "Registered" msgstr "" #: tracspamfilter/templates/admin_user.html:23 msgid "Unused [multi selection]" msgstr "" #: tracspamfilter/templates/admin_user.html:24 msgid "Unused" msgstr "" #: tracspamfilter/templates/admin_user.html:28 #, python-format msgid "" "There are %(total)s\n" " different entries in the database, %(registered)s users are\n" " registered and %(unused)s have not been used." msgstr "" #: tracspamfilter/templates/admin_user.html:35 msgid "Date" msgstr "Datum" #: tracspamfilter/templates/admin_user.html:36 #, python-format msgid "Action of user '%(username)s'" msgstr "" #: tracspamfilter/templates/admin_user.html:55 msgid "Remove selected" msgstr "" #: tracspamfilter/templates/admin_user.html:63 msgid "Values must be URL encoded!" msgstr "" #: tracspamfilter/templates/admin_user.html:65 msgid "Old user:" msgstr "" #: tracspamfilter/templates/admin_user.html:68 msgid "New user:" msgstr "" #: tracspamfilter/templates/admin_user.html:74 msgid "Change unauthorized user" msgstr "" #: tracspamfilter/templates/admin_user.html:74 msgid "Change user" msgstr "" #: tracspamfilter/templates/admin_user.html:81 #, python-format msgid "Remove %(num)s temporary session" msgid_plural "Remove %(num)s temporary sessions" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: tracspamfilter/templates/admin_user.html:86 msgid "Convert emails to registered usernames" msgstr "" #: tracspamfilter/templates/monitortable.html:15 msgid "IP Address" msgstr "IP adresa" #: tracspamfilter/templates/monitortable.html:16 msgid "Karma" msgstr "" #: tracspamfilter/templates/usertable.html:13 msgid "User name" msgstr "" #: tracspamfilter/templates/usertable.html:14 msgid "Last login" msgstr "" #: tracspamfilter/templates/usertable.html:16 msgid "Setup" msgstr "" #: tracspamfilter/templates/usertable.html:17 msgid "E-Mail" msgstr "E-mail" #: tracspamfilter/templates/usertable.html:18 msgid "Wiki edits" msgstr "" #: tracspamfilter/templates/usertable.html:19 msgid "Ticket edits" msgstr "" #: tracspamfilter/templates/usertable.html:20 msgid "SVN edits" msgstr "" #: tracspamfilter/templates/usertable.html:21 msgid "Other" msgstr "" #: tracspamfilter/templates/usertable.html:39 msgid "Source" msgstr "" #: tracspamfilter/templates/usertable.html:49 #: tracspamfilter/templates/usertable.html:50 msgid "(password)" msgstr "" #: tracspamfilter/templates/usertable.html:52 msgid "(double)" msgstr "" #: tracspamfilter/templates/usertable.html:55 #: tracspamfilter/templates/usertable.html:61 #: tracspamfilter/templates/usertable.html:67 #: tracspamfilter/templates/usertable.html:73 msgid "user" msgstr "uživatel" #: tracspamfilter/templates/usertable.html:56 #: tracspamfilter/templates/usertable.html:62 #: tracspamfilter/templates/usertable.html:68 #: tracspamfilter/templates/usertable.html:74 msgid "e-mail" msgstr "e-mail" #: tracspamfilter/templates/usertable.html:57 #: tracspamfilter/templates/usertable.html:63 #: tracspamfilter/templates/usertable.html:69 #: tracspamfilter/templates/usertable.html:75 msgid "both" msgstr "" #: tracspamfilter/templates/usertable.html:83 msgid "Remove" msgstr "" #: tracspamfilter/templates/verify_captcha.html:13 msgid "Captcha Error" msgstr "" #: tracspamfilter/templates/verify_captcha.html:17 msgid "" "Trac thinks your submission might be Spam.\n" " To prove otherwise please provide a response to the following." msgstr "" #: tracspamfilter/templates/verify_captcha.html:19 msgid "" "Note - the captcha method is choosen randomly. Retry if the captcha does " "not work on your system!" msgstr "" #: tracspamfilter/templates/verify_captcha.html:22 msgid "Response:" msgstr "" #~ msgid "Lifetime has invalid value" #~ msgstr "" #~ msgid "Text values are not numeric" #~ msgstr "" #~ msgid "Numeric image values are no numbers" #~ msgstr "" #~ msgid "" #~ msgstr "" #~ msgid "Data validation failed:" #~ msgstr "" spam-filter/tracspamfilter/locale/ko/0000755000175500017550000000000012725137772017740 5ustar debacledebaclespam-filter/tracspamfilter/locale/ko/LC_MESSAGES/0000755000175500017550000000000012725137772021525 5ustar debacledebaclespam-filter/tracspamfilter/locale/ko/LC_MESSAGES/tracspamfilter.po0000644000175500017550000021764512725137772025124 0ustar debacledebacle# Korean translations for TracSpamFilter. # Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the TracSpamFilter # project. # # Translators: # theYT , 2014-2016 msgid "" msgstr "" "Project-Id-Version: Trac Plugin L10N\n" "Report-Msgid-Bugs-To: trac@dstoecker.de\n" "POT-Creation-Date: 2016-03-05 18:00-0800\n" "PO-Revision-Date: 2016-02-20 13:47+0000\n" "Last-Translator: theYT \n" "Language-Team: Korean (http://www.transifex.com/hasienda/Trac_Plugin-" "L10N/language/ko/)\n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" #: tracspamfilter/accountadapter.py:33 msgid "" "Interface of spamfilter to account manager plugin to check new account\n" "registrations for spam.\n" "\n" "It provides an additional 'Details' input field to get more information\n" "for calculating the probability of a spam registration attempt.\n" "Knowledge gained from inspecting registration attempts is shared with all" "\n" "other spam filter adapters for this system." msgstr "" "신규 가입 과정에서 스팸을 검사하기 위한, 사용자 계정 플러그인에 대한 스팸 필터 인터페이스입니다.\n" "\n" "스팸의 등록 시도 가능성을 계산하기 위한 추가 정보를 얻기 위하여, '상세 내용' 입력 필드를 추가합니다.\n" "등록 시도를 검사하여 얻은 정보는 시스템에 있는 다른 모든 스팸 필터 어댑터와 공유됩니다." #: tracspamfilter/accountadapter.py:47 msgid "Details:" msgstr "상세 내용:" #: tracspamfilter/adapters.py:106 msgid "" "The maximum number of bytes from an attachment to pass through\n" "the spam filters." msgstr "스팸 필터로 전달할 첨부 파일의 최대 바이트 수입니다." #: tracspamfilter/admin.py:75 msgid "How many monitor entries are displayed by default (between 5 and 10000)." msgstr "몇 개의 감시 항목을 기본으로 표시할지를 지정합니다 (5에서 10000 사이)." #: tracspamfilter/admin.py:79 msgid "Show the buttons for training without deleting entry." msgstr "항목을 지우지 않고 학습시키는 버튼을 표시할 지 여부입니다." #: tracspamfilter/admin.py:87 tracspamfilter/admin.py:90 #: tracspamfilter/admin.py:315 tracspamfilter/admin.py:500 #: tracspamfilter/admin.py:593 tracspamfilter/admin.py:641 #: tracspamfilter/adminreport.py:43 tracspamfilter/adminusers.py:48 msgid "Spam Filtering" msgstr "스팸 필터링" #: tracspamfilter/admin.py:88 tracspamfilter/templates/admin_bayes.html:19 msgid "Configuration" msgstr "설정" #: tracspamfilter/admin.py:91 msgid "Monitoring" msgstr "감시" #: tracspamfilter/admin.py:113 tracspamfilter/filters/bayes.py:78 #, python-format msgid "SpamBayes determined spam probability of %s%%" msgstr "베이즈 스팸 필터가 %s%% 확률로 스팸이라고 판단함" #: tracspamfilter/admin.py:115 #, python-format msgid "Select 100.00%% entries" msgstr "100.00%% 항목 선택" #: tracspamfilter/admin.py:116 #, python-format msgid "Select >90.00%% entries" msgstr "90.00%% 초과 항목 선택" #: tracspamfilter/admin.py:117 #, python-format msgid "Select <10.00%% entries" msgstr "10.00%% 미만 항목 선택" #: tracspamfilter/admin.py:118 #, python-format msgid "Select 0.00%% entries" msgstr "0.00%% 항목 선택" #: tracspamfilter/admin.py:119 msgid "Select Spam entries" msgstr "스팸 항목 선택" #: tracspamfilter/admin.py:120 msgid "Select Ham entries" msgstr "햄(유효) 항목 선택" #: tracspamfilter/admin.py:246 tracspamfilter/adminreport.py:110 #: tracspamfilter/templates/admin_report.html:31 #: tracspamfilter/templates/admin_report.html:32 #: tracspamfilter/templates/admin_spammonitor.html:29 #: tracspamfilter/templates/admin_spammonitor.html:30 msgid "Previous Page" msgstr "이전 페이지" #: tracspamfilter/admin.py:250 tracspamfilter/adminreport.py:114 #: tracspamfilter/templates/admin_report.html:37 #: tracspamfilter/templates/admin_report.html:38 #: tracspamfilter/templates/admin_spammonitor.html:35 #: tracspamfilter/templates/admin_spammonitor.html:36 msgid "Next Page" msgstr "다음 페이지" #: tracspamfilter/admin.py:267 msgid "Log entry not found" msgstr "로그 항목이 없습니다." #: tracspamfilter/admin.py:272 tracspamfilter/admin.py:277 #, python-format msgid "Log Entry %(id)s" msgstr "로그 항목 %(id)s" #: tracspamfilter/admin.py:273 msgid "Log Entry List" msgstr "로그 항목 목록" #: tracspamfilter/admin.py:316 msgid "External" msgstr "외부 필터" #: tracspamfilter/admin.py:500 tracspamfilter/templates/admin_bayes.html:10 msgid "Bayes" msgstr "베이즈 필터" #: tracspamfilter/admin.py:552 #, python-format msgid "(ratio %.1f : 1)" msgstr "(비율 %.1f : 1)" #: tracspamfilter/admin.py:554 #, python-format msgid "(ratio 1 : %.1f)" msgstr "(비율 1 : %.1f)" #: tracspamfilter/admin.py:566 #, python-format msgid "%(num)d spam" msgid_plural "%(num)d spam" msgstr[0] "%(num)d 스팸" #: tracspamfilter/admin.py:568 #, python-format msgid "%(num)d ham" msgid_plural "%(num)d ham" msgstr[0] "%(num)d 햄(유효)" #: tracspamfilter/admin.py:570 #, python-format msgid "%(num)d line" msgid_plural "%(num)d lines" msgstr[0] "%(num)d 행" #: tracspamfilter/admin.py:572 #, python-format msgid "%(num)d mixed" msgid_plural "%(num)d mixed" msgstr[0] "%(num)d 혼합" #: tracspamfilter/admin.py:594 msgid "Statistics" msgstr "통계" #: tracspamfilter/admin.py:641 msgid "Captcha" msgstr "보안 문자" #: tracspamfilter/admin.py:663 tracspamfilter/admin.py:697 #: tracspamfilter/admin.py:706 tracspamfilter/admin.py:720 #: tracspamfilter/admin.py:729 #, python-format msgid "Invalid value for %(key)s" msgstr "" #: tracspamfilter/admin.py:673 msgid "The keys are invalid" msgstr "키가 올바르지 않습니다." #: tracspamfilter/admin.py:685 msgid "The key or user id are invalid" msgstr "키 또는 사용자 아이디가 올바르지 않습니다." #: tracspamfilter/adminreport.py:32 msgid "How many report entries are displayed by default (between 5 and 10000)." msgstr "몇 개의 신고 항목을 기본으로 표시할지를 지정합니다 (5에서 10000 사이)." #: tracspamfilter/adminreport.py:43 msgid "Reports" msgstr "스팸 신고" #: tracspamfilter/adminreport.py:134 msgid "Report entry not found" msgstr "신고된 항목이 없습니다." #: tracspamfilter/adminreport.py:142 tracspamfilter/adminreport.py:149 #, python-format msgid "Report Entry %d" msgstr "신고 항목 %d" #: tracspamfilter/adminreport.py:143 msgid "Report Entry List" msgstr "신고 항목 목록" #: tracspamfilter/adminusers.py:29 msgid "How many wiki edits are still an unused account." msgstr "위키 수정 횟수가 해당 값 이하이면 사용되지 않은 계정으로 간주합니다." #: tracspamfilter/adminusers.py:33 msgid "How many days no login are considered for dead accounts." msgstr "해당 날 수만큼 로그인 하지 않으면 비활성 계정으로 처리합니다." #: tracspamfilter/adminusers.py:38 msgid "Default mode for spam user admin panel." msgstr "스팸 사용자 관리 패널의 기본 모드입니다." #: tracspamfilter/adminusers.py:48 msgid "Users" msgstr "사용자" #: tracspamfilter/adminusers.py:58 msgid "Old or new value cannot be empty" msgstr "이전 및 새 값을 입력해야 합니다." #: tracspamfilter/adminusers.py:66 msgid "Old and new value cannot be equal" msgstr "이전과 새 값은 같지 않아야 합니다." #: tracspamfilter/adminusers.py:69 msgid "New name cannot be used in CC fields" msgstr "새 이름이 참조 필드에 사용되어서는 안됩니다." #: tracspamfilter/adminusers.py:71 msgid "Illegal user arguments passed or changing not allowed" msgstr "전달된 사용자 인수가 올바르지 않거나, 변경이 허용되지 않습니다." #: tracspamfilter/adminusers.py:73 #, python-format msgid "%(num)d entry has been updated" msgid_plural "%(num)d entries have been updated" msgstr[0] "%(num)d 항목을 변경하였습니다." #: tracspamfilter/adminusers.py:80 #, python-format msgid "Username '%s' cannot be used in CC fields" msgstr "사용자 이름 '%s'이(가) 참조 필드에 사용되어서는 안됩니다." #: tracspamfilter/adminusers.py:82 #, python-format msgid "Error for e-mail change for username '%s'" msgstr "사용자 이름 '%s'에 대한 메일 주소 변경을 실패하였습니다." #: tracspamfilter/adminusers.py:84 #, python-format msgid "%(num)d entry has been updated for user %(user)s" msgid_plural "%(num)d entries have been updated for user %(user)s" msgstr[0] "%(user)s 사용자에 대한 %(num)d 개 항목을 변경하였습니다." #: tracspamfilter/adminusers.py:87 #, python-format msgid "E-mails for user %s updated" msgstr "%s 사용자의 메일 주소를 변경하였습니다." #: tracspamfilter/adminusers.py:118 msgid "data overview" msgstr "데이터 개요" #: tracspamfilter/adminusers.py:120 msgid "unused accounts" msgstr "비활성 계정" #: tracspamfilter/adminusers.py:122 msgid "registered accounts" msgstr "등록된 계정" #: tracspamfilter/adminusers.py:124 #, python-format msgid "detailed user information for '%s'" msgstr "'%s' 사용자에 대한 상세 정보" #: tracspamfilter/adminusers.py:126 msgid "everything from accounts, wiki, tickets and svn" msgstr "사용자 계정, 위키, 티켓, SVN에 존재하는 모든 사용자" #: tracspamfilter/adminusers.py:127 #, python-format msgid "%(num)d entry" msgid_plural "%(num)d entries" msgstr[0] "%(num)d 개 항목" #: tracspamfilter/filtersystem.py:52 msgid "The minimum score required for a submission to be allowed." msgstr "제출된 내용을 승인하는 데 필요한 최소 점수입니다." #: tracspamfilter/filtersystem.py:56 msgid "" "The karma given to authenticated users, in case\n" "`trust_authenticated` is false." msgstr "`trust_authenticated`가 false로 설정되었을 때, 인증된 사용자에게 부여할 점수입니다." #: tracspamfilter/filtersystem.py:60 msgid "" "Whether all content submissions and spam filtering activity should\n" "be logged to the database." msgstr "제출된 모든 내용과 스팸 필터링 활동이 데이터베이스에 기록되어야 하는지 여부입니다." #: tracspamfilter/filtersystem.py:64 msgid "The number of days after which log entries should be purged." msgstr "지정한 날 수가 지난 로그 항목을 지웁니다." #: tracspamfilter/filtersystem.py:68 msgid "Allow usage of external services." msgstr "외부 서비스의 사용을 허용합니다." #: tracspamfilter/filtersystem.py:71 msgid "" "Skip external calls when this negative karma is already reached\n" "by internal tests." msgstr "내부 테스트에 의한 점수가 음의 값으로 이 값에 도달하면 외부 서비스를 통과합니다." #: tracspamfilter/filtersystem.py:75 msgid "" "Skip external calls when this positive karma is already reached\n" "by internal tests." msgstr "내부 테스트에 의한 점수가 양의 값으로 이 값에 도달하면 외부 서비스를 통과합니다." #: tracspamfilter/filtersystem.py:79 msgid "Stop external calls when this negative karma is reached." msgstr "점수가 음의 값으로 이 값에 도달하면 외부 서비스의 사용을 멈춥니다." #: tracspamfilter/filtersystem.py:83 msgid "Stop external calls when this positive karma is reached." msgstr "점수가 양의 값으로 이 값에 도달하면 외부 서비스의 사용을 멈춥니다." #: tracspamfilter/filtersystem.py:87 msgid "Allow training of external services." msgstr "외부 서비스에 대해 학습을 허용합니다." #: tracspamfilter/filtersystem.py:90 msgid "" "Whether content submissions by authenticated users should be trusted\n" "without checking for potential spam or other abuse." msgstr "인증 사용자에 대해, 잠재적인 스팸이나 오용 행위에 대한 확인 없이 제출된 내용을 신뢰할 지 여부입니다." #: tracspamfilter/filtersystem.py:96 msgid "The karma given to attachments." msgstr "첨부 파일에 대해 부여할 점수입니다." #: tracspamfilter/filtersystem.py:99 msgid "The karma given to registrations." msgstr "가입에 대해 부여할 점수입니다." #: tracspamfilter/filtersystem.py:102 msgid "The handler used to reject content." msgstr "내용을 거부하는 데 사용할 처리기입니다." #: tracspamfilter/filtersystem.py:106 msgid "Interpret X-Forwarded-For header for IP checks." msgstr "IP 검사를 위해 X-Forwarded-For 헤더를 해석합니다." #: tracspamfilter/filtersystem.py:110 msgid "" "This section is used to handle all configurations used by\n" "spam filter plugin." msgstr "이 구역은 스팸 필터 플러그인에 대한 모든 설정을 다룹니다." #: tracspamfilter/filtersystem.py:169 msgid "User is authenticated" msgstr "사용자가 인증됨" #: tracspamfilter/filtersystem.py:175 msgid "Attachment weighting" msgstr "첨부 파일 가중치" #: tracspamfilter/filtersystem.py:180 msgid "Registration weighting" msgstr "가입 가중치" #: tracspamfilter/filtersystem.py:304 #, python-format msgid "Submission rejected as potential spam %(message)s" msgstr "제출된 내용이 스팸으로 판단되어 거부되었습니다: %(message)s" #: tracspamfilter/model.py:422 msgid "external" msgstr "외부" #: tracspamfilter/model.py:422 msgid "internal" msgstr "내부" #: tracspamfilter/report.py:29 msgid "List of page types to add spam report link" msgstr "스팸 신고 링크를 표시할 페이지 종류" #: tracspamfilter/report.py:42 msgid "No page supplied to report as spam" msgstr "스팸 신고할 페이지를 지정하지 않았습니다." #: tracspamfilter/report.py:74 msgid "Reported spam" msgstr "스팸 신고함" #: tracspamfilter/report.py:76 msgid "Comment" msgstr "설명" #: tracspamfilter/report.py:78 msgid "Report spam" msgstr "스팸 신고" #: tracspamfilter/report.py:82 #, python-format msgid "%(num)d spam report" msgid_plural "%(num)d spam reports" msgstr[0] "스팸 신고가 %(num)d 개 들어왔습니다." #: tracspamfilter/users.py:38 #, python-format msgid "Wiki page '%(page)s' version %(version)s modified" msgstr "위키 페이지 '%(page)s'의 버전 %(version)s을(를) 변경함" #: tracspamfilter/users.py:41 #, python-format msgid "Attachment '%s' added" msgstr "첨부 파일 '%s'을(를) 추가함" #: tracspamfilter/users.py:45 #, python-format msgid "Ticket %(id)s field '%(field)s' changed" msgstr "티켓 %(id)s의 '%(field)s' 필드를 변경함" #: tracspamfilter/users.py:48 #, python-format msgid "Removed from ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "티켓 %(id)s의 '%(field)s' 필드에서 제거됨 ('%(old)s' → '%(new)s')" #: tracspamfilter/users.py:50 #, python-format msgid "Set in ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "티켓 %(id)s의 '%(field)s' 필드에 설정됨 ('%(old)s' → '%(new)s')" #: tracspamfilter/users.py:56 #, python-format msgid "Ticket %(id)s CC field change ('%(old)s' --> '%(new)s')" msgstr "티켓 %(id)s의 참조 필드가 변경됨 ('%(old)s' → '%(new)s')" #: tracspamfilter/users.py:60 #, python-format msgid "Reporter of ticket %s" msgstr "티켓 %s의 작성자" #: tracspamfilter/users.py:62 #, python-format msgid "Owner of ticket %s" msgstr "티켓 %s의 소유자" #: tracspamfilter/users.py:64 #, python-format msgid "In CC of ticket %(id)s ('%(cc)s')" msgstr "티켓 %(id)s의 참조에 포함됨 ('%(cc)s')" #: tracspamfilter/users.py:67 #, python-format msgid "Author of revision %s" msgstr "리비전 %s의 작성자" #: tracspamfilter/users.py:70 #, python-format msgid "Component '%s' owner" msgstr "컴포넌트 '%s'의 소유자" #: tracspamfilter/users.py:73 msgid "In permissions list" msgstr "권한 목록에 포함됨" #: tracspamfilter/users.py:76 #, python-format msgid "Author of report %d" msgstr "보고서 %d의 작성자" #: tracspamfilter/users.py:81 tracspamfilter/users.py:85 #, python-format msgid "Voted for '%s'" msgstr "'%s'에 대해 투표함" #: tracspamfilter/captcha/api.py:59 msgid "CAPTCHA method to use for verifying humans." msgstr "사람임을 확인하기 위해 사용할 보안 문자 방식입니다." #: tracspamfilter/captcha/api.py:64 msgid "" "By how many points a successful CAPTCHA response increases the\n" "overall score." msgstr "보안 문자 대답이 올바를 때 더할 점수입니다." #: tracspamfilter/captcha/api.py:69 msgid "By how many points a failed CAPTCHA impacts the overall score." msgstr "보안 문자 테스트를 실패했을 때 차감할 점수입니다." #: tracspamfilter/captcha/api.py:73 msgid "" "Time in seconds that a successful CAPTCHA response increases\n" "karma." msgstr "보안 문자 대답이 올바를 때 점수를 더할 시간(초)입니다." #: tracspamfilter/captcha/api.py:77 msgid "Time in seconds before CAPTCHA is removed." msgstr "생성한 보안 문자를 제거할 시간 간격(초)입니다." #: tracspamfilter/captcha/api.py:81 msgid "Time in seconds before database cleanup is called." msgstr "데이터베이스 정리를 실행할 시간 간격(초)입니다." #: tracspamfilter/captcha/api.py:100 #, python-format msgid "Human verified via CAPTCHA (%s)" msgstr "보안 문자(%s)를 통해 사람임이 확인됨" #: tracspamfilter/captcha/api.py:109 #, python-format msgid "Failed CAPTCHA (%s) attempts" msgstr "보안 문자 (%s) 시도를 실패함" #: tracspamfilter/captcha/api.py:141 msgid "CAPTCHA failed to handle original request" msgstr "보안 문자 처리기가 원본 요청의 처리를 실패하였습니다." #: tracspamfilter/captcha/api.py:143 msgid "CAPTCHA verification failed" msgstr "보안 문자 인증을 실패하였습니다." #: tracspamfilter/captcha/expression.py:32 msgid "Number of terms in numeric CAPTCHA expression." msgstr "수식 보안 문자에 사용할 항의 수입니다." #: tracspamfilter/captcha/expression.py:36 msgid "" "Maximum value of individual terms in numeric CAPTCHA\n" "expression." msgstr "수식 보안 문자의 각 항의 최댓값입니다." #: tracspamfilter/captcha/expression.py:40 msgid "multiplied by" msgstr "곱하기" #: tracspamfilter/captcha/expression.py:40 msgid "minus" msgstr "빼기" #: tracspamfilter/captcha/expression.py:41 msgid "plus" msgstr "더하기" #: tracspamfilter/captcha/expression.py:43 msgid "zero" msgstr "영" #: tracspamfilter/captcha/expression.py:43 msgid "one" msgstr "일" #: tracspamfilter/captcha/expression.py:43 msgid "two" msgstr "이" #: tracspamfilter/captcha/expression.py:43 msgid "three" msgstr "삼" #: tracspamfilter/captcha/expression.py:43 msgid "four" msgstr "사" #: tracspamfilter/captcha/expression.py:44 msgid "five" msgstr "오" #: tracspamfilter/captcha/expression.py:44 msgid "six" msgstr "육" #: tracspamfilter/captcha/expression.py:44 msgid "seven" msgstr "칠" #: tracspamfilter/captcha/expression.py:44 msgid "eight" msgstr "팔" #: tracspamfilter/captcha/expression.py:45 msgid "nine" msgstr "구" #: tracspamfilter/captcha/expression.py:45 msgid "ten" msgstr "십" #: tracspamfilter/captcha/expression.py:45 msgid "eleven" msgstr "십일" #: tracspamfilter/captcha/expression.py:45 msgid "twelve" msgstr "십이" #: tracspamfilter/captcha/expression.py:46 msgid "thirteen" msgstr "십삼" #: tracspamfilter/captcha/expression.py:46 msgid "fourteen" msgstr "십사" #: tracspamfilter/captcha/expression.py:46 msgid "fifteen" msgstr "십오" #: tracspamfilter/captcha/expression.py:47 msgid "sixteen" msgstr "십육" #: tracspamfilter/captcha/expression.py:47 msgid "seventeen" msgstr "십칠" #: tracspamfilter/captcha/expression.py:47 msgid "eighteen" msgstr "십팔" #: tracspamfilter/captcha/expression.py:48 msgid "nineteen" msgstr "십구" #. TRANSLATOR: if compound numbers like in english are not #. supported, simply add a "plus" command to the following #. translations! #: tracspamfilter/captcha/expression.py:53 msgid "twenty" msgstr "이십" #: tracspamfilter/captcha/expression.py:53 msgid "thirty" msgstr "삼십" #: tracspamfilter/captcha/expression.py:53 msgid "forty" msgstr "사십" #: tracspamfilter/captcha/expression.py:53 msgid "fifty" msgstr "오십" #: tracspamfilter/captcha/expression.py:54 msgid "sixty" msgstr "육십" #: tracspamfilter/captcha/expression.py:54 msgid "seventy" msgstr "칠십" #: tracspamfilter/captcha/expression.py:54 msgid "eighty" msgstr "팔십" #: tracspamfilter/captcha/expression.py:54 msgid "ninety" msgstr "구십" #: tracspamfilter/captcha/expression.py:61 msgid "Numeric captcha can not represent numbers > 100" msgstr "숫자 형식의 보안 문자는 100을 넘는 숫자를 표현할 수 없습니다." #: tracspamfilter/captcha/image.py:43 msgid "Set of fonts to choose from when generating image CAPTCHA." msgstr "이미지 형식의 보안 문자를 생성할 때 사용할 글꼴 목록입니다." #: tracspamfilter/captcha/image.py:47 msgid "Font size to use in image CAPTCHA." msgstr "이미지 형식의 보안 문자에서 사용할 글꼴 크기입니다." #: tracspamfilter/captcha/image.py:51 msgid "Alphabet to choose image CAPTCHA challenge from." msgstr "이미지 형식의 보안 문자에서 사용할 알파벳입니다." #: tracspamfilter/captcha/image.py:56 msgid "Number of letters to use in image CAPTCHA challenge." msgstr "이미지 형식의 보안 문자의 글자 수입니다." #: tracspamfilter/captcha/keycaptcha.py:32 msgid "Private key for KeyCaptcha usage." msgstr "KeyCaptcha 서비스 사용을 위한 개인 키입니다." #: tracspamfilter/captcha/keycaptcha.py:36 msgid "User id for KeyCaptcha usage." msgstr "KeyCaptcha 서비스의 사용자 아이디입니다." #: tracspamfilter/captcha/recaptcha.py:32 #: tracspamfilter/captcha/recaptcha2.py:34 msgid "Private key for reCaptcha usage." msgstr "reCaptcha 서비스 사용을 위한 개인 키입니다." #: tracspamfilter/captcha/recaptcha.py:36 #: tracspamfilter/captcha/recaptcha2.py:38 msgid "Public key for reCaptcha usage." msgstr "reCaptcha 서비스 사용을 위한 공개 키입니다." #: tracspamfilter/captcha/recaptcha.py:59 #: tracspamfilter/captcha/recaptcha2.py:50 #: tracspamfilter/templates/verify_captcha.html:24 msgid "Submit" msgstr "제출" #: tracspamfilter/filters/akismet.py:42 msgid "" "By how many points an Akismet reject impacts the overall karma of\n" "a submission." msgstr "Akismet 필터가 내용을 거부했을 때 차감할 점수입니다." #: tracspamfilter/filters/akismet.py:46 msgid "Wordpress key required to use the Akismet API." msgstr "Akismet API를 사용하는 데 필요한 워드프레스 키입니다." #: tracspamfilter/filters/akismet.py:50 msgid "URL of the Akismet service." msgstr "Akismet 서비스의 주소입니다." #: tracspamfilter/filters/akismet.py:75 msgid "Akismet says content is spam" msgstr "Akismet 서비스가 내용이 스팸이라고 판단함" #: tracspamfilter/filters/bayes.py:33 msgid "" "By what factor Bayesian spam probability score affects the overall\n" "karma of a submission." msgstr "베이지안 스팸 확률 점수를 전체 점수에 반영할 계수입니다." #: tracspamfilter/filters/bayes.py:37 msgid "" "The minimum number of submissions in the training database required\n" "for the filter to start impacting the karma of submissions." msgstr "스팸 점수 판단을 시작하기 전에 필요한, 학습 데이터베이스에 있는 제출된 내용의 수입니다." #: tracspamfilter/filters/bayes.py:42 msgid "" "Entries with a count less than this value get removed from the\n" "database when calling the reduce function." msgstr "감축 기능을 호출할 때, 이 값보다 수가 적은 항목은 데이터베이스에서 제거됩니다." #: tracspamfilter/filters/blogspam.py:35 msgid "" "By how many points an BlogSpam reject impacts the overall karma of\n" "a submission." msgstr "BlogSpam 필터가 내용을 거부했을 때 차감할 점수입니다." #: tracspamfilter/filters/blogspam.py:39 msgid "URL of the BlogSpam service." msgstr "BlogSpam 서비스의 주소입니다." #: tracspamfilter/filters/blogspam.py:42 msgid "Comma separated list of tests to skip." msgstr "통과할 테스트를 쉼표로 구분된 목록으로 입력합니다." #: tracspamfilter/filters/blogspam.py:64 #, python-format msgid "BlogSpam says content is spam (%s [%s])" msgstr "BlogSpam 서비스가 내용이 스팸이라고 판단함 (%s [%s])" #: tracspamfilter/filters/botscout.py:31 msgid "" "By how many points a BotScout reject impacts the overall karma of\n" "a submission." msgstr "BotScout 필터가 내용을 거부했을 때 차감할 점수입니다." #: tracspamfilter/filters/botscout.py:35 msgid "API key required to use BotScout." msgstr "BotScout 서비스를 사용하는 데 필요한 API 키입니다." #: tracspamfilter/filters/botscout.py:61 #, python-format msgid "BotScout says this is spam (%s)" msgstr "BotScout 서비스가 스팸이라고 판단함 (%s)" #: tracspamfilter/filters/extlinks.py:28 msgid "" "By how many points too many external links in a submission impact\n" "the overall score." msgstr "외부 링크가 너무 많을 때 차감할 점수입니다." #: tracspamfilter/filters/extlinks.py:32 msgid "" "The maximum number of external links allowed in a submission until\n" "that submission gets negative karma." msgstr "제출된 내용에 허용되는 외부 링크의 최대 수입니다. 이 수를 초과하면 점수가 차감됩니다." #: tracspamfilter/filters/extlinks.py:36 msgid "List of domains that should be allowed in external links" msgstr "외부 링크에 허용할 도메인의 목록입니다." #: tracspamfilter/filters/extlinks.py:64 msgid "Maximum number of external links per post exceeded" msgstr "내용에 포함할 수 있는 외부 링크의 최대 수를 초과함" #: tracspamfilter/filters/extlinks.py:67 msgid "External links in post found" msgstr "내용에 외부 링크가 존재함" #: tracspamfilter/filters/fspamlist.py:32 msgid "" "By how many points a FSpamList reject impacts the overall karma of\n" "a submission." msgstr "FSpamList 필터가 내용을 거부했을 때 차감할 점수입니다." #: tracspamfilter/filters/fspamlist.py:36 msgid "API key required to use FSpamList." msgstr "FSpamList 서비스를 사용하는 데 필요한 API 키입니다." #: tracspamfilter/filters/fspamlist.py:67 #, python-format msgid "FSpamList says this is spam (%s)" msgstr "FSpamList 서비스가 스팸이라고 판단함 (%s)" #: tracspamfilter/filters/httpbl.py:34 msgid "" "By how many points listing as \"comment spammer\" impacts the\n" "overall karma of a submission." msgstr "\"덧글 스패머\"로 판정되었을 때 차감할 점수입니다." #: tracspamfilter/filters/httpbl.py:38 msgid "Http:BL API key required for use." msgstr "Http:BL 서비스를 사용하는 데 필요한 API 키입니다." #: tracspamfilter/filters/httpbl.py:81 #, python-format msgid "IP %s blacklisted by Http:BL" msgstr "%s 아이피가 Http:BL에 의해 차단됨" #: tracspamfilter/filters/ip_blacklist.py:34 msgid "" "By how many points blacklisting by a single server impacts the\n" "overall karma of a submission." msgstr "각 서버에 의해 차단되었을 때마다 차감할 점수입니다." #: tracspamfilter/filters/ip_blacklist.py:39 msgid "Servers used for IPv4 blacklisting." msgstr "IPv4 차단에 사용할 서버입니다." #: tracspamfilter/filters/ip_blacklist.py:43 msgid "Servers used for IPv6 blacklisting." msgstr "IPv6 차단에 사용할 서버입니다." #: tracspamfilter/filters/ip_blacklist.py:87 #, python-format msgid "IP %s blacklisted by %s" msgstr "%s 아이피가 %s에 의해 차단됨" #: tracspamfilter/filters/ip_regex.py:34 msgid "" "By how many points a match with a pattern on the BadIP page\n" "impacts the overall karma of a submission." msgstr "BadIP 페이지에 있는 패턴과 일치했을 때마다 차감할 점수입니다." #: tracspamfilter/filters/ip_regex.py:37 msgid "" "Local file to be loaded to get BadIP. Can be used in\n" "addition to BadIP wiki page." msgstr "BadIP 패턴을 읽어 올 로컬 파일입니다. BadIP 위키 페이지 외에 추가로 사용할 수 있습니다." #: tracspamfilter/filters/ip_regex.py:40 msgid "Show the matched bad IP patterns in rejection message." msgstr "일치한 IP 패턴을 거부 메시지에 표시할 지 여부입니다." #: tracspamfilter/filters/ip_regex.py:76 #, python-format msgid "IP catched by these blacklisted patterns: %s" msgstr "IP가 다음 차단 패턴과 일치함: %s" #: tracspamfilter/filters/ip_regex.py:78 #, python-format msgid "IP catched by %s blacklisted patterns" msgstr "IP가 %s 개 차단 패턴과 일치함" #: tracspamfilter/filters/ip_throttle.py:27 msgid "" "By how many points exceeding the configured maximum number of posts\n" "per hour impacts the overall score." msgstr "설정된 시간 당 최대 게시 수를 초과하였을 때 차감할 점수입니다." #: tracspamfilter/filters/ip_throttle.py:31 msgid "" "The maximum allowed number of submissions per hour from a single IP\n" "address. If this limit is exceeded, subsequent submissions get negative\n" "karma." msgstr "단일 IP 주소에 허용된 시간 당 게시 수입니다. 이 제한을 초과하면 이후의 제출부터는 점수가 차감됩니다." #: tracspamfilter/filters/ip_throttle.py:52 msgid "Maximum number of posts per hour for this IP exceeded" msgstr "이 IP에서의 시간 당 최대 게시 수를 초과함" #: tracspamfilter/filters/mollom.py:36 msgid "" "By how many points an Mollom reject impacts the overall karma of\n" "a submission." msgstr "Mollom 필터가 내용을 거부했을 때 차감할 점수입니다." #: tracspamfilter/filters/mollom.py:40 msgid "Public key required to use the Mollom API." msgstr "Mollom API를 사용하는 데 필요한 공개 키입니다." #: tracspamfilter/filters/mollom.py:44 msgid "Private key required to use the Mollom API." msgstr "Mollom API를 사용하는 데 필요한 개인 키입니다." #: tracspamfilter/filters/mollom.py:48 msgid "URL of the Mollom service." msgstr "Mollom 서비스의 주소입니다." #: tracspamfilter/filters/mollom.py:95 msgid "Mollom says content is spam" msgstr "Mollom 서비스가 내용이 스팸이라고 판단함" #: tracspamfilter/filters/mollom.py:106 msgid "Mollom says content is ham" msgstr "Mollom 서비스가 내용이 햄(유효)이라고 판단함" #: tracspamfilter/filters/regex.py:31 msgid "" "By how many points a match with a pattern on the BadContent page\n" "impacts the overall karma of a submission." msgstr "BadContent 페이지에 있는 패턴과 일치했을 때마다 차감할 점수입니다." #: tracspamfilter/filters/regex.py:34 msgid "" "Local file to be loaded to get BadContent. Can be used in\n" "addition to BadContent wiki page." msgstr "BadContent 패턴을 읽어 올 로컬 파일입니다. BadContent 위키 페이지 외에 추가로 사용할 수 있습니다." #: tracspamfilter/filters/regex.py:37 msgid "Show the matched bad content patterns in rejection message." msgstr "일치한 내용 패턴을 거부 메시지에 표시할 지 여부입니다." #: tracspamfilter/filters/regex.py:77 #, python-format msgid "Content contained these blacklisted patterns: %s" msgstr "내용이 다음 차단 패턴을 포함함: %s" #: tracspamfilter/filters/regex.py:79 #, python-format msgid "Content contained %s blacklisted patterns" msgstr "내용이 %s 개 차단 패턴을 포함함" #: tracspamfilter/filters/registration.py:28 msgid "" "By how many points a failed registration check impacts\n" "the overall score." msgstr "사용자 가입 검사를 실패했을 때 차감할 점수입니다." #: tracspamfilter/filters/registration.py:32 msgid "Replace checks in account manager totally." msgstr "계정 관리자의 검사를 완전히 대체합니다." #: tracspamfilter/filters/registration.py:84 #, python-format msgid "Account registration failed (%s)" msgstr "계정 등록 실패 (%s)" #: tracspamfilter/filters/session.py:26 msgid "" "By how many points an existing and configured session improves the\n" "overall karma of the submission. A third of the points is granted for\n" "having an existing session at all, the other two thirds are granted\n" "when the user has his name and/or email address set in the session,\n" "respectively." msgstr "" "존재하며 설정된 세션에 따라 가산할 점수입니다. 점수의 삼분의 일은 세션이 존재할 때 부여되며, 나머지 삼분의 이는 각각 사용자가 " "이름을 설정하고 메일 주소를 설정했을 때 부여됩니다." #: tracspamfilter/filters/session.py:48 msgid "Existing session found" msgstr "세션이 존재함" #: tracspamfilter/filters/stopforumspam.py:30 msgid "" "By how many points a StopForumSpam reject impacts the overall karma of\n" "a submission." msgstr "StopForumSpam 필터가 내용을 거부했을 때 차감할 점수입니다." #: tracspamfilter/filters/stopforumspam.py:34 msgid "API key used to report SPAM." msgstr "스팸을 보고할 때 사용할 API 키입니다." #: tracspamfilter/filters/stopforumspam.py:62 #, python-format msgid "StopForumSpam says this is spam (%s)" msgstr "StopForumSpam 서비스가 스팸이라고 판단함 (%s)" #: tracspamfilter/filters/trapfield.py:27 msgid "" "By how many points a trap reject impacts the overall karma of\n" "a submission." msgstr "함정 필드 필터가 내용을 거부했을 때 차감할 점수입니다." #: tracspamfilter/filters/trapfield.py:30 msgid "" "Name of the invisible trap field, should contain some reference\n" "to e-mail for better results." msgstr "보이지 않는 함정 필드의 이름입니다. 향상된 결과를 위해 전자 메일과 관련된 것이어야 합니다." #: tracspamfilter/filters/trapfield.py:33 msgid "" "Name of the hidden trap field, should contain some reference\n" "to e-mail for better results." msgstr "숨겨진 함정 필드의 이름입니다. 향상된 결과를 위해 전자 메일과 관련된 것이어야 합니다." #: tracspamfilter/filters/trapfield.py:36 msgid "" "Name of the register trap field, should contain some reference\n" "to web/homepage for better results." msgstr "가입 함정 필드의 이름입니다. 향상된 결과를 위해 웹 또는 홈페이지와 관련된 것이어야 합니다." #: tracspamfilter/filters/trapfield.py:62 #, python-format msgid "Both trap fields says this is spam (%s, %s)" msgstr "두 함정 필드가 스팸이라고 판단함 (%s, %s)" #: tracspamfilter/filters/trapfield.py:65 #, python-format msgid "Invisible trap field says this is spam (%s)" msgstr "보이지 않는 함정 필드가 스팸이라고 판단함 (%s)" #: tracspamfilter/filters/trapfield.py:68 #, python-format msgid "Hidden trap field says this is spam (%s)" msgstr "숨겨진 함정 필드가 스팸이라고 판단함 (%s)" #: tracspamfilter/filters/trapfield.py:71 #, python-format msgid "Register trap field starts with HTTP URL (%s)" msgstr "HTTP URL로 시작하는 가입 함정 필드 (%s)" #: tracspamfilter/filters/url_blacklist.py:33 msgid "" "By how many points blacklisting by a single bad URL impacts the\n" "overall karma of a submission." msgstr "차단된 불량 URL 각각에 대해 차감할 점수입니다." #: tracspamfilter/filters/url_blacklist.py:38 msgid "Servers used for URL blacklisting." msgstr "URL 차단에 사용할 서버입니다." #: tracspamfilter/filters/url_blacklist.py:81 #, python-format msgid "URL's blacklisted by %s" msgstr "URL이 %s에 의해 차단됨" #: tracspamfilter/templates/admin_bayes.html:14 msgid "Spam Filtering: Bayes" msgstr "스팸 필터링: 베이즈 필터" #: tracspamfilter/templates/admin_bayes.html:20 #, python-format msgid "" "The bayesian filter requires training before it can effectively\n" " differentiate between spam and ham. The training database " "currently\n" " contains [1:%(numspam)s spam] and\n" " [2:%(numham)s ham] %(ratio)s submissions." msgstr "" "베이지안 필터가 스팸과 햄(유효한 내용)을 구별할 수 있으려면, 먼저 학습이 필요합니다.\n" "학습 데이터베이스는 현재 [1:%(numspam)s 개 스팸]과 [2:%(numham)s 개 햄] %(ratio)s 항목을 보유하고" " 있습니다." #: tracspamfilter/templates/admin_bayes.html:25 #, python-format msgid "" "The bayesian\n" " database contains currently %(lines)s lines with trained words " "(%(spamtext)s,\n" " %(hamtext)s, %(mixedtext)s)." msgstr "" "베이지안 데이터베이스는 현재 학습된 문장을 포함하는 %(lines)s 행을 보유하고 있습니다 (%(spamtext)s, " "%(hamtext)s, %(mixedtext)s)." #: tracspamfilter/templates/admin_bayes.html:30 #, python-format msgid "" "[1:]\n" " Reduce training database (%(linestext)s)" msgstr "" "[1:]\n" "학습 데이터베이스 감축 (%(linestext)s)" #: tracspamfilter/templates/admin_bayes.html:34 msgid "" "Reducing the training database can help when it got very large\n" " and tests take too long." msgstr "데이터베이스가 너무 크고 테스트가 오래 걸릴 경우, 학습 데이터베이스를 감축하면 도움이 될 수 있습니다." #: tracspamfilter/templates/admin_bayes.html:40 msgid "Minimum database count:" msgstr "최소 데이터베이스 항목 수:" #: tracspamfilter/templates/admin_bayes.html:44 msgid "" "Any database lines with less entries is removed when reducing\n" " the database." msgstr "데이터베이스를 감축할 때, 지정한 값보다 수가 적은 데이터베이스 행은 삭제됩니다." #: tracspamfilter/templates/admin_bayes.html:52 msgid "Clear training database" msgstr "학습 데이터베이스 지우기" #: tracspamfilter/templates/admin_bayes.html:55 msgid "" "Resetting the training database can help when training was incorrect\n" " and is producing bad results." msgstr "학습된 내용이 올바르지 않아 판단 결과가 좋지 않을 경우, 학습 데이터베이스를 초기화 할 수 있습니다." #: tracspamfilter/templates/admin_bayes.html:61 msgid "Minimum training required:" msgstr "필요한 최소 학습 수:" #: tracspamfilter/templates/admin_bayes.html:65 msgid "" "The minimum number of spam and ham in the training database before\n" " the filter starts affecting the karma of submissions." msgstr "스팸 점수 판단을 시작하기 전에 필요한, 학습 데이터베이스에 있는 스팸과 햄(유효 내용)의 최소 숫자입니다." #: tracspamfilter/templates/admin_bayes.html:71 #: tracspamfilter/templates/admin_captcha.html:173 #: tracspamfilter/templates/admin_external.html:263 #: tracspamfilter/templates/admin_spamconfig.html:123 msgid "Apply changes" msgstr "변경 사항 적용하기" #: tracspamfilter/templates/admin_bayes.html:76 msgid "Training" msgstr "학습" #: tracspamfilter/templates/admin_bayes.html:77 msgid "" "While you can train the spam filter from the “[1:Spam\n" " Filtering → Monitoring]” panel in the web\n" " administration interface, you can also manually train the " "filter by\n" " entering samples here, or check what kind of spam probability\n" " currently gets assigned to the content." msgstr "" "웹 관리자 화면의 “[1:스팸 필터링 → 모니터링]” 패널에서 스팸 필터를 학습시킬 수 있지만, 여기에 예제를 입력하여 수동으로 " "필터를 학습시킬 수도 있습니다.\n" "또한 내용에 따라 스팸 확률 점수가 어떻게 되는지 확인할 수 있습니다." #: tracspamfilter/templates/admin_bayes.html:85 msgid "Content:" msgstr "내용:" #: tracspamfilter/templates/admin_bayes.html:91 #, python-format msgid "Error: %(error)s" msgstr "오류: %(error)s" #: tracspamfilter/templates/admin_bayes.html:92 #, python-format msgid "Score: %(score)s%" msgstr "점수: %(score)s%" #: tracspamfilter/templates/admin_bayes.html:95 #: tracspamfilter/templates/admin_statistics.html:36 msgid "Test" msgstr "테스트" #: tracspamfilter/templates/admin_bayes.html:97 msgid "Train as Spam" msgstr "스팸으로 학습" #: tracspamfilter/templates/admin_bayes.html:98 msgid "Train as Ham" msgstr "햄(유효)으로 학습" #: tracspamfilter/templates/admin_captcha.html:11 msgid "Captcha handling" msgstr "보안 문자 처리" #: tracspamfilter/templates/admin_captcha.html:15 msgid "Spam Filtering: Captcha handling" msgstr "스팸 필터링: 보안 문자 처리" #: tracspamfilter/templates/admin_captcha.html:22 msgid "Enable captcha usage" msgstr "보안 문자 사용 활성" #: tracspamfilter/templates/admin_captcha.html:29 msgid "Captcha type:" msgstr "보안 문자 종류:" #: tracspamfilter/templates/admin_captcha.html:39 msgid "Maximum captcha lifetime in seconds" msgstr "보안 문자의 최대 유효 시간 (초):" #: tracspamfilter/templates/admin_captcha.html:47 msgid "reCAPTCHA" msgstr "reCAPTCHA" #: tracspamfilter/templates/admin_captcha.html:48 #, fuzzy msgid "" "The reCAPTCHA system provides a very good captcha system based on\n" " scanned books. See\n" " [1:Google\n" " reCAPTCHA] page. You need to obtain\n" " API keys to use the service, which is freely available for " "personal use." msgstr "" "reCAPTCHA 시스템은 스캔된 책을 기반으로 한 훌륭한 보안 문자 시스템을 제공합니다. [1:Google reCAPTCHA] " "페이지를 확인하십시오.\n" "서비스를 사용하기 위해서는 API 키를 받아야 하며, 개인 용도로 사용할 때는 무료입니다." #: tracspamfilter/templates/admin_captcha.html:58 #: tracspamfilter/templates/admin_external.html:201 msgid "Public key:" msgstr "공개 키:" #: tracspamfilter/templates/admin_captcha.html:65 #: tracspamfilter/templates/admin_captcha.html:97 msgid "Private key:" msgstr "개인 키:" #: tracspamfilter/templates/admin_captcha.html:75 #: tracspamfilter/templates/admin_captcha.html:106 msgid "Key validation failed:" msgstr "키 검증 실패:" #: tracspamfilter/templates/admin_captcha.html:80 msgid "KeyCaptcha" msgstr "KeyCaptcha" #: tracspamfilter/templates/admin_captcha.html:81 #, fuzzy msgid "" "The KeyCatcha system provides a captcha system based on JavaScript\n" " functions to reassemble a picture. See\n" " [1:KeyCaptcha]\n" " page. You need to obtain an API key to use the service, which" " is\n" " freely available for limited use." msgstr "" "KeyCatcha 시스템은 그림 조각 맞추기를 기반으로 한 보안 문자 시스템을 제공합니다. 실행에는 JavaScript 기능이 " "필요합니다. [1:KeyCaptcha] 페이지를 확인하십시오.\n" "서비스를 사용하기 위해서는 API 키를 받아야 하며, 제한된 용도로 사용할 때는 무료입니다." #: tracspamfilter/templates/admin_captcha.html:91 msgid "User ID:" msgstr "사용자 아이디:" #: tracspamfilter/templates/admin_captcha.html:111 msgid "Text captcha" msgstr "텍스트 보안 문자" #: tracspamfilter/templates/admin_captcha.html:112 #, fuzzy msgid "" "The text captcha constructs easy text questions. They can be\n" " broken relatively easy." msgstr "텍스트 보안 문자는 쉬운 단답형 질문을 생성합니다. 이는 상대적으로 쉽게 뚫릴 수 있습니다." #: tracspamfilter/templates/admin_captcha.html:119 msgid "Maximum value in a term:" msgstr "수식 각 항의 최댓값:" #: tracspamfilter/templates/admin_captcha.html:125 msgid "Number of terms:" msgstr "수식의 항 수:" #: tracspamfilter/templates/admin_captcha.html:135 msgid "Image captcha" msgstr "이미지 보안 문자" #: tracspamfilter/templates/admin_captcha.html:136 #, fuzzy msgid "" "The image captcha constructs obstructed images using Python\n" " imaging library." msgstr "이미지 보안 문자는 Python 이미징 라이브러리를 이용하여 변형된 이미지를 생성합니다." #: tracspamfilter/templates/admin_captcha.html:143 msgid "Number of letters:" msgstr "글자 수:" #: tracspamfilter/templates/admin_captcha.html:149 msgid "Font size:" msgstr "글꼴 크기:" #: tracspamfilter/templates/admin_captcha.html:155 msgid "Alphabet:" msgstr "알파벳:" #: tracspamfilter/templates/admin_captcha.html:161 msgid "Fonts:" msgstr "글꼴:" #: tracspamfilter/templates/admin_captcha.html:174 #: tracspamfilter/templates/admin_external.html:264 msgid "Revert changes" msgstr "변경 내용 되돌리기" #: tracspamfilter/templates/admin_external.html:10 msgid "External services" msgstr "외부 서비스" #: tracspamfilter/templates/admin_external.html:14 msgid "Spam Filtering: External services" msgstr "스팸 필터링: 외부 서비스" #: tracspamfilter/templates/admin_external.html:17 msgid "An error checking supplied data occured, see below for details." msgstr "입력된 데이터를 확인하는 과정에서 오류가 발생하였습니다. 상세 내용은 아래를 참조하십시오." #: tracspamfilter/templates/admin_external.html:24 msgid "Use external services" msgstr "외부 서비스 사용" #: tracspamfilter/templates/admin_external.html:32 msgid "Train external services" msgstr "외부 서비스에 대해 학습 사용" #: tracspamfilter/templates/admin_external.html:36 msgid "" "Skip external services, when internal tests reach a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" "내부 테스트의 점수가 다음에 도달하면 외부 서비스를 통과합니다 -\n" "스팸: [1:]\n" "햄(유효): [2:]" #: tracspamfilter/templates/admin_external.html:46 msgid "" "Stop external services, when reached a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" "점수가 다음에 도달하면 외부 서비스의 사용을 멈춥니다 -\n" "스팸: [1:]\n" "햄(유효): [2:]" #: tracspamfilter/templates/admin_external.html:58 msgid "" "The Akismet filter uses the free\n" " [1:Akismet]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" "Akismet 필터는 제출된 내용이 스팸인지를 판단하기 위해 무료 [1:Akismet] 서비스를 활용합니다. 서비스를 사용하기 " "위해서는 API 키를 받아야 하며, 개인 용도로 사용할 때는 무료입니다." #: tracspamfilter/templates/admin_external.html:65 #: tracspamfilter/templates/admin_external.html:135 #: tracspamfilter/templates/admin_external.html:152 #: tracspamfilter/templates/admin_external.html:168 #: tracspamfilter/templates/admin_external.html:185 msgid "API key:" msgstr "API 키:" #: tracspamfilter/templates/admin_external.html:71 #: tracspamfilter/templates/admin_external.html:91 #: tracspamfilter/templates/admin_external.html:213 msgid "URL:" msgstr "주소:" #: tracspamfilter/templates/admin_external.html:77 #: tracspamfilter/templates/admin_external.html:219 #: tracspamfilter/templates/admin_external.html:224 #, python-format msgid "[1:Key validation failed:] %(error)s" msgstr "[1:키 검증 실패:] %(error)s" #: tracspamfilter/templates/admin_external.html:85 msgid "" "The BlogSpam filter uses the free\n" " [1:BlogSpam]\n" " service to decide if content submissions are potential spam." msgstr "BlogSpam 필터는 제출된 내용이 스팸인지를 판단하기 위해 무료 [1:BlogSpam] 서비스를 활용합니다." #: tracspamfilter/templates/admin_external.html:97 msgid "Tests to skip (comma separated):" msgstr "통과할 테스트 (쉼표로 구분):" #: tracspamfilter/templates/admin_external.html:100 msgid "Possible Values:" msgstr "사용할 수 있는 값:" #: tracspamfilter/templates/admin_external.html:105 msgid "method" msgstr "방식" #: tracspamfilter/templates/admin_external.html:106 msgid "description" msgstr "설명" #: tracspamfilter/templates/admin_external.html:107 msgid "author" msgstr "작성자" #: tracspamfilter/templates/admin_external.html:127 msgid "" "The StopForumSpam filter uses the\n" " [1:StopForumSpam]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to report SPAM to the service, which is freely " "available." msgstr "" "StopForumSpam 필터는 제출된 내용이 스팸인지를 판단하기 위해 [1:StopForumSpam] 서비스를 활용합니다. " "서비스에 스팸을 보고하기 위해서는 API 키를 받아야 하며, 자유롭게 사용할 수 있습니다." #: tracspamfilter/templates/admin_external.html:144 msgid "" "The BotScout filter uses the\n" " [1:BotScout]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" "BotScout 필터는 제출된 내용이 스팸인지를 판단하기 위해 [1:BotScout] 서비스를 활용합니다. 서비스를 사용하기 " "위해서는 API 키를 받아야 하며, 자유롭게 사용할 수 있습니다." #: tracspamfilter/templates/admin_external.html:161 msgid "" "The FSpamList filter uses the\n" " [1:FSpamList]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" "FSpamList 필터는 제출된 내용이 스팸인지를 판단하기 위해 [1:FSpamList] 서비스를 활용합니다. 서비스를 사용하기 " "위해서는 API 키를 받아야 하며, 자유롭게 사용할 수 있습니다." #: tracspamfilter/templates/admin_external.html:177 msgid "" "The HTTP_BL filter uses the free\n" " [1:HTTP:BL]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" "HTTP_BL 필터는 제출된 내용이 스팸인지를 판단하기 위해 무료 [1:HTTP_BL] 서비스를 활용합니다. 서비스를 사용하기 " "위해서는 API 키를 받아야 하며, 개인 용도로 사용할 때는 무료입니다." #: tracspamfilter/templates/admin_external.html:194 msgid "" "The Mollom filter uses the free\n" " [1:Mollom]\n" " service to decide if content submissions are potential spam. " "You need to obtain\n" " API keys to use the service, which are freely available for " "personal use." msgstr "" "Mollom 필터는 제출된 내용이 스팸인지를 판단하기 위해 무료 [1:Mollom] 서비스를 활용합니다. 서비스를 사용하기 위해서는" " API 키를 받아야 하며, 개인 용도로 사용할 때는 무료입니다." #: tracspamfilter/templates/admin_external.html:207 msgid "Secret key:" msgstr "비밀 키:" #: tracspamfilter/templates/admin_external.html:229 msgid "Free access blacklists" msgstr "공개된 차단 목록" #: tracspamfilter/templates/admin_external.html:231 msgid "IPv4 Blacklists (comma separated):" msgstr "IPv4 차단 목록 (쉼표로 구분):" #: tracspamfilter/templates/admin_external.html:235 #: tracspamfilter/templates/admin_external.html:242 #: tracspamfilter/templates/admin_external.html:249 #, python-format msgid "(default: %(list)s)" msgstr "(기본값: %(list)s)" #: tracspamfilter/templates/admin_external.html:238 msgid "IPv6 Blacklists (comma separated):" msgstr "IPv6 차단 목록 (쉼표로 구분):" #: tracspamfilter/templates/admin_external.html:245 msgid "URL Blacklists (comma separated):" msgstr "URL 차단 목록 (쉼표로 구분):" #: tracspamfilter/templates/admin_external.html:251 msgid "" "A list of DNS blacklists can be found at the [1:RBLCheck]\n" " or [2:MultiRBL] services." msgstr "[1:RBLCheck]와 [2:MultiRBL] 서비스에서 DNS 차단 목록들을 찾아볼 수 있습니다." #: tracspamfilter/templates/admin_external.html:256 msgid "" "You can enable or disable these filters from the “[1:General →\n" " Plugins]” panel of the web administration interface." msgstr "웹 관리자 화면의 “[1:일반 → 플러그인]” 패널에서 필터를 활성화 또는 비활성화 할 수 있습니다." #: tracspamfilter/templates/admin_report.html:10 msgid "Spam Reports" msgstr "스팸 신고" #: tracspamfilter/templates/admin_report.html:19 msgid "Spam Filtering: Reports" msgstr "스팸 필터링: 신고" #: tracspamfilter/templates/admin_report.html:22 #: tracspamfilter/templates/admin_spammonitor.html:20 #, python-format msgid "Viewing entries %(start)s – %(end)s of %(total)s." msgstr "전체 %(total)s 개 중 %(start)s – %(end)s 항목을 표시합니다." #: tracspamfilter/templates/admin_report.html:50 #: tracspamfilter/templates/monitortable.html:13 msgid "Path" msgstr "경로" #: tracspamfilter/templates/admin_report.html:51 #: tracspamfilter/templates/monitortable.html:14 msgid "Author" msgstr "작성자" #: tracspamfilter/templates/admin_report.html:52 #: tracspamfilter/templates/monitortable.html:17 msgid "Date/time" msgstr "날짜 및 시간" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:44 msgid "User was logged in" msgstr "사용자가 로그인 함" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:44 msgid "User was not logged in" msgstr "사용자가 로그인하지 않음" #: tracspamfilter/templates/admin_report.html:79 #: tracspamfilter/templates/monitortable.html:64 msgid "No data available" msgstr "데이터가 없습니다." #: tracspamfilter/templates/admin_report.html:90 #: tracspamfilter/templates/admin_spammonitor.html:60 msgid "Delete selected" msgstr "선택한 항목 삭제하기" #: tracspamfilter/templates/admin_reportentry.html:10 msgid "Spam Report" msgstr "스팸 신고" #: tracspamfilter/templates/admin_reportentry.html:20 #: tracspamfilter/templates/admin_reportentry.html:21 msgid "Previous Report Entry" msgstr "이전 신고 항목" #: tracspamfilter/templates/admin_reportentry.html:25 #: tracspamfilter/templates/admin_spamentry.html:25 msgid "Back to List" msgstr "목록으로 돌아가기" #: tracspamfilter/templates/admin_reportentry.html:30 #: tracspamfilter/templates/admin_reportentry.html:31 msgid "Next Report Entry" msgstr "다음 신고 항목" #: tracspamfilter/templates/admin_reportentry.html:35 msgid "Spam Filtering: Report" msgstr "스팸 필터링: 신고 항목" #: tracspamfilter/templates/admin_reportentry.html:39 msgid "Report Entry:" msgstr "신고 항목:" #: tracspamfilter/templates/admin_reportentry.html:40 #: tracspamfilter/templates/admin_spamentry.html:40 msgid "Information" msgstr "정보" #: tracspamfilter/templates/admin_reportentry.html:42 #: tracspamfilter/templates/admin_spamentry.html:42 msgid "Time:" msgstr "시간:" #: tracspamfilter/templates/admin_reportentry.html:45 #: tracspamfilter/templates/admin_spamentry.html:45 msgid "Path:" msgstr "경로:" #: tracspamfilter/templates/admin_reportentry.html:48 #: tracspamfilter/templates/admin_spamentry.html:48 msgid "Author:" msgstr "작성자:" #: tracspamfilter/templates/admin_reportentry.html:51 #: tracspamfilter/templates/admin_spamentry.html:51 msgid "Authenticated:" msgstr "인증됨:" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:47 #: tracspamfilter/templates/usertable.html:49 msgid "yes" msgstr "예" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:48 #: tracspamfilter/templates/usertable.html:50 msgid "no" msgstr "아니요" #: tracspamfilter/templates/admin_reportentry.html:54 msgid "Comment:" msgstr "설명:" #: tracspamfilter/templates/admin_reportentry.html:58 #: tracspamfilter/templates/admin_spamentry.html:75 msgid "HTTP headers" msgstr "HTTP 헤더" #: tracspamfilter/templates/admin_reportentry.html:63 #: tracspamfilter/templates/admin_spamentry.html:82 msgid "Delete" msgstr "삭제" #: tracspamfilter/templates/admin_reportentry.html:68 msgid "Possibly related log entries:" msgstr "관련되었을만한 로그 항목:" #: tracspamfilter/templates/admin_spamconfig.html:10 msgid "Spam Filter" msgstr "스팸 필터" #: tracspamfilter/templates/admin_spamconfig.html:14 msgid "Spam Filtering: Configuration" msgstr "스팸 필터링: 설정" #: tracspamfilter/templates/admin_spamconfig.html:15 msgid "" "See [1:wiki page]\n" " for a short documentation." msgstr "[1:위키 페이지]에 있는 간단한 설명 문서를 참조하십시오." #: tracspamfilter/templates/admin_spamconfig.html:16 msgid "Help translating this plugin at [1:Transifex]." msgstr "[1:Transifex]에서 이 플러그인의 번역에 참여해 주십시오." #: tracspamfilter/templates/admin_spamconfig.html:21 msgid "Karma Tuning" msgstr "점수 조정" #: tracspamfilter/templates/admin_spamconfig.html:23 msgid "Minimum karma required for a successful submission:" msgstr "제출 성공을 위한 최소 점수:" #: tracspamfilter/templates/admin_spamconfig.html:29 msgid "" "Karma assigned to attachments (e.g. to allow relaxed rules for file " "uploads):" msgstr "첨부 파일에 대한 점수 (파일 올리기에 대해 완화된 규정을 적용할 때 사용):" #: tracspamfilter/templates/admin_spamconfig.html:35 msgid "" "Karma assigned to registering users (e.g. negative value to increase " "captcha usage):" msgstr "사용자 등록에 대한 점수 (보안 문자 사용을 늘리려면 음수 값을 입력):" #: tracspamfilter/templates/admin_spamconfig.html:41 msgid "" "Content submissions are passed through a set of registered and enabled\n" " [1:filter strategies], each of which check the submitted " "content\n" " and may assign [2:karma points] to it. The sum of these karma\n" " points needs to be greater than or equal to the minimum karma\n" " configured here for the submission to be accepted." msgstr "" "제출된 내용은 등록되고 활성화 되어 있는 [1:필터]에 전달됩니다. 각 필터는 제출된 내용을 검사한 다음 [2:점수]를 부여할 수 " "있습니다. 제출된 내용이 승인되려면, 점수의 총합이 여기에 설정한 최소 점수보다 같거나 커야합니다." #: tracspamfilter/templates/admin_spamconfig.html:50 #: tracspamfilter/templates/admin_statistics.html:34 msgid "Strategy" msgstr "방법" #: tracspamfilter/templates/admin_spamconfig.html:51 msgid "Karma points" msgstr "점수" #: tracspamfilter/templates/admin_spamconfig.html:52 msgid "Description" msgstr "설명" #: tracspamfilter/templates/admin_spamconfig.html:66 msgid "Logging" msgstr "로깅" #: tracspamfilter/templates/admin_spamconfig.html:70 msgid "Enable" msgstr "활성" #: tracspamfilter/templates/admin_spamconfig.html:74 msgid "" "The spam filter plugin can optionally log every content submission so\n" " that you can monitor and tune the effectiveness of the " "filtering. The\n" " log is stored in the database, and can be viewed under “[1:Spam" "\n" " Filtering → Monitoring]” from the web administration\n" " interface." msgstr "" "스팸 필터링의 상태를 확인하고 필터링의 효율성을 조절할 수 있도록, 스팸 필터 플러그인이 제출된 각 내용을 기록하도록 할 수 " "있습니다.\n" "로그 기록은 데이터베이스에 저장되며, 웹 관리자 화면의 “[1:스팸 필터링 → 모니터링]” 패널에서 볼 수 있습니다." #: tracspamfilter/templates/admin_spamconfig.html:82 msgid "" "Purge old entries after\n" " [1:]\n" " days" msgstr "[1:]일이 지난 항목 삭제" #: tracspamfilter/templates/admin_spamconfig.html:90 #, python-format msgid "" "Number of entries in log message display\n" " (%(min)s-%(max)s)\n" " [1:]" msgstr "" "목록에 표시할 로그 메시지의 수 (%(min)s-%(max)s)\n" "[1:]" #: tracspamfilter/templates/admin_spamconfig.html:100 msgid "Authenticated" msgstr "사용자 인증" #: tracspamfilter/templates/admin_spamconfig.html:104 msgid "Trust authenticated users" msgstr "인증 사용자를 신뢰함" #: tracspamfilter/templates/admin_spamconfig.html:108 msgid "" "If authenticated users should not be trusted automatically, this\n" " option must be disabled. Instead of full trust the supplied " "karma\n" " value is used in this case." msgstr "" "인증된 사용자를 자동으로 신뢰해서는 안된다면, 이 옵션은 비활성화 되어야 합니다. 이럴 경우 완전히 신뢰하는 대신에, 부여할 점수를" " 지정할 수 있습니다." #: tracspamfilter/templates/admin_spamconfig.html:114 msgid "Karma of authenticated users:" msgstr "인증된 사용자에 대한 점수:" #: tracspamfilter/templates/admin_spamentry.html:10 #: tracspamfilter/templates/admin_spammonitor.html:10 msgid "Spam Monitoring" msgstr "스팸 감시" #: tracspamfilter/templates/admin_spamentry.html:20 #: tracspamfilter/templates/admin_spamentry.html:21 msgid "Previous Log Entry" msgstr "이전 로그 항목" #: tracspamfilter/templates/admin_spamentry.html:30 #: tracspamfilter/templates/admin_spamentry.html:31 msgid "Next Log Entry" msgstr "다음 로그 항목" #: tracspamfilter/templates/admin_spamentry.html:35 #: tracspamfilter/templates/admin_spammonitor.html:14 msgid "Spam Filtering: Monitoring" msgstr "스팸 필터링: 감시" #: tracspamfilter/templates/admin_spamentry.html:39 msgid "Log Entry:" msgstr "로그 항목:" #: tracspamfilter/templates/admin_spamentry.html:54 msgid "IP address:" msgstr "IP 주소:" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "spam" msgstr "스팸" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "ham" msgstr "햄(유효)" #: tracspamfilter/templates/admin_spamentry.html:60 msgid "Karma:" msgstr "점수:" #: tracspamfilter/templates/admin_spamentry.html:61 #, python-format msgid "" "[1:%(karma)s]\n" " (marked as %(spam_or_ham)s)\n" " [2:\n" " [3:%(reasons)s]\n" " ]" msgstr "" "[1:%(karma)s]\n" "(%(spam_or_ham)s으로 표시됨)\n" "[2:[3:%(reasons)s]]" #: tracspamfilter/templates/admin_spamentry.html:71 msgid "Submitted content" msgstr "제출된 내용" #: tracspamfilter/templates/admin_spamentry.html:80 msgid "Mark as Spam" msgstr "스팸으로 표시" #: tracspamfilter/templates/admin_spamentry.html:81 msgid "Mark as Ham" msgstr "햄(유효)으로 표시" #: tracspamfilter/templates/admin_spamentry.html:83 msgid "Delete as Spam" msgstr "스팸으로 표시하고 삭제" #: tracspamfilter/templates/admin_spamentry.html:84 msgid "Delete as Ham" msgstr "햄(유효)으로 표시하고 삭제" #: tracspamfilter/templates/admin_spamentry.html:85 msgid "Delete (No Statistics)" msgstr "삭제 (통계 유지)" #: tracspamfilter/templates/admin_spamentry.html:93 msgid "Remove registered user" msgstr "등록된 사용자 삭제" #: tracspamfilter/templates/admin_spamentry.html:99 msgid "Search for user name" msgstr "사용자 이름 검색" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Note:" msgstr "주의:" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Logging by the spam filter is currently disabled." msgstr "스팸 필터에 의한 로깅이 현재 활성화되어 있지 않습니다." #: tracspamfilter/templates/admin_spammonitor.html:52 msgid "Delete > 90%" msgstr "90% 초과 항목 삭제" #: tracspamfilter/templates/admin_spammonitor.html:56 msgid "Mark selected as Spam" msgstr "선택한 항목을 스팸으로 표시" #: tracspamfilter/templates/admin_spammonitor.html:58 msgid "Mark selected as Ham" msgstr "선택한 항목을 햄(유효)으로 표시" #: tracspamfilter/templates/admin_spammonitor.html:62 msgid "Delete selected as Spam" msgstr "선택한 항목을 스팸으로 표시하고 삭제" #: tracspamfilter/templates/admin_spammonitor.html:64 msgid "Delete selected as Ham" msgstr "선택한 항목을 햄(유효)으로 표시하고 삭제" #: tracspamfilter/templates/admin_statistics.html:10 msgid "Spam Statistics" msgstr "스팸 통계" #: tracspamfilter/templates/admin_statistics.html:14 msgid "Spam Filtering: Statistics" msgstr "스팸 필터링: 통계" #: tracspamfilter/templates/admin_statistics.html:17 msgid "No submission statistics yet." msgstr "제출된 내용에 대한 통계가 아직 없습니다." #: tracspamfilter/templates/admin_statistics.html:21 #, python-format msgid "" "%(count)s Submissions tested since %(time)s,\n" " %(spam)s spam and\n" " %(ham)s ham\n" " (%(local)s of the tests could be solved local).\n" " %(spamerr)s spam and %(hamerr)s ham have been retrained." msgstr "" "%(time)s 이후로 %(count)s 개의 제출 내용을 테스트하였으며,\n" "%(spam)s는 스팸, %(ham)s는 햄(유효)입니다\n" "(%(local)s의 테스트를 내부 필터로 처리함).\n" "%(spamerr)s 개의 스팸과 %(hamerr)s 개의 햄이 재학습되었습니다." #: tracspamfilter/templates/admin_statistics.html:35 msgid "Type" msgstr "종류" #: tracspamfilter/templates/admin_statistics.html:37 msgid "Train / Verify" msgstr "학습 / 검증" #: tracspamfilter/templates/admin_statistics.html:38 msgid "Errors" msgstr "오류율" #: tracspamfilter/templates/admin_statistics.html:43 #: tracspamfilter/templates/admin_statistics.html:45 msgid "Spam" msgstr "스팸" #: tracspamfilter/templates/admin_statistics.html:44 #: tracspamfilter/templates/admin_statistics.html:46 msgid "Ham" msgstr "햄 (유효)" #: tracspamfilter/templates/admin_statistics.html:49 #: tracspamfilter/templates/admin_statistics.html:56 #: tracspamfilter/templates/admin_statistics.html:59 msgid "Total" msgstr "전체" #: tracspamfilter/templates/admin_statistics.html:50 msgid "Mean Delay" msgstr "평균 소요시간" #: tracspamfilter/templates/admin_statistics.html:51 msgid "No Result" msgstr "결과 없음" #: tracspamfilter/templates/admin_statistics.html:52 #: tracspamfilter/templates/admin_statistics.html:54 msgid "Match" msgstr "일치" #: tracspamfilter/templates/admin_statistics.html:53 #: tracspamfilter/templates/admin_statistics.html:55 msgid "Mismatch" msgstr "불일치" #: tracspamfilter/templates/admin_statistics.html:57 #: tracspamfilter/templates/admin_statistics.html:60 msgid "Right" msgstr "맞음" #: tracspamfilter/templates/admin_statistics.html:58 #: tracspamfilter/templates/admin_statistics.html:61 msgid "Wrong" msgstr "틀림" #: tracspamfilter/templates/admin_statistics.html:69 msgid "No tests yet." msgstr "테스트 결과가 아직 없습니다." #: tracspamfilter/templates/admin_statistics.html:72 #, python-format msgid "%(seconds)s s" msgstr "%(seconds)s 초" #: tracspamfilter/templates/admin_statistics.html:101 msgid "No spam yet." msgstr "스팸이 아직 없습니다." #: tracspamfilter/templates/admin_statistics.html:119 msgid "No ham yet." msgstr "햄(유효)이 아직 없습니다." #: tracspamfilter/templates/admin_statistics.html:141 msgid "Clear statistics" msgstr "통계 지우기" #: tracspamfilter/templates/admin_statistics.html:151 msgid "Clear statistics database" msgstr "통계 데이터베이스 지우기" #: tracspamfilter/templates/admin_user.html:10 msgid "Spam User Handling" msgstr "스팸 사용자 관리" #: tracspamfilter/templates/admin_user.html:14 #, python-format msgid "" "Spam Filtering: User handling (%(type)s)\n" " [1:%(count)s]" msgstr "" "스팸 필터링: 사용자 관리 (%(type)s)\n" "[1:%(count)s]" #: tracspamfilter/templates/admin_user.html:20 msgid "Overview" msgstr "개요" #: tracspamfilter/templates/admin_user.html:21 msgid "All" msgstr "전체" #: tracspamfilter/templates/admin_user.html:22 #: tracspamfilter/templates/usertable.html:15 msgid "Registered" msgstr "등록됨" #: tracspamfilter/templates/admin_user.html:23 msgid "Unused [multi selection]" msgstr "비활성 (다중 선택)" #: tracspamfilter/templates/admin_user.html:24 msgid "Unused" msgstr "비활성" #: tracspamfilter/templates/admin_user.html:28 #, python-format msgid "" "There are %(total)s\n" " different entries in the database, %(registered)s users are\n" " registered and %(unused)s have not been used." msgstr "" "데이터베이스에 %(total)s 개의 개별 항목이 존재하며, %(registered)s 명의 사용자가 등록하였고, " "%(unused)s 개 계정은 비활성 상태입니다." #: tracspamfilter/templates/admin_user.html:35 msgid "Date" msgstr "시간" #: tracspamfilter/templates/admin_user.html:36 #, python-format msgid "Action of user '%(username)s'" msgstr "'%(username)s' 사용자의 동작" #: tracspamfilter/templates/admin_user.html:55 msgid "Remove selected" msgstr "선택한 사용자 삭제" #: tracspamfilter/templates/admin_user.html:63 msgid "Values must be URL encoded!" msgstr "입력값은 URL 형식으로 인코드 되어 있어야 합니다." #: tracspamfilter/templates/admin_user.html:65 msgid "Old user:" msgstr "이전 사용자 이름:" #: tracspamfilter/templates/admin_user.html:68 msgid "New user:" msgstr "새 사용자 이름:" #: tracspamfilter/templates/admin_user.html:74 msgid "Change unauthorized user" msgstr "미인증 사용자 변경" #: tracspamfilter/templates/admin_user.html:74 msgid "Change user" msgstr "사용자 변경" #: tracspamfilter/templates/admin_user.html:81 #, python-format msgid "Remove %(num)s temporary session" msgid_plural "Remove %(num)s temporary sessions" msgstr[0] "임시 세션 %(num)s 개 삭제" #: tracspamfilter/templates/admin_user.html:86 msgid "Convert emails to registered usernames" msgstr "메일 주소를 등록된 사용자 이름으로 변경" #: tracspamfilter/templates/monitortable.html:15 msgid "IP Address" msgstr "IP 주소" #: tracspamfilter/templates/monitortable.html:16 msgid "Karma" msgstr "점수" #: tracspamfilter/templates/usertable.html:13 msgid "User name" msgstr "사용자 이름" #: tracspamfilter/templates/usertable.html:14 msgid "Last login" msgstr "최근 로그인" #: tracspamfilter/templates/usertable.html:16 msgid "Setup" msgstr "설정값" #: tracspamfilter/templates/usertable.html:17 msgid "E-Mail" msgstr "메일 주소" #: tracspamfilter/templates/usertable.html:18 msgid "Wiki edits" msgstr "위키 수정" #: tracspamfilter/templates/usertable.html:19 msgid "Ticket edits" msgstr "티켓 수정" #: tracspamfilter/templates/usertable.html:20 msgid "SVN edits" msgstr "SVN 수정" #: tracspamfilter/templates/usertable.html:21 msgid "Other" msgstr "기타" #: tracspamfilter/templates/usertable.html:39 msgid "Source" msgstr "출처" #: tracspamfilter/templates/usertable.html:49 #: tracspamfilter/templates/usertable.html:50 msgid "(password)" msgstr "(비밀번호)" #: tracspamfilter/templates/usertable.html:52 msgid "(double)" msgstr "(다중 등록)" #: tracspamfilter/templates/usertable.html:55 #: tracspamfilter/templates/usertable.html:61 #: tracspamfilter/templates/usertable.html:67 #: tracspamfilter/templates/usertable.html:73 msgid "user" msgstr "사용자" #: tracspamfilter/templates/usertable.html:56 #: tracspamfilter/templates/usertable.html:62 #: tracspamfilter/templates/usertable.html:68 #: tracspamfilter/templates/usertable.html:74 msgid "e-mail" msgstr "메일 주소" #: tracspamfilter/templates/usertable.html:57 #: tracspamfilter/templates/usertable.html:63 #: tracspamfilter/templates/usertable.html:69 #: tracspamfilter/templates/usertable.html:75 msgid "both" msgstr "둘 다" #: tracspamfilter/templates/usertable.html:83 msgid "Remove" msgstr "삭제" #: tracspamfilter/templates/verify_captcha.html:13 msgid "Captcha Error" msgstr "보안 문자 오류" #: tracspamfilter/templates/verify_captcha.html:17 msgid "" "Trac thinks your submission might be Spam.\n" " To prove otherwise please provide a response to the following." msgstr "" "Trac 시스템은 제출 내용이 스팸일 가능성이 있다고 판단하였습니다. 스팸이 아니라는 것을 증명하려면, 다음 내용에 대해 대답해 " "주십시오." #: tracspamfilter/templates/verify_captcha.html:19 msgid "" "Note - the captcha method is choosen randomly. Retry if the captcha does " "not work on your system!" msgstr "참고 - 보안 문자 방식은 무작위로 선택되었습니다. 만약 보안 문자가 표시되지 않았다면 재시도 하십시오." #: tracspamfilter/templates/verify_captcha.html:22 msgid "Response:" msgstr "대답:" #~ msgid "Lifetime has invalid value" #~ msgstr "유효 시간 값이 올바르지 않습니다." #~ msgid "Text values are not numeric" #~ msgstr "수식 보안 문자에 대해 지정한 값이 숫자가 아닙니다." #~ msgid "Numeric image values are no numbers" #~ msgstr "숫자 이미지에 대해 지정한 값이 숫자가 아닙니다." #~ msgid "Data validation failed:" #~ msgstr "데이터 검증 실패:" spam-filter/tracspamfilter/locale/de/0000755000175500017550000000000012725137772017717 5ustar debacledebaclespam-filter/tracspamfilter/locale/de/LC_MESSAGES/0000755000175500017550000000000012725137772021504 5ustar debacledebaclespam-filter/tracspamfilter/locale/de/LC_MESSAGES/tracspamfilter.po0000644000175500017550000021523712725137772025076 0ustar debacledebacle# German translations for TracSpamFilter. # Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the TracSpamFilter # project. # # Translators: # Dirk Stöcker , 2014,2016 msgid "" msgstr "" "Project-Id-Version: Trac Plugin L10N\n" "Report-Msgid-Bugs-To: trac@dstoecker.de\n" "POT-Creation-Date: 2016-03-05 18:00-0800\n" "PO-Revision-Date: 2016-03-30 13:17+0000\n" "Last-Translator: Dirk Stöcker \n" "Language-Team: German (http://www.transifex.com/hasienda/Trac_Plugin-" "L10N/language/de/)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" #: tracspamfilter/accountadapter.py:33 msgid "" "Interface of spamfilter to account manager plugin to check new account\n" "registrations for spam.\n" "\n" "It provides an additional 'Details' input field to get more information\n" "for calculating the probability of a spam registration attempt.\n" "Knowledge gained from inspecting registration attempts is shared with all" "\n" "other spam filter adapters for this system." msgstr "" "Spamfilter-Interface zum AccountManager-Plugin, um neue\n" "Benutzerregistrierungen auf Spam zu prüfen.\n" "\n" "Es bietet ein zusätzliches 'Details'-Eingabefeld, um mehr Informationen\n" "zur Berechnung der Spamwahrscheinlichkeit eines Spamregistrierversuches " "zu erhalten.\n" "Die Informationen, welche bei der Analyse eines Registrierversuches " "erhalten werden,\n" "werden mit allen anderen Spamfilteradaptern dieses Systems ausgetauscht." #: tracspamfilter/accountadapter.py:47 msgid "Details:" msgstr "Details:" #: tracspamfilter/adapters.py:106 msgid "" "The maximum number of bytes from an attachment to pass through\n" "the spam filters." msgstr "" "Die maximale Anzahl von Bytes eines Anhanges, welcher durch den " "Spamfilter geschickt werden soll." #: tracspamfilter/admin.py:75 msgid "How many monitor entries are displayed by default (between 5 and 10000)." msgstr "" "Wie viele Monitoreinträge werden standardmäßig angezeigt (zwischen 5 und " "10000)." #: tracspamfilter/admin.py:79 msgid "Show the buttons for training without deleting entry." msgstr "Zeige die Knöpfe zum Training ohne den Eintrag zu löschen." #: tracspamfilter/admin.py:87 tracspamfilter/admin.py:90 #: tracspamfilter/admin.py:315 tracspamfilter/admin.py:500 #: tracspamfilter/admin.py:593 tracspamfilter/admin.py:641 #: tracspamfilter/adminreport.py:43 tracspamfilter/adminusers.py:48 msgid "Spam Filtering" msgstr "Spamfilter" #: tracspamfilter/admin.py:88 tracspamfilter/templates/admin_bayes.html:19 msgid "Configuration" msgstr "Einstellungen" #: tracspamfilter/admin.py:91 msgid "Monitoring" msgstr "Überwachung" #: tracspamfilter/admin.py:113 tracspamfilter/filters/bayes.py:78 #, python-format msgid "SpamBayes determined spam probability of %s%%" msgstr "SpamBayes hat eine Spamwahrscheinlichkeit von %s%% ermittelt." #: tracspamfilter/admin.py:115 #, python-format msgid "Select 100.00%% entries" msgstr "100%%-Einträge wählen" #: tracspamfilter/admin.py:116 #, python-format msgid "Select >90.00%% entries" msgstr "Einträge >90%% wählen" #: tracspamfilter/admin.py:117 #, python-format msgid "Select <10.00%% entries" msgstr "Einträge <10%% wählen" #: tracspamfilter/admin.py:118 #, python-format msgid "Select 0.00%% entries" msgstr "0.00%%-Einträge wählen" #: tracspamfilter/admin.py:119 msgid "Select Spam entries" msgstr "Spam-Einträge wählen" #: tracspamfilter/admin.py:120 msgid "Select Ham entries" msgstr "Ham-Einträge wählen" #: tracspamfilter/admin.py:246 tracspamfilter/adminreport.py:110 #: tracspamfilter/templates/admin_report.html:31 #: tracspamfilter/templates/admin_report.html:32 #: tracspamfilter/templates/admin_spammonitor.html:29 #: tracspamfilter/templates/admin_spammonitor.html:30 msgid "Previous Page" msgstr "Vorige Seite" #: tracspamfilter/admin.py:250 tracspamfilter/adminreport.py:114 #: tracspamfilter/templates/admin_report.html:37 #: tracspamfilter/templates/admin_report.html:38 #: tracspamfilter/templates/admin_spammonitor.html:35 #: tracspamfilter/templates/admin_spammonitor.html:36 msgid "Next Page" msgstr "Nächste Seite" #: tracspamfilter/admin.py:267 msgid "Log entry not found" msgstr "Logeintrag nicht gefunden" #: tracspamfilter/admin.py:272 tracspamfilter/admin.py:277 #, python-format msgid "Log Entry %(id)s" msgstr "Logeintrag %(id)s" #: tracspamfilter/admin.py:273 msgid "Log Entry List" msgstr "Logliste" #: tracspamfilter/admin.py:316 msgid "External" msgstr "Extern" #: tracspamfilter/admin.py:500 tracspamfilter/templates/admin_bayes.html:10 msgid "Bayes" msgstr "Bayes" #: tracspamfilter/admin.py:552 #, python-format msgid "(ratio %.1f : 1)" msgstr "(Rate %.1f : 1)" #: tracspamfilter/admin.py:554 #, python-format msgid "(ratio 1 : %.1f)" msgstr "(Rate 1 : %.1f)" #: tracspamfilter/admin.py:566 #, python-format msgid "%(num)d spam" msgid_plural "%(num)d spam" msgstr[0] "Spam: %(num)d" msgstr[1] "Spam: %(num)d" #: tracspamfilter/admin.py:568 #, python-format msgid "%(num)d ham" msgid_plural "%(num)d ham" msgstr[0] "Ham: %(num)d" msgstr[1] "Ham: %(num)d" #: tracspamfilter/admin.py:570 #, python-format msgid "%(num)d line" msgid_plural "%(num)d lines" msgstr[0] "%(num)d Zeile" msgstr[1] "%(num)d Zeilen" #: tracspamfilter/admin.py:572 #, python-format msgid "%(num)d mixed" msgid_plural "%(num)d mixed" msgstr[0] "Gemischt: %(num)d" msgstr[1] "Gemischt: %(num)d" #: tracspamfilter/admin.py:594 msgid "Statistics" msgstr "Statistik" #: tracspamfilter/admin.py:641 msgid "Captcha" msgstr "Captcha" #: tracspamfilter/admin.py:663 tracspamfilter/admin.py:697 #: tracspamfilter/admin.py:706 tracspamfilter/admin.py:720 #: tracspamfilter/admin.py:729 #, python-format msgid "Invalid value for %(key)s" msgstr "Ungültiger Wert für \"%(key)s\"." #: tracspamfilter/admin.py:673 msgid "The keys are invalid" msgstr "Die Schlüssel sind ungültig" #: tracspamfilter/admin.py:685 msgid "The key or user id are invalid" msgstr "Die Schlüssel oder die Nutzerkennung sind ungültig" #: tracspamfilter/adminreport.py:32 msgid "How many report entries are displayed by default (between 5 and 10000)." msgstr "" "Wie viele Berichtseinträge werden standardmäßig angezeigt (zwischen 5 und" " 10000)." #: tracspamfilter/adminreport.py:43 msgid "Reports" msgstr "Berichte" #: tracspamfilter/adminreport.py:134 msgid "Report entry not found" msgstr "Berichtseintrag nicht gefunden" #: tracspamfilter/adminreport.py:142 tracspamfilter/adminreport.py:149 #, python-format msgid "Report Entry %d" msgstr "Berichtseintrag %d" #: tracspamfilter/adminreport.py:143 msgid "Report Entry List" msgstr "Berichtsliste" #: tracspamfilter/adminusers.py:29 msgid "How many wiki edits are still an unused account." msgstr "Wieviele Wikiänderungen gelten trotzdem als inaktiver Nutzer." #: tracspamfilter/adminusers.py:33 msgid "How many days no login are considered for dead accounts." msgstr "Wie viele Tage keine Anmeldung gelten als inaktiver Nutzer." #: tracspamfilter/adminusers.py:38 msgid "Default mode for spam user admin panel." msgstr "Voreinstellung für Spam-Nutzerbearbeitung." #: tracspamfilter/adminusers.py:48 msgid "Users" msgstr "Nutzer" #: tracspamfilter/adminusers.py:58 msgid "Old or new value cannot be empty" msgstr "Alter oder neuer Wert dürfen nicht leer sein" #: tracspamfilter/adminusers.py:66 msgid "Old and new value cannot be equal" msgstr "Alter und neuer Wert dürfen nicht gleich sein" #: tracspamfilter/adminusers.py:69 msgid "New name cannot be used in CC fields" msgstr "Neuer Name kann in CC-Feldern nicht genutzt werden" #: tracspamfilter/adminusers.py:71 msgid "Illegal user arguments passed or changing not allowed" msgstr "Ungültige Argumente übergeben oder die Änderung ist nicht erlaubt" #: tracspamfilter/adminusers.py:73 #, python-format msgid "%(num)d entry has been updated" msgid_plural "%(num)d entries have been updated" msgstr[0] "%(num)d Eintrag wurde aktualisiert" msgstr[1] "%(num)d Einträge wurden aktualisiert" #: tracspamfilter/adminusers.py:80 #, python-format msgid "Username '%s' cannot be used in CC fields" msgstr "Nutzername '%s' kann in CC-Feldern nicht genutzt werden" #: tracspamfilter/adminusers.py:82 #, python-format msgid "Error for e-mail change for username '%s'" msgstr "Fehler bei der E-Mail-Änderung für Nutzernamen '%s'" #: tracspamfilter/adminusers.py:84 #, python-format msgid "%(num)d entry has been updated for user %(user)s" msgid_plural "%(num)d entries have been updated for user %(user)s" msgstr[0] "%(num)d Eintrag für Nutzer %(user)s wurde aktualisiert" msgstr[1] "%(num)d Einträge für Nutzer %(user)s wurden aktualisiert" #: tracspamfilter/adminusers.py:87 #, python-format msgid "E-mails for user %s updated" msgstr "E-Mails für Nutzer %s aktualisiert" #: tracspamfilter/adminusers.py:118 msgid "data overview" msgstr "Datenübersicht" #: tracspamfilter/adminusers.py:120 msgid "unused accounts" msgstr "Unbenutzte Zugänge" #: tracspamfilter/adminusers.py:122 msgid "registered accounts" msgstr "Registrierte Nutzer" #: tracspamfilter/adminusers.py:124 #, python-format msgid "detailed user information for '%s'" msgstr "Detailierte Nutzerinformationen für '%s'" #: tracspamfilter/adminusers.py:126 msgid "everything from accounts, wiki, tickets and svn" msgstr "Alles von Zugängen, Wiki, Tickets und SVN" #: tracspamfilter/adminusers.py:127 #, python-format msgid "%(num)d entry" msgid_plural "%(num)d entries" msgstr[0] "%(num)d Eintrag" msgstr[1] "%(num)d Einträge" #: tracspamfilter/filtersystem.py:52 msgid "The minimum score required for a submission to be allowed." msgstr "Der minimal nötige Wert für eine erlaubte Übertragung." #: tracspamfilter/filtersystem.py:56 msgid "" "The karma given to authenticated users, in case\n" "`trust_authenticated` is false." msgstr "" "Das Karma, welches angemeldeten Nutzern gegeben wird, wenn\n" "`trust_authenticated` nicht gesetzt ist." #: tracspamfilter/filtersystem.py:60 msgid "" "Whether all content submissions and spam filtering activity should\n" "be logged to the database." msgstr "" "Legt fest, ob alle Übertragungen und Spamfilteraktivitäten in der\n" "Datenbank aufgezeichnet werden." #: tracspamfilter/filtersystem.py:64 msgid "The number of days after which log entries should be purged." msgstr "Die Anzahl an Tagen, nach denen Aufzeichnungen gelöscht werden." #: tracspamfilter/filtersystem.py:68 msgid "Allow usage of external services." msgstr "Nutzung externer Dienste erlauben." #: tracspamfilter/filtersystem.py:71 msgid "" "Skip external calls when this negative karma is already reached\n" "by internal tests." msgstr "" "Externe Tests überspringen, wenn dieser negative Karma-Wert bereits\n" "durch interne Tests erreicht wird." #: tracspamfilter/filtersystem.py:75 msgid "" "Skip external calls when this positive karma is already reached\n" "by internal tests." msgstr "" "Externe Tests überspringen, wenn dieser positive Karma-Wert bereits\n" "durch interne Tests erreicht wird." #: tracspamfilter/filtersystem.py:79 msgid "Stop external calls when this negative karma is reached." msgstr "Externe Tests abbrechen, wenn dieser negative Karma-Wert erreicht ist." #: tracspamfilter/filtersystem.py:83 msgid "Stop external calls when this positive karma is reached." msgstr "Externe Tests abbrechen, wenn dieser postive Karma-Wert erreicht ist." #: tracspamfilter/filtersystem.py:87 msgid "Allow training of external services." msgstr "Training externer Dienste erlauben." #: tracspamfilter/filtersystem.py:90 msgid "" "Whether content submissions by authenticated users should be trusted\n" "without checking for potential spam or other abuse." msgstr "" "Legt fest, ob Übertragungen von angemeldeten Nutzern ohne Prüfung\n" "auf Spam oder Missbrauch vertraut werden soll." #: tracspamfilter/filtersystem.py:96 msgid "The karma given to attachments." msgstr "Das an Anhänge vergebene Karma." #: tracspamfilter/filtersystem.py:99 msgid "The karma given to registrations." msgstr "Das an Registrierungen vergebene Karma." #: tracspamfilter/filtersystem.py:102 msgid "The handler used to reject content." msgstr "Die Bearbeitungsroutine für zurückgewiesene Inhalte." #: tracspamfilter/filtersystem.py:106 msgid "Interpret X-Forwarded-For header for IP checks." msgstr "Die Kopfzeile X-Forwarded-For für IP-Prüfungen nutzen." #: tracspamfilter/filtersystem.py:110 msgid "" "This section is used to handle all configurations used by\n" "spam filter plugin." msgstr "" "Dieser Abschnitt wird genutzt, um alle Einstellungen des SpamFilter-" "Plugins zu speichern." #: tracspamfilter/filtersystem.py:169 msgid "User is authenticated" msgstr "Nutzer ist angemeldet" #: tracspamfilter/filtersystem.py:175 msgid "Attachment weighting" msgstr "Bewertung von Anhängen" #: tracspamfilter/filtersystem.py:180 msgid "Registration weighting" msgstr "Bewertung von Registrierungen" #: tracspamfilter/filtersystem.py:304 #, python-format msgid "Submission rejected as potential spam %(message)s" msgstr "Übertragung wird als potentieller Spam zurückgewiesen %(message)s" #: tracspamfilter/model.py:422 msgid "external" msgstr "extern" #: tracspamfilter/model.py:422 msgid "internal" msgstr "intern" #: tracspamfilter/report.py:29 msgid "List of page types to add spam report link" msgstr "Liste von Seiten, auf denen ein Meldelink für Spam eingefügt werden soll" #: tracspamfilter/report.py:42 msgid "No page supplied to report as spam" msgstr "Keine Seite zum Melden als Spam angegeben" #: tracspamfilter/report.py:74 msgid "Reported spam" msgstr "Spam gemeldet" #: tracspamfilter/report.py:76 msgid "Comment" msgstr "Kommentar" #: tracspamfilter/report.py:78 msgid "Report spam" msgstr "Spam melden" #: tracspamfilter/report.py:82 #, python-format msgid "%(num)d spam report" msgid_plural "%(num)d spam reports" msgstr[0] "%(num)d Spam-Bericht" msgstr[1] "%(num)d Spam-Berichte" #: tracspamfilter/users.py:38 #, python-format msgid "Wiki page '%(page)s' version %(version)s modified" msgstr "Version %(version)s der Wikiseite '%(page)s' geändert" #: tracspamfilter/users.py:41 #, python-format msgid "Attachment '%s' added" msgstr "Anhang '%s' hinzugefügt" #: tracspamfilter/users.py:45 #, python-format msgid "Ticket %(id)s field '%(field)s' changed" msgstr "Feld '%(field)s' von Ticket %(id)s geändert" #: tracspamfilter/users.py:48 #, python-format msgid "Removed from ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "Feld '%(field)s' von Ticket %(id)s entfernt ('%(old)s' --> '%(new)s')" #: tracspamfilter/users.py:50 #, python-format msgid "Set in ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "Feld '%(field)s' in Ticket %(id)s gesetzt ('%(old)s' --> '%(new)s')" #: tracspamfilter/users.py:56 #, python-format msgid "Ticket %(id)s CC field change ('%(old)s' --> '%(new)s')" msgstr "Anderung CC-Feld in Ticket %(id)s ('%(old)s' --> '%(new)s')" #: tracspamfilter/users.py:60 #, python-format msgid "Reporter of ticket %s" msgstr "Ersteller von Ticket %s" #: tracspamfilter/users.py:62 #, python-format msgid "Owner of ticket %s" msgstr "Verantwortlicher für Ticket %s" #: tracspamfilter/users.py:64 #, python-format msgid "In CC of ticket %(id)s ('%(cc)s')" msgstr "Im CC-Feld von Ticket %(id)s ('%(cc)s')" #: tracspamfilter/users.py:67 #, python-format msgid "Author of revision %s" msgstr "Autor der Revision %s" #: tracspamfilter/users.py:70 #, python-format msgid "Component '%s' owner" msgstr "Eigentümer der Komponente '%s'" #: tracspamfilter/users.py:73 msgid "In permissions list" msgstr "In Liste der Berechtigungen" #: tracspamfilter/users.py:76 #, python-format msgid "Author of report %d" msgstr "Autor des Berichts %d" #: tracspamfilter/users.py:81 tracspamfilter/users.py:85 #, python-format msgid "Voted for '%s'" msgstr "Für '%s' gestimmt" #: tracspamfilter/captcha/api.py:59 msgid "CAPTCHA method to use for verifying humans." msgstr "Captcha-Methode zur Erkennung von Menschen." #: tracspamfilter/captcha/api.py:64 msgid "" "By how many points a successful CAPTCHA response increases the\n" "overall score." msgstr "" "Punktanzahl mit der eine eine erfolgreiche Captcha-Antwort das\n" "Gesamtkarma beeinflusst." #: tracspamfilter/captcha/api.py:69 msgid "By how many points a failed CAPTCHA impacts the overall score." msgstr "" "Punktanzahl mit der eine fehlgeschlagene Captcha-Antwort das\n" "Gesamtkarma beeinflusst." #: tracspamfilter/captcha/api.py:73 msgid "" "Time in seconds that a successful CAPTCHA response increases\n" "karma." msgstr "" "Zeit in Sekunden, in welcher eine erfolgreiche Captcha-Antwort\n" "das Karma erhöht." #: tracspamfilter/captcha/api.py:77 msgid "Time in seconds before CAPTCHA is removed." msgstr "Zeit in Sekunden, bevor ein Captcha gelöscht wird." #: tracspamfilter/captcha/api.py:81 msgid "Time in seconds before database cleanup is called." msgstr "Zeit in Sekunden, bevor die Datenbank aufgeräumt wird." #: tracspamfilter/captcha/api.py:100 #, python-format msgid "Human verified via CAPTCHA (%s)" msgstr "Durch Captcha (%s) verifizierter Mensch" #: tracspamfilter/captcha/api.py:109 #, python-format msgid "Failed CAPTCHA (%s) attempts" msgstr "Fehlgeschlagene Captcha-Versuche (%s)" #: tracspamfilter/captcha/api.py:141 msgid "CAPTCHA failed to handle original request" msgstr "Captcha konnte Originalanfrage nicht bearbeiten" #: tracspamfilter/captcha/api.py:143 msgid "CAPTCHA verification failed" msgstr "Captcha-Prüfung fehlgeschlagen" #: tracspamfilter/captcha/expression.py:32 msgid "Number of terms in numeric CAPTCHA expression." msgstr "Anzahl von Termen in einem numerischen Captcha-Ausdruck." #: tracspamfilter/captcha/expression.py:36 msgid "" "Maximum value of individual terms in numeric CAPTCHA\n" "expression." msgstr "" "Höchster Wert eines einzelnen Terms in einem numerischen\n" "Captcha-Ausdruck." #: tracspamfilter/captcha/expression.py:40 msgid "multiplied by" msgstr "multipliziert mit" #: tracspamfilter/captcha/expression.py:40 msgid "minus" msgstr "minus" #: tracspamfilter/captcha/expression.py:41 msgid "plus" msgstr "plus" #: tracspamfilter/captcha/expression.py:43 msgid "zero" msgstr "Null" #: tracspamfilter/captcha/expression.py:43 msgid "one" msgstr "Eins" #: tracspamfilter/captcha/expression.py:43 msgid "two" msgstr "Zwei" #: tracspamfilter/captcha/expression.py:43 msgid "three" msgstr "Drei" #: tracspamfilter/captcha/expression.py:43 msgid "four" msgstr "Vier" #: tracspamfilter/captcha/expression.py:44 msgid "five" msgstr "Fünf" #: tracspamfilter/captcha/expression.py:44 msgid "six" msgstr "Sechs" #: tracspamfilter/captcha/expression.py:44 msgid "seven" msgstr "Sieben" #: tracspamfilter/captcha/expression.py:44 msgid "eight" msgstr "Acht" #: tracspamfilter/captcha/expression.py:45 msgid "nine" msgstr "Neun" #: tracspamfilter/captcha/expression.py:45 msgid "ten" msgstr "Zehn" #: tracspamfilter/captcha/expression.py:45 msgid "eleven" msgstr "Elf" #: tracspamfilter/captcha/expression.py:45 msgid "twelve" msgstr "Zwölf" #: tracspamfilter/captcha/expression.py:46 msgid "thirteen" msgstr "Dreizehn" #: tracspamfilter/captcha/expression.py:46 msgid "fourteen" msgstr "Vierzehn" #: tracspamfilter/captcha/expression.py:46 msgid "fifteen" msgstr "Fünfzehn" #: tracspamfilter/captcha/expression.py:47 msgid "sixteen" msgstr "Sechzehn" #: tracspamfilter/captcha/expression.py:47 msgid "seventeen" msgstr "Siebzehn" #: tracspamfilter/captcha/expression.py:47 msgid "eighteen" msgstr "Achtzehn" #: tracspamfilter/captcha/expression.py:48 msgid "nineteen" msgstr "Neunzehn" #. TRANSLATOR: if compound numbers like in english are not #. supported, simply add a "plus" command to the following #. translations! #: tracspamfilter/captcha/expression.py:53 msgid "twenty" msgstr "Zwanzig plus" #: tracspamfilter/captcha/expression.py:53 msgid "thirty" msgstr "Dreißig plus" #: tracspamfilter/captcha/expression.py:53 msgid "forty" msgstr "Vierzig plus" #: tracspamfilter/captcha/expression.py:53 msgid "fifty" msgstr "Fünfzig plus" #: tracspamfilter/captcha/expression.py:54 msgid "sixty" msgstr "Sechzig plus" #: tracspamfilter/captcha/expression.py:54 msgid "seventy" msgstr "Siebzig plus" #: tracspamfilter/captcha/expression.py:54 msgid "eighty" msgstr "Achtzig plus" #: tracspamfilter/captcha/expression.py:54 msgid "ninety" msgstr "Neunzig plus" #: tracspamfilter/captcha/expression.py:61 msgid "Numeric captcha can not represent numbers > 100" msgstr "Numerische Captcha kann keine Zahlen größer 100 nutzen" #: tracspamfilter/captcha/image.py:43 msgid "Set of fonts to choose from when generating image CAPTCHA." msgstr "Liste von Schriftarten für die Erzeugung des Bildcaptchas." #: tracspamfilter/captcha/image.py:47 msgid "Font size to use in image CAPTCHA." msgstr "Schriftgröße in einem Bildcaptcha." #: tracspamfilter/captcha/image.py:51 msgid "Alphabet to choose image CAPTCHA challenge from." msgstr "Alphabet, aus dem die Captcha-Frage eines Bildcaptchas besteht." #: tracspamfilter/captcha/image.py:56 msgid "Number of letters to use in image CAPTCHA challenge." msgstr "Anzahl von Buchstaben in der Bildcaptcha-Frage." #: tracspamfilter/captcha/keycaptcha.py:32 msgid "Private key for KeyCaptcha usage." msgstr "Privater Schlüssel for KeyCaptcha-Nutzung." #: tracspamfilter/captcha/keycaptcha.py:36 msgid "User id for KeyCaptcha usage." msgstr "Nutzerkennung für KeyCaptcha-Nutzung." #: tracspamfilter/captcha/recaptcha.py:32 #: tracspamfilter/captcha/recaptcha2.py:34 msgid "Private key for reCaptcha usage." msgstr "Privater Schlüssel for reCaptcha-Nutzung." #: tracspamfilter/captcha/recaptcha.py:36 #: tracspamfilter/captcha/recaptcha2.py:38 msgid "Public key for reCaptcha usage." msgstr "Öffentlicher Schlüssel für reCaptcha-Nutzung." #: tracspamfilter/captcha/recaptcha.py:59 #: tracspamfilter/captcha/recaptcha2.py:50 #: tracspamfilter/templates/verify_captcha.html:24 msgid "Submit" msgstr "Absenden" #: tracspamfilter/filters/akismet.py:42 msgid "" "By how many points an Akismet reject impacts the overall karma of\n" "a submission." msgstr "" "Punktanzahl mit der eine Akismet-Ablehnung das Gesamtkarma\n" "einer Übertragung beeinflusst." #: tracspamfilter/filters/akismet.py:46 msgid "Wordpress key required to use the Akismet API." msgstr "Ein Wordpress-Schlüssel wird zur Nutzung des Akismet-API benötigt." #: tracspamfilter/filters/akismet.py:50 msgid "URL of the Akismet service." msgstr "URL des Akismet-Dienstes." #: tracspamfilter/filters/akismet.py:75 msgid "Akismet says content is spam" msgstr "Akismet bezeichnet den Inhalt als Spam" #: tracspamfilter/filters/bayes.py:33 msgid "" "By what factor Bayesian spam probability score affects the overall\n" "karma of a submission." msgstr "" "Punktanzahl mit der die Bayes-Spamwahrscheinlichkeit das Gesamtkarma\n" "einer Übertragung beeinflusst." #: tracspamfilter/filters/bayes.py:37 msgid "" "The minimum number of submissions in the training database required\n" "for the filter to start impacting the karma of submissions." msgstr "" "Die Minimalzahl an Übertragungen in der Trainingsdatenbank, bevor\n" "der Filter das Karma von Übertragungen beeinflusst." #: tracspamfilter/filters/bayes.py:42 msgid "" "Entries with a count less than this value get removed from the\n" "database when calling the reduce function." msgstr "" "Einträge mit einer Anzahl kleiner als dieser Wert werden beim Aufruf der " "Funktion zum Reduzieren der Datenbank entfernt." #: tracspamfilter/filters/blogspam.py:35 msgid "" "By how many points an BlogSpam reject impacts the overall karma of\n" "a submission." msgstr "" "Punktanzahl mit der eine BlogSpam-Ablehnung das Gesamtkarma\n" "einer Übertragung beeinflusst." #: tracspamfilter/filters/blogspam.py:39 msgid "URL of the BlogSpam service." msgstr "URL des BlogSpam-Dienstes." #: tracspamfilter/filters/blogspam.py:42 msgid "Comma separated list of tests to skip." msgstr "Kommagetrennte Liste von zu überspringenden Tests." #: tracspamfilter/filters/blogspam.py:64 #, python-format msgid "BlogSpam says content is spam (%s [%s])" msgstr "BlogSpam bezeichnet den Inhalt als Spam (%s [%s])" #: tracspamfilter/filters/botscout.py:31 msgid "" "By how many points a BotScout reject impacts the overall karma of\n" "a submission." msgstr "" "Punktanzahl mit der eine BotScout-Ablehnung das Gesamtkarma\n" "einer Übertragung beeinflusst." #: tracspamfilter/filters/botscout.py:35 msgid "API key required to use BotScout." msgstr "Für BotScout benötigter API-Schlüssel." #: tracspamfilter/filters/botscout.py:61 #, python-format msgid "BotScout says this is spam (%s)" msgstr "BotScout bezeichnet dies als Spam (%s)" #: tracspamfilter/filters/extlinks.py:28 msgid "" "By how many points too many external links in a submission impact\n" "the overall score." msgstr "" "Punktanzahl mit der zu viele externe Links das Gesamtkarma\n" "einer Übertragung beeinflussen." #: tracspamfilter/filters/extlinks.py:32 msgid "" "The maximum number of external links allowed in a submission until\n" "that submission gets negative karma." msgstr "" "Die Maximalzahl an erlaubten externen Links in einer Übertragung,\n" "bevor ein negatives Karma entsteht." #: tracspamfilter/filters/extlinks.py:36 msgid "List of domains that should be allowed in external links" msgstr "Liste von Domainnamen, welche in externen Links erlaubt sind" #: tracspamfilter/filters/extlinks.py:64 msgid "Maximum number of external links per post exceeded" msgstr "Anzahl von externen Links pro Übertragung überschritten" #: tracspamfilter/filters/extlinks.py:67 msgid "External links in post found" msgstr "Externe Links in Übertragung gefunden" #: tracspamfilter/filters/fspamlist.py:32 msgid "" "By how many points a FSpamList reject impacts the overall karma of\n" "a submission." msgstr "" "Punktanzahl mit der eine FSpamList-Ablehnung das Gesamtkarma\n" "einer Übertragung beeinflusst." #: tracspamfilter/filters/fspamlist.py:36 msgid "API key required to use FSpamList." msgstr "Für FSpamList benötigter API-Schlüssel." #: tracspamfilter/filters/fspamlist.py:67 #, python-format msgid "FSpamList says this is spam (%s)" msgstr "FSpamList bezeichnet dies als Spam (%s)" #: tracspamfilter/filters/httpbl.py:34 msgid "" "By how many points listing as \"comment spammer\" impacts the\n" "overall karma of a submission." msgstr "" "Punktanzahl mit der eine Eintragung als \"Kommentarspammer\" das\n" "Gesamtkarma einer Übertragung beeinflusst." #: tracspamfilter/filters/httpbl.py:38 msgid "Http:BL API key required for use." msgstr "Für Http:BL benötigter API-Schlüssel." #: tracspamfilter/filters/httpbl.py:81 #, python-format msgid "IP %s blacklisted by Http:BL" msgstr "IP %s ist in der schwarzen Liste von Http:BL" #: tracspamfilter/filters/ip_blacklist.py:34 msgid "" "By how many points blacklisting by a single server impacts the\n" "overall karma of a submission." msgstr "" "Punktanzahl mit der ein Eintrag auf der schwarzen Liste eines\n" "Servers das Gesamtkarma einer Übertragung beeinflusst." #: tracspamfilter/filters/ip_blacklist.py:39 msgid "Servers used for IPv4 blacklisting." msgstr "Server mit schwarzen Listen für IPv4-Adressen." #: tracspamfilter/filters/ip_blacklist.py:43 msgid "Servers used for IPv6 blacklisting." msgstr "Server mit schwarzen Listen für IPv6-Adressen." #: tracspamfilter/filters/ip_blacklist.py:87 #, python-format msgid "IP %s blacklisted by %s" msgstr "IP %s ist in der schwarzen Liste von %s" #: tracspamfilter/filters/ip_regex.py:34 msgid "" "By how many points a match with a pattern on the BadIP page\n" "impacts the overall karma of a submission." msgstr "" "Punktanzahl mit der eine Übereinstimmung mit einem Eintrag auf\n" "der BadIP-Seite das Gesamtkarma einer Übertragung beeinflusst." #: tracspamfilter/filters/ip_regex.py:37 msgid "" "Local file to be loaded to get BadIP. Can be used in\n" "addition to BadIP wiki page." msgstr "" "Lokale Datei, welche unerwünschte IP-Daten enthält.\n" "Kann zusätzlich zur BadIP-Wikiseite genutzt werden." #: tracspamfilter/filters/ip_regex.py:40 msgid "Show the matched bad IP patterns in rejection message." msgstr "" "Zeige übereinstimmende Muster der unerwünschten IP-Adressen in der " "Abweisungsnachricht." #: tracspamfilter/filters/ip_regex.py:76 #, python-format msgid "IP catched by these blacklisted patterns: %s" msgstr "IP wurde von folgenden Mustern der schwarzen Liste getroffen: %s" #: tracspamfilter/filters/ip_regex.py:78 #, python-format msgid "IP catched by %s blacklisted patterns" msgstr "IP wurde von %s Mustern der schwarzen Liste getroffen" #: tracspamfilter/filters/ip_throttle.py:27 msgid "" "By how many points exceeding the configured maximum number of posts\n" "per hour impacts the overall score." msgstr "" "Punktanzahl mit der eine Überschreitung der maximalen Anzahl an\n" "Übertragungen je Stunde das Gesamtkarma einer Übertragung beeinflusst." #: tracspamfilter/filters/ip_throttle.py:31 msgid "" "The maximum allowed number of submissions per hour from a single IP\n" "address. If this limit is exceeded, subsequent submissions get negative\n" "karma." msgstr "" "Die maximal erlaubte Anzahl an Übertragungen je Stunde von einer " "einzelnen\n" "IP-Adresse. Falls dieses Limit überschritten wird, erhalten alle " "folgenden\n" "Übertragungen ein negatives Karma." #: tracspamfilter/filters/ip_throttle.py:52 msgid "Maximum number of posts per hour for this IP exceeded" msgstr "Maximale Anzahl an Übertragungen je Stunde für diese IP überschritten" #: tracspamfilter/filters/mollom.py:36 msgid "" "By how many points an Mollom reject impacts the overall karma of\n" "a submission." msgstr "" "Punktanzahl mit der eine Mollom-Ablehnung das Gesamtkarma\n" "einer Übertragung beeinflusst." #: tracspamfilter/filters/mollom.py:40 msgid "Public key required to use the Mollom API." msgstr "Öffentlicher Schlüssel zur Benutzung des Mollom-API." #: tracspamfilter/filters/mollom.py:44 msgid "Private key required to use the Mollom API." msgstr "Privater Schlüssel zur Benutzung des Mollom-API." #: tracspamfilter/filters/mollom.py:48 msgid "URL of the Mollom service." msgstr "URL des Mollom-Dienstes." #: tracspamfilter/filters/mollom.py:95 msgid "Mollom says content is spam" msgstr "Mollom bezeichnet den Inhalt als Spam" #: tracspamfilter/filters/mollom.py:106 msgid "Mollom says content is ham" msgstr "Mollom bezeichnet den Inhalt als Ham" #: tracspamfilter/filters/regex.py:31 msgid "" "By how many points a match with a pattern on the BadContent page\n" "impacts the overall karma of a submission." msgstr "" "Punktanzahl mit der eine Zurückweisung durch ein Muster der BadContent-" "Seite das\n" "Gesamtkarma eine Übertragung beeinflusst." #: tracspamfilter/filters/regex.py:34 msgid "" "Local file to be loaded to get BadContent. Can be used in\n" "addition to BadContent wiki page." msgstr "" "Lokale Datei zum Laden der Beschreibung unerwünschter Inhalte. Kann " "zusätzlich zur\n" "BadContent-Wikiseite verwendet werden." #: tracspamfilter/filters/regex.py:37 msgid "Show the matched bad content patterns in rejection message." msgstr "" "Zeige die passenden Muster für unerwünschte Inhalte in der " "Zurückweisungsnachricht." #: tracspamfilter/filters/regex.py:77 #, python-format msgid "Content contained these blacklisted patterns: %s" msgstr "Enthält diese Muster der schwarzen Liste: %s" #: tracspamfilter/filters/regex.py:79 #, python-format msgid "Content contained %s blacklisted patterns" msgstr "Enthält %s Muster der schwarzen Liste" #: tracspamfilter/filters/registration.py:28 msgid "" "By how many points a failed registration check impacts\n" "the overall score." msgstr "" "Punktanzahl mit der eine fehlgeschlagene Registrierprüfung das\n" "Gesamtkarma beeinflusst." #: tracspamfilter/filters/registration.py:32 msgid "Replace checks in account manager totally." msgstr "Prüfungen im Account-Manager komplett ersetzen." #: tracspamfilter/filters/registration.py:84 #, python-format msgid "Account registration failed (%s)" msgstr "Benutzerregistrierung fehlgeschlagen (%s)" #: tracspamfilter/filters/session.py:26 msgid "" "By how many points an existing and configured session improves the\n" "overall karma of the submission. A third of the points is granted for\n" "having an existing session at all, the other two thirds are granted\n" "when the user has his name and/or email address set in the session,\n" "respectively." msgstr "" "Punktanzahl mit der eine existierende und konfigurierte Sitzung das " "Gesamtkarma einer Übertragung beeinflusst. Ein Drittel der Punkte wird " "für eine existierende Sitzung gewährt, die anderen zwei Drittel, wenn der" " Nutzer seinen Namen und/oder seine E-Mail in den Einstellungen " "konfiguriert hat." #: tracspamfilter/filters/session.py:48 msgid "Existing session found" msgstr "Existierende Sitzung gefunden" #: tracspamfilter/filters/stopforumspam.py:30 msgid "" "By how many points a StopForumSpam reject impacts the overall karma of\n" "a submission." msgstr "" "Punktanzahl mit der eine Zurückweisung durch StopForumSpam das\n" "Gesamtkarma beeinflusst." #: tracspamfilter/filters/stopforumspam.py:34 msgid "API key used to report SPAM." msgstr "API-Schlüssel zum Melden von Spam." #: tracspamfilter/filters/stopforumspam.py:62 #, python-format msgid "StopForumSpam says this is spam (%s)" msgstr "StopForumSpam bezeichnet dies als Spam (%s)" #: tracspamfilter/filters/trapfield.py:27 msgid "" "By how many points a trap reject impacts the overall karma of\n" "a submission." msgstr "" "Punktanzahl mit der eine Zurückweisung durch eine Falle das\n" "Gesamtkarma beeinflusst." #: tracspamfilter/filters/trapfield.py:30 msgid "" "Name of the invisible trap field, should contain some reference\n" "to e-mail for better results." msgstr "" "Name des unsichtbaren Fallen-Feldes, sollte für bessere Resultate eine " "Referenz auf E-Mail enthalten." #: tracspamfilter/filters/trapfield.py:33 msgid "" "Name of the hidden trap field, should contain some reference\n" "to e-mail for better results." msgstr "" "Name des versteckten Fallen-Feldes, sollte für bessere Resultate eine " "Referenz auf E-Mail enthalten." #: tracspamfilter/filters/trapfield.py:36 msgid "" "Name of the register trap field, should contain some reference\n" "to web/homepage for better results." msgstr "" "Name des Registrier-Fallen-Feldes, sollte für bessere Resultate eine " "Referenz auf Webseiten enthalten." #: tracspamfilter/filters/trapfield.py:62 #, python-format msgid "Both trap fields says this is spam (%s, %s)" msgstr "Beide Fallen-Felder bezeichnen dies als Spam (%s, %s)" #: tracspamfilter/filters/trapfield.py:65 #, python-format msgid "Invisible trap field says this is spam (%s)" msgstr "Unsichtbares Fallen-Feld bezeichnet dies als Spam (%s)" #: tracspamfilter/filters/trapfield.py:68 #, python-format msgid "Hidden trap field says this is spam (%s)" msgstr "Verstecktes Fallen-Feld bezeichnet dies als Spam (%s)" #: tracspamfilter/filters/trapfield.py:71 #, python-format msgid "Register trap field starts with HTTP URL (%s)" msgstr "Registrier-Fallen-Feld beginnt mit HTTP-URL (%s)" #: tracspamfilter/filters/url_blacklist.py:33 msgid "" "By how many points blacklisting by a single bad URL impacts the\n" "overall karma of a submission." msgstr "" "Punktanzahl mit der ein Eintrag auf der schwarzen URL-Liste eines\n" "Servers das Gesamtkarma einer Übertragung beeinflusst." #: tracspamfilter/filters/url_blacklist.py:38 msgid "Servers used for URL blacklisting." msgstr "Server mit schwarzen Listen für Domains." #: tracspamfilter/filters/url_blacklist.py:81 #, python-format msgid "URL's blacklisted by %s" msgstr "URLs in der schwarzen Liste von %s" #: tracspamfilter/templates/admin_bayes.html:14 msgid "Spam Filtering: Bayes" msgstr "Spamfilter: Bayes" #: tracspamfilter/templates/admin_bayes.html:20 #, python-format msgid "" "The bayesian filter requires training before it can effectively\n" " differentiate between spam and ham. The training database " "currently\n" " contains [1:%(numspam)s spam] and\n" " [2:%(numham)s ham] %(ratio)s submissions." msgstr "" "Der Bayes-Filter benötigt Training, bevor er effektiv zwischen Spam und " "Ham\n" "unterscheiden kann. Die Trainingsdatenbank enthält momentan " "[1:%(numspam)s Spam]- und\n" "[2:%(numham)s Ham]-Einträge %(ratio)s." #: tracspamfilter/templates/admin_bayes.html:25 #, python-format msgid "" "The bayesian\n" " database contains currently %(lines)s lines with trained words " "(%(spamtext)s,\n" " %(hamtext)s, %(mixedtext)s)." msgstr "" "Die Bayes-Datenbank enthält momentan %(lines)s Zeilen mit angelernten " "Wörtern (%(spamtext)s, %(hamtext)s, %(mixedtext)s)." #: tracspamfilter/templates/admin_bayes.html:30 #, python-format msgid "" "[1:]\n" " Reduce training database (%(linestext)s)" msgstr "" "[1:]\n" "Trainingsdatenbank reduzieren (%(linestext)s)" #: tracspamfilter/templates/admin_bayes.html:34 msgid "" "Reducing the training database can help when it got very large\n" " and tests take too long." msgstr "" "Das Reduzieren der Trainingsdatenbank kann helfen, wenn diese sehr groß " "geworden ist und die Tests zu lange dauern." #: tracspamfilter/templates/admin_bayes.html:40 msgid "Minimum database count:" msgstr "Minimale Zahl an Datenbankeinträgen:" #: tracspamfilter/templates/admin_bayes.html:44 msgid "" "Any database lines with less entries is removed when reducing\n" " the database." msgstr "" "Jede Datenbankzeile mit weniger Einträgen wird beim Reduzieren der " "Datenbank entfernt." #: tracspamfilter/templates/admin_bayes.html:52 msgid "Clear training database" msgstr "Traingsdatenbank löschen" #: tracspamfilter/templates/admin_bayes.html:55 msgid "" "Resetting the training database can help when training was incorrect\n" " and is producing bad results." msgstr "" "Zurücksetzen der Trainingsdatenbank kann helfen, wenn das Training\n" "falsch war und schlechte Ergebnisse liefert." #: tracspamfilter/templates/admin_bayes.html:61 msgid "Minimum training required:" msgstr "Minimal nötiges Training:" #: tracspamfilter/templates/admin_bayes.html:65 msgid "" "The minimum number of spam and ham in the training database before\n" " the filter starts affecting the karma of submissions." msgstr "" "Die Anzahl von Spam und Ham in der Trainingsdatenbank, bevor der Filter\n" "beginnt das Karma von Übertragungen zu beeinflussen." #: tracspamfilter/templates/admin_bayes.html:71 #: tracspamfilter/templates/admin_captcha.html:173 #: tracspamfilter/templates/admin_external.html:263 #: tracspamfilter/templates/admin_spamconfig.html:123 msgid "Apply changes" msgstr "Änderungen übernehmen" #: tracspamfilter/templates/admin_bayes.html:76 msgid "Training" msgstr "Training" #: tracspamfilter/templates/admin_bayes.html:77 msgid "" "While you can train the spam filter from the “[1:Spam\n" " Filtering → Monitoring]” panel in the web\n" " administration interface, you can also manually train the " "filter by\n" " entering samples here, or check what kind of spam probability\n" " currently gets assigned to the content." msgstr "" "Zusätzlich zum Training des Spamfilters in der Administrations-Webseite " "[1:Spamfilter → Überwachung], kann der Filter hier auch manuell durch die" " Eingabe von Beispielen trainiert werden. Außerdem ist es möglich zu " "prüfen, welche Spamwahrscheinlichkeit einem Text momentan zugeordnet " "wird." #: tracspamfilter/templates/admin_bayes.html:85 msgid "Content:" msgstr "Inhalt:" #: tracspamfilter/templates/admin_bayes.html:91 #, python-format msgid "Error: %(error)s" msgstr "Fehler: %(error)s" #: tracspamfilter/templates/admin_bayes.html:92 #, python-format msgid "Score: %(score)s%" msgstr "Kennzahl: %(score)s%" #: tracspamfilter/templates/admin_bayes.html:95 #: tracspamfilter/templates/admin_statistics.html:36 msgid "Test" msgstr "Test" #: tracspamfilter/templates/admin_bayes.html:97 msgid "Train as Spam" msgstr "Als Spam trainieren" #: tracspamfilter/templates/admin_bayes.html:98 msgid "Train as Ham" msgstr "Als Ham trainieren" #: tracspamfilter/templates/admin_captcha.html:11 msgid "Captcha handling" msgstr "Captchanutzung" #: tracspamfilter/templates/admin_captcha.html:15 msgid "Spam Filtering: Captcha handling" msgstr "Spamfilter: Captchanutzung" #: tracspamfilter/templates/admin_captcha.html:22 msgid "Enable captcha usage" msgstr "Captchanutzung aktivieren" #: tracspamfilter/templates/admin_captcha.html:29 msgid "Captcha type:" msgstr "Captchatyp:" #: tracspamfilter/templates/admin_captcha.html:39 msgid "Maximum captcha lifetime in seconds" msgstr "Maximale Lebensdauer eines Captchas in Sekunden" #: tracspamfilter/templates/admin_captcha.html:47 msgid "reCAPTCHA" msgstr "reCAPTCHA" #: tracspamfilter/templates/admin_captcha.html:48 msgid "" "The reCAPTCHA system provides a very good captcha system based on\n" " scanned books. See\n" " [1:Google\n" " reCAPTCHA] page. You need to obtain\n" " API keys to use the service, which is freely available for " "personal use." msgstr "" "Das reCAPTCHA-System, bietet ein sehr gutes Captcha-System basierend auf " "eingescannten Büchern (siehe [1:Google reCAPTCHA]-Seite). Zur Benutzung " "wird ein API-Schlüssel benötigt, welcher bei privater Nutzung frei " "erhältlich ist." #: tracspamfilter/templates/admin_captcha.html:58 #: tracspamfilter/templates/admin_external.html:201 msgid "Public key:" msgstr "Öffentl. Schlüssel:" #: tracspamfilter/templates/admin_captcha.html:65 #: tracspamfilter/templates/admin_captcha.html:97 msgid "Private key:" msgstr "Privater Schlüssel:" #: tracspamfilter/templates/admin_captcha.html:75 #: tracspamfilter/templates/admin_captcha.html:106 msgid "Key validation failed:" msgstr "Schlüsselprüfung fehlgeschlagen:" #: tracspamfilter/templates/admin_captcha.html:80 msgid "KeyCaptcha" msgstr "KeyCaptcha" #: tracspamfilter/templates/admin_captcha.html:81 msgid "" "The KeyCatcha system provides a captcha system based on JavaScript\n" " functions to reassemble a picture. See\n" " [1:KeyCaptcha]\n" " page. You need to obtain an API key to use the service, which" " is\n" " freely available for limited use." msgstr "" "Das KeyCaptcha-System, bietet ein Javascript-basiertes Captcha-System, " "bei dem ein Bild zusammengesetzt werden muss (siehe " "[1:KeyCaptcha]-Seite). Zur Benutzung wird ein API-Schlüssel benötigt, " "welcher bei eingeschränkter Nutzung frei erhältlich ist." #: tracspamfilter/templates/admin_captcha.html:91 msgid "User ID:" msgstr "Nutzerkennung:" #: tracspamfilter/templates/admin_captcha.html:111 msgid "Text captcha" msgstr "Text-Captcha" #: tracspamfilter/templates/admin_captcha.html:112 msgid "" "The text captcha constructs easy text questions. They can be\n" " broken relatively easy." msgstr "" "Das Text-Captcha konstruiert eine einfache Textfrage. Diese\n" "kann relativ einfach überwunden werden." #: tracspamfilter/templates/admin_captcha.html:119 msgid "Maximum value in a term:" msgstr "Maximumaler Wert in einem Ausdruck:" #: tracspamfilter/templates/admin_captcha.html:125 msgid "Number of terms:" msgstr "Anzahl an Ausdrücken:" #: tracspamfilter/templates/admin_captcha.html:135 msgid "Image captcha" msgstr "Bild-Captcha" #: tracspamfilter/templates/admin_captcha.html:136 msgid "" "The image captcha constructs obstructed images using Python\n" " imaging library." msgstr "" "Das Bild-Captcha erzeugt gestörte Bilder mithilfe der\n" "Python-Bildbearbeitungsbibliothek." #: tracspamfilter/templates/admin_captcha.html:143 msgid "Number of letters:" msgstr "Buchstabenanzahl:" #: tracspamfilter/templates/admin_captcha.html:149 msgid "Font size:" msgstr "Schriftgröße:" #: tracspamfilter/templates/admin_captcha.html:155 msgid "Alphabet:" msgstr "Alphabet:" #: tracspamfilter/templates/admin_captcha.html:161 msgid "Fonts:" msgstr "Zeichensätze:" #: tracspamfilter/templates/admin_captcha.html:174 #: tracspamfilter/templates/admin_external.html:264 msgid "Revert changes" msgstr "Änderungen verwerfen" #: tracspamfilter/templates/admin_external.html:10 msgid "External services" msgstr "Externe Dienste" #: tracspamfilter/templates/admin_external.html:14 msgid "Spam Filtering: External services" msgstr "Spamfilter: Externe Dienste" #: tracspamfilter/templates/admin_external.html:17 msgid "An error checking supplied data occured, see below for details." msgstr "" "Beim Prüfen der übergebenen Daten ist ein Fehler aufgetreten. Details " "siehe weiter unten." #: tracspamfilter/templates/admin_external.html:24 msgid "Use external services" msgstr "Externe Dienste nutzen" #: tracspamfilter/templates/admin_external.html:32 msgid "Train external services" msgstr "Externe Dienste trainieren" #: tracspamfilter/templates/admin_external.html:36 msgid "" "Skip external services, when internal tests reach a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" "Externe Dienste überspringen, wenn interne Tests bereits dieses Karma " "erreicht haben - Spam: [1:] Ham: [2:]" #: tracspamfilter/templates/admin_external.html:46 msgid "" "Stop external services, when reached a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" "Externe Dienste abbrechen, wenn dieses Karma erreicht wurde - Spam: [1:] " "Ham: [2:]" #: tracspamfilter/templates/admin_external.html:58 msgid "" "The Akismet filter uses the free\n" " [1:Akismet]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" "Der Akismet-Filter nutzt den freien [1:Akismet]-Service, um zu " "entscheiden, ob Übertragungen potentieller Spam sind. Zur Benutzung des " "Dienstes ist ein API-Schlüssel notwendig, welcher bei privater Nutzung " "kostenfrei ist." #: tracspamfilter/templates/admin_external.html:65 #: tracspamfilter/templates/admin_external.html:135 #: tracspamfilter/templates/admin_external.html:152 #: tracspamfilter/templates/admin_external.html:168 #: tracspamfilter/templates/admin_external.html:185 msgid "API key:" msgstr "API-Schlüssel:" #: tracspamfilter/templates/admin_external.html:71 #: tracspamfilter/templates/admin_external.html:91 #: tracspamfilter/templates/admin_external.html:213 msgid "URL:" msgstr "URL:" #: tracspamfilter/templates/admin_external.html:77 #: tracspamfilter/templates/admin_external.html:219 #: tracspamfilter/templates/admin_external.html:224 #, python-format msgid "[1:Key validation failed:] %(error)s" msgstr "[1:Schlüsselprüfung fehlgeschlagen:] %(error)s" #: tracspamfilter/templates/admin_external.html:85 msgid "" "The BlogSpam filter uses the free\n" " [1:BlogSpam]\n" " service to decide if content submissions are potential spam." msgstr "" "Der BlogSpam-Filter nutzt den freien [1:BlogSpam]-Service, um zu " "entscheiden, ob Übertragungen potentieller Spam sind." #: tracspamfilter/templates/admin_external.html:97 msgid "Tests to skip (comma separated):" msgstr "Zu überspringende Prüfungen (kommagetrennt):" #: tracspamfilter/templates/admin_external.html:100 msgid "Possible Values:" msgstr "Mögliche Werte:" #: tracspamfilter/templates/admin_external.html:105 msgid "method" msgstr "Methode" #: tracspamfilter/templates/admin_external.html:106 msgid "description" msgstr "Beschreibung" #: tracspamfilter/templates/admin_external.html:107 msgid "author" msgstr "Autor" #: tracspamfilter/templates/admin_external.html:127 msgid "" "The StopForumSpam filter uses the\n" " [1:StopForumSpam]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to report SPAM to the service, which is freely " "available." msgstr "" "Der StopForumSpam-Filter nutzt den freien [1:StopForumSpam]-Service, um " "zu entscheiden, ob Übertragungen potentieller Spam sind. Zur Benutzung " "des Dienstes ist ein API-Schlüssel notwendig, welcher kostenfrei ist." #: tracspamfilter/templates/admin_external.html:144 msgid "" "The BotScout filter uses the\n" " [1:BotScout]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" "Der BotScout-Filter nutzt den freien [1:BotScout]-Service, um zu " "entscheiden, ob Übertragungen potentieller Spam sind. Zur Benutzung des " "Dienstes ist ein API-Schlüssel notwendig, welcher kostenfrei ist." #: tracspamfilter/templates/admin_external.html:161 msgid "" "The FSpamList filter uses the\n" " [1:FSpamList]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" "Der FSpamList-Filter nutzt den freien [1:FSpamList]-Service, um zu " "entscheiden, ob Übertragungen potentieller Spam sind. Zur Benutzung des " "Dienstes ist ein API-Schlüssel notwendig, welcher kostenfrei ist." #: tracspamfilter/templates/admin_external.html:177 msgid "" "The HTTP_BL filter uses the free\n" " [1:HTTP:BL]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" "Der HTTP_BL-Filter nutzt den freien [1:HTTP:BL]-Service, um zu " "entscheiden, ob Übertragungen potentieller Spam sind. Zur Benutzung des " "Dienstes ist ein API-Schlüssel notwendig, welcher bei privater Nutzung " "kostenfrei ist." #: tracspamfilter/templates/admin_external.html:194 msgid "" "The Mollom filter uses the free\n" " [1:Mollom]\n" " service to decide if content submissions are potential spam. " "You need to obtain\n" " API keys to use the service, which are freely available for " "personal use." msgstr "" "Der Mollom-Filter nutzt den freien [1:Mollom]-Service, um zu entscheiden," " ob Übertragungen potentieller Spam sind. Zur Benutzung des Dienstes sind" " API-Schlüssel notwendig, welche bei privater Nutzung kostenfrei sind." #: tracspamfilter/templates/admin_external.html:207 msgid "Secret key:" msgstr "Privater Schlüssel:" #: tracspamfilter/templates/admin_external.html:229 msgid "Free access blacklists" msgstr "Frei zugängliche schwarze Listen (Blacklists)" #: tracspamfilter/templates/admin_external.html:231 msgid "IPv4 Blacklists (comma separated):" msgstr "IPv4-Schwarze Listen (kommagetrennt):" #: tracspamfilter/templates/admin_external.html:235 #: tracspamfilter/templates/admin_external.html:242 #: tracspamfilter/templates/admin_external.html:249 #, python-format msgid "(default: %(list)s)" msgstr "(Standard: %(list)s)" #: tracspamfilter/templates/admin_external.html:238 msgid "IPv6 Blacklists (comma separated):" msgstr "IPv6-Schwarze Listen (kommagetrennt):" #: tracspamfilter/templates/admin_external.html:245 msgid "URL Blacklists (comma separated):" msgstr "Schwarze URL-Listen (kommagetrennt):" #: tracspamfilter/templates/admin_external.html:251 msgid "" "A list of DNS blacklists can be found at the [1:RBLCheck]\n" " or [2:MultiRBL] services." msgstr "" "Eine Übersicht von schwarzen Listen für DNS kann beim\n" "[1:RBLCheck]- oder [2:MultiRBL]-Service gefunden werden." #: tracspamfilter/templates/admin_external.html:256 msgid "" "You can enable or disable these filters from the “[1:General →\n" " Plugins]” panel of the web administration interface." msgstr "" "Diese Filter können im Abschnitt “[1:Allgemein → Plugins]” der " "Administrationswebseiten aktiviert und deaktiviert werden." #: tracspamfilter/templates/admin_report.html:10 msgid "Spam Reports" msgstr "Spam-Berichte" #: tracspamfilter/templates/admin_report.html:19 msgid "Spam Filtering: Reports" msgstr "Spamfilter: Berichte" #: tracspamfilter/templates/admin_report.html:22 #: tracspamfilter/templates/admin_spammonitor.html:20 #, python-format msgid "Viewing entries %(start)s – %(end)s of %(total)s." msgstr "Einträge %(start)s – %(end)s von %(total)s anzeigen." #: tracspamfilter/templates/admin_report.html:50 #: tracspamfilter/templates/monitortable.html:13 msgid "Path" msgstr "Pfad" #: tracspamfilter/templates/admin_report.html:51 #: tracspamfilter/templates/monitortable.html:14 msgid "Author" msgstr "Autor" #: tracspamfilter/templates/admin_report.html:52 #: tracspamfilter/templates/monitortable.html:17 msgid "Date/time" msgstr "Datum/Zeit" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:44 msgid "User was logged in" msgstr "Nutzer war angemeldet" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:44 msgid "User was not logged in" msgstr "Nutzer war nicht angemeldet" #: tracspamfilter/templates/admin_report.html:79 #: tracspamfilter/templates/monitortable.html:64 msgid "No data available" msgstr "Keine Daten verfügbar" #: tracspamfilter/templates/admin_report.html:90 #: tracspamfilter/templates/admin_spammonitor.html:60 msgid "Delete selected" msgstr "Gewähltes löschen" #: tracspamfilter/templates/admin_reportentry.html:10 msgid "Spam Report" msgstr "Spam Bericht" #: tracspamfilter/templates/admin_reportentry.html:20 #: tracspamfilter/templates/admin_reportentry.html:21 msgid "Previous Report Entry" msgstr "Voriger Berichtseintrag" #: tracspamfilter/templates/admin_reportentry.html:25 #: tracspamfilter/templates/admin_spamentry.html:25 msgid "Back to List" msgstr "Zurück zur Liste" #: tracspamfilter/templates/admin_reportentry.html:30 #: tracspamfilter/templates/admin_reportentry.html:31 msgid "Next Report Entry" msgstr "Nächster Berichtseintrag" #: tracspamfilter/templates/admin_reportentry.html:35 msgid "Spam Filtering: Report" msgstr "Spamfilter: Bericht" #: tracspamfilter/templates/admin_reportentry.html:39 msgid "Report Entry:" msgstr "Berichtseintrag:" #: tracspamfilter/templates/admin_reportentry.html:40 #: tracspamfilter/templates/admin_spamentry.html:40 msgid "Information" msgstr "Information" #: tracspamfilter/templates/admin_reportentry.html:42 #: tracspamfilter/templates/admin_spamentry.html:42 msgid "Time:" msgstr "Zeit:" #: tracspamfilter/templates/admin_reportentry.html:45 #: tracspamfilter/templates/admin_spamentry.html:45 msgid "Path:" msgstr "Pfad:" #: tracspamfilter/templates/admin_reportentry.html:48 #: tracspamfilter/templates/admin_spamentry.html:48 msgid "Author:" msgstr "Autor:" #: tracspamfilter/templates/admin_reportentry.html:51 #: tracspamfilter/templates/admin_spamentry.html:51 msgid "Authenticated:" msgstr "Angemeldet:" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:47 #: tracspamfilter/templates/usertable.html:49 msgid "yes" msgstr "ja" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:48 #: tracspamfilter/templates/usertable.html:50 msgid "no" msgstr "nein" #: tracspamfilter/templates/admin_reportentry.html:54 msgid "Comment:" msgstr "Kommentar:" #: tracspamfilter/templates/admin_reportentry.html:58 #: tracspamfilter/templates/admin_spamentry.html:75 msgid "HTTP headers" msgstr "HTTP-Kopfzeilen" #: tracspamfilter/templates/admin_reportentry.html:63 #: tracspamfilter/templates/admin_spamentry.html:82 msgid "Delete" msgstr "Löschen" #: tracspamfilter/templates/admin_reportentry.html:68 msgid "Possibly related log entries:" msgstr "Möglicherweise damit zusammenhängene Logeinträge:" #: tracspamfilter/templates/admin_spamconfig.html:10 msgid "Spam Filter" msgstr "Spamfilter" #: tracspamfilter/templates/admin_spamconfig.html:14 msgid "Spam Filtering: Configuration" msgstr "Spamfilter: Einstellungen" #: tracspamfilter/templates/admin_spamconfig.html:15 msgid "" "See [1:wiki page]\n" " for a short documentation." msgstr "Für eine kurze Dokumentation bitte die [1:Wikiseite] ansehen." #: tracspamfilter/templates/admin_spamconfig.html:16 msgid "Help translating this plugin at [1:Transifex]." msgstr "Helfen Sie dieses Plugin auf [1:Transifex] zu übersetzen." #: tracspamfilter/templates/admin_spamconfig.html:21 msgid "Karma Tuning" msgstr "Karmanpassung" #: tracspamfilter/templates/admin_spamconfig.html:23 msgid "Minimum karma required for a successful submission:" msgstr "Minimales Karma für eine erfolgreiche Übertragung:" #: tracspamfilter/templates/admin_spamconfig.html:29 msgid "" "Karma assigned to attachments (e.g. to allow relaxed rules for file " "uploads):" msgstr "" "Karma für Anhänge (z.B. um vereinfachte Regeln für das Hochladen von " "Dateien zu ermöglichen):" #: tracspamfilter/templates/admin_spamconfig.html:35 msgid "" "Karma assigned to registering users (e.g. negative value to increase " "captcha usage):" msgstr "" "Karma für Registrierungen (z.B. ein negativer Wert, um die Captcha-" "Nutzung zu erhöhen):" #: tracspamfilter/templates/admin_spamconfig.html:41 msgid "" "Content submissions are passed through a set of registered and enabled\n" " [1:filter strategies], each of which check the submitted " "content\n" " and may assign [2:karma points] to it. The sum of these karma\n" " points needs to be greater than or equal to the minimum karma\n" " configured here for the submission to be accepted." msgstr "" "Übertragungen neuer Inhalte werden durch einen Satz registrierter und " "aktivierter\n" "[1:Filterstrategien] geprüft, von denen jede den gesendeten Inhalt prüft " "und ein [2:Karma] zuweisen kann. Die Summe dieser Karmawerte muss größer " "oder gleich dem hier eingestellten Minimalkarmasein, damit eine " "Übertragung akzeptiert wird." #: tracspamfilter/templates/admin_spamconfig.html:50 #: tracspamfilter/templates/admin_statistics.html:34 msgid "Strategy" msgstr "Strategie" #: tracspamfilter/templates/admin_spamconfig.html:51 msgid "Karma points" msgstr "Karma-Punkte" #: tracspamfilter/templates/admin_spamconfig.html:52 msgid "Description" msgstr "Beschreibung" #: tracspamfilter/templates/admin_spamconfig.html:66 msgid "Logging" msgstr "Aufzeichnung" #: tracspamfilter/templates/admin_spamconfig.html:70 msgid "Enable" msgstr "Aktivieren" #: tracspamfilter/templates/admin_spamconfig.html:74 msgid "" "The spam filter plugin can optionally log every content submission so\n" " that you can monitor and tune the effectiveness of the " "filtering. The\n" " log is stored in the database, and can be viewed under “[1:Spam" "\n" " Filtering → Monitoring]” from the web administration\n" " interface." msgstr "" "Das Spamfilterplugin kann optional jede Übertragung aufzeichnen, so dass " "die Effektivität der Filter überwacht und verbessert werden kann. Die " "Logeinträge werden in der Datenbank gespeichert und sind unter Punkt " "[1:Spamfilter → Überwachung] des Webinterfaces einsehbar." #: tracspamfilter/templates/admin_spamconfig.html:82 msgid "" "Purge old entries after\n" " [1:]\n" " days" msgstr "" "Alte Einträge nach\n" " [1:]\n" " Tagen entfernen" #: tracspamfilter/templates/admin_spamconfig.html:90 #, python-format msgid "" "Number of entries in log message display\n" " (%(min)s-%(max)s)\n" " [1:]" msgstr "" "Anzahl von Einträgen in Log-Nachrichtenanzeige\n" " (%(min)s-%(max)s)\n" " [1:]" #: tracspamfilter/templates/admin_spamconfig.html:100 msgid "Authenticated" msgstr "Anmeldung" #: tracspamfilter/templates/admin_spamconfig.html:104 msgid "Trust authenticated users" msgstr "Angemeldeten Nutzern vertrauen" #: tracspamfilter/templates/admin_spamconfig.html:108 msgid "" "If authenticated users should not be trusted automatically, this\n" " option must be disabled. Instead of full trust the supplied " "karma\n" " value is used in this case." msgstr "" "Falls angemeldeten Nutzenr nicht automatisch vertraut werden soll, so\n" "muss diese Option deaktiviert werden. Statt vollem Vertrauen wird dann\n" "der angegebene Karma-Wert genutzt." #: tracspamfilter/templates/admin_spamconfig.html:114 msgid "Karma of authenticated users:" msgstr "Karma von angemeldeten Nutzern:" #: tracspamfilter/templates/admin_spamentry.html:10 #: tracspamfilter/templates/admin_spammonitor.html:10 msgid "Spam Monitoring" msgstr "Spam-Überwachung" #: tracspamfilter/templates/admin_spamentry.html:20 #: tracspamfilter/templates/admin_spamentry.html:21 msgid "Previous Log Entry" msgstr "Voriger Logeintrag" #: tracspamfilter/templates/admin_spamentry.html:30 #: tracspamfilter/templates/admin_spamentry.html:31 msgid "Next Log Entry" msgstr "Nächster Logeintrag" #: tracspamfilter/templates/admin_spamentry.html:35 #: tracspamfilter/templates/admin_spammonitor.html:14 msgid "Spam Filtering: Monitoring" msgstr "Spamfilter: Überwachung" #: tracspamfilter/templates/admin_spamentry.html:39 msgid "Log Entry:" msgstr "Logeintrag:" #: tracspamfilter/templates/admin_spamentry.html:54 msgid "IP address:" msgstr "IP-Adresse:" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "spam" msgstr "Spam" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "ham" msgstr "Ham" #: tracspamfilter/templates/admin_spamentry.html:60 msgid "Karma:" msgstr "Karma:" #: tracspamfilter/templates/admin_spamentry.html:61 #, python-format msgid "" "[1:%(karma)s]\n" " (marked as %(spam_or_ham)s)\n" " [2:\n" " [3:%(reasons)s]\n" " ]" msgstr "" "[1:%(karma)s]\n" " (markiert als %(spam_or_ham)s)\n" " [2:\n" " [3:%(reasons)s]\n" " ]" #: tracspamfilter/templates/admin_spamentry.html:71 msgid "Submitted content" msgstr "Übertragener Inhalt" #: tracspamfilter/templates/admin_spamentry.html:80 msgid "Mark as Spam" msgstr "Als Spam markieren" #: tracspamfilter/templates/admin_spamentry.html:81 msgid "Mark as Ham" msgstr "Als Ham markieren" #: tracspamfilter/templates/admin_spamentry.html:83 msgid "Delete as Spam" msgstr "Als Spam löschen" #: tracspamfilter/templates/admin_spamentry.html:84 msgid "Delete as Ham" msgstr "Als Ham löschen" #: tracspamfilter/templates/admin_spamentry.html:85 msgid "Delete (No Statistics)" msgstr "Löschen (ohne Statistik)" #: tracspamfilter/templates/admin_spamentry.html:93 msgid "Remove registered user" msgstr "Registrierten Nutzer entfernen" #: tracspamfilter/templates/admin_spamentry.html:99 msgid "Search for user name" msgstr "Nach Nutzernamen suchen" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Note:" msgstr "Notiz:" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Logging by the spam filter is currently disabled." msgstr "Aufzeichnung des Spamfilters is momentan deaktiviert." #: tracspamfilter/templates/admin_spammonitor.html:52 msgid "Delete > 90%" msgstr "> 90% löschen" #: tracspamfilter/templates/admin_spammonitor.html:56 msgid "Mark selected as Spam" msgstr "Gewähltes als Spam markieren" #: tracspamfilter/templates/admin_spammonitor.html:58 msgid "Mark selected as Ham" msgstr "Gewähltes als Ham markieren" #: tracspamfilter/templates/admin_spammonitor.html:62 msgid "Delete selected as Spam" msgstr "Gewähltes als Spam löschen" #: tracspamfilter/templates/admin_spammonitor.html:64 msgid "Delete selected as Ham" msgstr "Gewähltes als Ham löschen" #: tracspamfilter/templates/admin_statistics.html:10 msgid "Spam Statistics" msgstr "Spam-Statistik" #: tracspamfilter/templates/admin_statistics.html:14 msgid "Spam Filtering: Statistics" msgstr "Spamfilter: Statistik" #: tracspamfilter/templates/admin_statistics.html:17 msgid "No submission statistics yet." msgstr "Bisher keine Übertragunsstatistik." #: tracspamfilter/templates/admin_statistics.html:21 #, python-format msgid "" "%(count)s Submissions tested since %(time)s,\n" " %(spam)s spam and\n" " %(ham)s ham\n" " (%(local)s of the tests could be solved local).\n" " %(spamerr)s spam and %(hamerr)s ham have been retrained." msgstr "" "%(count)s Übertragungen seit %(time)s getestet: %(spam)s Spam und %(ham)s" " Ham (%(local)s der Tests konnten lokal gelöst werden).\n" "%(spamerr)s Spam und %(hamerr)s Ham wurden umtrainiert." #: tracspamfilter/templates/admin_statistics.html:35 msgid "Type" msgstr "Typ" #: tracspamfilter/templates/admin_statistics.html:37 msgid "Train / Verify" msgstr "Training / Überprüfung" #: tracspamfilter/templates/admin_statistics.html:38 msgid "Errors" msgstr "Fehler" #: tracspamfilter/templates/admin_statistics.html:43 #: tracspamfilter/templates/admin_statistics.html:45 msgid "Spam" msgstr "Spam" #: tracspamfilter/templates/admin_statistics.html:44 #: tracspamfilter/templates/admin_statistics.html:46 msgid "Ham" msgstr "Ham" #: tracspamfilter/templates/admin_statistics.html:49 #: tracspamfilter/templates/admin_statistics.html:56 #: tracspamfilter/templates/admin_statistics.html:59 msgid "Total" msgstr "Gesamt" #: tracspamfilter/templates/admin_statistics.html:50 msgid "Mean Delay" msgstr "Mittl. Verzög." #: tracspamfilter/templates/admin_statistics.html:51 msgid "No Result" msgstr "Ergebnislos" #: tracspamfilter/templates/admin_statistics.html:52 #: tracspamfilter/templates/admin_statistics.html:54 msgid "Match" msgstr "Gleich" #: tracspamfilter/templates/admin_statistics.html:53 #: tracspamfilter/templates/admin_statistics.html:55 msgid "Mismatch" msgstr "Ungleich" #: tracspamfilter/templates/admin_statistics.html:57 #: tracspamfilter/templates/admin_statistics.html:60 msgid "Right" msgstr "Korrekt" #: tracspamfilter/templates/admin_statistics.html:58 #: tracspamfilter/templates/admin_statistics.html:61 msgid "Wrong" msgstr "Falsch" #: tracspamfilter/templates/admin_statistics.html:69 msgid "No tests yet." msgstr "Bisher keine Tests." #: tracspamfilter/templates/admin_statistics.html:72 #, python-format msgid "%(seconds)s s" msgstr "%(seconds)s s" #: tracspamfilter/templates/admin_statistics.html:101 msgid "No spam yet." msgstr "Bisher kein Spam." #: tracspamfilter/templates/admin_statistics.html:119 msgid "No ham yet." msgstr "Bisher kein Ham." #: tracspamfilter/templates/admin_statistics.html:141 msgid "Clear statistics" msgstr "Statistik löschen" #: tracspamfilter/templates/admin_statistics.html:151 msgid "Clear statistics database" msgstr "Statistikdatenbank löschen" #: tracspamfilter/templates/admin_user.html:10 msgid "Spam User Handling" msgstr "Spamfilter: Nutzerbearbeitung" #: tracspamfilter/templates/admin_user.html:14 #, python-format msgid "" "Spam Filtering: User handling (%(type)s)\n" " [1:%(count)s]" msgstr "" "Spamfilter: Nutzerbearbeitung (%(type)s)\n" " [1:%(count)s]" #: tracspamfilter/templates/admin_user.html:20 msgid "Overview" msgstr "Übersicht" #: tracspamfilter/templates/admin_user.html:21 msgid "All" msgstr "Alle" #: tracspamfilter/templates/admin_user.html:22 #: tracspamfilter/templates/usertable.html:15 msgid "Registered" msgstr "Registriert" #: tracspamfilter/templates/admin_user.html:23 msgid "Unused [multi selection]" msgstr "Unbenutzt [Mehrfachauswahl]" #: tracspamfilter/templates/admin_user.html:24 msgid "Unused" msgstr "Unbenutzt" #: tracspamfilter/templates/admin_user.html:28 #, python-format msgid "" "There are %(total)s\n" " different entries in the database, %(registered)s users are\n" " registered and %(unused)s have not been used." msgstr "" "Es gibt insgesamt %(total)s verschiedene Einträge in der Datenbank, " "%(registered)s Nutzer sind registriert und %(unused)s wurden nie benutzt." #: tracspamfilter/templates/admin_user.html:35 msgid "Date" msgstr "Datum" #: tracspamfilter/templates/admin_user.html:36 #, python-format msgid "Action of user '%(username)s'" msgstr "Aktion des Nutzers '%(username)s'" #: tracspamfilter/templates/admin_user.html:55 msgid "Remove selected" msgstr "Gewählte entfernen" #: tracspamfilter/templates/admin_user.html:63 msgid "Values must be URL encoded!" msgstr "Werte müssen URL-kodiert sein!" #: tracspamfilter/templates/admin_user.html:65 msgid "Old user:" msgstr "Alter Nutzer:" #: tracspamfilter/templates/admin_user.html:68 msgid "New user:" msgstr "Neuer Nutzer:" #: tracspamfilter/templates/admin_user.html:74 msgid "Change unauthorized user" msgstr "Nichtregistrierten Nutzer ändern" #: tracspamfilter/templates/admin_user.html:74 msgid "Change user" msgstr "Nutzer ändern" #: tracspamfilter/templates/admin_user.html:81 #, python-format msgid "Remove %(num)s temporary session" msgid_plural "Remove %(num)s temporary sessions" msgstr[0] "%(num)s temporäre Sitzung entfernen" msgstr[1] "%(num)s temporäre Sitzungen entfernen" #: tracspamfilter/templates/admin_user.html:86 msgid "Convert emails to registered usernames" msgstr "E-Mails in registrierte Nutzernamen konvertieren" #: tracspamfilter/templates/monitortable.html:15 msgid "IP Address" msgstr "IP-Adresse" #: tracspamfilter/templates/monitortable.html:16 msgid "Karma" msgstr "Karma" #: tracspamfilter/templates/usertable.html:13 msgid "User name" msgstr "Nutzername" #: tracspamfilter/templates/usertable.html:14 msgid "Last login" msgstr "Letze Anmeldung" #: tracspamfilter/templates/usertable.html:16 msgid "Setup" msgstr "Einrichtung" #: tracspamfilter/templates/usertable.html:17 msgid "E-Mail" msgstr "E-Mail" #: tracspamfilter/templates/usertable.html:18 msgid "Wiki edits" msgstr "Wiki" #: tracspamfilter/templates/usertable.html:19 msgid "Ticket edits" msgstr "Tickets" #: tracspamfilter/templates/usertable.html:20 msgid "SVN edits" msgstr "SVN" #: tracspamfilter/templates/usertable.html:21 msgid "Other" msgstr "Anderes" #: tracspamfilter/templates/usertable.html:39 msgid "Source" msgstr "Quelle" #: tracspamfilter/templates/usertable.html:49 #: tracspamfilter/templates/usertable.html:50 msgid "(password)" msgstr "(Passwort)" #: tracspamfilter/templates/usertable.html:52 msgid "(double)" msgstr "(doppelt)" #: tracspamfilter/templates/usertable.html:55 #: tracspamfilter/templates/usertable.html:61 #: tracspamfilter/templates/usertable.html:67 #: tracspamfilter/templates/usertable.html:73 msgid "user" msgstr "Nutzer" #: tracspamfilter/templates/usertable.html:56 #: tracspamfilter/templates/usertable.html:62 #: tracspamfilter/templates/usertable.html:68 #: tracspamfilter/templates/usertable.html:74 msgid "e-mail" msgstr "E-Mail" #: tracspamfilter/templates/usertable.html:57 #: tracspamfilter/templates/usertable.html:63 #: tracspamfilter/templates/usertable.html:69 #: tracspamfilter/templates/usertable.html:75 msgid "both" msgstr "Beides" #: tracspamfilter/templates/usertable.html:83 msgid "Remove" msgstr "Entfernen" #: tracspamfilter/templates/verify_captcha.html:13 msgid "Captcha Error" msgstr "Captcha-Fehler" #: tracspamfilter/templates/verify_captcha.html:17 msgid "" "Trac thinks your submission might be Spam.\n" " To prove otherwise please provide a response to the following." msgstr "" "Trac wertet Ihre Übertragung als Spam.\n" "Um das Gegenteil zu beweisen, geben Sie bitte Antwort auf folgendes." #: tracspamfilter/templates/verify_captcha.html:19 msgid "" "Note - the captcha method is choosen randomly. Retry if the captcha does " "not work on your system!" msgstr "" "Achtung - die Captcha-Methode wird zufällig ausgewählt. Erneut versuchen," " wenn das Captcha auf Ihrem System nicht funktioniert!" #: tracspamfilter/templates/verify_captcha.html:22 msgid "Response:" msgstr "Antwort:" spam-filter/tracspamfilter/locale/fr/0000755000175500017550000000000012725137772017736 5ustar debacledebaclespam-filter/tracspamfilter/locale/fr/LC_MESSAGES/0000755000175500017550000000000012725137772021523 5ustar debacledebaclespam-filter/tracspamfilter/locale/fr/LC_MESSAGES/tracspamfilter.po0000644000175500017550000020642512725137772025114 0ustar debacledebacle# French translations for TracSpamFilter. # Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the TracSpamFilter # project. # # Translators: # Christian Boos , 2012 # Vincent Privat , 2014 msgid "" msgstr "" "Project-Id-Version: Trac Plugin L10N\n" "Report-Msgid-Bugs-To: trac@dstoecker.de\n" "POT-Creation-Date: 2016-03-05 18:00-0800\n" "PO-Revision-Date: 2016-02-20 12:42+0000\n" "Last-Translator: Dirk Stöcker \n" "Language-Team: French (http://www.transifex.com/hasienda/Trac_Plugin-" "L10N/language/fr/)\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" #: tracspamfilter/accountadapter.py:33 msgid "" "Interface of spamfilter to account manager plugin to check new account\n" "registrations for spam.\n" "\n" "It provides an additional 'Details' input field to get more information\n" "for calculating the probability of a spam registration attempt.\n" "Knowledge gained from inspecting registration attempts is shared with all" "\n" "other spam filter adapters for this system." msgstr "" #: tracspamfilter/accountadapter.py:47 msgid "Details:" msgstr "Détails :" #: tracspamfilter/adapters.py:106 msgid "" "The maximum number of bytes from an attachment to pass through\n" "the spam filters." msgstr "" "La taille en octets du fragment initial de la pièce jointe qui doit " "passer\n" "par le dispositif anti-spam." #: tracspamfilter/admin.py:75 msgid "How many monitor entries are displayed by default (between 5 and 10000)." msgstr "Nombre d'enregistrements affichés par défaut (entre 5 et 10000)" #: tracspamfilter/admin.py:79 msgid "Show the buttons for training without deleting entry." msgstr "" #: tracspamfilter/admin.py:87 tracspamfilter/admin.py:90 #: tracspamfilter/admin.py:315 tracspamfilter/admin.py:500 #: tracspamfilter/admin.py:593 tracspamfilter/admin.py:641 #: tracspamfilter/adminreport.py:43 tracspamfilter/adminusers.py:48 msgid "Spam Filtering" msgstr "Dispositif anti-spam" #: tracspamfilter/admin.py:88 tracspamfilter/templates/admin_bayes.html:19 msgid "Configuration" msgstr "Configuration" #: tracspamfilter/admin.py:91 msgid "Monitoring" msgstr "Surveillance" #: tracspamfilter/admin.py:113 tracspamfilter/filters/bayes.py:78 #, python-format msgid "SpamBayes determined spam probability of %s%%" msgstr "SpamBayes à estimé la probabilité de spam à %s%%" #: tracspamfilter/admin.py:115 #, python-format msgid "Select 100.00%% entries" msgstr "Sélectionner ceux à 100.00%%" #: tracspamfilter/admin.py:116 #, python-format msgid "Select >90.00%% entries" msgstr "Sélectionner ceux > à 90.00%%" #: tracspamfilter/admin.py:117 #, python-format msgid "Select <10.00%% entries" msgstr "Sélectionner ceux < à 10.00%%" #: tracspamfilter/admin.py:118 #, python-format msgid "Select 0.00%% entries" msgstr "Sélectionner ceux à 0.00%%" #: tracspamfilter/admin.py:119 msgid "Select Spam entries" msgstr "Sélectionner ceux catégorisés spam" #: tracspamfilter/admin.py:120 msgid "Select Ham entries" msgstr "Sélectionner ceux catégorisés non-spam" #: tracspamfilter/admin.py:246 tracspamfilter/adminreport.py:110 #: tracspamfilter/templates/admin_report.html:31 #: tracspamfilter/templates/admin_report.html:32 #: tracspamfilter/templates/admin_spammonitor.html:29 #: tracspamfilter/templates/admin_spammonitor.html:30 msgid "Previous Page" msgstr "Page précédente" #: tracspamfilter/admin.py:250 tracspamfilter/adminreport.py:114 #: tracspamfilter/templates/admin_report.html:37 #: tracspamfilter/templates/admin_report.html:38 #: tracspamfilter/templates/admin_spammonitor.html:35 #: tracspamfilter/templates/admin_spammonitor.html:36 msgid "Next Page" msgstr "Page suivante" #: tracspamfilter/admin.py:267 msgid "Log entry not found" msgstr "Enregistrement non trouvé" #: tracspamfilter/admin.py:272 tracspamfilter/admin.py:277 #, python-format msgid "Log Entry %(id)s" msgstr "Enregistrement %(id)s" #: tracspamfilter/admin.py:273 msgid "Log Entry List" msgstr "Tous les enregistrements" #: tracspamfilter/admin.py:316 msgid "External" msgstr "Services externes" #: tracspamfilter/admin.py:500 tracspamfilter/templates/admin_bayes.html:10 msgid "Bayes" msgstr "Bayes" #: tracspamfilter/admin.py:552 #, python-format msgid "(ratio %.1f : 1)" msgstr "(ratio %.1f : 1)" #: tracspamfilter/admin.py:554 #, python-format msgid "(ratio 1 : %.1f)" msgstr "(ratio 1 : %.1f)" #: tracspamfilter/admin.py:566 #, python-format msgid "%(num)d spam" msgid_plural "%(num)d spam" msgstr[0] "" msgstr[1] "" #: tracspamfilter/admin.py:568 #, python-format msgid "%(num)d ham" msgid_plural "%(num)d ham" msgstr[0] "" msgstr[1] "" #: tracspamfilter/admin.py:570 #, python-format msgid "%(num)d line" msgid_plural "%(num)d lines" msgstr[0] "" msgstr[1] "" #: tracspamfilter/admin.py:572 #, python-format msgid "%(num)d mixed" msgid_plural "%(num)d mixed" msgstr[0] "" msgstr[1] "" #: tracspamfilter/admin.py:594 msgid "Statistics" msgstr "Statistiques" #: tracspamfilter/admin.py:641 msgid "Captcha" msgstr "Captcha" #: tracspamfilter/admin.py:663 tracspamfilter/admin.py:697 #: tracspamfilter/admin.py:706 tracspamfilter/admin.py:720 #: tracspamfilter/admin.py:729 #, python-format msgid "Invalid value for %(key)s" msgstr "" #: tracspamfilter/admin.py:673 msgid "The keys are invalid" msgstr "Les clés sont incorrectes" #: tracspamfilter/admin.py:685 msgid "The key or user id are invalid" msgstr "La clé ou l'id utilisateur sont invalides" #: tracspamfilter/adminreport.py:32 msgid "How many report entries are displayed by default (between 5 and 10000)." msgstr "Combien d'enregistrements sont affichés par défaut (entre 5 et 10000)." #: tracspamfilter/adminreport.py:43 msgid "Reports" msgstr "Rapports" #: tracspamfilter/adminreport.py:134 msgid "Report entry not found" msgstr "Enregistrement introuvable" #: tracspamfilter/adminreport.py:142 tracspamfilter/adminreport.py:149 #, python-format msgid "Report Entry %d" msgstr "Enregistrement %d" #: tracspamfilter/adminreport.py:143 msgid "Report Entry List" msgstr "Liste des enregistrements" #: tracspamfilter/adminusers.py:29 msgid "How many wiki edits are still an unused account." msgstr "" #: tracspamfilter/adminusers.py:33 msgid "How many days no login are considered for dead accounts." msgstr "" "Combien de jours sans connexion sont considérés pour les comptes " "abandonnés." #: tracspamfilter/adminusers.py:38 msgid "Default mode for spam user admin panel." msgstr "Mode par défaut pour le panneau d'administration du spam utilisateur." #: tracspamfilter/adminusers.py:48 msgid "Users" msgstr "Utilisateurs" #: tracspamfilter/adminusers.py:58 msgid "Old or new value cannot be empty" msgstr "L'ancienne ou la nouvelle valeur ne peuvent pas êtres vides" #: tracspamfilter/adminusers.py:66 msgid "Old and new value cannot be equal" msgstr "L'ancienne et la nouvelle valeur ne peuvent pas être égales" #: tracspamfilter/adminusers.py:69 msgid "New name cannot be used in CC fields" msgstr "Le nouveau nom ne peut pas être utilisé dans les champs CC" #: tracspamfilter/adminusers.py:71 msgid "Illegal user arguments passed or changing not allowed" msgstr "" #: tracspamfilter/adminusers.py:73 #, python-format msgid "%(num)d entry has been updated" msgid_plural "%(num)d entries have been updated" msgstr[0] "%(num)d enregistrement a été mis à jour" msgstr[1] "%(num)d enregistrements ont été mis à jour" #: tracspamfilter/adminusers.py:80 #, python-format msgid "Username '%s' cannot be used in CC fields" msgstr "Le nom d'utilisateur '%s' ne peut pas être utilisé dans les champs CC" #: tracspamfilter/adminusers.py:82 #, python-format msgid "Error for e-mail change for username '%s'" msgstr "Erreur lors de la modification de l'e-mail pour le nom d'utilisateur '%s'" #: tracspamfilter/adminusers.py:84 #, python-format msgid "%(num)d entry has been updated for user %(user)s" msgid_plural "%(num)d entries have been updated for user %(user)s" msgstr[0] "%(num)d enregistrement a été mis à jour pour l'utilisateur %(user)s" msgstr[1] "%(num)d enregistrements ont été mis à jour pour l'utilisateur %(user)s" #: tracspamfilter/adminusers.py:87 #, python-format msgid "E-mails for user %s updated" msgstr "E-mails pour l'utilisateur %s mis à jour" #: tracspamfilter/adminusers.py:118 msgid "data overview" msgstr "aperçu des données" #: tracspamfilter/adminusers.py:120 msgid "unused accounts" msgstr "comptes inutilisés" #: tracspamfilter/adminusers.py:122 msgid "registered accounts" msgstr "comptes enregistrés" #: tracspamfilter/adminusers.py:124 #, python-format msgid "detailed user information for '%s'" msgstr "" #: tracspamfilter/adminusers.py:126 msgid "everything from accounts, wiki, tickets and svn" msgstr "" #: tracspamfilter/adminusers.py:127 #, python-format msgid "%(num)d entry" msgid_plural "%(num)d entries" msgstr[0] "%(num)d enregistrement" msgstr[1] "%(num)d enregistrements" #: tracspamfilter/filtersystem.py:52 msgid "The minimum score required for a submission to be allowed." msgstr "Le score minimum requis pour qu'une soumission soit validée." #: tracspamfilter/filtersystem.py:56 msgid "" "The karma given to authenticated users, in case\n" "`trust_authenticated` is false." msgstr "" "Le karma donné aux utilisateurs identifiés, si le paramètre\n" "« `trust_authenticated` » a pour valeur « `false` »." #: tracspamfilter/filtersystem.py:60 msgid "" "Whether all content submissions and spam filtering activity should\n" "be logged to the database." msgstr "" "Toutes les soumissions de contenu et toute l'activité de filtrage\n" "doivent être enregistrées dans la base de donnée." #: tracspamfilter/filtersystem.py:64 msgid "The number of days after which log entries should be purged." msgstr "" "Le nombre de jour au bout duquel les enregistrements de surveillance " "doivent être supprimés." #: tracspamfilter/filtersystem.py:68 msgid "Allow usage of external services." msgstr "Autorise l'utilisation de services externes." #: tracspamfilter/filtersystem.py:71 msgid "" "Skip external calls when this negative karma is already reached\n" "by internal tests." msgstr "" #: tracspamfilter/filtersystem.py:75 msgid "" "Skip external calls when this positive karma is already reached\n" "by internal tests." msgstr "" #: tracspamfilter/filtersystem.py:79 msgid "Stop external calls when this negative karma is reached." msgstr "" #: tracspamfilter/filtersystem.py:83 msgid "Stop external calls when this positive karma is reached." msgstr "" #: tracspamfilter/filtersystem.py:87 msgid "Allow training of external services." msgstr "Autorise l'apprentissage pour les services externes." #: tracspamfilter/filtersystem.py:90 msgid "" "Whether content submissions by authenticated users should be trusted\n" "without checking for potential spam or other abuse." msgstr "" "Toutes les soumissions de contenu effectuées par des utilisateurs " "identifiés\n" "doivent-elles être validées sans passer par le dispositif anti-spam ?" #: tracspamfilter/filtersystem.py:96 msgid "The karma given to attachments." msgstr "Le « karma » donné aux pièces jointes." #: tracspamfilter/filtersystem.py:99 msgid "The karma given to registrations." msgstr "" #: tracspamfilter/filtersystem.py:102 msgid "The handler used to reject content." msgstr "Le module utilisé pour rejeter le contenu." #: tracspamfilter/filtersystem.py:106 msgid "Interpret X-Forwarded-For header for IP checks." msgstr "" "Interpréter l'entête HTTP « X-Forwarded-For » pour les vérifications " "d'adresse IP." #: tracspamfilter/filtersystem.py:110 msgid "" "This section is used to handle all configurations used by\n" "spam filter plugin." msgstr "" #: tracspamfilter/filtersystem.py:169 msgid "User is authenticated" msgstr "L'utilisateur est identifié" #: tracspamfilter/filtersystem.py:175 msgid "Attachment weighting" msgstr "Pondération pour les pièces jointes" #: tracspamfilter/filtersystem.py:180 msgid "Registration weighting" msgstr "" #: tracspamfilter/filtersystem.py:304 #, python-format msgid "Submission rejected as potential spam %(message)s" msgstr "Soumission rejetée en tant que spam potentiel %(message)s" #: tracspamfilter/model.py:422 msgid "external" msgstr "externe" #: tracspamfilter/model.py:422 msgid "internal" msgstr "interne" #: tracspamfilter/report.py:29 msgid "List of page types to add spam report link" msgstr "" #: tracspamfilter/report.py:42 msgid "No page supplied to report as spam" msgstr "" #: tracspamfilter/report.py:74 msgid "Reported spam" msgstr "" #: tracspamfilter/report.py:76 msgid "Comment" msgstr "" #: tracspamfilter/report.py:78 msgid "Report spam" msgstr "" #: tracspamfilter/report.py:82 #, python-format msgid "%(num)d spam report" msgid_plural "%(num)d spam reports" msgstr[0] "" msgstr[1] "" #: tracspamfilter/users.py:38 #, python-format msgid "Wiki page '%(page)s' version %(version)s modified" msgstr "" #: tracspamfilter/users.py:41 #, python-format msgid "Attachment '%s' added" msgstr "" #: tracspamfilter/users.py:45 #, python-format msgid "Ticket %(id)s field '%(field)s' changed" msgstr "" #: tracspamfilter/users.py:48 #, python-format msgid "Removed from ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:50 #, python-format msgid "Set in ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:56 #, python-format msgid "Ticket %(id)s CC field change ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:60 #, python-format msgid "Reporter of ticket %s" msgstr "" #: tracspamfilter/users.py:62 #, python-format msgid "Owner of ticket %s" msgstr "" #: tracspamfilter/users.py:64 #, python-format msgid "In CC of ticket %(id)s ('%(cc)s')" msgstr "" #: tracspamfilter/users.py:67 #, python-format msgid "Author of revision %s" msgstr "" #: tracspamfilter/users.py:70 #, python-format msgid "Component '%s' owner" msgstr "" #: tracspamfilter/users.py:73 msgid "In permissions list" msgstr "" #: tracspamfilter/users.py:76 #, python-format msgid "Author of report %d" msgstr "" #: tracspamfilter/users.py:81 tracspamfilter/users.py:85 #, python-format msgid "Voted for '%s'" msgstr "" #: tracspamfilter/captcha/api.py:59 msgid "CAPTCHA method to use for verifying humans." msgstr "" "Méthode CAPTCHA utilisée pour vérifier la présence physique d'un " "utilisateur." #: tracspamfilter/captcha/api.py:64 msgid "" "By how many points a successful CAPTCHA response increases the\n" "overall score." msgstr "" "De combien de points une réponse correcte au CAPTCHA augmente-t-elle le\n" "score global." #: tracspamfilter/captcha/api.py:69 msgid "By how many points a failed CAPTCHA impacts the overall score." msgstr "" #: tracspamfilter/captcha/api.py:73 msgid "" "Time in seconds that a successful CAPTCHA response increases\n" "karma." msgstr "" "Temps en secondes pour lequel une réponse correcte au CAPTCHA augmente\n" "le « karma »." #: tracspamfilter/captcha/api.py:77 msgid "Time in seconds before CAPTCHA is removed." msgstr "Temps en secondes au bout duquel le CAPTCHA est supprimé." #: tracspamfilter/captcha/api.py:81 msgid "Time in seconds before database cleanup is called." msgstr "Temps en secondes au bout duquel la base de donnée est nettoyée." #: tracspamfilter/captcha/api.py:100 #, python-format msgid "Human verified via CAPTCHA (%s)" msgstr "" #: tracspamfilter/captcha/api.py:109 #, python-format msgid "Failed CAPTCHA (%s) attempts" msgstr "" #: tracspamfilter/captcha/api.py:141 msgid "CAPTCHA failed to handle original request" msgstr "Le CAPTCHA n'a pas pu gérer la requête intiale." #: tracspamfilter/captcha/api.py:143 msgid "CAPTCHA verification failed" msgstr "La vérification du CAPTCHA a échouée :" #: tracspamfilter/captcha/expression.py:32 msgid "Number of terms in numeric CAPTCHA expression." msgstr "Nombre de terms dans une expression CAPTCHA de type numérique" #: tracspamfilter/captcha/expression.py:36 msgid "" "Maximum value of individual terms in numeric CAPTCHA\n" "expression." msgstr "" "Valeur maximale pour un terme dans une expression CAPTCHA de type " "numérique." #: tracspamfilter/captcha/expression.py:40 msgid "multiplied by" msgstr "multiplié par" #: tracspamfilter/captcha/expression.py:40 msgid "minus" msgstr "moin" #: tracspamfilter/captcha/expression.py:41 msgid "plus" msgstr "plus" #: tracspamfilter/captcha/expression.py:43 msgid "zero" msgstr "zero" #: tracspamfilter/captcha/expression.py:43 msgid "one" msgstr "un" #: tracspamfilter/captcha/expression.py:43 msgid "two" msgstr "deux" #: tracspamfilter/captcha/expression.py:43 msgid "three" msgstr "trois" #: tracspamfilter/captcha/expression.py:43 msgid "four" msgstr "quatre" #: tracspamfilter/captcha/expression.py:44 msgid "five" msgstr "cinq" #: tracspamfilter/captcha/expression.py:44 msgid "six" msgstr "six" #: tracspamfilter/captcha/expression.py:44 msgid "seven" msgstr "sept" #: tracspamfilter/captcha/expression.py:44 msgid "eight" msgstr "huit" #: tracspamfilter/captcha/expression.py:45 msgid "nine" msgstr "neuf" #: tracspamfilter/captcha/expression.py:45 msgid "ten" msgstr "dix" #: tracspamfilter/captcha/expression.py:45 msgid "eleven" msgstr "onze" #: tracspamfilter/captcha/expression.py:45 msgid "twelve" msgstr "douze" #: tracspamfilter/captcha/expression.py:46 msgid "thirteen" msgstr "treize" #: tracspamfilter/captcha/expression.py:46 msgid "fourteen" msgstr "quatorze" #: tracspamfilter/captcha/expression.py:46 msgid "fifteen" msgstr "quinze" #: tracspamfilter/captcha/expression.py:47 msgid "sixteen" msgstr "seize" #: tracspamfilter/captcha/expression.py:47 msgid "seventeen" msgstr "dix-sept" #: tracspamfilter/captcha/expression.py:47 msgid "eighteen" msgstr "dix-huit" #: tracspamfilter/captcha/expression.py:48 msgid "nineteen" msgstr "dix-neuf" #. TRANSLATOR: if compound numbers like in english are not #. supported, simply add a "plus" command to the following #. translations! #: tracspamfilter/captcha/expression.py:53 msgid "twenty" msgstr "vingt" #: tracspamfilter/captcha/expression.py:53 msgid "thirty" msgstr "trente" #: tracspamfilter/captcha/expression.py:53 msgid "forty" msgstr "quarante" #: tracspamfilter/captcha/expression.py:53 msgid "fifty" msgstr "cinquante" #: tracspamfilter/captcha/expression.py:54 msgid "sixty" msgstr "soixante" #: tracspamfilter/captcha/expression.py:54 msgid "seventy" msgstr "soixante-dix" #: tracspamfilter/captcha/expression.py:54 msgid "eighty" msgstr "quatre-vingt" #: tracspamfilter/captcha/expression.py:54 msgid "ninety" msgstr "quatre-vingt-dix" #: tracspamfilter/captcha/expression.py:61 msgid "Numeric captcha can not represent numbers > 100" msgstr "Un captcha numérique ne peut représenter des nombres > 100" #: tracspamfilter/captcha/image.py:43 msgid "Set of fonts to choose from when generating image CAPTCHA." msgstr "Jeu de polices de caractères utilisées pour générer une image CAPTCHA." #: tracspamfilter/captcha/image.py:47 msgid "Font size to use in image CAPTCHA." msgstr "Taille de la police de caractères à utiliser pour une image CAPTCHA." #: tracspamfilter/captcha/image.py:51 msgid "Alphabet to choose image CAPTCHA challenge from." msgstr "Alphabet utilisé pour composer une image CAPTCHA." #: tracspamfilter/captcha/image.py:56 msgid "Number of letters to use in image CAPTCHA challenge." msgstr "Nombre de lettres à utiliser dans une image CAPTCHA." #: tracspamfilter/captcha/keycaptcha.py:32 msgid "Private key for KeyCaptcha usage." msgstr "" #: tracspamfilter/captcha/keycaptcha.py:36 msgid "User id for KeyCaptcha usage." msgstr "" #: tracspamfilter/captcha/recaptcha.py:32 #: tracspamfilter/captcha/recaptcha2.py:34 msgid "Private key for reCaptcha usage." msgstr "Clé privée pour utiliser le service reCaptcha." #: tracspamfilter/captcha/recaptcha.py:36 #: tracspamfilter/captcha/recaptcha2.py:38 msgid "Public key for reCaptcha usage." msgstr "Clé publique pour utiliser le service reCaptcha." #: tracspamfilter/captcha/recaptcha.py:59 #: tracspamfilter/captcha/recaptcha2.py:50 #: tracspamfilter/templates/verify_captcha.html:24 msgid "Submit" msgstr "Envoyer" #: tracspamfilter/filters/akismet.py:42 msgid "" "By how many points an Akismet reject impacts the overall karma of\n" "a submission." msgstr "" "Le karma global pour une soumission va être modifié par ce nombre\n" "de points lors d'un rejet par Akismet." #: tracspamfilter/filters/akismet.py:46 msgid "Wordpress key required to use the Akismet API." msgstr "Clé Wordpress requise pour l'utilisation de l'API Akismet." #: tracspamfilter/filters/akismet.py:50 msgid "URL of the Akismet service." msgstr "URL du service Akismet." #: tracspamfilter/filters/akismet.py:75 msgid "Akismet says content is spam" msgstr "Akismet dit que le contenu est un spam." #: tracspamfilter/filters/bayes.py:33 msgid "" "By what factor Bayesian spam probability score affects the overall\n" "karma of a submission." msgstr "" "Par quel facteur le score lié à la probabilité bayésienne d'être un spam " "affecte-t-elle\n" "le karma global d'une soumission." #: tracspamfilter/filters/bayes.py:37 msgid "" "The minimum number of submissions in the training database required\n" "for the filter to start impacting the karma of submissions." msgstr "" "Le nombre de soumissions dans la base d'apprentissage minimal requis\n" "pour que le filtre commence à affecter le karma des soumissions." #: tracspamfilter/filters/bayes.py:42 msgid "" "Entries with a count less than this value get removed from the\n" "database when calling the reduce function." msgstr "" #: tracspamfilter/filters/blogspam.py:35 msgid "" "By how many points an BlogSpam reject impacts the overall karma of\n" "a submission." msgstr "" "De combien de points un rejet par BlogSpam affecte-t-il le karma global\n" "d'une soumission." #: tracspamfilter/filters/blogspam.py:39 msgid "URL of the BlogSpam service." msgstr "URL du service BlogSpam." #: tracspamfilter/filters/blogspam.py:42 msgid "Comma separated list of tests to skip." msgstr "Liste des tests à ne pas effectuer (séparés par des virgules)." #: tracspamfilter/filters/blogspam.py:64 #, python-format msgid "BlogSpam says content is spam (%s [%s])" msgstr "" #: tracspamfilter/filters/botscout.py:31 msgid "" "By how many points a BotScout reject impacts the overall karma of\n" "a submission." msgstr "" "De combien de points un rejet par BotScout affecte-t-il le karma global\n" "d'une soumission." #: tracspamfilter/filters/botscout.py:35 msgid "API key required to use BotScout." msgstr "Clé de l'API requise pour utiliser BotScout." #: tracspamfilter/filters/botscout.py:61 #, python-format msgid "BotScout says this is spam (%s)" msgstr "BotScout affirme qu'il s'agit de spam (%s)" #: tracspamfilter/filters/extlinks.py:28 msgid "" "By how many points too many external links in a submission impact\n" "the overall score." msgstr "" "De combien de points un nombre trop élévé de liens externes dans une\n" "soumission affecte-t-il son karma global." #: tracspamfilter/filters/extlinks.py:32 msgid "" "The maximum number of external links allowed in a submission until\n" "that submission gets negative karma." msgstr "" "Le nombre maximum de liens externes permis dans une soumission avant\n" "que cette soumission recoive un karma négatif." #: tracspamfilter/filters/extlinks.py:36 msgid "List of domains that should be allowed in external links" msgstr "Liste des domaines qui devraient être autorisées dans les liens externes" #: tracspamfilter/filters/extlinks.py:64 msgid "Maximum number of external links per post exceeded" msgstr "Le nombre maximal de liens externes par soumission est dépassé" #: tracspamfilter/filters/extlinks.py:67 msgid "External links in post found" msgstr "Liens externes trouvés dans la soumission." #: tracspamfilter/filters/fspamlist.py:32 msgid "" "By how many points a FSpamList reject impacts the overall karma of\n" "a submission." msgstr "" "De combien de points un rejet par FSpamList affecte-t-il le karma global\n" "d'une soumission." #: tracspamfilter/filters/fspamlist.py:36 msgid "API key required to use FSpamList." msgstr "Clé de l'API requise pour utiliser FSpamList." #: tracspamfilter/filters/fspamlist.py:67 #, python-format msgid "FSpamList says this is spam (%s)" msgstr "FSpamList affirme qu'il s'agit de spam (%s)" #: tracspamfilter/filters/httpbl.py:34 msgid "" "By how many points listing as \"comment spammer\" impacts the\n" "overall karma of a submission." msgstr "" "De combien de points le fait d'être catégorisé en tant que \n" "« comment spammer » affecte-t-il le karma global d'une soumission." #: tracspamfilter/filters/httpbl.py:38 msgid "Http:BL API key required for use." msgstr "Clé pour l'utilisation de l'API Http:BL." #: tracspamfilter/filters/httpbl.py:81 #, python-format msgid "IP %s blacklisted by Http:BL" msgstr "L'IP %s a été mis sur liste noire par Http:BL" #: tracspamfilter/filters/ip_blacklist.py:34 msgid "" "By how many points blacklisting by a single server impacts the\n" "overall karma of a submission." msgstr "" "De combien de points la présence sur la liste noire d'un server " "affecte-t-il\n" "le karma global d'une soumission." #: tracspamfilter/filters/ip_blacklist.py:39 msgid "Servers used for IPv4 blacklisting." msgstr "" #: tracspamfilter/filters/ip_blacklist.py:43 msgid "Servers used for IPv6 blacklisting." msgstr "" #: tracspamfilter/filters/ip_blacklist.py:87 #, python-format msgid "IP %s blacklisted by %s" msgstr "Adresse IP %s sur la liste noire de %s" #: tracspamfilter/filters/ip_regex.py:34 msgid "" "By how many points a match with a pattern on the BadIP page\n" "impacts the overall karma of a submission." msgstr "" "De combien de points une correspondance avec un filtre sur la page " "« BadIP »\n" "affecte-t-il le karma global d'une soumission." #: tracspamfilter/filters/ip_regex.py:37 msgid "" "Local file to be loaded to get BadIP. Can be used in\n" "addition to BadIP wiki page." msgstr "" "Fichier local qui va être lu pour obtenir des adresses IP hostiles. Peut " "être utilisé en complément de la page wiki « BadIP »." #: tracspamfilter/filters/ip_regex.py:40 msgid "Show the matched bad IP patterns in rejection message." msgstr "" "Inclure dans le message de rejet les filtres d'adresses IP hostiles qui " "correspondent à la soumission." #: tracspamfilter/filters/ip_regex.py:76 #, python-format msgid "IP catched by these blacklisted patterns: %s" msgstr "Adresse IP répondant aux types suivants d'adresses sur liste noire : %s" #: tracspamfilter/filters/ip_regex.py:78 #, python-format msgid "IP catched by %s blacklisted patterns" msgstr "Adresse IP correspondant à %s type(s) d'adresse(s) sur liste noire :" #: tracspamfilter/filters/ip_throttle.py:27 msgid "" "By how many points exceeding the configured maximum number of posts\n" "per hour impacts the overall score." msgstr "" "De combien de points un dépassement du nombre maximal de soumissions\n" "autorisé par heure affecte-t-il le karma global d'une soumission." #: tracspamfilter/filters/ip_throttle.py:31 msgid "" "The maximum allowed number of submissions per hour from a single IP\n" "address. If this limit is exceeded, subsequent submissions get negative\n" "karma." msgstr "" #: tracspamfilter/filters/ip_throttle.py:52 msgid "Maximum number of posts per hour for this IP exceeded" msgstr "" "Le nombre maximal de soumissions autorisé par heure pour cette adresse IP" " a été dépassé" #: tracspamfilter/filters/mollom.py:36 msgid "" "By how many points an Mollom reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/mollom.py:40 msgid "Public key required to use the Mollom API." msgstr "" #: tracspamfilter/filters/mollom.py:44 msgid "Private key required to use the Mollom API." msgstr "" #: tracspamfilter/filters/mollom.py:48 msgid "URL of the Mollom service." msgstr "" #: tracspamfilter/filters/mollom.py:95 msgid "Mollom says content is spam" msgstr "" #: tracspamfilter/filters/mollom.py:106 msgid "Mollom says content is ham" msgstr "" #: tracspamfilter/filters/regex.py:31 msgid "" "By how many points a match with a pattern on the BadContent page\n" "impacts the overall karma of a submission." msgstr "" "De combien de points une correspondance avec un filtre présent dans la " "page\n" "wiki « BadContent » affecte-t-il le karma global d'une soumission." #: tracspamfilter/filters/regex.py:34 msgid "" "Local file to be loaded to get BadContent. Can be used in\n" "addition to BadContent wiki page." msgstr "" "Fichier local qui va être lu pour obtenir du contenu indésirable. Peut " "être utilisé en complément de la page wiki « BadContent »." #: tracspamfilter/filters/regex.py:37 msgid "Show the matched bad content patterns in rejection message." msgstr "" "Inclure dans le message de rejet les filtres de contenu indésirables qui " "correspondent à la soumission." #: tracspamfilter/filters/regex.py:77 #, python-format msgid "Content contained these blacklisted patterns: %s" msgstr "Le contenu répond aux types suivants de contenu indésirable : %s" #: tracspamfilter/filters/regex.py:79 #, python-format msgid "Content contained %s blacklisted patterns" msgstr "Le contenu correspondant à %s type(s) de contenu indésirable." #: tracspamfilter/filters/registration.py:28 msgid "" "By how many points a failed registration check impacts\n" "the overall score." msgstr "" #: tracspamfilter/filters/registration.py:32 msgid "Replace checks in account manager totally." msgstr "" #: tracspamfilter/filters/registration.py:84 #, python-format msgid "Account registration failed (%s)" msgstr "" #: tracspamfilter/filters/session.py:26 msgid "" "By how many points an existing and configured session improves the\n" "overall karma of the submission. A third of the points is granted for\n" "having an existing session at all, the other two thirds are granted\n" "when the user has his name and/or email address set in the session,\n" "respectively." msgstr "" "De combien de points une session existante et configurée augmente-t-elle " "le\n" "karma global d'une soumission. Un tiers des points est octroyé pour\n" "la simple existence de la session, les deux autres tiers sont octroyés\n" "chacun lorsque l'utilisateur a son nom et son adresse de courrier " "électroniquerenseignés pour cette session." #: tracspamfilter/filters/session.py:48 msgid "Existing session found" msgstr "Session existante trouvée." #: tracspamfilter/filters/stopforumspam.py:30 msgid "" "By how many points a StopForumSpam reject impacts the overall karma of\n" "a submission." msgstr "" "De combien de points un rejet par StopForumSpam affecte-t-il le karma " "global\n" "d'une soumission." #: tracspamfilter/filters/stopforumspam.py:34 msgid "API key used to report SPAM." msgstr "Clé de l'API utilisée pour signaler du spam." #: tracspamfilter/filters/stopforumspam.py:62 #, python-format msgid "StopForumSpam says this is spam (%s)" msgstr "StopForumSpam affirme qu'il s'agit de spam (%s)" #: tracspamfilter/filters/trapfield.py:27 msgid "" "By how many points a trap reject impacts the overall karma of\n" "a submission." msgstr "" "De combien de points un rejet par le champ « piège » affecte-t-il le " "karma global\n" "d'une soumission." #: tracspamfilter/filters/trapfield.py:30 msgid "" "Name of the invisible trap field, should contain some reference\n" "to e-mail for better results." msgstr "" "Nom du champ « piège » invisible, qui devrait contenir une référence\n" "à « mail » pour obtenir de meilleurs résultats." #: tracspamfilter/filters/trapfield.py:33 msgid "" "Name of the hidden trap field, should contain some reference\n" "to e-mail for better results." msgstr "" "Nom du champ « piège » caché, qui devrait contenir une référence\n" "à « mail » pour obtenir de meilleurs résultats." #: tracspamfilter/filters/trapfield.py:36 msgid "" "Name of the register trap field, should contain some reference\n" "to web/homepage for better results." msgstr "" #: tracspamfilter/filters/trapfield.py:62 #, python-format msgid "Both trap fields says this is spam (%s, %s)" msgstr "Les deux champs « pièges » ont détecté un spam (%s, %s)" #: tracspamfilter/filters/trapfield.py:65 #, python-format msgid "Invisible trap field says this is spam (%s)" msgstr "Le champ « piège » invisible a détecté un spam (%s)" #: tracspamfilter/filters/trapfield.py:68 #, python-format msgid "Hidden trap field says this is spam (%s)" msgstr "Le champ « piège » caché a détecté un spam (%s)" #: tracspamfilter/filters/trapfield.py:71 #, python-format msgid "Register trap field starts with HTTP URL (%s)" msgstr "" #: tracspamfilter/filters/url_blacklist.py:33 msgid "" "By how many points blacklisting by a single bad URL impacts the\n" "overall karma of a submission." msgstr "" #: tracspamfilter/filters/url_blacklist.py:38 msgid "Servers used for URL blacklisting." msgstr "" #: tracspamfilter/filters/url_blacklist.py:81 #, python-format msgid "URL's blacklisted by %s" msgstr "" #: tracspamfilter/templates/admin_bayes.html:14 msgid "Spam Filtering: Bayes" msgstr "Dispositif anti-spam : Bayes" #: tracspamfilter/templates/admin_bayes.html:20 #, python-format msgid "" "The bayesian filter requires training before it can effectively\n" " differentiate between spam and ham. The training database " "currently\n" " contains [1:%(numspam)s spam] and\n" " [2:%(numham)s ham] %(ratio)s submissions." msgstr "" "Le filtre bayésien nécessite d'être entrainé pour être capable de " "faire la différence entre ce qui est du spam et ce qui n'en est pas." " La base d'apprentissage contient pour le moment\n" " [1:%(numspam)s soumissions de type spam] et \n" " [2:%(numham)s soumissions qui n'en sont pas] %(ratio)s." #: tracspamfilter/templates/admin_bayes.html:25 #, python-format msgid "" "The bayesian\n" " database contains currently %(lines)s lines with trained words " "(%(spamtext)s,\n" " %(hamtext)s, %(mixedtext)s)." msgstr "" #: tracspamfilter/templates/admin_bayes.html:30 #, python-format msgid "" "[1:]\n" " Reduce training database (%(linestext)s)" msgstr "" #: tracspamfilter/templates/admin_bayes.html:34 msgid "" "Reducing the training database can help when it got very large\n" " and tests take too long." msgstr "" #: tracspamfilter/templates/admin_bayes.html:40 msgid "Minimum database count:" msgstr "" #: tracspamfilter/templates/admin_bayes.html:44 msgid "" "Any database lines with less entries is removed when reducing\n" " the database." msgstr "" #: tracspamfilter/templates/admin_bayes.html:52 msgid "Clear training database" msgstr "Réinitialiser la base d'apprentissage" #: tracspamfilter/templates/admin_bayes.html:55 msgid "" "Resetting the training database can help when training was incorrect\n" " and is producing bad results." msgstr "" "Réinitialiser la base de données d'apprentissage peut être utile lorsque\n" "l'apprentissage était mal réalisé et produisait de mauvais résultats." #: tracspamfilter/templates/admin_bayes.html:61 msgid "Minimum training required:" msgstr "Quantité minimal d'apprentissage requis :" #: tracspamfilter/templates/admin_bayes.html:65 msgid "" "The minimum number of spam and ham in the training database before\n" " the filter starts affecting the karma of submissions." msgstr "" "Le nombre minimal de spam et de non-spam dans la base d'apprentissage " "avant\n" " que le filtre ne commence à modifier le karma des soumissions." #: tracspamfilter/templates/admin_bayes.html:71 #: tracspamfilter/templates/admin_captcha.html:173 #: tracspamfilter/templates/admin_external.html:263 #: tracspamfilter/templates/admin_spamconfig.html:123 msgid "Apply changes" msgstr "Enregistrer les changements" #: tracspamfilter/templates/admin_bayes.html:76 msgid "Training" msgstr "Apprentissage" #: tracspamfilter/templates/admin_bayes.html:77 msgid "" "While you can train the spam filter from the “[1:Spam\n" " Filtering → Monitoring]” panel in the web\n" " administration interface, you can also manually train the " "filter by\n" " entering samples here, or check what kind of spam probability\n" " currently gets assigned to the content." msgstr "" "Bien qu'il vous soit possible d'entrainer le filtre à spam à partir du" " panneau « [1:Dispositif anti-spam → Surveillance] » de\n" " l'interface d'administration, vous pouvez également effectuer un" " apprentissage manuel en saisissant des exemples ici-même, ou " "bien\n" " vérifier quelle est la probabilité d'être un spam qui va être\n" " associée au contenu." #: tracspamfilter/templates/admin_bayes.html:85 msgid "Content:" msgstr "Contenu :" #: tracspamfilter/templates/admin_bayes.html:91 #, python-format msgid "Error: %(error)s" msgstr "Erreur : %(error)s" #: tracspamfilter/templates/admin_bayes.html:92 #, python-format msgid "Score: %(score)s%" msgstr "Score : %(score)s%" #: tracspamfilter/templates/admin_bayes.html:95 #: tracspamfilter/templates/admin_statistics.html:36 msgid "Test" msgstr "Test" #: tracspamfilter/templates/admin_bayes.html:97 msgid "Train as Spam" msgstr "Catégoriser en tant que spam" #: tracspamfilter/templates/admin_bayes.html:98 msgid "Train as Ham" msgstr "Catégoriser en tant que non-spam" #: tracspamfilter/templates/admin_captcha.html:11 msgid "Captcha handling" msgstr "Génération des captcha" #: tracspamfilter/templates/admin_captcha.html:15 msgid "Spam Filtering: Captcha handling" msgstr "Dispositif anti-spam : Captcha" #: tracspamfilter/templates/admin_captcha.html:22 msgid "Enable captcha usage" msgstr "Autoriser l'utilisation des captchas" #: tracspamfilter/templates/admin_captcha.html:29 msgid "Captcha type:" msgstr "Type de captcha :" #: tracspamfilter/templates/admin_captcha.html:39 msgid "Maximum captcha lifetime in seconds" msgstr "Durée de vie maximale d'une captcha en secondes" #: tracspamfilter/templates/admin_captcha.html:47 msgid "reCAPTCHA" msgstr "reCAPTCHA" #: tracspamfilter/templates/admin_captcha.html:48 #, fuzzy msgid "" "The reCAPTCHA system provides a very good captcha system based on\n" " scanned books. See\n" " [1:Google\n" " reCAPTCHA] page. You need to obtain\n" " API keys to use the service, which is freely available for " "personal use." msgstr "" "Le système reCAPTCHA est un très bon système de captcha basé sur\n" " l'utilisation de livres scannés. Voir\n" " la page [1:Google reCAPTCHA].\n" " Vous devez obtenir les clés pour l'API afin d'utiliser ce" " service, qui est disponible gratuitement pour un usage " "personnel." #: tracspamfilter/templates/admin_captcha.html:58 #: tracspamfilter/templates/admin_external.html:201 msgid "Public key:" msgstr "Clé publique :" #: tracspamfilter/templates/admin_captcha.html:65 #: tracspamfilter/templates/admin_captcha.html:97 msgid "Private key:" msgstr "Clé privée :" #: tracspamfilter/templates/admin_captcha.html:75 #: tracspamfilter/templates/admin_captcha.html:106 msgid "Key validation failed:" msgstr "La validation de la clé a échouée :" #: tracspamfilter/templates/admin_captcha.html:80 msgid "KeyCaptcha" msgstr "" #: tracspamfilter/templates/admin_captcha.html:81 msgid "" "The KeyCatcha system provides a captcha system based on JavaScript\n" " functions to reassemble a picture. See\n" " [1:KeyCaptcha]\n" " page. You need to obtain an API key to use the service, which" " is\n" " freely available for limited use." msgstr "" #: tracspamfilter/templates/admin_captcha.html:91 msgid "User ID:" msgstr "ID utilisateur:" #: tracspamfilter/templates/admin_captcha.html:111 msgid "Text captcha" msgstr "Captcha de type texte" #: tracspamfilter/templates/admin_captcha.html:112 #, fuzzy msgid "" "The text captcha constructs easy text questions. They can be\n" " broken relatively easy." msgstr "" "Un captcha de type texte construit des questions appellant une réponse " "simple. Elles peuvent être attaquées relativement aisément." #: tracspamfilter/templates/admin_captcha.html:119 msgid "Maximum value in a term:" msgstr "La valeur maximale pour un terme :" #: tracspamfilter/templates/admin_captcha.html:125 msgid "Number of terms:" msgstr "Nombre de termes :" #: tracspamfilter/templates/admin_captcha.html:135 msgid "Image captcha" msgstr "Captcha de type image" #: tracspamfilter/templates/admin_captcha.html:136 #, fuzzy msgid "" "The image captcha constructs obstructed images using Python\n" " imaging library." msgstr "" "Une captcha de type image consiste en un texte dessiné et brouillé,\n" "générée grâce à l'utilisation de PIL (Python Imaging Library)." #: tracspamfilter/templates/admin_captcha.html:143 msgid "Number of letters:" msgstr "Nombre de lettres :" #: tracspamfilter/templates/admin_captcha.html:149 msgid "Font size:" msgstr "Taille de la police de caractères :" #: tracspamfilter/templates/admin_captcha.html:155 msgid "Alphabet:" msgstr "Alphabet :" #: tracspamfilter/templates/admin_captcha.html:161 msgid "Fonts:" msgstr "Polices de caractères :" #: tracspamfilter/templates/admin_captcha.html:174 #: tracspamfilter/templates/admin_external.html:264 msgid "Revert changes" msgstr "Défaire les modifications" #: tracspamfilter/templates/admin_external.html:10 msgid "External services" msgstr "Services externes" #: tracspamfilter/templates/admin_external.html:14 msgid "Spam Filtering: External services" msgstr "Dispositif anti-spam : services externes" #: tracspamfilter/templates/admin_external.html:17 msgid "An error checking supplied data occured, see below for details." msgstr "" #: tracspamfilter/templates/admin_external.html:24 msgid "Use external services" msgstr "Utiliser les services externes" #: tracspamfilter/templates/admin_external.html:32 msgid "Train external services" msgstr "Apprentissage pour les services externes" #: tracspamfilter/templates/admin_external.html:36 msgid "" "Skip external services, when internal tests reach a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" #: tracspamfilter/templates/admin_external.html:46 msgid "" "Stop external services, when reached a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" #: tracspamfilter/templates/admin_external.html:58 msgid "" "The Akismet filter uses the free\n" " [1:Akismet]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" "Le filtre Akismet utilise le service gratuit\n" " [1:Akismet]\n" " pour décider si le contenu d'une soumission est un spam" " potentiel.\n" "Vous devez obtenir une\n" " clé pour l'API afin de pouvoir utiliser ce service, clé qui" " est disponible gratuitement pour un usage personnel." #: tracspamfilter/templates/admin_external.html:65 #: tracspamfilter/templates/admin_external.html:135 #: tracspamfilter/templates/admin_external.html:152 #: tracspamfilter/templates/admin_external.html:168 #: tracspamfilter/templates/admin_external.html:185 msgid "API key:" msgstr "Clé de l'API :" #: tracspamfilter/templates/admin_external.html:71 #: tracspamfilter/templates/admin_external.html:91 #: tracspamfilter/templates/admin_external.html:213 msgid "URL:" msgstr "URL :" #: tracspamfilter/templates/admin_external.html:77 #: tracspamfilter/templates/admin_external.html:219 #: tracspamfilter/templates/admin_external.html:224 #, python-format msgid "[1:Key validation failed:] %(error)s" msgstr "[1:La validation de la clé a échouée :] %(error)s" #: tracspamfilter/templates/admin_external.html:85 msgid "" "The BlogSpam filter uses the free\n" " [1:BlogSpam]\n" " service to decide if content submissions are potential spam." msgstr "" "Le filtre BlogSpam utilise le service gratuit\n" " [1:BlogSpam]\n" " pour décider si le contenu d'une soumission est un spam" " potentiel." #: tracspamfilter/templates/admin_external.html:97 msgid "Tests to skip (comma separated):" msgstr "Tests à ne pas effectuer (séparés par des virgules) :" #: tracspamfilter/templates/admin_external.html:100 msgid "Possible Values:" msgstr "" #: tracspamfilter/templates/admin_external.html:105 msgid "method" msgstr "" #: tracspamfilter/templates/admin_external.html:106 msgid "description" msgstr "" #: tracspamfilter/templates/admin_external.html:107 msgid "author" msgstr "" #: tracspamfilter/templates/admin_external.html:127 msgid "" "The StopForumSpam filter uses the\n" " [1:StopForumSpam]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to report SPAM to the service, which is freely " "available." msgstr "" "Le filtre StopForumSpam utilise le service\n" " [1:StopForumSpam]\n" " pour décider si le contenu d'une soumission est un spam" " potentiel.\n" "Vous devez obtenir une\n" " clé pour l'API afin de pouvoir signaler du spam, clé qui" " est disponible gratuitement." #: tracspamfilter/templates/admin_external.html:144 msgid "" "The BotScout filter uses the\n" " [1:BotScout]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" "Le filtre BotScout utilise le service\n" " [1:BotScout]\n" " pour décider si le contenu d'une soumission est un spam" " potentiel.\n" "Vous devez obtenir une\n" " clé pour l'API afin de pouvoir signaler du spam, clé qui" " est disponible gratuitement." #: tracspamfilter/templates/admin_external.html:161 msgid "" "The FSpamList filter uses the\n" " [1:FSpamList]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" "Le filtre FSpamList utilise le service\n" " [1:FSpamList]\n" " pour décider si le contenu d'une soumission est un spam" " potentiel.\n" "Vous devez obtenir une\n" " clé pour l'API afin de pouvoir signaler du spam, clé qui" " est disponible gratuitement." #: tracspamfilter/templates/admin_external.html:177 msgid "" "The HTTP_BL filter uses the free\n" " [1:HTTP:BL]\n" " service to decide if content submissions are potential spam. " "You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" "Le filtre HTTP_BL utilise le service gratuit\n" " [1:HTTP_BL]\n" " pour décider si le contenu d'une soumission est un spam" " potentiel.\n" "Vous devez obtenir une\n" " clé pour l'API afin de pouvoir signaler du spam, clé qui" " est disponible gratuitement pour un usage personnel." #: tracspamfilter/templates/admin_external.html:194 msgid "" "The Mollom filter uses the free\n" " [1:Mollom]\n" " service to decide if content submissions are potential spam. " "You need to obtain\n" " API keys to use the service, which are freely available for " "personal use." msgstr "" #: tracspamfilter/templates/admin_external.html:207 msgid "Secret key:" msgstr "Clé secrète:" #: tracspamfilter/templates/admin_external.html:229 msgid "Free access blacklists" msgstr "Listes noires en accès libre" #: tracspamfilter/templates/admin_external.html:231 msgid "IPv4 Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:235 #: tracspamfilter/templates/admin_external.html:242 #: tracspamfilter/templates/admin_external.html:249 #, python-format msgid "(default: %(list)s)" msgstr "" #: tracspamfilter/templates/admin_external.html:238 msgid "IPv6 Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:245 msgid "URL Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:251 msgid "" "A list of DNS blacklists can be found at the [1:RBLCheck]\n" " or [2:MultiRBL] services." msgstr "" #: tracspamfilter/templates/admin_external.html:256 msgid "" "You can enable or disable these filters from the “[1:General →\n" " Plugins]” panel of the web administration interface." msgstr "" "Vous pouvez activer ou désactiver ces filtres à partir du panneau\n" "« [1:Général → Extensions] » de l'interface d'administration web." #: tracspamfilter/templates/admin_report.html:10 msgid "Spam Reports" msgstr "" #: tracspamfilter/templates/admin_report.html:19 msgid "Spam Filtering: Reports" msgstr "" #: tracspamfilter/templates/admin_report.html:22 #: tracspamfilter/templates/admin_spammonitor.html:20 #, python-format msgid "Viewing entries %(start)s – %(end)s of %(total)s." msgstr "Voir les enregistrements %(start)s - %(end)s sur %(total)s." #: tracspamfilter/templates/admin_report.html:50 #: tracspamfilter/templates/monitortable.html:13 msgid "Path" msgstr "Chemin" #: tracspamfilter/templates/admin_report.html:51 #: tracspamfilter/templates/monitortable.html:14 msgid "Author" msgstr "Auteur" #: tracspamfilter/templates/admin_report.html:52 #: tracspamfilter/templates/monitortable.html:17 msgid "Date/time" msgstr "Date et heure" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:44 msgid "User was logged in" msgstr "L'utilisateur était identifié" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:44 msgid "User was not logged in" msgstr "L'utilisateur n'était pas identifié" #: tracspamfilter/templates/admin_report.html:79 #: tracspamfilter/templates/monitortable.html:64 msgid "No data available" msgstr "Pas de données disponible" #: tracspamfilter/templates/admin_report.html:90 #: tracspamfilter/templates/admin_spammonitor.html:60 msgid "Delete selected" msgstr "Supprimer la sélection" #: tracspamfilter/templates/admin_reportentry.html:10 msgid "Spam Report" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:20 #: tracspamfilter/templates/admin_reportentry.html:21 msgid "Previous Report Entry" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:25 #: tracspamfilter/templates/admin_spamentry.html:25 msgid "Back to List" msgstr "Retour à la liste" #: tracspamfilter/templates/admin_reportentry.html:30 #: tracspamfilter/templates/admin_reportentry.html:31 msgid "Next Report Entry" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:35 msgid "Spam Filtering: Report" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:39 msgid "Report Entry:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:40 #: tracspamfilter/templates/admin_spamentry.html:40 msgid "Information" msgstr "Information" #: tracspamfilter/templates/admin_reportentry.html:42 #: tracspamfilter/templates/admin_spamentry.html:42 msgid "Time:" msgstr "Heure :" #: tracspamfilter/templates/admin_reportentry.html:45 #: tracspamfilter/templates/admin_spamentry.html:45 msgid "Path:" msgstr "Chemin :" #: tracspamfilter/templates/admin_reportentry.html:48 #: tracspamfilter/templates/admin_spamentry.html:48 msgid "Author:" msgstr "Auteur :" #: tracspamfilter/templates/admin_reportentry.html:51 #: tracspamfilter/templates/admin_spamentry.html:51 msgid "Authenticated:" msgstr "Identifié :" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:47 #: tracspamfilter/templates/usertable.html:49 msgid "yes" msgstr "oui" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:48 #: tracspamfilter/templates/usertable.html:50 msgid "no" msgstr "non" #: tracspamfilter/templates/admin_reportentry.html:54 msgid "Comment:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:58 #: tracspamfilter/templates/admin_spamentry.html:75 msgid "HTTP headers" msgstr "Entêtes HTTP" #: tracspamfilter/templates/admin_reportentry.html:63 #: tracspamfilter/templates/admin_spamentry.html:82 msgid "Delete" msgstr "Supprimer" #: tracspamfilter/templates/admin_reportentry.html:68 msgid "Possibly related log entries:" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:10 msgid "Spam Filter" msgstr "Dispositif anti-spam" #: tracspamfilter/templates/admin_spamconfig.html:14 msgid "Spam Filtering: Configuration" msgstr "Dispositif anti-spam : configuration" #: tracspamfilter/templates/admin_spamconfig.html:15 msgid "" "See [1:wiki page]\n" " for a short documentation." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:16 msgid "Help translating this plugin at [1:Transifex]." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:21 msgid "Karma Tuning" msgstr "Ajustements du karma" #: tracspamfilter/templates/admin_spamconfig.html:23 msgid "Minimum karma required for a successful submission:" msgstr "Karma minimum requis pour qu'une soumission soit autorisée :" #: tracspamfilter/templates/admin_spamconfig.html:29 msgid "" "Karma assigned to attachments (e.g. to allow relaxed rules for file " "uploads):" msgstr "" "Karma associé aux pièces jointes (par exemple pour autoriser plus " "facilement le téléchargement de fichiers) :" #: tracspamfilter/templates/admin_spamconfig.html:35 msgid "" "Karma assigned to registering users (e.g. negative value to increase " "captcha usage):" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:41 msgid "" "Content submissions are passed through a set of registered and enabled\n" " [1:filter strategies], each of which check the submitted " "content\n" " and may assign [2:karma points] to it. The sum of these karma\n" " points needs to be greater than or equal to the minimum karma\n" " configured here for the submission to be accepted." msgstr "" "Les soumissions de contenu passent au travers d'un ensemble de\n" " [1:stratégies de filtrage], chacune vérifiant le contenu\n" " et y associant éventuellement un certain nombre de \n" " [2:points de karma].\n" " Le total de ces points de karma doit\n" " être supérieur ou égal au nombre de points de karma minimal\n" " configuré ici pour que la soumission soit acceptée." #: tracspamfilter/templates/admin_spamconfig.html:50 #: tracspamfilter/templates/admin_statistics.html:34 msgid "Strategy" msgstr "Stratégie" #: tracspamfilter/templates/admin_spamconfig.html:51 msgid "Karma points" msgstr "Points de karma" #: tracspamfilter/templates/admin_spamconfig.html:52 msgid "Description" msgstr "Description" #: tracspamfilter/templates/admin_spamconfig.html:66 msgid "Logging" msgstr "Surveillance" #: tracspamfilter/templates/admin_spamconfig.html:70 msgid "Enable" msgstr "Activer" #: tracspamfilter/templates/admin_spamconfig.html:74 msgid "" "The spam filter plugin can optionally log every content submission so\n" " that you can monitor and tune the effectiveness of the " "filtering. The\n" " log is stored in the database, and can be viewed under “[1:Spam" "\n" " Filtering → Monitoring]” from the web administration\n" " interface." msgstr "" "Le dispositif anti-spam peut de façon optionelle enregistrer toute \n" "soumission de contenu de façon à pouvoir surveiller et optimiser \n" "l'efficacité avec laquelle le filtrage s'effectue. Les enregistrements\n" "sont conservés dans la base de données et peuvent être consultés sous la\n" "rubrique « [1:Dispositif anti-spam : surveillance] » dans l'interface \n" "d'administration web." #: tracspamfilter/templates/admin_spamconfig.html:82 msgid "" "Purge old entries after\n" " [1:]\n" " days" msgstr "" "Supprimer les anciens extraits au bout de\n" " [1:]\n" " jours" #: tracspamfilter/templates/admin_spamconfig.html:90 #, python-format msgid "" "Number of entries in log message display\n" " (%(min)s-%(max)s)\n" " [1:]" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:100 msgid "Authenticated" msgstr "Identifié" #: tracspamfilter/templates/admin_spamconfig.html:104 msgid "Trust authenticated users" msgstr "Faire confiance aux utilisateurs s'étant identifiés" #: tracspamfilter/templates/admin_spamconfig.html:108 msgid "" "If authenticated users should not be trusted automatically, this\n" " option must be disabled. Instead of full trust the supplied " "karma\n" " value is used in this case." msgstr "" "S'il ne faut pas faire confiance de façon automatique aux utilisateurs \n" "s'étant identifiés, cette option doit être désactivée. Plutôt qu'une \n" "confiance absolue, le nombre de points de karma spécifé sera alloué." #: tracspamfilter/templates/admin_spamconfig.html:114 msgid "Karma of authenticated users:" msgstr "Karma donné aux utilisateurs identifiés :" #: tracspamfilter/templates/admin_spamentry.html:10 #: tracspamfilter/templates/admin_spammonitor.html:10 msgid "Spam Monitoring" msgstr "Surveillance du spam" #: tracspamfilter/templates/admin_spamentry.html:20 #: tracspamfilter/templates/admin_spamentry.html:21 msgid "Previous Log Entry" msgstr "Enregistrement précédent" #: tracspamfilter/templates/admin_spamentry.html:30 #: tracspamfilter/templates/admin_spamentry.html:31 msgid "Next Log Entry" msgstr "Enregistrement suivant" #: tracspamfilter/templates/admin_spamentry.html:35 #: tracspamfilter/templates/admin_spammonitor.html:14 msgid "Spam Filtering: Monitoring" msgstr "Dispositif anti-spam : surveillance" #: tracspamfilter/templates/admin_spamentry.html:39 msgid "Log Entry:" msgstr "Enregistrement de surveillance :" #: tracspamfilter/templates/admin_spamentry.html:54 msgid "IP address:" msgstr "Adresse IP :" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "spam" msgstr "spam" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "ham" msgstr "non-spam" #: tracspamfilter/templates/admin_spamentry.html:60 msgid "Karma:" msgstr "Karma :" #: tracspamfilter/templates/admin_spamentry.html:61 #, python-format msgid "" "[1:%(karma)s]\n" " (marked as %(spam_or_ham)s)\n" " [2:\n" " [3:%(reasons)s]\n" " ]" msgstr "" "[1:%(karma)s]\n" " (catégorisé en tant que %(spam_or_ham)s)\n" " [2:\n" " [3:%(reasons)s]\n" " ]" #: tracspamfilter/templates/admin_spamentry.html:71 msgid "Submitted content" msgstr "Contenu soumis" #: tracspamfilter/templates/admin_spamentry.html:80 msgid "Mark as Spam" msgstr "Catégoriser en spam" #: tracspamfilter/templates/admin_spamentry.html:81 msgid "Mark as Ham" msgstr "Catégoriser en non-spam" #: tracspamfilter/templates/admin_spamentry.html:83 msgid "Delete as Spam" msgstr "Supprimer (spam)" #: tracspamfilter/templates/admin_spamentry.html:84 msgid "Delete as Ham" msgstr "Supprimer (en tant que non-spam)" #: tracspamfilter/templates/admin_spamentry.html:85 msgid "Delete (No Statistics)" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:93 msgid "Remove registered user" msgstr "Supprimer l'utilisateur identifié" #: tracspamfilter/templates/admin_spamentry.html:99 msgid "Search for user name" msgstr "Rechercher un nom d'utilisateur" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Note:" msgstr "Note :" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Logging by the spam filter is currently disabled." msgstr "L'enregistrement n'est pas activée pour le dispositif anti-spam." #: tracspamfilter/templates/admin_spammonitor.html:52 msgid "Delete > 90%" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:56 msgid "Mark selected as Spam" msgstr "Catégoriser la sélection en spam" #: tracspamfilter/templates/admin_spammonitor.html:58 msgid "Mark selected as Ham" msgstr "Catégoriser la sélection en non-spam" #: tracspamfilter/templates/admin_spammonitor.html:62 msgid "Delete selected as Spam" msgstr "Supprimer la séléction (spam)" #: tracspamfilter/templates/admin_spammonitor.html:64 msgid "Delete selected as Ham" msgstr "Supprimer la sélection (non-spam)" #: tracspamfilter/templates/admin_statistics.html:10 msgid "Spam Statistics" msgstr "" #: tracspamfilter/templates/admin_statistics.html:14 msgid "Spam Filtering: Statistics" msgstr "" #: tracspamfilter/templates/admin_statistics.html:17 msgid "No submission statistics yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:21 #, python-format msgid "" "%(count)s Submissions tested since %(time)s,\n" " %(spam)s spam and\n" " %(ham)s ham\n" " (%(local)s of the tests could be solved local).\n" " %(spamerr)s spam and %(hamerr)s ham have been retrained." msgstr "" #: tracspamfilter/templates/admin_statistics.html:35 msgid "Type" msgstr "Type" #: tracspamfilter/templates/admin_statistics.html:37 msgid "Train / Verify" msgstr "" #: tracspamfilter/templates/admin_statistics.html:38 msgid "Errors" msgstr "Erreurs" #: tracspamfilter/templates/admin_statistics.html:43 #: tracspamfilter/templates/admin_statistics.html:45 msgid "Spam" msgstr "Spam" #: tracspamfilter/templates/admin_statistics.html:44 #: tracspamfilter/templates/admin_statistics.html:46 msgid "Ham" msgstr "Ham" #: tracspamfilter/templates/admin_statistics.html:49 #: tracspamfilter/templates/admin_statistics.html:56 #: tracspamfilter/templates/admin_statistics.html:59 msgid "Total" msgstr "Total" #: tracspamfilter/templates/admin_statistics.html:50 msgid "Mean Delay" msgstr "" #: tracspamfilter/templates/admin_statistics.html:51 msgid "No Result" msgstr "Pas de résultat" #: tracspamfilter/templates/admin_statistics.html:52 #: tracspamfilter/templates/admin_statistics.html:54 msgid "Match" msgstr "" #: tracspamfilter/templates/admin_statistics.html:53 #: tracspamfilter/templates/admin_statistics.html:55 msgid "Mismatch" msgstr "" #: tracspamfilter/templates/admin_statistics.html:57 #: tracspamfilter/templates/admin_statistics.html:60 msgid "Right" msgstr "" #: tracspamfilter/templates/admin_statistics.html:58 #: tracspamfilter/templates/admin_statistics.html:61 msgid "Wrong" msgstr "" #: tracspamfilter/templates/admin_statistics.html:69 msgid "No tests yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:72 #, python-format msgid "%(seconds)s s" msgstr "" #: tracspamfilter/templates/admin_statistics.html:101 msgid "No spam yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:119 msgid "No ham yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:141 msgid "Clear statistics" msgstr "Effacer les statistiques" #: tracspamfilter/templates/admin_statistics.html:151 msgid "Clear statistics database" msgstr "Effacer la base de données des statistiques" #: tracspamfilter/templates/admin_user.html:10 msgid "Spam User Handling" msgstr "" #: tracspamfilter/templates/admin_user.html:14 #, python-format msgid "" "Spam Filtering: User handling (%(type)s)\n" " [1:%(count)s]" msgstr "" #: tracspamfilter/templates/admin_user.html:20 msgid "Overview" msgstr "Aperçu" #: tracspamfilter/templates/admin_user.html:21 msgid "All" msgstr "" #: tracspamfilter/templates/admin_user.html:22 #: tracspamfilter/templates/usertable.html:15 msgid "Registered" msgstr "Enregistré" #: tracspamfilter/templates/admin_user.html:23 msgid "Unused [multi selection]" msgstr "" #: tracspamfilter/templates/admin_user.html:24 msgid "Unused" msgstr "Inutilisé" #: tracspamfilter/templates/admin_user.html:28 #, python-format msgid "" "There are %(total)s\n" " different entries in the database, %(registered)s users are\n" " registered and %(unused)s have not been used." msgstr "" #: tracspamfilter/templates/admin_user.html:35 msgid "Date" msgstr "Date" #: tracspamfilter/templates/admin_user.html:36 #, python-format msgid "Action of user '%(username)s'" msgstr "" #: tracspamfilter/templates/admin_user.html:55 msgid "Remove selected" msgstr "" #: tracspamfilter/templates/admin_user.html:63 msgid "Values must be URL encoded!" msgstr "" #: tracspamfilter/templates/admin_user.html:65 msgid "Old user:" msgstr "Ancien utilisateur:" #: tracspamfilter/templates/admin_user.html:68 msgid "New user:" msgstr "Nouvel utilisateur:" #: tracspamfilter/templates/admin_user.html:74 msgid "Change unauthorized user" msgstr "" #: tracspamfilter/templates/admin_user.html:74 msgid "Change user" msgstr "" #: tracspamfilter/templates/admin_user.html:81 #, python-format msgid "Remove %(num)s temporary session" msgid_plural "Remove %(num)s temporary sessions" msgstr[0] "" msgstr[1] "" #: tracspamfilter/templates/admin_user.html:86 msgid "Convert emails to registered usernames" msgstr "" #: tracspamfilter/templates/monitortable.html:15 msgid "IP Address" msgstr "Adresse\tIP" #: tracspamfilter/templates/monitortable.html:16 msgid "Karma" msgstr "Karma" #: tracspamfilter/templates/usertable.html:13 msgid "User name" msgstr "Nom d'utilisateur" #: tracspamfilter/templates/usertable.html:14 msgid "Last login" msgstr "Dernière connexion" #: tracspamfilter/templates/usertable.html:16 msgid "Setup" msgstr "" #: tracspamfilter/templates/usertable.html:17 msgid "E-Mail" msgstr "E-Mail" #: tracspamfilter/templates/usertable.html:18 msgid "Wiki edits" msgstr "" #: tracspamfilter/templates/usertable.html:19 msgid "Ticket edits" msgstr "" #: tracspamfilter/templates/usertable.html:20 msgid "SVN edits" msgstr "" #: tracspamfilter/templates/usertable.html:21 msgid "Other" msgstr "Autre" #: tracspamfilter/templates/usertable.html:39 msgid "Source" msgstr "Source" #: tracspamfilter/templates/usertable.html:49 #: tracspamfilter/templates/usertable.html:50 msgid "(password)" msgstr "" #: tracspamfilter/templates/usertable.html:52 msgid "(double)" msgstr "" #: tracspamfilter/templates/usertable.html:55 #: tracspamfilter/templates/usertable.html:61 #: tracspamfilter/templates/usertable.html:67 #: tracspamfilter/templates/usertable.html:73 msgid "user" msgstr "utilisateur" #: tracspamfilter/templates/usertable.html:56 #: tracspamfilter/templates/usertable.html:62 #: tracspamfilter/templates/usertable.html:68 #: tracspamfilter/templates/usertable.html:74 msgid "e-mail" msgstr "e-mail" #: tracspamfilter/templates/usertable.html:57 #: tracspamfilter/templates/usertable.html:63 #: tracspamfilter/templates/usertable.html:69 #: tracspamfilter/templates/usertable.html:75 msgid "both" msgstr "" #: tracspamfilter/templates/usertable.html:83 msgid "Remove" msgstr "Retirer" #: tracspamfilter/templates/verify_captcha.html:13 msgid "Captcha Error" msgstr "Erreur de Captcha" #: tracspamfilter/templates/verify_captcha.html:17 msgid "" "Trac thinks your submission might be Spam.\n" " To prove otherwise please provide a response to the following." msgstr "" "Trac pense que votre soumission peut être du spam.\n" " Pour montrer qu'il en est autrement, répondez au test suivant." #: tracspamfilter/templates/verify_captcha.html:19 msgid "" "Note - the captcha method is choosen randomly. Retry if the captcha does " "not work on your system!" msgstr "" #: tracspamfilter/templates/verify_captcha.html:22 msgid "Response:" msgstr "Réponse :" #~ msgid "Lifetime has invalid value" #~ msgstr "La durée de vie a une valeur incorrecte" #~ msgid "Text values are not numeric" #~ msgstr "Les valeurs extraites du texte ne sont pas numériques" #~ msgid "Numeric image values are no numbers" #~ msgstr "Les valeurs extraites de l'image ne sont pas des nombres" #~ msgid "" #~ msgstr "" #~ msgid "Data validation failed:" #~ msgstr "La validation des données a échouée :" spam-filter/tracspamfilter/locale/messages.pot0000644000175500017550000014627512731667632021701 0ustar debacledebacle# Translations template for TracSpamFilter. # Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the TracSpamFilter # project. # FIRST AUTHOR , 2016. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: TracSpamFilter 1.0.9\n" "Report-Msgid-Bugs-To: trac@dstoecker.de\n" "POT-Creation-Date: 2016-06-17 18:13-0600\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.3.4\n" #: tracspamfilter/accountadapter.py:33 msgid "" "Interface of spamfilter to account manager plugin to check new " "account\n" "registrations for spam.\n" "\n" "It provides an additional 'Details' input field to get more " "information\n" "for calculating the probability of a spam registration attempt.\n" "Knowledge gained from inspecting registration attempts is shared with" " all\n" "other spam filter adapters for this system." msgstr "" #: tracspamfilter/accountadapter.py:47 msgid "Details:" msgstr "" #: tracspamfilter/adapters.py:106 msgid "" "The maximum number of bytes from an attachment to pass through\n" "the spam filters." msgstr "" #: tracspamfilter/admin.py:67 msgid "" "How many monitor entries are displayed by default (between 5 and " "10000)." msgstr "" #: tracspamfilter/admin.py:71 msgid "Show the buttons for training without deleting entry." msgstr "" #: tracspamfilter/admin.py:79 tracspamfilter/admin.py:82 #: tracspamfilter/admin.py:306 tracspamfilter/admin.py:497 #: tracspamfilter/admin.py:589 tracspamfilter/adminreport.py:45 #: tracspamfilter/adminusers.py:48 tracspamfilter/captcha/admin.py:68 msgid "Spam Filtering" msgstr "" #: tracspamfilter/admin.py:80 tracspamfilter/templates/admin_bayes.html:19 msgid "Configuration" msgstr "" #: tracspamfilter/admin.py:83 msgid "Monitoring" msgstr "" #: tracspamfilter/admin.py:105 tracspamfilter/filters/bayes.py:80 #, python-format msgid "SpamBayes determined spam probability of %s%%" msgstr "" #: tracspamfilter/admin.py:107 #, python-format msgid "Select 100.00%% entries" msgstr "" #: tracspamfilter/admin.py:108 #, python-format msgid "Select >90.00%% entries" msgstr "" #: tracspamfilter/admin.py:109 #, python-format msgid "Select <10.00%% entries" msgstr "" #: tracspamfilter/admin.py:110 #, python-format msgid "Select 0.00%% entries" msgstr "" #: tracspamfilter/admin.py:111 msgid "Select Spam entries" msgstr "" #: tracspamfilter/admin.py:112 msgid "Select Ham entries" msgstr "" #: tracspamfilter/admin.py:237 tracspamfilter/adminreport.py:111 #: tracspamfilter/templates/admin_report.html:31 #: tracspamfilter/templates/admin_report.html:32 #: tracspamfilter/templates/admin_spammonitor.html:29 #: tracspamfilter/templates/admin_spammonitor.html:30 msgid "Previous Page" msgstr "" #: tracspamfilter/admin.py:241 tracspamfilter/adminreport.py:115 #: tracspamfilter/templates/admin_report.html:37 #: tracspamfilter/templates/admin_report.html:38 #: tracspamfilter/templates/admin_spammonitor.html:35 #: tracspamfilter/templates/admin_spammonitor.html:36 msgid "Next Page" msgstr "" #: tracspamfilter/admin.py:258 msgid "Log entry not found" msgstr "" #: tracspamfilter/admin.py:263 tracspamfilter/admin.py:268 #, python-format msgid "Log Entry %(id)s" msgstr "" #: tracspamfilter/admin.py:264 msgid "Log Entry List" msgstr "" #: tracspamfilter/admin.py:307 msgid "External Services" msgstr "" #: tracspamfilter/admin.py:497 tracspamfilter/templates/admin_bayes.html:10 msgid "Bayes" msgstr "" #: tracspamfilter/admin.py:549 #, python-format msgid "(ratio %.1f : 1)" msgstr "" #: tracspamfilter/admin.py:551 #, python-format msgid "(ratio 1 : %.1f)" msgstr "" #: tracspamfilter/admin.py:562 #, python-format msgid "%(num)d spam" msgid_plural "%(num)d spam" msgstr[0] "" msgstr[1] "" #: tracspamfilter/admin.py:564 #, python-format msgid "%(num)d ham" msgid_plural "%(num)d ham" msgstr[0] "" msgstr[1] "" #: tracspamfilter/admin.py:566 #, python-format msgid "%(num)d line" msgid_plural "%(num)d lines" msgstr[0] "" msgstr[1] "" #: tracspamfilter/admin.py:568 #, python-format msgid "%(num)d mixed" msgid_plural "%(num)d mixed" msgstr[0] "" msgstr[1] "" #: tracspamfilter/admin.py:590 msgid "Statistics" msgstr "" #: tracspamfilter/adminreport.py:34 msgid "" "How many report entries are displayed by default (between 5 and " "10000)." msgstr "" #: tracspamfilter/adminreport.py:45 msgid "Reports" msgstr "" #: tracspamfilter/adminreport.py:135 msgid "Report entry not found" msgstr "" #: tracspamfilter/adminreport.py:143 tracspamfilter/adminreport.py:150 #, python-format msgid "Report Entry %d" msgstr "" #: tracspamfilter/adminreport.py:144 msgid "Report Entry List" msgstr "" #: tracspamfilter/adminusers.py:29 msgid "How many wiki edits are still an unused account." msgstr "" #: tracspamfilter/adminusers.py:33 msgid "How many days no login are considered for dead accounts." msgstr "" #: tracspamfilter/adminusers.py:38 msgid "Default mode for spam user admin panel." msgstr "" #: tracspamfilter/adminusers.py:48 msgid "Users" msgstr "" #: tracspamfilter/adminusers.py:58 msgid "Old or new value cannot be empty" msgstr "" #: tracspamfilter/adminusers.py:66 msgid "Old and new value cannot be equal" msgstr "" #: tracspamfilter/adminusers.py:69 msgid "New name cannot be used in CC fields" msgstr "" #: tracspamfilter/adminusers.py:71 msgid "Illegal user arguments passed or changing not allowed" msgstr "" #: tracspamfilter/adminusers.py:73 #, python-format msgid "%(num)d entry has been updated" msgid_plural "%(num)d entries have been updated" msgstr[0] "" msgstr[1] "" #: tracspamfilter/adminusers.py:80 #, python-format msgid "Username '%s' cannot be used in CC fields" msgstr "" #: tracspamfilter/adminusers.py:82 #, python-format msgid "Error for e-mail change for username '%s'" msgstr "" #: tracspamfilter/adminusers.py:84 #, python-format msgid "%(num)d entry has been updated for user %(user)s" msgid_plural "%(num)d entries have been updated for user %(user)s" msgstr[0] "" msgstr[1] "" #: tracspamfilter/adminusers.py:87 #, python-format msgid "E-mails for user %s updated" msgstr "" #: tracspamfilter/adminusers.py:116 msgid "data overview" msgstr "" #: tracspamfilter/adminusers.py:118 msgid "unused accounts" msgstr "" #: tracspamfilter/adminusers.py:120 msgid "registered accounts" msgstr "" #: tracspamfilter/adminusers.py:122 #, python-format msgid "detailed user information for '%s'" msgstr "" #: tracspamfilter/adminusers.py:124 msgid "everything from accounts, wiki, tickets and svn" msgstr "" #: tracspamfilter/adminusers.py:125 #, python-format msgid "%(num)d entry" msgid_plural "%(num)d entries" msgstr[0] "" msgstr[1] "" #: tracspamfilter/filtersystem.py:54 msgid "The minimum score required for a submission to be allowed." msgstr "" #: tracspamfilter/filtersystem.py:58 msgid "" "The karma given to authenticated users, in case\n" "`trust_authenticated` is false." msgstr "" #: tracspamfilter/filtersystem.py:62 msgid "" "Whether all content submissions and spam filtering activity should\n" "be logged to the database." msgstr "" #: tracspamfilter/filtersystem.py:66 msgid "The number of days after which log entries should be purged." msgstr "" #: tracspamfilter/filtersystem.py:70 msgid "Allow usage of external services." msgstr "" #: tracspamfilter/filtersystem.py:73 msgid "" "Skip external calls when this negative karma is already reached\n" "by internal tests." msgstr "" #: tracspamfilter/filtersystem.py:77 msgid "" "Skip external calls when this positive karma is already reached\n" "by internal tests." msgstr "" #: tracspamfilter/filtersystem.py:81 msgid "Stop external calls when this negative karma is reached." msgstr "" #: tracspamfilter/filtersystem.py:85 msgid "Stop external calls when this positive karma is reached." msgstr "" #: tracspamfilter/filtersystem.py:89 msgid "Allow training of external services." msgstr "" #: tracspamfilter/filtersystem.py:92 msgid "" "Whether content submissions by authenticated users should be trusted\n" "without checking for potential spam or other abuse." msgstr "" #: tracspamfilter/filtersystem.py:98 msgid "The karma given to attachments." msgstr "" #: tracspamfilter/filtersystem.py:101 msgid "The karma given to registrations." msgstr "" #: tracspamfilter/filtersystem.py:104 msgid "The handler used to reject content." msgstr "" #: tracspamfilter/filtersystem.py:108 msgid "Interpret X-Forwarded-For header for IP checks." msgstr "" #: tracspamfilter/filtersystem.py:112 msgid "" "This section is used to handle all configurations used by\n" "spam filter plugin." msgstr "" #: tracspamfilter/filtersystem.py:171 msgid "User is authenticated" msgstr "" #: tracspamfilter/filtersystem.py:177 msgid "Attachment weighting" msgstr "" #: tracspamfilter/filtersystem.py:182 msgid "Registration weighting" msgstr "" #: tracspamfilter/filtersystem.py:306 #, python-format msgid "Submission rejected as potential spam %(message)s" msgstr "" #: tracspamfilter/model.py:424 msgid "external" msgstr "" #: tracspamfilter/model.py:424 msgid "internal" msgstr "" #: tracspamfilter/report.py:29 msgid "List of page types to add spam report link" msgstr "" #: tracspamfilter/report.py:42 msgid "No page supplied to report as spam" msgstr "" #: tracspamfilter/report.py:74 msgid "Reported spam" msgstr "" #: tracspamfilter/report.py:76 msgid "Comment" msgstr "" #: tracspamfilter/report.py:78 msgid "Report spam" msgstr "" #: tracspamfilter/report.py:82 #, python-format msgid "%(num)d spam report" msgid_plural "%(num)d spam reports" msgstr[0] "" msgstr[1] "" #: tracspamfilter/users.py:40 #, python-format msgid "Wiki page '%(page)s' version %(version)s modified" msgstr "" #: tracspamfilter/users.py:43 #, python-format msgid "Attachment '%s' added" msgstr "" #: tracspamfilter/users.py:47 #, python-format msgid "Ticket %(id)s field '%(field)s' changed" msgstr "" #: tracspamfilter/users.py:50 #, python-format msgid "Removed from ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:52 #, python-format msgid "Set in ticket %(id)s field '%(field)s' ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:58 #, python-format msgid "Ticket %(id)s CC field change ('%(old)s' --> '%(new)s')" msgstr "" #: tracspamfilter/users.py:62 #, python-format msgid "Reporter of ticket %s" msgstr "" #: tracspamfilter/users.py:64 #, python-format msgid "Owner of ticket %s" msgstr "" #: tracspamfilter/users.py:66 #, python-format msgid "In CC of ticket %(id)s ('%(cc)s')" msgstr "" #: tracspamfilter/users.py:69 #, python-format msgid "Author of revision %s" msgstr "" #: tracspamfilter/users.py:72 #, python-format msgid "Component '%s' owner" msgstr "" #: tracspamfilter/users.py:75 msgid "In permissions list" msgstr "" #: tracspamfilter/users.py:78 #, python-format msgid "Author of report %d" msgstr "" #: tracspamfilter/users.py:83 tracspamfilter/users.py:87 #, python-format msgid "Voted for '%s'" msgstr "" #: tracspamfilter/captcha/admin.py:68 msgid "Captcha" msgstr "" #: tracspamfilter/captcha/admin.py:90 tracspamfilter/captcha/admin.py:124 #: tracspamfilter/captcha/admin.py:133 tracspamfilter/captcha/admin.py:147 #: tracspamfilter/captcha/admin.py:156 #, python-format msgid "Invalid value for %(key)s" msgstr "" #: tracspamfilter/captcha/admin.py:100 msgid "The keys are invalid" msgstr "" #: tracspamfilter/captcha/admin.py:112 msgid "The key or user id are invalid" msgstr "" #: tracspamfilter/captcha/api.py:61 msgid "CAPTCHA method to use for verifying humans." msgstr "" #: tracspamfilter/captcha/api.py:66 msgid "" "By how many points a successful CAPTCHA response increases the\n" "overall score." msgstr "" #: tracspamfilter/captcha/api.py:71 msgid "By how many points a failed CAPTCHA impacts the overall score." msgstr "" #: tracspamfilter/captcha/api.py:75 msgid "" "Time in seconds that a successful CAPTCHA response increases\n" "karma." msgstr "" #: tracspamfilter/captcha/api.py:79 msgid "Time in seconds before CAPTCHA is removed." msgstr "" #: tracspamfilter/captcha/api.py:83 msgid "Time in seconds before database cleanup is called." msgstr "" #: tracspamfilter/captcha/api.py:103 #, python-format msgid "Human verified via CAPTCHA (%s)" msgstr "" #: tracspamfilter/captcha/api.py:112 #, python-format msgid "Failed CAPTCHA (%s) attempts" msgstr "" #: tracspamfilter/captcha/api.py:144 msgid "CAPTCHA failed to handle original request" msgstr "" #: tracspamfilter/captcha/api.py:146 msgid "CAPTCHA verification failed" msgstr "" #: tracspamfilter/captcha/expression.py:34 msgid "Number of terms in numeric CAPTCHA expression." msgstr "" #: tracspamfilter/captcha/expression.py:38 msgid "" "Maximum value of individual terms in numeric CAPTCHA\n" "expression." msgstr "" #: tracspamfilter/captcha/expression.py:42 msgid "multiplied by" msgstr "" #: tracspamfilter/captcha/expression.py:42 msgid "minus" msgstr "" #: tracspamfilter/captcha/expression.py:43 msgid "plus" msgstr "" #: tracspamfilter/captcha/expression.py:45 msgid "zero" msgstr "" #: tracspamfilter/captcha/expression.py:45 msgid "one" msgstr "" #: tracspamfilter/captcha/expression.py:45 msgid "two" msgstr "" #: tracspamfilter/captcha/expression.py:45 msgid "three" msgstr "" #: tracspamfilter/captcha/expression.py:46 msgid "four" msgstr "" #: tracspamfilter/captcha/expression.py:46 msgid "five" msgstr "" #: tracspamfilter/captcha/expression.py:46 msgid "six" msgstr "" #: tracspamfilter/captcha/expression.py:46 msgid "seven" msgstr "" #: tracspamfilter/captcha/expression.py:47 msgid "eight" msgstr "" #: tracspamfilter/captcha/expression.py:47 msgid "nine" msgstr "" #: tracspamfilter/captcha/expression.py:47 msgid "ten" msgstr "" #: tracspamfilter/captcha/expression.py:47 msgid "eleven" msgstr "" #: tracspamfilter/captcha/expression.py:48 msgid "twelve" msgstr "" #: tracspamfilter/captcha/expression.py:48 msgid "thirteen" msgstr "" #: tracspamfilter/captcha/expression.py:48 msgid "fourteen" msgstr "" #: tracspamfilter/captcha/expression.py:49 msgid "fifteen" msgstr "" #: tracspamfilter/captcha/expression.py:49 msgid "sixteen" msgstr "" #: tracspamfilter/captcha/expression.py:49 msgid "seventeen" msgstr "" #: tracspamfilter/captcha/expression.py:50 msgid "eighteen" msgstr "" #: tracspamfilter/captcha/expression.py:50 msgid "nineteen" msgstr "" #. TRANSLATOR: if compound numbers like in english are not #. supported, simply add a "plus" command to the following #. translations! #: tracspamfilter/captcha/expression.py:55 msgid "twenty" msgstr "" #: tracspamfilter/captcha/expression.py:55 msgid "thirty" msgstr "" #: tracspamfilter/captcha/expression.py:55 msgid "forty" msgstr "" #: tracspamfilter/captcha/expression.py:55 msgid "fifty" msgstr "" #: tracspamfilter/captcha/expression.py:56 msgid "sixty" msgstr "" #: tracspamfilter/captcha/expression.py:56 msgid "seventy" msgstr "" #: tracspamfilter/captcha/expression.py:56 msgid "eighty" msgstr "" #: tracspamfilter/captcha/expression.py:56 msgid "ninety" msgstr "" #: tracspamfilter/captcha/expression.py:63 msgid "Numeric captcha can not represent numbers > 100" msgstr "" #: tracspamfilter/captcha/image.py:43 msgid "Set of fonts to choose from when generating image CAPTCHA." msgstr "" #: tracspamfilter/captcha/image.py:47 msgid "Font size to use in image CAPTCHA." msgstr "" #: tracspamfilter/captcha/image.py:51 msgid "Alphabet to choose image CAPTCHA challenge from." msgstr "" #: tracspamfilter/captcha/image.py:56 msgid "Number of letters to use in image CAPTCHA challenge." msgstr "" #: tracspamfilter/captcha/keycaptcha.py:32 msgid "Private key for KeyCaptcha usage." msgstr "" #: tracspamfilter/captcha/keycaptcha.py:35 msgid "User id for KeyCaptcha usage." msgstr "" #: tracspamfilter/captcha/recaptcha.py:32 #: tracspamfilter/captcha/recaptcha2.py:38 msgid "Private key for reCaptcha usage." msgstr "" #: tracspamfilter/captcha/recaptcha.py:35 #: tracspamfilter/captcha/recaptcha2.py:41 msgid "Public key for reCaptcha usage." msgstr "" #: tracspamfilter/captcha/recaptcha.py:57 #: tracspamfilter/captcha/recaptcha2.py:52 #: tracspamfilter/templates/verify_captcha.html:24 msgid "Submit" msgstr "" #: tracspamfilter/filters/akismet.py:42 msgid "" "By how many points an Akismet reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/akismet.py:46 msgid "Wordpress key required to use the Akismet API." msgstr "" #: tracspamfilter/filters/akismet.py:50 msgid "URL of the Akismet service." msgstr "" #: tracspamfilter/filters/akismet.py:77 msgid "Akismet says content is spam" msgstr "" #: tracspamfilter/filters/bayes.py:35 msgid "" "By what factor Bayesian spam probability score affects the overall\n" "karma of a submission." msgstr "" #: tracspamfilter/filters/bayes.py:39 msgid "" "The minimum number of submissions in the training database required\n" "for the filter to start impacting the karma of submissions." msgstr "" #: tracspamfilter/filters/bayes.py:44 msgid "" "Entries with a count less than this value get removed from the\n" "database when calling the reduce function." msgstr "" #: tracspamfilter/filters/blogspam.py:38 msgid "" "By how many points an BlogSpam reject impacts the overall\n" "karma of a submission." msgstr "" #: tracspamfilter/filters/blogspam.py:42 msgid "URL of the BlogSpam service." msgstr "" #: tracspamfilter/filters/blogspam.py:46 msgid "Comma separated list of tests to skip." msgstr "" #: tracspamfilter/filters/blogspam.py:71 #, python-format msgid "BlogSpam says content is spam (%s [%s])" msgstr "" #: tracspamfilter/filters/botscout.py:32 msgid "" "By how many points a BotScout reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/botscout.py:36 msgid "API key required to use BotScout." msgstr "" #: tracspamfilter/filters/botscout.py:63 #, python-format msgid "BotScout says this is spam (%s)" msgstr "" #: tracspamfilter/filters/extlinks.py:29 msgid "" "By how many points too many external links in a submission impact\n" "the overall score." msgstr "" #: tracspamfilter/filters/extlinks.py:33 msgid "" "The maximum number of external links allowed in a submission until\n" "that submission gets negative karma." msgstr "" #: tracspamfilter/filters/extlinks.py:37 msgid "List of domains that should be allowed in external links" msgstr "" #: tracspamfilter/filters/extlinks.py:64 msgid "Maximum number of external links per post exceeded" msgstr "" #: tracspamfilter/filters/extlinks.py:67 msgid "External links in post found" msgstr "" #: tracspamfilter/filters/fspamlist.py:33 msgid "" "By how many points a FSpamList reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/fspamlist.py:37 msgid "API key required to use FSpamList." msgstr "" #: tracspamfilter/filters/fspamlist.py:77 #, python-format msgid "FSpamList says this is spam (%s)" msgstr "" #: tracspamfilter/filters/httpbl.py:35 msgid "" "By how many points listing as \"comment spammer\" impacts the\n" "overall karma of a submission." msgstr "" #: tracspamfilter/filters/httpbl.py:39 msgid "Http:BL API key required for use." msgstr "" #: tracspamfilter/filters/httpbl.py:82 #, python-format msgid "IP %s blacklisted by Http:BL" msgstr "" #: tracspamfilter/filters/ip_blacklist.py:35 msgid "" "By how many points blacklisting by a single server impacts the\n" "overall karma of a submission." msgstr "" #: tracspamfilter/filters/ip_blacklist.py:41 msgid "Servers used for IPv4 blacklisting." msgstr "" #: tracspamfilter/filters/ip_blacklist.py:47 msgid "Servers used for IPv6 blacklisting." msgstr "" #: tracspamfilter/filters/ip_blacklist.py:93 #, python-format msgid "IP %s blacklisted by %s" msgstr "" #: tracspamfilter/filters/ip_regex.py:36 msgid "" "By how many points a match with a pattern on the BadIP page\n" "impacts the overall karma of a submission." msgstr "" #: tracspamfilter/filters/ip_regex.py:41 msgid "" "Local file to be loaded to get BadIP. Can be used in\n" "addition to BadIP wiki page." msgstr "" #: tracspamfilter/filters/ip_regex.py:45 msgid "Show the matched bad IP patterns in rejection message." msgstr "" #: tracspamfilter/filters/ip_regex.py:83 #, python-format msgid "IP catched by these blacklisted patterns: %s" msgstr "" #: tracspamfilter/filters/ip_regex.py:86 #, python-format msgid "IP catched by %s blacklisted patterns" msgstr "" #: tracspamfilter/filters/ip_throttle.py:29 msgid "" "By how many points exceeding the configured maximum number of\n" "posts per hour impacts the overall score." msgstr "" #: tracspamfilter/filters/ip_throttle.py:34 msgid "" "The maximum allowed number of submissions per hour from a single\n" "IP address. If this limit is exceeded, subsequent submissions get\n" "negative karma." msgstr "" #: tracspamfilter/filters/ip_throttle.py:55 msgid "Maximum number of posts per hour for this IP exceeded" msgstr "" #: tracspamfilter/filters/mollom.py:37 msgid "" "By how many points an Mollom reject impacts the overall karma\n" "of a submission." msgstr "" #: tracspamfilter/filters/mollom.py:41 msgid "Public key required to use the Mollom API." msgstr "" #: tracspamfilter/filters/mollom.py:45 msgid "Private key required to use the Mollom API." msgstr "" #: tracspamfilter/filters/mollom.py:49 msgid "URL of the Mollom service." msgstr "" #: tracspamfilter/filters/mollom.py:98 msgid "Mollom says content is spam" msgstr "" #: tracspamfilter/filters/mollom.py:109 msgid "Mollom says content is ham" msgstr "" #: tracspamfilter/filters/regex.py:34 msgid "" "By how many points a match with a pattern on the BadContent page\n" "impacts the overall karma of a submission." msgstr "" #: tracspamfilter/filters/regex.py:39 msgid "" "Local file to be loaded to get BadContent. Can be used in\n" "addition to BadContent wiki page." msgstr "" #: tracspamfilter/filters/regex.py:44 msgid "Show the matched bad content patterns in rejection message." msgstr "" #: tracspamfilter/filters/regex.py:89 #, python-format msgid "Content contained these blacklisted patterns: %s" msgstr "" #: tracspamfilter/filters/regex.py:92 #, python-format msgid "Content contained %s blacklisted patterns" msgstr "" #: tracspamfilter/filters/regex.py:138 #, python-format msgid "Error in pattern %(pattern)s: %(error)s." msgstr "" #: tracspamfilter/filters/registration.py:30 msgid "" "By how many points a failed registration check impacts\n" "the overall score." msgstr "" #: tracspamfilter/filters/registration.py:34 msgid "Replace checks in account manager totally." msgstr "" #: tracspamfilter/filters/registration.py:95 #, python-format msgid "Account registration failed (%s)" msgstr "" #: tracspamfilter/filters/session.py:28 msgid "" "By how many points an existing and configured session improves the\n" "overall karma of the submission. A third of the points is granted for" "\n" "having an existing session at all, the other two thirds are granted\n" "when the user has his name and/or email address set in the session,\n" "respectively." msgstr "" #: tracspamfilter/filters/session.py:50 msgid "Existing session found" msgstr "" #: tracspamfilter/filters/stopforumspam.py:32 msgid "" "By how many points a StopForumSpam reject impacts the overall\n" "karma of a submission." msgstr "" #: tracspamfilter/filters/stopforumspam.py:36 msgid "API key used to report SPAM." msgstr "" #: tracspamfilter/filters/stopforumspam.py:65 #, python-format msgid "StopForumSpam says this is spam (%s)" msgstr "" #: tracspamfilter/filters/trapfield.py:29 msgid "" "By how many points a trap reject impacts the overall karma of\n" "a submission." msgstr "" #: tracspamfilter/filters/trapfield.py:33 msgid "" "Name of the invisible trap field, should contain some reference\n" "to e-mail for better results." msgstr "" #: tracspamfilter/filters/trapfield.py:37 msgid "" "Name of the hidden trap field, should contain some reference\n" "to e-mail for better results." msgstr "" #: tracspamfilter/filters/trapfield.py:41 msgid "" "Name of the register trap field, should contain some reference\n" "to web/homepage for better results." msgstr "" #: tracspamfilter/filters/trapfield.py:68 #, python-format msgid "Both trap fields says this is spam (%s, %s)" msgstr "" #: tracspamfilter/filters/trapfield.py:72 #, python-format msgid "Invisible trap field says this is spam (%s)" msgstr "" #: tracspamfilter/filters/trapfield.py:76 #, python-format msgid "Hidden trap field says this is spam (%s)" msgstr "" #: tracspamfilter/filters/trapfield.py:80 #, python-format msgid "Register trap field starts with HTTP URL (%s)" msgstr "" #: tracspamfilter/filters/url_blacklist.py:34 msgid "" "By how many points blacklisting by a single bad URL impacts the\n" "overall karma of a submission." msgstr "" #: tracspamfilter/filters/url_blacklist.py:40 msgid "Servers used for URL blacklisting." msgstr "" #: tracspamfilter/filters/url_blacklist.py:85 #, python-format msgid "URL's blacklisted by %s" msgstr "" #: tracspamfilter/templates/admin_bayes.html:14 msgid "Spam Filtering: Bayes" msgstr "" #: tracspamfilter/templates/admin_bayes.html:20 #, python-format msgid "" "The bayesian filter requires training before it can effectively\n" " differentiate between spam and ham. The training database " "currently\n" " contains [1:%(numspam)s spam] and\n" " [2:%(numham)s ham] %(ratio)s submissions." msgstr "" #: tracspamfilter/templates/admin_bayes.html:25 #, python-format msgid "" "The bayesian\n" " database contains currently %(lines)s lines with trained " "words (%(spamtext)s,\n" " %(hamtext)s, %(mixedtext)s)." msgstr "" #: tracspamfilter/templates/admin_bayes.html:30 #, python-format msgid "" "[1:]\n" " Reduce training database (%(linestext)s)" msgstr "" #: tracspamfilter/templates/admin_bayes.html:34 msgid "" "Reducing the training database can help when it got very large\n" " and tests take too long." msgstr "" #: tracspamfilter/templates/admin_bayes.html:40 msgid "Minimum database count:" msgstr "" #: tracspamfilter/templates/admin_bayes.html:44 msgid "" "Any database lines with less entries is removed when reducing\n" " the database." msgstr "" #: tracspamfilter/templates/admin_bayes.html:52 msgid "Clear training database" msgstr "" #: tracspamfilter/templates/admin_bayes.html:55 msgid "" "Resetting the training database can help when training was incorrect\n" " and is producing bad results." msgstr "" #: tracspamfilter/templates/admin_bayes.html:61 msgid "Minimum training required:" msgstr "" #: tracspamfilter/templates/admin_bayes.html:65 msgid "" "The minimum number of spam and ham in the training database before\n" " the filter starts affecting the karma of submissions." msgstr "" #: tracspamfilter/templates/admin_bayes.html:71 #: tracspamfilter/templates/admin_captcha.html:173 #: tracspamfilter/templates/admin_external.html:263 #: tracspamfilter/templates/admin_spamconfig.html:122 msgid "Apply changes" msgstr "" #: tracspamfilter/templates/admin_bayes.html:76 msgid "Training" msgstr "" #: tracspamfilter/templates/admin_bayes.html:77 msgid "" "While you can train the spam filter from the “[1:Spam\n" " Filtering → Monitoring]” panel in the web\n" " administration interface, you can also manually train the " "filter by\n" " entering samples here, or check what kind of spam " "probability\n" " currently gets assigned to the content." msgstr "" #: tracspamfilter/templates/admin_bayes.html:85 msgid "Content:" msgstr "" #: tracspamfilter/templates/admin_bayes.html:91 #, python-format msgid "Error: %(error)s" msgstr "" #: tracspamfilter/templates/admin_bayes.html:92 #, python-format msgid "Score: %(score)s%" msgstr "" #: tracspamfilter/templates/admin_bayes.html:95 #: tracspamfilter/templates/admin_statistics.html:36 msgid "Test" msgstr "" #: tracspamfilter/templates/admin_bayes.html:97 msgid "Train as Spam" msgstr "" #: tracspamfilter/templates/admin_bayes.html:98 msgid "Train as Ham" msgstr "" #: tracspamfilter/templates/admin_captcha.html:11 msgid "Captcha handling" msgstr "" #: tracspamfilter/templates/admin_captcha.html:15 msgid "Spam Filtering: Captcha handling" msgstr "" #: tracspamfilter/templates/admin_captcha.html:22 msgid "Enable captcha usage" msgstr "" #: tracspamfilter/templates/admin_captcha.html:29 msgid "Captcha type:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:39 msgid "Maximum captcha lifetime in seconds" msgstr "" #: tracspamfilter/templates/admin_captcha.html:47 msgid "reCAPTCHA" msgstr "" #: tracspamfilter/templates/admin_captcha.html:48 msgid "" "The reCAPTCHA system provides a very good captcha system based on\n" " scanned books. See\n" " [1:Google\n" " reCAPTCHA] page. You need to obtain\n" " API keys to use the service, which is freely available " "for personal use." msgstr "" #: tracspamfilter/templates/admin_captcha.html:58 #: tracspamfilter/templates/admin_external.html:201 msgid "Public key:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:65 #: tracspamfilter/templates/admin_captcha.html:97 msgid "Private key:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:75 #: tracspamfilter/templates/admin_captcha.html:106 msgid "Key validation failed:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:80 msgid "KeyCaptcha" msgstr "" #: tracspamfilter/templates/admin_captcha.html:81 msgid "" "The KeyCatcha system provides a captcha system based on JavaScript\n" " functions to reassemble a picture. See\n" " [1:KeyCaptcha]\n" " page. You need to obtain an API key to use the service, " "which is\n" " freely available for limited use." msgstr "" #: tracspamfilter/templates/admin_captcha.html:91 msgid "User ID:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:111 msgid "Text captcha" msgstr "" #: tracspamfilter/templates/admin_captcha.html:112 msgid "" "The text captcha constructs easy text questions. They can be\n" " broken relatively easy." msgstr "" #: tracspamfilter/templates/admin_captcha.html:119 msgid "Maximum value in a term:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:125 msgid "Number of terms:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:135 msgid "Image captcha" msgstr "" #: tracspamfilter/templates/admin_captcha.html:136 msgid "" "The image captcha constructs obstructed images using Python\n" " imaging library." msgstr "" #: tracspamfilter/templates/admin_captcha.html:143 msgid "Number of letters:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:149 msgid "Font size:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:155 msgid "Alphabet:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:161 msgid "Fonts:" msgstr "" #: tracspamfilter/templates/admin_captcha.html:174 #: tracspamfilter/templates/admin_external.html:264 msgid "Revert changes" msgstr "" #: tracspamfilter/templates/admin_external.html:10 msgid "External services" msgstr "" #: tracspamfilter/templates/admin_external.html:14 msgid "Spam Filtering: External services" msgstr "" #: tracspamfilter/templates/admin_external.html:17 msgid "An error checking supplied data occured, see below for details." msgstr "" #: tracspamfilter/templates/admin_external.html:24 msgid "Use external services" msgstr "" #: tracspamfilter/templates/admin_external.html:32 msgid "Train external services" msgstr "" #: tracspamfilter/templates/admin_external.html:36 msgid "" "Skip external services, when internal tests reach a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" #: tracspamfilter/templates/admin_external.html:46 msgid "" "Stop external services, when reached a karma of -\n" " Spam:\n" " [1:]\n" " Ham:\n" " [2:]" msgstr "" #: tracspamfilter/templates/admin_external.html:58 msgid "" "The Akismet filter uses the free\n" " [1:Akismet]\n" " service to decide if content submissions are potential " "spam. You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" #: tracspamfilter/templates/admin_external.html:65 #: tracspamfilter/templates/admin_external.html:135 #: tracspamfilter/templates/admin_external.html:152 #: tracspamfilter/templates/admin_external.html:168 #: tracspamfilter/templates/admin_external.html:185 msgid "API key:" msgstr "" #: tracspamfilter/templates/admin_external.html:71 #: tracspamfilter/templates/admin_external.html:91 #: tracspamfilter/templates/admin_external.html:213 msgid "URL:" msgstr "" #: tracspamfilter/templates/admin_external.html:77 #: tracspamfilter/templates/admin_external.html:219 #: tracspamfilter/templates/admin_external.html:224 #, python-format msgid "[1:Key validation failed:] %(error)s" msgstr "" #: tracspamfilter/templates/admin_external.html:85 msgid "" "The BlogSpam filter uses the free\n" " [1:BlogSpam]\n" " service to decide if content submissions are potential spam." msgstr "" #: tracspamfilter/templates/admin_external.html:97 msgid "Tests to skip (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:100 msgid "Possible Values:" msgstr "" #: tracspamfilter/templates/admin_external.html:105 msgid "method" msgstr "" #: tracspamfilter/templates/admin_external.html:106 msgid "description" msgstr "" #: tracspamfilter/templates/admin_external.html:107 msgid "author" msgstr "" #: tracspamfilter/templates/admin_external.html:127 msgid "" "The StopForumSpam filter uses the\n" " [1:StopForumSpam]\n" " service to decide if content submissions are potential " "spam. You need to obtain an\n" " API key to report SPAM to the service, which is freely " "available." msgstr "" #: tracspamfilter/templates/admin_external.html:144 msgid "" "The BotScout filter uses the\n" " [1:BotScout]\n" " service to decide if content submissions are potential " "spam. You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" #: tracspamfilter/templates/admin_external.html:161 msgid "" "The FSpamList filter uses the\n" " [1:FSpamList]\n" " service to decide if content submissions are potential " "spam. You need to obtain an\n" " API key to use the service, which is freely available." msgstr "" #: tracspamfilter/templates/admin_external.html:177 msgid "" "The HTTP_BL filter uses the free\n" " [1:HTTP:BL]\n" " service to decide if content submissions are potential " "spam. You need to obtain an\n" " API key to use the service, which is freely available for " "personal use." msgstr "" #: tracspamfilter/templates/admin_external.html:194 msgid "" "The Mollom filter uses the free\n" " [1:Mollom]\n" " service to decide if content submissions are potential " "spam. You need to obtain\n" " API keys to use the service, which are freely available for" " personal use." msgstr "" #: tracspamfilter/templates/admin_external.html:207 msgid "Secret key:" msgstr "" #: tracspamfilter/templates/admin_external.html:229 msgid "Free access blacklists" msgstr "" #: tracspamfilter/templates/admin_external.html:231 msgid "IPv4 Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:235 #: tracspamfilter/templates/admin_external.html:242 #: tracspamfilter/templates/admin_external.html:249 #, python-format msgid "(default: %(list)s)" msgstr "" #: tracspamfilter/templates/admin_external.html:238 msgid "IPv6 Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:245 msgid "URL Blacklists (comma separated):" msgstr "" #: tracspamfilter/templates/admin_external.html:251 msgid "" "A list of DNS blacklists can be found at the [1:RBLCheck]\n" " or [2:MultiRBL] services." msgstr "" #: tracspamfilter/templates/admin_external.html:256 msgid "" "You can enable or disable these filters from the “[1:General →\n" " Plugins]” panel of the web administration interface." msgstr "" #: tracspamfilter/templates/admin_report.html:10 msgid "Spam Reports" msgstr "" #: tracspamfilter/templates/admin_report.html:19 msgid "Spam Filtering: Reports" msgstr "" #: tracspamfilter/templates/admin_report.html:22 #: tracspamfilter/templates/admin_spammonitor.html:20 #, python-format msgid "Viewing entries %(start)s – %(end)s of %(total)s." msgstr "" #: tracspamfilter/templates/admin_report.html:50 #: tracspamfilter/templates/monitortable.html:13 msgid "Path" msgstr "" #: tracspamfilter/templates/admin_report.html:51 #: tracspamfilter/templates/monitortable.html:14 msgid "Author" msgstr "" #: tracspamfilter/templates/admin_report.html:52 #: tracspamfilter/templates/monitortable.html:17 msgid "Date/time" msgstr "" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:45 msgid "User was logged in" msgstr "" #: tracspamfilter/templates/admin_report.html:68 #: tracspamfilter/templates/monitortable.html:39 #: tracspamfilter/templates/monitortable.html:45 msgid "User was not logged in" msgstr "" #: tracspamfilter/templates/admin_report.html:80 #: tracspamfilter/templates/monitortable.html:66 msgid "No data available" msgstr "" #: tracspamfilter/templates/admin_report.html:91 #: tracspamfilter/templates/admin_spammonitor.html:60 msgid "Delete selected" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:10 msgid "Spam Report" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:20 #: tracspamfilter/templates/admin_reportentry.html:21 msgid "Previous Report Entry" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:25 #: tracspamfilter/templates/admin_spamentry.html:25 msgid "Back to List" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:30 #: tracspamfilter/templates/admin_reportentry.html:31 msgid "Next Report Entry" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:35 msgid "Spam Filtering: Report" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:39 msgid "Report Entry:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:40 #: tracspamfilter/templates/admin_spamentry.html:40 msgid "Information" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:42 #: tracspamfilter/templates/admin_spamentry.html:42 msgid "Time:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:45 #: tracspamfilter/templates/admin_spamentry.html:45 msgid "Path:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:48 #: tracspamfilter/templates/admin_spamentry.html:48 msgid "Author:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:51 #: tracspamfilter/templates/admin_spamentry.html:51 msgid "Authenticated:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:47 #: tracspamfilter/templates/usertable.html:49 msgid "yes" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:52 #: tracspamfilter/templates/admin_spamentry.html:52 #: tracspamfilter/templates/usertable.html:48 #: tracspamfilter/templates/usertable.html:50 msgid "no" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:54 msgid "Comment:" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:58 #: tracspamfilter/templates/admin_spamentry.html:75 msgid "HTTP headers" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:63 #: tracspamfilter/templates/admin_spamentry.html:84 msgid "Delete" msgstr "" #: tracspamfilter/templates/admin_reportentry.html:68 msgid "Possibly related log entries:" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:10 msgid "Spam Filter" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:14 msgid "Spam Filtering: Configuration" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:15 msgid "" "See [1:wiki page]\n" " for a short documentation." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:16 msgid "Help translating this plugin at [1:Transifex]." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:21 msgid "Karma Tuning" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:23 msgid "Minimum karma required for a successful submission:" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:29 msgid "" "Karma assigned to attachments (e.g. to allow relaxed rules for file " "uploads):" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:35 msgid "" "Karma assigned to registering users (e.g. negative value to increase " "captcha usage):" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:41 msgid "" "Content submissions are passed through a set of registered and " "enabled\n" " [1:filter strategies], each of which check the submitted " "content\n" " and may assign [2:karma points] to it. The sum of these " "karma\n" " points needs to be greater than or equal to the minimum " "karma\n" " configured here for the submission to be accepted." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:50 #: tracspamfilter/templates/admin_statistics.html:34 msgid "Strategy" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:51 msgid "Karma points" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:52 msgid "Description" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:66 msgid "Logging" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:70 msgid "Enable" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:74 msgid "" "The spam filter plugin can optionally log every content submission so" " that you can monitor and tune the effectiveness of the filtering. " "The log can be viewed on the [1:Monitoring] page." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:81 msgid "" "Purge old entries after\n" " [1:]\n" " days" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:89 #, python-format msgid "" "Number of entries in log message display\n" " (%(min)s-%(max)s)\n" " [1:]" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:99 msgid "Authenticated" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:103 msgid "Trust authenticated users" msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:107 msgid "" "If authenticated users should not be trusted automatically, this\n" " option must be disabled. Instead of full trust the supplied" " karma\n" " value is used in this case." msgstr "" #: tracspamfilter/templates/admin_spamconfig.html:113 msgid "Karma of authenticated users:" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:10 #: tracspamfilter/templates/admin_spammonitor.html:10 msgid "Spam Monitoring" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:20 #: tracspamfilter/templates/admin_spamentry.html:21 msgid "Previous Log Entry" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:30 #: tracspamfilter/templates/admin_spamentry.html:31 msgid "Next Log Entry" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:35 #: tracspamfilter/templates/admin_spammonitor.html:14 msgid "Spam Filtering: Monitoring" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:39 msgid "Log Entry:" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:54 msgid "IP address:" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "spam" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:57 msgid "ham" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:60 msgid "Karma:" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:61 #, python-format msgid "" "[1:%(karma)s]\n" " (marked as %(spam_or_ham)s)\n" " [2:\n" " [3:%(reasons)s]\n" " ]" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:71 msgid "Submitted content" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:80 msgid "Mark as Spam" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:82 msgid "Mark as Ham" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:85 msgid "Delete as Spam" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:87 msgid "Delete as Ham" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:89 msgid "Delete (No Statistics)" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:98 msgid "Remove registered user" msgstr "" #: tracspamfilter/templates/admin_spamentry.html:105 msgid "Search for user name" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Note:" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:18 msgid "Logging by the spam filter is currently disabled." msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:52 msgid "Delete > 90%" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:56 msgid "Mark selected as Spam" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:58 msgid "Mark selected as Ham" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:62 msgid "Delete selected as Spam" msgstr "" #: tracspamfilter/templates/admin_spammonitor.html:64 msgid "Delete selected as Ham" msgstr "" #: tracspamfilter/templates/admin_statistics.html:10 msgid "Spam Statistics" msgstr "" #: tracspamfilter/templates/admin_statistics.html:14 msgid "Spam Filtering: Statistics" msgstr "" #: tracspamfilter/templates/admin_statistics.html:17 msgid "No submission statistics yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:21 #, python-format msgid "" "%(count)s Submissions tested since %(time)s,\n" " %(spam)s spam and\n" " %(ham)s ham\n" " (%(local)s of the tests could be solved local).\n" " %(spamerr)s spam and %(hamerr)s ham have been retrained." msgstr "" #: tracspamfilter/templates/admin_statistics.html:35 msgid "Type" msgstr "" #: tracspamfilter/templates/admin_statistics.html:37 msgid "Train / Verify" msgstr "" #: tracspamfilter/templates/admin_statistics.html:38 msgid "Errors" msgstr "" #: tracspamfilter/templates/admin_statistics.html:43 #: tracspamfilter/templates/admin_statistics.html:45 msgid "Spam" msgstr "" #: tracspamfilter/templates/admin_statistics.html:44 #: tracspamfilter/templates/admin_statistics.html:46 msgid "Ham" msgstr "" #: tracspamfilter/templates/admin_statistics.html:49 #: tracspamfilter/templates/admin_statistics.html:56 #: tracspamfilter/templates/admin_statistics.html:59 msgid "Total" msgstr "" #: tracspamfilter/templates/admin_statistics.html:50 msgid "Mean Delay" msgstr "" #: tracspamfilter/templates/admin_statistics.html:51 msgid "No Result" msgstr "" #: tracspamfilter/templates/admin_statistics.html:52 #: tracspamfilter/templates/admin_statistics.html:54 msgid "Match" msgstr "" #: tracspamfilter/templates/admin_statistics.html:53 #: tracspamfilter/templates/admin_statistics.html:55 msgid "Mismatch" msgstr "" #: tracspamfilter/templates/admin_statistics.html:57 #: tracspamfilter/templates/admin_statistics.html:60 msgid "Right" msgstr "" #: tracspamfilter/templates/admin_statistics.html:58 #: tracspamfilter/templates/admin_statistics.html:61 msgid "Wrong" msgstr "" #: tracspamfilter/templates/admin_statistics.html:69 msgid "No tests yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:72 #, python-format msgid "%(seconds)s s" msgstr "" #: tracspamfilter/templates/admin_statistics.html:101 msgid "No spam yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:119 msgid "No ham yet." msgstr "" #: tracspamfilter/templates/admin_statistics.html:141 msgid "Clear statistics" msgstr "" #: tracspamfilter/templates/admin_statistics.html:151 msgid "Clear statistics database" msgstr "" #: tracspamfilter/templates/admin_user.html:10 msgid "Spam User Handling" msgstr "" #: tracspamfilter/templates/admin_user.html:14 #, python-format msgid "" "Spam Filtering: User handling (%(type)s)\n" " [1:%(count)s]" msgstr "" #: tracspamfilter/templates/admin_user.html:20 msgid "Overview" msgstr "" #: tracspamfilter/templates/admin_user.html:21 msgid "All" msgstr "" #: tracspamfilter/templates/admin_user.html:22 #: tracspamfilter/templates/usertable.html:15 msgid "Registered" msgstr "" #: tracspamfilter/templates/admin_user.html:23 msgid "Unused [multi selection]" msgstr "" #: tracspamfilter/templates/admin_user.html:24 msgid "Unused" msgstr "" #: tracspamfilter/templates/admin_user.html:28 #, python-format msgid "" "There are %(total)s\n" " different entries in the database, %(registered)s users are\n" " registered and %(unused)s have not been used." msgstr "" #: tracspamfilter/templates/admin_user.html:35 msgid "Date" msgstr "" #: tracspamfilter/templates/admin_user.html:36 #, python-format msgid "Action of user '%(username)s'" msgstr "" #: tracspamfilter/templates/admin_user.html:55 msgid "Remove selected" msgstr "" #: tracspamfilter/templates/admin_user.html:64 msgid "Values must be URL encoded!" msgstr "" #: tracspamfilter/templates/admin_user.html:66 msgid "Old user:" msgstr "" #: tracspamfilter/templates/admin_user.html:69 msgid "New user:" msgstr "" #: tracspamfilter/templates/admin_user.html:75 msgid "Change unauthorized user" msgstr "" #: tracspamfilter/templates/admin_user.html:75 msgid "Change user" msgstr "" #: tracspamfilter/templates/admin_user.html:83 #, python-format msgid "Remove %(num)s temporary session" msgid_plural "Remove %(num)s temporary sessions" msgstr[0] "" msgstr[1] "" #: tracspamfilter/templates/admin_user.html:88 msgid "Convert emails to registered usernames" msgstr "" #: tracspamfilter/templates/monitortable.html:15 msgid "IP Address" msgstr "" #: tracspamfilter/templates/monitortable.html:16 msgid "Karma" msgstr "" #: tracspamfilter/templates/usertable.html:13 msgid "User name" msgstr "" #: tracspamfilter/templates/usertable.html:14 msgid "Last login" msgstr "" #: tracspamfilter/templates/usertable.html:16 msgid "Setup" msgstr "" #: tracspamfilter/templates/usertable.html:17 msgid "E-Mail" msgstr "" #: tracspamfilter/templates/usertable.html:18 msgid "Wiki edits" msgstr "" #: tracspamfilter/templates/usertable.html:19 msgid "Ticket edits" msgstr "" #: tracspamfilter/templates/usertable.html:20 msgid "SVN edits" msgstr "" #: tracspamfilter/templates/usertable.html:21 msgid "Other" msgstr "" #: tracspamfilter/templates/usertable.html:39 msgid "Source" msgstr "" #: tracspamfilter/templates/usertable.html:49 #: tracspamfilter/templates/usertable.html:50 msgid "(password)" msgstr "" #: tracspamfilter/templates/usertable.html:52 msgid "(double)" msgstr "" #: tracspamfilter/templates/usertable.html:55 #: tracspamfilter/templates/usertable.html:61 #: tracspamfilter/templates/usertable.html:67 #: tracspamfilter/templates/usertable.html:73 msgid "user" msgstr "" #: tracspamfilter/templates/usertable.html:56 #: tracspamfilter/templates/usertable.html:62 #: tracspamfilter/templates/usertable.html:68 #: tracspamfilter/templates/usertable.html:74 msgid "e-mail" msgstr "" #: tracspamfilter/templates/usertable.html:57 #: tracspamfilter/templates/usertable.html:63 #: tracspamfilter/templates/usertable.html:69 #: tracspamfilter/templates/usertable.html:75 msgid "both" msgstr "" #: tracspamfilter/templates/usertable.html:83 msgid "Remove" msgstr "" #: tracspamfilter/templates/verify_captcha.html:13 msgid "Captcha Error" msgstr "" #: tracspamfilter/templates/verify_captcha.html:17 msgid "" "Trac thinks your submission might be Spam.\n" " To prove otherwise please provide a response to the " "following." msgstr "" #: tracspamfilter/templates/verify_captcha.html:19 msgid "" "Note - the captcha method is choosen randomly. Retry if the captcha " "does not work on your system!" msgstr "" #: tracspamfilter/templates/verify_captcha.html:22 msgid "Response:" msgstr "" spam-filter/tracspamfilter/filtersystem.py0000644000175500017550000005131112722402375021164 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2005-2012 Edgewall Software # Copyright (C) 2005-2006 Matthew Good # Copyright (C) 2006 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Matthew Good # Christopher Lenz from __future__ import with_statement import inspect import textwrap import time from StringIO import StringIO from difflib import SequenceMatcher from pkg_resources import resource_filename from trac.config import BoolOption, ConfigSection, ExtensionOption, IntOption from trac.core import Component, ExtensionPoint, implements from trac.env import IEnvironmentSetupParticipant, ISystemInfoProvider from trac.perm import IPermissionRequestor from trac.util import get_pkginfo from trac.util.html import tag from trac.util.text import shorten_line, to_unicode from trac.web.api import Request from tracspamfilter.api import ( IFilterStrategy, IRejectHandler, RejectContent, _, N_, add_domain, get_strategy_name, gettext, tag_) from tracspamfilter.filters.trapfield import TrapFieldFilterStrategy from tracspamfilter.model import LogEntry, Statistics, schema_version class FilterSystem(Component): """The central component for spam filtering. Must be enabled always to allow filtering of spam. """ strategies = ExtensionPoint(IFilterStrategy) implements(IEnvironmentSetupParticipant, IPermissionRequestor, IRejectHandler, ISystemInfoProvider) min_karma = IntOption('spam-filter', 'min_karma', '0', """The minimum score required for a submission to be allowed.""", doc_domain='tracspamfilter') authenticated_karma = IntOption('spam-filter', 'authenticated_karma', '20', """The karma given to authenticated users, in case `trust_authenticated` is false.""", doc_domain='tracspamfilter') logging_enabled = BoolOption('spam-filter', 'logging_enabled', 'true', """Whether all content submissions and spam filtering activity should be logged to the database.""", doc_domain='tracspamfilter') purge_age = IntOption('spam-filter', 'purge_age', '7', """The number of days after which log entries should be purged.""", doc_domain='tracspamfilter') use_external = BoolOption('spam-filter', 'use_external', 'true', """Allow usage of external services.""", doc_domain='tracspamfilter') skip_external = IntOption('spam-filter', 'skip_external', '20', """Skip external calls when this negative karma is already reached by internal tests.""", doc_domain='tracspamfilter') skip_externalham = IntOption('spam-filter', 'skip_externalham', '30', """Skip external calls when this positive karma is already reached by internal tests.""", doc_domain='tracspamfilter') stop_external = IntOption('spam-filter', 'stop_external', '50', """Stop external calls when this negative karma is reached.""", doc_domain='tracspamfilter') stop_externalham = IntOption('spam-filter', 'stop_externalham', '50', """Stop external calls when this positive karma is reached.""", doc_domain='tracspamfilter') train_external = BoolOption('spam-filter', 'train_external', 'true', """Allow training of external services.""", doc_domain='tracspamfilter') trust_authenticated = BoolOption('spam-filter', 'trust_authenticated', 'false', """Whether content submissions by authenticated users should be trusted without checking for potential spam or other abuse.""", doc_domain='tracspamfilter') attachment_karma = IntOption('spam-filter', 'attachment_karma', '0', """The karma given to attachments.""", doc_domain='tracspamfilter') register_karma = IntOption('spam-filter', 'register_karma', '0', """The karma given to registrations.""", doc_domain='tracspamfilter') reject_handler = ExtensionOption('spam-filter', 'reject_handler', IRejectHandler, 'FilterSystem', """The handler used to reject content.""", doc_domain='tracspamfilter') isforwarded = BoolOption('spam-filter', 'is_forwarded', 'false', """Interpret X-Forwarded-For header for IP checks.""", doc_domain='tracspamfilter') spam_section = ConfigSection('spam-filter', """This section is used to handle all configurations used by spam filter plugin.""", doc_domain='tracspamfilter') def __init__(self): """Set up translation domain""" try: locale_dir = resource_filename(__name__, 'locale') except KeyError: pass else: add_domain(self.env.path, locale_dir) # IRejectHandler methods def reject_content(self, req, message): raise RejectContent(message) # Public methods def test(self, req, author, changes): """Test a submission against the registered filter strategies. @param req: the request object @param author: the name of the logged in user, or 'anonymous' if the user is not logged in @param changes: a list of `(old_content, new_content)` tuples for every modified "field", where `old_content` may contain the previous version of that field (if available), and `new_content` contains the newly submitted content """ start = time.time() ip = req.remote_addr if self.isforwarded: x_forwarded = req.get_header('X-Forwarded-For') if x_forwarded and x_forwarded != '': ip = x_forwarded.split(',',1)[0] if author.find("@") < 1: trap = TrapFieldFilterStrategy(self.env).get_trap(req) if trap: if trap.find("@") > 0: author += " <%s>" % trap else: self.log.debug("Append trap field to changes") changes.append((None, trap)) score = 0 if self.trust_authenticated: # Authenticated users are trusted if req.authname and req.authname != 'anonymous': return reasons = [] outreasons = [] results = [] if req.authname and req.authname != 'anonymous': reasons.append(("AuthenticatedUserScore", str(self.authenticated_karma), N_("User is authenticated"))) score += self.authenticated_karma if self.attachment_karma != 0 and \ req.args.get('attachment') is not None: reasons.append(("AttachmentScore", str(self.attachment_karma), N_("Attachment weighting"))) score += self.attachment_karma if self.register_karma != 0 and (req.path_info == "/register" or req.args.get(TrapFieldFilterStrategy(self.env).name_register)): reasons.append(("RegisterScore", str(self.register_karma), N_("Registration weighting"))) score += self.register_karma if not author: author = 'anonymous' self.log.debug("Spam testing for %s", req.path_info) content = self._combine_changes(changes) abbrev = shorten_line(content) self.log.debug('Testing content %r submitted by "%s"', abbrev, author) externals = [] for strategy in self.strategies: tim = time.time() try: if not strategy.is_external(): retval = strategy.test(req, author, content, ip) tim = time.time()-tim if tim > 3: self.log.warn('Test %s took %d seconds to complete.', strategy, tim) if retval: points = retval[0] if len(retval) > 2: reason = retval[1] % retval[2:] else: reason = retval[1] if points < 0: if len(retval) > 2: outreasons.append(gettext(retval[1]) % retval[2:]) else: outreasons.append(gettext(retval[1])) self.log.debug('Filter strategy %r gave submission %d ' 'karma points (reason: %r)', strategy, points, reason) score += points if reason: name = get_strategy_name(strategy) reasons.append((name, str(points)) + retval[1:]) results.append((strategy, points, tim)) else: self._record_action('test','empty', '', strategy, tim) elif self.use_external: externals.append(strategy) except Exception, e: self._record_action('test', 'error', '', strategy, time.time()-tim) self.log.exception('Filter strategy %s raised exception: %s', strategy, e) extint = "testint" if score > -self.skip_external and score < self.skip_externalham: externals = Statistics(self.env).sortbyperformance(externals) for strategy in externals: tim = time.time() try: if score > -self.stop_external and score < self.stop_externalham: extint = "testext" retval = strategy.test(req, author, content, ip) tim = time.time()-tim if tim > 3: self.log.warn('Test %s took %d seconds to ' 'complete.', strategy, tim) if retval: points = retval[0] if len(retval) > 2: reason = retval[1] % retval[2:] else: reason = retval[1] if points < 0: if len(retval) > 2: outreasons.append(gettext(retval[1]) % retval[2:]) else: outreasons.append(gettext(retval[1])) self.log.debug('Filter strategy %r gave submission %d ' 'karma points (reason: %r)', strategy, points, reason) score += points if reason: name = get_strategy_name(strategy) reasons.append((name,str(points)) + retval[1:]) results.append((strategy, points, tim)) else: self._record_action('test','empty', '', strategy, tim) except Exception, e: self._record_action('test','error', '', strategy, time.time()-tim) self.log.exception('Filter strategy %s raised exception: %s', strategy, e) reasons = sorted(reasons, key=lambda r: r[0]) if score < self.min_karma: type = "spam" self._record_action(extint, 'spam', '', '', time.time()-start) else: type = "ham" self._record_action(extint, 'ham', '', '', time.time()-start) for strategy,points,tim in results: if (points < 0 and score < self.min_karma) or (points > 0 and score >= self.min_karma): status = "ok" else: status = "error" self._record_action("test", type, status, strategy, tim) if self.logging_enabled: headers = '\n'.join(['%s: %s' % (k[5:].replace('_', '-').title(), v) for k, v in req.environ.items() if k.startswith('HTTP_')]) LogEntry(self.env, time.time(), req.path_info, author, req.authname and req.authname != 'anonymous', ip, headers, content, score < self.min_karma, score, reasons, [req.path_info, req.args]).insert() LogEntry.purge(self.env, self.purge_age) if score < self.min_karma: self.log.debug('Rejecting submission %r by "%s" (%r) because it ' 'earned only %d karma points (%d are required).', abbrev, author, req.remote_addr, score, self.min_karma) rejects = [] outreasons.sort() for r in outreasons: rejects.append(tag.li(r)) msg = tag.ul(rejects) self.reject_handler.reject_content( req, tag.div( tag_('Submission rejected as potential spam %(message)s', message=msg), class_='message')) def train(self, req, ids, spam=True, delete=False): environ = {} for name, value in req.environ.items(): if not name.startswith('HTTP_'): environ[name] = value if not isinstance(ids, list): ids = [ids] for log_id in ids: start = time.time() entry = LogEntry.fetch(self.env, log_id) if entry: extint = "trainint" self.log.debug('Marking as %s: %r submitted by "%s"', spam and 'spam' or 'ham', shorten_line(entry.content), entry.author) fakeenv = environ.copy() for header in entry.headers.splitlines(): name, value = header.split(':', 1) if name == 'Cookie': # breaks SimpleCookie somehow continue cgi_name = 'HTTP_%s' % name.strip().replace('-', '_').upper() fakeenv[cgi_name] = value.strip() fakeenv['REQUEST_METHOD'] = 'POST' fakeenv['PATH_INFO'] = entry.path fakeenv['wsgi.input'] = StringIO('') fakeenv['REMOTE_ADDR'] = entry.ipnr if entry.authenticated: fakeenv['REMOTE_USER'] = entry.author type = "spam" if spam else "ham" for strategy in self.strategies: status = "trainskip" if (self.use_external and self.train_external) or not strategy.is_external(): tim = time.time() extint = "trainext" res = strategy.train(Request(fakeenv, None), entry.author or 'anonymous', entry.content, entry.ipnr, spam=spam) tim = time.time()-tim if tim > 3: self.log.warn('Training %s took %d seconds to ' 'complete.', strategy, tim) if res == -1: status = "trainerror" elif res == -2: status = "traincond" elif res > 0: status = "train" count = entry.findreasonvalue(get_strategy_name(strategy)) if count: spamstatus = count < 0 self._record_action(status, type, ("ok" if spamstatus == spam else "error"), strategy, tim) else: self._record_action(status, type, '', strategy, tim) self._record_action(extint, type, ("ok" if entry.rejected == spam else "error"), '', time.time()-start) entry.update(rejected=spam) if delete: self.delete(req, log_id, True) def delete(self, req, ids, stats): if not isinstance(ids, list): ids = [ids] if stats: for log_id in ids: entry = LogEntry.fetch(self.env, log_id) if entry: type = "spam" if entry.rejected else "ham" status = "delete" for strategy in self.strategies: count = entry.findreasonvalue(get_strategy_name(strategy)) if count: spamstatus = count < 0 self._record_action(status, type, ("ok" if spamstatus == entry.rejected else "error"), strategy, 0) else: self._record_action(status, type, '', strategy, 0) self._record_action(status, type, '', '', 0) LogEntry.delete(self.env, log_id) else: LogEntry.delete(self.env,ids) def deleteobvious(self, req): ids = LogEntry.getobvious(self.env) if ids: self.delete(req, ids, True) # IEnvironmentSetupParticipant def environment_created(self): with self.env.db_transaction as db: self.upgrade_environment(db) def environment_needs_upgrade(self, db): try: row = db("SELECT value FROM system " "WHERE name='spamfilter_version'") if not row or int(row[0][0]) < schema_version: return True except: return True def upgrade_environment(self, db): try: row = db("SELECT value FROM system " "WHERE name='spamfilter_version'") current_version = row and int(row[0][0]) or 0 except: current_version = 0 from tracspamfilter import upgrades for version in range(current_version + 1, schema_version + 1): for function in upgrades.version_map.get(version): self.log.info(textwrap.fill(inspect.getdoc(function))) function(self.env, db) self.log.info('Done.') if current_version == 0: db("INSERT INTO system VALUES ('spamfilter_version',%s)", (schema_version,)) self.log.info('Created SpamFilter tables') else: db("UPDATE system SET value=%s WHERE " "name='spamfilter_version'", (schema_version,)) self.log.info('Upgraded SpamFilter tables from version %d to %d', current_version, schema_version) # IPermissionRequestor def get_permission_actions(self): return ['SPAM_CONFIG', 'SPAM_MONITOR', 'SPAM_TRAIN', 'SPAM_USER', 'SPAM_REPORT', 'SPAM_CHECKREPORTS', ('SPAM_ADMIN', ['SPAM_CONFIG', 'SPAM_MONITOR', 'SPAM_TRAIN', 'SPAM_USER', 'SPAM_REPORT', 'SPAM_CHECKREPORTS'])] # ISystemInfoProvider methods def get_system_info(self): # Move implementation to httpbl, ip_blacklist and url_blacklist # when support for Trac < 1.2 is dropped (#12294). try: import dns except ImportError: pass else: yield 'dnspython', get_pkginfo(dns)['version'] # Internal methods def _combine_changes(self, changes, sep='\n\n'): fields = [] for old_content, new_content in changes: new_content = to_unicode(new_content) if old_content: old_content = to_unicode(old_content) new_content = self._get_added_lines(old_content, new_content) fields.append(new_content) return sep.join(fields) def _get_added_lines(self, old_content, new_content): buf = [] old_lines = old_content.splitlines() new_lines = new_content.splitlines() matcher = SequenceMatcher(None, old_lines, new_lines) for group in matcher.get_grouped_opcodes(0): for tag, i1, i2, j1, j2 in group: if tag in ('insert', 'replace'): buf.append('\n'.join(new_lines[j1:j2])) return '\n'.join(buf) def _record_action(self, action, data, status, strategy, delay): stats = Statistics(self.env) if strategy: name = get_strategy_name(strategy) stats.insert_or_update(name, action, data, status, delay, 1 if strategy.is_external() else 0) else: stats.insert_or_update('', action, data, status, delay, None) spam-filter/tracspamfilter/tests/0000755000175500017550000000000012724472512017223 5ustar debacledebaclespam-filter/tracspamfilter/tests/model.py0000644000175500017550000000435712724472512020706 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from __future__ import with_statement import time import unittest from datetime import datetime, timedelta from trac.test import EnvironmentStub from tracspamfilter.filtersystem import FilterSystem from tracspamfilter.model import LogEntry def drop_tables(env): with env.db_transaction as db: for table in ('spamfilter_bayes', 'spamfilter_log', 'spamfilter_report', 'spamfilter_statistics'): db("DROP TABLE IF EXISTS %s" % table) db("DELETE FROM system WHERE name='spamfilter_version'") class LogEntryTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() with self.env.db_transaction as db: FilterSystem(self.env).upgrade_environment(db) def tearDown(self): drop_tables(self.env) self.env.reset_db() def test_purge(self): now = datetime.now() oneweekago = time.mktime((now - timedelta(weeks=1)).timetuple()) onedayago = time.mktime((now - timedelta(days=1)).timetuple()) req = None LogEntry(self.env, oneweekago, '/foo', 'john', False, '127.0.0.1', '', 'Test', False, 5, [], req).insert() LogEntry(self.env, onedayago, '/foo', 'anonymous', False, '127.0.0.1', '', 'Test', True, -3, [], req).insert() LogEntry.purge(self.env, days=4) log = list(LogEntry.select(self.env)) self.assertEqual(1, len(log)) entry = log[0] self.assertEqual('anonymous', entry.author) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(LogEntryTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/tests/__init__.py0000644000175500017550000000157712724472512021346 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import unittest import tracspamfilter.filters.tests from tracspamfilter.tests import api, model def test_suite(): suite = unittest.TestSuite() suite.addTest(api.test_suite()) suite.addTest(model.test_suite()) suite.addTest(tracspamfilter.filters.tests.test_suite()) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/tests/api.py0000644000175500017550000002060012724472512020344 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from __future__ import with_statement import time import unittest from trac.core import Component, implements from trac.test import EnvironmentStub, Mock from tracspamfilter.api import IFilterStrategy, RejectContent from tracspamfilter.filtersystem import FilterSystem from tracspamfilter.model import LogEntry from tracspamfilter.tests.model import drop_tables class DummyStrategy(Component): implements(IFilterStrategy) def __init__(self): self.test_called = self.train_called = False self.req = self.author = self.content = None self.karma = 0 self.message = None self.spam = None def configure(self, karma, message="Dummy"): self.karma = karma self.message = message def test(self, req, author, content, ip): self.test_called = True self.req = req self.author = author self.content = content self.ip = ip return self.karma, self.message def train(self, req, author, content, ip, spam=True): self.train_called = True self.req = req self.author = author self.content = content self.ip = ip self.spam = spam def is_external(self): return False class FilterSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[FilterSystem, DummyStrategy]) with self.env.db_transaction as db: FilterSystem(self.env).upgrade_environment(db) def tearDown(self): drop_tables(self.env) self.env.reset_db() def test_trust_authenticated(self): self.env.config.set('spam-filter', 'trust_authenticated', True) req = Mock(environ={}, path_info='/foo', authname='john', args={}, remote_addr='127.0.0.1') FilterSystem(self.env).test(req, '', []) self.assertFalse(DummyStrategy(self.env).test_called) def test_dont_trust_authenticated(self): self.env.config.set('spam-filter', 'trust_authenticated', False) req = Mock(environ={}, path_info='/foo', authname='john', args={}, remote_addr='127.0.0.1') FilterSystem(self.env).test(req, '', []) self.assertTrue(DummyStrategy(self.env).test_called) def test_without_oldcontent(self): req = Mock(environ={}, path_info='/foo', authname='anonymous', args={}, remote_addr='127.0.0.1') FilterSystem(self.env).test(req, 'John Doe', [(None, 'Test')]) self.assertEqual('Test', DummyStrategy(self.env).content) def test_with_oldcontent(self): req = Mock(environ={}, path_info='/foo', authname='anonymous', args={}, remote_addr='127.0.0.1') FilterSystem(self.env).test(req, 'John Doe', [('Test', 'Test 1 2 3')]) self.assertEqual('Test 1 2 3', DummyStrategy(self.env).content) def test_with_oldcontent_multiline(self): req = Mock(environ={}, path_info='/foo', authname='anonymous', args={}, remote_addr='127.0.0.1') FilterSystem(self.env).test(req, 'John Doe', [('Text\n1 2 3\n7 8 9', 'Test\n1 2 3\n4 5 6')]) self.assertEqual('Test\n4 5 6', DummyStrategy(self.env).content) def test_bad_karma(self): req = Mock(environ={}, path_info='/foo', authname='anonymous', args={}, remote_addr='127.0.0.1') DummyStrategy(self.env).configure(-5, 'Blacklisted') try: FilterSystem(self.env).test(req, 'John Doe', [(None, 'Test')]) self.fail('Expected RejectContent exception') except RejectContent, e: self.assertEqual('
' 'Submission rejected as potential spam ' '
  • Blacklisted
', str(e)) def test_good_karma(self): req = Mock(environ={}, path_info='/foo', authname='anonymous', args={}, remote_addr='127.0.0.1') DummyStrategy(self.env).configure(5) FilterSystem(self.env).test(req, 'John Doe', [(None, 'Test')]) def test_log_reject(self): req = Mock(environ={}, path_info='/foo', authname='anonymous', args={}, remote_addr='127.0.0.1') DummyStrategy(self.env).configure(-5, 'Blacklisted') try: FilterSystem(self.env).test(req, 'John Doe', [(None, 'Test')]) self.fail('Expected RejectContent exception') except RejectContent, e: pass log = list(LogEntry.select(self.env)) self.assertEqual(1, len(log)) entry = log[0] self.assertEqual('/foo', entry.path) self.assertEqual('John Doe', entry.author) self.assertEqual(False, entry.authenticated) self.assertEqual('127.0.0.1', entry.ipnr) self.assertEqual('Test', entry.content) self.assertEqual(True, entry.rejected) self.assertEqual(-5, entry.karma) self.assertEqual([['DummyStrategy', '-5', 'Blacklisted']], entry.reasons) def test_log_accept(self): req = Mock(environ={}, path_info='/foo', authname='anonymous', args={}, remote_addr='127.0.0.1') DummyStrategy(self.env).configure(5) FilterSystem(self.env).test(req, 'John Doe', [(None, 'Test')]) log = list(LogEntry.select(self.env)) self.assertEqual(1, len(log)) entry = log[0] self.assertEqual('/foo', entry.path) self.assertEqual('John Doe', entry.author) self.assertEqual(False, entry.authenticated) self.assertEqual('127.0.0.1', entry.ipnr) self.assertEqual('Test', entry.content) self.assertEqual(False, entry.rejected) self.assertEqual(5, entry.karma) self.assertEqual([['DummyStrategy', '5', 'Dummy']], entry.reasons) def test_train_spam(self): req = Mock(environ={'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', 'wsgi.url_scheme': 'http'}, path_info='/foo', authname='anonymous', args={}, remote_addr='127.0.0.1') entry = LogEntry(self.env, time.time(), '/foo', 'john', False, '127.0.0.1', '', 'Test', False, 5, [], req) entry.insert() FilterSystem(self.env).train(req, entry.id, spam=True) strategy = DummyStrategy(self.env) self.assertEqual(True, strategy.train_called) self.assertEqual('john', strategy.author) self.assertEqual('Test', strategy.content) self.assertEqual(True, strategy.spam) log = list(LogEntry.select(self.env)) self.assertEqual(1, len(log)) entry = log[0] self.assertEqual(True, entry.rejected) def test_train_ham(self): req = Mock(environ={'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', 'wsgi.url_scheme': 'http'}, path_info='/foo', authname='anonymous', remote_addr='127.0.0.1') entry = LogEntry(self.env, time.time(), '/foo', 'john', False, '127.0.0.1', '', 'Test', True, -5, [], req) entry.insert() FilterSystem(self.env).train(req, entry.id, spam=False) strategy = DummyStrategy(self.env) self.assertEqual(True, strategy.train_called) self.assertEqual('john', strategy.author) self.assertEqual('Test', strategy.content) self.assertEqual(False, strategy.spam) log = list(LogEntry.select(self.env)) self.assertEqual(1, len(log)) entry = log[0] self.assertEqual(False, entry.rejected) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(FilterSystemTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') spam-filter/tracspamfilter/tests/compat.py0000644000175500017550000000713212724453333021063 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2016 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import StringIO from trac.perm import PermissionCache from trac.test import Mock, MockPerm, locale_en from trac.util.datefmt import utc from trac.web.api import _RequestArgs, Request from trac.web.session import DetachedSession try: from trac.test import MockRequest except ImportError: def MockRequest(env, **kwargs): """Request object for testing. Keyword arguments populate an `environ` dictionary and the callbacks. If `authname` is specified in a keyword arguments a `PermissionCache` object is created, otherwise if `authname` is not specified or is `None` a `MockPerm` object is used and the `authname` is set to 'anonymous'. The following keyword arguments are commonly used: :keyword args: dictionary of request arguments :keyword authname: the name of the authenticated user, or 'anonymous' :keyword method: the HTTP request method :keyword path_info: the request path inside the application Additionally `format`, `locale`, `lc_time` and `tz` can be specified as keyword arguments. :since: 1.0.11 """ authname = kwargs.get('authname') if authname is None: authname = 'anonymous' perm = MockPerm() else: perm = PermissionCache(env, authname) args = _RequestArgs() args.update(kwargs.get('args', {})) environ = { 'trac.base_url': env.abs_href(), 'wsgi.url_scheme': 'http', 'HTTP_ACCEPT_LANGUAGE': 'en-US', 'PATH_INFO': kwargs.get('path_info', '/'), 'REQUEST_METHOD': kwargs.get('method', 'GET'), 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_USER': authname, 'SCRIPT_NAME': '/trac.cgi', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', } status_sent = [] headers_sent = {} response_sent = StringIO.StringIO() def start_response(status, headers, exc_info=None): status_sent.append(status) headers_sent.update(dict(headers)) return response_sent.write req = Mock(Request, environ, start_response) req.status_sent = status_sent req.headers_sent = headers_sent req.response_sent = response_sent from trac.web.chrome import Chrome req.callbacks.update({ 'arg_list': None, 'args': lambda req: args, 'authname': lambda req: authname, 'chrome': Chrome(env).prepare_request, 'form_token': lambda req: kwargs.get('form_token'), 'languages': Request._parse_languages, 'lc_time': lambda req: kwargs.get('lc_time', locale_en), 'locale': lambda req: kwargs.get('locale'), 'incookie': Request._parse_cookies, 'perm': lambda req: perm, 'session': lambda req: DetachedSession(env, authname), 'tz': lambda req: kwargs.get('tz', utc), 'use_xsendfile': False, 'xsendfile_header': None, '_inheaders': Request._parse_headers }) return reqspam-filter/tracspamfilter/admin.py0000644000175500017550000006406112725137772017541 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Edgewall Software # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Dirk Stöcker import urllib2 from trac.admin import IAdminPanelProvider from trac.config import BoolOption, IntOption from trac.core import Component, ExtensionPoint, TracError, implements from trac.util import as_int from trac.web.api import HTTPNotFound from trac.web.chrome import ( ITemplateProvider, add_link, add_script, add_script_data, add_stylesheet, add_warning) from tracspamfilter.api import _, gettext, ngettext from tracspamfilter.filters.akismet import AkismetFilterStrategy from tracspamfilter.filters.botscout import BotScoutFilterStrategy from tracspamfilter.filters.fspamlist import FSpamListFilterStrategy from tracspamfilter.filters.stopforumspam import StopForumSpamFilterStrategy from tracspamfilter.filtersystem import FilterSystem from tracspamfilter.model import LogEntry, Statistics try: from tracspamfilter.filters.blogspam import BlogSpamFilterStrategy except ImportError: # simplejson not installed BlogSpamFilterStrategy = None try: from tracspamfilter.filters.bayes import BayesianFilterStrategy except ImportError: # SpamBayes not installed BayesianFilterStrategy = None try: from tracspamfilter.filters.httpbl import HttpBLFilterStrategy from tracspamfilter.filters.ip_blacklist import IPBlacklistFilterStrategy from tracspamfilter.filters.url_blacklist import URLBlacklistFilterStrategy except ImportError: # DNS python not installed HttpBLFilterStrategy = None IPBlacklistFilterStrategy = None URLBlacklistFilterStrategy = None try: from tracspamfilter.filters.mollom import MollomFilterStrategy except ImportError: # Mollom not installed MollomFilterStrategy = None class SpamFilterAdminPageProvider(Component): """Web administration panel for configuring and monitoring the spam filtering system. """ implements(ITemplateProvider) implements(IAdminPanelProvider) MAX_PER_PAGE = 10000 MIN_PER_PAGE = 5 DEF_PER_PAGE = IntOption('spam-filter', 'spam_monitor_entries', '100', "How many monitor entries are displayed by default " "(between 5 and 10000).", doc_domain='tracspamfilter') train_only = BoolOption('spam-filter', 'show_train_only', False, "Show the buttons for training without deleting entry.", doc_domain='tracspamfilter') # IAdminPanelProvider methods def get_admin_panels(self, req): if 'SPAM_CONFIG' in req.perm: yield ('spamfilter', _("Spam Filtering"), 'config', _("Configuration")) if 'SPAM_MONITOR' in req.perm: yield ('spamfilter', _("Spam Filtering"), 'monitor', _("Monitoring")) def render_admin_panel(self, req, cat, page, path_info): if page == 'config': if req.method == 'POST': if self._process_config_panel(req): req.redirect(req.href.admin(cat, page)) data = self._render_config_panel(req, cat, page) else: if req.method == 'POST': if self._process_monitoring_panel(req): req.redirect(req.href.admin(cat, page, page=req.args.get('page'), num=req.args.get('num'))) if path_info: data = self._render_monitoring_entry(req, cat, page, path_info) page = 'entry' else: data = self._render_monitoring_panel(req, cat, page) data['allowselect'] = True data['monitor'] = True add_script_data(req, { 'bayestext': _("SpamBayes determined spam probability " "of %s%%"), 'sel100text': _("Select 100.00%% entries") % (), 'sel90text': _("Select >90.00%% entries") % (), 'sel10text': _("Select <10.00%% entries") % (), 'sel0text': _("Select 0.00%% entries") % (), 'selspamtext': _("Select Spam entries"), 'selhamtext': _('Select Ham entries') }) add_script(req, 'spamfilter/adminmonitor.js') add_script_data(req, {'toggleform': 'spammonitorform'}) add_script(req, 'spamfilter/toggle.js') add_stylesheet(req, 'spamfilter/admin.css') data['accmgr'] = 'ACCTMGR_USER_ADMIN' in req.perm return 'admin_spam%s.html' % page, data # ITemplateProvider methods def get_htdocs_dirs(self): from pkg_resources import resource_filename return [('spamfilter', resource_filename(__name__, 'htdocs'))] def get_templates_dirs(self): from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] # Internal methods def _render_config_panel(self, req, cat, page): req.perm.require('SPAM_CONFIG') filter_system = FilterSystem(self.env) strategies = [] for strategy in filter_system.strategies: for variable in dir(strategy): if variable.endswith('karma_points'): strategies.append({ 'name': strategy.__class__.__name__, 'karma_points': getattr(strategy, variable), 'variable': variable, 'karma_help': gettext(getattr(strategy.__class__, variable).__doc__) }) add_script(req, 'spamfilter/adminconfig.js') return { 'strategies': sorted(strategies, key=lambda x: x['name']), 'min_karma': filter_system.min_karma, 'authenticated_karma': filter_system.authenticated_karma, 'attachment_karma': filter_system.attachment_karma, 'register_karma': filter_system.register_karma, 'trust_authenticated': filter_system.trust_authenticated, 'logging_enabled': filter_system.logging_enabled, 'purge_age': filter_system.purge_age, 'spam_monitor_entries_min': self.MIN_PER_PAGE, 'spam_monitor_entries_max': self.MAX_PER_PAGE, 'spam_monitor_entries': self.DEF_PER_PAGE } def _process_config_panel(self, req): req.perm.require('SPAM_CONFIG') spam_config = self.config['spam-filter'] min_karma = as_int(req.args.get('min_karma'), None) if min_karma is not None: spam_config.set('min_karma', min_karma) attachment_karma = as_int(req.args.get('attachment_karma'), None) if attachment_karma is not None: spam_config.set('attachment_karma', attachment_karma) register_karma = as_int(req.args.get('register_karma'), None) if register_karma is not None: spam_config.set('register_karma', register_karma) authenticated_karma = as_int(req.args.get('authenticated_karma'), None) if authenticated_karma is not None: spam_config.set('authenticated_karma', authenticated_karma) for strategy in FilterSystem(self.env).strategies: for variable in dir(strategy): if variable.endswith('karma_points'): points = req.args.get(strategy.__class__.__name__ + '_' + variable) if points is not None: option = getattr(strategy.__class__, variable) self.config.set(option.section, option.name, points) logging_enabled = 'logging_enabled' in req.args spam_config.set('logging_enabled', logging_enabled) trust_authenticated = 'trust_authenticated' in req.args spam_config.set('trust_authenticated', trust_authenticated) if logging_enabled: purge_age = as_int(req.args.get('purge_age'), None) if purge_age is not None: spam_config.set('purge_age', purge_age) spam_monitor_entries = as_int(req.args.get('spam_monitor_entries'), None, min=self.MIN_PER_PAGE, max=self.MAX_PER_PAGE) if spam_monitor_entries is not None: spam_config.set('spam_monitor_entries', spam_monitor_entries) self.config.save() return True def _render_monitoring_panel(self, req, cat, page): req.perm.require('SPAM_MONITOR') pagenum = as_int(req.args.get('page', 1), 1) - 1 pagesize = as_int(req.args.get('num', self.DEF_PER_PAGE), self.DEF_PER_PAGE, min=self.MIN_PER_PAGE, max=self.MAX_PER_PAGE) total = LogEntry.count(self.env) if total < pagesize: pagenum = 0 elif total <= pagenum * pagesize: pagenum = (total - 1) / pagesize offset = pagenum * pagesize entries = list(LogEntry.select(self.env, limit=pagesize, offset=offset)) if pagenum > 0: add_link(req, 'prev', req.href.admin(cat, page, page=pagenum, num=pagesize), _("Previous Page")) if offset + pagesize < total: add_link(req, 'next', req.href.admin(cat, page, page=pagenum + 2, num=pagesize), _("Next Page")) return { 'enabled': FilterSystem(self.env).logging_enabled, 'entries': entries, 'offset': offset + 1, 'page': pagenum + 1, 'num': pagesize, 'total': total, 'train_only': self.train_only } def _render_monitoring_entry(self, req, cat, page, entry_id): req.perm.require('SPAM_MONITOR') entry = LogEntry.fetch(self.env, entry_id) if not entry: raise HTTPNotFound(_("Log entry not found")) previous = entry.get_previous() if previous: add_link(req, 'prev', req.href.admin(cat, page, previous.id), _("Log Entry %(id)s", id=previous.id)) add_link(req, 'up', req.href.admin(cat, page), _("Log Entry List")) next = entry.get_next() if next: add_link(req, 'next', req.href.admin(cat, page, next.id), _("Log Entry %(id)s", id=next.id)) return {'entry': entry, 'train_only': self.train_only} def _process_monitoring_panel(self, req): req.perm.require('SPAM_TRAIN') filtersys = FilterSystem(self.env) spam = 'markspam' in req.args or 'markspamdel' in req.args train = spam or 'markham' in req.args or 'markhamdel' in req.args delete = 'delete' in req.args or 'markspamdel' in req.args or \ 'markhamdel' in req.args or 'deletenostats' in req.args deletestats = 'delete' in req.args if train or delete: entries = req.args.getlist('sel') if entries: if train: filtersys.train(req, entries, spam=spam, delete=delete) elif delete: filtersys.delete(req, entries, deletestats) if 'deleteobvious' in req.args: filtersys.deleteobvious(req) return True class ExternalAdminPageProvider(Component): """Web administration panel for configuring the External spam filters.""" implements(IAdminPanelProvider) # IAdminPanelProvider methods def get_admin_panels(self, req): if 'SPAM_CONFIG' in req.perm: yield ('spamfilter', _("Spam Filtering"), 'external', _("External Services")) def render_admin_panel(self, req, cat, page, path_info): req.perm.require('SPAM_CONFIG') data = {} spam_config = self.config['spam-filter'] akismet = AkismetFilterStrategy(self.env) stopforumspam = StopForumSpamFilterStrategy(self.env) botscout = BotScoutFilterStrategy(self.env) fspamlist = FSpamListFilterStrategy(self.env) ip_blacklist_default = ip6_blacklist_default = \ url_blacklist_default = None if HttpBLFilterStrategy: ip_blacklist = IPBlacklistFilterStrategy(self.env) ip_blacklist_default = ip_blacklist.servers_default ip6_blacklist_default = ip_blacklist.servers6_default url_blacklist = URLBlacklistFilterStrategy(self.env) url_blacklist_default = url_blacklist.servers_default mollom = 0 if MollomFilterStrategy: mollom = MollomFilterStrategy(self.env) if BlogSpamFilterStrategy: blogspam = BlogSpamFilterStrategy(self.env) if req.method == 'POST': if 'cancel' in req.args: req.redirect(req.href.admin(cat, page)) akismet_api_url = req.args.get('akismet_api_url') akismet_api_key = req.args.get('akismet_api_key') mollom_api_url = req.args.get('mollom_api_url') mollom_public_key = req.args.get('mollom_public_key') mollom_private_key = req.args.get('mollom_private_key') stopforumspam_api_key = req.args.get('stopforumspam_api_key') botscout_api_key = req.args.get('botscout_api_key') fspamlist_api_key = req.args.get('fspamlist_api_key') httpbl_api_key = req.args.get('httpbl_api_key') ip_blacklist_servers = req.args.get('ip_blacklist_servers') ip6_blacklist_servers = req.args.get('ip6_blacklist_servers') url_blacklist_servers = req.args.get('url_blacklist_servers') blogspam_api_url = req.args.get('blogspam_api_url') blogspam_skip_tests = req.args.get('blogspam_skip_tests') use_external = 'use_external' in req.args train_external = 'train_external' in req.args skip_external = req.args.get('skip_external') stop_external = req.args.get('stop_external') skip_externalham = req.args.get('skip_externalham') stop_externalham = req.args.get('stop_externalham') try: verified_key = akismet.verify_key(req, akismet_api_url, akismet_api_key) if akismet_api_key and not verified_key: data['akismeterror'] = 'The API key is invalid' data['error'] = 1 except urllib2.URLError, e: data['alismeterror'] = e.reason[1] data['error'] = 1 if mollom: try: verified_key = mollom.verify_key(req, mollom_api_url, mollom_public_key, mollom_private_key) except urllib2.URLError, e: data['mollomerror'] = e.reason[1] data['error'] = 1 else: if mollom_public_key and mollom_private_key and \ not verified_key: data['mollomerror'] = 'The API keys are invalid' data['error'] = 1 if not data.get('error', 0): spam_config.set('akismet_api_url', akismet_api_url) spam_config.set('akismet_api_key', akismet_api_key) spam_config.set('mollom_api_url', mollom_api_url) spam_config.set('mollom_public_key', mollom_public_key) spam_config.set('mollom_private_key', mollom_private_key) spam_config.set('stopforumspam_api_key', stopforumspam_api_key) spam_config.set('botscout_api_key', botscout_api_key) spam_config.set('fspamlist_api_key', fspamlist_api_key) spam_config.set('httpbl_api_key', httpbl_api_key) if HttpBLFilterStrategy: if ip_blacklist_servers != ip_blacklist_default: spam_config.set('ip_blacklist_servers', ip_blacklist_servers) else: spam_config.remove('ip_blacklist_servers') if ip6_blacklist_servers != ip6_blacklist_default: spam_config.set('ip6_blacklist_servers', ip6_blacklist_servers) else: spam_config.remove('ip6_blacklist_servers') if url_blacklist_servers != url_blacklist_default: spam_config.set('url_blacklist_servers', url_blacklist_servers) else: spam_config.remove('url_blacklist_servers') if BlogSpamFilterStrategy: spam_config.set('blogspam_json_api_url', blogspam_api_url) spam_config.set('blogspam_json_skip_tests', blogspam_skip_tests) spam_config.set('use_external', use_external) spam_config.set('train_external', train_external) spam_config.set('skip_external', skip_external) spam_config.set('stop_external', stop_external) spam_config.set('skip_externalham', skip_externalham) spam_config.set('stop_externalham', stop_externalham) self.config.save() req.redirect(req.href.admin(cat, page)) else: filter_system = FilterSystem(self.env) use_external = filter_system.use_external train_external = filter_system.train_external skip_external = filter_system.skip_external stop_external = filter_system.stop_external skip_externalham = filter_system.skip_externalham stop_externalham = filter_system.stop_externalham if BlogSpamFilterStrategy: blogspam_api_url = blogspam.api_url blogspam_skip_tests = ','.join(blogspam.skip_tests) akismet_api_url = akismet.api_url akismet_api_key = akismet.api_key mollom_public_key = mollom_private_key = mollom_api_url = None if MollomFilterStrategy: mollom_api_url = mollom.api_url mollom_public_key = mollom.public_key mollom_private_key = mollom.private_key stopforumspam_api_key = stopforumspam.api_key botscout_api_key = botscout.api_key fspamlist_api_key = fspamlist.api_key httpbl_api_key = spam_config.get('httpbl_api_key') ip_blacklist_servers = spam_config.get('ip_blacklist_servers') ip6_blacklist_servers = spam_config.get('ip6_blacklist_servers') url_blacklist_servers = spam_config.get('url_blacklist_servers') if HttpBLFilterStrategy: data['blacklists'] = 1 data['ip_blacklist_default'] = ip_blacklist_default data['ip6_blacklist_default'] = ip6_blacklist_default data['url_blacklist_default'] = url_blacklist_default if MollomFilterStrategy: data['mollom'] = 1 data['mollom_public_key'] = mollom_public_key data['mollom_private_key'] = mollom_private_key data['mollom_api_url'] = mollom_api_url if BlogSpamFilterStrategy: data['blogspam'] = 1 data['blogspam_api_url'] = blogspam_api_url data['blogspam_skip_tests'] = blogspam_skip_tests data['blogspam_methods'] = blogspam.getmethods() data.update({ 'akismet_api_key': akismet_api_key, 'akismet_api_url': akismet_api_url, 'httpbl_api_key': httpbl_api_key, 'stopforumspam_api_key': stopforumspam_api_key, 'botscout_api_key': botscout_api_key, 'fspamlist_api_key': fspamlist_api_key, 'use_external': use_external, 'train_external': train_external, 'skip_external': skip_external, 'stop_external': stop_external, 'skip_externalham': skip_externalham, 'stop_externalham': stop_externalham, 'ip_blacklist_servers': ip_blacklist_servers, 'ip6_blacklist_servers': ip6_blacklist_servers, 'url_blacklist_servers': url_blacklist_servers }) add_script(req, 'spamfilter/adminexternal.js') add_stylesheet(req, 'spamfilter/admin.css') return 'admin_external.html', data class BayesAdminPageProvider(Component): """Web administration panel for configuring the Bayes spam filter.""" if BayesianFilterStrategy: implements(IAdminPanelProvider) # IAdminPanelProvider methods def get_admin_panels(self, req): if 'SPAM_CONFIG' in req.perm: yield 'spamfilter', _("Spam Filtering"), 'bayes', _("Bayes") def render_admin_panel(self, req, cat, page, path_info): req.perm.require('SPAM_CONFIG') bayes = BayesianFilterStrategy(self.env) hammie = bayes._get_hammie() data = {} if req.method == 'POST': if 'train' in req.args: bayes.train(None, None, req.args['bayes_content'], '127.0.0.1', spam='spam' in req.args['train'].lower()) req.redirect(req.href.admin(cat, page)) elif 'test' in req.args: bayes_content = req.args['bayes_content'] data['content'] = bayes_content try: data['score'] = hammie.score(bayes_content.encode('utf-8')) except Exception, e: self.log.warn('Bayes test failed: %s', e, exc_info=True) data['error'] = unicode(e) else: if 'reset' in req.args: self.log.info('Resetting SpamBayes training database') self.env.db_transaction("DELETE FROM spamfilter_bayes") elif 'reduce' in req.args: self.log.info('Reducing SpamBayes training database') bayes.reduce() min_training = as_int(req.args['min_training'], None) if min_training is not None and \ min_training != bayes.min_training: self.config.set('spam-filter', 'bayes_min_training', min_training) self.config.save() min_dbcount = as_int(req.args['min_dbcount'], None) if min_dbcount is not None and \ min_dbcount != bayes.min_dbcount: self.config.set('spam-filter', 'bayes_min_dbcount', min_dbcount) self.config.save() req.redirect(req.href.admin(cat, page)) ratio = '' nspam = hammie.bayes.nspam nham = hammie.bayes.nham if nham and nspam: if nspam > nham: ratio = _("(ratio %.1f : 1)") % (float(nspam) / float(nham)) else: ratio = _("(ratio 1 : %.1f)") % (float(nham) / float(nspam)) dblines, dblines_spamonly, dblines_hamonly, dblines_reduce = \ bayes.dblines() dblines_mixed = dblines - dblines_hamonly - dblines_spamonly data.update({ 'min_training': bayes.min_training, 'min_dbcount': bayes.min_dbcount, 'dblines': dblines, 'dblinesreducenum': dblines_reduce, 'dblinesspamonly': ngettext("%(num)d spam", "%(num)d spam", dblines_spamonly), 'dblineshamonly': ngettext("%(num)d ham", "%(num)d ham", dblines_hamonly), 'dblinesreduce': ngettext("%(num)d line", "%(num)d lines", dblines_reduce), 'dblinesmixed': ngettext("%(num)d mixed", "%(num)d mixed", dblines_mixed), 'nspam': nspam, 'nham': nham, 'ratio': ratio }) add_script_data(req, {'hasdata': True if nham + nspam > 0 else False}) add_script(req, 'spamfilter/adminbayes.js') add_stylesheet(req, 'spamfilter/admin.css') return 'admin_bayes.html', data class StatisticsAdminPageProvider(Component): """Web administration panel for spam filter statistics.""" implements(IAdminPanelProvider) # IAdminPanelProvider methods def get_admin_panels(self, req): if 'SPAM_CONFIG' in req.perm: yield ('spamfilter', _("Spam Filtering"), 'statistics', _("Statistics")) def render_admin_panel(self, req, cat, page, path_info): req.perm.require('SPAM_CONFIG') stats = Statistics(self.env) if req.method == 'POST': if 'clean' in req.args: stats.clean(req.args['strategy']) elif 'cleanall' in req.args: stats.cleanall() req.redirect(req.href.admin(cat, page)) strategies, overall = stats.getstats() data = {'strategies': strategies, 'overall': overall} add_stylesheet(req, 'spamfilter/admin.css') return 'admin_statistics.html', data spam-filter/tracspamfilter/htdocs/0000755000175500017550000000000012671220455017343 5ustar debacledebaclespam-filter/tracspamfilter/htdocs/yes.gif0000644000175500017550000000045310520411402020615 0ustar debacledebacleGIF89a B?DAmkަإki٧ȮܭtrکQOHFє]ZԜǂȀ~|٦ܮa^!, H@P8 4hz (8ЀBm.t2(Y$B\ UOJDA;spam-filter/tracspamfilter/htdocs/adminmonitor.js0000644000175500017550000000637612642373253022420 0ustar debacledebaclefunction ToggleSelectPerfect(all, no, name) { var bayes = bayestext.replace("%%", "%"); var box = document.getElementById(name); var state = box.checked; var table = document.getElementById("spamtable"); var respam = new RegExp(bayes.replace("%s", "9\\d\\.\\d\\d")); var reham = new RegExp(bayes.replace("%s", "\\d\\.\\d\\d")); var lines = table.getElementsByTagName('tr'); for(var i = 0; i < lines.length; i += 2) { var checkbox = lines[i].getElementsByTagName('input')[0]; var sstate = lines[i].className.indexOf("rejected"); var html = lines[i+1].innerHTML; if(all && sstate > 0 && html.indexOf(bayes.replace("%s", "100.00")) > 0) checkbox.checked = state; else if(all == 2 && sstate > 0 && respam.exec(html)) checkbox.checked = state; else if(no == 2 && sstate < 0 && reham.exec(html)) checkbox.checked = state; else if(no && sstate < 0 && html.indexOf(bayes.replace("%s", "0.00")) > 0) checkbox.checked = state; } } function ToggleSelectSpam(mode) { var box = document.getElementById(mode ? "selhambutton" : "selspambutton"); var state = box.checked; var table = document.getElementById("spamtable"); var lines = table.getElementsByTagName('tr'); for(var i = 0; i < lines.length; i += 2) { var checkbox = lines[i].getElementsByTagName('input')[0]; var sstate = lines[i].className.indexOf("rejected"); if(mode ? sstate < 0 : sstate >= 0) checkbox.checked = state; } } function setbuttons() { document.getElementById("boxes").innerHTML += "" +"" +"
<\/td>" +"" + "<\/td>" +"<\/td>" +"" + "<\/td>" +"<\/td>" +"" + "<\/td>" +"<\/td>" +"" + "<\/td>" +"<\/tr>
<\/td>" +"" + "<\/td>" +"<\/td>" +"" + "<\/td>" +"<\/tr><\/table>"; } jQuery(document).ready(function($) { $("#spammonitor").addSelectAllCheckboxes(); if(document.forms["spammonitorform"].elements["sel"]) { setbuttons(); } }); spam-filter/tracspamfilter/htdocs/reportspam.js0000644000175500017550000000034612671220455022100 0ustar debacledebaclejQuery(function($){ $("#reportspam").click(function(){ var comment = prompt(spamreport_comment, ""); if(comment == null) { this.href = document.URL; } else { this.href += "&comment=" + comment; } }); }); spam-filter/tracspamfilter/htdocs/adminexternal.js0000644000175500017550000000057312602153153022533 0ustar debacledebaclefunction my_enable(element, state) { var input = document.getElementById(element).getElementsByTagName("input"); for(var i = 0; i < input.length; i++) { input[i].disabled = !state; } } jQuery(document).ready(function($) { my_enable("external", $("#use_external").checked()); $("#use_external").click(function() { my_enable("external", this.checked); }); }); spam-filter/tracspamfilter/htdocs/adminbayes.js0000644000175500017550000000011012602153153021777 0ustar debacledebaclejQuery(document).ready(function($) { $("#reset").enable(hasdata); }); spam-filter/tracspamfilter/htdocs/admin.css0000644000175500017550000000777512460714620021163 0ustar debacledebacle/* Generic styles */ form p.hint { color: #666; font-size: 85%; font-style: italic; margin: .5em 0; padding-left: 1em; } fieldset.radio { border: none; margin: 0; padding: 0 } fieldset.radio legend { color: #000; float: left; font-size: 100%; font-weight: normal; padding: 0 1em 0 0; } fieldset.radio label { padding-right: 1em; vertical-align: middle; } fieldset.radio input { vertical-align: middle; } /* Configuration panel */ fieldset#karmatuning { margin-bottom: 1em; } table#karmapoints { width: 100%; } table#karmapoints p.hint { margin: 0; padding: 0; } /* Monitoring and Reports panel */ table#spammonitor, table#reportmonitor { margin-bottom: 1em; width: 100%; } table#spammonitor td, table#reportmonitor td { font-size: 90%; } table#spammonitor td.path :link, table#spammonitor td.path :visited, table#reportmonitor td.path :link, table#reportmonitor td.path :visited { color: #666; } table#spammonitor td.author, table#reportmonitor td.author { overflow: hidden; } table#spammonitor td.path, table#spammonitor td.ipnr, table#spammonitor td.time, table#reportmonitor td.path, table#reportmonitor td.time { font-size: 80%; } table#spammonitor td.karma { padding-right: 1em; text-align: right; white-space: nowrap; } table#spammonitor tr.rejected td { background: #f4d7d7 !important; } table#spammonitor tr.rejected td.author { font-weight: bold; } table#spammonitor tr.rejected td.karma { color: #b00; font-weight: bold; } table#spammonitor td.details { font-size: 85%; font-weight: normal; } table#spammonitor td.details ul { list-style: square; margin: 0 0 .5em; padding-left: 2em; } table#spammonitor td.details blockquote { color: #999; font-style: italic; margin: 0; } /* Log or Report entry details */ form#spamentry .meta, form#reportentry .meta { margin: 0; } form#spamentry .meta th, form#reportentry .meta th { font-weight: bold; padding-right: .5em; text-align: right; vertical-align: top; } form#spamentry .meta th, form#spamentry .meta td, form#reportentry .meta th, form#reportentry .meta td { font-size: 90%; } form#spamentry pre, form#reportentry pre { background: #e6e6e6; border: 1px solid #666; font-size: 90%; padding: .25em; width: 90%; overflow: auto; } input.spambutton {background-color: #f4d7d7} input.dangerbutton {background-color: #ff3737} input.hambutton {background-color: #d7f4d7} input.spambox {color: #f40710} input.hambox {color: #10a410} td.spambox {color: #f40710} td.hambox {color: #10a410} table#spamstatistics { width: auto; } table#spamstatistics th { text-align: center; vertical-align: middle; } table#spamstatistics td.text { text-align: center; color: white; } table#spamstatistics td.spampercent { text-align: right; } table#spamstatistics td.spamcount { text-align: right; } table#spamstatistics td.spamstatsbutton { padding: 0; } table#spamstatistics tr.strategyexternal { background: #c8cdf1; } table#spamstatistics tr.strategyinternal { background: #c8ecf1; } table#spamstatistics tbody td.mismatch { color: #e16464; } table#spamstatistics tbody td.error { color: #eb2f2f; font-weight: bold; } table#spamstatistics tbody td.sepcells { border-left: 2px solid rgb(221, 221, 221); } table#spamstatistics thead th.sepcells { border-left: 2px solid rgb(221, 221, 221); } table#userstatistics span.username { color: blue; } table#userstatistics span.doublemail { color: #e16464; } table#userstatistics td.active { background: #80FF80; } table#userstatistics td.inactive { background: yellow; } table#userstatistics td.asuser { background: #80FF80; } table#userstatistics td.asmail { background: yellow; } table#userstatistics td.asmailonly { background: #B0FFB0; } table#userstatistics td.asboth { background: yellow; } span.entryinfo { margin-left: 40px; } span.entrypassword { margin-left: 6px; font-size: 80%; } /* external configuration */ table.blogspammethods { margin-top: 5px; } table.blogspammethods td, table.blogspammethods th { border: 1px solid #7f7f7f; } spam-filter/tracspamfilter/htdocs/adminconfig.js0000644000175500017550000000075612602153153022161 0ustar debacledebaclejQuery(document).ready(function($) { $("#purge_age").enable($("#logging_enabled").checked()); $("#spam_monitor_entries").enable($("#logging_enabled").checked()); $("#logging_enabled").click(function() { $("#purge_age").enable(this.checked); $("#spam_monitor_entries").enable(this.checked); }); $("#authenticated_karma").enable(!$("#trust_authenticated").checked()); $("#trust_authenticated").click(function() { $("#authenticated_karma").enable(!this.checked); }); }); spam-filter/tracspamfilter/htdocs/no.gif0000644000175500017550000000026010520411402020425 0ustar debacledebacleGIF89a !!33..HH``bbXXZZuurr!, - @4(D@:@Ю1A;p cM0ITa;spam-filter/tracspamfilter/htdocs/toggle.js0000644000175500017550000000217512642373253021172 0ustar debacledebacle// Copied from Trac source. Remove when Trac < 1.2 is no longer supported. // Add a Select All checkbox to each thead in the table. if (!$.isFunction($.fn.addSelectAllCheckboxes)) { $.fn.addSelectAllCheckboxes = function () { var $table = this; if ($("tr td.sel", $table).length > 0) { $("tr th.sel", $table).append( $('').attr({ title: _("Toggle group") }).click(function () { $("tr td.sel input", $(this).closest("thead, tbody").next()) .prop("checked", this.checked).change(); }) ); $("tr td.sel", $table).click(function () { var $tbody = $(this).closest("tbody"); var $checkboxes = $("tr td.sel input", $tbody); var num_selected = $checkboxes.filter(":checked").length; var none_selected = num_selected === 0; var all_selected = num_selected === $checkboxes.length; $("tr th.sel input", $tbody.prev()) .prop({ "checked": all_selected, "indeterminate": !(none_selected || all_selected) }); }); } }; } spam-filter/tracspamfilter/timeoutserverproxy.py0000644000175500017550000000314312662164224022452 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2011 Edgewall Software # Copyright (C) 2011 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Dirk Stöcker import httplib from xmlrpclib import ServerProxy, Transport class TimeoutHTTPConnection(httplib.HTTPConnection): def __init__(self,host,timeout=3): httplib.HTTPConnection.__init__(self,host,timeout=timeout) # in python 2.6, xmlrpclib expects to use an httplib.HTTP # instance and will call getreply() and getfile() def getreply(self): response = self.getresponse() self.file = response.fp return response.status, response.reason, response.msg def getfile(self): return self.file class TimeoutTransport(Transport): def __init__(self, timeout=3, *l, **kw): Transport.__init__(self,*l,**kw) self.timeout=timeout def make_connection(self, host): conn = TimeoutHTTPConnection(host,self.timeout) return conn class TimeoutServerProxy(ServerProxy): def __init__(self,uri,timeout=3,*l,**kw): kw['transport']=TimeoutTransport(timeout=timeout, use_datetime=kw.get('use_datetime',0)) ServerProxy.__init__(self,uri,*l,**kw) spam-filter/tracspamfilter/adminreport.py0000644000175500017550000001373512722763433020773 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2014 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from __future__ import with_statement from trac.admin import IAdminPanelProvider from trac.config import IntOption from trac.core import Component, implements from trac.web.api import HTTPNotFound from trac.web.chrome import ( add_link, add_script, add_script_data, add_stylesheet) from tracspamfilter.api import _ from tracspamfilter.model import LogEntry class ReportAdminPageProvider(Component): """Web administration panel for reviewing Spam reports""" implements(IAdminPanelProvider) MAX_PER_PAGE = 10000 MIN_PER_PAGE = 5 DEF_PER_PAGE = IntOption('spam-filter', 'spam_report_entries', '100', "How many report entries are displayed by default (between 5 and 10000).", doc_domain='tracspamfilter') # IAdminPanelProvider methods def get_admin_panels(self, req): if 'SPAM_CHECKREPORTS' in req.perm: total = self.env.db_query("SELECT COUNT(*) FROM spamfilter_report") total = total[0][0] yield ('spamfilter', _("Spam Filtering"), 'report', _("Reports") \ + (" (%s)"%total if total else "")) def render_admin_panel(self, req, cat, page, path_info): req.perm.require('SPAM_CHECKREPORTS') if req.method == 'POST' and 'delete' in req.args: entries = req.args.getlist('sel') if entries: self.env.db_transaction(""" DELETE FROM spamfilter_report WHERE id IN (%s) """ % ",".join("'%s'" % each for each in entries)) req.redirect(req.href.admin(cat, page, page=req.args.get('page'), num=req.args.get('num'))) if path_info: data = self._render_entry(req, cat, page, path_info) page = 'entry' data['allowselect'] = False data['entries'] = LogEntry.selectrelated(self.env, data['path'], data['time']) else: data = self._render_panel(req, cat, page) page = '' add_stylesheet(req, 'spamfilter/admin.css') return 'admin_report%s.html' % page, data # Internal methods def _render_panel(self, req, cat, page): try: pagenum = int(req.args.get('page', 1)) - 1 except ValueError: pagenum = 1 try: pagesize = int(req.args.get('num', self.DEF_PER_PAGE)) except ValueError: pagesize = self.DEF_PER_PAGE if pagesize < self.MIN_PER_PAGE: pagesize = self.MIN_PER_PAGE elif pagesize > self.MAX_PER_PAGE: pagesize = self.MAX_PER_PAGE total = self.env.db_query("SELECT COUNT(*) FROM spamfilter_report") total = total[0][0] if total < pagesize: pagenum = 0 elif total <= pagenum * pagesize: pagenum = (total-1)/pagesize offset = pagenum * pagesize entries = [] idx = 0; for e in self.env.db_query(""" SELECT id,time,entry,author,authenticated,comment FROM spamfilter_report ORDER BY time DESC LIMIT %s OFFSET %s""", (pagesize, offset)): # don't display additional appended values p = e[2].split("#") entries.append(('odd' if idx %2 else 'even',)+e[0:2]+(p[0],)+e[3:]) idx += 1; if pagenum > 0: add_link(req, 'prev', req.href.admin(cat, page, page=pagenum, num=pagesize), _('Previous Page')) if offset + pagesize < total: add_link(req, 'next', req.href.admin(cat, page, page=pagenum+2, num=pagesize), _('Next Page')) if entries: add_script_data(req, {'toggleform': "spamreportform"}) add_script(req, 'spamfilter/toggle.js') return { 'entries': entries, 'offset': offset + 1, 'page': pagenum + 1, 'num': pagesize, 'total': total } def _render_entry(self, req, cat, page, entry_id): with self.env.db_query as db: entry = db(""" SELECT time,entry,author,authenticated,headers,comment FROM spamfilter_report WHERE id = %s""", (entry_id,)) if not entry: raise HTTPNotFound(_('Report entry not found')) entry = entry[0] for previous, in db(""" SELECT id FROM spamfilter_report WHERE id<%s ORDER BY id DESC LIMIT 1""", (entry_id,)): add_link(req, 'prev', req.href.admin(cat, page, previous), _('Report Entry %d') % previous) add_link(req, 'up', req.href.admin(cat, page), _('Report Entry List')) for next, in db(""" SELECT id FROM spamfilter_report WHERE id>%s ORDER BY id DESC LIMIT 1""", (entry_id,)): add_link(req, 'next', req.href.admin(cat, page, next), _('Report Entry %d') % next) # don't display additional appended values path = entry[1].split("#") return {'time': entry[0], 'monitor': 'SPAM_MONITOR' in req.perm, 'id': entry_id, 'path': path[0], 'author': entry[2], 'authenticated': entry[3], 'headers': entry[4], 'comment': entry[5]} spam-filter/tracspamfilter/upgrades.py0000644000175500017550000001262212671220455020246 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. from trac.db import Column, DatabaseManager, Table def _schema_to_sql(env, db, table): connector, _ = DatabaseManager(env)._get_connector() return connector.to_sql(table) def add_log_table(env, db): """Add a table for storing the spamfilter logs.""" table = Table('spamfilter_log', key='id')[ Column('id', auto_increment=True), Column('time', type='int'), Column('path'), Column('author'), Column('authenticated', type='int'), Column('ipnr'), Column('content'), Column('rejected', type='int'), Column('karma', type='int'), Column('reasons') ] cursor = db.cursor() for stmt in _schema_to_sql(env, db, table): cursor.execute(stmt) def add_headers_column_to_log_table(env, db): """Add a column to the log table for storing the request headers.""" table = Table('spamfilter_log', key='id')[ Column('id', auto_increment=True), Column('time', type='int'), Column('path'), Column('author'), Column('authenticated', type='int'), Column('ipnr'), Column('headers'), Column('content'), Column('rejected', type='int'), Column('karma', type='int'), Column('reasons') ] cursor = db.cursor() cursor.execute("CREATE TEMPORARY TABLE spamfilter_log_old AS " "SELECT * FROM spamfilter_log") cursor.execute("DROP TABLE spamfilter_log") for stmt in _schema_to_sql(env, db, table): cursor.execute(stmt) cursor.execute("INSERT INTO spamfilter_log (id,time,path,author," "authenticated,ipnr,content,rejected,karma,reasons) " "SELECT id,time,path,author,authenticated,ipnr,content," "rejected,karma,reasons FROM spamfilter_log_old") cursor.execute("DROP TABLE spamfilter_log_old") def add_bayes_table(env, db): """Add table required for bayesian filtering.""" table = Table('spamfilter_bayes', key='word')[ Column('word'), Column('nspam', type='int'), Column('nham', type='int') ] cursor = db.cursor() for stmt in _schema_to_sql(env, db, table): cursor.execute(stmt) def add_statistics_table(env, db): """Add table required for filtering statistics.""" table = Table('spamfilter_statistics', key=['strategy', 'action', 'data', 'status'])[ Column('strategy'), Column('action'), Column('data'), Column('status'), Column('delay', type='double precision'), Column('delay_max', type='double precision'), Column('delay_min', type='double precision'), Column('count', type='int'), Column('external', type='int'), Column('time', type='int') ] cursor = db.cursor() for stmt in _schema_to_sql(env, db, table): cursor.execute(stmt) def add_request_column_to_log_table(env, db): """Add a column to the log table for storing the complete request.""" table = Table('spamfilter_log', key='id')[ Column('id', auto_increment=True), Column('time', type='int'), Column('path'), Column('author'), Column('authenticated', type='int'), Column('ipnr'), Column('headers'), Column('content'), Column('rejected', type='int'), Column('karma', type='int'), Column('reasons'), Column('request') ] cursor = db.cursor() cursor.execute("CREATE TEMPORARY TABLE spamfilter_log_old AS " "SELECT * FROM spamfilter_log") cursor.execute("DROP TABLE spamfilter_log") for stmt in _schema_to_sql(env, db, table): cursor.execute(stmt) # copy old, but strip entries with non-XML reasons cursor.execute("INSERT INTO spamfilter_log (id,time,path,author," "authenticated,ipnr,headers,content,rejected,karma,reasons) " "SELECT id,time,path,author,authenticated,ipnr,headers,content," "rejected,karma,reasons FROM spamfilter_log_old " "WHERE reasons LIKE '%'") cursor.execute("DROP TABLE spamfilter_log_old") def add_report_table(env, db): """Add table required for spam reports by users.""" table = Table('spamfilter_report', key=['id'])[ Column('id', auto_increment=True), Column('entry'), Column('headers'), Column('author'), Column('authenticated', type='int'), Column('comment'), Column('time', type='int') ] cursor = db.cursor() for stmt in _schema_to_sql(env, db, table): cursor.execute(stmt) version_map = { 1: [add_log_table], 2: [add_headers_column_to_log_table], 3: [add_bayes_table], 4: [add_statistics_table, add_request_column_to_log_table, add_report_table] } spam-filter/tracspamfilter/captcha/0000755000175500017550000000000012724352556017471 5ustar debacledebaclespam-filter/tracspamfilter/captcha/mollom.py0000644000175500017550000000441512673646660021353 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Edgewall Software # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Dirk Stöcker import urllib2 from xml.etree import ElementTree from trac.core import Component, implements from trac.util.html import html from tracspamfilter.captcha import ICaptchaMethod from tracspamfilter.filters.mollom import MollomFilterStrategy class MollomCaptcha(Component): """An image captcha server by http://www.mollom.com/""" implements(ICaptchaMethod) def __init__(self): self.mollom = MollomFilterStrategy(self.env) def generate_captcha(self, req): resp, content = self.mollom._call('captcha', {'type': 'image'}) tree = ElementTree.fromstring(content) req.session['captcha_mollom_id'] = tree.find('./captcha/id').text url = tree.find('./captcha/url').text return '', html.img(src=url, alt='captcha') def verify_captcha(self, req): mollom_id = req.session.get('captcha_mollom_id') solution = req.args.get('captcha_response', '') try: resp, content = self.mollom._call('captcha/%s' % mollom_id, {'solution': solution}) except Exception, e: self.log.warning("Exception in Mollom captcha handling (%s)", e) return False else: tree = ElementTree.fromstring(content) del req.session['captcha_mollom_id'] if tree.find('./captcha/solved').text == '1': return True else: self.log.warning("Mollom captcha returned error: %s", tree.find('./message')) return False def is_usable(self, req): try: return self.mollom.verify_key(req) except urllib2.URLError: return False spam-filter/tracspamfilter/captcha/__init__.py0000644000175500017550000000120611003424214021555 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # Copyright (C) 2006 Alec Thomas # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Alec Thomas from tracspamfilter.captcha.api import * spam-filter/tracspamfilter/captcha/keycaptcha.py0000644000175500017550000001062112674637512022161 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import hashlib import random import urllib2 from pkg_resources import get_distribution from trac import __version__ as TRAC_VERSION from trac.config import Option from trac.core import Component, implements from trac.util.html import tag from tracspamfilter.captcha import ICaptchaMethod class KeycaptchaCaptcha(Component): """KeyCaptcha implementation""" implements(ICaptchaMethod) private_key = Option('spam-filter', 'captcha_keycaptcha_private_key', '', """Private key for KeyCaptcha usage.""", doc_domain="tracspamfilter") user_id = Option('spam-filter', 'captcha_keycaptcha_user_id', '', """User id for KeyCaptcha usage.""", doc_domain="tracspamfilter") user_agent = 'Trac/%s | SpamFilter/%s' \ % (TRAC_VERSION, get_distribution('TracSpamFilter').version) def generate_captcha(self, req): session_id = "%d-3.4.0.001" % random.randint(1, 10000000) sign1 = hashlib.md5(session_id + req.remote_addr + self.private_key).hexdigest() sign2 = hashlib.md5(session_id + self.private_key).hexdigest() varblock = "var s_s_c_user_id = '%s';\n" % self.user_id varblock += "var s_s_c_session_id = '%s';\n" % session_id varblock += "var s_s_c_captcha_field_id = 'keycaptcha_response_field';\n" varblock += "var s_s_c_submit_button_id = 'keycaptcha_response_button';\n" varblock += "var s_s_c_web_server_sign = '%s';\n" % sign1 varblock += "var s_s_c_web_server_sign2 = '%s';\n" % sign2 varblock += "document.s_s_c_debugmode=1;\n" fragment = tag(tag.script(varblock, type='text/javascript')) fragment.append( tag.script(type='text/javascript', src='http://backs.keycaptcha.com/swfs/cap.js') ) fragment.append( tag.input(type='hidden', id='keycaptcha_response_field', name='keycaptcha_response_field') ) fragment.append( tag.input(type='submit', id='keycaptcha_response_button', name='keycaptcha_response_button') ) req.session['captcha_key_session'] = session_id return None, fragment def verify_key(self, private_key, user_id): if private_key is None or user_id is None: return False # FIXME - Not yet implemented return True def verify_captcha(self, req): session = None if 'captcha_key_session' in req.session: session = req.session['captcha_key_session'] del req.session['captcha_key_session'] response_field = req.args.get('keycaptcha_response_field') val = response_field.split('|') s = hashlib.md5('accept' + val[1] + self.private_key + val[2]).hexdigest() self.log.debug("KeyCaptcha response: %s .. %s .. %s", response_field, s, session) if s == val[0] and session == val[3]: try: request = urllib2.Request( url=val[2], headers={"User-agent": self.user_agent} ) response = urllib2.urlopen(request) return_values = response.read() response.close() except Exception, e: self.log.warning("Exception in KeyCaptcha handling (%s)", e) else: self.log.debug("KeyCaptcha check result: %s", return_values) if return_values == '1': return True self.log.warning("KeyCaptcha returned invalid check result: " "%s (%s)", return_values, response_field) else: self.log.warning("KeyCaptcha returned invalid data: " "%s (%s,%s)", response_field, s, session) return False def is_usable(self, req): return self.private_key and self.user_id spam-filter/tracspamfilter/captcha/expression.py0000644000175500017550000000646112673646660022256 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # Copyright (C) 2006 Alec Thomas # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Alec Thomas import random from trac.core import Component, TracError, implements from trac.config import IntOption from tracspamfilter.captcha import ICaptchaMethod from tracspamfilter.api import N_, _, gettext class ExpressionCaptcha(Component): """captcha in the form of a human readable numeric expression Initial implementation by sergeych@tancher.com. """ implements(ICaptchaMethod) terms = IntOption('spam-filter', 'captcha_expression_terms', 3, """Number of terms in numeric CAPTCHA expression.""", doc_domain='tracspamfilter') ceiling = IntOption('spam-filter', 'captcha_expression_ceiling', 10, """Maximum value of individual terms in numeric CAPTCHA expression.""", doc_domain='tracspamfilter') operations = {'*': N_("multiplied by"), '-': N_("minus"), '+': N_("plus")} numerals = (N_("zero"), N_("one"), N_("two"), N_("three"), N_("four"), N_("five"), N_("six"), N_("seven"), N_("eight"), N_("nine"), N_("ten"), N_("eleven"), N_("twelve"), N_("thirteen"), N_("fourteen"), N_("fifteen"), N_("sixteen"), N_("seventeen"), N_("eighteen"), N_("nineteen")) # TRANSLATOR: if compound numbers like in english are not # supported, simply add a "plus" command to the following # translations! tens = (N_("twenty"), N_("thirty"), N_("forty"), N_("fifty"), N_("sixty"), N_("seventy"), N_("eighty"), N_("ninety")) # ICaptchaMethod methods def generate_captcha(self, req): if self.ceiling > 100: raise TracError( _("Numeric captcha can not represent numbers > 100")) terms = [unicode(random.randrange(0, self.ceiling)) for i in xrange(self.terms)] operations = [random.choice(self.operations.keys()) for i in xrange(self.terms)] expression = sum(zip(terms, operations), ())[:-1] expression = eval(compile(' '.join(expression), 'captcha_eval', 'eval')) human = sum(zip([self._humanise(int(t)) for t in terms], [gettext(self.operations[o]) for o in operations]), ())[:-1] return expression, u' '.join(map(unicode, human)) def verify_captcha(self, req): return False def is_usable(self, req): return True # Internal methods def _humanise(self, value): if value < 20: return gettext(self.numerals[value]) english = gettext(self.tens[value / 10 - 2]) if value % 10: english += u' ' + gettext(self.numerals[value % 10]) return english spam-filter/tracspamfilter/captcha/image.py0000644000175500017550000001077112671220455021124 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # Copyright (C) 2006 Alec Thomas # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Alec Thomas import StringIO import os import random from PIL import Image from PIL import ImageFont from PIL import ImageDraw from PIL import ImageFilter from trac.config import ListOption, IntOption, Option from trac.core import Component, TracError, implements from trac.env import ISystemInfoProvider from trac.util import get_pkginfo from trac.util.html import html from trac.web.api import IRequestHandler from tracspamfilter.captcha.api import ICaptchaMethod class ImageCaptcha(Component): """An image captcha courtesy of http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440588 """ implements(ICaptchaMethod, IRequestHandler, ISystemInfoProvider) fonts = ListOption('spam-filter', 'captcha_image_fonts', 'vera.ttf', doc="""Set of fonts to choose from when generating image CAPTCHA.""", doc_domain='tracspamfilter') font_size = IntOption('spam-filter', 'captcha_image_font_size', 25, """Font size to use in image CAPTCHA.""", doc_domain='tracspamfilter') alphabet = Option('spam-filter', 'captcha_image_alphabet', 'abcdefghkmnopqrstuvwxyz', """Alphabet to choose image CAPTCHA challenge from.""", doc_domain='tracspamfilter') letters = IntOption('spam-filter', 'captcha_image_letters', 6, """Number of letters to use in image CAPTCHA challenge.""", doc_domain='tracspamfilter') # IRequestHandler methods def match_request(self, req): return req.path_info == '/captcha/image' def process_request(self, req): if 'captcha_expected' not in req.session: # TODO Probably need to render an error image here raise TracError("No CAPTCHA response in session") req.send_response(200) req.send_header('Content-Type', 'image/jpeg') image = StringIO.StringIO() from pkg_resources import resource_filename font = os.path.join(resource_filename('tracspamfilter', 'fonts'), random.choice(self.fonts)) self.gen_captcha(image, req.session['captcha_expected'], font, self.font_size) img = image.getvalue() req.send_header('Content-Length', len(img)) req.end_headers() req.write(img) # ICaptchaMethod methods def generate_captcha(self, req): challenge = ''.join(random.choice(self.alphabet) for i in xrange(self.letters)) return challenge, html.img(src=req.href('/captcha/image'), width='33%', alt='captcha') def verify_captcha(self, req): return False def is_usable(self, req): return True # ISystemInfoProvider methods def get_system_info(self): import PIL yield 'Pillow', get_pkginfo(PIL)['version'] # Internal methods def gen_captcha(self, file, text, fnt, fnt_sz, fmt='JPEG'): # randomly select the foreground color fgcolor = random.randint(0, 0xffff00) # make the background color the opposite of fgcolor bgcolor = fgcolor ^ 0xffffff # create a font object font = ImageFont.truetype(fnt, fnt_sz) # determine dimensions of the text dim = font.getsize(text) # create a new image slightly larger that the text im = Image.new('RGB', (dim[0] + 5, dim[1] + 5), bgcolor) d = ImageDraw.Draw(im) x, y = im.size r = random.randint # draw 100 random colored boxes on the background for num in xrange(100): d.rectangle((r(0, x), r(0, y), r(0, x), r(0, y)), fill=r(0, 0xffffff)) # add the text to the image d.text((3, 3), text, font=font, fill=fgcolor) im = im.filter(ImageFilter.EDGE_ENHANCE_MORE) # save the image to a file im.save(file, format=fmt) spam-filter/tracspamfilter/captcha/recaptcha.py0000644000175500017550000000771512674637512022011 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import urllib import urllib2 from pkg_resources import get_distribution from trac import __version__ as TRAC_VERSION from trac.config import Option from trac.core import Component, implements from trac.util.html import tag from tracspamfilter.api import _ from tracspamfilter.captcha import ICaptchaMethod class RecaptchaCaptcha(Component): """reCAPTCHA implementation""" implements(ICaptchaMethod) private_key = Option('spam-filter', 'captcha_recaptcha_private_key', '', """Private key for reCaptcha usage.""", doc_domain="tracspamfilter") public_key = Option('spam-filter', 'captcha_recaptcha_public_key', '', """Public key for reCaptcha usage.""", doc_domain="tracspamfilter") user_agent = 'Trac/%s | SpamFilter/%s' \ % (TRAC_VERSION, get_distribution('TracSpamFilter').version) def generate_captcha(self, req): fragment = tag.script(type='text/javascript', src='https://www.google.com/recaptcha/api/' 'challenge?k=%s' % self.public_key) # note - this is not valid XHTML! fragment.append( tag.noscript( tag.iframe(src='https://www.google.com/recaptcha/' 'api/noscript?k=%s' % self.public_key, height=300, width=500, frameborder=0), tag.br(), tag.textarea(name='recaptcha_challenge_field', rows=3, cols=40), tag.input(type='hidden', name='recaptcha_response_field', value='manual_challenge'), tag.br(), tag.input(type='submit', value=_("Submit")) ) ) return None, fragment def encode_if_necessary(self, s): if isinstance(s, unicode): return s.encode('utf-8') return s def verify_key(self, private_key, public_key): if private_key is None or public_key is None: return False # FIXME - Not yet implemented return True def verify_captcha(self, req): recaptcha_challenge_field = req.args.get('recaptcha_challenge_field') recaptcha_response_field = req.args.get('recaptcha_response_field') remoteip = req.remote_addr try: params = urllib.urlencode({ 'privatekey': self.encode_if_necessary(self.private_key), 'remoteip': self.encode_if_necessary(remoteip), 'challenge': self.encode_if_necessary(recaptcha_challenge_field), 'response': self.encode_if_necessary(recaptcha_response_field), }) request = urllib2.Request( url='https://www.google.com/recaptcha/api/verify', data=params, headers={ 'Content-type': 'application/x-www-form-urlencoded', 'User-agent': self.user_agent } ) response = urllib2.urlopen(request) return_values = response.read().splitlines() response.close() except Exception, e: self.log.warning("Exception in reCAPTCHA handling (%s)", e) else: if return_values[0] == 'true': return True else: self.log.warning("reCAPTCHA returned error: %s", return_values[1]) return False def is_usable(self, req): return self.public_key and self.private_key spam-filter/tracspamfilter/captcha/rand.py0000644000175500017550000000520312673646660020774 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2006 Edgewall Software # Copyright (C) 2006 Alec Thomas # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import random from trac.core import Component, ExtensionPoint, implements from tracspamfilter.captcha import ICaptchaMethod class RandomCaptcha(Component): """An random captcha chooser""" handlers = ExtensionPoint(ICaptchaMethod) implements(ICaptchaMethod) # ICaptchaMethod methods def generate_captcha(self, req): count = 0 for handler in self.handlers: if not isinstance(handler, RandomCaptcha) and \ handler.is_usable(req): count += 1 choice = random.randint(1, count) count = 0 for handler in self.handlers: if not isinstance(handler, RandomCaptcha) and \ handler.is_usable(req): count += 1 if count == choice: req.session['captcha_handler'] = count req.session.save() return handler.generate_captcha(req) def verify_captcha(self, req): captcha_handler = req.session.get('captcha_handler') if captcha_handler is not None: del req.session['captcha_handler'] captcha_handler = int(captcha_handler) else: return False count = 0 for handler in self.handlers: if not isinstance(handler, RandomCaptcha) and \ handler.is_usable(req): count += 1 if count == captcha_handler: return handler.verify_captcha(req) return False def is_usable(self, req): return True def name(self, req): name = self.__class__.__name__ captcha_handler = req.session.get('captcha_handler') if captcha_handler is not None: captcha_handler = int(captcha_handler) count = 0 for newhandler in self.handlers: if not isinstance(newhandler, RandomCaptcha) and \ newhandler.is_usable(req): count += 1 if count == captcha_handler: name = newhandler.__class__.__name__ return name spam-filter/tracspamfilter/captcha/admin.py0000644000175500017550000002625612722760003021132 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015-2016 Edgewall Software # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Dirk Stöcker from trac.admin import IAdminPanelProvider from trac.core import Component, ExtensionPoint, TracError, implements from trac.util import as_int from trac.util.html import tag from trac.util.translation import tag_ from trac.web.chrome import ( ITemplateProvider, add_link, add_script, add_script_data, add_stylesheet, add_warning) from tracspamfilter.api import _ from tracspamfilter.captcha import ICaptchaMethod from tracspamfilter.captcha.expression import ExpressionCaptcha from tracspamfilter.captcha.keycaptcha import KeycaptchaCaptcha from tracspamfilter.captcha.recaptcha import RecaptchaCaptcha try: from tracspamfilter.captcha.recaptcha2 import Recaptcha2Captcha except ImportError: # simplejson not installed Recaptcha2Captcha = None try: from tracspamfilter.captcha.image import ImageCaptcha except ImportError: # PIL not installed ImageCaptcha = None class CaptchaAdminPageProvider(Component): """Web administration panel for configuring the Captcha handling.""" implements(IAdminPanelProvider) handlers = ExtensionPoint(ICaptchaMethod) def __init__(self): self.recaptcha_enabled = \ self.env.is_enabled(RecaptchaCaptcha) or \ (Recaptcha2Captcha and self.env.is_enabled(Recaptcha2Captcha)) self.keycaptcha_enabled =\ self.env.is_enabled(KeycaptchaCaptcha) self.expressioncaptcha_enabled = \ self.env.is_enabled(ExpressionCaptcha) self.imagecaptcha_enabled = \ ImageCaptcha and self.env.is_enabled(ImageCaptcha) # IAdminPanelProvider methods def get_admin_panels(self, req): any_captchas_enabled = \ any((self.recaptcha_enabled, self.keycaptcha_enabled, self.expressioncaptcha_enabled, self.imagecaptcha_enabled)) if 'SPAM_CONFIG' in req.perm and any_captchas_enabled: yield 'spamfilter', _("Spam Filtering"), 'captcha', _("Captcha") def render_admin_panel(self, req, cat, page, path_info): req.perm.require('SPAM_CONFIG') spam_config = self.config['spam-filter'] data = {} if req.method == 'POST': if 'cancel' in req.args: req.redirect(req.href.admin(cat, page)) captcha_enabled = 'captcha_enabled' in req.args captcha = req.args.get('captcha') captcha_karma_lifetime = \ as_int(req.args.get('captcha_karma_lifetime'), None) if captcha_karma_lifetime is None: captcha_karma_lifetime = \ spam_config.get('captcha_karma_lifetime') add_warning(req, tag_("Invalid value for %(key)s", key=tag.tt('captcha_karma_lifetime'))) recaptcha_private_key = recaptcha_public_key = None if self.recaptcha_enabled: recaptcha_private_key = req.args.get('recaptcha_private_key') recaptcha_public_key = req.args.get('recaptcha_public_key') recaptcha = RecaptchaCaptcha(self.env) verified_key = recaptcha.verify_key(recaptcha_private_key, recaptcha_public_key) if recaptcha_private_key and not verified_key: data['recaptcha_error'] = _("The keys are invalid") data['error'] = 1 keycaptcha_private_key = keycaptcha_user_id = None if self.keycaptcha_enabled: keycaptcha_private_key = req.args.get('keycaptcha_private_key') keycaptcha_user_id = req.args.get('keycaptcha_user_id') keycaptcha = KeycaptchaCaptcha(self.env) verified_key = keycaptcha.verify_key(keycaptcha_private_key, keycaptcha_user_id) if keycaptcha_private_key and not verified_key: data['keycaptcha_error'] = \ _("The key or user id are invalid") data['error'] = 1 expressioncaptcha_ceiling = expressioncaptcha_terms = None if self.expressioncaptcha_enabled: expressioncaptcha_ceiling = \ as_int(req.args.get('expressioncaptcha_ceiling'), None) if expressioncaptcha_ceiling is None: expressioncaptcha_ceiling = \ spam_config.get('captcha_expression_ceiling') add_warning(req, tag_("Invalid value for %(key)s", key=tag.tt('captcha_expression_ceiling'))) expressioncaptcha_terms = \ as_int(req.args.get('expressioncaptcha_terms'), None) if expressioncaptcha_terms is None: expressioncaptcha_terms = \ spam_config.get('captcha_expression_terms') add_warning(req, tag_("Invalid value for %(key)s", key=tag.tt('captcha_expression_terms'))) imagecaptcha_alphabet = imagecaptcha_fonts = \ imagecaptcha_letters = imagecaptcha_font_size = None if self.imagecaptcha_enabled: imagecaptcha_alphabet = req.args.get('imagecaptcha_alphabet') imagecaptcha_fonts = req.args.get('imagecaptcha_fonts') imagecaptcha_letters = \ as_int(req.args.get('imagecaptcha_letters'), None) if imagecaptcha_letters is None: imagecaptcha_letters = \ spam_config.get('captcha_image_letters') add_warning(req, tag_("Invalid value for %(key)s", key=tag.tt('captcha_image_letters'))) imagecaptcha_font_size = \ as_int(req.args.get('imagecaptcha_font_size'), None) if imagecaptcha_font_size is None: imagecaptcha_font_size = \ spam_config.get('captcha_image_font_size') add_warning(req, tag_("Invalid value for %(key)s", key=tag.tt('captcha_image_font_size'))) if 'error' not in data or not data['error']: spam_config.set('captcha', captcha) if captcha_enabled: spam_config.set('reject_handler', 'CaptchaSystem') else: spam_config.set('reject_handler', 'FilterSystem') spam_config.set('captcha_karma_lifetime', captcha_karma_lifetime) if self.recaptcha_enabled: spam_config.set('captcha_recaptcha_private_key', recaptcha_private_key) spam_config.set('captcha_recaptcha_public_key', recaptcha_public_key) if self.keycaptcha_enabled: spam_config.set('captcha_keycaptcha_private_key', keycaptcha_private_key) spam_config.set('captcha_keycaptcha_user_id', keycaptcha_user_id) if self.expressioncaptcha_enabled: spam_config.set('captcha_expression_ceiling', expressioncaptcha_ceiling) spam_config.set('captcha_expression_terms', expressioncaptcha_terms) if self.imagecaptcha_enabled: spam_config.set('captcha_image_alphabet', imagecaptcha_alphabet) spam_config.set('captcha_image_letters', imagecaptcha_letters) spam_config.set('captcha_image_font_size', imagecaptcha_font_size) spam_config.set('captcha_image_fonts', imagecaptcha_fonts) self.config.save() req.redirect(req.href.admin(cat, page)) else: captcha = spam_config.get('captcha') reject_handler = spam_config.get('reject_handler') captcha_enabled = reject_handler == 'CaptchaSystem' captcha_karma_lifetime = spam_config.get('captcha_karma_lifetime') recaptcha_private_key = \ spam_config.get('captcha_recaptcha_private_key') recaptcha_public_key = \ spam_config.get('captcha_recaptcha_public_key') keycaptcha_private_key = \ spam_config.get('captcha_keycaptcha_private_key') keycaptcha_user_id = \ spam_config.get('captcha_keycaptcha_user_id') expressioncaptcha_ceiling = \ spam_config.get('captcha_expression_ceiling') expressioncaptcha_terms = \ spam_config.get('captcha_expression_terms') imagecaptcha_alphabet = \ spam_config.get('captcha_image_alphabet') imagecaptcha_letters = \ spam_config.get('captcha_image_letters') imagecaptcha_font_size = \ spam_config.get('captcha_image_font_size') imagecaptcha_fonts = \ spam_config.get('captcha_image_fonts') captcha_types = sorted(h.__class__.__name__ for h in self.handlers) data.update({ 'captcha': captcha, 'captcha_types': captcha_types, 'captcha_enabled': captcha_enabled, 'captcha_karma_lifetime': captcha_karma_lifetime, 'recaptcha_enabled': self.recaptcha_enabled, 'recaptcha_private_key': recaptcha_private_key, 'recaptcha_public_key': recaptcha_public_key, 'keycaptcha_enabled': self.keycaptcha_enabled, 'keycaptcha_private_key': keycaptcha_private_key, 'keycaptcha_user_id': keycaptcha_user_id, 'expressioncaptcha_enabled': self.expressioncaptcha_enabled, 'expressioncaptcha_ceiling': expressioncaptcha_ceiling, 'expressioncaptcha_terms': expressioncaptcha_terms, 'imagecaptcha_enabled': self.imagecaptcha_enabled, 'imagecaptcha_alphabet': imagecaptcha_alphabet, 'imagecaptcha_letters': imagecaptcha_letters, 'imagecaptcha_font_size': imagecaptcha_font_size, 'imagecaptcha_fonts': imagecaptcha_fonts }) add_stylesheet(req, 'spamfilter/admin.css') return 'admin_captcha.html', data spam-filter/tracspamfilter/captcha/api.py0000644000175500017550000003026712724352556020624 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Edgewall Software # Copyright (C) 2006 Alec Thomas # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Alec Thomas from __future__ import with_statement import time from trac.config import ExtensionOption, IntOption from trac.core import Component, ExtensionPoint, Interface, TracError, \ implements from trac.util.html import Markup from trac.web.api import IRequestFilter, IRequestHandler from tracspamfilter.api import _, IFilterStrategy, IRejectHandler, N_, \ RejectContent class ICaptchaMethod(Interface): """ A CAPTCHA implementation. """ def generate_captcha(req): """Return a tuple of `(result, html)`, where `result` is the expected response and `html` is a HTML fragment for displaying the CAPTCHA challenge.""" """When "" is returned for result, then standard HTML code is used, but verification done by verify_captcha() nevertheless.""" def verify_captcha(req): """Checks if captcha is valid (only in case generate_captcha returns None for challenge parameter, otherwise check directly.""" def is_usable(req): """Check if captcha can be used for the request.""" class CaptchaSystem(Component): """ Main captcha handling system required to allow captcha based score updating in case submission was rejected. """ implements(IRequestHandler, IRejectHandler, IFilterStrategy, IRequestFilter) handlers = ExtensionPoint(IRequestHandler) _name = "Captcha" captcha = ExtensionOption('spam-filter', 'captcha', ICaptchaMethod, 'ExpressionCaptcha', """CAPTCHA method to use for verifying humans.""", doc_domain="tracspamfilter") karma_points = IntOption('spam-filter', 'captcha_karma', 20, """By how many points a successful CAPTCHA response increases the overall score.""", doc_domain="tracspamfilter") repeat_karma_points = IntOption('spam-filter', 'captcha_failed_karma', 1, """By how many points a failed CAPTCHA impacts the overall score.""", doc_domain="tracspamfilter") karma_lifetime = IntOption('spam-filter', 'captcha_karma_lifetime', 86400, """Time in seconds that a successful CAPTCHA response increases karma.""", doc_domain="tracspamfilter") captcha_lifetime = IntOption('spam-filter', 'captcha_lifetime', 120, """Time in seconds before CAPTCHA is removed.""", doc_domain="tracspamfilter") captcha_cleantime = IntOption('spam-filter', 'captcha_lifetime', 3600, """Time in seconds before database cleanup is called.""", doc_domain="tracspamfilter") # IFilterStrategy methods def is_external(self): return False def _getcaptchaname(self, req): name = self.captcha.__class__.__name__ if name.endswith("Captcha"): name = name[:-7] return req.session.get('captcha_verified_name', name) def test(self, req, author, content, ip): if not self._expired(req): self.log.debug("CAPTCHA: Test %s not expired", self._getcaptchaname(req)) return (self.karma_points, N_("Human verified via CAPTCHA (%s)"), self._getcaptchaname(req)) else: # simply test to down-weight wrong captcha solutions val = int(req.session.get('captcha_reject_count', 0)) self.log.debug("CAPTCHA: Test %s reject %d", self._getcaptchaname(req), val) if val > 0: return (-self.repeat_karma_points * val, N_("Failed CAPTCHA (%s) attempts"), self._getcaptchaname(req)) def train(self, req, author, content, ip, spam=True): pass # IRejectHandler methods def reject_content(self, req, message): self._cleanup() if self._expired(req): req.session['captcha_reject_time'] = int(time.time()) val = int(req.session.get('captcha_reject_count', 0)) req.session['captcha_reject_count'] = val + 1 req.session['captcha_reject_reason'] = message req.session['captcha_redirect'] = req.path_info for key, value in req.args.iteritems(): req.session['captcha_arg_%s' % key] = value req.redirect(req.href.captcha()) else: raise RejectContent(message) # IRequestHandler methods def match_request(self, req): return req.path_info == '/captcha' def process_request(self, req): data = {} exp = req.session.get('captcha_expected') if req.method == 'POST': if req.args.get('captcha_response') == exp: data['error'] = _("CAPTCHA failed to handle original request") else: data['error'] = _("CAPTCHA verification failed") else: data['error'] = Markup(req.session.get("captcha_reject_reason")) # cleanup old values if exp is not None: del req.session['captcha_expected'] # generate new captcha result, html = self.captcha.generate_captcha(req) data['challenge'] = html if self.captcha.__class__.__name__ == 'RandomCaptcha': data['random'] = 1 if result == "": data['defaultform'] = 1 elif result is not None: data['defaultform'] = 1 req.session['captcha_expected'] = result req.session.save() return 'verify_captcha.html', data, None def pre_process_request(self, req, handler): if req.path_info == '/captcha' and req.method == 'POST': valid = False exp = req.session.get('captcha_expected') try: name = self.captcha.name(req) except Exception: name = self.captcha.__class__.__name__ if name.endswith('Captcha'): name = name[:-7] if exp is None: valid = self.captcha.verify_captcha(req) elif req.args.get('captcha_response', '') == exp: valid = True if valid: req.environ['PATH_INFO'] = \ req.session.get('captcha_redirect', req.href()) if 'SCRIPT_NAME' in req.environ and \ len(req.environ['SCRIPT_NAME']) > 1: path_info = req.environ['PATH_INFO'] req.environ['PATH_INFO'] = \ path_info.replace(req.environ['SCRIPT_NAME'], '') req.environ['PATH_INFO'] = \ req.environ['PATH_INFO'].encode('utf-8') if 'captcha_redirect' in req.session: del req.session['captcha_redirect'] if 'captcha_reject_reason' in req.session: del req.session['captcha_reject_reason'] if 'captcha_reject_time' in req.session: del req.session['captcha_reject_time'] keys = req.session.keys() for key in keys: if key.startswith('captcha_arg_'): arg = key[12:] req.args[arg] = req.session[key] del req.session[key] try: for newhandler in self.handlers: try: if newhandler.match_request(req): keys = req.session.keys() for key in keys: if key.startswith('captcha_'): self.log.info("Remove useless key: " "%s", key) del req.session[key] handler = newhandler break except Exception, e: self.log.debug("Exception when parsing handlers: " "(%s)", e) except TracError, e: self.log.debug("CAPTCHA: PreProcess End %s %s", name, e) req.session['captcha_verified_name'] = name return handler req.session['captcha_verified_name'] = name req.session['captcha_verified'] = int(time.time()) self.log.debug("CAPTCHA: PreProcess OK %s", name) req.session.save() return handler def post_process_request(self, req, template, content_type): return template, content_type def post_process_request(self, req, template, data, content_type): return template, data, content_type # Internal methods def _expired(self, req): return int(req.session.get('captcha_verified', 0)) + \ self.karma_lifetime < time.time() # remove old entries from database def _cleanup(self): last = 0 for value, in self.env.db_query(""" SELECT value FROM system WHERE name='spamfilter_lastclean' """): last = int(value) break tim = int(time.time()) if last+self.captcha_cleantime < tim: self.log.debug('CAPTCHA: Cleanup captcha %s+%s < %s', last, self.captcha_cleantime, tim) t = tim - self.captcha_lifetime tc = tim - self.karma_lifetime with self.env.db_transaction as db: if last == 0: db("INSERT INTO system VALUES " "('spamfilter_lastclean', %s)", (tim,)) else: db("UPDATE system SET value=%s WHERE " "name='spamfilter_lastclean'", (tim,)) db("""DELETE FROM session_attribute WHERE name LIKE 'captcha%%' AND (name != 'captcha_verified' OR %s < %%s) AND (name != 'captcha_verified_name' OR ( sid IN (SELECT * FROM (SELECT sid FROM session_attribute WHERE name = 'captcha_verified' AND %s < %%s ) AS tmp1 ) ) ) AND ( sid IN (SELECT * FROM (SELECT sid FROM session_attribute WHERE name = 'captcha_reject_time' AND %s < %%s ) AS tmp1 ) OR sid NOT IN (SELECT * FROM (SELECT sid FROM session_attribute WHERE name = 'captcha_reject_time' ) AS tmp2 ) ) """ % (db.cast('value', 'int'), db.cast('value', 'int'), db.cast('value', 'int')), (tc, tc, t)) spam-filter/tracspamfilter/captcha/recaptcha2.py0000644000175500017550000000652112722760003022047 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2015 Dirk Stöcker # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. import urllib import urllib2 from pkg_resources import get_distribution try: import json except ImportError: import simplejson as json from trac import __version__ as TRAC_VERSION from trac.config import Option from trac.core import Component, implements from trac.util.html import tag from trac.web.chrome import add_script from tracspamfilter.api import _ from tracspamfilter.captcha.api import ICaptchaMethod class Recaptcha2Captcha(Component): """reCAPTCHA2 implementation""" implements(ICaptchaMethod) # use same values as reCAPTCHA1 private_key = Option('spam-filter', 'captcha_recaptcha_private_key', '', """Private key for reCaptcha usage.""", doc_domain="tracspamfilter") public_key = Option('spam-filter', 'captcha_recaptcha_public_key', '', """Public key for reCaptcha usage.""", doc_domain="tracspamfilter") user_agent = 'Trac/%s | SpamFilter/%s' \ % (TRAC_VERSION, get_distribution('TracSpamFilter').version) def generate_captcha(self, req): add_script(req, 'https://www.google.com/recaptcha/api.js') return None, tag( tag.div(class_='g-recaptcha', data_sitekey=self.public_key), tag.input(type='submit', value=_("Submit")) ) def encode_if_necessary(self, s): if isinstance(s, unicode): return s.encode('utf-8') return s def verify_key(self, private_key, public_key): if private_key is None or public_key is None: return False # FIXME - Not yet implemented return True def verify_captcha(self, req): recaptcha_response_field = req.args.get('g-recaptcha-response') remoteip = req.remote_addr try: params = urllib.urlencode({ 'secret': self.encode_if_necessary(self.private_key), 'remoteip': self.encode_if_necessary(remoteip), 'response': self.encode_if_necessary(recaptcha_response_field), }) request = urllib2.Request( url='https://www.google.com/recaptcha/api/siteverify', data=params, headers={ 'Content-type': 'application/x-www-form-urlencoded', 'User-agent': self.user_agent } ) response = urllib2.urlopen(request) return_values = json.loads(response.read()) response.close() except Exception, e: self.log.warning("Exception in reCAPTCHA handling (%s)", e) else: if return_values['success'] is True: return True else: self.log.warning("reCAPTCHA returned error: %s", return_values['error-codes']) return False def is_usable(self, req): return self.public_key and self.private_key spam-filter/tracspamfilter/api.py0000644000175500017550000000560112665715006017210 0ustar debacledebacle# -*- coding: utf-8 -*- # # Copyright (C) 2005-2012 Edgewall Software # Copyright (C) 2005-2006 Matthew Good # Copyright (C) 2006 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Matthew Good # Christopher Lenz from trac.core import Interface, TracError from trac.util.translation import domain_functions _, tag_, N_, add_domain, gettext, ngettext = domain_functions( 'tracspamfilter', ('_', 'tag_', 'N_', 'add_domain', 'gettext', 'ngettext')) class RejectContent(TracError): """Exception raised when content is rejected by a filter.""" class IFilterStrategy(Interface): """Mainfilter class, mainly consisting of test() and train() function Filters using network access should return True for is_external() call. Any variable ending in "karma_points" is presented in the karma admin interface. """ def is_external(self): """Is this an service sending data to external servers. Return True if data is passed to external servers """ def test(req, author, content, ip): """Test the given content submission. Should return a `(points, reason)` tuple to affect the score of the submission, where `points` is an integer, and `reason` is a brief description of why the score is being affected. If the filter strategy does not want (or is not able) to effectively test the submission, it should return `None`. """ def train(req, author, content, ip, spam=True): """Train the filter by reporting a false negative or positive. The spam keyword argument is `True` if the content should be considered spam (a false negative), and `False` if the content was legitimate (a false positive). returns 0 (no training), 1 (training ok), -1 (training error) or -2 (missing preconditions, e.g. keys) """ class IRejectHandler(Interface): """Handle content rejection.""" def reject_content(req, reason): """Reject content. `reason` is a human readable message describing why the content was rejected. """ def get_strategy_name(strategy): try: name = strategy._name except Exception: name = strategy.__class__.__name__ if name.endswith("FilterStrategy"): name = name[:-14] return name