Kaynağa Gözat

Merge branch 'feature/permissions' into develop (#2)

tags/v0.1^2
Ben Kurtovic 12 yıl önce
ebeveyn
işleme
39740a65cd
11 değiştirilmiş dosya ile 496 ekleme ve 97 silme
  1. +141
    -0
      earwigbot/commands/access.py
  2. +1
    -1
      earwigbot/commands/chanops.py
  3. +2
    -2
      earwigbot/commands/git_command.py
  4. +1
    -1
      earwigbot/commands/quit.py
  5. +1
    -1
      earwigbot/commands/threads.py
  6. +20
    -89
      earwigbot/config/__init__.py
  7. +51
    -0
      earwigbot/config/formatter.py
  8. +97
    -0
      earwigbot/config/node.py
  9. +174
    -0
      earwigbot/config/permissions.py
  10. +7
    -2
      earwigbot/irc/connection.py
  11. +1
    -1
      earwigbot/wiki/copyvios/exclusions.py

+ 141
- 0
earwigbot/commands/access.py Dosyayı Görüntüle

@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2012 Ben Kurtovic <ben.kurtovic@verizon.net>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import re

from earwigbot.commands import Command

class Access(Command):
"""Control and get info on who can access the bot."""
name = "access"
commands = ["access", "permission", "permissions", "perm", "perms"]

def process(self, data):
if not data.args:
self.reply(data, "Subcommands are self, list, add, remove.")
return
db = self.config.irc["permissions"]
if data.args[0] == "self":
self.do_self(data, db)
elif data.args[0] == "list":
self.do_list(data, db)
elif data.args[0] == "add":
self.do_add(data, db)
elif data.args[0] == "remove":
self.do_remove(data, db)
else:
msg = "Unknown subcommand \x0303{0}\x0F.".format(data.args[0])
self.reply(data, msg)

def do_self(self, data, db):
if db.is_owner(data):
msg = "You are a bot owner (matching rule \x0302{0}\x0F)."
self.reply(data, msg.format(db.is_owner(data)))
elif db.is_admin(data):
msg = "You are a bot admin (matching rule \x0302{0}\x0F)."
self.reply(data, msg.format(db.is_admin(data)))
else:
self.reply(data, "You do not match any bot access rules.")

def do_list(self, data, db):
if len(data.args) > 1:
if data.args[1] in ["owner", "owners"]:
name, rules = "owners", db.data.get(db.OWNERS)
elif data.args[1] in ["admin", "admins"]:
name, rules = "admins", db.data.get(db.ADMINS)
else:
msg = "Unknown access level \x0302{0}\x0F."
self.reply(data, msg.format(data.args[1]))
return
if rules:
msg = "Bot {0}: {1}.".format(name, ", ".join(map(str, rules)))
else:
msg = "No bot {0}.".format(name)
self.reply(data, msg)
else:
owners = len(db.data.get(db.OWNERS, []))
admins = len(db.data.get(db.ADMINS, []))
msg = "There are {0} bot owners and {1} bot admins. Use '!{2} list owners' or '!{2} list admins' for details."
self.reply(data, msg.format(owners, admins, data.command))

def do_add(self, data, db):
user = self.get_user_from_args(data)
if user:
nick, ident, host = user
if data.args[1] in ["owner", "owners"]:
name, level, adder = "owner", db.OWNER, db.add_owner
else:
name, level, adder = "admin", db.ADMIN, db.add_admin
if db.has_exact(nick, ident, host, level):
rule = "{0}!{1}@{2}".format(nick, ident, host)
msg = "\x0302{0}\x0F is already a bot {1}.".format(rule, name)
self.reply(data, msg)
else:
rule = adder(nick, ident, host)
msg = "Added bot {0} \x0302{1}\x0F.".format(name, rule)
self.reply(data, msg)

def do_remove(self, data, db):
user = self.get_user_from_args(data)
if user:
nick, ident, host = user
if data.args[1] in ["owner", "owners"]:
name, level, rmver = "owner", db.OWNER, db.remove_owner
else:
name, level, rmver = "admin", db.ADMIN, db.remove_admin
rule = rmver(nick, ident, host)
if rule:
msg = "Removed bot {0} \x0302{1}\x0F.".format(name, rule)
self.reply(data, msg)
else:
rule = "{0}!{1}@{2}".format(nick, ident, host)
msg = "No bot {0} matching \x0302{1}\x0F.".format(name, rule)
self.reply(data, msg)

def get_user_from_args(self, data):
if not db.is_owner(data):
msg = "You must be a bot owner to add users to the access list."
self.reply(data, msg)
return
levels = ["owner", "owners", "admin", "admins"]
if len(data.args) == 1 or data.args[1] not in levels:
msg = "Please specify an access level ('owners' or 'admins')."
self.reply(data, msg)
return
if len(data.args) == 2:
self.no_arg_error(data)
return
if "nick" in data.kwargs or "ident" in kwargs or "host" in kwargs:
nick = data.kwargs.get("nick", "*")
ident = data.kwargs.get("ident", "*")
host = data.kwargs.get("host", "*")
return nick, ident, host
user = re.match(r"(.*?)!(.*?)@(.*?)$", data.args[2])
if not user:
self.no_arg_error(data)
return
return user.group(1), user.group(2), user.group(3)

def no_arg_error(self, data):
msg = 'Please specify a user, either as "\x0302nick\x0F!\x0302ident\x0F@\x0302host\x0F"'
msg += ' or "nick=\x0302nick\x0F, ident=\x0302ident\x0F, host=\x0302host\x0F".'
self.reply(data, msg)

+ 1
- 1
earwigbot/commands/chanops.py Dosyayı Görüntüle

@@ -36,7 +36,7 @@ class ChanOps(Command):
de_escalate = data.command in ["devoice", "deop"]
if de_escalate and (not data.args or data.args[0] == data.nick):
target = data.nick
elif data.host not in self.config.irc["permissions"]["admins"]:
elif not self.config.irc["permissions"].is_admin(data):
self.reply(data, "You must be a bot admin to use this command.")
return



+ 2
- 2
earwigbot/commands/git_command.py Dosyayı Görüntüle

@@ -39,7 +39,7 @@ class Git(Command):

def process(self, data):
self.data = data
if data.host not in self.config.irc["permissions"]["owners"]:
if not self.config.irc["permissions"].is_owner(data):
msg = "You must be a bot owner to use this command."
self.reply(data, msg)
return
@@ -78,7 +78,7 @@ class Git(Command):
elif command == "status":
self.do_status()
else: # They asked us to do something we don't know
msg = "Ynknown argument: \x0303{0}\x0F.".format(data.args[0])
msg = "Unknown argument: \x0303{0}\x0F.".format(data.args[0])
self.reply(data, msg)

def get_repos(self):


+ 1
- 1
earwigbot/commands/quit.py Dosyayı Görüntüle

@@ -29,7 +29,7 @@ class Quit(Command):
commands = ["quit", "restart", "reload"]

def process(self, data):
if data.host not in self.config.irc["permissions"]["owners"]:
if not self.config.irc["permissions"].is_owner(data):
self.reply(data, "You must be a bot owner to use this command.")
return
if data.command == "quit":


+ 1
- 1
earwigbot/commands/threads.py Dosyayı Görüntüle

@@ -32,7 +32,7 @@ class Threads(Command):

def process(self, data):
self.data = data
if data.host not in self.config.irc["permissions"]["owners"]:
if not self.config.irc["permissions"].is_owner(data):
msg = "You must be a bot owner to use this command."
self.reply(data, msg)
return


earwigbot/config.py → earwigbot/config/__init__.py Dosyayı Görüntüle

@@ -41,6 +41,9 @@ try:
except ImportError:
yaml = None

from earwigbot.config.formatter import BotFormatter
from earwigbot.config.node import ConfigNode
from earwigbot.config.permissions import PermissionsDB
from earwigbot.exceptions import NoConfigError

__all__ = ["BotConfig"]
@@ -75,17 +78,19 @@ class BotConfig(object):
def __init__(self, root_dir, level):
self._root_dir = root_dir
self._logging_level = level
self._config_path = path.join(self._root_dir, "config.yml")
self._log_dir = path.join(self._root_dir, "logs")
self._config_path = path.join(self.root_dir, "config.yml")
self._log_dir = path.join(self.root_dir, "logs")
perms_file = path.join(self.root_dir, "permissions.db")
self._permissions = PermissionsDB(perms_file)
self._decryption_cipher = None
self._data = None

self._components = _ConfigNode()
self._wiki = _ConfigNode()
self._irc = _ConfigNode()
self._commands = _ConfigNode()
self._tasks = _ConfigNode()
self._metadata = _ConfigNode()
self._components = ConfigNode()
self._wiki = ConfigNode()
self._irc = ConfigNode()
self._commands = ConfigNode()
self._tasks = ConfigNode()
self._metadata = ConfigNode()

self._nodes = [self._components, self._wiki, self._irc, self._commands,
self._tasks, self._metadata]
@@ -123,8 +128,8 @@ class BotConfig(object):
logger = logging.getLogger("earwigbot")
logger.handlers = [] # Remove any handlers already attached to us
logger.setLevel(logging.DEBUG)
color_formatter = _BotFormatter(color=True)
formatter = _BotFormatter()
color_formatter = BotFormatter(color=True)
formatter = BotFormatter()

if self.metadata.get("enableLogging"):
hand = logging.handlers.TimedRotatingFileHandler
@@ -253,7 +258,7 @@ class BotConfig(object):
exit.

Data from the config file is stored in six
:py:class:`~earwigbot.config._ConfigNode`\ s (:py:attr:`components`,
:py:class:`~earwigbot.config.ConfigNode`\ s (:py:attr:`components`,
:py:attr:`wiki`, :py:attr:`irc`, :py:attr:`commands`, :py:attr:`tasks`,
:py:attr:`metadata`) for easy access (as well as the lower-level
:py:attr:`data` attribute). If passwords are encrypted, we'll use
@@ -289,6 +294,10 @@ class BotConfig(object):
for node, nodes in self._decryptable_nodes:
self._decrypt(node, nodes)

if self.irc:
self.irc["permissions"] = self._permissions
self._permissions.load()

def decrypt(self, node, *nodes):
"""Decrypt an object in our config tree.

@@ -342,81 +351,3 @@ class BotConfig(object):
pass

return tasks


class _ConfigNode(object):
def __iter__(self):
for key in self.__dict__:
yield key

def __getitem__(self, item):
return self.__dict__.__getitem__(item)

def _dump(self):
data = self.__dict__.copy()
for key, val in data.iteritems():
if isinstance(val, _ConfigNode):
data[key] = val._dump()
return data

def _load(self, data):
self.__dict__ = data.copy()

def _decrypt(self, cipher, intermediates, item):
base = self.__dict__
for inter in intermediates:
try:
base = base[inter]
except KeyError:
return
if item in base:
ciphertext = base[item].decode("hex")
base[item] = cipher.decrypt(ciphertext).rstrip("\x00")

def get(self, *args, **kwargs):
return self.__dict__.get(*args, **kwargs)

def keys(self):
return self.__dict__.keys()

def values(self):
return self.__dict__.values()

def items(self):
return self.__dict__.items()

def iterkeys(self):
return self.__dict__.iterkeys()

def itervalues(self):
return self.__dict__.itervalues()

def iteritems(self):
return self.__dict__.iteritems()


class _BotFormatter(logging.Formatter):
def __init__(self, color=False):
self._format = super(_BotFormatter, self).format
if color:
fmt = "[%(asctime)s %(lvl)s] %(name)s: %(message)s"
self.format = lambda record: self._format(self.format_color(record))
else:
fmt = "[%(asctime)s %(levelname)-8s] %(name)s: %(message)s"
self.format = self._format
datefmt = "%Y-%m-%d %H:%M:%S"
super(_BotFormatter, self).__init__(fmt=fmt, datefmt=datefmt)

def format_color(self, record):
l = record.levelname.ljust(8)
if record.levelno == logging.DEBUG:
record.lvl = l.join(("\x1b[34m", "\x1b[0m")) # Blue
if record.levelno == logging.INFO:
record.lvl = l.join(("\x1b[32m", "\x1b[0m")) # Green
if record.levelno == logging.WARNING:
record.lvl = l.join(("\x1b[33m", "\x1b[0m")) # Yellow
if record.levelno == logging.ERROR:
record.lvl = l.join(("\x1b[31m", "\x1b[0m")) # Red
if record.levelno == logging.CRITICAL:
record.lvl = l.join(("\x1b[1m\x1b[31m", "\x1b[0m")) # Bold red
return record

+ 51
- 0
earwigbot/config/formatter.py Dosyayı Görüntüle

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2012 Ben Kurtovic <ben.kurtovic@verizon.net>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import logging

__all__ = ["BotFormatter"]

class BotFormatter(logging.Formatter):
def __init__(self, color=False):
self._format = super(BotFormatter, self).format
if color:
fmt = "[%(asctime)s %(lvl)s] %(name)s: %(message)s"
self.format = lambda rec: self._format(self.format_color(rec))
else:
fmt = "[%(asctime)s %(levelname)-8s] %(name)s: %(message)s"
self.format = self._format
datefmt = "%Y-%m-%d %H:%M:%S"
super(BotFormatter, self).__init__(fmt=fmt, datefmt=datefmt)

def format_color(self, record):
l = record.levelname.ljust(8)
if record.levelno == logging.DEBUG:
record.lvl = l.join(("\x1b[34m", "\x1b[0m")) # Blue
if record.levelno == logging.INFO:
record.lvl = l.join(("\x1b[32m", "\x1b[0m")) # Green
if record.levelno == logging.WARNING:
record.lvl = l.join(("\x1b[33m", "\x1b[0m")) # Yellow
if record.levelno == logging.ERROR:
record.lvl = l.join(("\x1b[31m", "\x1b[0m")) # Red
if record.levelno == logging.CRITICAL:
record.lvl = l.join(("\x1b[1m\x1b[31m", "\x1b[0m")) # Bold red
return record

+ 97
- 0
earwigbot/config/node.py Dosyayı Görüntüle

@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2012 Ben Kurtovic <ben.kurtovic@verizon.net>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

__all__ = ["ConfigNode"]

class ConfigNode(object):
def __init__(self):
self._data = {}

def __repr__(self):
return self._data

def __nonzero__(self):
return bool(self._data)

def __len__(self):
retrun len(self._data)

def __getitem__(self, key):
return self._data[key]

def __setitem__(self, key, item):
self._data[key] = item

def __getattr__(self, key):
return self._data[key]

def __setattr__(self, key, item):
self._data[key] = item

def __iter__(self):
for key in self._data:
yield key

def __contains__(self, item):
return item in self._data

def _dump(self):
data = self._data.copy()
for key, val in data.iteritems():
if isinstance(val, ConfigNode):
data[key] = val._dump()
return data

def _load(self, data):
self._data = data.copy()

def _decrypt(self, cipher, intermediates, item):
base = self._data
for inter in intermediates:
try:
base = base[inter]
except KeyError:
return
if item in base:
ciphertext = base[item].decode("hex")
base[item] = cipher.decrypt(ciphertext).rstrip("\x00")

def get(self, *args, **kwargs):
return self._data.get(*args, **kwargs)

def keys(self):
return self._data.keys()

def values(self):
return self._data.values()

def items(self):
return self._data.items()

def iterkeys(self):
return self._data.iterkeys()

def itervalues(self):
return self._data.itervalues()

def iteritems(self):
return self.__dict__.iteritems()

+ 174
- 0
earwigbot/config/permissions.py Dosyayı Görüntüle

@@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2012 Ben Kurtovic <ben.kurtovic@verizon.net>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from fnmatch import fnmatch
import sqlite3 as sqlite
from threading import Lock

__all__ = ["PermissionsDB"]

class PermissionsDB(object):
"""
**EarwigBot: Permissions Database Manager**

Controls the :file:`permissions.db` file, which stores the bot's owners and
admins for the purposes of using certain dangerous IRC commands.
"""
ADMIN = 1
OWNER = 2

def __init__(self, dbfile):
self._dbfile = dbfile
self._db_access_lock = Lock()
self._data = {}

def __repr__(self):
"""Return the canonical string representation of the PermissionsDB."""
res = "PermissionsDB(dbfile={0!r})"
return res.format(self._dbfile)

def __str__(self):
"""Return a nice string representation of the PermissionsDB."""
return "<PermissionsDB at {0}>".format(self._dbfile)

def _create(self, conn):
"""Initialize the permissions database with its necessary tables."""
query = """CREATE TABLE users (user_nick, user_ident, user_host,
user_rank)"""
conn.execute(query)

def _is_rank(self, user, rank):
"""Return True if the given user has the given rank, else False."""
try:
for rule in self._data[rank]:
if user in rule:
return rule
except KeyError:
pass
return False

def _set_rank(self, user, rank):
"""Add a User to the database under a given rank."""
try:
self._data[rank].append(user)
except KeyError:
self._data[rank] = [user]
query = "INSERT INTO users VALUES (?, ?, ?, ?)"
with sqlite.connect(self._dbfile) as conn, self._db_access_lock:
conn.execute(query, (user.nick, user.ident, user.host, rank))
return user

def _del_rank(self, user, rank):
"""Remove a User from the database."""
query = """DELETE FROM users WHERE user_nick = ? AND user_ident = ? AND
user_host = ? AND user_rank = ?"""
try:
for rule in self._data[rank]:
if user in rule:
with self._db_access_lock:
with sqlite.connect(self._dbfile) as conn:
args = (user.nick, user.ident, user.host, rank)
conn.execute(query, args)
return rule
except KeyError:
pass
return None

@property
def data(self):
"""A dict of all entries in the permissions database."""
return self._data

def load(self):
"""Load permissions from an existing database, or create a new one."""
query = "SELECT user_nick, user_ident, user_host, user_rank FROM users"
self._data = {}
with sqlite.connect(self._dbfile) as conn, self._db_access_lock:
try:
for nick, ident, host, rank in conn.execute(query):
try:
self._data[rank].append(_User(nick, ident, host))
except KeyError:
self._data[rank] = [_User(nick, ident, host)]
except sqlite.OperationalError:
self._create(conn)

def has_exact(self, nick="*", ident="*", host="*", rule):
"""Return ``True`` if there is an exact match for this rule."""
try:
for usr in self._data[rank]:
if nick != usr.nick or ident != usr.ident or host != usr.host:
continue
return usr
except KeyError:
pass
return False

def is_admin(self, data):
"""Return ``True`` if the given user is a bot admin, else ``False``."""
user = _User(data.nick, data.ident, data.host)
return self._is_rank(user, rank=self.ADMIN)

def is_owner(self, data):
"""Return ``True`` if the given user is a bot owner, else ``False``."""
user = _User(data.nick, data.ident, data.host)
return self._is_rank(user, rank=self.OWNER)

def add_admin(self, nick="*", ident="*", host="*"):
"""Add a nick/ident/host combo to the bot admins list."""
return self._set_rank(_User(nick, ident, host), rank=self.ADMIN)

def add_owner(self, nick="*", ident="*", host="*"):
"""Add a nick/ident/host combo to the bot owners list."""
return self._set_rank(_User(nick, ident, host), rank=self.OWNER)

def remove_admin(self, nick="*", ident="*", host="*"):
"""Remove a nick/ident/host combo to the bot admins list."""
return self._del_rank(_User(nick, ident, host), rank=self.ADMIN)

def remove_owner(self, nick="*", ident="*", host="*"):
"""Remove a nick/ident/host combo to the bot owners list."""
return self._del_rank(_User(nick, ident, host), rank=self.OWNER)


class _User(object):
"""A class that represents an IRC user for the purpose of testing rules."""
def __init__(self, nick, ident, host):
self.nick = nick
self.ident = ident
self.host = host

def __repr__(self):
"""Return the canonical string representation of the User."""
res = "_User(nick={0!r}, ident={1!r}, host={2!r})"
return res.format(self.nick, self.ident, self.host)

def __str__(self):
"""Return a nice string representation of the User."""
return "{0}!{1}@{2}".format(self.nick, self.ident, self.host)

def __contains__(self, user):
if fnmatch(user.nick, self.nick):
if fnmatch(user.ident, self.ident):
if fnmatch(user.host, self.host):
return True
return False

+ 7
- 2
earwigbot/irc/connection.py Dosyayı Görüntüle

@@ -43,6 +43,7 @@ class IRCConnection(object):
self._send_lock = Lock()

self._last_recv = time()
self._last_send = 0
self._last_ping = 0

def __repr__(self):
@@ -87,6 +88,9 @@ class IRCConnection(object):
def _send(self, msg, hidelog=False):
"""Send data to the server."""
with self._send_lock:
time_since_last = time() - self._last_send
if time_since_last < 0.75:
time.sleep(0.75 - time_since_last)
try:
self._sock.sendall(msg + "\r\n")
except socket.error:
@@ -94,6 +98,7 @@ class IRCConnection(object):
else:
if not hidelog:
self.logger.debug(msg)
self._last_send = time()

def _split(self, msgs, maxlen, maxsplits=3):
"""Split a large message into multiple messages smaller than maxlen."""
@@ -158,7 +163,7 @@ class IRCConnection(object):

def say(self, target, msg, hidelog=False):
"""Send a private message to a target on the server."""
for msg in self._split(msg, 500 - len(target)):
for msg in self._split(msg, 400):
msg = "PRIVMSG {0} :{1}".format(target, msg)
self._send(msg, hidelog)

@@ -177,7 +182,7 @@ class IRCConnection(object):

def notice(self, target, msg, hidelog=False):
"""Send a notice to a target on the server."""
for msg in self._split(msg, 500 - len(target)):
for msg in self._split(msg, 400):
msg = "NOTICE {0} :{1}".format(target, msg)
self._send(msg, hidelog)



+ 1
- 1
earwigbot/wiki/copyvios/exclusions.py Dosyayı Görüntüle

@@ -44,7 +44,7 @@ class ExclusionsDB(object):
"""
**EarwigBot: Wiki Toolset: Exclusions Database Manager**

Controls the :file:`.exclusions.db` file, which stores URLs excluded from
Controls the :file:`exclusions.db` file, which stores URLs excluded from
copyright violation checks on account of being known mirrors, for example.
"""



Yükleniyor…
İptal
Kaydet