Skip to content

Commit

Permalink
Merge branch 'gonzobot' into gonzobot+pytest-leaks
Browse files Browse the repository at this point in the history
  • Loading branch information
linuxdaemon committed Jan 3, 2018
2 parents d726e0a + 6595c14 commit 38ad13d
Show file tree
Hide file tree
Showing 22 changed files with 158 additions and 126 deletions.
17 changes: 7 additions & 10 deletions cloudbot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from sqlalchemy.schema import MetaData
from watchdog.observers import Observer

from cloudbot.client import Client
from cloudbot.client import Client, CLIENTS
from cloudbot.clients.irc import IrcClient, irc_clean
from cloudbot.config import Config
from cloudbot.event import Event, CommandEvent, RegexEvent, EventType
Expand Down Expand Up @@ -151,15 +151,9 @@ def create_connections(self):
# strip all spaces and capitalization from the connection name
name = clean_name(config['name'])
nick = config['nick']
server = config['connection']['server']
port = config['connection'].get('port', 6667)
local_bind = (config['connection'].get('bind_addr', False), config['connection'].get('bind_port', 0))
if local_bind[0] is False:
local_bind = False

self.connections[name] = IrcClient(self, name, nick, config=config, channels=config['channels'],
server=server, port=port, use_ssl=config['connection'].get('ssl', False),
local_bind=local_bind)
_type = config.get("type", "irc")

self.connections[name] = CLIENTS[_type](self, name, nick, config=config, channels=config['channels'])
logger.debug("[{}] Created connection.".format(name))

@asyncio.coroutine
Expand Down Expand Up @@ -246,6 +240,9 @@ def add_hook(hook, _event, _run_before=False):
if halted:
return False

if hook.clients and _event.conn.type not in hook.clients:
return True

coro = self.plugin_manager.launch(hook, _event)
if _run_before:
run_before_tasks.append(coro)
Expand Down
17 changes: 17 additions & 0 deletions cloudbot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@

logger = logging.getLogger("cloudbot")

CLIENTS = {}


def client(_type):
def _decorate(cls):
CLIENTS[_type] = cls
cls._type = _type
return cls

return lambda cls: _decorate(cls)


class Client:
"""
Expand All @@ -22,6 +33,8 @@ class Client:
:type permissions: PermissionManager
"""

_type = None

def __init__(self, bot, name, nick, *, channels=None, config=None):
"""
:type bot: cloudbot.bot.CloudBot
Expand Down Expand Up @@ -155,3 +168,7 @@ def is_nick_valid(self, nick):
@property
def connected(self):
raise NotImplementedError

@property
def type(self):
return self._type
58 changes: 27 additions & 31 deletions cloudbot/clients/irc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@
from functools import partial
from ssl import SSLContext

from cloudbot.client import Client
from cloudbot.client import Client, client
from cloudbot.event import Event, EventType, IrcOutEvent
from cloudbot.util import async_util
from cloudbot.util.parsers.irc import Message

logger = logging.getLogger("cloudbot")

irc_prefix_re = re.compile(r":([^ ]*) ([^ ]*) (.*)")
irc_noprefix_re = re.compile(r"([^ ]*) (.*)")
irc_netmask_re = re.compile(r"([^!@]*)!([^@]*)@(.*)")
irc_param_re = re.compile(r"(?:^|(?<= ))(:.*|[^ ]+)")

irc_nick_re = re.compile(r'[A-Za-z0-9^{\}\[\]\-`_|\\]+')

irc_bad_chars = ''.join([chr(x) for x in list(range(0, 1)) + list(range(4, 32)) + list(range(127, 160))])
Expand Down Expand Up @@ -50,6 +45,7 @@ def decode(bytestring):
return bytestring.decode('utf-8', errors='ignore')


@client("irc")
class IrcClient(Client):
"""
An implementation of Client for IRC.
Expand All @@ -60,27 +56,26 @@ class IrcClient(Client):
:type _ignore_cert_errors: bool
"""

def __init__(self, bot, name, nick, *, channels=None, config=None,
server, port=6667, use_ssl=False, ignore_cert_errors=True, timeout=300, local_bind=False):
def __init__(self, bot, name, nick, *, channels=None, config=None):
"""
:type bot: cloudbot.bot.CloudBot
:type name: str
:type nick: str
:type channels: list[str]
:type config: dict[str, unknown]
:type server: str
:type port: int
:type use_ssl: bool
:type ignore_cert_errors: bool
:type timeout: int
"""
super().__init__(bot, name, nick, channels=channels, config=config)

self.use_ssl = use_ssl
self._ignore_cert_errors = ignore_cert_errors
self._timeout = timeout
self.server = server
self.port = port
self.use_ssl = config['connection'].get('ssl', False)
self._ignore_cert_errors = config['connection']['ignore_cert']
self._timeout = config['connection'].get('timeout', 300)
self.server = config['connection']['server']
self.port = config['connection'].get('port', 6667)

local_bind = (config['connection'].get('bind_addr', False), config['connection'].get('bind_port', 0))
if local_bind[0] is False:
local_bind = False

self.local_bind = local_bind
# create SSL context
if self.use_ssl:
Expand Down Expand Up @@ -109,16 +104,15 @@ def describe_server(self):

@asyncio.coroutine
def try_connect(self):
timeout = self.config["connection"].get("timeout", 30)
while True:
try:
yield from self.connect(timeout)
yield from self.connect(self._timeout)
except (asyncio.TimeoutError, OSError):
logger.exception("[%s] Error occurred while connecting", self.name)
else:
break

yield from asyncio.sleep(random.randrange(timeout))
yield from asyncio.sleep(random.randrange(self._timeout))

@asyncio.coroutine
def connect(self, timeout=None):
Expand Down Expand Up @@ -153,6 +147,7 @@ def connect(self, timeout=None):
tasks = [
self.bot.plugin_manager.launch(hook, Event(bot=self.bot, conn=self, hook=hook))
for hook in self.bot.plugin_manager.connect_hooks
if not hook.clients or self.type in hook.clients
]
# TODO stop connecting if a connect hook fails?
yield from asyncio.gather(*tasks)
Expand All @@ -177,7 +172,6 @@ def close(self):

def message(self, target, *messages):
for text in messages:
text = "".join(text.splitlines())
self.cmd("PRIVMSG", target, text)

def admin_log(self, text, console=True):
Expand All @@ -189,11 +183,9 @@ def admin_log(self, text, console=True):
logger.info("[%s|admin] %s", self.name, text)

def action(self, target, text):
text = "".join(text.splitlines())
self.ctcp(target, "ACTION", text)

def notice(self, target, text):
text = "".join(text.splitlines())
self.cmd("NOTICE", target, text)

def set_nick(self, nick):
Expand Down Expand Up @@ -239,22 +231,23 @@ def cmd(self, command, *params):
else:
self.send(command)

def send(self, line):
def send(self, line, log=True):
"""
Sends a raw IRC line
:type line: str
:type log: bool
"""
if not self._connected:
raise ValueError("Client must be connected to irc server to use send")
self.loop.call_soon_threadsafe(self._send, line)
self.loop.call_soon_threadsafe(self._send, line, log)

def _send(self, line):
def _send(self, line, log=True):
"""
Sends a raw IRC line unchecked. Doesn't do connected check, and is *not* threadsafe
:type line: str
:type log: bool
"""
logger.info("[{}] >> {}".format(self.name, line))
async_util.wrap_future(self._protocol.send(line), loop=self.loop)
async_util.wrap_future(self._protocol.send(line, log=log), loop=self.loop)

@property
def connected(self):
Expand Down Expand Up @@ -321,7 +314,7 @@ def eof_received(self):
return True

@asyncio.coroutine
def send(self, line):
def send(self, line, log=True):
# make sure we are connected before sending
if not self._connected:
yield from self._connected_future
Expand Down Expand Up @@ -357,6 +350,9 @@ def send(self, line):
# the line must be encoded before we send it, one of the sieves didn't encode it, fall back to the default
line = line.encode("utf-8", "replace")

if log:
logger.info("[{}|out] >> {!r}".format(self.conn.name, line))

self._transport.write(line)

def data_received(self, data):
Expand All @@ -381,7 +377,7 @@ def data_received(self, data):
# Reply to pings immediately

if command == "PING":
async_util.wrap_future(self.send("PONG " + command_params[-1]), loop=self.loop)
self.conn.send("PONG " + command_params[-1], log=False)

# Parse the command and params

Expand Down
7 changes: 7 additions & 0 deletions cloudbot/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,13 @@ def __init__(self, _type, plugin, func_hook):
self.action = func_hook.kwargs.pop("action", Action.CONTINUE)
self.priority = func_hook.kwargs.pop("priority", Priority.NORMAL)

clients = func_hook.kwargs.pop("clients", [])

if isinstance(clients, str):
clients = [clients]

self.clients = clients

if func_hook.kwargs:
# we should have popped all the args, so warn if there are any left
logger.warning("Ignoring extra args {} from {}".format(func_hook.kwargs, self.description))
Expand Down
2 changes: 1 addition & 1 deletion cloudbot/util/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,4 @@ def _convert(string):
elif formatting in IRC_FORMATTING_DICT:
ret += get_format(formatting)

return ret.strip()
return ret
4 changes: 3 additions & 1 deletion config.default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
"connections": [
{
"name": "esper",
"type": "irc",
"connection": {
"server": "irc.esper.net",
"port": 6667,
"ssl": false,
"ignore_cert": true,
"password": ""
"password": "",
"timeout": 300
},
"nick": "MyCloudBot",
"user": "cloudbot",
Expand Down
1 change: 1 addition & 0 deletions data/food/beer.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"Rauchbier",
"Schwarzbier",
"Vienna Lager",
"Victoria Bitter",
"Happoshu"
]
}
Expand Down
1 change: 1 addition & 0 deletions data/food/burger.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"side of sesame ginger coleslaw",
"side of macaroni and cheese",
"a large chilly soda",
"a side of authentic 'strayan prawns fresh off the barbie",
"a cup of BBQ dip"
]
}
Expand Down
1 change: 1 addition & 0 deletions data/food/cookies.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"Chocolate Oatmeal Fudge",
"Toffee Peanut",
"Danish Sugar",
"Tim Tam",
"Triple Chocolate",
"Oreo"
],
Expand Down
1 change: 1 addition & 0 deletions data/food/pie.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"rhubarb pie",
"pie chart",
"broccoli quiche",
"Neenish tart",
"potato knish"
],
"extra": [
Expand Down
2 changes: 1 addition & 1 deletion plugins/cats.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def cats(reply, bot):
"""- gets a fucking fact about cats."""
r = get_data('https://catfact.ninja/fact', reply, bot, params={'max_length': 100})
json = r.json()
response = json['facts']
response = json['fact']
return response


Expand Down
19 changes: 15 additions & 4 deletions plugins/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from cloudbot import hook
from cloudbot.event import CommandEvent
from cloudbot.util import database
from cloudbot.util.formatting import chunk_str
from cloudbot.util.formatting import chunk_str, pluralize

commands = Table(
'chain_commands',
Expand Down Expand Up @@ -84,15 +84,26 @@ def chainallow(text, db, notice_doc, bot):

values['allowed'] = allow

db.execute(commands.insert().values(**values))
updated = True
res = db.execute(commands.update().values(**values).where(commands.c.hook == hook_name))
if res.rowcount == 0:
updated = False
db.execute(commands.insert().values(**values))

db.commit()
load_cache(db)
return "Add '{}' as an allowed command".format(hook_name)
if updated:
return "Updated state of '{}' in chainallow to allowed={}".format(hook_name, allow_cache.get(hook_name))

if allow_cache.get(hook_name):
return "Added '{}' as an allowed command".format(hook_name)

return "Added '{}' as a denied command".format(hook_name)
elif subcmd == "del":
res = db.execute(commands.delete().where(commands.c.hook == hook_name))
db.commit()
load_cache(db)
return "Deleted {} rows.".format(res.rowcount)
return "Deleted {}.".format(pluralize(res.rowcount, "row"))
else:
return notice_doc()

Expand Down
2 changes: 1 addition & 1 deletion plugins/chan_track.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ def on_away(conn, nick, irc_paramlist):

@hook.irc_raw('352')
def on_who(conn, irc_paramlist):
_, ident, host, server, nick, status, realname = irc_paramlist
_, _, ident, host, server, nick, status, realname = irc_paramlist
realname = realname.split(None, 1)[1]
user = conn.memory["users"][nick]
status = list(status)
Expand Down
2 changes: 1 addition & 1 deletion plugins/core/cap.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from cloudbot.util.parsers.irc import CapList


@hook.connect(priority=-10)
@hook.connect(priority=-10, clients="irc")
def send_cap_ls(conn):
conn.cmd("CAP", "LS", "302")
conn.memory.setdefault("available_caps", set()).clear()
Expand Down
4 changes: 2 additions & 2 deletions plugins/core/core_connect.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from cloudbot import hook


@hook.connect(priority=0)
@hook.connect(priority=0, clients="irc")
def conn_pass(conn):
conn.set_pass(conn.config["connection"].get("password"))

Expand All @@ -11,7 +11,7 @@ def conn_nick(conn):
conn.set_nick(conn.nick)


@hook.connect(priority=20)
@hook.connect(priority=20, clients="irc")
def conn_user(conn):
conn.cmd(
"USER", conn.config.get('user', 'cloudbot'), "3", "*",
Expand Down
Loading

0 comments on commit 38ad13d

Please sign in to comment.