Repository: sopel-irc/sopel

Issue:
When an IRC person leaves & returns, I get a traceback error...
Hello.

For an example:
...
��� SignOff Flexo: #bots,#linux (Quit: restarting, back in 5...)
10:51PM ��� Flexo [~rbot@[deleted its IP address] has joined #linux
...

$ Traceback (most recent call last):
  File "/home/ant/SopelPython3/lib/python3.4/site-packages/sopel/bot.py", line 441, in call
    exit_code = func(sopel, trigger)
  File "/home/ant/SopelPython3/lib/python3.4/site-packages/sopel/coretasks.py", line 375, in track_quit
    channel.clear_user(trigger.nick)
  File "/home/ant/SopelPython3/lib/python3.4/site-packages/sopel/tools/target.py", line 63, in clear_user
    user = self.users[nick]
KeyError: Identifier('Flexo')

Thank you in advance. :)



Discussion:
I manually patched this in my copy since I was getting a similar thing. Can someone help patch this into sopel itself?

``` diff
                              official            my copy
                         __________________   _______________
gracefu:~ gracefu$ diff "sopel-core 2/sopel" "sopel-core copy"
Only in sopel-core copy: __init__.pyc
Only in sopel-core copy: bot.pyc
Common subdirectories: sopel-core 2/sopel/config and sopel-core copy/config
diff sopel-core 2/sopel/coretasks.py sopel-core copy/coretasks.py
181a182
>         bot.users[nick] = User(nick, None, None)
357a359
>         bot.users[trigger.nick] = user
Only in sopel-core copy: coretasks.pyc
Only in sopel-core copy: db.pyc
Only in sopel-core copy: formatting.pyc
Only in sopel-core copy: irc.pyc
Only in sopel-core copy: loader.pyc
Only in sopel-core copy: logger.pyc
Only in sopel-core copy: module.pyc
Common subdirectories: sopel-core 2/sopel/modules and sopel-core copy/modules
Only in sopel-core copy: run_script.pyc
Only in sopel-core copy: test_tools.pyc
Common subdirectories: sopel-core 2/sopel/tools and sopel-core copy/tools
Only in sopel-core copy: trigger.pyc
Only in sopel-core copy: web.pyc
```

Reason why this occurs is that the bot.users dict is only populated when a user joins, but if you join a channel, bot.users is not populated. I added code to insert the user into bot.users after a NAMREPLY, but the problem is that you lose the username/hostname info that's stored in a User. Good enough for me.

The same problem will occur if you live on a silly IRC server which sends you QUIT messages of nicks you don't know of. To properly fix this, you may _ALSO_ need to change line 63 in tools/target.py:

``` diff
     def clear_user(self, nick):
-        user = self.users[nick]
-        user.channels.pop(self.name, None)
-        del self.users[nick]
-        del self.privileges[nick]
+        user = self.users.pop(nick, None) # user == None if nick doesn't exist, rather than raising an exception. At the same time, replaces the del statement below.
+        if user != None:
+            user.channels.pop(self.name, None)
+        self.privileges.pop(nick, None)
```

@thomastanck I'd be willing to patch in your changes, but you don't show the line numbers of the two lines it looks like you've added in coretasks. If you provide those two I'd be happy to test and merge.


Resolving patch:
diff --git a/sopel/coretasks.py b/sopel/coretasks.py
--- a/sopel/coretasks.py
+++ b/sopel/coretasks.py
@@ -355,6 +355,7 @@ def track_join(bot, trigger):
     user = bot.users.get(trigger.nick)
     if user is None:
         user = User(trigger.nick, trigger.user, trigger.host)
+        bot.users[trigger.nick] = user
     bot.channels[trigger.sender].add_user(user)
 
     if len(trigger.args) > 1 and trigger.args[1] != '*' and (
diff --git a/sopel/tools/target.py b/sopel/tools/target.py
--- a/sopel/tools/target.py
+++ b/sopel/tools/target.py
@@ -60,10 +60,10 @@ def __init__(self, name):
         be compared to appropriate constants from ``sopel.module``."""
 
     def clear_user(self, nick):
-        user = self.users[nick]
-        user.channels.pop(self.name, None)
-        del self.users[nick]
-        del self.privileges[nick]
+        user = self.users.pop(nick, None)
+        self.privileges.pop(nick, None)
+        if user != None:
+            user.channels.pop(self.name, None)
 
     def add_user(self, user):
         assert isinstance(user, User)


Test patch: