Skip to content

Commit 4c645af

Browse files
committed
Merge branch 'gonzobot' into gonzobot+fix-readme
2 parents 39f9729 + 4e2c8f4 commit 4c645af

83 files changed

Lines changed: 1037 additions & 397 deletions

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ install:
1212

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

1818
after_success:

cloudbot/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
__version__ = "1.0.9"
1414

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

1717

1818
def _setup():

cloudbot/bot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ def _init_routine(self):
221221
self.observer.start()
222222

223223
# Connect to servers
224-
yield from asyncio.gather(*[conn.connect() for conn in self.connections.values()], loop=self.loop)
224+
yield from asyncio.gather(*[conn.try_connect() for conn in self.connections.values()], loop=self.loop)
225225

226226
# Activate web interface.
227227
if self.config.get("web", {}).get("enabled", False) and web_installed:

cloudbot/client.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import collections
33
import logging
4+
import random
45

56
from cloudbot.permissions import PermissionManager
67

@@ -59,7 +60,20 @@ def describe_server(self):
5960
raise NotImplementedError
6061

6162
@asyncio.coroutine
62-
def connect(self):
63+
def try_connect(self):
64+
timeout = 30
65+
while True:
66+
try:
67+
yield from self.connect(timeout)
68+
except Exception:
69+
logger.exception("[%s] Error occurred while connecting.")
70+
else:
71+
break
72+
73+
yield from asyncio.sleep(random.randrange(timeout))
74+
75+
@asyncio.coroutine
76+
def connect(self, timeout=None):
6377
"""
6478
Connects to the server, or reconnects if already connected.
6579
"""

cloudbot/clients/irc.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import logging
3+
import random
34
import re
45
import ssl
56
from _ssl import PROTOCOL_SSLv23
@@ -106,7 +107,20 @@ def describe_server(self):
106107
return "{}:{}".format(self.server, self.port)
107108

108109
@asyncio.coroutine
109-
def connect(self):
110+
def try_connect(self):
111+
timeout = self.config["connection"].get("timeout", 30)
112+
while True:
113+
try:
114+
yield from self.connect(timeout)
115+
except (asyncio.TimeoutError, OSError):
116+
logger.exception("[%s] Error occurred while connecting", self.name)
117+
else:
118+
break
119+
120+
yield from asyncio.sleep(random.randrange(timeout))
121+
122+
@asyncio.coroutine
123+
def connect(self, timeout=None):
110124
"""
111125
Connects to the IRC server, or reconnects if already connected.
112126
"""
@@ -125,8 +139,15 @@ def connect(self):
125139
optional_params = {}
126140
if self.local_bind:
127141
optional_params["local_addr"] = self.local_bind
128-
self._transport, self._protocol = yield from self.loop.create_connection(
129-
lambda: _IrcProtocol(self), host=self.server, port=self.port, ssl=self.ssl_context, **optional_params)
142+
143+
coro = self.loop.create_connection(
144+
lambda: _IrcProtocol(self), host=self.server, port=self.port, ssl=self.ssl_context, **optional_params
145+
)
146+
147+
if timeout is not None:
148+
coro = asyncio.wait_for(coro, timeout)
149+
150+
self._transport, self._protocol = yield from coro
130151

131152
tasks = [
132153
self.bot.plugin_manager.launch(hook, Event(bot=self.bot, conn=self, hook=hook))

cloudbot/config.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import os
44
import sys
55
import time
6+
from collections import OrderedDict
67

78
logger = logging.getLogger("cloudbot")
89

910

10-
class Config(dict):
11+
class Config(OrderedDict):
1112
"""
1213
:type filename: str
1314
:type path: str
@@ -41,8 +42,10 @@ def load_config(self):
4142
sys.exit()
4243

4344
with open(self.path) as f:
44-
self.update(json.load(f))
45-
logger.debug("Config loaded from file.")
45+
data = json.load(f, object_pairs_hook=OrderedDict)
46+
47+
self.update(data)
48+
logger.debug("Config loaded from file.")
4649

4750
# reload permissions
4851
if self.bot.connections:
@@ -51,5 +54,7 @@ def load_config(self):
5154

5255
def save_config(self):
5356
"""saves the contents of the config dict to the config file"""
54-
json.dump(self, open(self.path, 'w'), sort_keys=True, indent=4)
57+
with open(self.path, 'w') as f:
58+
json.dump(self, f, indent=4)
59+
5560
logger.info("Config saved to file.")

cloudbot/util/formatting.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
4545
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4646
"""
47-
47+
import copy
4848
import re
4949
import html.entities
5050

@@ -318,3 +318,20 @@ def get_text_list(list_, last_word='or'):
318318
# Translators: This string is used as a separator between list elements
319319
', '.join([i for i in list_][:-1]),
320320
last_word, list_[-1])
321+
322+
323+
def gen_markdown_table(headers, rows):
324+
"""
325+
Generates a Markdown formatted table from the data
326+
"""
327+
rows = copy.copy(rows)
328+
rows.insert(0, headers)
329+
rotated = zip(*reversed(rows))
330+
331+
sizes = tuple(map(lambda l: max(max(map(len, l)), 3), rotated))
332+
rows.insert(1, tuple(('-' * size) for size in sizes))
333+
lines = [
334+
"| {} |".format(' | '.join(cell.ljust(sizes[i]) for i, cell in enumerate(row)))
335+
for row in rows
336+
]
337+
return '\n'.join(lines)

plugins/admin_channel.py

Lines changed: 67 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,126 @@
11
from cloudbot import hook
22

33

4+
def check_for_chan_mode(char, conn):
5+
serv_info = conn.memory["server_info"]
6+
modes = serv_info.get("channel_modes", "")
7+
return bool(char in modes)
8+
9+
410
def mode_cmd(mode, text, text_inp, chan, conn, notice, nick, admin_log):
511
""" generic mode setting function """
12+
if not check_for_chan_mode(mode[1], conn):
13+
return False
14+
615
split = text_inp.split(" ")
716
if split[0].startswith("#"):
817
channel = split[0]
918
target = split[1]
10-
notice("Attempting to {} {} in {}...".format(text, target, channel))
11-
admin_log("{} used {} to set {} on {} in {}.".format(nick, text, mode, target, channel))
12-
conn.send("MODE {} {} {}".format(channel, mode, target))
1319
else:
1420
channel = chan
1521
target = split[0]
16-
notice("Attempting to {} {} in {}...".format(text, target, channel))
17-
admin_log("{} used {} to set {} on {} in {}.".format(nick, text, mode, target, channel))
18-
conn.send("MODE {} {} {}".format(channel, mode, target))
22+
23+
notice("Attempting to {} {} in {}...".format(text, target, channel))
24+
admin_log("{} used {} to set {} on {} in {}.".format(nick, text, mode, target, channel))
25+
conn.send("MODE {} {} {}".format(channel, mode, target))
26+
27+
return True
1928

2029

2130
def mode_cmd_no_target(mode, text, text_inp, chan, conn, notice, nick, admin_log):
2231
""" generic mode setting function without a target"""
32+
if not check_for_chan_mode(mode[1], conn):
33+
return False
34+
2335
split = text_inp.split(" ")
2436
if split[0].startswith("#"):
2537
channel = split[0]
26-
notice("Attempting to {} {}...".format(text, channel))
27-
admin_log("{} used {} to set {} in {}.".format(nick, text, mode, channel))
28-
conn.send("MODE {} {}".format(channel, mode))
2938
else:
3039
channel = chan
31-
notice("Attempting to {} {}...".format(text, channel))
32-
admin_log("{} used {} to set {} in {}.".format(nick, text, mode, channel))
33-
conn.send("MODE {} {}".format(channel, mode))
3440

41+
notice("Attempting to {} {}...".format(text, channel))
42+
admin_log("{} used {} to set {} in {}.".format(nick, text, mode, channel))
43+
conn.send("MODE {} {}".format(channel, mode))
44+
return True
3545

36-
@hook.command(permissions=["op_ban", "op"])
46+
47+
def do_extban(char, text, text_inp, chan, conn, notice, nick, admin_log, adding=True):
48+
serv_info = conn.memory["server_info"]
49+
if char not in serv_info.get("extbans", ""):
50+
return False
51+
52+
extban_pfx = serv_info["extban_prefix"]
53+
54+
split = text_inp.split(" ")
55+
if split[0].startswith("#"):
56+
channel = split[0]
57+
target = split[1]
58+
text_inp = "{} {}{}:{}".format(channel, extban_pfx, char, target)
59+
else:
60+
target = split[0]
61+
text_inp = "{}{}:{}".format(extban_pfx, char, target)
62+
63+
mode_cmd("+b" if adding else "-b", text, text_inp, chan, conn, notice, nick, admin_log)
64+
return True
65+
66+
67+
@hook.command(permissions=["op_ban", "op", "chanop"])
3768
def ban(text, conn, chan, notice, nick, admin_log):
3869
"""[channel] <user> - bans <user> in [channel], or in the caller's channel if no channel is specified"""
3970
mode_cmd("+b", "ban", text, chan, conn, notice, nick, admin_log)
4071

4172

42-
@hook.command(permissions=["op_ban", "op"])
73+
@hook.command(permissions=["op_ban", "op", "chanop"])
4374
def unban(text, conn, chan, notice, nick, admin_log):
4475
"""[channel] <user> - unbans <user> in [channel], or in the caller's channel if no channel is specified"""
4576
mode_cmd("-b", "unban", text, chan, conn, notice, nick, admin_log)
4677

4778

48-
@hook.command(permissions=["op_quiet", "op"])
79+
@hook.command(permissions=["op_quiet", "op", "chanop"])
4980
def quiet(text, conn, chan, notice, nick, admin_log):
5081
"""[channel] <user> - quiets <user> in [channel], or in the caller's channel if no channel is specified"""
51-
if conn.name == "snoonet":
52-
out = "mode {} +b m:{}".format(chan, text)
53-
conn.send(out)
82+
if mode_cmd("+q", "quiet", text, chan, conn, notice, nick, admin_log):
5483
return
55-
mode_cmd("+q", "quiet", text, chan, conn, notice, nick, admin_log)
84+
85+
if not do_extban('m', "quiet", text, chan, conn, notice, nick, admin_log, True):
86+
notice("Unable to set +q or a mute extban on this network.")
5687

5788

58-
@hook.command(permissions=["op_quiet", "op"])
89+
@hook.command(permissions=["op_quiet", "op", "chanop"])
5990
def unquiet(text, conn, chan, notice, nick, admin_log):
6091
"""[channel] <user> - unquiets <user> in [channel], or in the caller's channel if no channel is specified"""
61-
if conn.name == "snoonet":
62-
out = "mode {} -b m:{}".format(chan, text)
63-
conn.send(out)
92+
if mode_cmd("-q", "unquiet", text, chan, conn, notice, nick, admin_log):
6493
return
65-
mode_cmd("-q", "unquiet", text, chan, conn, notice, nick, admin_log)
6694

95+
if not do_extban('m', "unquiet", text, chan, conn, notice, nick, admin_log, False):
96+
notice("Unable to unset +q or a mute extban on this network.")
6797

68-
@hook.command(permissions=["op_voice", "op"])
98+
99+
@hook.command(permissions=["op_voice", "op", "chanop"])
69100
def voice(text, conn, chan, notice, nick, admin_log):
70101
"""[channel] <user> - voices <user> in [channel], or in the caller's channel if no channel is specified"""
71102
mode_cmd("+v", "voice", text, chan, conn, notice, nick, admin_log)
72103

73104

74-
@hook.command(permissions=["op_voice", "op"])
105+
@hook.command(permissions=["op_voice", "op", "chanop"])
75106
def devoice(text, conn, chan, notice, nick, admin_log):
76107
"""[channel] <user> - devoices <user> in [channel], or in the caller's channel if no channel is specified"""
77108
mode_cmd("-v", "devoice", text, chan, conn, notice, nick, admin_log)
78109

79110

80-
@hook.command(permissions=["op_op", "op"])
111+
@hook.command(permissions=["op_op", "op", "chanop"])
81112
def op(text, conn, chan, notice, nick, admin_log):
82113
"""[channel] <user> - ops <user> in [channel], or in the caller's channel if no channel is specified"""
83114
mode_cmd("+o", "op", text, chan, conn, notice, nick, admin_log)
84115

85116

86-
@hook.command(permissions=["op_op", "op"])
117+
@hook.command(permissions=["op_op", "op", "chanop"])
87118
def deop(text, conn, chan, notice, nick, admin_log):
88119
"""[channel] <user> - deops <user> in [channel], or in the caller's channel if no channel is specified"""
89120
mode_cmd("-o", "deop", text, chan, conn, notice, nick, admin_log)
90121

91122

92-
@hook.command(permissions=["op_topic", "op"])
123+
@hook.command(permissions=["op_topic", "op", "chanop"])
93124
def topic(text, conn, chan, nick, admin_log):
94125
"""[channel] <topic> - changes the topic to <topic> in [channel], or in the caller's channel
95126
if no channel is specified"""
@@ -104,7 +135,7 @@ def topic(text, conn, chan, nick, admin_log):
104135
conn.send("TOPIC {} :{}".format(chan, msg))
105136

106137

107-
@hook.command(permissions=["op_kick", "op"])
138+
@hook.command(permissions=["op_kick", "op", "chanop"])
108139
def kick(text, chan, conn, notice, nick, admin_log):
109140
"""[channel] <user> - kicks <user> from [channel], or from the caller's channel if no channel is specified"""
110141
split = text.split(" ")
@@ -114,7 +145,7 @@ def kick(text, chan, conn, notice, nick, admin_log):
114145
target = split[1]
115146
if len(split) > 2:
116147
reason = " ".join(split[2:])
117-
out = "KICK {} {}: {}".format(channel, target, reason)
148+
out = "KICK {} {} :{}".format(channel, target, reason)
118149
else:
119150
out = "KICK {} {}".format(channel, target)
120151
else:
@@ -131,7 +162,7 @@ def kick(text, chan, conn, notice, nick, admin_log):
131162
conn.send(out)
132163

133164

134-
@hook.command(permissions=["op_rem", "op"])
165+
@hook.command(permissions=["op_rem", "op", "chanop"])
135166
def remove(text, chan, conn, nick, admin_log):
136167
"""<user> - force removes <user> from the caller's channel."""
137168
split = text.split(" ")
@@ -145,25 +176,25 @@ def remove(text, chan, conn, nick, admin_log):
145176
conn.send(out)
146177

147178

148-
@hook.command(permissions=["op_mute", "op"], autohelp=False)
179+
@hook.command(permissions=["op_mute", "op", "chanop"], autohelp=False)
149180
def mute(text, conn, chan, notice, nick, admin_log):
150181
"""[channel] - mutes [channel], or in the caller's channel if no channel is specified"""
151182
mode_cmd_no_target("+m", "mute", text, chan, conn, notice, nick, admin_log)
152183

153184

154-
@hook.command(permissions=["op_mute", "op"], autohelp=False)
185+
@hook.command(permissions=["op_mute", "op", "chanop"], autohelp=False)
155186
def unmute(text, conn, chan, notice, nick, admin_log):
156187
"""[channel] - unmutes [channel], or in the caller's channel if no channel is specified"""
157188
mode_cmd_no_target("-m", "unmute", text, chan, conn, notice, nick, admin_log)
158189

159190

160-
@hook.command(permissions=["op_lock", "op"], autohelp=False)
191+
@hook.command(permissions=["op_lock", "op", "chanop"], autohelp=False)
161192
def lock(text, conn, chan, notice, nick, admin_log):
162193
"""[channel] - locks [channel], or in the caller's channel if no channel is specified"""
163194
mode_cmd_no_target("+i", "lock", text, chan, conn, notice, nick, admin_log)
164195

165196

166-
@hook.command(permissions=["op_lock", "op"], autohelp=False)
197+
@hook.command(permissions=["op_lock", "op", "chanop"], autohelp=False)
167198
def unlock(text, conn, chan, notice, nick, admin_log):
168199
"""[channel] - unlocks [channel], or in the caller's channel if no channel is specified"""
169200
mode_cmd_no_target("-i", "unlock", text, chan, conn, notice, nick, admin_log)

0 commit comments

Comments
 (0)