Skip to content

Commit 39e7cf2

Browse files
committed
Add basic irc_out hooks to allow outward filtering
1 parent 0eb6b91 commit 39e7cf2

5 files changed

Lines changed: 117 additions & 18 deletions

File tree

cloudbot/clients/irc.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from ssl import SSLContext
77

88
from cloudbot.client import Client
9-
from cloudbot.event import Event, EventType
9+
from cloudbot.event import Event, EventType, IrcOutEvent
1010

1111
logger = logging.getLogger("cloudbot")
1212

@@ -287,9 +287,20 @@ def send(self, line):
287287
# make sure we are connected before sending
288288
if not self._connected:
289289
yield from self._connected_future
290-
line = line[:510] + "\r\n"
291-
data = line.encode("utf-8", "replace")
292-
self._transport.write(data)
290+
291+
for out_sieve in self.bot.plugin_manager.out_sieves:
292+
event = IrcOutEvent(bot=self.bot, hook=out_sieve, conn=self.conn, irc_raw=line)
293+
ok, new_line = yield from self.bot.plugin_manager.internal_launch(out_sieve, event)
294+
if not ok:
295+
logger.warning("Error occurred in outgoing sieve, not sending line")
296+
logger.debug("Line was: %s", line)
297+
return
298+
299+
line = new_line
300+
if not line:
301+
return
302+
303+
self._transport.write(line)
293304

294305
def data_received(self, data):
295306
self._input_buffer += data

cloudbot/event.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,9 @@ def __init__(self, *args, cap, cap_param=None, **kwargs):
390390
super().__init__(*args, **kwargs)
391391
self.cap = cap
392392
self.cap_param = cap_param
393+
394+
395+
class IrcOutEvent(Event):
396+
@property
397+
def line(self):
398+
return self.irc_raw

cloudbot/hook.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,19 @@ def _on_connect_hook(func):
439439

440440

441441
connect = on_connect
442+
443+
444+
def irc_out(param=None, **kwargs):
445+
def _on_connect_hook(func):
446+
hook = _get_hook(func, "irc_out")
447+
if hook is None:
448+
hook = _Hook(func, "irc_out")
449+
_add_hook(func, hook)
450+
451+
hook._add_hook(kwargs)
452+
return func
453+
454+
if callable(param):
455+
return _on_connect_hook(param)
456+
else:
457+
return lambda func: _on_connect_hook(func)

cloudbot/plugin.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import os
77
import re
88
from collections import defaultdict
9-
from operator import attrgetter
109
from itertools import chain
10+
from operator import attrgetter
1111

1212
import sqlalchemy
1313

@@ -37,9 +37,10 @@ def find_hooks(parent, module):
3737
on_cap_ack = []
3838
on_cap_available = []
3939
on_connect = []
40+
out_sieve = []
4041
type_lists = {"command": command, "regex": regex, "irc_raw": raw, "sieve": sieve, "event": event,
4142
"periodic": periodic, "on_start": on_start, "on_stop": on_stop, "on_cap_ack": on_cap_ack,
42-
"on_cap_available": on_cap_available, "on_connect": on_connect}
43+
"on_cap_available": on_cap_available, "on_connect": on_connect, "irc_out": out_sieve}
4344
for name, func in module.__dict__.items():
4445
if hasattr(func, "_cloudbot_hook"):
4546
# if it has cloudbot hook
@@ -51,7 +52,7 @@ def find_hooks(parent, module):
5152
# delete the hook to free memory
5253
del func._cloudbot_hook
5354

54-
return command, regex, raw, sieve, event, periodic, on_start, on_stop, on_cap_ack, on_cap_available, on_connect
55+
return command, regex, raw, sieve, event, periodic, on_start, on_stop, on_cap_ack, on_cap_available, on_connect, out_sieve
5556

5657

5758
def find_tables(code):
@@ -108,6 +109,7 @@ def __init__(self, bot):
108109
self.sieves = []
109110
self.cap_hooks = {"on_available": defaultdict(list), "on_ack": defaultdict(list)}
110111
self.connect_hooks = []
112+
self.out_sieves = []
111113
self._hook_waiting_queues = {}
112114

113115
@asyncio.coroutine
@@ -429,27 +431,36 @@ def _execute_hook_sync(self, hook, event):
429431
yield from event.close()
430432

431433
@asyncio.coroutine
432-
def _execute_hook(self, hook, event):
434+
def internal_launch(self, hook, event):
433435
"""
434-
Runs the specific hook with the given bot and event.
435-
436-
Returns False if the hook errored, True otherwise.
437-
438-
:type hook: cloudbot.plugin.Hook
439-
:type event: cloudbot.event.Event
440-
:rtype: bool
436+
Launches a hook with the data from [event]
437+
:param hook: The hook to launch
438+
:param event: The event providing data for the hook
439+
:return: a tuple of (ok, result) where ok is a boolean that determines if the hook ran without error and result is the result from the hook
441440
"""
442441
try:
443-
# _internal_run_threaded and _internal_run_coroutine prepare the database, and run the hook.
444-
# _internal_run_* will prepare parameters and the database session, but won't do any error catching.
445442
if hook.threaded:
446443
out = yield from self.bot.loop.run_in_executor(None, self._execute_hook_threaded, hook, event)
447444
else:
448445
out = yield from self._execute_hook_sync(hook, event)
449446
except Exception:
450447
logger.exception("Error in hook {}".format(hook.description))
451-
return False
448+
return False, None
449+
450+
return True, out
452451

452+
@asyncio.coroutine
453+
def _execute_hook(self, hook, event):
454+
"""
455+
Runs the specific hook with the given bot and event.
456+
457+
Returns False if the hook errored, True otherwise.
458+
459+
:type hook: cloudbot.plugin.Hook
460+
:type event: cloudbot.event.Event
461+
:rtype: bool
462+
"""
463+
ok, out = yield from self.internal_launch(hook, event)
453464
if out is not None:
454465
if isinstance(out, (list, tuple)):
455466
# if there are multiple items in the response, return them on multiple lines
@@ -878,6 +889,17 @@ def __str__(self):
878889
return "{name} {func} from {file}".format(name=self.type, func=self.function_name, file=self.plugin.file_name)
879890

880891

892+
class IrcOutHook(Hook):
893+
def __init__(self, plugin, out_hook):
894+
super().__init__("irc_out", plugin, out_hook)
895+
896+
def __repr__(self):
897+
return "Irc_Out[{}]".format(Hook.__repr__(self))
898+
899+
def __str__(self):
900+
return "irc_out {} from {}".format(self.function_name, self.plugin.file_name)
901+
902+
881903
_hook_name_to_plugin = {
882904
"command": CommandHook,
883905
"regex": RegexHook,
@@ -890,4 +912,5 @@ def __str__(self):
890912
"on_cap_available": OnCapAvaliableHook,
891913
"on_cap_ack": OnCapAckHook,
892914
"on_connect": OnConnectHook,
915+
"irc_out": IrcOutHook,
893916
}

plugins/core_out.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""
2+
Core filters for IRC raw lines
3+
"""
4+
5+
from cloudbot import hook
6+
from cloudbot.hook import Priority
7+
8+
NEW_LINE_TRANS_TBL = str.maketrans({
9+
'\r': None,
10+
'\n': None,
11+
'\0': None,
12+
})
13+
14+
15+
@hook.irc_out(priority=Priority.HIGHEST)
16+
def strip_newlines(line, conn):
17+
"""
18+
Removes newline characters from a message
19+
:param line: str
20+
:param conn: cloudbot.clients.irc.IrcClient
21+
:return: str
22+
"""
23+
do_strip = conn.config.get("strip_newlines", True)
24+
if do_strip:
25+
return line.translate(NEW_LINE_TRANS_TBL)
26+
else:
27+
return line
28+
29+
30+
@hook.irc_out(priority=Priority.HIGH)
31+
def truncate_line(line, conn):
32+
line_len = conn.config.get("max_line_length", 510)
33+
return line[:line_len] + "\r\n"
34+
35+
36+
@hook.irc_out(priority=Priority.LOWEST)
37+
def encode_line(line, conn):
38+
if not isinstance(line, str):
39+
return line
40+
41+
encoding = conn.config.get("encoding", "utf-8")
42+
errors = conn.config.get("encoding_errors", "replace")
43+
return line.encode(encoding, errors)

0 commit comments

Comments
 (0)