Skip to content

Commit 26d20aa

Browse files
authored
Merge branch 'gonzobot' into gonzobot+fix-badwords
2 parents e9ec901 + 4542cd1 commit 26d20aa

136 files changed

Lines changed: 779 additions & 685 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ install:
1111
- "pip install -r ./travis/requirements.txt"
1212

1313
script:
14-
- "python ./travis/test_json.py"
1514
- "git diff --diff-filter=d --name-only ${TRAVIS_COMMIT_RANGE} | grep -i '\\.py$' | xargs -r pylint --rcfile=travis/pylintrc"
1615
- "py.test . -v --cov . --cov-report term-missing"
1716

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ The following guidelines for contribution should be followed if you want to subm
2222

2323
* You need a [GitHub account](https://github.com/signup/free)
2424
* Submit an [issue ticket](https://github.com/ClouDev/CloudBot/issues) for your issue if there is no one yet.
25-
* Try to describe the issue and include steps to reproduce if it's a bug.
25+
* Try to describe the issue and include steps to reproduce if it's a bug.
2626
* If you are able and want to fix this, fork the repository on GitHub
2727

2828
## Make Changes
2929

30-
* In your forked repository, create a topic branch for your upcoming patch. (optional)
30+
* In your forked repository, create a topic branch for your upcoming patch. (optional)
3131
* Make sure you stick to the coding style that is used already.
3232
* Make use of the [`.editorconfig`](http://editorconfig.org/) file.
3333
* Make commits that make sense and describe them properly.
@@ -38,7 +38,7 @@ The following guidelines for contribution should be followed if you want to subm
3838

3939
* Push your changes to a topic branch in your fork of the repository.
4040
* Open a pull request to the original repository and choose the `python3.4` branch.
41-
_Advanced users may use [`hub`](https://github.com/defunkt/hub#git-pull-request) gem for that._
41+
_Advanced users may use [`hub`](https://github.com/defunkt/hub#git-pull-request) gem for that._
4242
* If not done in commit messages (which you really should do) please reference and update your issue with the code changes. But _please do not close the issue yourself_.
4343
_Notice: You can [turn your previously filed issues into a pull-request here](http://issue2pr.herokuapp.com/)._
4444

cloudbot/__init__.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212

1313
__version__ = "1.0.9"
1414

15-
__all__ = ["clients", "util", "bot", "client", "config", "event", "hook", "permissions", "plugin", "reloader", "logging_dir"]
15+
__all__ = ["clients", "util", "bot", "client", "config", "event", "hook", "permissions", "plugin", "reloader",
16+
"logging_dir"]
1617

1718

1819
def _setup():
@@ -23,6 +24,8 @@ def _setup():
2324
else:
2425
logging_config = {}
2526

27+
file_log = logging_config.get("file_log", False)
28+
2629
global logging_dir
2730
logging_dir = os.path.join(os.path.abspath(os.path.curdir), "logs")
2831

@@ -49,25 +52,29 @@ def _setup():
4952
"formatter": "brief",
5053
"level": "INFO",
5154
"stream": "ext://sys.stdout"
52-
},
53-
"file": {
54-
"class": "logging.handlers.RotatingFileHandler",
55-
"maxBytes": 1000000,
56-
"backupCount": 5,
57-
"formatter": "full",
58-
"level": "INFO",
59-
"encoding": "utf-8",
60-
"filename": os.path.join(logging_dir, "bot.log")
6155
}
6256
},
6357
"loggers": {
6458
"cloudbot": {
6559
"level": "DEBUG",
66-
"handlers": ["console", "file"]
60+
"handlers": ["console"]
6761
}
6862
}
6963
}
7064

65+
if file_log:
66+
dict_config["handlers"]["file"] = {
67+
"class": "logging.handlers.RotatingFileHandler",
68+
"maxBytes": 1000000,
69+
"backupCount": 5,
70+
"formatter": "full",
71+
"level": "INFO",
72+
"encoding": "utf-8",
73+
"filename": os.path.join(logging_dir, "bot.log")
74+
}
75+
76+
dict_config["loggers"]["cloudbot"]["handlers"].append("file")
77+
7178
if logging_config.get("console_debug", False):
7279
dict_config["handlers"]["console"]["level"] = "DEBUG"
7380
dict_config["loggers"]["asyncio"] = {
@@ -89,4 +96,5 @@ def _setup():
8996

9097
logging.config.dictConfig(dict_config)
9198

99+
92100
_setup()

cloudbot/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def main():
4141
# define closure for signal handling
4242
# The handler is called with two arguments: the signal number and the current stack frame
4343
# These parameters should NOT be removed
44+
# noinspection PyUnusedLocal
4445
def exit_gracefully(signum, frame):
4546
nonlocal stopped_while_restarting
4647
if not _bot:

cloudbot/bot.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from sqlalchemy.schema import MetaData
1414
from watchdog.observers import Observer
1515

16-
from cloudbot.client import Client
16+
from cloudbot.client import Client, CLIENTS
1717
from cloudbot.clients.irc import IrcClient, irc_clean
1818
from cloudbot.config import Config
1919
from cloudbot.event import Event, CommandEvent, RegexEvent, EventType
@@ -27,6 +27,7 @@
2727

2828
web_installed = True
2929
except ImportError:
30+
WebInterface = None
3031
web_installed = False
3132

3233
logger = logging.getLogger("cloudbot")
@@ -44,7 +45,7 @@ class CloudBot:
4445
"""
4546
:type start_time: float
4647
:type running: bool
47-
:type connections: list[Client | IrcClient]
48+
:type connections: dict[str, Client]
4849
:type data_dir: bytes
4950
:type config: core.config.Config
5051
:type plugin_manager: PluginManager
@@ -150,15 +151,9 @@ def create_connections(self):
150151
# strip all spaces and capitalization from the connection name
151152
name = clean_name(config['name'])
152153
nick = config['nick']
153-
server = config['connection']['server']
154-
port = config['connection'].get('port', 6667)
155-
local_bind = (config['connection'].get('bind_addr', False), config['connection'].get('bind_port', 0))
156-
if local_bind[0] is False:
157-
local_bind = False
158-
159-
self.connections[name] = IrcClient(self, name, nick, config=config, channels=config['channels'],
160-
server=server, port=port, use_ssl=config['connection'].get('ssl', False),
161-
local_bind=local_bind)
154+
_type = config.get("type", "irc")
155+
156+
self.connections[name] = CLIENTS[_type](self, name, nick, config=config, channels=config['channels'])
162157
logger.debug("[{}] Created connection.".format(name))
163158

164159
@asyncio.coroutine
@@ -245,6 +240,9 @@ def add_hook(hook, _event, _run_before=False):
245240
if halted:
246241
return False
247242

243+
if hook.clients and _event.conn.type not in hook.clients:
244+
return True
245+
248246
coro = self.plugin_manager.launch(hook, _event)
249247
if _run_before:
250248
run_before_tasks.append(coro)

cloudbot/client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@
77

88
logger = logging.getLogger("cloudbot")
99

10+
CLIENTS = {}
11+
12+
13+
def client(_type):
14+
def _decorate(cls):
15+
CLIENTS[_type] = cls
16+
cls._type = _type
17+
return cls
18+
19+
return lambda cls: _decorate(cls)
20+
1021

1122
class Client:
1223
"""
@@ -22,6 +33,8 @@ class Client:
2233
:type permissions: PermissionManager
2334
"""
2435

36+
_type = None
37+
2538
def __init__(self, bot, name, nick, *, channels=None, config=None):
2639
"""
2740
:type bot: cloudbot.bot.CloudBot
@@ -155,3 +168,7 @@ def is_nick_valid(self, nick):
155168
@property
156169
def connected(self):
157170
raise NotImplementedError
171+
172+
@property
173+
def type(self):
174+
return self._type

cloudbot/clients/irc.py

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,13 @@
77
from functools import partial
88
from ssl import SSLContext
99

10-
from cloudbot.client import Client
10+
from cloudbot.client import Client, client
1111
from cloudbot.event import Event, EventType, IrcOutEvent
1212
from cloudbot.util import async_util
1313
from cloudbot.util.parsers.irc import Message
1414

1515
logger = logging.getLogger("cloudbot")
1616

17-
irc_prefix_re = re.compile(r":([^ ]*) ([^ ]*) (.*)")
18-
irc_noprefix_re = re.compile(r"([^ ]*) (.*)")
19-
irc_netmask_re = re.compile(r"([^!@]*)!([^@]*)@(.*)")
20-
irc_param_re = re.compile(r"(?:^|(?<= ))(:.*|[^ ]+)")
21-
2217
irc_nick_re = re.compile(r'[A-Za-z0-9^{\}\[\]\-`_|\\]+')
2318

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

5247

48+
@client("irc")
5349
class IrcClient(Client):
5450
"""
5551
An implementation of Client for IRC.
@@ -60,27 +56,26 @@ class IrcClient(Client):
6056
:type _ignore_cert_errors: bool
6157
"""
6258

63-
def __init__(self, bot, name, nick, *, channels=None, config=None,
64-
server, port=6667, use_ssl=False, ignore_cert_errors=True, timeout=300, local_bind=False):
59+
def __init__(self, bot, name, nick, *, channels=None, config=None):
6560
"""
6661
:type bot: cloudbot.bot.CloudBot
6762
:type name: str
6863
:type nick: str
6964
:type channels: list[str]
7065
:type config: dict[str, unknown]
71-
:type server: str
72-
:type port: int
73-
:type use_ssl: bool
74-
:type ignore_cert_errors: bool
75-
:type timeout: int
7666
"""
7767
super().__init__(bot, name, nick, channels=channels, config=config)
7868

79-
self.use_ssl = use_ssl
80-
self._ignore_cert_errors = ignore_cert_errors
81-
self._timeout = timeout
82-
self.server = server
83-
self.port = port
69+
self.use_ssl = config['connection'].get('ssl', False)
70+
self._ignore_cert_errors = config['connection']['ignore_cert']
71+
self._timeout = config['connection'].get('timeout', 300)
72+
self.server = config['connection']['server']
73+
self.port = config['connection'].get('port', 6667)
74+
75+
local_bind = (config['connection'].get('bind_addr', False), config['connection'].get('bind_port', 0))
76+
if local_bind[0] is False:
77+
local_bind = False
78+
8479
self.local_bind = local_bind
8580
# create SSL context
8681
if self.use_ssl:
@@ -109,16 +104,15 @@ def describe_server(self):
109104

110105
@asyncio.coroutine
111106
def try_connect(self):
112-
timeout = self.config["connection"].get("timeout", 30)
113107
while True:
114108
try:
115-
yield from self.connect(timeout)
109+
yield from self.connect(self._timeout)
116110
except (asyncio.TimeoutError, OSError):
117111
logger.exception("[%s] Error occurred while connecting", self.name)
118112
else:
119113
break
120114

121-
yield from asyncio.sleep(random.randrange(timeout))
115+
yield from asyncio.sleep(random.randrange(self._timeout))
122116

123117
@asyncio.coroutine
124118
def connect(self, timeout=None):
@@ -153,6 +147,7 @@ def connect(self, timeout=None):
153147
tasks = [
154148
self.bot.plugin_manager.launch(hook, Event(bot=self.bot, conn=self, hook=hook))
155149
for hook in self.bot.plugin_manager.connect_hooks
150+
if not hook.clients or self.type in hook.clients
156151
]
157152
# TODO stop connecting if a connect hook fails?
158153
yield from asyncio.gather(*tasks)
@@ -177,7 +172,6 @@ def close(self):
177172

178173
def message(self, target, *messages):
179174
for text in messages:
180-
text = "".join(text.splitlines())
181175
self.cmd("PRIVMSG", target, text)
182176

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

191185
def action(self, target, text):
192-
text = "".join(text.splitlines())
193186
self.ctcp(target, "ACTION", text)
194187

195188
def notice(self, target, text):
196-
text = "".join(text.splitlines())
197189
self.cmd("NOTICE", target, text)
198190

199191
def set_nick(self, nick):
@@ -239,22 +231,23 @@ def cmd(self, command, *params):
239231
else:
240232
self.send(command)
241233

242-
def send(self, line):
234+
def send(self, line, log=True):
243235
"""
244236
Sends a raw IRC line
245237
:type line: str
238+
:type log: bool
246239
"""
247240
if not self._connected:
248241
raise ValueError("Client must be connected to irc server to use send")
249-
self.loop.call_soon_threadsafe(self._send, line)
242+
self.loop.call_soon_threadsafe(self._send, line, log)
250243

251-
def _send(self, line):
244+
def _send(self, line, log=True):
252245
"""
253246
Sends a raw IRC line unchecked. Doesn't do connected check, and is *not* threadsafe
254247
:type line: str
248+
:type log: bool
255249
"""
256-
logger.info("[{}] >> {}".format(self.name, line))
257-
async_util.wrap_future(self._protocol.send(line), loop=self.loop)
250+
async_util.wrap_future(self._protocol.send(line, log=log), loop=self.loop)
258251

259252
@property
260253
def connected(self):
@@ -321,7 +314,7 @@ def eof_received(self):
321314
return True
322315

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

353+
if log:
354+
logger.info("[{}|out] >> {!r}".format(self.conn.name, line))
355+
360356
self._transport.write(line)
361357

362358
def data_received(self, data):
@@ -381,7 +377,7 @@ def data_received(self, data):
381377
# Reply to pings immediately
382378

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

386382
# Parse the command and params
387383

cloudbot/hook.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def sieve(param=None, **kwargs):
318318
"""
319319

320320
def _sieve_hook(func):
321-
assert len(inspect.getfullargspec(func).args) == 3, \
321+
assert len(inspect.signature(func).parameters) == 3, \
322322
"Sieve plugin has incorrect argument count. Needs params: bot, input, plugin"
323323

324324
hook = _get_hook(func, "sieve")

cloudbot/permissions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from fnmatch import fnmatch
21
import logging
2+
from fnmatch import fnmatch
33

44
logger = logging.getLogger("cloudbot")
55

cloudbot/plugin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,13 @@ def __init__(self, _type, plugin, func_hook):
730730
self.action = func_hook.kwargs.pop("action", Action.CONTINUE)
731731
self.priority = func_hook.kwargs.pop("priority", Priority.NORMAL)
732732

733+
clients = func_hook.kwargs.pop("clients", [])
734+
735+
if isinstance(clients, str):
736+
clients = [clients]
737+
738+
self.clients = clients
739+
733740
if func_hook.kwargs:
734741
# we should have popped all the args, so warn if there are any left
735742
logger.warning("Ignoring extra args {} from {}".format(func_hook.kwargs, self.description))

0 commit comments

Comments
 (0)