From de348c22d5f1287cc38734e6e3b77fc492629721 Mon Sep 17 00:00:00 2001 From: oldmud0 Date: Sat, 10 Nov 2018 23:15:54 -0600 Subject: [PATCH] Coalesce server changes into patch file (this is not a monorepo) --- server/__init__.py | 0 server/aoprotocol.py | 807 ------------ server/area_manager.py | 412 ------- server/ban_manager.py | 54 - server/client_manager.py | 457 ------- server/commands.py | 1255 ------------------- server/constants.py | 11 - server/districtclient.py | 79 -- server/evidence.py | 100 -- server/exceptions.py | 32 - server/fantacrypt.py | 45 - server/logger.py | 78 -- server/masterserverclient.py | 89 -- server/tsuserver.py | 305 ----- server/websocket.py | 215 ---- tsuserver3.patch | 2227 ++++++++++++++++++++++++++++++++++ 16 files changed, 2227 insertions(+), 3939 deletions(-) delete mode 100644 server/__init__.py delete mode 100644 server/aoprotocol.py delete mode 100644 server/area_manager.py delete mode 100644 server/ban_manager.py delete mode 100644 server/client_manager.py delete mode 100644 server/commands.py delete mode 100644 server/constants.py delete mode 100644 server/districtclient.py delete mode 100644 server/evidence.py delete mode 100644 server/exceptions.py delete mode 100644 server/fantacrypt.py delete mode 100644 server/logger.py delete mode 100644 server/masterserverclient.py delete mode 100644 server/tsuserver.py delete mode 100644 server/websocket.py create mode 100644 tsuserver3.patch diff --git a/server/__init__.py b/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/aoprotocol.py b/server/aoprotocol.py deleted file mode 100644 index 2cf6fb4..0000000 --- a/server/aoprotocol.py +++ /dev/null @@ -1,807 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import asyncio -import re -from time import localtime, strftime -from enum import Enum - -from . import commands -from . import logger -from .exceptions import ClientError, AreaError, ArgumentError, ServerError -from .fantacrypt import fanta_decrypt -from .evidence import EvidenceList -from .websocket import WebSocket -import unicodedata - - -class AOProtocol(asyncio.Protocol): - """ - The main class that deals with the AO protocol. - """ - - class ArgType(Enum): - STR = 1, - STR_OR_EMPTY = 2, - INT = 3 - - def __init__(self, server): - super().__init__() - self.server = server - self.client = None - self.buffer = '' - self.ping_timeout = None - self.websocket = None - - def data_received(self, data): - """ Handles any data received from the network. - - Receives data, parses them into a command and passes it - to the command handler. - - :param data: bytes of data - """ - - - if self.websocket is None: - self.websocket = WebSocket(self.client, self) - if not self.websocket.handshake(data): - self.websocket = False - else: - self.client.websocket = self.websocket - - buf = data - - if not self.client.is_checked and self.server.ban_manager.is_banned(self.client.ipid): - self.client.transport.close() - else: - self.client.is_checked = True - - if self.websocket: - buf = self.websocket.handle(data) - - if buf is None: - buf = b'' - - if not isinstance(buf, str): - # try to decode as utf-8, ignore any erroneous characters - self.buffer += buf.decode('utf-8', 'ignore') - else: - self.buffer = buf - - if len(self.buffer) > 8192: - self.client.disconnect() - for msg in self.get_messages(): - if len(msg) < 2: - continue - # general netcode structure is not great - if msg[0] in ('#', '3', '4'): - if msg[0] == '#': - msg = msg[1:] - spl = msg.split('#', 1) - msg = '#'.join([fanta_decrypt(spl[0])] + spl[1:]) - logger.log_debug('[INC][RAW]{}'.format(msg), self.client) - try: - cmd, *args = msg.split('#') - self.net_cmd_dispatcher[cmd](self, args) - except KeyError: - logger.log_debug('[INC][UNK]{}'.format(msg), self.client) - - def connection_made(self, transport): - """ Called upon a new client connecting - - :param transport: the transport object - """ - self.client = self.server.new_client(transport) - self.ping_timeout = asyncio.get_event_loop().call_later(self.server.config['timeout'], self.client.disconnect) - asyncio.get_event_loop().call_later(0.25, self.client.send_command, 'decryptor', 34) # just fantacrypt things) - - def connection_lost(self, exc): - """ User disconnected - - :param exc: reason - """ - self.server.remove_client(self.client) - self.ping_timeout.cancel() - - def get_messages(self): - """ Parses out full messages from the buffer. - - :return: yields messages - """ - while '#%' in self.buffer: - spl = self.buffer.split('#%', 1) - self.buffer = spl[1] - yield spl[0] - # exception because bad netcode - askchar2 = '#615810BC07D12A5A#' - if self.buffer == askchar2: - self.buffer = '' - yield askchar2 - - def validate_net_cmd(self, args, *types, needs_auth=True): - """ Makes sure the net command's arguments match expectations. - - :param args: actual arguments to the net command - :param types: what kind of data types are expected - :param needs_auth: whether you need to have chosen a character - :return: returns True if message was validated - """ - if needs_auth and self.client.char_id == -1: - return False - if len(args) != len(types): - return False - for i, arg in enumerate(args): - if len(arg) == 0 and types[i] != self.ArgType.STR_OR_EMPTY: - return False - if types[i] == self.ArgType.INT: - try: - args[i] = int(arg) - except ValueError: - return False - return True - - def net_cmd_hi(self, args): - """ Handshake. - - HI##% - - :param args: a list containing all the arguments - """ - if not self.validate_net_cmd(args, self.ArgType.STR, needs_auth=False): - return - self.client.hdid = args[0] - if self.client.hdid not in self.client.server.hdid_list: - self.client.server.hdid_list[self.client.hdid] = [] - if self.client.ipid not in self.client.server.hdid_list[self.client.hdid]: - self.client.server.hdid_list[self.client.hdid].append(self.client.ipid) - self.client.server.dump_hdids() - for ipid in self.client.server.hdid_list[self.client.hdid]: - if self.server.ban_manager.is_banned(ipid): - self.client.send_command('BD') - self.client.disconnect() - return - logger.log_server('Connected. HDID: {}.'.format(self.client.hdid), self.client) - self.client.send_command('ID', self.client.id, self.server.software, self.server.get_version_string()) - self.client.send_command('PN', self.server.get_player_count() - 1, self.server.config['playerlimit']) - - def net_cmd_id(self, args): - """ Client version and PV - - ID####% - - """ - - self.client.is_ao2 = False - - if len(args) < 2: - return - - version_list = args[1].split('.') - - if len(version_list) < 3: - return - - release = int(version_list[0]) - major = int(version_list[1]) - minor = int(version_list[2]) - - if args[0] != 'AO2': - return - if release < 2: - return - elif release == 2: - if major < 2: - return - elif major == 2: - if minor < 5: - return - - self.client.is_ao2 = True - - self.client.send_command('FL', 'yellowtext', 'customobjections', 'flipping', 'fastloading', 'noencryption', 'deskmod', 'evidence', 'modcall_reason', 'cccc_ic_support', 'arup', 'casing_alerts') - - def net_cmd_ch(self, _): - """ Periodically checks the connection. - - CHECK#% - - """ - self.client.send_command('CHECK') - self.ping_timeout.cancel() - self.ping_timeout = asyncio.get_event_loop().call_later(self.server.config['timeout'], self.client.disconnect) - - def net_cmd_askchaa(self, _): - """ Ask for the counts of characters/evidence/music - - askchaa#% - - """ - char_cnt = len(self.server.char_list) - evi_cnt = 0 - music_cnt = sum([len(x) for x in self.server.music_pages_ao1]) - self.client.send_command('SI', char_cnt, evi_cnt, music_cnt) - - def net_cmd_askchar2(self, _): - """ Asks for the character list. - - askchar2#% - - """ - self.client.send_command('CI', *self.server.char_pages_ao1[0]) - - def net_cmd_an(self, args): - """ Asks for specific pages of the character list. - - AN##% - - """ - if not self.validate_net_cmd(args, self.ArgType.INT, needs_auth=False): - return - if len(self.server.char_pages_ao1) > args[0] >= 0: - self.client.send_command('CI', *self.server.char_pages_ao1[args[0]]) - else: - self.client.send_command('EM', *self.server.music_pages_ao1[0]) - - def net_cmd_ae(self, _): - """ Asks for specific pages of the evidence list. - - AE##% - - """ - pass # todo evidence maybe later - - def net_cmd_am(self, args): - """ Asks for specific pages of the music list. - - AM##% - - """ - if not self.validate_net_cmd(args, self.ArgType.INT, needs_auth=False): - return - if len(self.server.music_pages_ao1) > args[0] >= 0: - self.client.send_command('EM', *self.server.music_pages_ao1[args[0]]) - else: - self.client.send_done() - self.client.send_area_list() - self.client.send_motd() - - def net_cmd_rc(self, _): - """ Asks for the whole character list(AO2) - - AC#% - - """ - - self.client.send_command('SC', *self.server.char_list) - - def net_cmd_rm(self, _): - """ Asks for the whole music list(AO2) - - AM#% - - """ - - self.client.send_command('SM', *self.server.music_list_ao2) - - - def net_cmd_rd(self, _): - """ Asks for server metadata(charscheck, motd etc.) and a DONE#% signal(also best packet) - - RD#% - - """ - - self.client.send_done() - self.client.send_area_list() - self.client.send_motd() - - def net_cmd_cc(self, args): - """ Character selection. - - CC####% - - """ - if not self.validate_net_cmd(args, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR, needs_auth=False): - return - cid = args[1] - try: - self.client.change_character(cid) - except ClientError: - return - - def net_cmd_ms(self, args): - """ IC message. - - Refer to the implementation for details. - - """ - if self.client.is_muted: # Checks to see if the client has been muted by a mod - self.client.send_host_message("You have been muted by a moderator") - return - if not self.client.area.can_send_message(self.client): - return - - target_area = [] - - if self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, - self.ArgType.STR, - self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, - self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, - self.ArgType.INT, self.ArgType.INT, self.ArgType.INT): - # Vanilla validation monstrosity. - msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color = args - showname = "" - charid_pair = -1 - offset_pair = 0 - nonint_pre = 0 - elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, - self.ArgType.STR, - self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, - self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, - self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY): - # 1.3.0 validation monstrosity. - msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname = args - charid_pair = -1 - offset_pair = 0 - nonint_pre = 0 - if len(showname) > 0 and not self.client.area.showname_changes_allowed: - self.client.send_host_message("Showname changes are forbidden in this area!") - return - elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, - self.ArgType.STR, - self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, - self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, - self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY, self.ArgType.INT, self.ArgType.INT): - # 1.3.5 validation monstrosity. - msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname, charid_pair, offset_pair = args - nonint_pre = 0 - if len(showname) > 0 and not self.client.area.showname_changes_allowed: - self.client.send_host_message("Showname changes are forbidden in this area!") - return - elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, - self.ArgType.STR, - self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, - self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, - self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT): - # 1.4.0 validation monstrosity. - msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname, charid_pair, offset_pair, nonint_pre = args - if len(showname) > 0 and not self.client.area.showname_changes_allowed: - self.client.send_host_message("Showname changes are forbidden in this area!") - return - else: - return - if self.client.area.is_iniswap(self.client, pre, anim, folder) and folder != self.client.get_char_name(): - self.client.send_host_message("Iniswap is blocked in this area") - return - if len(self.client.charcurse) > 0 and folder != self.client.get_char_name(): - self.client.send_host_message("You may not iniswap while you are charcursed!") - return - if not self.client.area.blankposting_allowed: - if text == ' ': - self.client.send_host_message("Blankposting is forbidden in this area!") - return - if text.isspace(): - self.client.send_host_message("Blankposting is forbidden in this area, and putting more spaces in does not make it not blankposting.") - return - if len(re.sub(r'[{}\\`|(~~)]','', text).replace(' ', '')) < 3 and text != '<' and text != '>': - self.client.send_host_message("While that is not a blankpost, it is still pretty spammy. Try forming sentences.") - return - if text.startswith('/a '): - part = text.split(' ') - try: - aid = int(part[1]) - if self.client in self.server.area_manager.get_area_by_id(aid).owners: - target_area.append(aid) - if not target_area: - self.client.send_host_message('You don\'t own {}!'.format(self.server.area_manager.get_area_by_id(aid).name)) - return - text = ' '.join(part[2:]) - except ValueError: - self.client.send_host_message("That does not look like a valid area ID!") - return - elif text.startswith('/s '): - part = text.split(' ') - for a in self.server.area_manager.areas: - if self.client in a.owners: - target_area.append(a.id) - if not target_area: - self.client.send_host_message('You don\'t any areas!') - return - text = ' '.join(part[1:]) - if msg_type not in ('chat', '0', '1'): - return - if anim_type not in (0, 1, 2, 5, 6): - return - if cid != self.client.char_id: - return - if sfx_delay < 0: - return - if button not in (0, 1, 2, 3, 4): - return - if evidence < 0: - return - if ding not in (0, 1): - return - if color not in (0, 1, 2, 3, 4, 5, 6, 7, 8): - return - if len(showname) > 15: - self.client.send_host_message("Your IC showname is way too long!") - return - if nonint_pre == 1: - if button in (1, 2, 3, 4, 23): - if anim_type == 1 or anim_type == 2: - anim_type = 0 - elif anim_type == 6: - anim_type = 5 - if self.client.area.non_int_pres_only: - if anim_type == 1 or anim_type == 2: - anim_type = 0 - nonint_pre = 1 - elif anim_type == 6: - anim_type = 5 - nonint_pre = 1 - if not self.client.area.shouts_allowed: - # Old clients communicate the objecting in anim_type. - if anim_type == 2: - anim_type = 1 - elif anim_type == 6: - anim_type = 5 - # New clients do it in a specific objection message area. - button = 0 - # Turn off the ding. - ding = 0 - if color == 2 and not (self.client.is_mod or self.client in self.client.area.owners): - color = 0 - if color == 6: - text = re.sub(r'[^\x00-\x7F]+',' ', text) #remove all unicode to prevent redtext abuse - if len(text.strip( ' ' )) == 1: - color = 0 - else: - if text.strip( ' ' ) in ('', '', '', ''): - color = 0 - if self.client.pos: - pos = self.client.pos - else: - if pos not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit', 'jur', 'sea'): - return - msg = text[:256] - if self.client.shaken: - msg = self.client.shake_message(msg) - if self.client.disemvowel: - msg = self.client.disemvowel_message(msg) - self.client.pos = pos - if evidence: - if self.client.area.evi_list.evidences[self.client.evi_list[evidence] - 1].pos != 'all': - self.client.area.evi_list.evidences[self.client.evi_list[evidence] - 1].pos = 'all' - self.client.area.broadcast_evidence_list() - - # Here, we check the pair stuff, and save info about it to the client. - # Notably, while we only get a charid_pair and an offset, we send back a chair_pair, an emote, a talker offset - # and an other offset. - self.client.charid_pair = charid_pair - self.client.offset_pair = offset_pair - if anim_type not in (5, 6): - self.client.last_sprite = anim - self.client.flip = flip - self.client.claimed_folder = folder - other_offset = 0 - other_emote = '' - other_flip = 0 - other_folder = '' - - confirmed = False - if charid_pair > -1: - for target in self.client.area.clients: - if target.char_id == self.client.charid_pair and target.charid_pair == self.client.char_id and target != self.client and target.pos == self.client.pos: - confirmed = True - other_offset = target.offset_pair - other_emote = target.last_sprite - other_flip = target.flip - other_folder = target.claimed_folder - break - - if not confirmed: - charid_pair = -1 - offset_pair = 0 - - self.client.area.send_command('MS', msg_type, pre, folder, anim, msg, pos, sfx, anim_type, cid, - sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, - charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) - - self.client.area.send_owner_command('MS', msg_type, pre, folder, anim, '[' + self.client.area.abbreviation + ']' + msg, pos, sfx, anim_type, cid, - sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, - charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) - - self.server.area_manager.send_remote_command(target_area, 'MS', msg_type, pre, folder, anim, msg, pos, sfx, anim_type, cid, - sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, - charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) - - self.client.area.set_next_msg_delay(len(msg)) - logger.log_server('[IC][{}][{}]{}'.format(self.client.area.abbreviation, self.client.get_char_name(), msg), self.client) - - if (self.client.area.is_recording): - self.client.area.recorded_messages.append(args) - - def net_cmd_ct(self, args): - """ OOC Message - - CT###% - - """ - if self.client.is_ooc_muted: # Checks to see if the client has been muted by a mod - self.client.send_host_message("You have been muted by a moderator") - return - if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR): - return - if self.client.name != args[0] and self.client.fake_name != args[0]: - if self.client.is_valid_name(args[0]): - self.client.name = args[0] - self.client.fake_name = args[0] - else: - self.client.fake_name = args[0] - if self.client.name == '': - self.client.send_host_message('You must insert a name with at least one letter') - return - if len(self.client.name) > 30: - self.client.send_host_message('Your OOC name is too long! Limit it to 30 characters.') - return - for c in self.client.name: - if unicodedata.category(c) == 'Cf': - self.client.send_host_message('You cannot use format characters in your name!') - return - if self.client.name.startswith(self.server.config['hostname']) or self.client.name.startswith('G') or self.client.name.startswith('M'): - self.client.send_host_message('That name is reserved!') - return - if args[1].startswith(' /'): - self.client.send_host_message('Your message was not sent for safety reasons: you left a space before that slash.') - return - if args[1].startswith('/'): - spl = args[1][1:].split(' ', 1) - cmd = spl[0].lower() - arg = '' - if len(spl) == 2: - arg = spl[1][:256] - try: - called_function = 'ooc_cmd_{}'.format(cmd) - getattr(commands, called_function)(self.client, arg) - except AttributeError: - print('Attribute error with ' + called_function) - self.client.send_host_message('Invalid command.') - except (ClientError, AreaError, ArgumentError, ServerError) as ex: - self.client.send_host_message(ex) - else: - if self.client.shaken: - args[1] = self.client.shake_message(args[1]) - if self.client.disemvowel: - args[1] = self.client.disemvowel_message(args[1]) - self.client.area.send_command('CT', self.client.name, args[1]) - self.client.area.send_owner_command('CT', '[' + self.client.area.abbreviation + ']' + self.client.name, args[1]) - logger.log_server( - '[OOC][{}][{}]{}'.format(self.client.area.abbreviation, self.client.get_char_name(), - args[1]), self.client) - - def net_cmd_mc(self, args): - """ Play music. - - MC###% - - """ - try: - area = self.server.area_manager.get_area_by_name(args[0]) - self.client.change_area(area) - except AreaError: - if self.client.is_muted: # Checks to see if the client has been muted by a mod - self.client.send_host_message("You have been muted by a moderator") - return - if not self.client.is_dj: - self.client.send_host_message('You were blockdj\'d by a moderator.') - return - if self.client.area.cannot_ic_interact(self.client): - self.client.send_host_message("You are not on the area's invite list, and thus, you cannot change music!") - return - if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT) and not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT, self.ArgType.STR): - return - if args[1] != self.client.char_id: - return - if self.client.change_music_cd(): - self.client.send_host_message('You changed song too many times. Please try again after {} seconds.'.format(int(self.client.change_music_cd()))) - return - try: - name, length = self.server.get_song_data(args[0]) - - if self.client.area.jukebox: - showname = '' - if len(args) > 2: - showname = args[2] - if len(showname) > 0 and not self.client.area.showname_changes_allowed: - self.client.send_host_message("Showname changes are forbidden in this area!") - return - self.client.area.add_jukebox_vote(self.client, name, length, showname) - logger.log_server('[{}][{}]Added a jukebox vote for {}.'.format(self.client.area.abbreviation, self.client.get_char_name(), name), self.client) - else: - if len(args) > 2: - showname = args[2] - if len(showname) > 0 and not self.client.area.showname_changes_allowed: - self.client.send_host_message("Showname changes are forbidden in this area!") - return - self.client.area.play_music_shownamed(name, self.client.char_id, showname, length) - self.client.area.add_music_playing_shownamed(self.client, showname, name) - else: - self.client.area.play_music(name, self.client.char_id, length) - self.client.area.add_music_playing(self.client, name) - logger.log_server('[{}][{}]Changed music to {}.' - .format(self.client.area.abbreviation, self.client.get_char_name(), name), self.client) - except ServerError: - return - except ClientError as ex: - self.client.send_host_message(ex) - - def net_cmd_rt(self, args): - """ Plays the Testimony/CE animation. - - RT##% - - """ - if not self.client.area.shouts_allowed: - self.client.send_host_message("You cannot use the testimony buttons here!") - return - if self.client.is_muted: # Checks to see if the client has been muted by a mod - self.client.send_host_message("You have been muted by a moderator") - return - if not self.client.can_wtce: - self.client.send_host_message('You were blocked from using judge signs by a moderator.') - return - if self.client.area.cannot_ic_interact(self.client): - self.client.send_host_message("You are not on the area's invite list, and thus, you cannot use the WTCE buttons!") - return - if not self.validate_net_cmd(args, self.ArgType.STR) and not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT): - return - if args[0] == 'testimony1': - sign = 'WT' - elif args[0] == 'testimony2': - sign = 'CE' - elif args[0] == 'judgeruling': - sign = 'JR' - else: - return - if self.client.wtce_mute(): - self.client.send_host_message('You used witness testimony/cross examination signs too many times. Please try again after {} seconds.'.format(int(self.client.wtce_mute()))) - return - if len(args) == 1: - self.client.area.send_command('RT', args[0]) - elif len(args) == 2: - self.client.area.send_command('RT', args[0], args[1]) - self.client.area.add_to_judgelog(self.client, 'used {}'.format(sign)) - logger.log_server("[{}]{} Used WT/CE".format(self.client.area.abbreviation, self.client.get_char_name()), self.client) - - def net_cmd_hp(self, args): - """ Sets the penalty bar. - - HP###% - - """ - if self.client.is_muted: # Checks to see if the client has been muted by a mod - self.client.send_host_message("You have been muted by a moderator") - return - if self.client.area.cannot_ic_interact(self.client): - self.client.send_host_message("You are not on the area's invite list, and thus, you cannot change the Confidence bars!") - return - if not self.validate_net_cmd(args, self.ArgType.INT, self.ArgType.INT): - return - try: - self.client.area.change_hp(args[0], args[1]) - self.client.area.add_to_judgelog(self.client, 'changed the penalties') - logger.log_server('[{}]{} changed HP ({}) to {}' - .format(self.client.area.abbreviation, self.client.get_char_name(), args[0], args[1]), self.client) - except AreaError: - return - - def net_cmd_pe(self, args): - """ Adds a piece of evidence. - - PE####% - - """ - if len(args) < 3: - return - #evi = Evidence(args[0], args[1], args[2], self.client.pos) - self.client.area.evi_list.add_evidence(self.client, args[0], args[1], args[2], 'all') - self.client.area.broadcast_evidence_list() - - def net_cmd_de(self, args): - """ Deletes a piece of evidence. - - DE##% - - """ - - self.client.area.evi_list.del_evidence(self.client, self.client.evi_list[int(args[0])]) - self.client.area.broadcast_evidence_list() - - def net_cmd_ee(self, args): - """ Edits a piece of evidence. - - EE#####% - - """ - - if len(args) < 4: - return - - evi = (args[1], args[2], args[3], 'all') - - self.client.area.evi_list.edit_evidence(self.client, self.client.evi_list[int(args[0])], evi) - self.client.area.broadcast_evidence_list() - - - def net_cmd_zz(self, args): - """ Sent on mod call. - - """ - if self.client.is_muted: # Checks to see if the client has been muted by a mod - self.client.send_host_message("You have been muted by a moderator") - return - - if not self.client.can_call_mod(): - self.client.send_host_message("You must wait 30 seconds between mod calls.") - return - - current_time = strftime("%H:%M", localtime()) - - if len(args) < 1: - self.server.send_all_cmd_pred('ZZ', '[{}] {} ({}) in {} without reason (not using the Case Café client?)' - .format(current_time, self.client.get_char_name(), self.client.get_ip(), self.client.area.name), pred=lambda c: c.is_mod) - self.client.set_mod_call_delay() - logger.log_server('[{}]{} called a moderator.'.format(self.client.area.abbreviation, self.client.get_char_name()), self.client) - else: - self.server.send_all_cmd_pred('ZZ', '[{}] {} ({}) in {} with reason: {}' - .format(current_time, self.client.get_char_name(), self.client.get_ip(), self.client.area.name, args[0][:100]), pred=lambda c: c.is_mod) - self.client.set_mod_call_delay() - logger.log_server('[{}]{} called a moderator: {}.'.format(self.client.area.abbreviation, self.client.get_char_name(), args[0]), self.client) - - def net_cmd_opKICK(self, args): - self.net_cmd_ct(['opkick', '/kick {}'.format(args[0])]) - - def net_cmd_opBAN(self, args): - self.net_cmd_ct(['opban', '/ban {}'.format(args[0])]) - - net_cmd_dispatcher = { - 'HI': net_cmd_hi, # handshake - 'ID': net_cmd_id, # client version - 'CH': net_cmd_ch, # keepalive - 'askchaa': net_cmd_askchaa, # ask for list lengths - 'askchar2': net_cmd_askchar2, # ask for list of characters - 'AN': net_cmd_an, # character list - 'AE': net_cmd_ae, # evidence list - 'AM': net_cmd_am, # music list - 'RC': net_cmd_rc, # AO2 character list - 'RM': net_cmd_rm, # AO2 music list - 'RD': net_cmd_rd, # AO2 done request, charscheck etc. - 'CC': net_cmd_cc, # select character - 'MS': net_cmd_ms, # IC message - 'CT': net_cmd_ct, # OOC message - 'MC': net_cmd_mc, # play song - 'RT': net_cmd_rt, # WT/CE buttons - 'HP': net_cmd_hp, # penalties - 'PE': net_cmd_pe, # add evidence - 'DE': net_cmd_de, # delete evidence - 'EE': net_cmd_ee, # edit evidence - 'ZZ': net_cmd_zz, # call mod button - 'opKICK': net_cmd_opKICK, # /kick with guard on - 'opBAN': net_cmd_opBAN, # /ban with guard on - } diff --git a/server/area_manager.py b/server/area_manager.py deleted file mode 100644 index cfb2be0..0000000 --- a/server/area_manager.py +++ /dev/null @@ -1,412 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import asyncio -import random - -import time -import yaml - -from server.exceptions import AreaError -from server.evidence import EvidenceList -from enum import Enum - - -class AreaManager: - class Area: - def __init__(self, area_id, server, name, background, bg_lock, evidence_mod = 'FFA', locking_allowed = False, iniswap_allowed = True, showname_changes_allowed = False, shouts_allowed = True, jukebox = False, abbreviation = '', non_int_pres_only = False): - self.iniswap_allowed = iniswap_allowed - self.clients = set() - self.invite_list = {} - self.id = area_id - self.name = name - self.background = background - self.bg_lock = bg_lock - self.server = server - self.music_looper = None - self.next_message_time = 0 - self.hp_def = 10 - self.hp_pro = 10 - self.doc = 'No document.' - self.status = 'IDLE' - self.judgelog = [] - self.current_music = '' - self.current_music_player = '' - self.current_music_player_ipid = -1 - self.evi_list = EvidenceList() - self.is_recording = False - self.recorded_messages = [] - self.evidence_mod = evidence_mod - self.locking_allowed = locking_allowed - self.showname_changes_allowed = showname_changes_allowed - self.shouts_allowed = shouts_allowed - self.abbreviation = abbreviation - self.cards = dict() - - """ - #debug - self.evidence_list.append(Evidence("WOW", "desc", "1.png")) - self.evidence_list.append(Evidence("wewz", "desc2", "2.png")) - self.evidence_list.append(Evidence("weeeeeew", "desc3", "3.png")) - """ - - self.is_locked = self.Locked.FREE - self.blankposting_allowed = True - self.non_int_pres_only = non_int_pres_only - self.jukebox = jukebox - self.jukebox_votes = [] - self.jukebox_prev_char_id = -1 - - self.owners = [] - - class Locked(Enum): - FREE = 1, - SPECTATABLE = 2, - LOCKED = 3 - - def new_client(self, client): - self.clients.add(client) - self.server.area_manager.send_arup_players() - - def remove_client(self, client): - self.clients.remove(client) - if len(self.clients) == 0: - self.change_status('IDLE') - - def unlock(self): - self.is_locked = self.Locked.FREE - self.blankposting_allowed = True - self.invite_list = {} - self.server.area_manager.send_arup_lock() - self.send_host_message('This area is open now.') - - def spectator(self): - self.is_locked = self.Locked.SPECTATABLE - for i in self.clients: - self.invite_list[i.id] = None - for i in self.owners: - self.invite_list[i.id] = None - self.server.area_manager.send_arup_lock() - self.send_host_message('This area is spectatable now.') - - def lock(self): - self.is_locked = self.Locked.LOCKED - for i in self.clients: - self.invite_list[i.id] = None - for i in self.owners: - self.invite_list[i.id] = None - self.server.area_manager.send_arup_lock() - self.send_host_message('This area is locked now.') - - def is_char_available(self, char_id): - return char_id not in [x.char_id for x in self.clients] - - def get_rand_avail_char_id(self): - avail_set = set(range(len(self.server.char_list))) - set([x.char_id for x in self.clients]) - if len(avail_set) == 0: - raise AreaError('No available characters.') - return random.choice(tuple(avail_set)) - - def send_command(self, cmd, *args): - for c in self.clients: - c.send_command(cmd, *args) - - def send_owner_command(self, cmd, *args): - for c in self.owners: - if not c in self.clients: - c.send_command(cmd, *args) - - def send_host_message(self, msg): - self.send_command('CT', self.server.config['hostname'], msg, '1') - self.send_owner_command('CT', '[' + self.abbreviation + ']' + self.server.config['hostname'], msg, '1') - - def set_next_msg_delay(self, msg_length): - delay = min(3000, 100 + 60 * msg_length) - self.next_message_time = round(time.time() * 1000.0 + delay) - - def is_iniswap(self, client, anim1, anim2, char): - if self.iniswap_allowed: - return False - if '..' in anim1 or '..' in anim2: - return True - for char_link in self.server.allowed_iniswaps: - if client.get_char_name() in char_link and char in char_link: - return False - return True - - def add_jukebox_vote(self, client, music_name, length=-1, showname=''): - if not self.jukebox: - return - if length <= 0: - self.remove_jukebox_vote(client, False) - else: - self.remove_jukebox_vote(client, True) - self.jukebox_votes.append(self.JukeboxVote(client, music_name, length, showname)) - client.send_host_message('Your song was added to the jukebox.') - if len(self.jukebox_votes) == 1: - self.start_jukebox() - - def remove_jukebox_vote(self, client, silent): - if not self.jukebox: - return - for current_vote in self.jukebox_votes: - if current_vote.client.id == client.id: - self.jukebox_votes.remove(current_vote) - if not silent: - client.send_host_message('You removed your song from the jukebox.') - - def get_jukebox_picked(self): - if not self.jukebox: - return - if len(self.jukebox_votes) == 0: - return None - elif len(self.jukebox_votes) == 1: - return self.jukebox_votes[0] - else: - weighted_votes = [] - for current_vote in self.jukebox_votes: - i = 0 - while i < current_vote.chance: - weighted_votes.append(current_vote) - i += 1 - return random.choice(weighted_votes) - - def start_jukebox(self): - # There is a probability that the jukebox feature has been turned off since then, - # we should check that. - # We also do a check if we were the last to play a song, just in case. - if not self.jukebox: - if self.current_music_player == 'The Jukebox' and self.current_music_player_ipid == 'has no IPID': - self.current_music = '' - return - - vote_picked = self.get_jukebox_picked() - - if vote_picked is None: - self.current_music = '' - return - - if vote_picked.client.char_id != self.jukebox_prev_char_id or vote_picked.name != self.current_music or len(self.jukebox_votes) > 1: - self.jukebox_prev_char_id = vote_picked.client.char_id - if vote_picked.showname == '': - self.send_command('MC', vote_picked.name, vote_picked.client.char_id) - else: - self.send_command('MC', vote_picked.name, vote_picked.client.char_id, vote_picked.showname) - else: - self.send_command('MC', vote_picked.name, -1) - - self.current_music_player = 'The Jukebox' - self.current_music_player_ipid = 'has no IPID' - self.current_music = vote_picked.name - - for current_vote in self.jukebox_votes: - # Choosing the same song will get your votes down to 0, too. - # Don't want the same song twice in a row! - if current_vote.name == vote_picked.name: - current_vote.chance = 0 - else: - current_vote.chance += 1 - - if self.music_looper: - self.music_looper.cancel() - self.music_looper = asyncio.get_event_loop().call_later(vote_picked.length, lambda: self.start_jukebox()) - - def play_music(self, name, cid, length=-1): - self.send_command('MC', name, cid) - if self.music_looper: - self.music_looper.cancel() - if length > 0: - self.music_looper = asyncio.get_event_loop().call_later(length, - lambda: self.play_music(name, -1, length)) - - def play_music_shownamed(self, name, cid, showname, length=-1): - self.send_command('MC', name, cid, showname) - if self.music_looper: - self.music_looper.cancel() - if length > 0: - self.music_looper = asyncio.get_event_loop().call_later(length, - lambda: self.play_music(name, -1, length)) - - - def can_send_message(self, client): - if self.cannot_ic_interact(client): - client.send_host_message('This is a locked area - ask the CM to speak.') - return False - return (time.time() * 1000.0 - self.next_message_time) > 0 - - def cannot_ic_interact(self, client): - return self.is_locked != self.Locked.FREE and not client.is_mod and not client.id in self.invite_list - - def change_hp(self, side, val): - if not 0 <= val <= 10: - raise AreaError('Invalid penalty value.') - if not 1 <= side <= 2: - raise AreaError('Invalid penalty side.') - if side == 1: - self.hp_def = val - elif side == 2: - self.hp_pro = val - self.send_command('HP', side, val) - - def change_background(self, bg): - if bg.lower() not in (name.lower() for name in self.server.backgrounds): - raise AreaError('Invalid background name.') - self.background = bg - self.send_command('BN', self.background) - - def change_status(self, value): - allowed_values = ('idle', 'rp', 'casing', 'looking-for-players', 'lfp', 'recess', 'gaming') - if value.lower() not in allowed_values: - raise AreaError('Invalid status. Possible values: {}'.format(', '.join(allowed_values))) - if value.lower() == 'lfp': - value = 'looking-for-players' - self.status = value.upper() - self.server.area_manager.send_arup_status() - - def change_doc(self, doc='No document.'): - self.doc = doc - - def add_to_judgelog(self, client, msg): - if len(self.judgelog) >= 10: - self.judgelog = self.judgelog[1:] - self.judgelog.append('{} ({}) {}.'.format(client.get_char_name(), client.get_ip(), msg)) - - def add_music_playing(self, client, name): - self.current_music_player = client.get_char_name() - self.current_music_player_ipid = client.ipid - self.current_music = name - - def add_music_playing_shownamed(self, client, showname, name): - self.current_music_player = showname + " (" + client.get_char_name() + ")" - self.current_music_player_ipid = client.ipid - self.current_music = name - - def get_evidence_list(self, client): - client.evi_list, evi_list = self.evi_list.create_evi_list(client) - return evi_list - - def broadcast_evidence_list(self): - """ - LE#&&# - - """ - for client in self.clients: - client.send_command('LE', *self.get_evidence_list(client)) - - def get_cms(self): - msg = '' - for i in self.owners: - msg = msg + '[' + str(i.id) + '] ' + i.get_char_name() + ', ' - if len(msg) > 2: - msg = msg[:-2] - return msg - - class JukeboxVote: - def __init__(self, client, name, length, showname): - self.client = client - self.name = name - self.length = length - self.chance = 1 - self.showname = showname - - def __init__(self, server): - self.server = server - self.cur_id = 0 - self.areas = [] - self.load_areas() - - def load_areas(self): - with open('config/areas.yaml', 'r') as chars: - areas = yaml.load(chars) - for item in areas: - if 'evidence_mod' not in item: - item['evidence_mod'] = 'FFA' - if 'locking_allowed' not in item: - item['locking_allowed'] = False - if 'iniswap_allowed' not in item: - item['iniswap_allowed'] = True - if 'showname_changes_allowed' not in item: - item['showname_changes_allowed'] = False - if 'shouts_allowed' not in item: - item['shouts_allowed'] = True - if 'jukebox' not in item: - item['jukebox'] = False - if 'noninterrupting_pres' not in item: - item['noninterrupting_pres'] = False - if 'abbreviation' not in item: - item['abbreviation'] = self.get_generated_abbreviation(item['area']) - self.areas.append( - self.Area(self.cur_id, self.server, item['area'], item['background'], item['bglock'], item['evidence_mod'], item['locking_allowed'], item['iniswap_allowed'], item['showname_changes_allowed'], item['shouts_allowed'], item['jukebox'], item['abbreviation'], item['noninterrupting_pres'])) - self.cur_id += 1 - - def default_area(self): - return self.areas[0] - - def get_area_by_name(self, name): - for area in self.areas: - if area.name == name: - return area - raise AreaError('Area not found.') - - def get_area_by_id(self, num): - for area in self.areas: - if area.id == num: - return area - raise AreaError('Area not found.') - - def get_generated_abbreviation(self, name): - if name.lower().startswith("courtroom"): - return "CR" + name.split()[-1] - elif name.lower().startswith("area"): - return "A" + name.split()[-1] - elif len(name.split()) > 1: - return "".join(item[0].upper() for item in name.split()) - elif len(name) > 3: - return name[:3].upper() - else: - return name.upper() - - def send_remote_command(self, area_ids, cmd, *args): - for a_id in area_ids: - self.get_area_by_id(a_id).send_command(cmd, *args) - self.get_area_by_id(a_id).send_owner_command(cmd, *args) - - def send_arup_players(self): - players_list = [0] - for area in self.areas: - players_list.append(len(area.clients)) - self.server.send_arup(players_list) - - def send_arup_status(self): - status_list = [1] - for area in self.areas: - status_list.append(area.status) - self.server.send_arup(status_list) - - def send_arup_cms(self): - cms_list = [2] - for area in self.areas: - cm = 'FREE' - if len(area.owners) > 0: - cm = area.get_cms() - cms_list.append(cm) - self.server.send_arup(cms_list) - - def send_arup_lock(self): - lock_list = [3] - for area in self.areas: - lock_list.append(area.is_locked.name) - self.server.send_arup(lock_list) diff --git a/server/ban_manager.py b/server/ban_manager.py deleted file mode 100644 index 20c186f..0000000 --- a/server/ban_manager.py +++ /dev/null @@ -1,54 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import json - -from server.exceptions import ServerError - - -class BanManager: - def __init__(self): - self.bans = [] - self.load_banlist() - - def load_banlist(self): - try: - with open('storage/banlist.json', 'r') as banlist_file: - self.bans = json.load(banlist_file) - except FileNotFoundError: - return - - def write_banlist(self): - with open('storage/banlist.json', 'w') as banlist_file: - json.dump(self.bans, banlist_file) - - def add_ban(self, ip): - if ip not in self.bans: - self.bans.append(ip) - else: - raise ServerError('This IPID is already banned.') - self.write_banlist() - - def remove_ban(self, ip): - if ip in self.bans: - self.bans.remove(ip) - else: - raise ServerError('This IPID is not banned.') - self.write_banlist() - - def is_banned(self, ipid): - return (ipid in self.bans) \ No newline at end of file diff --git a/server/client_manager.py b/server/client_manager.py deleted file mode 100644 index 432c39d..0000000 --- a/server/client_manager.py +++ /dev/null @@ -1,457 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from server import fantacrypt -from server import logger -from server.exceptions import ClientError, AreaError -from enum import Enum -from server.constants import TargetType -from heapq import heappop, heappush - -import time -import re - - - -class ClientManager: - class Client: - def __init__(self, server, transport, user_id, ipid): - self.is_checked = False - self.transport = transport - self.hdid = '' - self.pm_mute = False - self.id = user_id - self.char_id = -1 - self.area = server.area_manager.default_area() - self.server = server - self.name = '' - self.fake_name = '' - self.is_mod = False - self.is_dj = True - self.can_wtce = True - self.pos = '' - self.evi_list = [] - self.disemvowel = False - self.shaken = False - self.charcurse = [] - self.muted_global = False - self.muted_adverts = False - self.is_muted = False - self.is_ooc_muted = False - self.pm_mute = False - self.mod_call_time = 0 - self.in_rp = False - self.ipid = ipid - self.websocket = None - - # Pairing stuff - self.charid_pair = -1 - self.offset_pair = 0 - self.last_sprite = '' - self.flip = 0 - self.claimed_folder = '' - - # Casing stuff - self.casing_cm = False - self.casing_cases = "" - self.casing_def = False - self.casing_pro = False - self.casing_jud = False - self.casing_jur = False - self.casing_steno = False - self.case_call_time = 0 - - #flood-guard stuff - self.mus_counter = 0 - self.mus_mute_time = 0 - self.mus_change_time = [x * self.server.config['music_change_floodguard']['interval_length'] for x in range(self.server.config['music_change_floodguard']['times_per_interval'])] - self.wtce_counter = 0 - self.wtce_mute_time = 0 - self.wtce_time = [x * self.server.config['wtce_floodguard']['interval_length'] for x in range(self.server.config['wtce_floodguard']['times_per_interval'])] - - def send_raw_message(self, msg): - if self.websocket: - self.websocket.send_text(msg.encode('utf-8')) - else: - self.transport.write(msg.encode('utf-8')) - - def send_command(self, command, *args): - if args: - if command == 'MS': - for evi_num in range(len(self.evi_list)): - if self.evi_list[evi_num] == args[11]: - lst = list(args) - lst[11] = evi_num - args = tuple(lst) - break - self.send_raw_message('{}#{}#%'.format(command, '#'.join([str(x) for x in args]))) - else: - self.send_raw_message('{}#%'.format(command)) - - def send_host_message(self, msg): - self.send_command('CT', self.server.config['hostname'], msg, '1') - - def send_motd(self): - self.send_host_message('=== MOTD ===\r\n{}\r\n============='.format(self.server.config['motd'])) - - def send_player_count(self): - self.send_host_message('{}/{} players online.'.format( - self.server.get_player_count(), - self.server.config['playerlimit'])) - - def is_valid_name(self, name): - name_ws = name.replace(' ', '') - if not name_ws or name_ws.isdigit(): - return False - for client in self.server.client_manager.clients: - print(client.name == name) - if client.name == name: - return False - return True - - def disconnect(self): - self.transport.close() - - def change_character(self, char_id, force=False): - if not self.server.is_valid_char_id(char_id): - raise ClientError('Invalid Character ID.') - if len(self.charcurse) > 0: - if not char_id in self.charcurse: - raise ClientError('Character not available.') - force = True - if not self.area.is_char_available(char_id): - if force: - for client in self.area.clients: - if client.char_id == char_id: - client.char_select() - else: - raise ClientError('Character not available.') - old_char = self.get_char_name() - self.char_id = char_id - self.pos = '' - self.send_command('PV', self.id, 'CID', self.char_id) - self.area.send_command('CharsCheck', *self.get_available_char_list()) - logger.log_server('[{}]Changed character from {} to {}.' - .format(self.area.abbreviation, old_char, self.get_char_name()), self) - - def change_music_cd(self): - if self.is_mod or self in self.area.owners: - return 0 - if self.mus_mute_time: - if time.time() - self.mus_mute_time < self.server.config['music_change_floodguard']['mute_length']: - return self.server.config['music_change_floodguard']['mute_length'] - (time.time() - self.mus_mute_time) - else: - self.mus_mute_time = 0 - times_per_interval = self.server.config['music_change_floodguard']['times_per_interval'] - interval_length = self.server.config['music_change_floodguard']['interval_length'] - if time.time() - self.mus_change_time[(self.mus_counter - times_per_interval + 1) % times_per_interval] < interval_length: - self.mus_mute_time = time.time() - return self.server.config['music_change_floodguard']['mute_length'] - self.mus_counter = (self.mus_counter + 1) % times_per_interval - self.mus_change_time[self.mus_counter] = time.time() - return 0 - - def wtce_mute(self): - if self.is_mod or self in self.area.owners: - return 0 - if self.wtce_mute_time: - if time.time() - self.wtce_mute_time < self.server.config['wtce_floodguard']['mute_length']: - return self.server.config['wtce_floodguard']['mute_length'] - (time.time() - self.wtce_mute_time) - else: - self.wtce_mute_time = 0 - times_per_interval = self.server.config['wtce_floodguard']['times_per_interval'] - interval_length = self.server.config['wtce_floodguard']['interval_length'] - if time.time() - self.wtce_time[(self.wtce_counter - times_per_interval + 1) % times_per_interval] < interval_length: - self.wtce_mute_time = time.time() - return self.server.config['music_change_floodguard']['mute_length'] - self.wtce_counter = (self.wtce_counter + 1) % times_per_interval - self.wtce_time[self.wtce_counter] = time.time() - return 0 - - def reload_character(self): - try: - self.change_character(self.char_id, True) - except ClientError: - raise - - def change_area(self, area): - if self.area == area: - raise ClientError('User already in specified area.') - if area.is_locked == area.Locked.LOCKED and not self.is_mod and not self.id in area.invite_list: - raise ClientError("That area is locked!") - if area.is_locked == area.Locked.SPECTATABLE and not self.is_mod and not self.id in area.invite_list: - self.send_host_message('This area is spectatable, but not free - you will be unable to send messages ICly unless invited.') - - if self.area.jukebox: - self.area.remove_jukebox_vote(self, True) - - old_area = self.area - if not area.is_char_available(self.char_id): - try: - new_char_id = area.get_rand_avail_char_id() - except AreaError: - raise ClientError('No available characters in that area.') - - self.change_character(new_char_id) - self.send_host_message('Character taken, switched to {}.'.format(self.get_char_name())) - - self.area.remove_client(self) - self.area = area - area.new_client(self) - - self.send_host_message('Changed area to {}.[{}]'.format(area.name, self.area.status)) - logger.log_server( - '[{}]Changed area from {} ({}) to {} ({}).'.format(self.get_char_name(), old_area.name, old_area.id, - self.area.name, self.area.id), self) - self.area.send_command('CharsCheck', *self.get_available_char_list()) - self.send_command('HP', 1, self.area.hp_def) - self.send_command('HP', 2, self.area.hp_pro) - self.send_command('BN', self.area.background) - self.send_command('LE', *self.area.get_evidence_list(self)) - - def send_area_list(self): - msg = '=== Areas ===' - for i, area in enumerate(self.server.area_manager.areas): - owner = 'FREE' - if len(area.owners) > 0: - owner = 'CM: {}'.format(area.get_cms()) - lock = {area.Locked.FREE: '', area.Locked.SPECTATABLE: '[SPECTATABLE]', area.Locked.LOCKED: '[LOCKED]'} - msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(area.abbreviation, area.name, len(area.clients), area.status, owner, lock[area.is_locked]) - if self.area == area: - msg += ' [*]' - self.send_host_message(msg) - - def get_area_info(self, area_id, mods): - info = '\r\n' - try: - area = self.server.area_manager.get_area_by_id(area_id) - except AreaError: - raise - info += '=== {} ==='.format(area.name) - info += '\r\n' - - lock = {area.Locked.FREE: '', area.Locked.SPECTATABLE: '[SPECTATABLE]', area.Locked.LOCKED: '[LOCKED]'} - info += '[{}]: [{} users][{}]{}'.format(area.abbreviation, len(area.clients), area.status, lock[area.is_locked]) - - sorted_clients = [] - for client in area.clients: - if (not mods) or client.is_mod: - sorted_clients.append(client) - for owner in area.owners: - if not (mods or owner in area.clients): - sorted_clients.append(owner) - if not sorted_clients: - return '' - sorted_clients = sorted(sorted_clients, key=lambda x: x.get_char_name()) - for c in sorted_clients: - info += '\r\n' - if c in area.owners: - if not c in area.clients: - info += '[RCM]' - else: - info +='[CM]' - info += '[{}] {}'.format(c.id, c.get_char_name()) - if self.is_mod: - info += ' ({})'.format(c.ipid) - info += ': {}'.format(c.name) - - return info - - def send_area_info(self, area_id, mods): - #if area_id is -1 then return all areas. If mods is True then return only mods - info = '' - if area_id == -1: - # all areas info - cnt = 0 - info = '\n== Area List ==' - for i in range(len(self.server.area_manager.areas)): - if len(self.server.area_manager.areas[i].clients) > 0 or len(self.server.area_manager.areas[i].owners) > 0: - cnt += len(self.server.area_manager.areas[i].clients) - info += '{}'.format(self.get_area_info(i, mods)) - info = 'Current online: {}'.format(cnt) + info - else: - try: - info = 'People in this area: {}'.format(len(self.server.area_manager.areas[area_id].clients)) + self.get_area_info(area_id, mods) - except AreaError: - raise - self.send_host_message(info) - - def send_area_hdid(self, area_id): - try: - info = self.get_area_hdid(area_id) - except AreaError: - raise - self.send_host_message(info) - - def send_all_area_hdid(self): - info = '== HDID List ==' - for i in range (len(self.server.area_manager.areas)): - if len(self.server.area_manager.areas[i].clients) > 0: - info += '\r\n{}'.format(self.get_area_hdid(i)) - self.send_host_message(info) - - def send_all_area_ip(self): - info = '== IP List ==' - for i in range (len(self.server.area_manager.areas)): - if len(self.server.area_manager.areas[i].clients) > 0: - info += '\r\n{}'.format(self.get_area_ip(i)) - self.send_host_message(info) - - def send_done(self): - self.send_command('CharsCheck', *self.get_available_char_list()) - self.send_command('HP', 1, self.area.hp_def) - self.send_command('HP', 2, self.area.hp_pro) - self.send_command('BN', self.area.background) - self.send_command('LE', *self.area.get_evidence_list(self)) - self.send_command('MM', 1) - - self.server.area_manager.send_arup_players() - self.server.area_manager.send_arup_status() - self.server.area_manager.send_arup_cms() - self.server.area_manager.send_arup_lock() - - self.send_command('DONE') - - def char_select(self): - self.char_id = -1 - self.send_done() - - def get_available_char_list(self): - if len(self.charcurse) > 0: - avail_char_ids = set(range(len(self.server.char_list))) and set(self.charcurse) - else: - avail_char_ids = set(range(len(self.server.char_list))) - set([x.char_id for x in self.area.clients]) - char_list = [-1] * len(self.server.char_list) - for x in avail_char_ids: - char_list[x] = 0 - return char_list - - def auth_mod(self, password): - if self.is_mod: - raise ClientError('Already logged in.') - if password == self.server.config['modpass']: - self.is_mod = True - else: - raise ClientError('Invalid password.') - - def get_ip(self): - return self.ipid - - - - def get_char_name(self): - if self.char_id == -1: - return 'CHAR_SELECT' - return self.server.char_list[self.char_id] - - def change_position(self, pos=''): - if pos not in ('', 'def', 'pro', 'hld', 'hlp', 'jud', 'wit', 'jur', 'sea'): - raise ClientError('Invalid position. Possible values: def, pro, hld, hlp, jud, wit, jur, sea.') - self.pos = pos - - def set_mod_call_delay(self): - self.mod_call_time = round(time.time() * 1000.0 + 30000) - - def can_call_mod(self): - return (time.time() * 1000.0 - self.mod_call_time) > 0 - - def set_case_call_delay(self): - self.case_call_time = round(time.time() * 1000.0 + 60000) - - def can_call_case(self): - return (time.time() * 1000.0 - self.case_call_time) > 0 - - def disemvowel_message(self, message): - message = re.sub("[aeiou]", "", message, flags=re.IGNORECASE) - return re.sub(r"\s+", " ", message) - - def shake_message(self, message): - import random - parts = message.split() - random.shuffle(parts) - return ' '.join(parts) - - - def __init__(self, server): - self.clients = set() - self.server = server - self.cur_id = [i for i in range(self.server.config['playerlimit'])] - self.clients_list = [] - - def new_client(self, transport): - c = self.Client(self.server, transport, heappop(self.cur_id), self.server.get_ipid(transport.get_extra_info('peername')[0])) - self.clients.add(c) - return c - - - def remove_client(self, client): - if client.area.jukebox: - client.area.remove_jukebox_vote(client, True) - for a in self.server.area_manager.areas: - if client in a.owners: - a.owners.remove(client) - client.server.area_manager.send_arup_cms() - if len(a.owners) == 0: - if a.is_locked != a.Locked.FREE: - a.unlock() - heappush(self.cur_id, client.id) - self.clients.remove(client) - - def get_targets(self, client, key, value, local = False): - #possible keys: ip, OOC, id, cname, ipid, hdid - areas = None - if local: - areas = [client.area] - else: - areas = client.server.area_manager.areas - targets = [] - if key == TargetType.ALL: - for nkey in range(6): - targets += self.get_targets(client, nkey, value, local) - for area in areas: - for client in area.clients: - if key == TargetType.IP: - if value.lower().startswith(client.get_ip().lower()): - targets.append(client) - elif key == TargetType.OOC_NAME: - if value.lower().startswith(client.name.lower()) and client.name: - targets.append(client) - elif key == TargetType.CHAR_NAME: - if value.lower().startswith(client.get_char_name().lower()): - targets.append(client) - elif key == TargetType.ID: - if client.id == value: - targets.append(client) - elif key == TargetType.IPID: - if client.ipid == value: - targets.append(client) - return targets - - - def get_muted_clients(self): - clients = [] - for client in self.clients: - if client.is_muted: - clients.append(client) - return clients - - def get_ooc_muted_clients(self): - clients = [] - for client in self.clients: - if client.is_ooc_muted: - clients.append(client) - return clients diff --git a/server/commands.py b/server/commands.py deleted file mode 100644 index d02eff2..0000000 --- a/server/commands.py +++ /dev/null @@ -1,1255 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -#possible keys: ip, OOC, id, cname, ipid, hdid -import random -import re -import hashlib -import string -from server.constants import TargetType - -from server import logger -from server.exceptions import ClientError, ServerError, ArgumentError, AreaError - -def ooc_cmd_a(client, arg): - if len(arg) == 0: - raise ArgumentError('You must specify an area.') - arg = arg.split(' ') - - try: - area = client.server.area_manager.get_area_by_id(int(arg[0])) - except AreaError: - raise - - message_areas_cm(client, [area], ' '.join(arg[1:])) - -def ooc_cmd_s(client, arg): - areas = [] - for a in client.server.area_manager.areas: - if client in a.owners: - areas.append(a) - if not areas: - client.send_host_message('You aren\'t a CM in any area!') - return - message_areas_cm(client, areas, arg) - -def message_areas_cm(client, areas, message): - for a in areas: - if not client in a.owners: - client.send_host_message('You are not a CM in {}!'.format(a.name)) - return - a.send_command('CT', client.name, message) - a.send_owner_command('CT', client.name, message) - -def ooc_cmd_switch(client, arg): - if len(arg) == 0: - raise ArgumentError('You must specify a character name.') - try: - cid = client.server.get_char_id_by_name(arg) - except ServerError: - raise - try: - client.change_character(cid, client.is_mod) - except ClientError: - raise - client.send_host_message('Character changed.') - -def ooc_cmd_bg(client, arg): - if len(arg) == 0: - raise ArgumentError('You must specify a name. Use /bg .') - if not client.is_mod and client.area.bg_lock == "true": - raise AreaError("This area's background is locked") - try: - client.area.change_background(arg) - except AreaError: - raise - client.area.send_host_message('{} changed the background to {}.'.format(client.get_char_name(), arg)) - logger.log_server('[{}][{}]Changed background to {}'.format(client.area.abbreviation, client.get_char_name(), arg), client) - -def ooc_cmd_bglock(client,arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - if client.area.bg_lock == "true": - client.area.bg_lock = "false" - else: - client.area.bg_lock = "true" - client.area.send_host_message('{} [{}] has set the background lock to {}.'.format(client.get_char_name(), client.id, client.area.bg_lock)) - logger.log_server('[{}][{}]Changed bglock to {}'.format(client.area.abbreviation, client.get_char_name(), client.area.bg_lock), client) - -def ooc_cmd_evidence_mod(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if not arg: - client.send_host_message('current evidence mod: {}'.format(client.area.evidence_mod)) - return - if arg in ['FFA', 'Mods', 'CM', 'HiddenCM']: - if arg == client.area.evidence_mod: - client.send_host_message('current evidence mod: {}'.format(client.area.evidence_mod)) - return - if client.area.evidence_mod == 'HiddenCM': - for i in range(len(client.area.evi_list.evidences)): - client.area.evi_list.evidences[i].pos = 'all' - client.area.evidence_mod = arg - client.send_host_message('current evidence mod: {}'.format(client.area.evidence_mod)) - return - else: - raise ArgumentError('Wrong Argument. Use /evidence_mod . Possible values: FFA, CM, Mods, HiddenCM') - return - -def ooc_cmd_allow_iniswap(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - client.area.iniswap_allowed = not client.area.iniswap_allowed - answer = {True: 'allowed', False: 'forbidden'} - client.send_host_message('iniswap is {}.'.format(answer[client.area.iniswap_allowed])) - return - -def ooc_cmd_allow_blankposting(client, arg): - if not client.is_mod and not client in client.area.owners: - raise ClientError('You must be authorized to do that.') - client.area.blankposting_allowed = not client.area.blankposting_allowed - answer = {True: 'allowed', False: 'forbidden'} - client.area.send_host_message('{} [{}] has set blankposting in the area to {}.'.format(client.get_char_name(), client.id, answer[client.area.blankposting_allowed])) - return - -def ooc_cmd_force_nonint_pres(client, arg): - if not client.is_mod and not client in client.area.owners: - raise ClientError('You must be authorized to do that.') - client.area.non_int_pres_only = not client.area.non_int_pres_only - answer = {True: 'non-interrupting only', False: 'non-interrupting or interrupting as you choose'} - client.area.send_host_message('{} [{}] has set pres in the area to be {}.'.format(client.get_char_name(), client.id, answer[client.area.non_int_pres_only])) - return - -def ooc_cmd_roll(client, arg): - roll_max = 11037 - if len(arg) != 0: - try: - val = list(map(int, arg.split(' '))) - if not 1 <= val[0] <= roll_max: - raise ArgumentError('Roll value must be between 1 and {}.'.format(roll_max)) - except ValueError: - raise ArgumentError('Wrong argument. Use /roll [] []') - else: - val = [6] - if len(val) == 1: - val.append(1) - if len(val) > 2: - raise ArgumentError('Too many arguments. Use /roll [] []') - if val[1] > 20 or val[1] < 1: - raise ArgumentError('Num of rolls must be between 1 and 20') - roll = '' - for i in range(val[1]): - roll += str(random.randint(1, val[0])) + ', ' - roll = roll[:-2] - if val[1] > 1: - roll = '(' + roll + ')' - client.area.send_host_message('{} rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) - logger.log_server( - '[{}][{}]Used /roll and got {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0]), client) - -def ooc_cmd_rollp(client, arg): - roll_max = 11037 - if len(arg) != 0: - try: - val = list(map(int, arg.split(' '))) - if not 1 <= val[0] <= roll_max: - raise ArgumentError('Roll value must be between 1 and {}.'.format(roll_max)) - except ValueError: - raise ArgumentError('Wrong argument. Use /rollp [] []') - else: - val = [6] - if len(val) == 1: - val.append(1) - if len(val) > 2: - raise ArgumentError('Too many arguments. Use /rollp [] []') - if val[1] > 20 or val[1] < 1: - raise ArgumentError('Num of rolls must be between 1 and 20') - roll = '' - for i in range(val[1]): - roll += str(random.randint(1, val[0])) + ', ' - roll = roll[:-2] - if val[1] > 1: - roll = '(' + roll + ')' - client.send_host_message('{} rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) - - client.area.send_host_message('{} rolled in secret.'.format(client.get_char_name())) - for c in client.area.owners: - c.send_host_message('[{}]{} secretly rolled {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0])) - - logger.log_server( - '[{}][{}]Used /rollp and got {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0]), client) - -def ooc_cmd_currentmusic(client, arg): - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - if client.area.current_music == '': - raise ClientError('There is no music currently playing.') - if client.is_mod: - client.send_host_message('The current music is {} and was played by {} ({}).'.format(client.area.current_music, - client.area.current_music_player, client.area.current_music_player_ipid)) - else: - client.send_host_message('The current music is {} and was played by {}.'.format(client.area.current_music, - client.area.current_music_player)) - -def ooc_cmd_jukebox_toggle(client, arg): - if not client.is_mod and not client in client.area.owners: - raise ClientError('You must be authorized to do that.') - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - client.area.jukebox = not client.area.jukebox - client.area.jukebox_votes = [] - client.area.send_host_message('{} [{}] has set the jukebox to {}.'.format(client.get_char_name(), client.id, client.area.jukebox)) - -def ooc_cmd_jukebox_skip(client, arg): - if not client.is_mod and not client in client.area.owners: - raise ClientError('You must be authorized to do that.') - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - if not client.area.jukebox: - raise ClientError('This area does not have a jukebox.') - if len(client.area.jukebox_votes) == 0: - raise ClientError('There is no song playing right now, skipping is pointless.') - client.area.start_jukebox() - if len(client.area.jukebox_votes) == 1: - client.area.send_host_message('{} [{}] has forced a skip, restarting the only jukebox song.'.format(client.get_char_name(), client.id)) - else: - client.area.send_host_message('{} [{}] has forced a skip to the next jukebox song.'.format(client.get_char_name(), client.id)) - logger.log_server('[{}][{}]Skipped the current jukebox song.'.format(client.area.abbreviation, client.get_char_name()), client) - -def ooc_cmd_jukebox(client, arg): - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - if not client.area.jukebox: - raise ClientError('This area does not have a jukebox.') - if len(client.area.jukebox_votes) == 0: - client.send_host_message('The jukebox has no songs in it.') - else: - total = 0 - songs = [] - voters = dict() - chance = dict() - message = '' - - for current_vote in client.area.jukebox_votes: - if songs.count(current_vote.name) == 0: - songs.append(current_vote.name) - voters[current_vote.name] = [current_vote.client] - chance[current_vote.name] = current_vote.chance - else: - voters[current_vote.name].append(current_vote.client) - chance[current_vote.name] += current_vote.chance - total += current_vote.chance - - for song in songs: - message += '\n- ' + song + '\n' - message += '-- VOTERS: ' - - first = True - for voter in voters[song]: - if first: - first = False - else: - message += ', ' - message += voter.get_char_name() + ' [' + str(voter.id) + ']' - if client.is_mod: - message += '(' + str(voter.ipid) + ')' - message += '\n' - - if total == 0: - message += '-- CHANCE: 100' - else: - message += '-- CHANCE: ' + str(round(chance[song] / total * 100)) - - client.send_host_message('The jukebox has the following songs in it:{}'.format(message)) - -def ooc_cmd_coinflip(client, arg): - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - coin = ['heads', 'tails'] - flip = random.choice(coin) - client.area.send_host_message('{} flipped a coin and got {}.'.format(client.get_char_name(), flip)) - logger.log_server( - '[{}][{}]Used /coinflip and got {}.'.format(client.area.abbreviation, client.get_char_name(), flip), client) - -def ooc_cmd_motd(client, arg): - if len(arg) != 0: - raise ArgumentError("This command doesn't take any arguments") - client.send_motd() - -def ooc_cmd_pos(client, arg): - if len(arg) == 0: - client.change_position() - client.send_host_message('Position reset.') - else: - try: - client.change_position(arg) - except ClientError: - raise - client.area.broadcast_evidence_list() - client.send_host_message('Position changed.') - -def ooc_cmd_forcepos(client, arg): - if not client in client.area.owners and not client.is_mod: - raise ClientError('You must be authorized to do that.') - - args = arg.split() - - if len(args) < 1: - raise ArgumentError( - 'Not enough arguments. Use /forcepos . Target should be ID, OOC-name or char-name. Use /getarea for getting info like "[ID] char-name".') - - targets = [] - - pos = args[0] - if len(args) > 1: - targets = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, " ".join(args[1:]), True) - if len(targets) == 0 and args[1].isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg[1]), True) - if len(targets) == 0: - targets = client.server.client_manager.get_targets( - client, TargetType.OOC_NAME, " ".join(args[1:]), True) - if len(targets) == 0: - raise ArgumentError('No targets found.') - else: - for c in client.area.clients: - targets.append(c) - - - - for t in targets: - try: - t.change_position(pos) - t.area.broadcast_evidence_list() - t.send_host_message('Forced into /pos {}.'.format(pos)) - except ClientError: - raise - - client.area.send_host_message( - '{} forced {} client(s) into /pos {}.'.format(client.get_char_name(), len(targets), pos)) - logger.log_server( - '[{}][{}]Used /forcepos {} for {} client(s).'.format(client.area.abbreviation, client.get_char_name(), pos, len(targets)), client) - -def ooc_cmd_help(client, arg): - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - help_url = 'http://casecafe.byethost14.com/commandlist' - help_msg = 'The commands available on this server can be found here: {}'.format(help_url) - client.send_host_message(help_msg) - -def ooc_cmd_kick(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /kick ...') - args = list(arg.split(' ')) - client.send_host_message('Attempting to kick {} IPIDs.'.format(len(args))) - for raw_ipid in args: - try: - ipid = int(raw_ipid) - except: - raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) - targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) - if targets: - for c in targets: - logger.log_server('Kicked {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) - logger.log_mod('Kicked {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) - client.send_host_message("{} was kicked.".format(c.get_char_name())) - c.send_command('KK', c.char_id) - c.disconnect() - else: - client.send_host_message("No targets with the IPID {} were found.".format(ipid)) - -def ooc_cmd_ban(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /ban ...') - args = list(arg.split(' ')) - client.send_host_message('Attempting to ban {} IPIDs.'.format(len(args))) - for raw_ipid in args: - try: - ipid = int(raw_ipid) - except: - raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) - try: - client.server.ban_manager.add_ban(ipid) - except ServerError: - raise - if ipid != None: - targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) - if targets: - for c in targets: - c.send_command('KB', c.char_id) - c.disconnect() - client.send_host_message('{} clients was kicked.'.format(len(targets))) - client.send_host_message('{} was banned.'.format(ipid)) - logger.log_server('Banned {}.'.format(ipid), client) - logger.log_mod('Banned {}.'.format(ipid), client) - -def ooc_cmd_unban(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /unban ...') - args = list(arg.split(' ')) - client.send_host_message('Attempting to unban {} IPIDs.'.format(len(args))) - for raw_ipid in args: - try: - client.server.ban_manager.remove_ban(int(raw_ipid)) - except: - raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) - logger.log_server('Unbanned {}.'.format(raw_ipid), client) - logger.log_mod('Unbanned {}.'.format(raw_ipid), client) - client.send_host_message('Unbanned {}'.format(raw_ipid)) - -def ooc_cmd_play(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a song.') - client.area.play_music(arg, client.char_id, -1) - client.area.add_music_playing(client, arg) - logger.log_server('[{}][{}]Changed music to {}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - -def ooc_cmd_mute(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /mute .') - args = list(arg.split(' ')) - client.send_host_message('Attempting to mute {} IPIDs.'.format(len(args))) - for raw_ipid in args: - if raw_ipid.isdigit(): - ipid = int(raw_ipid) - clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) - if (clients): - msg = 'Muted the IPID ' + str(ipid) + '\'s following clients:' - for c in clients: - c.is_muted = True - logger.log_server('Muted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) - logger.log_mod('Muted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) - msg += ' ' + c.get_char_name() + ' [' + str(c.id) + '],' - msg = msg[:-1] - msg += '.' - client.send_host_message('{}'.format(msg)) - else: - client.send_host_message("No targets found. Use /mute ... for mute.") - else: - client.send_host_message('{} does not look like a valid IPID.'.format(raw_ipid)) - -def ooc_cmd_unmute(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target.') - args = list(arg.split(' ')) - client.send_host_message('Attempting to unmute {} IPIDs.'.format(len(args))) - for raw_ipid in args: - if raw_ipid.isdigit(): - ipid = int(raw_ipid) - clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) - if (clients): - msg = 'Unmuted the IPID ' + str(ipid) + '\'s following clients::' - for c in clients: - c.is_muted = False - logger.log_server('Unmuted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) - logger.log_mod('Unmuted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) - msg += ' ' + c.get_char_name() + ' [' + str(c.id) + '],' - msg = msg[:-1] - msg += '.' - client.send_host_message('{}'.format(msg)) - else: - client.send_host_message("No targets found. Use /unmute ... for unmute.") - else: - client.send_host_message('{} does not look like a valid IPID.'.format(raw_ipid)) - -def ooc_cmd_login(client, arg): - if len(arg) == 0: - raise ArgumentError('You must specify the password.') - try: - client.auth_mod(arg) - except ClientError: - raise - if client.area.evidence_mod == 'HiddenCM': - client.area.broadcast_evidence_list() - client.send_host_message('Logged in as a moderator.') - logger.log_server('Logged in as moderator.', client) - logger.log_mod('Logged in as moderator.', client) - -def ooc_cmd_g(client, arg): - if client.muted_global: - raise ClientError('Global chat toggled off.') - if len(arg) == 0: - raise ArgumentError("You can't send an empty message.") - client.server.broadcast_global(client, arg) - logger.log_server('[{}][{}][GLOBAL]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - -def ooc_cmd_gm(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if client.muted_global: - raise ClientError('You have the global chat muted.') - if len(arg) == 0: - raise ArgumentError("Can't send an empty message.") - client.server.broadcast_global(client, arg, True) - logger.log_server('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - logger.log_mod('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - -def ooc_cmd_m(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError("You can't send an empty message.") - client.server.send_modchat(client, arg) - logger.log_server('[{}][{}][MODCHAT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - logger.log_mod('[{}][{}][MODCHAT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - -def ooc_cmd_lm(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError("Can't send an empty message.") - client.area.send_command('CT', '{}[MOD][{}]' - .format(client.server.config['hostname'], client.get_char_name()), arg) - logger.log_server('[{}][{}][LOCAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - logger.log_mod('[{}][{}][LOCAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - -def ooc_cmd_announce(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError("Can't send an empty message.") - client.server.send_all_cmd_pred('CT', '{}'.format(client.server.config['hostname']), - '=== Announcement ===\r\n{}\r\n=================='.format(arg), '1') - logger.log_server('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - logger.log_mod('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - -def ooc_cmd_toggleglobal(client, arg): - if len(arg) != 0: - raise ArgumentError("This command doesn't take any arguments") - client.muted_global = not client.muted_global - glob_stat = 'on' - if client.muted_global: - glob_stat = 'off' - client.send_host_message('Global chat turned {}.'.format(glob_stat)) - - -def ooc_cmd_need(client, arg): - if client.muted_adverts: - raise ClientError('You have advertisements muted.') - if len(arg) == 0: - raise ArgumentError("You must specify what you need.") - client.server.broadcast_need(client, arg) - logger.log_server('[{}][{}][NEED]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) - -def ooc_cmd_toggleadverts(client, arg): - if len(arg) != 0: - raise ArgumentError("This command doesn't take any arguments") - client.muted_adverts = not client.muted_adverts - adv_stat = 'on' - if client.muted_adverts: - adv_stat = 'off' - client.send_host_message('Advertisements turned {}.'.format(adv_stat)) - -def ooc_cmd_doc(client, arg): - if len(arg) == 0: - client.send_host_message('Document: {}'.format(client.area.doc)) - logger.log_server( - '[{}][{}]Requested document. Link: {}'.format(client.area.abbreviation, client.get_char_name(), client.area.doc), client) - else: - client.area.change_doc(arg) - client.area.send_host_message('{} changed the doc link.'.format(client.get_char_name())) - logger.log_server('[{}][{}]Changed document to: {}'.format(client.area.abbreviation, client.get_char_name(), arg), client) - - -def ooc_cmd_cleardoc(client, arg): - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - client.area.send_host_message('{} cleared the doc link.'.format(client.get_char_name())) - logger.log_server('[{}][{}]Cleared document. Old link: {}' - .format(client.area.abbreviation, client.get_char_name(), client.area.doc), client) - client.area.change_doc() - - -def ooc_cmd_status(client, arg): - if len(arg) == 0: - client.send_host_message('Current status: {}'.format(client.area.status)) - else: - try: - client.area.change_status(arg) - client.area.send_host_message('{} changed status to {}.'.format(client.get_char_name(), client.area.status)) - logger.log_server( - '[{}][{}]Changed status to {}'.format(client.area.abbreviation, client.get_char_name(), client.area.status), client) - except AreaError: - raise - - -def ooc_cmd_online(client, _): - client.send_player_count() - - -def ooc_cmd_area(client, arg): - args = arg.split() - if len(args) == 0: - client.send_area_list() - elif len(args) == 1: - try: - area = client.server.area_manager.get_area_by_id(int(args[0])) - client.change_area(area) - except ValueError: - raise ArgumentError('Area ID must be a number.') - except (AreaError, ClientError): - raise - else: - raise ArgumentError('Too many arguments. Use /area .') - -def ooc_cmd_pm(client, arg): - args = arg.split() - key = '' - msg = None - if len(args) < 2: - raise ArgumentError('Not enough arguments. use /pm . Target should be ID, OOC-name or char-name. Use /getarea for getting info like "[ID] char-name".') - targets = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, arg, True) - key = TargetType.CHAR_NAME - if len(targets) == 0 and args[0].isdigit(): - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) - key = TargetType.ID - if len(targets) == 0: - targets = client.server.client_manager.get_targets(client, TargetType.OOC_NAME, arg, True) - key = TargetType.OOC_NAME - if len(targets) == 0: - raise ArgumentError('No targets found.') - try: - if key == TargetType.ID: - msg = ' '.join(args[1:]) - else: - if key == TargetType.CHAR_NAME: - msg = arg[len(targets[0].get_char_name()) + 1:] - if key == TargetType.OOC_NAME: - msg = arg[len(targets[0].name) + 1:] - except: - raise ArgumentError('Not enough arguments. Use /pm .') - c = targets[0] - if c.pm_mute: - raise ClientError('This user muted all pm conversation') - else: - if c.is_mod: - c.send_host_message('PM from {} (ID: {}, IPID: {}) in {} ({}): {}'.format(client.name, client.id, client.ipid, client.area.name, client.get_char_name(), msg)) - else: - c.send_host_message('PM from {} (ID: {}) in {} ({}): {}'.format(client.name, client.id, client.area.name, client.get_char_name(), msg)) - client.send_host_message('PM sent to {}. Message: {}'.format(args[0], msg)) - -def ooc_cmd_mutepm(client, arg): - if len(arg) != 0: - raise ArgumentError("This command doesn't take any arguments") - client.pm_mute = not client.pm_mute - client.send_host_message({True:'You stopped receiving PMs', False:'You are now receiving PMs'}[client.pm_mute]) - -def ooc_cmd_charselect(client, arg): - if not arg: - client.char_select() - else: - if client.is_mod: - try: - client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0].char_select() - except: - raise ArgumentError('Wrong arguments. Use /charselect ') - -def ooc_cmd_reload(client, arg): - if len(arg) != 0: - raise ArgumentError("This command doesn't take any arguments") - try: - client.reload_character() - except ClientError: - raise - client.send_host_message('Character reloaded.') - -def ooc_cmd_randomchar(client, arg): - if len(arg) != 0: - raise ArgumentError('This command has no arguments.') - if len(client.charcurse) > 0: - free_id = random.choice(client.charcurse) - else: - try: - free_id = client.area.get_rand_avail_char_id() - except AreaError: - raise - try: - client.change_character(free_id) - except ClientError: - raise - client.send_host_message('Randomly switched to {}'.format(client.get_char_name())) - -def ooc_cmd_getarea(client, arg): - client.send_area_info(client.area.id, False) - -def ooc_cmd_getareas(client, arg): - client.send_area_info(-1, False) - -def ooc_cmd_mods(client, arg): - client.send_area_info(-1, True) - -def ooc_cmd_evi_swap(client, arg): - args = list(arg.split(' ')) - if len(args) != 2: - raise ClientError("you must specify 2 numbers") - try: - client.area.evi_list.evidence_swap(client, int(args[0]), int(args[1])) - client.area.broadcast_evidence_list() - except: - raise ClientError("you must specify 2 numbers") - -def ooc_cmd_cm(client, arg): - if 'CM' not in client.area.evidence_mod: - raise ClientError('You can\'t become a CM in this area') - if len(client.area.owners) == 0: - if len(arg) > 0: - raise ArgumentError('You cannot \'nominate\' people to be CMs when you are not one.') - client.area.owners.append(client) - if client.area.evidence_mod == 'HiddenCM': - client.area.broadcast_evidence_list() - client.server.area_manager.send_arup_cms() - client.area.send_host_message('{} [{}] is CM in this area now.'.format(client.get_char_name(), client.id)) - elif client in client.area.owners: - if len(arg) > 0: - arg = arg.split(' ') - for id in arg: - try: - id = int(id) - c = client.server.client_manager.get_targets(client, TargetType.ID, id, False)[0] - if c in client.area.owners: - client.send_host_message('{} [{}] is already a CM here.'.format(c.get_char_name(), c.id)) - else: - client.area.owners.append(c) - if client.area.evidence_mod == 'HiddenCM': - client.area.broadcast_evidence_list() - client.server.area_manager.send_arup_cms() - client.area.send_host_message('{} [{}] is CM in this area now.'.format(c.get_char_name(), c.id)) - except: - client.send_host_message('{} does not look like a valid ID.'.format(id)) - else: - raise ClientError('You must be authorized to do that.') - - -def ooc_cmd_uncm(client, arg): - if client in client.area.owners: - if len(arg) > 0: - arg = arg.split(' ') - else: - arg = [client.id] - for id in arg: - try: - id = int(id) - c = client.server.client_manager.get_targets(client, TargetType.ID, id, False)[0] - if c in client.area.owners: - client.area.owners.remove(c) - client.server.area_manager.send_arup_cms() - client.area.send_host_message('{} [{}] is no longer CM in this area.'.format(c.get_char_name(), c.id)) - else: - client.send_host_message('You cannot remove someone from CMing when they aren\'t a CM.') - except: - client.send_host_message('{} does not look like a valid ID.'.format(id)) - else: - raise ClientError('You must be authorized to do that.') - -def ooc_cmd_setcase(client, arg): - args = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', arg) - if len(args) == 0: - raise ArgumentError('Please do not call this command manually!') - else: - client.casing_cases = args[0] - client.casing_cm = args[1] == "1" - client.casing_def = args[2] == "1" - client.casing_pro = args[3] == "1" - client.casing_jud = args[4] == "1" - client.casing_jur = args[5] == "1" - client.casing_steno = args[6] == "1" - -def ooc_cmd_anncase(client, arg): - if client in client.area.owners: - if not client.can_call_case(): - raise ClientError('Please wait 60 seconds between case announcements!') - args = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', arg) - if len(args) == 0: - raise ArgumentError('Please do not call this command manually!') - elif len(args) == 1: - raise ArgumentError('You should probably announce the case to at least one person.') - else: - if not args[1] == "1" and not args[2] == "1" and not args[3] == "1" and not args[4] == "1" and not args[5] == "1": - raise ArgumentError('You should probably announce the case to at least one person.') - msg = '=== Case Announcement ===\r\n{} [{}] is hosting {}, looking for '.format(client.get_char_name(), client.id, args[0]) - - lookingfor = [] - - if args[1] == "1": - lookingfor.append("defence") - if args[2] == "1": - lookingfor.append("prosecutor") - if args[3] == "1": - lookingfor.append("judge") - if args[4] == "1": - lookingfor.append("juror") - if args[5] == "1": - lookingfor.append("stenographer") - - msg = msg + ', '.join(lookingfor) + '.\r\n==================' - - client.server.send_all_cmd_pred('CASEA', msg, args[1], args[2], args[3], args[4], args[5], '1') - - client.set_case_call_delay() - - logger.log_server('[{}][{}][CASE_ANNOUNCEMENT]{}, DEF: {}, PRO: {}, JUD: {}, JUR: {}, STENO: {}.'.format(client.area.abbreviation, client.get_char_name(), args[0], args[1], args[2], args[3], args[4], args[5]), client) - else: - raise ClientError('You cannot announce a case in an area where you are not a CM!') - -def ooc_cmd_unmod(client, arg): - client.is_mod = False - if client.area.evidence_mod == 'HiddenCM': - client.area.broadcast_evidence_list() - client.send_host_message('you\'re not a mod now') - -def ooc_cmd_area_lock(client, arg): - if not client.area.locking_allowed: - client.send_host_message('Area locking is disabled in this area.') - return - if client.area.is_locked == client.area.Locked.LOCKED: - client.send_host_message('Area is already locked.') - if client in client.area.owners: - client.area.lock() - return - else: - raise ClientError('Only CM can lock the area.') - -def ooc_cmd_area_spectate(client, arg): - if not client.area.locking_allowed: - client.send_host_message('Area locking is disabled in this area.') - return - if client.area.is_locked == client.area.Locked.SPECTATABLE: - client.send_host_message('Area is already spectatable.') - if client in client.area.owners: - client.area.spectator() - return - else: - raise ClientError('Only CM can make the area spectatable.') - -def ooc_cmd_area_unlock(client, arg): - if client.area.is_locked == client.area.Locked.FREE: - raise ClientError('Area is already unlocked.') - if not client in client.area.owners: - raise ClientError('Only CM can unlock area.') - client.area.unlock() - client.send_host_message('Area is unlocked.') - -def ooc_cmd_invite(client, arg): - if not arg: - raise ClientError('You must specify a target. Use /invite ') - if client.area.is_locked == client.area.Locked.FREE: - raise ClientError('Area isn\'t locked.') - if not client in client.area.owners and not client.is_mod: - raise ClientError('You must be authorized to do that.') - try: - c = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0] - client.area.invite_list[c.id] = None - client.send_host_message('{} is invited to your area.'.format(c.get_char_name())) - c.send_host_message('You were invited and given access to {}.'.format(client.area.name)) - except: - raise ClientError('You must specify a target. Use /invite ') - -def ooc_cmd_uninvite(client, arg): - if not client in client.area.owners and not client.is_mod: - raise ClientError('You must be authorized to do that.') - if client.area.is_locked == client.area.Locked.FREE: - raise ClientError('Area isn\'t locked.') - if not arg: - raise ClientError('You must specify a target. Use /uninvite ') - arg = arg.split(' ') - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg[0]), True) - if targets: - try: - for c in targets: - client.send_host_message("You have removed {} from the whitelist.".format(c.get_char_name())) - c.send_host_message("You were removed from the area whitelist.") - if client.area.is_locked != client.area.Locked.FREE: - client.area.invite_list.pop(c.id) - except AreaError: - raise - except ClientError: - raise - else: - client.send_host_message("No targets found.") - -def ooc_cmd_area_kick(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if client.area.is_locked == client.area.Locked.FREE: - raise ClientError('Area isn\'t locked.') - if not arg: - raise ClientError('You must specify a target. Use /area_kick [destination #]') - arg = arg.split(' ') - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg[0]), False) - if targets: - try: - for c in targets: - if len(arg) == 1: - area = client.server.area_manager.get_area_by_id(int(0)) - output = 0 - else: - try: - area = client.server.area_manager.get_area_by_id(int(arg[1])) - output = arg[1] - except AreaError: - raise - client.send_host_message("Attempting to kick {} to area {}.".format(c.get_char_name(), output)) - c.change_area(area) - c.send_host_message("You were kicked from the area to area {}.".format(output)) - if client.area.is_locked != client.area.Locked.FREE: - client.area.invite_list.pop(c.id) - except AreaError: - raise - except ClientError: - raise - else: - client.send_host_message("No targets found.") - - -def ooc_cmd_ooc_mute(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /ooc_mute .') - targets = client.server.client_manager.get_targets(client, TargetType.OOC_NAME, arg, False) - if not targets: - raise ArgumentError('Targets not found. Use /ooc_mute .') - for target in targets: - target.is_ooc_muted = True - client.send_host_message('Muted {} existing client(s).'.format(len(targets))) - -def ooc_cmd_ooc_unmute(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /ooc_unmute .') - targets = client.server.client_manager.get_ooc_muted_clients() - if not targets: - raise ArgumentError('Targets not found. Use /ooc_unmute .') - for target in targets: - target.is_ooc_muted = False - client.send_host_message('Unmuted {} existing client(s).'.format(len(targets))) - -def ooc_cmd_disemvowel(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - elif len(arg) == 0: - raise ArgumentError('You must specify a target.') - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) - except: - raise ArgumentError('You must specify a target. Use /disemvowel .') - if targets: - for c in targets: - logger.log_server('Disemvowelling {}.'.format(c.get_ip()), client) - logger.log_mod('Disemvowelling {}.'.format(c.get_ip()), client) - c.disemvowel = True - client.send_host_message('Disemvowelled {} existing client(s).'.format(len(targets))) - else: - client.send_host_message('No targets found.') - -def ooc_cmd_undisemvowel(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - elif len(arg) == 0: - raise ArgumentError('You must specify a target.') - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) - except: - raise ArgumentError('You must specify a target. Use /undisemvowel .') - if targets: - for c in targets: - logger.log_server('Undisemvowelling {}.'.format(c.get_ip()), client) - logger.log_mod('Undisemvowelling {}.'.format(c.get_ip()), client) - c.disemvowel = False - client.send_host_message('Undisemvowelled {} existing client(s).'.format(len(targets))) - else: - client.send_host_message('No targets found.') - -def ooc_cmd_shake(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - elif len(arg) == 0: - raise ArgumentError('You must specify a target.') - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) - except: - raise ArgumentError('You must specify a target. Use /shake .') - if targets: - for c in targets: - logger.log_server('Shaking {}.'.format(c.get_ip()), client) - logger.log_mod('Shaking {}.'.format(c.get_ip()), client) - c.shaken = True - client.send_host_message('Shook {} existing client(s).'.format(len(targets))) - else: - client.send_host_message('No targets found.') - -def ooc_cmd_unshake(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - elif len(arg) == 0: - raise ArgumentError('You must specify a target.') - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) - except: - raise ArgumentError('You must specify a target. Use /unshake .') - if targets: - for c in targets: - logger.log_server('Unshaking {}.'.format(c.get_ip()), client) - logger.log_mod('Unshaking {}.'.format(c.get_ip()), client) - c.shaken = False - client.send_host_message('Unshook {} existing client(s).'.format(len(targets))) - else: - client.send_host_message('No targets found.') - -def ooc_cmd_charcurse(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - elif len(arg) == 0: - raise ArgumentError('You must specify a target (an ID) and at least one character ID. Consult /charids for the character IDs.') - elif len(arg) == 1: - raise ArgumentError('You must specific at least one character ID. Consult /charids for the character IDs.') - args = arg.split() - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) - except: - raise ArgumentError('You must specify a valid target! Make sure it is a valid ID.') - if targets: - for c in targets: - log_msg = ' ' + str(c.get_ip()) + ' to' - part_msg = ' [' + str(c.id) + '] to' - for raw_cid in args[1:]: - try: - cid = int(raw_cid) - c.charcurse.append(cid) - part_msg += ' ' + str(client.server.char_list[cid]) + ',' - log_msg += ' ' + str(client.server.char_list[cid]) + ',' - except: - ArgumentError('' + str(raw_cid) + ' does not look like a valid character ID.') - part_msg = part_msg[:-1] - part_msg += '.' - log_msg = log_msg[:-1] - log_msg += '.' - c.char_select() - logger.log_server('Charcursing' + log_msg, client) - logger.log_mod('Charcursing' + log_msg, client) - client.send_host_message('Charcursed' + part_msg) - else: - client.send_host_message('No targets found.') - -def ooc_cmd_uncharcurse(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - elif len(arg) == 0: - raise ArgumentError('You must specify a target (an ID).') - args = arg.split() - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) - except: - raise ArgumentError('You must specify a valid target! Make sure it is a valid ID.') - if targets: - for c in targets: - if len(c.charcurse) > 0: - c.charcurse = [] - logger.log_server('Uncharcursing {}.'.format(c.get_ip()), client) - logger.log_mod('Uncharcursing {}.'.format(c.get_ip()), client) - client.send_host_message('Uncharcursed [{}].'.format(c.id)) - c.char_select() - else: - client.send_host_message('[{}] is not charcursed.'.format(c.id)) - else: - client.send_host_message('No targets found.') - -def ooc_cmd_charids(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) != 0: - raise ArgumentError("This command doesn't take any arguments") - msg = 'Here is a list of all available characters on the server:' - for c in range(0, len(client.server.char_list)): - msg += '\n[' + str(c) + '] ' + client.server.char_list[c] - client.send_host_message(msg) - -def ooc_cmd_blockdj(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /blockdj .') - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) - except: - raise ArgumentError('You must enter a number. Use /blockdj .') - if not targets: - raise ArgumentError('Target not found. Use /blockdj .') - for target in targets: - target.is_dj = False - target.send_host_message('A moderator muted you from changing the music.') - logger.log_server('BlockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) - logger.log_mod('BlockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) - target.area.remove_jukebox_vote(target, True) - client.send_host_message('blockdj\'d {}.'.format(targets[0].get_char_name())) - -def ooc_cmd_unblockdj(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /unblockdj .') - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) - except: - raise ArgumentError('You must enter a number. Use /unblockdj .') - if not targets: - raise ArgumentError('Target not found. Use /blockdj .') - for target in targets: - target.is_dj = True - target.send_host_message('A moderator unmuted you from changing the music.') - logger.log_server('UnblockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) - logger.log_mod('UnblockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) - client.send_host_message('Unblockdj\'d {}.'.format(targets[0].get_char_name())) - -def ooc_cmd_blockwtce(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /blockwtce .') - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) - except: - raise ArgumentError('You must enter a number. Use /blockwtce .') - if not targets: - raise ArgumentError('Target not found. Use /blockwtce .') - for target in targets: - target.can_wtce = False - target.send_host_message('A moderator blocked you from using judge signs.') - logger.log_server('BlockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) - logger.log_mod('BlockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) - client.send_host_message('blockwtce\'d {}.'.format(targets[0].get_char_name())) - -def ooc_cmd_unblockwtce(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /unblockwtce .') - try: - targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) - except: - raise ArgumentError('You must enter a number. Use /unblockwtce .') - if not targets: - raise ArgumentError('Target not found. Use /unblockwtce .') - for target in targets: - target.can_wtce = True - target.send_host_message('A moderator unblocked you from using judge signs.') - logger.log_server('UnblockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) - logger.log_mod('UnblockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) - client.send_host_message('unblockwtce\'d {}.'.format(targets[0].get_char_name())) - -def ooc_cmd_notecard(client, arg): - if len(arg) == 0: - raise ArgumentError('You must specify the contents of the note card.') - client.area.cards[client.get_char_name()] = arg - client.area.send_host_message('{} wrote a note card.'.format(client.get_char_name())) - -def ooc_cmd_notecard_clear(client, arg): - try: - del client.area.cards[client.get_char_name()] - client.area.send_host_message('{} erased their note card.'.format(client.get_char_name())) - except KeyError: - raise ClientError('You do not have a note card.') - -def ooc_cmd_notecard_reveal(client, arg): - if not client in client.area.owners and not client.is_mod: - raise ClientError('You must be a CM or moderator to reveal cards.') - if len(client.area.cards) == 0: - raise ClientError('There are no cards to reveal in this area.') - msg = 'Note cards have been revealed.\n' - for card_owner, card_msg in client.area.cards.items(): - msg += '{}: {}\n'.format(card_owner, card_msg) - client.area.cards.clear() - client.area.send_host_message(msg) - -def ooc_cmd_rolla_reload(client, arg): - if not client.is_mod: - raise ClientError('You must be a moderator to load the ability dice configuration.') - rolla_reload(client.area) - client.send_host_message('Reloaded ability dice configuration.') - -def rolla_reload(area): - try: - import yaml - with open('config/dice.yaml', 'r') as dice: - area.ability_dice = yaml.load(dice) - except: - raise ServerError('There was an error parsing the ability dice configuration. Check your syntax.') - -def ooc_cmd_rolla_set(client, arg): - if not hasattr(client.area, 'ability_dice'): - rolla_reload(client.area) - available_sets = ', '.join(client.area.ability_dice.keys()) - if len(arg) == 0: - raise ArgumentError('You must specify the ability set name.\nAvailable sets: {}'.format(available_sets)) - if arg in client.area.ability_dice: - client.ability_dice_set = arg - client.send_host_message("Set ability set to {}.".format(arg)) - else: - raise ArgumentError('Invalid ability set \'{}\'.\nAvailable sets: {}'.format(arg, available_sets)) - -def ooc_cmd_rolla(client, arg): - if not hasattr(client.area, 'ability_dice'): - rolla_reload(client.area) - if not hasattr(client, 'ability_dice_set'): - raise ClientError('You must set your ability set using /rolla_set .') - ability_dice = client.area.ability_dice[client.ability_dice_set] - max_roll = ability_dice['max'] if 'max' in ability_dice else 6 - roll = random.randint(1, max_roll) - ability = ability_dice[roll] if roll in ability_dice else "Nothing happens" - client.area.send_host_message( - '{} rolled a {} (out of {}): {}.'.format(client.get_char_name(), roll, max_roll, ability)) - -def ooc_cmd_refresh(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len (arg) > 0: - raise ClientError('This command does not take in any arguments!') - else: - try: - client.server.refresh() - client.send_host_message('You have reloaded the server.') - except ServerError: - raise - -def ooc_cmd_judgelog(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - if len(arg) != 0: - raise ArgumentError('This command does not take any arguments.') - jlog = client.area.judgelog - if len(jlog) > 0: - jlog_msg = '== Judge Log ==' - for x in jlog: - jlog_msg += '\r\n{}'.format(x) - client.send_host_message(jlog_msg) - else: - raise ServerError('There have been no judge actions in this area since start of session.') diff --git a/server/constants.py b/server/constants.py deleted file mode 100644 index fa07e8e..0000000 --- a/server/constants.py +++ /dev/null @@ -1,11 +0,0 @@ -from enum import Enum - -class TargetType(Enum): - #possible keys: ip, OOC, id, cname, ipid, hdid - IP = 0 - OOC_NAME = 1 - ID = 2 - CHAR_NAME = 3 - IPID = 4 - HDID = 5 - ALL = 6 \ No newline at end of file diff --git a/server/districtclient.py b/server/districtclient.py deleted file mode 100644 index c766ba5..0000000 --- a/server/districtclient.py +++ /dev/null @@ -1,79 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import asyncio - -from server import logger - - -class DistrictClient: - def __init__(self, server): - self.server = server - self.reader = None - self.writer = None - self.message_queue = [] - - async def connect(self): - loop = asyncio.get_event_loop() - while True: - try: - self.reader, self.writer = await asyncio.open_connection(self.server.config['district_ip'], - self.server.config['district_port'], loop=loop) - await self.handle_connection() - except (ConnectionRefusedError, TimeoutError): - pass - except (ConnectionResetError, asyncio.IncompleteReadError): - self.writer = None - self.reader = None - finally: - logger.log_debug("Couldn't connect to the district, retrying in 30 seconds.") - await asyncio.sleep(30) - - async def handle_connection(self): - logger.log_debug('District connected.') - self.send_raw_message('AUTH#{}'.format(self.server.config['district_password'])) - while True: - data = await self.reader.readuntil(b'\r\n') - if not data: - return - raw_msg = data.decode()[:-2] - logger.log_debug('[DISTRICT][INC][RAW]{}'.format(raw_msg)) - cmd, *args = raw_msg.split('#') - if cmd == 'GLOBAL': - glob_name = '{}[{}:{}][{}]'.format('G', args[1], args[2], args[3]) - if args[0] == '1': - glob_name += '[M]' - self.server.send_all_cmd_pred('CT', glob_name, args[4], pred=lambda x: not x.muted_global) - elif cmd == 'NEED': - need_msg = '=== Cross Advert ===\r\n{} at {} in {} [{}] needs {}\r\n====================' \ - .format(args[1], args[0], args[2], args[3], args[4]) - self.server.send_all_cmd_pred('CT', '{}'.format(self.server.config['hostname']), need_msg, '1', - pred=lambda x: not x.muted_adverts) - - async def write_queue(self): - while self.message_queue: - msg = self.message_queue.pop(0) - try: - self.writer.write(msg) - await self.writer.drain() - except ConnectionResetError: - return - - def send_raw_message(self, msg): - if not self.writer: - return - self.message_queue.append('{}\r\n'.format(msg).encode()) - asyncio.ensure_future(self.write_queue(), loop=asyncio.get_event_loop()) diff --git a/server/evidence.py b/server/evidence.py deleted file mode 100644 index b34172a..0000000 --- a/server/evidence.py +++ /dev/null @@ -1,100 +0,0 @@ -class EvidenceList: - limit = 35 - - class Evidence: - def __init__(self, name, desc, image, pos): - self.name = name - self.desc = desc - self.image = image - self.public = False - self.pos = pos - - def set_name(self, name): - self.name = name - - def set_desc(self, desc): - self.desc = desc - - def set_image(self, image): - self.image = image - - def to_string(self): - sequence = (self.name, self.desc, self.image) - return '&'.join(sequence) - - def __init__(self): - self.evidences = [] - self.poses = {'def':['def', 'hld'], - 'pro':['pro', 'hlp'], - 'wit':['wit', 'sea'], - 'sea':['sea', 'wit'], - 'hlp':['hlp', 'pro'], - 'hld':['hld', 'def'], - 'jud':['jud', 'jur'], - 'jur':['jur', 'jud'], - 'all':['hlp', 'hld', 'wit', 'jud', 'pro', 'def', 'jur', 'sea', ''], - 'pos':[]} - - def login(self, client): - if client.area.evidence_mod == 'FFA': - pass - if client.area.evidence_mod == 'Mods': - if not client in client.area.owners: - return False - if client.area.evidence_mod == 'CM': - if not client in client.area.owners and not client.is_mod: - return False - if client.area.evidence_mod == 'HiddenCM': - if not client in client.area.owners and not client.is_mod: - return False - return True - - def correct_format(self, client, desc): - if client.area.evidence_mod != 'HiddenCM': - return True - else: - #correct format: \ndesc - if desc[:9] == '\n': - return True - return False - - - def add_evidence(self, client, name, description, image, pos = 'all'): - if self.login(client): - if client.area.evidence_mod == 'HiddenCM': - pos = 'pos' - if len(self.evidences) >= self.limit: - client.send_host_message('You can\'t have more than {} evidence items at a time.'.format(self.limit)) - else: - self.evidences.append(self.Evidence(name, description, image, pos)) - - def evidence_swap(self, client, id1, id2): - if self.login(client): - self.evidences[id1], self.evidences[id2] = self.evidences[id2], self.evidences[id1] - - def create_evi_list(self, client): - evi_list = [] - nums_list = [0] - for i in range(len(self.evidences)): - if client.area.evidence_mod == 'HiddenCM' and self.login(client): - nums_list.append(i + 1) - evi = self.evidences[i] - evi_list.append(self.Evidence(evi.name, '\n{}'.format(evi.pos, evi.desc), evi.image, evi.pos).to_string()) - elif client.pos in self.poses[self.evidences[i].pos]: - nums_list.append(i + 1) - evi_list.append(self.evidences[i].to_string()) - return nums_list, evi_list - - def del_evidence(self, client, id): - if self.login(client): - self.evidences.pop(id) - - def edit_evidence(self, client, id, arg): - if self.login(client): - if client.area.evidence_mod == 'HiddenCM' and self.correct_format(client, arg[1]): - self.evidences[id] = self.Evidence(arg[0], arg[1][14:], arg[2], arg[1][9:12]) - return - if client.area.evidence_mod == 'HiddenCM': - client.send_host_message('You entered a wrong pos.') - return - self.evidences[id] = self.Evidence(arg[0], arg[1], arg[2], arg[3]) \ No newline at end of file diff --git a/server/exceptions.py b/server/exceptions.py deleted file mode 100644 index d3503e9..0000000 --- a/server/exceptions.py +++ /dev/null @@ -1,32 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -class ClientError(Exception): - pass - - -class AreaError(Exception): - pass - - -class ArgumentError(Exception): - pass - - -class ServerError(Exception): - pass diff --git a/server/fantacrypt.py b/server/fantacrypt.py deleted file mode 100644 index e31548e..0000000 --- a/server/fantacrypt.py +++ /dev/null @@ -1,45 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# fantacrypt was a mistake, just hardcoding some numbers is good enough - -import binascii - -CRYPT_CONST_1 = 53761 -CRYPT_CONST_2 = 32618 -CRYPT_KEY = 5 - - -def fanta_decrypt(data): - data_bytes = [int(data[x:x + 2], 16) for x in range(0, len(data), 2)] - key = CRYPT_KEY - ret = '' - for byte in data_bytes: - val = byte ^ ((key & 0xffff) >> 8) - ret += chr(val) - key = ((byte + key) * CRYPT_CONST_1) + CRYPT_CONST_2 - return ret - - -def fanta_encrypt(data): - key = CRYPT_KEY - ret = '' - for char in data: - val = ord(char) ^ ((key & 0xffff) >> 8) - ret += binascii.hexlify(val.to_bytes(1, byteorder='big')).decode().upper() - key = ((val + key) * CRYPT_CONST_1) + CRYPT_CONST_2 - return ret diff --git a/server/logger.py b/server/logger.py deleted file mode 100644 index fb1b8b3..0000000 --- a/server/logger.py +++ /dev/null @@ -1,78 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import logging - -import time - - -def setup_logger(debug): - logging.Formatter.converter = time.gmtime - debug_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') - srv_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') - mod_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') - - debug_log = logging.getLogger('debug') - debug_log.setLevel(logging.DEBUG) - - debug_handler = logging.FileHandler('logs/debug.log', encoding='utf-8') - debug_handler.setLevel(logging.DEBUG) - debug_handler.setFormatter(debug_formatter) - debug_log.addHandler(debug_handler) - - if not debug: - debug_log.disabled = True - - server_log = logging.getLogger('server') - server_log.setLevel(logging.INFO) - - server_handler = logging.FileHandler('logs/server.log', encoding='utf-8') - server_handler.setLevel(logging.INFO) - server_handler.setFormatter(srv_formatter) - server_log.addHandler(server_handler) - - mod_log = logging.getLogger('mod') - mod_log.setLevel(logging.INFO) - - mod_handler = logging.FileHandler('logs/mod.log', encoding='utf-8') - mod_handler.setLevel(logging.INFO) - mod_handler.setFormatter(mod_formatter) - mod_log.addHandler(mod_handler) - - -def log_debug(msg, client=None): - msg = parse_client_info(client) + msg - logging.getLogger('debug').debug(msg) - - -def log_server(msg, client=None): - msg = parse_client_info(client) + msg - logging.getLogger('server').info(msg) - - -def log_mod(msg, client=None): - msg = parse_client_info(client) + msg - logging.getLogger('mod').info(msg) - - -def parse_client_info(client): - if client is None: - return '' - info = client.get_ip() - if client.is_mod: - return '[{:<15}][{:<3}][{}][MOD]'.format(info, client.id, client.name) - return '[{:<15}][{:<3}][{}]'.format(info, client.id, client.name) diff --git a/server/masterserverclient.py b/server/masterserverclient.py deleted file mode 100644 index 49af043..0000000 --- a/server/masterserverclient.py +++ /dev/null @@ -1,89 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -import asyncio -import time -from server import logger - - -class MasterServerClient: - def __init__(self, server): - self.server = server - self.reader = None - self.writer = None - - async def connect(self): - loop = asyncio.get_event_loop() - while True: - try: - self.reader, self.writer = await asyncio.open_connection(self.server.config['masterserver_ip'], - self.server.config['masterserver_port'], - loop=loop) - await self.handle_connection() - except (ConnectionRefusedError, TimeoutError): - pass - except (ConnectionResetError, asyncio.IncompleteReadError): - self.writer = None - self.reader = None - finally: - logger.log_debug("Couldn't connect to the master server, retrying in 30 seconds.") - print("Couldn't connect to the master server, retrying in 30 seconds.") - await asyncio.sleep(30) - - async def handle_connection(self): - logger.log_debug('Master server connected.') - await self.send_server_info() - fl = False - lastping = time.time() - 20 - while True: - self.reader.feed_data(b'END') - full_data = await self.reader.readuntil(b'END') - full_data = full_data[:-3] - if len(full_data) > 0: - data_list = list(full_data.split(b'#%'))[:-1] - for data in data_list: - raw_msg = data.decode() - cmd, *args = raw_msg.split('#') - if cmd != 'CHECK' and cmd != 'PONG': - logger.log_debug('[MASTERSERVER][INC][RAW]{}'.format(raw_msg)) - elif cmd == 'CHECK': - await self.send_raw_message('PING#%') - elif cmd == 'PONG': - fl = False - elif cmd == 'NOSERV': - await self.send_server_info() - if time.time() - lastping > 5: - if fl: - return - lastping = time.time() - fl = True - await self.send_raw_message('PING#%') - await asyncio.sleep(1) - - async def send_server_info(self): - cfg = self.server.config - msg = 'SCC#{}#{}#{}#{}#%'.format(cfg['port'], cfg['masterserver_name'], cfg['masterserver_description'], - self.server.software) - await self.send_raw_message(msg) - - async def send_raw_message(self, msg): - try: - self.writer.write(msg.encode()) - await self.writer.drain() - except ConnectionResetError: - return diff --git a/server/tsuserver.py b/server/tsuserver.py deleted file mode 100644 index 5af8161..0000000 --- a/server/tsuserver.py +++ /dev/null @@ -1,305 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2016 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import asyncio - -import yaml -import json - -from server import logger -from server.aoprotocol import AOProtocol -from server.area_manager import AreaManager -from server.ban_manager import BanManager -from server.client_manager import ClientManager -from server.districtclient import DistrictClient -from server.exceptions import ServerError -from server.masterserverclient import MasterServerClient - -class TsuServer3: - def __init__(self): - self.config = None - self.allowed_iniswaps = None - self.load_config() - self.load_iniswaps() - self.client_manager = ClientManager(self) - self.area_manager = AreaManager(self) - self.ban_manager = BanManager() - self.software = 'tsuserver3' - self.version = 'tsuserver3dev' - self.release = 3 - self.major_version = 1 - self.minor_version = 1 - self.ipid_list = {} - self.hdid_list = {} - self.char_list = None - self.char_pages_ao1 = None - self.music_list = None - self.music_list_ao2 = None - self.music_pages_ao1 = None - self.backgrounds = None - self.load_characters() - self.load_music() - self.load_backgrounds() - self.load_ids() - self.district_client = None - self.ms_client = None - self.rp_mode = False - logger.setup_logger(debug=self.config['debug']) - - def start(self): - loop = asyncio.get_event_loop() - - bound_ip = '0.0.0.0' - if self.config['local']: - bound_ip = '127.0.0.1' - - ao_server_crt = loop.create_server(lambda: AOProtocol(self), bound_ip, self.config['port']) - ao_server = loop.run_until_complete(ao_server_crt) - - if self.config['use_district']: - self.district_client = DistrictClient(self) - asyncio.ensure_future(self.district_client.connect(), loop=loop) - - if self.config['use_masterserver']: - self.ms_client = MasterServerClient(self) - asyncio.ensure_future(self.ms_client.connect(), loop=loop) - - logger.log_debug('Server started.') - - try: - loop.run_forever() - except KeyboardInterrupt: - pass - - logger.log_debug('Server shutting down.') - - ao_server.close() - loop.run_until_complete(ao_server.wait_closed()) - loop.close() - - def get_version_string(self): - return str(self.release) + '.' + str(self.major_version) + '.' + str(self.minor_version) - - def new_client(self, transport): - c = self.client_manager.new_client(transport) - if self.rp_mode: - c.in_rp = True - c.server = self - c.area = self.area_manager.default_area() - c.area.new_client(c) - return c - - def remove_client(self, client): - client.area.remove_client(client) - self.client_manager.remove_client(client) - - def get_player_count(self): - return len(self.client_manager.clients) - - def load_config(self): - with open('config/config.yaml', 'r', encoding = 'utf-8') as cfg: - self.config = yaml.load(cfg) - self.config['motd'] = self.config['motd'].replace('\\n', ' \n') - if 'music_change_floodguard' not in self.config: - self.config['music_change_floodguard'] = {'times_per_interval': 1, 'interval_length': 0, 'mute_length': 0} - if 'wtce_floodguard' not in self.config: - self.config['wtce_floodguard'] = {'times_per_interval': 1, 'interval_length': 0, 'mute_length': 0} - - def load_characters(self): - with open('config/characters.yaml', 'r', encoding = 'utf-8') as chars: - self.char_list = yaml.load(chars) - self.build_char_pages_ao1() - - def load_music(self): - with open('config/music.yaml', 'r', encoding = 'utf-8') as music: - self.music_list = yaml.load(music) - self.build_music_pages_ao1() - self.build_music_list_ao2() - - def load_ids(self): - self.ipid_list = {} - self.hdid_list = {} - #load ipids - try: - with open('storage/ip_ids.json', 'r', encoding = 'utf-8') as whole_list: - self.ipid_list = json.loads(whole_list.read()) - except: - logger.log_debug('Failed to load ip_ids.json from ./storage. If ip_ids.json is exist then remove it.') - #load hdids - try: - with open('storage/hd_ids.json', 'r', encoding = 'utf-8') as whole_list: - self.hdid_list = json.loads(whole_list.read()) - except: - logger.log_debug('Failed to load hd_ids.json from ./storage. If hd_ids.json is exist then remove it.') - - def dump_ipids(self): - with open('storage/ip_ids.json', 'w') as whole_list: - json.dump(self.ipid_list, whole_list) - - def dump_hdids(self): - with open('storage/hd_ids.json', 'w') as whole_list: - json.dump(self.hdid_list, whole_list) - - def get_ipid(self, ip): - if not (ip in self.ipid_list): - self.ipid_list[ip] = len(self.ipid_list) - self.dump_ipids() - return self.ipid_list[ip] - - def load_backgrounds(self): - with open('config/backgrounds.yaml', 'r', encoding = 'utf-8') as bgs: - self.backgrounds = yaml.load(bgs) - - def load_iniswaps(self): - try: - with open('config/iniswaps.yaml', 'r', encoding = 'utf-8') as iniswaps: - self.allowed_iniswaps = yaml.load(iniswaps) - except: - logger.log_debug('cannot find iniswaps.yaml') - - - def build_char_pages_ao1(self): - self.char_pages_ao1 = [self.char_list[x:x + 10] for x in range(0, len(self.char_list), 10)] - for i in range(len(self.char_list)): - self.char_pages_ao1[i // 10][i % 10] = '{}#{}&&0&&&0&'.format(i, self.char_list[i]) - - def build_music_pages_ao1(self): - self.music_pages_ao1 = [] - index = 0 - # add areas first - for area in self.area_manager.areas: - self.music_pages_ao1.append('{}#{}'.format(index, area.name)) - index += 1 - # then add music - for item in self.music_list: - self.music_pages_ao1.append('{}#{}'.format(index, item['category'])) - index += 1 - for song in item['songs']: - self.music_pages_ao1.append('{}#{}'.format(index, song['name'])) - index += 1 - self.music_pages_ao1 = [self.music_pages_ao1[x:x + 10] for x in range(0, len(self.music_pages_ao1), 10)] - - def build_music_list_ao2(self): - self.music_list_ao2 = [] - # add areas first - for area in self.area_manager.areas: - self.music_list_ao2.append(area.name) - # then add music - for item in self.music_list: - self.music_list_ao2.append(item['category']) - for song in item['songs']: - self.music_list_ao2.append(song['name']) - - def is_valid_char_id(self, char_id): - return len(self.char_list) > char_id >= 0 - - def get_char_id_by_name(self, name): - for i, ch in enumerate(self.char_list): - if ch.lower() == name.lower(): - return i - raise ServerError('Character not found.') - - def get_song_data(self, music): - for item in self.music_list: - if item['category'] == music: - return item['category'], -1 - for song in item['songs']: - if song['name'] == music: - try: - return song['name'], song['length'] - except KeyError: - return song['name'], -1 - raise ServerError('Music not found.') - - def send_all_cmd_pred(self, cmd, *args, pred=lambda x: True): - for client in self.client_manager.clients: - if pred(client): - client.send_command(cmd, *args) - - def broadcast_global(self, client, msg, as_mod=False): - char_name = client.get_char_name() - ooc_name = '{}[{}][{}]'.format('G', client.area.abbreviation, char_name) - if as_mod: - ooc_name += '[M]' - self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: not x.muted_global) - if self.config['use_district']: - self.district_client.send_raw_message( - 'GLOBAL#{}#{}#{}#{}'.format(int(as_mod), client.area.id, char_name, msg)) - - def send_modchat(self, client, msg): - name = client.name - ooc_name = '{}[{}][{}]'.format('M', client.area.abbreviation, name) - self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: x.is_mod) - if self.config['use_district']: - self.district_client.send_raw_message( - 'MODCHAT#{}#{}#{}'.format(client.area.id, char_name, msg)) - - def broadcast_need(self, client, msg): - char_name = client.get_char_name() - area_name = client.area.name - area_id = client.area.abbreviation - self.send_all_cmd_pred('CT', '{}'.format(self.config['hostname']), - ['=== Advert ===\r\n{} in {} [{}] needs {}\r\n===============' - .format(char_name, area_name, area_id, msg), '1'], pred=lambda x: not x.muted_adverts) - if self.config['use_district']: - self.district_client.send_raw_message('NEED#{}#{}#{}#{}'.format(char_name, area_name, area_id, msg)) - - def send_arup(self, args): - """ Updates the area properties on the Case Café Custom Client. - - Playercount: - ARUP#0###... - Status: - ARUP#1#####... - CM: - ARUP#2#####... - Lockedness: - ARUP#3#####... - - """ - if len(args) < 2: - # An argument count smaller than 2 means we only got the identifier of ARUP. - return - if args[0] not in (0,1,2,3): - return - - if args[0] == 0: - for part_arg in args[1:]: - try: - sanitised = int(part_arg) - except: - return - elif args[0] in (1, 2, 3): - for part_arg in args[1:]: - try: - sanitised = str(part_arg) - except: - return - - self.send_all_cmd_pred('ARUP', *args, pred=lambda x: True) - - def refresh(self): - with open('config/config.yaml', 'r') as cfg: - self.config['motd'] = yaml.load(cfg)['motd'].replace('\\n', ' \n') - with open('config/characters.yaml', 'r') as chars: - self.char_list = yaml.load(chars) - with open('config/music.yaml', 'r') as music: - self.music_list = yaml.load(music) - self.build_music_pages_ao1() - self.build_music_list_ao2() - with open('config/backgrounds.yaml', 'r') as bgs: - self.backgrounds = yaml.load(bgs) diff --git a/server/websocket.py b/server/websocket.py deleted file mode 100644 index ba4258f..0000000 --- a/server/websocket.py +++ /dev/null @@ -1,215 +0,0 @@ -# tsuserver3, an Attorney Online server -# -# Copyright (C) 2017 argoneus -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Partly authored by Johan Hanssen Seferidis (MIT license): -# https://github.com/Pithikos/python-websocket-server - -import asyncio -import re -import struct -from base64 import b64encode -from hashlib import sha1 - -from server import logger - - -class Bitmasks: - FIN = 0x80 - OPCODE = 0x0f - MASKED = 0x80 - PAYLOAD_LEN = 0x7f - PAYLOAD_LEN_EXT16 = 0x7e - PAYLOAD_LEN_EXT64 = 0x7f - - -class Opcode: - CONTINUATION = 0x0 - TEXT = 0x1 - BINARY = 0x2 - CLOSE_CONN = 0x8 - PING = 0x9 - PONG = 0xA - - -class WebSocket: - """ - State data for clients that are connected via a WebSocket that wraps - over a conventional TCP connection. - """ - - def __init__(self, client, protocol): - self.client = client - self.transport = client.transport - self.protocol = protocol - self.keep_alive = True - self.handshake_done = False - self.valid = False - - def handle(self, data): - if not self.handshake_done: - return self.handshake(data) - return self.parse(data) - - def parse(self, data): - b1, b2 = 0, 0 - if len(data) >= 2: - b1, b2 = data[0], data[1] - - fin = b1 & Bitmasks.FIN - opcode = b1 & Bitmasks.OPCODE - masked = b2 & Bitmasks.MASKED - payload_length = b2 & Bitmasks.PAYLOAD_LEN - - if not b1: - # Connection closed - self.keep_alive = 0 - return - if opcode == Opcode.CLOSE_CONN: - # Connection close requested - self.keep_alive = 0 - return - if not masked: - # Client was not masked (spec violation) - logger.log_debug("ws: client was not masked.", self.client) - self.keep_alive = 0 - print(data) - return - if opcode == Opcode.CONTINUATION: - # No continuation frames supported - logger.log_debug("ws: client tried to send continuation frame.", self.client) - return - elif opcode == Opcode.BINARY: - # No binary frames supported - logger.log_debug("ws: client tried to send binary frame.", self.client) - return - elif opcode == Opcode.TEXT: - def opcode_handler(s, msg): - return msg - elif opcode == Opcode.PING: - opcode_handler = self.send_pong - elif opcode == Opcode.PONG: - opcode_handler = lambda s, msg: None - else: - # Unknown opcode - logger.log_debug("ws: unknown opcode!", self.client) - self.keep_alive = 0 - return - - mask_offset = 2 - if payload_length == 126: - payload_length = struct.unpack(">H", data[2:4])[0] - mask_offset = 4 - elif payload_length == 127: - payload_length = struct.unpack(">Q", data[2:10])[0] - mask_offset = 10 - - masks = data[mask_offset:mask_offset + 4] - decoded = "" - for char in data[mask_offset + 4:payload_length + mask_offset + 4]: - char ^= masks[len(decoded) % 4] - decoded += chr(char) - - return opcode_handler(self, decoded) - - def send_message(self, message): - self.send_text(message) - - def send_pong(self, message): - self.send_text(message, Opcode.PONG) - - def send_text(self, message, opcode=Opcode.TEXT): - """ - Important: Fragmented (continuation) messages are not supported since - their usage cases are limited - when we don't know the payload length. - """ - - # Validate message - if isinstance(message, bytes): - message = message.decode("utf-8") - elif isinstance(message, str): - pass - else: - raise TypeError("Message must be either str or bytes") - - header = bytearray() - payload = message.encode("utf-8") - payload_length = len(payload) - - # Normal payload - if payload_length <= 125: - header.append(Bitmasks.FIN | opcode) - header.append(payload_length) - - # Extended payload - elif payload_length >= 126 and payload_length <= 65535: - header.append(Bitmasks.FIN | opcode) - header.append(Bitmasks.PAYLOAD_LEN_EXT16) - header.extend(struct.pack(">H", payload_length)) - - # Huge extended payload - elif payload_length < (1 << 64): - header.append(Bitmasks.FIN | opcode) - header.append(Bitmasks.PAYLOAD_LEN_EXT64) - header.extend(struct.pack(">Q", payload_length)) - - else: - raise Exception("Message is too big") - - self.transport.write(header + payload) - - def handshake(self, data): - try: - message = data[0:1024].decode().strip() - except UnicodeDecodeError: - return False - - upgrade = re.search('\nupgrade[\s]*:[\s]*websocket', message.lower()) - if not upgrade: - self.keep_alive = False - return False - - key = re.search('\n[sS]ec-[wW]eb[sS]ocket-[kK]ey[\s]*:[\s]*(.*)\r\n', message) - if key: - key = key.group(1) - else: - logger.log_debug("Client tried to connect but was missing a key", self.client) - self.keep_alive = False - return False - - response = self.make_handshake_response(key) - print(response.encode()) - self.transport.write(response.encode()) - self.handshake_done = True - self.valid = True - return True - - def make_handshake_response(self, key): - return \ - 'HTTP/1.1 101 Switching Protocols\r\n'\ - 'Upgrade: websocket\r\n' \ - 'Connection: Upgrade\r\n' \ - 'Sec-WebSocket-Accept: %s\r\n' \ - '\r\n' % self.calculate_response_key(key) - - def calculate_response_key(self, key): - GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' - hash = sha1(key.encode() + GUID.encode()) - response_key = b64encode(hash.digest()).strip() - return response_key.decode('ASCII') - - def finish(self): - self.protocol.connection_lost(self) diff --git a/tsuserver3.patch b/tsuserver3.patch new file mode 100644 index 0000000..021e599 --- /dev/null +++ b/tsuserver3.patch @@ -0,0 +1,2227 @@ +diff --git a/tsuserver3/server/aoprotocol.py b/AO2-Client/server/aoprotocol.py +index c5e4f63..2cf6fb4 100644 +--- a/tsuserver3/server/aoprotocol.py ++++ b/AO2-Client/server/aoprotocol.py +@@ -26,6 +26,7 @@ from .exceptions import ClientError, AreaError, ArgumentError, ServerError + from .fantacrypt import fanta_decrypt + from .evidence import EvidenceList + from .websocket import WebSocket ++import unicodedata + + + class AOProtocol(asyncio.Protocol): +@@ -171,6 +172,7 @@ class AOProtocol(asyncio.Protocol): + self.client.server.dump_hdids() + for ipid in self.client.server.hdid_list[self.client.hdid]: + if self.server.ban_manager.is_banned(ipid): ++ self.client.send_command('BD') + self.client.disconnect() + return + logger.log_server('Connected. HDID: {}.'.format(self.client.hdid), self.client) +@@ -211,7 +213,7 @@ class AOProtocol(asyncio.Protocol): + + self.client.is_ao2 = True + +- self.client.send_command('FL', 'yellowtext', 'customobjections', 'flipping', 'fastloading', 'noencryption', 'deskmod', 'evidence') ++ self.client.send_command('FL', 'yellowtext', 'customobjections', 'flipping', 'fastloading', 'noencryption', 'deskmod', 'evidence', 'modcall_reason', 'cccc_ic_support', 'arup', 'casing_alerts') + + def net_cmd_ch(self, _): + """ Periodically checks the connection. +@@ -333,16 +335,94 @@ class AOProtocol(asyncio.Protocol): + return + if not self.client.area.can_send_message(self.client): + return +- if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, ++ ++ target_area = [] ++ ++ if self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, + self.ArgType.STR, + self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT): ++ # Vanilla validation monstrosity. ++ msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color = args ++ showname = "" ++ charid_pair = -1 ++ offset_pair = 0 ++ nonint_pre = 0 ++ elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, ++ self.ArgType.STR, ++ self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, ++ self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, ++ self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY): ++ # 1.3.0 validation monstrosity. ++ msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname = args ++ charid_pair = -1 ++ offset_pair = 0 ++ nonint_pre = 0 ++ if len(showname) > 0 and not self.client.area.showname_changes_allowed: ++ self.client.send_host_message("Showname changes are forbidden in this area!") ++ return ++ elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, ++ self.ArgType.STR, ++ self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, ++ self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, ++ self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY, self.ArgType.INT, self.ArgType.INT): ++ # 1.3.5 validation monstrosity. ++ msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname, charid_pair, offset_pair = args ++ nonint_pre = 0 ++ if len(showname) > 0 and not self.client.area.showname_changes_allowed: ++ self.client.send_host_message("Showname changes are forbidden in this area!") ++ return ++ elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, ++ self.ArgType.STR, ++ self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, ++ self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, ++ self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT): ++ # 1.4.0 validation monstrosity. ++ msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname, charid_pair, offset_pair, nonint_pre = args ++ if len(showname) > 0 and not self.client.area.showname_changes_allowed: ++ self.client.send_host_message("Showname changes are forbidden in this area!") ++ return ++ else: + return +- msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color = args + if self.client.area.is_iniswap(self.client, pre, anim, folder) and folder != self.client.get_char_name(): + self.client.send_host_message("Iniswap is blocked in this area") + return ++ if len(self.client.charcurse) > 0 and folder != self.client.get_char_name(): ++ self.client.send_host_message("You may not iniswap while you are charcursed!") ++ return ++ if not self.client.area.blankposting_allowed: ++ if text == ' ': ++ self.client.send_host_message("Blankposting is forbidden in this area!") ++ return ++ if text.isspace(): ++ self.client.send_host_message("Blankposting is forbidden in this area, and putting more spaces in does not make it not blankposting.") ++ return ++ if len(re.sub(r'[{}\\`|(~~)]','', text).replace(' ', '')) < 3 and text != '<' and text != '>': ++ self.client.send_host_message("While that is not a blankpost, it is still pretty spammy. Try forming sentences.") ++ return ++ if text.startswith('/a '): ++ part = text.split(' ') ++ try: ++ aid = int(part[1]) ++ if self.client in self.server.area_manager.get_area_by_id(aid).owners: ++ target_area.append(aid) ++ if not target_area: ++ self.client.send_host_message('You don\'t own {}!'.format(self.server.area_manager.get_area_by_id(aid).name)) ++ return ++ text = ' '.join(part[2:]) ++ except ValueError: ++ self.client.send_host_message("That does not look like a valid area ID!") ++ return ++ elif text.startswith('/s '): ++ part = text.split(' ') ++ for a in self.server.area_manager.areas: ++ if self.client in a.owners: ++ target_area.append(a.id) ++ if not target_area: ++ self.client.send_host_message('You don\'t any areas!') ++ return ++ text = ' '.join(part[1:]) + if msg_type not in ('chat', '0', '1'): + return + if anim_type not in (0, 1, 2, 5, 6): +@@ -354,12 +434,38 @@ class AOProtocol(asyncio.Protocol): + if button not in (0, 1, 2, 3, 4): + return + if evidence < 0: +- return +- if ding not in (0, 1): + return +- if color not in (0, 1, 2, 3, 4, 5, 6): ++ if ding not in (0, 1): + return +- if color == 2 and not self.client.is_mod: ++ if color not in (0, 1, 2, 3, 4, 5, 6, 7, 8): ++ return ++ if len(showname) > 15: ++ self.client.send_host_message("Your IC showname is way too long!") ++ return ++ if nonint_pre == 1: ++ if button in (1, 2, 3, 4, 23): ++ if anim_type == 1 or anim_type == 2: ++ anim_type = 0 ++ elif anim_type == 6: ++ anim_type = 5 ++ if self.client.area.non_int_pres_only: ++ if anim_type == 1 or anim_type == 2: ++ anim_type = 0 ++ nonint_pre = 1 ++ elif anim_type == 6: ++ anim_type = 5 ++ nonint_pre = 1 ++ if not self.client.area.shouts_allowed: ++ # Old clients communicate the objecting in anim_type. ++ if anim_type == 2: ++ anim_type = 1 ++ elif anim_type == 6: ++ anim_type = 5 ++ # New clients do it in a specific objection message area. ++ button = 0 ++ # Turn off the ding. ++ ding = 0 ++ if color == 2 and not (self.client.is_mod or self.client in self.client.area.owners): + color = 0 + if color == 6: + text = re.sub(r'[^\x00-\x7F]+',' ', text) #remove all unicode to prevent redtext abuse +@@ -371,9 +477,11 @@ class AOProtocol(asyncio.Protocol): + if self.client.pos: + pos = self.client.pos + else: +- if pos not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit'): ++ if pos not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit', 'jur', 'sea'): + return + msg = text[:256] ++ if self.client.shaken: ++ msg = self.client.shake_message(msg) + if self.client.disemvowel: + msg = self.client.disemvowel_message(msg) + self.client.pos = pos +@@ -381,13 +489,53 @@ class AOProtocol(asyncio.Protocol): + if self.client.area.evi_list.evidences[self.client.evi_list[evidence] - 1].pos != 'all': + self.client.area.evi_list.evidences[self.client.evi_list[evidence] - 1].pos = 'all' + self.client.area.broadcast_evidence_list() ++ ++ # Here, we check the pair stuff, and save info about it to the client. ++ # Notably, while we only get a charid_pair and an offset, we send back a chair_pair, an emote, a talker offset ++ # and an other offset. ++ self.client.charid_pair = charid_pair ++ self.client.offset_pair = offset_pair ++ if anim_type not in (5, 6): ++ self.client.last_sprite = anim ++ self.client.flip = flip ++ self.client.claimed_folder = folder ++ other_offset = 0 ++ other_emote = '' ++ other_flip = 0 ++ other_folder = '' ++ ++ confirmed = False ++ if charid_pair > -1: ++ for target in self.client.area.clients: ++ if target.char_id == self.client.charid_pair and target.charid_pair == self.client.char_id and target != self.client and target.pos == self.client.pos: ++ confirmed = True ++ other_offset = target.offset_pair ++ other_emote = target.last_sprite ++ other_flip = target.flip ++ other_folder = target.claimed_folder ++ break ++ ++ if not confirmed: ++ charid_pair = -1 ++ offset_pair = 0 ++ + self.client.area.send_command('MS', msg_type, pre, folder, anim, msg, pos, sfx, anim_type, cid, +- sfx_delay, button, self.client.evi_list[evidence], flip, ding, color) ++ sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, ++ charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) ++ ++ self.client.area.send_owner_command('MS', msg_type, pre, folder, anim, '[' + self.client.area.abbreviation + ']' + msg, pos, sfx, anim_type, cid, ++ sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, ++ charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) ++ ++ self.server.area_manager.send_remote_command(target_area, 'MS', msg_type, pre, folder, anim, msg, pos, sfx, anim_type, cid, ++ sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, ++ charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) ++ + self.client.area.set_next_msg_delay(len(msg)) +- logger.log_server('[IC][{}][{}]{}'.format(self.client.area.id, self.client.get_char_name(), msg), self.client) ++ logger.log_server('[IC][{}][{}]{}'.format(self.client.area.abbreviation, self.client.get_char_name(), msg), self.client) + + if (self.client.area.is_recording): +- self.client.area.recorded_messages.append(args) ++ self.client.area.recorded_messages.append(args) + + def net_cmd_ct(self, args): + """ OOC Message +@@ -409,12 +557,22 @@ class AOProtocol(asyncio.Protocol): + if self.client.name == '': + self.client.send_host_message('You must insert a name with at least one letter') + return +- if self.client.name.startswith(self.server.config['hostname']) or self.client.name.startswith('G'): ++ if len(self.client.name) > 30: ++ self.client.send_host_message('Your OOC name is too long! Limit it to 30 characters.') ++ return ++ for c in self.client.name: ++ if unicodedata.category(c) == 'Cf': ++ self.client.send_host_message('You cannot use format characters in your name!') ++ return ++ if self.client.name.startswith(self.server.config['hostname']) or self.client.name.startswith('G') or self.client.name.startswith('M'): + self.client.send_host_message('That name is reserved!') + return ++ if args[1].startswith(' /'): ++ self.client.send_host_message('Your message was not sent for safety reasons: you left a space before that slash.') ++ return + if args[1].startswith('/'): + spl = args[1][1:].split(' ', 1) +- cmd = spl[0] ++ cmd = spl[0].lower() + arg = '' + if len(spl) == 2: + arg = spl[1][:256] +@@ -427,11 +585,14 @@ class AOProtocol(asyncio.Protocol): + except (ClientError, AreaError, ArgumentError, ServerError) as ex: + self.client.send_host_message(ex) + else: ++ if self.client.shaken: ++ args[1] = self.client.shake_message(args[1]) + if self.client.disemvowel: + args[1] = self.client.disemvowel_message(args[1]) + self.client.area.send_command('CT', self.client.name, args[1]) ++ self.client.area.send_owner_command('CT', '[' + self.client.area.abbreviation + ']' + self.client.name, args[1]) + logger.log_server( +- '[OOC][{}][{}][{}]{}'.format(self.client.area.id, self.client.get_char_name(), self.client.name, ++ '[OOC][{}][{}]{}'.format(self.client.area.abbreviation, self.client.get_char_name(), + args[1]), self.client) + + def net_cmd_mc(self, args): +@@ -450,7 +611,10 @@ class AOProtocol(asyncio.Protocol): + if not self.client.is_dj: + self.client.send_host_message('You were blockdj\'d by a moderator.') + return +- if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT): ++ if self.client.area.cannot_ic_interact(self.client): ++ self.client.send_host_message("You are not on the area's invite list, and thus, you cannot change music!") ++ return ++ if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT) and not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT, self.ArgType.STR): + return + if args[1] != self.client.char_id: + return +@@ -459,10 +623,29 @@ class AOProtocol(asyncio.Protocol): + return + try: + name, length = self.server.get_song_data(args[0]) +- self.client.area.play_music(name, self.client.char_id, length) +- self.client.area.add_music_playing(self.client, name) +- logger.log_server('[{}][{}]Changed music to {}.' +- .format(self.client.area.id, self.client.get_char_name(), name), self.client) ++ ++ if self.client.area.jukebox: ++ showname = '' ++ if len(args) > 2: ++ showname = args[2] ++ if len(showname) > 0 and not self.client.area.showname_changes_allowed: ++ self.client.send_host_message("Showname changes are forbidden in this area!") ++ return ++ self.client.area.add_jukebox_vote(self.client, name, length, showname) ++ logger.log_server('[{}][{}]Added a jukebox vote for {}.'.format(self.client.area.abbreviation, self.client.get_char_name(), name), self.client) ++ else: ++ if len(args) > 2: ++ showname = args[2] ++ if len(showname) > 0 and not self.client.area.showname_changes_allowed: ++ self.client.send_host_message("Showname changes are forbidden in this area!") ++ return ++ self.client.area.play_music_shownamed(name, self.client.char_id, showname, length) ++ self.client.area.add_music_playing_shownamed(self.client, showname, name) ++ else: ++ self.client.area.play_music(name, self.client.char_id, length) ++ self.client.area.add_music_playing(self.client, name) ++ logger.log_server('[{}][{}]Changed music to {}.' ++ .format(self.client.area.abbreviation, self.client.get_char_name(), name), self.client) + except ServerError: + return + except ClientError as ex: +@@ -474,26 +657,37 @@ class AOProtocol(asyncio.Protocol): + RT##% + + """ ++ if not self.client.area.shouts_allowed: ++ self.client.send_host_message("You cannot use the testimony buttons here!") ++ return + if self.client.is_muted: # Checks to see if the client has been muted by a mod + self.client.send_host_message("You have been muted by a moderator") + return + if not self.client.can_wtce: + self.client.send_host_message('You were blocked from using judge signs by a moderator.') + return +- if not self.validate_net_cmd(args, self.ArgType.STR): ++ if self.client.area.cannot_ic_interact(self.client): ++ self.client.send_host_message("You are not on the area's invite list, and thus, you cannot use the WTCE buttons!") ++ return ++ if not self.validate_net_cmd(args, self.ArgType.STR) and not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT): + return + if args[0] == 'testimony1': + sign = 'WT' + elif args[0] == 'testimony2': + sign = 'CE' ++ elif args[0] == 'judgeruling': ++ sign = 'JR' + else: + return + if self.client.wtce_mute(): + self.client.send_host_message('You used witness testimony/cross examination signs too many times. Please try again after {} seconds.'.format(int(self.client.wtce_mute()))) + return +- self.client.area.send_command('RT', args[0]) ++ if len(args) == 1: ++ self.client.area.send_command('RT', args[0]) ++ elif len(args) == 2: ++ self.client.area.send_command('RT', args[0], args[1]) + self.client.area.add_to_judgelog(self.client, 'used {}'.format(sign)) +- logger.log_server("[{}]{} Used WT/CE".format(self.client.area.id, self.client.get_char_name()), self.client) ++ logger.log_server("[{}]{} Used WT/CE".format(self.client.area.abbreviation, self.client.get_char_name()), self.client) + + def net_cmd_hp(self, args): + """ Sets the penalty bar. +@@ -504,13 +698,16 @@ class AOProtocol(asyncio.Protocol): + if self.client.is_muted: # Checks to see if the client has been muted by a mod + self.client.send_host_message("You have been muted by a moderator") + return ++ if self.client.area.cannot_ic_interact(self.client): ++ self.client.send_host_message("You are not on the area's invite list, and thus, you cannot change the Confidence bars!") ++ return + if not self.validate_net_cmd(args, self.ArgType.INT, self.ArgType.INT): + return + try: + self.client.area.change_hp(args[0], args[1]) + self.client.area.add_to_judgelog(self.client, 'changed the penalties') + logger.log_server('[{}]{} changed HP ({}) to {}' +- .format(self.client.area.id, self.client.get_char_name(), args[0], args[1]), self.client) ++ .format(self.client.area.abbreviation, self.client.get_char_name(), args[0], args[1]), self.client) + except AreaError: + return + +@@ -552,7 +749,7 @@ class AOProtocol(asyncio.Protocol): + self.client.area.broadcast_evidence_list() + + +- def net_cmd_zz(self, _): ++ def net_cmd_zz(self, args): + """ Sent on mod call. + + """ +@@ -566,11 +763,16 @@ class AOProtocol(asyncio.Protocol): + + current_time = strftime("%H:%M", localtime()) + +- self.server.send_all_cmd_pred('ZZ', '[{}] {} ({}) in {} ({})' +- .format(current_time, self.client.get_char_name(), self.client.get_ip(), self.client.area.name, +- self.client.area.id), pred=lambda c: c.is_mod) +- self.client.set_mod_call_delay() +- logger.log_server('[{}][{}]{} called a moderator.'.format(self.client.get_ip(), self.client.area.id, self.client.get_char_name())) ++ if len(args) < 1: ++ self.server.send_all_cmd_pred('ZZ', '[{}] {} ({}) in {} without reason (not using the Case Café client?)' ++ .format(current_time, self.client.get_char_name(), self.client.get_ip(), self.client.area.name), pred=lambda c: c.is_mod) ++ self.client.set_mod_call_delay() ++ logger.log_server('[{}]{} called a moderator.'.format(self.client.area.abbreviation, self.client.get_char_name()), self.client) ++ else: ++ self.server.send_all_cmd_pred('ZZ', '[{}] {} ({}) in {} with reason: {}' ++ .format(current_time, self.client.get_char_name(), self.client.get_ip(), self.client.area.name, args[0][:100]), pred=lambda c: c.is_mod) ++ self.client.set_mod_call_delay() ++ logger.log_server('[{}]{} called a moderator: {}.'.format(self.client.area.abbreviation, self.client.get_char_name(), args[0]), self.client) + + def net_cmd_opKICK(self, args): + self.net_cmd_ct(['opkick', '/kick {}'.format(args[0])]) +diff --git a/tsuserver3/server/area_manager.py b/AO2-Client/server/area_manager.py +index 39eb211..cfb2be0 100644 +--- a/tsuserver3/server/area_manager.py ++++ b/AO2-Client/server/area_manager.py +@@ -22,11 +22,12 @@ import yaml + + from server.exceptions import AreaError + from server.evidence import EvidenceList ++from enum import Enum + + + class AreaManager: + class Area: +- def __init__(self, area_id, server, name, background, bg_lock, evidence_mod = 'FFA', locking_allowed = False, iniswap_allowed = True): ++ def __init__(self, area_id, server, name, background, bg_lock, evidence_mod = 'FFA', locking_allowed = False, iniswap_allowed = True, showname_changes_allowed = False, shouts_allowed = True, jukebox = False, abbreviation = '', non_int_pres_only = False): + self.iniswap_allowed = iniswap_allowed + self.clients = set() + self.invite_list = {} +@@ -44,12 +45,15 @@ class AreaManager: + self.judgelog = [] + self.current_music = '' + self.current_music_player = '' ++ self.current_music_player_ipid = -1 + self.evi_list = EvidenceList() + self.is_recording = False + self.recorded_messages = [] + self.evidence_mod = evidence_mod + self.locking_allowed = locking_allowed +- self.owned = False ++ self.showname_changes_allowed = showname_changes_allowed ++ self.shouts_allowed = shouts_allowed ++ self.abbreviation = abbreviation + self.cards = dict() + + """ +@@ -59,23 +63,53 @@ class AreaManager: + self.evidence_list.append(Evidence("weeeeeew", "desc3", "3.png")) + """ + +- self.is_locked = False ++ self.is_locked = self.Locked.FREE ++ self.blankposting_allowed = True ++ self.non_int_pres_only = non_int_pres_only ++ self.jukebox = jukebox ++ self.jukebox_votes = [] ++ self.jukebox_prev_char_id = -1 ++ ++ self.owners = [] ++ ++ class Locked(Enum): ++ FREE = 1, ++ SPECTATABLE = 2, ++ LOCKED = 3 + + def new_client(self, client): + self.clients.add(client) ++ self.server.area_manager.send_arup_players() + + def remove_client(self, client): + self.clients.remove(client) +- if client.is_cm: +- client.is_cm = False +- self.owned = False +- if self.is_locked: +- self.unlock() ++ if len(self.clients) == 0: ++ self.change_status('IDLE') + + def unlock(self): +- self.is_locked = False ++ self.is_locked = self.Locked.FREE ++ self.blankposting_allowed = True + self.invite_list = {} ++ self.server.area_manager.send_arup_lock() + self.send_host_message('This area is open now.') ++ ++ def spectator(self): ++ self.is_locked = self.Locked.SPECTATABLE ++ for i in self.clients: ++ self.invite_list[i.id] = None ++ for i in self.owners: ++ self.invite_list[i.id] = None ++ self.server.area_manager.send_arup_lock() ++ self.send_host_message('This area is spectatable now.') ++ ++ def lock(self): ++ self.is_locked = self.Locked.LOCKED ++ for i in self.clients: ++ self.invite_list[i.id] = None ++ for i in self.owners: ++ self.invite_list[i.id] = None ++ self.server.area_manager.send_arup_lock() ++ self.send_host_message('This area is locked now.') + + def is_char_available(self, char_id): + return char_id not in [x.char_id for x in self.clients] +@@ -89,9 +123,15 @@ class AreaManager: + def send_command(self, cmd, *args): + for c in self.clients: + c.send_command(cmd, *args) ++ ++ def send_owner_command(self, cmd, *args): ++ for c in self.owners: ++ if not c in self.clients: ++ c.send_command(cmd, *args) + + def send_host_message(self, msg): +- self.send_command('CT', self.server.config['hostname'], msg) ++ self.send_command('CT', self.server.config['hostname'], msg, '1') ++ self.send_owner_command('CT', '[' + self.abbreviation + ']' + self.server.config['hostname'], msg, '1') + + def set_next_msg_delay(self, msg_length): + delay = min(3000, 100 + 60 * msg_length) +@@ -106,6 +146,83 @@ class AreaManager: + if client.get_char_name() in char_link and char in char_link: + return False + return True ++ ++ def add_jukebox_vote(self, client, music_name, length=-1, showname=''): ++ if not self.jukebox: ++ return ++ if length <= 0: ++ self.remove_jukebox_vote(client, False) ++ else: ++ self.remove_jukebox_vote(client, True) ++ self.jukebox_votes.append(self.JukeboxVote(client, music_name, length, showname)) ++ client.send_host_message('Your song was added to the jukebox.') ++ if len(self.jukebox_votes) == 1: ++ self.start_jukebox() ++ ++ def remove_jukebox_vote(self, client, silent): ++ if not self.jukebox: ++ return ++ for current_vote in self.jukebox_votes: ++ if current_vote.client.id == client.id: ++ self.jukebox_votes.remove(current_vote) ++ if not silent: ++ client.send_host_message('You removed your song from the jukebox.') ++ ++ def get_jukebox_picked(self): ++ if not self.jukebox: ++ return ++ if len(self.jukebox_votes) == 0: ++ return None ++ elif len(self.jukebox_votes) == 1: ++ return self.jukebox_votes[0] ++ else: ++ weighted_votes = [] ++ for current_vote in self.jukebox_votes: ++ i = 0 ++ while i < current_vote.chance: ++ weighted_votes.append(current_vote) ++ i += 1 ++ return random.choice(weighted_votes) ++ ++ def start_jukebox(self): ++ # There is a probability that the jukebox feature has been turned off since then, ++ # we should check that. ++ # We also do a check if we were the last to play a song, just in case. ++ if not self.jukebox: ++ if self.current_music_player == 'The Jukebox' and self.current_music_player_ipid == 'has no IPID': ++ self.current_music = '' ++ return ++ ++ vote_picked = self.get_jukebox_picked() ++ ++ if vote_picked is None: ++ self.current_music = '' ++ return ++ ++ if vote_picked.client.char_id != self.jukebox_prev_char_id or vote_picked.name != self.current_music or len(self.jukebox_votes) > 1: ++ self.jukebox_prev_char_id = vote_picked.client.char_id ++ if vote_picked.showname == '': ++ self.send_command('MC', vote_picked.name, vote_picked.client.char_id) ++ else: ++ self.send_command('MC', vote_picked.name, vote_picked.client.char_id, vote_picked.showname) ++ else: ++ self.send_command('MC', vote_picked.name, -1) ++ ++ self.current_music_player = 'The Jukebox' ++ self.current_music_player_ipid = 'has no IPID' ++ self.current_music = vote_picked.name ++ ++ for current_vote in self.jukebox_votes: ++ # Choosing the same song will get your votes down to 0, too. ++ # Don't want the same song twice in a row! ++ if current_vote.name == vote_picked.name: ++ current_vote.chance = 0 ++ else: ++ current_vote.chance += 1 ++ ++ if self.music_looper: ++ self.music_looper.cancel() ++ self.music_looper = asyncio.get_event_loop().call_later(vote_picked.length, lambda: self.start_jukebox()) + + def play_music(self, name, cid, length=-1): + self.send_command('MC', name, cid) +@@ -114,14 +231,25 @@ class AreaManager: + if length > 0: + self.music_looper = asyncio.get_event_loop().call_later(length, + lambda: self.play_music(name, -1, length)) ++ ++ def play_music_shownamed(self, name, cid, showname, length=-1): ++ self.send_command('MC', name, cid, showname) ++ if self.music_looper: ++ self.music_looper.cancel() ++ if length > 0: ++ self.music_looper = asyncio.get_event_loop().call_later(length, ++ lambda: self.play_music(name, -1, length)) + + + def can_send_message(self, client): +- if self.is_locked and not client.is_mod and not client.ipid in self.invite_list: ++ if self.cannot_ic_interact(client): + client.send_host_message('This is a locked area - ask the CM to speak.') + return False + return (time.time() * 1000.0 - self.next_message_time) > 0 + ++ def cannot_ic_interact(self, client): ++ return self.is_locked != self.Locked.FREE and not client.is_mod and not client.id in self.invite_list ++ + def change_hp(self, side, val): + if not 0 <= val <= 10: + raise AreaError('Invalid penalty value.') +@@ -140,10 +268,13 @@ class AreaManager: + self.send_command('BN', self.background) + + def change_status(self, value): +- allowed_values = ('idle', 'building-open', 'building-full', 'casing-open', 'casing-full', 'recess') ++ allowed_values = ('idle', 'rp', 'casing', 'looking-for-players', 'lfp', 'recess', 'gaming') + if value.lower() not in allowed_values: + raise AreaError('Invalid status. Possible values: {}'.format(', '.join(allowed_values))) ++ if value.lower() == 'lfp': ++ value = 'looking-for-players' + self.status = value.upper() ++ self.server.area_manager.send_arup_status() + + def change_doc(self, doc='No document.'): + self.doc = doc +@@ -155,6 +286,12 @@ class AreaManager: + + def add_music_playing(self, client, name): + self.current_music_player = client.get_char_name() ++ self.current_music_player_ipid = client.ipid ++ self.current_music = name ++ ++ def add_music_playing_shownamed(self, client, showname, name): ++ self.current_music_player = showname + " (" + client.get_char_name() + ")" ++ self.current_music_player_ipid = client.ipid + self.current_music = name + + def get_evidence_list(self, client): +@@ -168,7 +305,22 @@ class AreaManager: + """ + for client in self.clients: + client.send_command('LE', *self.get_evidence_list(client)) +- ++ ++ def get_cms(self): ++ msg = '' ++ for i in self.owners: ++ msg = msg + '[' + str(i.id) + '] ' + i.get_char_name() + ', ' ++ if len(msg) > 2: ++ msg = msg[:-2] ++ return msg ++ ++ class JukeboxVote: ++ def __init__(self, client, name, length, showname): ++ self.client = client ++ self.name = name ++ self.length = length ++ self.chance = 1 ++ self.showname = showname + + def __init__(self, server): + self.server = server +@@ -186,8 +338,18 @@ class AreaManager: + item['locking_allowed'] = False + if 'iniswap_allowed' not in item: + item['iniswap_allowed'] = True ++ if 'showname_changes_allowed' not in item: ++ item['showname_changes_allowed'] = False ++ if 'shouts_allowed' not in item: ++ item['shouts_allowed'] = True ++ if 'jukebox' not in item: ++ item['jukebox'] = False ++ if 'noninterrupting_pres' not in item: ++ item['noninterrupting_pres'] = False ++ if 'abbreviation' not in item: ++ item['abbreviation'] = self.get_generated_abbreviation(item['area']) + self.areas.append( +- self.Area(self.cur_id, self.server, item['area'], item['background'], item['bglock'], item['evidence_mod'], item['locking_allowed'], item['iniswap_allowed'])) ++ self.Area(self.cur_id, self.server, item['area'], item['background'], item['bglock'], item['evidence_mod'], item['locking_allowed'], item['iniswap_allowed'], item['showname_changes_allowed'], item['shouts_allowed'], item['jukebox'], item['abbreviation'], item['noninterrupting_pres'])) + self.cur_id += 1 + + def default_area(self): +@@ -204,3 +366,47 @@ class AreaManager: + if area.id == num: + return area + raise AreaError('Area not found.') ++ ++ def get_generated_abbreviation(self, name): ++ if name.lower().startswith("courtroom"): ++ return "CR" + name.split()[-1] ++ elif name.lower().startswith("area"): ++ return "A" + name.split()[-1] ++ elif len(name.split()) > 1: ++ return "".join(item[0].upper() for item in name.split()) ++ elif len(name) > 3: ++ return name[:3].upper() ++ else: ++ return name.upper() ++ ++ def send_remote_command(self, area_ids, cmd, *args): ++ for a_id in area_ids: ++ self.get_area_by_id(a_id).send_command(cmd, *args) ++ self.get_area_by_id(a_id).send_owner_command(cmd, *args) ++ ++ def send_arup_players(self): ++ players_list = [0] ++ for area in self.areas: ++ players_list.append(len(area.clients)) ++ self.server.send_arup(players_list) ++ ++ def send_arup_status(self): ++ status_list = [1] ++ for area in self.areas: ++ status_list.append(area.status) ++ self.server.send_arup(status_list) ++ ++ def send_arup_cms(self): ++ cms_list = [2] ++ for area in self.areas: ++ cm = 'FREE' ++ if len(area.owners) > 0: ++ cm = area.get_cms() ++ cms_list.append(cm) ++ self.server.send_arup(cms_list) ++ ++ def send_arup_lock(self): ++ lock_list = [3] ++ for area in self.areas: ++ lock_list.append(area.is_locked.name) ++ self.server.send_arup(lock_list) +diff --git a/tsuserver3/server/ban_manager.py b/AO2-Client/server/ban_manager.py +index 24518b2..20c186f 100644 +--- a/tsuserver3/server/ban_manager.py ++++ b/AO2-Client/server/ban_manager.py +@@ -51,4 +51,4 @@ class BanManager: + self.write_banlist() + + def is_banned(self, ipid): +- return (ipid in self.bans) ++ return (ipid in self.bans) +\ No newline at end of file +diff --git a/tsuserver3/server/client_manager.py b/AO2-Client/server/client_manager.py +index 38974b3..432c39d 100644 +--- a/tsuserver3/server/client_manager.py ++++ b/AO2-Client/server/client_manager.py +@@ -44,9 +44,10 @@ class ClientManager: + self.is_dj = True + self.can_wtce = True + self.pos = '' +- self.is_cm = False + self.evi_list = [] + self.disemvowel = False ++ self.shaken = False ++ self.charcurse = [] + self.muted_global = False + self.muted_adverts = False + self.is_muted = False +@@ -56,7 +57,24 @@ class ClientManager: + self.in_rp = False + self.ipid = ipid + self.websocket = None ++ ++ # Pairing stuff ++ self.charid_pair = -1 ++ self.offset_pair = 0 ++ self.last_sprite = '' ++ self.flip = 0 ++ self.claimed_folder = '' + ++ # Casing stuff ++ self.casing_cm = False ++ self.casing_cases = "" ++ self.casing_def = False ++ self.casing_pro = False ++ self.casing_jud = False ++ self.casing_jur = False ++ self.casing_steno = False ++ self.case_call_time = 0 ++ + #flood-guard stuff + self.mus_counter = 0 + self.mus_mute_time = 0 +@@ -85,7 +103,7 @@ class ClientManager: + self.send_raw_message('{}#%'.format(command)) + + def send_host_message(self, msg): +- self.send_command('CT', self.server.config['hostname'], msg) ++ self.send_command('CT', self.server.config['hostname'], msg, '1') + + def send_motd(self): + self.send_host_message('=== MOTD ===\r\n{}\r\n============='.format(self.server.config['motd'])) +@@ -111,6 +129,10 @@ class ClientManager: + def change_character(self, char_id, force=False): + if not self.server.is_valid_char_id(char_id): + raise ClientError('Invalid Character ID.') ++ if len(self.charcurse) > 0: ++ if not char_id in self.charcurse: ++ raise ClientError('Character not available.') ++ force = True + if not self.area.is_char_available(char_id): + if force: + for client in self.area.clients: +@@ -122,11 +144,12 @@ class ClientManager: + self.char_id = char_id + self.pos = '' + self.send_command('PV', self.id, 'CID', self.char_id) ++ self.area.send_command('CharsCheck', *self.get_available_char_list()) + logger.log_server('[{}]Changed character from {} to {}.' +- .format(self.area.id, old_char, self.get_char_name()), self) ++ .format(self.area.abbreviation, old_char, self.get_char_name()), self) + + def change_music_cd(self): +- if self.is_mod or self.is_cm: ++ if self.is_mod or self in self.area.owners: + return 0 + if self.mus_mute_time: + if time.time() - self.mus_mute_time < self.server.config['music_change_floodguard']['mute_length']: +@@ -143,7 +166,7 @@ class ClientManager: + return 0 + + def wtce_mute(self): +- if self.is_mod or self.is_cm: ++ if self.is_mod or self in self.area.owners: + return 0 + if self.wtce_mute_time: + if time.time() - self.wtce_mute_time < self.server.config['wtce_floodguard']['mute_length']: +@@ -168,9 +191,14 @@ class ClientManager: + def change_area(self, area): + if self.area == area: + raise ClientError('User already in specified area.') +- if area.is_locked and not self.is_mod and not self.ipid in area.invite_list: +- self.send_host_message('This area is locked - you will be unable to send messages ICly.') +- #raise ClientError("That area is locked!") ++ if area.is_locked == area.Locked.LOCKED and not self.is_mod and not self.id in area.invite_list: ++ raise ClientError("That area is locked!") ++ if area.is_locked == area.Locked.SPECTATABLE and not self.is_mod and not self.id in area.invite_list: ++ self.send_host_message('This area is spectatable, but not free - you will be unable to send messages ICly unless invited.') ++ ++ if self.area.jukebox: ++ self.area.remove_jukebox_vote(self, True) ++ + old_area = self.area + if not area.is_char_available(self.char_id): + try: +@@ -189,6 +217,7 @@ class ClientManager: + logger.log_server( + '[{}]Changed area from {} ({}) to {} ({}).'.format(self.get_char_name(), old_area.name, old_area.id, + self.area.name, self.area.id), self) ++ self.area.send_command('CharsCheck', *self.get_available_char_list()) + self.send_command('HP', 1, self.area.hp_def) + self.send_command('HP', 2, self.area.hp_pro) + self.send_command('BN', self.area.background) +@@ -196,34 +225,50 @@ class ClientManager: + + def send_area_list(self): + msg = '=== Areas ===' +- lock = {True: '[LOCKED]', False: ''} + for i, area in enumerate(self.server.area_manager.areas): + owner = 'FREE' +- if area.owned: +- for client in [x for x in area.clients if x.is_cm]: +- owner = 'MASTER: {}'.format(client.get_char_name()) +- break +- msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(i, area.name, len(area.clients), area.status, owner, lock[area.is_locked]) ++ if len(area.owners) > 0: ++ owner = 'CM: {}'.format(area.get_cms()) ++ lock = {area.Locked.FREE: '', area.Locked.SPECTATABLE: '[SPECTATABLE]', area.Locked.LOCKED: '[LOCKED]'} ++ msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(area.abbreviation, area.name, len(area.clients), area.status, owner, lock[area.is_locked]) + if self.area == area: + msg += ' [*]' + self.send_host_message(msg) + + def get_area_info(self, area_id, mods): +- info = '' ++ info = '\r\n' + try: + area = self.server.area_manager.get_area_by_id(area_id) + except AreaError: + raise +- info += '= Area {}: {} =='.format(area.id, area.name) ++ info += '=== {} ==='.format(area.name) ++ info += '\r\n' ++ ++ lock = {area.Locked.FREE: '', area.Locked.SPECTATABLE: '[SPECTATABLE]', area.Locked.LOCKED: '[LOCKED]'} ++ info += '[{}]: [{} users][{}]{}'.format(area.abbreviation, len(area.clients), area.status, lock[area.is_locked]) ++ + sorted_clients = [] + for client in area.clients: + if (not mods) or client.is_mod: + sorted_clients.append(client) ++ for owner in area.owners: ++ if not (mods or owner in area.clients): ++ sorted_clients.append(owner) ++ if not sorted_clients: ++ return '' + sorted_clients = sorted(sorted_clients, key=lambda x: x.get_char_name()) + for c in sorted_clients: +- info += '\r\n[{}] {}'.format(c.id, c.get_char_name()) ++ info += '\r\n' ++ if c in area.owners: ++ if not c in area.clients: ++ info += '[RCM]' ++ else: ++ info +='[CM]' ++ info += '[{}] {}'.format(c.id, c.get_char_name()) + if self.is_mod: + info += ' ({})'.format(c.ipid) ++ info += ': {}'.format(c.name) ++ + return info + + def send_area_info(self, area_id, mods): +@@ -234,13 +279,13 @@ class ClientManager: + cnt = 0 + info = '\n== Area List ==' + for i in range(len(self.server.area_manager.areas)): +- if len(self.server.area_manager.areas[i].clients) > 0: ++ if len(self.server.area_manager.areas[i].clients) > 0 or len(self.server.area_manager.areas[i].owners) > 0: + cnt += len(self.server.area_manager.areas[i].clients) +- info += '\r\n{}'.format(self.get_area_info(i, mods)) ++ info += '{}'.format(self.get_area_info(i, mods)) + info = 'Current online: {}'.format(cnt) + info + else: + try: +- info = 'People in this area: {}\n'.format(len(self.server.area_manager.areas[area_id].clients)) + self.get_area_info(area_id, mods) ++ info = 'People in this area: {}'.format(len(self.server.area_manager.areas[area_id].clients)) + self.get_area_info(area_id, mods) + except AreaError: + raise + self.send_host_message(info) +@@ -267,22 +312,34 @@ class ClientManager: + self.send_host_message(info) + + def send_done(self): +- avail_char_ids = set(range(len(self.server.char_list))) - set([x.char_id for x in self.area.clients]) +- char_list = [-1] * len(self.server.char_list) +- for x in avail_char_ids: +- char_list[x] = 0 +- self.send_command('CharsCheck', *char_list) ++ self.send_command('CharsCheck', *self.get_available_char_list()) + self.send_command('HP', 1, self.area.hp_def) + self.send_command('HP', 2, self.area.hp_pro) + self.send_command('BN', self.area.background) + self.send_command('LE', *self.area.get_evidence_list(self)) + self.send_command('MM', 1) ++ ++ self.server.area_manager.send_arup_players() ++ self.server.area_manager.send_arup_status() ++ self.server.area_manager.send_arup_cms() ++ self.server.area_manager.send_arup_lock() ++ + self.send_command('DONE') + + def char_select(self): + self.char_id = -1 + self.send_done() + ++ def get_available_char_list(self): ++ if len(self.charcurse) > 0: ++ avail_char_ids = set(range(len(self.server.char_list))) and set(self.charcurse) ++ else: ++ avail_char_ids = set(range(len(self.server.char_list))) - set([x.char_id for x in self.area.clients]) ++ char_list = [-1] * len(self.server.char_list) ++ for x in avail_char_ids: ++ char_list[x] = 0 ++ return char_list ++ + def auth_mod(self, password): + if self.is_mod: + raise ClientError('Already logged in.') +@@ -302,8 +359,8 @@ class ClientManager: + return self.server.char_list[self.char_id] + + def change_position(self, pos=''): +- if pos not in ('', 'def', 'pro', 'hld', 'hlp', 'jud', 'wit'): +- raise ClientError('Invalid position. Possible values: def, pro, hld, hlp, jud, wit.') ++ if pos not in ('', 'def', 'pro', 'hld', 'hlp', 'jud', 'wit', 'jur', 'sea'): ++ raise ClientError('Invalid position. Possible values: def, pro, hld, hlp, jud, wit, jur, sea.') + self.pos = pos + + def set_mod_call_delay(self): +@@ -312,9 +369,22 @@ class ClientManager: + def can_call_mod(self): + return (time.time() * 1000.0 - self.mod_call_time) > 0 + ++ def set_case_call_delay(self): ++ self.case_call_time = round(time.time() * 1000.0 + 60000) ++ ++ def can_call_case(self): ++ return (time.time() * 1000.0 - self.case_call_time) > 0 ++ + def disemvowel_message(self, message): + message = re.sub("[aeiou]", "", message, flags=re.IGNORECASE) + return re.sub(r"\s+", " ", message) ++ ++ def shake_message(self, message): ++ import random ++ parts = message.split() ++ random.shuffle(parts) ++ return ' '.join(parts) ++ + + def __init__(self, server): + self.clients = set() +@@ -329,6 +399,15 @@ class ClientManager: + + + def remove_client(self, client): ++ if client.area.jukebox: ++ client.area.remove_jukebox_vote(client, True) ++ for a in self.server.area_manager.areas: ++ if client in a.owners: ++ a.owners.remove(client) ++ client.server.area_manager.send_arup_cms() ++ if len(a.owners) == 0: ++ if a.is_locked != a.Locked.FREE: ++ a.unlock() + heappush(self.cur_id, client.id) + self.clients.remove(client) + +diff --git a/tsuserver3/server/commands.py b/AO2-Client/server/commands.py +index 13d50f9..d02eff2 100644 +--- a/tsuserver3/server/commands.py ++++ b/AO2-Client/server/commands.py +@@ -16,6 +16,7 @@ + # along with this program. If not, see . + #possible keys: ip, OOC, id, cname, ipid, hdid + import random ++import re + import hashlib + import string + from server.constants import TargetType +@@ -23,6 +24,35 @@ from server.constants import TargetType + from server import logger + from server.exceptions import ClientError, ServerError, ArgumentError, AreaError + ++def ooc_cmd_a(client, arg): ++ if len(arg) == 0: ++ raise ArgumentError('You must specify an area.') ++ arg = arg.split(' ') ++ ++ try: ++ area = client.server.area_manager.get_area_by_id(int(arg[0])) ++ except AreaError: ++ raise ++ ++ message_areas_cm(client, [area], ' '.join(arg[1:])) ++ ++def ooc_cmd_s(client, arg): ++ areas = [] ++ for a in client.server.area_manager.areas: ++ if client in a.owners: ++ areas.append(a) ++ if not areas: ++ client.send_host_message('You aren\'t a CM in any area!') ++ return ++ message_areas_cm(client, areas, arg) ++ ++def message_areas_cm(client, areas, message): ++ for a in areas: ++ if not client in a.owners: ++ client.send_host_message('You are not a CM in {}!'.format(a.name)) ++ return ++ a.send_command('CT', client.name, message) ++ a.send_owner_command('CT', client.name, message) + + def ooc_cmd_switch(client, arg): + if len(arg) == 0: +@@ -47,7 +77,7 @@ def ooc_cmd_bg(client, arg): + except AreaError: + raise + client.area.send_host_message('{} changed the background to {}.'.format(client.get_char_name(), arg)) +- logger.log_server('[{}][{}]Changed background to {}'.format(client.area.id, client.get_char_name(), arg), client) ++ logger.log_server('[{}][{}]Changed background to {}'.format(client.area.abbreviation, client.get_char_name(), arg), client) + + def ooc_cmd_bglock(client,arg): + if not client.is_mod: +@@ -58,8 +88,8 @@ def ooc_cmd_bglock(client,arg): + client.area.bg_lock = "false" + else: + client.area.bg_lock = "true" +- client.area.send_host_message('A mod has set the background lock to {}.'.format(client.area.bg_lock)) +- logger.log_server('[{}][{}]Changed bglock to {}'.format(client.area.id, client.get_char_name(), client.area.bg_lock), client) ++ client.area.send_host_message('{} [{}] has set the background lock to {}.'.format(client.get_char_name(), client.id, client.area.bg_lock)) ++ logger.log_server('[{}][{}]Changed bglock to {}'.format(client.area.abbreviation, client.get_char_name(), client.area.bg_lock), client) + + def ooc_cmd_evidence_mod(client, arg): + if not client.is_mod: +@@ -89,7 +119,21 @@ def ooc_cmd_allow_iniswap(client, arg): + client.send_host_message('iniswap is {}.'.format(answer[client.area.iniswap_allowed])) + return + ++def ooc_cmd_allow_blankposting(client, arg): ++ if not client.is_mod and not client in client.area.owners: ++ raise ClientError('You must be authorized to do that.') ++ client.area.blankposting_allowed = not client.area.blankposting_allowed ++ answer = {True: 'allowed', False: 'forbidden'} ++ client.area.send_host_message('{} [{}] has set blankposting in the area to {}.'.format(client.get_char_name(), client.id, answer[client.area.blankposting_allowed])) ++ return + ++def ooc_cmd_force_nonint_pres(client, arg): ++ if not client.is_mod and not client in client.area.owners: ++ raise ClientError('You must be authorized to do that.') ++ client.area.non_int_pres_only = not client.area.non_int_pres_only ++ answer = {True: 'non-interrupting only', False: 'non-interrupting or interrupting as you choose'} ++ client.area.send_host_message('{} [{}] has set pres in the area to be {}.'.format(client.get_char_name(), client.id, answer[client.area.non_int_pres_only])) ++ return + + def ooc_cmd_roll(client, arg): + roll_max = 11037 +@@ -116,7 +160,7 @@ def ooc_cmd_roll(client, arg): + roll = '(' + roll + ')' + client.area.send_host_message('{} rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) + logger.log_server( +- '[{}][{}]Used /roll and got {} out of {}.'.format(client.area.id, client.get_char_name(), roll, val[0])) ++ '[{}][{}]Used /roll and got {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0]), client) + + def ooc_cmd_rollp(client, arg): + roll_max = 11037 +@@ -126,13 +170,13 @@ def ooc_cmd_rollp(client, arg): + if not 1 <= val[0] <= roll_max: + raise ArgumentError('Roll value must be between 1 and {}.'.format(roll_max)) + except ValueError: +- raise ArgumentError('Wrong argument. Use /roll [] []') ++ raise ArgumentError('Wrong argument. Use /rollp [] []') + else: + val = [6] + if len(val) == 1: + val.append(1) + if len(val) > 2: +- raise ArgumentError('Too many arguments. Use /roll [] []') ++ raise ArgumentError('Too many arguments. Use /rollp [] []') + if val[1] > 20 or val[1] < 1: + raise ArgumentError('Num of rolls must be between 1 and 20') + roll = '' +@@ -142,19 +186,97 @@ def ooc_cmd_rollp(client, arg): + if val[1] > 1: + roll = '(' + roll + ')' + client.send_host_message('{} rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) +- client.area.send_host_message('{} rolled.'.format(client.get_char_name(), roll, val[0])) +- SALT = ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)) ++ ++ client.area.send_host_message('{} rolled in secret.'.format(client.get_char_name())) ++ for c in client.area.owners: ++ c.send_host_message('[{}]{} secretly rolled {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0])) ++ + logger.log_server( +- '[{}][{}]Used /roll and got {} out of {}.'.format(client.area.id, client.get_char_name(), hashlib.sha1((str(roll) + SALT).encode('utf-8')).hexdigest() + '|' + SALT, val[0])) ++ '[{}][{}]Used /rollp and got {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0]), client) + + def ooc_cmd_currentmusic(client, arg): + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') + if client.area.current_music == '': + raise ClientError('There is no music currently playing.') +- client.send_host_message('The current music is {} and was played by {}.'.format(client.area.current_music, ++ if client.is_mod: ++ client.send_host_message('The current music is {} and was played by {} ({}).'.format(client.area.current_music, ++ client.area.current_music_player, client.area.current_music_player_ipid)) ++ else: ++ client.send_host_message('The current music is {} and was played by {}.'.format(client.area.current_music, + client.area.current_music_player)) + ++def ooc_cmd_jukebox_toggle(client, arg): ++ if not client.is_mod and not client in client.area.owners: ++ raise ClientError('You must be authorized to do that.') ++ if len(arg) != 0: ++ raise ArgumentError('This command has no arguments.') ++ client.area.jukebox = not client.area.jukebox ++ client.area.jukebox_votes = [] ++ client.area.send_host_message('{} [{}] has set the jukebox to {}.'.format(client.get_char_name(), client.id, client.area.jukebox)) ++ ++def ooc_cmd_jukebox_skip(client, arg): ++ if not client.is_mod and not client in client.area.owners: ++ raise ClientError('You must be authorized to do that.') ++ if len(arg) != 0: ++ raise ArgumentError('This command has no arguments.') ++ if not client.area.jukebox: ++ raise ClientError('This area does not have a jukebox.') ++ if len(client.area.jukebox_votes) == 0: ++ raise ClientError('There is no song playing right now, skipping is pointless.') ++ client.area.start_jukebox() ++ if len(client.area.jukebox_votes) == 1: ++ client.area.send_host_message('{} [{}] has forced a skip, restarting the only jukebox song.'.format(client.get_char_name(), client.id)) ++ else: ++ client.area.send_host_message('{} [{}] has forced a skip to the next jukebox song.'.format(client.get_char_name(), client.id)) ++ logger.log_server('[{}][{}]Skipped the current jukebox song.'.format(client.area.abbreviation, client.get_char_name()), client) ++ ++def ooc_cmd_jukebox(client, arg): ++ if len(arg) != 0: ++ raise ArgumentError('This command has no arguments.') ++ if not client.area.jukebox: ++ raise ClientError('This area does not have a jukebox.') ++ if len(client.area.jukebox_votes) == 0: ++ client.send_host_message('The jukebox has no songs in it.') ++ else: ++ total = 0 ++ songs = [] ++ voters = dict() ++ chance = dict() ++ message = '' ++ ++ for current_vote in client.area.jukebox_votes: ++ if songs.count(current_vote.name) == 0: ++ songs.append(current_vote.name) ++ voters[current_vote.name] = [current_vote.client] ++ chance[current_vote.name] = current_vote.chance ++ else: ++ voters[current_vote.name].append(current_vote.client) ++ chance[current_vote.name] += current_vote.chance ++ total += current_vote.chance ++ ++ for song in songs: ++ message += '\n- ' + song + '\n' ++ message += '-- VOTERS: ' ++ ++ first = True ++ for voter in voters[song]: ++ if first: ++ first = False ++ else: ++ message += ', ' ++ message += voter.get_char_name() + ' [' + str(voter.id) + ']' ++ if client.is_mod: ++ message += '(' + str(voter.ipid) + ')' ++ message += '\n' ++ ++ if total == 0: ++ message += '-- CHANCE: 100' ++ else: ++ message += '-- CHANCE: ' + str(round(chance[song] / total * 100)) ++ ++ client.send_host_message('The jukebox has the following songs in it:{}'.format(message)) ++ + def ooc_cmd_coinflip(client, arg): + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') +@@ -162,7 +284,7 @@ def ooc_cmd_coinflip(client, arg): + flip = random.choice(coin) + client.area.send_host_message('{} flipped a coin and got {}.'.format(client.get_char_name(), flip)) + logger.log_server( +- '[{}][{}]Used /coinflip and got {}.'.format(client.area.id, client.get_char_name(), flip)) ++ '[{}][{}]Used /coinflip and got {}.'.format(client.area.abbreviation, client.get_char_name(), flip), client) + + def ooc_cmd_motd(client, arg): + if len(arg) != 0: +@@ -182,7 +304,7 @@ def ooc_cmd_pos(client, arg): + client.send_host_message('Position changed.') + + def ooc_cmd_forcepos(client, arg): +- if not client.is_cm and not client.is_mod: ++ if not client in client.area.owners and not client.is_mod: + raise ClientError('You must be authorized to do that.') + + args = arg.split() +@@ -222,60 +344,81 @@ def ooc_cmd_forcepos(client, arg): + client.area.send_host_message( + '{} forced {} client(s) into /pos {}.'.format(client.get_char_name(), len(targets), pos)) + logger.log_server( +- '[{}][{}]Used /forcepos {} for {} client(s).'.format(client.area.id, client.get_char_name(), pos, len(targets))) ++ '[{}][{}]Used /forcepos {} for {} client(s).'.format(client.area.abbreviation, client.get_char_name(), pos, len(targets)), client) + + def ooc_cmd_help(client, arg): + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') +- help_url = 'https://github.com/AttorneyOnline/tsuserver3/blob/master/README.md' +- help_msg = 'Available commands, source code and issues can be found here: {}'.format(help_url) ++ help_url = 'http://casecafe.byethost14.com/commandlist' ++ help_msg = 'The commands available on this server can be found here: {}'.format(help_url) + client.send_host_message(help_msg) + + def ooc_cmd_kick(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) == 0: +- raise ArgumentError('You must specify a target. Use /kick .') +- targets = client.server.client_manager.get_targets(client, TargetType.IPID, int(arg), False) +- if targets: +- for c in targets: +- logger.log_server('Kicked {}.'.format(c.ipid), client) +- client.send_host_message("{} was kicked.".format(c.get_char_name())) +- c.disconnect() +- else: +- client.send_host_message("No targets found.") +- +-def ooc_cmd_ban(client, arg): +- if not client.is_mod: +- raise ClientError('You must be authorized to do that.') +- try: +- ipid = int(arg.strip()) +- except: +- raise ClientError('You must specify ipid') +- try: +- client.server.ban_manager.add_ban(ipid) +- except ServerError: +- raise +- if ipid != None: ++ raise ArgumentError('You must specify a target. Use /kick ...') ++ args = list(arg.split(' ')) ++ client.send_host_message('Attempting to kick {} IPIDs.'.format(len(args))) ++ for raw_ipid in args: ++ try: ++ ipid = int(raw_ipid) ++ except: ++ raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) + targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) + if targets: + for c in targets: ++ logger.log_server('Kicked {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) ++ logger.log_mod('Kicked {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) ++ client.send_host_message("{} was kicked.".format(c.get_char_name())) ++ c.send_command('KK', c.char_id) + c.disconnect() +- client.send_host_message('{} clients was kicked.'.format(len(targets))) +- client.send_host_message('{} was banned.'.format(ipid)) +- logger.log_server('Banned {}.'.format(ipid), client) ++ else: ++ client.send_host_message("No targets with the IPID {} were found.".format(ipid)) ++ ++def ooc_cmd_ban(client, arg): ++ if not client.is_mod: ++ raise ClientError('You must be authorized to do that.') ++ if len(arg) == 0: ++ raise ArgumentError('You must specify a target. Use /ban ...') ++ args = list(arg.split(' ')) ++ client.send_host_message('Attempting to ban {} IPIDs.'.format(len(args))) ++ for raw_ipid in args: ++ try: ++ ipid = int(raw_ipid) ++ except: ++ raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) ++ try: ++ client.server.ban_manager.add_ban(ipid) ++ except ServerError: ++ raise ++ if ipid != None: ++ targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) ++ if targets: ++ for c in targets: ++ c.send_command('KB', c.char_id) ++ c.disconnect() ++ client.send_host_message('{} clients was kicked.'.format(len(targets))) ++ client.send_host_message('{} was banned.'.format(ipid)) ++ logger.log_server('Banned {}.'.format(ipid), client) ++ logger.log_mod('Banned {}.'.format(ipid), client) + + def ooc_cmd_unban(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') +- try: +- client.server.ban_manager.remove_ban(int(arg.strip())) +- except: +- raise ClientError('You must specify \'hdid\'') +- logger.log_server('Unbanned {}.'.format(arg), client) +- client.send_host_message('Unbanned {}'.format(arg)) +- +- ++ if len(arg) == 0: ++ raise ArgumentError('You must specify a target. Use /unban ...') ++ args = list(arg.split(' ')) ++ client.send_host_message('Attempting to unban {} IPIDs.'.format(len(args))) ++ for raw_ipid in args: ++ try: ++ client.server.ban_manager.remove_ban(int(raw_ipid)) ++ except: ++ raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) ++ logger.log_server('Unbanned {}.'.format(raw_ipid), client) ++ logger.log_mod('Unbanned {}.'.format(raw_ipid), client) ++ client.send_host_message('Unbanned {}'.format(raw_ipid)) ++ + def ooc_cmd_play(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') +@@ -283,31 +426,59 @@ def ooc_cmd_play(client, arg): + raise ArgumentError('You must specify a song.') + client.area.play_music(arg, client.char_id, -1) + client.area.add_music_playing(client, arg) +- logger.log_server('[{}][{}]Changed music to {}.'.format(client.area.id, client.get_char_name(), arg), client) ++ logger.log_server('[{}][{}]Changed music to {}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + + def ooc_cmd_mute(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) == 0: +- raise ArgumentError('You must specify a target.') +- try: +- c = client.server.client_manager.get_targets(client, TargetType.IPID, int(arg), False)[0] +- c.is_muted = True +- client.send_host_message('{} existing client(s).'.format(c.get_char_name())) +- except: +- client.send_host_message("No targets found. Use /mute for mute") ++ raise ArgumentError('You must specify a target. Use /mute .') ++ args = list(arg.split(' ')) ++ client.send_host_message('Attempting to mute {} IPIDs.'.format(len(args))) ++ for raw_ipid in args: ++ if raw_ipid.isdigit(): ++ ipid = int(raw_ipid) ++ clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) ++ if (clients): ++ msg = 'Muted the IPID ' + str(ipid) + '\'s following clients:' ++ for c in clients: ++ c.is_muted = True ++ logger.log_server('Muted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) ++ logger.log_mod('Muted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) ++ msg += ' ' + c.get_char_name() + ' [' + str(c.id) + '],' ++ msg = msg[:-1] ++ msg += '.' ++ client.send_host_message('{}'.format(msg)) ++ else: ++ client.send_host_message("No targets found. Use /mute ... for mute.") ++ else: ++ client.send_host_message('{} does not look like a valid IPID.'.format(raw_ipid)) + + def ooc_cmd_unmute(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) == 0: + raise ArgumentError('You must specify a target.') +- try: +- c = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0] +- c.is_muted = False +- client.send_host_message('{} existing client(s).'.format(c.get_char_name())) +- except: +- client.send_host_message("No targets found. Use /mute for mute") ++ args = list(arg.split(' ')) ++ client.send_host_message('Attempting to unmute {} IPIDs.'.format(len(args))) ++ for raw_ipid in args: ++ if raw_ipid.isdigit(): ++ ipid = int(raw_ipid) ++ clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) ++ if (clients): ++ msg = 'Unmuted the IPID ' + str(ipid) + '\'s following clients::' ++ for c in clients: ++ c.is_muted = False ++ logger.log_server('Unmuted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) ++ logger.log_mod('Unmuted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) ++ msg += ' ' + c.get_char_name() + ' [' + str(c.id) + '],' ++ msg = msg[:-1] ++ msg += '.' ++ client.send_host_message('{}'.format(msg)) ++ else: ++ client.send_host_message("No targets found. Use /unmute ... for unmute.") ++ else: ++ client.send_host_message('{} does not look like a valid IPID.'.format(raw_ipid)) + + def ooc_cmd_login(client, arg): + if len(arg) == 0: +@@ -320,6 +491,7 @@ def ooc_cmd_login(client, arg): + client.area.broadcast_evidence_list() + client.send_host_message('Logged in as a moderator.') + logger.log_server('Logged in as moderator.', client) ++ logger.log_mod('Logged in as moderator.', client) + + def ooc_cmd_g(client, arg): + if client.muted_global: +@@ -327,7 +499,7 @@ def ooc_cmd_g(client, arg): + if len(arg) == 0: + raise ArgumentError("You can't send an empty message.") + client.server.broadcast_global(client, arg) +- logger.log_server('[{}][{}][GLOBAL]{}.'.format(client.area.id, client.get_char_name(), arg), client) ++ logger.log_server('[{}][{}][GLOBAL]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + + def ooc_cmd_gm(client, arg): + if not client.is_mod: +@@ -337,7 +509,17 @@ def ooc_cmd_gm(client, arg): + if len(arg) == 0: + raise ArgumentError("Can't send an empty message.") + client.server.broadcast_global(client, arg, True) +- logger.log_server('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.id, client.get_char_name(), arg), client) ++ logger.log_server('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) ++ logger.log_mod('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) ++ ++def ooc_cmd_m(client, arg): ++ if not client.is_mod: ++ raise ClientError('You must be authorized to do that.') ++ if len(arg) == 0: ++ raise ArgumentError("You can't send an empty message.") ++ client.server.send_modchat(client, arg) ++ logger.log_server('[{}][{}][MODCHAT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) ++ logger.log_mod('[{}][{}][MODCHAT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + + def ooc_cmd_lm(client, arg): + if not client.is_mod: +@@ -346,7 +528,8 @@ def ooc_cmd_lm(client, arg): + raise ArgumentError("Can't send an empty message.") + client.area.send_command('CT', '{}[MOD][{}]' + .format(client.server.config['hostname'], client.get_char_name()), arg) +- logger.log_server('[{}][{}][LOCAL-MOD]{}.'.format(client.area.id, client.get_char_name(), arg), client) ++ logger.log_server('[{}][{}][LOCAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) ++ logger.log_mod('[{}][{}][LOCAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + + def ooc_cmd_announce(client, arg): + if not client.is_mod: +@@ -354,8 +537,9 @@ def ooc_cmd_announce(client, arg): + if len(arg) == 0: + raise ArgumentError("Can't send an empty message.") + client.server.send_all_cmd_pred('CT', '{}'.format(client.server.config['hostname']), +- '=== Announcement ===\r\n{}\r\n=================='.format(arg)) +- logger.log_server('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.id, client.get_char_name(), arg), client) ++ '=== Announcement ===\r\n{}\r\n=================='.format(arg), '1') ++ logger.log_server('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) ++ logger.log_mod('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + + def ooc_cmd_toggleglobal(client, arg): + if len(arg) != 0: +@@ -373,7 +557,7 @@ def ooc_cmd_need(client, arg): + if len(arg) == 0: + raise ArgumentError("You must specify what you need.") + client.server.broadcast_need(client, arg) +- logger.log_server('[{}][{}][NEED]{}.'.format(client.area.id, client.get_char_name(), arg), client) ++ logger.log_server('[{}][{}][NEED]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + + def ooc_cmd_toggleadverts(client, arg): + if len(arg) != 0: +@@ -388,11 +572,11 @@ def ooc_cmd_doc(client, arg): + if len(arg) == 0: + client.send_host_message('Document: {}'.format(client.area.doc)) + logger.log_server( +- '[{}][{}]Requested document. Link: {}'.format(client.area.id, client.get_char_name(), client.area.doc)) ++ '[{}][{}]Requested document. Link: {}'.format(client.area.abbreviation, client.get_char_name(), client.area.doc), client) + else: + client.area.change_doc(arg) + client.area.send_host_message('{} changed the doc link.'.format(client.get_char_name())) +- logger.log_server('[{}][{}]Changed document to: {}'.format(client.area.id, client.get_char_name(), arg)) ++ logger.log_server('[{}][{}]Changed document to: {}'.format(client.area.abbreviation, client.get_char_name(), arg), client) + + + def ooc_cmd_cleardoc(client, arg): +@@ -400,7 +584,7 @@ def ooc_cmd_cleardoc(client, arg): + raise ArgumentError('This command has no arguments.') + client.area.send_host_message('{} cleared the doc link.'.format(client.get_char_name())) + logger.log_server('[{}][{}]Cleared document. Old link: {}' +- .format(client.area.id, client.get_char_name(), client.area.doc)) ++ .format(client.area.abbreviation, client.get_char_name(), client.area.doc), client) + client.area.change_doc() + + +@@ -412,7 +596,7 @@ def ooc_cmd_status(client, arg): + client.area.change_status(arg) + client.area.send_host_message('{} changed status to {}.'.format(client.get_char_name(), client.area.status)) + logger.log_server( +- '[{}][{}]Changed status to {}'.format(client.area.id, client.get_char_name(), client.area.status)) ++ '[{}][{}]Changed status to {}'.format(client.area.abbreviation, client.get_char_name(), client.area.status), client) + except AreaError: + raise + +@@ -466,7 +650,10 @@ def ooc_cmd_pm(client, arg): + if c.pm_mute: + raise ClientError('This user muted all pm conversation') + else: +- c.send_host_message('PM from {} in {} ({}): {}'.format(client.name, client.area.name, client.get_char_name(), msg)) ++ if c.is_mod: ++ c.send_host_message('PM from {} (ID: {}, IPID: {}) in {} ({}): {}'.format(client.name, client.id, client.ipid, client.area.name, client.get_char_name(), msg)) ++ else: ++ c.send_host_message('PM from {} (ID: {}) in {} ({}): {}'.format(client.name, client.id, client.area.name, client.get_char_name(), msg)) + client.send_host_message('PM sent to {}. Message: {}'.format(args[0], msg)) + + def ooc_cmd_mutepm(client, arg): +@@ -497,10 +684,13 @@ def ooc_cmd_reload(client, arg): + def ooc_cmd_randomchar(client, arg): + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') +- try: +- free_id = client.area.get_rand_avail_char_id() +- except AreaError: +- raise ++ if len(client.charcurse) > 0: ++ free_id = random.choice(client.charcurse) ++ else: ++ try: ++ free_id = client.area.get_rand_avail_char_id() ++ except AreaError: ++ raise + try: + client.change_character(free_id) + except ClientError: +@@ -529,12 +719,105 @@ def ooc_cmd_evi_swap(client, arg): + def ooc_cmd_cm(client, arg): + if 'CM' not in client.area.evidence_mod: + raise ClientError('You can\'t become a CM in this area') +- if client.area.owned == False: +- client.area.owned = True +- client.is_cm = True ++ if len(client.area.owners) == 0: ++ if len(arg) > 0: ++ raise ArgumentError('You cannot \'nominate\' people to be CMs when you are not one.') ++ client.area.owners.append(client) + if client.area.evidence_mod == 'HiddenCM': + client.area.broadcast_evidence_list() +- client.area.send_host_message('{} is CM in this area now.'.format(client.get_char_name())) ++ client.server.area_manager.send_arup_cms() ++ client.area.send_host_message('{} [{}] is CM in this area now.'.format(client.get_char_name(), client.id)) ++ elif client in client.area.owners: ++ if len(arg) > 0: ++ arg = arg.split(' ') ++ for id in arg: ++ try: ++ id = int(id) ++ c = client.server.client_manager.get_targets(client, TargetType.ID, id, False)[0] ++ if c in client.area.owners: ++ client.send_host_message('{} [{}] is already a CM here.'.format(c.get_char_name(), c.id)) ++ else: ++ client.area.owners.append(c) ++ if client.area.evidence_mod == 'HiddenCM': ++ client.area.broadcast_evidence_list() ++ client.server.area_manager.send_arup_cms() ++ client.area.send_host_message('{} [{}] is CM in this area now.'.format(c.get_char_name(), c.id)) ++ except: ++ client.send_host_message('{} does not look like a valid ID.'.format(id)) ++ else: ++ raise ClientError('You must be authorized to do that.') ++ ++ ++def ooc_cmd_uncm(client, arg): ++ if client in client.area.owners: ++ if len(arg) > 0: ++ arg = arg.split(' ') ++ else: ++ arg = [client.id] ++ for id in arg: ++ try: ++ id = int(id) ++ c = client.server.client_manager.get_targets(client, TargetType.ID, id, False)[0] ++ if c in client.area.owners: ++ client.area.owners.remove(c) ++ client.server.area_manager.send_arup_cms() ++ client.area.send_host_message('{} [{}] is no longer CM in this area.'.format(c.get_char_name(), c.id)) ++ else: ++ client.send_host_message('You cannot remove someone from CMing when they aren\'t a CM.') ++ except: ++ client.send_host_message('{} does not look like a valid ID.'.format(id)) ++ else: ++ raise ClientError('You must be authorized to do that.') ++ ++def ooc_cmd_setcase(client, arg): ++ args = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', arg) ++ if len(args) == 0: ++ raise ArgumentError('Please do not call this command manually!') ++ else: ++ client.casing_cases = args[0] ++ client.casing_cm = args[1] == "1" ++ client.casing_def = args[2] == "1" ++ client.casing_pro = args[3] == "1" ++ client.casing_jud = args[4] == "1" ++ client.casing_jur = args[5] == "1" ++ client.casing_steno = args[6] == "1" ++ ++def ooc_cmd_anncase(client, arg): ++ if client in client.area.owners: ++ if not client.can_call_case(): ++ raise ClientError('Please wait 60 seconds between case announcements!') ++ args = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', arg) ++ if len(args) == 0: ++ raise ArgumentError('Please do not call this command manually!') ++ elif len(args) == 1: ++ raise ArgumentError('You should probably announce the case to at least one person.') ++ else: ++ if not args[1] == "1" and not args[2] == "1" and not args[3] == "1" and not args[4] == "1" and not args[5] == "1": ++ raise ArgumentError('You should probably announce the case to at least one person.') ++ msg = '=== Case Announcement ===\r\n{} [{}] is hosting {}, looking for '.format(client.get_char_name(), client.id, args[0]) ++ ++ lookingfor = [] ++ ++ if args[1] == "1": ++ lookingfor.append("defence") ++ if args[2] == "1": ++ lookingfor.append("prosecutor") ++ if args[3] == "1": ++ lookingfor.append("judge") ++ if args[4] == "1": ++ lookingfor.append("juror") ++ if args[5] == "1": ++ lookingfor.append("stenographer") ++ ++ msg = msg + ', '.join(lookingfor) + '.\r\n==================' ++ ++ client.server.send_all_cmd_pred('CASEA', msg, args[1], args[2], args[3], args[4], args[5], '1') ++ ++ client.set_case_call_delay() ++ ++ logger.log_server('[{}][{}][CASE_ANNOUNCEMENT]{}, DEF: {}, PRO: {}, JUD: {}, JUR: {}, STENO: {}.'.format(client.area.abbreviation, client.get_char_name(), args[0], args[1], args[2], args[3], args[4], args[5]), client) ++ else: ++ raise ClientError('You cannot announce a case in an area where you are not a CM!') + + def ooc_cmd_unmod(client, arg): + client.is_mod = False +@@ -546,21 +829,30 @@ def ooc_cmd_area_lock(client, arg): + if not client.area.locking_allowed: + client.send_host_message('Area locking is disabled in this area.') + return +- if client.area.is_locked: ++ if client.area.is_locked == client.area.Locked.LOCKED: + client.send_host_message('Area is already locked.') +- if client.is_cm: +- client.area.is_locked = True +- client.area.send_host_message('Area is locked.') +- for i in client.area.clients: +- client.area.invite_list[i.ipid] = None ++ if client in client.area.owners: ++ client.area.lock() + return + else: + raise ClientError('Only CM can lock the area.') ++ ++def ooc_cmd_area_spectate(client, arg): ++ if not client.area.locking_allowed: ++ client.send_host_message('Area locking is disabled in this area.') ++ return ++ if client.area.is_locked == client.area.Locked.SPECTATABLE: ++ client.send_host_message('Area is already spectatable.') ++ if client in client.area.owners: ++ client.area.spectator() ++ return ++ else: ++ raise ClientError('Only CM can make the area spectatable.') + + def ooc_cmd_area_unlock(client, arg): +- if not client.area.is_locked: ++ if client.area.is_locked == client.area.Locked.FREE: + raise ClientError('Area is already unlocked.') +- if not client.is_cm: ++ if not client in client.area.owners: + raise ClientError('Only CM can unlock area.') + client.area.unlock() + client.send_host_message('Area is unlocked.') +@@ -568,22 +860,22 @@ def ooc_cmd_area_unlock(client, arg): + def ooc_cmd_invite(client, arg): + if not arg: + raise ClientError('You must specify a target. Use /invite ') +- if not client.area.is_locked: ++ if client.area.is_locked == client.area.Locked.FREE: + raise ClientError('Area isn\'t locked.') +- if not client.is_cm or client.is_mod: ++ if not client in client.area.owners and not client.is_mod: + raise ClientError('You must be authorized to do that.') + try: + c = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0] +- client.area.invite_list[c.ipid] = None ++ client.area.invite_list[c.id] = None + client.send_host_message('{} is invited to your area.'.format(c.get_char_name())) +- c.send_host_message('You were invited and given access to area {}.'.format(client.area.id)) ++ c.send_host_message('You were invited and given access to {}.'.format(client.area.name)) + except: + raise ClientError('You must specify a target. Use /invite ') + + def ooc_cmd_uninvite(client, arg): +- if not client.is_cm or client.is_mod: ++ if not client in client.area.owners and not client.is_mod: + raise ClientError('You must be authorized to do that.') +- if not client.area.is_locked and not client.is_mod: ++ if client.area.is_locked == client.area.Locked.FREE: + raise ClientError('Area isn\'t locked.') + if not arg: + raise ClientError('You must specify a target. Use /uninvite ') +@@ -594,8 +886,8 @@ def ooc_cmd_uninvite(client, arg): + for c in targets: + client.send_host_message("You have removed {} from the whitelist.".format(c.get_char_name())) + c.send_host_message("You were removed from the area whitelist.") +- if client.area.is_locked: +- client.area.invite_list.pop(c.ipid) ++ if client.area.is_locked != client.area.Locked.FREE: ++ client.area.invite_list.pop(c.id) + except AreaError: + raise + except ClientError: +@@ -606,7 +898,7 @@ def ooc_cmd_uninvite(client, arg): + def ooc_cmd_area_kick(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') +- if not client.area.is_locked and not client.is_mod: ++ if client.area.is_locked == client.area.Locked.FREE: + raise ClientError('Area isn\'t locked.') + if not arg: + raise ClientError('You must specify a target. Use /area_kick [destination #]') +@@ -627,8 +919,8 @@ def ooc_cmd_area_kick(client, arg): + client.send_host_message("Attempting to kick {} to area {}.".format(c.get_char_name(), output)) + c.change_area(area) + c.send_host_message("You were kicked from the area to area {}.".format(output)) +- if client.area.is_locked: +- client.area.invite_list.pop(c.ipid) ++ if client.area.is_locked != client.area.Locked.FREE: ++ client.area.invite_list.pop(c.id) + except AreaError: + raise + except ClientError: +@@ -653,10 +945,10 @@ def ooc_cmd_ooc_unmute(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) == 0: +- raise ArgumentError('You must specify a target. Use /ooc_mute .') +- targets = client.server.client_manager.get_targets(client, TargetType.ID, arg, False) ++ raise ArgumentError('You must specify a target. Use /ooc_unmute .') ++ targets = client.server.client_manager.get_ooc_muted_clients() + if not targets: +- raise ArgumentError('Target not found. Use /ooc_mute .') ++ raise ArgumentError('Targets not found. Use /ooc_unmute .') + for target in targets: + target.is_ooc_muted = False + client.send_host_message('Unmuted {} existing client(s).'.format(len(targets))) +@@ -673,6 +965,7 @@ def ooc_cmd_disemvowel(client, arg): + if targets: + for c in targets: + logger.log_server('Disemvowelling {}.'.format(c.get_ip()), client) ++ logger.log_mod('Disemvowelling {}.'.format(c.get_ip()), client) + c.disemvowel = True + client.send_host_message('Disemvowelled {} existing client(s).'.format(len(targets))) + else: +@@ -686,15 +979,120 @@ def ooc_cmd_undisemvowel(client, arg): + try: + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) + except: +- raise ArgumentError('You must specify a target. Use /disemvowel .') ++ raise ArgumentError('You must specify a target. Use /undisemvowel .') + if targets: + for c in targets: + logger.log_server('Undisemvowelling {}.'.format(c.get_ip()), client) ++ logger.log_mod('Undisemvowelling {}.'.format(c.get_ip()), client) + c.disemvowel = False + client.send_host_message('Undisemvowelled {} existing client(s).'.format(len(targets))) + else: + client.send_host_message('No targets found.') + ++def ooc_cmd_shake(client, arg): ++ if not client.is_mod: ++ raise ClientError('You must be authorized to do that.') ++ elif len(arg) == 0: ++ raise ArgumentError('You must specify a target.') ++ try: ++ targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) ++ except: ++ raise ArgumentError('You must specify a target. Use /shake .') ++ if targets: ++ for c in targets: ++ logger.log_server('Shaking {}.'.format(c.get_ip()), client) ++ logger.log_mod('Shaking {}.'.format(c.get_ip()), client) ++ c.shaken = True ++ client.send_host_message('Shook {} existing client(s).'.format(len(targets))) ++ else: ++ client.send_host_message('No targets found.') ++ ++def ooc_cmd_unshake(client, arg): ++ if not client.is_mod: ++ raise ClientError('You must be authorized to do that.') ++ elif len(arg) == 0: ++ raise ArgumentError('You must specify a target.') ++ try: ++ targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) ++ except: ++ raise ArgumentError('You must specify a target. Use /unshake .') ++ if targets: ++ for c in targets: ++ logger.log_server('Unshaking {}.'.format(c.get_ip()), client) ++ logger.log_mod('Unshaking {}.'.format(c.get_ip()), client) ++ c.shaken = False ++ client.send_host_message('Unshook {} existing client(s).'.format(len(targets))) ++ else: ++ client.send_host_message('No targets found.') ++ ++def ooc_cmd_charcurse(client, arg): ++ if not client.is_mod: ++ raise ClientError('You must be authorized to do that.') ++ elif len(arg) == 0: ++ raise ArgumentError('You must specify a target (an ID) and at least one character ID. Consult /charids for the character IDs.') ++ elif len(arg) == 1: ++ raise ArgumentError('You must specific at least one character ID. Consult /charids for the character IDs.') ++ args = arg.split() ++ try: ++ targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) ++ except: ++ raise ArgumentError('You must specify a valid target! Make sure it is a valid ID.') ++ if targets: ++ for c in targets: ++ log_msg = ' ' + str(c.get_ip()) + ' to' ++ part_msg = ' [' + str(c.id) + '] to' ++ for raw_cid in args[1:]: ++ try: ++ cid = int(raw_cid) ++ c.charcurse.append(cid) ++ part_msg += ' ' + str(client.server.char_list[cid]) + ',' ++ log_msg += ' ' + str(client.server.char_list[cid]) + ',' ++ except: ++ ArgumentError('' + str(raw_cid) + ' does not look like a valid character ID.') ++ part_msg = part_msg[:-1] ++ part_msg += '.' ++ log_msg = log_msg[:-1] ++ log_msg += '.' ++ c.char_select() ++ logger.log_server('Charcursing' + log_msg, client) ++ logger.log_mod('Charcursing' + log_msg, client) ++ client.send_host_message('Charcursed' + part_msg) ++ else: ++ client.send_host_message('No targets found.') ++ ++def ooc_cmd_uncharcurse(client, arg): ++ if not client.is_mod: ++ raise ClientError('You must be authorized to do that.') ++ elif len(arg) == 0: ++ raise ArgumentError('You must specify a target (an ID).') ++ args = arg.split() ++ try: ++ targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) ++ except: ++ raise ArgumentError('You must specify a valid target! Make sure it is a valid ID.') ++ if targets: ++ for c in targets: ++ if len(c.charcurse) > 0: ++ c.charcurse = [] ++ logger.log_server('Uncharcursing {}.'.format(c.get_ip()), client) ++ logger.log_mod('Uncharcursing {}.'.format(c.get_ip()), client) ++ client.send_host_message('Uncharcursed [{}].'.format(c.id)) ++ c.char_select() ++ else: ++ client.send_host_message('[{}] is not charcursed.'.format(c.id)) ++ else: ++ client.send_host_message('No targets found.') ++ ++def ooc_cmd_charids(client, arg): ++ if not client.is_mod: ++ raise ClientError('You must be authorized to do that.') ++ if len(arg) != 0: ++ raise ArgumentError("This command doesn't take any arguments") ++ msg = 'Here is a list of all available characters on the server:' ++ for c in range(0, len(client.server.char_list)): ++ msg += '\n[' + str(c) + '] ' + client.server.char_list[c] ++ client.send_host_message(msg) ++ + def ooc_cmd_blockdj(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') +@@ -709,6 +1107,9 @@ def ooc_cmd_blockdj(client, arg): + for target in targets: + target.is_dj = False + target.send_host_message('A moderator muted you from changing the music.') ++ logger.log_server('BlockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) ++ logger.log_mod('BlockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) ++ target.area.remove_jukebox_vote(target, True) + client.send_host_message('blockdj\'d {}.'.format(targets[0].get_char_name())) + + def ooc_cmd_unblockdj(client, arg): +@@ -725,6 +1126,8 @@ def ooc_cmd_unblockdj(client, arg): + for target in targets: + target.is_dj = True + target.send_host_message('A moderator unmuted you from changing the music.') ++ logger.log_server('UnblockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) ++ logger.log_mod('UnblockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) + client.send_host_message('Unblockdj\'d {}.'.format(targets[0].get_char_name())) + + def ooc_cmd_blockwtce(client, arg): +@@ -741,6 +1144,8 @@ def ooc_cmd_blockwtce(client, arg): + for target in targets: + target.can_wtce = False + target.send_host_message('A moderator blocked you from using judge signs.') ++ logger.log_server('BlockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) ++ logger.log_mod('BlockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) + client.send_host_message('blockwtce\'d {}.'.format(targets[0].get_char_name())) + + def ooc_cmd_unblockwtce(client, arg): +@@ -757,6 +1162,8 @@ def ooc_cmd_unblockwtce(client, arg): + for target in targets: + target.can_wtce = True + target.send_host_message('A moderator unblocked you from using judge signs.') ++ logger.log_server('UnblockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) ++ logger.log_mod('UnblockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) + client.send_host_message('unblockwtce\'d {}.'.format(targets[0].get_char_name())) + + def ooc_cmd_notecard(client, arg): +@@ -773,7 +1180,7 @@ def ooc_cmd_notecard_clear(client, arg): + raise ClientError('You do not have a note card.') + + def ooc_cmd_notecard_reveal(client, arg): +- if not client.is_cm and not client.is_mod: ++ if not client in client.area.owners and not client.is_mod: + raise ClientError('You must be a CM or moderator to reveal cards.') + if len(client.area.cards) == 0: + raise ClientError('There are no cards to reveal in this area.') +@@ -800,7 +1207,7 @@ def rolla_reload(area): + def ooc_cmd_rolla_set(client, arg): + if not hasattr(client.area, 'ability_dice'): + rolla_reload(client.area) +- available_sets = client.area.ability_dice.keys() ++ available_sets = ', '.join(client.area.ability_dice.keys()) + if len(arg) == 0: + raise ArgumentError('You must specify the ability set name.\nAvailable sets: {}'.format(available_sets)) + if arg in client.area.ability_dice: +diff --git a/tsuserver3/server/districtclient.py b/AO2-Client/server/districtclient.py +index adc29ec..c766ba5 100644 +--- a/tsuserver3/server/districtclient.py ++++ b/AO2-Client/server/districtclient.py +@@ -60,7 +60,7 @@ class DistrictClient: + elif cmd == 'NEED': + need_msg = '=== Cross Advert ===\r\n{} at {} in {} [{}] needs {}\r\n====================' \ + .format(args[1], args[0], args[2], args[3], args[4]) +- self.server.send_all_cmd_pred('CT', '{}'.format(self.server.config['hostname']), need_msg, ++ self.server.send_all_cmd_pred('CT', '{}'.format(self.server.config['hostname']), need_msg, '1', + pred=lambda x: not x.muted_adverts) + + async def write_queue(self): +diff --git a/tsuserver3/server/evidence.py b/AO2-Client/server/evidence.py +index ddd9ba3..b34172a 100644 +--- a/tsuserver3/server/evidence.py ++++ b/AO2-Client/server/evidence.py +@@ -24,19 +24,28 @@ class EvidenceList: + + def __init__(self): + self.evidences = [] +- self.poses = {'def':['def', 'hld'], 'pro':['pro', 'hlp'], 'wit':['wit'], 'hlp':['hlp', 'pro'], 'hld':['hld', 'def'], 'jud':['jud'], 'all':['hlp', 'hld', 'wit', 'jud', 'pro', 'def', ''], 'pos':[]} ++ self.poses = {'def':['def', 'hld'], ++ 'pro':['pro', 'hlp'], ++ 'wit':['wit', 'sea'], ++ 'sea':['sea', 'wit'], ++ 'hlp':['hlp', 'pro'], ++ 'hld':['hld', 'def'], ++ 'jud':['jud', 'jur'], ++ 'jur':['jur', 'jud'], ++ 'all':['hlp', 'hld', 'wit', 'jud', 'pro', 'def', 'jur', 'sea', ''], ++ 'pos':[]} + + def login(self, client): + if client.area.evidence_mod == 'FFA': + pass + if client.area.evidence_mod == 'Mods': +- if not client.is_cm: ++ if not client in client.area.owners: + return False + if client.area.evidence_mod == 'CM': +- if not client.is_cm and not client.is_mod: ++ if not client in client.area.owners and not client.is_mod: + return False + if client.area.evidence_mod == 'HiddenCM': +- if not client.is_cm and not client.is_mod: ++ if not client in client.area.owners and not client.is_mod: + return False + return True + +diff --git a/tsuserver3/server/logger.py b/AO2-Client/server/logger.py +index 85c39b2..fb1b8b3 100644 +--- a/tsuserver3/server/logger.py ++++ b/AO2-Client/server/logger.py +@@ -16,22 +16,20 @@ + # along with this program. If not, see . + + import logging +-import logging.handlers + + import time + + +-def setup_logger(debug, log_size, log_backups): ++def setup_logger(debug): + logging.Formatter.converter = time.gmtime + debug_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') + srv_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') ++ mod_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') + + debug_log = logging.getLogger('debug') + debug_log.setLevel(logging.DEBUG) + +- # 0 maxBytes = no rotation +- # backupCount = number of old logs to save +- debug_handler = logging.handlers.RotatingFileHandler('logs/debug.log', maxBytes = log_size, backupCount = log_backups, encoding='utf-8') ++ debug_handler = logging.FileHandler('logs/debug.log', encoding='utf-8') + debug_handler.setLevel(logging.DEBUG) + debug_handler.setFormatter(debug_formatter) + debug_log.addHandler(debug_handler) +@@ -42,11 +40,19 @@ def setup_logger(debug, log_size, log_backups): + server_log = logging.getLogger('server') + server_log.setLevel(logging.INFO) + +- server_handler = logging.handlers.RotatingFileHandler('logs/server.log', maxBytes = log_size, backupCount = log_backups, encoding='utf-8') ++ server_handler = logging.FileHandler('logs/server.log', encoding='utf-8') + server_handler.setLevel(logging.INFO) + server_handler.setFormatter(srv_formatter) + server_log.addHandler(server_handler) + ++ mod_log = logging.getLogger('mod') ++ mod_log.setLevel(logging.INFO) ++ ++ mod_handler = logging.FileHandler('logs/mod.log', encoding='utf-8') ++ mod_handler.setLevel(logging.INFO) ++ mod_handler.setFormatter(mod_formatter) ++ mod_log.addHandler(mod_handler) ++ + + def log_debug(msg, client=None): + msg = parse_client_info(client) + msg +@@ -58,10 +64,15 @@ def log_server(msg, client=None): + logging.getLogger('server').info(msg) + + ++def log_mod(msg, client=None): ++ msg = parse_client_info(client) + msg ++ logging.getLogger('mod').info(msg) ++ ++ + def parse_client_info(client): + if client is None: + return '' + info = client.get_ip() + if client.is_mod: +- return '[{:<15}][{}][MOD]'.format(info, client.id) +- return '[{:<15}][{}]'.format(info, client.id) ++ return '[{:<15}][{:<3}][{}][MOD]'.format(info, client.id, client.name) ++ return '[{:<15}][{:<3}][{}]'.format(info, client.id, client.name) +diff --git a/tsuserver3/server/tsuserver.py b/AO2-Client/server/tsuserver.py +index 5e04b23..5af8161 100644 +--- a/tsuserver3/server/tsuserver.py ++++ b/AO2-Client/server/tsuserver.py +@@ -58,7 +58,7 @@ class TsuServer3: + self.district_client = None + self.ms_client = None + self.rp_mode = False +- logger.setup_logger(debug=self.config['debug'], log_size=self.config['log_size'], log_backups=self.config['log_backups']) ++ logger.setup_logger(debug=self.config['debug']) + + def start(self): + loop = asyncio.get_event_loop() +@@ -118,10 +118,6 @@ class TsuServer3: + self.config['music_change_floodguard'] = {'times_per_interval': 1, 'interval_length': 0, 'mute_length': 0} + if 'wtce_floodguard' not in self.config: + self.config['wtce_floodguard'] = {'times_per_interval': 1, 'interval_length': 0, 'mute_length': 0} +- if 'log_size' not in self.config: +- self.config['log_size'] = 1048576 +- if 'log_backups' not in self.config: +- self.config['log_backups'] = 5 + + def load_characters(self): + with open('config/characters.yaml', 'r', encoding = 'utf-8') as chars: +@@ -236,7 +232,7 @@ class TsuServer3: + + def broadcast_global(self, client, msg, as_mod=False): + char_name = client.get_char_name() +- ooc_name = '{}[{}][{}]'.format('G', client.area.id, char_name) ++ ooc_name = '{}[{}][{}]'.format('G', client.area.abbreviation, char_name) + if as_mod: + ooc_name += '[M]' + self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: not x.muted_global) +@@ -244,16 +240,58 @@ class TsuServer3: + self.district_client.send_raw_message( + 'GLOBAL#{}#{}#{}#{}'.format(int(as_mod), client.area.id, char_name, msg)) + ++ def send_modchat(self, client, msg): ++ name = client.name ++ ooc_name = '{}[{}][{}]'.format('M', client.area.abbreviation, name) ++ self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: x.is_mod) ++ if self.config['use_district']: ++ self.district_client.send_raw_message( ++ 'MODCHAT#{}#{}#{}'.format(client.area.id, char_name, msg)) ++ + def broadcast_need(self, client, msg): + char_name = client.get_char_name() + area_name = client.area.name +- area_id = client.area.id ++ area_id = client.area.abbreviation + self.send_all_cmd_pred('CT', '{}'.format(self.config['hostname']), +- '=== Advert ===\r\n{} in {} [{}] needs {}\r\n===============' +- .format(char_name, area_name, area_id, msg), pred=lambda x: not x.muted_adverts) ++ ['=== Advert ===\r\n{} in {} [{}] needs {}\r\n===============' ++ .format(char_name, area_name, area_id, msg), '1'], pred=lambda x: not x.muted_adverts) + if self.config['use_district']: + self.district_client.send_raw_message('NEED#{}#{}#{}#{}'.format(char_name, area_name, area_id, msg)) + ++ def send_arup(self, args): ++ """ Updates the area properties on the Case Café Custom Client. ++ ++ Playercount: ++ ARUP#0###... ++ Status: ++ ARUP#1#####... ++ CM: ++ ARUP#2#####... ++ Lockedness: ++ ARUP#3#####... ++ ++ """ ++ if len(args) < 2: ++ # An argument count smaller than 2 means we only got the identifier of ARUP. ++ return ++ if args[0] not in (0,1,2,3): ++ return ++ ++ if args[0] == 0: ++ for part_arg in args[1:]: ++ try: ++ sanitised = int(part_arg) ++ except: ++ return ++ elif args[0] in (1, 2, 3): ++ for part_arg in args[1:]: ++ try: ++ sanitised = str(part_arg) ++ except: ++ return ++ ++ self.send_all_cmd_pred('ARUP', *args, pred=lambda x: True) ++ + def refresh(self): + with open('config/config.yaml', 'r') as cfg: + self.config['motd'] = yaml.load(cfg)['motd'].replace('\\n', ' \n')