Convert var.DISCONNECTED to users (#302)

* Fix inaccuracy in users._add docstring

Also don't track disconnected users by nick anymore. But that seems less
major than fixing the docstring.

* Fixes

* Fix docstring
This commit is contained in:
Ryan Schmidt 2017-12-04 12:06:20 -07:00 committed by Em Barry
parent 65e4d9c484
commit 0b07d9193f
4 changed files with 114 additions and 123 deletions

View File

@ -283,7 +283,7 @@ class command:
if self.phases and var.PHASE not in self.phases:
return
if self.playing and (user not in get_players() or user.nick in var.DISCONNECTED): # FIXME: Need to change this once var.DISCONNECTED uses User instances
if self.playing and (user not in get_players() or user in var.DISCONNECTED):
return
for role in self.roles:
@ -430,7 +430,7 @@ class cmd:
if self.phases and var.PHASE not in self.phases:
return
if self.playing and (nick not in list_players() or nick in var.DISCONNECTED):
if self.playing and (nick not in list_players() or users._get(nick) in var.DISCONNECTED):
return
for role in self.roles:

View File

@ -552,6 +552,8 @@ def join_chan(cli, rawnick, chan, account=None, realname=None):
user = users._add(cli, nick=rawnick, realname=realname, account=account) # FIXME
ch.users.add(user)
user.channels[ch] = set()
# mark the user as here, in case they used to be connected before but left
user.disconnected = False
if user is users.Bot:
ch.mode()

View File

@ -91,7 +91,6 @@ def _add(cli, *, nick, ident=None, host=None, realname=None, account=None):
This function takes up to 5 keyword-only arguments (and one positional
argument, cli): nick, ident, host, realname and account.
With the exception of the first one, any parameter can be omitted.
If a matching user already exists, a ValueError will be raised.
"""
@ -149,6 +148,10 @@ def users():
"""Iterate over the users in the registry."""
yield from _users
def disconnected():
"""Iterate over the users who are in-game but disconnected."""
yield from _ghosts
def complete_match(string, users):
matches = []
string = lower(string)
@ -178,10 +181,10 @@ def parse_rawnick_as_dict(rawnick, *, default=None):
def _cleanup_user(evt, var, user):
"""Removes a user from our global tracking set once it has left all channels."""
if var.PHASE not in var.GAME_PHASES or user not in var.ALL_PLAYERS:
_users.discard(user)
elif var.PHASE in var.GAME_PHASES and user in var.ALL_PLAYERS:
if var.PHASE in var.GAME_PHASES and user in var.ALL_PLAYERS:
_ghosts.add(user)
else:
_users.discard(user)
def _reset(evt, var):
"""Cleans up users that left during game during game end."""
@ -190,13 +193,17 @@ def _reset(evt, var):
_users.discard(user)
_ghosts.clear()
def _swap_player(evt, var, old_user, user):
"""Mark the user as no longer being a ghost, if they are one."""
_ghosts.discard(old_user)
if not old_user.channels:
_users.discard(old_user)
# Can't use @event_listener decorator since src/decorators.py imports us
# (meaning decorator isn't defined at the point in time we are run)
events.add_listener("cleanup_user", _cleanup_user)
events.add_listener("reset", _reset)
# FIXME: when there is a swap_player event, we need a listener for that as well
# to remove the swapped player from _ghosts if they're in there (helps prevent
# duplicate user lookup bugs where the ghost and new player have the same nick)
events.add_listener("swap_player", _swap_player, priority=1)
class User(IRCContext):
@ -564,6 +571,20 @@ class User(IRCContext):
def userhost(self, userhost):
nick, self.ident, self.host = parse_rawnick(userhost)
@property
def disconnected(self):
return self in _ghosts
@disconnected.setter
def disconnected(self, disconnected):
if disconnected:
_ghosts.add(self)
else:
_ghosts.discard(self)
# ensure dangling users aren't left around in our tracking var
if not self.channels:
_users.discard(self)
class FakeUser(User):
is_fake = True

View File

@ -90,12 +90,10 @@ var.GAME_START_TIME = datetime.now() # for idle checker only
var.CAN_START_TIME = 0
var.STARTED_DAY_PLAYERS = 0
var.DISCONNECTED = {} # players who got disconnected
var.DISCONNECTED = {} # players who are still alive but disconnected
var.RESTARTING = False
#var.OPPED = False # Keeps track of whether the bot is opped
var.BITTEN_ROLES = {}
var.LYCAN_ROLES = {}
var.CHARMED = set()
@ -537,7 +535,7 @@ def replace(var, wrapper, message):
wrapper.pm(messages["invalid_channel"].format(channels.Main))
return
if wrapper.source.nick in list_players(): # FIXME: Need to fix when list_players() holds User instances
if wrapper.source in get_players():
wrapper.pm(messages["already_playing"].format("You"))
return
@ -585,9 +583,9 @@ def replace(var, wrapper, message):
evt = Event("swap_player", {})
evt.dispatch(var, target, wrapper.source)
rename_player(var, wrapper.source, target.nick)
# Make sure to remove player from var.DISCONNECTED if they were in there
if var.PHASE in var.GAME_PHASES:
return_to_village(var, channels.Main, target, show_message=False)
# FIXME: This doesn't actually do anything right now because rename_player calls return_to_village with show_message=True
return_to_village(var, target, show_message=False)
if not var.DEVOICE_DURING_NIGHT or var.PHASE != "night":
mode = hooks.Features["PREFIX"]["+"]
@ -2675,8 +2673,7 @@ def del_player(player, *, devoice=True, end_game=True, death_triggers=True, kill
for k in list(x):
if player.nick in (k, x[k]):
del x[k]
if player.nick in var.DISCONNECTED:
del var.DISCONNECTED[player.nick]
var.DISCONNECTED.pop(player, None)
if var.PHASE == "night":
# remove players from night variables
# the dicts are handled above, these are the lists of who has acted which is used to determine whether night should end
@ -2786,39 +2783,38 @@ def reaper(cli, gameid):
cli.msg(chan, messages["channel_idle_warning"].format(", ".join(x)))
msg_targets = [p for p in to_warn_pm if p in pl]
mass_privmsg(cli, msg_targets, messages["player_idle_warning"].format(chan), privmsg=True)
for dcedplayer in list(var.DISCONNECTED.keys()):
acc, hostmask, timeofdc, what = var.DISCONNECTED[dcedplayer]
for dcedplayer, (timeofdc, what) in list(var.DISCONNECTED.items()):
mainrole = get_main_role(dcedplayer)
revealrole = get_reveal_role(dcedplayer.nick) # FIXME
if what in ("quit", "badnick") and (datetime.now() - timeofdc) > timedelta(seconds=var.QUIT_GRACE_TIME):
if get_role(dcedplayer) != "person" and var.ROLE_REVEAL in ("on", "team"):
cli.msg(chan, messages["quit_death"].format(dcedplayer, get_reveal_role(dcedplayer)))
if mainrole != "person" and var.ROLE_REVEAL in ("on", "team"):
channels.Main.send(messages["quit_death"].format(dcedplayer, revealrole))
else:
cli.msg(chan, messages["quit_death_no_reveal"].format(dcedplayer))
channels.Main.send(messages["quit_death_no_reveal"].format(dcedplayer))
if var.PHASE != "join" and var.PART_PENALTY:
add_warning(cli, dcedplayer, var.PART_PENALTY, botconfig.NICK, messages["quit_warning"], expires=var.PART_EXPIRY)
if not del_player(users._get(dcedplayer), devoice=False, death_triggers=False): # FIXME
add_warning(cli, dcedplayer.nick, var.PART_PENALTY, botconfig.NICK, messages["quit_warning"], expires=var.PART_EXPIRY) # FIXME
if not del_player(dcedplayer, devoice=False, death_triggers=False):
return
elif what == "part" and (datetime.now() - timeofdc) > timedelta(seconds=var.PART_GRACE_TIME):
if get_role(dcedplayer) != "person" and var.ROLE_REVEAL in ("on", "team"):
cli.msg(chan, messages["part_death"].format(dcedplayer, get_reveal_role(dcedplayer)))
if mainrole != "person" and var.ROLE_REVEAL in ("on", "team"):
channels.Main.send(messages["part_death"].format(dcedplayer, revealrole))
else:
cli.msg(chan, messages["part_death_no_reveal"].format(dcedplayer))
channels.Main.send(messages["part_death_no_reveal"].format(dcedplayer))
if var.PHASE != "join" and var.PART_PENALTY:
add_warning(cli, dcedplayer, var.PART_PENALTY, botconfig.NICK, messages["part_warning"], expires=var.PART_EXPIRY)
if not del_player(users._get(dcedplayer), devoice=False, death_triggers=False): # FIXME
add_warning(cli, dcedplayer.nick, var.PART_PENALTY, botconfig.NICK, messages["part_warning"], expires=var.PART_EXPIRY) # FIXME
if not del_player(dcedplayer, devoice=False, death_triggers=False):
return
elif what == "account" and (datetime.now() - timeofdc) > timedelta(seconds=var.ACC_GRACE_TIME):
if get_role(dcedplayer) != "person" and var.ROLE_REVEAL in ("on", "team"):
cli.msg(chan, messages["account_death"].format(dcedplayer, get_reveal_role(dcedplayer)))
if mainrole != "person" and var.ROLE_REVEAL in ("on", "team"):
channels.Main.send(messages["account_death"].format(dcedplayer, revealrole))
else:
cli.msg(chan, messages["account_death_no_reveal"].format(dcedplayer))
channels.Main.send(messages["account_death_no_reveal"].format(dcedplayer))
if var.PHASE != "join" and var.ACC_PENALTY:
add_warning(cli, dcedplayer, var.ACC_PENALTY, botconfig.NICK, messages["acc_warning"], expires=var.ACC_EXPIRY)
if not del_player(users._get(dcedplayer), devoice=False, death_triggers=False): # FIXME
add_warning(cli, dcedplayer.nick, var.ACC_PENALTY, botconfig.NICK, messages["acc_warning"], expires=var.ACC_EXPIRY) # FIXME
if not del_player(dcedplayer, devoice=False, death_triggers=False):
return
time.sleep(10)
@cmd("") # update last said
def update_last_said(cli, nick, chan, rest):
if chan != botconfig.CHANNEL:
@ -2855,51 +2851,24 @@ def setup_role_commands(evt):
# (as no IRC connection exists at this point)
events.add_listener("init", setup_role_commands, priority=10000)
@hook("join")
def on_join(cli, raw_nick, chan, acc="*", rname=""):
nick, _, ident, host = parse_nick(raw_nick)
if nick == botconfig.NICK:
@event_listener("chan_join", priority=1)
def on_join(evt, var, chan, user):
if user is users.Bot:
plog("Joined {0}".format(chan))
elif not users.exists(nick):
users.add(nick, ident=ident,host=host,account=acc,inchan=(chan == botconfig.CHANNEL),modes=set(),moded=set())
# FIXME: kill all of this off along with var.USERS
elif not users.exists(user.nick):
users.add(user.nick, ident=user.ident,host=user.host,account=user.account,inchan=(chan is channels.Main),modes=set(),moded=set())
else:
users.get(nick).ident = ident
users.get(nick).host = host
users.get(nick).account = acc
if not users.get(nick).inchan:
baduser = users.get(user.nick)
baduser.ident = user.ident
baduser.host = user.host
baduser.account = user.account
if not baduser.inchan:
# Will be True if the user joined the main channel, else False
users.get(nick).inchan = (chan == botconfig.CHANNEL)
if chan != botconfig.CHANNEL:
baduser.inchan = (chan is channels.Main)
if chan is not channels.Main:
return
with var.GRAVEYARD_LOCK:
hostmask = irc_lower(ident) + "@" + host.lower()
lacc = irc_lower(acc)
if nick in var.DISCONNECTED.keys():
hm = var.DISCONNECTED[nick][1]
act = var.DISCONNECTED[nick][0]
if (lacc == act and not var.DISABLE_ACCOUNTS) or (hostmask == hm and not var.ACCOUNTS_ONLY):
if not var.DEVOICE_DURING_NIGHT or var.PHASE != "night":
cli.mode(chan, "+v", nick, nick+"!*@*")
del var.DISCONNECTED[nick]
var.LAST_SAID_TIME[nick] = datetime.now()
cli.msg(chan, messages["player_return"].format(nick))
for r,rlist in var.ORIGINAL_ROLES.items():
if "(dced)"+nick in rlist:
rlist.remove("(dced)"+nick)
rlist.add(nick)
break
if nick in var.DCED_PLAYERS.keys():
var.PLAYERS[nick] = var.DCED_PLAYERS.pop(nick)
if nick == botconfig.NICK:
#var.OPPED = False
cli.send("NAMES " + chan)
#if nick == var.CHANSERV and not var.OPPED and var.CHANSERV_OP_COMMAND:
# cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL))
#@hook("namreply")
#def on_names(cli, _, __, *names):
# if "@" + botconfig.NICK in names:
# var.OPPED = True
return_to_village(var, user, show_message=True)
@command("goat")
def goat(var, wrapper, message):
@ -2936,27 +2905,38 @@ def fgoat(var, wrapper, message):
wrapper.send(messages["goat_success"].format(wrapper.source, goatact, togoat))
@handle_error
def return_to_village(var, chan, target, *, show_message):
def return_to_village(var, target, *, show_message):
# Note: we do not manipulate or check target.disconnected, as that property
# is used to determine if they are entirely dc'ed rather than just maybe using
# a different account or /parting the channel. If they were dced for real and
# rejoined IRC, the join handler already took care of marking them no longer dced.
with var.GRAVEYARD_LOCK:
temp = target.lower()
if temp.nick in var.DISCONNECTED:
account, hostmask, when, what = var.DISCONNECTED[temp.nick]
if ((not var.DISABLE_ACCOUNTS and temp.account is not None and users.equals(temp.account, account)) or
(not var.ACCOUNTS_ONLY and temp.userhost is not None and users.equals(temp.userhost, hostmask))):
if target in var.DISCONNECTED:
del var.DISCONNECTED[target]
var.LAST_SAID_TIME[target.nick] = datetime.now()
for roleset in var.ORIGINAL_ROLES.values():
if "(dced)" + target.nick in roleset:
roleset.remove("(dced)" + target.nick)
roleset.add(target.nick)
del var.DISCONNECTED[temp.nick]
var.LAST_SAID_TIME[temp.nick] = datetime.now()
for roleset in var.ORIGINAL_ROLES.values():
if "(dced)" + temp.nick in roleset:
roleset.remove("(dced)" + temp.nick)
roleset.add(temp.nick)
if target.nick in var.DCED_PLAYERS:
var.PLAYERS[target.nick] = var.DCED_PLAYERS.pop(target.nick)
if temp.nick in var.DCED_PLAYERS:
var.PLAYERS[temp.nick] = var.DCED_PLAYERS.pop(temp.nick)
if show_message:
channels.Main.mode(("+" + hooks.Features["PREFIX"]["+"], target))
channels.Main.send(messages["player_return"].format(target))
if show_message:
channels.Main.mode(("+" + hooks.Features["PREFIX"]["+"], target))
channels.Main.send(messages["player_return"].format(target))
else:
# this particular user doesn't exist in var.DISCONNECTED, but that doesn't
# mean that they aren't dced. They may have rejoined as a different nick,
# for example, and we want to mark them as back without requiring them to do
# a !swap.
if var.ACCOUNTS_ONLY or target.account:
userlist = users._get(account=target.account, allow_multiple=True) # FIXME
else: # match host (hopefully the ircd uses vhosts to differentiate users)
userlist = users._get(host=target.host, allow_multiple=True)
userlist = [u for u in userlist if u in var.DISCONNECTED]
if len(userlist) == 1:
return_to_village(var, userlist[0], show_message=show_message)
def rename_player(var, user, prefix):
nick = user.nick
@ -3080,7 +3060,7 @@ def rename_player(var, user, prefix):
# Check if player was disconnected
if var.PHASE in var.GAME_PHASES:
return_to_village(var, channels.Main, user, show_message=True)
return_to_village(var, user, show_message=True)
if prefix in var.NO_LYNCH:
var.NO_LYNCH.remove(prefix)
@ -3093,7 +3073,7 @@ def account_change(evt, var, user):
voice = hooks.Features["PREFIX"]["+"]
if user.account is None and var.ACCOUNTS_ONLY and user.nick in list_players(): # FIXME: need changed when list_players holds User instances
if user.account is None and var.ACCOUNTS_ONLY and user in get_players():
leave(var, "account", user)
if var.PHASE == "join":
user.send(messages["account_midgame_change"], notice=True)
@ -3101,29 +3081,14 @@ def account_change(evt, var, user):
channels.Main.mode(["-" + voice, user.nick])
user.send(messages["account_reidentify"].format(user.account), notice=True)
if user.nick in var.DISCONNECTED: # FIXME: need to change this when var.DISCONNECTED holds User instances
account, hostmask, when, what = var.DISCONNECTED[user.nick]
if users.equals(user.account, account):
with var.GRAVEYARD_LOCK:
if not var.DISABLE_ACCOUNTS or not var.ACCOUNTS_ONLY and user.match_hostmask(hostmask):
channels.Main.mode(["+" + voice, user.nick])
del var.DISCONNECTED[user.nick]
var.LAST_SAID[user.nick] = datetime.now() # FIXME: need updating when var.LAST_SAID holds User instances
channels.Main.send(messages["player_return"].format(user))
for roleset in var.ORIGINAL_ROLES.values():
if "(dced)" + user.nick in roleset: # FIXME: Get rid of the (dced) hack for everything at once, and also fix once role sets hold User instances
roleset.remove("(dced)" + user.nick)
roleset.add(user.nick)
break
if user.nick in var.DCED_PLAYERS: # FIXME: Fix this once this variable holds User instances
var.PLAYERS[user.nick] = var.DCED_PLAYERS.pop(nick)
# if they were gone, maybe mark them as back
return_to_village(var, user, show_message=True)
@event_listener("nick_change")
def nick_change(evt, var, user, old_rawnick):
nick = users.parse_rawnick_as_dict(old_rawnick)["nick"] # FIXME: We won't need that when all variables hold User instances
if user.nick not in var.DISCONNECTED and nick in list_players() and re.search(var.GUEST_NICK_PATTERN, user.nick): # FIXME: Fix this once var.DISCONNECTED and list_players() hold User instances
if user not in var.DISCONNECTED and user in get_players() and re.search(var.GUEST_NICK_PATTERN, user.nick):
if var.PHASE != "join":
channels.Main.mode(["-" + hooks.Features["PREFIX"]["+"], user.nick])
temp = users.FakeUser(None, nick, user.ident, user.host, user.realname, user.account)
@ -3133,8 +3098,7 @@ def nick_change(evt, var, user, old_rawnick):
if user not in channels.Main.users:
return
if nick not in var.DISCONNECTED: # FIXME: Need to update this once var.DISCONNECTED holds User instances
rename_player(var, user, nick) # FIXME: Fix when rename_player supports the new interface
rename_player(var, user, nick)
@event_listener("cleanup_user")
def cleanup_user(evt, var, user):
@ -3166,8 +3130,9 @@ def leave(var, what, user, why=None):
if var.PHASE == "none":
return
ps = get_players()
# Only mark living players as disconnected, unless they were kicked
if user.nick in var.PLAYERS and (what == "kick" or user.nick in list_players()): # FIXME: Need to fix this once var.PLAYERS and list_players() hold User instances
if user in var.ALL_PLAYERS and (what == "kick" or user in ps):
# Prevent duplicate entry in var.ORIGINAL_ROLES
for roleset in var.ORIGINAL_ROLES.values():
if user.nick in roleset: # FIXME: Need to fix this once the role sets hold User instances
@ -3175,9 +3140,9 @@ def leave(var, what, user, why=None):
roleset.add("(dced)" + user.nick) # FIXME: Need to get rid of all the (dced) hacks
break
var.DCED_PLAYERS[user.nick] = var.PLAYERS.pop(user.nick) # FIXME: Need to fix this once var.{DCED_}PLAYERS hold User instances
var.DCED_PLAYERS[user.nick] = var.PLAYERS.pop(user.nick) # FIXME: Need to fix this once var.DCED_PLAYERS hold User instances
if user.nick not in list_players() or user.nick in var.DISCONNECTED: # FIXME: Need to fix this once list_players() and var.DISCONNECTED hold User instances
if user not in ps or user in var.DISCONNECTED:
return
# If we got that far, the player was in the game. This variable tracks whether or not we want to kill them off.
@ -3186,7 +3151,7 @@ def leave(var, what, user, why=None):
population = ""
if var.PHASE == "join":
lpl = len(list_players()) - 1
lpl = len(ps) - 1
if lpl < var.MIN_PLAYERS:
with var.WARNING_LOCK:
var.START_VOTES.clear()
@ -3209,6 +3174,8 @@ def leave(var, what, user, why=None):
reason = "leave"
if reason in grace_times and (grace_times[reason] <= 0 or var.PHASE == "join"):
# possible message keys (for easy grep):
# "quit_death", "quit_death_no_reveal", "leave_death", "leave_death_no_reveal", "account_death", "account_death_no_reveal"
msg = messages["{0}_death{1}".format(reason, reveal)]
elif what != "kick": # There's time for the player to rejoin the game
user.send(messages["part_grace_time_notice"].format(botconfig.CHANNEL, var.PART_GRACE_TIME))
@ -3229,7 +3196,7 @@ def leave(var, what, user, why=None):
del_player(user, death_triggers=False)
else:
temp = user.lower()
var.DISCONNECTED[user.nick] = (temp.account, temp.userhost, datetime.now(), what) # FIXME: Need to make var.DISCONNECTED hold User instances
var.DISCONNECTED[user] = (datetime.now(), what)
@cmd("quit", "leave", pm=True, phases=("join", "day", "night"))
def leave_game(cli, nick, chan, rest):
@ -4223,7 +4190,8 @@ def retract(cli, nick, chan, rest):
if chan != botconfig.CHANNEL:
return
if nick not in list_players() or nick in var.DISCONNECTED.keys():
user = users._get(nick) # FIXME
if user not in get_players() or user in var.DISCONNECTED:
return
with var.GRAVEYARD_LOCK, var.WARNING_LOCK: