Skip to content

Commit 57d2772

Browse files
authored
Merge pull request #2001 from Exirel/coretasks-docstrings
coretasks: trying to improve docstrings
2 parents 0b0d4a5 + 81cf6d6 commit 57d2772

File tree

1 file changed

+134
-14
lines changed

1 file changed

+134
-14
lines changed

sopel/coretasks.py

Lines changed: 134 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
# coding=utf-8
2-
"""Tasks that allow the bot to run, but aren't user-facing functionality
2+
"""Core Sopel plugin that handles IRC protocol functions.
3+
4+
This plugin allows the bot to run without user-facing functionality:
5+
6+
* it handles client capability negotiation
7+
* it handles client auth (both nick auth and server auth)
8+
* it handles connection registration (RPL_WELCOME, RPL_LUSERCLIENT), dealing
9+
with error cases such as nick already in use
10+
* it tracks known channels & users (join, quit, nick change and other events)
11+
* it manages blocked (ignored) users
312
413
This is written as a plugin to make it easier to extend to support more
514
responses to standard IRC codes without having to shove them all into the
6-
dispatch function in bot.py and making it easier to maintain.
15+
dispatch function in :class:`sopel.bot.Sopel` and making it easier to maintain.
716
"""
817
# Copyright 2008-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich
918
# (yanovich.net)
@@ -41,6 +50,11 @@
4150

4251

4352
def setup(bot):
53+
"""Set up the coretasks plugin.
54+
55+
The setup phase is used to activate the throttle feature to prevent a flood
56+
of JOIN commands when there are too many channels to join.
57+
"""
4458
bot.memory['join_events_queue'] = collections.deque()
4559

4660
# Manage JOIN flood protection
@@ -59,6 +73,7 @@ def processing_job(bot):
5973

6074

6175
def shutdown(bot):
76+
"""Clean up coretasks-related values in the bot's memory."""
6277
try:
6378
bot.memory['join_events_queue'].clear()
6479
except KeyError:
@@ -84,7 +99,25 @@ def _join_event_processing(bot):
8499

85100

86101
def auth_after_register(bot):
87-
"""Do NickServ/AuthServ auth"""
102+
"""Do NickServ/AuthServ auth.
103+
104+
:param bot: a connected Sopel instance
105+
:type bot: :class:`sopel.bot.Sopel`
106+
107+
This function can be used, **after** the bot is connected, to handle one of
108+
these auth methods:
109+
110+
* ``nickserv``: send a private message to the NickServ service
111+
* ``authserv``: send an ``AUTHSERV`` command
112+
* ``Q``: send an ``AUTH`` command
113+
* ``userserv``: send a private message to the UserServ service
114+
115+
.. important::
116+
117+
If ``core.auth_method`` is set, then ``core.nick_auth_method`` will be
118+
ignored. If none is set, then this function does nothing.
119+
120+
"""
88121
if bot.config.core.auth_method:
89122
auth_method = bot.config.core.auth_method
90123
auth_username = bot.config.core.auth_username
@@ -120,15 +153,28 @@ def auth_after_register(bot):
120153

121154

122155
def _execute_perform(bot):
123-
"""Execute commands specified to perform on IRC server connect."""
156+
"""Execute commands specified to perform on IRC server connect.
157+
158+
This function executes the list of commands that can be found in the
159+
``core.commands_on_connect`` setting. It automatically replaces any
160+
``$nickname`` placeholder in the command with the bot's configured nick.
161+
"""
124162
if not bot.connection_registered:
125163
# How did you even get this command, bot?
126164
raise Exception('Bot must be connected to server to perform commands.')
127165

128-
LOGGER.debug('{} commands to execute:'.format(len(bot.config.core.commands_on_connect)))
129-
for i, command in enumerate(bot.config.core.commands_on_connect):
166+
commands = bot.config.core.commands_on_connect
167+
count = len(commands)
168+
169+
if not count:
170+
LOGGER.info('No custom command to execute.')
171+
return
172+
173+
LOGGER.info('Executing %d custom commands.', count)
174+
for i, command in enumerate(commands, 1):
130175
command = command.replace('$nickname', bot.config.core.nick)
131-
LOGGER.debug(command)
176+
LOGGER.debug(
177+
'Executing custom command [%d/%d]: %s', i, count, command)
132178
bot.write((command,))
133179

134180

@@ -137,6 +183,16 @@ def _execute_perform(bot):
137183
@plugin.priority('high')
138184
@plugin.unblockable
139185
def on_nickname_in_use(bot, trigger):
186+
"""Change the bot's nick when the current one is already in use.
187+
188+
This can be triggered when the bot disconnects then reconnects before the
189+
server can notice a client timeout. Other reasons include mischief,
190+
trolling, and obviously, PEBKAC.
191+
192+
This will change the current nick by adding a trailing ``_``. If the bot
193+
sees that a user with its configured nick disconnects (see ``QUIT`` event
194+
handling), the bot will try to regain it.
195+
"""
140196
LOGGER.error(
141197
'Nickname already in use! '
142198
'(Nick: %s; Sender: %s; Args: %r)',
@@ -151,7 +207,11 @@ def on_nickname_in_use(bot, trigger):
151207
@module.require_admin("This command requires admin privileges.")
152208
@module.commands('execute')
153209
def execute_perform(bot, trigger):
154-
"""Execute commands specified to perform on IRC server connect."""
210+
"""Execute commands specified to perform on IRC server connect.
211+
212+
This allows a bot owner or admin to force the execution of commands
213+
that are automatically performed when the bot connects.
214+
"""
155215
_execute_perform(bot)
156216

157217

@@ -162,28 +222,41 @@ def execute_perform(bot, trigger):
162222
def startup(bot, trigger):
163223
"""Do tasks related to connecting to the network.
164224
165-
001 RPL_WELCOME is from RFC2812 and is the first message that is sent after
166-
the connection has been registered on the network.
225+
``001 RPL_WELCOME`` is from RFC2812 and is the first message that is sent
226+
after the connection has been registered on the network.
227+
228+
``251 RPL_LUSERCLIENT`` is a mandatory message that is sent after the
229+
client connects to the server in RFC1459. RFC2812 does not require it and
230+
all networks might not send it. We support both.
167231
168-
251 RPL_LUSERCLIENT is a mandatory message that is sent after client
169-
connects to the server in rfc1459. RFC2812 does not require it and all
170-
networks might not send it. We support both.
232+
If ``sopel.irc.AbstractBot.connection_registered`` is set, this function
233+
does nothing and returns immediately. Otherwise, the flag is set and the
234+
function proceeds normally to:
171235
236+
1. trigger auth method
237+
2. set bot's ``MODE`` (from ``core.modes``)
238+
3. join channels (or queue them to join later)
239+
4. check for security when the ``account-tag`` capability is enabled
240+
5. execute custom commands
172241
"""
173242
if bot.connection_registered:
174243
return
175244

245+
# set flag
176246
bot.connection_registered = True
177247

248+
# handle auth method
178249
auth_after_register(bot)
179250

251+
# set bot's MODE
180252
modes = bot.config.core.modes
181253
if modes:
182254
if not modes.startswith(('+', '-')):
183255
# Assume "+" by default.
184256
modes = '+' + modes
185257
bot.write(('MODE', bot.nick, modes))
186258

259+
# join channels
187260
bot.memory['retry_join'] = dict()
188261

189262
channels = bot.config.core.channels
@@ -216,6 +289,7 @@ def startup(bot, trigger):
216289
for channel in bot.config.core.channels:
217290
bot.join(channel)
218291

292+
# warn for insecure auth method if necessary
219293
if (not bot.config.core.owner_account and
220294
'account-tag' in bot.enabled_capabilities and
221295
'@' not in bot.config.core.owner):
@@ -227,6 +301,7 @@ def startup(bot, trigger):
227301
).format(bot.config.core.help_prefix)
228302
bot.say(msg, bot.config.core.owner)
229303

304+
# execute custom commands
230305
_execute_perform(bot)
231306

232307

@@ -276,6 +351,14 @@ def parse_reply_myinfo(bot, trigger):
276351
@module.require_owner()
277352
@module.commands('useserviceauth')
278353
def enable_service_auth(bot, trigger):
354+
"""Set owner's account from an authenticated owner.
355+
356+
This command can be used to automatically configure ``core.owner_account``
357+
when the owner is known and has a registered account, but the bot doesn't
358+
have ``core.owner_account`` configured.
359+
360+
This doesn't work if the ``account-tag`` capability is not available.
361+
"""
279362
if bot.config.core.owner_account:
280363
return
281364
if 'account-tag' not in bot.enabled_capabilities:
@@ -321,7 +404,10 @@ def retry_join(bot, trigger):
321404
@module.thread(False)
322405
@module.unblockable
323406
def handle_names(bot, trigger):
324-
"""Handle NAMES response, happens when joining to channels."""
407+
"""Handle NAMES responses.
408+
409+
This function keeps track of users' privileges when Sopel joins channels.
410+
"""
325411
names = trigger.split()
326412

327413
# TODO specific to one channel type. See issue 281.
@@ -499,6 +585,7 @@ def track_nicks(bot, trigger):
499585
@module.thread(False)
500586
@module.unblockable
501587
def track_part(bot, trigger):
588+
"""Track users leaving channels."""
502589
nick = trigger.nick
503590
channel = trigger.sender
504591
_remove_from_channel(bot, nick, channel)
@@ -509,6 +596,7 @@ def track_part(bot, trigger):
509596
@module.thread(False)
510597
@module.unblockable
511598
def track_kick(bot, trigger):
599+
"""Track users kicked from channels."""
512600
nick = Identifier(trigger.args[1])
513601
channel = trigger.sender
514602
_remove_from_channel(bot, nick, channel)
@@ -587,6 +675,11 @@ def _periodic_send_who(bot):
587675
@module.thread(False)
588676
@module.unblockable
589677
def track_join(bot, trigger):
678+
"""Track users joining channels.
679+
680+
When a user joins a channel, the bot will send (or queue) a ``WHO`` command
681+
to know more about said user (privileges, modes, etc.).
682+
"""
590683
channel = trigger.sender
591684

592685
# is it a new channel?
@@ -624,6 +717,7 @@ def track_join(bot, trigger):
624717
@module.thread(False)
625718
@module.unblockable
626719
def track_quit(bot, trigger):
720+
"""Track when users quit channels."""
627721
for chanprivs in bot.privileges.values():
628722
chanprivs.pop(trigger.nick, None)
629723
for channel in bot.channels.values():
@@ -641,6 +735,7 @@ def track_quit(bot, trigger):
641735
@module.priority('high')
642736
@module.unblockable
643737
def receive_cap_list(bot, trigger):
738+
"""Handle client capability negotiation."""
644739
cap = trigger.strip('-=~')
645740
# Server is listing capabilities
646741
if trigger.args[1] == 'LS':
@@ -833,6 +928,18 @@ def send_authenticate(bot, token):
833928
@module.event('AUTHENTICATE')
834929
@module.unblockable
835930
def auth_proceed(bot, trigger):
931+
"""Handle client-initiated SASL auth.
932+
933+
If the chosen mechanism is client-first, the server sends an empty
934+
response (``AUTHENTICATE +``). In that case, Sopel will handle SASL auth
935+
that uses a token.
936+
937+
.. important::
938+
939+
If ``core.auth_method`` is set, then ``core.server_auth_method`` will
940+
be ignored. If none is set, then this function does nothing.
941+
942+
"""
836943
if trigger.args[0] != '+':
837944
# How did we get here? I am not good with computer.
838945
return
@@ -857,6 +964,13 @@ def _make_sasl_plain_token(account, password):
857964
@module.event(events.RPL_SASLSUCCESS)
858965
@module.unblockable
859966
def sasl_success(bot, trigger):
967+
"""End CAP request on successful SASL auth.
968+
969+
If SASL is configured, then the bot won't send ``CAP END`` once it gets
970+
all the capability responses; it will wait for SASL auth result.
971+
972+
In this case, the SASL auth is a success, so we can close the negotiation.
973+
"""
860974
bot.write(('CAP', 'END'))
861975

862976

@@ -1009,6 +1123,7 @@ def blocks(bot, trigger):
10091123

10101124
@module.event('ACCOUNT')
10111125
def account_notify(bot, trigger):
1126+
"""Track users' accounts."""
10121127
if trigger.nick not in bot.users:
10131128
bot.users[trigger.nick] = target.User(
10141129
trigger.nick, trigger.user, trigger.host)
@@ -1022,6 +1137,7 @@ def account_notify(bot, trigger):
10221137
@module.priority('high')
10231138
@module.unblockable
10241139
def recv_whox(bot, trigger):
1140+
"""Track ``WHO`` responses when ``WHOX`` is enabled."""
10251141
if len(trigger.args) < 2 or trigger.args[1] not in who_reqs:
10261142
# Ignored, some plugin probably called WHO
10271143
return
@@ -1076,6 +1192,7 @@ def _record_who(bot, channel, user, host, nick, account=None, away=None, modes=N
10761192
@module.priority('high')
10771193
@module.unblockable
10781194
def recv_who(bot, trigger):
1195+
"""Track ``WHO`` responses when ``WHOX`` is not enabled."""
10791196
channel, user, host, _, nick, status = trigger.args[1:7]
10801197
away = 'G' in status
10811198
modes = ''.join([c for c in status if c in '~&@%+!'])
@@ -1086,6 +1203,7 @@ def recv_who(bot, trigger):
10861203
@module.priority('high')
10871204
@module.unblockable
10881205
def end_who(bot, trigger):
1206+
"""Handle the end of a response to a ``WHO`` command (if needed)."""
10891207
if 'WHOX' in bot.isupport:
10901208
who_reqs.pop(trigger.args[1], None)
10911209

@@ -1095,6 +1213,7 @@ def end_who(bot, trigger):
10951213
@module.thread(False)
10961214
@module.unblockable
10971215
def track_notify(bot, trigger):
1216+
"""Track users going away or coming back."""
10981217
if trigger.nick not in bot.users:
10991218
bot.users[trigger.nick] = target.User(
11001219
trigger.nick, trigger.user, trigger.host)
@@ -1108,6 +1227,7 @@ def track_notify(bot, trigger):
11081227
@module.thread(False)
11091228
@module.unblockable
11101229
def track_topic(bot, trigger):
1230+
"""Track channels' topics."""
11111231
if trigger.event != 'TOPIC':
11121232
channel = trigger.args[1]
11131233
else:

0 commit comments

Comments
 (0)